/
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/Languages/
com/planet_ink/coffee_mud/Abilities/Misc/
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/Behaviors/
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/Common/
com/planet_ink/coffee_mud/Common/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/CompTech/
com/planet_ink/coffee_mud/Items/MiscMagic/
com/planet_ink/coffee_mud/Items/Weapons/
com/planet_ink/coffee_mud/Items/interfaces/
com/planet_ink/coffee_mud/Libraries/
com/planet_ink/coffee_mud/Libraries/interfaces/
com/planet_ink/coffee_mud/Locales/
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/
com/planet_ink/coffee_mud/core/collections/
com/planet_ink/coffee_mud/core/interfaces/
com/planet_ink/coffee_mud/core/intermud/
com/planet_ink/coffee_mud/core/intermud/i3/
com/planet_ink/coffee_web/server/
com/planet_ink/siplet/applet/
lib/
resources/factions/
resources/fakedb/
resources/progs/autoplayer/
resources/quests/holidays/
web/
web/admin.templates/
web/admin/grinder/
web/admin/images/
web/clan.templates/
web/pub.templates/
web/pub/images/mxp/
web/pub/sounds/
web/pub/textedit/
package com.planet_ink.coffee_mud.Common;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.exceptions.CMException;
import com.planet_ink.coffee_mud.core.exceptions.CoffeeMudException;
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.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.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.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.*;

