/
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.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.AccountStats.Agent;
import com.planet_ink.coffee_mud.Common.interfaces.TimeClock.TimePeriod;
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.Libraries.interfaces.AbilityMapper.AbilityMapping;
import com.planet_ink.coffee_mud.Libraries.interfaces.AchievementLibrary.Achievement;
import com.planet_ink.coffee_mud.Libraries.interfaces.AchievementLibrary.Tracker;
import com.planet_ink.coffee_mud.Libraries.interfaces.XMLLibrary.XMLTag;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.StdMOB;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.nio.ByteBuffer;
import java.util.*;

/*
   Copyright 2010-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 DefaultPlayerAccount implements PlayerAccount
{
	@Override
	public String ID()
	{
		return "DefaultPlayerAccount";
	}

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

	protected SHashSet<String>	friends				= new SHashSet<String>();
	protected SHashSet<String>	ignored				= new SHashSet<String>();
	protected SVector<String>	players				= new SVector<String>();
	protected String			accountName 		= "";
	protected String			lastIP				= "";
	protected long				lastDateTime		= System.currentTimeMillis();
	protected long				lastUpdated			= 0;
	protected String			email				= "";
	protected String			password			= "";
	protected String			notes				= "";
	protected long 				accountExpiration	= 0;
	protected int				bonusCommonSk		= 0;
	protected int				bonusCraftSk		= 0;
	protected int				bonusNonCraftSk		= 0;
	protected int				bonusLanguages		= 0;
	protected int				bonusCharStatPt		= 0;
	protected int				bonusCharLimit		= 0;
	protected int				bonusCharOnlineLimit= 0;
	protected String[]			xtraValues			= null;
	protected Set<AccountFlag>	acctFlags			= new SHashSet<AccountFlag>();
	protected volatile MOB 		fakePlayerM			= null;
	protected long[]			prideExpireTime		= new long[TimeClock.TimePeriod.values().length];
	protected int[][]			prideStats			= new int[TimeClock.TimePeriod.values().length][AccountStats.PrideStat.values().length];

	protected SVector<PlayerLibrary.ThinPlayer> thinPlayers 	= new SVector<PlayerLibrary.ThinPlayer>();
	protected Map<String,Tracker>				achievementers	= new STreeMap<String,Tracker>();
	protected CMUniqNameSortSVec<Tattoo>		tattoos			= new CMUniqNameSortSVec<Tattoo>(1);

	public DefaultPlayerAccount()
	{
		super();
		xtraValues=CMProps.getExtraStatCodesHolder(this);
	}

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

	@Override public void initializeClass(){}

	@Override
	public CMObject copyOf()
	{
		try
		{
			final DefaultPlayerAccount O=(DefaultPlayerAccount)this.clone();
			O.friends=friends.copyOf();
			O.ignored=ignored.copyOf();
			O.xtraValues=(xtraValues==null)?null:(String[])xtraValues.clone();
			return O;
		}
		catch(final CloneNotSupportedException e)
		{
			return new DefaultPlayerStats();
		}
	}

	@Override
	public String getLastIP()
	{
		return lastIP;
	}

	@Override
	public void setLastIP(String ip)
	{
		lastIP=ip;
	}

	@Override
	public String getEmail()
	{
		if(email==null)
			return "";
		return email;
	}

	@Override
	public void setEmail(String newAdd)
	{
		email=newAdd;
	}

	@Override
	public long getLastUpdated()
	{
		return lastUpdated;
	}

	@Override
	public void setLastUpdated(long time)
	{
		lastUpdated=time;
	}

	@Override
	public long getLastDateTime()
	{
		return lastDateTime;
	}

	@Override
	public void setLastDateTime(long C)
	{
		lastDateTime=C;
	}

	@Override
	public String getPasswordStr()
	{
		return password;
	}

	@Override
	public void setPassword(String newPassword)
	{
		if(CMProps.getBoolVar(CMProps.Bool.HASHPASSWORDS)
		&&(!CMLib.encoder().isARandomHashString(newPassword)))
			password=CMLib.encoder().makeRandomHashString(newPassword);
		else
			password=newPassword;
	}

	@Override
	public boolean matchesPassword(String checkPass)
	{
		return CMLib.encoder().passwordCheck(checkPass, password);
	}

	@Override
	public String getNotes()
	{
		return notes;
	}

	@Override
	public void setNotes(String newnotes)
	{
		notes=newnotes;
	}

	protected SHashSet<String> getHashFrom(String str)
	{
		final SHashSet<String> h=new SHashSet<String>();
		int x=str.indexOf(';');
		while(x>=0)
		{
			final String fi=str.substring(0,x).trim();
			if(fi.length()>0)
				h.add(fi);
			str=str.substring(x+1);
			x=str.indexOf(';');
		}
		if(str.trim().length()>0)
			h.add(str.trim());
		return h;
	}

	@Override
	public Set<String> getFriends()
	{
		return friends;
	}

	@Override
	public Set<String> getIgnored()
	{
		return ignored;
	}

	@Override
	public void bumpPrideStat(PrideStat stat, int amt)
	{
		final long now=System.currentTimeMillis();
		if(stat!=null)
		for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values())
		{
			if(period==TimeClock.TimePeriod.ALLTIME)
				prideStats[period.ordinal()][stat.ordinal()]+=amt;
			else
			{
				if(now>prideExpireTime[period.ordinal()])
				{
					for(final AccountStats.PrideStat stat2 : AccountStats.PrideStat.values())
						prideStats[period.ordinal()][stat2.ordinal()]=0;
					prideExpireTime[period.ordinal()]=period.nextPeriod();
				}
				prideStats[period.ordinal()][stat.ordinal()]+=amt;
			}
		}
	}

	@Override
	public int getPrideStat(TimePeriod period, PrideStat stat)
	{
		if((period==null)||(stat==null))
			return 0;
		return prideStats[period.ordinal()][stat.ordinal()];
	}

	@Override
	public MOB getAccountMob()
	{
		if(fakePlayerM!=null)
			return fakePlayerM;
		synchronized(this)
		{
			if(fakePlayerM!=null)
				return fakePlayerM;
			fakePlayerM=CMClass.getMOB("StdMOB");
			fakePlayerM.setName(getAccountName());
			fakePlayerM.setPlayerStats((PlayerStats)CMClass.getCommon("DefaultPlayerStats"));
			fakePlayerM.playerStats().setAccount(this);
			fakePlayerM.playerStats().setEmail(getEmail());
			fakePlayerM.basePhyStats().setLevel(1);
			fakePlayerM.recoverPhyStats();
			return fakePlayerM;
		}
	}

	protected String getPrivateList(Set<String> h)
	{
		if((h==null)||(h.size()==0))
			return "";
		final StringBuffer list=new StringBuffer("");
		for (final String string : h)
			list.append((string)+";");
		return list.toString();
	}

	@Override
	public String getXML()
	{
		final StringBuffer rest=new StringBuffer("");
		final String[] codes=getStatCodes();
		final XMLLibrary libXML = CMLib.xml();
		for (final String code2 : codes)
		{
			final String code=code2.toUpperCase();
			final String value = getStat(code);
			if(value.length()==0)
				rest.append("<"+code+" />");
			else
				rest.append("<"+code+">"+libXML.parseOutAngleBrackets(value)+"</"+code+">");
		}
		rest.append("<NEXTPRIDEPERIODS>").append(CMParms.toTightListString(prideExpireTime)).append("</NEXTPRIDEPERIODS>");
		rest.append("<PRIDESTATS>");
		for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values())
			rest.append(CMParms.toTightListString(prideStats[period.ordinal()])).append(";");
		rest.append("</PRIDESTATS>");
		rest.append("<ACHIEVEMENTS");
		for(Iterator<Tracker> i=achievementers.values().iterator();i.hasNext();)
		{
			final Tracker T = i.next();
			if(T.getAchievement().isSavableTracker() && (T.getCount(null) != 0))
				rest.append(" ").append(T.getAchievement().getTattoo()).append("=").append(T.getCount(null));
			// getCount(null) should be ok, because it's only the un-savable trackers that need the mob obj
		}
		rest.append(" />");
		rest.append("<TATTOOS>").append(CMParms.toListString(tattoos)).append("</TATTOOS>");
		rest.append("<ACCSTATS>")
			.append(bonusCommonSk).append(';')
			.append(bonusCraftSk).append(';')
			.append(bonusNonCraftSk).append(';')
			.append(bonusLanguages).append(';')
			.append(bonusCharStatPt).append(';')
			.append(bonusCharLimit).append(';')
			.append(bonusCharOnlineLimit).append(';')
			.append("</ACCSTATS>");
		return rest.toString();
	}

	@Override
	public void setXML(String str)
	{
		final XMLLibrary xmlLib = CMLib.xml();
		final List<XMLLibrary.XMLTag> xml = xmlLib.parseAllXML(str);
		final String[] codes=getStatCodes();
		for (final String code : codes)
		{
			String val=xmlLib.getValFromPieces(xml,code.toUpperCase());
			if(val==null)
				val="";
			setStat(code.toUpperCase(),xmlLib.restoreAngleBrackets(val));
		}
		final String[] nextPeriods=xmlLib.getValFromPieces(xml, "NEXTPRIDEPERIODS").split(",");
		final String[] prideStats=xmlLib.getValFromPieces(xml, "PRIDESTATS").split(";");
		final Pair<Long,int[]>[] finalPrideStats = CMLib.players().parsePrideStats(nextPeriods, prideStats);
		for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values())
		{
			if(period.ordinal()<finalPrideStats.length)
			{
				this.prideExpireTime[period.ordinal()]=finalPrideStats[period.ordinal()].first.longValue();
				this.prideStats[period.ordinal()]=finalPrideStats[period.ordinal()].second;
			}
		}
		final XMLTag achievePiece = xmlLib.getPieceFromPieces(xml, "ACHIEVEMENTS");
		achievementers.clear();
		for(Enumeration<Achievement> a=CMLib.achievements().achievements(Agent.ACCOUNT);a.hasMoreElements();)
		{
			final Achievement A=a.nextElement();
			if((achievePiece != null) && achievePiece.parms().containsKey(A.getTattoo()))
				achievementers.put(A.getTattoo(), A.getTracker(CMath.s_int(achievePiece.parms().get(A.getTattoo()).trim())));
			else
				achievementers.put(A.getTattoo(), A.getTracker(0));
		}
		final String[] allTattoos=xmlLib.getValFromPieces(xml, "TATTOOS").split(",");
		this.tattoos.clear();
		for(String tattoo : allTattoos)
			this.addTattoo(tattoo);
		
		final String[] allAccStats=xmlLib.getValFromPieces(xml, "ACCSTATS").split(";");
		if(allAccStats.length>=7)
		{
			bonusCommonSk=CMath.s_int(allAccStats[0]);
			bonusCraftSk=CMath.s_int(allAccStats[1]);
			bonusNonCraftSk=CMath.s_int(allAccStats[2]);
			bonusLanguages=CMath.s_int(allAccStats[3]);
			bonusCharStatPt=CMath.s_int(allAccStats[4]);
			bonusCharLimit=CMath.s_int(allAccStats[5]);
			bonusCharOnlineLimit=CMath.s_int(allAccStats[6]);
		}
	}

	// Acct Expire Code
	@Override
	public long getAccountExpiration()
	{
		return accountExpiration;
	}

	@Override
	public void setAccountExpiration(long newVal)
	{
		accountExpiration=newVal;
	}

	@Override
	public String getAccountName()
	{
		return accountName;
	}

	@Override
	public void setAccountName(String name)
	{
		accountName = name;
	}

	@Override
	public void addNewPlayer(MOB mob)
	{
		if(players.contains(mob.Name()))
			return;
		if(mob==fakePlayerM)
			return;
		try
		{
			for(final String name : players)
				if(name.equalsIgnoreCase(mob.Name()))
					return;
		}
		catch(final Exception e) {}
		players.add(mob.Name());
		thinPlayers.clear();
	}

	@Override
	public String findPlayer(String name)
	{
		if(name==null)
			return null;
		for(final String pName : players)
			if(pName.equalsIgnoreCase(name))
				return pName;
		return null;
	}

	@Override
	public void delPlayer(String name)
	{
		players.remove(name);
		try
		{
			for(final String name1 : players)
				if(name1.equalsIgnoreCase(name))
					players.remove(name1);
		}
		catch(final Exception e) {}
		thinPlayers.clear();
	}

	@Override
	public void delPlayer(MOB mob)
	{
		if(mob==fakePlayerM)
			return;
		players.remove(mob.Name());
		try
		{
			for(final String name : players)
				if(name.equalsIgnoreCase(mob.Name()))
					players.remove(name);
		}
		catch(final Exception e) {}
		thinPlayers.clear();
	}

	@Override
	public Enumeration<MOB> getLoadPlayers()
	{
		final Vector<MOB> mobs = new Vector<MOB>(players.size());
		for(final Enumeration<String> e=getPlayers();e.hasMoreElements();)
		{
			final MOB M=CMLib.players().getLoadPlayer(e.nextElement());
			if(M!=null)
				mobs.addElement(M);
		}
		return mobs.elements();
	}

	@Override
	public Enumeration<PlayerLibrary.ThinPlayer> getThinPlayers()
	{
		synchronized(thinPlayers)
		{
			if(thinPlayers.size() != players.size())
			{
				for(Enumeration<String> e=getPlayers();e.hasMoreElements();)
				{
					final String name = e.nextElement();
					PlayerLibrary.ThinPlayer tP = CMLib.database().getThinUser(name);
					if (tP == null)
					{
						tP = new PlayerLibrary.ThinPlayer()
						{
							@Override
							public String name()
							{
								return name;
							}

							@Override
							public String charClass()
							{
								return "";
							}

							@Override
							public String race()
							{
								return "";
							}

							@Override
							public int level()
							{
								return 0;
							}

							@Override
							public int age()
							{
								return 0;
							}

							@Override
							public long last()
							{
								return 0;
							}

							@Override
							public String email()
							{
								return null;
							}

							@Override
							public String ip()
							{
								return "";
							}

							@Override
							public int exp()
							{
								return 0;
							}

							@Override
							public int expLvl()
							{
								return 0;
							}
						};
					}
					thinPlayers.add(tP);
				}
			}
		}
		return thinPlayers.elements();
	}

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

	@Override
	public void setPlayerNames(Vector<String> names)
	{
		if(names != null)
		{
			players = new SVector<String>(names);
			for(final String name : players)
			{
				final MOB M=CMLib.players().getPlayer(name);
				if((M!=null)&&(M.playerStats()!=null)&&(M.playerStats().getAccount()==null))
					M.playerStats().setAccount(this);
			}
		}
	}

	@Override
	public int numPlayers()
	{
		return players.size();
	}

	@Override
	public boolean isSet(AccountFlag flag)
	{
		return acctFlags.contains(flag);
	}

	@Override
	public void setFlag(AccountFlag flag, boolean setOrUnset)
	{
		if(setOrUnset)
			acctFlags.add(flag);
		else
			acctFlags.remove(flag);
	}

	@Override
	public void killAchievementTracker(final Achievement A, final MOB mob)
	{
		if(achievementers.containsKey(A.getTattoo()))
		{
			achievementers.remove(A.getTattoo());
			if(mob != null)
			{
				mob.delTattoo(A.getTattoo());
			}
		}
	}
	
	@Override
	public Tracker getAchievementTracker(final Achievement A, final MOB mob)
	{
		final Tracker T;
		if(achievementers.containsKey(A.getTattoo()))
		{
			T=achievementers.get(A.getTattoo());
		}
		else
		{
			T=A.getTracker(0);
			achievementers.put(A.getTattoo(), T);
		}
		return T;
	}

	@Override
	public void rebuildAchievementTracker(final MOB mob, String achievementTattoo)
	{
		Achievement A=CMLib.achievements().getAchievement(achievementTattoo);
		if(A!=null)
		{
			if(achievementers.containsKey(A.getTattoo()))
				achievementers.put(A.getTattoo(), A.getTracker(achievementers.get(A.getTattoo()).getCount(mob)));
			else
				achievementers.put(A.getTattoo(), A.getTracker(0));
		}
		else
			achievementers.remove(achievementTattoo);
	}
	
	/** Manipulation of the tatoo list */
	@Override
	public void addTattoo(String of)
	{
		final Tattoo T=(Tattoo)CMClass.getCommon("DefaultTattoo");
		addTattoo(T.set(of));
	}

	@Override
	public void addTattoo(String of, int tickDown)
	{
		final Tattoo T=(Tattoo)CMClass.getCommon("DefaultTattoo");
		addTattoo(T.set(of,tickDown));
	}

	@Override
	public void delTattoo(String of)
	{
		final Tattoo T=findTattoo(of);
		if(T!=null)
			tattoos.remove(T);
	}
	
	@Override
	public void addTattoo(Tattoo of)
	{
		if ((of == null) || (of.getTattooName() == null) || (of.getTattooName().length() == 0) || findTattoo(of.getTattooName()) != null)
			return;
		tattoos.addElement(of);
	}

	@Override
	public void delTattoo(Tattoo of)
	{
		if ((of == null) || (of.getTattooName() == null) || (of.getTattooName().length() == 0))
			return;
		final Tattoo tat = findTattoo(of.getTattooName());
		if (tat == null)
			return;
		tattoos.remove(tat);
	}

	@Override
	public Enumeration<Tattoo> tattoos()
	{
		return tattoos.elements();
	}

	@Override
	public Tattoo findTattoo(String of)
	{
		if ((of == null) || (of.length() == 0))
			return null;
		return tattoos.find(of.trim());
	}


	@Override
	public int getBonusCharStatPoints()
	{
		return this.bonusCharStatPt;
	}

	@Override
	public void setBonusCharStatPoints(int bonus)
	{
		this.bonusCharStatPt = bonus;
	}

	@Override
	public int getBonusCommonSkillLimits()
	{
		return this.bonusCommonSk;
	}

	@Override
	public void setBonusCommonSkillLimits(int bonus)
	{
		this.bonusCommonSk = bonus;
	}

	@Override
	public int getBonusCraftingSkillLimits()
	{
		return this.bonusCraftSk;
	}

	@Override
	public void setBonusCraftingSkillLimits(int bonus)
	{
		this.bonusCraftSk = bonus;
	}

	@Override
	public int getBonusNonCraftingSkillLimits()
	{
		return this.bonusNonCraftSk;
	}

	@Override
	public void setBonusNonCraftingSkillLimits(int bonus)
	{
		this.bonusNonCraftSk = bonus;
	}

	@Override
	public int getBonusLanguageLimits()
	{
		return this.bonusLanguages;
	}

	@Override
	public void setBonusLanguageLimits(int bonus)
	{
		this.bonusLanguages = bonus;
	}

	@Override
	public int getBonusCharsOnlineLimit()
	{
		return bonusCharOnlineLimit;
	}

	@Override
	public void setBonusCharsOnlineLimit(int bonus)
	{
		bonusCharOnlineLimit = bonus;
	}

	@Override
	public int getBonusCharsLimit()
	{
		return bonusCharLimit;
	}

	@Override
	public void setBonusCharsLimit(int bonus)
	{
		bonusCharLimit = bonus;
	}

	protected static String[] CODES={"CLASS","FRIENDS","IGNORE","LASTIP","LASTDATETIME",
									 "NOTES","ACCTEXPIRATION","FLAGS","EMAIL",
									 "BONUSCOMMON", "BONUSCRAFT","BONUSNONCRAFT","BONUSLANGS",
									 "BONUSCHARSTATS", "BONUSCHARLIMIT", "BONUSCHARONLINE"};

	@Override
	public String getStat(String code)
	{
		switch(getCodeNum(code))
		{
		case 0:
			return ID();
		case 1:
			return getPrivateList(getFriends());
		case 2:
			return getPrivateList(getIgnored());
		case 3:
			return lastIP;
		case 4:
			return "" + lastDateTime;
		case 5:
			return notes;
		case 6:
			return "" + accountExpiration;
		case 7:
			return CMParms.toListString(acctFlags);
		case 8:
			return email;
		case 9:
			return "" + bonusCommonSk;
		case 10:
			return "" + bonusCraftSk;
		case 11:
			return "" + bonusNonCraftSk;
		case 12:
			return "" + bonusLanguages;
		case 13:
			return "" + bonusCharStatPt;
		case 14:
			return "" + bonusCharLimit;
		case 15:
			return "" + bonusCharOnlineLimit;
		default:
			return CMProps.getStatCodeExtensionValue(getStatCodes(), xtraValues, code);
		}
	}

	@Override
	public void setStat(String code, String val)
	{
		switch(getCodeNum(code))
		{
		case 0: break;
		case 1:
		{
			friends.clear();
			friends.addAll(getHashFrom(val));
			break;
		}
		case 2:
		{
			ignored.clear();
			ignored.addAll(getHashFrom(val));
			break;
		}
		case 3:
			lastIP = val;
			break;
		case 4:
			lastDateTime = CMath.s_long(val);
			break;
		case 5:
			notes = val;
			break;
		case 6:
			accountExpiration = CMath.s_long(val);
			break;
		case 7:
		{
			acctFlags = new SHashSet<AccountFlag>();
			for(String s : CMParms.parseCommas(val.toUpperCase(),true))
			{
				final AccountFlag flag = (AccountFlag)CMath.s_valueOf(AccountFlag.class, s);
				if(flag != null)
					acctFlags.add(flag);
			}
			break;
		}
		case 8:
			email = val;
			break;
		case 9:
			bonusCommonSk = CMath.s_parseIntExpression(val);
			break;
		case 10:
			bonusCraftSk = CMath.s_parseIntExpression(val);
			break;
		case 11:
			bonusNonCraftSk = CMath.s_parseIntExpression(val);
			break;
		case 12:
			bonusLanguages = CMath.s_parseIntExpression(val);
			break;
		case 13:
			bonusCharStatPt = CMath.s_parseIntExpression(val);
			break;
		case 14:
			bonusCharLimit = CMath.s_parseIntExpression(val);
			break;
		case 15:
			bonusCharOnlineLimit = CMath.s_parseIntExpression(val);
			break;
		default:
			CMProps.setStatCodeExtensionValue(getStatCodes(), xtraValues, code, val);
			break;
		}
	}

	@Override
	public int getSaveStatIndex()
	{
		return (xtraValues == null) ? getStatCodes().length : getStatCodes().length - xtraValues.length;
	}

	private static String[]	codes	= null;

	@Override
	public String[] getStatCodes()
	{
		if (codes == null)
			codes = CMProps.getStatCodesList(CODES, this);
		return codes;
	}

	@Override
	public boolean isStat(String code)
	{
		return CMParms.indexOf(getStatCodes(), code.toUpperCase().trim()) >= 0;
	}

	protected int getCodeNum(String code)
	{
		for (int i = 0; i < CODES.length; i++)
		{
			if (code.equalsIgnoreCase(CODES[i]))
				return i;
		}
		return -1;
	}

	public boolean sameAs(PlayerAccount E)
	{
		if (!(E instanceof DefaultPlayerAccount))
			return false;
		for (int i = 0; i < getStatCodes().length; i++)
		{
			if (!E.getStat(getStatCodes()[i]).equals(getStat(getStatCodes()[i])))
				return false;
		}
		return true;
	}

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

}