/
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.core.database;

import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.SecFlag;
import com.planet_ink.coffee_mud.core.CMSecurity.SecGroup;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.database.DBConnector.DBPreparedBatchEntry;
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.Clan.MemberRecord;
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.sql.*;
import java.util.*;
import java.util.Map.Entry;

/*
 * Copyright 2000-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 MOBloader
{
	protected DBConnector DB=null;

	public MOBloader(DBConnector newDB)
	{
		DB=newDB;
	}
	protected Room emptyRoom=null;

	public MOB DBReadUserOnly(final String name, final String[] locationID)
	{
		if((name==null)||(name.length()==0)) 
			return null;
		DBConnection D=null;
		MOB mob=null;
		int oldDisposition=0;
		try
		{
			D=DB.DBFetch();
			ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMUSERID='"+name+"'");
			if(R.next())
			{
				mob=CMClass.getMOB(DBConnections.getRes(R,"CMCHID"));
				if(mob == null)
				{
					mob=CMClass.getMOB("StdMOB");
					if(mob == null)
						return null;
				}
				oldDisposition=mob.basePhyStats().disposition();
				mob.basePhyStats().setDisposition(PhyStats.IS_NOT_SEEN|PhyStats.IS_SNEAKING);
				mob.phyStats().setDisposition(PhyStats.IS_NOT_SEEN|PhyStats.IS_SNEAKING);
				
				final CharStats stats=mob.baseCharStats();
				final CharState state=mob.baseState();
				final PlayerStats pstats=(PlayerStats)CMClass.getCommon("DefaultPlayerStats");
				mob.setPlayerStats(pstats);
				final String username=DBConnections.getRes(R,"CMUSERID");
				final String password=DBConnections.getRes(R,"CMPASS");
				mob.setName(username);
				pstats.setPassword(password);
				stats.setMyClasses(DBConnections.getRes(R,"CMCLAS"));
				stats.setStat(CharStats.STAT_STRENGTH,CMath.s_int(DBConnections.getRes(R,"CMSTRE")));
				stats.setMyRace(CMClass.getRace(DBConnections.getRes(R,"CMRACE")));
				stats.setStat(CharStats.STAT_DEXTERITY,CMath.s_int(DBConnections.getRes(R,"CMDEXT")));
				stats.setStat(CharStats.STAT_CONSTITUTION,CMath.s_int(DBConnections.getRes(R,"CMCONS")));
				stats.setStat(CharStats.STAT_GENDER,DBConnections.getRes(R,"CMGEND").charAt(0));
				stats.setStat(CharStats.STAT_WISDOM,CMath.s_int(DBConnections.getRes(R,"CMWISD")));
				stats.setStat(CharStats.STAT_INTELLIGENCE,CMath.s_int(DBConnections.getRes(R,"CMINTE")));
				stats.setStat(CharStats.STAT_CHARISMA,CMath.s_int(DBConnections.getRes(R,"CMCHAR")));
				state.setHitPoints(CMath.s_int(DBConnections.getRes(R,"CMHITP")));
				stats.setMyLevels(DBConnections.getRes(R,"CMLEVL"));
				int level=0;
				for(int i=0;i<mob.baseCharStats().numClasses();i++)
					level+=stats.getClassLevel(mob.baseCharStats().getMyClass(i));
				mob.basePhyStats().setLevel(level);
				state.setMana(CMath.s_int(DBConnections.getRes(R,"CMMANA")));
				state.setMovement(CMath.s_int(DBConnections.getRes(R,"CMMOVE")));
				mob.setDescription(DBConnections.getRes(R,"CMDESC"));
				final int align=(CMath.s_int(DBConnections.getRes(R,"CMALIG")));
				if((CMLib.factions().getFaction(CMLib.factions().AlignID())!=null)&&(align>=0))
					CMLib.factions().setAlignmentOldRange(mob,align);
				mob.setExperience(CMath.s_int(DBConnections.getRes(R,"CMEXPE")));
				//mob.setExpNextLevel(CMath.s_int(DBConnections.getRes(R,"CMEXLV")));
				mob.setWorshipCharID(DBConnections.getRes(R,"CMWORS"));
				mob.setPractices(CMath.s_int(DBConnections.getRes(R,"CMPRAC")));
				mob.setTrains(CMath.s_int(DBConnections.getRes(R,"CMTRAI")));
				mob.setAgeMinutes(CMath.s_long(DBConnections.getRes(R,"CMAGEH")));
				mob.setMoney(CMath.s_int(DBConnections.getRes(R,"CMGOLD")));
				mob.setWimpHitPoint(CMath.s_int(DBConnections.getRes(R,"CMWIMP")));
				mob.setQuestPoint(CMath.s_int(DBConnections.getRes(R,"CMQUES")));
				String roomID=DBConnections.getRes(R,"CMROID");
				if(roomID==null)
					roomID="";
				final int x=roomID.indexOf("||");
				if(x>=0)
				{
					locationID[0]=roomID.substring(x+2);
					mob.setLocation(CMLib.map().getRoom(locationID[0]));
					roomID=roomID.substring(0,x);
				}
				mob.setStartRoom(CMLib.map().getRoom(roomID));
				pstats.setLastDateTime(CMath.s_long(DBConnections.getRes(R,"CMDATE")));
				pstats.setChannelMask((int)DBConnections.getLongRes(R,"CMCHAN"));
				mob.basePhyStats().setAttackAdjustment(CMath.s_int(DBConnections.getRes(R,"CMATTA")));
				mob.basePhyStats().setArmor(CMath.s_int(DBConnections.getRes(R,"CMAMOR")));
				mob.basePhyStats().setDamage(CMath.s_int(DBConnections.getRes(R,"CMDAMG")));
				mob.setAttributesBitmap(CMath.s_int(DBConnections.getRes(R,"CMBTMP")));
				mob.setLiegeID(DBConnections.getRes(R,"CMLEIG"));
				mob.basePhyStats().setHeight((int)DBConnections.getLongRes(R,"CMHEIT"));
				mob.basePhyStats().setWeight((int)DBConnections.getLongRes(R,"CMWEIT"));
				pstats.setPrompt(DBConnections.getRes(R,"CMPRPT"));
				final String colorStr=DBConnections.getRes(R,"CMCOLR");
				if((colorStr!=null)&&(colorStr.length()>0)&&(!colorStr.equalsIgnoreCase("NULL"))) 
					pstats.setColorStr(colorStr);
				pstats.setLastIP(DBConnections.getRes(R,"CMLSIP"));
				mob.setClan("", Integer.MIN_VALUE); // delete all sequence
				pstats.setEmail(DBConnections.getRes(R,"CMEMAL"));
				final String buf=DBConnections.getRes(R,"CMPFIL");
				pstats.setXML(buf);
				stats.setNonBaseStatsFromString(DBConnections.getRes(R,"CMSAVE"));
				List<String> V9=CMParms.parseSemicolons(CMLib.xml().returnXMLValue(buf,"TATTS"),true);
				for(final Enumeration<Tattoo> e=mob.tattoos();e.hasMoreElements();)
					mob.delTattoo(e.nextElement());
				for(int v=0;v<V9.size();v++)
					mob.addTattoo(parseTattoo(V9.get(v)));
				V9=CMParms.parseSemicolons(CMLib.xml().returnXMLValue(buf,"EDUS"),true);
				mob.delAllExpertises();
				for(int v=0;v<V9.size();v++)
					mob.addExpertise(V9.get(v));
				
				// check for a non-existant birthday and fix it
				if(pstats.getBirthday()==null)
					stats.setStat(CharStats.STAT_AGE,
						pstats.initializeBirthday(CMLib.time().localClock(mob.getStartRoom()),(int)Math.round(CMath.div(mob.getAgeMinutes(),60.0)),stats.getMyRace()));
				
				final TimeClock C=CMLib.time().localClock(mob.getStartRoom());
				// check for a messed up/reset birthday and fix it
				if((pstats.getBirthday()[PlayerStats.BIRTHDEX_YEAR]==1)
				||(pstats.getBirthday()[PlayerStats.BIRTHDEX_YEAR]>C.getYear()))
				{
					final int age = mob.baseCharStats().getStat(CharStats.STAT_AGE);
					if((pstats.getBirthday()[PlayerStats.BIRTHDEX_MONTH]==1)
					&&(pstats.getBirthday()[PlayerStats.BIRTHDEX_DAY]==1))
					{
						if((age > 1)&&(C.getYear() > age))
							pstats.initializeBirthday(C,(int)Math.round(CMath.div(mob.getAgeMinutes(),60.0)),stats.getMyRace());
						Log.warnOut("MOBloader","Reset the birthday of player '"+mob.Name()+"' (Might have been holdover from being first-year player)");
					}
					if((age > 1)&&(C.getYear() > age))
					{
						pstats.getBirthday()[PlayerStats.BIRTHDEX_YEAR]=C.getYear()-age;
						pstats.getBirthday()[PlayerStats.BIRTHDEX_LASTYEARCELEBRATED]=C.getYear();
					}
				}
				mob.setImage(CMLib.xml().returnXMLValue(buf,"IMG"));
				final List<XMLLibrary.XMLTag> CleanXML=CMLib.xml().parseAllXML(DBConnections.getRes(R,"CMMXML"));
				R.close();
				if(pstats.getSavedPose().length()>0)
					mob.setDisplayText(pstats.getSavedPose());
				CMLib.coffeeMaker().setFactionFromXML(mob,CleanXML);
				if((CMProps.isUsingAccountSystem())&&(pstats.getAccount()==null))
				{
					// yes, this can happen when you wiggle in and out of the account system.
					for(final Enumeration<PlayerAccount> a = CMLib.players().accounts(); a.hasMoreElements();)
					{
						final PlayerAccount pA=a.nextElement();
						if(pA.findPlayer(mob.Name())!=null)
						{
							pstats.setAccount(pA);
							break;
						}
					}
				}
				CMLib.achievements().loadPlayerSkillAwards(mob, pstats);
			}
			R.close();
			R=D.query("SELECT * FROM CMCHCL WHERE CMUSERID='"+name+"'");
			while(R.next() && (mob!=null))
			{
				final String clanID=DBConnections.getRes(R,"CMCLAN");
				final int clanRole = this.BuildClanMemberRole(R);
				final Clan C=CMLib.clans().getClan(clanID);
				if(C!=null) 
					mob.setClan(C.clanID(), clanRole);
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		if(mob != null)
		{
			mob.basePhyStats().setDisposition(oldDisposition);
			mob.recoverPhyStats();
		}
		return mob;
	}

	public MOB DBRead(final String name)
	{
		if((name==null)||(name.length()==0)) 
			return null;
		if(emptyRoom==null) 
			emptyRoom=CMClass.getLocale("StdRoom");
		final String[] oldLocID=new String[1];
		if(CMLib.players().getPlayer(name)!=null)
			return CMLib.players().getPlayer(name);
		final MOB mob=DBReadUserOnly(name,oldLocID);
		if(mob == null)
			return null;
		final int oldDisposition=mob.basePhyStats().disposition();
		mob.basePhyStats().setDisposition(PhyStats.IS_NOT_SEEN|PhyStats.IS_SNEAKING);
		mob.phyStats().setDisposition(PhyStats.IS_NOT_SEEN|PhyStats.IS_SNEAKING);
		CMLib.players().addPlayer(mob);
		mob.recoverPhyStats();
		mob.recoverCharStats();
		Room oldLoc=mob.location();
		boolean inhab=false;
		if(oldLoc!=null) 
			inhab=oldLoc.isInhabitant(mob);
		mob.setLocation(emptyRoom);
		DBConnection D=null;
		// now grab the items
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHIT WHERE CMUSERID='"+mob.Name()+"'");
			final Hashtable<String,Item> itemNums=new Hashtable<String,Item>();
			final Hashtable<Item,String> itemLocs=new Hashtable<Item,String>();
			while(R.next())
			{
				final String itemNum=DBConnections.getRes(R,"CMITNM");
				final String itemID=DBConnections.getRes(R,"CMITID");
				final Item newItem=CMClass.getItem(itemID);
				if(newItem==null)
					Log.errOut("MOB","Couldn't find item '"+itemID+"'");
				else
				{
					itemNums.put(itemNum,newItem);
					boolean addToMOB=true;
					String text=DBConnections.getResQuietly(R,"CMITTX");
					int roomX;
					if(text.startsWith("<ROOM") && ((roomX=text.indexOf("/>"))>=0))
					{
						final String roomXML=text.substring(0,roomX+2);
						text=text.substring(roomX+2);
						newItem.setMiscText(text);
						final List<XMLLibrary.XMLTag> xml=CMLib.xml().parseAllXML(roomXML);
						if((xml!=null)&&(xml.size()>0))
						{
							final String roomID=xml.get(0).parms().get("ID");
							final long expirationDate=CMath.s_long(xml.get(0).parms().get("EXPIRE"));
							if(roomID.startsWith("SPACE.") && (newItem instanceof SpaceObject))
							{
								CMLib.map().addObjectToSpace((SpaceObject)newItem,CMParms.toLongArray(CMParms.parseCommas(roomID.substring(6), true)));
								addToMOB=false;
							}
							else
							{
								final Room itemR=CMLib.map().getRoom(roomID);
								if(itemR!=null)
								{
									if(newItem instanceof BoardableShip)
										((BoardableShip)newItem).dockHere(itemR);
									else
										itemR.addItem(newItem);
									newItem.setExpirationDate(expirationDate);
									addToMOB=false;
								}
							}
						}
					}
					else
					{
						newItem.setMiscText(text);
					}
					if((oldLoc==null)
					&&(newItem instanceof BoardableShip))
					{
						final Area area=((BoardableShip)newItem).getShipArea();
						if(area != null)
							oldLoc=area.getRoom(oldLocID[0]);
					}
					final String loc=DBConnections.getResQuietly(R,"CMITLO");
					if(loc.length()>0)
					{
						final Item container=itemNums.get(loc);
						if(container instanceof Container)
							newItem.setContainer((Container)container);
						else
							itemLocs.put(newItem,loc);
					}
					newItem.wearAt((int)DBConnections.getLongRes(R,"CMITWO"));
					newItem.setUsesRemaining((int)DBConnections.getLongRes(R,"CMITUR"));
					newItem.basePhyStats().setLevel((int)DBConnections.getLongRes(R,"CMITLV"));
					newItem.basePhyStats().setAbility((int)DBConnections.getLongRes(R,"CMITAB"));
					newItem.basePhyStats().setHeight((int)DBConnections.getLongRes(R,"CMHEIT"));
					newItem.recoverPhyStats();
					if(addToMOB)
						mob.addItem(newItem);
					else
					{
						CMLib.map().registerWorldObjectLoaded(null, null, newItem);
						mob.playerStats().getExtItems().addItem(newItem);
					}
				}
			}
			for(final Enumeration<Item> e=itemLocs.keys();e.hasMoreElements();)
			{
				final Item keyItem=e.nextElement();
				final String location=itemLocs.get(keyItem);
				final Item container=itemNums.get(location);
				if(container instanceof Container)
				{
					keyItem.setContainer((Container)container);
					keyItem.recoverPhyStats();
					container.recoverPhyStats();
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		D=null;
		if(oldLoc!=null)
		{
			mob.setLocation(oldLoc);
			if(inhab&&(!oldLoc.isInhabitant(mob)))
				oldLoc.addInhabitant(mob);
		}
		// now grab the abilities
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAB WHERE CMUSERID='"+mob.Name()+"'");
			while(R.next())
			{
				final String abilityID=DBConnections.getRes(R,"CMABID");
				int proficiency=(int)DBConnections.getLongRes(R,"CMABPF");
				if((proficiency==Integer.MIN_VALUE)||(proficiency==Integer.MIN_VALUE+1))
				{
					if(abilityID.equalsIgnoreCase("ScriptingEngine"))
					{
						if(CMClass.getCommon("DefaultScriptingEngine")==null)
							Log.errOut("MOB","Couldn't find scripting engine!");
						else
						{

							final String xml=DBConnections.getRes(R,"CMABTX");
							if(xml.length()>0)
								CMLib.coffeeMaker().setGenScripts(mob,CMLib.xml().parseAllXML(xml),true);
						}
					}
					else
					{
						final Behavior newBehavior=CMClass.getBehavior(abilityID);
						if(newBehavior==null)
							Log.errOut("MOB","Couldn't find behavior '"+abilityID+"'");
						else
						{
							newBehavior.setParms(DBConnections.getRes(R,"CMABTX"));
							mob.addBehavior(newBehavior);
						}
					}
				}
				else
				{
					final Ability newAbility=CMClass.getAbility(abilityID);
					if(newAbility==null)
						Log.errOut("MOB","Couldn't find ability '"+abilityID+"'");
					else
					{
						if((proficiency<0)||(proficiency==Integer.MAX_VALUE))
						{
							if(proficiency==Integer.MAX_VALUE)
							{
								newAbility.setProficiency(CMLib.ableMapper().getMaxProficiency(newAbility.ID()));
								mob.addNonUninvokableEffect(newAbility);
								newAbility.setMiscText(DBConnections.getRes(R,"CMABTX"));
							}else
							{
								proficiency=proficiency+200;
								newAbility.setProficiency(proficiency);
								newAbility.setMiscText(DBConnections.getRes(R,"CMABTX"));
								final Ability newAbility2=(Ability)newAbility.copyOf();
								mob.addNonUninvokableEffect(newAbility);
								mob.addAbility(newAbility2);
							}
						}else
						{
							newAbility.setProficiency(proficiency);
							newAbility.setMiscText(DBConnections.getRes(R,"CMABTX"));
							mob.addAbility(newAbility);
						}
					}
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		D=null;
		mob.basePhyStats().setDisposition(oldDisposition);
		mob.recoverPhyStats();
		if(mob.baseCharStats()!=null)
		{
			mob.baseCharStats().getCurrentClass().startCharacter(mob,false,true);
			final int oldWeight=mob.basePhyStats().weight();
			final int oldHeight=mob.basePhyStats().height();
			mob.baseCharStats().getMyRace().startRacing(mob,true);
			if(oldWeight>0)
				mob.basePhyStats().setWeight(oldWeight);
			if(oldHeight>0)
				mob.basePhyStats().setHeight(oldHeight);
		}
		mob.recoverCharStats();
		mob.recoverPhyStats();
		mob.recoverMaxState();
		mob.resetToMaxState();
		CMLib.threads().suspendResumeRecurse(mob, false, true);
		return mob;
	}

	public List<String> getUserList()
	{
		DBConnection D=null;
		final Vector<String> V=new Vector<String>();
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR");
			if(R!=null) while(R.next())
			{
				final String username=DBConnections.getRes(R,"CMUSERID");
				V.addElement(username);
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return V;
	}

	protected PlayerLibrary.ThinPlayer parseThinUser(ResultSet R)
	{
		try
		{
			final String name=DBConnections.getRes(R,"CMUSERID");
			String cclass=DBConnections.getRes(R,"CMCLAS");
			final int x=cclass.lastIndexOf(';');
			CharClass C=null;
			if((x>0)&&(x<cclass.length()-2))
			{
				C=CMClass.getCharClass(cclass.substring(x+1));
				if(C!=null)
					cclass=C.name();
			}
			final String charClass=(cclass);
			final String rrace=DBConnections.getRes(R,"CMRACE");
			final Race R2=CMClass.getRace(rrace);
			final String race;
			if(R2!=null)
				race=(R2.name());
			else
				race=rrace;
			final List<String> lvls=CMParms.parseSemicolons(DBConnections.getRes(R,"CMLEVL"), true);
			int calcLevel=0;
			for(final String lvl : lvls)
				calcLevel+=CMath.s_int(lvl);
			final int level = calcLevel;
			final int age=(int)DBConnections.getLongRes(R,"CMAGEH");
			final MOB M=CMLib.players().getPlayer(name);
			final long last;
			if((M!=null)&&(M.lastTickedDateTime()>0))
				last=M.lastTickedDateTime();
			else
				last=DBConnections.getLongRes(R,"CMDATE");
			final String lsIP=DBConnections.getRes(R,"CMLSIP");
			final String email=DBConnections.getRes(R,"CMEMAL");
			final String ip=lsIP;
			final int exp=CMath.s_int(DBConnections.getRes(R,"CMEXPE"));
			final int expLvl=CMath.s_int(DBConnections.getRes(R,"CMEXLV"));
			return new PlayerLibrary.ThinPlayer()
			{

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

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

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

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

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

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

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

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

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

				@Override
				public int expLvl()
				{
					return expLvl;
				}
			};
		}catch(final Exception e)
		{
			Log.errOut("MOBloader",e);
		}
		return null;
	}

	public PlayerLibrary.ThinPlayer getThinUser(String name)
	{
		DBConnection D=null;
		PlayerLibrary.ThinPlayer thisUser=null;
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMUSERID='"+name+"'");
			if(R!=null) while(R.next())
				thisUser=parseThinUser(R);
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return thisUser;
	}

	public List<PlayerLibrary.ThinPlayer> getExtendedUserList()
	{
		DBConnection D=null;
		final Vector<PlayerLibrary.ThinPlayer> allUsers=new Vector<PlayerLibrary.ThinPlayer>();
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR");
			if(R!=null) while(R.next())
			{
				final PlayerLibrary.ThinPlayer thisUser=parseThinUser(R);
				if(thisUser != null)
					allUsers.addElement(thisUser);
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return allUsers;
	}

	public Tattoo parseTattoo(String tattoo)
	{
		if(tattoo==null)
			return (Tattoo)CMClass.getCommon("DefaultTattoo");
		int tickDown = 0;
		if((tattoo.length()>0)
		&&(Character.isDigit(tattoo.charAt(0))))
		{
			final int x=tattoo.indexOf(' ');
			if((x>0)
			&&(CMath.isNumber(tattoo.substring(0,x).trim())))
			{
				tickDown=CMath.s_int(tattoo.substring(0,x));
				tattoo=tattoo.substring(x+1).trim();
			}
		}
		final Tattoo T=(Tattoo)CMClass.getCommon("DefaultTattoo");
		return T.set(tattoo, tickDown);
	}

	public List<PlayerLibrary.ThinPlayer> vassals(MOB mob, String liegeID)
	{
		DBConnection D=null;
		List<PlayerLibrary.ThinPlayer> list=new ArrayList<PlayerLibrary.ThinPlayer>();
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMLEIG='"+liegeID+"'");
			if(R!=null) 
			while(R.next())
				list.add(this.parseThinUser(R));
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return list;
	}

	public List<PlayerLibrary.ThinPlayer> worshippers(String deityID)
	{
		DBConnection D=null;
		final List<PlayerLibrary.ThinPlayer> DV=new Vector<PlayerLibrary.ThinPlayer>();
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMWORS='"+deityID+"'");
			if(R!=null) while(R.next())
			{
				DV.add(parseThinUser(R));
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return DV;
	}

	public List<MOB> DBScanFollowers(MOB mob)
	{
		DBConnection D=null;
		final Vector<MOB> V=new Vector<MOB>();
		// now grab the followers
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHFO WHERE CMUSERID='"+mob.Name()+"'");
			while(R.next())
			{
				final String MOBID=DBConnections.getRes(R,"CMFOID");
				final MOB newMOB=CMClass.getMOB(MOBID);
				if(newMOB==null)
					Log.errOut("MOB","Couldn't find MOB '"+MOBID+"'");
				else
				{
					newMOB.setMiscText(DBConnections.getResQuietly(R,"CMFOTX"));
					newMOB.basePhyStats().setLevel(((int)DBConnections.getLongRes(R,"CMFOLV")));
					newMOB.basePhyStats().setAbility((int)DBConnections.getLongRes(R,"CMFOAB"));
					newMOB.basePhyStats().setRejuv(PhyStats.NO_REJUV);
					newMOB.recoverPhyStats();
					newMOB.recoverCharStats();
					newMOB.recoverMaxState();
					newMOB.resetToMaxState();
					V.addElement(newMOB);
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return V;
	}

	public void DBReadFollowers(MOB mob, boolean bringToLife)
	{
		Room location=mob.location();
		if(location==null)
			location=mob.getStartRoom();
		final List<MOB> V=DBScanFollowers(mob);
		for(int v=0;v<V.size();v++)
		{
			final MOB newMOB=V.get(v);
			final Room room=(location==null)?newMOB.getStartRoom():location;
			newMOB.setStartRoom(room);
			newMOB.setLocation(room);
			newMOB.setFollowing(mob);
			if((newMOB.getStartRoom()!=null)
			&&(CMLib.law().doesHavePriviledgesHere(mob,newMOB.getStartRoom()))
			&&((newMOB.location()==null)
				||(!CMLib.law().doesHavePriviledgesHere(mob,newMOB.location()))))
			{
				newMOB.setLocation(newMOB.getStartRoom());
			}
			if(bringToLife)
			{
				newMOB.bringToLife(mob.location(),true);
				mob.location().showOthers(newMOB,null,CMMsg.MSG_OK_ACTION,CMLib.lang().L("<S-NAME> appears!"));
			}
		}
	}

	public void DBUpdateEmail(MOB mob)
	{
		final PlayerStats pstats=mob.playerStats();
		if(pstats==null)
			return;
		DB.update("UPDATE CMCHAR SET CMEMAL='"+pstats.getEmail()+"' WHERE CMUSERID='"+mob.Name()+"'");
	}

	private int BuildClanMemberRole(ResultSet R)
	{
		return (int)DBConnections.getLongRes(R,"CMCLRO");
	}

	private MemberRecord BuildClanMemberRecord(ResultSet R)
	{
		final String username=DB.getRes(R,"CMUSERID");
		final int clanRole = BuildClanMemberRole(R);
		int mobpvps=0;
		int playerpvps=0;
		final String stats=DB.getRes(R,"CMCLSTS");
		if(stats!=null)
		{
			final String[] splitstats=stats.split(";");
			if(splitstats.length>0)
				mobpvps=CMath.s_int(splitstats[0]);
			if(splitstats.length>1)
				playerpvps=CMath.s_int(splitstats[1]);
		}
		return new Clan.MemberRecord(username,clanRole,mobpvps,playerpvps);
	}

	public MemberRecord DBGetClanMember(String clan, String name)
	{
		DBConnection D=null;
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHCL where CMCLAN='"+clan+"' and CMUSERID='"+name+"'");
			if(R!=null) while(R.next())
			{
				return BuildClanMemberRecord(R);
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return null;
	}

	public List<Clan.MemberRecord> DBClanMembers(String clan)
	{
		final List<Clan.MemberRecord> members = new Vector<Clan.MemberRecord>();
		DBConnection D=null;
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHCL where CMCLAN='"+clan+"'");
			if(R!=null) while(R.next())
			{
				members.add(BuildClanMemberRecord(R));
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return members;
	}

	public void DBUpdateClanMembership(String name, String clan, int role)
	{
		final MOB M=CMLib.players().getPlayer(name);
		if(M!=null)
		{
			M.setClan(clan, role);
		}
		DBConnection D=null;
		try
		{
			D=DB.DBFetch();
			if(role<0)
			{
				DB.update("DELETE FROM CMCHCL WHERE CMUSERID='"+name+"' AND CMCLAN='"+clan+"'");
				if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANMEMBERS))
					Log.debugOut("User '"+name+"' was deleted from clan '"+clan+"'");
			}
			else
			{
				final ResultSet R=D.query("SELECT * FROM CMCHCL where CMCLAN='"+clan+"' and CMUSERID='"+name+"'");
				if((R!=null) && (R.next()))
				{
					final int clanRole = BuildClanMemberRole(R);
					R.close();
					if(clanRole == role)
						return;
					D.update("UPDATE CMCHCL SET CMCLRO="+role+" where CMCLAN='"+clan+"' and CMUSERID='"+name+"'", 0);
					if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANMEMBERS))
						Log.debugOut("User '"+name+"' had role in clan '"+clan+"' changed from "+clanRole+" to "+role);
				}
				else
				{
					D.update("INSERT INTO CMCHCL (CMUSERID, CMCLAN, CMCLRO, CMCLSTS) values ('"+name+"','"+clan+"',"+role+",'0;0')",0);
					if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANMEMBERS))
						Log.debugOut("User '"+name+"' was inserted into clan '"+clan+"' as role "+role);
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
	}

	public void DBUpdateClanKills(String clan, String name, int adjMobKills, int adjPlayerKills)
	{
		if(((adjMobKills==0)&&(adjPlayerKills==0))
		||(clan==null)
		||(name==null))
			return;

		DBConnection D=null;
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHCL where CMCLAN='"+clan+"' and CMUSERID='"+name+"'");
			MemberRecord M=null;
			if(R!=null)
			{
				if(R.next())
				{
					M=BuildClanMemberRecord(R);
					R.close();
					M.mobpvps+=adjMobKills;
					M.playerpvps+=adjPlayerKills;
					final String newStats=M.mobpvps+";"+M.playerpvps;
					D.update("UPDATE CMCHCL SET CMCLSTS='"+newStats+"' where CMCLAN='"+clan+"' and CMUSERID='"+name+"'", 0);
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
	}

	public void DBUpdate(MOB mob)
	{
		DBUpdateJustMOB(mob);
		final PlayerStats pStats = mob.playerStats();
		if((mob.Name().length()==0)||(pStats==null))
			return;
		DBUpdateItems(mob);
		DBUpdateAbilities(mob);
		pStats.setLastUpdated(System.currentTimeMillis());
		final PlayerAccount account = pStats.getAccount();
		if(account != null)
		{
			DBUpdateAccount(account);
			account.setLastUpdated(System.currentTimeMillis());
		}
	}

	public void DBUpdatePassword(String name, String password)
	{
		name=CMStrings.capitalizeAndLower(name);
		DB.update("UPDATE CMCHAR SET CMPASS='"+password+"' WHERE CMUSERID='"+name.replace('\'', 'n')+"'");
	}

	private String getPlayerStatsXML(MOB mob)
	{
		final PlayerStats pstats=mob.playerStats();
		if(pstats==null)
			return "";
		final StringBuilder pfxml=new StringBuilder(pstats.getXML());
		if(mob.tattoos().hasMoreElements())
		{
			pfxml.append("<TATTS>");
			for(final Enumeration<Tattoo> e=mob.tattoos();e.hasMoreElements();)
				pfxml.append(e.nextElement().toString()+";");
			pfxml.append("</TATTS>");
		}
		if(mob.expertises().hasMoreElements())
		{
			pfxml.append("<EDUS>");
			for(final Enumeration<String> x=mob.expertises();x.hasMoreElements();)
				pfxml.append(x.nextElement()).append(';');
			pfxml.append("</EDUS>");
		}
		pfxml.append(CMLib.xml().convertXMLtoTag("IMG",mob.rawImage()));
		return pfxml.toString();
	}

	public void DBUpdateJustPlayerStats(MOB mob)
	{
		if(mob.Name().length()==0)
		{
			DBCreateCharacter(mob);
			return;
		}
		final PlayerStats pstats=mob.playerStats();
		if(pstats==null)
			return;
		final String pfxml=getPlayerStatsXML(mob);
		DB.updateWithClobs("UPDATE CMCHAR SET CMPFIL=? WHERE CMUSERID='"+mob.Name()+"'", pfxml.toString());
	}

	public void DBUpdateJustMOB(MOB mob)
	{
		if(mob.Name().length()==0)
		{
			DBCreateCharacter(mob);
			return;
		}
		final PlayerStats pstats=mob.playerStats();
		if(pstats==null)
			return;
		final String strStartRoomID=(mob.getStartRoom()!=null)?CMLib.map().getExtendedRoomID(mob.getStartRoom()):"";
		String strOtherRoomID=(mob.location()!=null)?CMLib.map().getExtendedRoomID(mob.location()):"";

		if((mob.location()!=null)
		&&(mob.location().getArea()!=null)
		&&(CMath.bset(mob.location().getArea().flags(),Area.FLAG_INSTANCE_PARENT)
			||CMath.bset(mob.location().getArea().flags(),Area.FLAG_INSTANCE_CHILD)))
			strOtherRoomID=strStartRoomID;

		final String playerStatsXML=getPlayerStatsXML(mob);
		final String factionDataXML=CMLib.coffeeMaker().getFactionXML(mob).toString();
		DB.updateWithClobs(
				 "UPDATE CMCHAR SET  CMPASS='"+pstats.getPasswordStr()+"'"
				+", CMCHID='"+mob.ID()+"'"
				+", CMCLAS='"+mob.baseCharStats().getMyClassesStr()+"'"
				+", CMSTRE="+mob.baseCharStats().getStat(CharStats.STAT_STRENGTH)
				+", CMRACE='"+mob.baseCharStats().getMyRace().ID()+"'"
				+", CMDEXT="+mob.baseCharStats().getStat(CharStats.STAT_DEXTERITY)
				+", CMCONS="+mob.baseCharStats().getStat(CharStats.STAT_CONSTITUTION)
				+", CMGEND='"+((char)mob.baseCharStats().getStat(CharStats.STAT_GENDER))+"'"
				+", CMWISD="+mob.baseCharStats().getStat(CharStats.STAT_WISDOM)
				+", CMINTE="+mob.baseCharStats().getStat(CharStats.STAT_INTELLIGENCE)
				+", CMCHAR="+mob.baseCharStats().getStat(CharStats.STAT_CHARISMA)
				+", CMHITP="+mob.baseState().getHitPoints()
				+", CMLEVL='"+mob.baseCharStats().getMyLevelsStr()+"'"
				+", CMMANA="+mob.baseState().getMana()
				+", CMMOVE="+mob.baseState().getMovement()
				+", CMALIG=-1"
				+", CMEXPE="+mob.getExperience()
				+", CMEXLV="+mob.getExpNextLevel()
				+", CMWORS='"+mob.getWorshipCharID()+"'"
				+", CMPRAC="+mob.getPractices()
				+", CMTRAI="+mob.getTrains()
				+", CMAGEH="+mob.getAgeMinutes()
				+", CMGOLD="+mob.getMoney()
				+", CMWIMP="+mob.getWimpHitPoint()
				+", CMQUES="+mob.getQuestPoint()
				+", CMROID='"+strStartRoomID+"||"+strOtherRoomID+"'"
				+", CMDATE='"+pstats.getLastDateTime()+"'"
				+", CMCHAN="+pstats.getChannelMask()
				+", CMATTA="+mob.basePhyStats().attackAdjustment()
				+", CMAMOR="+mob.basePhyStats().armor()
				+", CMDAMG="+mob.basePhyStats().damage()
				+", CMBTMP="+mob.getAttributesBitmap()
				+", CMLEIG='"+mob.getLiegeID()+"'"
				+", CMHEIT="+mob.basePhyStats().height()
				+", CMWEIT="+mob.basePhyStats().weight()
				+", CMPRPT=?"
				+", CMCOLR=?"
				+", CMLSIP='"+pstats.getLastIP()+"'"
				+", CMEMAL=?"
				+", CMPFIL=?"
				+", CMSAVE=?"
				+", CMMXML=?"
				+", CMDESC=?"
				+"  WHERE CMUSERID='"+mob.Name()+"'"
				,new String[][]{{
					pstats.getPrompt(),
					pstats.getColorStr(),
					pstats.getEmail(),
					playerStatsXML,
					mob.baseCharStats().getNonBaseStatsAsString(),
					factionDataXML,
					mob.description()+" "
				}}
				);
		final List<String> clanStatements=new LinkedList<String>();
		DBConnection D=null;
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHCL WHERE CMUSERID='"+mob.Name()+"'");
			final Set<String> savedClans=new HashSet<String>();
			if(R!=null)
			{
				while(R.next())
				{
					final String clanID=DB.getRes(R,"CMCLAN");
					final Pair<Clan,Integer> role=mob.getClanRole(clanID);
					if(role==null)
					{
						clanStatements.add("DELETE FROM CMCHCL WHERE CMUSERID='"+mob.Name()+"' AND CMCLAN='"+clanID+"'");
						if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANMEMBERS))
							Log.debugOut("User '"+mob.Name()+"' was deleted from clan '"+clanID+"'");
					}
					else
					{
						final MemberRecord M=BuildClanMemberRecord(R);
						if(role.second.intValue()!=M.role)
						{
							clanStatements.add("UPDATE CMCHCL SET CMCLRO="+role+" where CMCLAN='"+clanID+"' and CMUSERID='"+mob.Name()+"'");
							if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANMEMBERS))
								Log.debugOut("User '"+mob.Name()+"' had role in clan '"+clanID+"' changed from "+M.role+" to role "+role.second.intValue());
						}
					}
					savedClans.add(clanID.toUpperCase());
				}
				R.close();
			}
			for(final Pair<Clan,Integer> p : mob.clans())
				if(!savedClans.contains(p.first.clanID().toUpperCase()))
				{
					clanStatements.add("INSERT INTO CMCHCL (CMUSERID, CMCLAN, CMCLRO, CMCLSTS) values ('"+mob.Name()+"','"+p.first.clanID()+"',"+p.second.intValue()+",'0;0')");
					if(CMSecurity.isDebugging(CMSecurity.DbgFlag.CLANMEMBERS))
						Log.debugOut("User '"+mob.Name()+"' was added to clan '"+p.first.clanID()+"' as role "+p.second.intValue());
				}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		if(clanStatements.size()>0)
			DB.update(clanStatements.toArray(new String[0]));
	}

	protected String getDBItemUpdateString(final MOB mob, final Item thisItem)
	{
		CMLib.catalog().updateCatalogIntegrity(thisItem);
		final String container=((thisItem.container()!=null)?(""+thisItem.container()):"");
		return "INSERT INTO CMCHIT (CMUSERID, CMITNM, CMITID, CMITTX, CMITLO, CMITWO, "
		+"CMITUR, CMITLV, CMITAB, CMHEIT"
		+") values ('"+mob.Name()+"','"+(thisItem)+"','"+thisItem.ID()+"',?,'"+container+"',"+thisItem.rawWornCode()+","
		+thisItem.usesRemaining()+","+thisItem.basePhyStats().level()+","+thisItem.basePhyStats().ability()+","
		+thisItem.basePhyStats().height()+")";
	}

	private List<DBPreparedBatchEntry> getDBItemUpdateStrings(MOB mob)
	{
		final HashSet<String> done=new HashSet<String>();
		final List<DBPreparedBatchEntry> strings=new LinkedList<DBPreparedBatchEntry>();
		for(int i=0;i<mob.numItems();i++)
		{
			final Item thisItem=mob.getItem(i);
			if((thisItem!=null)&&(!done.contains(""+thisItem))&&(thisItem.isSavable()))
			{
				CMLib.catalog().updateCatalogIntegrity(thisItem);
				final String sql=getDBItemUpdateString(mob,thisItem);
				strings.add(new DBPreparedBatchEntry(sql,thisItem.text()+" "));
				done.add(""+thisItem);
			}
		}
		final PlayerStats pStats=mob.playerStats();
		if(pStats !=null)
		{
			final ItemCollection coll=pStats.getExtItems();
			final List<Item> finalCollection=new LinkedList<Item>();
			final List<Item> extraItems=new LinkedList<Item>();
			for(int i=coll.numItems()-1;i>=0;i--)
			{
				final Item thisItem=coll.getItem(i);
				if((thisItem!=null)&&(!thisItem.amDestroyed()))
				{
					final Item cont=thisItem.ultimateContainer(null);
					if(cont.owner() instanceof Room)
						finalCollection.add(thisItem);
				}
			}
			for(final Item thisItem : finalCollection)
			{
				if(thisItem instanceof Container)
				{
					final List<Item> contents=((Container)thisItem).getDeepContents();
					for(final Item I : contents)
						if(!finalCollection.contains(I))
							extraItems.add(I);
				}
			}
			finalCollection.addAll(extraItems);
			for(final Item thisItem : finalCollection)
			{
				if(!done.contains(""+thisItem))
				{
					CMLib.catalog().updateCatalogIntegrity(thisItem);
					final Item cont=thisItem.ultimateContainer(null);
					final String sql=getDBItemUpdateString(mob,thisItem);
					final String roomID=((cont.owner()==null)&&(thisItem instanceof SpaceObject)&&(CMLib.map().isObjectInSpace((SpaceObject)thisItem)))?
							("SPACE."+CMParms.toListString(((SpaceObject)thisItem).coordinates())):CMLib.map().getExtendedRoomID((Room)cont.owner());
					final String text="<ROOM ID=\""+roomID+"\" EXPIRE="+thisItem.expirationDate()+" />"+thisItem.text();
					strings.add(new DBPreparedBatchEntry(sql,text));
					done.add(""+thisItem);
				}
			}
		}
		return strings;
	}

	public void DBUpdateItems(MOB mob)
	{
		if(mob.Name().length()==0)
			return;
		final List<DBPreparedBatchEntry> statements=new LinkedList<DBPreparedBatchEntry>();
		statements.add(new DBPreparedBatchEntry("DELETE FROM CMCHIT WHERE CMUSERID='"+mob.Name()+"'"));
		statements.addAll(getDBItemUpdateStrings(mob));
		DB.updateWithClobs(statements);
	}

	protected List<Pair<String,Integer>>[][] DBFindPrideWinners(int topThisMany, short scanCPUPercent, boolean players)
	{
		@SuppressWarnings("unchecked")
		final List<Pair<String,Integer>>[][] top=new Vector[TimeClock.TimePeriod.values().length][AccountStats.PrideStat.values().length];
		for(int x=0;x<top.length;x++)
		{
			for(int y=0;y<top[x].length;y++)
				top[x][y]=new Vector<Pair<String,Integer>>(topThisMany+1);
		}
		DBConnection D=null;
		final long msWait=Math.round(1000.0 * CMath.div(scanCPUPercent, 100));
		final long sleepAmount=1000 - msWait;
		try
		{
			long nextWaitAfter=System.currentTimeMillis() + msWait;
			final long now=System.currentTimeMillis();

			D=DB.DBFetch();
			ResultSet R;
			if(players)
				R=D.query("SELECT CMUSERID,CMPFIL FROM CMCHAR");
			else
				R=D.query("SELECT CMANAM,CMAXML FROM CMACCT");
			while((R!=null)&&(R.next()))
			{
				final String userID=DB.getRes(R, players?"CMUSERID":"CMANAM");
				String pxml;
				final MOB M=CMLib.players().getPlayer(userID);
				if((M!=null)&&(M.playerStats()!=null)&&(players))
					pxml=M.playerStats().getXML();
				else
				if((M!=null)&&(M.playerStats()!=null)&&(!players)&&(M.playerStats().getAccount()!=null))
					pxml=M.playerStats().getAccount().getXML();
				else
				if(players)
					pxml=DB.getRes(R, "CMPFIL");
				else
					pxml=DB.getRes(R, "CMAXML");
				final String[] pridePeriods=CMLib.xml().returnXMLValue(pxml, "NEXTPRIDEPERIODS").split(",");
				final String[] prideStats=CMLib.xml().returnXMLValue(pxml, "PRIDESTATS").split(";");
				final Pair<Long,int[]>[] allData = CMLib.players().parsePrideStats(pridePeriods, prideStats);
				for(final TimeClock.TimePeriod period : TimeClock.TimePeriod.values())
				{
					if(allData.length > period.ordinal())
					{
						final Pair<Long,int[]> p=allData[period.ordinal()];
						final List<Pair<String,Integer>>[] topPeriods=top[period.ordinal()];
						if((period==TimeClock.TimePeriod.ALLTIME)||(now < p.first.longValue()))
						{
							for(final AccountStats.PrideStat pride : AccountStats.PrideStat.values())
							{
								if((p.second.length>pride.ordinal())&&(p.second[pride.ordinal()]>0))
								{
									final int val=p.second[pride.ordinal()];
									final List<Pair<String,Integer>> topPrides=topPeriods[pride.ordinal()];
									final int oldSize=topPrides.size();
									for(int i=0;i<topPrides.size();i++)
									{
										if(val >= topPrides.get(i).second.intValue())
										{
											topPrides.add(i, new Pair<String,Integer>(userID,Integer.valueOf(val)));
											while(topPrides.size()>topThisMany)
												topPrides.remove(topPrides.size()-1);
											break;
										}
									}
									if((oldSize==topPrides.size())&&(topPrides.size()<topThisMany))
										topPrides.add(new Pair<String,Integer>(userID,Integer.valueOf(val)));
								}
							}
						}
					}
				}
				if((sleepAmount>0)&&(System.currentTimeMillis() > nextWaitAfter))
				{
					CMLib.s_sleep(sleepAmount);
					nextWaitAfter=System.currentTimeMillis() + msWait;
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return top;
	}

	public List<Pair<String,Integer>>[][] DBScanPridePlayerWinners(int topThisMany, short scanCPUPercent)
	{
		return DBFindPrideWinners(topThisMany,scanCPUPercent,true);
	}

	public List<Pair<String,Integer>>[][] DBScanPrideAccountWinners(int topThisMany, short scanCPUPercent)
	{
		return DBFindPrideWinners(topThisMany,scanCPUPercent,false);
	}

	// this method is unused, but is a good idea of how to collect riders, followers, carts, etc.
	protected void addFollowerDependent(PhysicalAgent P, DVector list, String parent)
	{
		if(P==null)
			return;
		if(list.contains(P))
			return;
		if((P instanceof MOB)
		&&((!((MOB)P).isMonster())||(((MOB)P).isPossessing())))
			return;
		CMLib.catalog().updateCatalogIntegrity(P);
		final String myCode=""+(list.size()-1);
		list.addElement(P,CMClass.classID(P)+"#"+myCode+parent);
		if(P instanceof Rideable)
		{
			final Rideable R=(Rideable)P;
			for(int r=0;r<R.numRiders();r++)
				addFollowerDependent(R.fetchRider(r),list,"@"+myCode+"R");
		}
		if(P instanceof Container)
		{
			final Container C=(Container)P;
			final List<Item> contents=C.getDeepContents();
			for(int c=0;c<contents.size();c++)
				addFollowerDependent(contents.get(c),list,"@"+myCode+"C");
		}

	}

	public void DBUpdateFollowers(MOB mob)
	{
		if((mob==null)||(mob.Name().length()==0))
			return;
		final List<DBPreparedBatchEntry> statements=new LinkedList<DBPreparedBatchEntry>();
		statements.add(new DBPreparedBatchEntry("DELETE FROM CMCHFO WHERE CMUSERID='"+mob.Name()+"'"));
		for(int f=0;f<mob.numFollowers();f++)
		{
			final MOB thisMOB=mob.fetchFollower(f);
			if((thisMOB!=null)&&(thisMOB.isMonster())&&(!thisMOB.isPossessing())&&(CMLib.flags().isSavable(thisMOB)))
			{
				CMLib.catalog().updateCatalogIntegrity(thisMOB);
				final String sql="INSERT INTO CMCHFO (CMUSERID, CMFONM, CMFOID, CMFOTX, CMFOLV, CMFOAB"
								+") values ('"+mob.Name()+"',"+f+",'"+CMClass.classID(thisMOB)+"',?,"
								+thisMOB.basePhyStats().level()+","+thisMOB.basePhyStats().ability()+")";
								statements.add(new DBPreparedBatchEntry(sql,thisMOB.text()+" "));
			}
		}
		DB.updateWithClobs(statements);
	}

	public void DBNameChange(String oldName, String newName)
	{
		if((oldName==null)
		||(oldName.trim().length()==0)
		||(oldName.indexOf('\'')>=0)
		||(newName==null)
		||(newName.trim().length()==0)
		||(newName.indexOf('\'')>=0))
			return;
		DB.update("UPDATE CMCHAB SET CMUSERID='"+newName+"' WHERE CMUSERID='"+oldName+"'");
		DB.update("UPDATE CMCHAR SET CMUSERID='"+newName+"' WHERE CMUSERID='"+oldName+"'");
		DB.update("UPDATE CMCHAR SET CMWORS='"+newName+"' WHERE CMWORS='"+oldName+"'");
		DB.update("UPDATE CMCHAR SET CMLEIG='"+newName+"' WHERE CMLEIG='"+oldName+"'");
		DB.update("UPDATE CMCHCL SET CMUSERID='"+newName+"' WHERE CMUSERID='"+oldName+"'");

		DB.update("UPDATE CMCHFO SET CMUSERID='"+newName+"' WHERE CMUSERID='"+oldName+"'");
		DB.update("UPDATE CMCHIT SET CMUSERID='"+newName+"' WHERE CMUSERID='"+oldName+"'");
		DB.update("UPDATE CMJRNL SET CMFROM='"+newName+"' WHERE CMFROM='"+oldName+"'");
		DB.update("UPDATE CMJRNL SET CMTONM='"+newName+"' WHERE CMTONM='"+oldName+"'");
		DB.update("UPDATE CMPDAT SET CMPLID='"+newName+"' WHERE CMPLID='"+oldName+"'");
	}

	public void DBDelete(MOB mob, boolean deleteAssets)
	{
		if(mob.Name().length()==0)
			return;
		final List<String> channels=CMLib.channels().getFlaggedChannelNames(ChannelsLibrary.ChannelFlag.PLAYERPURGES);
		for(int i=0;i<channels.size();i++)
			CMLib.commands().postChannel(channels.get(i),mob.clans(),CMLib.lang().fullSessionTranslation("@x1 has just been deleted.",mob.Name()),true);
		CMLib.coffeeTables().bump(mob,CoffeeTableRow.STAT_PURGES);
		DB.update("DELETE FROM CMCHAR WHERE CMUSERID='"+mob.Name()+"'");
		DB.update("DELETE FROM CMCHCL WHERE CMUSERID='"+mob.Name()+"'");
		mob.delAllItems(false);
		for(int i=0;i<mob.numItems();i++)
		{
			final Item I=mob.getItem(i);
			if(I!=null)
				I.setContainer(null);
		}
		mob.delAllItems(false);
		DBUpdateItems(mob);
		while(mob.numFollowers()>0)
		{
			final MOB follower=mob.fetchFollower(0);
			if(follower!=null)
				follower.setFollowing(null);
		}
		if(deleteAssets)
		{
			DBUpdateFollowers(mob);
		}
		mob.delAllAbilities();
		DBUpdateAbilities(mob);
		if(deleteAssets)
		{
			CMLib.database().DBDeletePlayerJournals(mob.Name());
			CMLib.database().DBDeletePlayerData(mob.Name());
		}
		final PlayerStats pstats = mob.playerStats();
		if(pstats!=null)
		{
			final PlayerAccount account = pstats.getAccount();
			if(account != null)
			{
				account.delPlayer(mob);
				DBUpdateAccount(account);
				account.setLastUpdated(System.currentTimeMillis());
			}
		}
		if(deleteAssets)
		{
			for(int q=0;q<CMLib.quests().numQuests();q++)
			{
				final Quest Q=CMLib.quests().fetchQuest(q);
				if(Q.wasWinner(mob.Name()))
					Q.declareWinner("-"+mob.Name());
			}
		}
	}

	public void DBUpdateAbilities(MOB mob)
	{
		if(mob.Name().length()==0)
			return;
		final List<DBPreparedBatchEntry> statements=new LinkedList<DBPreparedBatchEntry>();
		statements.add(new DBPreparedBatchEntry("DELETE FROM CMCHAB WHERE CMUSERID='"+mob.Name()+"'"));
		final HashSet<String> H=new HashSet<String>();
		for(int a=0;a<mob.numAbilities();a++)
		{
			final Ability thisAbility=mob.fetchAbility(a);
			if((thisAbility!=null)&&(thisAbility.isSavable()))
			{
				int proficiency=thisAbility.proficiency();
				final Ability effectA=mob.fetchEffect(thisAbility.ID());
				if(effectA!=null)
				{
					if((effectA.isSavable())&&(!effectA.canBeUninvoked())&&(!effectA.isAutoInvoked()))
						proficiency=proficiency-200;
				}
				H.add(thisAbility.ID());
				final String sql="INSERT INTO CMCHAB (CMUSERID, CMABID, CMABPF,CMABTX"
				+") values ('"+mob.Name()+"','"+thisAbility.ID()+"',"+proficiency+",?)";
				statements.add(new DBPreparedBatchEntry(sql,thisAbility.text()));
			}
		}
		for(final Enumeration<Ability> a=mob.personalEffects();a.hasMoreElements();)
		{
			final Ability A=a.nextElement();
			if((A!=null)&&(!H.contains(A.ID()))&&(A.isSavable())&&(!A.canBeUninvoked()))
			{
				final String sql="INSERT INTO CMCHAB (CMUSERID, CMABID, CMABPF,CMABTX"
				+") values ('"+mob.Name()+"','"+A.ID()+"',"+Integer.MAX_VALUE+",?)";
				statements.add(new DBPreparedBatchEntry(sql,A.text()));
			}
		}
		for(final Enumeration<Behavior> e=mob.behaviors();e.hasMoreElements();)
		{
			final Behavior B=e.nextElement();
			if((B!=null)&&(B.isSavable()))
			{
				final String sql="INSERT INTO CMCHAB (CMUSERID, CMABID, CMABPF,CMABTX"
				+") values ('"+mob.Name()+"','"+B.ID()+"',"+(Integer.MIN_VALUE+1)+",?"
				+")";
				statements.add(new DBPreparedBatchEntry(sql,B.getParms()));
			}
		}
		final String scriptStuff = CMLib.coffeeMaker().getGenScripts(mob,true);
		if(scriptStuff.length()>0)
		{
			final String sql="INSERT INTO CMCHAB (CMUSERID, CMABID, CMABPF,CMABTX"
			+") values ('"+mob.Name()+"','ScriptingEngine',"+(Integer.MIN_VALUE+1)+",?"
			+")";
			statements.add(new DBPreparedBatchEntry(sql,scriptStuff));
		}

		DB.updateWithClobs(statements);
	}

	public void DBCreateCharacter(MOB mob)
	{
		if(mob.Name().length()==0)
			return;
		final PlayerStats pstats=mob.playerStats();
		if(pstats==null)
			return;
		DB.update("INSERT INTO CMCHAR (CMCHID, CMUSERID, CMPASS, CMCLAS, CMRACE, CMGEND "
				+") VALUES ('"+mob.ID()+"','"+mob.Name()+"','"+pstats.getPasswordStr()+"','"+mob.baseCharStats().getMyClassesStr()
				+"','"+mob.baseCharStats().getMyRace().ID()+"','"+((char)mob.baseCharStats().getStat(CharStats.STAT_GENDER))
				+"')");
		final PlayerAccount account = pstats.getAccount();
		if(account != null)
		{
			account.addNewPlayer(mob);
			DBUpdateAccount(account);
			account.setLastUpdated(System.currentTimeMillis());
		}
	}

	public void DBUpdateAccount(PlayerAccount account)
	{
		if(account == null)
			return;
		final String characters = CMParms.toSemicolonListString(account.getPlayers());
		DB.updateWithClobs("UPDATE CMACCT SET CMPASS='"+account.getPasswordStr()+"',  CMCHRS=?,  CMAXML=?  WHERE CMANAM='"+account.getAccountName()+"'",
				new String[][]{{characters,account.getXML()}});
	}

	public void DBDeleteAccount(PlayerAccount account)
	{
		if(account == null)
			return;
		DB.update("DELETE FROM CMACCT WHERE CMANAM='"+account.getAccountName()+"'");
	}

	public void DBCreateAccount(PlayerAccount account)
	{
		if(account == null)
			return;
		account.setAccountName(CMStrings.capitalizeAndLower(account.getAccountName()));
		final String characters = CMParms.toSemicolonListString(account.getPlayers());
		DB.updateWithClobs("INSERT INTO CMACCT (CMANAM, CMPASS, CMCHRS, CMAXML) "
				+"VALUES ('"+account.getAccountName()+"','"+account.getPasswordStr()+"',?,?)",new String[][]{{characters,account.getXML()}});
	}

	public PlayerAccount MakeAccount(String username, ResultSet R) throws SQLException
	{
		PlayerAccount account = null;
		account = (PlayerAccount)CMClass.getCommon("DefaultPlayerAccount");
		final String password=DB.getRes(R,"CMPASS");
		final String chrs=DB.getRes(R,"CMCHRS");
		final String xml=DB.getRes(R,"CMAXML");
		final Vector<String> names = new Vector<String>();
		if(chrs!=null)
			names.addAll(CMParms.parseSemicolons(chrs,true));
		account.setAccountName(CMStrings.capitalizeAndLower(username));
		account.setPassword(password);
		account.setPlayerNames(names);
		account.setXML(xml);
		return account;
	}

	public PlayerAccount DBReadAccount(String Login)
	{
		DBConnection D=null;
		PlayerAccount account = null;
		try
		{
			// why in the hell is this a memory scan?
			// case insensitivity from databases configured almost
			// certainly by amateurs is the answer. That, and fakedb
			// doesn't understand 'LIKE'
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMACCT WHERE CMANAM='"+CMStrings.replaceAll(CMStrings.capitalizeAndLower(Login),"\'", "n")+"'");
			if(R!=null) while(R.next())
			{
				final String username=DB.getRes(R,"CMANAM");
				if(Login.equalsIgnoreCase(username))
					account = MakeAccount(username,R);
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return account;
	}

	public List<PlayerAccount> DBListAccounts(String mask)
	{
		DBConnection D=null;
		PlayerAccount account = null;
		final Vector<PlayerAccount> accounts = new Vector<PlayerAccount>();
		if(mask!=null)
			mask=mask.toLowerCase();
		try
		{
			// why in the hell is this a memory scan?
			// case insensitivity from databases configured almost
			// certainly by amateurs is the answer. That, and fakedb
			// doesn't understand 'LIKE'
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMACCT");
			if(R!=null) while(R.next())
			{
				final String username=DB.getRes(R,"CMANAM");
				if((mask==null)||(mask.length()==0)||(username.toLowerCase().indexOf(mask)>=0))
				{
					account = MakeAccount(username,R);
					accounts.add(account);
				}
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return accounts;
	}

	public List<String> DBExpiredCharNameSearch(Set<String> skipNames)
	{
		DBConnection D=null;
		String buf=null;
		final List<String> expiredPlayers = new ArrayList<String>();
		final long now=System.currentTimeMillis();
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR");
			if(R!=null) while(R.next())
			{
				final String username=DB.getRes(R,"CMUSERID");
				if((skipNames!=null)&&(skipNames.contains(username)))
					continue;
				buf=DBConnections.getRes(R,"CMPFIL");
				final String secGrps=CMLib.xml().restoreAngleBrackets(CMLib.xml().returnXMLValue(buf,"SECGRPS"));
				final SecGroup g=CMSecurity.instance().createGroup("", CMParms.parseSemicolons(secGrps,true));
				if(g.contains(SecFlag.NOEXPIRE, false))
					continue;
				long expiration;
				if(CMLib.xml().returnXMLValue(buf,"ACCTEXP").length()>0)
					expiration=CMath.s_long(CMLib.xml().returnXMLValue(buf,"ACCTEXP"));
				else
				{
					final Calendar C=Calendar.getInstance();
					C.add(Calendar.DATE,CMProps.getIntVar(CMProps.Int.TRIALDAYS));
					expiration=C.getTimeInMillis();
				}
				if(now>=expiration)
					expiredPlayers.add(username);
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return expiredPlayers;
	}

	public PlayerLibrary.ThinnerPlayer DBUserSearch(String Login)
	{
		DBConnection D=null;
		String buf=null;
		PlayerLibrary.ThinnerPlayer thinPlayer = null;
		try
		{
			// why in the hell is this a memory scan?
			// case insensitivity from databases configured almost
			// certainly by amateurs is the answer. That, and fakedb
			// doesn't understand 'LIKE'
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMUSERID='"+CMStrings.capitalizeAndLower(Login).replace('\'', 'n')+"'");
			if(R!=null) while(R.next())
			{
				final String username=DB.getRes(R,"CMUSERID");
				thinPlayer = new PlayerLibrary.ThinnerPlayer();
				final String password=DB.getRes(R,"CMPASS");
				final String email=DB.getRes(R,"CMEMAL");
				thinPlayer.name=username;
				thinPlayer.password=password;
				thinPlayer.email=email;
				// Acct Exp Code
				buf=DBConnections.getRes(R,"CMPFIL");
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		if((buf!=null)&&(thinPlayer!=null))
		{
			PlayerAccount acct = null;
			thinPlayer.accountName = CMLib.xml().returnXMLValue(buf,"ACCOUNT");
			if((thinPlayer.accountName!=null)&&(thinPlayer.accountName.length()>0))
				acct = CMLib.players().getLoadAccount(thinPlayer.accountName);
			if((acct != null)&&(CMProps.isUsingAccountSystem()))
				thinPlayer.expiration=acct.getAccountExpiration();
			else
			if(CMLib.xml().returnXMLValue(buf,"ACCTEXP").length()>0)
				thinPlayer.expiration=CMath.s_long(CMLib.xml().returnXMLValue(buf,"ACCTEXP"));
			else
			{
				final Calendar C=Calendar.getInstance();
				C.add(Calendar.DATE,CMProps.getIntVar(CMProps.Int.TRIALDAYS));
				thinPlayer.expiration=C.getTimeInMillis();
			}
		}
		return thinPlayer;
	}

	public String[] DBFetchEmailData(String name)
	{
		final String[] data=new String[2];
		for(final Enumeration<MOB> e=CMLib.players().players();e.hasMoreElements();)
		{
			final MOB M=e.nextElement();
			if((M.Name().equalsIgnoreCase(name))&&(M.playerStats()!=null))
			{
				data[0]=M.playerStats().getEmail();
				data[1]=""+M.isAttributeSet(MOB.Attrib.AUTOFORWARD);
				return data;
			}
		}
		DBConnection D=null;
		try
		{
			D=DB.DBFetch();
			final ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMUSERID='"+name.replace('\'', 'n')+"'");
			if(R!=null) while(R.next())
			{
				// String username=DB.getRes(R,"CMUSERID");
				final int btmp=CMath.s_int(DB.getRes(R,"CMBTMP"));
				final String temail=DB.getRes(R,"CMEMAL");
				data[0]=temail;
				data[1]=""+((btmp&MOB.Attrib.AUTOFORWARD.getBitCode())!=0);
				return data;
			}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return null;
	}

	public String DBPlayerEmailSearch(String email)
	{
		DBConnection D=null;
		for(final Enumeration<MOB> e=CMLib.players().players();e.hasMoreElements();)
		{
			final MOB M=e.nextElement();
			if((M.playerStats()!=null)&&(M.playerStats().getEmail().equalsIgnoreCase(email)))
				return M.Name();
		}
		try
		{
			D=DB.DBFetch();
			email=DB.injectionClean(email.trim());
			ResultSet R=D.query("SELECT * FROM CMCHAR WHERE CMEMAL='"+email+"'");
			if(((R==null)||(!R.next()))&&(!CMStrings.isLowerCase(email)))
				R=D.query("SELECT * FROM CMCHAR WHERE CMEMAL='"+email.toLowerCase()+"'");
			if((R==null)||(!R.next()))
				R=D.query("SELECT * FROM CMCHAR WHERE CMEMAL LIKE '"+email+"'");
			if((R==null)||(!R.next()))
				R=D.query("SELECT * FROM CMCHAR");
			if(R!=null)
				while(R.next())
				{
					final String username=DB.getRes(R,"CMUSERID");
					final String temail=DB.getRes(R,"CMEMAL");
					if(temail.equalsIgnoreCase(email))
					{
						return username;
					}
				}
		}
		catch(final Exception sqle)
		{
			Log.errOut("MOB",sqle);
		}
		finally
		{
			DB.DBDone(D);
		}
		return null;
	}
}