/*
** j###t ########## #### ####
** j###t ########## #### ####
** j###T "###L J###"
** ######P' ########## #########
** ######k, ########## T######T
** ####~###L ####
** #### q###L ########## .#####
** #### \###L ########## #####"
*/
package key;
import key.primitive.*;
import key.util.LinkedList;
import key.util.FilteredEnumeration;
import key.util.MultiEnumeration;
import key.util.RecursiveEnumeration;
import key.util.CircularBuffer;
import key.collections.StringKeyCollection;
import java.io.*;
import java.util.Date;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Hashtable;
import java.util.Vector;
import key.effect.*;
import key.util.StringTokenizer;
import key.commands.Go;
/**
* The player class holds all the player specific information, such as
* eachs players title, password, and received email.
*/
public final class Player
extends Container
implements Targetable,CommandContainer,Interactive,Splashable
{
private static final long serialVersionUID = 8665452585164135888L;
public static final int MAX_PLAN_LINES = 6;
public static final int MAX_PLAN_BYTES = MAX_PLAN_LINES * 80;
public static final int MAX_DESCRIPTION_LINES = 22;
public static final int MAX_DESCRIPTION_BYTES = MAX_DESCRIPTION_LINES * 80;
public static final int MAX_PROMPT_LENGTH = 20;
public static final int MAX_TITLE_LENGTH = 60;
public static final int MAX_PREFIX_LENGTH = 10;
public static final int MAX_AKA_LENGTH = 20;
public static final int MAX_IDLEMSG_LENGTH = 70;
public static final int MAX_BLOCKMSG_LENGTH = 70;
public static final int MAX_BLOCKINGMSG_LENGTH = 70;
public static final int MAX_LAST_CONNECT_FROM_SITE_LENGTH = 70;
public static final int MAX_LOGIN_SCRIPT_LENGTH = 70;
public static final int MAX_CONNECT_MSG_LENGTH = 40;
public static final int MAX_OLD_LOGIN_LENGTH = 50;
public static final int MAX_MODE_LENGTH = 50;
public static final int MAX_TERMINAL_NAME_LENGTH = 25;
private static final AtomicElement[] ELEMENTS =
{
AtomicElement.construct( Player.class, String.class,
"password",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"the players password" ),
AtomicElement.construct( Player.class, String.class,
"prompt", "prompt",
AtomicElement.PUBLIC_FIELD,
"the players prompt",
AtomicSpecial.StringLengthLimit( MAX_PROMPT_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"title", "title",
AtomicElement.PUBLIC_FIELD,
"a string that sometimes comes after the players name",
AtomicSpecial.StringLengthLimit( MAX_TITLE_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"mode", "mode",
AtomicElement.PUBLIC_FIELD,
"a string that is prepended to every line typed",
AtomicSpecial.StringLengthLimit( MAX_MODE_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"prefix", "prefix",
AtomicElement.PUBLIC_ACCESSORS,
"a string that sometimes comes before the players name",
AtomicSpecial.StringLengthLimit( MAX_PREFIX_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"aka", "aka",
AtomicElement.PUBLIC_FIELD,
"'also known as', another name for the player",
AtomicSpecial.StringLengthLimit( MAX_AKA_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"loginMsg", "loginMsg",
AtomicElement.PUBLIC_FIELD,
"Message appended to informs on login (prefix with ': ')",
AtomicSpecial.StringLengthLimit( MAX_CONNECT_MSG_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"logoutMsg", "logoutMsg",
AtomicElement.PUBLIC_FIELD,
"Message appended to informs on logout (prefix with ': ')",
AtomicSpecial.StringLengthLimit( MAX_CONNECT_MSG_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, TextParagraph.class,
"description", "description",
AtomicElement.PUBLIC_FIELD,
"a description of the player",
// a players description is limited to 22 lines and 80*22 bytes
// also set in commands.Describe.java - redundant atm
AtomicSpecial.TextParagraphLengthLimit( MAX_DESCRIPTION_BYTES,
MAX_DESCRIPTION_LINES ) ),
AtomicElement.construct( Player.class, TextParagraph.class,
"plan", "plan",
AtomicElement.PUBLIC_FIELD,
"the players plan",
AtomicSpecial.TextParagraphLengthLimit( MAX_PLAN_BYTES,
MAX_PLAN_LINES ) ),
AtomicElement.construct( Player.class, Gender.class,
"gender",
AtomicElement.PUBLIC_FIELD,
"the players gender" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"quiet",
AtomicElement.PUBLIC_FIELD,
"if true, the player will never be 'beeped'" ),
AtomicElement.construct( Player.class, Integer.TYPE,
"age",
AtomicElement.PUBLIC_FIELD,
"the players age" ),
AtomicElement.construct( Player.class, Integer.TYPE,
"florins",
//AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
AtomicElement.PUBLIC_FIELD,
"the amount of money the player has" ),
AtomicElement.construct( Player.class, Inventory.class,
"inventory",
AtomicElement.PUBLIC_FIELD,
"the objects the player has" ),
AtomicElement.construct( Player.class, NoKeyContainer.class,
"objects",
AtomicElement.PUBLIC_FIELD,
"the individual objects the player has" ),
AtomicElement.construct( Player.class, String.class,
"forcedTerminal", "forcedTerminal",
AtomicElement.PUBLIC_FIELD,
"the terminal that the user has forced the use of",
AtomicSpecial.StringLengthLimit( MAX_TERMINAL_NAME_LENGTH, false, false ) ),
AtomicElement.construct( Player.class, String.class,
"idleMsg", "idleMsg",
AtomicElement.PUBLIC_FIELD,
"the message output when the player is idle",
AtomicSpecial.StringLengthLimit( MAX_IDLEMSG_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"blockMsg", "blockMsg",
AtomicElement.PUBLIC_FIELD,
"the message output when a message to this player is blocked",
AtomicSpecial.StringLengthLimit( MAX_BLOCKMSG_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, String.class,
"blockingMsg", "blockingMsg",
AtomicElement.PUBLIC_FIELD,
"the message output when this player is blocking all messages",
AtomicSpecial.StringLengthLimit( MAX_BLOCKINGMSG_LENGTH, false, true ) ),
AtomicElement.construct( Player.class, Duration.class,
"timezone",
AtomicElement.PUBLIC_FIELD,
"the time-offset of this player compared to local time" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"isIdle",
AtomicElement.PUBLIC_FIELD,
"true if the player is currently idle" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"hidden",
AtomicElement.PUBLIC_FIELD,
"true if the player is currently hidden" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"liberated",
AtomicElement.PUBLIC_FIELD,
"true if the player can't be enrolled in a clan" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"brief",
AtomicElement.PUBLIC_FIELD,
"true if the player wants shorter output" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"privateEmail",
AtomicElement.PUBLIC_FIELD,
"true if the player wants an unlisted email address" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"citizen",
AtomicElement.PUBLIC_FIELD,
"true if the player is a citizen" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"expert",
AtomicElement.PUBLIC_FIELD,
"true if the player is in expert mode" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"hideTime",
AtomicElement.PUBLIC_FIELD,
"true if the player has hidden his logintime" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"newmail",
AtomicElement.PUBLIC_FIELD,
"true if the player might have unread mail (check on next login)" ),
AtomicElement.construct( Player.class, Integer.TYPE,
"titleTolerance",
AtomicElement.PUBLIC_FIELD,
"the largest number of people in the room with a verbose list" ),
AtomicElement.construct( Player.class, DateTime.class,
"banishedUntil",
AtomicElement.PUBLIC_FIELD,
"the date at which the player will be unbanished" ),
AtomicElement.construct( Player.class, String.class,
"banishType", "banishType",
AtomicElement.PUBLIC_FIELD,
"how the player is banished, 'S' for siteban too, 'B' otherwise",
AtomicSpecial.StringLengthLimit( 1, false, false ) ),
AtomicElement.construct( Player.class, String.class,
"lastConnectFrom", "lastConnectFrom",
AtomicElement.PUBLIC_FIELD,
"the last site the player connected from",
AtomicSpecial.StringLengthLimit( MAX_LAST_CONNECT_FROM_SITE_LENGTH, false, false ) ),
AtomicElement.construct( Player.class, CommandList.class,
"commands",
AtomicElement.PUBLIC_FIELD | AtomicElement.ATOMIC,
"the players personal command list" ),
AtomicElement.construct( Player.class, CommandList.class,
"specialCommands",
AtomicElement.PUBLIC_FIELD,
"the players custom command list" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"canSave",
AtomicElement.PUBLIC_FIELD,
"true iff the player has permission to write to disk" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"staff",
AtomicElement.PUBLIC_FIELD,
"true iff the player is a member of the talker staff" ),
AtomicElement.construct( Player.class, MessageBox.class,
"mailbox",
AtomicElement.PUBLIC_FIELD | AtomicElement.ATOMIC,
"the players local mailbox" ),
AtomicElement.construct( Player.class, TimeStatistics.class,
"loginStats",
AtomicElement.PUBLIC_FIELD,
"the players login statistics" ),
AtomicElement.construct( Player.class, ConnectionStatistics.class,
"connectionStats",
AtomicElement.PUBLIC_FIELD,
"the players connection statistics" ),
AtomicElement.construct( Player.class, Friends.class,
"friends",
AtomicElement.PUBLIC_FIELD | AtomicElement.ATOMIC,
"the players friend list" ),
AtomicElement.construct( Player.class, Group.class,
"prefer",
AtomicElement.PUBLIC_FIELD | AtomicElement.ATOMIC,
"the players common mis-tell preferences" ),
AtomicElement.construct( Player.class, InformList.class,
"inform",
AtomicElement.PUBLIC_FIELD | AtomicElement.ATOMIC,
"this player wants to know when this list of people are online" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"informEveryone",
AtomicElement.PUBLIC_FIELD,
"this player wants to know when everyone connects" ),
AtomicElement.construct( Player.class, Clan.class,
"clan",
AtomicElement.PUBLIC_FIELD,
"the clan that the player is in" ),
AtomicElement.construct( Player.class, Room.class,
"home",
AtomicElement.PUBLIC_FIELD,
"the players home room" ),
AtomicElement.construct( Player.class, Room.class,
"location",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"the players current location" ),
AtomicElement.construct( Player.class, Realm.class,
"realm",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"the players current realm" ),
AtomicElement.construct( Player.class, Room.class,
"lastPublicRoomLocation",
AtomicElement.PUBLIC_FIELD,
"the players last location while in a public room" ),
AtomicElement.construct( Player.class, Room.class,
"loginRoom",
AtomicElement.PUBLIC_FIELD,
"the players requested login room" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"saved",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"true if this player has been written to disk (at least once)" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"beyond",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"true if this player has beyond access" ),
AtomicElement.construct( Player.class, Integer.TYPE,
"scapeCount",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"the number of scapes this player is in" ),
AtomicElement.construct( Player.class, Integer.TYPE,
"rankCount",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"the number of ranks this player is in" ),
AtomicElement.construct( Player.class, InteractiveConnection.class,
"connection",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
"the connection for this player (or null if not connected)" ),
AtomicElement.construct( Player.class, String.class,
"titledName",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY |
AtomicElement.GENERATED,
"the name and title of this player" ),
AtomicElement.construct( Player.class, String.class,
"possessiveName",
AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY |
AtomicElement.GENERATED,
"the name of this player with 's appended" ),
AtomicElement.construct( Player.class, EmailAddress.class,
"email",
AtomicElement.PUBLIC_FIELD,
"the email address of this player" ),
AtomicElement.construct( Player.class, Webpage.class,
"homepage",
AtomicElement.PUBLIC_FIELD,
"the homepage of this player" ),
AtomicElement.construct( Player.class, Site.class,
"bannedWithSite",
AtomicElement.PUBLIC_FIELD,
"the site that was banned along with this player" ),
AtomicElement.construct( Player.class, Boolean.TYPE,
"showFlags",
AtomicElement.PUBLIC_FIELD,
"if this player is seeing tell codes " ),
AtomicElement.construct( Player.class, String.class,
"oldLoginTime", "oldLoginTime",
AtomicElement.PUBLIC_FIELD,
"old EW forest login time",
AtomicSpecial.StringLengthLimit( MAX_OLD_LOGIN_LENGTH, false, false ) ),
AtomicElement.construct( Player.class, String.class,
"loginScript", "loginScript",
AtomicElement.PUBLIC_FIELD,
"pipe seperated list of commands to be executed at login",
AtomicSpecial.StringLengthLimit( MAX_LOGIN_SCRIPT_LENGTH, false, false ) ),
};
// this class is immutable and final, therefore this is safe
public static final AtomicStructure STRUCTURE = new AtomicStructure( Container.STRUCTURE, ELEMENTS );
public static final int DEFAULT_MAX_OBJECTS = 4;
public static final int DEFAULT_MAX_MAIL = 20;
public static final int DEFAULT_MAX_FRIENDS = 50;
public static final int DEFAULT_MAX_PREFER = 10;
public static final int DEFAULT_MAX_INFORM = 50;
public static final int MAX_NAME = 16;
public static final int MIN_NAME = 3;
public static final int SHOWFLAG_DIRECTED = 0;
public static final int SHOWFLAG_FRIENDS = 1;
public static final int SHOWFLAG_ROOM = 2;
public static final int SHOWFLAG_SHOUTS = 3;
public static final int SHOWFLAG_ENTER = 4;
public static final int SHOWFLAG_LEAVE = 5;
public static final int SHOWFLAG_LOGIN = 6;
public static final int SHOWFLAG_LOGOUT = 7;
public static final int SHOWFLAG_BLOCKING = 8; // name blocks the clan chan
public static final char[] SHOWCHARS =
{ '>', '*', '-', '!', ')', '(', '}', '{', '#' };
/** The players connection */
transient InteractiveConnection ic;
/** The players location */
// some confusion about this vs the property - I think the
// property might be the 'last public room' location.
transient Room location;
transient Reference realm;
/** The current context for the player */
transient Atom context;
/** a reverseReference style linked list of the scapes this player is in */
transient Vector reverseScapes;
public static final int MAX_HISTORY_LINES = 10;
/** a list of communication to/from this player */
private transient CircularBuffer history;
/**
* This is not the definitive reference as to the ranks a player is in,
* although it is reasonable to expect that it is up to date. This
* data structure gets updated by Rank.java as players are added and
* removed to a rank. It is used to load Ranks into memory as a player
* comes online, allowing them to detect that the Player has come online,
* and link them into the rank if they wish.
*/
Vector reverseRanks = new Vector( 5, 5 );
/** a list of the people in the multi to reply to */
transient LWPlayerGroup replyList;
/** a linkedList of qualifier ref's */
QualifierList qualifierList;
/** Whether the player is 'beyond' (account access) or not */
private boolean beyond;
private boolean canPage = true;
boolean saved;
public String getName()
{
//if( saved )
return( super.getName() );
//else
//return( "VISITOR:" + super.getName() );
}
boolean showFlags;
boolean wordwrap = true;
//--- system accessable properties ---//
private Password password = new Password();
String prompt = "-> ";
String title = " the newbie, so please treat me nicely";
String mode = null;
String prefix = "";
String aka = "";
TextParagraph description = new TextParagraph();
TextParagraph plan = new TextParagraph();
Gender gender = Gender.NEUTER_GENDER;
boolean quiet = false;
int age = 0;
int florins = 0;
String forcedTerminal = null;
String idleMsg = "";
String blockMsg = "";
String blockingMsg = "";
String loginMsg = "";
String logoutMsg = "";
Duration timezone = new Duration();
boolean isIdle = false;
boolean hidden = false;
boolean liberated = false;
boolean brief = false;
boolean privateEmail = true;
boolean citizen = false;
boolean expert = false;
boolean hideTime = false;
boolean newmail = false;
boolean informEveryone = false;
int titleTolerance = 10;
DateTime banishedUntil = null;
String banishType = "";
String lastConnectFrom = "";
Reference commands = Reference.EMPTY;
Reference specialCommands = Reference.EMPTY;
String oldLoginTime = "";
boolean canSave = true;
boolean staff = false;
// it isn't a security violation to have these fields public,
// as they are final, and they look after their own immutability.
public final TimeStatistics loginStats = (TimeStatistics) Factory.makeAtom( TimeStatistics.class, "loginStats" );
public final ConnectionStatistics connectionStats = (ConnectionStatistics) Factory.makeAtom( ConnectionStatistics.class, "connectionStats" );
public final Friends friends = (Friends) Factory.makeAtom( Friends.class, "friends" );
public final Group prefer = (Group) Factory.makeAtom( Group.class, "prefer" );
public final InformList inform = (InformList) Factory.makeAtom( InformList.class, "inform" );
public final Inventory inventory = (Inventory) Factory.makeAtom( Inventory.class, "inventory" );
public final NoKeyContainer objects = (NoKeyContainer) Factory.makeAtom( NoKeyContainer.class, "objects" );
public final MessageBox mailbox = (MessageBox) Factory.makeAtom( MessageBox.class, "mailbox" );
Reference clan = Reference.EMPTY;
Reference room = Reference.EMPTY;
Reference lastPublicRoomLocation = Reference.EMPTY;
Reference loginRoom = Reference.EMPTY;
Reference home = Reference.EMPTY;
String loginScript = "orient|l";
public boolean getSaved() { return( saved ); }
public boolean getBeyond() { return( beyond ); }
public int getScapeCount() { return( reverseScapes.size() ); }
public int getRankCount() { return( reverseRanks.size() ); }
EmailAddress email = new EmailAddress();
Webpage homepage = new Webpage();
Reference bannedWithSite = Reference.EMPTY;
//--- end accessable properties ---//
static char originatorCode = 'o';
/** Codes used for % replacements when this player does a command */
transient String[] codes;
/** Used to store the information which allows the 'repeat' command to work */
transient Repeated repeat;
// for detecting the player idling out in the latencycache
private transient LatentlyCached latentCacheHook;
/** Constructs a player */
Player()
{
setLimit( DEFAULT_MAX_OBJECTS );
ic = null;
location = null;
realm = Key.instance().getDefaultRealm().getThis();
beyond = false;
saved = false;
// this now static
//originatorCode = new Character( 'o' );
init();
qualifierList = new QualifierList();
showFlags = true;
mailbox.setLimit( DEFAULT_MAX_MAIL );
friends.setLimit( DEFAULT_MAX_FRIENDS );
prefer.setLimit( DEFAULT_MAX_PREFER );
inform.setLimit( DEFAULT_MAX_INFORM );
// just a default so that a new player *has* some commands...
commands = ((Atom) Key.instance().commandSets.getElement( "base" )).getThis();
// set up some default permissions
// generally, its okay to talk to people & friend them
permissionList.allow( tellAction );
permissionList.allow( friendAction );
// and its okay to send them email
mailbox.getPermissionList().allow( Container.addToAction );
inventory.getPermissionList().allow( Container.addToAction );
}
private void readObject( ObjectInputStream ois ) throws IOException
{
try
{
ois.defaultReadObject();
}
catch( ClassNotFoundException e )
{
throw new UnexpectedResult( e.toString() );
}
init();
// Playerfile revision code (example)
// We just added loginRoom, which is a Reference,
// and we want it to default to Reference.EMPTY.
if( loginRoom == null )
loginRoom = Reference.EMPTY;
if( loginMsg == null )
loginMsg = "";
if( logoutMsg == null )
logoutMsg = "";
}
private void init()
{
reverseScapes = new Vector( 5, 5 );
codes = new String[26];
idleTicks = MAX_IDLE_TICKS;
context = this;
idlePrompt = "";
repeat = new Repeated();
spamThrottle = new long[ NUMBER_OF_THROTTLES ];
spamViolations = new int[ NUMBER_OF_THROTTLES ];
if( history == null )
history = new CircularBuffer( MAX_HISTORY_LINES );
totalPlayers++;
commandLoopCount = 0;
}
public Enumeration getTellHistory()
{
if( history != null )
{
if( getCurrent() != this )
throw new AccessViolationException( this, "No-one may access another players tell history" );
return( history.elements() );
}
return( key.util.EmptyEnumeration.EMPTY );
}
public AtomicStructure getDeclaredStructure()
{
return( STRUCTURE );
}
void stopBeingTemporary()
{
// do nothing, we upgrade from temporary to distinct later
//index = Registry.instance.allocateTemporaryIndex( this );
}
/**
* Players always own themselves
*/
void assignInitialOwner()
{
owner = getThis();
}
public final void putCode( char c, String value )
{
try
{
codes[ c - 'a' ] = value;
}
catch( Exception e )
{
e.printStackTrace();
Log.error( "During Player::putCode()", e );
}
}
public final String[] getCodes()
{
return( codes );
}
public boolean brief()
{
return( brief );
}
public void setBrief( boolean nb )
{
permissionList.check( modifyAction );
brief = nb;
}
public boolean isBeyond()
{
return( beyond );
}
public boolean isExpert()
{
return( expert );
}
public void setWordwrap( boolean ww )
{
wordwrap = ww;
if( connected() )
{
if( ic instanceof TelnetIC )
((TelnetIC)ic).setWordwrap( ww );
else
throw new WrongTerminalTypeException();
}
}
public boolean getWordwrap()
{
return( wordwrap );
}
// required for the 'term' command.
public synchronized boolean setTerminal( String name )
{
permissionList.check( modifyAction );
if( connected() )
{
if( ic instanceof TelnetIC )
{
boolean r = ((TelnetIC)ic).forceTerminal( name );
if( r )
forcedTerminal = name;
return( r );
}
else
throw new WrongTerminalTypeException();
}
else
throw new PlayerNotConnectedException();
}
// required for the 'term' command.
public synchronized void resetTerminal()
{
permissionList.check( modifyAction );
if( connected() )
{
if( ic instanceof TelnetIC )
((TelnetIC)ic).resetTerminal();
else
throw new WrongTerminalTypeException();
}
forcedTerminal = null;
}
public Terminal getTerminal()
{
if( connected() )
if( ic instanceof TelnetIC )
return( ((TelnetIC)ic).getTerminal() );
else
throw new WrongTerminalTypeException();
else
throw new PlayerNotConnectedException();
}
// this would be an invasion of privacy to export
public Repeated getRepeated()
{
// META: this modifyAction is temporary until we have some kind
// of action to support this
permissionList.check( modifyAction );
return( repeat );
}
public void addMail( Letter l ) throws NonUniqueKeyException,BadKeyException
{
mailbox.add( l );
newmail = true;
if( connected() )
{
ic.sendSystem( "New mail from " + l.from + ", '" + l.description + "'. Use 'read mail 1' to read this message." );
ic.flush();
}
}
public MessageBox getMailbox()
{
return( mailbox );
}
public QualifierList.Immutable getImmutableQualifierList()
{
// this is not a security risk because ImmutableQL isn't
// changeable
return( qualifierList.getImmutable() );
}
public QualifierList getQualifierList()
{
permissionList.check( modifyAction );
return( qualifierList );
}
public final Clan getClan()
{
try
{
return( (Clan) clan.get() );
}
catch( OutOfDateReferenceException e )
{
clan = Reference.EMPTY;
return( null );
}
catch( ClassCastException e )
{
Log.error( "somebody set " + getId() + ".clan wrong (reset)", e );
clan = Reference.EMPTY;
return( null );
}
}
public final Realm getRealm()
{
try
{
return( (Realm) realm.get() );
}
catch( OutOfDateReferenceException e )
{
realm = Key.instance().getDefaultRealm().getThis();
return( (Realm) realm.get() );
}
catch( ClassCastException e )
{
Log.error( "somebody set " + getId() + ".realm wrong (reset)", e );
realm = Reference.EMPTY;
return( null );
}
}
public void setCanPage( boolean v )
{
permissionList.check( modifyAction );
canPage = v;
}
public String getIdleMsg()
{
return( idleMsg );
}
// it is the inventories problem to prevent itself from being
// modified by unauthorised people
public Inventory getInventory()
{
return( inventory );
}
public EmailAddress getEmail()
{
// this could be made better (immutable email address?)
permissionList.check( seePrivateInfoAction );
permissionList.check( modifyAction );
return( email );
}
/**
* META: this needs to be updated for the new permissionlist
* that has a boolean check method (that doesn't throw exceptions)
* for efficiency.
*/
public String getEmailAddress()
{
try
{
if( privateEmail )
permissionList.check( seePrivateInfoAction );
return( email.toString() );
}
catch( AccessViolationException e )
{
return( "<private>" );
}
}
public Webpage getWebpage()
{
return( homepage );
}
public String getBlockMsg()
{
return( blockMsg );
}
public String getLoginMsg()
{
return( loginMsg );
}
public String getLogoutMsg()
{
return( logoutMsg );
}
public String getAka()
{
return( aka );
}
public int getAge()
{
return( age );
}
// this should be automatically determined
public boolean isEmailPrivate()
{
return( privateEmail );
}
public String getPassword()
{
return( password.toString() );
}
public Password getActualPassword()
{
permissionList.check( modifyAction );
return( password );
}
public String getBlockingMsg()
{
return( blockingMsg );
}
public Paragraph getPlan()
{
return( plan );
}
public Paragraph getDescription()
{
return( description );
}
public final boolean isCitizen()
{
return( citizen );
}
public final boolean getExpert()
{
return( expert );
}
public final Duration getTimezone()
{
return( timezone );
}
public final void setTimezone( Duration nd )
{
permissionList.check( modifyAction );
timezone = nd;
}
public boolean isHiding()
{
return( hidden );
}
public String getLastConnectFrom()
{
return( lastConnectFrom );
}
public void setIdle( boolean tf )
{
permissionList.check( modifyAction );
isIdle = tf;
recalculateIdlePrompt();
}
public void setHiding( boolean tf )
{
permissionList.check( modifyAction );
hidden = tf;
}
/** if the player can't be put into a clan */
public boolean isLiberated()
{
return( liberated );
}
public void setLiberated( boolean nl )
{
permissionList.check( modifyAction );
liberated = nl;
}
public Room getLastPublicRoom()
{
try
{
return( (Room) lastPublicRoomLocation.get() );
}
catch( OutOfDateReferenceException e )
{
lastPublicRoomLocation = Reference.EMPTY;
return( null );
}
catch( ClassCastException e )
{
Log.error( "somebody set " + getId() + ".lastpublicroom wrong (reset)", e );
lastPublicRoomLocation = Reference.EMPTY;
return( null );
}
}
public Room getHome()
{
try
{
return( (Room) home.get() );
}
catch( OutOfDateReferenceException e )
{
home = Reference.EMPTY;
return( null );
}
catch( ClassCastException e )
{
Log.error( "somebody set " + getId() + ".home wrong (reset)", e );
home = Reference.EMPTY;
return( null );
}
}
public void setHome( Room r )
{
permissionList.check( modifyAction );
try
{
home = r.getThis();
}
catch( TypeMismatchException e )
{
throw new UnexpectedResult( "property 'home' is not a Room in Player: " + e.toString() );
}
}
public Friends getFriends()
{
return( friends );
}
public Group getPrefer()
{
return( prefer );
}
public String getPrefix()
{
return( prefix );
}
public void setPrefix( String s )
{
if( s == null || s.length() == 0 )
prefix = "";
else
prefix = s + " ";
}
public String getTitledName()
{
return( getName() + title );
}
public InformList getInform()
{
return( inform );
}
// should modify this routine to do
// authentication - don't allow anyone
// to set the password?
public void setPassword( String newValue )
{
permissionList.check( modifyAction );
password.set( newValue );
}
void linkToScape( Scape s )
{
reverseScapes.addElement( s );
}
void unlinkFromScape( Scape s )
{
reverseScapes.removeElement( s );
}
void ejected( Room from, Effect enter, Effect leave )
{
Room wanted = Key.instance().getConnectRoom( this );
if( leave != null )
leave.cause();
moveTo( wanted );
enter.cause();
roomLook();
}
void addReverseRank( Rank r )
{
reverseRanks.addElement( r.getThis() );
System.out.println( "added revrank '" + r.getName() + "' to player " + getName() );
}
void removeReverseRank( Rank r )
{
reverseRanks.addElement( r.getThis() );
System.out.println( "removed revrank '" + r.getName() + "' to player " + getName() );
}
public Enumeration scapes()
{
return( reverseScapes.elements() );
}
public Enumeration ranks()
{
return( new FilteredEnumeration(
reverseRanks.elements(),
new ReferenceEnumeratorFilter(
new ReferenceEnumeratorFilter.EnumeratedThing()
{
public void noSideEffectRemove( Reference r )
throws NonUniqueKeyException, java.util.NoSuchElementException,BadKeyException
{
Player.this.reverseRanks.removeElement( r );
}
}
, true ) ) );
}
/**
* You can target a player if you can target
* any of the ranks that they're in. That means
* that most staff members will _not_ be in the
* 'resident' rank (so they can't hurt each other.
*/
public boolean isOutRankedBy( Rank rank )
{
// cache this value for speed
Targets targets = rank.getTargets();
if( targets != null )
{
for( Enumeration e = targets.elements(); e.hasMoreElements(); )
{
Object o = e.nextElement();
if( o instanceof Rank )
{
Rank theRank = (Rank) o;
if( theRank.containsAtAll( getThis() ) )
{
return true;
}
}
else if( o == this )
return true;
}
}
return false;
}
/** eg: "subtle's" */
// META: better representation for name would be nice, these are called a lot
public final String getPossessiveName()
{
return( getName() + "'s" );
}
/** Gets the players location */
public final Room getLocation()
{
if( isHiding() )
permissionList.check( findAction );
return( location );
}
//public static final int MAX_IDLE_TICKS = 1;
//public static final int MAX_IDLE_TICKS = 5;
public static final int MAX_IDLE_TICKS = 10;
private transient int idleTicks;
/**
* Called by the interactive connection to reset the
* players idletime to 0
*/
final void unIdle()
{
// don't you dare add anything to this routine to stop
// certain players idling out: it needs to be as small
// and fast as possible. Put it in 'resetModify',
// if you must.
connectionStats.unIdle();
idleTicks = MAX_IDLE_TICKS;
}
public final Duration getIdle( DateTime now )
{
return( connectionStats.getIdleTime( now ) );
}
/** A players aspect is its description when looked at */
public Paragraph aspect()
{
return( description );
}
/** Called when a password entry is required */
public boolean authenticate( InteractiveConnection ic ) throws IOException,PasswordEntryCancelled
{
return( password.check( getName(), ic ) );
}
private transient int connecting = 0;
void beginConnect()
{
connecting++;
}
void endConnect()
{
if( connecting > 0 )
connecting--;
}
public void setContext( Atom r )
{
permissionList.check( modifyAction );
if( r != null )
context = r;
else
context = this;
}
/** Returns the current players context */
public Atom getContext()
{
return( context );
}
protected transient int lagAmount = 0;
protected transient boolean lagOnce = false;
protected transient boolean discard = false;
public void setLag( int amount, boolean continual )
{
permissionList.check( modifyAction );
if( amount >= 0 )
{
lagAmount = amount * 1000;
lagOnce = continual;
}
}
/** The main thread of a player - simply starts the parser */
public void run( InteractiveConnection ic )
{
//setPriority( Thread.NORM_PRIORITY-1 );
if( ic != getConnection() )
{
ic.send( "Poor connection" );
ic.close();
return;
}
ic.setKey( "IC: " + getKey().toString() );
try
{
synchronized( this )
{
ic.blankLines( 3 );
// connect the player to a room, initially
{
Room startRoom = null;
try
{
startRoom = (Room) loginRoom.get();
}
catch( Exception e )
{
loginRoom = Reference.EMPTY;
ic.sendFailure( "^hYour login room is no longer valid.^-" );
}
if( startRoom == null )
startRoom = Key.instance().getConnectRoom( this );
String message = startRoom.getLoginMessage();
putCode( originatorCode, getName() );
message = Grammar.substitute( message, getCodes() );
moveTo( startRoom, null, new key.effect.Enter( this, startRoom, message ) );
}
if( location == null )
{
ic.sendError( "Could not connect you to a room, disconnecting..." );
disconnect();
return;
}
// scan ranks
int warnLostRank = 0;
for( int i = 0; i < reverseRanks.size(); i++ )
{
Reference o = (Reference) reverseRanks.elementAt( i );
try
{
Rank r = (Rank) o.get();
if( !r.contains( this ) )
{
reverseRanks.removeElementAt( i-- );
warnLostRank++;
}
else
r.establish( this );
}
catch( OutOfDateReferenceException e )
{
reverseRanks.removeElementAt( i-- );
warnLostRank++;
}
catch( ClassCastException e )
{
reverseRanks.removeElementAt( i-- );
warnLostRank++;
}
}
// might as well let them know...
if( warnLostRank > 0 )
{
if( warnLostRank == 1 )
ic.send( "^hWhile you were offline, a rank that you were in was erased. (Just letting you know) You will not be notified of this again, and I have no way of determining which rank it was.^-" );
else
ic.send( "^hWhile you were offline, " + warnLostRank + " ranks that you were in were erased. (Just letting you know) You will not be notified of this again, and I have no way of determining which ranks they were.^-" );
}
// this is the bit that links the player back
// into any groups they should be in. :)
putCode( originatorCode, getName() );
new key.effect.Login( this, "%o has connected^@" + loginMsg + "^$" ).cause();
// execute the players login script
{
StringTokenizer st = new StringTokenizer( loginScript, "|", false );
commandLoopCount = 0;
while( st.hasMoreTokens() )
{
command( st.nextToken(), ic, false );
Thread.yield(); // be nice and friendly
}
}
}
try
{
String entered;
while( connected() )
{
// just a quick check for the lag code
// *grin*
if( lagAmount >= 0 )
{
try
{
Thread.sleep( lagAmount );
}
catch( InterruptedException e )
{
}
if( lagOnce == true )
{
lagAmount = 0;
lagOnce = false;
if( discard )
{
try
{
ic.discard();
}
catch( IOException e )
{
}
discard = false;
}
}
}
if( mode == null )
entered = ic.input( getContextualPrompt() );
else
{
entered = ic.input( getContextualPrompt() + mode );
if( entered.length() == 0 )
mode = null;
else
entered = mode + entered;
}
Thread.yield(); // be nice and friendly
if( entered.length() == 0 )
{
if( ic.isPaging() )
{
((TelnetIC)ic).drawNextPage();
}
continue;
}
try
{
commandLoopCount = 0;
command( entered, ic, false );
}
catch( Exception e )
{
if( e instanceof UserOutputException )
((UserOutputException)e).send( ic );
else
throw e;
}
}
}
catch( NetworkException e )
{
System.err.println( e.toString() );
e.printStackTrace();
}
}
catch( ThreadDeath td )
{
synchronized( this )
{
if( ic == getConnection() )
{
disconnect();
ic = null;
}
else if( ic != null )
{
ic.close();
ic = null;
}
}
throw td;
}
catch( Exception e )
{
Log.error( "Player::run() [" + getName() + "]", e );
}
synchronized( this )
{
// necessary for reconnections - we don't
// want to disconnect the newly connected
// player, but neither do we want to exit
// this thread with an unclosed connection.
if( connected() )
{
if( ic == getConnection() )
disconnect();
else if( ic != null )
ic.close();
}
else if( ic != null )
ic.close();
}
}
private transient String idlePrompt;
private final void recalculateIdlePrompt()
{
if( isIdle )
idlePrompt = "[IDLE] ";
else
idlePrompt = "";
}
public final boolean isIdle()
{
return( isIdle );
}
private transient boolean isafk = false;
public final void afk( boolean v )
{
permissionList.check( modifyAction );
isafk = v;
}
public final boolean isAfk()
{
return( isafk );
}
/**
* Returns an ordered list of the commands that this
* Player has access to.
*
* I believe this isn't being used.
Enumeration getCommands()
{
// technically, this Observer might only be used
// by the Commands class - so we probably should just
// have a method that returns the above delayed enumeration
return(
new RecursiveEnumeration(
getCommandListEnumerations(),
false, // don't recurse deeply
null // no observer required to just enumerate
)
);
}
*/
/**
* Returns an enumeration of enumerations of the
* commands this player has access to.
*
* I believe this isn't being used
*/
Enumeration getCommandListEnumerations()
{
return(
new FilteredEnumeration(
getCommandLists(),
new FilteredEnumeration.Filter()
{
public boolean isValid( Object element, Enumeration enum )
{ return( true ); }
public Object replace( Object element, Enumeration enum )
{
if( element instanceof CommandList )
return( ((CommandList)element).elements() );
else
return( null );
}
}
)
);
}
/**
* Returns an enumeration of enumerations of the
* commands this player has access to.
*/
public Enumeration getCommandLists()
{
final Enumeration clist = new Enumeration()
{
int state = 0;
int substate = 0;
int last_substate = 0;
Object next = scanNextElement();
public boolean hasMoreElements()
{
return( next != null );
}
public Object nextElement()
{
if( next == null )
throw new NoSuchElementException();
Object o = next;
next = scanNextElement();
return( o );
}
/**
* This is done like this because, believe it or not,
* it's cleaner and more efficient this way. Really.
* The old way sucked.
* <P>
* Because of the way this is written, it can dynamically
* scan command lists without having to pre-process them,
* but also through the Enumeration interface.
*/
public Object scanNextElement()
{
// temporary variable for result
CommandList cl;
switch( state )
{
case 0:
state++; // state 1 is next
if( context != null && context instanceof CommandContainer )
{
cl = ((CommandContainer)context).getCommandList();
if( cl != null && cl.count() > 0 )
return( cl );
}
// proceed to next state now
case 1:
// special commands for the player
// (overriddable only by context)
state++; // state 3 is next
cl = Player.this.getSpecialCommandList();
if( cl != null && cl != context && cl.count() > 0 )
return( cl );
case 2:
state++; // state 3 is next
if( context != Player.this )
{
cl = Player.this.getCommandList();
if( cl != null && cl.count() > 0 )
return( cl );
}
// proceed to next state now
case 3:
state++; // state 4 is next
last_substate = substate; // save the old substate
substate = 0; // initialise
// proceed to next state now
case 4:
// return groups for each player based on
// the substate, which is the group we're
// currently upto
int rss = reverseScapes.size() - 1;
if( (substate) > rss )
{
// this is an error, we should
// already be on the next state -
// just skip through
state++;
}
else
{
if( substate == rss )
{
// this is the last time we can do this
state++; // next time we're in the next state
}
CommandContainer cc = ((CommandContainer)(reverseScapes.elementAt( substate++ )));
if( cc != null && cc != context )
{
cl = cc.getCommandList();
if( cl != null )
return( cl );
else
return( scanNextElement() );
}
else
return( scanNextElement() );
}
case 5: // this is not a valid state.
default:
return( null );
}
}
/**
* Used to get some information about what we're
* enumerating through for the command list.
* <P>
* Returns a string of the form:
* <BR>
* [context] name - x commands
* <P>
* This method is called to get information about
* the result of the <I>last</I> nextElement() call.
*
* @return formatted details string
*/
String getLastDetails()
{
switch( state - 1 )
{
case -1:
throw new UnexpectedResult( "Player::getLastDetails() before nextElement()" );
case 0:
// state 0 is the context scan
return( "context " + context.getName() + " - " + "" );
//break;
case 1:
break;
case 2:
break;
case 3:
break;
default:
throw new UnexpectedResult( "Player::getLastDetails(): invalid state" );
}
return null;
}
};
return( clist );
}
private String getContextualPrompt()
{
StringBuffer gprompt = new StringBuffer();
gprompt.append( idlePrompt );
if( context != this )
{
gprompt.append( '[' );
gprompt.append( context.getName() );
gprompt.append( "] " );
}
if( ic.isPaging() )
gprompt.append( "[PAGER] " );
gprompt.append( prompt );
return( gprompt.toString() );
}
/**
* Returns an enumeration of enumerations of the
* modal commands this player has access to that
* matches a certain prefix string. The enumeration
* contains Object[2] in each element. The first
* is the matched commandcontainer, the second is
* the top level parent of that command container,
* usually indicating the providing rank or scape.
*/
public Enumeration getCategoryCommandsMatching( java.util.StringTokenizer args )
{
//if( !args.hasMoreTokens() )
//throw new UnexpectedResult( "No arguments to getCategoryCommandsMatching specified" );
//String first = args.nextElement();
Vector path = null;
CommandList cl = null;
Vector result = new Vector( 16, 16 );
if( args != null && args.hasMoreTokens() )
{
path = new Vector( 6, 6 );
do
{
path.addElement( args.nextToken() );
} while( args.hasMoreTokens() );
}
//if( path.size() == 0 )
//throw new UnexpectedResult( "No arguments to getCategoryCommandsMatching specified" );
scan: for( Enumeration e = getCommandLists(); e.hasMoreElements(); )
{
cl = (CommandList) e.nextElement();
if( cl == null )
continue;
CommandList top = cl;
Object match = null;
if( path != null )
{
for( Enumeration f = path.elements(); f.hasMoreElements(); )
{
match = cl.getExactElement( (String) f.nextElement() );
//ic.send( "DEBUG TODO new2 match is " + match );
if( match instanceof CommandContainer )
{
if( f.hasMoreElements() )
{
// set up the loop to repeat
cl = ((CommandContainer)match).getCommandList();
if( cl == null )
continue scan; // no more from this modal
}
else
break; // just an optimisation, not reqd.
}
else
continue scan; // not enough in this modal
}
}
Object[] o = new Object[2];
o[0] = match;
o[1] = top;
// if we get here, we have a matching CommandContainer
result.addElement( o );
}
if( path != null )
path.setSize( 0 );
return( result.elements() );
}
private transient int commandLoopCount = 0;
/**
* This routine kind of doubles up with CategoryCommand,
* but searches many more places than CategoryCommand
* does. Searches the current context + others
*/
public void command( String fullLine, InteractiveConnection ic, boolean query )
{
if( commandLoopCount > 5 )
{
ic.sendError( "Too many command loops." );
return;
}
commandLoopCount++;
StringTokenizer args = new StringTokenizer( fullLine.trim() );
Commandable match = null;
String command;
CommandList cl = null;
Commandable last = null;
Commandable lastUnique = null;
Command.Match lastcm = null;
StringTokenizer argsFor = null;
int highest_loop_count = -1;
if( !args.hasMoreTokens() )
return;
command = args.nextToken(); // the first word
// attempt to find a matching command:
scan: for( Enumeration e = getCommandLists(); e.hasMoreElements(); )
{
try
{
last = match;
match = (Commandable) ((CommandList) e.nextElement()).getExactElement( command );
if( last != match )
lastUnique = last;
//ic.send( "set last to " + last + ", new match is " + match );
}
catch( ClassCastException t )
{
Log.error( "during command matching", t );
match = null;
}
if( match != null )
{
StringTokenizer args_backup = (StringTokenizer) args.clone();
if( match.recloneArgs() )
{
// take a copy of the arguments
// now in case we need to repass
// them to the command if it doesn't
// match later.
argsFor = (StringTokenizer) args.clone();
}
else
argsFor = null;
int loop_count = 0;
do
{
if( highest_loop_count < loop_count )
{
// this is the furthest we've matched
// this command - get an error string
// in case we don't find a good match
Command.Match cm = match.getFinalMatch( this, args );
lastcm = cm;
if( cm.match != match )
lastUnique = match;
last = match;
match = cm.match;
//ic.send( "set 2 last to " + lastUnique + ", new match is " + match );
highest_loop_count = loop_count;
}
else
{
last = match;
match = match.getMatch( this, args );
if( last != match )
lastUnique = last;
//ic.send( "set 3 last to " + last + ", new match is " + match );
}
if( match == last )
{
if( argsFor != null )
args = argsFor;
break scan;
}
else if( match == null )
{
args = args_backup;
continue scan;
}
else
{
if( match.recloneArgs() )
{
// take a copy of the arguments
// now in case we need to repass
// them to the command if it doesn't
// match later.
argsFor = (StringTokenizer) args.clone();
}
else
argsFor = null;
}
} while( loop_count++ < 5 );
// I don't do infinite loops on general
// principle in code like this. This
// will limit the damage of a circular
// routine.
throw new LimitExceededException( "too many modal commands" );
}
}
// check the exits in the players current room
if( match == null && location != null && lastcm == null )
{
Object o = location.getElement( command );
if( o instanceof Exit )
{
Go.useExit( this, (Exit) o, ic, args, null );
return;
}
}
if( match == null && lastcm != null && lastcm.lastchance != null )
{
if( lastcm.lastchance.recloneArgs() )
{
last = match;
match = lastcm.lastchance;
if( last != match )
lastUnique = last;
//ic.send( "set 4 last to " + last + ", new match is " + match );
if( argsFor != null )
args = argsFor;
}
}
if( match != null )
{
CategoryCommand caller;
try
{
caller = (CategoryCommand) lastUnique;
}
catch( ClassCastException t )
{
caller = null;
}
if( match.isDisabled() )
{
ic.sendFailure( "This command has been temporarily deactivated" );
return;
}
if( query )
{
if( caller != null )
ic.sendFeedback( caller.getWhichId() + " -> " + match.getWhichId() );
else
ic.sendFeedback( match.getWhichId() );
return;
}
try
{
if( caller != null )
caller.runCommand( match, this, args, fullLine, ic, null );
else
match.run( this, args, fullLine, caller, ic, null );
}
catch( NotEnoughParametersException t )
{
// do nothing other than signal an error
// state (ie, return code != 0, if we
// were in unix. we're not, so nothing)
}
catch( Exception t )
{
if( connected() )
{
if( t instanceof UserOutputException )
((UserOutputException)t).send( ic );
else
{
ic.sendError( "Error: " + t.toString() );
if( expert )
ic.printStackTrace( t );
else
t.printStackTrace();
}
}
}
catch( Error t )
{
ic.sendError( t.toString() );
if( expert )
ic.printStackTrace( t );
Log.error( t );
throw t;
}
}
else
{
if( lastcm != null )
ic.sendError( lastcm.getErrorString() );
else
ic.sendError( "Cannot find command '" + command + "'" );
}
}
/** true if the player is connected */
public boolean connected()
{
return( ic != null );
}
public InteractiveConnection getConnection()
{
if( connected() )
return( ic );
else
throw new PlayerNotConnectedException();
}
/** true if the player has a location */
public boolean located()
{
return( location != null );
}
/**
* Called to connect a player to an interactive connection.
*
* <ul>
* <li>splashes everyone on the program, letting them know this person
* has connected.
* <li>tells the Key object to link this player into the online players
* <li>locks this players soul into memory
* <li>updates the players statistics (tells the loginStats to begin
* <li>tells the interactive connection which player to unidle (this) when
* a command is entered.
* </ul>
*/
public synchronized void connectTo( InteractiveConnection connection ) throws NonUniqueKeyException,BadKeyException
{
// safety. should almost certainly have the authenticate here
permissionList.check( modifyAction );
if( connected() )
{
throw new UnexpectedResult( "already connected" );
}
Key.instance().linkPlayer( this ); // do this first - this is the one
// that will throw the exceptions
ic = connection;
// this is the bit that restored their
// terminal parameters to the overridden ones
if( ic instanceof TelnetIC )
{
TelnetIC tic = (TelnetIC) ic;
if( forcedTerminal != null )
tic.forceTerminal( forcedTerminal );
tic.setCanPage( canPage );
tic.setWordwrap( wordwrap );
}
{
// only if they've connected before
if( lastConnectFrom.length() > 0 && loginStats.lastConnection != null )
{
ic.send( "\n\nYou last connected at " + loginStats.lastConnection.toString( this ) + ",\nfrom: " + lastConnectFrom );
}
}
// update the onSince time to now
lastConnectFrom = ic.getFullSiteName();
loginStats.startConnection();
// begin un-idling the player
ic.startUnIdling( this );
if( latentCacheHook == null )
{
latentCacheHook = new LatentlyCached()
{
// don't you dare add anything to this routine to stop
// certain players idling out: it needs to be as small
// and fast as possible. Put it in 'resetModify'
// if you must.
public final boolean modified()
{
return( idleTicks > 0 );
}
public void resetModify()
{
idleTicks--;
// we used to have an idle warning here, but that
// isn't a good idea (people use trigger macros on
// it. Increased possible 'idle' time to one hour
// to compensate for the lack.
//if( idleTicks == 1 && connected() )
// ic.sendError( "\n\n" );
//
//if( I don't want to idle out as often )
// make idleTicks bigger, and remember not to do it
// every single time (idle everyone out, eventually)
}
public synchronized void deallocate()
{
if( connected() )
{
ic.sendFeedback( "\n\n\nYou have been idle for too long, disconnecting." );
disconnect();
}
}
};
}
Key.getLatencyCache().addToCache( latentCacheHook );
Thread.yield();
// exile: when they reconnect they are no longer idle
isIdle = false;
}
public final void setAge( int na )
{
permissionList.check( modifyAction );
age = na;
}
public final void setAka( String na )
{
permissionList.check( modifyAction );
aka = na;
}
public final void setBlockMsg( String na )
{
permissionList.check( modifyAction );
blockMsg = na;
}
public final void setCitizen( boolean ba )
{
permissionList.check( modifyAction );
citizen = ba;
}
public final void setBlockingMsg( String na )
{
permissionList.check( modifyAction );
blockingMsg = na;
}
private transient boolean disconnecting = false;
/**
* Called to terminate a players connection.
*
* <ul>
* <li>stops time recording
* <li>takes the player out of the room that they're in
* <li>removes the player from the online list of players in Key
* <li>splashes everyone that the player is disconnecting
* <li>closese the interactive connection
* <li>stops the thread
* </ul>
*/
public synchronized void disconnect()
{
permissionList.check( modifyAction );
if( disconnecting )
return;
if( connected() )
{
try
{
ic.flush();
}
catch( Exception e )
{
}
try
{
disconnecting = true;
InteractiveConnection dcd = ic;
Key.getLatencyCache().removeFromCache( latentCacheHook );
florins += (int) loginStats.getTimeSinceConnection().getHours();
loginStats.endConnection();
if( Key.isRunning() )
{
{
String message = location.getLogoutMessage();
putCode( originatorCode, getName() );
message = Grammar.substitute( message, getCodes() );
(new key.effect.Leave( this, location, message )).cause();
}
(new key.effect.Logout( this, getName() + " has disconnected^@" + logoutMsg + "^$" )).cause();
}
int rss = reverseScapes.size();
while( rss > 0 )
{
Scape s = (Scape) reverseScapes.elementAt( 0 );
try
{
s.unlinkPlayer( this );
}
catch( NonUniqueKeyException t )
{
Log.error( "while removing ourselves from scapes", t );
reverseScapes.removeElement( s );
}
catch( BadKeyException t )
{
Log.error( "while removing ourselves from scapes", t );
reverseScapes.removeElement( s );
}
// over engineering to prevent an infinite loop here
int nrs = reverseScapes.size();
if( nrs == rss )
{
Log.error( "did not seem to remove scape as intended: please analyse algorithm" );
reverseScapes.removeElement( s );
nrs = reverseScapes.size();
if( nrs == rss )
{
Log.error( "as above, but even more curious" );
break;
}
}
rss = nrs;
}
try
{
// sync it only if we're a temporary atom atm.
if( Registry.instance.getStorageTypeIndex( index, timestamp ) == Registry.STORAGE_TEMPORARY )
sync();
}
catch( IOException e )
{
dcd.send( e.toString() + " occured while trying to save your character" );
Log.error( "while saving player " + getName(), e );
}
finally
{
dcd.close();
}
}
catch( Exception t )
{
Log.error( "during Player::disconnect", t );
}
finally
{
ic.stopUnIdling();
ic.close();
ic = null;
location = null;
idleTicks = 0;
if( !willSync() )
dispose();
disconnecting = false;
}
}
else
throw new UnexpectedResult( "disconnecting a non-connected player" );
}
void setSaved()
{
saved = true;
}
public boolean willSync()
{
return( hasPassword() && canSave );
}
public boolean hasPassword()
{
return( password.isSet() );
}
public boolean getCanSave()
{
return( canSave );
}
public boolean isStaff()
{
return( staff );
}
public String getRank()
{
if( willSync() )
{
//if( beyond )
//return( "director" );
//else if( isStaff() )
//return( "staff" );
//else
if( isCitizen() )
return( "citizen" );
else
return( "resident" );
}
else
return( "visitor" );
}
void clearTransient()
{
super.clearTransient();
if( connected() )
disconnect();
}
public void sync() throws IOException
{
if( canSave )
{
if( hasPassword() )
{
setSaved();
super.sync();
if( connected() )
ic.send( "Character saved." );
}
else
{
if( connected() )
ic.send( "You have no password, so your character has not been saved." );
}
}
else
{
if( connected() )
ic.send( "You have not been given permission to save" );
}
}
public void moveTo( Room newLocation, Effect leave, Effect enter )
{
if( !connected() )
throw new PlayerNotConnectedException();
permissionList.check( summonAction );
if( leave != null )
leave.cause();
moveTo( newLocation );
enter.cause();
}
public void roomLook()
{
if( ic == null )
throw new PlayerNotConnectedException();
//permissionList.check( modifyAction );
Room l = getLocation();
if( brief() )
{
ic.send( new TextParagraph( (String) getLocation().called ), false );
ic.send( l.who( this ), false );
}
else
ic.send( l.aspect( this ), false );
ic.blankLine();
ic.flush();
}
void moveTo( Room newLocation )
{
try
{
if( location != null )
location.unlinkPlayer( this );
location = newLocation;
realm = newLocation.getRealm().getThis();
if( location != null )
{
location.linkPlayer( this );
// if they're moving into a public
// room, store that they're there,
// so we can return them to it when
// they type 'leave', or when they
// reconnect.
if( !(location.getParent() instanceof Player) )
lastPublicRoomLocation = location.getThis();
}
}
catch( BadKeyException e )
{
Log.error( e );
}
catch( NonUniqueKeyException e )
{
Log.error( e );
}
}
public boolean getShowFlags()
{
return( showFlags );
}
public final String himHer()
{
return( gender.himHer() );
}
public final String HisHer()
{
return( gender.HisHer() );
}
public final String hisHer()
{
return( gender.hisHer() );
}
public final String HeShe()
{
return( gender.HeShe() );
}
public final String heShe()
{
return( gender.heShe() );
}
public final String maleFemale()
{
return( gender.maleFemale() );
}
public final void setGender( Gender ng )
{
gender = ng;
}
public final Gender getGender()
{
return( gender );
}
public synchronized void enrolIntoClan( Clan newClan )
{
Clan old = getClan();
// this will be a problem if we ever do
// untrusted coding - they could simply
// enrolIntoClan( null ) & enrolIntoClan( newC )
// to get around the whole thing.
if( old != null && newClan != null )
throw new AccessViolationException( this, getName() + " is already in clan " + old.getName() + ", and cannot be enrolled into a new clan until they leave." );
else
{
if( newClan == null )
{
clan = Reference.EMPTY;
// check to ensure the old clan isn't still
// on the inform list
try
{
inform.removeClanInform( old );
}
catch( BadKeyException e )
{
}
catch( NonUniqueKeyException e )
{
}
}
else
clan = newClan.getThis();
}
}
/**
* This is a special accessor that allows limited access
* to the supplied players florin count: only to increase
* it.
*
* @to the player to give the florins to
* @amount the amount of florins to transfer
*/
public void transferFlorins( Player to, int amount )
{
if( amount > florins )
throw new InvalidArgumentException( getName() + " doesn't have that many florins" );
if( amount < 1 )
throw new InvalidArgumentException( "Unable to transfer negative amounts of florins" );
permissionList.check( modifyAction );
to.florins += amount;
florins -= amount;
// if they are connected tell them about the good deed.
if( to.connected() )
to.send( getName() + " gives you " + amount + " silver florins." );
}
public int getFlorins()
{
return( florins );
}
public boolean canMakeFriend( Player p )
{
// this one just does a straight check (doesn't check groups, ranks,
// or whatever).
//return( permissionList.check( p.getThis(), friendAction ) );
return( permissionList.permissionCheck( friendAction, false, false ) );
}
public String getFullName()
{
return( prefix + getName() );
}
/**
* I hereby declare each player to be
* a soverign state, owned by no
* other. ;p~
*
* taken out only for the reason that it isn't
* strictly necessary and I wanted to make isOwner final
* subtle, 05Nov98
*
public boolean isOwner( Atom potential )
{
if( potential == this )
return( true );
else
return( false );
}
*/
/*
public boolean isTellBlockingPlayer( Reference p )
{
return( !permissionList.check( originator.getThis(), tellAction ) );
}
*/
public void splash( Effect e, SuppressionList s )
{
super.splash( e, s );
QualifierList.Entry q = null;
boolean bounce = false;
char preChar = ' ';
String message = e.getMessage( this );
Player originator = e.getOriginator();
if( e instanceof Communication )
{
if( originator != this || e instanceof Broadcast )
{
// if not allowed, splash a message back and finish
if( !permissionList.check( originator.getThis(), tellAction ) )
{
if( isBeyond() )
{
if( s != null )
s.add( this, SuppressionList.SPECIFIC, "(beyond used to send anyway)" );
}
else
{
if( s != null )
s.add( this, SuppressionList.SPECIFIC, blockMsg );
return;
}
}
q = qualifierList.getEntryFor( Type.typeOf( e.getSplasher() ) );
if( q == null )
q = qualifierList.getEntryFor( Type.typeOf( e ) );
if( showFlags && originator != this )
{
if( e instanceof Directed )
preChar = SHOWCHARS[ SHOWFLAG_DIRECTED ];
else if( e instanceof Broadcast )
{
Atom rs = e.getSplasher();
if( rs instanceof Friends )
preChar = SHOWCHARS[ SHOWFLAG_FRIENDS ];
else if( rs instanceof Room )
preChar = SHOWCHARS[ SHOWFLAG_ROOM ];
else if( e instanceof Shout )
preChar = SHOWCHARS[ SHOWFLAG_SHOUTS ];
}
}
bounce = true; // tell them if a communication message fails
}
}
else if( e instanceof Movement )
{
if( showFlags && originator != this )
{
if( e instanceof Enter )
preChar = SHOWCHARS[ SHOWFLAG_ENTER ];
else if( e instanceof Leave )
preChar = SHOWCHARS[ SHOWFLAG_LEAVE ];
}
//q = qualifierList.getEntryFor( Type.typeOf( e ) );
q = qualifierList.getEntryFor( Type.MOVEMENT );
}
else if( e instanceof key.effect.Connection )
{
StringBuffer foundBy = new StringBuffer( message );
Reference pRef = originator.getThis();
boolean triggered = false;
for( Enumeration en = inform.referenceElements(); en.hasMoreElements(); )
{
Reference a = (Reference) en.nextElement();
// this is an optimisation - (this code gets called a lot)
// if the Reference isn't loaded, there
// isn't much chance that this list entry
// counts. if you take this out, be sure
// to put it back in on the final 'else'
// clause, since it is required there.
if( a.isLoaded() )
{
if( pRef.equals( a ) )
{
foundBy.append( " [inform]" );
triggered = true;
}
else if( clan.equals( a ) )
{
foundBy.append( " [clan]" );
triggered = true;
}
else // if( a.isLoaded() ) <taken out, see above>
{
Atom j = a.get();
//if( j instanceof Scape && ((Scape)j).containsPlayer( originator ) )
if( j instanceof Scape )
{
if( ((Scape)j).containsPlayer( originator ) )
{
foundBy.append( " [" );
foundBy.append( j.getName() );
foundBy.append( "]" );
triggered = true;
}
}
}
}
}
if( !triggered )
return;
message = foundBy.toString();
if( showFlags && originator != this )
{
if( e instanceof Login )
preChar = SHOWCHARS[ SHOWFLAG_LOGIN ];
else if( e instanceof Logout )
preChar = SHOWCHARS[ SHOWFLAG_LOGOUT ];
}
q = qualifierList.getEntryFor( Type.CONNECTION );
}
else if( e instanceof Blocking )
{
q = qualifierList.getEntryFor( Type.BLOCKING );
if( showFlags && originator != this )
preChar = SHOWCHARS[ SHOWFLAG_BLOCKING ];
if( q == null && e instanceof Block )
{
Scape sc = ((Block)e).blockOf();
q = qualifierList.getEntryFor( Type.typeOf( sc ) );
}
}
else if( e instanceof Global )
{
ic.send( "\n\n" );
ic.send( new HeadingParagraph( message ) );
ic.send( "\n\n" );
ic.flush();
return;
}
else
ic.send( " --[ Unknown splash type recvd ]--" );
if( q != null )
{
char c = q.get();
if( c == Qualifiers.UNKNOWN_CODE )
sendSplash( preChar, message, s, e );
else if( c == Qualifiers.SUPPRESSION_CODE )
{
//Log.debug( this, "suppressing message, bounce is " + bounce + ", sl is " + s.toString() );
// beyond goes through for directed events
if( !(e.getOriginator().isBeyond() && e instanceof Directed ) )
{
// this has been suppressed - splash a message back
if( bounce && s != null )
s.add( this, SuppressionList.GENERAL, "(beyond used to deliver message anyway)" );
return;
}
else
{
// this has been suppressed - splash a message back
if( bounce && s != null )
s.add( this, SuppressionList.GENERAL, getBlockingMsg() );
c = q.getMark();
if( c != Qualifiers.UNKNOWN_CODE )
sendSplash( preChar, "^" + q.getMark() + message, s, e );
else
sendSplash( preChar, message, s, e );
}
}
else
sendSplash( preChar, "^" + c + message, s, e );
}
else
sendSplash( preChar, message, s, e );
ic.flushIfWaiting();
}
public void addHistoryLine( String s )
{
history.addElement( s );
}
public void beep()
{
if( connected() && !quiet )
ic.beep();
}
private final void sendSplash( char preChar, String message, SuppressionList sl, Effect e )
{
e.sending( message, this );
if( preChar == ' ' )
ic.send( message );
else
ic.send( preChar, message );
if( sl != null )
{
if( isIdle() || isAfk() )
sl.add( this, SuppressionList.IDLING, idleMsg );
}
}
public void sendSystem( String message )
{
ic.send( message );
ic.flush();
}
/**
* This function is used to output direct,
* *successful* feedback of a players command.
* It is important that this feedback is normal
* and expected by the player, since it generally
* won't be hilited in any way. Please try
* to restrict this output to a single line.
* <p>
* this command does start a new line
* <p>
* Examples:
* <p>
* You tell name 'hi' <br>
*
* @param message the text to be displayed
*/
public void sendFeedback( String message )
{
ic.send( message );
ic.flush();
}
/**
* This function is used when the user makes some
* sort of error in typing - *not* if a command
* can't be executed because of a failure. So,
* Command not found would go here, but 'Not enough
* privs to nuke snapper' wouldn't.
* <p>
* this command does start a new line
*/
public void sendError( String message )
{
ic.send( message );
ic.flush();
}
public void sendFailure( String message )
{
ic.sendFailure( message );
ic.flush();
}
/**
* Meant to be used to send part of what would
* normally be a paragraph.
*/
public void send( String message )
{
ic.send( message );
ic.flush();
}
final void maybeClearBeyond()
{
if( !Key.instance().isRunning() )
beyond = false;
else
throw new UnexpectedResult( "trying to clear beyond while running" );
}
final void maybeSetBeyond()
{
if( !Key.instance().isRunning() )
{
beyond = true;
expert = true;
}
else
throw new UnexpectedResult( "trying to set beyond while running" );
}
/**
* Returns the number of rooms the player has
*/
public final int countRooms()
{
int i = 0;
for( Enumeration e = elements(); e.hasMoreElements(); )
{
if( e.nextElement() instanceof Room )
i++;
}
return( i );
}
//--- commands ---//
/**
* A shortcut, since matching it from the
* string could be prohibitively slow
*/
public final CommandList getCommandList()
{
try
{
return( (CommandList) commands.get() );
}
catch( OutOfDateReferenceException e )
{
commands = Reference.EMPTY;
return( null );
}
catch( ClassCastException e )
{
Log.error( "somebody set " + getId() + ".commands wrong (reset)", e );
commands = Reference.EMPTY;
return( null );
}
}
public final CommandList getSpecialCommandList()
{
try
{
return( (CommandList) specialCommands.get() );
}
catch( OutOfDateReferenceException e )
{
specialCommands = Reference.EMPTY;
return( null );
}
catch( NullPointerException e )
{
Log.error( "during getSpecialCommandList: this is once-only code for converting pfiles", e );
specialCommands = Reference.EMPTY;
return( null );
}
catch( ClassCastException e )
{
Log.error( "somebody set " + getId() + ".specialcommands wrong (reset)", e );
commands = Reference.EMPTY;
return( null );
}
}
//--- actions ---//
protected static StringKeyCollection staticActions;
public static Action tellAction;
public static Action findAction;
public static Action friendAction;
public static Action summonAction;
public static Action seePrivateInfoAction;
static
{
staticActions = new StringKeyCollection();
findAction = newAction( Player.class, staticActions, "find", false, false );
friendAction = newAction( Player.class, staticActions, "friend", false, false );
seePrivateInfoAction = newAction( Player.class, staticActions, "seePrivateInfo", false, false );
summonAction = newAction( Player.class, staticActions, "summon", false, false );
tellAction = newAction( Player.class, staticActions, "tell", false, false );
}
public Enumeration getActions()
{
return( new MultiEnumeration( staticActions.elements(), super.getActions() ) );
}
public boolean containsAction( Action a )
{
return( staticActions.contains( a ) || super.containsAction( a ) );
}
/**
* Returns the action corresponding to the
* supplied name. This routine will need to
* be overriden by sub-classes using actions.
*/
public Action getAction( String name )
{
Action a = (Action) staticActions.get( name );
if( a == null )
return( super.getAction( name ) );
else
return( a );
}
private static int totalPlayers;
public static int getTotalPlayers()
{
return( totalPlayers );
}
protected void finalize() throws Throwable
{
super.finalize();
totalPlayers--;
}
boolean canSwap()
{
return( !(connected() || (connecting > 0)) );
}
// property routines
public boolean isBanished()
{
DateTime now = new DateTime();
if( banishedUntil == null )
return( false );
if( now.getTime() > banishedUntil.getTime() )
{
banishedUntil = null;
banishType = "";
return( false );
}
return( true );
}
public boolean checkNewMail()
{
return( newmail );
}
public void clearNewMail()
{
newmail = false;
}
public void sendExamineScreen( InteractiveConnection ic )
{
boolean seeThrough = permissionList.permissionCheck( seePrivateInfoAction, false, true );
Player p = getCurrent();
ic.sendLine();
ic.send( getTitledName() );
// show the description
{
if( description != null )
{
ic.sendLine();
ic.send( description );
}
}
ic.sendLine();
if( connected() )
{
ic.send( getFullName() + " has been logged in for " + loginStats.getTimeSinceConnection() );
InteractiveConnection pic = getConnection();
if( pic instanceof SocketIC )
ic.send( getName() + " is connected from: " + ((SocketIC)pic).getFullSiteName() );
}
else
{
DateTime dateTime = (DateTime) loginStats.getLastConnection();
if( dateTime == null )
ic.send( getFullName() + " has never logged in." );
else
ic.send( getFullName() + " was last seen at " + dateTime.toString( p ) );
}
ic.sendLine();
// output ranks.
ic.send( Grammar.enumerate( new FilteredEnumeration( ranks(),
new key.util.FilteredEnumeration.Filter()
{
public boolean isValid( Object element, Enumeration enum )
{
return( element instanceof Rank);
}
public Object replace( Object element, Enumeration enum )
{
return( ((Rank)element).getContainedId() );
}
}) ) );
/*
for( Enumeration e = ranks(); e.hasMoreElements(); )
{
Rank r = (Rank) e.nextElement();
}
*/
ic.sendLine();
}
private static final Paragraph planHeading = new HeadingParagraph( "plan", HeadingParagraph.RIGHT );
public void sendFingerScreen( InteractiveConnection ic )
{
boolean seeThrough = permissionList.permissionCheck( seePrivateInfoAction, false, true );
Player p = getCurrent();
ic.sendLine();
ic.send( getTitledName() );
ic.sendLine();
if( connected() )
{
ic.send( getFullName() + " has been logged in for " + loginStats.getTimeSinceConnection() );
InteractiveConnection pic = getConnection();
if( pic instanceof SocketIC )
ic.send( getName() + " is connected from: " + ((SocketIC)pic).getFullSiteName() );
}
else
{
DateTime dateTime = (DateTime) loginStats.getLastConnection();
if( dateTime == null )
ic.send( getFullName() + " has never logged in." );
else
ic.send( getFullName() + " was last seen at " + dateTime.toString( p ) );
}
if( !hideTime )
{
ic.send( gender.HisHer() + " total login time is " + loginStats.getTotalConnectionTime() );
if( oldLoginTime != null && oldLoginTime.length() > 0 )
{
ic.send( gender.HisHer() + " pre-eclipse login time was " + oldLoginTime );
}
}
if( age != 0 )
ic.send( gender.HeShe() + " is " + age + " year" + (age==1?"":"s") + " old" );
{
if( aka.length() > 0 )
ic.sendFeedback( "Also known as: " + aka );
}
// email addresses
{
if( !privateEmail || seeThrough )
{
String ea = getEmailAddress();
if( ea != null )
{
ic.sendFeedback( HisHer() + " email address is: " + ea );
}
}
}
{
String wp = homepage.get();
if( wp != null )
ic.sendFeedback( HisHer() + " web page is: " + wp );
}
Paragraph plan = getPlan();
// show the plan (exile 28jul97)
if( plan != null && !plan.isEmpty() )
{
ic.send( planHeading );
ic.send( plan );
}
ic.sendLine();
}
// -- cookies aren't saved, but are kind of cool
// -- for whatever you want to use them for. Generally
// -- saving state during seperate commands is a good
// -- thing.
private transient Hashtable cookies;
public void setCookie( Object key, Object value )
{
if( cookies == null )
cookies = new Hashtable();
cookies.put( key, value );
}
public void removeCookie( Object key )
{
if( cookies != null )
cookies.remove( key );
}
public Object getCookie( Object key )
{
if( cookies == null )
return( null );
else
return( cookies.get( key ) );
}
private transient long[] spamThrottle;
private transient int[] spamViolations;
private static final int[] THROTTLE_THRESHOLD = { 0, 3 };
private static final int[] THROTTLE_PENALTY = { 1000, 2000 };
public static final int NUMBER_OF_THROTTLES = 2;
/**
* Called just before a broadcast event. It prevents
* spamming broadcast channels. The type is the
* 'type' of the channel. Each channel type has different
* throttle rates (the more public, generally, the less
* tolerant we are of 'spam' type communication.
*/
public final void aboutToBroadcast( int type )
{
long a = System.currentTimeMillis() / 1000;
if( (a - THROTTLE_THRESHOLD[type]) <= spamThrottle[type] )
{
try
{
spamViolations[type]++;
if( spamViolations[type] > 13 )
{
for( int i = 0; i < NUMBER_OF_THROTTLES; i++ )
spamViolations[type] = 0;
ic.send( "^hYou have been broadcasting too quickly.^-" );
ic.send( "^hPlease talk a little slower and be more lag.friendly in the future.^-" );
disconnect();
}
else
{
if( spamViolations[type] > 3 )
{
Thread.sleep( THROTTLE_PENALTY[type] );
spamThrottle[type]++;
}
else if( spamViolations[type] > 10 )
{
throw new LimitExceededException( "You are sending communication too quickly. Please be lag.friendly and wait a few seconds between says, shouts and tells." );
}
}
}
catch( InterruptedException e )
{
}
}
else
{
if( a - spamThrottle[type] > 10 )
spamViolations[type] = 0;
else
{
if( spamViolations[type] > 0 )
spamViolations[type]--;
}
spamThrottle[type] = a;
}
}
public static Player getCurrent()
{
Thread t = Thread.currentThread();
if( t instanceof Animated )
{
Animate a = ((Animated)t).is();
if( a instanceof InteractiveConnection )
{
return( ((InteractiveConnection)a).getPlayer() );
}
}
else
{
// sometimes, from a system script, currentThread won't be
// animated. In this case, however, we're always going
// to be the only player online, and Key isn't going to
// be running. In that case:
Key key = Key.instance();
if( key != null && !key.isRunning() && key.numberPlayers() == 1 )
{
for( Enumeration e = key.players(); e.hasMoreElements(); )
{
return( (Player) e.nextElement() );
}
}
}
return( null );
}
public Object retrieveField( java.lang.reflect.Field f )
throws IllegalAccessException
{
if( f.getDeclaringClass() == Player.class )
return( f.get( this ) );
else
return( super.retrieveField( f ) );
}
}