/*
   Copyright 2001-2016 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 DefaultCharStats implements CharStats
{
	@Override
	public String ID()
	{
		return "DefaultCharStats";
	}

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

	@Override
	public CMObject newInstance()
	{
		try
		{
			final DefaultCharStats newStats=getClass().newInstance();
			if(newStats.myRace==null)
				newStats.myRace=CMClass.getRace("StdRace");
			return newStats;
		}
		catch(final Exception e)
		{
			return new DefaultCharStats();
		}
	}

	@Override
	public void initializeClass()
	{
	}

	// competency characteristics
	protected short[]		stats				= new short[CharStats.CODES.instance().total()];
	protected CharClass[]	myClasses			= null;
	protected Integer[]		myLevels			= null;
	protected Race			myRace;
	protected String		raceName			= null;
	protected String		genderName			= null;
	protected String		displayClassName	= null;
	protected String		displayClassLevel	= null;
	protected short[]		bodyAlterations		= null;
	protected long			unwearableBitmap	= 0;
	protected int[]			breathables			= null;

	public DefaultCharStats()
	{
		reset();
		setMyRace(CMClass.getRace("StdRace"));
		setCurrentClass(CMClass.getCharClass("StdCharClass"));
	}

	@Override
	public void setAllBaseValues(int def)
	{
		if((def>Short.MAX_VALUE)||(def<Short.MIN_VALUE))
			Log.errOut("Value out of range",new CMException("Value out of range: "+def+" for all"));
		for(final int i : CharStats.CODES.BASECODES())
			stats[i]=(short)def;
	}
	@Override
	public void setAllValues(int def)
	{
		if((def>Short.MAX_VALUE)||(def<Short.MIN_VALUE))
			Log.errOut("Value out of range",new CMException("Value out of range: "+def+" for all"));
		for(final int i: CharStats.CODES.ALLCODES())
			stats[i]=(short)def;
		unwearableBitmap=0;
	}

	@Override
	public void reset()
	{
		setAllBaseValues(VALUE_ALLSTATS_DEFAULT);
		stats[STAT_GENDER]='M';
	}

	@Override
	public void copyInto(CharStats intoStats)
	{
		if(intoStats instanceof DefaultCharStats)
		{
			((DefaultCharStats)intoStats).breathables = breathables;
			if(myClasses==null)
				((DefaultCharStats)intoStats).myClasses=null;
			else
			if((((DefaultCharStats)intoStats).myClasses!=null)
			&&(((DefaultCharStats)intoStats).myClasses.length==myClasses.length))
			{
				for(int i=0;i<myClasses.length;i++)
					((DefaultCharStats)intoStats).myClasses[i]=myClasses[i];
			}
			else
				((DefaultCharStats)intoStats).myClasses=myClasses.clone();
			if(myLevels==null)
				((DefaultCharStats)intoStats).myLevels=null;
			else
			if((((DefaultCharStats)intoStats).myLevels!=null)
			&&(((DefaultCharStats)intoStats).myLevels.length==myLevels.length))
			{
				for(int i=0;i<myLevels.length;i++)
					((DefaultCharStats)intoStats).myLevels[i]=myLevels[i];
			}
			else
				((DefaultCharStats)intoStats).myLevels=myLevels.clone();
			if(myRace!=null)
				((DefaultCharStats)intoStats).myRace=myRace;
			((DefaultCharStats)intoStats).raceName=raceName;
			((DefaultCharStats)intoStats).genderName=genderName;
			((DefaultCharStats)intoStats).displayClassName=displayClassName;
			((DefaultCharStats)intoStats).displayClassLevel=displayClassLevel;
			if(bodyAlterations==null)
				((DefaultCharStats)intoStats).bodyAlterations=null;
			else
			if((((DefaultCharStats)intoStats).bodyAlterations!=null)
			&&(((DefaultCharStats)intoStats).bodyAlterations.length==bodyAlterations.length))
			{
				for(int i=0;i<bodyAlterations.length;i++)
					((DefaultCharStats)intoStats).bodyAlterations[i]=bodyAlterations[i];
			}
			else
				((DefaultCharStats)intoStats).bodyAlterations=bodyAlterations.clone();
			for(int i=0;i<stats.length;i++)
				((DefaultCharStats)intoStats).stats[i]=stats[i];
			((DefaultCharStats)intoStats).unwearableBitmap=unwearableBitmap;
		}
		else
		{
			intoStats.setMyClasses(getMyClassesStr());
			intoStats.setMyLevels(getMyLevelsStr());
			intoStats.setMyRace(getMyRace());
			intoStats.setRaceName(raceName);
			intoStats.setRaceName(raceName);
			intoStats.setGenderName(genderName);
			intoStats.setDisplayClassName(displayClassName);
			intoStats.setDisplayClassLevel(displayClassLevel);
			intoStats.setBodyPartsFromStringAfterRace(getBodyPartsAsString());
			intoStats.setWearableRestrictionsBitmap(unwearableBitmap);
			intoStats.setBreathables(breathables);
		}
	}

	@Override
	public void setMyClasses(String classes)
	{
		int x=classes.indexOf(';');
		final ArrayList<CharClass> classV=new ArrayList<CharClass>();
		CharClass C=null;
		while(x>=0)
		{
			final String theClass=classes.substring(0,x).trim();
			classes=classes.substring(x+1);
			if(theClass.length()>0)
			{
				C=CMClass.getCharClass(theClass);
				if(C==null)
					C=CMClass.getCharClass("StdCharClass");
				classV.add(C);
			}
			x=classes.indexOf(';');
		}
		if(classes.trim().length()>0)
		{
			C=CMClass.getCharClass(classes.trim());
			if(C==null)
				C=CMClass.getCharClass("StdCharClass");
			classV.add(C);
		}
		myClasses=classV.toArray(new CharClass[0]);
	}

	@Override
	public void setMyLevels(String levels)
	{
		if((levels.length()==0)&&(myClasses!=null)&&(myClasses.length>0))
			levels="0";
		int x=levels.indexOf(';');
		final ArrayList<Integer> levelV=new ArrayList<Integer>();
		while(x>=0)
		{
			final String theLevel=levels.substring(0,x).trim();
			levels=levels.substring(x+1);
			if(theLevel.length()>0)
				levelV.add(Integer.valueOf(CMath.s_int(theLevel)));
			x=levels.indexOf(';');
		}
		if(levels.trim().length()>0)
			levelV.add(Integer.valueOf(CMath.s_int(levels)));
		myLevels=levelV.toArray(new Integer[0]);
	}

	@Override
	public String getMyClassesStr()
	{
		if(myClasses==null)
			return "StdCharClass";
		String classStr="";
		for (final CharClass myClasse : myClasses)
			classStr+=";"+myClasse.ID();
		if(classStr.length()>0)
			classStr=classStr.substring(1);
		return classStr;
	}

	@Override
	public String getMyLevelsStr()
	{
		if(myLevels==null)
			return "";
		String levelStr="";
		for (final Integer myLevel : myLevels)
			levelStr+=";"+myLevel.intValue();
		if(levelStr.length()>0)
			levelStr=levelStr.substring(1);
		return levelStr;
	}

	@Override
	public long getWearableRestrictionsBitmap()
	{
		return unwearableBitmap | this.getMyRace().forbiddenWornBits();
	}

	@Override
	public void setWearableRestrictionsBitmap(long bitmap)
	{
		unwearableBitmap = bitmap;
	}

	@Override
	public int numClasses()
	{
		if(myClasses==null)
			return 0;
		return myClasses.length;
	}

	@Override
	public int combinedSubLevels()
	{
		if((myClasses==null)
		||(myLevels==null)
		||(myClasses.length<2))
			return 0;

		int combined=0;
		for(int i=0;i<myLevels.length-1;i++)
			combined+=myLevels[i].intValue();
		return combined;
	}
	public int combinedLevels()
	{
		if((myClasses==null)
		||(myLevels==null))
			return 0;

		int combined=0;
		for(int i=0;i<myLevels.length-1;i++)
			combined+=myLevels[i].intValue();
		return combined;
	}

	@Override
	public void setDisplayClassName(String newName)
	{
		displayClassName = newName;
	}

	@Override
	public String displayClassName()
	{
		if(displayClassName!=null)
			return displayClassName;
		return getCurrentClass().name(getCurrentClassLevel());
	}

	@Override
	public void setDisplayClassLevel(String newLevel)
	{
		displayClassLevel = newLevel;
	}

	@Override
	public String displayClassLevel(MOB mob, boolean shortForm)
	{
		if(displayClassLevel!=null)
		{
			if(shortForm)
				return displayClassName()+" "+displayClassLevel;
			return "level "+displayClassLevel+" "+displayClassName;
		}
		if(mob==null)
			return "";
		final int classLevel=getClassLevel(getCurrentClass());
		String levelStr=null;
		if(classLevel>=mob.phyStats().level())
			levelStr=""+mob.phyStats().level();
		else
			levelStr=classLevel+"/"+mob.phyStats().level();
		if(shortForm)
			return displayClassName()+" "+levelStr;
		return "level "+levelStr+" "+displayClassName();
	}

	@Override
	public String displayClassLevelOnly(MOB mob)
	{
		if(mob==null)
			return "";
		if(displayClassLevel!=null)
			return displayClassLevel;
		final int classLevel=getClassLevel(getCurrentClass());
		String levelStr=null;
		if(classLevel>=mob.phyStats().level())
			levelStr=""+mob.phyStats().level();
		else
			levelStr=classLevel+"/"+mob.phyStats().level();
		return levelStr;
	}

	@Override
	public String getNonBaseStatsAsString()
	{
		final StringBuffer str=new StringBuffer("");
		final CharStats.CODES C = CharStats.CODES.instance();
		for(final int x : C.all())
		{
			if((!C.isBase(x))&&(x!=CharStats.STAT_GENDER))
				str.append(stats[x]+";");
		}
		return str.toString();
	}

	@Override
	public void setNonBaseStatsFromString(String str)
	{
		final List<String> V=CMParms.parseSemicolons(str,false);
		final CharStats.CODES C = CharStats.CODES.instance();
		for(final int x : C.all())
		{
			if((!C.isBase(x))&&(x!=CharStats.STAT_GENDER)&&(V.size()>0))
			{
				final long val=CMath.s_long(V.remove(0));
				if((val>Short.MAX_VALUE)||(val<Short.MIN_VALUE))
					Log.errOut("Value out of range","Value out of range: "+val+" for "+x+" from "+str);
				stats[x]=(short)val;
			}
		}
	}

	@Override
	public void setRaceName(String newRaceName)
	{
		raceName=newRaceName;
	}

	@Override
	public String raceName()
	{
		if(raceName!=null)
			return raceName;
		if(myRace!=null)
			return myRace.name();
		return "MOB";
	}

	@Override
	public CharClass getMyClass(int i)
	{
		if((myClasses==null)
		||(i<0)
		||(i>=myClasses.length))
			return CMClass.getCharClass("StdCharClass");
		return myClasses[i];
	}

	@Override
	public int getClassLevel(String aClass)
	{
		if(myClasses==null)
			return -1;
		for(int i=0;i<myClasses.length;i++)
		{
			if((myClasses[i]!=null)
			&&(myClasses[i].ID().equals(aClass))
			&&(i<myLevels.length))
				return myLevels[i].intValue();
		}
		return -1;
	}

	@Override
	public int getClassLevel(CharClass aClass)
	{
		if((myClasses==null)||(aClass==null))
			return -1;
		for(int i=0;i<myClasses.length;i++)
		{
			if((myClasses[i]!=null)
			&&(myClasses[i].ID().equals(aClass.ID()))
			&&(i<myLevels.length))
				return myLevels[i].intValue();
		}
		return -1;
	}

	@Override
	public void setClassLevel(CharClass aClass, int level)
	{
		if(aClass==null)
			return;
		if(myClasses==null)
		{
			myClasses=new CharClass[1];
			myLevels=new Integer[1];
			myClasses[0]=aClass;
			myLevels[0]=Integer.valueOf(level);
		}
		else
		{
			if((level<0)&&(myClasses.length>1))
			{
				final CharClass[] oldClasses=myClasses;
				final Integer[] oldLevels=myLevels;
				final CharClass[] myNewClasses=new CharClass[oldClasses.length-1];
				final Integer[] myNewLevels=new Integer[oldClasses.length-1];
				for(int c=0;c<myNewClasses.length;c++)
				{
					myNewClasses[c]=oldClasses[c];
					if(c<oldLevels.length)
						myNewLevels[c]=oldLevels[c];
					else
						myNewLevels[c]=Integer.valueOf(0);
				}
				myClasses=myNewClasses;
				myLevels=myNewLevels;
			}
			else
			if(getClassLevel(aClass)<0)
				setCurrentClass(aClass);
			for(int i=0;i<numClasses();i++)
			{
				final CharClass C=getMyClass(i);
				if((C==aClass)&&(myLevels[i].intValue()!=level))
				{
					myLevels[i]=Integer.valueOf(level);
					break;
				}
			}
		}
	}

	@Override
	public boolean isLevelCapped(CharClass C)
	{
		if((C==null)||(C.getLevelCap()<0)||(C.getLevelCap()==Integer.MAX_VALUE))
			return false;
		return getClassLevel(C) >= C.getLevelCap();
	}

	@Override
	public void setCurrentClassLevel(int level)
	{
		final CharClass currentClass=getCurrentClass();
		if(currentClass!=null)
			setClassLevel(currentClass,level);
	}

	@Override
	public void setCurrentClass(CharClass aClass)
	{
		if(aClass==null)
			return;
		if(((myClasses==null)||(myLevels==null))
		||((numClasses()==1)&&(myClasses[0].ID().equals("StdCharClass"))))
		{
			myClasses=new CharClass[1];
			myLevels=new Integer[1];
			myClasses[0]=aClass;
			myLevels[0]=Integer.valueOf(0);
			return;
		}

		final int level=getClassLevel(aClass);
		if(level<0)
		{
			final CharClass[] oldClasses=myClasses;
			final Integer[] oldLevels=myLevels;
			final CharClass[] myNewClasses=new CharClass[oldClasses.length+1];
			final Integer[] myNewLevels=new Integer[oldClasses.length+1];
			for(int c=0;c<oldClasses.length;c++)
			{
				myNewClasses[c]=oldClasses[c];
				myNewLevels[c]=oldLevels[c];
			}
			myNewClasses[oldClasses.length]=aClass;
			myNewLevels[oldClasses.length]=Integer.valueOf(0);
			myClasses=myNewClasses;
			myLevels=myNewLevels;
		}
		else
		{
			if(myClasses[myClasses.length-1]==aClass)
				return;
			final Integer oldI=Integer.valueOf(level);
			boolean go=false;
			final CharClass[] myNewClasses=myClasses.clone();
			final Integer[] myNewLevels=myLevels.clone();
			for(int i=0;i<myNewClasses.length-1;i++)
			{
				final CharClass C=getMyClass(i);
				if((C==aClass)||(go))
				{
					go=true;
					myNewClasses[i]=myNewClasses[i+1];
					myNewLevels[i]=myNewLevels[i+1];
				}
			}
			myNewClasses[myNewClasses.length-1]=aClass;
			myNewLevels[myNewClasses.length-1]=oldI;
			myClasses=myNewClasses;
			myLevels=myNewLevels;
		}
	}
	@Override
	public CharClass getCurrentClass()
	{
		return myClasses[myClasses.length-1];
	}

	@Override
	public Collection<CharClass> getCharClasses()
	{
		return Arrays.asList(myClasses);
	}

	@Override
	public int getCurrentClassLevel()
	{
		if(myLevels==null)
			return -1;
		return myLevels[myLevels.length-1].intValue();
	}

	@Override
	public Race getMyRace()
	{
		return myRace;
	}

	@Override
	public void setMyRace(Race newVal)
	{
		if(newVal != null)
			myRace=newVal;
	}

	@Override
	public int[] getBreathables()
	{
		return (breathables!=null)?breathables:myRace.getBreathables();
	}

	@Override
	public void setBreathables(int[] newArray)
	{
		breathables=newArray;
	}

	@Override
	public int getBodyPart(int racialPartNumber)
	{
		int num=getMyRace().bodyMask()[racialPartNumber];
		if((num<0)||(bodyAlterations==null))
			return num;
		num+=bodyAlterations[racialPartNumber];
		if(num<0)
			return 0;
		return num;
	}

	@Override
	public String getBodyPartsAsString()
	{
		final StringBuffer str=new StringBuffer("");
		for(int i=0;i<getMyRace().bodyMask().length;i++)
			str.append(getBodyPart(i)+";");
		return str.toString();
	}

	@Override
	public void setBodyPartsFromStringAfterRace(String str)
	{
		final List<String> V=CMParms.parseSemicolons(str,true);
		bodyAlterations=null;
		for(int i=0;i<getMyRace().bodyMask().length;i++)
		{
			if(V.size()<=i)
				break;
			final int val=CMath.s_int(V.get(i));
			final int num=getMyRace().bodyMask()[i];
			if(num!=val)
				alterBodypart(i,val-num);
		}
	}

	@Override
	public int getBodypartAlteration(int racialPartNumber)
	{
		if(bodyAlterations==null)
			return 0;
		return bodyAlterations[racialPartNumber];
	}

	@Override
	public void alterBodypart(int racialPartNumber, int deviation)
	{
		if(bodyAlterations==null)
			bodyAlterations=new short[Race.BODY_PARTS];
		bodyAlterations[racialPartNumber]+=deviation;
	}

	@Override
	public int ageCategory()
	{
		final int age=getStat(STAT_AGE);
		int cat=Race.AGE_INFANT;
		final int[] chart=getMyRace().getAgingChart();
		if(age<chart[1])
			return cat;
		while((cat<=Race.AGE_ANCIENT)&&(age>=chart[cat]))
			cat++;
		return cat-1;
	}

	@Override
	public String ageName()
	{
		final int cat=ageCategory();
		if(cat<Race.AGE_ANCIENT)
			return Race.AGE_DESCS[cat];
		int age=getStat(STAT_AGE);
		final int[] chart=getMyRace().getAgingChart();
		final int diff=chart[Race.AGE_ANCIENT]-chart[Race.AGE_VENERABLE];
		age=age-chart[Race.AGE_ANCIENT];
		final int num=(diff>0)?(int)Math.abs(Math.floor(CMath.div(age,diff))):0;
		if(num<=0)
			return Race.AGE_DESCS[cat];
		return Race.AGE_DESCS[cat]+" "+CMath.convertToRoman(num);
	}

	@Override
	public int getSave(int which)
	{
		switch(which)
		{
		case STAT_SAVE_PARALYSIS:
			return getStat(STAT_SAVE_PARALYSIS)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_STRENGTH),2.0));
		case STAT_SAVE_FIRE:
			return getStat(STAT_SAVE_FIRE)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_DEXTERITY),2.0));
		case STAT_SAVE_COLD:
			return getStat(STAT_SAVE_COLD)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_DEXTERITY),2.0));
		case STAT_SAVE_WATER:
			return getStat(STAT_SAVE_WATER)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_DEXTERITY),2.0));
		case STAT_SAVE_GAS:
			return getStat(STAT_SAVE_GAS)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_STRENGTH),2.0));
		case STAT_SAVE_MIND:
			return getStat(STAT_SAVE_MIND)+(int)Math.round(CMath.div(getStat(STAT_WISDOM)+getStat(STAT_INTELLIGENCE)+getStat(STAT_CHARISMA),3.0));
		case STAT_SAVE_GENERAL:
			return getStat(STAT_SAVE_GENERAL)+getStat(STAT_CONSTITUTION);
		case STAT_SAVE_JUSTICE:
			return getStat(STAT_SAVE_JUSTICE)+getStat(STAT_CHARISMA);
		case STAT_SAVE_ACID:
			return getStat(STAT_SAVE_ACID)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_DEXTERITY),2.0));
		case STAT_SAVE_ELECTRIC:
			return getStat(STAT_SAVE_ELECTRIC)+(int)Math.round(CMath.div(getStat(STAT_CONSTITUTION)+getStat(STAT_DEXTERITY),2.0));
		case STAT_SAVE_POISON:
			return getStat(STAT_SAVE_POISON)+getStat(STAT_CONSTITUTION);
		case STAT_SAVE_UNDEAD:
			return getStat(STAT_SAVE_UNDEAD)+getStat(STAT_WISDOM)+getStat(STAT_FAITH);
		case STAT_SAVE_DISEASE:
			return getStat(STAT_SAVE_DISEASE)+getStat(STAT_CONSTITUTION);
		case STAT_SAVE_MAGIC:
			return getStat(STAT_SAVE_MAGIC)+getStat(STAT_INTELLIGENCE);
		case STAT_SAVE_TRAPS:
			return getStat(STAT_SAVE_TRAPS)+getStat(STAT_DEXTERITY);
		case STAT_SAVE_OVERLOOKING:
			return getStat(STAT_SAVE_OVERLOOKING);
		case STAT_SAVE_DETECTION:
			return getStat(STAT_SAVE_DETECTION);
		case STAT_FAITH:
			return getStat(STAT_FAITH);
		}
		return getStat(which);
	}

	// create a new one of these
	@Override
	public CMObject copyOf()
	{
		final DefaultCharStats newOne=new DefaultCharStats();
		if(myClasses!=null)
			newOne.myClasses=myClasses.clone();
		if(myRace!=null)
			newOne.myRace=myRace;
		if(myLevels!=null)
			newOne.myLevels=myLevels.clone();
		if(bodyAlterations!=null)
			newOne.bodyAlterations=bodyAlterations.clone();
		newOne.stats=stats.clone();
		return newOne;
	}

	@Override
	public void setGenderName(String gname)
	{
		genderName=gname;
	}

	@Override
	public String genderName()
	{
		if(genderName!=null)
			return genderName;
		switch(getStat(STAT_GENDER))
		{
		case 'M':
			return CMLib.lang().L("male");
		case 'F':
			return CMLib.lang().L("female");
		default:
			return CMLib.lang().L("neuter");
		}
	}

	@Override
	public String himher()
	{
		final char c=((genderName!=null)&&(genderName.length()>0))
					? Character.toUpperCase(genderName.charAt(0)) 
					: (char)getStat(STAT_GENDER);
		switch(c)
		{
		case 'M':
			return CMLib.lang().L("him");
		case 'F':
			return CMLib.lang().L("her");
		default:
			return CMLib.lang().L("it");
		}
	}

	@Override
	public String hisher()
	{
		final char c=((genderName!=null)&&(genderName.length()>0))
					? Character.toUpperCase(genderName.charAt(0)) 
					: (char)getStat(STAT_GENDER);
		switch(c)
		{
		case 'M':
			return CMLib.lang().L("his");
		case 'F':
			return CMLib.lang().L("her");
		default:
			return CMLib.lang().L("its");
		}
	}

	@Override
	public String heshe()
	{
		final char c=((genderName!=null)&&(genderName.length()>0))
					? Character.toUpperCase(genderName.charAt(0)) 
					: (char)getStat(STAT_GENDER);
		switch(c)
		{
		case 'M':
			return CMLib.lang().L("he");
		case 'F':
			return CMLib.lang().L("she");
		default:
			return CMLib.lang().L("it");
		}
	}

	@Override
	public String sirmadam()
	{
		final char c=((genderName!=null)&&(genderName.length()>0))
					? Character.toUpperCase(genderName.charAt(0)) 
					: (char)getStat(STAT_GENDER);
		switch(c)
		{
		case 'M':
			return CMLib.lang().L("sir");
		case 'F':
			return CMLib.lang().L("madam");
		default:
			return CMLib.lang().L("sir");
		}
	}

	@Override
	public String SirMadam()
	{
		final char c=((genderName!=null)&&(genderName.length()>0))
					? Character.toUpperCase(genderName.charAt(0)) 
					: (char)getStat(STAT_GENDER);
		switch(c)
		{
		case 'M':
			return CMLib.lang().L("Sir");
		case 'F':
			return CMLib.lang().L("Madam");
		default:
			return CMLib.lang().L("Sir");
		}
	}

	@Override
	public String HeShe()
	{
		final char c=((genderName!=null)&&(genderName.length()>0))
					? Character.toUpperCase(genderName.charAt(0)) 
					: (char)getStat(STAT_GENDER);
		switch(c)
		{
		case 'M':
			return CMLib.lang().L("He");
		case 'F':
			return CMLib.lang().L("She");
		default:
			return CMLib.lang().L("It");
		}
	}

	@Override
	public int getStat(int abilityCode)
	{
		if(abilityCode<stats.length)
			return stats[abilityCode];
		return 0;
	}

	@Override
	public void setPermanentStat(int abilityCode, int value)
	{
		setStat(abilityCode,value);
		if(CharStats.CODES.isBASE(abilityCode))
			setStat(CharStats.CODES.toMAXBASE(abilityCode),value-CMProps.getIntVar(CMProps.Int.BASEMAXSTAT));
	}

	@Override
	public int getMaxStat(int abilityCode)
	{
		final int baseMax = CMProps.getIntVar(CMProps.Int.BASEMAXSTAT);
		return baseMax + getStat(CharStats.CODES.toMAXBASE(abilityCode));
	}

	@Override
	public int getRacialStat(MOB mob, int statNum)
	{
		final CharStats copyStats=(CharStats)copyOf();
		getMyRace().affectCharStats(mob,copyStats);
		for(int c=0;c<numClasses();c++)
			getMyClass(c).affectCharStats(mob,copyStats);
		return copyStats.getStat(statNum);
	}

	@Override
	public void setRacialStat(final int abilityCode, final int racialMax)
	{
		if((!CharStats.CODES.isBASE(abilityCode))||(getStat(abilityCode)==VALUE_ALLSTATS_DEFAULT))
			setPermanentStat(abilityCode,racialMax);
		else
		{
			final int baseMax=CMProps.getIntVar(CMProps.Int.BASEMAXSTAT);
			int currMax=getStat(CharStats.CODES.toMAXBASE(abilityCode))+baseMax;
			if(currMax<=0)
				currMax=1;
			int curStat=getStat(abilityCode);
			if(curStat > currMax*7)
			{
				final String errorMsg="Detected mob with "+curStat+"/"+currMax+" "+CharStats.CODES.ABBR(abilityCode);
				@SuppressWarnings({ "unchecked", "rawtypes" })
				Set<String> errs=(Set)Resources.getResource("SYSTEM_DEFCHARSTATS_ERRORS");
				if(errs==null)
				{
					errs=new TreeSet<String>();
					Resources.submitResource("SYSTEM_DEFCHARSTATS_ERRORS", errs);
				}
				if(!errs.contains(errorMsg))
				{
					errs.add(errorMsg);
					final StringBuilder str=new StringBuilder(errorMsg);
					//ByteArrayOutputStream stream=new ByteArrayOutputStream();
					//new Exception().printStackTrace(new PrintStream(stream));
					//str.append("\n\r"+new String(stream.toByteArray()));
					Log.errOut("DefCharStats",str.toString());
				}
				curStat=currMax*7;
			}
			final int pctOfMax=Math.round(((float)curStat/(float)currMax)*racialMax);
			final int stdMaxAdj=Math.round((((float)(currMax-VALUE_ALLSTATS_DEFAULT))/(float)currMax)*racialMax);
			final int racialStat=pctOfMax+stdMaxAdj;
			setStat(abilityCode,((racialStat<1)&&(racialMax>0))?1:racialStat);
			setStat(CharStats.CODES.toMAXBASE(abilityCode),racialMax-baseMax);
		}
	}

	@Override
	public void setStat(int abilityCode, int value)
	{
		if((value>Short.MAX_VALUE)||(value<Short.MIN_VALUE))  //TODO: DELME
			throw new IllegalArgumentException("Value out of range: "+value+" for "+abilityCode);
		else
		if(abilityCode<stats.length)
			stats[abilityCode]=(short)value;
	}

	@Override
	public int getCode(String abilityName)
	{
		final String[] DESCS = CODES.DESCS();
		for(final int i : CharStats.CODES.ALLCODES())
		{
			if(DESCS[i].startsWith(abilityName))
				return i;
		}
		return -1;
	}

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

	@Override
	public int getSaveStatIndex()
	{
		return getStatCodes().length;
	}

	@Override
	public String getStat(String abilityName)
	{
		final int dex=CMParms.indexOfIgnoreCase(getStatCodes(),abilityName);
		if(dex>=0)
			return Integer.toString(getStat(dex));

		final String[] DESCS=CODES.DESCS();
		for(final int i : CharStats.CODES.ALLCODES())
		{
			if(DESCS[i].startsWith(abilityName))
				return Integer.toString(getStat(i));
		}
		return null;
	}

	@Override
	public String[] getStatCodes()
	{
		return CharStats.CODES.NAMES();
	}

	@Override
	public boolean isStat(String code)
	{
		return CMParms.containsIgnoreCase(getStatCodes(), code);
	}

	@Override
	public void setStat(String code, String val)
	{
		final int dex=CMParms.indexOfIgnoreCase(getStatCodes(),code);
		if(dex>=0)
			setStat(dex,CMath.s_parseIntExpression(val));
		else
		for(final int i : CharStats.CODES.ALLCODES())
		{
			if(CODES.DESC(i).startsWith(code))
			{
				setStat(dex,CMath.s_parseIntExpression(val));
				return;
			}
		}
	}
}