/
com/planet_ink/coffee_mud/Abilities/
com/planet_ink/coffee_mud/Abilities/Common/
com/planet_ink/coffee_mud/Abilities/Diseases/
com/planet_ink/coffee_mud/Abilities/Druid/
com/planet_ink/coffee_mud/Abilities/Fighter/
com/planet_ink/coffee_mud/Abilities/Prayers/
com/planet_ink/coffee_mud/Abilities/Properties/
com/planet_ink/coffee_mud/Abilities/Skills/
com/planet_ink/coffee_mud/Abilities/Songs/
com/planet_ink/coffee_mud/Abilities/Spells/
com/planet_ink/coffee_mud/Abilities/Thief/
com/planet_ink/coffee_mud/Abilities/Traps/
com/planet_ink/coffee_mud/Areas/interfaces/
com/planet_ink/coffee_mud/Behaviors/
com/planet_ink/coffee_mud/Behaviors/interfaces/
com/planet_ink/coffee_mud/CharClasses/interfaces/
com/planet_ink/coffee_mud/Commands/
com/planet_ink/coffee_mud/Commands/interfaces/
com/planet_ink/coffee_mud/Exits/interfaces/
com/planet_ink/coffee_mud/Items/Armor/
com/planet_ink/coffee_mud/Items/Basic/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Software/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
com/planet_ink/coffee_mud/Locales/interfaces/
com/planet_ink/coffee_mud/MOBS/
com/planet_ink/coffee_mud/Races/
com/planet_ink/coffee_mud/Races/interfaces/
com/planet_ink/coffee_mud/WebMacros/
com/planet_ink/coffee_mud/WebMacros/interfaces/
com/planet_ink/coffee_mud/core/smtp/
com/planet_ink/coffee_mud/core/threads/
com/planet_ink/siplet/applet/
lib/
resources/fakedb/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
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.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.FactionData;
import com.planet_ink.coffee_mud.Common.interfaces.Faction.FactionRange;
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.Races.interfaces.*;

import java.io.ByteArrayInputStream;
import java.util.*;
import java.lang.reflect.*;
/**
 * Portions Copyright (c) 2003 Jeremy Vyska
 * Portions Copyright (c) 2004-2010 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.
 */

