/
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/Specializations/
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/
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/BasicTech/
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.Areas;
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.interfaces.BoundedObject;
import com.planet_ink.coffee_mud.core.interfaces.BoundedObject.BoundedCube;
import com.planet_ink.coffee_mud.core.interfaces.Places;
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.Basic.GenPortal;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.Technical.TechCommand;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.lang.ref.WeakReference;
import java.util.*;

/*
   Copyright 2004-2019 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 StdSpaceShip extends StdBoardableShip implements SpaceShip
{
	private static final long STALE_AIR_INTERVAL = 5 * 60 * 1000;

	private static final long STALE_WARN_INTERVAL = 5 * 30 * 1000;

	protected static Climate		climateObj	= null;
	protected volatile ShipEngine	lastEngine	= null;
	protected volatile double		lastEThrust	= 0.0;

	protected volatile int	mass			= -1;
	protected SpaceObject	spaceSource		= null;
	protected TimeClock		localClock		= (TimeClock) CMClass.getCommon("DefaultTimeClock");
	protected int			atmosphere		= RawMaterial.RESOURCE_AIR;
	protected long			radius			= 50;
	protected double		omlCoeff		= SpaceObject.ATMOSPHERIC_DRAG_STREAMLINE + ((SpaceObject.ATMOSPHERIC_DRAG_BRICK - SpaceObject.ATMOSPHERIC_DRAG_STREAMLINE) / 2.0);
	protected volatile long	nextStaleCheck	= System.currentTimeMillis() + STALE_AIR_INTERVAL;
	protected volatile long	nextStaleWarn	= System.currentTimeMillis() + STALE_WARN_INTERVAL;
	protected Set<String> 	staleAirList	= new HashSet<String>();
	protected Ability 		gravityFloaterA = null;


	@Override
	public String ID()
	{
		return "StdSpaceShip";
	}

	@Override
	public void initializeClass()
	{
	}

	@Override
	public Room getIsDocked()
	{
		return CMLib.map().getRoom(savedDock);
	}

	@Override
	public void setClimateObj(final Climate obj)
	{
		climateObj = obj;
	}

	@Override
	public Climate getClimateObj()
	{
		if (climateObj == null)
		{
			climateObj = (Climate) CMClass.getCommon("DefaultClimate");
			climateObj.setCurrentWeatherType(Climate.WEATHER_CLEAR);
			climateObj.setNextWeatherType(Climate.WEATHER_CLEAR);
		}
		return climateObj;
	}

	@Override
	public double getOMLCoeff()
	{
		return omlCoeff;
	}

	@Override
	public void setOMLCoeff(final double coeff)
	{
		omlCoeff = coeff;
	}

	@Override
	public TimeClock getTimeObj()
	{
		return localClock;
	}

	@Override
	public void setTimeObj(final TimeClock obj)
	{
		localClock = obj;
	}

	@Override
	public int getAtmosphereCode()
	{
		return atmosphere;
	}

	@Override
	public void setAtmosphere(final int resourceCode)
	{
		atmosphere = resourceCode;
	}

	@Override
	public int getAtmosphere()
	{
		return atmosphere == ATMOSPHERE_INHERIT ? RawMaterial.RESOURCE_AIR : atmosphere;
	}

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

	@Override
	public void setRadius(final long radius)
	{
		this.radius = radius;
	}

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

	@Override
	public SpaceObject knownSource()
	{
		return spaceSource;
	}

	@Override
	public void setKnownSource(final SpaceObject O)
	{
		spaceSource = O;
	}

	@Override
	public long[] coordinates()
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).coordinates() : new long[3];
	}

	@Override
	public void setCoords(final long[] coords)
	{
		if (shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setCoords(coords);
	}

	@Override
	public double[] direction()
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).direction() : new double[2];
	}

	@Override
	public double roll()
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).roll() : 0;
	}

	@Override
	public void setRoll(final double dir)
	{
		if (shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setRoll(dir);
	}

	@Override
	public void setDirection(final double[] dir)
	{
		if (shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setDirection(dir);
	}

	@Override
	public double[] facing()
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).facing() : new double[2];
	}

	@Override
	public void setFacing(final double[] dir)
	{
		if (shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setFacing(dir);
	}

	@Override
	public double speed()
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).speed() : 0;
	}

	@Override
	public void setSpeed(final double v)
	{
		if (shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setSpeed(v);
	}

	@Override
	public SpaceObject knownTarget()
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).knownTarget() : null;
	}

	@Override
	public void setKnownTarget(final SpaceObject O)
	{
		if (shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setKnownTarget(O);
	}

	@Override
	public void setShipFlag(final ShipFlag flag, final boolean setShipFlag)
	{
		if(shipItem instanceof SpaceShip)
			((SpaceShip) shipItem).setShipFlag(flag,setShipFlag);
	}

	@Override
	public boolean getShipFlag(final ShipFlag flag)
	{
		return (shipItem instanceof SpaceShip) ? ((SpaceShip) shipItem).getShipFlag(flag) : false;
	}

	@Override
	public void setDockableItem(final Item dockableItem)
	{
		if(dockableItem instanceof SpaceShip)
			shipItem=(SpaceShip)dockableItem;
	}

	@Override
	public BoundedCube getBounds()
	{
		return new BoundedObject.BoundedCube(coordinates(),radius());
	}

	@Override
	public long getMass()
	{
		final long mass=this.mass;
		if(mass<0)
		{
			int newMass=phyStats().weight();
			for(final Enumeration<Room> r=getProperMap(); r.hasMoreElements();)
			{
				final Room R=r.nextElement();
				if(R!=null)
				{
					for(int i=0;i<R.numItems();i++)
					{
						final Item I=R.getItem(i);
						if(I!=null)
							newMass += I.phyStats().weight();
					}
					for(int i=0;i<R.numInhabitants();i++)
					{
						final MOB M=R.fetchInhabitant(i);
						if(M!=null)
							newMass += M.phyStats().weight();
					}
				}
			}
			this.mass=newMass;
		}
		return this.mass;
	}

	@Override
	public void destroy()
	{
		CMLib.map().delObjectInSpace(this);
		super.destroy();
		spaceSource=null;
		climateObj=null;
	}

	@Override
	public int getClimateTypeCode()
	{
		return Places.CLIMASK_NORMAL;
	}

	@Override
	public int getClimateType()
	{
		return Places.CLIMASK_NORMAL;
	}

	@Override
	public void setClimateType(final int newClimateType)
	{
	}

	public StdSpaceShip()
	{
		super();
		setName("a space ship");
	}

	@Override
	public void setName(final String newName)
	{
		super.setName(newName);
		localClock.setLoadName(newName);
	}

	@Override
	public int getTheme()
	{
		return Area.THEME_TECHNOLOGY;
	}

	@Override
	public int getThemeCode()
	{
		return Area.THEME_TECHNOLOGY;
	}

	@Override
	public void setTheme(final int level)
	{
	}

	@Override
	public CMObject newInstance()
	{
		try
		{
			return this.getClass().newInstance();
		}
		catch(final Exception e)
		{
			Log.errOut(ID(),e);
		}
		return new StdSpaceShip();
	}

	@Override
	public boolean isGeneric()
	{
		return false;
	}

	@Override
	protected void cloneFix(final StdBoardableShip ship)
	{
		super.cloneFix(ship);
		setTimeObj((TimeClock)CMClass.getCommon("DefaultTimeClock"));
	}

	@Override
	public CMObject copyOf()
	{
		try
		{
			final StdSpaceShip E=(StdSpaceShip)this.clone();
			//CMClass.bumpCounter(E,CMClass.CMObjectType.AREA);//removed for mem & perf
			E.xtraValues=(xtraValues==null)?null:(String[])xtraValues.clone();
			E.cloneFix(this);
			return E;

		}
		catch(final CloneNotSupportedException e)
		{
			return this.newInstance();
		}
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		super.executeMsg(myHost, msg);

		if((msg.sourceMinor()==CMMsg.TYP_DROP)||(msg.sourceMinor()==CMMsg.TYP_GET))
			mass=-1;

		if(msg.amITarget(this))
		{
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_ACTIVATE:
				if(CMath.bset(msg.targetMajor(), CMMsg.MASK_CNTRLMSG))
				{
					final String[] parts=msg.targetMessage().split(" ");
					final TechCommand command=TechCommand.findCommand(parts);
					if(command!=null)
					{
						final Object[] parms=command.confirmAndTranslate(parts);
						if(parms!=null)
						{
							if(command==Technical.TechCommand.AIRREFRESH)
							{
								if((staleAirList.size()==0)
								&&(msg.tool() instanceof Item)
								&&(((Item)msg.tool()).owner() instanceof Room)
								&&(((Room)((Item)msg.tool()).owner()).getAtmosphere()<=0))
									doStaleCheck();
								if(staleAirList.size()>0)
								{
									final double pct=((Double)parms[0]).doubleValue();
									final int atmoResource=((Integer)parms[1]).intValue();
									int numToClear=(int)Math.round(CMath.mul(staleAirList.size(),pct));
									while((numToClear>0)&&(staleAirList.size()>0))
									{
										final String roomID=staleAirList.iterator().next();
										staleAirList.remove(roomID);
										changeRoomAir(getRoom(roomID),null,atmoResource);
										numToClear--;
									}
									changeRoomAir(getRandomMetroRoom(),null,atmoResource);
									for(final Pair<Room,Integer> p  : shipExitCache)
										changeRoomAir(p.first,null,atmoResource);
								}
								//if(CMSecurity.isDebugging(DbgFlag.SPACESHIP))
								//	Log.debugOut("Refreshed the air in "+Name()+", stale rooms: "+staleAirList.size());
							}
						}
					}
				}
				break;
			}
		}
		else
		{
			switch(msg.sourceMinor())
			{
			case CMMsg.TYP_STAND:
			case CMMsg.TYP_DISMOUNT:
			{
				if(this.getShipFlag(ShipFlag.NO_GRAVITY))
				{
					msg.addTrailerRunnable(new Runnable()
					{
						@Override
						public void run()
						{
							final Ability floater = getGravityFloat();
							if(floater != null)
								floater.invoke(floater.invoker(), msg.source(), false, 0);
						}
					});
				}
				break;
			}
			}
			switch(msg.targetMinor())
			{
			case CMMsg.TYP_DROP:
			case CMMsg.TYP_THROW:
				if(((msg.target() instanceof Item)||(msg.tool() instanceof Item))
				&&(!msg.targetMajor(CMMsg.MASK_INTERMSG))) // give is different
				{
					if(this.getShipFlag(ShipFlag.NO_GRAVITY))
					{
						final Item I=(msg.target() instanceof Item) ? (Item)msg.target(): (Item)msg.tool();
						msg.addTrailerRunnable(new Runnable()
						{
							@Override
							public void run()
							{
								final Ability floater = getGravityFloat();
								if(floater != null)
									floater.invoke(floater.invoker(), I, false, 0);
							}
						});
					}
				}
				break;
			}
		}
	}

	public int[] addMaskAndReturn(final int[] one, final int[] two)
	{
		if(one.length!=two.length)
			return one;
		final int[] returnable=new int[one.length];
		for(int o=0;o<one.length;o++)
			returnable[o]=one[o]+two[o];
		return returnable;
	}

	protected boolean changeRoomAir(final Room R, final Room notifyRoom, final int atmoResource)
	{
		if(R==null)
			return false;
		if(R.getAtmosphere()!=atmoResource)
		{
			if(atmoResource==0)
			{
				R.showHappens(CMMsg.MSG_OK_ACTION, L("@x1 rushes out of the room.",RawMaterial.CODES.NAME(R.getAtmosphere()).toLowerCase()));
				if((notifyRoom!=null)&&(notifyRoom!=R))
					notifyRoom.showHappens(CMMsg.MSG_OK_ACTION, L("@x1 rushes out of the room.",RawMaterial.CODES.NAME(R.getAtmosphere()).toLowerCase()));
			}
			else
			{
				R.showHappens(CMMsg.MSG_OK_ACTION, L("@x1 rushes into the room.",RawMaterial.CODES.NAME(atmoResource).toLowerCase()));
				if((notifyRoom!=null)&&(notifyRoom!=R))
					notifyRoom.showHappens(CMMsg.MSG_OK_ACTION, L("@x1 rushes into the room.",RawMaterial.CODES.NAME(atmoResource).toLowerCase()));
			}
			if(atmoResource==getAtmosphere())
				R.setAtmosphere(-1);
			else
				R.setAtmosphere(atmoResource);
			return true;
		}
		return false;
	}

	protected void moveAtmosphereOut(final Set<Room> doneRooms, final Room startRoom, final int atmo)
	{
		final LinkedList<Room> toDoRooms=new LinkedList<Room>();
		toDoRooms.add(startRoom);
		while(toDoRooms.size()>0)
		{
			final Room R=toDoRooms.removeFirst();
			doneRooms.add(R);
			if(atmo == RawMaterial.RESOURCE_NOTHING)
			{
				if((R.roomID().length()>0)
				&&(!staleAirList.contains(R.roomID())))
					staleAirList.add(R.roomID());
			}
			else
			if(atmo == RawMaterial.RESOURCE_AIR)
				staleAirList.remove(R.roomID());
			if(changeRoomAir(R,startRoom,atmo))
				break;
			for(int d=0;d<Directions.NUM_DIRECTIONS();d++)
			{
				final Room R2=R.getRoomInDir(d);
				final Exit E2=R.getExitInDir(d);
				if((R2!=null)
				&&(R2.getArea()==R.getArea())
				&&(E2!=null)
				&&(E2.isOpen())
				&&(!doneRooms.contains(R2)))
					toDoRooms.add(R2);
			}
		}
	}

	protected void doStaleCheck()
	{
		nextStaleCheck=System.currentTimeMillis()+STALE_AIR_INTERVAL;
		for(final Enumeration<Room> r=getProperMap();r.hasMoreElements();)
		{
			final Room R=r.nextElement();
			if(!staleAirList.contains(R.roomID()))
			{
				if(R.numInhabitants()>0)
					staleAirList.add(R.roomID());
			}
			else
				R.setAtmosphere(RawMaterial.RESOURCE_NOTHING); // WE NOW HAVE A VACUUM HERE!!!
		}
	}

	protected Ability getGravityFloat()
	{
		if(gravityFloaterA == null)
		{
			gravityFloaterA=CMClass.getAbility("GravityFloat");
			if(gravityFloaterA != null)
			{
				final MOB M=CMClass.getMOB("StdMOB");
				M.setName(Name());
				M.setLocation(this.getRandomProperRoom());
				gravityFloaterA.setInvoker(M);
			}
		}
		return gravityFloaterA;
	}

	protected void doGravityChanges()
	{
		if((lastEngine != null)
		&&(!lastEngine.amDestroyed())
		&&(lastEngine.activated())
		&&(lastEngine.getThrust()>1.0)
		&&(CMLib.map().areaLocation(lastEngine)==this))
			lastEThrust = lastEngine.getThrust();
		else
		{
			lastEngine = null;
			lastEThrust = 0.0;
			final List<Electronics> electronics=CMLib.tech().getMakeRegisteredElectronics(CMLib.tech().getElectronicsKey(this));
			for(final Electronics E : electronics)
			{
				if((E instanceof ShipEngine)
				&&(E.activated())
				&&((lastEngine == null)
					||(((ShipEngine)E).getThrust() > lastEngine.getThrust())))
						lastEngine = (ShipEngine)E;
			}
			lastEThrust =  (lastEngine == null) ? 0.0 : lastEngine.getThrust();
		}
		final boolean gravExistsNow =
				getShipFlag(ShipFlag.IN_THE_AIR)
			|| getShipFlag(ShipFlag.ARTI_GRAV)
			|| (getIsDocked() != null)
			|| (lastEThrust > 0.2);

		setShipFlag(ShipFlag.ARTI_GRAV, false);
		if(gravExistsNow == getShipFlag(ShipFlag.NO_GRAVITY)) // opposite, so it needs changing
		{
			final Ability floater = getGravityFloat();
			if(floater != null)
			{
				final SpaceObject spaceObject=getShipSpaceObject();
				final String code=Technical.TechCommand.GRAVITYCHANGE.makeCommand(Boolean.valueOf(gravExistsNow));
				final String msgStr;
				if(gravExistsNow)
					msgStr=L("You feel the pull of gravity returning.");
				else
					msgStr=L("You no longer feel the pull of gravity.");
				final CMMsg msg=CMClass.getMsg(floater.invoker(), spaceObject, me, CMMsg.NO_EFFECT, null, CMMsg.MSG_ACTIVATE|CMMsg.MASK_CNTRLMSG, code, CMMsg.MSG_QUIETMOVEMENT,msgStr);
				CMMsg gmsg;
				if(lastEThrust >= (SpaceObject.ACCELERATION_PASSOUT-0.49))
				{
					gmsg=CMClass.getMsg(floater.invoker(), null, me, CMMsg.NO_EFFECT, null, CMMsg.NO_EFFECT, null, CMMsg.MSG_GRAVITY, null);
					gmsg.setValue((int)Math.round(lastEThrust));
				}
				else
					gmsg=null;
				boolean cancelled = false;
				for(final Enumeration<Room> r=getProperMap();r.hasMoreElements();)
				{
					final Room R=r.nextElement();
					if(R!=null)
					{
						if(!R.okMessage(msg.source(), msg))
							cancelled=true;
						if((gmsg != null)
						&& ((!R.okMessage(gmsg.source(), gmsg))
							||((gmsg.value() < (SpaceObject.ACCELERATION_PASSOUT-0.49)))))
							gmsg = null;
					}
				}
				setShipFlag(ShipFlag.NO_GRAVITY, !gravExistsNow);
				if(gmsg != null)
				{
					for(final Enumeration<Room> r=getProperMap();r.hasMoreElements();)
					{
						final Room R=r.nextElement();
						if(R!=null)
							R.send(gmsg.source(), gmsg);
					}
				}
				if(cancelled)
				{
					return;
				}
				for(final Enumeration<Room> r=getProperMap();r.hasMoreElements();)
				{
					final Room R=r.nextElement();
					if(R!=null)
						R.send(floater.invoker(), msg);
				}
				for(final Enumeration<Room> r=getProperMap();r.hasMoreElements();)
				{
					final Room R=r.nextElement();
					if(R!=null)
					{
						for(int i=0;i<R.numInhabitants();i++)
						{
							final MOB M=R.fetchInhabitant(i);
							if(M!=null)
								floater.invoke(floater.invoker(), M, gravExistsNow, 0);
						}
						for(int i=0;i<R.numItems();i++)
						{
							final Item I=R.getItem(i);
							if(I!=null)
								floater.invoke(floater.invoker(), I, gravExistsNow, 0);
						}
					}
				}
			}
		}
	}

	protected void doAtmosphereChanges()
	{
		final Set<Room> doneRooms=new HashSet<Room>();
		for(final Pair<Room,Integer> p : shipExitCache)
		{
			final Room R=p.first;
			final Exit E=R.getExitInDir(p.second.intValue());
			if((E!=null)&&(E.isOpen()))
			{
				final Room exitRoom=R;
				final Room otherRoom=R.getRoomInDir(p.second.intValue());
				final int atmo=otherRoom.getAtmosphere();
				moveAtmosphereOut(doneRooms,exitRoom,atmo);
			}
		}
		if((System.currentTimeMillis() > nextStaleWarn)
		&&(staleAirList.size()>0))
		{
			nextStaleWarn = System.currentTimeMillis() + STALE_WARN_INTERVAL;
			for(final Enumeration<Room> r=getProperMap();r.hasMoreElements();)
			{
				final Room R=r.nextElement();
				if((staleAirList.contains(R.roomID()))
				&&(R.numInhabitants()>0)
				&&(R.numPCInhabitants()>0))
				{
					final int atmo=R.getAtmosphere();
					if(atmo>0)
					for(int i=0;i<R.numInhabitants();i++)
					{
						final MOB M=R.fetchInhabitant(i);
						if((M!=null)
						&&(!M.isMonster())
						&&(!CMLib.flags().canBreatheThis(M,RawMaterial.RESOURCE_NOTHING)))
							M.tell(L("The @x1 is seeming a bit stale.",RawMaterial.CODES.NAME(atmo).toLowerCase()));
					}
				}
			}
		}
		if(System.currentTimeMillis() >= nextStaleCheck)
		{
			final int numStaleRooms = staleAirList.size();
			doStaleCheck();
			if(staleAirList.size()>numStaleRooms)
				nextStaleWarn = System.currentTimeMillis() + STALE_WARN_INTERVAL;
			if(CMSecurity.isDebugging(DbgFlag.SPACESHIP) && (staleAirList.size()>0))
				Log.debugOut("Used up the air in "+Name()+", stale rooms: "+staleAirList.size());

		}
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(!super.tick(ticking, tickID))
			return false;
		tickStatus=Tickable.STATUS_START;
		if(tickID==Tickable.TICKID_AREA)
		{
			doAtmosphereChanges();
			doGravityChanges();
			final BoardableShip item=this.shipItem;
			if(item != null)
				item.tick(ticking, tickID);
		}
		tickStatus=Tickable.STATUS_NOT;
		return true;
	}

	@Override
	public void dockHere(final Room roomR)
	{
		super.dockHere(roomR);
		if(roomR==null)
			return;
		CMLib.map().delObjectInSpace(getShipSpaceObject());
	}

	@Override
	public SpaceObject getShipSpaceObject()
	{
		return (shipItem instanceof SpaceObject) ? (SpaceObject)shipItem : null;
	}

	private static final String[] LOCAL_CODES={"RADIUS","AUTHOR"};
	private static String[] codes=null;

	@Override
	public String[] getStatCodes()
	{
		if(codes!=null)
			return codes;
		final String[] superCodes=super.getStatCodes();
		codes=new String[superCodes.length+LOCAL_CODES.length];
		int i=0;
		for(;i<superCodes.length;i++)
			codes[i]=superCodes[i];
		for(int x=0;x<LOCAL_CODES.length;i++,x++)
			codes[i]=LOCAL_CODES[x];
		return codes;
	}

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

	@Override
	public String getStat(final String code)
	{
		if(getCodeNum(code) < super.getStatCodes().length)
			return super.getStat(code);
		switch(CMParms.indexOf(LOCAL_CODES,code.toUpperCase().trim()))
		{
		case 0:
			return "" + getOMLCoeff();
		case 1:
			return "" + radius();
		}
		return "";
	}

	@Override
	public void setStat(final String code, final String val)
	{
		if(getCodeNum(code) < super.getStatCodes().length)
			super.setStat(code, val);
		else
		switch(CMParms.indexOf(LOCAL_CODES,code.toUpperCase().trim()))
		{
			case 0:
				setOMLCoeff(CMath.s_double(val));
				break;
			case 1:
				setRadius(CMath.s_long(val));
				break;
		}
	}
}