key0-96/
key0-96/doc/key/
key0-96/doc/key/credits/
key0-96/doc/key/developers/
key0-96/doc/key/developers/resources/
key0-96/setup/caves/
key0-96/setup/help/
key0-96/setup/ruins/
key0-96/src/
key0-96/src/commands/
key0-96/src/events/
key0-96/src/hack/
key0-96/src/sql/
key0-96/src/swing/
key0-96/src/talker/forest/
key0-96/src/talker/objects/
key0-96/src/terminals/
/*
**               j###t  ########## ####   ####
**              j###t   ########## ####   ####
**             j###T               "###L J###"
**          ######P'    ##########  #########
**          ######k,    ##########   T######T
**          ####~###L   ####
**          #### q###L  ##########   .#####
**          ####  \###L ##########   #####"
**
**  $Id$
**
**  Class History
**
**  Date        Name         Description
**  ---------|------------|-----------------------------------------------
**  19Aug98     subtle       start of recorded history
**
*/

package key;

import key.collections.*;

import java.util.Enumeration;
import java.io.*;
import java.util.StringTokenizer;

/**
  *  Scape is a general way of saying 'players exist here for
  *  and communication can be sent to them all in a uniform
  *  way.  The basis for all channels & rooms.
  *
  *  Shouldn't be necessary to store players in the linkedTrie
  *  as Tags, as players can't be swapped out while they're online.
  *
  *  *right*?
 */
public class Scape extends Container implements CommandContainer, PlayerGroup
{
	private static final long serialVersionUID = -2480334989348845021L;
	
	public static final AtomicElement[] ELEMENTS =
	{
		AtomicElement.construct( Scape.class, Integer.TYPE,
		    "numberPlayers",
			AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
			"the number of online players in this scape" ),
		AtomicElement.construct( Scape.class, String.class,
		    "allNames",
			AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY |
			AtomicElement.GENERATED,
			"all the names of the players in this scape" ),
		AtomicElement.construct( Scape.class, CommandList.class,
			"commands",
			AtomicElement.PUBLIC_FIELD | AtomicElement.ATOMIC,
			"the scapes custom command list" )
	};
	
	public static final AtomicStructure STRUCTURE = new AtomicStructure( Container.STRUCTURE, ELEMENTS );
	
	transient PlayerGroup playergroup;
	
	/**
	  *  Commands that are specific for this particular
	  *  atom.  Examples might be custom commands in
	  *  rooms, such as 'look' (if its overridden to
	  *  give different messages each time you type
	  *  it)
	 */
	public Reference commands = Reference.EMPTY;
	
	public Scape()
	{
		this( false );
	}
	
	/**
	  *  For the rare case of a reference scape - 
	  *  key baseclass only, atm
	 */
	Scape( boolean reference )
	{
		super( reference );
		init();
	}

	private void init()
	{
		totalScapes++;
		
		playergroup = new LWPlayerGroup();
		setupPlayersCollection();
	}
	
	public void setupPlayersCollection()
	{
		playergroup.setCollection( new StringKeyCollection() );
	}

	public void setCollection( Collection c )
	{
		playergroup.setCollection( c );
	}
	
	public AtomicStructure getDeclaredStructure()
	{
		return( STRUCTURE );
	}
	
	public void loaded()
	{
		super.loaded();
		init();
	}
	
	/**
	  * Add this player to the list of players
	  *
	  * @param p the player to add to the list
	  * @exception NonUniqueKeyException if there is already a player in with this name
	  * @exception BadKeyException if this players name is malformed somehow
	 */
	public void linkPlayer( Player p ) throws NonUniqueKeyException,BadKeyException
	{
		playergroup.linkPlayer( p );
		p.linkToScape( this );
	}
	
	/**
	  * Take this player out of the list of players
	  *
	  * @param p the player to remove from the list
	  * @exception NoSuchElementException if the player is not in the list
	  * @exception BadKeyException if the players name is malformed somehow
	 */
	public void unlinkPlayer( Player p ) throws NonUniqueKeyException,java.util.NoSuchElementException,BadKeyException
	{
		playergroup.unlinkPlayer( p );
		p.unlinkFromScape( this );
	}

