package com.planet_ink.coffee_mud.Libraries;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;
import com.planet_ink.coffee_mud.core.CMath.CompiledFormula;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.Fighter;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.AccountStats.PrideStat;
import com.planet_ink.coffee_mud.Common.interfaces.CMMsg.View;
import com.planet_ink.coffee_mud.Common.interfaces.PlayerStats.PlayerCombatStat;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.MOB.Attrib;
import com.planet_ink.coffee_mud.Races.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import java.util.*;
/*
Copyright 2004-2019 Bo Zimmerman
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
public class MUDFight extends StdLibrary implements CombatLibrary
{
@Override
public String ID()
{
return "MUDFight";
}
static final char[] PARENS={'(',')'};
public String lastStr = "";
public long lastRes = 0;
public Object[][][] hitWordIndex = null;
public Object[][][] hitWordsChanged = null;
protected CMath.CompiledFormula attackAdjustmentFormula = null;
protected CMath.CompiledFormula armorAdjustmentFormula = null;
protected CMath.CompiledFormula attackerFudgeBonusFormula = null;
protected CMath.CompiledFormula pvpAttackerFudgeBonusFormula = null;
protected CMath.CompiledFormula spellFudgeDamageFormula = null;
protected CMath.CompiledFormula pvpSpellFudgeDamageFormula = null;
protected CMath.CompiledFormula spellCritChanceFormula = null;
protected CMath.CompiledFormula pvpSpellCritChanceFormula = null;
protected CMath.CompiledFormula spellCritDmgFormula = null;
protected CMath.CompiledFormula pvpSpellCritDmgFormula = null;
protected CMath.CompiledFormula targetedRangedDamageFormula = null;
protected CMath.CompiledFormula pvpTargetedRangedDamageFormula = null;
protected CMath.CompiledFormula rangedFudgeDamageFormula = null;
protected CMath.CompiledFormula pvpRangedFudgeDamageFormula = null;
protected CMath.CompiledFormula targetedMeleeDamageFormula = null;
protected CMath.CompiledFormula pvpTargetedMeleeDamageFormula = null;
protected CMath.CompiledFormula meleeFudgeDamageFormula = null;
protected CMath.CompiledFormula pvpMeleeFudgeDamageFormula = null;
protected CMath.CompiledFormula staticRangedDamageFormula = null;
protected CMath.CompiledFormula staticMeleeDamageFormula = null;
protected CMath.CompiledFormula weaponCritChanceFormula = null;
protected CMath.CompiledFormula pvpWeaponCritChanceFormula = null;
protected CMath.CompiledFormula weaponCritDmgFormula = null;
protected CMath.CompiledFormula pvpWeaponCritDmgFormula = null;
protected CMath.CompiledFormula stateHitPointRecoverFormula = null;
protected CMath.CompiledFormula stateManaRecoverFormula = null;
protected CMath.CompiledFormula stateMovesRecoverFormula = null;
protected CMath.CompiledFormula totalCombatExperienceFormula = null;
protected CMath.CompiledFormula individualCombatExpFormula = null;
private static final int ATTACK_ADJUSTMENT = 50;
@Override
public boolean activate()
{
attackAdjustmentFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_ATTACKADJUSTMENT));
armorAdjustmentFormula= CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_ARMORADJUSTMENT));
attackerFudgeBonusFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_ATTACKFUDGEBONUS));
pvpAttackerFudgeBonusFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPATTACKFUDGEBONUS));
spellCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_CHANCESPELLCRIT));
pvpSpellCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPCHANCESPELLCRIT));
spellCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGESPELLCRIT));
pvpSpellCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGESPELLCRIT));
targetedRangedDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGERANGEDTARGETED));
pvpTargetedRangedDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGERANGEDTARGETED));
targetedMeleeDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEMELEETARGETED));
pvpTargetedMeleeDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGEMELEETARGETED));
staticRangedDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGERANGEDSTATIC));
staticMeleeDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEMELEESTATIC));
weaponCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_CHANCEWEAPONCRIT));
pvpWeaponCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPCHANCEWEAPONCRIT));
weaponCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEWEAPONCRIT));
pvpWeaponCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGEWEAPONCRIT));
spellFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGESPELLFUDGE));
pvpSpellFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGESPELLFUDGE));
meleeFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEMELEEFUDGE));
pvpMeleeFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGEMELEEFUDGE));
rangedFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGERANGEDFUDGE));
pvpRangedFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGERANGEDFUDGE));
stateHitPointRecoverFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_HITPOINTRECOVER));
stateManaRecoverFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_MANARECOVER));
stateMovesRecoverFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_MOVESRECOVER));
totalCombatExperienceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_TOTALCOMBATXP));
individualCombatExpFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_INDCOMBATXP));
if(serviceClient==null)
{
name="THCombat"+Thread.currentThread().getThreadGroup().getName().charAt(0);
serviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK, CMProps.getTickMillis(), TICKS_PER_SHIP_COMBAT);
}
return true;
}
@Override
public void propertiesLoaded()
{
activate();
}
@Override
public Set<MOB> allPossibleCombatants(final MOB mob, final boolean includePlayers)
{
final SHashSet<MOB> h=new SHashSet<MOB>();
final Room thisRoom=mob.location();
if(thisRoom==null)
return null;
final Set<MOB> h1=mob.getGroupMembers(new HashSet<MOB>());
for(int m=0;m<thisRoom.numInhabitants();m++)
{
final MOB inhab=thisRoom.fetchInhabitant(m);
if((inhab!=null)
&&(inhab!=mob)
&&(!h1.contains(inhab))
&&(CMLib.flags().isSeeable(inhab)||CMLib.flags().canMove(inhab))
&&(CMLib.flags().isSeeable(inhab)||(!CMLib.flags().isCloaked(inhab)))
&&((includePlayers)||(!mob.isMonster())||(!inhab.isMonster())))
h.addUnsafe(inhab);
}
return h;
}
@Override
public Set<MOB> properTargets(final Ability A, final MOB caster, final boolean includePlayers)
{
Set<MOB> h=null;
if(A.abstractQuality()!=Ability.QUALITY_MALICIOUS)
{
if(caster.Name().equalsIgnoreCase("somebody"))
h=new SHashSet<MOB>();
else
h=caster.getGroupMembers(new SHashSet<MOB>());
for (final MOB M : h)
{
if(M.location()!=caster.location())
h.remove(M);
}
}
else
if(caster.isInCombat())
h=allCombatants(caster);
else
h=allPossibleCombatants(caster,includePlayers);
return h;
}
@Override
public int adjustedAttackBonus(final MOB mob, final MOB target)
{
final int maxStr = mob.charStats().getMaxStat(CharStats.STAT_STRENGTH);
int currStr = mob.charStats().getStat(CharStats.STAT_STRENGTH);
int strBonus = 0;
if(currStr > maxStr)
{
strBonus = currStr - maxStr;
currStr = maxStr;
}
int baseStr = mob.baseCharStats().getStat(CharStats.STAT_STRENGTH);
if(baseStr > maxStr)
baseStr = maxStr;
final double[] vars = {mob.phyStats().attackAdjustment(),
currStr,
baseStr,
strBonus,
(mob.curState().getHunger()<1)?1.0:0.0,
(mob.curState().getThirst()<1)?1.0:0.0,
(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0
};
return (int)Math.round(CMath.parseMathExpression(attackAdjustmentFormula, vars, 0.0));
}
@Override
public int adjustedAttackBonus(final int baseAttack)
{
final double[] vars = {baseAttack,
CharStats.VALUE_ALLSTATS_DEFAULT,
CharStats.VALUE_ALLSTATS_DEFAULT,
0,//strength bonus
0.0,//hunger
0.0,//thirst
0.0//fatigue
};
return (int)Math.round(CMath.parseMathExpression(attackAdjustmentFormula, vars, 0.0));
}
@Override
public void postItemDamage(final MOB mob, final Item I, final Environmental tool, final int damageAmount, final int messageType, final String message)
{
if(mob==null)
return ;
final Room R=mob.location();
if(R==null)
return ;
CMMsg msg=CMClass.getMsg(mob,I,tool,CMMsg.MASK_MALICIOUS|CMMsg.MASK_ALWAYS|messageType,message,CMMsg.MASK_MALICIOUS|CMMsg.MASK_ALWAYS|messageType,message,CMMsg.NO_EFFECT,null);
if(R.okMessage(mob,msg))
{
R.send(mob,msg);
if(msg.value()<=0)
{
if(I.subjectToWearAndTear())
{
I.setUsesRemaining(I.usesRemaining()-damageAmount);
I.recoverPhyStats(); // important relation to setuses -- for brittle
if(I.usesRemaining()<=0)
{
I.setUsesRemaining(100);
I.unWear();
msg=CMClass.getMsg(mob,null,I,CMMsg.MSG_OK_VISUAL,L("<O-NAME> is destroyed!"),null,L("<O-NAME> carried by <S-NAME> is destroyed!"));
if(R.okMessage(mob,msg))
R.send(mob,msg);
I.destroy();
mob.recoverPhyStats();
mob.recoverCharStats();
mob.recoverMaxState();
R.recoverRoomStats();
}
else
if(I.usesRemaining()<=10)
{
mob.tell(L("@x1 is looking really bad.",I.name(mob)));
}
}
else
if(damageAmount > 0)
{
I.unWear();
msg=CMClass.getMsg(mob,null,I,CMMsg.MSG_OK_VISUAL,L("<O-NAME> is destroyed!"),null,L("<O-NAME> carried by <S-NAME> is destroyed!"));
if(R.okMessage(mob,msg))
R.send(mob,msg);
I.destroy();
mob.recoverPhyStats();
mob.recoverCharStats();
mob.recoverMaxState();
R.recoverRoomStats();
}
}
}
}
@Override
public boolean doTurnBasedCombat(final MOB mob, final Room R, final Area A)
{
int index = R.getCombatTurnMobIndex();
MOB M=null;
if(mob.actions() < 1.0)
{
if((index >= R.numInhabitants())||((M=R.fetchInhabitant(index))==mob)||(M==null)||(!M.isInCombat()))
{
if((index<0)||(index>=R.numInhabitants()-1))
index=-1;
for(index++;index<R.numInhabitants();index++)
{
M=R.fetchInhabitant(index);
if((M!=null)&&(M.isInCombat()))
{
M.setActions((M.actions() - Math.floor(M.actions())) + (CMLib.flags().isSitting(M) ? M.phyStats().speed() / 2.0 : M.phyStats().speed()));
R.setCombatTurnMobIndex(index);
break;
}
}
return false;
}
else
{
return true;
}
}
else
if((index < R.numInhabitants())&&((M=R.fetchInhabitant(index))!=null)&&(M!=mob)&&(M.isInCombat()))
mob.setActions(mob.actions()-Math.floor(mob.actions()));
return false;
}
@Override
public int adjustedArmor(final MOB mob)
{
int currDex=mob.charStats().getStat(CharStats.STAT_DEXTERITY);
final int maxDex = mob.charStats().getMaxStat(CharStats.STAT_DEXTERITY);
int dexBonus = 0;
if(currDex > maxDex)
{
dexBonus = currDex - maxDex;
currDex = maxDex;
}
double baseDex=mob.baseCharStats().getStat(CharStats.STAT_DEXTERITY);
if(baseDex > maxDex)
baseDex = maxDex;
final double[] vars = {
mob.phyStats().armor(),
currDex,
baseDex,
dexBonus,
(mob.curState().getHunger()<1)?1.0:0.0,
(mob.curState().getThirst()<1)?1.0:0.0,
(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,
CMLib.flags().isSitting(mob)?0.0:1.0,
CMLib.flags().isSleeping(mob)?0.0:1.0
};
return (int)Math.round(CMath.parseMathExpression(armorAdjustmentFormula, vars, 0.0));
}
@Override
public int adjustedArmor(final int armorValue)
{
final double[] vars = {
armorValue,
CharStats.VALUE_ALLSTATS_DEFAULT,
CharStats.VALUE_ALLSTATS_DEFAULT,
0, //dexBonus,
0.0,//hungry
0.0,//thirsty
0.0,//fatigued
1.0,//sitting
1.0 //sleeping
};
return (int)Math.round(CMath.parseMathExpression(armorAdjustmentFormula, vars, 0.0));
}
@Override
public boolean rollToHit(final MOB attacker, final MOB defender)
{
if((attacker==null)||(defender==null))
return false;
final double vars[] = {
attacker.phyStats().level(),
defender.phyStats().level(),
attacker.phyStats().level() > defender.phyStats().level() ? 1 : -1
};
final boolean isPVP=(attacker.isPlayer()&&defender.isPlayer());
final int attackerFudgeBonusAmt = (int)Math.round(CMath.parseMathExpression(isPVP?pvpAttackerFudgeBonusFormula:attackerFudgeBonusFormula, vars, 0.0));
return rollToHit(adjustedAttackBonus(attacker,defender),adjustedArmor(defender),attackerFudgeBonusAmt);
}
@Override
public boolean rollToHit(final int attack, final int defence, final int adjustment)
{
double myArmor= -((double)defence);
if(myArmor==0)
myArmor=1.0;
else
if(myArmor<0.0)
myArmor=-CMath.div(1.0,myArmor);
double hisAttack=attack;
if(hisAttack==0.0)
hisAttack=1.0;
else
if(hisAttack<0.0)
hisAttack=-CMath.div(1.0,myArmor);
return CMLib.dice().normalizeAndRollLess((int)Math.round(50.0*(hisAttack/myArmor)) + adjustment);
}
@Override
public Set<MOB> allCombatants(final MOB mob)
{
final SHashSet<MOB> h=new SHashSet<MOB>();
final Room thisRoom=mob.location();
if(thisRoom==null)
return null;
if(!mob.isInCombat())
return null;
Set<MOB> h1=null;
if(mob.Name().equalsIgnoreCase("nobody"))
h1=new HashSet<MOB>();
else
h1=mob.getGroupMembers(new HashSet<MOB>());
for(int m=0;m<thisRoom.numInhabitants();m++)
{
final MOB inhab=thisRoom.fetchInhabitant(m);
if((inhab!=null)
&&(inhab!=mob)
&&((inhab==mob.getVictim())
||((inhab!=mob)
&&(inhab.getVictim()!=mob.getVictim())
&&(CMLib.flags().isSeeable(inhab)||(!CMLib.flags().isCloaked(inhab)))
&&(CMLib.flags().canMove(inhab)||CMLib.flags().isSeeable(inhab))
&&(!h1.contains(inhab)))))
h.addUnsafe(inhab);
}
return h;
}
@Override
public void makePeaceInGroup(final MOB mob)
{
final Set<MOB> myGroup=mob.getGroupMembers(new HashSet<MOB>());
for (final MOB mob2 : myGroup)
{
if(mob2.isInCombat()&&(myGroup.contains(mob2.getVictim())))
mob2.makePeace(true);
}
}
@Override
public void postPanic(final MOB mob, final CMMsg addHere)
{
if(mob==null)
return;
// make sure he's not already dead, or with a pending death.
if(mob.amDead())
return;
if((addHere!=null)&&(addHere.trailerMsgs()!=null))
for(final CMMsg msg : addHere.trailerMsgs())
if((msg.source()==mob)
&&((msg.sourceMinor()==CMMsg.TYP_PANIC))
||(msg.sourceMinor()==CMMsg.TYP_DEATH))
return;
final CMMsg msg=CMClass.getMsg(mob,null,CMMsg.MSG_PANIC,null);
if(addHere!=null)
addHere.addTrailerMsg(msg);
else
if((mob.location()!=null)&&(mob.location().okMessage(mob,msg)))
mob.location().send(mob,msg);
}
@Override
public void postDeath(final MOB killerM, final MOB deadM, final CMMsg addHere)
{
if(deadM==null)
return;
final Room deathRoom=deadM.location();
if(deathRoom==null)
return;
// make sure he's not already dead, or with a pending death.
if(deadM.amDead())
return;
if((addHere!=null)&&(addHere.trailerMsgs()!=null))
{
for(final CMMsg msg : addHere.trailerMsgs())
{
if((msg.source()==deadM)
&&((msg.sourceMinor()==CMMsg.TYP_PANIC))
||(msg.sourceMinor()==CMMsg.TYP_DEATH))
return;
}
}
final String msp=CMLib.protocol().msp("death"+CMLib.dice().roll(1,7,0)+".wav",50);
CMMsg msg=null;
if((deadM.playerStats()!=null)
&&(deadM.playerStats().getDeathPoof().length()>0))
{
final String dpoof=deadM.playerStats().getDeathPoof();
msg=CMClass.getMsg(deadM,null,killerM,
CMMsg.MSG_OK_VISUAL,"^f^*^<FIGHT^>"+dpoof+"^</FIGHT^>^?^.\n\r"+msp,
CMMsg.MSG_OK_VISUAL,null,
CMMsg.MSG_DEATH,"^F^<FIGHT^>"+dpoof+"</FIGHT^>^?\n\r@x1"+msp);
}
else
if(isKnockedOutUponDeath(deadM,killerM))
{
msg=CMClass.getMsg(deadM,null,killerM,
CMMsg.MSG_OK_VISUAL,L("^f^*^<FIGHT^>!!!!!!!!!!!!!!YOU ARE DEFEATED!!!!!!!!!!!!!!^</FIGHT^>^?^.\n\r@x1",msp),
CMMsg.MSG_OK_VISUAL,null,
CMMsg.MSG_DEATH,L("^F^<FIGHT^><S-NAME> is DEFEATED!!!^</FIGHT^>^?\n\r@x1",msp));
}
else
{
msg=CMClass.getMsg(deadM,null,killerM,
CMMsg.MSG_OK_VISUAL,L("^f^*^<FIGHT^>!!!!!!!!!!!!!!YOU ARE DEAD!!!!!!!!!!!!!!^</FIGHT^>^?^.\n\r@x1",msp),
CMMsg.MSG_OK_VISUAL,null,
CMMsg.MSG_DEATH,L("^F^<FIGHT^><S-NAME> is DEAD!!!^</FIGHT^>^?\n\r@x1",msp));
}
CMLib.map().sendGlobalMessage(deadM,CMMsg.TYP_DEATH, CMClass.getMsg(deadM,null,killerM, CMMsg.TYP_DEATH,null, CMMsg.TYP_DEATH,null, CMMsg.TYP_DEATH,null));
final CMMsg msg2=CMClass.getMsg(deadM,null,killerM, CMMsg.MSG_DEATH,null, CMMsg.MSG_DEATH,null, CMMsg.MSG_DEATH,null);
if(addHere!=null)
{
if(deathRoom.okMessage(deadM,msg2))
{
addHere.addTrailerMsg(msg);
addHere.addTrailerMsg(msg2);
}
}
else
if(deathRoom.okMessage(deadM,msg))
{
deathRoom.send(deadM,msg);
if(deathRoom.okMessage(deadM,msg2))
deathRoom.send(deadM,msg2);
}
}
@Override
public boolean postAttack(final MOB attacker, final MOB target, Item weapon)
{
if((attacker==null)||(!attacker.mayPhysicallyAttack(target)))
return false;
if((weapon==null)
&&(attacker.isAttributeSet(MOB.Attrib.AUTODRAW)))
{
CMLib.commands().postDraw(attacker,false,true);
weapon=attacker.fetchWieldedItem();
}
final CMMsg msg=CMClass.getMsg(attacker,target,weapon,CMMsg.MSG_WEAPONATTACK,null);
final Room R=target.location();
if(R!=null)
{
if(R.okMessage(attacker,msg))
{
R.send(attacker,msg);
return msg.value()>0;
}
}
return false;
}
protected static boolean ownerSecurityCheck(final String ownerName, final MOB mob)
{
return (ownerName.length()>0)
&&(mob!=null)
&&((mob.Name().equals(ownerName))
||(mob.getLiegeID().equals(ownerName)&mob.isMarriedToLiege())
||(CMLib.clans().checkClanPrivilege(mob, ownerName, Clan.Function.PROPERTY_OWNER)));
}
@Override
public boolean mayIAttackThisVessel(final MOB mob, final PhysicalAgent defender)
{
final String defenderOwnerName = (defender instanceof PrivateProperty) ? ((PrivateProperty)defender).getOwnerName() : "";
// is this how we determine npc ships?
if(((defenderOwnerName == null)||(defenderOwnerName.length()==0))&&(defender instanceof PrivateProperty))
return true;
if(CMSecurity.isASysOp(mob) && mob.isAttributeSet(Attrib.PLAYERKILL))
return true;
if(CMLib.flags().isUnattackable(defender))
return false;
if(defender instanceof BoardableShip)
{
final Area otherArea = ((BoardableShip)defender).getShipArea();
if(otherArea != null)
{
for(final Enumeration<Room> r=otherArea.getProperMap();r.hasMoreElements();)
{
final Room R=r.nextElement();
if(R!=null)
{
for(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)
{
final MOB M=m.nextElement();
if((M!=null)
&&(ownerSecurityCheck(defenderOwnerName,M))
&&(mob.mayIFight(M)))
{
return true;
}
}
}
}
}
}
else
if(defender instanceof Rideable)
{
final Rideable rideableDefender=(Rideable)defender;
for(int i=0;i<rideableDefender.numRiders();i++)
{
final Rider R=rideableDefender.fetchRider(i);
if((R instanceof MOB)
&&(!mob.mayIFight(R)))
{
return false;
}
}
return true;
}
return false;
}
@Override
public final int getShipHullPoints(final BoardableShip ship)
{
if(ship == null)
return 0;
return 10 * ship.getShipArea().numberOfProperIDedRooms();
}
@Override
public final boolean isAShipSiegeWeapon(final Item I)
{
if((I instanceof AmmunitionWeapon)
&&(I instanceof Rideable)
&&((AmmunitionWeapon)I).isFreeStanding()
&&(((AmmunitionWeapon)I).requiresAmmunition()))
return true;
return false;
}
@Override
public boolean postShipAttack(final MOB attacker, final PhysicalAgent attackingShip, final PhysicalAgent target, final Weapon weapon, final boolean wasAHit)
{
// if not in combat, howd you get here? if you are, this MUST happen
//(!mayIAttack(attacker,attackingShip, target))
if((attacker==null)||(weapon==null))
return false;
final CMMsg msg=CMClass.getMsg(attacker,target,weapon,CMMsg.MSG_WEAPONATTACK,null);
final Room R=CMLib.map().roomLocation(target);
if(R!=null)
{
msg.setValue(wasAHit?1:0);
if(R.okMessage(attacker,msg))
{
R.send(attacker,msg);
return wasAHit;
}
}
return false;
}
@Override
public boolean postHealing(MOB healer,
final MOB target,
final Ability tool,
final int healing,
final int messageCode,
final String allDisplayMessage)
{
if(healer==null)
healer=target;
if((healer==null)||(target==null)||(target.location()==null))
return false;
final CMMsg msg=CMClass.getMsg(healer,target,tool,messageCode,CMMsg.MSG_HEALING,messageCode,allDisplayMessage);
msg.setValue(healing);
final Room R=target.location();
if(R!=null)
{
if(R.okMessage(target,msg))
{
R.send(target,msg);
return true;
}
}
return false;
}
@Override
public String replaceDamageTag(final String str, final int damage, final int damageType, final View sourceTargetSTO)
{
if(str==null)
return null;
final int replace=str.indexOf("<DAMAGE");
if(replace < 0)
return str;
if(str.length() < replace+9)
return str;
final boolean damages = (str.charAt(replace+7)=='S') && (str.charAt(replace+8)=='>');
final String showDamage = CMProps.getVar(CMProps.Str.SHOWDAMAGE);
final boolean showNumbers = showDamage.equalsIgnoreCase("YES")
||((sourceTargetSTO==CMMsg.View.SOURCE)&&showDamage.equalsIgnoreCase("SOURCE"))
||((sourceTargetSTO==CMMsg.View.TARGET)&&showDamage.equalsIgnoreCase("TARGET"));
if(damages)
{
final String hitWord=CMStrings.deleteAllofAny(standardHitWord(damageType,damage),PARENS);
if(!showNumbers)
return str.substring(0,replace)+hitWord+str.substring(replace+9);
return str.substring(0,replace)+hitWord+" ("+damage+")"+ str.substring(replace+9);
}
else
if(str.charAt(replace+7)=='>')
{
if(!showNumbers)
return str.substring(0,replace)+standardHitWord(damageType,damage)+str.substring(replace+8);
return str.substring(0,replace)+standardHitWord(damageType,damage)+" ("+damage+")"+ str.substring(replace+8);
}
return str;
}
@Override
public void forcePeaceAllFightingAgainst(final MOB mob, final Set<MOB> exceptionSet)
{
final Room R=mob.location();
if(R==null)
return;
for(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)
{
final MOB M=m.nextElement();
if((M!=null)
&&(M.getVictim()==mob)
&&(M!=mob)
&&((exceptionSet==null)||(!exceptionSet.contains(M))))
M.setVictim(null);
}
}
@Override
public Set<MOB> getAllFightingAgainst(final MOB mob, Set<MOB> set)
{
if(set == null)
set=new HashSet<MOB>(1);
final Room R=mob.location();
if(R==null)
return set;
for(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)
{
final MOB M=m.nextElement();
if((M!=null)&&(M.getVictim()==mob)&&(M!=mob))
set.add(M);
}
return set;
}
@Override
public void postDamage(final MOB attacker, final MOB target, final Environmental weapon, int damage, final int messageCode, final int damageType, String allDisplayMessage)
{
if((attacker==null)||(target==null)||(target.location()==null))
return;
if(allDisplayMessage!=null)
allDisplayMessage="^F^<FIGHT^>"+allDisplayMessage+"^</FIGHT^>^?";
final int damageTypeMsg;
if(attacker != target)
{
if((weapon instanceof Ability)
&&(damage>0)
&&(attacker.isMine(weapon)||(attacker.phyStats().level()>1))) // why >1? because quickly made fake-mobs tend to have lvl=1
damage = modifySpellDamage(attacker, target, damage);
damageTypeMsg = CMMsg.TYP_DAMAGE | (CMMsg.MASK_MALICIOUS & messageCode);
}
else
damageTypeMsg = CMMsg.TYP_DAMAGE;
final CMMsg msg=CMClass.getMsg(attacker,target,weapon,messageCode,damageTypeMsg,messageCode,allDisplayMessage);
msg.setValue(damage);
CMLib.color().fixSourceFightColor(msg);
final Room R=target.location();
if(R!=null)
{
if(R.okMessage(target,msg))
{
if(damageType>=0)
{
msg.modify(msg.source(),
msg.target(),
msg.tool(),
msg.sourceCode(),
replaceDamageTag(msg.sourceMessage(),msg.value(),damageType,CMMsg.View.SOURCE),
msg.targetCode(),
replaceDamageTag(msg.targetMessage(),msg.value(),damageType,CMMsg.View.TARGET),
msg.othersCode(),
replaceDamageTag(msg.othersMessage(),msg.value(),damageType,CMMsg.View.OTHERS));
}
R.send(target,msg);
}
}
}
public int modifySpellDamage(final MOB attacker, final MOB target, int baseDamage)
{
final int maxInt = attacker.charStats().getMaxStat(CharStats.STAT_INTELLIGENCE);
int currInt = attacker.charStats().getStat(CharStats.STAT_INTELLIGENCE);
int intBonus = 0;
if(currInt > maxInt)
{
intBonus = currInt - maxInt;
currInt = maxInt;
}
int baseInt = attacker.baseCharStats().getStat(CharStats.STAT_INTELLIGENCE);
if(baseInt > maxInt)
baseInt = maxInt;
final double[] vars = {
baseDamage,
currInt,
baseInt,
intBonus,
(attacker.curState().getHunger()<1)?1.0:0.0,
(attacker.curState().getThirst()<1)?1.0:0.0,
(attacker.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,
attacker.phyStats().level(),
target.phyStats().level()
};
final boolean isPVP=(attacker.isPlayer()&&target.isPlayer());
baseDamage = (int)Math.round(CMath.parseMathExpression(isPVP?pvpSpellFudgeDamageFormula:spellFudgeDamageFormula, vars, 0.0));
vars[0]=baseDamage;
final int spellCritChancePct = (int)Math.round(CMath.parseMathExpression(isPVP?pvpSpellCritChanceFormula:spellCritChanceFormula, vars, 0.0))
+ attacker.charStats().getStat(CharStats.STAT_CRIT_CHANCE_PCT_MAGIC);
if(CMLib.dice().rollPercentage()<spellCritChancePct)
{
final int spellCritDamageAmt = (int)Math.round(CMath.parseMathExpression(isPVP?pvpSpellCritDmgFormula:spellCritDmgFormula, vars, 0.0));
baseDamage+=spellCritDamageAmt;
if(attacker.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_MAGIC)>0)
baseDamage += (int)Math.round(CMath.mul(spellCritDamageAmt,CMath.div(attacker.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_MAGIC),100.0)));
}
return baseDamage;
}
@Override
public int adjustedDamage(final MOB mob, final Weapon weapon, final MOB target, final int bonusDamage, final boolean allowCrits, final boolean biasHigh)
{
double damageAmount=0.0;
Physical useDmg = null;
boolean rangedAttack = false;
if((weapon!=null)
&&((weapon.weaponClassification()==Weapon.CLASS_RANGED)||(weapon.weaponClassification()==Weapon.CLASS_THROWN)))
{
useDmg = weapon;
rangedAttack = true;
}
else
useDmg = mob;
final boolean isPVP=((target!=null)&&mob.isPlayer()&&target.isPlayer());
final int maxStr = mob.charStats().getMaxStat(CharStats.STAT_STRENGTH);
int currStr = mob.charStats().getStat(CharStats.STAT_STRENGTH);
if(currStr > maxStr)
currStr = maxStr;
if(target!=null)
{
final double[] vars = {
useDmg.phyStats().damage()+bonusDamage,
currStr,
mob.phyStats().level(),
target.phyStats().level(),
(mob.curState().getHunger()<1)?1.0:0.0,
(mob.curState().getThirst()<1)?1.0:0.0,
(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,
CMLib.flags().canBeSeenBy(target,mob)?0:1,
CMLib.flags().isSleeping(target)?1:0,
CMLib.flags().isSitting(target)?1:0
};
CMath.CompiledFormula formula;
if(rangedAttack)
formula = isPVP?pvpTargetedRangedDamageFormula:targetedRangedDamageFormula;
else
formula = isPVP?pvpTargetedMeleeDamageFormula:targetedMeleeDamageFormula;
if(biasHigh)
damageAmount = CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0);
else
damageAmount = CMath.parseMathExpression(formula, vars, 0.0);
}
else
{
final double[] vars = {
useDmg.phyStats().damage()+bonusDamage,
currStr,
mob.phyStats().level(),
0,
(mob.curState().getHunger()<1)?1.0:0.0,
(mob.curState().getThirst()<1)?1.0:0.0,
(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,
0,
0,
0
};
CMath.CompiledFormula formula;
if((weapon!=null)&&((weapon.weaponClassification()==Weapon.CLASS_RANGED)||(weapon.weaponClassification()==Weapon.CLASS_THROWN)))
formula = staticRangedDamageFormula;
else
formula = staticMeleeDamageFormula;
if(biasHigh)
damageAmount = CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0);
else
damageAmount = CMath.parseMathExpression(formula, vars, 0.0);
}
final int maxDex = mob.charStats().getMaxStat(CharStats.STAT_DEXTERITY);
int currDex = mob.charStats().getStat(CharStats.STAT_DEXTERITY);
int dexBonus = 0;
if(currDex > maxDex)
{
dexBonus = currDex - maxDex;
currDex = maxDex;
}
int baseDex = mob.baseCharStats().getStat(CharStats.STAT_DEXTERITY);
if(baseDex > maxDex)
baseDex = maxDex;
final double[] vars = {
damageAmount,
currDex,
baseDex,
dexBonus,
(mob.curState().getHunger()<1)?1.0:0.0,
(mob.curState().getThirst()<1)?1.0:0.0,
(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,
mob.phyStats().level(),
(target==null)?0:target.phyStats().level()
};
if(allowCrits)
{
final int weaponCritChancePct = (int)Math.round(CMath.parseMathExpression(isPVP?pvpWeaponCritChanceFormula:weaponCritChanceFormula, vars, 0.0))
+ mob.charStats().getStat(CharStats.STAT_CRIT_CHANCE_PCT_WEAPON);
if((CMLib.dice().rollPercentage()<weaponCritChancePct)||(biasHigh))
{
final CMath.CompiledFormula formula = isPVP?pvpWeaponCritDmgFormula:weaponCritDmgFormula;
final int weaponCritDmgAmt;
if(biasHigh)
weaponCritDmgAmt = (int)Math.round(CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0));
else
weaponCritDmgAmt = (int)Math.round(CMath.parseMathExpression(formula, vars, 0.0));
damageAmount += weaponCritDmgAmt;
if(mob.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_WEAPON)>0)
damageAmount += (int)Math.round(CMath.mul(weaponCritDmgAmt,CMath.div(mob.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_WEAPON),100.0)));
}
}
if(target != null)
{
vars[0] = damageAmount;
CMath.CompiledFormula formula;
if(rangedAttack)
formula = isPVP?pvpRangedFudgeDamageFormula:rangedFudgeDamageFormula;
else
formula = isPVP?pvpMeleeFudgeDamageFormula:meleeFudgeDamageFormula;
if(biasHigh)
damageAmount = CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0);
else
damageAmount = CMath.parseMathExpression(formula, vars, 0.0);
}
return (int)Math.round(damageAmount);
}
@Override
public int adjustedDamage(final int baseDamage, final int level, final boolean biasHigh)
{
double damageAmount=0.0;
final double[] vars = {
baseDamage,
CharStats.VALUE_ALLSTATS_DEFAULT,
level,
0,
0.0,//hunger
0.0,//thirst
0.0,//fatigue
0,
0,
0
};
if(biasHigh)
damageAmount = CMath.parseMathExpression(staticMeleeDamageFormula, CMath.NotRandomHigh, vars, 0.0);
else
damageAmount = CMath.parseMathExpression(staticMeleeDamageFormula, vars, 0.0);
return (int)Math.round(damageAmount);
}
@Override
public void recoverTick(final MOB mob)
{
if((mob!=null)
&&(!mob.isInCombat()))
{
final CharStats charStats=mob.charStats();
final CharState curState=mob.curState();
final CharState maxState=mob.maxState();
if((curState.getHitPoints()<maxState.getHitPoints())
||(curState.getMana()<maxState.getMana())
||(curState.getMovement()<maxState.getMovement()))
{
final Room R=mob.location();
final Area A=(R!=null)?R.getArea():null;
if((A instanceof BoardableShip)
&&(((BoardableShip)A).getShipItem() instanceof Combatant)
&&(((Combatant)((BoardableShip)A).getShipItem()).isInCombat()))
return;
final Rideable riding=mob.riding();
final boolean isSleeping=CMLib.flags().isSleeping(mob);
final boolean bedBonus=isSleeping && (riding!=null) && (riding.rideBasis()==Rideable.RIDEABLE_SLEEP);
final boolean isSittingOrRiding=((!isSleeping) && ((CMLib.flags().isSitting(mob))||(mob.riding()!=null))) || bedBonus;
final boolean isFlying=((!isSleeping) && (!isSittingOrRiding) && CMLib.flags().isFlying(mob)) || bedBonus;
final boolean isSwimming=(!isSleeping) && (!isSittingOrRiding) && (!isFlying) && CMLib.flags().isSwimming(mob);
final double[] vals=new double[]{
charStats.getStat(CharStats.STAT_CONSTITUTION),
mob.phyStats().level(),
(curState.getHunger()<1)?1.0:0.0,
(curState.getThirst()<1)?1.0:0.0,
(curState.getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,
isSleeping?1.0:0.0,
isSittingOrRiding?1.0:0.0,
isFlying?1.0:0.0,
isSwimming?1.0:0.0
};
final double hpGain = CMath.parseMathExpression(stateHitPointRecoverFormula, vals, 0.0);
if((hpGain>0)&&(!CMLib.flags().isGolem(mob)))
curState.adjHitPoints((int)Math.round(hpGain),maxState);
vals[0]=((charStats.getStat(CharStats.STAT_INTELLIGENCE)+charStats.getStat(CharStats.STAT_WISDOM)));
final double manaGain = CMath.parseMathExpression(stateManaRecoverFormula, vals, 0.0);
if(manaGain>0)
curState.adjMana((int)Math.round(manaGain),maxState);
vals[0]=charStats.getStat(CharStats.STAT_STRENGTH);
final double moveGain = CMath.parseMathExpression(this.stateMovesRecoverFormula, vals, 0.0);
if(moveGain>0)
curState.adjMovement((int)Math.round(moveGain),maxState);
}
}
}
@Override
public CMMsg postWeaponDamage(final MOB source, final MOB target, final Item item, int damageInt)
{
int damageType=Weapon.TYPE_BASHING;
Weapon weapon=null;
if(item instanceof Weapon)
{
weapon=(Weapon)item;
damageType=weapon.weaponDamageType();
}
// calculate Base Damage (with Strength bonus)
final String oldHitString="^F^<FIGHT^>"+((weapon!=null)?
weapon.hitString(damageInt):
standardHitString(Weapon.TYPE_NATURAL,Weapon.CLASS_BLUNT,damageInt,item.name()))+"^</FIGHT^>^?";
final CMMsg msg=CMClass.getMsg(source,
target,
item,
CMMsg.MSG_OK_VISUAL,
CMMsg.MSG_DAMAGE,
CMMsg.MSG_OK_VISUAL,
oldHitString);
CMLib.color().fixSourceFightColor(msg);
msg.setValue(damageInt);
// why was there no okaffect here?
final Room room=source.location();
if((room!=null)&&(room.okMessage(source,msg)))
{
if(msg.targetMinor()==CMMsg.TYP_DAMAGE)
{
damageInt=msg.value();
msg.modify(msg.source(),
msg.target(),
msg.tool(),
msg.sourceCode(),
replaceDamageTag(msg.sourceMessage(),msg.value(),damageType,CMMsg.View.SOURCE),
msg.targetCode(),
replaceDamageTag(msg.targetMessage(),msg.value(),damageType,CMMsg.View.TARGET),
msg.othersCode(),
replaceDamageTag(msg.othersMessage(),msg.value(),damageType,CMMsg.View.OTHERS));
}
if(source.mayIFight(target))
{
if((msg.source().riding() instanceof BoardableShip)
&&(CMLib.combat().isAShipSiegeWeapon(item)))
{
room.send(source,msg);
return msg;
}
else
if((source.location()==room)
&&(target.location()==room))
{
room.send(source,msg);
return msg;
}
}
}
return null;
}
@Override
public CMMsg postWeaponAttackResult(final MOB sourceM, final MOB targetM, final Item item, final boolean success)
{
if(sourceM==null)
return null;
if(!sourceM.mayIFight(targetM))
return null;
Weapon weapon=null;
int damageInt = 0;
if(item instanceof Weapon)
{
weapon=(Weapon)item;
damageInt=adjustedDamage(sourceM,weapon,targetM,0,true,false);
}
if(success)
{
if((sourceM.playerStats()!=null)
&&(sourceM!=targetM))
sourceM.playerStats().bumpLevelCombatStat(PlayerCombatStat.HITS_DONE, sourceM.basePhyStats().level(), 1);
if(targetM.playerStats()!=null)
targetM.playerStats().bumpLevelCombatStat(PlayerCombatStat.HITS_TAKEN, targetM.basePhyStats().level(), 1);
postWeaponDamage(sourceM,targetM,item,damageInt);
}
else
{
final String missString="^F^<FIGHT^>"+((weapon!=null)?
weapon.missString():
standardMissString(Weapon.TYPE_BASHING,Weapon.CLASS_BLUNT,item.name(),false))+"^</FIGHT^>^?";
final CMMsg msg=CMClass.getMsg(sourceM,
targetM,
weapon,
CMMsg.MSG_ATTACKMISS,
missString);
CMLib.color().fixSourceFightColor(msg);
// why was there no okaffect here?
final Room R=sourceM.location();
if(R!=null)
if(R.okMessage(sourceM,msg) && (!sourceM.amDead()) && (!sourceM.amDestroyed()))
{
R.send(sourceM,msg);
return msg;
}
}
return null;
}
@Override
public void postShipWeaponAttackResult(final MOB source, final PhysicalAgent attacker, final PhysicalAgent defender, final Weapon weapon, final boolean success)
{
if(source==null)
return;
// if you aren't in combat, how'd you get here.
// if you are in combat, this needs to happen regardless
//if(!mayIAttack(source, attacker, defender))
// return;
int damageInt=adjustedDamage(source,weapon,null,0,false,false);
int damageType=Weapon.TYPE_BASHING;
if(weapon != null)
damageType= weapon.weaponDamageType();
final Room room=CMLib.map().roomLocation(attacker);
if(success)
{
// calculate Base Damage (with Strength bonus)
final String oldHitString="^F^<FIGHT^>"+((weapon!=null)?
weapon.hitString(damageInt):
standardHitString(Weapon.TYPE_NATURAL,Weapon.CLASS_BLUNT,(int)Math.round(Math.pow(2,damageInt)),attacker.Name()))+"^</FIGHT^>^?";
final CMMsg msg=CMClass.getMsg(source,
defender,
weapon,
CMMsg.MSG_OK_VISUAL,
CMMsg.MSG_DAMAGE,
CMMsg.MSG_OK_VISUAL,
oldHitString);
CMLib.color().fixSourceFightColor(msg);
msg.setValue(damageInt);
// why was there no okaffect here?
if((room!=null)&&(room.okMessage(source,msg)))
{
if(msg.targetMinor()==CMMsg.TYP_DAMAGE)
{
damageInt=msg.value();
msg.modify(msg.source(),
msg.target(),
msg.tool(),
msg.sourceCode(),
replaceDamageTag(msg.sourceMessage(),msg.value(),damageType,CMMsg.View.SOURCE),
msg.targetCode(),
replaceDamageTag(msg.targetMessage(),msg.value(),damageType,CMMsg.View.TARGET),
msg.othersCode(),
replaceDamageTag(msg.othersMessage(),msg.value(),damageType,CMMsg.View.OTHERS));
}
if(//(mayIAttack(source,attacker,defender))&&
(CMLib.map().roomLocation(attacker)==room)
&&(CMLib.map().roomLocation(defender)==room))
room.send(source,msg);
}
}
else
{
final String missString="^F^<FIGHT^>"+((weapon!=null)?
weapon.missString():
standardMissString(Weapon.TYPE_BASHING,Weapon.CLASS_BLUNT,attacker.name(),false))+"^</FIGHT^>^?";
final CMMsg msg=CMClass.getMsg(source,
defender,
weapon,
CMMsg.MSG_ATTACKMISS,
missString);
CMLib.color().fixSourceFightColor(msg);
// why was there no okaffect here?
if((room!=null)
&&(room.okMessage(source,msg)))
room.send(source,msg);
}
}
protected void processFormation(final List<MOB>[] done, final MOB leader, final int level)
{
for (final List<MOB> element : done)
{
if((element!=null)&&(element.contains(leader)))
return;
}
if(level>=done.length)
return;
if(done[level]==null)
done[level]=new Vector<MOB>();
done[level].add(leader);
for(int f=0;f<leader.numFollowers();f++)
{
final MOB M=leader.fetchFollower(f);
if(M==null)
continue;
int range=leader.fetchFollowerOrder(M);
if(range<0)
range=0;
processFormation(done,M,level+range);
}
}
@Override
public MOB getFollowedLeader(final MOB mob)
{
MOB leader=mob;
if(leader.amFollowing()!=null)
leader=leader.amUltimatelyFollowing();
return leader;
}
@Override
@SuppressWarnings("unchecked")
public List<MOB>[] getFormation(final MOB mob)
{
MOB leader=mob;
if(leader.amFollowing()!=null)
leader=leader.amUltimatelyFollowing();
final Vector<MOB>[] done=new Vector[20];
processFormation(done,leader,0);
return done;
}
@Override
public List<MOB> getFormationFollowed(final MOB mob)
{
final List<MOB>[] form=getFormation(mob);
for(int i=1;i<form.length;i++)
{
if((form[i]!=null)&&(form[i].contains(mob)))
{
i--;
while(i>=0)
{
if((form[i]!=null)&&(form[i].size()>0))
return form[i];
i--;
}
}
}
return null;
}
@Override
public int getFormationAbsOrder(final MOB mob)
{
final List<MOB>[] form=getFormation(mob);
for(int i=1;i<form.length;i++)
{
if((form[i]!=null)&&(form[i].contains(mob)))
return i;
}
return 0;
}
public MOB getCombatDominentKiller(final MOB killer, final MOB killed)
{
if((!killer.isMonster())&&(killer.charStats()!=null))
return killer;
MOB M=killer;
Set<MOB> checked=new HashSet<MOB>();
checked.add(M);
while(M.isMonster()
&&(M.amFollowing()!=null)
&&(!checked.contains(M.amFollowing())))
{
M=M.amFollowing();
checked.add(M);
}
if((!M.isMonster())&&(M.charStats()!=null))
return M;
checked.clear();
checked=killer.getGroupMembers(checked);
for(final Iterator<MOB> m=checked.iterator(); m.hasNext(); )
{
M=m.next();
if((!M.isMonster())&&(M.charStats()!=null))
return M;
}
return killer;
}
@Override
public CharClass getCombatDominantClass(final MOB killer, final MOB killed)
{
CharClass C=null;
if((killer!=null)&&(killer.charStats()!=null))
{
C=killer.charStats().getCurrentClass();
MOB M=killer;
final HashSet<MOB> checked=new HashSet<MOB>();
checked.add(M);
while(M.isMonster()
&&(M.amFollowing()!=null)
&&(!checked.contains(M.amFollowing())))
{
M=M.amFollowing();
checked.add(M);
}
if((!M.isMonster())&&(M.charStats()!=null))
C=M.charStats().getCurrentClass();
}
else
C=CMClass.getCharClass("StdCharClass");
return C;
}
protected Set<MOB> getCombatBeneficiaries(final MOB killer, final MOB killed, final Room deathRoom, final Set<MOB> beneficiaries, CharClass combatCharClass)
{
final Set<MOB> followers=(killer!=null)?killer.getGroupMembers(new HashSet<MOB>()):(new SHashSet<MOB>());
if(combatCharClass==null)
combatCharClass=CMClass.getCharClass("StdCharClass");
if(deathRoom!=null)
{
for(int m=0;m<deathRoom.numInhabitants();m++)
{
final MOB mob=deathRoom.fetchInhabitant(m);
if((combatCharClass.isValidClassBeneficiary(killer,killed,mob,followers))
&&(!beneficiaries.contains(mob)))
beneficiaries.add(mob);
}
}
if((killer!=null)&&(!beneficiaries.contains(killer))&&(killer!=killed)&&(CMLib.flags().isInTheGame(killer,true)))
beneficiaries.add(killer);
return beneficiaries;
}
@Override
public Set<MOB> getCombatBeneficiaries(final MOB killer, final MOB killed, final CharClass combatCharClass)
{
if((killer==null)||(killed==null))
return new SHashSet<MOB>();
final SHashSet<MOB> beneficiaries=new SHashSet<MOB>();
Room R=killer.location();
if(R!=null)
getCombatBeneficiaries(killer,killed,R,beneficiaries,combatCharClass);
R=killed.location();
if((R!=null)&&(R!=killer.location()))
getCombatBeneficiaries(killer,killed,R,beneficiaries,combatCharClass);
return beneficiaries;
}
protected Set<MOB> getCombatDividers(final MOB killer, final MOB killed, final Room deathRoom, final Set<MOB> dividers, CharClass combatCharClass)
{
final Set<MOB> followers=(killer!=null)?killer.getGroupMembers(new HashSet<MOB>()):(new HashSet<MOB>());
if(combatCharClass==null)
combatCharClass=CMClass.getCharClass("StdCharClass");
if(deathRoom!=null)
{
for(int m=0;m<deathRoom.numInhabitants();m++)
{
final MOB mob=deathRoom.fetchInhabitant(m);
if((combatCharClass.isValidClassDivider(killer,killed,mob,followers))
&&(!dividers.contains(mob)))
dividers.add(mob);
}
}
if((killer!=null)&&(!dividers.contains(killer))&&(killer!=killed)&&(CMLib.flags().isInTheGame(killer,true)))
dividers.add(killer);
return dividers;
}
@Override
public Set<MOB> getCombatDividers(final MOB killer, final MOB killed, final CharClass combatCharClass)
{
if((killer==null)||(killed==null))
return new SHashSet<MOB>();
final Set<MOB> dividers=new SHashSet<MOB>();
Room R=killer.location();
if(R!=null)
getCombatDividers(killer,killed,R,dividers,combatCharClass);
R=killed.location();
if((R!=null)&&(R!=killer.location()))
getCombatDividers(killer,killed,R,dividers,combatCharClass);
return dividers;
}
protected DeadBody justDie(final MOB killerM, final MOB deadM)
{
if(deadM==null)
return null;
final Room deathRoom=deadM.location();
if(deathRoom == null)
return null;
if(deadM.isPlayer())
{
if(killerM != null)
CMLib.achievements().possiblyBumpAchievement(deadM, AchievementLibrary.Event.DEATHS, 1, killerM);
else
CMLib.achievements().possiblyBumpAchievement(deadM, AchievementLibrary.Event.DEATHS, 1, deadM);
}
final Session killerSess=(killerM==null)?null:killerM.session();
final Session deadMSess=deadM.session();
//TODO: this creates too many loops. The right thing is to loop once
// and call a boolean function to populate all these lists.
final CharClass combatCharClass=getCombatDominantClass(killerM,deadM);
final Set<MOB> beneficiaries=getCombatBeneficiaries(killerM,deadM,combatCharClass);
final Set<MOB> hisGroupH=deadM.getGroupMembers(new HashSet<MOB>());
for(final Enumeration<MOB> m=deathRoom.inhabitants();m.hasMoreElements();)
{
final MOB M=m.nextElement();
if((M!=null)
&&(M.getVictim()==deadM))
pickNextVictim(M, deadM, hisGroupH);
}
final Set<MOB> dividers=getCombatDividers(killerM,deadM,combatCharClass);
if(killerM != null)
CMLib.get(killerSess)._combat().dispenseExperience(beneficiaries,dividers,deadM);
else
CMLib.get(deadMSess)._combat().dispenseExperience(beneficiaries,dividers,deadM);
final String currency=CMLib.beanCounter().getCurrency(deadM);
final double deadMoney=CMLib.beanCounter().getTotalAbsoluteValue(deadM,currency);
double myAmountOfDeadMoney=0.0;
final List<MOB> goldLooters=new ArrayList<MOB>();
for (final MOB M : beneficiaries)
{
if(((M.isAttributeSet(MOB.Attrib.AUTOGOLD))
&&(!goldLooters.contains(M)))
&&(M!=deadM)
&&(M.location()==deathRoom)
&&(deathRoom.isInhabitant(M)))
goldLooters.add(M);
}
if((goldLooters.size()>0)&&(deadMoney>0))
{
myAmountOfDeadMoney=CMath.div(deadMoney,goldLooters.size());
CMLib.beanCounter().subtractMoney(deadM,deadMoney);
}
final int[] expLost={100*deadM.phyStats().level()};
if(expLost[0]<100)
expLost[0]=100;
String[] cmds=null;
if((deadM.isMonster())||(deadM.soulMate()!=null))
cmds=CMParms.toStringArray(CMParms.parseCommas(CMProps.getVar(CMProps.Str.MOBDEATH),true));
else
cmds=CMParms.toStringArray(CMParms.parseCommas(CMProps.get(deadM.session()).getStr(CMProps.Str.PLAYERDEATH),true));
DeadBody body=null; //must be done before consequences because consequences could be purging
if((!CMParms.containsIgnoreCase(cmds,"RECALL"))
&&(!isKnockedOutUponDeath(deadM,killerM)))
body=deadM.killMeDead(true);
final boolean stillExists = handleCombatLossConsequences(deadM,killerM,cmds,expLost,"^*You lose @x1 experience points.^?^.");
if(!isKnockedOutUponDeath(deadM,killerM))
{
Room bodyRoom=deathRoom;
if((body!=null)&&(body.owner() instanceof Room)&&(((Room)body.owner()).isContent(body)))
bodyRoom=(Room)body.owner();
if((killerM!=null)&&(body!=null))
{
body.setKillerName(killerM.Name());
body.setIsKillerPlayer(!killerM.isMonster());
body.setKillerTool(killerM.fetchWieldedItem());
if(body.getKillerTool()==null)
body.setKillerTool(killerM.getNaturalWeapon());
}
if((!deadM.isMonster())
&&(CMLib.dice().rollPercentage()==1)
&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.AUTODISEASE))
&&(stillExists))
{
final Ability A=CMClass.getAbility("Disease_Amnesia");
if((A!=null)
&&(deadM.fetchEffect(A.ID())==null)
&&(!CMSecurity.isAbilityDisabled(A.ID())))
A.invoke(deadM,deadM,true,0);
}
if(deadM.soulMate()!=null)
{
final Session s=deadM.session();
s.setMob(deadM.soulMate());
deadM.soulMate().setSession(s);
deadM.setSession(null);
deadM.soulMate().tell(L("^HYour spirit has returned to your body...\n\r\n\r^N"));
CMLib.commands().postLook(deadM.soulMate(),true);
deadM.setSoulMate(null);
}
if((killerM!=null)
&&(bodyRoom!=null)
&&(body!=null)
&&(killerM.location()==bodyRoom)
&&(bodyRoom.isInhabitant(killerM))
&&(killerM.isAttributeSet(MOB.Attrib.AUTOLOOT)))
{
if((killerM.riding()!=null)&&(killerM.riding() instanceof MOB))
killerM.tell(L("You'll need to dismount to loot the body."));
else
if((killerM.riding()!=null)&&(killerM.riding() instanceof MOB))
killerM.tell(L("You'll need to disembark to loot the body."));
else
for(int i=bodyRoom.numItems()-1;i>=0;i--)
{
final Item item=bodyRoom.getItem(i);
if((item!=null)
&&(item.container()==body)
&&(CMLib.flags().canBeSeenBy(body,killerM))
&&((!body.isDestroyedAfterLooting())||(!(item instanceof RawMaterial)))
&&(CMLib.flags().canBeSeenBy(item,killerM)))
CMLib.commands().postGet(killerM,body,item,false);
}
if(body.isDestroyedAfterLooting())
bodyRoom.recoverRoomStats();
}
Coins C=null;
if((deadMoney>0)
&&(myAmountOfDeadMoney>0)
&&(body!=null)
&&(bodyRoom!=null))
{
for(int g=0;g<goldLooters.size();g++)
{
C=CMLib.beanCounter().makeBestCurrency(currency,myAmountOfDeadMoney,null,body);
if(C!=null)
{
C.recoverPhyStats();
bodyRoom.addItem(C,ItemPossessor.Expire.Monster_EQ);
bodyRoom.recoverRoomStats();
final MOB mob=goldLooters.get(g);
if(mob.location()==bodyRoom)
{
if((mob.riding()!=null)&&(mob.riding() instanceof MOB))
mob.tell(L("You'll need to dismount to get @x1 off the body.",C.name()));
else
if((mob.riding()!=null)&&(mob.riding() instanceof Item))
mob.tell(L("You'll need to disembark to get @x1 off the body.",C.name()));
else
if(CMLib.flags().canBeSeenBy(body,mob))
CMLib.commands().postGet(mob,body,C,false);
}
}
}
}
if((killerM != null)&&(killerM.getVictim()==deadM))
killerM.setVictim(null);
deadM.setVictim(null);
if((body!=null)&&(bodyRoom!=null)&&(body.isDestroyedAfterLooting()))
{
for(int i=bodyRoom.numItems()-1;i>=0;i--)
{
final Item item=bodyRoom.getItem(i);
if((item!=null)
&&(item.container()==body))
item.setContainer(null);
}
body.destroy();
bodyRoom.recoverPhyStats();
return null;
}
return body;
}
return null;
}
@Override
public String standardHitWord(int type, final double pct)
{
if((type<0)||(type>=Weapon.TYPE_DESCS.length))
type=Weapon.TYPE_BURSTING;
final int[] thresholds=CMProps.getListFileIntList(CMProps.ListFile.DAMAGE_WORDS_THRESHOLDS);
int damnCode=(int)Math.round(pct * (thresholds.length-1));
if(damnCode > thresholds.length-1)
damnCode = thresholds.length-1;
return getStandardHitWordInternal(type, damnCode);
}
protected String getStandardHitWordInternal(final int type, int damnCode)
{
damnCode++; // always add 1 because index into hitwords is type=0, annoy=1;
final Object[][][] hitWords = CMProps.getListFileGrid(CMProps.ListFile.DAMAGE_WORDS);
if(hitWords != hitWordsChanged)
{
hitWordsChanged=hitWords;
hitWordIndex=null;
}
if(hitWordIndex==null)
{
final Object[][][] newWordIndex=new Object[Weapon.TYPE_DESCS.length][][];
for(int w=0;w<Weapon.TYPE_DESCS.length;w++)
{
Object[][] ALL=null;
Object[][] MINE=null;
for (final Object[][] hitWord : hitWords)
{
if(((String)hitWord[0][0]).equalsIgnoreCase("ALL"))
ALL=hitWord;
else
if(((String)hitWord[0][0]).equalsIgnoreCase(Weapon.TYPE_DESCS[w]))
{
MINE = hitWord;
break;
}
}
if(MINE!=null)
newWordIndex[w]=MINE;
else
newWordIndex[w]=ALL;
}
hitWordIndex=newWordIndex;
}
final Object[][] HIT_WORDS=hitWordIndex[type];
if(damnCode<1)
damnCode=1;
if(damnCode>=HIT_WORDS.length)
damnCode=HIT_WORDS.length-1;
return (String)CMLib.dice().pick(HIT_WORDS[damnCode]);
}
@Override
public String standardHitWord(int type, final int damage)
{
if((type<0)||(type>=Weapon.TYPE_DESCS.length))
type=Weapon.TYPE_BURSTING;
final int[] thresholds=CMProps.getListFileIntList(CMProps.ListFile.DAMAGE_WORDS_THRESHOLDS);
int damnCode=thresholds.length-2;
for(int i=0;i<thresholds.length;i++)
{
if (damage <= thresholds[i])
{
damnCode = i;
break;
}
}
return getStandardHitWordInternal(type, damnCode);
}
protected String getExtremeValue(final int extreme)
{
final StringBuilder str=new StringBuilder("");
for(final char c : CMath.convertToRoman(extreme).toCharArray())
{
switch(c)
{
case 'I':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,0)).append(" ");
break;
case 'V':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,1)).append(" ");
break;
case 'X':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,2)).append(" ");
break;
case 'L':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,3)).append(" ");
break;
case 'C':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,4)).append(" ");
break;
case 'D':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,5)).append(" ");
break;
case 'M':
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,6)).append(" ");
break;
}
}
return str.toString();
}
@Override
public String armorStr(final MOB mob)
{
final int prowessCode = CMProps.getIntVar(CMProps.Int.COMBATPROWESS);
if(CMProps.Int.Prowesses.NONE.is(prowessCode))
return "";
final int armor = -adjustedArmor(mob);
final StringBuilder str=new StringBuilder("");
if(CMProps.Int.Prowesses.ARMOR_ADJ.is(prowessCode)||CMProps.Int.Prowesses.ARMOR_ADV.is(prowessCode))
{
//System.out.println("Player: "+armor+", Mob="+(-adjustedArmor(CMLib.leveler().getLevelMOBArmor(mob))));
final int normalizedArmor = (int)Math.round(Math.ceil(CMath.div(armor + adjustedArmor(CMLib.leveler().getLevelMOBArmor(mob)),5.0)));
final int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.ARMOR_ADJS);
final int medianValue = normalizedMax / 2;
int adjIndex = normalizedArmor + medianValue;
int extreme = 0;
if(adjIndex < 0)
{
extreme = -adjIndex;
adjIndex = 0;
}
if(adjIndex >= normalizedMax)
{
extreme = normalizedMax-adjIndex;
adjIndex = normalizedMax-1;
}
if((extreme != 0)&&(CMProps.Int.Prowesses.ARMOR_ADV.is(prowessCode)))
str.append(this.getExtremeValue(extreme));
if(CMProps.Int.Prowesses.ARMOR_ADJ.is(prowessCode))
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_ADJS,adjIndex)).append(" ");
}
if(CMProps.Int.Prowesses.ARMOR_ABSOLUTE.is(prowessCode))
{
final int ARMOR_CEILING=CMProps.getListFileFirstInt(CMProps.ListFile.ARMOR_DESCS_CEILING);
final int numArmorDescs = CMProps.getListFileIndexedListSize(CMProps.ListFile.ARMOR_DESCS);
str.append((armor<0)?CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_DESCS,0):(
(armor>=ARMOR_CEILING)?
CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_DESCS,numArmorDescs-1)
+(CMStrings.repeatWithLimit('!',((armor-ARMOR_CEILING)/100),10))
:
(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_DESCS,(int)Math.round(Math.floor(CMath.mul(CMath.div(armor,ARMOR_CEILING),numArmorDescs)))))));
str.append(" ");
}
if(CMProps.Int.Prowesses.ARMOR_NUMBER.is(prowessCode))
str.append("^.("+armor+")");
return str.toString().trim()+"^.";
}
@Override
public String fightingProwessStr(final MOB mob)
{
final int prowessCode = CMProps.getIntVar(CMProps.Int.COMBATPROWESS);
if(CMProps.Int.Prowesses.NONE.is(prowessCode))
return "";
final int attackProwess = adjustedAttackBonus(mob,null) - ATTACK_ADJUSTMENT;
final StringBuilder str=new StringBuilder("");
if(CMProps.Int.Prowesses.COMBAT_ADJ.is(prowessCode)||CMProps.Int.Prowesses.COMBAT_ADV.is(prowessCode))
{
//System.out.println("Player: "+attackProwess+", Mob="+(adjustedAttackBonus(CMLib.leveler().getLevelAttack(mob))- ATTACK_ADJUSTMENT));
final int normalizedAttack = (int)Math.round(Math.ceil(CMath.div(attackProwess - (adjustedAttackBonus(CMLib.leveler().getLevelAttack(mob))- ATTACK_ADJUSTMENT),12.0)));
final int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.COMBAT_ADJS);
final int medianValue = normalizedMax / 2;
int adjIndex = normalizedAttack + medianValue;
int extreme = 0;
if(adjIndex < 0)
{
extreme = -adjIndex;
adjIndex = 0;
}
if(adjIndex >= normalizedMax)
{
extreme = normalizedMax-adjIndex;
adjIndex = normalizedMax-1;
}
if((extreme != 0)&&(CMProps.Int.Prowesses.COMBAT_ADV.is(prowessCode)))
str.append(this.getExtremeValue(extreme));
if(CMProps.Int.Prowesses.COMBAT_ADJ.is(prowessCode))
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.COMBAT_ADJS,adjIndex)).append(" ");
}
if(CMProps.Int.Prowesses.COMBAT_ABSOLUTE.is(prowessCode))
{
final int PROWESS_CEILING=CMProps.getListFileFirstInt(CMProps.ListFile.PROWESS_DESCS_CEILING);
final int numProwessDescs = CMProps.getListFileIndexedListSize(CMProps.ListFile.PROWESS_DESCS);
str.append((attackProwess<0)?CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.PROWESS_DESCS,0):(
(attackProwess>=PROWESS_CEILING)
?
CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.PROWESS_DESCS,numProwessDescs-1)
+(CMStrings.repeatWithLimit('!',((attackProwess-PROWESS_CEILING)/100),10))
:
(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.PROWESS_DESCS,(int)Math.round(Math.floor(CMath.mul(CMath.div(attackProwess,PROWESS_CEILING),numProwessDescs)))))));
}
if(CMProps.Int.Prowesses.COMBAT_NOUN.is(prowessCode))
{
final int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.COMBAT_NOUNS);
final double divisor = CMath.div(CMProps.get(mob.session()).getInt(CMProps.Int.LASTPLAYERLEVEL),normalizedMax);
int nounIndex = (int)Math.round(CMath.div(mob.phyStats().level()-1, divisor));
if(nounIndex >= normalizedMax)
nounIndex = normalizedMax-1;
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.COMBAT_NOUNS,nounIndex)).append(" ");
}
if(CMProps.Int.Prowesses.COMBAT_NUMBER.is(prowessCode))
str.append("^.("+attackProwess+")");
return str.toString().trim()+"^.";
}
@Override
public String damageProwessStr(final MOB mob)
{
final int prowessCode = CMProps.getIntVar(CMProps.Int.COMBATPROWESS);
if(CMProps.Int.Prowesses.NONE.is(prowessCode))
return "";
final int damageProwess = this.adjustedDamage(mob, (Weapon)mob.fetchWieldedItem(), null, 0, false,true);
final StringBuilder str=new StringBuilder("");
if(CMProps.Int.Prowesses.DAMAGE_ADJ.is(prowessCode)||CMProps.Int.Prowesses.DAMAGE_ADV.is(prowessCode))
{
//System.out.println("Player: "+damageProwess+", Mob="+(adjustedAttackBonus(CMLib.leveler().getLevelMOBDamage(mob))- ATTACK_ADJUSTMENT));
final int normalizedDamage = (int)Math.round(Math.ceil(CMath.div(damageProwess - (adjustedDamage(CMLib.leveler().getLevelMOBDamage(mob),mob.phyStats().level(),true)),3.0)));
final int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.DAMAGE_ADJS);
final int medianValue = normalizedMax / 2;
int adjIndex = normalizedDamage + medianValue;
int extreme = 0;
if(adjIndex < 0)
{
extreme = -adjIndex;
adjIndex = 0;
}
if(adjIndex >= normalizedMax)
{
extreme = normalizedMax-adjIndex;
adjIndex = normalizedMax-1;
}
if((extreme != 0)&&(CMProps.Int.Prowesses.DAMAGE_ADV.is(prowessCode)))
str.append(this.getExtremeValue(extreme));
if(CMProps.Int.Prowesses.DAMAGE_ADJ.is(prowessCode))
str.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_ADJS,adjIndex)).append(" ");
}
if(CMProps.Int.Prowesses.DAMAGE_ABSOLUTE.is(prowessCode))
{
final int DAMAGE_CEILING=CMProps.getListFileFirstInt(CMProps.ListFile.DAMAGE_DESCS_CEILING);
final int numProwessDescs = CMProps.getListFileIndexedListSize(CMProps.ListFile.DAMAGE_DESCS);
str.append((damageProwess<0)?CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_DESCS,0):(
(damageProwess>=DAMAGE_CEILING)
?
CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_DESCS,numProwessDescs-1)
+(CMStrings.repeatWithLimit('!',((damageProwess-DAMAGE_CEILING)/100),10))
:
(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_DESCS,(int)Math.round(Math.floor(CMath.mul(CMath.div(damageProwess,DAMAGE_CEILING),numProwessDescs)))))));
}
if(CMProps.Int.Prowesses.DAMAGE_NUMBER.is(prowessCode))
str.append("^.("+damageProwess+")");
return str.toString().trim()+"^.";
}
protected int getWeaponAttackIndex(final int weaponDamageType, final int weaponClassification)
{
switch(weaponClassification)
{
case Weapon.CLASS_RANGED:
return (weaponDamageType == Weapon.TYPE_LASERING) ? 5 : 0;
case Weapon.CLASS_THROWN:
return (weaponDamageType == Weapon.TYPE_LASERING) ? 5 : 1;
default:
switch(weaponDamageType)
{
case Weapon.TYPE_SLASHING:
case Weapon.TYPE_BASHING:
return 2;
case Weapon.TYPE_PIERCING:
return 4;
case Weapon.TYPE_SHOOT:
return 0;
case Weapon.TYPE_LASERING:
return 5;
default:
return 3;
}
}
}
@Override
public String standardMissString(final int weaponDamageType, final int weaponClassification, final String weaponName, final boolean useExtendedMissString)
{
final int listIndex = getWeaponAttackIndex(weaponDamageType, weaponClassification);
if(!useExtendedMissString)
return CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.MISS_DESCS,listIndex);
return CMStrings.replaceAll(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.WEAPON_MISS_DESCS,listIndex),"<TOOLNAME>",weaponName)+CMLib.protocol().msp("missed.wav",20);
}
@Override
public String standardHitString(final int weaponDamageType, final int weaponClass, final int damageAmount, final String weaponName)
{
final int listIndex;
if((weaponName==null)||(weaponName.length()==0))
listIndex = getWeaponAttackIndex(weaponDamageType, Weapon.CLASS_NATURAL);
else
listIndex = getWeaponAttackIndex(weaponDamageType, weaponClass);
final StringBuilder str=new StringBuilder(CMStrings.replaceAll(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.WEAPON_HIT_DESCS,listIndex),"<TOOLNAME>",weaponName));
switch(weaponClass)
{
case Weapon.CLASS_RANGED:
return str.append(CMLib.protocol().msp("arrow.wav",20)).toString();
case Weapon.CLASS_THROWN:
return str.append(CMLib.protocol().msp("arrow.wav",20)).toString();
default:
return str.append(CMLib.protocol().msp("punch"+CMLib.dice().roll(1,7,0)+".wav",20)).toString();
}
}
@Override
public String standardMobCondition(final MOB viewer,final MOB mob)
{
int pct=(int)Math.round(Math.floor((CMath.div(mob.curState().getHitPoints(),mob.maxState().getHitPoints()))*10));
if(pct<0)
pct=0;
final int numHealthDescs=CMProps.getListFileIndexedListSize(CMProps.ListFile.HEALTH_CHART);
if(pct>=numHealthDescs)
pct=numHealthDescs-1;
return CMStrings.replaceAll(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.HEALTH_CHART,pct),"<MOB>",mob.name(viewer));
}
@Override
public void resistanceMsgs(MOB source, final MOB target, final CMMsg msg)
{
if((msg.value()>0)||(msg.sourceMinor()==CMMsg.TYP_TEACH))
return;
if(target.amDead())
return;
String tool=null;
String endPart=" from <T-NAME>.";
if(source==target)
{
source=null;
endPart=".";
}
if(msg.tool() instanceof Trap)
endPart=".";
else
if(msg.tool() instanceof Ability)
tool=((Ability)msg.tool()).name();
String tackOn=null;
switch(msg.targetMinor())
{
case CMMsg.TYP_MIND:
if(tool == null)
tackOn = L("<S-NAME> shake(s) off the mental attack" + endPart);
else
tackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);
break;
case CMMsg.TYP_GAS:
if(tool == null)
tackOn = L("<S-NAME> resist(s) the noxious fumes" + endPart);
else
tackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);
break;
case CMMsg.TYP_COLD:
if(tool == null)
tackOn = L("<S-NAME> shake(s) off the cold blast" + endPart);
else
tackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);
break;
case CMMsg.TYP_ELECTRIC:
if(tool == null)
tackOn = L("<S-NAME> shake(s) off the electrical attack" + endPart);
else
tackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);
break;
case CMMsg.TYP_FIRE:
if(tool == null)
tackOn = L("<S-NAME> resist(s) the blast of heat" + endPart);
else
tackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);
break;
case CMMsg.TYP_WATER:
if(tool == null)
tackOn = L("<S-NAME> dodge(s) the wet blast" + endPart);
else
tackOn = L("<S-NAME> dodge(s) the @x1" + endPart,tool);
break;
case CMMsg.TYP_UNDEAD:
if(tool == null)
tackOn = L("<S-NAME> shake(s) off the evil attack" + endPart);
else
tackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);
break;
case CMMsg.TYP_POISON:
if(tool == null)
tackOn = L("<S-NAME> shake(s) off the poison" + endPart);
else
tackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);
break;
case CMMsg.TYP_DISEASE:
if(tool == null)
tackOn = L("<S-NAME> resist(s) the disease.");
else
tackOn = L("<S-NAME> resist(s) the @x1.",tool);
break;
case CMMsg.TYP_JUSTICE:
break;
case CMMsg.TYP_CAST_SPELL:
if(tool == null)
tackOn = L("<S-NAME> resist(s) the magical attack" + endPart);
else
tackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);
break;
case CMMsg.TYP_PARALYZE:
if(tool == null)
tackOn = L("<S-NAME> resist(s) the paralysis" + endPart);
else
tackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);
break;
case CMMsg.TYP_SONIC:
if(tool == null)
tackOn = L("<S-NAME> shake(s) off the sonic" + endPart);
else
tackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);
break;
case CMMsg.TYP_LASER:
if(tool == null)
tackOn = L("<S-NAME> dodge(s) the laser" + endPart);
else
tackOn = L("<S-NAME> dodge(s) the @x1" + endPart,tool);
break;
}
if(tackOn!=null)
{
final String newStr=target+"/"+source+"/"+tool;
if(!newStr.equals(lastStr)||((System.currentTimeMillis()-lastRes)>250))
msg.addTrailerMsg(CMClass.getMsg(target,source,CMMsg.MSG_OK_ACTION,tackOn));
lastStr=newStr;
lastRes=System.currentTimeMillis();
}
msg.setValue(msg.value()+1);
}
@Override
public boolean checkDamageSaves(final MOB mob, final CMMsg msg)
{
int chanceToFail = 0;
if(msg.tool() instanceof Weapon)
{
int charStatCode = -1;
switch(((Weapon)msg.tool()).weaponDamageType())
{
case Weapon.TYPE_BASHING:
charStatCode=CharStats.STAT_SAVE_BLUNT;
break;
case Weapon.TYPE_PIERCING:
charStatCode=CharStats.STAT_SAVE_PIERCE;
break;
case Weapon.TYPE_SLASHING:
charStatCode=CharStats.STAT_SAVE_SLASH;
break;
default:
return true;
}
chanceToFail = mob.charStats().getSave(charStatCode);
}
else
if((msg.tool() instanceof Ability)
&&(msg.sourceMinor()!=CMMsg.TYP_TEACH))
{
switch(((Ability)msg.tool()).classificationCode() & Ability.ALL_ACODES)
{
case Ability.ACODE_CHANT:
chanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_CHANTS)
+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);
break;
case Ability.ACODE_PRAYER:
chanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_PRAYERS)
+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);
break;
case Ability.ACODE_SPELL:
chanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_SPELLS)
+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);
break;
case Ability.ACODE_SONG:
chanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_SONGS)
+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);
break;
}
final int charStatCode = CharStats.CODES.RVSCMMSGMAP(msg.sourceMinor());
if(charStatCode >= 0)
chanceToFail += mob.charStats().getSave(charStatCode);
}
if ((chanceToFail != 0) && (chanceToFail > (Integer.MIN_VALUE/2)))
{
if (chanceToFail < -100)
chanceToFail = -100;
else
if (chanceToFail > 100)
chanceToFail = 100;
if (CMLib.dice().rollPercentage() < ((chanceToFail < 0) ? (-chanceToFail) : chanceToFail))
msg.setValue((int)Math.round(CMath.mul(msg.value(),CMath.div(100-chanceToFail,100))));
}
return true;
}
@Override
public boolean checkSavingThrows(final MOB mob, final CMMsg msg)
{
if ((msg.targetMinor() != CMMsg.TYP_WEAPONATTACK) && (msg.value() <= 0))
{
int charStatCode = -1;
int chanceToFail = 0;
if(msg.tool() instanceof Ability)
{
switch(((Ability)msg.tool()).classificationCode() & Ability.ALL_ACODES)
{
case Ability.ACODE_CHANT:
charStatCode=CharStats.STAT_SAVE_CHANTS;
break;
case Ability.ACODE_PRAYER:
charStatCode=CharStats.STAT_SAVE_PRAYERS;
break;
case Ability.ACODE_SPELL:
charStatCode=CharStats.STAT_SAVE_SPELLS;
break;
case Ability.ACODE_SONG:
charStatCode=CharStats.STAT_SAVE_SONGS;
break;
}
if((charStatCode > 0)
&&(msg.targetMinor()==CMMsg.TYP_CAST_SPELL))
chanceToFail = mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);
}
if(charStatCode<0)
charStatCode = CharStats.CODES.RVSCMMSGMAP(msg.targetMinor());
if(charStatCode >= 0)
{
chanceToFail += mob.charStats().getSave(charStatCode);
if (chanceToFail > (Integer.MIN_VALUE/2))
{
final int diff = (mob.phyStats().level() - msg.source().phyStats().level());
final int diffSign = diff < 0 ? -1 : 1;
chanceToFail += (diffSign * (diff * diff));
if (chanceToFail < 5)
chanceToFail = 5;
else
if (chanceToFail > 95)
chanceToFail = 95;
if (CMLib.dice().rollPercentage() < chanceToFail)
CMLib.combat().resistanceMsgs(msg.source(), mob, msg); // also applies the +1 to msg.value()
}
}
}
return true;
}
@Override
public void handleBeingGravitied(final MOB mob, final CMMsg msg)
{
if(msg.value() >= SpaceObject.ACCELERATION_PASSOUT)
{
final Room R=mob.location();
if(R==null)
return;
if(msg.value() >= SpaceObject.ACCELERATION_INSTANTDEATH)
{
R.show(mob,null,null,CMMsg.MSG_OK_ACTION, L("<S-NAME> is crushed to jelly by acceleration!"));
postDeath(null, mob, msg);
}
else
if(msg.value() >= SpaceObject.ACCELERATION_UNCONSCIOUSNESS)
{
Ability A=mob.fetchEffect("Fighter_Whomp");
if(A==null)
A=CMClass.getAbility("Fighter_Whomp");
A.startTickDown(msg.source(), mob, 30);
mob.recoverPhyStats();
if(CMLib.flags().isSleeping(mob))
R.show(mob,null,null,CMMsg.MSG_OK_ACTION, L("<S-NAME> fall(s) unconscious from extreme acceleration."));
}
else
if(msg.value() >= SpaceObject.ACCELERATION_PASSOUT)
{
Ability A=mob.fetchEffect("Fighter_Whomp");
if(A==null)
{
A=CMClass.getAbility("Fighter_Whomp");
A.startTickDown(msg.source(), mob, 3);
mob.recoverPhyStats();
if(CMLib.flags().isSleeping(mob))
R.show(mob,null,null,CMMsg.MSG_OK_ACTION, L("<S-NAME> pass(es) out from high acceleration."));
}
}
}
}
@Override
public void handleBeingHealed(final CMMsg msg)
{
if(!(msg.target() instanceof MOB))
return;
final MOB target=(MOB)msg.target();
final int amt=msg.value();
if(amt>0)
target.curState().adjHitPoints(amt,target.maxState());
}
protected boolean bleedableWeapon(final Environmental E)
{
if(E==null)
return false;
if(E instanceof Weapon)
{
return true;
}
else
if(E instanceof Ability)
{
final int code=((Ability)E).classificationCode()&Ability.ALL_ACODES;
switch(code)
{
case Ability.ACODE_DISEASE:
case Ability.ACODE_POISON:
return false;
}
return true;
}
else
return true;
}
@Override
public void handleBeingDamaged(final CMMsg msg)
{
if(!(msg.target() instanceof MOB))
return;
final MOB attacker=msg.source();
final MOB target=(MOB)msg.target();
if(target.amDead()) // already dead, don't take more damage.
return;
final int dmg=msg.value();
if(Log.combatChannelOn())
{
final Item DI=target.fetchWieldedItem();
final Item KI=attacker.fetchWieldedItem();
final String tool=(msg.tool()==null)?"null":msg.tool().name();
final String type=(msg.sourceMinor()==CMMsg.NO_EFFECT)?"??":CMMsg.TYPE_DESCS[msg.sourceMinor()];
Log.combatOut("DAMG",attacker.Name()+":"+attacker.phyStats().getCombatStats()+":"+attacker.curState().getCombatStats()+":"+((KI==null)?"null":KI.name())+":"+target.Name()+":"+target.phyStats().getCombatStats()+":"+target.curState().getCombatStats()+":"+((DI==null)?"null":DI.name())+":"+tool+":"+type+":"+dmg);
}
synchronized(("DMG"+target.Name().toUpperCase()).intern())
{
if((dmg>0)
&&(target.curState().getHitPoints()>0))
{
if((attacker!=null)
&&(attacker.playerStats()!=null)
&&(attacker!=target))
attacker.playerStats().bumpLevelCombatStat(PlayerCombatStat.DAMAGE_DONE, attacker.basePhyStats().level(), dmg);
if(target.playerStats()!=null)
target.playerStats().bumpLevelCombatStat(PlayerCombatStat.DAMAGE_TAKEN, target.basePhyStats().level(), dmg);
if((!target.curState().adjHitPoints(-dmg,target.maxState()))
&&(target.curState().getHitPoints()<1)
&&(target.location()!=null))
postDeath(attacker,target,msg);
else
{
if((Math.round(CMath.div(dmg,target.maxState().getHitPoints())*100.0)>=CMProps.getIntVar(CMProps.Int.INJBLEEDPCTHP))
&&bleedableWeapon(msg.tool()))
{
final Ability A2=CMClass.getAbility("Bleeding");
if(A2!=null)
A2.invoke((target),(target),true,0);
}
if((target.curState().getHitPoints()<target.getWimpHitPoint())
&&(target.getWimpHitPoint()>0)
&&(target.isInCombat()))
postPanic(target,msg);
else
if((CMProps.getIntVar(CMProps.Int.INJPCTHP)>=(int)Math.round(CMath.div(target.curState().getHitPoints(),target.maxState().getHitPoints())*100.0))
&&(!CMLib.flags().isGolem(target))
&&(target.fetchEffect("Injury")==null))
{
Ability A=CMClass.getAbility("Injury");
if(A!=null)
{
A.startTickDown(target,target,Ability.TICKS_ALMOST_FOREVER);
A=target.fetchEffect(A.ID());
if(A!=null)
A.okMessage(target,msg);
}
}
}
}
}
}
@Override
public void handleDeath(final CMMsg msg)
{
final MOB deadmob=msg.source();
if(!deadmob.amDead())
{
if((!deadmob.isMonster())&&(deadmob.soulMate()==null))
{
if(deadmob.playerStats()!=null)
deadmob.playerStats().bumpLevelCombatStat(PlayerCombatStat.DEATHS_TAKEN, deadmob.basePhyStats().level(), 1);
CMLib.coffeeTables().bump(deadmob,CoffeeTableRow.STAT_DEATHS);
final PlayerStats playerStats=deadmob.playerStats();
if(playerStats!=null)
playerStats.setHygiene(0);
final List<String> channels=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.DETAILEDDEATHS, deadmob);
final List<String> channels2=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.DEATHS, deadmob);
if(!CMLib.flags().isCloaked(deadmob))
{
for(int i=0;i<channels.size();i++)
{
if((msg.tool() instanceof MOB))
CMLib.commands().postChannel(channels.get(i),deadmob.clans(),L("@x1 was just killed in @x2 by @x3.",deadmob.Name(),CMLib.map().getExtendedRoomID(deadmob.location()),msg.tool().Name()),true);
else
CMLib.commands().postChannel(channels.get(i),deadmob.clans(),L("@x1 has just died at @x2",deadmob.Name(),CMLib.map().getExtendedRoomID(deadmob.location())),true);
}
}
if(!CMLib.flags().isCloaked(deadmob))
{
for(int i=0;i<channels2.size();i++)
if((msg.tool() instanceof MOB))
CMLib.commands().postChannel(channels2.get(i),deadmob.clans(),L("@x1 was just killed.",deadmob.Name()),true);
}
}
if(msg.tool() instanceof MOB)
{
final MOB killer=(MOB)msg.tool();
doDeathPostProcessing(msg);
int count=10;
MOB killerM=killer;
while((killerM != null)
&&(--count>0))
{
if(killerM.playerStats()!=null)
killerM.playerStats().bumpLevelCombatStat(PlayerCombatStat.DEATHS_DONE, killerM.basePhyStats().level(), 1);
killerM=killerM.amFollowing();
}
justDie(killer,deadmob);
}
else
justDie(null,deadmob);
deadmob.tell(deadmob,msg.target(),msg.tool(),msg.sourceMessage());
if(deadmob.riding()!=null)
deadmob.riding().delRider(deadmob);
if(CMLib.flags().isCataloged(deadmob))
CMLib.catalog().bumpDeathPickup(deadmob);
}
}
@Override
public boolean handleDamageSpam(final MOB observerM, final Physical target, final int amount)
{
if((observerM!=null)
&&(observerM.playerStats()!=null)
&&(target!=null)
&&(amount>0))
{
final Map<String,int[]> spam=observerM.playerStats().getCombatSpams();
synchronized(spam)
{
final String targetName = CMLib.flags().canBeSeenBy(target, observerM)?target.Name():L("Someone");
if(!spam.containsKey(targetName))
spam.put(targetName,new int[]{0});
spam.get(targetName)[0]+=amount;
}
return true;
}
return false;
}
@Override
public void doDeathPostProcessing(final CMMsg msg)
{
final MOB deadmob=msg.source();
if(msg.tool() instanceof MOB)
{
final MOB killer=(MOB)msg.tool();
if(Log.killsChannelOn())
{
final Item KI=killer.fetchWieldedItem();
final Item DI=deadmob.fetchWieldedItem();
final String room=CMLib.map().getExtendedRoomID((killer.location()!=null)?killer.location():deadmob.location());
Log.killsOut("KILL",room+":"+killer.Name()+":"+killer.phyStats().getCombatStats()+":"+killer.curState().getCombatStats()+":"+((KI==null)?"null":KI.name())+":"+deadmob.Name()+":"+deadmob.phyStats().getCombatStats()+":"+deadmob.curState().getCombatStats()+":"+((DI==null)?"null":DI.name()));
}
if(Log.combatChannelOn())
{
final Item DI=deadmob.fetchWieldedItem();
final Item KI=killer.fetchWieldedItem();
Log.combatOut("KILL",killer.Name()+":"+killer.phyStats().getCombatStats()+":"+killer.curState().getCombatStats()+":"+((KI==null)?"null":KI.name())+":"+deadmob.Name()+":"+deadmob.phyStats().getCombatStats()+":"+deadmob.curState().getCombatStats()+":"+((DI==null)?"null":DI.name()));
}
CMLib.achievements().possiblyBumpAchievement(killer, AchievementLibrary.Event.KILLS, 1, deadmob);
if((deadmob!=null)
&&(killer!=null)
&&(killer!=deadmob)
&&(deadmob.soulMate()==null)
&&(!killer.isMonster()))
{
if((!deadmob.isMonster())
&&((killer.isPlayer())
&&(deadmob.isPlayer()))
&&(!CMLib.players().isSameAccount(killer, deadmob)))
{
CMLib.coffeeTables().bump(deadmob,CoffeeTableRow.STAT_PKDEATHS);
if(killer.playerStats()!=null)
CMLib.get(killer.session())._players().bumpPrideStat(killer,PrideStat.PVPKILLS, 1);
}
if((killer.session()!=null)
&&(deadmob.clans().iterator().hasNext())
&&((deadmob.session()==null)
||(!deadmob.session().getAddress().equalsIgnoreCase(killer.session().getAddress())))
&&((deadmob.playerStats()==null)
||(killer.playerStats()==null)
||(deadmob.playerStats().getAccount()==null)
||(killer.playerStats().getAccount()==null)
||(deadmob.playerStats().getAccount()!=killer.playerStats().getAccount())))
{
final List<Pair<Clan,Integer>> list = CMLib.clans().findRivalrousClans(killer, deadmob);
if(list.size()>0)
{
for(final Pair<Clan,Integer> c : list)
{
c.first.recordClanKill(killer,deadmob);
if(killer.getClanRole(c.first.clanID()) != null)
CMLib.achievements().possiblyBumpAchievement(killer, AchievementLibrary.Event.CLANKILLS, 1, c.first, deadmob);
}
}
}
}
}
}
protected void pickNextVictim(final MOB observer, final MOB deadmob, final Set<MOB> deadGroupH)
{
final Room R=observer.location();
if(R!=null)
{
MOB newTargetM=null;
final Set<MOB> myGroupH=observer.getGroupMembers(new HashSet<MOB>());
for(int r=0;r<R.numInhabitants();r++)
{
final MOB M=R.fetchInhabitant(r);
if((M!=observer)
&&(M!=deadmob)
&&(M!=null)
&&(M.getVictim()==observer)
&&(!M.amDead())
&&(CMLib.flags().isInTheGame(M,true))
&&(observer.mayIFight(M)))
{
newTargetM=M;
break;
}
}
if(newTargetM==null)
for(int r=0;r<R.numInhabitants();r++)
{
final MOB M=R.fetchInhabitant(r);
if(M==null)
continue;
final MOB vic=M.getVictim();
if((M!=observer)
&&(M!=deadmob)
&&(deadGroupH.contains(M)
||((vic!=null)&&(myGroupH.contains(vic))))
&&(!M.amDead())
&&(CMLib.flags().isInTheGame(M,true))
&&(observer.mayIFight(M)))
{
newTargetM=M;
break;
}
}
observer.setVictim(null);
if(newTargetM!=null)
observer.setVictim(newTargetM);
}
}
@Override
public void handleObserveDeath(final MOB observer, final MOB fighting, final CMMsg msg)
{
// no longer does a damn thing
}
@Override
public void handleDamageSpamSummary(final MOB mob)
{
if((mob.isAttributeSet(MOB.Attrib.NOBATTLESPAM))
&&(mob.playerStats()!=null))
{
int numEnemies=0;
final Room R=mob.location();
for(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)
{
final MOB M=m.nextElement();
if((M!=null)&&(M!=mob)&&(M.getVictim()==mob))
numEnemies++;
}
final Map<String,int[]> combatSpam = mob.playerStats().getCombatSpams();
final StringBuilder msg=new StringBuilder("");
synchronized(combatSpam)
{
if(numEnemies>1)
msg.append("^<FIGHT^>"+L("Fighting @x1 enemies. ",""+numEnemies)+"^</FIGHT^>");
if(combatSpam.size()==0)
msg.append("^<FIGHT^>"+L("No new combat damage reported.")+"^</FIGHT^>");
else
{
msg.append("^<FIGHT^>"+L("New combat damage: "));
for(final String str : combatSpam.keySet())
{
msg.append(str).append(" ").append(combatSpam.get(str)[0]).append(" points. ");
}
msg.append("^</FIGHT^>");
}
mob.tell(msg.toString());
combatSpam.clear();
}
}
}
@Override
public void handleBeingAssaulted(final CMMsg msg)
{
if(!(msg.target() instanceof MOB))
return;
final MOB attacker=msg.source();
final MOB target=(MOB)msg.target();
if((!target.isInCombat())
&&(target.location()!=null)
&&(target.location().isInhabitant(attacker))
&&((!CMath.bset(msg.sourceMajor(),CMMsg.MASK_ALWAYS))
||(!(msg.tool() instanceof DiseaseAffect))))
{
establishRange(target,attacker,msg.tool());
target.setVictim(attacker);
}
if(target.isInCombat())
{
if((attacker.session()!=null)
&&(!attacker.isPossessing()))
{
if(!target.isMonster())
attacker.session().setLastPKFight();
else
attacker.session().setLastNPCFight();
}
if(msg.targetMinor()==CMMsg.TYP_WEAPONATTACK)
{
Item weapon=attacker.getNaturalWeapon();
if((msg.tool() instanceof Item))
weapon=(Item)msg.tool();
if(weapon!=null)
{
final boolean isHit=rollToHit(attacker,target);
postWeaponAttackResult(attacker,target,weapon,isHit);
if(isHit)
msg.setValue(1);
}
if((target.soulMate()==null)
&&(target.playerStats()!=null)
&&(target.location()!=null)
&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.HYGIENE)))
target.playerStats().adjHygiene(PlayerStats.HYGIENE_FIGHTDIRTY);
if((attacker.isMonster())&&(!attacker.isInCombat()))
attacker.setVictim(target);
}
else
if(msg.tool() instanceof Item)
postWeaponAttackResult(attacker,target,(Item)msg.tool(),true);
}
if(CMLib.flags().isSitting(target)||CMLib.flags().isSleeping(target))
CMLib.commands().postStand(target,true, false);
}
@Override
public void makeFollowersFight(final MOB observerM, final MOB defenderM, final MOB attackerM)
{
if((attackerM==null)||(defenderM==null)||observerM==null)
return;
if(attackerM==defenderM)
return;
if((defenderM==observerM)||(attackerM==observerM))
return;
if((defenderM.location()!=observerM.location())
||(defenderM.location()!=attackerM.location()))
return;
if((observerM.isAttributeSet(MOB.Attrib.AUTOASSIST))
||(CMath.bset(observerM.phyStats().disposition(), PhyStats.IS_UNHELPFUL)))
return;
if(observerM.isInCombat())
return;
final MOB observerFollows=observerM.amFollowing();
final MOB targetFollows=defenderM.amFollowing();
final MOB sourceFollows=attackerM.amFollowing();
if((observerFollows==defenderM)
||(targetFollows==observerM)
||((targetFollows!=null)&&(targetFollows==observerFollows)))
{
observerM.setVictim(attackerM);
establishRange(observerM,attackerM,observerM.fetchWieldedItem());
}
else
if((observerFollows==attackerM)
||(sourceFollows==observerM)
||((sourceFollows!=null)&&(sourceFollows==observerFollows)))
{
observerM.setVictim(defenderM);
establishRange(observerM,defenderM,observerM.fetchWieldedItem());
}
}
public List<MOB> getAllInProximity(final MOB to, final int distance)
{
final Room R=to.location();
final Vector<MOB> V=new Vector<MOB>();
V.addElement(to);
if(R==null)
return V;
final List<MOB> everyV=new ArrayList<MOB>();
for(int i=0;i<R.numInhabitants();i++)
everyV.add(R.fetchInhabitant(i));
if(!everyV.contains(to))
everyV.add(to);
final int[][] map=new int[everyV.size()][everyV.size()];
for(int x=0;x<map.length;x++)
{
for(int y=0;y<map.length;y++)
map[x][y]=-1;
}
return V;
}
private int maxRangeWith(final MOB mob, final Environmental tool)
{
int max = 0;
if (tool != null)
max = tool.maxRange();
if (mob.maxRange() < max)
max = mob.maxRange();
return max;
}
private int minRangeWith(final MOB mob, final Environmental tool)
{
if (tool != null)
return tool.minRange();
return mob.minRange();
}
@Override
public void establishRange(final MOB source, final MOB target, final Environmental tool)
{
// establish and enforce range
if((source.rangeToTarget()<0))
{
if(source.riding()!=null)
{
if((target==source.riding())||(source.riding().amRiding(target)))
source.setRangeToTarget(0);
else
if((source.riding() instanceof MOB)
&&(((MOB)source.riding()).isInCombat())
&&(((MOB)source.riding()).getVictim()==target)
&&(((MOB)source.riding()).rangeToTarget()>=0)
&&(((MOB)source.riding()).rangeToTarget()<source.rangeToTarget()))
{
source.setRangeToTarget(((MOB)source.riding()).rangeToTarget());
source.recoverPhyStats();
return;
}
else
for(int r=0;r<source.riding().numRiders();r++)
{
final Rider rider=source.riding().fetchRider(r);
if(!(rider instanceof MOB))
continue;
final MOB otherMOB=(MOB)rider;
if((otherMOB!=source)
&&(otherMOB.isInCombat())
&&(otherMOB.getVictim()==target)
&&(otherMOB.rangeToTarget()>=0)
&&(otherMOB.rangeToTarget()<source.rangeToTarget()))
{
source.setRangeToTarget(otherMOB.rangeToTarget());
source.recoverPhyStats();
return;
}
}
}
final MOB follow=source.amFollowing();
if((target.getVictim()==source)&&(target.rangeToTarget()>=0))
source.setRangeToTarget(target.rangeToTarget());
else
if((follow!=null)&&(follow.location()==source.location()))
{
int newRange=follow.fetchFollowerOrder(source);
if(newRange<0)
{
if(follow.rangeToTarget()>=0)
{
newRange=follow.rangeToTarget();
if(newRange<maxRangeWith(source,tool))
newRange=maxRangeWith(source,tool);
}
else
newRange=maxRangeWith(source,tool);
}
else
{
if(follow.rangeToTarget()>=0)
newRange=newRange+follow.rangeToTarget();
}
if((source.location()!=null)&&(source.location().maxRange()<newRange))
newRange=source.location().maxRange();
source.setRangeToTarget(newRange);
}
else
source.setRangeToTarget(maxRangeWith(source,tool));
source.recoverPhyStats();
}
}
protected void subtickAttack(final MOB fighter, Item weapon, final int folrange)
{
if((weapon!=null)
&&(weapon.amWearingAt(Wearable.IN_INVENTORY)))
weapon=fighter.fetchWieldedItem();
if((!fighter.isAttributeSet(MOB.Attrib.AUTOMELEE)))
postAttack(fighter,fighter.getVictim(),weapon);
else
{
boolean inminrange=(fighter.rangeToTarget()>=minRangeWith(fighter, weapon));
boolean inmaxrange=(fighter.rangeToTarget()<=maxRangeWith(fighter, weapon));
if((folrange>=0)
&&(fighter.rangeToTarget()>=0)
&&(folrange!=fighter.rangeToTarget()))
{
if(fighter.rangeToTarget()<folrange)
inminrange=false;
else
if(fighter.rangeToTarget()>folrange)
{
final MOB victim=fighter.getVictim();
// these settings are ONLY to ensure that neither of the
// next two conditions evaluate to true.
inminrange=true;
inmaxrange=false;
// we advance
final CMMsg msg=CMClass.getMsg(fighter,victim,CMMsg.MSG_ADVANCE,L("<S-NAME> advance(s) at <T-NAMESELF>."));
if(fighter.location().okMessage(fighter,msg))
{
fighter.location().send(fighter,msg);
fighter.setRangeToTarget(fighter.rangeToTarget()-1);
if((victim != null)&& (victim.getVictim()==fighter))
{
victim.setRangeToTarget(fighter.rangeToTarget());
victim.recoverPhyStats();
}
}
}
}
if((!inminrange)
&&(fighter.curState().getMovement()>=25))
{
final CMMsg msg=CMClass.getMsg(fighter,fighter.getVictim(),CMMsg.MSG_RETREAT,L("<S-NAME> retreat(s) before <T-NAME>."));
if(fighter.location().okMessage(fighter,msg))
fighter.location().send(fighter,msg);
}
else
if(inminrange
&&inmaxrange
&&((weapon!=null)||(fighter.rangeToTarget()==0)))
postAttack(fighter,fighter.getVictim(),weapon);
}
}
protected void subtickBeforeAttack(final MOB fighter, final CombatSystem combatSystem)
{
// combat que system eats up standard commands
// before using any attacks
while(((combatSystem==CombatLibrary.CombatSystem.QUEUE)||(combatSystem==CombatLibrary.CombatSystem.TURNBASED))
&&(!fighter.amDead())
&&(fighter.dequeCommand()))
{
}
}
protected void subtickAfterAttack(final MOB fighter)
{
// this code is for auto-retargeting of players
// is mostly not handled by combatabilities in a smarter way
final MOB target=fighter.getVictim();
if((target!=null)
&&(fighter.isMonster())
&&(target.isMonster())
&&(CMLib.dice().rollPercentage()==1)
&&((fighter.amFollowing()==null)||(fighter.amFollowing().isMonster()))
&&(!target.amDead())
&&(fighter.location()!=null))
{
MOB M=null;
final Room R=fighter.location();
MOB nextVictimM=null;
for(int m=0;m<R.numInhabitants();m++)
{
M=R.fetchInhabitant(m);
if((M!=null)
&&(!M.isMonster())
&&(M.getVictim()==fighter)
&&((nextVictimM==null)||(M.rangeToTarget()<nextVictimM.rangeToTarget())))
nextVictimM=M;
}
if((nextVictimM!=null)&&(nextVictimM.isInCombat()))
fighter.setVictim(nextVictimM);
}
}
protected MUDFight getSessionMUDFight(final Session sess)
{
if(sess == null)
return this;
final CombatLibrary lib=CMLib.get(sess)._combat();
if(lib instanceof MUDFight)
return (MUDFight)lib;
return this;
}
@Override
public void dispenseExperience(final Set<MOB> killers, final Set<MOB> dividers, final MOB killed)
{
int totalLevels=0;
int totalSquaredLevels=0;
for (final MOB mob : dividers)
{
totalSquaredLevels += (mob.phyStats().level()*mob.phyStats().level());
totalLevels += mob.phyStats().level();
}
final double[] totalVars={
dividers.size(),
totalSquaredLevels,
totalLevels,
killers.size()
};
final double expAmount = CMath.parseMathExpression(this.totalCombatExperienceFormula, totalVars, 0.0);
final double[] indiVars = {
expAmount,
0.0,
totalSquaredLevels,
0.0,
totalLevels,
killers.size()
};
for (final MOB mob : killers)
{
indiVars[1]=mob.phyStats().level()*mob.phyStats().level();
indiVars[3]=mob.phyStats().level();
final CompiledFormula indXPformula = getSessionMUDFight(mob.session()).individualCombatExpFormula;
final int myAmount=(int)Math.round(CMath.parseMathExpression(indXPformula, indiVars, 0.0));
CMLib.get(mob.session())._leveler().postExperience(mob,killed,"",myAmount,false);
}
}
@Override
public void tickCombat(final MOB fighter)
{
Item weapon=fighter.fetchWieldedItem();
if((fighter.isAttributeSet(MOB.Attrib.AUTODRAW))&&(weapon==null))
{
CMLib.commands().postDraw(fighter,false,true);
weapon=fighter.fetchWieldedItem();
}
final CombatSystem combatSystem=CombatSystem.values()[CMProps.getIntVar(CMProps.Int.COMBATSYSTEM) % CombatSystem.values().length];
subtickBeforeAttack(fighter, combatSystem);
final int folrange=(fighter.isAttributeSet(MOB.Attrib.AUTOMELEE)
&&(fighter.amFollowing()!=null)
&&(fighter.amFollowing().getVictim()==fighter.getVictim())
&&(fighter.amFollowing().rangeToTarget()>=0)
&&(fighter.amFollowing().fetchFollowerOrder(fighter)>=0))?
fighter.amFollowing().fetchFollowerOrder(fighter)+fighter.amFollowing().rangeToTarget():-1;
if(CMLib.flags().isAliveAwakeMobile(fighter,true))
{
if(((combatSystem!=CombatLibrary.CombatSystem.MANUAL)&&(combatSystem!=CombatLibrary.CombatSystem.TURNBASED))
||(fighter.isMonster()))
{
final int saveAction=(combatSystem!=CombatLibrary.CombatSystem.DEFAULT)?0:1;
int numAttacks=(int)Math.round(Math.floor(fighter.actions()))-saveAction;
if((combatSystem==CombatLibrary.CombatSystem.DEFAULT)
&&(numAttacks>(int)Math.round(Math.floor(fighter.phyStats().speed()+0.9))))
numAttacks=(int)Math.round(Math.floor(fighter.phyStats().speed()+0.9));
for(int s=0;s<numAttacks;s++)
{
if((!fighter.amDead())
&&(fighter.curState().getHitPoints()>0)
&&(fighter.isInCombat())
&&(fighter.actions()>=1.0)
&&((s==0)||(CMLib.flags().isStanding(fighter))))
{
fighter.setActions(fighter.actions()-1.0);
subtickAttack(fighter,weapon,folrange);
}
else
break;
}
if(CMLib.dice().rollPercentage()>(fighter.charStats().getStat(CharStats.STAT_CONSTITUTION)*4))
fighter.curState().adjMovement(-1,fighter.maxState());
}
}
subtickAfterAttack(fighter);
}
@Override
public boolean isKnockedOutUponDeath(final MOB deadM, final MOB killerM)
{
String whatToDo=null;
if(((deadM.isMonster())||(deadM.soulMate()!=null)))
whatToDo=CMProps.getVar(CMProps.Str.MOBDEATH).toUpperCase();
else
whatToDo=CMProps.get(deadM.session()).getStr(CMProps.Str.PLAYERDEATH).toUpperCase();
final List<String> whatsToDo=CMParms.parseCommas(whatToDo,true);
final double[] fakeVarVals={1.0,1.0,1.0};
for(int w=0;w<whatsToDo.size();w++)
{
whatToDo=whatsToDo.get(w);
if(whatToDo.startsWith("OUT ")&&(CMath.isMathExpression(whatToDo.substring(4).trim(),fakeVarVals)))
return true;
}
return false;
}
@Override
public void expendEnergy(final MOB mob, final boolean expendMovement)
{
if(mob==null)
{
return;
}
final Room room=mob.location();
if(room!=null)
{
final CharState curState=mob.curState();
final CharState maxState=mob.maxState();
if(expendMovement)
{
int move=-room.pointsPerMove();
if(mob.phyStats().weight()>mob.maxCarry())
move+=(int)Math.round(CMath.mul(move,10.0*CMath.div(mob.phyStats().weight()-mob.maxCarry(),mob.maxCarry())));
curState.adjMovement(move,maxState);
}
if((!CMLib.flags().isSleeping(mob))
&&(!CMSecurity.isAllowed(mob,room,CMSecurity.SecFlag.IMMORT)))
{
int factor=mob.baseWeight()/500;
if(factor<1)
factor=1;
if((!CMSecurity.isDisabled(CMSecurity.DisFlag.THIRST))
&&(mob.maxState().getThirst() < (Integer.MAX_VALUE/2)))
curState.adjThirst(-(room.thirstPerRound()*factor),maxState.maxThirst(mob.baseWeight()));
if((!CMSecurity.isDisabled(CMSecurity.DisFlag.HUNGER))
&&(mob.maxState().getHunger() < (Integer.MAX_VALUE/2)))
curState.adjHunger(-factor,maxState.maxHunger(mob.baseWeight()));
}
final boolean thirsty=(curState.getThirst()<=0);
final boolean hungry=(curState.getHunger()<=0);
if((hungry||thirsty)&&(!expendMovement))
{
final int ticksThirsty=curState.adjTicksThirsty(thirsty);
final int ticksHungry=curState.adjTicksHungry(hungry);
if((ticksThirsty>CharState.DEATH_THIRST_TICKS)
||(ticksHungry>CharState.DEATH_HUNGER_TICKS))
{
if(thirsty)
mob.tell(L("YOU ARE DYING OF THIRST!"));
if(hungry)
mob.tell(L("YOU ARE DYING OF HUNGER!"));
postDeath(null,mob,null);
}
else
if(ticksThirsty>CharState.DEATH_THIRST_TICKS-30)
mob.tell(L("You are dehydrated, and near death. DRINK SOMETHING!"));
else
if(ticksHungry>CharState.DEATH_HUNGER_TICKS-30)
mob.tell(L("You are starved, and near death. EAT SOMETHING!"));
else
{
if(thirsty && ((ticksThirsty-1 % CharState.ANNOYANCE_DEFAULT_TICKS)==0))
{
if(ticksThirsty>((CharState.DEATH_THIRST_TICKS/2)+(CharState.DEATH_THIRST_TICKS/4)))
mob.tell(L("You are dehydrated! Drink something!"));
else
if(ticksThirsty>(CharState.DEATH_THIRST_TICKS/2))
mob.tell(L("You are parched! Drink something!"));
else
mob.tell(L("You are thirsty."));
}
if((hungry) && ((ticksHungry-1 % CharState.ANNOYANCE_DEFAULT_TICKS)==0))
{
if(ticksHungry>((CharState.DEATH_HUNGER_TICKS/2)+(CharState.DEATH_HUNGER_TICKS/4)))
mob.tell(L("You are starved! Eat something!"));
else
if(ticksHungry>(CharState.DEATH_HUNGER_TICKS/2))
mob.tell(L("You are famished! Eat something!"));
else
mob.tell(L("You are hungry."));
}
}
}
}
}
@Override
public boolean handleCombatLossConsequences(final MOB deadM, final MOB killerM, final String[] consequences, int[] lostExperience, String message)
{
if((consequences==null)||(consequences.length==0))
return false;
if(lostExperience==null)
lostExperience=new int[1];
final int baseExperience=lostExperience[0];
lostExperience[0]=0;
int rejuv=deadM.phyStats().rejuv();
if((rejuv==0)||(rejuv==Integer.MAX_VALUE))
rejuv=deadM.phyStats().level();
if(((!deadM.isMonster())&&(deadM.soulMate()==null)))
rejuv=1;
final double[] varVals={
deadM.basePhyStats().level()>deadM.phyStats().level()?deadM.basePhyStats().level():deadM.phyStats().level(),
(killerM!=null)?killerM.phyStats().level():0,
rejuv
};
for (final String command : consequences)
{
final String whatToDo=command.toUpperCase();
if(whatToDo.startsWith("UNL"))
{
final Vector<String> V=CMParms.parse(whatToDo);
int times=1;
if((V.size()>1)&&(CMath.s_int(V.lastElement())>1))
times=CMath.s_int(V.lastElement());
for(int t=0;t<times;t++)
CMLib.leveler().unLevel(deadM);
}
else
if(whatToDo.startsWith("RECALL"))
deadM.killMeDead(false);
else
if(whatToDo.startsWith("ASTR"))
{
final Ability A=CMClass.getAbility("Prop_AstralSpirit");
if((A!=null)&&(deadM.fetchAbility(A.ID())==null))
{
deadM.tell(L("^HYou are now a spirit.^N"));
if(whatToDo.startsWith("ASTRAL_R"))
{
A.setMiscText("SELF-RES");
deadM.tell(L("^HFind your corpse and use ENTER [body name] to re-enter your body.^N"));
}
else
deadM.tell(L("^HFind your corpse have someone resurrect it.^N"));
deadM.addAbility(A);
A.autoInvocation(deadM, false);
}
}
else
if(whatToDo.startsWith("OUT ")&&(CMath.isMathExpression(whatToDo.substring(4).trim(),varVals)))
{
final Ability A=CMClass.getAbility("Skill_ArrestingSap");
final int tickDown=CMath.s_parseIntExpression(whatToDo.substring(4).trim(),varVals);
if((A!=null)&&(tickDown>0))
{
A.invoke(deadM,new XVector<String>(""+tickDown,"SAFELY"),deadM,true,0);
deadM.resetToMaxState();
}
}
else
if(whatToDo.startsWith("PUR"))
{
final MOB deadMOB=(!deadM.isPlayer())?CMLib.get(deadM.session())._players().getLoadPlayer(deadM.Name()):deadM;
if((deadMOB!=null)&&(deadM.session()!=null))
{
CMLib.threads().executeRunnable(deadM.session().getGroupName(), new Runnable()
{
@Override
public void run()
{
final Session session=deadMOB.session();
if(session!=null)
session.stopSession(true, true, true);
CMLib.players().obliteratePlayer(deadMOB,true,false);
deadMOB.destroy();
}
});
return false;
}
}
else
if(whatToDo.startsWith("LOSESK"))
{
if(deadM.numAbilities()>0)
{
final Ability A=deadM.fetchAbility(CMLib.dice().roll(1,deadM.numAbilities(),-1));
if(A!=null)
{
deadM.tell(L("You've forgotten @x1.",A.Name()));
deadM.delAbility(A);
if(A.isAutoInvoked())
{
final Ability A2=deadM.fetchEffect(A.ID());
A2.unInvoke();
deadM.delEffect(A2);
}
}
}
}
else
if(CMath.s_parseIntExpression(whatToDo,varVals)>0)
{
lostExperience[0]=CMath.s_parseIntExpression(whatToDo,varVals);
if(lostExperience[0]>0)
{
lostExperience[0]=-CMLib.leveler().postExperience(deadM,null,null,-lostExperience[0],false);
message=L(message,""+lostExperience[0]);
deadM.tell(message);
}
}
else
if(whatToDo.startsWith("EXPER"))
{
lostExperience[0]=baseExperience;
if(lostExperience[0]>0)
{
lostExperience[0]=-CMLib.leveler().postExperience(deadM,null,null,-lostExperience[0],false);
message=L(message,""+lostExperience[0]);
deadM.tell(message);
}
}
}
return true;
}
@Override
public boolean postRevengeAttack(final MOB attacker, final MOB defender)
{
if((attacker!=null)
&&(!attacker.isInCombat())
&&(!attacker.amDead())
&&(!attacker.amDestroyed())
&&(attacker.isMonster())
&&(attacker!=defender)
&&(defender!=null)
&&(attacker.location()==defender.location())
&&(attacker.location().isInhabitant(defender))
&&(CMLib.flags().canBeSeenBy(defender,attacker)))
return postAttack(attacker,defender,attacker.fetchWieldedItem());
return false;
}
@Override
public int getTickStatus()
{
return tickStatus;
}
protected void tickAllShips()
{
if((CMLib.map() != CMLib.get(MudHost.MAIN_HOST)._map())
||(Thread.currentThread().getThreadGroup().getName().charAt(0)==MudHost.MAIN_HOST))
{
for(final Enumeration<BoardableShip> s = CMLib.map().ships();s.hasMoreElements();)
{
final BoardableShip ship = s.nextElement();
ship.tick(ship, Tickable.TICKID_SPECIALMANEUVER);
}
for(final Enumeration<BoardableShip> s = CMLib.map().ships();s.hasMoreElements();)
{
final BoardableShip ship = s.nextElement();
ship.tick(ship, Tickable.TICKID_SPECIALCOMBAT);
}
}
}
protected void runSpecialCombat()
{
tickAllShips();
}
@Override
public boolean tick(final Tickable ticking, final int tickID)
{
try
{
if(!CMSecurity.isDisabled(CMSecurity.DisFlag.SPECOMBATTHREAD))
{
isDebugging=CMSecurity.isDebugging(DbgFlag.UTILITHREAD);
tickStatus=Tickable.STATUS_ALIVE;
runSpecialCombat();
}
}
finally
{
tickStatus=Tickable.STATUS_NOT;
setThreadStatus(serviceClient,"sleeping");
}
return true;
}
@Override
public MOB getBreatheKiller(final MOB victim)
{
MOB victimKiller = victim.getVictim();
if(victimKiller != null)
return victimKiller;
final Room R=victim.location();
if(R!=null)
{
for(final Enumeration<Ability> a=R.effects();a.hasMoreElements();)
{
final Ability A=a.nextElement();
if((A!=null)
&&(A.invoker()!=null)
&&(A.invoker()!=victim))
{
if(A.abstractQuality()==Ability.QUALITY_MALICIOUS)
return A.invoker();
victimKiller = A.invoker();
}
}
}
for(final Enumeration<Ability> a=victim.personalEffects();a.hasMoreElements();)
{
final Ability A=a.nextElement();
if((A!=null)
&&(A.invoker()!=null)
&&(A.invoker()!=victimKiller))
{
if(A.abstractQuality()==Ability.QUALITY_MALICIOUS)
return A.invoker();
victimKiller = A.invoker();
}
}
return (victimKiller == null) ? victim : victimKiller;
}
@Override
public boolean shutdown()
{
if(CMLib.threads().isTicking(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK))
{
CMLib.threads().deleteTick(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK);
serviceClient=null;
}
return true;
}
}