/
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.Libraries;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.DbgFlag;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.core.collections.MultiEnumeration.MultiEnumeratorBuilder;
import com.planet_ink.coffee_mud.core.interfaces.BoundedObject;
import com.planet_ink.coffee_mud.core.interfaces.BoundedObject.BoundedCube;
import com.planet_ink.coffee_mud.core.interfaces.MsgListener;
import com.planet_ink.coffee_mud.core.interfaces.PhysicalAgent;
import com.planet_ink.coffee_mud.core.interfaces.PrivateProperty;
import com.planet_ink.coffee_mud.core.interfaces.SpaceObject;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
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.Items.interfaces.TechComponent.ShipDir;
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.IOException;
import java.lang.ref.WeakReference;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
/*
   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 CMMap extends StdLibrary implements WorldMap
{
	@Override public String ID(){return "CMMap";}
	public final int			QUADRANT_WIDTH  		= 10;
	public static MOB   		deityStandIn			= null;
	public long 				lastVReset  			= 0;
	public CMNSortSVec<Area>	areasList   			= new CMNSortSVec<Area>();
	public List<Deity>  		deitiesList 			= new SVector<Deity>();
	public List<BoardableShip>	shipList 				= new SVector<BoardableShip>();
	public List<PostOffice> 	postOfficeList  		= new SVector<PostOffice>();
	public List<Auctioneer> 	auctionHouseList		= new SVector<Auctioneer>();
	public List<Banker> 		bankList				= new SVector<Banker>();
	public RTree<SpaceObject>	space					= new RTree<SpaceObject>();
	protected Map<String,Object>SCRIPT_HOST_SEMAPHORES	= new Hashtable<String,Object>();

	protected static final Comparator<Area>	areaComparator = new Comparator<Area>()
	{
		@Override 
		public int compare(Area o1, Area o2)
		{
			if(o1==null)
				return (o2==null)?0:-1;
			return o1.Name().compareToIgnoreCase(o2.Name());
		}
	};

	public Map<Integer,List<WeakReference<MsgListener>>>
								globalHandlers   		= new SHashtable<Integer,List<WeakReference<MsgListener>>>();
	public Map<String,SLinkedList<LocatedPair>>
								scriptHostMap			= new STreeMap<String,SLinkedList<LocatedPair>>();

	private static final long EXPIRE_1MIN	= 1*60*1000;
	private static final long EXPIRE_5MINS	= 5*60*1000;
	private static final long EXPIRE_10MINS	= 10*60*1000;
	private static final long EXPIRE_20MINS	= 20*60*1000;
	private static final long EXPIRE_30MINS	= 30*60*1000;
	private static final long EXPIRE_1HOUR	= 60*60*1000;

	private static class LocatedPairImpl implements LocatedPair
	{
		final WeakReference<Room> roomW;
		final WeakReference<PhysicalAgent> objW;
		
		private LocatedPairImpl(Room room, PhysicalAgent host)
		{
			roomW = new WeakReference<Room>(room);
			objW = new WeakReference<PhysicalAgent>(host);
		}
		@Override
		public Room room()
		{
			return roomW.get();
		}

		@Override
		public PhysicalAgent obj()
		{
			return objW.get();
		}
	}
	
	private static Filterer<Area> planetsAreaFilter=new Filterer<Area>()
	{
		@Override
		public boolean passesFilter(Area obj)
		{
			return (obj instanceof SpaceObject) && (!(obj instanceof SpaceShip));
		}
	};

	private static Filterer<Area> mundaneAreaFilter=new Filterer<Area>()
	{
		@Override
		public boolean passesFilter(Area obj)
		{
			return !(obj instanceof SpaceObject);
		}
	};
	
	protected int getGlobalIndex(List<Environmental> list, String name)
	{
		if(list.size()==0)
			return -1;
		int start=0;
		int end=list.size()-1;
		while(start<=end)
		{
			final int mid=(end+start)/2;
			final int comp=list.get(mid).Name().compareToIgnoreCase(name);
			if(comp==0)
				return mid;
			else
			if(comp>0)
				end=mid-1;
			else
				start=mid+1;

		}
		return -1;
	}

	@Override
	public void renamedArea(Area theA)
	{
		areasList.reSort(theA);
	}

	// areas
	@Override
	public int numAreas() 
	{
		return areasList.size();
	}

	@Override
	public void addArea(Area newOne)
	{
		areasList.add(newOne);
		if((newOne instanceof SpaceObject)&&(!space.contains((SpaceObject)newOne)))
			space.insert((SpaceObject)newOne);
	}

	@Override
	public void delArea(Area oneToDel)
	{
		areasList.remove(oneToDel);
		if((oneToDel instanceof SpaceObject)&&(space.contains((SpaceObject)oneToDel)))
			space.remove((SpaceObject)oneToDel);
		Resources.removeResource("SYSTEM_AREA_FINDER_CACHE");
	}

	@Override
	public Area getModelArea(Area A)
	{
		if(A!=null)
		{
			if(CMath.bset(A.flags(),Area.FLAG_INSTANCE_CHILD))
			{
				int x=A.Name().indexOf('_');
				if(x>0)
				{
					Area A2=getArea(A.Name().substring(x+1));
					if(A2!=null)
						return A2;
				}
			}
		}
		return A;
	}
	
	@Override
	public Area getArea(String calledThis)
	{
		final Map<String,Area> finder=getAreaFinder();
		Area A=finder.get(calledThis.toLowerCase());
		if((A!=null)&&(!A.amDestroyed()))
			return A;
		A=areasList.find(calledThis);
		if((A!=null)&&(!A.amDestroyed()))
		{
			if(!CMProps.getBoolVar(CMProps.Bool.MAPFINDSNOCACHE))
				finder.put(calledThis.toLowerCase(), A);
			return A;
		}
		return null;
	}
	
	@Override
	public Area findAreaStartsWith(String calledThis)
	{
		final boolean disableCaching=CMProps.getBoolVar(CMProps.Bool.MAPFINDSNOCACHE);
		Area A=getArea(calledThis);
		if(A!=null)
			return A;
		final Map<String,Area> finder=getAreaFinder();
		A=finder.get(calledThis.toLowerCase());
		if((A!=null)&&(!A.amDestroyed()))
			return A;
		for(final Enumeration<Area> a=areas();a.hasMoreElements();)
		{
			A=a.nextElement();
			if(A.Name().toUpperCase().startsWith(calledThis))
			{
				if(!disableCaching)
					finder.put(calledThis.toLowerCase(), A);
				return A;
			}
		}
		return null;
	}

	@Override
	public Area findArea(String calledThis)
	{
		final boolean disableCaching=CMProps.getBoolVar(CMProps.Bool.MAPFINDSNOCACHE);
		Area A=findAreaStartsWith(calledThis);
		if(A!=null)
			return A;
		final Map<String,Area> finder=getAreaFinder();
		A=finder.get(calledThis.toLowerCase());
		if((A!=null)&&(!A.amDestroyed()))
			return A;
		for(final Enumeration<Area> a=areas();a.hasMoreElements();)
		{
			A=a.nextElement();
			if(CMLib.english().containsString(A.Name(),calledThis))
			{
				if(!disableCaching)
					finder.put(calledThis.toLowerCase(), A);
				return A;
			}
		}
		return null;
	}
	
	@Override 
	public Enumeration<Area> areas() 
	{
		return new IteratorEnumeration<Area>(areasList.iterator());
	}
	
	@Override
    public Enumeration<Area> areasPlusShips() 
	{
		final MultiEnumeration<Area> m=new MultiEnumeration<Area>(new IteratorEnumeration<Area>(areasList.iterator()));
		m.addEnumeration(shipAreaEnumerator(null));
		return m;
	}
	
	@Override 
	public Enumeration<Area> mundaneAreas() 
	{
		return new FilteredEnumeration<Area>(areas(),mundaneAreaFilter);
	}
	
	@Override 
	public Enumeration<Area> spaceAreas() 
	{
		return new FilteredEnumeration<Area>(areas(),planetsAreaFilter);
	}

	@Override 
	public Enumeration<String> roomIDs()
	{
		return new Enumeration<String>()
		{
			private volatile Enumeration<String> roomIDEnumerator=null;
			private volatile Enumeration<Area> areaEnumerator=areasPlusShips();
			
			@Override
			public boolean hasMoreElements()
			{
				boolean hasMore = (roomIDEnumerator != null) && roomIDEnumerator.hasMoreElements();
				while(!hasMore)
				{
					if((areaEnumerator == null)||(!areaEnumerator.hasMoreElements()))
					{
						roomIDEnumerator=null;
						areaEnumerator = null;
						return false;
					}
					final Area A=areaEnumerator.nextElement();
					roomIDEnumerator=A.getProperRoomnumbers().getRoomIDs();
					hasMore = (roomIDEnumerator != null) && roomIDEnumerator.hasMoreElements();
				}
				return hasMore;
			}

			@Override
			public String nextElement()
			{
				return hasMoreElements() ? (String) roomIDEnumerator.nextElement() : null;
			}
		};
	}
	
	@Override
	public Area getFirstArea()
	{
		if (areas().hasMoreElements())
			return areas().nextElement();
		return null;
	}
	
	@Override
	public Area getDefaultParentArea()
	{
		final String defaultParentAreaName=CMProps.getVar(CMProps.Str.DEFAULTPARENTAREA);
		if((defaultParentAreaName!=null)&&(defaultParentAreaName.trim().length()>0))
			return getArea(defaultParentAreaName.trim());
		return null;
	}
	
	@Override
	public Area getRandomArea()
	{
		Area A=null;
		while((numAreas()>0)&&(A==null))
		{
			try
			{
				A=areasList.get(CMLib.dice().roll(1,numAreas(),-1));
			}catch(final ArrayIndexOutOfBoundsException e){}
		}
		return A;
	}

	@Override
	public void addGlobalHandler(MsgListener E, int category)
	{
		if(E==null)
			return;
		List<WeakReference<MsgListener>> V=globalHandlers.get(Integer.valueOf(category));
		if(V==null)
		{
			V=new SLinkedList<WeakReference<MsgListener>>();
			globalHandlers.put(Integer.valueOf(category),V);
		}
		synchronized(V)
		{
			for (final WeakReference<MsgListener> W : V)
			{
				if(W.get()==E)
					return;
			}
			V.add(new WeakReference<MsgListener>(E));
		}
	}

	@Override
	public void delGlobalHandler(MsgListener E, int category)
	{
		final List<WeakReference<MsgListener>> V=globalHandlers.get(Integer.valueOf(category));
		if((E==null)||(V==null))
			return;
		synchronized(V)
		{
			for (final WeakReference<MsgListener> W : V)
			{
				if(W.get()==E)
					V.remove(W);
			}
		}
	}
	@Override
	public MOB deity()
	{
		if(deities().hasMoreElements())
			return deities().nextElement();
		if((deityStandIn==null)
		||(deityStandIn.amDestroyed())
		||(deityStandIn.amDead())
		||(deityStandIn.location()==null)
		||(deityStandIn.location().isInhabitant(deityStandIn)))
		{
			if(deityStandIn!=null)
				deityStandIn.destroy();
			final MOB everywhereMOB=CMClass.getMOB("StdMOB");
			everywhereMOB.setName(L("god"));
			everywhereMOB.setLocation(this.getRandomRoom());
			deityStandIn=everywhereMOB;
		}
		return deityStandIn;
	}

	@Override
	public MOB getFactoryMOBInAnyRoom()
	{
		return getFactoryMOB(this.getRandomRoom());
	}

	@Override
	public MOB getFactoryMOB(Room R)
	{
		final MOB everywhereMOB=CMClass.getFactoryMOB();
		everywhereMOB.setName(L("somebody"));
		everywhereMOB.setLocation(R);
		return everywhereMOB;
	}

	@Override
	public boolean isObjectInSpace(SpaceObject O)
	{
		synchronized(space)
		{
			return space.contains(O);
		}
	}

	@Override
	public void delObjectInSpace(SpaceObject O)
	{
		synchronized(space)
		{
			space.remove(O);
		}
	}

	@Override
	public void addObjectToSpace(SpaceObject O, long[] coords)
	{
		synchronized(space)
		{
			O.coordinates()[0]=coords[0];
			O.coordinates()[1]=coords[1];
			O.coordinates()[2]=coords[2];
			space.insert(O); // won't accept dups, so is ok
		}
	}

	@Override
	public long getDistanceFrom(final long[] coord1, final long[] coord2)
	{
		return Math.round(Math.sqrt(CMath.mul((coord1[0]-coord2[0]),(coord1[0]-coord2[0]))
									+CMath.mul((coord1[1]-coord2[1]),(coord1[1]-coord2[1]))
									+CMath.mul((coord1[2]-coord2[2]),(coord1[2]-coord2[2]))));
	}
	
	@Override
	public long getDistanceFrom(SpaceObject O1, SpaceObject O2)
	{
		return getDistanceFrom(O1.coordinates(),O2.coordinates());
	}

	@Override
	public String getSectorName(long[] coordinates)
	{
		final String[] xsecs=CMProps.getListFileStringList(CMProps.ListFile.TECH_SECTOR_X_NAMES);
		final String[] ysecs=CMProps.getListFileStringList(CMProps.ListFile.TECH_SECTOR_Y_NAMES);
		final String[] zsecs=CMProps.getListFileStringList(CMProps.ListFile.TECH_SECTOR_Z_NAMES);
		
		final long dmsPerXSector = SpaceObject.Distance.GalaxyRadius.dm / xsecs.length;
		final long dmsPerYSector = SpaceObject.Distance.GalaxyRadius.dm / ysecs.length;
		final long dmsPerZSector = SpaceObject.Distance.GalaxyRadius.dm / zsecs.length;
		
		final int secDeX = (int)((coordinates[0] % SpaceObject.Distance.GalaxyRadius.dm) / dmsPerXSector / 2);
		final int secDeY = (int)((coordinates[1] % SpaceObject.Distance.GalaxyRadius.dm) / dmsPerYSector / 2);
		final int secDeZ = (int)((coordinates[2] % SpaceObject.Distance.GalaxyRadius.dm) / dmsPerZSector / 2);
		
		final StringBuilder sectorName = new StringBuilder("");
		sectorName.append(xsecs[(secDeX < 0)?(xsecs.length+secDeX):secDeX]).append(" ");
		sectorName.append(ysecs[(secDeY < 0)?(ysecs.length+secDeY):secDeY]).append(" ");
		sectorName.append(zsecs[(secDeZ < 0)?(zsecs.length+secDeZ):secDeZ]);
		return sectorName.toString();
	}
	
	@Override
	public long[] getInSectorCoords(long[] coordinates)
	{
		final String[] xsecs=CMProps.getListFileStringList(CMProps.ListFile.TECH_SECTOR_X_NAMES);
		final String[] ysecs=CMProps.getListFileStringList(CMProps.ListFile.TECH_SECTOR_Y_NAMES);
		final String[] zsecs=CMProps.getListFileStringList(CMProps.ListFile.TECH_SECTOR_Z_NAMES);
		
		final long dmsPerXSector = SpaceObject.Distance.GalaxyRadius.dm / xsecs.length;
		final long dmsPerYSector = SpaceObject.Distance.GalaxyRadius.dm / ysecs.length;
		final long dmsPerZSector = SpaceObject.Distance.GalaxyRadius.dm / zsecs.length;
		
		final int secDeX = (int)((coordinates[0] % SpaceObject.Distance.GalaxyRadius.dm) / dmsPerXSector / (2 * (coordinates[0]<0?-1:1)));
		final int secDeY = (int)((coordinates[1] % SpaceObject.Distance.GalaxyRadius.dm) / dmsPerYSector / (2 * (coordinates[0]<0?-1:1)));
		final int secDeZ = (int)((coordinates[2] % SpaceObject.Distance.GalaxyRadius.dm) / dmsPerZSector / (2 * (coordinates[0]<0?-1:1)));
		
		long[] sectorCoords = Arrays.copyOf(coordinates, 3);
		for(int i=0;i<sectorCoords.length;i++)
		{
			if(sectorCoords[i]<0)
				sectorCoords[i]*=-1;
		}
		sectorCoords[0] -= (secDeX * dmsPerXSector) / 1000;
		sectorCoords[1] -= (secDeY * dmsPerYSector) / 1000;
		sectorCoords[2] -= (secDeZ * dmsPerZSector) / 1000;
		return sectorCoords;
	}
	
	@Override
	public void moveSpaceObject(SpaceObject O, double[] newDirection, long newAccelleration)
	{
		final double directionYaw = O.direction()[0];
		double directionPitch = O.direction()[1];
		if(directionPitch > Math.PI)
			directionPitch = Math.abs(Math.PI-directionPitch);

		final double facingYaw = newDirection[0];
		double facingPitch = newDirection[1];
		if(facingPitch > Math.PI)
			facingPitch = Math.abs(Math.PI-facingPitch);

		final double currentSpeed = O.speed();
		final double acceleration = newAccelleration;

		final double directionCombinedAngle = directionYaw + directionPitch;
		final double facingCombinedAngle = facingYaw + facingPitch;
		final double anglesDelta = (directionCombinedAngle >  facingCombinedAngle) ? (directionCombinedAngle - facingCombinedAngle) : (facingCombinedAngle - directionCombinedAngle);
		final double yawDelta = (directionYaw >  facingYaw) ? (directionYaw - facingYaw) : (facingYaw - directionYaw);
		final double pitchDelta = (directionPitch >  facingPitch) ? (directionPitch - facingPitch) : (facingPitch - directionPitch);
		final double accelerationMultiplier = acceleration / currentSpeed;

		double newDirectionYaw;
		if(yawDelta < 0.1)
			newDirectionYaw = directionYaw;
		else
			newDirectionYaw = directionYaw + ((directionYaw > facingYaw) ? -(accelerationMultiplier * Math.sin(yawDelta)) : (accelerationMultiplier * Math.sin(yawDelta)));
		if (newDirectionYaw < 0.0)
			newDirectionYaw = (Math.PI * 2.0) + newDirectionYaw;
		double newDirectionPitch;
		if(pitchDelta < 0.1)
			newDirectionPitch = directionPitch;
		else
			newDirectionPitch = directionPitch + ((directionPitch > facingPitch) ? -(accelerationMultiplier * Math.sin(pitchDelta)) : (accelerationMultiplier * Math.sin(pitchDelta)));
		if (newDirectionPitch < 0.0)
			newDirectionPitch = (Math.PI * 2.0) + newDirectionPitch;

		double newSpeed = currentSpeed + (acceleration * Math.cos(anglesDelta));
		if(newSpeed < 0)
		{
			newSpeed = -newSpeed;
			newDirectionYaw = facingYaw;
			newDirectionPitch = facingPitch;
		}

		if((O.direction()[0]>Math.PI)&&(O.direction()[1]<=Math.PI))
			newDirectionPitch=Math.PI+newDirectionPitch;

		O.direction()[0]=newDirectionYaw;
		O.direction()[1]=newDirectionPitch;
		O.setSpeed(newSpeed);
	}

	@Override
	public TechComponent.ShipDir getDirectionFromDir(double[] facing, double roll, double[] direction)
	{

		double yD = ((Math.toDegrees(facing[0]) % 360.0) - (Math.toDegrees(facing[0]) % 360.0)) % 360.0;
		if(yD < 0)
			yD = 360.0 + yD;
		double pD = ((Math.toDegrees(facing[1]) % 180.0) - (Math.toDegrees(direction[1]) % 180.0)) % 180.0;
		if(pD < 0)
			pD = 180.0 + pD;
		double rD = (yD + (Math.toDegrees(roll) % 360.0)) % 360.0;
		if(rD < 0)
			rD = 360.0 + rD;
		if(pD<45 || pD > 135)
		{
			if(yD < 45.0 || yD > 315.0)
				return ShipDir.AFT;
			if(yD> 135.0 && yD < 225.0)
				return ShipDir.FORWARD;
		}
		if(rD >= 315.0 || rD<45.0)
			return ShipDir.DORSEL;
		if(rD >= 45.0 && rD <135.0)
			return ShipDir.STARBOARD;
		if(rD >= 135.0 && rD <225.0)
			return ShipDir.VENTRAL;
		if(rD >= 225.0 && rD <315.0)
			return ShipDir.PORT;
		return ShipDir.AFT;
	}
	
	@Override
	public double[] getDirection(SpaceObject FROM, SpaceObject TO)
	{
		final double[] dir=new double[2];
		final double x=TO.coordinates()[0]-FROM.coordinates()[0];
		final double y=TO.coordinates()[1]-FROM.coordinates()[1];
		final double z=TO.coordinates()[2]-FROM.coordinates()[2];
		if((x!=0)||(y!=0))
		{
			if(x<0)
				dir[0]=Math.PI-Math.asin(y/Math.sqrt((x*x)+(y*y)));
			else
				dir[0]=Math.asin(y/Math.sqrt((x*x)+(y*y)));
		}
		if((x!=0)||(y!=0)||(z!=0))
			dir[1]=Math.acos(z/Math.sqrt((z*z)+(y*y)+(x*x)));
		if(dir[1] > Math.PI)
			dir[1] = Math.abs(Math.PI-dir[1]);
		return dir;
	}

	protected void moveSpaceObject(SpaceObject O, long x, long y, long z)
	{
		synchronized(space)
		{
			final boolean reAdd=space.contains(O);
			if(reAdd)
				space.remove(O);
			O.coordinates()[0]=x;
			O.coordinates()[1]=y;
			O.coordinates()[2]=z;
			if(reAdd)
				space.insert(O);
		}
	}

	@Override
	public void moveSpaceObject(SpaceObject O, long[] coords)
	{
		moveSpaceObject(O, coords[0], coords[1], coords[2]);
	}

	@Override
	public void moveSpaceObject(SpaceObject O)
	{
		if(O.speed()>0)
		{
			final double x1=Math.cos(O.direction()[0])*Math.sin(O.direction()[1]);
			final double y1=Math.sin(O.direction()[0])*Math.sin(O.direction()[1]);
			final double z1=Math.cos(O.direction()[1]);
			moveSpaceObject(O,O.coordinates()[0]+Math.round(CMath.mul(O.speed(),x1)),
							O.coordinates()[1]+Math.round(CMath.mul(O.speed(),y1)),
							O.coordinates()[2]+Math.round(CMath.mul(O.speed(),z1)));
		}
	}

	@Override
	public long[] moveSpaceObject(final long[] coordinates, final double[] direction, long speed)
	{
		if(speed>0)
		{
			final double x1=Math.cos(direction[0])*Math.sin(direction[1]);
			final double y1=Math.sin(direction[0])*Math.sin(direction[1]);
			final double z1=Math.cos(direction[1]);
			return new long[]{coordinates[0]+Math.round(CMath.mul(speed,x1)),
							coordinates[1]+Math.round(CMath.mul(speed,y1)),
							coordinates[2]+Math.round(CMath.mul(speed,z1))};
		}
		return coordinates;
	}

	@Override
	public long[] getLocation(long[] oldLocation, double[] direction, long distance)
	{
		final double x1=Math.cos(direction[0])*Math.sin(direction[1]);
		final double y1=Math.sin(direction[0])*Math.sin(direction[1]);
		final double z1=Math.cos(direction[1]);
		final long[] location=new long[3];
		location[0]=oldLocation[0]+Math.round(CMath.mul(distance,x1));
		location[1]=oldLocation[1]+Math.round(CMath.mul(distance,y1));
		location[2]=oldLocation[2]+Math.round(CMath.mul(distance,z1));
		return location;
	}

	@Override
	public long getRelativeSpeed(SpaceObject O1, SpaceObject O2)
	{
		return Math.round(Math.sqrt(( CMath.bigMultiply(O1.speed(),O1.coordinates()[0])
										.subtract(CMath.bigMultiply(O2.speed(),O2.coordinates()[0]).multiply(CMath.bigMultiply(O1.speed(),O1.coordinates()[0])))
										.subtract(CMath.bigMultiply(O2.speed(),O2.coordinates()[0])))
									.add(CMath.bigMultiply(O1.speed(),O1.coordinates()[1])
										.subtract(CMath.bigMultiply(O2.speed(),O2.coordinates()[1]).multiply(CMath.bigMultiply(O1.speed(),O1.coordinates()[1])))
										.subtract(CMath.bigMultiply(O2.speed(),O2.coordinates()[1])))
									.add(CMath.bigMultiply(O1.speed(),O1.coordinates()[2])
										.subtract(CMath.bigMultiply(O2.speed(),O2.coordinates()[2]).multiply(CMath.bigMultiply(O1.speed(),O1.coordinates()[2])))
										.subtract(CMath.bigMultiply(O2.speed(),O2.coordinates()[2]))).doubleValue()));
	}

	@Override
	public SpaceObject findSpaceObject(String s, boolean exactOnly)
	{
		final Iterable<SpaceObject> i=new Iterable<SpaceObject>()
		{
			@Override
			public Iterator<SpaceObject> iterator()
			{
				return new EnumerationIterator<SpaceObject>(space.objects());
			}
			
		};
		return (SpaceObject)CMLib.english().fetchEnvironmental(i, s, exactOnly);
	}
	
	@Override
	public SpaceObject getSpaceObject(CMObject o, boolean ignoreMobs)
	{
		if(o instanceof SpaceObject)
			return (SpaceObject)o;
		if(o instanceof Item)
		{
			if(((Item)o).container()!=null)
				return getSpaceObject(((Item)o).container(),ignoreMobs);
			else
				return getSpaceObject(((Item)o).owner(),ignoreMobs);
		}
		if(o instanceof MOB)
			return ignoreMobs?null:getSpaceObject(((MOB)o).location(),false);
		if(o instanceof Room)
			return getSpaceObject(((Room)o).getArea(),ignoreMobs);
		if(o instanceof Area)
			for(final Enumeration<Area> a=((Area)o).getParents();a.hasMoreElements();)
			{
				final SpaceObject obj=getSpaceObject(a.nextElement(),ignoreMobs);
				if(obj != null)
					return obj;
			}
		return null;
	}

	@Override 
	public Enumeration<SpaceObject> getSpaceObjects() 
	{ 
		return this.space.objects(); 
	}

	@Override 
	public Enumeration<Entry<SpaceObject, List<WeakReference<TrackingVector<SpaceObject>>>>>  getSpaceObjectEntries() 
	{ 
		return this.space.objectEntries(); 
	}

	@Override
	public List<SpaceObject> getSpaceObjectsByCenterpointWithin(final long[] centerCoordinates, long minDistance, long maxDistance)
	{
		final List<SpaceObject> within=new ArrayList<SpaceObject>(1);
		if((centerCoordinates==null)||(centerCoordinates.length!=3))
			return within;
		synchronized(space)
		{
			space.query(within, new BoundedObject.BoundedCube(centerCoordinates, maxDistance));
		}
		if(within.size()<1)
			return within;
		for (final Iterator<SpaceObject> o=within.iterator();o.hasNext();)
		{
			SpaceObject O=o.next();
			final long dist=getDistanceFrom(O.coordinates(),centerCoordinates);
			if((dist<minDistance)||(dist>maxDistance))
				o.remove();
		}
		return within;
	}
	
	@Override
	public List<SpaceObject> getSpaceObjectsWithin(final SpaceObject ofObj, long minDistance, long maxDistance)
	{
		final List<SpaceObject> within=new ArrayList<SpaceObject>(1);
		if(ofObj==null)
			return within;
		synchronized(space)
		{
			space.query(within, new BoundedObject.BoundedCube(ofObj.coordinates(), maxDistance));
		}
		for (final Iterator<SpaceObject> o=within.iterator();o.hasNext();)
		{
			SpaceObject O=o.next();
			if(O!=ofObj)
			{
				final long dist=Math.round(Math.abs(getDistanceFrom(O,ofObj) - O.radius() - ofObj.radius()));
				if((dist<minDistance)||(dist>maxDistance))
					o.remove();
			}
		}
		if(within.size()<=1)
			return within;
		Collections.sort(within, new Comparator<SpaceObject>()
		{
			@Override public int compare(SpaceObject o1, SpaceObject o2)
			{
				final long distTo1=getDistanceFrom(o1,ofObj);
				final long distTo2=getDistanceFrom(o2,ofObj);
				if(distTo1==distTo2)
					return 0;
				return distTo1>distTo2?1:-1;
			}
		});
		return within;
	}

	@Override
	public String createNewExit(Room from, Room room, int direction)
	{
		Room opRoom=from.rawDoors()[direction];
		if((opRoom!=null)&&(opRoom.roomID().length()==0))
			opRoom=null;
		Room reverseRoom=null;
		if(opRoom!=null)
			reverseRoom=opRoom.rawDoors()[Directions.getOpDirectionCode(direction)];

		if((reverseRoom!=null)&&(reverseRoom==from))
			return "Opposite room already exists and heads this way.  One-way link created.";

		Exit thisExit=null;
		synchronized(("SYNC"+from.roomID()).intern())
		{
			from=getRoom(from);
			if(opRoom!=null)
				from.rawDoors()[direction]=null;

			from.rawDoors()[direction]=room;
			thisExit=from.getRawExit(direction);
			if(thisExit==null)
			{
				thisExit=CMClass.getExit("StdOpenDoorway");
				from.setRawExit(direction,thisExit);
			}
			CMLib.database().DBUpdateExits(from);
		}
		synchronized(("SYNC"+room.roomID()).intern())
		{
			room=getRoom(room);
			if(room.rawDoors()[Directions.getOpDirectionCode(direction)]==null)
			{
				room.rawDoors()[Directions.getOpDirectionCode(direction)]=from;
				room.setRawExit(Directions.getOpDirectionCode(direction),thisExit);
				CMLib.database().DBUpdateExits(room);
			}
		}
		return "";
	}

	@Override
	public int numRooms()
	{
		int total=0;
		for(final Enumeration<Area> e=areas();e.hasMoreElements();)
			total+=e.nextElement().properSize();
		return total;
	}

	@Override
	public boolean sendGlobalMessage(MOB host, int category, CMMsg msg)
	{
		final List<WeakReference<MsgListener>> V=globalHandlers.get(Integer.valueOf(category));
		if(V==null)
			return true;
		synchronized(V)
		{
			try
			{
				MsgListener O=null;
				Environmental E=null;
				for(final WeakReference<MsgListener> W : V)
				{
					O=W.get();
					if(O instanceof Environmental)
					{
						E=(Environmental)O;
						if(!CMLib.flags().isInTheGame(E,true))
						{
							if(!CMLib.flags().isInTheGame(E,false))
								delGlobalHandler(E,category);
						}
						else
						if(!E.okMessage(host,msg))
							return false;
					}
					else
					if(O!=null)
					{
						if(!O.okMessage(host, msg))
							return false;
					}
					else
						V.remove(W);
				}
				for(final WeakReference<MsgListener> W : V)
				{
					O=W.get();
					if(O !=null)
						O.executeMsg(host,msg);
				}
			}
			catch(final java.lang.ArrayIndexOutOfBoundsException xx){}
			catch(final Exception x){Log.errOut("CMMap",x);}
		}
		return true;
	}

	@Override
	public String getExtendedRoomID(final Room R)
	{
		if(R==null)
			return "";
		if(R.roomID().length()>0)
			return R.roomID();
		final Area A=R.getArea();
		if(A==null)
			return "";
		final GridLocale GR=R.getGridParent();
		if(GR!=null)
			return GR.getGridChildCode(R);
		return R.roomID();
	}

	@Override
	public String getDescriptiveExtendedRoomID(final Room room)
	{
		if(room==null)
			return "";
		final String roomID = getExtendedRoomID(room);
		if(roomID.length()>0)
			return roomID;
		final GridLocale gridParentRoom=room.getGridParent();
		if((gridParentRoom!=null)&&(gridParentRoom.roomID().length()==0))
		{
			for(int dir=0;dir<Directions.NUM_DIRECTIONS();dir++)
			{
				final Room attachedRoom = gridParentRoom.rawDoors()[dir];
				if(attachedRoom != null)
				{
					final String attachedRoomID = getExtendedRoomID(attachedRoom);
					if(attachedRoomID.length()>0)
						return Directions.getFromCompassDirectionName(Directions.getOpDirectionCode(dir))+" "+attachedRoomID;
				}
			}
		}
		Area area=room.getArea();
		if((area==null)&&(gridParentRoom!=null))
			area=gridParentRoom.getArea();
		if(area == null)
			return "";
		return area.Name()+"#?";
	}
	
	@Override
	public String getExtendedTwinRoomIDs(final Room R1,final Room R2)
	{
		final String R1s=getExtendedRoomID(R1);
		final String R2s=getExtendedRoomID(R2);
		if(R1s.compareTo(R2s)>0)
			return R1s+"_"+R2s;
		else
			return R2s+"_"+R1s;
	}

	@Override
	public Room getRoom(Enumeration<Room> roomSet, String calledThis)
	{
		try
		{
			if(calledThis==null)
				return null;
			if(calledThis.endsWith(")"))
			{
				final int child=calledThis.lastIndexOf("#(");
				if(child>1)
				{
					Room R=getRoom(roomSet,calledThis.substring(0,child));
					if((R!=null)&&(R instanceof GridLocale))
					{
						R=((GridLocale)R).getGridChild(calledThis);
						if(R!=null)
							return R;
					}
				}
			}
			Room R=null;
			if(roomSet==null)
			{
				final int x=calledThis.indexOf('#');
				if(x>=0)
				{
					final Area A=getArea(calledThis.substring(0,x));
					if(A!=null)
						R=A.getRoom(calledThis);
					if(R!=null)
						return R;
				}
				for(final Enumeration<Area> e=this.areas();e.hasMoreElements();)
				{
					R = e.nextElement().getRoom(calledThis);
					if(R!=null)
						return R;
				}
				for(final Enumeration<Area> e=shipAreaEnumerator(null);e.hasMoreElements();)
				{
					R = e.nextElement().getRoom(calledThis);
					if(R!=null)
						return R;
				}
			}
			else
			{
				for(final Enumeration<Room> e=roomSet;e.hasMoreElements();)
				{
					R=e.nextElement();
					if(R.roomID().equalsIgnoreCase(calledThis))
						return R;
				}
			}
		}
		catch (final java.util.NoSuchElementException x)
		{
		}
		return null;
	}

	@Override
	public List<Room> findRooms(Enumeration<Room> rooms, MOB mob, String srchStr, boolean displayOnly, int timePct)
	{
		final Vector<Room> roomsV=new Vector<Room>();
		if((srchStr.charAt(0)=='#')&&(mob!=null)&&(mob.location()!=null))
			addWorldRoomsLiberally(roomsV,getRoom(mob.location().getArea().Name()+srchStr));
		else
			addWorldRoomsLiberally(roomsV,getRoom(srchStr));
		addWorldRoomsLiberally(roomsV,findRooms(rooms,mob,srchStr,displayOnly,false,timePct));
		return roomsV;
	}

	@Override
	public Room findFirstRoom(Enumeration<Room> rooms, MOB mob, String srchStr, boolean displayOnly, int timePct)
	{
		final Vector<Room> roomsV=new Vector<Room>();
		if((srchStr.charAt(0)=='#')&&(mob!=null)&&(mob.location()!=null))
			addWorldRoomsLiberally(roomsV,getRoom(mob.location().getArea().Name()+srchStr));
		else
			addWorldRoomsLiberally(roomsV,getRoom(srchStr));
		if(roomsV.size()>0) 
			return roomsV.firstElement();
		addWorldRoomsLiberally(roomsV,findRooms(rooms,mob,srchStr,displayOnly,true,timePct));
		if(roomsV.size()>0) 
			return roomsV.firstElement();
		return null;
	}

	public List<Room> findRooms(Enumeration<Room> rooms, MOB mob, String srchStr, boolean displayOnly, boolean returnFirst, int timePct)
	{
		final List<Room> foundRooms=new Vector<Room>();
		Vector<Room> completeRooms=null;
		try
		{
			completeRooms=new XVector<Room>(rooms);
		}
		catch(final Exception nse)
		{
			Log.errOut("CMMap",nse);
			completeRooms=new Vector<Room>();
		}
		final long delay=Math.round(CMath.s_pct(timePct+"%") * 1000);

		Enumeration<Room> enumSet;
		enumSet=completeRooms.elements();
		while(enumSet.hasMoreElements())
		{
			findRoomsByDisplay(mob,enumSet,foundRooms,srchStr,returnFirst,delay);
			if((returnFirst)&&(foundRooms.size()>0))
				return foundRooms;
			if(enumSet.hasMoreElements()) CMLib.s_sleep(1000 - delay);
		}
		if(!displayOnly)
		{
			enumSet=completeRooms.elements();
			while(enumSet.hasMoreElements())
			{
				findRoomsByDesc(mob,enumSet,foundRooms,srchStr,returnFirst,delay);
				if((returnFirst)&&(foundRooms.size()>0))
					return foundRooms;
				if(enumSet.hasMoreElements()) CMLib.s_sleep(1000 - delay);
			}
		}
		return foundRooms;
	}

	protected void findRoomsByDisplay(MOB mob, Enumeration<Room> rooms, List<Room> foundRooms, String srchStr, boolean returnFirst, long maxTime)
	{
		final long startTime=System.currentTimeMillis();
		try
		{
			srchStr=srchStr.toUpperCase();
			final boolean useTimer=maxTime>1;
			Room room;
			for(;rooms.hasMoreElements();)
			{
				room=rooms.nextElement();
				if((CMLib.english().containsString(CMStrings.removeColors(room.displayText(mob)),srchStr))
				&&((mob==null)||CMLib.flags().canAccess(mob,room)))
					foundRooms.add(room);
				if((useTimer)&&((System.currentTimeMillis()-startTime)>maxTime))
					return;
			}
		}
		catch (final NoSuchElementException nse)
		{
		}
	}

	protected void findRoomsByDesc(MOB mob, Enumeration<Room> rooms, List<Room> foundRooms, String srchStr, boolean returnFirst, long maxTime)
	{
		final long startTime=System.currentTimeMillis();
		try
		{
			srchStr=srchStr.toUpperCase();
			final boolean useTimer=maxTime>1;
			for(;rooms.hasMoreElements();)
			{
				final Room room=rooms.nextElement();
				if((CMLib.english().containsString(CMStrings.removeColors(room.description()),srchStr))
				&&((mob==null)||CMLib.flags().canAccess(mob,room)))
					foundRooms.add(room);
				if((useTimer)&&((System.currentTimeMillis()-startTime)>maxTime))
					return;
			}
		}
		catch (final NoSuchElementException nse)
		{
		}
	}

	@Override
	public List<MOB> findInhabitants(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{ 
		return findInhabitants(rooms,mob,srchStr,false,timePct);
	}
	
	@Override
	public MOB findFirstInhabitant(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{
		final List<MOB> found=findInhabitants(rooms,mob,srchStr,true,timePct);
		if(found.size()>0)
			return found.get(0);
		return null;
	}
	
	public List<MOB> findInhabitants(Enumeration<Room> rooms, MOB mob, String srchStr, boolean returnFirst, int timePct)
	{
		final Vector<MOB> found=new Vector<MOB>();
		long delay=Math.round(CMath.s_pct(timePct+"%") * 1000);
		if(delay>1000)
			delay=1000;
		final boolean useTimer = delay>1;
		final boolean allRoomsAllowed=(mob==null);
		long startTime=System.currentTimeMillis();
		Room room;
		for(;rooms.hasMoreElements();)
		{
			room=rooms.nextElement();
			if((room != null) && (allRoomsAllowed || CMLib.flags().canAccess(mob,room)))
			{
				found.addAll(room.fetchInhabitants(srchStr));
				if((returnFirst)&&(found.size()>0))
					return found;
			}
			if((useTimer)&&((System.currentTimeMillis()-startTime)>delay))
				CMLib.s_sleep(1000 - delay); startTime=System.currentTimeMillis();
		}
		return found;
	}

	@Override
	public List<Item> findInventory(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{ 
		return findInventory(rooms,mob,srchStr,false,timePct);
	}
	
	@Override
	public Item findFirstInventory(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{
		final List<Item> found=findInventory(rooms,mob,srchStr,true,timePct);
		if(found.size()>0)
			return found.get(0);
		return null;
	}
	
	public List<Item> findInventory(Enumeration<Room> rooms, MOB mob, String srchStr, boolean returnFirst, int timePct)
	{
		final List<Item> found=new Vector<Item>();
		long delay=Math.round(CMath.s_pct(timePct+"%") * 1000);
		if(delay>1000)
			delay=1000;
		final boolean useTimer = delay>1;
		long startTime=System.currentTimeMillis();
		MOB M;
		Room room;
		if(rooms==null)
		{
			for(final Enumeration<MOB> e=CMLib.players().players();e.hasMoreElements();)
			{
				M=e.nextElement();
				if(M!=null)
					found.addAll(M.findItems(srchStr));
				if((returnFirst)&&(found.size()>0))
					return found;
			}
		}
		else
		for(;rooms.hasMoreElements();)
		{
			room=rooms.nextElement();
			if((room != null) && ((mob==null)||CMLib.flags().canAccess(mob,room)))
			{
				for(int m=0;m<room.numInhabitants();m++)
				{
					M=room.fetchInhabitant(m);
					if(M!=null)
						found.addAll(M.findItems(srchStr));
				}
				if((returnFirst)&&(found.size()>0))
					return found;
			}
			if((useTimer)&&((System.currentTimeMillis()-startTime)>delay))
				CMLib.s_sleep(1000 - delay); startTime=System.currentTimeMillis();
		}
		return found;
	}
	
	@Override
	public List<Environmental> findShopStock(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{ 
		return findShopStock(rooms,mob,srchStr,false,false,timePct);
	}
	
	@Override
	public Environmental findFirstShopStock(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{
		final List<Environmental> found=findShopStock(rooms,mob,srchStr,true,false,timePct);
		if(found.size()>0)
			return found.get(0);
		return null;
	}
	
	@Override
	public List<Environmental> findShopStockers(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{ 
		return findShopStock(rooms,mob,srchStr,false,true,timePct);
	}
	
	@Override
	public Environmental findFirstShopStocker(Enumeration<Room> rooms, MOB mob, String srchStr, int timePct)
	{
		final List<Environmental> found=findShopStock(rooms,mob,srchStr,true,true,timePct);
		if(found.size()>0)
			return found.get(0);
		return null;
	}
	
	public List<Environmental> findShopStock(Enumeration<Room> rooms, MOB mob, String srchStr, boolean returnFirst, boolean returnStockers, int timePct)
	{
		final XVector<Environmental> found=new XVector<Environmental>();
		long delay=Math.round(CMath.s_pct(timePct+"%") * 1000);
		if(delay>1000)
			delay=1000;
		final boolean useTimer = delay>1;
		long startTime=System.currentTimeMillis();
		MOB M=null;
		Item I=null;
		final HashSet<ShopKeeper> stocks=new HashSet<ShopKeeper>(1);
		final HashSet<Area> areas=new HashSet<Area>();
		ShopKeeper SK=null;
		final boolean allRoomsAllowed=(mob==null);
		if(rooms==null)
		{
			for(final Enumeration<MOB> e=CMLib.players().players();e.hasMoreElements();)
			{
				M=e.nextElement();
				if(M!=null)
				{
					SK=CMLib.coffeeShops().getShopKeeper(M);
					if((SK!=null)&&(!stocks.contains(SK)))
					{
						stocks.add(SK);
						final Iterator<Environmental> ei=SK.getShop().getStoreInventory(srchStr);
						if(ei.hasNext())
						{
							if(returnFirst)
								return (returnStockers)?new XVector<Environmental>(M):new XVector<Environmental>(ei);
							if(returnStockers)
								found.add(M);
							else
								found.addAll(ei);
						}
					}
					for(int i=0;i<M.numItems();i++)
					{
						I=M.getItem(i);
						if(I!=null)
						{
							SK=CMLib.coffeeShops().getShopKeeper(I);
							if((SK!=null)&&(!stocks.contains(SK)))
							{
								stocks.add(SK);
								final Iterator<Environmental> ei=SK.getShop().getStoreInventory(srchStr);
								if(ei.hasNext())
								{
									if(returnFirst)
										return (returnStockers)?new XVector<Environmental>(I):new XVector<Environmental>(ei);
									if(returnStockers)
										found.add(I);
									else
										found.addAll(ei);
								}
							}
						}
					}
				}
				if((useTimer)&&((System.currentTimeMillis()-startTime)>delay))
				{
					try
					{
						Thread.sleep(1000 - delay);
						startTime = System.currentTimeMillis();
					}
					catch (final Exception ex)
					{
					}
				}
			}
		}
		else
		for(;rooms.hasMoreElements();)
		{
			final Room room=rooms.nextElement();
			if((room != null) && (allRoomsAllowed||CMLib.flags().canAccess(mob,room)))
			{
				if(!areas.contains(room.getArea()))
					areas.add(room.getArea());
				SK=CMLib.coffeeShops().getShopKeeper(room);
				if((SK!=null)&&(!stocks.contains(SK)))
				{
					stocks.add(SK);
					final Iterator<Environmental> ei=SK.getShop().getStoreInventory(srchStr);
					if(ei.hasNext())
					{
						if(returnFirst)
							return (returnStockers)?new XVector<Environmental>(room):new XVector<Environmental>(ei);
						if(returnStockers)
							found.add(room);
						else
							found.addAll(ei);
					}
				}
				for(int m=0;m<room.numInhabitants();m++)
				{
					M=room.fetchInhabitant(m);
					if(M!=null)
					{
						SK=CMLib.coffeeShops().getShopKeeper(M);
						if((SK!=null)&&(!stocks.contains(SK)))
						{
							stocks.add(SK);
							final Iterator<Environmental> ei=SK.getShop().getStoreInventory(srchStr);
							if(ei.hasNext())
							{
								if(returnFirst)
									return (returnStockers)?new XVector<Environmental>(M):new XVector<Environmental>(ei);
								if(returnStockers)
									found.add(M);
								else
									found.addAll(ei);
							}
						}
					}
				}
				for(int i=0;i<room.numItems();i++)
				{
					I=room.getItem(i);
					if(I!=null)
					{
						SK=CMLib.coffeeShops().getShopKeeper(I);
						if((SK!=null)&&(!stocks.contains(SK)))
						{
							stocks.add(SK);
							final Iterator<Environmental> ei=SK.getShop().getStoreInventory(srchStr);
							if(ei.hasNext())
							{
								if(returnFirst)
									return (returnStockers)?new XVector<Environmental>(I):new XVector<Environmental>(ei);
								if(returnStockers)
									found.add(I);
								else
									found.addAll(ei);
							}
						}
					}
				}
			}
			if((useTimer)&&((System.currentTimeMillis()-startTime)>delay))
				CMLib.s_sleep(1000 - delay); startTime=System.currentTimeMillis();
		}
		for (final Area A : areas)
		{
			SK=CMLib.coffeeShops().getShopKeeper(A);
			if((SK!=null)&&(!stocks.contains(SK)))
			{
				stocks.add(SK);
				final Iterator<Environmental> ei=SK.getShop().getStoreInventory(srchStr);
				if(ei.hasNext())
				{
					if(returnFirst)
						return (returnStockers)?new XVector<Environmental>(A):new XVector<Environmental>(ei);
					if(returnStockers)
						found.add(A);
					else
						found.addAll(ei);
				}
			}
		}
		return found;
	}

	@Override
	public List<Item> findRoomItems(Enumeration<Room> rooms, MOB mob, String srchStr, boolean anyItems, int timePct)
	{ 
		return findRoomItems(rooms,mob,srchStr,anyItems,false,timePct);
	}
	
	@Override
	public Item findFirstRoomItem(Enumeration<Room> rooms, MOB mob, String srchStr, boolean anyItems, int timePct)
	{
		final List<Item> found=findRoomItems(rooms,mob,srchStr,anyItems,true,timePct);
		if(found.size()>0)
			return found.get(0);
		return null;
	}
	
	public List<Item> findRoomItems(Enumeration<Room> rooms, MOB mob, String srchStr, boolean anyItems, boolean returnFirst, int timePct)
	{
		final Vector<Item> found=new Vector<Item>();
		long delay=Math.round(CMath.s_pct(timePct+"%") * 1000);
		if(delay>1000)
			delay=1000;
		final boolean useTimer = delay>1;
		long startTime=System.currentTimeMillis();
		final boolean allRoomsAllowed=(mob==null);
		Room room;
		for(;rooms.hasMoreElements();)
		{
			room=rooms.nextElement();
			if((room != null) && (allRoomsAllowed||CMLib.flags().canAccess(mob,room)))
			{
				found.addAll(anyItems?room.findItems(srchStr):room.findItems(null,srchStr));
				if((returnFirst)&&(found.size()>0))
					return found;
			}
			if((useTimer)&&((System.currentTimeMillis()-startTime)>delay))
				CMLib.s_sleep(1000 - delay); startTime=System.currentTimeMillis();
		}
		return found;
	}

	@Override
	public Room getRoom(Room room)
	{
		if(room==null)
			return null;
		if(room.amDestroyed())
			return getRoom(getExtendedRoomID(room));
		return room;
	}

	@Override 
	public Room getRoom(String calledThis)
	{
		return getRoom(null,calledThis);
	}

	@Override 
	public Enumeration<Room> rooms()
	{
		return new AllAreasRoomEnumerator(false); 
	}

	@Override 
	public Enumeration<Room> roomsFilled()
	{
		return new AllAreasRoomEnumerator(true); 
	}

	@Override
	public Room getRandomRoom()
	{
		Room R=null;
		int numRooms=-1;
		while((R==null)&&((numRooms=numRooms())>0))
		{
			try
			{
				final int which=CMLib.dice().roll(1,numRooms,-1);
				int total=0;
				for(final Enumeration<Area> e=areas();e.hasMoreElements();)
				{
					final Area A=e.nextElement();
					if(which<(total+A.properSize()))
					{
						R = A.getRandomProperRoom();
						break;
					}
					total+=A.properSize();
				}
			}
			catch (final NoSuchElementException e)
			{
			}
		}
		return R;
	}

	public int numDeities()
	{
		return deitiesList.size();
	}

	protected void addDeity(Deity newOne)
	{
		if (!deitiesList.contains(newOne))
			deitiesList.add(newOne);
	}

	protected void delDeity(Deity oneToDel)
	{
		deitiesList.remove(oneToDel);
	}

	@Override
	public Deity getDeity(String calledThis)
	{
		for (final Deity D : deitiesList)
			if (D.Name().equalsIgnoreCase(calledThis))
				return D;
		return null;
	}
	
	@Override 
	public Enumeration<Deity> deities() 
	{
		return new IteratorEnumeration<Deity>(deitiesList.iterator()); 
	}

	public int numShips() 
	{ 
		return shipList.size(); 
	}

	protected void addShip(BoardableShip newOne)
	{
		if (!shipList.contains(newOne))
			shipList.add(newOne);
	}

	protected void delShip(BoardableShip oneToDel)
	{
		shipList.remove(oneToDel);
	}

	@Override
	public BoardableShip getShip(String calledThis)
	{
		for (final BoardableShip S : shipList)
		{
			if (S.Name().equalsIgnoreCase(calledThis))
				return S;
		}
		return null;
	}
	
	@Override 
	public Enumeration<BoardableShip> ships() 
	{ 
		return new IteratorEnumeration<BoardableShip>(shipList.iterator()); 
	}
	
	public Enumeration<Room> shipsRoomEnumerator(final Area inA)
	{
		return new Enumeration<Room>() 
		{
			private Enumeration<Room> cur = null;
			private Enumeration<Area> cA = shipAreaEnumerator(inA);
			
			@Override
			public boolean hasMoreElements()
			{
				boolean hasMore = (cur != null) && cur.hasMoreElements();
				while(!hasMore)
				{
					if((cA == null)||(!cA.hasMoreElements()))
					{
						cur=null;
						cA = null;
						return false;
					}
					cur = cA.nextElement().getProperMap();
					hasMore = (cur != null) && cur.hasMoreElements();
				}
				return hasMore;
			}

			@Override
			public Room nextElement()
			{
				if(!hasMoreElements())
					throw new NoSuchElementException();
				return cur.nextElement();
			}
		};
	}
	
	public Enumeration<Area> shipAreaEnumerator(final Area inA)
	{
		return new Enumeration<Area>() 
		{
			private volatile Area nextArea=null;
			private volatile Enumeration<BoardableShip> shipsEnum=ships();

			@Override
			public boolean hasMoreElements()
			{
				while(nextArea == null)
				{
					if((shipsEnum==null)||(!shipsEnum.hasMoreElements()))
					{
						shipsEnum=null;
						return false;
					}
					final BoardableShip ship=shipsEnum.nextElement();
					if((ship!=null)&&(ship.getShipArea()!=null))
					{
						if((inA==null)||(areaLocation(ship)==inA))
							nextArea=ship.getShipArea();
					}
				}
				return (nextArea != null);
			}
			
			@Override
			public Area nextElement()
			{
				if(!hasMoreElements())
					throw new NoSuchElementException();
				final Area A=nextArea;
				this.nextArea=null;
				return A;
			}
		};
	}

	public int numPostOffices()
	{
		return postOfficeList.size();
	}

	protected void addPostOffice(PostOffice newOne)
	{
		if(!postOfficeList.contains(newOne))
			postOfficeList.add(newOne);
	}
	protected void delPostOffice(PostOffice oneToDel)
	{
		postOfficeList.remove(oneToDel);
	}
	@Override
	public PostOffice getPostOffice(String chain, String areaNameOrBranch)
	{
		for (final PostOffice P : postOfficeList)
		{
			if((P.postalChain().equalsIgnoreCase(chain))
			&&(P.postalBranch().equalsIgnoreCase(areaNameOrBranch)))
				return P;
		}

		final Area A=findArea(areaNameOrBranch);
		if(A==null)
			return null;

		for (final PostOffice P : postOfficeList)
		{
			if((P.postalChain().equalsIgnoreCase(chain))
			&&(getStartArea(P)==A))
				return P;
		}
		return null;
	}

	@Override
	public Enumeration<PostOffice> postOffices()
	{
		return new IteratorEnumeration<PostOffice>(postOfficeList.iterator());
	}

	@Override
	public Enumeration<Auctioneer> auctionHouses()
	{
		return new IteratorEnumeration<Auctioneer>(auctionHouseList.iterator());
	}

	public int numAuctionHouses()
	{
		return auctionHouseList.size();
	}

	protected void addAuctionHouse(Auctioneer newOne)
	{
		if (!auctionHouseList.contains(newOne))
		{
			auctionHouseList.add(newOne);
		}
	}
	protected void delAuctionHouse(Auctioneer oneToDel)
	{
		auctionHouseList.remove(oneToDel);
	}

	@Override
	public Auctioneer getAuctionHouse(String chain, String areaNameOrBranch)
	{
		for (final Auctioneer C : auctionHouseList)
		{
			if((C.auctionHouse().equalsIgnoreCase(chain))
			&&(C.auctionHouse().equalsIgnoreCase(areaNameOrBranch)))
				return C;
		}

		final Area A=findArea(areaNameOrBranch);
		if(A==null)
			return null;

		for (final Auctioneer C : auctionHouseList)
		{
			if((C.auctionHouse().equalsIgnoreCase(chain))
			&&(getStartArea(C)==A))
				return C;
		}

		return null;
	}

	public int numBanks()
	{
		return bankList.size();
	}

	protected void addBank(Banker newOne)
	{
		if (!bankList.contains(newOne))
			bankList.add(newOne);
	}
	protected void delBank(Banker oneToDel)
	{
		bankList.remove(oneToDel);
	}
	@Override
	public Banker getBank(String chain, String areaNameOrBranch)
	{
		for (final Banker B : bankList)
		{
			if((B.bankChain().equalsIgnoreCase(chain))
			&&(B.bankChain().equalsIgnoreCase(areaNameOrBranch)))
				return B;
		}

		final Area A=findArea(areaNameOrBranch);
		if(A==null)
			return null;

		for (final Banker B : bankList)
		{
			if((B.bankChain().equalsIgnoreCase(chain))
			&&(getStartArea(B)==A))
				return B;
		}
		return null;
	}

	@Override
	public Enumeration<Banker> banks()
	{
		return new IteratorEnumeration<Banker>(bankList.iterator());
	}

	@Override
	public Iterator<String> bankChains(Area AreaOrNull)
	{
		final HashSet<String> H=new HashSet<String>();
		for (final Banker B : bankList)
		{
			if((!H.contains(B.bankChain()))
			&&((AreaOrNull==null)
				||(getStartArea(B)==AreaOrNull)
				||(AreaOrNull.isChild(getStartArea(B)))))
					H.add(B.bankChain());
		}
		return H.iterator();
	}

	@Override
	public void renameRooms(Area A, String oldName, List<Room> allMyDamnRooms)
	{
		final List<Room> onesToRenumber=new Vector<Room>();
		for(Room R : allMyDamnRooms)
		{
			synchronized(("SYNC"+R.roomID()).intern())
			{
				R=getRoom(R);
				R.setArea(A);
				if(oldName!=null)
				{
					if(R.roomID().toUpperCase().startsWith(oldName.toUpperCase()+"#"))
					{
						Room R2=A.getRoom(A.Name()+"#"+R.roomID().substring(oldName.length()+1));
						if(R2 == null)
							R2=getRoom(A.Name()+"#"+R.roomID().substring(oldName.length()+1));
						if((R2==null)||(!R2.roomID().startsWith(A.Name()+"#")))
						{
							final String oldID=R.roomID();
							R.setRoomID(A.Name()+"#"+R.roomID().substring(oldName.length()+1));
							CMLib.database().DBReCreate(R,oldID);
						}
						else
							onesToRenumber.add(R);
					}
					else
						CMLib.database().DBUpdateRoom(R);
				}
			}
		}
		if(oldName!=null)
		{
			for(final Room R: onesToRenumber)
			{
				final String oldID=R.roomID();
				R.setRoomID(A.getNewRoomID(R,-1));
				CMLib.database().DBReCreate(R,oldID);
			}
		}
	}

	@Override
	public int getRoomDir(Room from, Room to)
	{
		if((from==null)||(to==null))
			return -1;
		for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
			if(from.getRoomInDir(d)==to)
				return d;
		return -1;
	}

	@Override
	public Area getTargetArea(Room from, Exit to)
	{
		final Room R=getTargetRoom(from, to);
		if(R==null)
			return null;
		return R.getArea();
	}
	
	@Override
	public Room getTargetRoom(Room from, Exit to)
	{
		if((from==null)||(to==null))
			return null;
		final int d=getExitDir(from, to);
		if(d<0)
			return null;
		return from.getRoomInDir(d);
	}
	
	@Override
	public int getExitDir(Room from, Exit to)
	{
		if((from==null)||(to==null))
			return -1;
		for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
		{
			if(from.getExitInDir(d)==to)
				return d;
			if(from.getRawExit(d)==to)
				return d;
			if(from.getReverseExit(d)==to)
				return d;
		}
		return -1;
	}

	@Override
	public Room findConnectingRoom(Room room)
	{
		if(room==null)
			return null;
		Room R=null;
		final Vector<Room> otherChoices=new Vector<Room>();
		for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
		{
			R=room.getRoomInDir(d);
			if(R!=null)
			{
				for(int d1=Directions.NUM_DIRECTIONS()-1;d1>=0;d1--)
				{
					if(R.getRoomInDir(d1)==room)
					{
						if(R.getArea()==room.getArea())
							return R;
						otherChoices.addElement(R);
					}
				}
			}
		}
		for(final Enumeration<Room> e=rooms();e.hasMoreElements();)
		{
			R=e.nextElement();
			if(R==room)
				continue;
			for(int d1=Directions.NUM_DIRECTIONS()-1;d1>=0;d1--)
			{
				if(R.getRoomInDir(d1)==room)
				{
					if(R.getArea()==room.getArea())
						return R;
					otherChoices.addElement(R);
				}
			}
		}
		if(otherChoices.size()>0)
			return otherChoices.firstElement();
		return null;
	}

	@Override
	public boolean isClearableRoom(Room R)
	{
		if((R==null)||(R.amDestroyed()))
			return true;
		MOB M=null;
		Room sR=null;
		for(int i=0;i<R.numInhabitants();i++)
		{
			M=R.fetchInhabitant(i);
			if(M==null)
				continue;
			sR=M.getStartRoom();
			if((sR!=null)
			&&(!sR.roomID().equals(R.roomID())))
				return false;
			if(M.session()!=null)
				return false;
		}
		Item I=null;
		for(int i=0;i<R.numItems();i++)
		{
			I=R.getItem(i);
			if((I!=null)
			&&((I.expirationDate()!=0)
				||((I instanceof DeadBody)&&(((DeadBody)I).isPlayerCorpse()))
				||((I instanceof PrivateProperty)&&(((PrivateProperty)I).getOwnerName().length()>0))))
					return false;
		}
		for(final Enumeration<Ability> a=R.effects();a.hasMoreElements();)
		{
			final Ability A=a.nextElement();
			if((A!=null)&&(!A.isSavable()))
				return false;
		}
		return true;
	}

	@Override
	public boolean explored(Room R)
	{
		if((R==null)
		||(CMath.bset(R.phyStats().sensesMask(),PhyStats.SENSE_ROOMUNEXPLORABLE))
		||(R.getArea()==null))
			return false;
		return false;
	}

	public class AllAreasRoomEnumerator implements Enumeration<Room>
	{
		private Enumeration<Area> curAreaEnumeration=areasPlusShips();
		private Enumeration<Room> curRoomEnumeration=null;
		private boolean addSkys = false;
		public AllAreasRoomEnumerator(boolean includeSkys)
		{
			addSkys = includeSkys;
		}
		@Override
		public boolean hasMoreElements()
		{
			boolean hasMore = (curRoomEnumeration!=null)&&(curRoomEnumeration.hasMoreElements());
			while(!hasMore)
			{
				if((curAreaEnumeration == null)||(!curAreaEnumeration.hasMoreElements()))
				{
					curRoomEnumeration = null;
					curAreaEnumeration = null;
					return false;
				}
				if(addSkys)
					curRoomEnumeration=curAreaEnumeration.nextElement().getFilledProperMap();
				else
					curRoomEnumeration=curAreaEnumeration.nextElement().getProperMap();
				hasMore = (curRoomEnumeration!=null)&&(curRoomEnumeration.hasMoreElements());
			}
			return hasMore;
		}
		@Override
		public Room nextElement()
		{
			if(!hasMoreElements())
				throw new NoSuchElementException();
			return curRoomEnumeration.nextElement();
		}
	}

	@Override
	public void obliterateRoom(Room deadRoom)
	{
		for(final Enumeration<Ability> a=deadRoom.effects();a.hasMoreElements();)
		{
			final Ability A=a.nextElement();
			if(A!=null)
			{
				A.unInvoke();
				deadRoom.delEffect(A);
			}
		}
		try
		{
			for(final Enumeration<Room> r=rooms();r.hasMoreElements();)
			{
				Room R=r.nextElement();
				synchronized(("SYNC"+R.roomID()).intern())
				{
					R=getRoom(R);
					if(R==null)
						continue;
					boolean changes=false;
					for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
					{
						final Room thatRoom=R.rawDoors()[d];
						if(thatRoom==deadRoom)
						{
							R.rawDoors()[d]=null;
							changes=true;
							if((R.getRawExit(d)!=null)&&(R.getRawExit(d).isGeneric()))
							{
								final Exit GE=R.getRawExit(d);
								GE.setTemporaryDoorLink(deadRoom.roomID());
							}
						}
					}
					if(changes)
						CMLib.database().DBUpdateExits(R);
				}
			}
		}catch(final NoSuchElementException e){}
		emptyRoom(deadRoom,null,true);
		deadRoom.destroy();
		if(deadRoom instanceof GridLocale)
			((GridLocale)deadRoom).clearGrid(null);
		CMLib.database().DBDeleteRoom(deadRoom);
	}

	@Override
	public void emptyAreaAndDestroyRooms(Area area)
	{
		for(final Enumeration<Ability> a=area.effects();a.hasMoreElements();)
		{
			final Ability A=a.nextElement();
			if(A!=null)
			{
				A.unInvoke();
				area.delEffect(A);
			}
		}
		for(final Enumeration<Room> e=area.getProperMap();e.hasMoreElements();)
		{
			final Room R=e.nextElement();
			emptyRoom(R,null,true);
			R.destroy();
		}
	}

	@Override
	public Room roomLocation(Environmental E)
	{
		if(E==null)
			return null;
		if((E instanceof Area)&&(!((Area)E).isProperlyEmpty()))
			return ((Area)E).getRandomProperRoom();
		else
		if(E instanceof Room)
			return (Room)E;
		else
		if(E instanceof MOB)
			return ((MOB)E).location();
		else
		if((E instanceof Item)&&(((Item)E).owner() instanceof Room))
			return (Room)((Item)E).owner();
		else
		if((E instanceof Item)&&(((Item)E).owner() instanceof MOB))
		   return ((MOB)((Item)E).owner()).location();
		else
		if(E instanceof Ability)
			return roomLocation(((Ability)E).affecting());
		else
		if(E instanceof Exit)
			return roomLocation(((Exit)E).lastRoomUsedFrom());
		return null;
	}
	@Override
	public Area getStartArea(Environmental E)
	{
		if(E instanceof Area)
			return (Area)E;
		final Room R=getStartRoom(E);
		if(R==null)
			return null;
		return R.getArea();
	}

	@Override
	public Room getStartRoom(Environmental E)
	{
		if(E ==null)
			return null;
		if(E instanceof MOB)
			return ((MOB)E).getStartRoom();
		if(E instanceof Item)
		{
			if(((Item)E).owner() instanceof MOB)
				return getStartRoom(((Item)E).owner());
			if(CMLib.flags().isGettable((Item)E))
				return null;
		}
		if(E instanceof Ability)
			return getStartRoom(((Ability)E).affecting());
		if((E instanceof Area)&&(!((Area)E).isProperlyEmpty()))
			return ((Area)E).getRandomProperRoom();
		if(E instanceof Room)
			return (Room)E;
		return roomLocation(E);
	}

	@Override
	public ThreadGroup getOwnedThreadGroup(CMObject E)
	{
		final Area area=areaLocation(E);
		if(area != null)
		{
			final int theme=area.getTheme();
			if((theme>0)&&(theme<Area.THEME_NAMES.length))
				return CMProps.getPrivateOwner(Area.THEME_NAMES[theme]+"AREAS");
		}
		return null;
	}

	@Override
	public Area areaLocation(CMObject E)
	{
		if(E==null)
			return null;
		if(E instanceof Area)
			return (Area)E;
		else
		if(E instanceof Room)
			return ((Room)E).getArea();
		else
		if(E instanceof MOB)
			return areaLocation(((MOB)E).location());
		else
		if(E instanceof Item)
			return areaLocation(((Item) E).owner());
		else
		if((E instanceof Ability)&&(((Ability)E).affecting()!=null))
			return areaLocation(((Ability)E).affecting());
		else
		if(E instanceof Exit)
			return areaLocation(((Exit)E).lastRoomUsedFrom());
		return null;
	}
	
	@Override
	public Room getSafeRoomToMovePropertyTo(Room room, PrivateProperty I)
	{
		if(I instanceof BoardableShip)
		{
			final Room R=getRoom(((BoardableShip)I).getHomePortID());
			if((R!=null)&&(R!=room)&&(!R.amDestroyed()))
				return R;
		}
		if(room != null)
		{
			Room R=null;
			if(room.getGridParent()!=null)
			{
				R=getRoom(room.getGridParent());
				if((R!=null)&&(R!=room)&&(!R.amDestroyed())&&(R.roomID().length()>0))
					return R;
			}
			for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
			{
				R=getRoom(room.getRoomInDir(d));
				if((R!=null)&&(R!=room)&&(!R.amDestroyed())&&(R.roomID().length()>0))
					return R;
			}
			if(room.getGridParent()!=null)
			{
				for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
				{
					R=getRoom(room.getGridParent().getRoomInDir(d));
					if((R!=null)&&(R!=room)&&(!R.amDestroyed())&&(R.roomID().length()>0))
						return R;
				}
			}
			final Area A=room.getArea();
			if(A!=null)
			{
				for(int i=0;i<A.numberOfProperIDedRooms();i++)
				{
					R=getRoom(A.getRandomProperRoom());
					if((R!=null)&&(R!=room)&&(!R.amDestroyed())&&(R.roomID().length()>0))
						return R;
				}
			}
		}
		for(int i=0;i<100;i++)
		{
			final Room R=getRoom(this.getRandomRoom());
			if((R!=null)&&(R!=room)&&(!R.amDestroyed())&&(R.roomID().length()>0))
				return R;
		}
		return null;
	}

	@Override
	public void emptyRoom(Room room, Room toRoom, boolean clearPlayers)
	{
		if(room==null) 
			return;
		MOB M=null;
		if(toRoom != null)
		{
			for(final Enumeration<MOB> i=room.inhabitants();i.hasMoreElements();)
			{
				M=i.nextElement();
				if(M!=null)
					toRoom.bringMobHere(M,false);
			}
		}
		else
		if(clearPlayers)
		{
			for(final Enumeration<MOB> i=room.inhabitants();i.hasMoreElements();)
			{
				M=i.nextElement();
				if((M!=null) && (M.isPlayer()))
					M.getStartRoom().bringMobHere(M,true);
			}
		}
		for(final Enumeration<MOB> i=room.inhabitants();i.hasMoreElements();)
		{
			M=i.nextElement();
			if((M!=null)
			&&(!M.isPlayer())
			&&(M.isSavable())
			&&((M.amFollowing()==null)||(!M.amFollowing().isPlayer())))
			{
				if((M.getStartRoom()==null)
				||(M.getStartRoom()==room)
				||(M.getStartRoom().ID().length()==0))
					M.destroy();
				else
					M.getStartRoom().bringMobHere(M,false);
			}
		}
		
		Item I=null;
		if(toRoom != null)
		{
			for(final Enumeration<Item> i=room.items();i.hasMoreElements();)
			{
				I=i.nextElement();
				if(I!=null)
					toRoom.moveItemTo(I,ItemPossessor.Expire.Player_Drop);
			}
		}
		else
		{
			for(final Enumeration<Item> i=room.items();i.hasMoreElements();)
			{
				I=i.nextElement();
				if(I != null)
				{
					if((I instanceof PrivateProperty)&&((((PrivateProperty)I).getOwnerName().length()>0)))
					{
						final Room R=this.getSafeRoomToMovePropertyTo(room, (PrivateProperty)I);
						if((R!=null)&&(R!=room))
							R.moveItemTo(I,ItemPossessor.Expire.Player_Drop);
					}
					else
						I.destroy();
				}
			}
		}
		room.clearSky();
		CMLib.threads().clearDebri(room,0);
		if(room instanceof GridLocale)
			for(final Iterator<Room> r=((GridLocale)room).getExistingRooms();r.hasNext();)
				emptyRoom(r.next(), toRoom, clearPlayers);
	}

	@Override
	public void obliterateArea(Area A)
	{
		if(A==null)
			return;
		final LinkedList<Room> rooms=new LinkedList<Room>();
		Room R=null;
		Enumeration<Room> e=A.getCompleteMap();
		while(e.hasMoreElements())
		{
			for(int i=0;(i<100)&&e.hasMoreElements();i++)
			{
				R=e.nextElement();
				if((R!=null)&&(R.roomID()!=null))
					rooms.add(R);
			}
			if(rooms.size()==0)
				break;
			for(final Iterator<Room> e2=rooms.iterator();e2.hasNext();)
			{
				R=e2.next();
				if((R!=null)&&(R.roomID().length()>0))
					obliterateRoom(R);
				e2.remove();
			}
			e=A.getCompleteMap();
		}
		CMLib.database().DBDeleteArea(A);
		delArea(A);
		A.destroy(); // why not?
	}

	public CMMsg resetMsg=null;
	@Override
	public void resetRoom(Room room)
	{
		resetRoom(room,false);
	}

	@Override
	public void resetRoom(Room room, boolean rebuildGrids)
	{
		if(room==null)
			return;
		if(room.roomID().length()==0)
			return;
		synchronized(("SYNC"+room.roomID()).intern())
		{
			room=getRoom(room);
			if((rebuildGrids)&&(room instanceof GridLocale))
				((GridLocale)room).clearGrid(null);
			final boolean mobile=room.getMobility();
			room.toggleMobility(false);
			if(resetMsg==null)
				resetMsg=CMClass.getMsg(CMClass.sampleMOB(),room,CMMsg.MSG_ROOMRESET,null);
			resetMsg.setTarget(room);
			room.executeMsg(room,resetMsg);
			if(room.isSavable())
				emptyRoom(room,null,false);
			for(final Enumeration<Ability> a=room.effects();a.hasMoreElements();)
			{
				final Ability A=a.nextElement();
				if((A!=null)&&(A.canBeUninvoked()))
					A.unInvoke();
			}
			if(room.isSavable())
			{
				CMLib.database().DBReReadRoomData(room);
				CMLib.database().DBReadContent(room.roomID(),room,true);
			}
			room.startItemRejuv();
			room.setResource(-1);
			room.toggleMobility(mobile);
		}
	}

	@Override
	public Room findWorldRoomLiberally(MOB mob, String cmd, String srchWhatAERIPMVK, int timePct, long maxMillis)
	{
		final List<Room> rooms=findWorldRoomsLiberally(mob,cmd,srchWhatAERIPMVK,null,true,timePct, maxMillis);
		if((rooms!=null)&&(rooms.size()!=0))
			return rooms.get(0);
		return null;
	}

	@Override
	public List<Room> findWorldRoomsLiberally(MOB mob, String cmd, String srchWhatAERIPMVK, int timePct, long maxMillis)
	{
		return findWorldRoomsLiberally(mob,cmd,srchWhatAERIPMVK,null,false,timePct,maxMillis);
	}

	@Override
	public Room findAreaRoomLiberally(MOB mob, Area A,String cmd, String srchWhatAERIPMVK, int timePct)
	{
		final List<Room> rooms=findWorldRoomsLiberally(mob,cmd,srchWhatAERIPMVK,A,true,timePct,120);
		if((rooms!=null)&&(rooms.size()!=0))
			return rooms.get(0);
		return null;
	}

	@Override
	public List<Room> findAreaRoomsLiberally(MOB mob, Area A,String cmd, String srchWhatAERIPMVK, int timePct)
	{
		return findWorldRoomsLiberally(mob,cmd,srchWhatAERIPMVK,A,false,timePct,120);
	}

	protected Room addWorldRoomsLiberally(List<Room> rooms, List<? extends Environmental> choicesV)
	{
		if(choicesV==null)
			return null;
		if(rooms!=null)
		{
			for(final Environmental E : choicesV)
				addWorldRoomsLiberally(rooms,roomLocation(E));
			return null;
		}
		else
		{
			Room room=null;
			int tries=0;
			while(((room==null)||(room.roomID().length()==0))&&((++tries)<200))
				room=roomLocation(choicesV.get(CMLib.dice().roll(1,choicesV.size(),-1)));
			return room;
		}
	}

	protected Room addWorldRoomsLiberally(List<Room> rooms, Room room)
	{
		if(room==null)
			return null;
		if(rooms!=null)
		{
			if(!rooms.contains(room))
				rooms.add(room);
			return null;
		}
		return room;
	}

	protected Room addWorldRoomsLiberally(List<Room>rooms, Area area)
	{
		if((area==null)||(area.isProperlyEmpty()))
			return null;
		return addWorldRoomsLiberally(rooms,area.getRandomProperRoom());
	}

	protected List<Room> returnResponse(List<Room> rooms, Room room)
	{
		if(rooms!=null)
			return rooms;
		if(room==null)
			return new Vector<Room>(1);
		return new XVector<Room>(room);
	}

	protected boolean enforceTimeLimit(final long startTime,  final long maxMillis)
	{
		if(maxMillis<=0)
			return false;
		return ((System.currentTimeMillis() - startTime)) > maxMillis;
	}

	protected List<MOB> checkMOBCachedList(List<MOB> list)
	{
		if (list != null)
		{
			for(final Environmental E : list)
				if(E.amDestroyed())
					return null;
		}
		return list;
	}

	protected List<Item> checkInvCachedList(List<Item> list)
	{
		if (list != null)
		{
			for(final Item E : list)
				if((E.amDestroyed())||(!(E.owner() instanceof MOB)))
					return null;
		}
		return list;
	}

	protected List<Item> checkRoomItemCachedList(List<Item> list)
	{
		if (list != null)
		{
			for(final Item E : list)
				if((E.amDestroyed())||(!(E.owner() instanceof Room)))
					return null;
		}
		return list;
	}

	@SuppressWarnings("unchecked")
	public Map<String,List<MOB>> getMOBFinder()
	{
		Map<String,List<MOB>> finder=(Map<String,List<MOB>>)Resources.getResource("SYSTEM_MOB_FINDER_CACHE");
		if(finder==null)
		{
			finder=new PrioritizingLimitedMap<String,List<MOB>>(10,EXPIRE_5MINS,EXPIRE_10MINS,100);
			Resources.submitResource("SYSTEM_MOB_FINDER_CACHE",finder);
		}
		return finder;
	}
	@SuppressWarnings("unchecked")
	public Map<String,Area> getAreaFinder()
	{
		Map<String,Area> finder=(Map<String,Area>)Resources.getResource("SYSTEM_AREA_FINDER_CACHE");
		if(finder==null)
		{
			finder=new PrioritizingLimitedMap<String,Area>(50,EXPIRE_30MINS,EXPIRE_1HOUR,100);
			Resources.submitResource("SYSTEM_AREA_FINDER_CACHE",finder);
		}
		return finder;
	}

	@SuppressWarnings("unchecked")
	public Map<String,List<Item>> getRoomItemFinder()
	{
		Map<String,List<Item>> finder=(Map<String,List<Item>>)Resources.getResource("SYSTEM_RITEM_FINDER_CACHE");
		if(finder==null)
		{
			finder=new PrioritizingLimitedMap<String,List<Item>>(10,EXPIRE_5MINS,EXPIRE_10MINS,100);
			Resources.submitResource("SYSTEM_RITEM_FINDER_CACHE",finder);
		}
		return finder;
	}

	@SuppressWarnings("unchecked")
	public Map<String,List<Item>> getInvItemFinder()
	{
		Map<String,List<Item>> finder=(Map<String,List<Item>>)Resources.getResource("SYSTEM_IITEM_FINDER_CACHE");
		if(finder==null)
		{
			finder=new PrioritizingLimitedMap<String,List<Item>>(10,EXPIRE_1MIN,EXPIRE_10MINS,100);
			Resources.submitResource("SYSTEM_IITEM_FINDER_CACHE",finder);
		}
		return finder;
	}

	@SuppressWarnings("unchecked")
	public Map<String,List<Environmental>> getStockFinder()
	{
		Map<String,List<Environmental>> finder=(Map<String,List<Environmental>>)Resources.getResource("SYSTEM_STOCK_FINDER_CACHE");
		if(finder==null)
		{
			finder=new PrioritizingLimitedMap<String,List<Environmental>>(10,EXPIRE_10MINS,EXPIRE_1HOUR,100);
			Resources.submitResource("SYSTEM_STOCK_FINDER_CACHE",finder);
		}
		return finder;
	}

	@SuppressWarnings("unchecked")
	public Map<String,List<Room>> getRoomFinder()
	{
		Map<String,List<Room>> finder=(Map<String,List<Room>>)Resources.getResource("SYSTEM_ROOM_FINDER_CACHE");
		if(finder==null)
		{
			finder=new PrioritizingLimitedMap<String,List<Room>>(20,EXPIRE_20MINS,EXPIRE_1HOUR,100);
			Resources.submitResource("SYSTEM_ROOM_FINDER_CACHE",finder);
		}
		return finder;
	}

	protected List<Room> findWorldRoomsLiberally(MOB mob,
												 String cmd,
												 String srchWhatAERIPMVK,
												 Area area,
												 boolean returnFirst,
												 int timePct,
												 long maxMillis)
	{
		Room room=null;
		// wish this stuff could be cached, even temporarily, however,
		// far too much of the world is dynamic, and far too many searches
		// are looking for dynamic things.  the cached results would be useless
		// as soon as they are put away -- that's why the limited caches time them out!
		final boolean disableCaching= CMProps.getBoolVar(CMProps.Bool.MAPFINDSNOCACHE);

		final Vector<Room> rooms=(returnFirst)?null:new Vector<Room>();
		final Room curRoom=(mob!=null)?mob.location():null;

		boolean searchWeakAreas=false;
		boolean searchStrictAreas=false;
		boolean searchRooms=false;
		boolean searchPlayers=false;
		boolean searchItems=false;
		boolean searchInhabs=false;
		boolean searchInventories=false;
		boolean searchStocks=false;
		final char[] flags = srchWhatAERIPMVK.toUpperCase().toCharArray();
		for (final char flag : flags)
		{
			switch(flag)
			{
				case 'E': searchWeakAreas=true;   break;
				case 'A': searchStrictAreas=true; break;
				case 'R': searchRooms=true; 	  break;
				case 'P': searchPlayers=true;	 break;
				case 'I': searchItems=true; 	  break;
				case 'M': searchInhabs=true;	  break;
				case 'V': searchInventories=true; break;
				case 'K': searchStocks=true;	  break;
			}
		}
		final long startTime = System.currentTimeMillis();
		if(searchRooms)
		{
			final int dirCode=Directions.getGoodDirectionCode(cmd);
			if((dirCode>=0)&&(curRoom!=null))
				room=addWorldRoomsLiberally(rooms,curRoom.rawDoors()[dirCode]);
			if(room==null)
				room=addWorldRoomsLiberally(rooms,getRoom(cmd));
			if((room == null) && (curRoom != null) && (curRoom.getArea()!=null))
				room=addWorldRoomsLiberally(rooms,curRoom.getArea().getRoom(cmd));
		}

		if(room==null)
		{
			// first get room ids
			if((cmd.charAt(0)=='#')&&(curRoom!=null)&&(searchRooms))
			{
				room=addWorldRoomsLiberally(rooms,getRoom(curRoom.getArea().Name()+cmd));
				if(room == null)
					room=addWorldRoomsLiberally(rooms,curRoom.getArea().getRoom(curRoom.getArea().Name()+cmd));
			}
			else
			{
				final String srchStr=cmd;

				if(searchPlayers)
				{
					// then look for players
					final MOB M=CMLib.sessions().findPlayerOnline(srchStr,false);
					if(M!=null)
						room=addWorldRoomsLiberally(rooms,M.location());
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				// search areas strictly
				if(searchStrictAreas && (room==null) && (area==null))
				{
					area=getArea(srchStr);
					if((area!=null) &&(area.properSize()>0) &&(area.getProperRoomnumbers().roomCountAllAreas()>0))
						room=addWorldRoomsLiberally(rooms,area);
					area=null;
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				final Area A=area;
				final MultiEnumeratorBuilder<Room> roomer = new MultiEnumeratorBuilder<Room>()
				{
					@SuppressWarnings("unchecked")
					@Override
					public MultiEnumeration<Room> getList()
					{
						if(A==null)
							return new MultiEnumeration<Room>(roomsFilled());
						else
							return new MultiEnumeration<Room>(new Enumeration[]{A.getProperMap(),shipsRoomEnumerator(A)});
					}
				};

				// no good, so look for room inhabitants
				if(searchInhabs && room==null)
				{
					final Map<String,List<MOB>> finder=getMOBFinder();
					List<MOB> candidates=null;

					if((mob==null)||(mob.isMonster()))
					{
						candidates=checkMOBCachedList(finder.get(srchStr.toLowerCase()));
						if(returnFirst&&(candidates!=null)&&(candidates.size()>1))
							candidates=new XVector<MOB>(candidates.get(0));
					}
					if(candidates==null)
					{
						candidates=findInhabitants(roomer.getList(), mob, srchStr,returnFirst, timePct);
						if((!disableCaching)&&(!returnFirst)&&((mob==null)||(mob.isMonster())))
							finder.put(srchStr.toLowerCase(), candidates);

					}
					if(candidates.size()>0)
						room=addWorldRoomsLiberally(rooms,candidates);
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				// now check room text
				if(searchRooms && room==null)
				{
					final Map<String,List<Room>> finder=getRoomFinder();
					List<Room> candidates=null;
					if((mob==null)||(mob.isMonster()))
					{
						candidates=finder.get(srchStr.toLowerCase());
						if(returnFirst&&(candidates!=null)&&(candidates.size()>1))
							candidates=new XVector<Room>(candidates.get(0));
					}
					if(candidates==null)
					{
						candidates=findRooms(roomer.getList(), mob, srchStr, false,returnFirst, timePct);
						if((!disableCaching)&&(!returnFirst)&&((mob==null)||(mob.isMonster())))
							finder.put(srchStr.toLowerCase(), candidates);
					}
					if(candidates.size()>0)
						room=addWorldRoomsLiberally(rooms,candidates);
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				// check floor items
				if(searchItems && room==null)
				{
					final Map<String,List<Item>> finder=getRoomItemFinder();
					List<Item> candidates=null;
					if((mob==null)||(mob.isMonster()))
					{
						candidates=checkRoomItemCachedList(finder.get(srchStr.toLowerCase()));
						if(returnFirst&&(candidates!=null)&&(candidates.size()>1))
							candidates=new XVector<Item>(candidates.get(0));
					}
					if(candidates==null)
					{
						candidates=findRoomItems(roomer.getList(), mob, srchStr, false,returnFirst,timePct);
						if((!disableCaching)&&(!returnFirst)&&((mob==null)||(mob.isMonster())))
							finder.put(srchStr.toLowerCase(), candidates);
					}
					if(candidates.size()>0)
						room=addWorldRoomsLiberally(rooms,candidates);
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				// check inventories
				if(searchInventories && room==null)
				{
					final Map<String,List<Item>> finder=getInvItemFinder();
					List<Item> candidates=null;
					if((mob==null)||(mob.isMonster()))
					{
						candidates=checkInvCachedList(finder.get(srchStr.toLowerCase()));
						if(returnFirst&&(candidates!=null)&&(candidates.size()>1))
							candidates=new XVector<Item>(candidates.get(0));
					}
					if(candidates==null)
					{
						candidates=findInventory(roomer.getList(), mob, srchStr, returnFirst,timePct);
						if((!disableCaching)&&(!returnFirst)&&((mob==null)||(mob.isMonster())))
							finder.put(srchStr.toLowerCase(), candidates);
					}
					if(candidates.size()>0)
						room=addWorldRoomsLiberally(rooms,candidates);
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				// check stocks
				if(searchStocks && room==null)
				{
					final Map<String,List<Environmental>> finder=getStockFinder();
					List<Environmental> candidates=null;
					if((mob==null)||(mob.isMonster()))
					{
						candidates=finder.get(srchStr.toLowerCase());
						if(returnFirst&&(candidates!=null)&&(candidates.size()>1))
							candidates=new XVector<Environmental>(candidates.get(0));
					}
					if(candidates==null)
					{
						candidates=findShopStock(roomer.getList(), mob, srchStr, returnFirst,false,timePct);
						if((!disableCaching)&&(!returnFirst)&&((mob==null)||(mob.isMonster())))
							finder.put(srchStr.toLowerCase(), candidates);
					}
					if(candidates.size()>0)
						room=addWorldRoomsLiberally(rooms,candidates);
				}
				if(enforceTimeLimit(startTime,maxMillis))
					return returnResponse(rooms,room);

				// search areas weakly
				if(searchWeakAreas && (room==null) && (A==null))
				{
					Area A2=findArea(srchStr);
					if((A2!=null) &&(A2.properSize()>0) &&(A2.getProperRoomnumbers().roomCountAllAreas()>0))
						room=addWorldRoomsLiberally(rooms,A2);
				}
			}
		}
		final List<Room> responseSet = returnResponse(rooms,room);
		return responseSet;
	}

	@Override
	public boolean isHere(CMObject E2, Room here)
	{
		if(E2==null)
			return false;
		else
		if(E2==here)
			return true;
		else
		if((E2 instanceof MOB)
		&&(((MOB)E2).location()==here))
			return true;
		else
		if((E2 instanceof Item)
		&&(((Item)E2).owner()==here))
			return true;
		else
		if((E2 instanceof Item)
		&&(((Item)E2).owner()!=null)
		&&(((Item)E2).owner() instanceof MOB)
		&&(((MOB)((Item)E2).owner()).location()==here))
			return true;
		else
		if(E2 instanceof Exit)
		{
			for(int d=Directions.NUM_DIRECTIONS()-1;d>=0;d--)
				if(here.getRawExit(d)==E2)
					return true;
		}
		return false;
	}

	@Override
	public boolean isHere(CMObject E2, Area here)
	{
		if(E2==null)
			return false;
		else
		if(E2==here)
			return true;
		else
		if(E2 instanceof Room)
			return ((Room)E2).getArea()==here;
		else
		if(E2 instanceof MOB)
			return isHere(((MOB)E2).location(),here);
		else
		if(E2 instanceof Item)
			return isHere(((Item)E2).owner(),here);
		return false;
	}

	protected PairVector<MOB,String> getAllPlayersHere(Area area, boolean includeLocalFollowers)
	{
		final PairVector<MOB,String> playersHere=new PairVector<MOB,String>();
		MOB M=null;
		Room R=null;
		for(final Session S : CMLib.sessions().localOnlineIterable())
		{
			M=S.mob();
			R=(M!=null)?M.location():null;
			if((R!=null)&&(R.getArea()==area)&&(M!=null))
			{
				playersHere.addElement(M,getExtendedRoomID(R));
				if(includeLocalFollowers)
				{
					MOB M2=null;
					final Set<MOB> H=M.getGroupMembers(new HashSet<MOB>());
					for(final Iterator<MOB> i=H.iterator();i.hasNext();)
					{
						M2=i.next();
						if((M2!=M)&&(M2.location()==R))
							playersHere.addElement(M2,getExtendedRoomID(R));
					}
				}
			}
		}
		return playersHere;

	}

	@Override
	public void resetArea(Area area)
	{
		final Area.State oldFlag=area.getAreaState();
		area.setAreaState(Area.State.FROZEN);
		final PairVector<MOB,String> playersHere=getAllPlayersHere(area,true);
		final PairVector<PrivateProperty, String> propertyHere=new PairVector<PrivateProperty, String>();
		for(int p=0;p<playersHere.size();p++)
		{
			final MOB M=playersHere.elementAt(p).first;
			final Room R=M.location();
			R.delInhabitant(M);
		}
		for(final Enumeration<BoardableShip> b=ships();b.hasMoreElements();)
		{
			final BoardableShip ship=b.nextElement();
			final Room R=roomLocation(ship);
			if((R!=null)
			&&(R.getArea()==area)
			&&(ship instanceof PrivateProperty)
			&&(((PrivateProperty)ship).getOwnerName().length()>0)
			&&(ship instanceof Item))
			{
				R.delItem((Item)ship);
				propertyHere.add((PrivateProperty)ship,CMLib.map().getExtendedRoomID(R));
			}
		}
			
		for(final Enumeration<Room> r=area.getProperMap();r.hasMoreElements();)
			resetRoom(r.nextElement());
		area.fillInAreaRooms();
		for(int p=0;p<playersHere.size();p++)
		{
			final MOB M=playersHere.elementAt(p).first;
			Room R=getRoom(playersHere.elementAt(p).second);
			if(R==null)
				R=M.getStartRoom();
			if(R==null)
				R=getStartRoom(M);
			if(R!=null)
				R.bringMobHere(M,false);
		}
		for(int p=0;p<propertyHere.size();p++)
		{
			final PrivateProperty P=propertyHere.elementAt(p).first;
			Room R=getRoom(propertyHere.elementAt(p).second);
			if((R==null)||(R.amDestroyed()))
				R=getSafeRoomToMovePropertyTo((R==null)?area.getRandomProperRoom():R,P);
			if(R!=null)
				R.moveItemTo((Item)P);
		}
		CMLib.database().DBReadAreaData(area);
		area.setAreaState(oldFlag);
	}

	@Override
	public boolean hasASky(Room room)
	{
		if((room==null)
		||(room.domainType()==Room.DOMAIN_OUTDOORS_UNDERWATER)
		||((room.domainType()&Room.INDOORS)>0))
			return false;
		return true;
	}

	@Override
	public void registerWorldObjectDestroyed(Area area, Room room, CMObject o)
	{
		if(o instanceof Deity)
			delDeity((Deity)o);

		if(o instanceof BoardableShip)
			delShip((BoardableShip)o);

		if(o instanceof PostOffice)
			delPostOffice((PostOffice)o);

		if(o instanceof Banker)
			delBank((Banker)o);

		if(o instanceof Auctioneer)
			delAuctionHouse((Auctioneer)o);

		if(o instanceof PhysicalAgent)
		{
			final PhysicalAgent AE=(PhysicalAgent)o;
			if((area == null) && (room!=null))
				area = room.getArea();
			if(area == null)
				area =getStartArea(AE);
			delScriptHost(area, AE);
		}
	}

	@Override
	public void registerWorldObjectLoaded(Area area, Room room, final CMObject o)
	{
		if(o instanceof Deity)
			addDeity((Deity)o);

		if(o instanceof BoardableShip)
			addShip((BoardableShip)o);

		if(o instanceof PostOffice)
			addPostOffice((PostOffice)o);

		if(o instanceof Banker)
			addBank((Banker)o);

		if(o instanceof Auctioneer)
			addAuctionHouse((Auctioneer)o);

		if(o instanceof PhysicalAgent)
		{
			final PhysicalAgent AE=(PhysicalAgent)o;
			if(room == null)
				room = getStartRoom(AE);
			if((area == null) && (room!=null))
				area = room.getArea();
			if(area == null)
				area = getStartArea(AE);
			addScriptHost(area, room, AE);
			if(o instanceof MOB)
				for(final Enumeration<Item> i=((MOB)o).items();i.hasMoreElements();)
					addScriptHost(area, room, i.nextElement());
		}
	}

	protected void cleanScriptHosts(final SLinkedList<LocatedPair> hosts, final PhysicalAgent oneToDel, final boolean fullCleaning)
	{
		PhysicalAgent PA;
		for (final LocatedPair W : hosts)
		{
			if(W==null)
				hosts.remove(W);
			else
			{
				PA=W.obj();
				if((PA==null)
				||(PA==oneToDel)
				||(PA.amDestroyed())
				||((fullCleaning)&&(!isAQualifyingScriptHost(PA))))
					hosts.remove(W);
			}
		}
	}

	protected boolean isAQualifyingScriptHost(final PhysicalAgent host)
	{
		if(host==null)
			return false;
		for(final Enumeration<Behavior> e = host.behaviors();e.hasMoreElements();)
		{
			final Behavior B=e.nextElement();
			if((B!=null) && B.isSavable() && (B instanceof ScriptingEngine))
				return true;
		}
		for(final Enumeration<ScriptingEngine> e = host.scripts();e.hasMoreElements();)
		{
			final ScriptingEngine SE=e.nextElement();
			if((SE!=null) && SE.isSavable())
				return true;
		}
		return false;
	}

	@Override
	public int numSpaceObjects()
	{
		return space.count();
	}

	
	protected boolean isAScriptHost(final Area area, final PhysicalAgent host)
	{
		if(area == null)
			return false;
		return isAScriptHost(scriptHostMap.get(area.Name()), host);
	}

	protected boolean isAScriptHost(final SLinkedList<LocatedPair> hosts, final PhysicalAgent host)
	{
		if((hosts==null)||(host==null)||(hosts.size()==0))
			return false;
		for (final LocatedPair W : hosts)
		{
			if(W.obj()==host)
				return true;
		}
		return false;
	}

	protected final Object getScriptHostSemaphore(final Area area)
	{
		final Object semaphore;
		if(SCRIPT_HOST_SEMAPHORES.containsKey(area.Name()))
			semaphore=SCRIPT_HOST_SEMAPHORES.get(area.Name());
		else
		{
			synchronized(SCRIPT_HOST_SEMAPHORES)
			{
				semaphore=new Object();
				SCRIPT_HOST_SEMAPHORES.put(area.Name(), semaphore);
			}
		}
		return semaphore;
	}

	protected void addScriptHost(Area area, Room room, PhysicalAgent host)
	{
		if((area==null) || (host == null))
			return;
		if(!isAQualifyingScriptHost(host))
			return;
		synchronized(getScriptHostSemaphore(area))
		{
			SLinkedList<LocatedPair> hosts = scriptHostMap.get(area.Name());
			if(hosts == null)
			{
				hosts=new SLinkedList<LocatedPair>();
				scriptHostMap.put(area.Name(), hosts);
			}
			else
			{
				cleanScriptHosts(hosts, null, false);
				if(isAScriptHost(hosts,host))
					return;
			}
			hosts.add(new LocatedPairImpl(room, host));
		}
	}
	protected void delScriptHost(Area area, final PhysicalAgent oneToDel)
	{
		if(oneToDel == null)
			return;
		if(area == null)
			for(final Area A : areasList)
				if(isAScriptHost(A,oneToDel))
				{
					area = A;
					break;
				}
		if(area == null)
			return;
		synchronized(getScriptHostSemaphore(area))
		{
			final SLinkedList<LocatedPair> hosts = scriptHostMap.get(area.Name());
			if(hosts==null)
				return;
			cleanScriptHosts(hosts, oneToDel, false);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<LocatedPair> scriptHosts(final Area area)
	{
		final LinkedList<List<LocatedPair>> V = new LinkedList<List<LocatedPair>>();
		if(area == null)
		{
			for(final String areaKey : scriptHostMap.keySet())
				V.add(scriptHostMap.get(areaKey));
		}
		else
		{
			final SLinkedList<LocatedPair> hosts = scriptHostMap.get(area.Name());
			if(hosts==null)
				return EmptyEnumeration.INSTANCE;
			V.add(hosts);
		}
		if(V.size()==0)
			return EmptyEnumeration.INSTANCE;
		final MultiListEnumeration<LocatedPair> me=new MultiListEnumeration<LocatedPair>(V,true);
		return new Enumeration<LocatedPair>()
		{
			@Override
			public boolean hasMoreElements()
			{
				return me.hasMoreElements();
			}

			@Override
			public LocatedPair nextElement()
			{
				final LocatedPair W = me.nextElement();
				final PhysicalAgent E = W.obj();
				if(((E==null) || (E.amDestroyed())) && hasMoreElements())
					return nextElement();
				return W;
			}
		};
	}

	@Override
	public boolean activate()
	{
		if(serviceClient==null)
		{
			name="THMap"+Thread.currentThread().getThreadGroup().getName().charAt(0);
			serviceClient=CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK, MudHost.TIME_SAVETHREAD_SLEEP, 1);
		}
		return true;
	}

	@Override 
	public boolean tick(Tickable ticking, int tickID)
	{
		try
		{
			if((!CMSecurity.isDisabled(CMSecurity.DisFlag.SAVETHREAD))
			&&(!CMSecurity.isDisabled(CMSecurity.DisFlag.MAPTHREAD)))
			{
				isDebugging=CMSecurity.isDebugging(DbgFlag.MAPTHREAD);
				tickStatus=Tickable.STATUS_ALIVE;
				if(checkDatabase())
					roomMaintSweep();
			}
			Resources.savePropResources();
		}
		finally
		{
			tickStatus=Tickable.STATUS_NOT;
			setThreadStatus(serviceClient,"sleeping");
		}
		return true;
	}

	@Override
	public boolean shutdown()
	{
		final boolean debugMem = CMSecurity.isDebugging(CMSecurity.DbgFlag.SHUTDOWN);
		for(Enumeration<Area> a=areasList.elements();a.hasMoreElements();)
		{
			try 
			{
				final Area A = a.nextElement();
				if(A!=null)
				{
					CMProps.setUpAllLowVar(CMProps.Str.MUDSTATUS,"Shutting down Map area '"+A.Name()+"'...");
					for(Enumeration<Room> r=A.getProperMap();r.hasMoreElements();)
					{
						try 
						{
							final Room R=r.nextElement();
							A.delProperRoom(R);
							R.destroy();
						} 
						catch(Exception e) 
						{
						}
					}
				}
				if(debugMem)
				{
					try
					{
						Object obj = new Object();
						WeakReference<Object> ref = new WeakReference<Object>(obj);
						obj = null;
						System.gc();
						System.runFinalization();
						while(ref.get() != null) 
						{
							System.gc();
						}
						Thread.sleep(3000);
					}
					catch (final Exception e)
					{
					}
					final long free=Runtime.getRuntime().freeMemory()/1024;
					final long total=Runtime.getRuntime().totalMemory()/1024;
					if(A!=null)
						Log.debugOut("Memory: CMMap: "+A.Name()+": "+(total-free)+"/"+total);
				}
			}
			catch (Exception e)
			{
			}
		}
		areasList.clear();
		deitiesList.clear();
		shipList.clear();
		space.clear();
		globalHandlers.clear();
		if(CMLib.threads().isTicking(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK))
		{
			CMLib.threads().deleteTick(this, TICKID_SUPPORT|Tickable.TICKID_SOLITARYMASK);
			serviceClient=null;
		}
		return true;
	}

	public void roomMaintSweep()
	{
		final boolean corpsesOnly=CMSecurity.isSaveFlag(CMSecurity.SaveFlag.ROOMITEMS);
		final boolean noMobs=CMSecurity.isSaveFlag(CMSecurity.SaveFlag.ROOMMOBS);
		setThreadStatus(serviceClient,"expiration sweep");
		final long currentTime=System.currentTimeMillis();
		final boolean debug=CMSecurity.isDebugging(CMSecurity.DbgFlag.VACUUM);
		final MOB expireM=getFactoryMOB(null);
		try
		{
			final List<Environmental> stuffToGo=new ArrayList<Environmental>();
			Item I=null;
			MOB M=null;
			Room R=null;
			final List<Room> roomsToGo=new ArrayList<Room>();
			final CMMsg expireMsg=CMClass.getMsg(expireM,R,null,CMMsg.MSG_EXPIRE,null);
			for(final Enumeration<Room> r=rooms();r.hasMoreElements();)
			{
				R=r.nextElement();
				expireM.setLocation(R);
				expireMsg.setTarget(R);
				if((R.expirationDate()!=0)
				&&(currentTime>R.expirationDate())
				&&(R.okMessage(R,expireMsg)))
					roomsToGo.add(R);
				else
				if(!R.amDestroyed())
				{
					stuffToGo.clear();
					for(int i=0;i<R.numItems();i++)
					{
						I=R.getItem(i);
						if((I!=null)
						&&((!corpsesOnly)||(I instanceof DeadBody))
						&&(I.expirationDate()!=0)
						&&(I.owner()==R)
						&&(currentTime>I.expirationDate()))
							stuffToGo.add(I);
					}
					for(int i=0;i<R.numInhabitants();i++)
					{
						M=R.fetchInhabitant(i);
						if((M!=null)
						&&(!noMobs)
						&&(M.expirationDate()!=0)
						&&(currentTime>M.expirationDate()))
							stuffToGo.add(M);
					}
				}
				if(stuffToGo.size()>0)
				{
					boolean success=true;
					for(int s=0;s<stuffToGo.size();s++)
					{
						final Environmental E=stuffToGo.get(s);
						setThreadStatus(serviceClient,"expiring "+E.Name());
						expireMsg.setTarget(E);
						if(R.okMessage(expireM,expireMsg))
							R.sendOthers(expireM,expireMsg);
						else
							success=false;
						if(debug)
							Log.sysOut("UTILITHREAD","Expired "+E.Name()+" in "+getExtendedRoomID(R)+": "+success);
					}
					stuffToGo.clear();
				}
			}
			for(int r=0;r<roomsToGo.size();r++)
			{
				R=roomsToGo.get(r);
				expireM.setLocation(R);
				expireMsg.setTarget(R);
				setThreadStatus(serviceClient,"expirating room "+getExtendedRoomID(R));
				if(debug)
				{
					String roomID=getExtendedRoomID(R);
					if(roomID.length()==0)
						roomID="(unassigned grid room, probably in the air)";
					if(debug)
						Log.sysOut("UTILITHREAD","Expired "+roomID+".");
				}
				R.sendOthers(expireM,expireMsg);
			}

		}
		catch(final java.util.NoSuchElementException e)
		{}
		setThreadStatus(serviceClient,"title sweeping");
		final List<String> playerList=CMLib.database().getUserList();
		try
		{
			for(final Enumeration<Room> r=rooms();r.hasMoreElements();)
			{
				final Room R=r.nextElement();
				final LandTitle T=CMLib.law().getLandTitle(R);
				if(T!=null)
				{
					setThreadStatus(serviceClient,"checking title in "+R.roomID()+": "+Runtime.getRuntime().freeMemory());
					T.updateLot(playerList);
					setThreadStatus(serviceClient,"title sweeping");
				}
			}
		}
		catch(final NoSuchElementException nse)
		{}

		setThreadStatus(serviceClient,"cleaning scripts");
		for(final String areaKey : scriptHostMap.keySet())
			cleanScriptHosts(scriptHostMap.get(areaKey), null, true);

		final long lastDateTime=System.currentTimeMillis()-(5*TimeManager.MILI_MINUTE);
		setThreadStatus(serviceClient,"checking");
		try
		{
			for(final Enumeration<Room> r=rooms();r.hasMoreElements();)
			{
				final Room R=r.nextElement();
				for(int m=0;m<R.numInhabitants();m++)
				{
					final MOB mob=R.fetchInhabitant(m);
					if((mob!=null)&&(mob.lastTickedDateTime()>0)&&(mob.lastTickedDateTime()<lastDateTime))
					{
						final boolean ticked=CMLib.threads().isTicking(mob,Tickable.TICKID_MOB);
						final boolean isDead=mob.amDead();
						final Room startR=mob.getStartRoom();
						final String wasFrom=(startR!=null)?startR.roomID():"NULL";
						if(!ticked)
						{
							if(CMLib.players().getPlayer(mob.Name())==null)
							{
								if(ticked)
								{
									// we have a dead group.. let the group handler deal with it.
									Log.errOut(serviceClient.getName(),mob.name()+" in room "+R.roomID()+" unticked in dead group (Home="+wasFrom+") since: "+CMLib.time().date2String(mob.lastTickedDateTime())+".");
									continue;
								}
								else
									Log.errOut(serviceClient.getName(),mob.name()+" in room "+R.roomID()+" unticked (is ticking="+(ticked)+", dead="+isDead+", Home="+wasFrom+") since: "+CMLib.time().date2String(mob.lastTickedDateTime())+"."+(ticked?"":"  This mob has been destroyed. May he rest in peace."));
							}
							else
								Log.errOut(serviceClient.getName(),"Player "+mob.name()+" in room "+R.roomID()+" unticked (is ticking="+(ticked)+", dead="+isDead+", Home="+wasFrom+") since: "+CMLib.time().date2String(mob.lastTickedDateTime())+"."+(ticked?"":"  This mob has been put aside."));
							setThreadStatus(serviceClient,"destroying unticked mob "+mob.name());
							if(CMLib.players().getPlayer(mob.Name())==null)
								mob.destroy();
							R.delInhabitant(mob);
							setThreadStatus(serviceClient,"checking");
						}
					}
				}
			}
		}
		catch(final java.util.NoSuchElementException e)
		{
		}
		finally
		{
			if(expireM!=null)
				expireM.destroy();
		}
	}


	protected final static char[] cmfsFilenameifyChars=new char[]{'/','\\',' '};

	protected String cmfsFilenameify(String str)
	{
		return CMStrings.replaceAllofAny(str, cmfsFilenameifyChars, '_').toLowerCase().trim();
	}

	// this is a beautiful idea, but im scared of the memory of all the final refs
	protected void addMapStatFiles(final List<CMFile.CMVFSFile> rootFiles, final Room R, final Environmental E, final CMFile.CMVFSDir root)
	{
		rootFiles.add(new CMFile.CMVFSDir(root,root.getPath()+"stats/")
		{
			@Override 
			protected CMFile.CMVFSFile[] getFiles()
			{
				final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
				final String[] stats=E.getStatCodes();
				final String oldName=E.Name();
				for (final String statName : stats)
				{
					final String statValue=E.getStat(statName);
					myFiles.add(new CMFile.CMVFSFile(this.getPath()+statName,256,System.currentTimeMillis(),"SYS")
					{
						@Override 
						public int getMaskBits(MOB accessor)
						{
							if(accessor==null)
								return this.mask;
							if((E instanceof Area)&&(CMSecurity.isAllowed(accessor,((Area)E).getRandomProperRoom(),CMSecurity.SecFlag.CMDAREAS)))
								return this.mask;
							else
							if(CMSecurity.isAllowed(accessor,R,CMSecurity.SecFlag.CMDROOMS))
								return this.mask;
							else
							if((E instanceof MOB) && CMSecurity.isAllowed(accessor,R,CMSecurity.SecFlag.CMDMOBS))
								return this.mask;
							else
							if((E instanceof Item) && CMSecurity.isAllowed(accessor,R,CMSecurity.SecFlag.CMDITEMS))
								return this.mask;
							return this.mask|48;
						}

						@Override 
						public Object readData()
						{
							return statValue;
						}
						@Override 
						public void saveData(String filename, int vfsBits, String author, Object O)
						{
							E.setStat(statName, O.toString());
							if(E instanceof Area)
								CMLib.database().DBUpdateArea(oldName, (Area)E);
							else
							if(E instanceof Room)
								CMLib.database().DBUpdateRoom((Room)E);
							else
							if(E instanceof MOB)
								CMLib.database().DBUpdateMOB(R.roomID(), (MOB)E);
							else
							if(E instanceof Item)
								CMLib.database().DBUpdateItem(R.roomID(), (Item)E);
						}
					});
				}
				Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
				return myFiles.toArray(new CMFile.CMVFSFile[0]);
			}
		});
	}

	@Override
	public CMFile.CMVFSDir getMapRoot(final CMFile.CMVFSDir root)
	{
		return new CMFile.CMVFSDir(root,root.getPath()+"map/")
		{
			@Override 
			protected CMFile.CMVFSFile[] getFiles()
			{
				final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>(numAreas());
				for(final Enumeration<Area> a=CMLib.map().areas();a.hasMoreElements();)
				{
					final Area A=a.nextElement();
					myFiles.add(new CMFile.CMVFSFile(this.getPath()+cmfsFilenameify(A.Name())+".cmare",48,System.currentTimeMillis(),"SYS")
					{
						@Override 
						public Object readData()
						{
							return CMLib.coffeeMaker().getAreaXML(A, null, null, null, true);
						}
					});
					myFiles.add(new CMFile.CMVFSDir(this,this.getPath()+cmfsFilenameify(A.Name())+"/")
					{
						@Override 
						protected CMFile.CMVFSFile[] getFiles()
						{
							final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
							for(final Enumeration<Room> r=A.getFilledProperMap();r.hasMoreElements();)
							{
								final Room R=r.nextElement();
								if(R.roomID().length()>0)
								{
									String roomID=R.roomID();
									if(roomID.startsWith(A.Name()+"#"))
										roomID=roomID.substring(A.Name().length()+1);
									myFiles.add(new CMFile.CMVFSFile(this.getPath()+cmfsFilenameify(R.roomID())+".cmare",48,System.currentTimeMillis(),"SYS")
									{
										@Override 
										public Object readData()
										{
											return CMLib.coffeeMaker().getRoomXML(R, null, null, true);
										}
									});
									myFiles.add(new CMFile.CMVFSDir(this,this.getPath()+cmfsFilenameify(roomID).toLowerCase()+"/")
									{
										@Override 
										protected CMFile.CMVFSFile[] getFiles()
										{
											final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
											myFiles.add(new CMFile.CMVFSFile(this.getPath()+"items.cmare",48,System.currentTimeMillis(),"SYS")
											{
												@Override 
												public Object readData()
												{
													return CMLib.coffeeMaker().getRoomItems(R, new TreeMap<String,List<Item>>(), null, null);
												}
											});
											myFiles.add(new CMFile.CMVFSFile(this.path+"mobs.cmare",48,System.currentTimeMillis(),"SYS")
											{
												@Override 
												public Object readData()
												{
													return CMLib.coffeeMaker().getRoomMobs(R, null, null, new TreeMap<String,List<MOB>>());
												}
											});
											myFiles.add(new CMFile.CMVFSDir(this,this.path+"mobs/")
											{
												@Override 
												protected CMFile.CMVFSFile[] getFiles()
												{
													final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
													final Room R2=CMLib.coffeeMaker().makeNewRoomContent(R, false);
													for(int i=0;i<R2.numInhabitants();i++)
													{
														final MOB M=R2.fetchInhabitant(i);
														myFiles.add(new CMFile.CMVFSFile(this.path+cmfsFilenameify(R2.getContextName(M))+".cmare",48,System.currentTimeMillis(),"SYS")
														{
															@Override 
															public Object readData()
															{
																return CMLib.coffeeMaker().getMobXML(M);
															}
														});
														myFiles.add(new CMFile.CMVFSDir(this,this.path+cmfsFilenameify(R2.getContextName(M))+"/")
														{
															@Override 
															protected CMFile.CMVFSFile[] getFiles()
															{
																final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
																addMapStatFiles(myFiles,R,M,this);
																Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
																return myFiles.toArray(new CMFile.CMVFSFile[0]);
															}
														});
													}
													Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
													return myFiles.toArray(new CMFile.CMVFSFile[0]);
												}
											});
											myFiles.add(new CMFile.CMVFSDir(this,this.path+"items/")
											{
												@Override 
												protected CMFile.CMVFSFile[] getFiles()
												{
													final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
													final Room R2=CMLib.coffeeMaker().makeNewRoomContent(R, false);
													if(R2 != null)
													{
														for(int i=0;i<R2.numItems();i++)
														{
															final Item I=R2.getItem(i);
															myFiles.add(new CMFile.CMVFSFile(this.path+cmfsFilenameify(R2.getContextName(I))+".cmare",48,System.currentTimeMillis(),"SYS")
															{
																@Override 
																public Object readData()
																{
																	return CMLib.coffeeMaker().getItemXML(I);
																}
															});
															myFiles.add(new CMFile.CMVFSDir(this,this.path+cmfsFilenameify(R2.getContextName(I))+"/")
															{
																@Override 
																protected CMFile.CMVFSFile[] getFiles()
																{
																	final List<CMFile.CMVFSFile> myFiles=new Vector<CMFile.CMVFSFile>();
																	addMapStatFiles(myFiles,R,I,this);
																	Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
																	return myFiles.toArray(new CMFile.CMVFSFile[0]);
																}
															});
														}
														Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
														return myFiles.toArray(new CMFile.CMVFSFile[0]);
													}
													return new CMFile.CMVFSFile[0];
												}
											});
											addMapStatFiles(myFiles,R,R,this);
											Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
											return myFiles.toArray(new CMFile.CMVFSFile[0]);
										}
									});
								}
							}
							addMapStatFiles(myFiles,null,A,this);
							Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
							return myFiles.toArray(new CMFile.CMVFSFile[0]);
						}
					});
				}
				Collections.sort(myFiles,CMFile.CMVFSDir.fcomparator);
				return myFiles.toArray(new CMFile.CMVFSFile[0]);
			}
		};
	}

}