package com.planet_ink.coffee_mud.Common;
import com.planet_ink.coffee_mud.core.database.DBInterface;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMClass.CMObjectType;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.Misc.PresenceReaction;
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.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.Faction.FData;
import com.planet_ink.coffee_mud.Common.interfaces.Faction.FRange;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.MaskingLibrary;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//import java.lang.reflect.*;
/**
 * Portions Copyright (c) 2003 Jeremy Vyska
 * Portions Copyright (c) 2004-2020 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 DefaultFaction implements Faction, MsgListener
{
	@Override
	public String ID()
	{
		return "DefaultFaction";
	}

	@Override
	public CMObject newInstance()
	{
		try
		{
			return getClass().newInstance();
		}
		catch (final Exception e)
		{
			return new DefaultFaction();
		}
	}

	@Override
	public void initializeClass()
	{
	}

	@Override
	public CMObject copyOf()
	{
		try
		{
			return (CMObject) this.clone();
		}
		catch (final Exception e)
		{
			return newInstance();
		}
	}

	@Override
	public int compareTo(final CMObject o)
	{
		return CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o));
	}

	protected String	 ID					= "";
	protected String	 name				= "";
	protected String	 upName				= "";
	protected String	 choiceIntro		= "";
	protected long[]	 lastDataChange		= new long[1];
	protected int   	 minimum			= Integer.MIN_VALUE;
	protected int   	 middle				= 0;
	protected int   	 difference;
	protected int   	 maximum			= Integer.MAX_VALUE;
	protected int   	 highest			= Integer.MAX_VALUE;
	protected int   	 lowest				= Integer.MIN_VALUE;
	protected long  	 internalFlagBitmap	= 0;
	protected String	 experienceFlag		= "";
	protected boolean    useLightReactions	= false;
	protected boolean    isDisabled			= false;
	protected boolean    showInScore		= false;
	protected boolean    showInSpecialReport= false;
	protected boolean    showInEditor		= false;
	protected boolean    showInFacCommand	= true;
	protected boolean    destroyed			= false;

	protected CList<String>   					defaults		 = new SVector<String>();
	protected CList<String>   					autoDefaults	 = new SVector<String>();
	protected CMap<String,FRange> 				ranges			 = new SHashtable<String,FRange>();
	protected Map<Integer,FRange> 				rangeRangeMap	 = new PrioritizingLimitedMap<Integer,FRange>(10,60000,600000,100);
	protected CMap<String,String[]>   			affBehavs		 = new SHashtable<String,String[]>();
	protected double							rateModifier	 = 1.0;
	protected CMap<String,FactionChangeEvent[]>	changes			 = new SHashtable<String,FactionChangeEvent[]>();
	protected CMap<String,FactionChangeEvent[]>	abilChangeCache	 = new SHashtable<String,FactionChangeEvent[]>();
	protected CMap<String,FactionChangeEvent[]>	socChangeCache	 = new SHashtable<String,FactionChangeEvent[]>();
	protected CList<Faction.FZapFactor>			factors			 = new SVector<Faction.FZapFactor>();
	protected CMap<String,Double> 				relations		 = new SHashtable<String,Double>();
	protected CList<Faction.FAbilityUsage>		abilityUsages	 = new SVector<Faction.FAbilityUsage>();
	protected Map<String,Faction.FAbilityUsage> abilityUseCache	 = new STreeMap<String,Faction.FAbilityUsage>();
	protected Set<String> 						abilityUseMisses = new STreeSet<String>();
	protected CList<String>   					choices			 = new SVector<String>();
	protected CList<Faction.FReactionItem>		reactions		 = new SVector<Faction.FReactionItem>();
	protected CMap<String,CList<FReactionItem>>	reactionHash	 = new SHashtable<String,CList<Faction.FReactionItem>>();

	@Override
	public Enumeration<Faction.FReactionItem> reactions()
	{
		return reactions.elements();
	}

	@Override
	public Enumeration<Faction.FReactionItem> reactions(final String rangeName)
	{
		final CList<Faction.FReactionItem> V=reactionHash.get(rangeName.toUpperCase().trim());
		if(V!=null)
			return V.elements();
		return new Vector<Faction.FReactionItem>().elements();
	}

	protected Ability presenceReactionPrototype=null;

	@Override
	public String factionID()
	{
		return ID;
	}

	@Override
	public String name()
	{
		return name;
	}

	@Override
	public String upperName()
	{
		return upName;
	}

	@Override
	public long getInternalFlags()
	{
		return internalFlagBitmap;
	}

	@Override
	public String choiceIntro()
	{
		return choiceIntro;
	}

	@Override
	public int minimum()
	{
		return minimum;
	}

	@Override
	public int middle()
	{
		return middle;
	}

	@Override
	public int difference()
	{
		return difference;
	}

	@Override
	public int maximum()
	{
		return maximum;
	}

	@Override
	public boolean isDisabled()
	{
		return isDisabled;
	}

	@Override
	public void disable(final boolean truefalse)
	{
		isDisabled = truefalse;
	}

	public int highest()
	{
		return highest;
	}

	public int lowest()
	{
		return lowest;
	}

	@Override
	public String experienceFlag()
	{
		return experienceFlag;
	}

	@Override
	public boolean showInScore()
	{
		return showInScore;
	}

	@Override
	public boolean showInSpecialReported()
	{
		return showInSpecialReport;
	}

	@Override
	public boolean showInEditor()
	{
		return showInEditor;
	}

	@Override
	public boolean showInFactionsCommand()
	{
		return showInFacCommand;
	}

	@Override
	public Enumeration<Faction.FRange> ranges()
	{
		return ranges.elements();
	}

	@Override
	public Enumeration<String> defaults()
	{
		return defaults.elements();
	}

	@Override
	public Enumeration<String> autoDefaults()
	{
		return autoDefaults.elements();
	}

	@Override
	public double rateModifier()
	{
		return rateModifier;
	}

	@Override
	public Enumeration<String> changeEventKeys()
	{
		return changes.keys();
	}

	@Override
	public Enumeration<Faction.FZapFactor> factors()
	{
		return factors.elements();
	}

	@Override
	public Enumeration<String> relationFactions()
	{
		return relations.keys();
	}

	@Override
	public Enumeration<Faction.FAbilityUsage> abilityUsages()
	{
		return abilityUsages.elements();
	}

	@Override
	public Enumeration<String> choices()
	{
		return choices.elements();
	}

	@Override
	public Enumeration<String> affectsBehavs()
	{
		return affBehavs.keys();
	}

	@Override
	public void setLightReactions(final boolean truefalse)
	{
		useLightReactions = truefalse;
	}

	@Override
	public boolean useLightReactions()
	{
		return useLightReactions;
	}

	@Override
	public void setFactionID(final String newStr)
	{
		ID = newStr;
	}

	@Override
	public void setName(final String newStr)
	{
		name = newStr;
	}

	@Override
	public void setInternalFlags(final long bitmap)
	{
		internalFlagBitmap = bitmap;
	}

	@Override
	public void setChoiceIntro(final String newStr)
	{
		choiceIntro = newStr;
	}

	@Override
	public void setExperienceFlag(final String newStr)
	{
		experienceFlag = newStr;
	}

	@Override
	public void setShowInScore(final boolean truefalse)
	{
		showInScore = truefalse;
	}

	@Override
	public void setShowInSpecialReported(final boolean truefalse)
	{
		showInSpecialReport = truefalse;
	}

	@Override
	public void setShowInEditor(final boolean truefalse)
	{
		showInEditor = truefalse;
	}

	@Override
	public void setShowInFactionsCommand(final boolean truefalse)
	{
		showInFacCommand = truefalse;
	}

	@Override
	public void setChoices(final List<String> v)
	{
		choices = new SVector<String>(v);
	}

	@Override
	public void setAutoDefaults(final List<String> v)
	{
		autoDefaults = new SVector<String>(v);
	}

	@Override
	public void setDefaults(final List<String> v)
	{
		defaults = new SVector<String>(v);
	}

	@Override
	public void setRateModifier(final double d)
	{
		rateModifier = d;
	}

	@Override
	public boolean isPreLoaded()
	{
		return CMParms.parseSemicolons(CMProps.getVar(CMProps.Str.PREFACTIONS).toUpperCase(),true).contains(factionID().toUpperCase());
	}

	@Override
	public Faction.FAbilityUsage getAbilityUsage(final int x)
	{
		return ((x>=0)&&(x<abilityUsages.size()))
				?(Faction.FAbilityUsage)abilityUsages.get(x)
				:null;
	}

	@Override
	public boolean delFactor(final Faction.FZapFactor f)
	{
		if(!factors.remove(f))
			return false;
		factors.trimToSize();
		return true;
	}

	@Override
	public Faction.FZapFactor getFactor(final int x)
	{
		return ((x >= 0) && (x < factors.size())) ? factors.get(x) : null;
	}

	@Override
	public Faction.FZapFactor addFactor(final double gain, final double loss, final String mask)
	{
		final Faction.FZapFactor o=new DefaultFactionZapFactor(gain,loss,mask);
		factors.add(o);
		factors.trimToSize();
		return o;
	}

	@Override
	public boolean delRelation(final String factionID)
	{
		if(relations.remove(factionID)==null)
			return false;
		return true;
	}

	@Override
	public boolean addRelation(final String factionID, final double relation)
	{
		if(relations.containsKey(factionID))
			return false;
		relations.put(factionID,Double.valueOf(relation));
		return true;
	}

	@Override
	public double getRelation(final String factionID)
	{
		if(relations.containsKey(factionID))
			return relations.get(factionID).doubleValue();
		return 0.0;
	}

	public DefaultFaction()
	{
		super();
	}

	@Override
	public void initializeFaction(final String aname)
	{
		ID=aname;
		name=aname;
		upName=aname.toUpperCase();
		minimum=0;
		middle=50;
		maximum=100;
		highest=100;
		lowest=0;
		difference=CMath.abs(maximum-minimum);
		experienceFlag="EXTREME";
		addRange("0;100;Sample Range;SAMPLE;");
		defaults.add("0");
	}

	@Override
	public void initializeFaction(final StringBuffer file, final String fID)
	{
		final boolean debug = false;

		ID = fID;
		final CMProps alignProp = new CMProps(new ByteArrayInputStream(CMStrings.strToBytes(file.toString())));
		if(alignProp.isEmpty())
			return;
		name=alignProp.getStr("NAME");
		upName=name.toUpperCase();
		choiceIntro=alignProp.getStr("CHOICEINTRO");
		minimum=alignProp.getInt("MINIMUM");
		maximum=alignProp.getInt("MAXIMUM");
		if(maximum<minimum)
		{
			minimum=maximum;
			maximum=alignProp.getInt("MINIMUM");
		}
		recalc();
		experienceFlag=alignProp.getStr("EXPERIENCE").toUpperCase().trim();
		if(experienceFlag.length()==0)
			experienceFlag="NONE";
		rateModifier=alignProp.getDouble("RATEMODIFIER");
		showInScore=alignProp.getBoolean("SCOREDISPLAY");
		showInFacCommand=alignProp.getBoolean("SHOWINFACTIONSCMD");
		showInSpecialReport=alignProp.getBoolean("SPECIALREPORTED");
		showInEditor=alignProp.getBoolean("EDITALONE");
		defaults=new SVector<String>(CMParms.parseSemicolons(alignProp.getStr("DEFAULT"),true));
		autoDefaults =new SVector<String>(CMParms.parseSemicolons(alignProp.getStr("AUTODEFAULTS"),true));
		choices =new SVector<String>(CMParms.parseSemicolons(alignProp.getStr("AUTOCHOICES"),true));
		useLightReactions=alignProp.getBoolean("USELIGHTREACTIONS");
		ranges=new SHashtable<String,FRange>();
		changes=new SHashtable<String,FactionChangeEvent[]>();
		factors=new SVector<FZapFactor>();
		relations=new SHashtable<String,Double>();
		abilityUsages=new SVector<FAbilityUsage>();
		abilityUseCache=new STreeMap<String,FAbilityUsage>();
		abilityUseMisses=new STreeSet<String>();
		reactions=new SVector<FReactionItem>();
		reactionHash=new SHashtable<String,CList<FReactionItem>>();
		for(final Enumeration<Object> e=alignProp.keys();e.hasMoreElements();)
		{
			if(debug)
				Log.sysOut("FACTIONS","Starting Key Loop");
			final String key = (String) e.nextElement();
			if(debug)
				Log.sysOut("FACTIONS","  Key Found     :"+key);
			final String words = (String) alignProp.get(key);
			if(debug)
				Log.sysOut("FACTIONS","  Words Found   :"+words);
			if(key.startsWith("RANGE"))
				addRange(words);
			if(key.startsWith("CHANGE"))
				createChangeEvent(words);
			if(key.startsWith("AFFBEHAV"))
			{
				final Object[] O=CMParms.parseSafeSemicolonList(words,false).toArray();
				if(O.length==3)
					addAffectBehav((String)O[0],(String)O[1],(String)O[2]);
			}
			if(key.startsWith("FACTOR"))
			{
				final List<String> factor=CMParms.parseSemicolons(words,false);
				if(factor.size()>2)
					factors.add(new DefaultFactionZapFactor(CMath.s_double(factor.get(0)),
											 CMath.s_double(factor.get(1)),
											 factor.get(2)));
			}
			if(key.startsWith("RELATION"))
			{
				final Vector<String> V=CMParms.parse(words);
				if(V.size()>=2)
				{
					final String who=V.elementAt(0);
					double factor;
					final String amt=V.elementAt(1).trim();
					if(amt.endsWith("%"))
						factor=CMath.s_pct(amt);
					else
						factor=1;
					relations.put(who,Double.valueOf(factor));
				}
			}
			if(key.startsWith("ABILITY"))
				addAbilityUsage(words);
			if(key.startsWith("REACTION"))
			{
				final DefaultFactionReactionItem item = new DefaultFactionReactionItem(words);
				addReaction(item.rangeCodeName(), item.presentMOBMask(), item.reactionObjectID(), item.parameters());
			}
		}
	}

	private void recalc()
	{
		minimum=Integer.MAX_VALUE;
		maximum=Integer.MIN_VALUE;
		int num=0;
		for(final Enumeration<Faction.FRange> e=ranges();e.hasMoreElements();)
		{
			final Faction.FRange FR=e.nextElement();
			if(FR.high()>maximum)
				maximum=FR.high();
			if(FR.low()<minimum)
				minimum=FR.low();
			num++;
		}
		if(minimum==Integer.MAX_VALUE)
			minimum=Integer.MIN_VALUE;
		if(maximum==Integer.MIN_VALUE)
			maximum=Integer.MAX_VALUE;
		if(maximum<minimum)
		{
			final int oldMin=minimum;
			minimum=maximum;
			maximum=oldMin;
		}
		middle=minimum+(int)Math.round(CMath.div(maximum-minimum,2.0));
		difference=CMath.abs(maximum-minimum);
		lastDataChange[0]=System.currentTimeMillis();
		rangeRangeMap=new PrioritizingLimitedMap<Integer,FRange>(num*5,60000,600000,100);
	}

	@Override
	public String getTagValue(final String tag)
	{
		final int tagRef=CMLib.factions().isFactionTag(tag);
		if(tagRef<0)
			return "";
		int numCall=-1;
		if((tagRef<TAG_NAMES.length)&&(TAG_NAMES[tagRef].endsWith("*")))
		{
			if(CMath.isInteger(tag.substring(TAG_NAMES[tagRef].length()-1)))
				numCall=CMath.s_int(tag.substring(TAG_NAMES[tagRef].length()-1));
		}
		switch(tagRef)
		{
		case TAG_NAME:
			return name;
		case TAG_MINIMUM:
			return "" + minimum;
		case TAG_MAXIMUM:
			return "" + maximum;
		case TAG_SCOREDISPLAY:
			return Boolean.toString(showInScore).toUpperCase();
		case TAG_SHOWINFACTIONSCMD:
			return Boolean.toString(showInFacCommand).toUpperCase();
		case TAG_SPECIALREPORTED:
			return Boolean.toString(showInSpecialReport).toUpperCase();
		case TAG_EDITALONE:
			return Boolean.toString(showInEditor).toUpperCase();
		case TAG_DEFAULT:
			return CMParms.toSemicolonListString(defaults);
		case TAG_AUTODEFAULTS:
			return CMParms.toSemicolonListString(autoDefaults);
		case TAG_CHOICEINTRO:
			return choiceIntro;
		case TAG_AUTOCHOICES:
			return CMParms.toSemicolonListString(choices);
		case TAG_RATEMODIFIER:
			return "" + rateModifier;
		case TAG_EXPERIENCE:
			return "" + experienceFlag;
		case TAG_RANGE_:
		{
			if((numCall<0)||(numCall>=ranges.size()))
				return ""+ranges.size();
			int x=0;
			for(final Enumeration<Faction.FRange> e=ranges();e.hasMoreElements();)
			{
				final Faction.FRange FR=e.nextElement();
				if(x==numCall)
					return FR.toString();
				x++;
			}
			return "";
		}
		case TAG_CHANGE_:
		{
			int sz=0;
			for(final Enumeration<Faction.FactionChangeEvent[]> es=changes.elements();es.hasMoreElements();)
				sz+=es.nextElement().length;
			if((numCall<0)||(numCall>=sz))
				return ""+sz;
			int i=0;
			for(final Enumeration<Faction.FactionChangeEvent[]> e=changes.elements();e.hasMoreElements();)
			{
				final Faction.FactionChangeEvent[] FCs=e.nextElement();
				for (final FactionChangeEvent fc : FCs)
				{
					if(i==numCall)
						return fc.toString();
					i++;
				}
			}
			return "";
		}
		case TAG_ABILITY_:
		{
			if((numCall<0)||(numCall>=abilityUsages.size()))
				return ""+abilityUsages.size();
			return abilityUsages.get(numCall).toString();
		}
		case TAG_FACTOR_:
		{
			if((numCall<0)||(numCall>=factors.size()))
				return ""+factors.size();
			return factors.get(numCall).toString();
		}
		case TAG_RELATION_:
		{
			if((numCall<0)||(numCall>=relations.size()))
				return ""+relations.size();
			int i=0;
			for(final Enumeration<String> e=relations.keys();e.hasMoreElements();)
			{
				final String factionName=e.nextElement();
				final Double D=relations.get(factionName);
				if(i==numCall)
					return factionName+" "+CMath.toPct(D.doubleValue());
				i++;
			}
			return "";
		}
		case TAG_AFFBEHAV_:
		{
			if((numCall<0)||(numCall>=affBehavs.size()))
				return ""+affBehavs.size();
			int i=0;
			for(final Enumeration<String> e=affBehavs.keys();e.hasMoreElements();)
			{
				final String ID=e.nextElement();
				final String[] data=affBehavs.get(ID);
				if(i==numCall)
					return ID+";"+CMParms.toSafeSemicolonListString(data);
				i++;
			}
			return "";
		}
		case TAG_REACTION_:
		{
			if((numCall<0)||(numCall>=reactions.size()))
				return ""+reactions.size();
			final Faction.FReactionItem item = reactions.get(numCall);
			return item.toString();
		}
		case TAG_USELIGHTREACTIONS:
			return "" + useLightReactions;
		}
		return "";
	}

	@Override
	public String getINIDef(final String tag, final String delimeter)
	{
		final int tagRef=CMLib.factions().isFactionTag(tag);
		if(tagRef<0)
			return "";
		final String rawTagName=TAG_NAMES[tagRef];
		if(TAG_NAMES[tagRef].endsWith("*"))
		{
			final int number=CMath.s_int(getTagValue(rawTagName));
			final StringBuffer str=new StringBuffer("");
			for(int i=0;i<number;i++)
			{
				final String value=getTagValue(rawTagName.substring(0,rawTagName.length()-1)+i);
				str.append(rawTagName.substring(0,rawTagName.length()-1)+(i+1)+"="+value+delimeter);
			}
			return str.toString();
		}
		return rawTagName+"="+getTagValue(tag)+delimeter;
	}

	@Override
	public void updateFactionData(final MOB mob, final FData data)
	{
		data.resetFactionData(this);
		final List<Ability> aV=new ArrayList<Ability>();
		final List<Behavior> bV=new ArrayList<Behavior>();
		String ID=null;
		String[] stuff=null;
		if(mob.isMonster())
		{
			for(final Enumeration<String> e=affectsBehavs();e.hasMoreElements();)
			{
				ID=e.nextElement();
				stuff=getAffectBehav(ID);
				if(CMLib.masking().maskCheck(stuff[1],mob,true))
				{
					final Behavior B=CMClass.getBehavior(ID);
					if(B!=null)
					{
						B.setParms(stuff[0]);
						bV.add(B);
					}
					else
					{
						final Ability A=CMClass.getAbility(ID);
						A.setMiscText(stuff[0]);
						A.setAffectedOne(mob);
						aV.add(A);
					}
				}
			}
		}
		data.addHandlers(aV,bV);
	}

	@Override
	public FData makeFactionData(final MOB mob)
	{
		final FData data=new DefaultFactionData(this);
		updateFactionData(mob,data);
		return data;
	}

	@Override
	public boolean delAffectBehav(final String ID)
	{
		final boolean b=affBehavs.remove(ID.toUpperCase().trim())!=null;
		if(b)
			lastDataChange[0]=System.currentTimeMillis();
		return b;
	}

	@Override
	public boolean addAffectBehav(final String ID, final String parms, final String gainMask)
	{
		if(affBehavs.containsKey(ID.toUpperCase().trim()))
			return false;
		if((CMClass.getBehavior(ID)==null)&&(CMClass.getAbility(ID)==null))
			return false;
		affBehavs.put(ID.toUpperCase().trim(),new String[]{parms,gainMask});
		lastDataChange[0]=System.currentTimeMillis();
		return true;
	}

	@Override
	public String[] getAffectBehav(final String ID)
	{
		if(affBehavs.containsKey(ID.toUpperCase().trim()))
			return CMParms.toStringArray(new XVector<String>(affBehavs.get(ID.toUpperCase().trim())));
		return null;
	}

	@Override
	public boolean delReaction(final Faction.FReactionItem item)
	{
		final CList<Faction.FReactionItem> V=reactionHash.get(item.rangeCodeName().toUpperCase().trim());
		if(V!=null)
			V.remove(item);
		final boolean res = reactions.remove(item);
		if(reactions.size()==0)
			reactionHash.clear();
		lastDataChange[0]=System.currentTimeMillis();
		return res;
	}

	@Override
	public boolean addReaction(final String range, final String mask, final String abilityID, final String parms)
	{
		CList<Faction.FReactionItem> V=reactionHash.get(range.toUpperCase().trim());
		final DefaultFactionReactionItem item = new DefaultFactionReactionItem();
		item.setRangeName(range);
		item.setPresentMOBMask(mask);
		item.setReactionObjectID(abilityID);
		item.setParameters(parms);
		if(V==null)
		{
			V=new SVector<Faction.FReactionItem>();
			reactionHash.put(range.toUpperCase().trim(), V);
		}
		V.add(item);
		reactions.add(item);
		lastDataChange[0]=System.currentTimeMillis();
		return true;
	}

	@Override
	public FactionChangeEvent[] getChangeEvents(final String key)
	{
		return changes.get(key);
	}

	@Override
	public List<Integer> findChoices(final MOB mob)
	{
		final Vector<Integer> mine=new Vector<Integer>();
		String s;
		for(final Enumeration<String> e=choices.elements();e.hasMoreElements();)
		{
			s=e.nextElement();
			if(CMath.isInteger(s))
				mine.addElement(Integer.valueOf(CMath.s_int(s)));
			else
			if(CMLib.masking().maskCheck(s, mob,false))
			{
				final Vector<String> V=CMParms.parse(s);
				for(int j=0;j<V.size();j++)
				{
					if(CMath.isInteger(V.elementAt(j)))
						mine.addElement(Integer.valueOf(CMath.s_int(V.elementAt(j))));
				}
			}
		}
		return mine;
	}

	@Override
	public FactionChangeEvent[] findAbilityChangeEvents(final Ability key)
	{
		if(key==null)
			return null;
		if(abilChangeCache.size()==0)
		{
			if(CMClass.numPrototypes(CMObjectType.ABILITY)==0)
				return null;
			final Map<Integer,List<FactionChangeEvent>> abilityClassMap=new HashMap<Integer,List<FactionChangeEvent>>();
			for(int classificationCode = 0;classificationCode < Ability.ACODE_DESCS.length;classificationCode++)
			{
				for (final Enumeration<FactionChangeEvent[]> e=changes.elements();e.hasMoreElements();)
				{
					final FactionChangeEvent[] Cs=e.nextElement();
					for(final FactionChangeEvent C : Cs)
					{
						if(classificationCode==C.IDclassFilter())
						{
							if(!abilityClassMap.containsKey(Integer.valueOf(classificationCode)))
								abilityClassMap.put(Integer.valueOf(classificationCode), new ArrayList<FactionChangeEvent>());
							abilityClassMap.get(Integer.valueOf(classificationCode)).add(C);
						}
					}
				}
			}
			final Map<Integer,List<FactionChangeEvent>> abilityDomainMap=new HashMap<Integer,List<FactionChangeEvent>>();
			for(int domainCode = 0;domainCode < Ability.DOMAIN_DESCS.length;domainCode++)
			{
				final int domainID = domainCode << 5;
				for (final Enumeration<FactionChangeEvent[]> e=changes.elements();e.hasMoreElements();)
				{
					final FactionChangeEvent[] Cs=e.nextElement();
					for(final FactionChangeEvent C : Cs)
					{
						if(domainID==C.IDdomainFilter())
						{
							if(!abilityDomainMap.containsKey(Integer.valueOf(domainID)))
								abilityDomainMap.put(Integer.valueOf(domainID), new ArrayList<FactionChangeEvent>());
							abilityDomainMap.get(Integer.valueOf(domainID)).add(C);
						}
					}
				}
			}
			final Map<Long,List<FactionChangeEvent>> abilityFlagMap=new HashMap<Long,List<FactionChangeEvent>>();
			for(int flagIndex = 0;flagIndex < Ability.FLAG_DESCS.length;flagIndex++)
			{
				final long flagMask = Math.round(Math.pow(2, flagIndex));
				for (final Enumeration<FactionChangeEvent[]> e=changes.elements();e.hasMoreElements();)
				{
					final FactionChangeEvent[] Cs=e.nextElement();
					for(final FactionChangeEvent C : Cs)
					{
						if((C.IDflagFilter()>0)&&(CMath.bset(C.IDflagFilter(), flagMask)))
						{
							if(!abilityFlagMap.containsKey(Long.valueOf(flagMask)))
								abilityFlagMap.put(Long.valueOf(flagMask), new ArrayList<FactionChangeEvent>());
							abilityFlagMap.get(Long.valueOf(flagMask)).add(C);
						}
					}
				}
			}
			for(final Enumeration<Ability> a=CMClass.abilities();a.hasMoreElements();)
			{
				final Ability A=a.nextElement();
				final ArrayList<FactionChangeEvent> set=new ArrayList<FactionChangeEvent>(0);
				if(changes.containsKey(A.ID()))
					set.addAll(Arrays.asList(changes.get(A.ID())));
				if(abilityClassMap.containsKey(Integer.valueOf(A.classificationCode()&Ability.ALL_ACODES)))
					set.addAll(abilityClassMap.get(Integer.valueOf(A.classificationCode()&Ability.ALL_ACODES)));
				if(abilityDomainMap.containsKey(Integer.valueOf(A.classificationCode()&Ability.ALL_DOMAINS)))
					set.addAll(abilityDomainMap.get(Integer.valueOf(A.classificationCode()&Ability.ALL_DOMAINS)));
				if(A.flags()>0)
				{
					for(int flagIndex = 0;flagIndex < Ability.FLAG_DESCS.length;flagIndex++)
					{
						final long flagMask = Math.round(Math.pow(2, flagIndex));
						if(CMath.bset(A.flags(), flagMask))
						{
							if(abilityFlagMap.containsKey(Long.valueOf(flagMask)))
								set.addAll(abilityFlagMap.get(Long.valueOf(flagMask)));
						}
					}
				}
				if(set.size()>0)
					abilChangeCache.put(A.ID(), set.toArray(new FactionChangeEvent[0]));
			}
			if(abilChangeCache.size()==0)
				abilChangeCache.put("StdAbility", new FactionChangeEvent[0]);
		}
		return abilChangeCache.get(key.ID());
	}

	@Override
	public FactionChangeEvent[] findSocialChangeEvents(final Social soc)
	{
		if(soc==null)
			return null;
		if(socChangeCache.size()==0)
		{
			if(CMLib.socials().numSocialSets()==0)
				return null;
			for(final Enumeration<Social> s=CMLib.socials().getAllSocials();s.hasMoreElements();)
			{
				final Social S = s.nextElement();
				if(changes.containsKey(S.name()))
					socChangeCache.put(S.name(), changes.get(S.name()));
				else
				if(changes.containsKey(S.baseName()+" *"))
					socChangeCache.put(S.name(), changes.get(S.baseName()+" *"));
			}
			if(socChangeCache.size()==0)
				socChangeCache.put("DefaultSocial", new FactionChangeEvent[0]);
		}
		return socChangeCache.get(soc.name());
	}

	@Override
	public Faction.FRange fetchRange(final String codeName)
	{
		return ranges.get(codeName.toUpperCase().trim());
	}

	@Override
	public FRange fetchRange(final int faction)
	{
		final Integer I=Integer.valueOf(faction);
		FRange R=rangeRangeMap.get(I);
		if(R!=null)
			return R;
		for (final Enumeration<FRange> e=ranges.elements();e.hasMoreElements();)
		{
			R = e.nextElement();
			if ( (faction >= R.low()) && (faction <= R.high()))
			{
				rangeRangeMap.put(I, R);
				return R;
			}
		}
		return null;
	}

	@Override
	public String fetchRangeName(final int faction)
	{
		final FRange R= fetchRange(faction);
		if(R!=null)
			return R.name();
		return "";
	}

	@Override
	public int asPercent(final int faction)
	{
		return (int)Math.round(CMath.mul(CMath.div(faction-minimum,(maximum-minimum)),100));
	}

	@Override
	public int asPercentFromAvg(final int faction)
	{
		// =(( (B2+A2) / 2 ) - C2) / (B2-A2) * 100
		// C = current, A = min, B = Max
		return (int)Math.round(CMath.mul(CMath.div(((maximum+minimum)/2)-faction,maximum-minimum),100));
	}

	@Override
	public int randomFaction()
	{
		final Random gen = new Random();
		return maximum - gen.nextInt(maximum-minimum);
	}

	@Override
	public int findDefault(final MOB mob)
	{
		String s;
		for(final Enumeration<String> e=defaults.elements();e.hasMoreElements();)
		{
			s=e.nextElement();
			if(CMath.isNumber(s))
				return CMath.s_int(s);
			else
			if(CMLib.masking().maskCheck(s, mob,false))
			{
				final Vector<String> V=CMParms.parse(s);
				for(int j=0;j<V.size();j++)
				{
					if(CMath.isNumber(V.elementAt(j)))
						return CMath.s_int(V.elementAt(j));
				}
			}
		}
		return 0;
	}

	@Override
	public int findAutoDefault(final MOB mob)
	{
		String s;
		for(final Enumeration<String> e=autoDefaults.elements();e.hasMoreElements();)
		{
			s=e.nextElement();
			if(CMath.isNumber(s))
				return CMath.s_int(s);
			else
			if(CMLib.masking().maskCheck(s, mob,false))
			{
				final Vector<String> V=CMParms.parse(s);
				for(int j=0;j<V.size();j++)
				{
					if(CMath.isNumber(V.elementAt(j)))
						return CMath.s_int(V.elementAt(j));
				}
			}
		}
		return Integer.MAX_VALUE;
	}

	@Override
	public boolean hasFaction(final MOB mob)
	{
		return (mob.fetchFaction(ID)!=Integer.MAX_VALUE);
	}

	protected FAbilityUsage getAbilityUsage(final Ability A)
	{
		if(A==null)
			return null;
		if(abilityUseCache.containsKey(A.ID()))
			return abilityUseCache.get(A.ID());
		if(abilityUseMisses.contains(A.ID()))
			return null;

		for(final FAbilityUsage usage : abilityUsages)
		{
			if(usage.possibleAbilityID()
			&&(!usage.abilityFlags().equalsIgnoreCase(A.ID())))
				continue;

			if(usage.possibleAbilityID()
			||(((usage.type()<0)||((A.classificationCode()&Ability.ALL_ACODES)==usage.type()))
				&&((usage.flag()<0)||(CMath.bset(A.flags(),usage.flag())))
				&&((usage.notflag()<0)||(!CMath.bset(A.flags(),usage.notflag())))
				&&((usage.domain()<0)||((A.classificationCode()&Ability.ALL_DOMAINS)==usage.domain()))))
			{
				abilityUseCache.put(A.ID(), usage);
				return usage;
			}
		}
		abilityUseMisses.add(A.ID());
		return null;
	}

	@Override
	public boolean hasUsage(final Ability A)
	{
		return getAbilityUsage(A) != null;
	}

	@Override
	public boolean canUse(final MOB mob, final Ability A)
	{
		final FAbilityUsage usage = this.getAbilityUsage(A);
		if(usage == null)
			return true;
		final int faction=mob.fetchFaction(ID);
		if((faction < usage.low()) || (faction > usage.high()))
			return false;
		return true;
	}

	@Override
	public double findFactor(final MOB mob, final boolean gain)
	{
		for(final Faction.FZapFactor factor : factors)
		{
			if(CMLib.masking().maskCheck(factor.compiledMOBMask(),mob,false))
				return gain?factor.gainFactor():factor.lossFactor();
		}
		return 1.0;
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		FactionChangeEvent[] events;
		switch(msg.sourceMinor())
		{
		case CMMsg.TYP_DEATH:
			if(((msg.source()==myHost)||(msg.tool()==myHost))
			&&(msg.tool() instanceof MOB))
			{
				final MOB killedM=msg.source();
				final MOB killingBlowM=(MOB)msg.tool();
				events=getChangeEvents((msg.source()==myHost)?"MURDER":"KILL");
				if(events!=null)
				{
					for (final FactionChangeEvent event : events)
					{
						if(event.applies(killingBlowM,killedM))
						{
							final CharClass combatCharClass=CMLib.combat().getCombatDominantClass(killingBlowM,killedM);
							final Set<MOB> combatBeneficiaries=CMLib.combat().getCombatBeneficiaries(killingBlowM,killedM,combatCharClass);
							for (final MOB mob : combatBeneficiaries)
								executeChange(mob,killedM,event);
						}
					}
				}
			}
			break;
		case CMMsg.TYP_CAUSESINK:
			if((myHost instanceof MOB)
			&&(msg.target() instanceof BoardableShip))
			{
				final MOB killerM=msg.source();
				final Area shipArea=((BoardableShip)msg.target()).getShipArea();
				if(CMLib.map().areaLocation(killerM)==shipArea)
					events=getChangeEvents("SUNK");
				else
				if(killerM == myHost)
					events=getChangeEvents("SINK");
				else
					events=null;
				if(events!=null)
				{
					final MOB targetM=(killerM==myHost)?null:(MOB)myHost;
					for (final FactionChangeEvent event : events)
					{
						if(event.applies(killerM, targetM))
							executeChange(killerM,targetM,event);
					}
				}
			}
			break;
		case CMMsg.TYP_GIVE:
			if((msg.source()==myHost)
			&&(msg.tool() instanceof Coins)
			&&(msg.target() instanceof MOB))
			{
				events=getChangeEvents("BRIBE");
				if(events!=null)
				{
					for (final FactionChangeEvent event : events)
					{
						if(event.applies(msg.source(),(MOB)msg.target()))
						{
							double amount=CMath.s_double(event.getTriggerParm("AMOUNT"));
							final double pctAmount = CMath.s_pct(event.getTriggerParm("PCT"))
											 * CMLib.beanCounter().getTotalAbsoluteNativeValue((MOB)msg.target());
							if(pctAmount>amount)
								amount=pctAmount;
							if(amount==0)
								amount=1.0;
							if(((Coins)msg.tool()).getTotalValue()>=amount)
								executeChange(msg.source(),(MOB)msg.target(),event);
						}
					}
				}
			}
			break;
		case CMMsg.TYP_TEACH:
		case CMMsg.TYP_CHANNEL:
			// don't do the defaults!
			break;
		case CMMsg.TYP_SPEAK:
			if((msg.othersMessage()!=null)
			&&(msg.source()==myHost))
			{
				events=getChangeEvents("TALK");
				if((events!=null)&&(events.length>0))
				{
					final Room R=msg.source().location();
					final List<MOB> targets=new ArrayList<MOB>();
					if(msg.target() instanceof MOB)
						targets.add((MOB)msg.target());
					else
					for(int m=0;m<R.numInhabitants();m++)
					{
						final MOB M=R.fetchInhabitant(m);
						if((M!=null)&&(M.isMonster())
						&&(CMLib.flags().canBeHeardSpeakingBy(msg.source(),M))
						&&(M.amFollowing()!=msg.source()))
							targets.add(M);
					}
					final String sayMsg=CMStrings.getSayFromMessage(msg.othersMessage().toLowerCase());
					Matcher M=null;
					if((sayMsg!=null)&&(sayMsg.length()>0)&&(R!=null))
					{
						final MOB mob=msg.source();
						Faction.FData data = mob.fetchFactionData(factionID());
						for (final FactionChangeEvent event : events)
						{
							Pattern P=(Pattern)event.stateVariable(0);
							if(P==null)
							{
								String mask=event.getTriggerParm("REGEX");
								if((mask==null)||(mask.trim().length()==0))
									mask=".*";
								P=Pattern.compile(mask.toLowerCase());
								event.setStateVariable(0,P);
							}
							M=P.matcher(sayMsg);
							if(M.matches())
							{
								if((data != null) && (System.currentTimeMillis() < data.getNextChangeTimers(event)))
									continue;
								boolean foundOne=false;
								for(final MOB target : targets)
								{
									if(event.applies(mob,target))
									{
										executeChange(mob,target,event);
										foundOne=true;
										break;
									}
								}
								if(foundOne)
								{
									if(data == null)
										data = mob.fetchFactionData(factionID());
									if(data != null)
									{
										final long newTime=CMath.s_long(event.getTriggerParm("WAIT"))*CMProps.getTickMillis();
										if(newTime != 0)
											data.setNextChangeTimers(event, System.currentTimeMillis()+newTime);
									}
								}
							}
						}
					}
				}
				events=getChangeEvents("MUDCHAT");
				if((events!=null)&&(events.length>0))
				{
					final Room R=msg.source().location();
					final List<MOB> targets=new ArrayList<MOB>();
					if(msg.target() instanceof MOB)
						targets.add((MOB)msg.target());
					else
					for(int m=0;m<R.numInhabitants();m++)
					{
						final MOB M=R.fetchInhabitant(m);
						if((M!=null)&&(M.isMonster())
						&&(CMLib.flags().canBeHeardSpeakingBy(msg.source(),M))
						&&(M.amFollowing()!=msg.source()))
							targets.add(M);
					}
					boolean foundOne=false;
					for(final MOB target : targets)
					{
						ChattyBehavior mudChatB=null;
						Behavior B=null;
						for(final Enumeration<Behavior> e=target.behaviors();e.hasMoreElements();)
						{
							B=e.nextElement();
							if(B instanceof ChattyBehavior)
								mudChatB=(ChattyBehavior)B;
						}
						if(mudChatB!=null)
						{
							final String sayMsg=CMStrings.getSayFromMessage(msg.othersMessage().toLowerCase());
							if((sayMsg!=null)&&(sayMsg.length()>0))
							{
								final MOB mob=msg.source();
								Faction.FData data = mob.fetchFactionData(factionID());
								for (final FactionChangeEvent event : events)
								{
									if(event.applies(mob,target))
									{
										if((mudChatB.getLastRespondedTo()==mob)
										&&(mudChatB.getLastThingSaid()!=null)
										&&(!mudChatB.getLastThingSaid().equalsIgnoreCase(sayMsg)))
										{
											if((data != null) && (System.currentTimeMillis() < data.getNextChangeTimers(event)))
												continue;
											executeChange(mob,target,event);
											foundOne=true;
											if(data == null)
												data = mob.fetchFactionData(factionID());
											if(data != null)
											{
												final long newTime=CMath.s_long(event.getTriggerParm("WAIT"))*CMProps.getTickMillis();
												if(newTime != 0)
													data.setNextChangeTimers(event, System.currentTimeMillis()+newTime);
											}
										}
									}
								}
							}
							if(foundOne)
								break;
						}
					}
				}
			}
			break;
		default:
			if(msg.tool() instanceof Ability)
			{
				if((msg.target()==myHost)	// Arrested watching
				&&(msg.source().isMonster())
				&&(msg.tool().ID().equals("Skill_HandCuff")))
				{
					final Room R=msg.source().location();
					if((R!=null)&&(R.getArea()!=null))
					{
						events=getChangeEvents("ARRESTED");
						if(events!=null)
						{
							final LegalBehavior B=CMLib.law().getLegalBehavior(R);
							if((B!=null)&&(B.isAnyOfficer(R.getArea(), msg.source())))
							{
								for (final FactionChangeEvent event : events)
								{
									// reversed because the target is the one getting factioned
									if(event.applies(msg.source(),(MOB)msg.target()))
										executeChange((MOB)msg.target(),msg.source(),event);
								}
							}
						}
					}
				}
				if((msg.othersMessage()!=null)
				&&(msg.source()==myHost)
				&&((events=findAbilityChangeEvents((Ability)msg.tool()))!=null))
				{
					for (final FactionChangeEvent C : events)
					{
						final MOB target;
						if((msg.target() instanceof MOB)&&(C.applies(msg.source(),(MOB)msg.target())))
							target=(MOB)msg.target();
						else
						if (!(msg.target() instanceof MOB))
							target=null;
						else
							continue;
						Faction.FData data = msg.source().fetchFactionData(factionID());
						if((data != null) && (System.currentTimeMillis() < data.getNextChangeTimers(C)))
							continue;
						executeChange(msg.source(),target,C);
						if(data == null)
							data = msg.source().fetchFactionData(factionID());
						if(data != null)
						{
							final long newTime=CMath.s_long(C.getTriggerParm("WAIT"))*CMProps.getTickMillis();
							if(newTime != 0)
								data.setNextChangeTimers(C, System.currentTimeMillis()+newTime);
						}
					}
				}
			}
			else
			if((msg.tool() instanceof Social)		// socials
			&&(msg.source()==myHost)
			&&(msg.target() instanceof MOB)
			&&(((Social)msg.tool()).targetable(msg.source()))
			&&(msg.sourceMinor()!=CMMsg.TYP_CHANNEL))
			{
				events=getChangeEvents("SOCIAL");
				if(events == null)
					events=findSocialChangeEvents((Social)msg.tool());
				if(events!=null)
				{
					final Social social = (Social)msg.tool();
					final String socialName = social.baseName();
					final MOB mob=msg.source();
					Faction.FData data = mob.fetchFactionData(factionID());
					for (final FactionChangeEvent event : events)
					{
						if(event.eventID().equals("SOCIAL"))
						{
							final String triggerID=event.getTriggerParm("ID");
							if(triggerID.length()==0)
								continue;
							if(!socialName.equals(triggerID)
							&&(!triggerID.equalsIgnoreCase("ALL")))
								continue;
						}
						if((event.applies(mob,(MOB)msg.target())))
						{
							if((data != null) && (System.currentTimeMillis() < data.getNextChangeTimers(event)))
								continue;
							executeChange(mob,(MOB)msg.target(),event);
							if(data == null)
								data = mob.fetchFactionData(factionID());
							if(data != null)
							{
								final long newTime=CMath.s_long(event.getTriggerParm("WAIT"))*CMProps.getTickMillis();
								if(newTime != 0)
									data.setNextChangeTimers(event, System.currentTimeMillis()+newTime);
							}
						}
					}
				}
			}
			break;
		}
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		if(((msg.sourceMinor()==CMMsg.TYP_EXPCHANGE)  // Experience is being altered
			||(msg.sourceMinor()==CMMsg.TYP_RPXPCHANGE))
		&&(msg.target() instanceof MOB) 		  // because a mob died
		&&(myHost==msg.source())	  // this Faction is on the mob that killed them
		&&(!experienceFlag.equals("NONE"))
		&&(msg.value()>0))
		{
			final MOB killer=msg.source();
			final MOB vic=(MOB)msg.target();

			if(experienceFlag.equals("HIGHER"))
				msg.setValue( (int)Math.round(((msg.value())*.75) +( ((msg.value())*.25) * CMath.div(Math.abs(killer.fetchFaction(ID)-minimum),(maximum - minimum)))));
			else
			if(experienceFlag.equals("LOWER"))
				msg.setValue( (int)Math.round(((msg.value())*.75) +( ((msg.value())*.25) * CMath.div(Math.abs(maximum-killer.fetchFaction(ID)),(maximum - minimum)))));
			else
			if(vic.fetchFaction(ID)!=Integer.MAX_VALUE)
			{
				if(experienceFlag.equals("EXTREME"))
					msg.setValue( (int)Math.round(((msg.value())*.75) +( ((msg.value())*.25) * CMath.div(Math.abs(vic.fetchFaction(ID) - killer.fetchFaction(ID)),(maximum - minimum)))));
				else
				if(experienceFlag.equals("FOLLOWHIGHER"))
					msg.setValue( (int)Math.round(((msg.value())*.75) +( ((msg.value())*.25) * CMath.div(Math.abs(vic.fetchFaction(ID)-minimum),(maximum - minimum)))));
				else
				if(experienceFlag.equals("FOLLOWLOWER"))
					msg.setValue( (int)Math.round(((msg.value())*.75) +( ((msg.value())*.25) * CMath.div(Math.abs(maximum-vic.fetchFaction(ID)),(maximum - minimum)))));
				if(msg.value()<=0)
					msg.setValue(0);
			}
		}
		return true;
	}

	@Override
	public void executeChange(final MOB source, MOB target, final FactionChangeEvent event)
	{
		final int sourceFaction= source.fetchFaction(ID);
		int targetFaction = sourceFaction * -1;
		if((source==target)
		&&(!event.selfTargetOK())
		&&(!event.eventID().equalsIgnoreCase("TIME")))
			return;

		if(target!=null)
		{
			if(hasFaction(target))
				targetFaction=target.fetchFaction(ID);
			else
			if(!event.outsiderTargetOK())
				return;
		}
		else
			target = source;

		final String chance=event.getTriggerParm("CHANCE");
		if(CMath.isInteger(chance) && (CMLib.dice().rollPercentage()<CMath.s_int(chance)))
			return;

		double baseChangeAmount=100.0;
		if((source!=target)
		&&(!event.just100()))
		{
			final int levelLimit=CMProps.getIntVar(CMProps.Int.EXPRATE);
			final int levelDiff=target.phyStats().level()-source.phyStats().level();

			if(levelDiff<(-levelLimit) )
				baseChangeAmount=0.0;
			else
			if(levelLimit>0)
			{
				double levelFactor=CMath.div(levelDiff,levelLimit);
				if(levelFactor> (levelLimit))
					levelFactor=(levelLimit);
				baseChangeAmount=baseChangeAmount+CMath.mul(levelFactor,100);
			}
		}
		if(event.getBonusXP()!=0)
			CMLib.leveler().postExperience(source, target, "", event.getBonusXP(), false);
		if(event.getBonusRoleplayXP()!=0)
			CMLib.leveler().postRPExperience(source, target, "", event.getBonusRoleplayXP(), true);

		int factionAdj=1;
		int changeDir=0;
		switch(event.direction())
		{
		case FactionChangeEvent.CHANGE_DIRECTION_MAXIMUM:
			factionAdj=maximum-sourceFaction;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_MINIMUM:
			factionAdj=minimum-sourceFaction;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_UP:
			changeDir=1;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_DOWN:
			changeDir=-1;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_OPPOSITE:
			if(source!=target)
			{
				if(targetFaction==middle)
					changeDir=(sourceFaction>middle)?1:-1;
				else
					changeDir=(targetFaction>middle)?-1:1;
				if((sourceFaction>middle)&&(targetFaction>middle))
					changeDir=-1;
				baseChangeAmount=CMath.div(baseChangeAmount,2.0)
								+(int)Math.round(CMath.div(baseChangeAmount,2.0)
										*Math.abs((sourceFaction-targetFaction)
												/Math.abs((double)difference)));
			}
			else
				factionAdj=0;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_AWAY:
			if(source!=target)
				changeDir=targetFaction>=sourceFaction?-1:1;
			else
				factionAdj=0;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_TOWARD:
			if(source!=target)
				changeDir=targetFaction>=sourceFaction?1:-1;
			else
				factionAdj=0;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_REMOVE:
			factionAdj=Integer.MAX_VALUE;
			break;
		case FactionChangeEvent.CHANGE_DIRECTION_ADD:
			factionAdj=findDefault(source);
			if(!hasFaction(source))
				source.addFaction(ID,0);
			else
				factionAdj=0;
			break;
		}
		if(changeDir!=0)
		{
			//int baseExp=(int)Math.round(theAmount);

			// Pardon the completely random seeming 1.42 and 150.
			// They're the result of making graphs of scenarios and massaging the formula, nothing more or less.
			if((hasFaction(target))||(event.outsiderTargetOK()))
				factionAdj=changeDir*(int)Math.round(rateModifier*baseChangeAmount);
			else
				factionAdj=0;
			factionAdj*=event.factor();
			factionAdj=(int)Math.round(CMath.mul(factionAdj,findFactor(source,(factionAdj>=0))));
		}

		if(factionAdj==0)
			return;

		CMMsg facMsg=CMClass.getMsg(source,target,null,CMMsg.MASK_ALWAYS|CMMsg.TYP_FACTIONCHANGE,null,CMMsg.NO_EFFECT,null,CMMsg.NO_EFFECT,ID);
		facMsg.setValue(factionAdj);
		final Room R=source.location();
		if(R!=null)
		{
			if(R.okMessage(source,facMsg))
			{
				R.send(source, facMsg);
				factionAdj=facMsg.value();
				if((factionAdj!=Integer.MAX_VALUE)&&(factionAdj!=Integer.MIN_VALUE))
				{
					// Now execute the changes on the relation.  We do this AFTER the execution of the first so
					// that any changes from okMessage are incorporated
					if(relations.size()>0)
					{
						for(final Enumeration<String> e=relations.keys();e.hasMoreElements();)
						{
							final String relID=(e.nextElement());
							facMsg=CMClass.getMsg(source,target,null,CMMsg.MASK_ALWAYS|CMMsg.TYP_FACTIONCHANGE,null,CMMsg.NO_EFFECT,null,CMMsg.NO_EFFECT,relID);
							facMsg.setValue((int)Math.round(CMath.mul(factionAdj, relations.get(relID).doubleValue())));
							if(R.okMessage(source,facMsg))
								R.send(source, facMsg);
						}
					}
				}
			}
		}
		else
		if((factionAdj==Integer.MAX_VALUE)||(factionAdj==Integer.MIN_VALUE))
			source.removeFaction(ID);
		else
			source.adjustFaction(ID,factionAdj);
	}

	@Override
	public String usageFactorRangeDescription(final Ability A)
	{
		final StringBuffer rangeStr=new StringBuffer();
		final HashSet<String> namesAdded=new HashSet<String>();
		final FAbilityUsage usage = getAbilityUsage(A);
		if(usage != null)
		{
			for(final Enumeration<FRange> e=ranges();e.hasMoreElements();)
			{
				final FRange R=e.nextElement();
				if((((R.high()<=usage.high())&&(R.high()>=usage.low()))
					||((R.low()>=usage.low()))&&(R.low()<=usage.high()))
				&&(!namesAdded.contains(R.name())))
				{
					namesAdded.add(R.name());
					if(rangeStr.length()>0)
						rangeStr.append(", ");
					rangeStr.append(R.name());
				}
			}
		}
		return rangeStr.toString();
	}

	private static String _ALL_TYPES=null;

	@Override
	public String ALL_CHANGE_EVENT_TYPES()
	{
		final StringBuffer ALL_TYPES=new StringBuffer("");
		if(_ALL_TYPES!=null)
			return _ALL_TYPES;
		for (final String element : Faction.FactionChangeEvent.MISC_TRIGGERS)
			ALL_TYPES.append(element+", ");
		for (final String element : Ability.ACODE_DESCS)
			ALL_TYPES.append(element+", ");
		for (final String element : Ability.DOMAIN_DESCS)
			ALL_TYPES.append(element+", ");
		for (final String element : Ability.FLAG_DESCS)
			ALL_TYPES.append(element+", ");
		final Set<String> doneSoc=new HashSet<String>();
		for(final Enumeration<Social> s = CMLib.socials().getAllSocials();s.hasMoreElements();)
		{
			final Social S=s.nextElement();
			ALL_TYPES.append(S.name()).append(", ");
			if(!doneSoc.contains(S.baseName()))
			{
				doneSoc.add(S.baseName());
				ALL_TYPES.append(S.baseName()+" *, ");
			}
		}
		_ALL_TYPES=ALL_TYPES.toString()+" a valid Skill, Spell, Chant, etc. ID.";
		return _ALL_TYPES;
	}

	@Override
	public Faction.FactionChangeEvent createChangeEvent(final String key)
	{
		Faction.FactionChangeEvent event;
		if(key==null)
			return null;
		if(key.indexOf(';')<0)
		{
			event=new DefaultFaction.DefaultFactionChangeEvent(this);
			if(!event.setEventID(key))
				return null;
		}
		else
			event=new DefaultFaction.DefaultFactionChangeEvent(this,key);
		abilChangeCache.clear();
		socChangeCache.clear();
		Faction.FactionChangeEvent[] events=changes.get(event.eventID().toUpperCase().trim());
		if(events==null)
			events=new Faction.FactionChangeEvent[0];
		events=Arrays.copyOf(events, events.length+1);
		events[events.length-1]=event;
		changes.put(event.eventID().toUpperCase().trim(), events);
		return event;
	}

	private boolean replaceEvents(final String key, final Faction.FactionChangeEvent event, final boolean strict)
	{
		final Faction.FactionChangeEvent[] events=changes.get(key);
		if(events==null)
			return false;
		final Faction.FactionChangeEvent[] nevents=new Faction.FactionChangeEvent[events.length-1];
		int ne1=0;
		boolean done=false;
		for (final FactionChangeEvent event2 : events)
		{
			if((strict&&(event2 == event))||((!strict)&&(event2.toString().equals(event.toString()))))
			{
				if(nevents.length==0)
					changes.remove(key);
				else
					changes.put(key,nevents);
				done=true;
			}
			else
			if(ne1<nevents.length)
				nevents[ne1++]=event2;
		}
		if(done)
		{
			abilChangeCache.clear();
			socChangeCache.clear();
		}
		return done;
	}

	@Override
	public void clearChangeEvents()
	{
		abilChangeCache.clear();
		socChangeCache.clear();
		changes.clear();
	}

	@Override
	public boolean delChangeEvent(final Faction.FactionChangeEvent event)
	{
		for(final Enumeration<String> e=changes.keys();e.hasMoreElements();)
		{
			if(replaceEvents(e.nextElement(),event,true))
			{
				abilChangeCache.clear();
				socChangeCache.clear();
				return true;
			}
		}
		for(final Enumeration<String> e=changes.keys();e.hasMoreElements();)
		{
			if(replaceEvents(e.nextElement(),event,false))
			{
				abilChangeCache.clear();
				socChangeCache.clear();
				return true;
			}
		}
		return false;
	}

	public class DefaultFactionChangeEvent implements Faction.FactionChangeEvent
	{
		private String		ID				= "";
		private String		flagCache		= "";
		private int			IDclassFilter	= -1;
		private long		IDflagFilter	= -1;
		private int			IDdomainFilter	= -1;
		private int			direction		= 0;
		private double		factor			= 0.0;
		private String		targetZapperStr	= "";
		private boolean		outsiderTargetOK= false;
		private boolean		selfTargetOK	= false;
		private boolean		just100			= false;
		private int			bonusXP			= 0;
		private int			bonusRPXP		= 0;
		private Object[]	stateVariables	= new Object[0];
		private String		triggerParms	= "";

		private final Faction	myFaction;

		private Map<String, String>				flags					= new Hashtable<String, String>();
		private Map<String, String>				savedTriggerParms		= new Hashtable<String, String>();
		private MaskingLibrary.CompiledZMask	compiledTargetZapper	= null;
		private MaskingLibrary.CompiledZMask	compiledSourceZapper	= null;

		@Override
		public String eventID()
		{
			return ID;
		}

		@Override
		public String flagCache()
		{
			return flagCache;
		}

		@Override
		public int IDclassFilter()
		{
			return IDclassFilter;
		}

		@Override
		public long IDflagFilter()
		{
			return IDflagFilter;
		}

		@Override
		public int IDdomainFilter()
		{
			return IDdomainFilter;
		}

		@Override
		public int direction()
		{
			return direction;
		}

		@Override
		public double factor()
		{
			return factor;
		}

		@Override
		public int getBonusXP()
		{
			return bonusXP;
		}

		@Override
		public int getBonusRoleplayXP()
		{
			return bonusRPXP;
		}

		@Override
		public String targetZapper()
		{
			return targetZapperStr;
		}

		@Override
		public boolean outsiderTargetOK()
		{
			return outsiderTargetOK;
		}

		@Override
		public boolean selfTargetOK()
		{
			return selfTargetOK;
		}

		@Override
		public boolean just100()
		{
			return just100;
		}

		@Override
		public void setDirection(final int newVal)
		{
			direction = newVal;
		}

		@Override
		public void setFactor(final double newVal)
		{
			factor = newVal;
		}

		@Override
		public void setTargetZapper(final String newVal)
		{
			targetZapperStr = newVal;
		}

		@Override
		public MaskingLibrary.CompiledZMask compiledTargetZapper()
		{
			if(compiledTargetZapper == null)
			{
				if(targetZapperStr.trim().length()>0)
					compiledTargetZapper=CMLib.masking().maskCompile(targetZapperStr);
			}
			return compiledTargetZapper;
		}

		@Override
		public MaskingLibrary.CompiledZMask compiledSourceZapper()
		{
			if(compiledSourceZapper == null)
			{
				final String sourceZapperStr=savedTriggerParms.get("MASK");
				if((sourceZapperStr!=null)&&(sourceZapperStr.length()>0))
					compiledSourceZapper=CMLib.masking().maskCompile(sourceZapperStr);
			}
			return compiledSourceZapper;
		}

		@Override
		public String getTriggerParm(final String parmName)
		{
			if((triggerParms==null)||(triggerParms.length()==0))
				return "";
			final String S=savedTriggerParms.get(parmName);
			if(S!=null)
				return S;
			return "";
		}

		@Override
		public String toString()
		{
			if(triggerParms.trim().length()>0)
				return ID+"("+triggerParms.replace(';',',')+");"+CHANGE_DIRECTION_DESCS[direction]+";"+((int)Math.round(factor*100.0))+"%;"+flagCache+";"+targetZapperStr;
			else
				return ID+";"+CHANGE_DIRECTION_DESCS[direction]+";"+((int)Math.round(factor*100.0))+"%;"+flagCache+";"+targetZapperStr;
		}

		public DefaultFactionChangeEvent(final Faction F)
		{
			myFaction = F;
		}

		public DefaultFactionChangeEvent(final Faction F, final String key)
		{
			myFaction=F;
			final List<String> v = CMParms.parseSemicolons(key,false);

			String trigger =v.get(0);
			triggerParms="";
			final int x=trigger.indexOf('(');
			if((x>0)&&(trigger.endsWith(")")))
			{
				setTriggerParameters(trigger.substring(x+1,trigger.length()-1));
				trigger=trigger.substring(0,x);
			}

			setEventID(trigger);
			setDirection(v.get(1));
			final String amt=v.get(2).trim();
			if(amt.endsWith("%"))
				setFactor(CMath.s_pct(amt));
			else
				setFactor(1.0);

			if(v.size()>3)
				setFlags(v.get(3));
			if(v.size()>4)
				setTargetZapper(v.get(4));
		}

		@Override
		public boolean setEventID(final String newID)
		{
			IDclassFilter=-1;
			IDflagFilter=-1;
			IDdomainFilter=-1;
			for (final String element : MISC_TRIGGERS)
			{
				if(element.equalsIgnoreCase(newID))
				{
					ID = element;
					return true;
				}
			}
			for(int i=0;i<Ability.ACODE_DESCS.length;i++)
			{
				if(Ability.ACODE_DESCS[i].equalsIgnoreCase(newID))
				{
					IDclassFilter = i;
					ID = newID;
					return true;
				}
			}
			for(int i=0;i<Ability.DOMAIN_DESCS.length;i++)
			{
				if(Ability.DOMAIN_DESCS[i].equalsIgnoreCase(newID))
				{
					IDdomainFilter = i << 5;
					ID = newID;
					return true;
				}
			}
			for(int i=0;i< Ability.FLAG_DESCS.length;i++)
			{
				if(Ability.FLAG_DESCS[i].equalsIgnoreCase(newID))
				{
					IDflagFilter = Math.round(Math.pow(2, i));
					ID = newID;
					return true;
				}
			}
			if(CMClass.getAbility(newID)!=null)
			{
				ID = newID;
				return true;
			}
			if(CMLib.socials().fetchSocial(newID, true)!=null)
			{
				ID=newID.toUpperCase();
				return true;
			}
			if((newID.endsWith(" *"))
			&&(CMLib.socials().fetchSocial(newID.substring(0,newID.length()-2).trim(), true)!=null))
			{
				ID=newID.toUpperCase().trim();
				return true;
			}
			return false;
		}

		public boolean setDirection(final String d)
		{
			if(CMath.isInteger(d))
				direction=CMath.s_int(d);
			else
			if(d.startsWith("U"))
			{
				direction = CHANGE_DIRECTION_UP;
			}
			else
			if(d.startsWith("D"))
			{
				direction = CHANGE_DIRECTION_DOWN;
			}
			else
			if(d.startsWith("OPP"))
			{
				direction = CHANGE_DIRECTION_OPPOSITE;
			}
			else
			if(d.startsWith("REM"))
			{
				direction = CHANGE_DIRECTION_REMOVE;
			}
			else
			if(d.startsWith("MIN"))
			{
				direction = CHANGE_DIRECTION_MINIMUM;
			}
			else
			if(d.startsWith("MAX"))
			{
				direction = CHANGE_DIRECTION_MAXIMUM;
			}
			else
			if(d.startsWith("ADD"))
			{
				direction = CHANGE_DIRECTION_ADD;
			}
			else
			if(d.startsWith("TOW"))
			{
				direction = CHANGE_DIRECTION_TOWARD;
			}
			else
			if(d.startsWith("AWA"))
			{
				direction = CHANGE_DIRECTION_AWAY;
			}
			else
				return false;
			return true;
		}

		@Override
		public void setFlags(final String newFlagCache)
		{
			flagCache=newFlagCache.toUpperCase().trim();
			flags=CMParms.parseStrictEQParms(newFlagCache);
			outsiderTargetOK=flags.containsKey("OUTSIDER");
			selfTargetOK=flags.containsKey("SELFOK");
			just100=flags.containsKey("JUST100");
			bonusXP=flags.containsKey("XP")?CMath.s_int(flags.get("XP")):0;
			bonusRPXP=flags.containsKey("RPXP")?CMath.s_int(flags.get("RPXP")):0;
		}

		@Override
		public String getFlagValue(String key)
		{
			key=key.toUpperCase().trim();
			return flags.containsKey(key) ? flags.get(key) : "";
		}

		@Override
		public boolean applies(final MOB source, final MOB target)
		{
			if(!CMLib.masking().maskCheck(compiledTargetZapper(),target,false))
				return false;
			if(!CMLib.masking().maskCheck(compiledSourceZapper(),target,false))
				return false;
			return true;
		}

		@Override
		public String triggerParameters()
		{
			return triggerParms;
		}

		@Override
		public void setTriggerParameters(final String newVal)
		{
			triggerParms=newVal;
			savedTriggerParms=CMParms.parseEQParms(newVal);
			compiledSourceZapper=null;
		}

		@Override
		public Object stateVariable(final int x)
		{
			return ((x >= 0) && (x < stateVariables.length)) ? stateVariables[x] : null;
		}

		@Override
		public void setStateVariable(final int x, final Object newVal)
		{
			if(x<0)
				return;
			if(x>=stateVariables.length)
				stateVariables=Arrays.copyOf(stateVariables,x+1);
			stateVariables[x]=newVal;
		}

		@Override
		public Faction getFaction()
		{
			return myFaction;
		}
	}

	@Override
	public Faction.FRange addRange(final String key)
	{
		final Faction.FRange FR=new DefaultFaction.DefaultFactionRange(this,key);
		ranges.put(FR.codeName().toUpperCase().trim(),FR);
		recalc();
		return FR;
	}

	@Override
	public boolean delRange(final FRange FR)
	{
		if(!ranges.containsKey(FR.codeName().toUpperCase().trim()))
			return false;
		ranges.remove(FR.codeName().toUpperCase().trim());
		recalc();
		return true;
	}

	public class DefaultFactionRange implements Faction.FRange, Comparable<Faction.FRange>
	{
		public int				low;
		public int				high;
		public String			name		= "";
		public String			codeName	= "";
		public Faction.Align	alignEquiv;
		public Faction			faction		= null;

		@Override
		public int low()
		{
			return low;
		}

		@Override
		public int high()
		{
			return high;
		}

		@Override
		public String name()
		{
			return name;
		}

		@Override
		public String codeName()
		{
			return codeName;
		}

		@Override
		public Align alignEquiv()
		{
			return alignEquiv;
		}

		@Override
		public Faction getFaction()
		{
			return faction;
		}

		@Override
		public void setLow(final int newVal)
		{
			low = newVal;
		}

		@Override
		public void setHigh(final int newVal)
		{
			high = newVal;
		}

		@Override
		public void setName(final String newVal)
		{
			name = newVal;
		}

		@Override
		public void setAlignEquiv(final Faction.Align newVal)
		{
			alignEquiv = newVal;
		}

		public DefaultFactionRange(final Faction F, final String key)
		{
			faction=F;
			final List<String> v = CMParms.parseSemicolons(key,false);
			name = v.get(2);
			low = CMath.s_int( v.get(0));
			high = CMath.s_int( v.get(1));
			if(v.size()>3)
				codeName=v.get(3);
			if(v.size()>4)
				alignEquiv = CMLib.factions().getAlignEnum(v.get(4));
			else
				alignEquiv = Faction.Align.INDIFF;
		}

		@Override
		public String toString()
		{
			return low +";"+high+";"+name+";"+codeName+";"+alignEquiv.toString();
		}

		@Override
		public int random()
		{
			final Random gen = new Random();
			return high - gen.nextInt(high-low);
		}

		@Override
		public int med()
		{
			return high - (high-low)/2;
		}

		@Override
		public int compareTo(final FRange o)
		{
			if(low < o.low())
				return -1;
			if(high > o.high())
				return 1;
			return 0;
		}
	}

	@Override
	public Faction.FAbilityUsage addAbilityUsage(final String key)
	{
		final Faction.FAbilityUsage usage=
			(key==null)?new DefaultFaction.DefaultFactionAbilityUsage()
					  : new DefaultFaction.DefaultFactionAbilityUsage(key);
		abilityUsages.add(usage);
		abilityUsages.trimToSize();
		abilityUseCache.clear();
		return usage;
	}

	@Override
	public boolean delAbilityUsage(final Faction.FAbilityUsage usage)
	{
		if(!abilityUsages.remove(usage))
			return false;
		abilityUsages.trimToSize();
		abilityUseCache.clear();
		return true;
	}

	public class DefaultFactionData implements FData
	{
		private int				value;
		private boolean			noListeners;
		private boolean			noTickers;
		private boolean			noStatAffectors;
		private long			lastUpdated;
		private Ability[]		myEffects;
		private Behavior[]		myBehaviors;
		private Ability			lightPresenceAbilities[];
		private Faction.FRange	currentRange;
		private boolean			erroredOut;
		private Faction			myFaction;
		public boolean			isReset	= false;
		private DVector			currentReactionSets;

		private Map<Faction.FactionChangeEvent, Long> nextChangeTime;

		public DefaultFactionData(final Faction F)
		{
			resetFactionData(F);
		}

		@Override
		public void resetFactionData(final Faction F)
		{
			if(!isReset)
			{
				myFaction=F;
				value=0;
				noListeners=false;
				noTickers=false;
				noStatAffectors=false;
				lastUpdated=System.currentTimeMillis();
				myEffects=new Ability[0];
				myBehaviors=new Behavior[0];
				currentReactionSets = new DVector(2);
				lightPresenceAbilities = new Ability[0];
				currentRange = null;
				erroredOut=false;
				isReset = true;
				nextChangeTime = new Hashtable<Faction.FactionChangeEvent, Long>();
			}
		}

		@Override
		public int value()
		{
			return value;
		}

		@Override
		public Faction getFaction()
		{
			return myFaction;
		}

		@Override
		public long getNextChangeTimers(final Faction.FactionChangeEvent event)
		{
			if(nextChangeTime.containsKey(event))
				return nextChangeTime.get(event).longValue();
			return 0;
		}

		@Override
		public void setNextChangeTimers(final Faction.FactionChangeEvent event, final long time)
		{
			nextChangeTime.put(event, Long.valueOf(time));
		}

		@SuppressWarnings("unchecked")
		@Override
		public void setValue(final int newValue)
		{
			this.value=newValue;
			if((currentRange==null)||(this.value<currentRange.low())||(this.value>currentRange.high()))
			{
				synchronized(this)
				{
					if((currentRange!=null)&&(this.value>=currentRange.low())&&(this.value<=currentRange.high()))
						return;
					currentRange = fetchRange(value);
					if(currentRange==null)
					{
						if(!erroredOut)
							Log.errOut("DefaultFactionData","Faction "+factionID()+" does not define a range for "+this.value);
						erroredOut=true;
					}
					else
					{
						erroredOut=false;
						currentReactionSets=new DVector(2);
						for(final Enumeration<Faction.FReactionItem> e=reactions();e.hasMoreElements();)
						{
							Faction.FReactionItem react = e.nextElement();
							if(!react.rangeCodeName().equalsIgnoreCase(currentRange.codeName()))
								continue;
							Faction.FReactionItem sampleReact = null;
							Vector<Faction.FReactionItem> reactSet=null;
							for(int r=0;r<currentReactionSets.size();r++)
							{
								reactSet=(Vector<Faction.FReactionItem>)currentReactionSets.elementAt(r,2);
								sampleReact=reactSet.firstElement();
								if(react.presentMOBMask().trim().equalsIgnoreCase(sampleReact.presentMOBMask().trim()))
								{
									reactSet.addElement(react);
									react=null; break;
								}
							}
							if(react!=null)
								currentReactionSets.addElement(react.compiledPresentMOBMask(),new XVector<Faction.FReactionItem>(react));
						}
						//noReactions=currentReactionSets.size()==0;
					}
					noListeners=(myEffects.length==0) && (myBehaviors.length==0) && (currentReactionSets.size()==0);
					noTickers=(myBehaviors.length==0) && (myEffects.length==0) &&((currentReactionSets.size()==0)||(!useLightReactions()));
				}
			}
		}

		@Override
		public void affectPhyStats(final Physical affected, final PhyStats affectableStats)
		{
			if(!noStatAffectors)
				for(final Ability A : myEffects) A.affectPhyStats(affected, affectableStats);
		}

		@Override
		public void affectCharStats(final MOB affectedMob, final CharStats affectableStats)
		{
			if(!noStatAffectors)
				for(final Ability A : myEffects) A.affectCharStats(affectedMob, affectableStats);
		}

		@Override
		public void affectCharState(final MOB affectedMob, final CharState affectableMaxState)
		{
			if(!noStatAffectors)
				for(final Ability A : myEffects) A.affectCharState(affectedMob, affectableMaxState);
		}

		@Override
		public void addHandlers(final List<Ability> listeners, final List<Behavior> tickers)
		{
			this.myEffects=listeners.toArray(new Ability[0]);
			this.myBehaviors=tickers.toArray(new Behavior[0]);
			noListeners=(listeners.size()==0) && (tickers.size()==0) && (currentReactionSets.size()==0);
			noTickers=(listeners.size()==0) && (tickers.size()==0) && ((currentReactionSets.size()==0)||(!useLightReactions()));
			noStatAffectors=(listeners.size()==0);
			isReset = false;
		}

		@Override
		public boolean requiresUpdating()
		{
			return lastDataChange[0] > lastUpdated;
		}

		@SuppressWarnings("unchecked")
		private Ability setPresenceReaction(final MOB M, final Physical myHost)
		{
			if((!CMLib.flags().canBeSeenBy(myHost, M))
			&&(!CMLib.flags().canBeHeardMovingBy(myHost,M)))
				return null;
			if((M.amUltimatelyFollowing()!=null)
			&&(!M.amUltimatelyFollowing().isMonster()))
				return null;
			Vector<String> myReactions=null;
			List<Faction.FReactionItem> tempReactSet=null;
			for(int d=0;d<currentReactionSets.size();d++)
			{
				if(CMLib.masking().maskCheck((MaskingLibrary.CompiledZMask)currentReactionSets.elementAt(d,1),M,true))
				{
					if(myReactions==null)
						myReactions=new Vector<String>();
					tempReactSet=(List<Faction.FReactionItem>)currentReactionSets.elementAt(d,2);
					for(final Faction.FReactionItem reactionItem : tempReactSet)
						myReactions.add(reactionItem.reactionObjectID()+"="+reactionItem.parameters(myHost.Name()));
				}
			}
			if(myReactions!=null)
			{
				if(useLightReactions())
				{
					final Ability A=(Ability)presenceReactionPrototype.copyOf();
					A.invoke(M,myReactions,myHost,false,0);
					A.setInvoker(M);
					return A;
				}
				else
				if(M.fetchEffect(presenceReactionPrototype.ID())==null)
				{
					final Ability A=(Ability)presenceReactionPrototype.copyOf();
					A.invoke(M,myReactions,myHost,true,0);
				}
			}
			return null;
		}

		@Override
		public void executeMsg(final Environmental myHost, final CMMsg msg)
		{
			if(noListeners)
				return;
			synchronized(lightPresenceAbilities)
			{
				if((currentReactionSets.size()>0)
				&&(msg.sourceMinor()==CMMsg.TYP_ENTER)
				&&(msg.target() instanceof Room))
				{
					if(presenceReactionPrototype==null)
					{
						if((presenceReactionPrototype=CMClass.getAbility("PresenceReaction"))==null)
							return;
					}
					if((msg.source()==myHost)
					&&(!msg.source().isMonster()))
					{
						MOB M=null;
						final Room R=(Room)msg.target();
						final List<Ability> lightPresenceReactions=new LinkedList<Ability>();
						Ability A=null;
						for(int m=0;m<R.numInhabitants();m++)
						{
							M=R.fetchInhabitant(m);
							if((M!=null)
							&&(M!=myHost)
							&&(M.isMonster())) // follow checks are in setPresenceReaction
							{
								A=setPresenceReaction(M,msg.source());
								if(A!=null) // means yes, we are using light, and yes, heres a reaction to add
									lightPresenceReactions.add(A);
							}
						}
						lightPresenceAbilities = lightPresenceReactions.toArray(new Ability[0]);
					}
					else
					if((msg.source().isMonster())
					&&(msg.target()==CMLib.map().roomLocation(myHost))
					&&(myHost instanceof Physical))
					{
						if(!(myHost instanceof MOB))
						{
							// follow checks are in setPresenceReaction
							final Ability A=setPresenceReaction(msg.source(),(Physical)myHost);
							if(A!=null)
							{ // means yes, we are using light, and yes, heres a reaction to add
								lightPresenceAbilities = Arrays.copyOf(lightPresenceAbilities, lightPresenceAbilities.length+1);
								lightPresenceAbilities[lightPresenceAbilities.length-1]=A;
							}
						}
					}
				}
				else
				if((lightPresenceAbilities.length>0)
				&&(msg.sourceMinor()==CMMsg.TYP_LEAVE)
				&&(msg.target() instanceof Room))
				{
					if((msg.source()==myHost)
					&&(!msg.source().isMonster()))
					{
						final Room R=(Room)msg.target();
						MOB M=null;
						for(int m=0;m<R.numInhabitants();m++)
						{
							M=R.fetchInhabitant(m);
							if((M!=null)
							&&(M!=myHost)
							&&(M.isMonster()))
								presenceReactionPrototype.invoke(M,new Vector<String>(),null,true,0); // this shuts it down
						}
						lightPresenceAbilities=new Ability[0];
					}
					else
					if((msg.source().isMonster())
					&&(myHost instanceof Physical))
					{
						presenceReactionPrototype.invoke(msg.source(),new Vector<String>(),null,true,0);
						final Ability[] newAbilities = new Ability[lightPresenceAbilities.length];
						int l=0;
						for (final Ability lightPresenceAbilitie : lightPresenceAbilities)
						{
							if(lightPresenceAbilitie.affecting()==null)
							{
							}
							else
							if(lightPresenceAbilitie.affecting()==msg.source())
								lightPresenceAbilitie.invoke(msg.source(),new Vector<String>(),null,true,0);
							else
								newAbilities[l++]=lightPresenceAbilitie;
						}
						if(l==0)
							lightPresenceAbilities=new Ability[0];
						else
						if(l<lightPresenceAbilities.length)
							lightPresenceAbilities = Arrays.copyOf(newAbilities, l);
					}
				}
			}
			for(final Ability A : lightPresenceAbilities)
				A.executeMsg(A.invoker(), msg);
			for(final Ability A : myEffects)
				A.executeMsg(myHost, msg);
			for(final Behavior B : myBehaviors)
				B.executeMsg(myHost, msg);
		}

		@Override
		public boolean okMessage(final Environmental myHost, final CMMsg msg)
		{
			if(noListeners)
				return true;
			for(final Ability A : myEffects)
			{
				if(!A.okMessage(myHost, msg))
					return false;
			}
			for(final Behavior B : myBehaviors)
			{
				if(!B.okMessage(myHost, msg))
					return false;
			}
			for(final Ability A : lightPresenceAbilities)
			{
				if(!A.okMessage(A.invoker(), msg))
					return false;
			}
			return true;
		}

		@Override
		public boolean tick(final Tickable ticking, final int tickID)
		{
			if(noTickers)
				return true;
			for(final Ability A : myEffects)
			{
				if(!A.tick(ticking, tickID))
					return false;
			}
			for(final Behavior B : myBehaviors)
			{
				if(!B.tick(ticking, tickID))
					return false;
			}
			for(final Ability A : lightPresenceAbilities)
			{
				if(!A.tick(A.invoker(), tickID))
					return false;
			}
			return true;
		}
	}

	public class DefaultFactionZapFactor implements Faction.FZapFactor
	{
		private double gainF= 1.0;
		private double lossF= 1.0;
		private String mask	= "";
		private MaskingLibrary.CompiledZMask compiledMask=null;

		public DefaultFactionZapFactor(final double gain, final double loss, final String mask)
		{
			setGainFactor(gain);
			setLossFactor(loss);
			setMOBMask(mask);
		}

		@Override
		public double gainFactor()
		{
			return gainF;
		}

		@Override
		public void setGainFactor(final double val)
		{
			gainF = val;
		}

		@Override
		public double lossFactor()
		{
			return lossF;
		}

		@Override
		public void setLossFactor(final double val)
		{
			lossF = val;
		}

		@Override
		public String MOBMask()
		{
			return mask;
		}

		@Override
		public MaskingLibrary.CompiledZMask compiledMOBMask()
		{
			return compiledMask;
		}

		@Override
		public void setMOBMask(final String str)
		{
			mask=str;
			compiledMask=CMLib.masking().maskCompile(str);
		}

		@Override
		public String toString()
		{
			return gainF + ";" + lossF + ";" + mask;
		}
	}

	public class DefaultFactionReactionItem implements Faction.FReactionItem
	{
		private String reactionObjectID	="";
		private String mobMask			="";
		private String rangeName		="";
		private String parms			="";

		private MaskingLibrary.CompiledZMask compiledMobMask=null;

		@Override
		public String reactionObjectID()
		{
			return reactionObjectID;
		}

		@Override
		public void setReactionObjectID(final String str)
		{
			reactionObjectID = str;
		}

		@Override
		public String presentMOBMask()
		{
			return mobMask;
		}

		@Override
		public void setPresentMOBMask(final String str)
		{
			mobMask=str;
			if((str==null)||(str.trim().length()==0))
				compiledMobMask=null;
			else
				compiledMobMask=CMLib.masking().maskCompile(str);
		}

		@Override
		public MaskingLibrary.CompiledZMask compiledPresentMOBMask()
		{
			return compiledMobMask;
		}

		@Override
		public String rangeCodeName()
		{
			return rangeName;
		}

		@Override
		public void setRangeName(final String str)
		{
			rangeName = str.toUpperCase().trim();
		}

		@Override
		public String parameters()
		{
			return parms;
		}

		@Override
		public String parameters(final String name)
		{
			return CMStrings.replaceAll(parms, "<TARGET>", name);
		}

		@Override
		public void setParameters(final String str)
		{
			parms = str;
		}

		@Override
		public String toString()
		{
			return rangeName + ";" + mobMask + ";" + reactionObjectID + ";" + parms;
		}

		public DefaultFactionReactionItem()
		{
		}

		public DefaultFactionReactionItem(final String key)
		{
			int x=key.indexOf(';');
			String str = key.substring(0,x).toUpperCase().trim();
			String rest = key.substring(x+1);
			setRangeName(str);

			x=rest.indexOf(';');
			str = rest.substring(0,x).trim();
			rest = rest.substring(x+1);
			setPresentMOBMask(str);

			x=rest.indexOf(';');
			str = rest.substring(0,x).trim();
			rest = rest.substring(x+1);
			setReactionObjectID(str);
			setParameters(rest);
		}

	}

	public class DefaultFactionAbilityUsage implements Faction.FAbilityUsage
	{
		public String	ID					= "";
		public boolean	possibleAbilityID	= false;
		public int		type				= -1;
		public int		domain				= -1;
		public long		flag				= -1;
		public int		low					= 0;
		public int		high				= 0;
		public long		notflag				= -1;

		@Override
		public String abilityFlags()
		{
			return ID;
		}

		@Override
		public boolean possibleAbilityID()
		{
			return possibleAbilityID;
		}

		@Override
		public int type()
		{
			return type;
		}

		@Override
		public int domain()
		{
			return domain;
		}

		@Override
		public long flag()
		{
			return flag;
		}

		@Override
		public int low()
		{
			return low;
		}

		@Override
		public int high()
		{
			return high;
		}

		@Override
		public long notflag()
		{
			return notflag;
		}

		@Override
		public void setLow(final int newVal)
		{
			low = newVal;
		}

		@Override
		public void setHigh(final int newVal)
		{
			high = newVal;
		}

		public DefaultFactionAbilityUsage()
		{
		}

		public DefaultFactionAbilityUsage(final String key)
		{
			final List<String> v = CMParms.parseSemicolons(key,false);
			setAbilityFlag(v.get(0));
			low = CMath.s_int( v.get(1));
			high = CMath.s_int( v.get(2));
		}

		@Override
		public String toString()
		{
			return ID+";"+low+";"+high;
		}

		@Override
		public List<String> setAbilityFlag(final String str)
		{
			ID=str;
			final List<String> flags=CMParms.parse(ID);
			final List<String> unknowns=new Vector<String>();
			possibleAbilityID=false;
			for(int f=0;f<flags.size();f++)
			{
				String strflag=flags.get(f);
				final boolean not=strflag.startsWith("!");
				if(not)
					strflag=strflag.substring(1);
				switch(CMLib.factions().getAbilityFlagType(strflag))
				{
				case 1:
					type=CMParms.indexOfIgnoreCase(Ability.ACODE_DESCS, strflag);
					break;
				case 2:
					domain=CMParms.indexOfIgnoreCase(Ability.DOMAIN_DESCS, strflag);
					break;
				case 3:
					final int val=CMParms.indexOfIgnoreCase(Ability.FLAG_DESCS, strflag);
					if(not)
					{
						if(notflag<0)
							notflag=0;
						notflag=notflag|CMath.pow(2,val);
					}
					else
					{
						if(flag<0)
							flag=0;
						flag=flag|CMath.pow(2,val);
					}
					break;
				default:
					unknowns.add(strflag);
					break;
				}
			}
			if((type<0)&&(domain<0)&&(flag<0))
				possibleAbilityID=true;
			return unknowns;
		}
	}

	@Override
	public void destroy()
	{
		CMLib.factions().removeFaction(this.factionID());
		this.destroyed=true;
		defaults.clear();
		autoDefaults.clear();
		ranges.clear();
		affBehavs.clear();
		changes.clear();
		abilChangeCache.clear();
		socChangeCache.clear();
		factors.clear();
		relations.clear();
		abilityUsages.clear();
		abilityUseCache.clear();
		choices.clear();
		reactions.clear();
		reactionHash.clear();
	}

	@Override
	public boolean isSavable()
	{
		return (internalFlagBitmap & Faction.IFLAG_NEVERSAVE) == 0;
	}

	@Override
	public void setSavable(final boolean truefalse)
	{
	}

	@Override
	public boolean amDestroyed()
	{
		return destroyed;
	}
}
