/
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.Exits;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.interfaces.EachApplicable.ApplyAffectPhyStats;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.Basic.StdItem;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.util.*;

/*
   Copyright 2001-2016 Bo Zimmerman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
public class StdExit implements Exit
{
	@Override public String ID(){    return "StdExit";}

	protected PhyStats	phyStats		= (PhyStats) CMClass.getCommon("DefaultPhyStats");
	protected PhyStats	basePhyStats	= (PhyStats) CMClass.getCommon("DefaultPhyStats");
	protected boolean	isOpen			= true;
	protected boolean	isLocked		= false;
	protected String	miscText		= "";
	protected String	cachedImageName	= null;
	protected String	rawImageName	= null;
	protected boolean	amDestroyed		= false;
	protected short		usage			= 0;

	protected String				lastRoomID	= "";
	protected CList<Ability>		affects		= null;
	protected CList<Behavior>		behaviors	= null;
	protected CList<ScriptingEngine>scripts		= null;
	protected Exit					me			= this;

	protected ApplyAffectPhyStats<Ability>	affectPhyStats = new ApplyAffectPhyStats<Ability>(this);
	
	public StdExit()
	{
		super();
		//CMClass.bumpCounter(this,CMClass.CMObjectType.EXIT);
		isOpen=!defaultsClosed();
		isLocked=defaultsLocked();
	}

	//protected void finalize(){CMClass.unbumpCounter(this,CMClass.CMObjectType.EXIT);}//removed for mem & perf
	@Override
	public void initializeClass()
	{
	}

	@Override
	public String Name()
	{
		return "a walkway";
	}

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

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

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

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

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

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

	@Override
	public String description(MOB viewerMob)
	{
		return description();
	}

	@Override
	public String doorName()
	{
		return "door";
	}

	@Override
	public String closedText()
	{
		return "a closed door";
	}

	@Override
	public String closeWord()
	{
		return "close";
	}

	@Override
	public String openWord()
	{
		return "open";
	}

	@Override
	public String displayText(MOB viewerMob)
	{
		return displayText();
	}

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

	@Override
	public int getTickStatus()
	{
		return Tickable.STATUS_NOT;
	}

	@Override
	public short exitUsage(short change)
	{
		if(change<0)
		{
			if((-change)>usage)
				usage=0;
			else
				usage+=change;
		}
		else
		if(Short.MAX_VALUE-change>usage)
			usage+=change;
		return usage;
	}

	@Override
	public void setName(String newName)
	{
	}

	@Override
	public String name()
	{
		if(phyStats().newName()!=null)
			return phyStats().newName();
		return Name();
	}
	
	@Override
	public PhyStats phyStats()
	{
		return phyStats;
	}

	@Override
	public PhyStats basePhyStats()
	{
		return basePhyStats;
	}

	@Override
	public void recoverPhyStats()
	{
		basePhyStats.copyInto(phyStats);
		eachEffect(affectPhyStats);
	}

	@Override
	public void setBasePhyStats(PhyStats newStats)
	{
		basePhyStats=(PhyStats)newStats.copyOf();
	}

	@Override
	public void destroy()
	{
		CMLib.map().registerWorldObjectDestroyed(null,null,this);
		CMLib.threads().deleteTick(this,-1);
		affects=null;
		rawImageName=null;
		cachedImageName=null;
		behaviors=null;
		scripts=null;
		miscText=null;
		amDestroyed=true;
	}

	@Override
	public boolean amDestroyed()
	{
		return amDestroyed;
	}

	@Override
	public boolean isSavable()
	{
		return !amDestroyed && CMLib.flags().isSavable(this);
	}

	@Override
	public void setSavable(boolean truefalse)
	{
		CMLib.flags().setSavable(this, truefalse);
	}

	@Override
	public String image()
	{
		if(cachedImageName==null)
		{
			if((rawImageName!=null)&&(rawImageName.length()>0))
				cachedImageName=rawImageName;
			else
				cachedImageName=CMLib.protocol().getDefaultMXPImage(this);
		}
		return cachedImageName;
	}

	@Override
	public String rawImage()
	{
		if(rawImageName==null)
			return "";
		return rawImageName;
	}

	@Override
	public void setImage(String newImage)
	{
		if((newImage==null)||(newImage.trim().length()==0))
			rawImageName=null;
		else
			rawImageName=newImage;
		if((cachedImageName!=null)&&(!cachedImageName.equals(newImage)))
			cachedImageName=null;
	}

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

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

	protected void cloneFix(Exit X)
	{
		me=this;
		basePhyStats=(PhyStats)X.basePhyStats().copyOf();
		phyStats=(PhyStats)X.phyStats().copyOf();

		affectPhyStats = new ApplyAffectPhyStats<Ability>(this);
		
		affects=null;
		behaviors=null;
		scripts=null;
		for(final Enumeration<Behavior> e=X.behaviors();e.hasMoreElements();)
		{
			final Behavior B=e.nextElement();
			if(B!=null)
				addBehavior((Behavior)B.copyOf());
		}
		for(final Enumeration<ScriptingEngine> e=X.scripts();e.hasMoreElements();)
		{
			final ScriptingEngine SE=e.nextElement();
			if(SE!=null)
				addScript((ScriptingEngine)SE.copyOf());
		}
	}

	@Override
	public CMObject copyOf()
	{
		try
		{
			final StdExit E=(StdExit)this.clone();
			//CMClass.bumpCounter(this,CMClass.CMObjectType.EXIT);//removed for mem & perf
			E.cloneFix(this);
			return E;

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

	@Override
	public void setMiscText(String newMiscText)
	{
		miscText = newMiscText;
	}

	@Override
	public String text()
	{
		return miscText;
	}

	@Override
	public String miscTextFormat()
	{
		return CMParms.FORMAT_UNDEFINED;
	}

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

	@Override
	public void setExpirationDate(long time)
	{
	}

	@Override
	public void setDisplayText(String newDisplayText)
	{
	}

	@Override
	public void setDescription(String newDescription)
	{
	}

	@Override
	public int maxRange()
	{
		return Integer.MAX_VALUE;
	}

	@Override
	public int minRange()
	{
		return Integer.MIN_VALUE;
	}

	protected final String closeWordPastTense()
	{
		if(closeWord().length()==0)
			return "closed";
		else
		if(CMStrings.isVowel(closeWord().charAt(closeWord().length()-1)))
			return closeWord()+"d";
		else
			return closeWord()+"ed";
	}

	protected final String openWordPastTense()
	{
		if(openWord().length()==0)
			return "opened";
		else
		if(CMStrings.isVowel(openWord().charAt(openWord().length()-1)))
			return openWord()+"d";
		else
			return openWord()+"ed";
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		MsgListener N=null;
		for(int b=0;b<numBehaviors();b++)
		{
			N=fetchBehavior(b);
			if((N!=null)&&(!N.okMessage(this,msg)))
				return false;
		}
		for(int s=0;s<numScripts();s++)
		{
			N=fetchScript(s);
			if((N!=null)&&(!N.okMessage(this,msg)))
				return false;
		}
		for(final Enumeration<Ability> a=effects();a.hasMoreElements();)
		{
			N=a.nextElement();
			if((N!=null)&&(!N.okMessage(this,msg)))
				return false;
		}

		final MOB mob=msg.source();
		if((!msg.amITarget(this))&&(msg.tool()!=this))
			return true;
		else
		if(msg.targetMinor()==CMMsg.NO_EFFECT)
			return true;
		else
		switch(msg.targetMinor())
		{
		case CMMsg.TYP_LOOK:
		case CMMsg.TYP_EXAMINE:
		case CMMsg.TYP_READ:
		case CMMsg.TYP_OK_VISUAL:
		case CMMsg.TYP_KNOCK:
		case CMMsg.TYP_OK_ACTION:
			return true;
		case CMMsg.TYP_ENTER:
			if(msg.target() instanceof Room)
				lastRoomID=CMLib.map().getExtendedRoomID((Room)msg.target());
			if((hasADoor())&&(!isOpen())&&(mob.phyStats().height()>=0))
			{
				if(!CMLib.flags().canBeSeenBy(this,mob))
					mob.tell(L("You can't go that way."));
				else
					mob.tell(L("The @x1 is @x2.",doorName(),closeWordPastTense()));
				return false;
			}
			if((CMLib.flags().isFlying(this))
			&&(!CMLib.flags().isInFlight(mob))
			&&(!CMLib.flags().isFalling(mob)))
			{
				mob.tell(L("You can't fly."));
				return false;
			}
			if((CMLib.flags().isClimbing(this))
			&&(!CMLib.flags().isFalling(this))
			&&(!CMLib.flags().isClimbing(mob))
			&&(!CMLib.flags().isInFlight(mob)))
			{
				Rideable ladder=null;
				if(msg.target() instanceof Room)
					ladder=CMLib.tracking().findALadder(mob,(Room)msg.target());
				if(ladder!=null)
					CMLib.tracking().postMountLadder(mob,ladder);
				if((!CMLib.flags().isClimbing(mob))
				&&(!CMLib.flags().isFalling(mob)))
				{
					mob.tell(L("You need to climb that way, if you know how."));
					return false;
				}
			}
			return true;
		case CMMsg.TYP_LEAVE:
		case CMMsg.TYP_FLEE:
			return true;
		case CMMsg.TYP_CLOSE:
		{
			if(closeWord().length()==0)
				setExitParams(doorName(),openWord(),"close",closedText());
			if(isOpen)
			{
				if(!hasADoor())
				{
					mob.tell(L("There is nothing to @x1!",closeWord()));
					return false;
				}
				return true;
			}
			mob.tell(L("The @x1 is already @x2.",doorName(),closeWordPastTense()));
			return false;
		}
		case CMMsg.TYP_OPEN:
		{
			if(openWord().length()==0)
				setExitParams(doorName(),"open",closeWord(),closedText());
			if(!hasADoor())
			{
				mob.tell(L("There is nothing to @x1 that way!",openWord()));
				return false;
			}
			if(isOpen())
			{
				mob.tell(L("The @x1 is already @x2!",doorName(),openWordPastTense()));
				return false;
			}
			if(isLocked()&&hasALock())
			{
				mob.tell(L("The @x1 is locked.",doorName()));
				return false;
			}
			return true;
		}
		case CMMsg.TYP_PUSH:
			if((isOpen())||(!hasADoor()))
			{
				mob.tell(L("There is nothing to push over there."));
				return false;
			}
			return true;
		case CMMsg.TYP_DELICATE_HANDS_ACT:
		case CMMsg.TYP_JUSTICE:
		case CMMsg.TYP_CAST_SPELL:
		case CMMsg.TYP_SPEAK:
			return true;
		case CMMsg.TYP_PULL:
			if((isOpen())||(!hasADoor()))
			{
				mob.tell(L("There is nothing to pull over there."));
				return false;
			}
			return true;
		case CMMsg.TYP_LOCK:
			if(!hasADoor())
			{
				mob.tell(L("There is nothing to lock that way!"));
				return false;
			}
		//$FALL-THROUGH$
		case CMMsg.TYP_UNLOCK:
			if(!hasADoor())
			{
				mob.tell(L("There is nothing to unlock that way!"));
				return false;
			}
			if(isOpen())
			{
				mob.tell(L("The @x1 is already @x2!",doorName(),openWord()));
				return false;
			}
			else
			if(!hasALock())
			{
				mob.tell(L("There is no lock!"));
				return false;
			}
			else
			{
				if((!isLocked())&&(msg.targetMinor()==CMMsg.TYP_UNLOCK))
				{
					mob.tell(L("The @x1 is not locked.",doorName()));
					return false;
				}
				else
				if((isLocked())&&(msg.targetMinor()==CMMsg.TYP_LOCK))
				{
					mob.tell(L("The @x1 is already locked.",doorName()));
					return false;
				}
				else
				{
					for(int i=0;i<mob.numItems();i++)
					{
						final Item item=mob.getItem(i);
						if((item!=null)
						&&(item instanceof DoorKey)
						&&((DoorKey)item).getKey().equals(keyName())
						&&((item.container()==null)
						   ||((item.container().container()==null)
							  &&((item.container().containTypes()&Container.CONTAIN_KEYS)>0)))
						&&(CMLib.flags().canBeSeenBy(item,mob)))
							return true;
					}
					mob.tell(L("You don't seem to have the key."));
					return false;
				}
			}
			//break;
		default:
			break;
		}
		if(msg.amITarget(this))
		{
			mob.tell(L("You can't do that."));
			return false;
		}
		return true;
	}

	@Override
	public StringBuilder viewableText(MOB mob, Room room)
	{
		final StringBuilder viewMsg=new StringBuilder("");
		if(mob.isAttributeSet(MOB.Attrib.SYSOPMSGS))
		{
			if(room==null)
				viewMsg.append("^Z(null)^.^? ");
			else
				viewMsg.append("^H("+CMLib.map().getExtendedRoomID(room)+")^? "+room.displayText(mob)+CMLib.flags().getDispositionBlurbs(room,mob)+" ");
			viewMsg.append("via ^H("+ID()+")^? "+(isOpen()?displayText():closedText()));
		}
		else
		if(((CMLib.flags().canBeSeenBy(this,mob))||(isOpen()&&hasADoor()))
		&&(CMLib.flags().isSeeable(this)))
		{
			if(isOpen())
			{
				if((room!=null)&&(!CMLib.flags().canBeSeenBy(room,mob)))
					viewMsg.append("darkness");
				else
				if(displayText().length()>0)
					viewMsg.append(displayText()+CMLib.flags().getDispositionBlurbs(this,mob));
				else
				if(room!=null)
					viewMsg.append(room.displayText(mob)+CMLib.flags().getDispositionBlurbs(room,mob));
			}
			else
			if((CMLib.flags().canBeSeenBy(this,mob))&&(closedText().trim().length()>0))
				viewMsg.append(closedText()+CMLib.flags().getDispositionBlurbs(this,mob));
		}
		return viewMsg;
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		if(numBehaviors()>0)
		{
			eachBehavior(new EachApplicable<Behavior>()
			{ 
				@Override
				public final void apply(final Behavior B)
				{
					B.executeMsg(me, msg);
				} 
			});
		}
		if(numScripts()>0)
		{
			eachScript(new EachApplicable<ScriptingEngine>()
			{ 
				@Override
				public final void apply(final ScriptingEngine S)
				{
					S.executeMsg(me, msg);
				} 
			});
		}
		if(numEffects()>0)
		{
			eachEffect(new EachApplicable<Ability>()
			{ 
				@Override
				public final void apply(final Ability A)
				{
					A.executeMsg(me,msg);
				}
			});
		}

		final MOB mob=msg.source();
		if((!msg.amITarget(this))&&(msg.tool()!=this))
			return;
		switch(msg.targetMinor())
		{
		case CMMsg.TYP_LOOK:
		case CMMsg.TYP_EXAMINE:
			CMLib.commands().handleBeingLookedAt(msg);
			break;
		case CMMsg.TYP_READ:
			CMLib.commands().handleBeingRead(msg);
			break;
		case CMMsg.TYP_CLOSE:
			if((!hasADoor())||(!isOpen()))
				return;
			isOpen=false;
			break;
		case CMMsg.TYP_OPEN:
			if((!hasADoor())||(isOpen()))
				return;
			if(defaultsClosed()||defaultsLocked())
			{
				CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_REOPEN);
				CMLib.threads().startTickDown(this,Tickable.TICKID_EXIT_REOPEN,openDelayTicks());
			}
			isLocked=false;
			isOpen=true;
			break;
		case CMMsg.TYP_LOCK:
			if((!hasADoor())||(!hasALock())||(isLocked()))
				return;
			isOpen=false;
			isLocked=true;
			break;
		case CMMsg.TYP_PULL:
		case CMMsg.TYP_PUSH:
			mob.tell(L("It doesn't appear to be doing any good."));
			break;
		case CMMsg.TYP_UNLOCK:
			if((!hasADoor())||(!hasALock())||(isOpen())||(!isLocked()))
				return;
			isLocked=false;
			break;
		default:
			break;
		}
	}

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

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(amDestroyed())
			return false;

		if(usage<=0){ destroy(); return false;}

		if(tickID==Tickable.TICKID_EXIT_REOPEN)
		{
			if(defaultsClosed())
				isOpen=false;
			if(defaultsLocked())
			{
				isOpen=false;
				isLocked=true;
			}
			return false;
		}
		else
		if(tickID==Tickable.TICKID_EXIT_BEHAVIOR)
		{
			if(numBehaviors()>0)
			{
				eachBehavior(new EachApplicable<Behavior>(){ 
					@Override
					public final void apply(final Behavior B)
					{
						B.tick(ticking, tickID);
					} 
				});
			}
			if(numScripts()>0)
			{
				eachScript(new EachApplicable<ScriptingEngine>(){ 
					@Override
					public final void apply(final ScriptingEngine S)
					{
						S.tick(ticking, tickID);
					} 
				});
			}
			return !amDestroyed();
		}
		else
		{
			if(numEffects()>0)
			{
				eachEffect(new EachApplicable<Ability>(){ 
					@Override
					public final void apply(final Ability A)
					{
						if(!A.tick(ticking,tickID))
							A.unInvoke();
					}
				});
			}
			return true;
		}
	}

	@Override
	public boolean isOpen()
	{
		return isOpen;
	}

	@Override
	public boolean isLocked()
	{
		return isLocked;
	}

	@Override
	public void setDoorsNLocks(boolean newHasADoor,
								  boolean newIsOpen,
								  boolean newDefaultsClosed,
								  boolean newHasALock,
								  boolean newIsLocked,
								  boolean newDefaultsLocked)
	{
		isOpen=newIsOpen;
		isLocked=newIsLocked;
	}

	@Override
	public String readableText()
	{
		return (isReadable() ? miscText : "");
	}

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

	@Override
	public void setReadable(boolean isTrue)
	{
	}

	@Override
	public void setReadableText(String text)
	{
		miscText = temporaryDoorLink() + text;
	}

	@Override
	public void setExitParams(String newDoorName, String newCloseWord, String newOpenWord, String newClosedText)
	{
	}

	@Override
	public String keyName()
	{
		return (hasALock() ? miscText : "");
	}

	@Override
	public void setKeyName(String newKeyName)
	{
		miscText = temporaryDoorLink() + newKeyName;
	}

	@Override
	public Room lastRoomUsedFrom()
	{
		return CMLib.map().getRoom(lastRoomID);
	}

	@Override
	public void affectPhyStats(Physical affected, PhyStats affectableStats)
	{
	}// exits will never be asked this, so this method should always do NOTHING

	@Override
	public void affectCharStats(MOB affectedMob, CharStats affectableStats)
	{
	}// exits will never be asked this, so this method should always do NOTHING

	@Override
	public void affectCharState(MOB affectedMob, CharState affectableMaxState)
	{
	}// exits will never be asked this, so this method should always do NOTHING

	@Override
	public String temporaryDoorLink()
	{
		if(miscText.startsWith("{#"))
		{
			final int x=miscText.indexOf("#}");
			if(x>=0)
				return miscText.substring(2,x);
		}
		return "";
	}
	@Override
	public void setTemporaryDoorLink(String link)
	{
		if(link.startsWith("{{#"))
		{
			final int x=link.indexOf("#}}");
			if(x>=0)
				lastRoomID=link.substring(3,x);
			return;
		}
		if(miscText.startsWith("{#"))
		{
			final int x=miscText.indexOf("#}");
			if(x>=0)
				miscText=miscText.substring(x+2);
		}
		if(link.length()>0)
			miscText="{#"+link+"#}"+miscText;
	}

	@Override
	public void addNonUninvokableEffect(Ability to)
	{
		if(to==null)
			return;
		if(fetchEffect(to.ID())!=null)
			return;
		if(affects==null)
			affects=new SVector<Ability>(1);
		to.makeNonUninvokable();
		to.makeLongLasting();
		affects.add(to);
		to.setAffectedOne(this);
	}

	@Override
	public void addEffect(Ability to)
	{
		if(to==null)
			return;
		if(fetchEffect(to.ID())!=null)
			return;
		if(affects==null)
			affects=new SVector<Ability>(1);
		affects.add(to);
		to.setAffectedOne(this);
	}

	@Override
	public void delEffect(Ability to)
	{
		if(affects==null)
			return;
		if(affects.remove(to))
			to.setAffectedOne(null);
	}
	@Override
	public void eachEffect(final EachApplicable<Ability> applier)
	{
		final List<Ability> affects=this.affects;
		if(affects==null)
			return;
		try
		{
			for(int a=0;a<affects.size();a++)
			{
				final Ability A=affects.get(a);
				if(A!=null)
					applier.apply(A);
			}
		}
		catch(final ArrayIndexOutOfBoundsException e){}
	}

	@Override
	public void delAllEffects(boolean unInvoke)
	{
		final CList<Ability> affects=this.affects;
		if(affects==null)
			return;
		for(int a=numEffects()-1;a>=0;a--)
		{
			final Ability A=fetchEffect(a);
			if(A!=null)
			{
				if(unInvoke)
					A.unInvoke();
				A.setAffectedOne(null);
			}
		}
		affects.clear();
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<Ability> effects()
	{
		return (affects == null) ? EmptyEnumeration.INSTANCE : affects.elements();
	}

	@Override
	public int numEffects()
	{
		if(affects==null)
			return 0;
		return affects.size();
	}
	
	@Override
	public Ability fetchEffect(int index)
	{
		if(affects==null)
			return null;
		try
		{
			return affects.get(index);
		}
		catch (final java.lang.ArrayIndexOutOfBoundsException x)
		{
		}
		return null;
	}

	@Override
	public Ability fetchEffect(String ID)
	{
		if(affects==null)
			return null;
		for(final Enumeration<Ability> a=effects();a.hasMoreElements();)
		{
			final Ability A=a.nextElement();
			if((A!=null)&&(A.ID().equals(ID)))
				return A;
		}
		return null;
	}

	/** Manipulation of Behavior objects, which includes
	 * movement, speech, spellcasting, etc, etc.*/
	@Override
	public void addBehavior(Behavior to)
	{
		if(behaviors==null)
			behaviors=new SVector<Behavior>(1);
		if(to==null)
			return;
		for(final Behavior B : behaviors)
			if((B!=null)&&(B.ID().equals(to.ID())))
				return;
		// first one! so start ticking...
		if(behaviors.size()==0)
			CMLib.threads().startTickDown(this,Tickable.TICKID_EXIT_BEHAVIOR,1);
		to.startBehavior(this);
		behaviors.add(to);
	}

	@Override
	public void delBehavior(Behavior to)
	{
		if(behaviors==null)
			return;
		behaviors.remove(to);
		if(((behaviors==null)||(behaviors.size()==0))&&((scripts==null)||(scripts.size()==0)))
			CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
	}

	@Override
	public void delAllBehaviors()
	{
		final boolean didSomething=(behaviors!=null)&&(behaviors.size()>0);
		if(didSomething)
			behaviors.clear();
		behaviors=null;
		if(didSomething && ((scripts==null)||(scripts.size()==0)))
		  CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
	}

	@Override
	public int numBehaviors()
	{
		if(behaviors==null)
			return 0;
		return behaviors.size();
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<Behavior> behaviors()
	{
		return (behaviors == null) ? EmptyEnumeration.INSTANCE : behaviors.elements();
	}

	@Override
	public Behavior fetchBehavior(int index)
	{
		if(behaviors==null)
			return null;
		try
		{
			return behaviors.get(index);
		}
		catch(final java.lang.ArrayIndexOutOfBoundsException x){}
		return null;
	}

	@Override
	public Behavior fetchBehavior(String ID)
	{
		if(behaviors==null)
			return null;
		for(final Behavior B : behaviors)
		{
			if((B!=null)&&(B.ID().equalsIgnoreCase(ID)))
				return B;
		}
		return null;
	}

	@Override
	public void eachBehavior(final EachApplicable<Behavior> applier)
	{
		final List<Behavior> behaviors=this.behaviors;
		if(behaviors!=null)
		try
		{
			for(int a=0;a<behaviors.size();a++)
			{
				final Behavior B=behaviors.get(a);
				if(B!=null)
					applier.apply(B);
			}
		}
		catch (final ArrayIndexOutOfBoundsException e)
		{
		}
	}

	/** Manipulation of the scripts list */
	@Override
	public void addScript(ScriptingEngine S)
	{
		if(scripts==null)
			scripts=new SVector<ScriptingEngine>(1);
		if(S==null)
			return;
		if(!scripts.contains(S))
		{
			ScriptingEngine S2=null;
			for(int s=0;s<scripts.size();s++)
			{
				S2=scripts.get(s);
				if((S2!=null)&&(S2.getScript().equalsIgnoreCase(S.getScript())))
					return;
			}
			if(scripts.size()==0)
				CMLib.threads().startTickDown(this,Tickable.TICKID_EXIT_BEHAVIOR,1);
			scripts.add(S);
		}
	}

	@Override
	public void delScript(ScriptingEngine S)
	{
		if(scripts!=null)
		{
			if(scripts.remove(S))
			{
				if(scripts.size()==0)
					scripts=new SVector<ScriptingEngine>(1);
				if(((behaviors==null)||(behaviors.size()==0))&&((scripts==null)||(scripts.size()==0)))
					CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
			}
		}
	}

	@Override
	public void delAllScripts()
	{
		final boolean didSomething=(scripts!=null)&&(scripts.size()>0);
		if(didSomething)
			scripts.clear();
		scripts=null;
		if(didSomething && ((behaviors==null)||(behaviors.size()==0)))
		  CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
	}

	@Override
	public int numScripts()
	{
		return (scripts == null) ? 0 : scripts.size();
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<ScriptingEngine> scripts()
	{
		return (scripts == null) ? EmptyEnumeration.INSTANCE : scripts.elements();
	}

	@Override
	public ScriptingEngine fetchScript(int x)
	{
		try
		{
			return scripts.get(x);
		}
		catch (final Exception e)
		{
		}
		return null;
	}

	@Override
	public void eachScript(final EachApplicable<ScriptingEngine> applier)
	{
		final List<ScriptingEngine> scripts=this.scripts;
		if(scripts!=null)
		try
		{
			for(int a=0;a<scripts.size();a++)
			{
				final ScriptingEngine S=scripts.get(a);
				if(S!=null)
					applier.apply(S);
			}
		}
		catch(final ArrayIndexOutOfBoundsException e){}
	}

	@Override
	public int openDelayTicks()
	{
		return 45;
	}

	@Override
	public void setOpenDelayTicks(int numTicks)
	{
	}

	@Override
	public String L(final String str, final String... xs)
	{
		return CMLib.lang().fullSessionTranslation(str, xs);
	}

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

	private static final String[]	CODES	= { "CLASS", "TEXT" };

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

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

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

	@Override
	public String getStat(String code)
	{
		switch(getCodeNum(code))
		{
		case 0:
			return ID();
		case 1:
			return text();
		}
		return "";
	}

	@Override
	public void setStat(String code, String val)
	{
		switch(getCodeNum(code))
		{
		case 0:
			return;
		case 1:
			setMiscText(val);
			break;
		}
	}

	@Override
	public boolean sameAs(Environmental E)
	{
		if(!(E instanceof StdExit))
			return false;
		final String[] codes=getStatCodes();
		for(int i=0;i<codes.length;i++)
		{
			if(!E.getStat(codes[i]).equals(getStat(codes[i])))
				return false;
		}
		return true;
	}
}