<NME> MUDFight.java
<BEF> 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-2022 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().isCloaked(inhab))))
			&&((includePlayers)||inhab.isMonster()||mob.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);
			//BZ: The magical pre-cast mana-saving range check, pt 2/2
			//BZ: The magical pre-cast mana-saving range check, pt 2/2
			if((A!=null)
			&&(h.size()>0)
			&&(caster.getVictim()!=null)
			&&((A.minRange()>0)||((A.maxRange()>0))&&(A.maxRange()<9999))
			&&(A.abstractQuality()==Ability.QUALITY_MALICIOUS))
			{
				int disqualified=0;
				for(final MOB M : h)
				{
					if((A.minRange()>0)
					&&(((caster.getVictim()==M)&&(caster.rangeToTarget()<A.minRange()))
					  ||((M.getVictim()==caster)&&(M.rangeToTarget()<A.minRange())))
					)	disqualified++;
					else
					if((A.maxRange()>0)
					&&(A.maxRange()<99999)
					&&(((caster.getVictim()==M)&&(caster.rangeToTarget()>A.maxRange()))
					  ||((M.getVictim()==caster)&&(M.rangeToTarget()>A.maxRange())))
					)	disqualified++;
				}
				if(disqualified>=h.size())
				{
					caster.tell(L("You aren't in range!"));
					h.clear();
				}
			}
		}
		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();
						final MOB targetM = (I.owner() instanceof MOB)?(MOB)I.owner():mob;
						msg=CMClass.getMsg(mob,targetM,I,CMMsg.MSG_OK_VISUAL,L("<O-NAME> is destroyed!"),null,L("<O-NAME> carried by <T-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;
			}
		}

		if((killerM!=null)&&(killerM.getVictim()==deadM))
			handleDamageSpamSummary(killerM);

		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)
	{
		String defenderOwnerName = (defender instanceof PrivateProperty) ? ((PrivateProperty)defender).getOwnerName() : "";
		// is this how we determine npc ships?
		if(((defenderOwnerName == null)||(defenderOwnerName.length()==0))&&(defender instanceof PrivateProperty))
		{
			if(!(defender instanceof ClanItem))
				return true;
			defenderOwnerName = ((ClanItem)defender).clanID();
			if((defenderOwnerName == null)||(defenderOwnerName.length()==0))
				return true;
			final Clan C=CMLib.clans().getClan(defenderOwnerName);
			final Clan srcC=CMLib.clans().findConquerableClan(mob);
			if((C!=null)
			&&(srcC!=null)
			&&(C!=srcC)
			&&(srcC.getClanRelations(defenderOwnerName)==Clan.REL_WAR))
				return true;
		}
		if(CMSecurity.isASysOp(mob) && mob.isAttributeSet(Attrib.PLAYERKILL))
			return true;
		if(CMLib.flags().isUnattackable(defender))
			return false;
		if(defender instanceof Boardable)
		{
			final Area otherArea = ((Boardable)defender).getArea();
			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 boolean isASiegeWeapon(final Item I)
	{
		if((I instanceof AmmunitionWeapon)
		&&(I instanceof Rideable)
		&&((AmmunitionWeapon)I).isFreeStanding()
		&&(((AmmunitionWeapon)I).requiresAmmunition()))
			return true;
		return false;
	}

	@Override
	public boolean postSiegeAttack(final MOB attacker, final SiegableItem siegeAttacker, final SiegableItem siegeTarget, 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,siegeTarget,weapon,CMMsg.MSG_WEAPONATTACK,null);
		final Room R=CMLib.map().roomLocation(siegeTarget);
		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 boolean damage_ = (str.charAt(replace+7)=='-') && (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(damage_)
		{
			String hitWord=standardHitWord(damageType,damage);
			if(hitWord.indexOf(')')>=0)
			{
				final String[][] allPlurals=new String[][] {
					{"(S)",""},{"(s)",""},{"(YS)",""},{"(ys)",""}
				};
				hitWord=CMStrings.replaceAlls(hitWord, allPlurals);
			}
			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;
		final int srcMsgCode = messageCode;
		String srcMsgStr=allDisplayMessage;
		if(attacker != target)
		{
			if(weapon instanceof Ability)
			{
				if((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);
				if((((Ability)weapon).affecting() instanceof Item)
				&&(((Item)((Ability)weapon).affecting()).owner()==attacker)
				&&((((Ability)weapon).classificationCode()&Ability.ALL_ACODES)==Ability.ACODE_PROPERTY)
				&&(attacker.isAttributeSet(Attrib.NOBATTLESPAM)))
					srcMsgStr=null;
			}
			damageTypeMsg = CMMsg.TYP_DAMAGE | (CMMsg.MASK_MALICIOUS & messageCode);
		}
		else
			damageTypeMsg = CMMsg.TYP_DAMAGE;

		final CMMsg msg=CMClass.getMsg(attacker,target,weapon,srcMsgCode,srcMsgStr,
															  damageTypeMsg,allDisplayMessage,
															  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 Boardable)
				&&(((Boardable)A).getBoardableItem() instanceof Combatant)
				&&(((Combatant)((Boardable)A).getBoardableItem()).isInCombat()))
					return;

				final Rideable riding=mob.riding();
				final boolean isSleeping=CMLib.flags().isSleeping(mob);
				final boolean bedBonus=isSleeping && (riding!=null) && (riding.rideBasis()==Rideable.Basis.FURNITURE_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);

				if(((hpGain>0)||(manaGain>0)||(moveGain>0))
				&&(!isSleeping)
				&&(!isSittingOrRiding))
					mob.curState().adjFatigue(Math.round(CMProps.getTickMillis()), mob.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 Boardable)
				&&(CMLib.combat().isASiegeWeapon(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 int bonusDmg, 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(damageInt > 0)
				damageInt += bonusDmg;
			if(damageInt < 0)
				damageInt = 0;
		}
		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 postSiegeWeaponAttackResult(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=(DeadBody)deadM.killMeDead(true);

		if(deadM.isPlayer())
			deadM.playerStats().deathCounter(1);
		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))
		{
			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))
		{
			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;
			if(nounIndex<0)
				nounIndex=0;
			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))
		{
			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);
		}
		//re-consider this synchro-block -- looks heady and dangerous, but this is the only spot, so ok for now
		synchronized(target.curState())
		{
			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()));
			}
			if(killer.isPlayer())
				CMLib.achievements().possiblyBumpAchievement(killer, AchievementLibrary.Event.KILLS, 1, deadmob);
			final Set<MOB> grpS=killer.getGroupMembers(new HashSet<MOB>());
			for(final MOB M : grpS)
			{
				if(M.isPlayer())
					CMLib.achievements().possiblyBumpAchievement(M, AchievementLibrary.Event.GROUPKILLS, 1, deadmob, Integer.valueOf(grpS.size()));
			}
			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); //BZ: cause of range issues.
			if(newTargetM!=null)
			{
				observer.setVictim(newTargetM);
				if(newTargetM.getVictim()==observer)
					observer.setRangeToTarget(newTargetM.rangeToTarget());
			}
		}
	}

	@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)
				{
					if(mob.isInCombat()||(numEnemies>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,0, 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(),0, 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 int calculateRangeToTarget(final MOB source, final MOB target, final Environmental tool)
	{
		if(source.riding()!=null)
		{
			if((target==source.riding())||(source.riding().amRiding(target)))
				return 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()))
				return ((MOB)source.riding()).rangeToTarget();
			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()))
					return otherMOB.rangeToTarget();
			}
		}

		final MOB follow=source.amFollowing();
		if((target.getVictim()==source)&&(target.rangeToTarget()>=0))
			return 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();
			return newRange;
		}
		else
			return maxRangeWith(source,tool);
	}

	@Override
	public void establishRange(final MOB source, final MOB target, final Environmental tool)
	{
		// establish and enforce range
		if((source.rangeToTarget()<0))
		{
			final int newRange = calculateRangeToTarget(source, target, tool);
			if(newRange != source.rangeToTarget())
			{
				source.setRangeToTarget(newRange);
				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;
		int highestLevel=0;

		for (final MOB mob : dividers)
		{
			final int lvl=mob.phyStats().level();
			totalSquaredLevels += (lvl*lvl);
			totalLevels += lvl;
			if(lvl>highestLevel)
				highestLevel=lvl;
		}

		final int killedLevel=(killed==null)?highestLevel:killed.phyStats().level();
		final double[] totalVars={
			dividers.size(),
			totalSquaredLevels,
			totalLevels,
			killers.size(),
			highestLevel,
			killedLevel
		};
		final double expAmount = CMath.parseMathExpression(this.totalCombatExperienceFormula, totalVars, 0.0);

		final double[] indiVars = {
			expAmount,
			0.0,
			totalSquaredLevels,
			0.0,
			totalLevels,
			killers.size(),
			highestLevel,
			killedLevel
		};
		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")) // "UNLEVEL
			{
				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")) // "ASTRAL
			{
				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")) // "PURGE"
			{
				int maxLives = 1;
				final int x = whatToDo.indexOf(' ');
				if(x>0)
					maxLives = CMath.s_int(whatToDo.substring(x+1).trim());
				final MOB deadMOB=(!deadM.isPlayer())?CMLib.get(deadM.session())._players().getLoadPlayer(deadM.Name()):deadM;
				if((deadMOB!=null)&&(deadM.session()!=null))
				{
					if((deadMOB.playerStats()==null) // this happens after the bump, so...
					||(deadMOB.playerStats().deathCounter(0)>=maxLives))
					{
						CMLib.threads().executeRunnable(deadM.session().getGroupName(), new Runnable()
						{
							final MOB mob = deadMOB;
							@Override
							public void run()
							{
								final Session session=mob.session();
								if(session!=null)
									session.stopSession(true, true, true);
								// preserve their corpse, if it is in the game
								final PlayerStats pStat = mob.playerStats();
								if(pStat != null)
								{
									final List<Item> removeThese=new ArrayList<Item>(1);
									for(final Enumeration<Item>  o = pStat.getExtItems().items();o.hasMoreElements();)
									{
										final Item I = o.nextElement();
										Item chkI = I;
										if(I.container() instanceof DeadBody)
											chkI=I.container();
										if((chkI instanceof DeadBody)
										&&(((DeadBody)chkI).isPlayerCorpse())
										&&(CMLib.flags().isInTheGame(chkI, true)))
											removeThese.add(I);
									}
									for(final Item I : removeThese)
										pStat.getExtItems().delItem(I);
								}
								CMLib.players().obliteratePlayer(mob,true,CMSecurity.isDisabled(CMSecurity.DisFlag.DEATHCRY));
								mob.destroy();
							}
						});
						return false;
					}
					else
					if(deadMOB.isPlayer())
					{
						final int remain = (maxLives-deadMOB.playerStats().deathCounter(0));
						if(remain == 1)
							deadMOB.tell(L("\n\r^HYou have ^W1^? life remaining.^N"));
						else
							deadMOB.tell(L("\n\r^HYou have ^W@x1^? lives remaining.^N",""+remain));
					}
				}
			}
			else
			if(whatToDo.startsWith("LOSESK")) // "LOSESKILL
			{
				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)
				&&(!deadM.charStats().getCurrentClass().expless()))
				{
					lostExperience[0]=-CMLib.leveler().postExperience(deadM,null,null,-lostExperience[0],false);
					message=L(message,""+lostExperience[0]);
					deadM.tell(message);
				}
			}
			else
			if(whatToDo.startsWith("EXPER")) // "EXPERIENCE
			{
				lostExperience[0]=baseExperience;
				if((lostExperience[0]>0)
				&&(!deadM.charStats().getCurrentClass().expless()))
				{
					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<Boardable> s = CMLib.map().ships();s.hasMoreElements();)
			{
				final Boardable ship = s.nextElement();
				ship.tick(ship, Tickable.TICKID_SPECIALMANEUVER);
			}
			for(final Enumeration<Boardable> s = CMLib.map().ships();s.hasMoreElements();)
			{
				final Boardable 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;
	}
}

<MSG> a bunch of group target checks added to save player mana

git-svn-id: 0cdf8356e41b2d8ccbb41bb76c82068fe80b2514@20515 0d6f1817-ed0e-0410-87c9-987e46238f29

<DFF> @@ -181,7 +181,6 @@ public class MUDFight extends StdLibrary implements CombatLibrary
 			//BZ: The magical pre-cast mana-saving range check, pt 2/2
 			if((A!=null)
 			&&(h.size()>0)
-			&&(caster.getVictim()!=null)
 			&&((A.minRange()>0)||((A.maxRange()>0))&&(A.maxRange()<9999))
 			&&(A.abstractQuality()==Ability.QUALITY_MALICIOUS))
 			{

{'commit': 'diff --git a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_Earthquake.java b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_Earthquake.java\nindex 4ee92e1c93..65aeec0c03 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_Earthquake.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_Earthquake.java\n@@ -160,7 +160,7 @@ public class Chant_Earthquake extends Chant\n \t\t\tif(target instanceof MOB)\r\n \t\t\t{\r\n \t\t\t\tfinal Set<MOB> h=properTargets(mob,target,false);\r\n-\t\t\t\tif(h==null)\r\n+\t\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t\t\treturn Ability.QUALITY_INDIFFERENT;\r\n \t\t\t}\r\n \t\t}\r\n@@ -171,7 +171,7 @@ public class Chant_Earthquake extends Chant\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth shaking up."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_HighTide.java b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_HighTide.java\nindex ad2406b2bc..eda7c361ab 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_HighTide.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_HighTide.java\n@@ -160,7 +160,7 @@ public class Chant_HighTide extends Chant\n \t\t\tif(target instanceof MOB)\r\n \t\t\t{\r\n \t\t\t\tfinal Set<MOB> h=properTargets(mob,target,false);\r\n-\t\t\t\tif(h==null)\r\n+\t\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t\t\treturn Ability.QUALITY_INDIFFERENT;\r\n \t\t\t}\r\n \t\t}\r\n@@ -230,7 +230,7 @@ public class Chant_HighTide extends Chant\n \t\t}\r\n \r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth sending the tide at."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_MeteorStrike.java b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_MeteorStrike.java\nindex 9884bf89d5..65b65bf82f 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_MeteorStrike.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_MeteorStrike.java\n@@ -78,7 +78,7 @@ public class Chant_MeteorStrike extends Chant\n \t\tif(mob!=null)\r\n \t\t{\r\n \t\t\tfinal Set<MOB> h=properTargets(mob,target,false);\r\n-\t\t\tif(h==null)\r\n+\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t\treturn Ability.QUALITY_INDIFFERENT;\r\n \r\n \t\t\tfinal Room R=mob.location();\r\n@@ -95,7 +95,7 @@ public class Chant_MeteorStrike extends Chant\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth striking at."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_PlantSnare.java b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_PlantSnare.java\nindex 1a660276bd..bc6a235e0d 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_PlantSnare.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_PlantSnare.java\n@@ -153,7 +153,7 @@ public class Chant_PlantSnare extends Chant\n \t\tif(mob!=null)\r\n \t\t{\r\n \t\t\tfinal Set<MOB> h=properTargets(mob,target,false);\r\n-\t\t\tif(h==null)\r\n+\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t\treturn Ability.QUALITY_INDIFFERENT;\r\n \t\t\tfinal Room room=mob.location();\r\n \t\t\tif(room!=null)\r\n@@ -175,7 +175,7 @@ public class Chant_PlantSnare extends Chant\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth snaring."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonFear.java b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonFear.java\nindex fb2c57a6cf..a6d26ddde5 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonFear.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonFear.java\n@@ -86,7 +86,7 @@ public class Chant_SummonFear extends Chant\n \t\tif(mob!=null)\r\n \t\t{\r\n \t\t\tfinal Set<MOB> h=properTargets(mob,target,false);\r\n-\t\t\tif(h==null)\r\n+\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t\treturn Ability.QUALITY_INDIFFERENT;\r\n \t\t}\r\n \t\treturn super.castingQuality(mob,target);\r\n@@ -96,7 +96,7 @@ public class Chant_SummonFear extends Chant\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth scaring."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonInsects.java b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonInsects.java\nindex 1a49757063..ba9db452a0 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonInsects.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Druid/Chant_SummonInsects.java\n@@ -162,7 +162,7 @@ public class Chant_SummonInsects extends Chant\n \r\n \t\tif(success)\r\n \t\t{\r\n-\t\t\tif(h==null)\r\n+\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t{\r\n \t\t\t\tmob.location().show(mob,null,this,verbalCastCode(mob,null,auto),auto?L("A swarm of stinging insects appear, then flutter away!"):L("^S<S-NAME> chant(s) into the sky.  A swarm of stinging insects appear.  Finding no one to sting, they flutter away.^?"));\r\n \t\t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Battlecry.java b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Battlecry.java\nindex 564382f168..c6d2dad1e0 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Battlecry.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Battlecry.java\n@@ -128,6 +128,12 @@ public class Fighter_Battlecry extends FighterSkill\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("No one seems excited."));\r\n+\t\t\treturn false;\r\n+\t\t}\r\n \t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n@@ -138,9 +144,6 @@ public class Fighter_Battlecry extends FighterSkill\n \t\t\tif(mob.location().okMessage(mob,msg))\r\n \t\t\t{\r\n \t\t\t\tmob.location().send(mob,msg);\r\n-\t\t\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\t\t\tif(h==null)\r\n-\t\t\t\t\treturn false;\r\n \t\t\t\tfor (final Object element : h)\r\n \t\t\t\t{\r\n \t\t\t\t\tfinal MOB target=(MOB)element;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_CircleTrip.java b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_CircleTrip.java\nindex 4072cba07f..0cdce59036 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_CircleTrip.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_CircleTrip.java\n@@ -186,7 +186,7 @@ public class Fighter_CircleTrip extends FighterSkill\n \t\t\treturn false;\r\n \r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth tripping."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Rallycry.java b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Rallycry.java\nindex 8d7c8ff7b4..aef35263fd 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Rallycry.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Rallycry.java\n@@ -133,6 +133,13 @@ public class Fighter_Rallycry extends FighterSkill\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("No one seems to need rallying."));\r\n+\t\t\treturn false;\r\n+\t\t}\r\n+\t\t\r\n \t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n@@ -143,9 +150,6 @@ public class Fighter_Rallycry extends FighterSkill\n \t\t\tif(mob.location().okMessage(mob,msg))\r\n \t\t\t{\r\n \t\t\t\tmob.location().send(mob,msg);\r\n-\t\t\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\t\t\tif(h==null)\r\n-\t\t\t\t\treturn false;\r\n \t\t\t\tfor (final Object element : h)\r\n \t\t\t\t{\r\n \t\t\t\t\tfinal MOB target=(MOB)element;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Sweep.java b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Sweep.java\nindex deff228e66..e496024a8d 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Sweep.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Sweep.java\n@@ -100,7 +100,7 @@ public class Fighter_Sweep extends FighterSkill\n \t\tif((mob!=null)&&(target!=null))\r\n \t\t{\r\n \t\t\tfinal Set<MOB> h=properTargets(mob,target,false);\r\n-\t\t\tif(h.size()<2)\r\n+\t\t\tif((h==null)||(h.size()<2))\r\n \t\t\t\treturn Ability.QUALITY_INDIFFERENT;\r\n \t\t\tfor(final MOB M : h)\r\n \t\t\t{\r\n@@ -126,7 +126,9 @@ public class Fighter_Sweep extends FighterSkill\n \t\t\tmob.tell(L("You must be in combat to sweep!"));\r\n \t\t\treturn false;\r\n \t\t}\r\n-\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,false);\r\n+\t\tSet<MOB> h=properTargets(mob,givenTarget,false);\r\n+\t\tif(h==null)\r\n+\t\t\th=new HashSet<MOB>();\r\n \t\tfor(final MOB M : h)\r\n \t\t{\r\n \t\t\tif((M.rangeToTarget()<0)||(M.rangeToTarget()>0))\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Warcry.java b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Warcry.java\nindex 95f01c0db6..ae62d9d849 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Warcry.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Fighter/Fighter_Warcry.java\n@@ -128,6 +128,13 @@ public class Fighter_Warcry extends FighterSkill\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("No one here seems to want to get mad."))\r\n+\t\t\treturn false;\r\n+\t\t}\r\n+\t\t\r\n \t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n@@ -138,9 +145,6 @@ public class Fighter_Warcry extends FighterSkill\n \t\t\tif(mob.location().okMessage(mob,msg))\r\n \t\t\t{\r\n \t\t\t\tmob.location().send(mob,msg);\r\n-\t\t\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\t\t\tif(h==null)\r\n-\t\t\t\t\treturn false;\r\n \t\t\t\tfor (final Object element : h)\r\n \t\t\t\t{\r\n \t\t\t\t\tfinal MOB target=(MOB)element;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Misc/Dragonbreath.java b/com/planet_ink/coffee_mud/Abilities/Misc/Dragonbreath.java\nindex 97f8905884..a4b9b19fe0 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Misc/Dragonbreath.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Misc/Dragonbreath.java\n@@ -238,7 +238,7 @@ public class Dragonbreath extends StdAbility\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth breathing on."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_ChainStrike.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_ChainStrike.java\nindex 273ae50ee3..8a692966c7 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_ChainStrike.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_ChainStrike.java\n@@ -76,8 +76,11 @@ public class Prayer_ChainStrike extends Prayer\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, boolean auto, final int asLevel)\r\n \t{\r\n \t\tSet<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n-\t\t\th=new HashSet<MOB>();\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth striking at."));\r\n+\t\t\treturn false;\r\n+\t\t}\r\n \r\n \t\tfinal List<MOB> targets=new ArrayList<MOB>(h);\r\n \r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassBlindness.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassBlindness.java\nindex 514804fc3c..fe174cab30 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassBlindness.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassBlindness.java\n@@ -124,11 +124,14 @@ public class Prayer_MassBlindness extends Prayer\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n-\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("You don\'t have any targets!"));\r\n \t\t\treturn false;\r\n+\t\t}\r\n \r\n-\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n \t\tboolean success=proficiencyCheck(mob,0,auto);\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassDeafness.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassDeafness.java\nindex 790388806a..565df9fc70 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassDeafness.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassDeafness.java\n@@ -117,11 +117,14 @@ public class Prayer_MassDeafness extends Prayer\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n-\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("You don\'t have any targets!"));\r\n \t\t\treturn false;\r\n+\t\t}\r\n \r\n-\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n \t\tboolean success=proficiencyCheck(mob,0,auto);\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHarm.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHarm.java\nindex 75524bece0..1c875316eb 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHarm.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHarm.java\n@@ -83,11 +83,14 @@ public class Prayer_MassHarm extends Prayer\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n-\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("You seem to have no targets."));\r\n \t\t\treturn false;\r\n+\t\t}\r\n \r\n-\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n \t\tfinal boolean success=proficiencyCheck(mob,0,auto);\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHeal.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHeal.java\nindex f20619b659..ed66290415 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHeal.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_MassHeal.java\n@@ -92,13 +92,14 @@ public class Prayer_MassHeal extends Prayer implements MendingSkill\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n+\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t\treturn false;\r\n+\t\t\r\n \t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n \t\t\treturn false;\r\n \r\n \t\tfinal boolean success=proficiencyCheck(mob,0,auto);\r\n-\t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n-\t\t\treturn false;\r\n \t\tfor (final Object element : h)\r\n \t\t{\r\n \t\t\tfinal MOB target=(MOB)element;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Refuge.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Refuge.java\nindex 1e70dc94c5..dc439efe07 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Refuge.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Refuge.java\n@@ -130,7 +130,7 @@ public class Prayer_Refuge extends Prayer\n \t\t\t\tif((newRoom!=null)&&(newRoom!=msg.source().location()))\r\n \t\t\t\t{\r\n \t\t\t\t\tfinal Set<MOB> h=properTargets(msg.source(),null,false);\r\n-\t\t\t\t\tif(h==null)\r\n+\t\t\t\t\tif((h==null)||(h.size()==0))\r\n \t\t\t\t\t\treturn;\r\n \t\t\t\t\tfinal Room thisRoom=msg.source().location();\r\n \t\t\t\t\tfinal Ability thisA=this;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Tremor.java b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Tremor.java\nindex 1ba1866f80..788828019a 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Tremor.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Prayers/Prayer_Tremor.java\n@@ -162,7 +162,7 @@ public class Prayer_Tremor extends Prayer\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth shaking up."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_BreakALeg.java b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_BreakALeg.java\nindex badbd06390..011de24177 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_BreakALeg.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_BreakALeg.java\n@@ -133,16 +133,16 @@ public class Skill_BreakALeg extends BardSkill\n \t@Override\r\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n-\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n-\t\t\treturn false;\r\n-\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth wishing luck to."));\r\n \t\t\treturn false;\r\n \t\t}\r\n \r\n+\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n+\t\t\treturn false;\r\n+\r\n \t\tfinal boolean success=proficiencyCheck(mob,0,auto);\r\n \t\tif(success)\r\n \t\t{\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CenterOfAttention.java b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CenterOfAttention.java\nindex 27171867f8..c1f31cace9 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CenterOfAttention.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CenterOfAttention.java\n@@ -222,16 +222,16 @@ public class Skill_CenterOfAttention extends BardSkill\n \t\tif(!CMLib.flags().isAliveAwakeMobileUnbound(mob,false))\r\n \t\t\treturn false;\r\n \r\n-\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n-\t\t\treturn false;\r\n-\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth performing for."));\r\n \t\t\treturn false;\r\n \t\t}\r\n \r\n+\t\tif(!super.invoke(mob,commands,givenTarget,auto,asLevel))\r\n+\t\t\treturn false;\r\n+\r\n \t\tfinal boolean success=proficiencyCheck(mob,0,auto);\r\n \t\tif(success)\r\n \t\t{\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CurtainCall.java b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CurtainCall.java\nindex 2232cc4265..40ef672dd9 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CurtainCall.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_CurtainCall.java\n@@ -157,7 +157,7 @@ public class Skill_CurtainCall extends BardSkill\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be any other actors here."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_ExitStageLeft.java b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_ExitStageLeft.java\nindex 1332fe3dac..d5602e346a 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_ExitStageLeft.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_ExitStageLeft.java\n@@ -150,7 +150,7 @@ public class Skill_ExitStageLeft extends BardSkill\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth exiting."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_Upstage.java b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_Upstage.java\nindex 727bea8793..9f657c36db 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Songs/Skill_Upstage.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Songs/Skill_Upstage.java\n@@ -154,7 +154,7 @@ public class Skill_Upstage extends BardSkill\n \t\t\treturn false;\r\n \r\n \t\tfinal Set<MOB> h=this.properTargets(mob, givenTarget, auto);\r\n-\t\tif(h.size()==1)\r\n+\t\tif((h==null)||(h.size()==0)||(h.size()==1))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone to upstage."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_AcidFog.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_AcidFog.java\nindex 36684b410f..2acd7412a3 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_AcidFog.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_AcidFog.java\n@@ -141,7 +141,7 @@ public class Spell_AcidFog extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth melting."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ChainLightening.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ChainLightening.java\nindex 06ffa0be5d..ca01a8ceaa 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ChainLightening.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ChainLightening.java\n@@ -77,8 +77,11 @@ public class Spell_ChainLightening extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, boolean auto, final int asLevel)\r\n \t{\r\n \t\tSet<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n-\t\t\th=new HashSet<MOB>();\r\n+\t\tif((h==null)||(h.size()==0))\r\n+\t\t{\r\n+\t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth striking at."));\r\n+\t\t\treturn false;\r\n+\t\t}\r\n \r\n \t\tfinal Set<MOB> myGroup=mob.getGroupMembers(new HashSet<MOB>());\r\n \t\tfinal List<MOB> targets=new ArrayList<MOB>(h);\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Cloudkill.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Cloudkill.java\nindex 12e1d314da..7d5f9e7281 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Cloudkill.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Cloudkill.java\n@@ -131,7 +131,7 @@ public class Spell_Cloudkill extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth clouding."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ColorSpray.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ColorSpray.java\nindex ae9931d775..b6050eab3c 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ColorSpray.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ColorSpray.java\n@@ -171,7 +171,7 @@ public class Spell_ColorSpray extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth color spraying."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Dragonfire.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Dragonfire.java\nindex 222f60e285..6df63a36f4 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Dragonfire.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Dragonfire.java\n@@ -84,7 +84,7 @@ public class Spell_Dragonfire extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth burning."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Earthquake.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Earthquake.java\nindex 3d85b193a2..35e57a0111 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Earthquake.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Earthquake.java\n@@ -157,7 +157,7 @@ public class Spell_Earthquake extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth shaking up."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Ensnare.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Ensnare.java\nindex 92b419221b..c320f99250 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Ensnare.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Ensnare.java\n@@ -144,7 +144,7 @@ public class Spell_Ensnare extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth ensnaring."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Fear.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Fear.java\nindex e834d4ee05..01fa1de6b2 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Fear.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Fear.java\n@@ -127,7 +127,7 @@ public class Spell_Fear extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tif(!auto)\r\n \t\t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth scaring."));\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_FlamingEnsnarement.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_FlamingEnsnarement.java\nindex 4512034249..987372ee7e 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_FlamingEnsnarement.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_FlamingEnsnarement.java\n@@ -167,7 +167,7 @@ public class Spell_FlamingEnsnarement extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth ensnaring."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ForkedLightning.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ForkedLightning.java\nindex 8cc8000ec8..ad4c4bcaba 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ForkedLightning.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_ForkedLightning.java\n@@ -77,7 +77,7 @@ public class Spell_ForkedLightning extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth electrocuting."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_IceStorm.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_IceStorm.java\nindex 6eaa491f61..cf78de261f 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_IceStorm.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_IceStorm.java\n@@ -83,7 +83,7 @@ public class Spell_IceStorm extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth storming."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassDisintegrate.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassDisintegrate.java\nindex 79d890af93..52da7a78f3 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassDisintegrate.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassDisintegrate.java\n@@ -77,7 +77,7 @@ public class Spell_MassDisintegrate extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tSet<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif((h==null)||(h.size()<0))\r\n+\t\tif((h==null)||(h.size()<=0))\r\n \t\t{\r\n \t\t\tif(mob.location().numItems()==0)\r\n \t\t\t{\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFeatherfall.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFeatherfall.java\nindex 8c7d049cef..a1df1abfad 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFeatherfall.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFeatherfall.java\n@@ -96,7 +96,7 @@ public class Spell_MassFeatherfall extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,false);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth floating."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFly.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFly.java\nindex dcd5e9dfe0..7d574deddd 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFly.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassFly.java\n@@ -93,7 +93,7 @@ public class Spell_MassFly extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,false);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth making fly."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHaste.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHaste.java\nindex 29c7d865c3..329561c559 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHaste.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHaste.java\n@@ -93,7 +93,7 @@ public class Spell_MassHaste extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,false);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth speeding up."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHold.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHold.java\nindex e90ab23b09..3d56d141c9 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHold.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassHold.java\n@@ -91,7 +91,7 @@ public class Spell_MassHold extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth putting to sleep."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassInvisibility.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassInvisibility.java\nindex afacd279d8..b72d8f827e 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassInvisibility.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassInvisibility.java\n@@ -93,7 +93,7 @@ public class Spell_MassInvisibility extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,false);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth making invisible."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSleep.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSleep.java\nindex a03e5d4cb0..1545ef59d6 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSleep.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSleep.java\n@@ -93,7 +93,7 @@ public class Spell_MassSleep extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth putting to sleep."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSlow.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSlow.java\nindex 1de47cb0ff..4e90b304cd 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSlow.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassSlow.java\n@@ -91,7 +91,7 @@ public class Spell_MassSlow extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth slowing down."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassWaterbreath.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassWaterbreath.java\nindex 5e10bcf228..b603867381 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassWaterbreath.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MassWaterbreath.java\n@@ -94,7 +94,7 @@ public class Spell_MassWaterbreath extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,false);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth casting this on."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MeteorStorm.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MeteorStorm.java\nindex a5bd5b28a3..ba174ab325 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MeteorStorm.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_MeteorStorm.java\n@@ -77,7 +77,7 @@ public class Spell_MeteorStorm extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth storming at."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarBurst.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarBurst.java\nindex b82f907668..08eca53632 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarBurst.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarBurst.java\n@@ -115,7 +115,7 @@ public class Spell_PlanarBurst extends Spell\n \t\t\treturn false;\r\n \r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth bursting."));\r\n \t\t\treturn false;\r\n@@ -164,7 +164,7 @@ public class Spell_PlanarBurst extends Spell\n \t\t\treturn false;\r\n \r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth bursting."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarEnthrall.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarEnthrall.java\nindex 4244759646..d8ab7c294b 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarEnthrall.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_PlanarEnthrall.java\n@@ -97,7 +97,7 @@ public class Spell_PlanarEnthrall extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth enthralling."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Web.java b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Web.java\nindex b80549bbdd..c0bfc6a053 100644\n--- a/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Web.java\n+++ b/com/planet_ink/coffee_mud/Abilities/Spells/Spell_Web.java\n@@ -158,7 +158,7 @@ public class Spell_Web extends Spell\n \tpublic boolean invoke(final MOB mob, final List<String> commands, final Physical givenTarget, final boolean auto, final int asLevel)\r\n \t{\r\n \t\tfinal Set<MOB> h=properTargets(mob,givenTarget,auto);\r\n-\t\tif(h==null)\r\n+\t\tif((h==null)||(h.size()==0))\r\n \t\t{\r\n \t\t\tmob.tell(L("There doesn\'t appear to be anyone here worth webbing."));\r\n \t\t\treturn false;\r\ndiff --git a/com/planet_ink/coffee_mud/Libraries/MUDFight.java b/com/planet_ink/coffee_mud/Libraries/MUDFight.java\nindex 57c6f050f6..b02e4591b3 100644\n--- a/com/planet_ink/coffee_mud/Libraries/MUDFight.java\n+++ b/com/planet_ink/coffee_mud/Libraries/MUDFight.java\n@@ -181,7 +181,6 @@ public class MUDFight extends StdLibrary implements CombatLibrary\n \t\t\t//BZ: The magical pre-cast mana-saving range check, pt 2/2\r\n \t\t\tif((A!=null)\r\n \t\t\t&&(h.size()>0)\r\n-\t\t\t&&(caster.getVictim()!=null)\r\n \t\t\t&&((A.minRange()>0)||((A.maxRange()>0))&&(A.maxRange()<9999))\r\n \t\t\t&&(A.abstractQuality()==Ability.QUALITY_MALICIOUS))\r\n \t\t\t{\r\n', 'commit_subject': 'a bunch of group target checks added to save player mana', 'repo_name': 'bozimmerman/CoffeeMud', 'committer': {'name': 'bozimmerman', 'email': 'dc45fe023482b3b890b5e574e53c1d051f45b1ef@zimmers.net', 'time_sec': '1613967623', 'tz_offset': '0', 'date': {'seconds': '1613967623'}}, 'commit_tree': '9ac7336dc8577013f37591b8f1f0056189f0caa0', 'commit_message': 'a bunch of group target checks added to save player mana\n\ngit-svn-id: 0cdf8356e41b2d8ccbb41bb76c82068fe80b2514@20515 0d6f1817-ed0e-0410-87c9-987e46238f29\n', 'language_name': 'HTML', 'license': 'apache-2.0', 'hunks': '@@ -181,7 +181,6 @@ public class MUDFight extends StdLibrary implements CombatLibrary\n \t\t\t//BZ: The magical pre-cast mana-saving range check, pt 2/2\r\n \t\t\tif((A!=null)\r\n \t\t\t&&(h.size()>0)\r\n-\t\t\t&&(caster.getVictim()!=null)\r\n \t\t\t&&((A.minRange()>0)||((A.maxRange()>0))&&(A.maxRange()<9999))\r\n \t\t\t&&(A.abstractQuality()==Ability.QUALITY_MALICIOUS))\r\n \t\t\t{\r\n', 'addition_count': 0, 'deletion_count': 1, 'src_file': 'a/com/planet_ink/coffee_mud/Libraries/MUDFight.java', 'tgt_file': 'b/com/planet_ink/coffee_mud/Libraries/MUDFight.java', 'file_extension': '.java', 'before_file': 'package com.planet_ink.coffee_mud.Libraries;\nimport com.planet_ink.coffee_mud.core.interfaces.*;\nimport com.planet_ink.coffee_mud.core.*;\nimport com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;\nimport com.planet_ink.coffee_mud.core.CMath.CompiledFormula;\nimport com.planet_ink.coffee_mud.core.collections.*;\nimport com.planet_ink.coffee_mud.Abilities.interfaces.*;\nimport com.planet_ink.coffee_mud.Areas.interfaces.*;\nimport com.planet_ink.coffee_mud.Behaviors.interfaces.*;\nimport com.planet_ink.coffee_mud.CharClasses.Fighter;\nimport com.planet_ink.coffee_mud.CharClasses.interfaces.*;\nimport com.planet_ink.coffee_mud.Commands.interfaces.*;\nimport com.planet_ink.coffee_mud.Common.interfaces.*;\nimport com.planet_ink.coffee_mud.Common.interfaces.AccountStats.PrideStat;\nimport com.planet_ink.coffee_mud.Common.interfaces.CMMsg.View;\nimport com.planet_ink.coffee_mud.Common.interfaces.PlayerStats.PlayerCombatStat;\nimport com.planet_ink.coffee_mud.Exits.interfaces.*;\nimport com.planet_ink.coffee_mud.Items.interfaces.*;\nimport com.planet_ink.coffee_mud.Locales.interfaces.*;\nimport com.planet_ink.coffee_mud.MOBS.interfaces.*;\nimport com.planet_ink.coffee_mud.MOBS.interfaces.MOB.Attrib;\nimport com.planet_ink.coffee_mud.Races.interfaces.*;\nimport com.planet_ink.coffee_mud.Libraries.interfaces.*;\n\nimport java.util.*;\n\n/*\n   Copyright 2004-2022 Bo Zimmerman\n\n   Licensed under the Apache License, Version 2.0 (the "License");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n\t   http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an "AS IS" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\npublic class MUDFight extends StdLibrary implements CombatLibrary\n{\n\t@Override\n\tpublic String ID()\n\t{\n\t\treturn "MUDFight";\n\t}\n\tstatic final char[] PARENS={\'(\',\')\'};\n\n\tpublic String \t\tlastStr\t\t\t= "";\n\tpublic long \t\tlastRes\t\t\t= 0;\n\tpublic Object[][][] hitWordIndex\t= null;\n\tpublic Object[][][] hitWordsChanged\t= null;\n\n\tprotected CMath.CompiledFormula\tattackAdjustmentFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tarmorAdjustmentFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tattackerFudgeBonusFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpAttackerFudgeBonusFormula\t= null;\n\tprotected CMath.CompiledFormula\tspellFudgeDamageFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpSpellFudgeDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tspellCritChanceFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpSpellCritChanceFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tspellCritDmgFormula\t\t\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpSpellCritDmgFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\ttargetedRangedDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpTargetedRangedDamageFormula\t= null;\n\tprotected CMath.CompiledFormula\trangedFudgeDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpRangedFudgeDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\ttargetedMeleeDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpTargetedMeleeDamageFormula\t= null;\n\tprotected CMath.CompiledFormula\tmeleeFudgeDamageFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpMeleeFudgeDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tstaticRangedDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tstaticMeleeDamageFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tweaponCritChanceFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpWeaponCritChanceFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tweaponCritDmgFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tpvpWeaponCritDmgFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tstateHitPointRecoverFormula\t\t= null;\n\tprotected CMath.CompiledFormula\tstateManaRecoverFormula\t\t\t= null;\n\tprotected CMath.CompiledFormula\tstateMovesRecoverFormula\t\t= null;\n\tprotected CMath.CompiledFormula totalCombatExperienceFormula\t= null;\n\tprotected CMath.CompiledFormula individualCombatExpFormula\t\t= null;\n\n\tprivate static final int ATTACK_ADJUSTMENT = 50;\n\n\t@Override\n\tpublic boolean activate()\n\t{\n\t\tattackAdjustmentFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_ATTACKADJUSTMENT));\n\t\tarmorAdjustmentFormula= CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_ARMORADJUSTMENT));\n\t\tattackerFudgeBonusFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_ATTACKFUDGEBONUS));\n\t\tpvpAttackerFudgeBonusFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPATTACKFUDGEBONUS));\n\t\tspellCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_CHANCESPELLCRIT));\n\t\tpvpSpellCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPCHANCESPELLCRIT));\n\t\tspellCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGESPELLCRIT));\n\t\tpvpSpellCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGESPELLCRIT));\n\t\ttargetedRangedDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGERANGEDTARGETED));\n\t\tpvpTargetedRangedDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGERANGEDTARGETED));\n\t\ttargetedMeleeDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEMELEETARGETED));\n\t\tpvpTargetedMeleeDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGEMELEETARGETED));\n\t\tstaticRangedDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGERANGEDSTATIC));\n\t\tstaticMeleeDamageFormula=CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEMELEESTATIC));\n\t\tweaponCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_CHANCEWEAPONCRIT));\n\t\tpvpWeaponCritChanceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPCHANCEWEAPONCRIT));\n\t\tweaponCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEWEAPONCRIT));\n\t\tpvpWeaponCritDmgFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGEWEAPONCRIT));\n\t\tspellFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGESPELLFUDGE));\n\t\tpvpSpellFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGESPELLFUDGE));\n\t\tmeleeFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGEMELEEFUDGE));\n\t\tpvpMeleeFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGEMELEEFUDGE));\n\t\trangedFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_DAMAGERANGEDFUDGE));\n\t\tpvpRangedFudgeDamageFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_PVPDAMAGERANGEDFUDGE));\n\n\t\tstateHitPointRecoverFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_HITPOINTRECOVER));\n\t\tstateManaRecoverFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_MANARECOVER));\n\t\tstateMovesRecoverFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_MOVESRECOVER));\n\n\t\ttotalCombatExperienceFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_TOTALCOMBATXP));\n\t\tindividualCombatExpFormula = CMath.compileMathExpression(CMProps.getVar(CMProps.Str.FORMULA_INDCOMBATXP));\n\n\t\tif(serviceClient==null)\n\t\t{\n\t\t\tname="THCombat"+Thread.currentThread().getThreadGroup().getName().charAt(0);\n\t\t\tserviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK, CMProps.getTickMillis(), TICKS_PER_SHIP_COMBAT);\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void propertiesLoaded()\n\t{\n\t\tactivate();\n\t}\n\n\t@Override\n\tpublic Set<MOB> allPossibleCombatants(final MOB mob, final boolean includePlayers)\n\t{\n\t\tfinal SHashSet<MOB> h=new SHashSet<MOB>();\n\t\tfinal Room thisRoom=mob.location();\n\t\tif(thisRoom==null)\n\t\t\treturn null;\n\t\tfinal Set<MOB> h1=mob.getGroupMembers(new HashSet<MOB>());\n\t\tfor(int m=0;m<thisRoom.numInhabitants();m++)\n\t\t{\n\t\t\tfinal MOB inhab=thisRoom.fetchInhabitant(m);\n\t\t\tif((inhab!=null)\n\t\t\t&&(inhab!=mob)\n\t\t\t&&(!h1.contains(inhab))\n\t\t\t&&(CMLib.flags().isSeeable(inhab)||(CMLib.flags().canMove(inhab)&&(!CMLib.flags().isCloaked(inhab))))\n\t\t\t&&((includePlayers)||inhab.isMonster()||mob.isMonster()))\n\t\t\t\th.addUnsafe(inhab);\n\t\t}\n\t\treturn h;\n\t}\n\n\t@Override\n\tpublic Set<MOB> properTargets(final Ability A, final MOB caster, final boolean includePlayers)\n\t{\n\t\tSet<MOB> h=null;\n\t\tif(A.abstractQuality()!=Ability.QUALITY_MALICIOUS)\n\t\t{\n\t\t\tif(caster.Name().equalsIgnoreCase("somebody"))\n\t\t\t\th=new SHashSet<MOB>();\n\t\t\telse\n\t\t\t\th=caster.getGroupMembers(new SHashSet<MOB>());\n\t\t\tfor (final MOB M : h)\n\t\t\t{\n\t\t\t\tif(M.location()!=caster.location())\n\t\t\t\t\th.remove(M);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif(caster.isInCombat())\n\t\t\t\th=allCombatants(caster);\n\t\t\telse\n\t\t\t\th=allPossibleCombatants(caster,includePlayers);\n\t\t\t//BZ: The magical pre-cast mana-saving range check, pt 2/2\n\t\t\t//BZ: The magical pre-cast mana-saving range check, pt 2/2\r\n\t\t\tif((A!=null)\r\n\t\t\t&&(h.size()>0)\r\n\t\t\t&&(caster.getVictim()!=null)\r\n\t\t\t&&((A.minRange()>0)||((A.maxRange()>0))&&(A.maxRange()<9999))\r\n\t\t\t&&(A.abstractQuality()==Ability.QUALITY_MALICIOUS))\r\n\t\t\t{\r\n\t\t\t\tint disqualified=0;\n\t\t\t\tfor(final MOB M : h)\n\t\t\t\t{\n\t\t\t\t\tif((A.minRange()>0)\n\t\t\t\t\t&&(((caster.getVictim()==M)&&(caster.rangeToTarget()<A.minRange()))\n\t\t\t\t\t  ||((M.getVictim()==caster)&&(M.rangeToTarget()<A.minRange())))\n\t\t\t\t\t)\tdisqualified++;\n\t\t\t\t\telse\n\t\t\t\t\tif((A.maxRange()>0)\n\t\t\t\t\t&&(A.maxRange()<99999)\n\t\t\t\t\t&&(((caster.getVictim()==M)&&(caster.rangeToTarget()>A.maxRange()))\n\t\t\t\t\t  ||((M.getVictim()==caster)&&(M.rangeToTarget()>A.maxRange())))\n\t\t\t\t\t)\tdisqualified++;\n\t\t\t\t}\n\t\t\t\tif(disqualified>=h.size())\n\t\t\t\t{\n\t\t\t\t\tcaster.tell(L("You aren\'t in range!"));\n\t\t\t\t\th.clear();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn h;\n\t}\n\n\t@Override\n\tpublic int adjustedAttackBonus(final MOB mob, final MOB target)\n\t{\n\t\tfinal int maxStr = mob.charStats().getMaxStat(CharStats.STAT_STRENGTH);\n\t\tint currStr = mob.charStats().getStat(CharStats.STAT_STRENGTH);\n\t\tint strBonus = 0;\n\t\tif(currStr > maxStr)\n\t\t{\n\t\t\tstrBonus = currStr - maxStr;\n\t\t\tcurrStr = maxStr;\n\t\t}\n\t\tint baseStr = mob.baseCharStats().getStat(CharStats.STAT_STRENGTH);\n\t\tif(baseStr > maxStr)\n\t\t\tbaseStr = maxStr;\n\t\tfinal double[] vars = {mob.phyStats().attackAdjustment(),\n\t\t\t\t\t\t currStr,\n\t\t\t\t\t\t baseStr,\n\t\t\t\t\t\t strBonus,\n\t\t\t\t\t\t (mob.curState().getHunger()<1)?1.0:0.0,\n\t\t\t\t\t\t (mob.curState().getThirst()<1)?1.0:0.0,\n\t\t\t\t\t\t (mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0\n\t\t\t\t\t\t};\n\t\treturn (int)Math.round(CMath.parseMathExpression(attackAdjustmentFormula, vars, 0.0));\n\t}\n\n\t@Override\n\tpublic int adjustedAttackBonus(final int baseAttack)\n\t{\n\t\tfinal double[] vars = {baseAttack,\n\t\t\t\t\t\tCharStats.VALUE_ALLSTATS_DEFAULT,\n\t\t\t\t\t\tCharStats.VALUE_ALLSTATS_DEFAULT,\n\t\t\t\t\t\t 0,//strength bonus\n\t\t\t\t\t\t 0.0,//hunger\n\t\t\t\t\t\t 0.0,//thirst\n\t\t\t\t\t\t 0.0//fatigue\n\t\t\t\t\t\t};\n\t\treturn (int)Math.round(CMath.parseMathExpression(attackAdjustmentFormula, vars, 0.0));\n\t}\n\n\t@Override\n\tpublic void postItemDamage(final MOB mob, final Item I, final Environmental tool, final int damageAmount, final int messageType, final String message)\n\t{\n\t\tif(mob==null)\n\t\t\treturn ;\n\t\tfinal Room R=mob.location();\n\t\tif(R==null)\n\t\t\treturn ;\n\t\tCMMsg 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);\n\t\tif(R.okMessage(mob,msg))\n\t\t{\n\t\t\tR.send(mob,msg);\n\t\t\tif(msg.value()<=0)\n\t\t\t{\n\t\t\t\tif(I.subjectToWearAndTear())\n\t\t\t\t{\n\t\t\t\t\tI.setUsesRemaining(I.usesRemaining()-damageAmount);\n\t\t\t\t\tI.recoverPhyStats(); // important relation to setuses -- for brittle\n\t\t\t\t\tif(I.usesRemaining()<=0)\n\t\t\t\t\t{\n\t\t\t\t\t\tI.setUsesRemaining(100);\n\t\t\t\t\t\tI.unWear();\n\t\t\t\t\t\tfinal MOB targetM = (I.owner() instanceof MOB)?(MOB)I.owner():mob;\n\t\t\t\t\t\tmsg=CMClass.getMsg(mob,targetM,I,CMMsg.MSG_OK_VISUAL,L("<O-NAME> is destroyed!"),null,L("<O-NAME> carried by <T-NAME> is destroyed!"));\n\t\t\t\t\t\tif(R.okMessage(mob,msg))\n\t\t\t\t\t\t\tR.send(mob,msg);\n\t\t\t\t\t\tI.destroy();\n\t\t\t\t\t\tmob.recoverPhyStats();\n\t\t\t\t\t\tmob.recoverCharStats();\n\t\t\t\t\t\tmob.recoverMaxState();\n\t\t\t\t\t\tR.recoverRoomStats();\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\tif(I.usesRemaining()<=10)\n\t\t\t\t\t{\n\t\t\t\t\t\tmob.tell(L("@x1 is looking really bad.",I.name(mob)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\tif(damageAmount > 0)\n\t\t\t\t{\n\t\t\t\t\tI.unWear();\n\t\t\t\t\tmsg=CMClass.getMsg(mob,null,I,CMMsg.MSG_OK_VISUAL,L("<O-NAME> is destroyed!"),null,L("<O-NAME> carried by <S-NAME> is destroyed!"));\n\t\t\t\t\tif(R.okMessage(mob,msg))\n\t\t\t\t\t\tR.send(mob,msg);\n\t\t\t\t\tI.destroy();\n\t\t\t\t\tmob.recoverPhyStats();\n\t\t\t\t\tmob.recoverCharStats();\n\t\t\t\t\tmob.recoverMaxState();\n\t\t\t\t\tR.recoverRoomStats();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean doTurnBasedCombat(final MOB mob, final Room R, final Area A)\n\t{\n\t\tint index = R.getCombatTurnMobIndex();\n\t\tMOB M=null;\n\t\tif(mob.actions() < 1.0)\n\t\t{\n\t\t\tif((index >= R.numInhabitants())||((M=R.fetchInhabitant(index))==mob)||(M==null)||(!M.isInCombat()))\n\t\t\t{\n\t\t\t\tif((index<0)||(index>=R.numInhabitants()-1))\n\t\t\t\t\tindex=-1;\n\t\t\t\tfor(index++;index<R.numInhabitants();index++)\n\t\t\t\t{\n\t\t\t\t\tM=R.fetchInhabitant(index);\n\t\t\t\t\tif((M!=null)&&(M.isInCombat()))\n\t\t\t\t\t{\n\t\t\t\t\t\tM.setActions((M.actions() - Math.floor(M.actions())) + (CMLib.flags().isSitting(M) ? M.phyStats().speed() / 2.0 : M.phyStats().speed()));\n\t\t\t\t\t\tR.setCombatTurnMobIndex(index);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\telse\n\t\tif((index < R.numInhabitants())&&((M=R.fetchInhabitant(index))!=null)&&(M!=mob)&&(M.isInCombat()))\n\t\t\tmob.setActions(mob.actions()-Math.floor(mob.actions()));\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int adjustedArmor(final MOB mob)\n\t{\n\t\tint currDex=mob.charStats().getStat(CharStats.STAT_DEXTERITY);\n\t\tfinal int maxDex = mob.charStats().getMaxStat(CharStats.STAT_DEXTERITY);\n\t\tint dexBonus = 0;\n\t\tif(currDex > maxDex)\n\t\t{\n\t\t\tdexBonus = currDex - maxDex;\n\t\t\tcurrDex = maxDex;\n\t\t}\n\t\tdouble baseDex=mob.baseCharStats().getStat(CharStats.STAT_DEXTERITY);\n\t\tif(baseDex > maxDex)\n\t\t\tbaseDex = maxDex;\n\n\t\tfinal double[] vars = {\n\t\t\t\tmob.phyStats().armor(),\n\t\t\t\tcurrDex,\n\t\t\t\tbaseDex,\n\t\t\t\tdexBonus,\n\t\t\t\t(mob.curState().getHunger()<1)?1.0:0.0,\n\t\t\t\t(mob.curState().getThirst()<1)?1.0:0.0,\n\t\t\t\t(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,\n\t\t\t\tCMLib.flags().isSitting(mob)?0.0:1.0,\n\t\t\t\tCMLib.flags().isSleeping(mob)?0.0:1.0\n\t\t\t\t};\n\t\treturn (int)Math.round(CMath.parseMathExpression(armorAdjustmentFormula, vars, 0.0));\n\t}\n\n\t@Override\n\tpublic int adjustedArmor(final int armorValue)\n\t{\n\t\tfinal double[] vars = {\n\t\t\t\tarmorValue,\n\t\t\t\tCharStats.VALUE_ALLSTATS_DEFAULT,\n\t\t\t\tCharStats.VALUE_ALLSTATS_DEFAULT,\n\t\t\t\t0, //dexBonus,\n\t\t\t\t0.0,//hungry\n\t\t\t\t0.0,//thirsty\n\t\t\t\t0.0,//fatigued\n\t\t\t\t1.0,//sitting\n\t\t\t\t1.0 //sleeping\n\t\t\t\t};\n\t\treturn (int)Math.round(CMath.parseMathExpression(armorAdjustmentFormula, vars, 0.0));\n\t}\n\n\t@Override\n\tpublic boolean rollToHit(final MOB attacker, final MOB defender)\n\t{\n\t\tif((attacker==null)||(defender==null))\n\t\t\treturn false;\n\t\tfinal double vars[] = {\n\t\t\tattacker.phyStats().level(),\n\t\t\tdefender.phyStats().level(),\n\t\t\tattacker.phyStats().level() > defender.phyStats().level() ? 1 : -1\n\t\t};\n\t\tfinal boolean isPVP=(attacker.isPlayer()&&defender.isPlayer());\n\t\tfinal int attackerFudgeBonusAmt = (int)Math.round(CMath.parseMathExpression(isPVP?pvpAttackerFudgeBonusFormula:attackerFudgeBonusFormula, vars, 0.0));\n\t\treturn rollToHit(adjustedAttackBonus(attacker,defender),adjustedArmor(defender),attackerFudgeBonusAmt);\n\t}\n\n\t@Override\n\tpublic boolean rollToHit(final int attack, final int defence, final int adjustment)\n\t{\n\t\tdouble myArmor= -((double)defence);\n\t\tif(myArmor==0)\n\t\t\tmyArmor=1.0;\n\t\telse\n\t\tif(myArmor<0.0)\n\t\t\tmyArmor=-CMath.div(1.0,myArmor);\n\t\tdouble hisAttack=attack;\n\t\tif(hisAttack==0.0)\n\t\t\thisAttack=1.0;\n\t\telse\n\t\tif(hisAttack<0.0)\n\t\t\thisAttack=-CMath.div(1.0,myArmor);\n\t\treturn CMLib.dice().normalizeAndRollLess((int)Math.round(50.0*(hisAttack/myArmor)) + adjustment);\n\t}\n\n\t@Override\n\tpublic Set<MOB> allCombatants(final MOB mob)\n\t{\n\t\tfinal SHashSet<MOB> h=new SHashSet<MOB>();\n\t\tfinal Room thisRoom=mob.location();\n\t\tif(thisRoom==null)\n\t\t\treturn null;\n\t\tif(!mob.isInCombat())\n\t\t\treturn null;\n\n\t\tSet<MOB> h1=null;\n\t\tif(mob.Name().equalsIgnoreCase("nobody"))\n\t\t\th1=new HashSet<MOB>();\n\t\telse\n\t\t\th1=mob.getGroupMembers(new HashSet<MOB>());\n\t\tfor(int m=0;m<thisRoom.numInhabitants();m++)\n\t\t{\n\t\t\tfinal MOB inhab=thisRoom.fetchInhabitant(m);\n\t\t\tif((inhab!=null)\n\t\t\t&&(inhab!=mob)\n\t\t\t&&((inhab==mob.getVictim())\n\t\t\t\t||((inhab!=mob)\n\t\t\t\t\t&&(inhab.getVictim()!=mob.getVictim())\n\t\t\t\t\t&&(CMLib.flags().isSeeable(inhab)||(!CMLib.flags().isCloaked(inhab)))\n\t\t\t\t\t&&(CMLib.flags().canMove(inhab)||CMLib.flags().isSeeable(inhab))\n\t\t\t\t\t&&(!h1.contains(inhab)))))\n\t\t\t\t h.addUnsafe(inhab);\n\t\t}\n\t\treturn h;\n\n\t}\n\n\t@Override\n\tpublic void makePeaceInGroup(final MOB mob)\n\t{\n\t\tfinal Set<MOB> myGroup=mob.getGroupMembers(new HashSet<MOB>());\n\t\tfor (final MOB mob2 : myGroup)\n\t\t{\n\t\t\tif(mob2.isInCombat()&&(myGroup.contains(mob2.getVictim())))\n\t\t\t\tmob2.makePeace(true);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void postPanic(final MOB mob, final CMMsg addHere)\n\t{\n\t\tif(mob==null)\n\t\t\treturn;\n\n\t\t// make sure he\'s not already dead, or with a pending death.\n\t\tif(mob.amDead())\n\t\t\treturn;\n\t\tif((addHere!=null)&&(addHere.trailerMsgs()!=null))\n\t\t{\n\t\t\tfor(final CMMsg msg : addHere.trailerMsgs())\n\t\t\t{\n\t\t\t\tif((msg.source()==mob)\n\t\t\t\t&&((msg.sourceMinor()==CMMsg.TYP_PANIC))\n\t\t\t\t   ||(msg.sourceMinor()==CMMsg.TYP_DEATH))\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tfinal CMMsg msg=CMClass.getMsg(mob,null,CMMsg.MSG_PANIC,null);\n\t\tif(addHere!=null)\n\t\t\taddHere.addTrailerMsg(msg);\n\t\telse\n\t\tif((mob.location()!=null)&&(mob.location().okMessage(mob,msg)))\n\t\t\tmob.location().send(mob,msg);\n\t}\n\n\t@Override\n\tpublic void postDeath(final MOB killerM, final MOB deadM, final CMMsg addHere)\n\t{\n\t\tif(deadM==null)\n\t\t\treturn;\n\t\tfinal Room deathRoom=deadM.location();\n\t\tif(deathRoom==null)\n\t\t\treturn;\n\n\t\t// make sure he\'s not already dead, or with a pending death.\n\t\tif(deadM.amDead())\n\t\t\treturn;\n\t\tif((addHere!=null)&&(addHere.trailerMsgs()!=null))\n\t\t{\n\t\t\tfor(final CMMsg msg : addHere.trailerMsgs())\n\t\t\t{\n\t\t\t\tif((msg.source()==deadM)\n\t\t\t\t&&((msg.sourceMinor()==CMMsg.TYP_PANIC))\n\t\t\t\t   ||(msg.sourceMinor()==CMMsg.TYP_DEATH))\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif((killerM!=null)&&(killerM.getVictim()==deadM))\n\t\t\thandleDamageSpamSummary(killerM);\n\n\t\tfinal String msp=CMLib.protocol().msp("death"+CMLib.dice().roll(1,7,0)+".wav",50);\n\t\tCMMsg msg=null;\n\t\tif((deadM.playerStats()!=null)\n\t\t&&(deadM.playerStats().getDeathPoof().length()>0))\n\t\t{\n\t\t\tfinal String dpoof=deadM.playerStats().getDeathPoof();\n\t\t\tmsg=CMClass.getMsg(deadM,null,killerM,\n\t\t\t\t\tCMMsg.MSG_OK_VISUAL,"^f^*^<FIGHT^>"+dpoof+"^</FIGHT^>^?^.\\n\\r"+msp,\n\t\t\t\t\tCMMsg.MSG_OK_VISUAL,null,\n\t\t\t\t\tCMMsg.MSG_DEATH,"^F^<FIGHT^>"+dpoof+"</FIGHT^>^?\\n\\r@x1"+msp);\n\t\t}\n\t\telse\n\t\tif(isKnockedOutUponDeath(deadM,killerM))\n\t\t{\n\t\t\tmsg=CMClass.getMsg(deadM,null,killerM,\n\t\t\t\t\tCMMsg.MSG_OK_VISUAL,L("^f^*^<FIGHT^>!!!!!!!!!!!!!!YOU ARE DEFEATED!!!!!!!!!!!!!!^</FIGHT^>^?^.\\n\\r@x1",msp),\n\t\t\t\t\tCMMsg.MSG_OK_VISUAL,null,\n\t\t\t\t\tCMMsg.MSG_DEATH,L("^F^<FIGHT^><S-NAME> is DEFEATED!!!^</FIGHT^>^?\\n\\r@x1",msp));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmsg=CMClass.getMsg(deadM,null,killerM,\n\t\t\t\t\tCMMsg.MSG_OK_VISUAL,L("^f^*^<FIGHT^>!!!!!!!!!!!!!!YOU ARE DEAD!!!!!!!!!!!!!!^</FIGHT^>^?^.\\n\\r@x1",msp),\n\t\t\t\t\tCMMsg.MSG_OK_VISUAL,null,\n\t\t\t\t\tCMMsg.MSG_DEATH,L("^F^<FIGHT^><S-NAME> is DEAD!!!^</FIGHT^>^?\\n\\r@x1",msp));\n\t\t}\n\t\tCMLib.map().sendGlobalMessage(deadM,CMMsg.TYP_DEATH, CMClass.getMsg(deadM,null,killerM, CMMsg.TYP_DEATH,null, CMMsg.TYP_DEATH,null, CMMsg.TYP_DEATH,null));\n\t\tfinal CMMsg msg2=CMClass.getMsg(deadM,null,killerM, CMMsg.MSG_DEATH,null, CMMsg.MSG_DEATH,null, CMMsg.MSG_DEATH,null);\n\t\tif(addHere!=null)\n\t\t{\n\t\t\tif(deathRoom.okMessage(deadM,msg2))\n\t\t\t{\n\t\t\t\taddHere.addTrailerMsg(msg);\n\t\t\t\taddHere.addTrailerMsg(msg2);\n\t\t\t}\n\t\t}\n\t\telse\n\t\tif(deathRoom.okMessage(deadM,msg))\n\t\t{\n\t\t\tdeathRoom.send(deadM,msg);\n\t\t\tif(deathRoom.okMessage(deadM,msg2))\n\t\t\t\tdeathRoom.send(deadM,msg2);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean postAttack(final MOB attacker, final MOB target, Item weapon)\n\t{\n\t\tif((attacker==null)||(!attacker.mayPhysicallyAttack(target)))\n\t\t\treturn false;\n\t\tif((weapon==null)\n\t\t&&(attacker.isAttributeSet(MOB.Attrib.AUTODRAW)))\n\t\t{\n\t\t\tCMLib.commands().postDraw(attacker,false,true);\n\t\t\tweapon=attacker.fetchWieldedItem();\n\t\t}\n\t\tfinal CMMsg msg=CMClass.getMsg(attacker,target,weapon,CMMsg.MSG_WEAPONATTACK,null);\n\t\tfinal Room R=target.location();\n\t\tif(R!=null)\n\t\t{\n\t\t\tif(R.okMessage(attacker,msg))\n\t\t\t{\n\t\t\t\tR.send(attacker,msg);\n\t\t\t\treturn msg.value()>0;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tprotected static boolean ownerSecurityCheck(final String ownerName, final MOB mob)\n\t{\n\t\treturn (ownerName.length()>0)\n\t\t\t &&(mob!=null)\n\t\t\t &&((mob.Name().equals(ownerName))\n\t\t\t\t||(mob.getLiegeID().equals(ownerName)&mob.isMarriedToLiege())\n\t\t\t\t||(CMLib.clans().checkClanPrivilege(mob, ownerName, Clan.Function.PROPERTY_OWNER)));\n\t}\n\n\t@Override\n\tpublic boolean mayIAttackThisVessel(final MOB mob, final PhysicalAgent defender)\n\t{\n\t\tString defenderOwnerName = (defender instanceof PrivateProperty) ? ((PrivateProperty)defender).getOwnerName() : "";\n\t\t// is this how we determine npc ships?\n\t\tif(((defenderOwnerName == null)||(defenderOwnerName.length()==0))&&(defender instanceof PrivateProperty))\n\t\t{\n\t\t\tif(!(defender instanceof ClanItem))\n\t\t\t\treturn true;\n\t\t\tdefenderOwnerName = ((ClanItem)defender).clanID();\n\t\t\tif((defenderOwnerName == null)||(defenderOwnerName.length()==0))\n\t\t\t\treturn true;\n\t\t\tfinal Clan C=CMLib.clans().getClan(defenderOwnerName);\n\t\t\tfinal Clan srcC=CMLib.clans().findConquerableClan(mob);\n\t\t\tif((C!=null)\n\t\t\t&&(srcC!=null)\n\t\t\t&&(C!=srcC)\n\t\t\t&&(srcC.getClanRelations(defenderOwnerName)==Clan.REL_WAR))\n\t\t\t\treturn true;\n\t\t}\n\t\tif(CMSecurity.isASysOp(mob) && mob.isAttributeSet(Attrib.PLAYERKILL))\n\t\t\treturn true;\n\t\tif(CMLib.flags().isUnattackable(defender))\n\t\t\treturn false;\n\t\tif(defender instanceof Boardable)\n\t\t{\n\t\t\tfinal Area otherArea = ((Boardable)defender).getArea();\n\t\t\tif(otherArea != null)\n\t\t\t{\n\t\t\t\tfor(final Enumeration<Room> r=otherArea.getProperMap();r.hasMoreElements();)\n\t\t\t\t{\n\t\t\t\t\tfinal Room R=r.nextElement();\n\t\t\t\t\tif(R!=null)\n\t\t\t\t\t{\n\t\t\t\t\t\tfor(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfinal MOB M=m.nextElement();\n\t\t\t\t\t\t\tif((M!=null)\n\t\t\t\t\t\t\t&&(ownerSecurityCheck(defenderOwnerName,M))\n\t\t\t\t\t\t\t&&(mob.mayIFight(M)))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\tif(defender instanceof Rideable)\n\t\t{\n\t\t\tfinal Rideable rideableDefender=(Rideable)defender;\n\t\t\tfor(int i=0;i<rideableDefender.numRiders();i++)\n\t\t\t{\n\t\t\t\tfinal Rider R=rideableDefender.fetchRider(i);\n\t\t\t\tif((R instanceof MOB)\n\t\t\t\t&&(!mob.mayIFight(R)))\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic final boolean isASiegeWeapon(final Item I)\n\t{\n\t\tif((I instanceof AmmunitionWeapon)\n\t\t&&(I instanceof Rideable)\n\t\t&&((AmmunitionWeapon)I).isFreeStanding()\n\t\t&&(((AmmunitionWeapon)I).requiresAmmunition()))\n\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean postSiegeAttack(final MOB attacker, final SiegableItem siegeAttacker, final SiegableItem siegeTarget, final Weapon weapon, final boolean wasAHit)\n\t{\n\t\t// if not in combat, howd you get here? if you are, this MUST happen\n\t\t//(!mayIAttack(attacker,attackingShip, target))\n\t\tif((attacker==null)||(weapon==null))\n\t\t\treturn false;\n\t\tfinal CMMsg msg=CMClass.getMsg(attacker,siegeTarget,weapon,CMMsg.MSG_WEAPONATTACK,null);\n\t\tfinal Room R=CMLib.map().roomLocation(siegeTarget);\n\t\tif(R!=null)\n\t\t{\n\t\t\tmsg.setValue(wasAHit?1:0);\n\t\t\tif(R.okMessage(attacker,msg))\n\t\t\t{\n\t\t\t\tR.send(attacker,msg);\n\t\t\t\treturn wasAHit;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean postHealing(MOB healer,\n\t\t\t\t\t\t\t   final MOB target,\n\t\t\t\t\t\t\t   final Ability tool,\n\t\t\t\t\t\t\t   final int healing,\n\t\t\t\t\t\t\t   final int messageCode,\n\t\t\t\t\t\t\t   final String allDisplayMessage)\n\t{\n\t\tif(healer==null)\n\t\t\thealer=target;\n\t\tif((healer==null)||(target==null)||(target.location()==null))\n\t\t\treturn false;\n\t\tfinal CMMsg msg=CMClass.getMsg(healer,target,tool,messageCode,CMMsg.MSG_HEALING,messageCode,allDisplayMessage);\n\t\tmsg.setValue(healing);\n\t\tfinal Room R=target.location();\n\t\tif(R!=null)\n\t\t{\n\t\t\tif(R.okMessage(target,msg))\n\t\t\t{\n\t\t\t\tR.send(target,msg);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic String replaceDamageTag(final String str, final int damage, final int damageType, final View sourceTargetSTO)\n\t{\n\t\tif(str==null)\n\t\t\treturn null;\n\t\tfinal int replace=str.indexOf("<DAMAGE");\n\t\tif(replace < 0)\n\t\t\treturn str;\n\t\tif(str.length() < replace+9)\n\t\t\treturn str;\n\t\tfinal boolean damages = (str.charAt(replace+7)==\'S\') && (str.charAt(replace+8)==\'>\');\n\t\tfinal boolean damage_ = (str.charAt(replace+7)==\'-\') && (str.charAt(replace+8)==\'>\');\n\t\tfinal String showDamage = CMProps.getVar(CMProps.Str.SHOWDAMAGE);\n\t\tfinal boolean showNumbers = showDamage.equalsIgnoreCase("YES")\n\t\t\t\t\t\t\t\t||((sourceTargetSTO==CMMsg.View.SOURCE)&&showDamage.equalsIgnoreCase("SOURCE"))\n\t\t\t\t\t\t\t\t||((sourceTargetSTO==CMMsg.View.TARGET)&&showDamage.equalsIgnoreCase("TARGET"));\n\t\tif(damages)\n\t\t{\n\t\t\tfinal String hitWord=CMStrings.deleteAllofAny(standardHitWord(damageType,damage),PARENS);\n\t\t\tif(!showNumbers)\n\t\t\t\treturn str.substring(0,replace)+hitWord+str.substring(replace+9);\n\t\t\treturn str.substring(0,replace)+hitWord+" ("+damage+")"+ str.substring(replace+9);\n\t\t}\n\t\telse\n\t\tif(damage_)\n\t\t{\n\t\t\tString hitWord=standardHitWord(damageType,damage);\n\t\t\tif(hitWord.indexOf(\')\')>=0)\n\t\t\t{\n\t\t\t\tfinal String[][] allPlurals=new String[][] {\n\t\t\t\t\t{"(S)",""},{"(s)",""},{"(YS)",""},{"(ys)",""}\n\t\t\t\t};\n\t\t\t\thitWord=CMStrings.replaceAlls(hitWord, allPlurals);\n\t\t\t}\n\t\t\tif(!showNumbers)\n\t\t\t\treturn str.substring(0,replace)+hitWord+str.substring(replace+9);\n\t\t\treturn str.substring(0,replace)+hitWord+" ("+damage+")"+ str.substring(replace+9);\n\t\t}\n\t\telse\n\t\tif(str.charAt(replace+7)==\'>\')\n\t\t{\n\t\t\tif(!showNumbers)\n\t\t\t\treturn str.substring(0,replace)+standardHitWord(damageType,damage)+str.substring(replace+8);\n\t\t\treturn str.substring(0,replace)+standardHitWord(damageType,damage)+" ("+damage+")"+ str.substring(replace+8);\n\t\t}\n\t\treturn str;\n\t}\n\n\t@Override\n\tpublic void forcePeaceAllFightingAgainst(final MOB mob, final Set<MOB> exceptionSet)\n\t{\n\t\tfinal Room R=mob.location();\n\t\tif(R==null)\n\t\t\treturn;\n\t\tfor(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)\n\t\t{\n\t\t\tfinal MOB M=m.nextElement();\n\t\t\tif((M!=null)\n\t\t\t&&(M.getVictim()==mob)\n\t\t\t&&(M!=mob)\n\t\t\t&&((exceptionSet==null)||(!exceptionSet.contains(M))))\n\t\t\t\tM.setVictim(null);\n\t\t}\n\t}\n\n\t@Override\n\tpublic Set<MOB> getAllFightingAgainst(final MOB mob, Set<MOB> set)\n\t{\n\t\tif(set == null)\n\t\t\tset=new HashSet<MOB>(1);\n\t\tfinal Room R=mob.location();\n\t\tif(R==null)\n\t\t\treturn set;\n\t\tfor(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)\n\t\t{\n\t\t\tfinal MOB M=m.nextElement();\n\t\t\tif((M!=null)&&(M.getVictim()==mob)&&(M!=mob))\n\t\t\t\tset.add(M);\n\t\t}\n\t\treturn set;\n\t}\n\n\t@Override\n\tpublic void postDamage(final MOB attacker, final MOB target, final Environmental weapon,\n\t\t\t\t\t\t   int damage, final int messageCode, final int damageType, String allDisplayMessage)\n\t{\n\t\tif((attacker==null)||(target==null)||(target.location()==null))\n\t\t\treturn;\n\t\tif(allDisplayMessage!=null)\n\t\t\tallDisplayMessage="^F^<FIGHT^>"+allDisplayMessage+"^</FIGHT^>^?";\n\n\t\tfinal int damageTypeMsg;\n\t\tfinal int srcMsgCode = messageCode;\n\t\tString srcMsgStr=allDisplayMessage;\n\t\tif(attacker != target)\n\t\t{\n\t\t\tif(weapon instanceof Ability)\n\t\t\t{\n\t\t\t\tif((damage>0)\n\t\t\t\t&&(attacker.isMine(weapon)||(attacker.phyStats().level()>1))) // why >1? because quickly made fake-mobs tend to have lvl=1\n\t\t\t\t\tdamage = modifySpellDamage(attacker, target, damage);\n\t\t\t\tif((((Ability)weapon).affecting() instanceof Item)\n\t\t\t\t&&(((Item)((Ability)weapon).affecting()).owner()==attacker)\n\t\t\t\t&&((((Ability)weapon).classificationCode()&Ability.ALL_ACODES)==Ability.ACODE_PROPERTY)\n\t\t\t\t&&(attacker.isAttributeSet(Attrib.NOBATTLESPAM)))\n\t\t\t\t\tsrcMsgStr=null;\n\t\t\t}\n\t\t\tdamageTypeMsg = CMMsg.TYP_DAMAGE | (CMMsg.MASK_MALICIOUS & messageCode);\n\t\t}\n\t\telse\n\t\t\tdamageTypeMsg = CMMsg.TYP_DAMAGE;\n\n\t\tfinal CMMsg msg=CMClass.getMsg(attacker,target,weapon,srcMsgCode,srcMsgStr,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  damageTypeMsg,allDisplayMessage,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  messageCode,allDisplayMessage);\n\t\tmsg.setValue(damage);\n\t\tCMLib.color().fixSourceFightColor(msg);\n\t\tfinal Room R=target.location();\n\t\tif(R!=null)\n\t\t{\n\t\t\tif(R.okMessage(target,msg))\n\t\t\t{\n\t\t\t\tif(damageType>=0)\n\t\t\t\t{\n\t\t\t\t\tmsg.modify(msg.source(),\n\t\t\t\t\t\t\t   msg.target(),\n\t\t\t\t\t\t\t   msg.tool(),\n\t\t\t\t\t\t\t   msg.sourceCode(),\n\t\t\t\t\t\t\t   replaceDamageTag(msg.sourceMessage(),msg.value(),damageType,CMMsg.View.SOURCE),\n\t\t\t\t\t\t\t   msg.targetCode(),\n\t\t\t\t\t\t\t   replaceDamageTag(msg.targetMessage(),msg.value(),damageType,CMMsg.View.TARGET),\n\t\t\t\t\t\t\t   msg.othersCode(),\n\t\t\t\t\t\t\t   replaceDamageTag(msg.othersMessage(),msg.value(),damageType,CMMsg.View.OTHERS));\n\t\t\t\t}\n\t\t\t\tR.send(target,msg);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic int modifySpellDamage(final MOB attacker, final MOB target, int baseDamage)\n\t{\n\t\tfinal int maxInt = attacker.charStats().getMaxStat(CharStats.STAT_INTELLIGENCE);\n\t\tint currInt = attacker.charStats().getStat(CharStats.STAT_INTELLIGENCE);\n\t\tint intBonus = 0;\n\t\tif(currInt > maxInt)\n\t\t{\n\t\t\tintBonus = currInt - maxInt;\n\t\t\tcurrInt = maxInt;\n\t\t}\n\t\tint baseInt = attacker.baseCharStats().getStat(CharStats.STAT_INTELLIGENCE);\n\t\tif(baseInt > maxInt)\n\t\t\tbaseInt = maxInt;\n\t\tfinal double[] vars = {\n\t\t\t\tbaseDamage,\n\t\t\t\tcurrInt,\n\t\t\t\tbaseInt,\n\t\t\t\tintBonus,\n\t\t\t\t(attacker.curState().getHunger()<1)?1.0:0.0,\n\t\t\t\t(attacker.curState().getThirst()<1)?1.0:0.0,\n\t\t\t\t(attacker.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,\n\t\t\t\tattacker.phyStats().level(),\n\t\t\t\ttarget.phyStats().level()\n\t\t\t\t};\n\t\tfinal boolean isPVP=(attacker.isPlayer()&&target.isPlayer());\n\t\tbaseDamage = (int)Math.round(CMath.parseMathExpression(isPVP?pvpSpellFudgeDamageFormula:spellFudgeDamageFormula, vars, 0.0));\n\t\tvars[0]=baseDamage;\n\t\tfinal int spellCritChancePct = (int)Math.round(CMath.parseMathExpression(isPVP?pvpSpellCritChanceFormula:spellCritChanceFormula, vars, 0.0))\n\t\t\t\t\t\t\t\t\t+ attacker.charStats().getStat(CharStats.STAT_CRIT_CHANCE_PCT_MAGIC);\n\t\tif(CMLib.dice().rollPercentage()<spellCritChancePct)\n\t\t{\n\t\t\tfinal int spellCritDamageAmt = (int)Math.round(CMath.parseMathExpression(isPVP?pvpSpellCritDmgFormula:spellCritDmgFormula, vars, 0.0));\n\t\t\tbaseDamage+=spellCritDamageAmt;\n\t\t\tif(attacker.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_MAGIC)>0)\n\t\t\t\tbaseDamage += (int)Math.round(CMath.mul(spellCritDamageAmt,CMath.div(attacker.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_MAGIC),100.0)));\n\t\t}\n\t\treturn baseDamage;\n\t}\n\n\t@Override\n\tpublic int adjustedDamage(final MOB mob, final Weapon weapon, final MOB target, final int bonusDamage, final boolean allowCrits, final boolean biasHigh)\n\t{\n\t\tdouble damageAmount=0.0;\n\t\tPhysical useDmg = null;\n\t\tboolean rangedAttack = false;\n\t\tif((weapon!=null)\n\t\t&&((weapon.weaponClassification()==Weapon.CLASS_RANGED)||(weapon.weaponClassification()==Weapon.CLASS_THROWN)))\n\t\t{\n\t\t\tuseDmg = weapon;\n\t\t\trangedAttack = true;\n\t\t}\n\t\telse\n\t\t\tuseDmg = mob;\n\t\tfinal boolean isPVP=((target!=null)&&mob.isPlayer()&&target.isPlayer());\n\t\tfinal int maxStr = mob.charStats().getMaxStat(CharStats.STAT_STRENGTH);\n\t\tint currStr = mob.charStats().getStat(CharStats.STAT_STRENGTH);\n\t\tif(currStr > maxStr)\n\t\t\tcurrStr = maxStr;\n\t\tif(target!=null)\n\t\t{\n\t\t\tfinal double[] vars = {\n\t\t\t\t\tuseDmg.phyStats().damage()+bonusDamage,\n\t\t\t\t\tcurrStr,\n\t\t\t\t\tmob.phyStats().level(),\n\t\t\t\t\ttarget.phyStats().level(),\n\t\t\t\t\t(mob.curState().getHunger()<1)?1.0:0.0,\n\t\t\t\t\t(mob.curState().getThirst()<1)?1.0:0.0,\n\t\t\t\t\t(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,\n\t\t\t\t\tCMLib.flags().canBeSeenBy(target,mob)?0:1,\n\t\t\t\t\tCMLib.flags().isSleeping(target)?1:0,\n\t\t\t\t\tCMLib.flags().isSitting(target)?1:0\n\t\t\t};\n\t\t\tCMath.CompiledFormula formula;\n\t\t\tif(rangedAttack)\n\t\t\t\tformula = isPVP?pvpTargetedRangedDamageFormula:targetedRangedDamageFormula;\n\t\t\telse\n\t\t\t\tformula = isPVP?pvpTargetedMeleeDamageFormula:targetedMeleeDamageFormula;\n\t\t\tif(biasHigh)\n\t\t\t\tdamageAmount = CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0);\n\t\t\telse\n\t\t\t\tdamageAmount = CMath.parseMathExpression(formula, vars, 0.0);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfinal double[] vars = {\n\t\t\t\t\tuseDmg.phyStats().damage()+bonusDamage,\n\t\t\t\t\tcurrStr,\n\t\t\t\t\tmob.phyStats().level(),\n\t\t\t\t\t0,\n\t\t\t\t\t(mob.curState().getHunger()<1)?1.0:0.0,\n\t\t\t\t\t(mob.curState().getThirst()<1)?1.0:0.0,\n\t\t\t\t\t(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\t0\n\t\t\t};\n\t\t\tCMath.CompiledFormula formula;\n\t\t\tif((weapon!=null)&&((weapon.weaponClassification()==Weapon.CLASS_RANGED)||(weapon.weaponClassification()==Weapon.CLASS_THROWN)))\n\t\t\t\tformula = staticRangedDamageFormula;\n\t\t\telse\n\t\t\t\tformula = staticMeleeDamageFormula;\n\t\t\tif(biasHigh)\n\t\t\t\tdamageAmount = CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0);\n\t\t\telse\n\t\t\t\tdamageAmount = CMath.parseMathExpression(formula, vars, 0.0);\n\t\t}\n\n\t\tfinal int maxDex = mob.charStats().getMaxStat(CharStats.STAT_DEXTERITY);\n\t\tint currDex = mob.charStats().getStat(CharStats.STAT_DEXTERITY);\n\t\tint dexBonus = 0;\n\t\tif(currDex > maxDex)\n\t\t{\n\t\t\tdexBonus = currDex - maxDex;\n\t\t\tcurrDex = maxDex;\n\t\t}\n\t\tint baseDex = mob.baseCharStats().getStat(CharStats.STAT_DEXTERITY);\n\t\tif(baseDex > maxDex)\n\t\t\tbaseDex = maxDex;\n\n\t\tfinal double[] vars = {\n\t\t\t\tdamageAmount,\n\t\t\t\tcurrDex,\n\t\t\t\tbaseDex,\n\t\t\t\tdexBonus,\n\t\t\t\t(mob.curState().getHunger()<1)?1.0:0.0,\n\t\t\t\t(mob.curState().getThirst()<1)?1.0:0.0,\n\t\t\t\t(mob.curState().getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,\n\t\t\t\tmob.phyStats().level(),\n\t\t\t\t(target==null)?0:target.phyStats().level()\n\t\t};\n\t\tif(allowCrits)\n\t\t{\n\t\t\tfinal int weaponCritChancePct = (int)Math.round(CMath.parseMathExpression(isPVP?pvpWeaponCritChanceFormula:weaponCritChanceFormula, vars, 0.0))\n\t\t\t\t\t\t\t\t\t\t\t+ mob.charStats().getStat(CharStats.STAT_CRIT_CHANCE_PCT_WEAPON);\n\t\t\tif((CMLib.dice().rollPercentage()<weaponCritChancePct)||(biasHigh))\n\t\t\t{\n\t\t\t\tfinal CMath.CompiledFormula formula = isPVP?pvpWeaponCritDmgFormula:weaponCritDmgFormula;\n\t\t\t\tfinal int weaponCritDmgAmt;\n\t\t\t\tif(biasHigh)\n\t\t\t\t\tweaponCritDmgAmt = (int)Math.round(CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0));\n\t\t\t\telse\n\t\t\t\t\tweaponCritDmgAmt = (int)Math.round(CMath.parseMathExpression(formula, vars, 0.0));\n\t\t\t\tdamageAmount += weaponCritDmgAmt;\n\t\t\t\tif(mob.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_WEAPON)>0)\n\t\t\t\t\tdamageAmount += (int)Math.round(CMath.mul(weaponCritDmgAmt,CMath.div(mob.charStats().getStat(CharStats.STAT_CRIT_DAMAGE_PCT_WEAPON),100.0)));\n\t\t\t}\n\t\t}\n\t\tif(target != null)\n\t\t{\n\t\t\tvars[0] = damageAmount;\n\t\t\tCMath.CompiledFormula formula;\n\t\t\tif(rangedAttack)\n\t\t\t\tformula = isPVP?pvpRangedFudgeDamageFormula:rangedFudgeDamageFormula;\n\t\t\telse\n\t\t\t\tformula = isPVP?pvpMeleeFudgeDamageFormula:meleeFudgeDamageFormula;\n\t\t\tif(biasHigh)\n\t\t\t\tdamageAmount = CMath.parseMathExpression(formula, CMath.NotRandomHigh, vars, 0.0);\n\t\t\telse\n\t\t\t\tdamageAmount = CMath.parseMathExpression(formula, vars, 0.0);\n\t\t}\n\t\treturn (int)Math.round(damageAmount);\n\t}\n\n\t@Override\n\tpublic int adjustedDamage(final int baseDamage, final int level, final boolean biasHigh)\n\t{\n\t\tdouble damageAmount=0.0;\n\t\tfinal double[] vars = {\n\t\t\t\tbaseDamage,\n\t\t\t\tCharStats.VALUE_ALLSTATS_DEFAULT,\n\t\t\t\tlevel,\n\t\t\t\t0,\n\t\t\t\t0.0,//hunger\n\t\t\t\t0.0,//thirst\n\t\t\t\t0.0,//fatigue\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\t0\n\t\t};\n\t\tif(biasHigh)\n\t\t\tdamageAmount = CMath.parseMathExpression(staticMeleeDamageFormula, CMath.NotRandomHigh, vars, 0.0);\n\t\telse\n\t\t\tdamageAmount = CMath.parseMathExpression(staticMeleeDamageFormula, vars, 0.0);\n\t\treturn (int)Math.round(damageAmount);\n\t}\n\n\t@Override\n\tpublic void recoverTick(final MOB mob)\n\t{\n\t\tif((mob!=null)\n\t\t&&(!mob.isInCombat()))\n\t\t{\n\t\t\tfinal CharStats charStats=mob.charStats();\n\t\t\tfinal CharState curState=mob.curState();\n\t\t\tfinal CharState maxState=mob.maxState();\n\t\t\tif((curState.getHitPoints()<maxState.getHitPoints())\n\t\t\t||(curState.getMana()<maxState.getMana())\n\t\t\t||(curState.getMovement()<maxState.getMovement()))\n\t\t\t{\n\t\t\t\tfinal Room R=mob.location();\n\t\t\t\tfinal Area A=(R!=null)?R.getArea():null;\n\t\t\t\tif((A instanceof Boardable)\n\t\t\t\t&&(((Boardable)A).getBoardableItem() instanceof Combatant)\n\t\t\t\t&&(((Combatant)((Boardable)A).getBoardableItem()).isInCombat()))\n\t\t\t\t\treturn;\n\n\t\t\t\tfinal Rideable riding=mob.riding();\n\t\t\t\tfinal boolean isSleeping=CMLib.flags().isSleeping(mob);\n\t\t\t\tfinal boolean bedBonus=isSleeping && (riding!=null) && (riding.rideBasis()==Rideable.Basis.FURNITURE_SLEEP);\n\t\t\t\tfinal boolean isSittingOrRiding=((!isSleeping) && ((CMLib.flags().isSitting(mob))||(mob.riding()!=null))) || bedBonus;\n\t\t\t\tfinal boolean isFlying=((!isSleeping) && (!isSittingOrRiding) && CMLib.flags().isFlying(mob)) || bedBonus;\n\t\t\t\tfinal boolean isSwimming=(!isSleeping) && (!isSittingOrRiding) && (!isFlying) && CMLib.flags().isSwimming(mob);\n\t\t\t\tfinal double[] vals=new double[]{\n\t\t\t\t\tcharStats.getStat(CharStats.STAT_CONSTITUTION),\n\t\t\t\t\tmob.phyStats().level(),\n\t\t\t\t\t(curState.getHunger()<1)?1.0:0.0,\n\t\t\t\t\t(curState.getThirst()<1)?1.0:0.0,\n\t\t\t\t\t(curState.getFatigue()>CharState.FATIGUED_MILLIS)?1.0:0.0,\n\t\t\t\t\tisSleeping?1.0:0.0,\n\t\t\t\t\tisSittingOrRiding?1.0:0.0,\n\t\t\t\t\tisFlying?1.0:0.0,\n\t\t\t\t\tisSwimming?1.0:0.0\n\t\t\t\t};\n\t\t\t\tfinal double hpGain = CMath.parseMathExpression(stateHitPointRecoverFormula, vals, 0.0);\n\n\t\t\t\tif((hpGain>0)&&(!CMLib.flags().isGolem(mob)))\n\t\t\t\t\tcurState.adjHitPoints((int)Math.round(hpGain),maxState);\n\n\t\t\t\tvals[0]=((charStats.getStat(CharStats.STAT_INTELLIGENCE)+charStats.getStat(CharStats.STAT_WISDOM)));\n\t\t\t\tfinal double manaGain = CMath.parseMathExpression(stateManaRecoverFormula, vals, 0.0);\n\n\t\t\t\tif(manaGain>0)\n\t\t\t\t\tcurState.adjMana((int)Math.round(manaGain),maxState);\n\n\t\t\t\tvals[0]=charStats.getStat(CharStats.STAT_STRENGTH);\n\t\t\t\tfinal double moveGain = CMath.parseMathExpression(this.stateMovesRecoverFormula, vals, 0.0);\n\n\t\t\t\tif(moveGain>0)\n\t\t\t\t\tcurState.adjMovement((int)Math.round(moveGain),maxState);\n\n\t\t\t\tif(((hpGain>0)||(manaGain>0)||(moveGain>0))\n\t\t\t\t&&(!isSleeping)\n\t\t\t\t&&(!isSittingOrRiding))\n\t\t\t\t\tmob.curState().adjFatigue(Math.round(CMProps.getTickMillis()), mob.maxState());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic CMMsg postWeaponDamage(final MOB source, final MOB target, final Item item, int damageInt)\n\t{\n\t\tint damageType=Weapon.TYPE_BASHING;\n\t\tWeapon weapon=null;\n\t\tif(item instanceof Weapon)\n\t\t{\n\t\t\tweapon=(Weapon)item;\n\t\t\tdamageType=weapon.weaponDamageType();\n\t\t}\n\t\t// calculate Base Damage (with Strength bonus)\n\t\tfinal String oldHitString="^F^<FIGHT^>"+((weapon!=null)?\n\t\t\t\t\t\t\tweapon.hitString(damageInt):\n\t\t\t\t\t\t\tstandardHitString(Weapon.TYPE_NATURAL,Weapon.CLASS_BLUNT,damageInt,item.name()))+"^</FIGHT^>^?";\n\t\tfinal CMMsg msg=CMClass.getMsg(source,\n\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\titem,\n\t\t\t\t\t\t\t\tCMMsg.MSG_OK_VISUAL,\n\t\t\t\t\t\t\t\tCMMsg.MSG_DAMAGE,\n\t\t\t\t\t\t\t\tCMMsg.MSG_OK_VISUAL,\n\t\t\t\t\t\t\t\toldHitString);\n\t\tCMLib.color().fixSourceFightColor(msg);\n\n\t\tmsg.setValue(damageInt);\n\t\t// why was there no okaffect here?\n\t\tfinal Room room=source.location();\n\t\tif((room!=null)&&(room.okMessage(source,msg)))\n\t\t{\n\t\t\tif(msg.targetMinor()==CMMsg.TYP_DAMAGE)\n\t\t\t{\n\t\t\t\tdamageInt=msg.value();\n\t\t\t\tmsg.modify(msg.source(),\n\t\t\t\t\t\t   msg.target(),\n\t\t\t\t\t\t   msg.tool(),\n\t\t\t\t\t\t   msg.sourceCode(),\n\t\t\t\t\t\t   replaceDamageTag(msg.sourceMessage(),msg.value(),damageType,CMMsg.View.SOURCE),\n\t\t\t\t\t\t   msg.targetCode(),\n\t\t\t\t\t\t   replaceDamageTag(msg.targetMessage(),msg.value(),damageType,CMMsg.View.TARGET),\n\t\t\t\t\t\t   msg.othersCode(),\n\t\t\t\t\t\t   replaceDamageTag(msg.othersMessage(),msg.value(),damageType,CMMsg.View.OTHERS));\n\t\t\t}\n\t\t\tif(source.mayIFight(target))\n\t\t\t{\n\t\t\t\tif((msg.source().riding() instanceof Boardable)\n\t\t\t\t&&(CMLib.combat().isASiegeWeapon(item)))\n\t\t\t\t{\n\t\t\t\t\troom.send(source,msg);\n\t\t\t\t\treturn msg;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\tif((source.location()==room)\n\t\t\t\t&&(target.location()==room))\n\t\t\t\t{\n\t\t\t\t\troom.send(source,msg);\n\t\t\t\t\treturn msg;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic CMMsg postWeaponAttackResult(final MOB sourceM, final MOB targetM, final Item item, final int bonusDmg, final boolean success)\n\t{\n\t\tif(sourceM==null)\n\t\t\treturn null;\n\t\tif(!sourceM.mayIFight(targetM))\n\t\t\treturn null;\n\t\tWeapon weapon=null;\n\t\tint damageInt = 0;\n\t\tif(item instanceof Weapon)\n\t\t{\n\t\t\tweapon=(Weapon)item;\n\t\t\tdamageInt=adjustedDamage(sourceM,weapon,targetM,0,true,false);\n\t\t\tif(damageInt > 0)\n\t\t\t\tdamageInt += bonusDmg;\n\t\t\tif(damageInt < 0)\n\t\t\t\tdamageInt = 0;\n\t\t}\n\t\tif(success)\n\t\t{\n\t\t\tif((sourceM.playerStats()!=null)\n\t\t\t&&(sourceM!=targetM))\n\t\t\t\tsourceM.playerStats().bumpLevelCombatStat(PlayerCombatStat.HITS_DONE, sourceM.basePhyStats().level(), 1);\n\t\t\tif(targetM.playerStats()!=null)\n\t\t\t\ttargetM.playerStats().bumpLevelCombatStat(PlayerCombatStat.HITS_TAKEN, targetM.basePhyStats().level(), 1);\n\t\t\tpostWeaponDamage(sourceM,targetM,item,damageInt);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfinal String missString="^F^<FIGHT^>"+((weapon!=null)?\n\t\t\t\t\t\t\t\tweapon.missString():\n\t\t\t\t\t\t\t\tstandardMissString(Weapon.TYPE_BASHING,Weapon.CLASS_BLUNT,item.name(),false))+"^</FIGHT^>^?";\n\t\t\tfinal CMMsg msg=CMClass.getMsg(sourceM,\n\t\t\t\t\t\t\t\t\ttargetM,\n\t\t\t\t\t\t\t\t\tweapon,\n\t\t\t\t\t\t\t\t\tCMMsg.MSG_ATTACKMISS,\n\t\t\t\t\t\t\t\t\tmissString);\n\t\t\tCMLib.color().fixSourceFightColor(msg);\n\t\t\t// why was there no okaffect here?\n\t\t\tfinal Room R=sourceM.location();\n\t\t\tif(R!=null)\n\t\t\tif(R.okMessage(sourceM,msg) && (!sourceM.amDead()) && (!sourceM.amDestroyed()))\n\t\t\t{\n\t\t\t\tR.send(sourceM,msg);\n\t\t\t\treturn msg;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void postSiegeWeaponAttackResult(final MOB source, final PhysicalAgent attacker, final PhysicalAgent defender, final Weapon weapon, final boolean success)\n\t{\n\t\tif(source==null)\n\t\t\treturn;\n\t\t// if you aren\'t in combat, how\'d you get here.\n\t\t// if you are in combat, this needs to happen regardless\n\t\t//if(!mayIAttack(source, attacker, defender))\n\t\t//\treturn;\n\t\tint damageInt=adjustedDamage(source,weapon,null,0,false,false);\n\t\tint damageType=Weapon.TYPE_BASHING;\n\t\tif(weapon != null)\n\t\t\tdamageType= weapon.weaponDamageType();\n\t\tfinal Room room=CMLib.map().roomLocation(attacker);\n\t\tif(success)\n\t\t{\n\t\t\t// calculate Base Damage (with Strength bonus)\n\t\t\tfinal String oldHitString="^F^<FIGHT^>"+((weapon!=null)?\n\t\t\t\t\t\t\t\tweapon.hitString(damageInt):\n\t\t\t\t\t\t\t\tstandardHitString(Weapon.TYPE_NATURAL,Weapon.CLASS_BLUNT,(int)Math.round(Math.pow(2,damageInt)),attacker.Name()))+"^</FIGHT^>^?";\n\t\t\tfinal CMMsg msg=CMClass.getMsg(source,\n\t\t\t\t\t\t\t\t\tdefender,\n\t\t\t\t\t\t\t\t\tweapon,\n\t\t\t\t\t\t\t\t\tCMMsg.MSG_OK_VISUAL,\n\t\t\t\t\t\t\t\t\tCMMsg.MSG_DAMAGE,\n\t\t\t\t\t\t\t\t\tCMMsg.MSG_OK_VISUAL,\n\t\t\t\t\t\t\t\t\toldHitString);\n\t\t\tCMLib.color().fixSourceFightColor(msg);\n\n\t\t\tmsg.setValue(damageInt);\n\t\t\t// why was there no okaffect here?\n\t\t\tif((room!=null)&&(room.okMessage(source,msg)))\n\t\t\t{\n\t\t\t\tif(msg.targetMinor()==CMMsg.TYP_DAMAGE)\n\t\t\t\t{\n\t\t\t\t\tdamageInt=msg.value();\n\t\t\t\t\tmsg.modify(msg.source(),\n\t\t\t\t\t\t\t   msg.target(),\n\t\t\t\t\t\t\t   msg.tool(),\n\t\t\t\t\t\t\t   msg.sourceCode(),\n\t\t\t\t\t\t\t   replaceDamageTag(msg.sourceMessage(),msg.value(),damageType,CMMsg.View.SOURCE),\n\t\t\t\t\t\t\t   msg.targetCode(),\n\t\t\t\t\t\t\t   replaceDamageTag(msg.targetMessage(),msg.value(),damageType,CMMsg.View.TARGET),\n\t\t\t\t\t\t\t   msg.othersCode(),\n\t\t\t\t\t\t\t   replaceDamageTag(msg.othersMessage(),msg.value(),damageType,CMMsg.View.OTHERS));\n\t\t\t\t}\n\t\t\t\tif(//(mayIAttack(source,attacker,defender))&&\n\t\t\t\t(CMLib.map().roomLocation(attacker)==room)\n\t\t\t\t&&(CMLib.map().roomLocation(defender)==room))\n\t\t\t\t\troom.send(source,msg);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfinal String missString="^F^<FIGHT^>"+((weapon!=null)?\n\t\t\t\t\t\t\t\tweapon.missString():\n\t\t\t\t\t\t\t\tstandardMissString(Weapon.TYPE_BASHING,Weapon.CLASS_BLUNT,attacker.name(),false))+"^</FIGHT^>^?";\n\t\t\tfinal CMMsg msg=CMClass.getMsg(source,\n\t\t\t\t\t\t\t\t\t\t\tdefender,\n\t\t\t\t\t\t\t\t\t\t\tweapon,\n\t\t\t\t\t\t\t\t\t\t\tCMMsg.MSG_ATTACKMISS,\n\t\t\t\t\t\t\t\t\t\t\tmissString);\n\t\t\tCMLib.color().fixSourceFightColor(msg);\n\t\t\t// why was there no okaffect here?\n\t\t\tif((room!=null)\n\t\t\t&&(room.okMessage(source,msg)))\n\t\t\t\troom.send(source,msg);\n\t\t}\n\t}\n\n\tprotected void processFormation(final List<MOB>[] done, final MOB leader, final int level)\n\t{\n\t\tfor (final List<MOB> element : done)\n\t\t{\n\t\t\tif((element!=null)&&(element.contains(leader)))\n\t\t\t\treturn;\n\t\t}\n\t\tif(level>=done.length)\n\t\t\treturn;\n\t\tif(done[level]==null)\n\t\t\tdone[level]=new Vector<MOB>();\n\t\tdone[level].add(leader);\n\t\tfor(int f=0;f<leader.numFollowers();f++)\n\t\t{\n\t\t\tfinal MOB M=leader.fetchFollower(f);\n\t\t\tif(M==null)\n\t\t\t\tcontinue;\n\t\t\tint range=leader.fetchFollowerOrder(M);\n\t\t\tif(range<0)\n\t\t\t\trange=0;\n\t\t\tprocessFormation(done,M,level+range);\n\t\t}\n\t}\n\n\t@Override\n\tpublic MOB getFollowedLeader(final MOB mob)\n\t{\n\t\tMOB leader=mob;\n\t\tif(leader.amFollowing()!=null)\n\t\t\tleader=leader.amUltimatelyFollowing();\n\t\treturn leader;\n\t}\n\n\t@Override\n\t@SuppressWarnings("unchecked")\n\tpublic List<MOB>[] getFormation(final MOB mob)\n\t{\n\t\tMOB leader=mob;\n\t\tif(leader.amFollowing()!=null)\n\t\t\tleader=leader.amUltimatelyFollowing();\n\t\tfinal Vector<MOB>[] done=new Vector[20];\n\t\tprocessFormation(done,leader,0);\n\t\treturn done;\n\t}\n\n\t@Override\n\tpublic List<MOB> getFormationFollowed(final MOB mob)\n\t{\n\t\tfinal List<MOB>[] form=getFormation(mob);\n\t\tfor(int i=1;i<form.length;i++)\n\t\t{\n\t\t\tif((form[i]!=null)&&(form[i].contains(mob)))\n\t\t\t{\n\t\t\t\ti--;\n\t\t\t\twhile(i>=0)\n\t\t\t\t{\n\t\t\t\t\tif((form[i]!=null)&&(form[i].size()>0))\n\t\t\t\t\t\treturn form[i];\n\t\t\t\t\ti--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic int getFormationAbsOrder(final MOB mob)\n\t{\n\t\tfinal List<MOB>[] form=getFormation(mob);\n\t\tfor(int i=1;i<form.length;i++)\n\t\t{\n\t\t\tif((form[i]!=null)&&(form[i].contains(mob)))\n\t\t\t\treturn i;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tpublic MOB getCombatDominentKiller(final MOB killer, final MOB killed)\n\t{\n\t\tif((!killer.isMonster())&&(killer.charStats()!=null))\n\t\t\treturn killer;\n\t\tMOB M=killer;\n\t\tSet<MOB> checked=new HashSet<MOB>();\n\t\tchecked.add(M);\n\t\twhile(M.isMonster()\n\t\t&&(M.amFollowing()!=null)\n\t\t&&(!checked.contains(M.amFollowing())))\n\t\t{\n\t\t\tM=M.amFollowing();\n\t\t\tchecked.add(M);\n\t\t}\n\t\tif((!M.isMonster())&&(M.charStats()!=null))\n\t\t\treturn M;\n\t\tchecked.clear();\n\t\tchecked=killer.getGroupMembers(checked);\n\t\tfor(final Iterator<MOB> m=checked.iterator(); m.hasNext(); )\n\t\t{\n\t\t\tM=m.next();\n\t\t\tif((!M.isMonster())&&(M.charStats()!=null))\n\t\t\t\treturn M;\n\t\t}\n\t\treturn killer;\n\t}\n\n\t@Override\n\tpublic CharClass getCombatDominantClass(final MOB killer, final MOB killed)\n\t{\n\t\tCharClass C=null;\n\n\t\tif((killer!=null)&&(killer.charStats()!=null))\n\t\t{\n\t\t\tC=killer.charStats().getCurrentClass();\n\t\t\tMOB M=killer;\n\t\t\tfinal HashSet<MOB> checked=new HashSet<MOB>();\n\t\t\tchecked.add(M);\n\t\t\twhile(M.isMonster()\n\t\t\t&&(M.amFollowing()!=null)\n\t\t\t&&(!checked.contains(M.amFollowing())))\n\t\t\t{\n\t\t\t\tM=M.amFollowing();\n\t\t\t\tchecked.add(M);\n\t\t\t}\n\t\t\tif((!M.isMonster())&&(M.charStats()!=null))\n\t\t\t\tC=M.charStats().getCurrentClass();\n\t\t}\n\t\telse\n\t\t\tC=CMClass.getCharClass("StdCharClass");\n\t\treturn C;\n\t}\n\n\tprotected Set<MOB> getCombatBeneficiaries(final MOB killer, final MOB killed, final Room deathRoom, final Set<MOB> beneficiaries, CharClass combatCharClass)\n\t{\n\t\tfinal Set<MOB> followers=(killer!=null)?killer.getGroupMembers(new HashSet<MOB>()):(new SHashSet<MOB>());\n\t\tif(combatCharClass==null)\n\t\t\tcombatCharClass=CMClass.getCharClass("StdCharClass");\n\t\tif(deathRoom!=null)\n\t\t{\n\t\t\tfor(int m=0;m<deathRoom.numInhabitants();m++)\n\t\t\t{\n\t\t\t\tfinal MOB mob=deathRoom.fetchInhabitant(m);\n\t\t\t\tif((combatCharClass.isValidClassBeneficiary(killer,killed,mob,followers))\n\t\t\t\t&&(!beneficiaries.contains(mob)))\n\t\t\t\t\tbeneficiaries.add(mob);\n\t\t\t}\n\t\t}\n\t\tif((killer!=null)&&(!beneficiaries.contains(killer))&&(killer!=killed)&&(CMLib.flags().isInTheGame(killer,true)))\n\t\t\tbeneficiaries.add(killer);\n\t\treturn beneficiaries;\n\t}\n\n\t@Override\n\tpublic Set<MOB> getCombatBeneficiaries(final MOB killer, final MOB killed, final CharClass combatCharClass)\n\t{\n\t\tif((killer==null)||(killed==null))\n\t\t\treturn new SHashSet<MOB>();\n\t\tfinal SHashSet<MOB> beneficiaries=new SHashSet<MOB>();\n\t\tRoom R=killer.location();\n\t\tif(R!=null)\n\t\t\tgetCombatBeneficiaries(killer,killed,R,beneficiaries,combatCharClass);\n\t\tR=killed.location();\n\t\tif((R!=null)&&(R!=killer.location()))\n\t\t\tgetCombatBeneficiaries(killer,killed,R,beneficiaries,combatCharClass);\n\t\treturn beneficiaries;\n\t}\n\n\tprotected Set<MOB> getCombatDividers(final MOB killer, final MOB killed, final Room deathRoom, final Set<MOB> dividers, CharClass combatCharClass)\n\t{\n\t\tfinal Set<MOB> followers=(killer!=null)?killer.getGroupMembers(new HashSet<MOB>()):(new HashSet<MOB>());\n\t\tif(combatCharClass==null)\n\t\t\tcombatCharClass=CMClass.getCharClass("StdCharClass");\n\t\tif(deathRoom!=null)\n\t\t{\n\t\t\tfor(int m=0;m<deathRoom.numInhabitants();m++)\n\t\t\t{\n\t\t\t\tfinal MOB mob=deathRoom.fetchInhabitant(m);\n\t\t\t\tif((combatCharClass.isValidClassDivider(killer,killed,mob,followers))\n\t\t\t\t&&(!dividers.contains(mob)))\n\t\t\t\t\tdividers.add(mob);\n\t\t\t}\n\t\t}\n\t\tif((killer!=null)&&(!dividers.contains(killer))&&(killer!=killed)&&(CMLib.flags().isInTheGame(killer,true)))\n\t\t\tdividers.add(killer);\n\t\treturn dividers;\n\t}\n\n\t@Override\n\tpublic Set<MOB> getCombatDividers(final MOB killer, final MOB killed, final CharClass combatCharClass)\n\t{\n\t\tif((killer==null)||(killed==null))\n\t\t\treturn new SHashSet<MOB>();\n\t\tfinal Set<MOB> dividers=new SHashSet<MOB>();\n\t\tRoom R=killer.location();\n\t\tif(R!=null)\n\t\t\tgetCombatDividers(killer,killed,R,dividers,combatCharClass);\n\t\tR=killed.location();\n\t\tif((R!=null)&&(R!=killer.location()))\n\t\t\tgetCombatDividers(killer,killed,R,dividers,combatCharClass);\n\t\treturn dividers;\n\t}\n\n\tprotected DeadBody justDie(final MOB killerM, final MOB deadM)\n\t{\n\t\tif(deadM==null)\n\t\t\treturn null;\n\t\tfinal Room deathRoom=deadM.location();\n\t\tif(deathRoom == null)\n\t\t\treturn null;\n\t\tif(deadM.isPlayer())\n\t\t{\n\t\t\tif(killerM != null)\n\t\t\t\tCMLib.achievements().possiblyBumpAchievement(deadM, AchievementLibrary.Event.DEATHS, 1, killerM);\n\t\t\telse\n\t\t\t\tCMLib.achievements().possiblyBumpAchievement(deadM, AchievementLibrary.Event.DEATHS, 1, deadM);\n\t\t}\n\n\t\tfinal Session killerSess=(killerM==null)?null:killerM.session();\n\t\tfinal Session deadMSess=deadM.session();\n\n\t\t//TODO: this creates too many loops.  The right thing is to loop once\n\t\t// and call  a boolean function to populate all these lists.\n\t\tfinal CharClass combatCharClass=getCombatDominantClass(killerM,deadM);\n\t\tfinal Set<MOB> beneficiaries=getCombatBeneficiaries(killerM,deadM,combatCharClass);\n\t\tfinal Set<MOB> hisGroupH=deadM.getGroupMembers(new HashSet<MOB>());\n\t\tfor(final Enumeration<MOB> m=deathRoom.inhabitants();m.hasMoreElements();)\n\t\t{\n\t\t\tfinal MOB M=m.nextElement();\n\t\t\tif((M!=null)\n\t\t\t&&(M.getVictim()==deadM))\n\t\t\t\tpickNextVictim(M, deadM, hisGroupH);\n\t\t}\n\t\tfinal Set<MOB> dividers=getCombatDividers(killerM,deadM,combatCharClass);\n\n\t\tif(killerM != null)\n\t\t\tCMLib.get(killerSess)._combat().dispenseExperience(beneficiaries,dividers,deadM);\n\t\telse\n\t\t\tCMLib.get(deadMSess)._combat().dispenseExperience(beneficiaries,dividers,deadM);\n\n\t\tfinal String currency=CMLib.beanCounter().getCurrency(deadM);\n\t\tfinal double deadMoney=CMLib.beanCounter().getTotalAbsoluteValue(deadM,currency);\n\t\tdouble myAmountOfDeadMoney=0.0;\n\t\tfinal List<MOB> goldLooters=new ArrayList<MOB>();\n\t\tfor (final MOB M : beneficiaries)\n\t\t{\n\t\t\tif(((M.isAttributeSet(MOB.Attrib.AUTOGOLD))\n\t\t\t&&(!goldLooters.contains(M)))\n\t\t\t&&(M!=deadM)\n\t\t\t&&(M.location()==deathRoom)\n\t\t\t&&(deathRoom.isInhabitant(M)))\n\t\t\t\tgoldLooters.add(M);\n\t\t}\n\t\tif((goldLooters.size()>0)&&(deadMoney>0))\n\t\t{\n\t\t\tmyAmountOfDeadMoney=CMath.div(deadMoney,goldLooters.size());\n\t\t\tCMLib.beanCounter().subtractMoney(deadM,deadMoney);\n\t\t}\n\n\t\tfinal int[] expLost={100*deadM.phyStats().level()};\n\t\tif(expLost[0]<100)\n\t\t\texpLost[0]=100;\n\t\tString[] cmds=null;\n\t\tif((deadM.isMonster())||(deadM.soulMate()!=null))\n\t\t\tcmds=CMParms.toStringArray(CMParms.parseCommas(CMProps.getVar(CMProps.Str.MOBDEATH),true));\n\t\telse\n\t\t\tcmds=CMParms.toStringArray(CMParms.parseCommas(CMProps.get(deadM.session()).getStr(CMProps.Str.PLAYERDEATH),true));\n\n\t\tDeadBody body=null; //must be done before consequences because consequences could be purging\n\t\tif((!CMParms.containsIgnoreCase(cmds,"RECALL"))\n\t\t&&(!isKnockedOutUponDeath(deadM,killerM)))\n\t\t\tbody=(DeadBody)deadM.killMeDead(true);\n\n\t\tif(deadM.isPlayer())\n\t\t\tdeadM.playerStats().deathCounter(1);\n\t\tfinal boolean stillExists = handleCombatLossConsequences(deadM,killerM,cmds,expLost,"^*You lose @x1 experience points.^?^.");\n\t\tif(!isKnockedOutUponDeath(deadM,killerM))\n\t\t{\n\t\t\tRoom bodyRoom=deathRoom;\n\t\t\tif((body!=null)&&(body.owner() instanceof Room)&&(((Room)body.owner()).isContent(body)))\n\t\t\t\tbodyRoom=(Room)body.owner();\n\t\t\tif((killerM!=null)&&(body!=null))\n\t\t\t{\n\t\t\t\tbody.setKillerName(killerM.Name());\n\t\t\t\tbody.setIsKillerPlayer(!killerM.isMonster());\n\t\t\t\tbody.setKillerTool(killerM.fetchWieldedItem());\n\t\t\t\tif(body.getKillerTool()==null)\n\t\t\t\t\tbody.setKillerTool(killerM.getNaturalWeapon());\n\t\t\t}\n\n\t\t\tif((!deadM.isMonster())\n\t\t\t&&(CMLib.dice().rollPercentage()==1)\n\t\t\t&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.AUTODISEASE))\n\t\t\t&&(stillExists))\n\t\t\t{\n\t\t\t\tfinal Ability A=CMClass.getAbility("Disease_Amnesia");\n\t\t\t\tif((A!=null)\n\t\t\t\t&&(deadM.fetchEffect(A.ID())==null)\n\t\t\t\t&&(!CMSecurity.isAbilityDisabled(A.ID())))\n\t\t\t\t\tA.invoke(deadM,deadM,true,0);\n\t\t\t}\n\n\t\t\tif(deadM.soulMate()!=null)\n\t\t\t{\n\t\t\t\tfinal Session s=deadM.session();\n\t\t\t\ts.setMob(deadM.soulMate());\n\t\t\t\tdeadM.soulMate().setSession(s);\n\t\t\t\tdeadM.setSession(null);\n\t\t\t\tdeadM.soulMate().tell(L("^HYour spirit has returned to your body...\\n\\r\\n\\r^N"));\n\t\t\t\tCMLib.commands().postLook(deadM.soulMate(),true);\n\t\t\t\tdeadM.setSoulMate(null);\n\t\t\t}\n\n\t\t\tif((killerM!=null)\n\t\t\t&&(bodyRoom!=null)\n\t\t\t&&(body!=null)\n\t\t\t&&(killerM.location()==bodyRoom)\n\t\t\t&&(bodyRoom.isInhabitant(killerM))\n\t\t\t&&(killerM.isAttributeSet(MOB.Attrib.AUTOLOOT)))\n\t\t\t{\n\t\t\t\tif((killerM.riding()!=null)&&(killerM.riding() instanceof MOB))\n\t\t\t\t\tkillerM.tell(L("You\'ll need to dismount to loot the body."));\n\t\t\t\telse\n\t\t\t\tif((killerM.riding()!=null)&&(killerM.riding() instanceof MOB))\n\t\t\t\t\tkillerM.tell(L("You\'ll need to disembark to loot the body."));\n\t\t\t\telse\n\t\t\t\tfor(int i=bodyRoom.numItems()-1;i>=0;i--)\n\t\t\t\t{\n\t\t\t\t\tfinal Item item=bodyRoom.getItem(i);\n\t\t\t\t\tif((item!=null)\n\t\t\t\t\t&&(item.container()==body)\n\t\t\t\t\t&&(CMLib.flags().canBeSeenBy(body,killerM))\n\t\t\t\t\t&&((!body.isDestroyedAfterLooting())||(!(item instanceof RawMaterial)))\n\t\t\t\t\t&&(CMLib.flags().canBeSeenBy(item,killerM)))\n\t\t\t\t\t\tCMLib.commands().postGet(killerM,body,item,false);\n\t\t\t\t}\n\t\t\t\tif(body.isDestroyedAfterLooting())\n\t\t\t\t\tbodyRoom.recoverRoomStats();\n\t\t\t}\n\n\t\t\tCoins C=null;\n\t\t\tif((deadMoney>0)\n\t\t\t&&(myAmountOfDeadMoney>0)\n\t\t\t&&(body!=null)\n\t\t\t&&(bodyRoom!=null))\n\t\t\t{\n\t\t\t\tfor(int g=0;g<goldLooters.size();g++)\n\t\t\t\t{\n\t\t\t\t\tC=CMLib.beanCounter().makeBestCurrency(currency,myAmountOfDeadMoney,null,body);\n\t\t\t\t\tif(C!=null)\n\t\t\t\t\t{\n\t\t\t\t\t\tC.recoverPhyStats();\n\t\t\t\t\t\tbodyRoom.addItem(C,ItemPossessor.Expire.Monster_EQ);\n\t\t\t\t\t\tbodyRoom.recoverRoomStats();\n\t\t\t\t\t\tfinal MOB mob=goldLooters.get(g);\n\t\t\t\t\t\tif(mob.location()==bodyRoom)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif((mob.riding()!=null)&&(mob.riding() instanceof MOB))\n\t\t\t\t\t\t\t\tmob.tell(L("You\'ll need to dismount to get @x1 off the body.",C.name()));\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\tif((mob.riding()!=null)&&(mob.riding() instanceof Item))\n\t\t\t\t\t\t\t\tmob.tell(L("You\'ll need to disembark to get @x1 off the body.",C.name()));\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\tif(CMLib.flags().canBeSeenBy(body,mob))\n\t\t\t\t\t\t\t\tCMLib.commands().postGet(mob,body,C,false);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif((killerM != null)&&(killerM.getVictim()==deadM))\n\t\t\t\tkillerM.setVictim(null);\n\t\t\tdeadM.setVictim(null);\n\t\t\tif((body!=null)&&(bodyRoom!=null)&&(body.isDestroyedAfterLooting()))\n\t\t\t{\n\t\t\t\tfor(int i=bodyRoom.numItems()-1;i>=0;i--)\n\t\t\t\t{\n\t\t\t\t\tfinal Item item=bodyRoom.getItem(i);\n\t\t\t\t\tif((item!=null)\n\t\t\t\t\t&&(item.container()==body))\n\t\t\t\t\t\titem.setContainer(null);\n\t\t\t\t}\n\t\t\t\tbody.destroy();\n\t\t\t\tbodyRoom.recoverPhyStats();\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn body;\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String standardHitWord(int type, final double pct)\n\t{\n\t\tif((type<0)||(type>=Weapon.TYPE_DESCS.length))\n\t\t\ttype=Weapon.TYPE_BURSTING;\n\t\tfinal int[] thresholds=CMProps.getListFileIntList(CMProps.ListFile.DAMAGE_WORDS_THRESHOLDS);\n\t\tint damnCode=(int)Math.round(pct * (thresholds.length-1));\n\t\tif(damnCode > thresholds.length-1)\n\t\t\tdamnCode = thresholds.length-1;\n\t\treturn getStandardHitWordInternal(type, damnCode);\n\t}\n\n\tprotected String getStandardHitWordInternal(final int type, int damnCode)\n\t{\n\t\tdamnCode++; // always add 1 because index into hitwords is type=0, annoy=1;\n\t\tfinal Object[][][] hitWords = CMProps.getListFileGrid(CMProps.ListFile.DAMAGE_WORDS);\n\t\tif(hitWords != hitWordsChanged)\n\t\t{\n\t\t\thitWordsChanged=hitWords;\n\t\t\thitWordIndex=null;\n\t\t}\n\t\tif(hitWordIndex==null)\n\t\t{\n\t\t\tfinal Object[][][] newWordIndex=new Object[Weapon.TYPE_DESCS.length][][];\n\t\t\tfor(int w=0;w<Weapon.TYPE_DESCS.length;w++)\n\t\t\t{\n\t\t\t\tObject[][] ALL=null;\n\t\t\t\tObject[][] MINE=null;\n\t\t\t\tfor (final Object[][] hitWord : hitWords)\n\t\t\t\t{\n\t\t\t\t\tif(((String)hitWord[0][0]).equalsIgnoreCase("ALL"))\n\t\t\t\t\t\tALL=hitWord;\n\t\t\t\t\telse\n\t\t\t\t\tif(((String)hitWord[0][0]).equalsIgnoreCase(Weapon.TYPE_DESCS[w]))\n\t\t\t\t\t{\n\t\t\t\t\t\tMINE = hitWord;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif(MINE!=null)\n\t\t\t\t\tnewWordIndex[w]=MINE;\n\t\t\t\telse\n\t\t\t\t\tnewWordIndex[w]=ALL;\n\t\t\t}\n\t\t\thitWordIndex=newWordIndex;\n\t\t}\n\t\tfinal Object[][] HIT_WORDS=hitWordIndex[type];\n\t\tif(damnCode<1)\n\t\t\tdamnCode=1;\n\t\tif(damnCode>=HIT_WORDS.length)\n\t\t\tdamnCode=HIT_WORDS.length-1;\n\t\treturn (String)CMLib.dice().pick(HIT_WORDS[damnCode]);\n\t}\n\n\t@Override\n\tpublic String standardHitWord(int type, final int damage)\n\t{\n\t\tif((type<0)||(type>=Weapon.TYPE_DESCS.length))\n\t\t\ttype=Weapon.TYPE_BURSTING;\n\t\tfinal int[] thresholds=CMProps.getListFileIntList(CMProps.ListFile.DAMAGE_WORDS_THRESHOLDS);\n\t\tint damnCode=thresholds.length-2;\n\t\tfor(int i=0;i<thresholds.length;i++)\n\t\t{\n\t\t\tif (damage <= thresholds[i])\n\t\t\t{\n\t\t\t\tdamnCode = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn getStandardHitWordInternal(type, damnCode);\n\t}\n\n\tprotected String getExtremeValue(final int extreme)\n\t{\n\t\tfinal StringBuilder str=new StringBuilder("");\n\t\tfor(final char c : CMath.convertToRoman(extreme).toCharArray())\n\t\t{\n\t\t\tswitch(c)\n\t\t\t{\n\t\t\tcase \'I\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,0)).append(" ");\n\t\t\t\tbreak;\n\t\t\tcase \'V\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,1)).append(" ");\n\t\t\t\tbreak;\n\t\t\tcase \'X\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,2)).append(" ");\n\t\t\t\tbreak;\n\t\t\tcase \'L\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,3)).append(" ");\n\t\t\t\tbreak;\n\t\t\tcase \'C\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,4)).append(" ");\n\t\t\t\tbreak;\n\t\t\tcase \'D\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,5)).append(" ");\n\t\t\t\tbreak;\n\t\t\tcase \'M\':\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.EXTREME_ADVS,6)).append(" ");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn str.toString();\n\t}\n\n\t@Override\n\tpublic String armorStr(final MOB mob)\n\t{\n\t\tfinal int prowessCode = CMProps.getIntVar(CMProps.Int.COMBATPROWESS);\n\t\tif(CMProps.Int.Prowesses.NONE.is(prowessCode))\n\t\t\treturn "";\n\t\tfinal int armor = -adjustedArmor(mob);\n\t\tfinal StringBuilder str=new StringBuilder("");\n\t\tif(CMProps.Int.Prowesses.ARMOR_ADJ.is(prowessCode)||CMProps.Int.Prowesses.ARMOR_ADV.is(prowessCode))\n\t\t{\n\t\t\tfinal int normalizedArmor = (int)Math.round(Math.ceil(CMath.div(armor + adjustedArmor(CMLib.leveler().getLevelMOBArmor(mob)),5.0)));\n\t\t\tfinal int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.ARMOR_ADJS);\n\t\t\tfinal int medianValue = normalizedMax / 2;\n\t\t\tint adjIndex = normalizedArmor + medianValue;\n\t\t\tint extreme = 0;\n\t\t\tif(adjIndex < 0)\n\t\t\t{\n\t\t\t\textreme = -adjIndex;\n\t\t\t\tadjIndex = 0;\n\t\t\t}\n\t\t\tif(adjIndex >= normalizedMax)\n\t\t\t{\n\t\t\t\textreme = normalizedMax-adjIndex;\n\t\t\t\tadjIndex = normalizedMax-1;\n\t\t\t}\n\t\t\tif((extreme != 0)&&(CMProps.Int.Prowesses.ARMOR_ADV.is(prowessCode)))\n\t\t\t\tstr.append(this.getExtremeValue(extreme));\n\t\t\tif(CMProps.Int.Prowesses.ARMOR_ADJ.is(prowessCode))\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_ADJS,adjIndex)).append(" ");\n\t\t}\n\t\tif(CMProps.Int.Prowesses.ARMOR_ABSOLUTE.is(prowessCode))\n\t\t{\n\t\t\tfinal int ARMOR_CEILING=CMProps.getListFileFirstInt(CMProps.ListFile.ARMOR_DESCS_CEILING);\n\t\t\tfinal int numArmorDescs = CMProps.getListFileIndexedListSize(CMProps.ListFile.ARMOR_DESCS);\n\t\t\tstr.append((armor<0)?CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_DESCS,0):(\n\t\t\t\t   (armor>=ARMOR_CEILING)?\n\t\t\t\t\t\t   CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_DESCS,numArmorDescs-1)\n\t\t\t\t\t\t   +(CMStrings.repeatWithLimit(\'!\',((armor-ARMOR_CEILING)/100),10))\n\t\t\t\t\t\t:\n\t\t\t\t\t\t   (CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.ARMOR_DESCS,(int)Math.round(Math.floor(CMath.mul(CMath.div(armor,ARMOR_CEILING),numArmorDescs)))))));\n\t\t\tstr.append(" ");\n\t\t}\n\t\tif(CMProps.Int.Prowesses.ARMOR_NUMBER.is(prowessCode))\n\t\t\tstr.append("^.("+armor+")");\n\t\treturn str.toString().trim()+"^.";\n\t}\n\n\t@Override\n\tpublic String fightingProwessStr(final MOB mob)\n\t{\n\t\tfinal int prowessCode = CMProps.getIntVar(CMProps.Int.COMBATPROWESS);\n\t\tif(CMProps.Int.Prowesses.NONE.is(prowessCode))\n\t\t\treturn "";\n\t\tfinal int attackProwess = adjustedAttackBonus(mob,null) - ATTACK_ADJUSTMENT;\n\t\tfinal StringBuilder str=new StringBuilder("");\n\t\tif(CMProps.Int.Prowesses.COMBAT_ADJ.is(prowessCode)||CMProps.Int.Prowesses.COMBAT_ADV.is(prowessCode))\n\t\t{\n\t\t\tfinal int normalizedAttack = (int)Math.round(Math.ceil(CMath.div(attackProwess - (adjustedAttackBonus(CMLib.leveler().getLevelAttack(mob))- ATTACK_ADJUSTMENT),12.0)));\n\t\t\tfinal int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.COMBAT_ADJS);\n\t\t\tfinal int medianValue = normalizedMax / 2;\n\t\t\tint adjIndex = normalizedAttack + medianValue;\n\t\t\tint extreme = 0;\n\t\t\tif(adjIndex < 0)\n\t\t\t{\n\t\t\t\textreme = -adjIndex;\n\t\t\t\tadjIndex = 0;\n\t\t\t}\n\t\t\tif(adjIndex >= normalizedMax)\n\t\t\t{\n\t\t\t\textreme = normalizedMax-adjIndex;\n\t\t\t\tadjIndex = normalizedMax-1;\n\t\t\t}\n\t\t\tif((extreme != 0)&&(CMProps.Int.Prowesses.COMBAT_ADV.is(prowessCode)))\n\t\t\t\tstr.append(this.getExtremeValue(extreme));\n\t\t\tif(CMProps.Int.Prowesses.COMBAT_ADJ.is(prowessCode))\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.COMBAT_ADJS,adjIndex)).append(" ");\n\t\t}\n\t\tif(CMProps.Int.Prowesses.COMBAT_ABSOLUTE.is(prowessCode))\n\t\t{\n\t\t\tfinal int PROWESS_CEILING=CMProps.getListFileFirstInt(CMProps.ListFile.PROWESS_DESCS_CEILING);\n\t\t\tfinal int numProwessDescs = CMProps.getListFileIndexedListSize(CMProps.ListFile.PROWESS_DESCS);\n\t\t\tstr.append((attackProwess<0)?CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.PROWESS_DESCS,0):(\n\t\t\t\t   (attackProwess>=PROWESS_CEILING)\n\t\t\t\t\t\t\t\t\t\t\t ?\n\t\t\t\t\t\t\t\t\t CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.PROWESS_DESCS,numProwessDescs-1)\n\t\t\t\t\t\t\t\t\t +(CMStrings.repeatWithLimit(\'!\',((attackProwess-PROWESS_CEILING)/100),10))\n\t\t\t\t\t\t\t\t\t\t\t :\n\t\t\t\t\t\t\t\t\t (CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.PROWESS_DESCS,(int)Math.round(Math.floor(CMath.mul(CMath.div(attackProwess,PROWESS_CEILING),numProwessDescs)))))));\n\t\t}\n\t\tif(CMProps.Int.Prowesses.COMBAT_NOUN.is(prowessCode))\n\t\t{\n\t\t\tfinal int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.COMBAT_NOUNS);\n\t\t\tfinal double divisor = CMath.div(CMProps.get(mob.session()).getInt(CMProps.Int.LASTPLAYERLEVEL),normalizedMax);\n\t\t\tint nounIndex = (int)Math.round(CMath.div(mob.phyStats().level()-1, divisor));\n\t\t\tif(nounIndex >= normalizedMax)\n\t\t\t\tnounIndex = normalizedMax-1;\n\t\t\tif(nounIndex<0)\n\t\t\t\tnounIndex=0;\n\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.COMBAT_NOUNS,nounIndex)).append(" ");\n\t\t}\n\t\tif(CMProps.Int.Prowesses.COMBAT_NUMBER.is(prowessCode))\n\t\t\tstr.append("^.("+attackProwess+")");\n\t\treturn str.toString().trim()+"^.";\n\t}\n\n\t@Override\n\tpublic String damageProwessStr(final MOB mob)\n\t{\n\t\tfinal int prowessCode = CMProps.getIntVar(CMProps.Int.COMBATPROWESS);\n\t\tif(CMProps.Int.Prowesses.NONE.is(prowessCode))\n\t\t\treturn "";\n\t\tfinal int damageProwess = this.adjustedDamage(mob, (Weapon)mob.fetchWieldedItem(), null, 0, false,true);\n\t\tfinal StringBuilder str=new StringBuilder("");\n\t\tif(CMProps.Int.Prowesses.DAMAGE_ADJ.is(prowessCode)||CMProps.Int.Prowesses.DAMAGE_ADV.is(prowessCode))\n\t\t{\n\t\t\tfinal int normalizedDamage = (int)Math.round(Math.ceil(CMath.div(damageProwess - (adjustedDamage(CMLib.leveler().getLevelMOBDamage(mob),mob.phyStats().level(),true)),3.0)));\n\t\t\tfinal int normalizedMax = CMProps.getListFileIndexedListSize(CMProps.ListFile.DAMAGE_ADJS);\n\t\t\tfinal int medianValue = normalizedMax / 2;\n\t\t\tint adjIndex = normalizedDamage + medianValue;\n\t\t\tint extreme = 0;\n\t\t\tif(adjIndex < 0)\n\t\t\t{\n\t\t\t\textreme = -adjIndex;\n\t\t\t\tadjIndex = 0;\n\t\t\t}\n\t\t\tif(adjIndex >= normalizedMax)\n\t\t\t{\n\t\t\t\textreme = normalizedMax-adjIndex;\n\t\t\t\tadjIndex = normalizedMax-1;\n\t\t\t}\n\t\t\tif((extreme != 0)&&(CMProps.Int.Prowesses.DAMAGE_ADV.is(prowessCode)))\n\t\t\t\tstr.append(this.getExtremeValue(extreme));\n\t\t\tif(CMProps.Int.Prowesses.DAMAGE_ADJ.is(prowessCode))\n\t\t\t\tstr.append(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_ADJS,adjIndex)).append(" ");\n\t\t}\n\t\tif(CMProps.Int.Prowesses.DAMAGE_ABSOLUTE.is(prowessCode))\n\t\t{\n\t\t\tfinal int DAMAGE_CEILING=CMProps.getListFileFirstInt(CMProps.ListFile.DAMAGE_DESCS_CEILING);\n\t\t\tfinal int numProwessDescs = CMProps.getListFileIndexedListSize(CMProps.ListFile.DAMAGE_DESCS);\n\t\t\tstr.append((damageProwess<0)?CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_DESCS,0):(\n\t\t\t\t   (damageProwess>=DAMAGE_CEILING)\n\t\t\t\t\t\t\t\t\t\t\t ?\n\t\t\t\t\t\t\t\t\t CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_DESCS,numProwessDescs-1)\n\t\t\t\t\t\t\t\t\t +(CMStrings.repeatWithLimit(\'!\',((damageProwess-DAMAGE_CEILING)/100),10))\n\t\t\t\t\t\t\t\t\t\t\t :\n\t\t\t\t\t\t\t\t\t (CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.DAMAGE_DESCS,(int)Math.round(Math.floor(CMath.mul(CMath.div(damageProwess,DAMAGE_CEILING),numProwessDescs)))))));\n\t\t}\n\t\tif(CMProps.Int.Prowesses.DAMAGE_NUMBER.is(prowessCode))\n\t\t\tstr.append("^.("+damageProwess+")");\n\t\treturn str.toString().trim()+"^.";\n\t}\n\n\tprotected int getWeaponAttackIndex(final int weaponDamageType, final int weaponClassification)\n\t{\n\t\tswitch(weaponClassification)\n\t\t{\n\t\tcase Weapon.CLASS_RANGED:\n\t\t\treturn (weaponDamageType == Weapon.TYPE_LASERING) ? 5 : 0;\n\t\tcase Weapon.CLASS_THROWN:\n\t\t\treturn (weaponDamageType == Weapon.TYPE_LASERING) ? 5 : 1;\n\t\tdefault:\n\t\t\tswitch(weaponDamageType)\n\t\t\t{\n\t\t\tcase Weapon.TYPE_SLASHING:\n\t\t\tcase Weapon.TYPE_BASHING:\n\t\t\t\treturn 2;\n\t\t\tcase Weapon.TYPE_PIERCING:\n\t\t\t\treturn 4;\n\t\t\tcase Weapon.TYPE_SHOOT:\n\t\t\t\treturn 0;\n\t\t\tcase Weapon.TYPE_LASERING:\n\t\t\t\treturn 5;\n\t\t\tdefault:\n\t\t\t\treturn 3;\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic String standardMissString(final int weaponDamageType, final int weaponClassification, final String weaponName, final boolean useExtendedMissString)\n\t{\n\t\tfinal int listIndex = getWeaponAttackIndex(weaponDamageType, weaponClassification);\n\t\tif(!useExtendedMissString)\n\t\t\treturn CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.MISS_DESCS,listIndex);\n\t\treturn CMStrings.replaceAll(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.WEAPON_MISS_DESCS,listIndex),"<TOOLNAME>",weaponName)+CMLib.protocol().msp("missed.wav",20);\n\t}\n\n\t@Override\n\tpublic String standardHitString(final int weaponDamageType, final int weaponClass, final int damageAmount, final String weaponName)\n\t{\n\t\tfinal int listIndex;\n\t\tif((weaponName==null)||(weaponName.length()==0))\n\t\t\tlistIndex = getWeaponAttackIndex(weaponDamageType, Weapon.CLASS_NATURAL);\n\t\telse\n\t\t\tlistIndex = getWeaponAttackIndex(weaponDamageType, weaponClass);\n\t\tfinal StringBuilder str=new StringBuilder(CMStrings.replaceAll(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.WEAPON_HIT_DESCS,listIndex),"<TOOLNAME>",weaponName));\n\t\tswitch(weaponClass)\n\t\t{\n\t\tcase Weapon.CLASS_RANGED:\n\t\t\treturn str.append(CMLib.protocol().msp("arrow.wav",20)).toString();\n\t\tcase Weapon.CLASS_THROWN:\n\t\t\treturn str.append(CMLib.protocol().msp("arrow.wav",20)).toString();\n\t\tdefault:\n\t\t\treturn str.append(CMLib.protocol().msp("punch"+CMLib.dice().roll(1,7,0)+".wav",20)).toString();\n\t\t}\n\t}\n\n\t@Override\n\tpublic String standardMobCondition(final MOB viewer,final MOB mob)\n\t{\n\t\tint pct=(int)Math.round(Math.floor((CMath.div(mob.curState().getHitPoints(),mob.maxState().getHitPoints()))*10));\n\t\tif(pct<0)\n\t\t\tpct=0;\n\t\tfinal int numHealthDescs=CMProps.getListFileIndexedListSize(CMProps.ListFile.HEALTH_CHART);\n\t\tif(pct>=numHealthDescs)\n\t\t\tpct=numHealthDescs-1;\n\t\treturn CMStrings.replaceAll(CMProps.getListFileChoiceFromIndexedList(CMProps.ListFile.HEALTH_CHART,pct),"<MOB>",mob.name(viewer));\n\t}\n\n\t@Override\n\tpublic void resistanceMsgs(MOB source, final MOB target, final CMMsg msg)\n\t{\n\t\tif((msg.value()>0)||(msg.sourceMinor()==CMMsg.TYP_TEACH))\n\t\t\treturn;\n\n\t\tif(target.amDead())\n\t\t\treturn;\n\n\t\tString tool=null;\n\t\tString endPart=" from <T-NAME>.";\n\t\tif(source==target)\n\t\t{\n\t\t\tsource=null;\n\t\t\tendPart=".";\n\t\t}\n\t\tif(msg.tool() instanceof Trap)\n\t\t\tendPart=".";\n\t\telse\n\t\tif(msg.tool() instanceof Ability)\n\t\t\ttool=((Ability)msg.tool()).name();\n\n\t\tString tackOn=null;\n\t\tswitch(msg.targetMinor())\n\t\t{\n\t\tcase CMMsg.TYP_MIND:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the mental attack" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_GAS:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the noxious fumes" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_COLD:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the cold blast" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_ELECTRIC:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the electrical attack" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_FIRE:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the blast of heat" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_WATER:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> dodge(s) the wet blast" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> dodge(s) the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_UNDEAD:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the evil attack" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_POISON:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the poison" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_DISEASE:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the disease.");\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the @x1.",tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_JUSTICE:\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_CAST_SPELL:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the magical attack" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_PARALYZE:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the paralysis" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> resist(s) the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_SONIC:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the sonic" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> shake(s) off the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\tcase CMMsg.TYP_LASER:\n\t\t\tif(tool == null)\n\t\t\t\ttackOn = L("<S-NAME> dodge(s) the laser" + endPart);\n\t\t\telse\n\t\t\t\ttackOn = L("<S-NAME> dodge(s) the @x1" + endPart,tool);\n\t\t\tbreak;\n\t\t}\n\t\tif(tackOn!=null)\n\t\t{\n\t\t\tfinal String newStr=target+"/"+source+"/"+tool;\n\t\t\tif(!newStr.equals(lastStr)||((System.currentTimeMillis()-lastRes)>250))\n\t\t\t\tmsg.addTrailerMsg(CMClass.getMsg(target,source,CMMsg.MSG_OK_ACTION,tackOn));\n\t\t\tlastStr=newStr;\n\t\t\tlastRes=System.currentTimeMillis();\n\t\t}\n\t\tmsg.setValue(msg.value()+1);\n\t}\n\n\t@Override\n\tpublic boolean checkDamageSaves(final MOB mob, final CMMsg msg)\n\t{\n\t\tint chanceToFail = 0;\n\t\tif(msg.tool() instanceof Weapon)\n\t\t{\n\t\t\tint charStatCode = -1;\n\t\t\tswitch(((Weapon)msg.tool()).weaponDamageType())\n\t\t\t{\n\t\t\tcase Weapon.TYPE_BASHING:\n\t\t\t\tcharStatCode=CharStats.STAT_SAVE_BLUNT;\n\t\t\t\tbreak;\n\t\t\tcase Weapon.TYPE_PIERCING:\n\t\t\t\tcharStatCode=CharStats.STAT_SAVE_PIERCE;\n\t\t\t\tbreak;\n\t\t\tcase Weapon.TYPE_SLASHING:\n\t\t\t\tcharStatCode=CharStats.STAT_SAVE_SLASH;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tchanceToFail = mob.charStats().getSave(charStatCode);\n\t\t}\n\t\telse\n\t\tif((msg.tool() instanceof Ability)\n\t\t&&(msg.sourceMinor()!=CMMsg.TYP_TEACH))\n\t\t{\n\t\t\tswitch(((Ability)msg.tool()).classificationCode() & Ability.ALL_ACODES)\n\t\t\t{\n\t\t\tcase Ability.ACODE_CHANT:\n\t\t\t\tchanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_CHANTS)\n\t\t\t\t\t\t\t+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);\n\t\t\t\tbreak;\n\t\t\tcase Ability.ACODE_PRAYER:\n\t\t\t\tchanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_PRAYERS)\n\t\t\t\t\t\t\t+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);\n\t\t\t\tbreak;\n\t\t\tcase Ability.ACODE_SPELL:\n\t\t\t\tchanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_SPELLS)\n\t\t\t\t\t\t\t+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);\n\t\t\t\tbreak;\n\t\t\tcase Ability.ACODE_SONG:\n\t\t\t\tchanceToFail=mob.charStats().getSave(CharStats.STAT_SAVE_SONGS)\n\t\t\t\t\t\t\t+mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfinal int charStatCode = CharStats.CODES.RVSCMMSGMAP(msg.sourceMinor());\n\t\t\tif(charStatCode >= 0)\n\t\t\t\tchanceToFail += mob.charStats().getSave(charStatCode);\n\t\t}\n\t\tif ((chanceToFail != 0) && (chanceToFail > (Integer.MIN_VALUE/2)))\n\t\t{\n\t\t\tif (chanceToFail < -100)\n\t\t\t\tchanceToFail = -100;\n\t\t\telse\n\t\t\tif (chanceToFail > 100)\n\t\t\t\tchanceToFail = 100;\n\t\t\tif (CMLib.dice().rollPercentage() < ((chanceToFail < 0) ? (-chanceToFail) : chanceToFail))\n\t\t\t\tmsg.setValue((int)Math.round(CMath.mul(msg.value(),CMath.div(100-chanceToFail,100))));\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean checkSavingThrows(final MOB mob, final CMMsg msg)\n\t{\n\t\tif ((msg.targetMinor() != CMMsg.TYP_WEAPONATTACK) && (msg.value() <= 0))\n\t\t{\n\t\t\tint charStatCode = -1;\n\t\t\tint chanceToFail = 0;\n\t\t\tif(msg.tool() instanceof Ability)\n\t\t\t{\n\t\t\t\tswitch(((Ability)msg.tool()).classificationCode() & Ability.ALL_ACODES)\n\t\t\t\t{\n\t\t\t\tcase Ability.ACODE_CHANT:\n\t\t\t\t\tcharStatCode=CharStats.STAT_SAVE_CHANTS;\n\t\t\t\t\tbreak;\n\t\t\t\tcase Ability.ACODE_PRAYER:\n\t\t\t\t\tcharStatCode=CharStats.STAT_SAVE_PRAYERS;\n\t\t\t\t\tbreak;\n\t\t\t\tcase Ability.ACODE_SPELL:\n\t\t\t\t\tcharStatCode=CharStats.STAT_SAVE_SPELLS;\n\t\t\t\t\tbreak;\n\t\t\t\tcase Ability.ACODE_SONG:\n\t\t\t\t\tcharStatCode=CharStats.STAT_SAVE_SONGS;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif((charStatCode > 0)\n\t\t\t\t&&(msg.targetMinor()==CMMsg.TYP_CAST_SPELL))\n\t\t\t\t\tchanceToFail = mob.charStats().getSave(CharStats.STAT_SAVE_MAGIC);\n\t\t\t}\n\t\t\tif(charStatCode<0)\n\t\t\t\tcharStatCode = CharStats.CODES.RVSCMMSGMAP(msg.targetMinor());\n\t\t\tif(charStatCode >= 0)\n\t\t\t{\n\t\t\t\tchanceToFail += mob.charStats().getSave(charStatCode);\n\t\t\t\tif (chanceToFail > (Integer.MIN_VALUE/2))\n\t\t\t\t{\n\t\t\t\t\tfinal int diff = (mob.phyStats().level() - msg.source().phyStats().level());\n\t\t\t\t\tfinal int diffSign = diff < 0 ? -1 : 1;\n\t\t\t\t\tchanceToFail += (diffSign * (diff * diff));\n\t\t\t\t\tif (chanceToFail < 5)\n\t\t\t\t\t\tchanceToFail = 5;\n\t\t\t\t\telse\n\t\t\t\t\tif (chanceToFail > 95)\n\t\t\t\t\t\tchanceToFail = 95;\n\n\t\t\t\t\tif (CMLib.dice().rollPercentage() < chanceToFail)\n\t\t\t\t\t\tCMLib.combat().resistanceMsgs(msg.source(), mob, msg); // also applies the +1 to msg.value()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void handleBeingGravitied(final MOB mob, final CMMsg msg)\n\t{\n\t\tif(msg.value() >= SpaceObject.ACCELERATION_PASSOUT)\n\t\t{\n\t\t\tfinal Room R=mob.location();\n\t\t\tif(R==null)\n\t\t\t\treturn;\n\t\t\tif(msg.value() >= SpaceObject.ACCELERATION_INSTANTDEATH)\n\t\t\t{\n\t\t\t\tR.show(mob,null,null,CMMsg.MSG_OK_ACTION, L("<S-NAME> is crushed to jelly by acceleration!"));\n\t\t\t\tpostDeath(null, mob, msg);\n\t\t\t}\n\t\t\telse\n\t\t\tif(msg.value() >= SpaceObject.ACCELERATION_UNCONSCIOUSNESS)\n\t\t\t{\n\t\t\t\tAbility A=mob.fetchEffect("Fighter_Whomp");\n\t\t\t\tif(A==null)\n\t\t\t\t\tA=CMClass.getAbility("Fighter_Whomp");\n\t\t\t\tA.startTickDown(msg.source(), mob, 30);\n\t\t\t\tmob.recoverPhyStats();\n\t\t\t\tif(CMLib.flags().isSleeping(mob))\n\t\t\t\t\tR.show(mob,null,null,CMMsg.MSG_OK_ACTION, L("<S-NAME> fall(s) unconscious from extreme acceleration."));\n\t\t\t}\n\t\t\telse\n\t\t\tif(msg.value() >= SpaceObject.ACCELERATION_PASSOUT)\n\t\t\t{\n\t\t\t\tAbility A=mob.fetchEffect("Fighter_Whomp");\n\t\t\t\tif(A==null)\n\t\t\t\t{\n\t\t\t\t\tA=CMClass.getAbility("Fighter_Whomp");\n\t\t\t\t\tA.startTickDown(msg.source(), mob, 3);\n\t\t\t\t\tmob.recoverPhyStats();\n\t\t\t\t\tif(CMLib.flags().isSleeping(mob))\n\t\t\t\t\t\tR.show(mob,null,null,CMMsg.MSG_OK_ACTION, L("<S-NAME> pass(es) out from high acceleration."));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handleBeingHealed(final CMMsg msg)\n\t{\n\t\tif(!(msg.target() instanceof MOB))\n\t\t\treturn;\n\t\tfinal MOB target=(MOB)msg.target();\n\t\tfinal int amt=msg.value();\n\t\tif(amt>0)\n\t\t\ttarget.curState().adjHitPoints(amt,target.maxState());\n\t}\n\n\tprotected boolean bleedableWeapon(final Environmental E)\n\t{\n\t\tif(E==null)\n\t\t\treturn false;\n\t\tif(E instanceof Weapon)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\tif(E instanceof Ability)\n\t\t{\n\t\t\tfinal int code=((Ability)E).classificationCode()&Ability.ALL_ACODES;\n\t\t\tswitch(code)\n\t\t\t{\n\t\t\tcase Ability.ACODE_DISEASE:\n\t\t\tcase Ability.ACODE_POISON:\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void handleBeingDamaged(final CMMsg msg)\n\t{\n\t\tif(!(msg.target() instanceof MOB))\n\t\t\treturn;\n\t\tfinal MOB attacker=msg.source();\n\t\tfinal MOB target=(MOB)msg.target();\n\t\tif(target.amDead()) // already dead, don\'t take more damage.\n\t\t\treturn;\n\t\tfinal int dmg=msg.value();\n\t\tif(Log.combatChannelOn())\n\t\t{\n\t\t\tfinal Item DI=target.fetchWieldedItem();\n\t\t\tfinal Item KI=attacker.fetchWieldedItem();\n\t\t\tfinal String tool=(msg.tool()==null)?"null":msg.tool().name();\n\t\t\tfinal String type=(msg.sourceMinor()==CMMsg.NO_EFFECT)?"??":CMMsg.TYPE_DESCS[msg.sourceMinor()];\n\t\t\tLog.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);\n\t\t}\n\t\t//re-consider this synchro-block -- looks heady and dangerous, but this is the only spot, so ok for now\n\t\tsynchronized(target.curState())\n\t\t{\n\t\t\tif((dmg>0)\n\t\t\t&&(target.curState().getHitPoints()>0))\n\t\t\t{\n\t\t\t\tif((attacker!=null)\n\t\t\t\t&&(attacker.playerStats()!=null)\n\t\t\t\t&&(attacker!=target))\n\t\t\t\t\tattacker.playerStats().bumpLevelCombatStat(PlayerCombatStat.DAMAGE_DONE, attacker.basePhyStats().level(), dmg);\n\t\t\t\tif(target.playerStats()!=null)\n\t\t\t\t\ttarget.playerStats().bumpLevelCombatStat(PlayerCombatStat.DAMAGE_TAKEN, target.basePhyStats().level(), dmg);\n\t\t\t\tif((!target.curState().adjHitPoints(-dmg,target.maxState()))\n\t\t\t\t&&(target.curState().getHitPoints()<1)\n\t\t\t\t&&(target.location()!=null))\n\t\t\t\t\tpostDeath(attacker,target,msg);\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif((Math.round(CMath.div(dmg,target.maxState().getHitPoints())*100.0)>=CMProps.getIntVar(CMProps.Int.INJBLEEDPCTHP))\n\t\t\t\t\t&&bleedableWeapon(msg.tool()))\n\t\t\t\t\t{\n\t\t\t\t\t\tfinal Ability A2=CMClass.getAbility("Bleeding");\n\t\t\t\t\t\tif(A2!=null)\n\t\t\t\t\t\t\tA2.invoke((target),(target),true,0);\n\t\t\t\t\t}\n\t\t\t\t\tif((target.curState().getHitPoints()<target.getWimpHitPoint())\n\t\t\t\t\t&&(target.getWimpHitPoint()>0)\n\t\t\t\t\t&&(target.isInCombat()))\n\t\t\t\t\t\tpostPanic(target,msg);\n\t\t\t\t\telse\n\t\t\t\t\tif((CMProps.getIntVar(CMProps.Int.INJPCTHP)>=(int)Math.round(CMath.div(target.curState().getHitPoints(),target.maxState().getHitPoints())*100.0))\n\t\t\t\t\t&&(!CMLib.flags().isGolem(target))\n\t\t\t\t\t&&(target.fetchEffect("Injury")==null))\n\t\t\t\t\t{\n\t\t\t\t\t\tAbility A=CMClass.getAbility("Injury");\n\t\t\t\t\t\tif(A!=null)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tA.startTickDown(target,target,Ability.TICKS_ALMOST_FOREVER);\n\t\t\t\t\t\t\tA=target.fetchEffect(A.ID());\n\t\t\t\t\t\t\tif(A!=null)\n\t\t\t\t\t\t\t\tA.okMessage(target,msg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handleDeath(final CMMsg msg)\n\t{\n\t\tfinal MOB deadmob=msg.source();\n\t\tif(!deadmob.amDead())\n\t\t{\n\t\t\tif((!deadmob.isMonster())&&(deadmob.soulMate()==null))\n\t\t\t{\n\t\t\t\tif(deadmob.playerStats()!=null)\n\t\t\t\t\tdeadmob.playerStats().bumpLevelCombatStat(PlayerCombatStat.DEATHS_TAKEN, deadmob.basePhyStats().level(), 1);\n\t\t\t\tCMLib.coffeeTables().bump(deadmob,CoffeeTableRow.STAT_DEATHS);\n\t\t\t\tfinal PlayerStats playerStats=deadmob.playerStats();\n\t\t\t\tif(playerStats!=null)\n\t\t\t\t\tplayerStats.setHygiene(0);\n\t\t\t\tfinal List<String> channels=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.DETAILEDDEATHS, deadmob);\n\t\t\t\tfinal List<String> channels2=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.DEATHS, deadmob);\n\t\t\t\tif(!CMLib.flags().isCloaked(deadmob))\n\t\t\t\t{\n\t\t\t\t\tfor(int i=0;i<channels.size();i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tif((msg.tool() instanceof MOB))\n\t\t\t\t\t\t\tCMLib.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);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tCMLib.commands().postChannel(channels.get(i),deadmob.clans(),L("@x1 has just died at @x2",deadmob.Name(),CMLib.map().getExtendedRoomID(deadmob.location())),true);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif(!CMLib.flags().isCloaked(deadmob))\n\t\t\t\t{\n\t\t\t\t\tfor(int i=0;i<channels2.size();i++)\n\t\t\t\t\t\tif((msg.tool() instanceof MOB))\n\t\t\t\t\t\t\tCMLib.commands().postChannel(channels2.get(i),deadmob.clans(),L("@x1 was just killed.",deadmob.Name()),true);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(msg.tool() instanceof MOB)\n\t\t\t{\n\t\t\t\tfinal MOB killer=(MOB)msg.tool();\n\t\t\t\tdoDeathPostProcessing(msg);\n\t\t\t\tint count=10;\n\t\t\t\tMOB killerM=killer;\n\t\t\t\twhile((killerM != null)\n\t\t\t\t&&(--count>0))\n\t\t\t\t{\n\t\t\t\t\tif(killerM.playerStats()!=null)\n\t\t\t\t\t\tkillerM.playerStats().bumpLevelCombatStat(PlayerCombatStat.DEATHS_DONE, killerM.basePhyStats().level(), 1);\n\t\t\t\t\tkillerM=killerM.amFollowing();\n\t\t\t\t}\n\t\t\t\tjustDie(killer,deadmob);\n\t\t\t}\n\t\t\telse\n\t\t\t\tjustDie(null,deadmob);\n\t\t\tdeadmob.tell(deadmob,msg.target(),msg.tool(),msg.sourceMessage());\n\t\t\tif(deadmob.riding()!=null)\n\t\t\t\tdeadmob.riding().delRider(deadmob);\n\t\t\tif(CMLib.flags().isCataloged(deadmob))\n\t\t\t\tCMLib.catalog().bumpDeathPickup(deadmob);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean handleDamageSpam(final MOB observerM, final Physical target, final int amount)\n\t{\n\t\tif((observerM!=null)\n\t\t&&(observerM.playerStats()!=null)\n\t\t&&(target!=null)\n\t\t&&(amount>0))\n\t\t{\n\t\t\tfinal Map<String,int[]> spam=observerM.playerStats().getCombatSpams();\n\t\t\tsynchronized(spam)\n\t\t\t{\n\t\t\t\tfinal String targetName = CMLib.flags().canBeSeenBy(target, observerM)?target.Name():L("Someone");\n\t\t\t\tif(!spam.containsKey(targetName))\n\t\t\t\t\tspam.put(targetName,new int[]{0});\n\t\t\t\tspam.get(targetName)[0]+=amount;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void doDeathPostProcessing(final CMMsg msg)\n\t{\n\t\tfinal MOB deadmob=msg.source();\n\t\tif(msg.tool() instanceof MOB)\n\t\t{\n\t\t\tfinal MOB killer=(MOB)msg.tool();\n\t\t\tif(Log.killsChannelOn())\n\t\t\t{\n\t\t\t\tfinal Item KI=killer.fetchWieldedItem();\n\t\t\t\tfinal Item DI=deadmob.fetchWieldedItem();\n\t\t\t\tfinal String room=CMLib.map().getExtendedRoomID((killer.location()!=null)?killer.location():deadmob.location());\n\t\t\t\tLog.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()));\n\t\t\t}\n\t\t\tif(Log.combatChannelOn())\n\t\t\t{\n\t\t\t\tfinal Item DI=deadmob.fetchWieldedItem();\n\t\t\t\tfinal Item KI=killer.fetchWieldedItem();\n\t\t\t\tLog.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()));\n\t\t\t}\n\t\t\tif(killer.isPlayer())\n\t\t\t\tCMLib.achievements().possiblyBumpAchievement(killer, AchievementLibrary.Event.KILLS, 1, deadmob);\n\t\t\tfinal Set<MOB> grpS=killer.getGroupMembers(new HashSet<MOB>());\n\t\t\tfor(final MOB M : grpS)\n\t\t\t{\n\t\t\t\tif(M.isPlayer())\n\t\t\t\t\tCMLib.achievements().possiblyBumpAchievement(M, AchievementLibrary.Event.GROUPKILLS, 1, deadmob, Integer.valueOf(grpS.size()));\n\t\t\t}\n\t\t\tif((deadmob!=null)\n\t\t\t&&(killer!=null)\n\t\t\t&&(killer!=deadmob)\n\t\t\t&&(deadmob.soulMate()==null)\n\t\t\t&&(!killer.isMonster()))\n\t\t\t{\n\t\t\t\tif((!deadmob.isMonster())\n\t\t\t\t&&((killer.isPlayer())\n\t\t\t\t&&(deadmob.isPlayer()))\n\t\t\t\t&&(!CMLib.players().isSameAccount(killer, deadmob)))\n\t\t\t\t{\n\t\t\t\t\tCMLib.coffeeTables().bump(deadmob,CoffeeTableRow.STAT_PKDEATHS);\n\t\t\t\t\tif(killer.playerStats()!=null)\n\t\t\t\t\t\tCMLib.get(killer.session())._players().bumpPrideStat(killer,PrideStat.PVPKILLS, 1);\n\t\t\t\t}\n\t\t\t\tif((killer.session()!=null)\n\t\t\t\t&&(deadmob.clans().iterator().hasNext())\n\t\t\t\t&&((deadmob.session()==null)\n\t\t\t\t\t||(!deadmob.session().getAddress().equalsIgnoreCase(killer.session().getAddress())))\n\t\t\t\t&&((deadmob.playerStats()==null)\n\t\t\t\t\t||(killer.playerStats()==null)\n\t\t\t\t\t||(deadmob.playerStats().getAccount()==null)\n\t\t\t\t\t||(killer.playerStats().getAccount()==null)\n\t\t\t\t\t||(deadmob.playerStats().getAccount()!=killer.playerStats().getAccount())))\n\t\t\t\t{\n\t\t\t\t\tfinal List<Pair<Clan,Integer>> list = CMLib.clans().findRivalrousClans(killer, deadmob);\n\t\t\t\t\tif(list.size()>0)\n\t\t\t\t\t{\n\t\t\t\t\t\tfor(final Pair<Clan,Integer> c : list)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tc.first.recordClanKill(killer,deadmob);\n\t\t\t\t\t\t\tif(killer.getClanRole(c.first.clanID()) != null)\n\t\t\t\t\t\t\t\tCMLib.achievements().possiblyBumpAchievement(killer, AchievementLibrary.Event.CLANKILLS, 1, c.first, deadmob);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void pickNextVictim(final MOB observer, final MOB deadmob, final Set<MOB> deadGroupH)\n\t{\n\t\tfinal Room R=observer.location();\n\t\tif(R!=null)\n\t\t{\n\t\t\tMOB newTargetM=null;\n\t\t\tfinal Set<MOB> myGroupH=observer.getGroupMembers(new HashSet<MOB>());\n\t\t\tfor(int r=0;r<R.numInhabitants();r++)\n\t\t\t{\n\t\t\t\tfinal MOB M=R.fetchInhabitant(r);\n\t\t\t\tif((M!=observer)\n\t\t\t\t&&(M!=deadmob)\n\t\t\t\t&&(M!=null)\n\t\t\t\t&&(M.getVictim()==observer)\n\t\t\t\t&&(!M.amDead())\n\t\t\t\t&&(CMLib.flags().isInTheGame(M,true))\n\t\t\t\t&&(observer.mayIFight(M)))\n\t\t\t\t{\n\t\t\t\t\tnewTargetM=M;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(newTargetM==null)\n\t\t\tfor(int r=0;r<R.numInhabitants();r++)\n\t\t\t{\n\t\t\t\tfinal MOB M=R.fetchInhabitant(r);\n\t\t\t\tif(M==null)\n\t\t\t\t\tcontinue;\n\t\t\t\tfinal MOB vic=M.getVictim();\n\t\t\t\tif((M!=observer)\n\t\t\t\t&&(M!=deadmob)\n\t\t\t\t&&(deadGroupH.contains(M)\n\t\t\t\t\t||((vic!=null)&&(myGroupH.contains(vic))))\n\t\t\t\t&&(!M.amDead())\n\t\t\t\t&&(CMLib.flags().isInTheGame(M,true))\n\t\t\t\t&&(observer.mayIFight(M)))\n\t\t\t\t{\n\t\t\t\t\tnewTargetM=M;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tobserver.setVictim(null); //BZ: cause of range issues.\n\t\t\tif(newTargetM!=null)\n\t\t\t{\n\t\t\t\tobserver.setVictim(newTargetM);\n\t\t\t\tif(newTargetM.getVictim()==observer)\n\t\t\t\t\tobserver.setRangeToTarget(newTargetM.rangeToTarget());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handleObserveDeath(final MOB observer, final MOB fighting, final CMMsg msg)\n\t{\n\t\t// no longer does a damn thing\n\t}\n\n\t@Override\n\tpublic void handleDamageSpamSummary(final MOB mob)\n\t{\n\t\tif((mob.isAttributeSet(MOB.Attrib.NOBATTLESPAM))\n\t\t&&(mob.playerStats()!=null))\n\t\t{\n\t\t\tint numEnemies=0;\n\t\t\tfinal Room R=mob.location();\n\t\t\tfor(final Enumeration<MOB> m=R.inhabitants();m.hasMoreElements();)\n\t\t\t{\n\t\t\t\tfinal MOB M=m.nextElement();\n\t\t\t\tif((M!=null)&&(M!=mob)&&(M.getVictim()==mob))\n\t\t\t\t\tnumEnemies++;\n\t\t\t}\n\t\t\tfinal Map<String,int[]> combatSpam = mob.playerStats().getCombatSpams();\n\t\t\tfinal StringBuilder msg=new StringBuilder("");\n\t\t\tsynchronized(combatSpam)\n\t\t\t{\n\t\t\t\tif(numEnemies>1)\n\t\t\t\t\tmsg.append("^<FIGHT^>"+L("Fighting @x1 enemies.  ",""+numEnemies)+"^</FIGHT^>");\n\t\t\t\tif(combatSpam.size()==0)\n\t\t\t\t{\n\t\t\t\t\tif(mob.isInCombat()||(numEnemies>0))\n\t\t\t\t\t\tmsg.append("^<FIGHT^>"+L("No new combat damage reported.")+"^</FIGHT^>");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tmsg.append("^<FIGHT^>"+L("New combat damage: "));\n\t\t\t\t\tfor(final String str : combatSpam.keySet())\n\t\t\t\t\t{\n\t\t\t\t\t\tmsg.append(str).append(" ").append(combatSpam.get(str)[0]).append(" points. ");\n\t\t\t\t\t}\n\t\t\t\t\tmsg.append("^</FIGHT^>");\n\t\t\t\t}\n\t\t\t\tmob.tell(msg.toString());\n\t\t\t\tcombatSpam.clear();\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void handleBeingAssaulted(final CMMsg msg)\n\t{\n\t\tif(!(msg.target() instanceof MOB))\n\t\t\treturn;\n\t\tfinal MOB attacker=msg.source();\n\t\tfinal MOB target=(MOB)msg.target();\n\n\t\tif((!target.isInCombat())\n\t\t&&(target.location()!=null)\n\t\t&&(target.location().isInhabitant(attacker))\n\t\t&&((!CMath.bset(msg.sourceMajor(),CMMsg.MASK_ALWAYS))\n\t\t\t||(!(msg.tool() instanceof DiseaseAffect))))\n\t\t{\n\t\t\testablishRange(target,attacker,msg.tool());\n\t\t\ttarget.setVictim(attacker);\n\t\t}\n\t\tif(target.isInCombat())\n\t\t{\n\t\t\tif((attacker.session()!=null)\n\t\t\t&&(!attacker.isPossessing()))\n\t\t\t{\n\t\t\t\tif(!target.isMonster())\n\t\t\t\t\tattacker.session().setLastPKFight();\n\t\t\t\telse\n\t\t\t\t\tattacker.session().setLastNPCFight();\n\t\t\t}\n\t\t\tif(msg.targetMinor()==CMMsg.TYP_WEAPONATTACK)\n\t\t\t{\n\t\t\t\tItem weapon=attacker.getNaturalWeapon();\n\t\t\t\tif((msg.tool() instanceof Item))\n\t\t\t\t\tweapon=(Item)msg.tool();\n\t\t\t\tif(weapon!=null)\n\t\t\t\t{\n\t\t\t\t\tfinal boolean isHit=rollToHit(attacker,target);\n\t\t\t\t\tpostWeaponAttackResult(attacker,target,weapon,0, isHit);\n\t\t\t\t\tif(isHit)\n\t\t\t\t\t\tmsg.setValue(1);\n\t\t\t\t}\n\t\t\t\tif((target.soulMate()==null)\n\t\t\t\t&&(target.playerStats()!=null)\n\t\t\t\t&&(target.location()!=null)\n\t\t\t\t&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.HYGIENE)))\n\t\t\t\t\ttarget.playerStats().adjHygiene(PlayerStats.HYGIENE_FIGHTDIRTY);\n\n\t\t\t\tif((attacker.isMonster())&&(!attacker.isInCombat()))\n\t\t\t\t\tattacker.setVictim(target);\n\t\t\t}\n\t\t\telse\n\t\t\tif(msg.tool() instanceof Item)\n\t\t\t\tpostWeaponAttackResult(attacker,target,(Item)msg.tool(),0, true);\n\t\t}\n\t\tif(CMLib.flags().isSitting(target)||CMLib.flags().isSleeping(target))\n\t\t\tCMLib.commands().postStand(target,true, false);\n\t}\n\n\t@Override\n\tpublic void makeFollowersFight(final MOB observerM, final MOB defenderM, final MOB attackerM)\n\t{\n\t\tif((attackerM==null)||(defenderM==null)||observerM==null)\n\t\t\treturn;\n\t\tif(attackerM==defenderM)\n\t\t\treturn;\n\t\tif((defenderM==observerM)||(attackerM==observerM))\n\t\t\treturn;\n\t\tif((defenderM.location()!=observerM.location())\n\t\t||(defenderM.location()!=attackerM.location()))\n\t\t\treturn;\n\t\tif((observerM.isAttributeSet(MOB.Attrib.AUTOASSIST))\n\t\t||(CMath.bset(observerM.phyStats().disposition(), PhyStats.IS_UNHELPFUL)))\n\t\t\treturn;\n\t\tif(observerM.isInCombat())\n\t\t\treturn;\n\t\tfinal MOB observerFollows=observerM.amFollowing();\n\t\tfinal MOB targetFollows=defenderM.amFollowing();\n\t\tfinal MOB sourceFollows=attackerM.amFollowing();\n\n\t\tif((observerFollows==defenderM)\n\t\t||(targetFollows==observerM)\n\t\t||((targetFollows!=null)&&(targetFollows==observerFollows)))\n\t\t{\n\t\t\tobserverM.setVictim(attackerM);\n\t\t\testablishRange(observerM,attackerM,observerM.fetchWieldedItem());\n\t\t}\n\t\telse\n\t\tif((observerFollows==attackerM)\n\t\t||(sourceFollows==observerM)\n\t\t||((sourceFollows!=null)&&(sourceFollows==observerFollows)))\n\t\t{\n\t\t\tobserverM.setVictim(defenderM);\n\t\t\testablishRange(observerM,defenderM,observerM.fetchWieldedItem());\n\t\t}\n\t}\n\n\tpublic List<MOB> getAllInProximity(final MOB to, final int distance)\n\t{\n\t\tfinal Room R=to.location();\n\t\tfinal Vector<MOB> V=new Vector<MOB>();\n\t\tV.addElement(to);\n\t\tif(R==null)\n\t\t\treturn V;\n\t\tfinal List<MOB> everyV=new ArrayList<MOB>();\n\t\tfor(int i=0;i<R.numInhabitants();i++)\n\t\t\teveryV.add(R.fetchInhabitant(i));\n\t\tif(!everyV.contains(to))\n\t\t\teveryV.add(to);\n\t\tfinal int[][] map=new int[everyV.size()][everyV.size()];\n\t\tfor(int x=0;x<map.length;x++)\n\t\t{\n\t\t\tfor(int y=0;y<map.length;y++)\n\t\t\t\tmap[x][y]=-1;\n\t\t}\n\n\t\treturn V;\n\t}\n\n\tprivate int maxRangeWith(final MOB mob, final Environmental tool)\n\t{\n\t\tint max = 0;\n\t\tif (tool != null)\n\t\t\tmax = tool.maxRange();\n\t\tif (mob.maxRange() < max)\n\t\t\tmax = mob.maxRange();\n\t\treturn max;\n\t}\n\n\tprivate int minRangeWith(final MOB mob, final Environmental tool)\n\t{\n\t\tif (tool != null)\n\t\t\treturn tool.minRange();\n\t\treturn mob.minRange();\n\t}\n\n\t@Override\n\tpublic int calculateRangeToTarget(final MOB source, final MOB target, final Environmental tool)\n\t{\n\t\tif(source.riding()!=null)\n\t\t{\n\t\t\tif((target==source.riding())||(source.riding().amRiding(target)))\n\t\t\t\treturn 0;\n\t\t\telse\n\t\t\tif((source.riding() instanceof MOB)\n\t\t\t   &&(((MOB)source.riding()).isInCombat())\n\t\t\t   &&(((MOB)source.riding()).getVictim()==target)\n\t\t\t   &&(((MOB)source.riding()).rangeToTarget()>=0)\n\t\t\t   &&(((MOB)source.riding()).rangeToTarget()<source.rangeToTarget()))\n\t\t\t\treturn ((MOB)source.riding()).rangeToTarget();\n\t\t\telse\n\t\t\tfor(int r=0;r<source.riding().numRiders();r++)\n\t\t\t{\n\t\t\t\tfinal Rider rider=source.riding().fetchRider(r);\n\t\t\t\tif(!(rider instanceof MOB))\n\t\t\t\t\tcontinue;\n\t\t\t\tfinal MOB otherMOB=(MOB)rider;\n\t\t\t\tif((otherMOB!=source)\n\t\t\t\t   &&(otherMOB.isInCombat())\n\t\t\t\t   &&(otherMOB.getVictim()==target)\n\t\t\t\t   &&(otherMOB.rangeToTarget()>=0)\n\t\t\t\t   &&(otherMOB.rangeToTarget()<source.rangeToTarget()))\n\t\t\t\t\treturn otherMOB.rangeToTarget();\n\t\t\t}\n\t\t}\n\n\t\tfinal MOB follow=source.amFollowing();\n\t\tif((target.getVictim()==source)&&(target.rangeToTarget()>=0))\n\t\t\treturn target.rangeToTarget();\n\t\telse\n\t\tif((follow!=null)&&(follow.location()==source.location()))\n\t\t{\n\t\t\tint newRange=follow.fetchFollowerOrder(source);\n\t\t\tif(newRange<0)\n\t\t\t{\n\t\t\t\tif(follow.rangeToTarget()>=0)\n\t\t\t\t{\n\t\t\t\t\tnewRange=follow.rangeToTarget();\n\t\t\t\t\tif(newRange<maxRangeWith(source,tool))\n\t\t\t\t\t\tnewRange=maxRangeWith(source,tool);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tnewRange=maxRangeWith(source,tool);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif(follow.rangeToTarget()>=0)\n\t\t\t\t\tnewRange=newRange+follow.rangeToTarget();\n\t\t\t}\n\t\t\tif((source.location()!=null)&&(source.location().maxRange()<newRange))\n\t\t\t\tnewRange=source.location().maxRange();\n\t\t\treturn newRange;\n\t\t}\n\t\telse\n\t\t\treturn maxRangeWith(source,tool);\n\t}\n\n\t@Override\n\tpublic void establishRange(final MOB source, final MOB target, final Environmental tool)\n\t{\n\t\t// establish and enforce range\n\t\tif((source.rangeToTarget()<0))\n\t\t{\n\t\t\tfinal int newRange = calculateRangeToTarget(source, target, tool);\n\t\t\tif(newRange != source.rangeToTarget())\n\t\t\t{\n\t\t\t\tsource.setRangeToTarget(newRange);\n\t\t\t\tsource.recoverPhyStats();\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void subtickAttack(final MOB fighter, Item weapon, final int folrange)\n\t{\n\t\tif((weapon!=null)\n\t\t&&(weapon.amWearingAt(Wearable.IN_INVENTORY)))\n\t\t\tweapon=fighter.fetchWieldedItem();\n\t\tif((!fighter.isAttributeSet(MOB.Attrib.AUTOMELEE)))\n\t\t\tpostAttack(fighter,fighter.getVictim(),weapon);\n\t\telse\n\t\t{\n\t\t\tboolean inminrange=(fighter.rangeToTarget()>=minRangeWith(fighter, weapon));\n\t\t\tboolean inmaxrange=(fighter.rangeToTarget()<=maxRangeWith(fighter, weapon));\n\t\t\tif((folrange>=0)\n\t\t\t&&(fighter.rangeToTarget()>=0)\n\t\t\t&&(folrange!=fighter.rangeToTarget()))\n\t\t\t{\n\t\t\t\tif(fighter.rangeToTarget()<folrange)\n\t\t\t\t\tinminrange=false;\n\t\t\t\telse\n\t\t\t\tif(fighter.rangeToTarget()>folrange)\n\t\t\t\t{\n\t\t\t\t\tfinal MOB victim=fighter.getVictim();\n\t\t\t\t\t// these settings are ONLY to ensure that neither of the\n\t\t\t\t\t// next two conditions evaluate to true.\n\t\t\t\t\tinminrange=true;\n\t\t\t\t\tinmaxrange=false;\n\t\t\t\t\t// we advance\n\t\t\t\t\tfinal CMMsg msg=CMClass.getMsg(fighter,victim,CMMsg.MSG_ADVANCE,L("<S-NAME> advance(s) at <T-NAMESELF>."));\n\t\t\t\t\tif(fighter.location().okMessage(fighter,msg))\n\t\t\t\t\t{\n\t\t\t\t\t\tfighter.location().send(fighter,msg);\n\t\t\t\t\t\tfighter.setRangeToTarget(fighter.rangeToTarget()-1);\n\t\t\t\t\t\tif((victim != null)&& (victim.getVictim()==fighter))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvictim.setRangeToTarget(fighter.rangeToTarget());\n\t\t\t\t\t\t\tvictim.recoverPhyStats();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif((!inminrange)\n\t\t\t&&(fighter.curState().getMovement()>=25))\n\t\t\t{\n\t\t\t\tfinal CMMsg msg=CMClass.getMsg(fighter,fighter.getVictim(),CMMsg.MSG_RETREAT,L("<S-NAME> retreat(s) before <T-NAME>."));\n\t\t\t\tif(fighter.location().okMessage(fighter,msg))\n\t\t\t\t\tfighter.location().send(fighter,msg);\n\t\t\t}\n\t\t\telse\n\t\t\tif(inminrange\n\t\t\t&&inmaxrange\n\t\t\t&&((weapon!=null)||(fighter.rangeToTarget()==0)))\n\t\t\t\tpostAttack(fighter,fighter.getVictim(),weapon);\n\t\t}\n\t}\n\n\tprotected void subtickBeforeAttack(final MOB fighter, final CombatSystem combatSystem)\n\t{\n\t\t// combat que system eats up standard commands\n\t\t// before using any attacks\n\t\twhile(((combatSystem==CombatLibrary.CombatSystem.QUEUE)||(combatSystem==CombatLibrary.CombatSystem.TURNBASED))\n\t\t&&(!fighter.amDead())\n\t\t&&(fighter.dequeCommand()))\n\t\t\t{\n\t\t\t}\n\t}\n\n\tprotected void subtickAfterAttack(final MOB fighter)\n\t{\n\t\t// this code is for auto-retargeting of players\n\t\t// is mostly not handled by combatabilities in a smarter way\n\t\tfinal MOB target=fighter.getVictim();\n\t\tif((target!=null)\n\t\t&&(fighter.isMonster())\n\t\t&&(target.isMonster())\n\t\t&&(CMLib.dice().rollPercentage()==1)\n\t\t&&((fighter.amFollowing()==null)||(fighter.amFollowing().isMonster()))\n\t\t&&(!target.amDead())\n\t\t&&(fighter.location()!=null))\n\t\t{\n\t\t\tMOB M=null;\n\t\t\tfinal Room R=fighter.location();\n\t\t\tMOB nextVictimM=null;\n\t\t\tfor(int m=0;m<R.numInhabitants();m++)\n\t\t\t{\n\t\t\t\tM=R.fetchInhabitant(m);\n\t\t\t\tif((M!=null)\n\t\t\t\t&&(!M.isMonster())\n\t\t\t\t&&(M.getVictim()==fighter)\n\t\t\t\t&&((nextVictimM==null)||(M.rangeToTarget()<nextVictimM.rangeToTarget())))\n\t\t\t\t\tnextVictimM=M;\n\t\t\t}\n\t\t\tif((nextVictimM!=null)&&(nextVictimM.isInCombat()))\n\t\t\t\tfighter.setVictim(nextVictimM);\n\t\t}\n\t}\n\n\tprotected MUDFight getSessionMUDFight(final Session sess)\n\t{\n\t\tif(sess == null)\n\t\t\treturn this;\n\t\tfinal CombatLibrary lib=CMLib.get(sess)._combat();\n\t\tif(lib instanceof MUDFight)\n\t\t\treturn (MUDFight)lib;\n\t\treturn this;\n\t}\n\n\t@Override\n\tpublic void dispenseExperience(final Set<MOB> killers, final Set<MOB> dividers, final MOB killed)\n\t{\n\t\tint totalLevels=0;\n\t\tint totalSquaredLevels=0;\n\t\tint highestLevel=0;\n\n\t\tfor (final MOB mob : dividers)\n\t\t{\n\t\t\tfinal int lvl=mob.phyStats().level();\n\t\t\ttotalSquaredLevels += (lvl*lvl);\n\t\t\ttotalLevels += lvl;\n\t\t\tif(lvl>highestLevel)\n\t\t\t\thighestLevel=lvl;\n\t\t}\n\n\t\tfinal int killedLevel=(killed==null)?highestLevel:killed.phyStats().level();\n\t\tfinal double[] totalVars={\n\t\t\tdividers.size(),\n\t\t\ttotalSquaredLevels,\n\t\t\ttotalLevels,\n\t\t\tkillers.size(),\n\t\t\thighestLevel,\n\t\t\tkilledLevel\n\t\t};\n\t\tfinal double expAmount = CMath.parseMathExpression(this.totalCombatExperienceFormula, totalVars, 0.0);\n\n\t\tfinal double[] indiVars = {\n\t\t\texpAmount,\n\t\t\t0.0,\n\t\t\ttotalSquaredLevels,\n\t\t\t0.0,\n\t\t\ttotalLevels,\n\t\t\tkillers.size(),\n\t\t\thighestLevel,\n\t\t\tkilledLevel\n\t\t};\n\t\tfor (final MOB mob : killers)\n\t\t{\n\t\t\tindiVars[1]=mob.phyStats().level()*mob.phyStats().level();\n\t\t\tindiVars[3]=mob.phyStats().level();\n\t\t\tfinal CompiledFormula indXPformula = getSessionMUDFight(mob.session()).individualCombatExpFormula;\n\t\t\tfinal int myAmount=(int)Math.round(CMath.parseMathExpression(indXPformula, indiVars, 0.0));\n\t\t\tCMLib.get(mob.session())._leveler().postExperience(mob,killed,"",myAmount,false);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void tickCombat(final MOB fighter)\n\t{\n\t\tItem weapon=fighter.fetchWieldedItem();\n\n\t\tif((fighter.isAttributeSet(MOB.Attrib.AUTODRAW))&&(weapon==null))\n\t\t{\n\t\t\tCMLib.commands().postDraw(fighter,false,true);\n\t\t\tweapon=fighter.fetchWieldedItem();\n\t\t}\n\n\t\tfinal CombatSystem combatSystem=CombatSystem.values()[CMProps.getIntVar(CMProps.Int.COMBATSYSTEM) % CombatSystem.values().length];\n\n\t\tsubtickBeforeAttack(fighter, combatSystem);\n\n\t\tfinal int folrange=(fighter.isAttributeSet(MOB.Attrib.AUTOMELEE)\n\t\t\t\t\t\t&&(fighter.amFollowing()!=null)\n\t\t\t\t\t\t&&(fighter.amFollowing().getVictim()==fighter.getVictim())\n\t\t\t\t\t\t&&(fighter.amFollowing().rangeToTarget()>=0)\n\t\t\t\t\t\t&&(fighter.amFollowing().fetchFollowerOrder(fighter)>=0))?\n\t\t\t\t\t\t\tfighter.amFollowing().fetchFollowerOrder(fighter)+fighter.amFollowing().rangeToTarget():-1;\n\t\tif(CMLib.flags().isAliveAwakeMobile(fighter,true))\n\t\t{\n\t\t\tif(((combatSystem!=CombatLibrary.CombatSystem.MANUAL)&&(combatSystem!=CombatLibrary.CombatSystem.TURNBASED))\n\t\t\t||(fighter.isMonster()))\n\t\t\t{\n\t\t\t\tfinal int saveAction=(combatSystem!=CombatLibrary.CombatSystem.DEFAULT)?0:1;\n\t\t\t\tint numAttacks=(int)Math.round(Math.floor(fighter.actions()))-saveAction;\n\t\t\t\tif((combatSystem==CombatLibrary.CombatSystem.DEFAULT)\n\t\t\t\t&&(numAttacks>(int)Math.round(Math.floor(fighter.phyStats().speed()+0.9))))\n\t\t\t\t\tnumAttacks=(int)Math.round(Math.floor(fighter.phyStats().speed()+0.9));\n\t\t\t\tfor(int s=0;s<numAttacks;s++)\n\t\t\t\t{\n\t\t\t\t\tif((!fighter.amDead())\n\t\t\t\t\t&&(fighter.curState().getHitPoints()>0)\n\t\t\t\t\t&&(fighter.isInCombat())\n\t\t\t\t\t&&(fighter.actions()>=1.0)\n\t\t\t\t\t&&((s==0)||(CMLib.flags().isStanding(fighter))))\n\t\t\t\t\t{\n\t\t\t\t\t\tfighter.setActions(fighter.actions()-1.0);\n\t\t\t\t\t\tsubtickAttack(fighter,weapon,folrange);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif(CMLib.dice().rollPercentage()>(fighter.charStats().getStat(CharStats.STAT_CONSTITUTION)*4))\n\t\t\t\t\tfighter.curState().adjMovement(-1,fighter.maxState());\n\t\t\t}\n\t\t}\n\t\tsubtickAfterAttack(fighter);\n\t}\n\n\t@Override\n\tpublic boolean isKnockedOutUponDeath(final MOB deadM, final MOB killerM)\n\t{\n\t\tString whatToDo=null;\n\t\tif(((deadM.isMonster())||(deadM.soulMate()!=null)))\n\t\t\twhatToDo=CMProps.getVar(CMProps.Str.MOBDEATH).toUpperCase();\n\t\telse\n\t\t\twhatToDo=CMProps.get(deadM.session()).getStr(CMProps.Str.PLAYERDEATH).toUpperCase();\n\t\tfinal List<String> whatsToDo=CMParms.parseCommas(whatToDo,true);\n\t\tfinal double[] fakeVarVals={1.0,1.0,1.0};\n\t\tfor(int w=0;w<whatsToDo.size();w++)\n\t\t{\n\t\t\twhatToDo=whatsToDo.get(w);\n\t\t\tif(whatToDo.startsWith("OUT ")&&(CMath.isMathExpression(whatToDo.substring(4).trim(),fakeVarVals)))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void expendEnergy(final MOB mob, final boolean expendMovement)\n\t{\n\t\tif(mob==null)\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\tfinal Room room=mob.location();\n\t\tif(room!=null)\n\t\t{\n\t\t\tfinal CharState curState=mob.curState();\n\t\t\tfinal CharState maxState=mob.maxState();\n\t\t\tif(expendMovement)\n\t\t\t{\n\t\t\t\tint move=-room.pointsPerMove();\n\t\t\t\tif(mob.phyStats().weight()>mob.maxCarry())\n\t\t\t\t\tmove+=(int)Math.round(CMath.mul(move,10.0*CMath.div(mob.phyStats().weight()-mob.maxCarry(),mob.maxCarry())));\n\t\t\t\tcurState.adjMovement(move,maxState);\n\t\t\t}\n\t\t\tif((!CMLib.flags().isSleeping(mob))\n\t\t\t&&(!CMSecurity.isAllowed(mob,room,CMSecurity.SecFlag.IMMORT)))\n\t\t\t{\n\t\t\t\tint factor=mob.baseWeight()/500;\n\t\t\t\tif(factor<1)\n\t\t\t\t\tfactor=1;\n\t\t\t\tif((!CMSecurity.isDisabled(CMSecurity.DisFlag.THIRST))\n\t\t\t\t&&(mob.maxState().getThirst() < (Integer.MAX_VALUE/2)))\n\t\t\t\t\tcurState.adjThirst(-(room.thirstPerRound()*factor),maxState.maxThirst(mob.baseWeight()));\n\t\t\t\tif((!CMSecurity.isDisabled(CMSecurity.DisFlag.HUNGER))\n\t\t\t\t&&(mob.maxState().getHunger() < (Integer.MAX_VALUE/2)))\n\t\t\t\t\tcurState.adjHunger(-factor,maxState.maxHunger(mob.baseWeight()));\n\t\t\t}\n\t\t\tfinal boolean thirsty=(curState.getThirst()<=0);\n\t\t\tfinal boolean hungry=(curState.getHunger()<=0);\n\t\t\tif((hungry||thirsty)&&(!expendMovement))\n\t\t\t{\n\t\t\t\tfinal int ticksThirsty=curState.adjTicksThirsty(thirsty);\n\t\t\t\tfinal int ticksHungry=curState.adjTicksHungry(hungry);\n\n\t\t\t\tif((ticksThirsty>CharState.DEATH_THIRST_TICKS)\n\t\t\t\t||(ticksHungry>CharState.DEATH_HUNGER_TICKS))\n\t\t\t\t{\n\t\t\t\t\tif(thirsty)\n\t\t\t\t\t\tmob.tell(L("YOU ARE DYING OF THIRST!"));\n\t\t\t\t\tif(hungry)\n\t\t\t\t\t\tmob.tell(L("YOU ARE DYING OF HUNGER!"));\n\t\t\t\t\tpostDeath(null,mob,null);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\tif(ticksThirsty>CharState.DEATH_THIRST_TICKS-30)\n\t\t\t\t\tmob.tell(L("You are dehydrated, and near death.  DRINK SOMETHING!"));\n\t\t\t\telse\n\t\t\t\tif(ticksHungry>CharState.DEATH_HUNGER_TICKS-30)\n\t\t\t\t\tmob.tell(L("You are starved, and near death.  EAT SOMETHING!"));\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif(thirsty && (((ticksThirsty-1) % CharState.ANNOYANCE_DEFAULT_TICKS)==0))\n\t\t\t\t\t{\n\t\t\t\t\t\tif(ticksThirsty>((CharState.DEATH_THIRST_TICKS/2)+(CharState.DEATH_THIRST_TICKS/4)))\n\t\t\t\t\t\t\tmob.tell(L("You are dehydrated! Drink something!"));\n\t\t\t\t\t\telse\n\t\t\t\t\t\tif(ticksThirsty>(CharState.DEATH_THIRST_TICKS/2))\n\t\t\t\t\t\t\tmob.tell(L("You are parched! Drink something!"));\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tmob.tell(L("You are thirsty."));\n\t\t\t\t\t}\n\t\t\t\t\tif((hungry) && (((ticksHungry-1) % CharState.ANNOYANCE_DEFAULT_TICKS)==0))\n\t\t\t\t\t{\n\t\t\t\t\t\tif(ticksHungry>((CharState.DEATH_HUNGER_TICKS/2)+(CharState.DEATH_HUNGER_TICKS/4)))\n\t\t\t\t\t\t\tmob.tell(L("You are starved! Eat something!"));\n\t\t\t\t\t\telse\n\t\t\t\t\t\tif(ticksHungry>(CharState.DEATH_HUNGER_TICKS/2))\n\t\t\t\t\t\t\tmob.tell(L("You are famished! Eat something!"));\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tmob.tell(L("You are hungry."));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean handleCombatLossConsequences(final MOB deadM, final MOB killerM, final String[] consequences, int[] lostExperience, String message)\n\t{\n\t\tif((consequences==null)||(consequences.length==0))\n\t\t\treturn false;\n\t\tif(lostExperience==null)\n\t\t\tlostExperience=new int[1];\n\t\tfinal int baseExperience=lostExperience[0];\n\t\tlostExperience[0]=0;\n\t\tint rejuv=deadM.phyStats().rejuv();\n\t\tif((rejuv==0)||(rejuv==Integer.MAX_VALUE))\n\t\t\trejuv=deadM.phyStats().level();\n\t\tif(((!deadM.isMonster())&&(deadM.soulMate()==null)))\n\t\t\trejuv=1;\n\t\tfinal double[] varVals={\n\t\t\t\tdeadM.basePhyStats().level()>deadM.phyStats().level()?deadM.basePhyStats().level():deadM.phyStats().level(),\n\t\t\t\t(killerM!=null)?killerM.phyStats().level():0,\n\t\t\t\trejuv\n\t\t};\n\t\tfor (final String command : consequences)\n\t\t{\n\t\t\tfinal String whatToDo=command.toUpperCase();\n\t\t\tif(whatToDo.startsWith("UNL")) // "UNLEVEL\n\t\t\t{\n\t\t\t\tfinal Vector<String> V=CMParms.parse(whatToDo);\n\t\t\t\tint times=1;\n\t\t\t\tif((V.size()>1)&&(CMath.s_int(V.lastElement())>1))\n\t\t\t\t\ttimes=CMath.s_int(V.lastElement());\n\t\t\t\tfor(int t=0;t<times;t++)\n\t\t\t\t\tCMLib.leveler().unLevel(deadM);\n\t\t\t}\n\t\t\telse\n\t\t\tif(whatToDo.startsWith("RECALL"))\n\t\t\t\tdeadM.killMeDead(false);\n\t\t\telse\n\t\t\tif(whatToDo.startsWith("ASTR")) // "ASTRAL\n\t\t\t{\n\t\t\t\tfinal Ability A=CMClass.getAbility("Prop_AstralSpirit");\n\t\t\t\tif((A!=null)&&(deadM.fetchAbility(A.ID())==null))\n\t\t\t\t{\n\t\t\t\t\tdeadM.tell(L("^HYou are now a spirit.^N"));\n\t\t\t\t\tif(whatToDo.startsWith("ASTRAL_R"))\n\t\t\t\t\t{\n\t\t\t\t\t\tA.setMiscText("SELF-RES");\n\t\t\t\t\t\tdeadM.tell(L("^HFind your corpse and use ENTER [body name] to re-enter your body.^N"));\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tdeadM.tell(L("^HFind your corpse have someone resurrect it.^N"));\n\t\t\t\t\tdeadM.addAbility(A);\n\t\t\t\t\tA.autoInvocation(deadM, false);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\tif(whatToDo.startsWith("OUT ")&&(CMath.isMathExpression(whatToDo.substring(4).trim(),varVals)))\n\t\t\t{\n\t\t\t\tfinal Ability A=CMClass.getAbility("Skill_ArrestingSap");\n\t\t\t\tfinal int tickDown=CMath.s_parseIntExpression(whatToDo.substring(4).trim(),varVals);\n\t\t\t\tif((A!=null)&&(tickDown>0))\n\t\t\t\t{\n\t\t\t\t\tA.invoke(deadM,new XVector<String>(""+tickDown,"SAFELY"),deadM,true,0);\n\t\t\t\t\tdeadM.resetToMaxState();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\tif(whatToDo.startsWith("PUR")) // "PURGE"\n\t\t\t{\n\t\t\t\tint maxLives = 1;\n\t\t\t\tfinal int x = whatToDo.indexOf(\' \');\n\t\t\t\tif(x>0)\n\t\t\t\t\tmaxLives = CMath.s_int(whatToDo.substring(x+1).trim());\n\t\t\t\tfinal MOB deadMOB=(!deadM.isPlayer())?CMLib.get(deadM.session())._players().getLoadPlayer(deadM.Name()):deadM;\n\t\t\t\tif((deadMOB!=null)&&(deadM.session()!=null))\n\t\t\t\t{\n\t\t\t\t\tif((deadMOB.playerStats()==null) // this happens after the bump, so...\n\t\t\t\t\t||(deadMOB.playerStats().deathCounter(0)>=maxLives))\n\t\t\t\t\t{\n\t\t\t\t\t\tCMLib.threads().executeRunnable(deadM.session().getGroupName(), new Runnable()\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfinal MOB mob = deadMOB;\n\t\t\t\t\t\t\t@Override\n\t\t\t\t\t\t\tpublic void run()\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfinal Session session=mob.session();\n\t\t\t\t\t\t\t\tif(session!=null)\n\t\t\t\t\t\t\t\t\tsession.stopSession(true, true, true);\n\t\t\t\t\t\t\t\t// preserve their corpse, if it is in the game\n\t\t\t\t\t\t\t\tfinal PlayerStats pStat = mob.playerStats();\n\t\t\t\t\t\t\t\tif(pStat != null)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tfinal List<Item> removeThese=new ArrayList<Item>(1);\n\t\t\t\t\t\t\t\t\tfor(final Enumeration<Item>  o = pStat.getExtItems().items();o.hasMoreElements();)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tfinal Item I = o.nextElement();\n\t\t\t\t\t\t\t\t\t\tItem chkI = I;\n\t\t\t\t\t\t\t\t\t\tif(I.container() instanceof DeadBody)\n\t\t\t\t\t\t\t\t\t\t\tchkI=I.container();\n\t\t\t\t\t\t\t\t\t\tif((chkI instanceof DeadBody)\n\t\t\t\t\t\t\t\t\t\t&&(((DeadBody)chkI).isPlayerCorpse())\n\t\t\t\t\t\t\t\t\t\t&&(CMLib.flags().isInTheGame(chkI, true)))\n\t\t\t\t\t\t\t\t\t\t\tremoveThese.add(I);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tfor(final Item I : removeThese)\n\t\t\t\t\t\t\t\t\t\tpStat.getExtItems().delItem(I);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tCMLib.players().obliteratePlayer(mob,true,CMSecurity.isDisabled(CMSecurity.DisFlag.DEATHCRY));\n\t\t\t\t\t\t\t\tmob.destroy();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\tif(deadMOB.isPlayer())\n\t\t\t\t\t{\n\t\t\t\t\t\tfinal int remain = (maxLives-deadMOB.playerStats().deathCounter(0));\n\t\t\t\t\t\tif(remain == 1)\n\t\t\t\t\t\t\tdeadMOB.tell(L("\\n\\r^HYou have ^W1^? life remaining.^N"));\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tdeadMOB.tell(L("\\n\\r^HYou have ^W@x1^? lives remaining.^N",""+remain));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\tif(whatToDo.startsWith("LOSESK")) // "LOSESKILL\n\t\t\t{\n\t\t\t\tif(deadM.numAbilities()>0)\n\t\t\t\t{\n\t\t\t\t\tfinal Ability A=deadM.fetchAbility(CMLib.dice().roll(1,deadM.numAbilities(),-1));\n\t\t\t\t\tif(A!=null)\n\t\t\t\t\t{\n\t\t\t\t\t\tdeadM.tell(L("You\'ve forgotten @x1.",A.Name()));\n\t\t\t\t\t\tdeadM.delAbility(A);\n\t\t\t\t\t\tif(A.isAutoInvoked())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfinal Ability A2=deadM.fetchEffect(A.ID());\n\t\t\t\t\t\t\tA2.unInvoke();\n\t\t\t\t\t\t\tdeadM.delEffect(A2);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\tif(CMath.s_parseIntExpression(whatToDo,varVals)>0)\n\t\t\t{\n\t\t\t\tlostExperience[0]=CMath.s_parseIntExpression(whatToDo,varVals);\n\t\t\t\tif((lostExperience[0]>0)\n\t\t\t\t&&(!deadM.charStats().getCurrentClass().expless()))\n\t\t\t\t{\n\t\t\t\t\tlostExperience[0]=-CMLib.leveler().postExperience(deadM,null,null,-lostExperience[0],false);\n\t\t\t\t\tmessage=L(message,""+lostExperience[0]);\n\t\t\t\t\tdeadM.tell(message);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\tif(whatToDo.startsWith("EXPER")) // "EXPERIENCE\n\t\t\t{\n\t\t\t\tlostExperience[0]=baseExperience;\n\t\t\t\tif((lostExperience[0]>0)\n\t\t\t\t&&(!deadM.charStats().getCurrentClass().expless()))\n\t\t\t\t{\n\t\t\t\t\tlostExperience[0]=-CMLib.leveler().postExperience(deadM,null,null,-lostExperience[0],false);\n\t\t\t\t\tmessage=L(message,""+lostExperience[0]);\n\t\t\t\t\tdeadM.tell(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean postRevengeAttack(final MOB attacker, final MOB defender)\n\t{\n\t\tif((attacker!=null)\n\t\t&&(!attacker.isInCombat())\n\t\t&&(!attacker.amDead())\n\t\t&&(!attacker.amDestroyed())\n\t\t&&(attacker.isMonster())\n\t\t&&(attacker!=defender)\n\t\t&&(defender!=null)\n\t\t&&(attacker.location()==defender.location())\n\t\t&&(attacker.location().isInhabitant(defender))\n\t\t&&(CMLib.flags().canBeSeenBy(defender,attacker)))\n\t\t\treturn postAttack(attacker,defender,attacker.fetchWieldedItem());\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int getTickStatus()\n\t{\n\t\treturn tickStatus;\n\t}\n\n\tprotected void tickAllShips()\n\t{\n\t\tif((CMLib.map() != CMLib.get(MudHost.MAIN_HOST)._map())\n\t\t||(Thread.currentThread().getThreadGroup().getName().charAt(0)==MudHost.MAIN_HOST))\n\t\t{\n\t\t\tfor(final Enumeration<Boardable> s = CMLib.map().ships();s.hasMoreElements();)\n\t\t\t{\n\t\t\t\tfinal Boardable ship = s.nextElement();\n\t\t\t\tship.tick(ship, Tickable.TICKID_SPECIALMANEUVER);\n\t\t\t}\n\t\t\tfor(final Enumeration<Boardable> s = CMLib.map().ships();s.hasMoreElements();)\n\t\t\t{\n\t\t\t\tfinal Boardable ship = s.nextElement();\n\t\t\t\tship.tick(ship, Tickable.TICKID_SPECIALCOMBAT);\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void runSpecialCombat()\n\t{\n\t\ttickAllShips();\n\t}\n\n\t@Override\n\tpublic boolean tick(final Tickable ticking, final int tickID)\n\t{\n\t\ttry\n\t\t{\n\t\t\tif(!CMSecurity.isDisabled(CMSecurity.DisFlag.SPECOMBATTHREAD))\n\t\t\t{\n\t\t\t\tisDebugging=CMSecurity.isDebugging(DbgFlag.UTILITHREAD);\n\t\t\t\ttickStatus=Tickable.STATUS_ALIVE;\n\t\t\t\trunSpecialCombat();\n\t\t\t}\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\ttickStatus=Tickable.STATUS_NOT;\n\t\t\tsetThreadStatus(serviceClient,"sleeping");\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic MOB getBreatheKiller(final MOB victim)\n\t{\n\t\tMOB victimKiller = victim.getVictim();\n\t\tif(victimKiller != null)\n\t\t\treturn victimKiller;\n\t\tfinal Room R=victim.location();\n\t\tif(R!=null)\n\t\t{\n\t\t\tfor(final Enumeration<Ability> a=R.effects();a.hasMoreElements();)\n\t\t\t{\n\t\t\t\tfinal Ability A=a.nextElement();\n\t\t\t\tif((A!=null)\n\t\t\t\t&&(A.invoker()!=null)\n\t\t\t\t&&(A.invoker()!=victim))\n\t\t\t\t{\n\t\t\t\t\tif(A.abstractQuality()==Ability.QUALITY_MALICIOUS)\n\t\t\t\t\t\treturn A.invoker();\n\t\t\t\t\tvictimKiller = A.invoker();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor(final Enumeration<Ability> a=victim.personalEffects();a.hasMoreElements();)\n\t\t{\n\t\t\tfinal Ability A=a.nextElement();\n\t\t\tif((A!=null)\n\t\t\t&&(A.invoker()!=null)\n\t\t\t&&(A.invoker()!=victimKiller))\n\t\t\t{\n\t\t\t\tif(A.abstractQuality()==Ability.QUALITY_MALICIOUS)\n\t\t\t\t\treturn A.invoker();\n\t\t\t\tvictimKiller = A.invoker();\n\t\t\t}\n\t\t}\n\t\treturn (victimKiller == null) ? victim : victimKiller;\n\t}\n\n\t@Override\n\tpublic boolean shutdown()\n\t{\n\t\tif(CMLib.threads().isTicking(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK))\n\t\t{\n\t\t\tCMLib.threads().deleteTick(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK);\n\t\t\tserviceClient=null;\n\t\t}\n\t\treturn true;\n\t}\n}\n', 'message': 'a bunch of group target checks added to save player mana\n\ngit-svn-id: 0cdf8356e41b2d8ccbb41bb76c82068fe80b2514@20515 0d6f1817-ed0e-0410-87c9-987e46238f29\n', 'author_name': 'bozimmerman', 'lang': 'java'}