@SuppressWarnings("unchecked")
public class DefaultFaction implements Faction, MsgListener
{
    public String ID(){return "DefaultFaction";}
    public CMObject newInstance(){try{return (CMObject)getClass().newInstance();}catch(Exception e){return new DefaultFaction();}}
    public void initializeClass(){}
    public CMObject copyOf(){try{return (CMObject)this.clone();}catch(Exception e){return newInstance();}}
    public int compareTo(CMObject o){ return CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o));}
	protected String ID="";
	protected String name="";
	protected String choiceIntro="";
	protected long[] lastAffectBehaviorChange=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 String experienceFlag="";
	protected boolean showInScore=false;
	protected boolean showInSpecialReported=false;
	protected boolean showInEditor=false;
	protected boolean showInFactionsCommand=true;
	protected Hashtable ranges=new Hashtable();
	protected Hashtable affBehavs=new Hashtable();
	protected Vector defaults=new Vector();
	protected Vector autoDefaults=new Vector();
	protected double rateModifier=1.0;
    protected Hashtable changes=new Hashtable();
    protected Vector factors=new Vector();
    protected Hashtable relations=new Hashtable();
    protected Vector abilityUsages=new Vector();
    protected Vector choices=new Vector();

    public String factionID(){return ID;}
    public String name(){return name;}
    public String choiceIntro(){return choiceIntro;}
    public int minimum(){return minimum;}
    public int middle(){return middle;}
    public int difference(){return difference;}
    public int maximum(){return maximum;}
    public int highest(){return highest;}
    public int lowest(){return lowest;}
    public String experienceFlag(){return experienceFlag;}
    public boolean showInScore(){return showInScore;}
    public boolean showInSpecialReported(){return showInSpecialReported;}
    public boolean showInEditor(){return showInEditor;}
    public boolean showInFactionsCommand(){return showInFactionsCommand;}
    public Enumeration ranges(){ return DVector.s_enum(ranges,false); }
    public Enumeration defaults(){return DVector.s_enum(defaults);}
    public Enumeration autoDefaults(){return DVector.s_enum(autoDefaults);}
    public double rateModifier(){return rateModifier;}
    public Enumeration changeEventKeys(){return  DVector.s_enum(changes,true);}
    public Enumeration factors(){return  DVector.s_enum(factors);}
    public Enumeration relationFactions(){return  DVector.s_enum(relations,true);}
    public Enumeration abilityUsages(){return  DVector.s_enum(abilityUsages);}
    public Enumeration choices(){return  DVector.s_enum(choices);}

    public void setFactionID(String newStr){ID=newStr;}
    public void setName(String newStr){name=newStr;}
    public void setChoiceIntro(String newStr){choiceIntro=newStr;}
    public void setExperienceFlag(String newStr){experienceFlag=newStr;}
    public void setShowInScore(boolean truefalse){showInScore=truefalse;}
    public void setShowInSpecialReported(boolean truefalse){showInSpecialReported=truefalse;}
    public void setShowInEditor(boolean truefalse){showInEditor=truefalse;}
    public void setShowInFactionsCommand(boolean truefalse){showInFactionsCommand=truefalse;}
    public void setChoices(Vector v){choices=(v==null)?new Vector():v;}
    public void setAutoDefaults(Vector v){autoDefaults=(v==null)?new Vector():v;}
    public void setDefaults(Vector v){defaults=(v==null)?new Vector():v;}
    public void setRateModifier(double d){rateModifier=d;}
    public Faction.FactionAbilityUsage getAbilityUsage(int x){
        return ((x>=0)&&(x<abilityUsages.size()))
                ?(Faction.FactionAbilityUsage)abilityUsages.elementAt(x)
                :null;
    }
    public boolean delFactor(Object[] o){ return factors.remove(o); }
    public Object[] getFactor(int x){ return ((x>=0)&&(x<factors.size()))?(Object[])factors.elementAt(x):null;}
    public Object[] addFactor(double gain, double loss, String mask){
        Object[] o=new Object[]{Double.valueOf(gain),Double.valueOf(loss),mask};
        factors.addElement(o);
        return o;
    }
    public boolean delRelation(String factionID) { return relations.remove(factionID)!=null;}
    public boolean addRelation(String factionID, double relation) {
        if(relations.containsKey(factionID))
            return false;
        relations.put(factionID,Double.valueOf(relation));
        return true;
    }
    public double getRelation(String factionID) {
        if(relations.containsKey(factionID))
            return ((Double)relations.get(factionID)).doubleValue();
        return 0.0;
    }

    public DefaultFaction(){super();}
    public void initializeFaction(String aname)
    {
        ID=aname;
        name=aname;
        minimum=0;
        middle=50;
        maximum=100;
        highest=100;
        lowest=0;
        difference=CMath.abs(maximum-minimum);
        experienceFlag="EXTREME";
        addRange("0;100;Sample Range;SAMPLE;");
        defaults.addElement("0");
    }

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

        ID = fID;
        CMProps alignProp = new CMProps(new ByteArrayInputStream(CMStrings.strToBytes(file.toString())));
        if(alignProp.isEmpty()) return;
        name=alignProp.getStr("NAME");
        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");
        showInFactionsCommand=alignProp.getBoolean("SHOWINFACTIONSCMD");
        showInSpecialReported=alignProp.getBoolean("SPECIALREPORTED");
        showInEditor=alignProp.getBoolean("EDITALONE");
        defaults =CMParms.parseSemicolons(alignProp.getStr("DEFAULT"),true);
        autoDefaults =CMParms.parseSemicolons(alignProp.getStr("AUTODEFAULTS"),true);
        choices =CMParms.parseSemicolons(alignProp.getStr("AUTOCHOICES"),true);
        ranges=new Hashtable();
        changes=new Hashtable();
        factors=new Vector();
        relations=new Hashtable();
        abilityUsages=new Vector();
        for(Enumeration e=alignProp.keys();e.hasMoreElements();)
        {
            if(debug) Log.sysOut("FACTIONS","Starting Key Loop");
            String key = (String) e.nextElement();
            if(debug) Log.sysOut("FACTIONS","  Key Found     :"+key);
            String words = (String) alignProp.get(key);
            if(debug) Log.sysOut("FACTIONS","  Words Found   :"+words);
            if(key.startsWith("RANGE"))
                addRange(words);
            if(key.startsWith("CHANGE"))
                addChangeEvent(words);
            if(key.startsWith("AFFBEHAV"))
            {
                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"))
            {
                Vector factor=CMParms.parseSemicolons(words,false);
                if(factor.size()>2)
                    factors.add(new Object[]{Double.valueOf(CMath.s_double((String)factor.elementAt(0))),
                                             Double.valueOf(CMath.s_double((String)factor.elementAt(1))),
                                             (String)factor.elementAt(2)});
            }
            if(key.startsWith("RELATION"))
            {
                Vector V=CMParms.parse(words);
                if(V.size()>=2)
                {
                    String who=(String)V.elementAt(0);
                    double factor;
                    String amt=((String)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);
        }
    }

    private void recalc() {
        minimum=Integer.MAX_VALUE;
        maximum=Integer.MIN_VALUE;
        for(Enumeration e=ranges();e.hasMoreElements();)
        {
            Faction.FactionRange FR=(Faction.FactionRange)e.nextElement();
            if(FR.high()>maximum) maximum=FR.high();
            if(FR.low()<minimum) minimum=FR.low();
        }
        if(minimum==Integer.MAX_VALUE) minimum=Integer.MIN_VALUE;
        if(maximum==Integer.MIN_VALUE) maximum=Integer.MAX_VALUE;
        if(maximum<minimum)
        {
            int oldMin=minimum;
            minimum=maximum;
            maximum=oldMin;
        }
        middle=minimum+(int)Math.round(CMath.div(maximum-minimum,2.0));
        difference=CMath.abs(maximum-minimum);
    }

    public String getTagValue(String tag)
    {
        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(showInFactionsCommand).toUpperCase();
        case TAG_SPECIALREPORTED: return Boolean.toString(showInSpecialReported).toUpperCase();
        case TAG_EDITALONE: return Boolean.toString(showInEditor).toUpperCase();
        case TAG_DEFAULT: return CMParms.toSemicolonList(defaults);
        case TAG_AUTODEFAULTS: return CMParms.toSemicolonList(autoDefaults);
        case TAG_CHOICEINTRO: return choiceIntro;
        case TAG_AUTOCHOICES: return CMParms.toSemicolonList(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(Enumeration e=ranges();e.hasMoreElements();)
            {
                Faction.FactionRange FR=(Faction.FactionRange)e.nextElement();
                if(x==numCall) return FR.toString();
                x++;
            }
            return "";
        }
        case TAG_CHANGE_:
        {
            if((numCall<0)||(numCall>=changes.size()))
                return ""+changes.size();
            int i=0;
            for(Enumeration e=changes.elements();e.hasMoreElements();)
            {
                FactionChangeEvent FC=(FactionChangeEvent)e.nextElement();
                if(i==numCall)
                    return FC.toString();
                i++;
            }
            return "";
        }
        case TAG_ABILITY_:
        {
            if((numCall<0)||(numCall>=abilityUsages.size()))
                return ""+abilityUsages.size();
            return ((FactionAbilityUsage)abilityUsages.elementAt(numCall)).toString();
        }
        case TAG_FACTOR_:
        {
            if((numCall<0)||(numCall>=factors.size()))
                return ""+factors.size();
            return CMParms.toSemicolonList((Object[])factors.elementAt(numCall));
        }
        case TAG_RELATION_:
        {
            if((numCall<0)||(numCall>=relations.size()))
                return ""+relations.size();
            int i=0;
            for(Enumeration e=relations.keys();e.hasMoreElements();)
            {
                String factionName=(String)e.nextElement();
                Double D=(Double)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(Enumeration e=affBehavs.keys();e.hasMoreElements();)
            {
                String ID=(String)e.nextElement();
                String[] data=(String[])affBehavs.get(ID);
                if(i==numCall)
                    return ID+";"+CMParms.toSafeSemicolonList(data);
                i++;
            }
            return "";
        }
        }
        return "";
    }

    public String getINIDef(String tag, String delimeter)
    {
        int tagRef=CMLib.factions().isFactionTag(tag);
        if(tagRef<0)
            return "";
        String rawTagName=TAG_NAMES[tagRef];
        if(TAG_NAMES[tagRef].endsWith("*"))
        {
            int number=CMath.s_int(getTagValue(rawTagName));
            StringBuffer str=new StringBuffer("");
            for(int i=0;i<number;i++)
            {
                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;
    }
    
    public FactionData makeFactionData(MOB mob)
    {
        FactionData data=new DefaultFactionData(lastAffectBehaviorChange);
        Vector V=new Vector();
        String ID=null;
        String[] stuff=null;
        if(mob.isMonster())
        for(Enumeration e=affectsBehavs();e.hasMoreElements();)
        {
            ID=(String)e.nextElement();
            stuff=getAffectBehav(ID);
            if(CMLib.masking().maskCheck(stuff[1],mob,true))
            {
                Behavior B=CMClass.getBehavior(ID);
                if(B!=null)
                {
                    B.setParms(stuff[0]);
                    V.addElement(B);
                }
                else
                {
                    Ability A=CMClass.getAbility(ID);
                    A.setMiscText(stuff[0]);
                    A.setAffectedOne(mob);
                    V.addElement(A);
                }
            }
        }
        data.addListenersNTickers(V,V);
        return data;
    }
    
    public Enumeration affectsBehavs(){return  DVector.s_enum(affBehavs,true);}

    public boolean delAffectBehav(String ID) {
        boolean b=affBehavs.remove(ID.toUpperCase().trim())!=null;
        if(b) lastAffectBehaviorChange[0]=System.currentTimeMillis();
        return b;
    }
    
    public boolean addAffectBehav(String ID, String parms, 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});
        lastAffectBehaviorChange[0]=System.currentTimeMillis();
        return true;
    }
    
    public String[] getAffectBehav(String ID) {
        if(affBehavs.containsKey(ID.toUpperCase().trim()))
            return CMParms.toStringArray(CMParms.makeVector((String[])affBehavs.get(ID.toUpperCase().trim())));
        return null;
    }
    
    public FactionChangeEvent getChangeEvent(String key) 
    {
        if(changes.containsKey(key))
            return (FactionChangeEvent)changes.get(key);
        return null;
    }

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


    public FactionChangeEvent findChangeEvent(Ability key)
    {
        if(key==null) return null;
        // Direct ability ID's
        if(changes.containsKey(key.ID()))
            return (FactionChangeEvent)changes.get(key.ID().toUpperCase());
        // By TYPE or FLAGS
        FactionChangeEvent C =null;
        for (Enumeration e=changes.elements();e.hasMoreElements();)
        {
            C= (FactionChangeEvent)e.nextElement();
            if((key.classificationCode()&Ability.ALL_ACODES)==C.IDclassFilter())
                return C;
            if((key.classificationCode()&Ability.ALL_DOMAINS)==C.IDdomainFilter())
                return C;
            if((C.IDflagFilter()>0)&&(CMath.bset(key.flags(),C.IDflagFilter())))
                return C;
        }
        return null;
    }

    public Faction.FactionRange fetchRange(String codeName)
    {
        return (FactionRange)ranges.get(codeName.toUpperCase().trim());
    }

    public FactionRange fetchRange(int faction)
    {
        for (Enumeration e=ranges.elements();e.hasMoreElements();)
        {
            FactionRange R = (FactionRange)e.nextElement();
            if ( (faction >= R.low()) && (faction <= R.high()))
                return R;
        }
        return null;
    }
    public String fetchRangeName(int faction)
    {
        for (Enumeration e=ranges.elements();e.hasMoreElements();)
        {
            FactionRange R = (FactionRange)e.nextElement();
            if ( (faction >= R.low()) && (faction <= R.high()))
                return R.name();
        }
        return "";
    }

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

    public int asPercentFromAvg(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));
    }

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

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

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

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

    public boolean hasUsage(Ability A)
    {
        FactionAbilityUsage usage=null;
        for(int i=0;i<abilityUsages.size();i++)
        {
            usage=(FactionAbilityUsage)abilityUsages.elementAt(i);
            if((usage.possibleAbilityID()&&usage.abilityFlags().equalsIgnoreCase(A.ID()))
            ||(((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()))))
                return true;
        }
        return false;
    }

    public boolean canUse(MOB mob, Ability A)
    {
        FactionAbilityUsage usage=null;
        for(int i=0;i<abilityUsages.size();i++)
        {
            usage=(FactionAbilityUsage)abilityUsages.elementAt(i);
            if((usage.possibleAbilityID()&&usage.abilityFlags().equalsIgnoreCase(A.ID()))
            ||(((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()))))
            {
                int faction=mob.fetchFaction(ID);
                if((faction < usage.low()) || (faction > usage.high()))
                    return false;
            }
        }
        return true;
    }

    public double findFactor(MOB mob, boolean gain)
    {
        Object[] factor=null;
        for(int i=0;i<factors.size();i++)
        {
            factor=(Object[])factors.elementAt(i);
            if(CMLib.masking().maskCheck(((String)factor[2]),mob,false))
            {
                 if(gain)
                     return ((Double)factor[0]).doubleValue();
                 return ((Double)factor[1]).doubleValue();
             }
        }
        return 1.0;
    }

    public void executeMsg(Environmental myHost, CMMsg msg)
    {
        if((msg.sourceMinor()==CMMsg.TYP_DEATH)    // A death occured
        &&(msg.source()==myHost)
        &&(msg.tool() instanceof MOB))
        {
            MOB killedM=msg.source();
            final String[] murders={"MURDER","MURDER2","MURDER3","MURDER4","MURDER5"};
            for(int m=0;m<murders.length;m++)
            {
                FactionChangeEvent eventC=getChangeEvent(murders[m]);
                if((eventC!=null)&&eventC.applies(killedM))
                {
                    MOB killingBlowM=(MOB)msg.tool();
                    CharClass combatCharClass=CMLib.combat().getCombatDominantClass(killingBlowM,killedM);
                    HashSet combatBeneficiaries=CMLib.combat().getCombatBeneficiaries(killingBlowM,killedM,combatCharClass);
                    if(combatBeneficiaries.contains(myHost))
                    {
                        MOB killerM=(MOB)myHost;
                        executeChange(killerM,killedM,eventC);
                    }
                    if(myHost==msg.source())
                        for(Iterator i=combatBeneficiaries.iterator();i.hasNext();)
                        {
                            MOB killerM=(MOB)i.next();
                            if(eventC.applies(killerM))
                                executeChange(killerM,killedM,eventC);
                        }
                }
            }
        }

        // Ability Watching
        if((msg.tool() instanceof Ability)
        &&(msg.othersMessage()!=null)
        &&(findChangeEvent((Ability)msg.tool())!=null))
        {
            FactionChangeEvent C=findChangeEvent((Ability)msg.tool());
            if((msg.target() instanceof MOB)&&(C.applies((MOB)msg.target())))
                executeChange(msg.source(),(MOB)msg.target(),C);
            else
            if (!(msg.target() instanceof MOB))
                executeChange(msg.source(),null,C);
        }
    }

    public boolean okMessage(Environmental myHost, CMMsg msg)
    {
        if((msg.sourceMinor()==CMMsg.TYP_EXPCHANGE)  // Experience is being altered
        &&(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))
        {
            MOB killer=msg.source();
            MOB vic=(MOB)msg.target();

            if(experienceFlag.equals("HIGHER"))
                msg.setValue( (int)Math.round((msg.value()/2.0) +( (msg.value()/2.0) * CMath.div(Math.abs(killer.fetchFaction(ID)-minimum),(maximum - minimum)))));
            else
            if(experienceFlag.equals("LOWER"))
                msg.setValue( (int)Math.round((msg.value()/2.0) +( (msg.value()/2.0) * 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()/2.0) +( (msg.value()/2.0) * CMath.div(Math.abs(vic.fetchFaction(ID) - killer.fetchFaction(ID)),(maximum - minimum)))));
                else
                if(experienceFlag.equals("FOLLOWHIGHER"))
                    msg.setValue( (int)Math.round((msg.value()/2.0) +( (msg.value()/2.0) * CMath.div(Math.abs(vic.fetchFaction(ID)-minimum),(maximum - minimum)))));
                else
                if(experienceFlag.equals("FOLLOWLOWER"))
                    msg.setValue( (int)Math.round((msg.value()/2.0) +( (msg.value()/2.0) * CMath.div(Math.abs(maximum-vic.fetchFaction(ID)),(maximum - minimum)))));
                if(msg.value()<=0)
                    msg.setValue(0);
            }
        }
        return true;
    }

    public void executeChange(MOB source, MOB target, FactionChangeEvent event)
    {
        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;

        double baseChangeAmount=100.0;
        if((source!=target)&&(!event.just100()))
        {
            int levelLimit=CMProps.getIntVar(CMProps.SYSTEMI_EXPRATE);
            int levelDiff=target.envStats().level()-source.envStats().level();

            if(levelDiff<(-levelLimit) )
                baseChangeAmount=0.0;
            else
            if(levelLimit>0)
            {
                double levelFactor=CMath.div(levelDiff,levelLimit);
                if(levelFactor> ((double)levelLimit))
                    levelFactor=((double)levelLimit);
                baseChangeAmount=baseChangeAmount+CMath.mul(levelFactor,100);
            }
        }

        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(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);
        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
                    for(Enumeration e=relations.keys();e.hasMoreElements();)
                    {
                        String relID=((String)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, ((Double)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);
    }

     public String usageFactorRangeDescription(Ability A)
     {
         StringBuffer rangeStr=new StringBuffer();
         FactionAbilityUsage usage=null;
         HashSet namesAdded=new HashSet();
         for(int i=0;i<abilityUsages.size();i++)
         {
             usage=(FactionAbilityUsage)abilityUsages.elementAt(i);
             if((usage.possibleAbilityID()&&usage.abilityFlags().equalsIgnoreCase(A.ID()))
             ||(((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()))))
             {
                for(Enumeration e=ranges();e.hasMoreElements();)
                {
                     FactionRange R=(FactionRange)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;
     public String ALL_CHANGE_EVENT_TYPES()
     {
         StringBuffer ALL_TYPES=new StringBuffer("");
         if(_ALL_TYPES!=null) return _ALL_TYPES;
         for(int i=0;i<Faction.FactionChangeEvent.MISC_TRIGGERS.length;i++)
             ALL_TYPES.append(Faction.FactionChangeEvent.MISC_TRIGGERS[i]+", ");
         for(int i=0;i<Ability.ACODE_DESCS.length;i++)
             ALL_TYPES.append(Ability.ACODE_DESCS[i]+", ");
         for(int i=0;i<Ability.DOMAIN_DESCS.length;i++)
             ALL_TYPES.append(Ability.DOMAIN_DESCS[i]+", ");
         for(int i=0;i<Ability.FLAG_DESCS.length;i++)
             ALL_TYPES.append(Ability.FLAG_DESCS[i]+", ");
         _ALL_TYPES=ALL_TYPES.toString()+" a valid Skill, Spell, Chant, etc. ID.";
         return _ALL_TYPES;
     }

     public Faction.FactionChangeEvent addChangeEvent(String key)
     {
         Faction.FactionChangeEvent event;
         if(key==null)
             event=new DefaultFaction.DefaultFactionChangeEvent();
         else
         if(key.indexOf(';')<0)
         {
             event=new DefaultFaction.DefaultFactionChangeEvent();
             if(!event.setEventID(key))
                 return null;
         }
         else
             event=new DefaultFaction.DefaultFactionChangeEvent(key);
         changes.put(event.eventID().toUpperCase().trim(),event);
         return event;
     }

     public boolean delChangeEvent(String eventKey)
     {
         return changes.remove(eventKey)!=null;
     }

     public static class DefaultFactionChangeEvent implements Faction.FactionChangeEvent
     {
        public String ID="";
        public String flagCache="";
        public int IDclassFilter=-1;
        public int IDflagFilter=-1;
        public int IDdomainFilter=-1;
        public int direction=0;
        public double factor=0.0;
        public String zapper="";
        public boolean outsiderTargetOK=false;
        public boolean selfTargetOK=false;
        public boolean just100=false;

        public String eventID(){return ID;}
        public String flagCache(){return flagCache;}
        public int IDclassFilter(){return IDclassFilter;}
        public int IDflagFilter(){return IDflagFilter;}
        public int IDdomainFilter(){return IDdomainFilter;}
        public int direction(){return direction;}
        public double factor(){return factor;}
        public String zapper(){return zapper;}
        public boolean outsiderTargetOK(){return outsiderTargetOK;}
        public boolean selfTargetOK(){return selfTargetOK;}
        public boolean just100(){return just100;}
        public void setDirection(int newVal){direction=newVal;}
        public void setFactor(double newVal){factor=newVal;}
        public void setZapper(String newVal){zapper=newVal;}

        public String toString()
        {
            return ID+";"+CHANGE_DIRECTION_DESCS[direction]+";"+((int)Math.round(factor*100.0))+"%;"+flagCache+";"+zapper;
        }

        public DefaultFactionChangeEvent(){}

        public DefaultFactionChangeEvent(String key)
        {
            Vector v = CMParms.parseSemicolons(key,false);
            setEventID((String)v.elementAt(0));
            setDirection((String)v.elementAt(1));
            String amt=((String)v.elementAt(2)).trim();
            if(amt.endsWith("%"))
                factor=CMath.s_pct(amt);
            else
                factor=1.0;

            if(v.size()>3)
                setFlags((String)v.elementAt(3));
            if(v.size()>4)
                zapper = (String)v.elementAt(4);
        }

        public boolean setEventID(String newID)
        {
            IDclassFilter=-1;
            IDflagFilter=-1;
            IDdomainFilter=-1;
            for(int i=0;i<MISC_TRIGGERS.length;i++)
                if(MISC_TRIGGERS[i].equalsIgnoreCase(newID))
                { ID=newID;    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=(int)CMath.pow(2,i);  ID=newID; return true;}
            if(CMClass.getAbility(newID)!=null)
            { ID=newID; return true;}
            return false;
        }
        public boolean setDirection(String d)
        {
            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;
        }
        public void setFlags(String newFlagCache)
        {
            flagCache=newFlagCache.toUpperCase().trim();
            Vector flags=CMParms.parse(flagCache);
            if(flags.contains("OUTSIDER")) outsiderTargetOK=true;
            if(flags.contains("SELFOK")) selfTargetOK=true;
            if(flags.contains("JUST100")) just100=true;
        }
        public boolean applies(MOB mob)
        {
            if(zapper==null) return true;
            return CMLib.masking().maskCheck(zapper,mob,false);
        }
    }


    public Faction.FactionRange addRange(String key){
        Faction.FactionRange FR=new DefaultFaction.DefaultFactionRange(this,key);
        ranges.put(FR.codeName().toUpperCase().trim(),FR);
        recalc();
        return FR;
    }
    public boolean delRange(FactionRange FR)
    {
        if(!ranges.containsKey(FR.codeName().toUpperCase().trim())) return false;
        ranges.remove(FR.codeName().toUpperCase().trim());
        recalc();
        return true;
    }
    public static class DefaultFactionRange implements Faction.FactionRange
    {
        public int low;
        public int high;
        public String Name="";
        public String CodeName="";
        public int AlignEquiv;
        public Faction myFaction=null;

        public int low(){return low;}
        public int high(){return high;}
        public String name(){return Name;}
        public String codeName(){return CodeName;}
        public int alignEquiv(){return AlignEquiv;}
        public Faction myFaction(){return myFaction;}

        public void setLow(int newVal){low=newVal;}
        public void setHigh(int newVal){high=newVal;}
        public void setName(String newVal){Name=newVal;}
        public void setAlignEquiv(int newVal){AlignEquiv=newVal;}

        public DefaultFactionRange(Faction F, String key)
        {
            myFaction=F;
            Vector v = CMParms.parseSemicolons(key,false);
            Name = (String) v.elementAt(2);
            low = CMath.s_int( (String) v.elementAt(0));
            high = CMath.s_int( (String) v.elementAt(1));
            if(v.size()>3)
                CodeName=(String)v.elementAt(3);
            if(v.size()>4)
                AlignEquiv = CMLib.factions().getAlignEquiv((String)v.elementAt(4));
            else
                AlignEquiv = Faction.ALIGN_INDIFF;
        }

        public String toString()
        {
            return low +";"+high+";"+Name+";"+CodeName+";"+ALIGN_NAMES[AlignEquiv];
        }
        public int random()
        {
            Random gen = new Random();
            return high - gen.nextInt(high-low);
        }
    }

    public Faction.FactionAbilityUsage addAbilityUsage(String key){
        Faction.FactionAbilityUsage usage=
            (key==null)?new DefaultFaction.DefaultFactionAbilityUsage()
                       : new DefaultFaction.DefaultFactionAbilityUsage(key);
        abilityUsages.addElement(usage);
        return usage;
    }
    public boolean delAbilityUsage(Faction.FactionAbilityUsage usage){return abilityUsages.remove(usage);}

    public static class DefaultFactionData implements FactionData
    {
        private boolean noListeners=false;
        private boolean noTickers=false;
        private static Enumeration empty=new Vector().elements();
        public DefaultFactionData(long[] factionLastUpdated) {
            this.factionLastUpdated=factionLastUpdated;
        }
        public long lastUpdated=System.currentTimeMillis();
        public long[] factionLastUpdated=new long[0];
        public Vector listeners=new Vector();
        public Vector tickers=listeners;
        public int value=0;
        
        public int value() { return value;}
        public void setValue(int newValue){ this.value=newValue;}
        public Enumeration listeners() { return noListeners?empty:DVector.s_enum(listeners); }
        public Enumeration tickers() { return noTickers?empty:DVector.s_enum(tickers); }
        public void addListenersNTickers(Vector listeners, Vector tickers) {
            this.listeners=listeners;
            this.tickers=tickers;
            noListeners=listeners.size()==0;
            noTickers=tickers.size()==0;
        }
        public boolean requiresUpdating() { return factionLastUpdated[0] > lastUpdated; }
    }
    
    
    public static class DefaultFactionAbilityUsage implements Faction.FactionAbilityUsage
    {
        public String ID="";
        public boolean possibleAbilityID=false;
        public int type=-1;
        public int domain=-1;
        public int flag=-1;
        public int low=0;
        public int high=0;
        public int notflag=-1;

        public String abilityFlags(){return ID;}
        public boolean possibleAbilityID(){return possibleAbilityID;}
        public int type(){return type;}
        public int domain(){return domain;}
        public int flag(){return flag;}
        public int low(){return low;}
        public int high(){return high;}
        public int notflag(){return notflag;}
        public void setLow(int newVal){low=newVal;}
        public void setHigh(int newVal){high=newVal;}

        public DefaultFactionAbilityUsage(){}
        public DefaultFactionAbilityUsage(String key)
        {
            Vector v = CMParms.parseSemicolons(key,false);
            setAbilityFlag((String)v.firstElement());
            low = CMath.s_int( (String) v.elementAt(1));
            high = CMath.s_int( (String) v.elementAt(2));
        }
        public String toString()
        {
            return ID+";"+low+";"+high;
        }

        public Vector setAbilityFlag(String str)
        {
            ID=str;
            Vector flags=CMParms.parse(ID);
            Vector unknowns=new Vector();
            possibleAbilityID=false;
            for(int f=0;f<flags.size();f++)
            {
                String strflag=(String)flags.elementAt(f);
                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:
                    int val=CMParms.indexOfIgnoreCase(Ability.FLAG_DESCS, strflag);
                    if(not)
                    {
                        if(notflag<0) notflag=0;
                        notflag=notflag|(int)CMath.pow(2,val);
                    }
                    else
                    {
                        if(flag<0) flag=0;
                        flag=flag|(int)CMath.pow(2,val);
                    }
                    break;
                default:
                    unknowns.addElement(strflag);
                    break;
                }
            }
            if((type<0)&&(domain<0)&&(flag<0))
                possibleAbilityID=true;
            return unknowns;
        }
    }
}