	/**
	  * Returns the player matched, or, possibly, an instance of
	  * a Trie object that contains all the matching players.
	  * <p>
	  * A null is returned if no matches were found at all.  The
	  * match string is searched until the end of the string or
	  * a non-alphabetical character is found.
	  * <p>
	  * If you specify a comma seperated list of players, a scape
	  * containing the players will be returned.  Such a scape can
	  * also contain errors when individual multiple matches are
	  * caught
	  *
	  * @param match the start or whole string to match from
	  * @return A player object, referring to the sole match, or a Trie
	 */
	public final Object getPlayer( Player p, String match )
	{
		return( playergroup.getPlayer( p, match ) );
	}
	
	public final Object getPlayer( String match )
	{
		return( playergroup.getPlayer( match ) );
	}
	
	public final Enumeration players()
	{
		return( playergroup.players() );
	}
	
	public boolean containsPlayer( Player p )
	{
		return( playergroup.containsPlayer( p ) );
	}
	
	public void clearTransient()
	{
		for( Enumeration e = players(); e.hasMoreElements(); )
		{
			Player p = (Player) e.nextElement();
			
			try
			{
				p.sendSystem( "You discern that " + getName() + " no longer exists." );
			}
			catch( PlayerNotConnectedException ex )
			{
			}
			
			try
			{
				unlinkPlayer( p );
			}
			catch( NonUniqueKeyException t )
			{
				throw new UnexpectedResult( t.toString() );
			}
			catch( BadKeyException t )
			{
				throw new UnexpectedResult( t.toString() );
			}
		}
		
		contained.deallocate();
		
		super.clearTransient();
	}
	
	public final String allNames()
	{
		return( playergroup.allNames() );
	}
	
	/**
	  *  All the names, except substitute 'you' for player p
	 */
	public final String allNames( Player p )
	{
		return( playergroup.allNames( p ) );
	}
	
	public final int numberPlayers()
	{
		return( playergroup.numberPlayers() );
	}
	
	public final int getNumberPlayers()
	{
		return( playergroup.getNumberPlayers() );
	}
	
	public String getAllNames() { return( playergroup.allNames() ); }
	
	/**
	  *  A general way of saying that the people in
	  *  this scape should be notified of the
	  *  supplied Effect.
	 */
	public void splash( Effect t, SuppressionList s )
	{
		super.splash( t, s );
		
		Atom oldsplasher = t.getSplasher();
		t.setSplasher( this );
		
		playergroup.splash( t, s );
		
		t.setSplasher( oldsplasher );
	}
	
	/**
	  *  Sends the splash to everyone except the listed
	  *  atom
	 */
	public void splashExcept( Effect t, Splashable except, SuppressionList sl )
	{
		super.splash( t, sl );
		
		Atom oldsplasher = t.getSplasher();
		t.setSplasher( this );
		
		playergroup.splashExcept( t, except, sl );
		
		t.setSplasher( oldsplasher );
	}
	
	/**
	  *  Sends the splash to everyone except the listed
	  *  atoms
	 */
	public void splashExcept( Effect t, Splashable[] except, SuppressionList sl )
	{
		super.splash( t, sl );

		Atom oldsplasher = t.getSplasher();
		t.setSplasher( this );
		
		playergroup.splashExcept( t, except, sl );
		
		t.setSplasher( oldsplasher );
	}
	
	/**
	  *  A shortcut, since matching it from the
	  *  string could be prohibitively slow
	 */
	public final CommandList getCommandList()
	{
		try
		{
			return( (CommandList) commands.get() );
		}
		catch( OutOfDateReferenceException e )
		{
			Log.debug( this, e.toString() + " in scape " + getName() + ":getCommandList()" );
			commands = Reference.EMPTY;
			return( null );
		}
		catch( ClassCastException e )
		{
			Log.error( getId() + ".commands", e );
			commands = Reference.EMPTY;
			return( null );
		}
	}
	
	private static int totalScapes;
	
	public static int getTotalScapes()
	{
		return( totalScapes );
	}
	
	protected void finalize() throws Throwable
	{
		super.finalize();
		
		totalScapes--;
	}
	
	boolean canSwap()
	{
		return( numberPlayers() == 0 );
	}
}