/*
** j###t ########## #### ####
** j###t ########## #### ####
** j###T "###L J###"
** ######P' ########## #########
** ######k, ########## T######T
** ####~###L ####
** #### q###L ########## .#####
** #### \###L ########## #####"
*/
package key;
import key.primitive.Gender;
import java.io.*;
import java.util.Date;
import key.config.*;
import key.util.Trie;
/**
* Is the main instance of a player on the program. The
* authenticate() routine has been changed in player to
* attempt to stop a player being swapped out while they
* connect, but no guarantee's... we should probably
* be using tags in here somewhere.
*
* It is the responsibility of this class to send the opening
* logo, and retrieve a player from the name entered. (Authenticating
* the player or creating a suitable player as necessary). No further
* action need be taken.
*/
public class ConnectingPlayer
implements LatentlyCached, Interactive
{
InteractiveConnection ic;
Player p = null;
Player created = null;
public ConnectingPlayer()
{
}
public void run( InteractiveConnection connection )
{
Key key = Key.instance();
Container shortcuts = (Container) Key.shortcuts();
ConnectionConfiguration connectConfig = (ConnectionConfiguration) key.getConfig().getElement( "connection" );
ic = connection;
outputLogo();
Player p = null;
try
{
String name;
int tries = 0;
Key.getLatencyCache().addToCache( this );
// loop this continually until they
// managed to get a name correct
do
{
name = getPlayersName( ic );
// the player probably typed 'quit', this
// will clean it up.
if( name == null )
break;
ic.blankLine();
ic.blankLine();
p = getPlayerFromName( ic, name );
} while( p == null && ic.isConnected() && ++tries < 3 );
if( p == null || !ic.isConnected() || tries >= 3 )
{
if( p != null )
{
p.endConnect();
Registry.instance.deleteIfTemporary( p );
p = null;
}
ic.flush();
ic.close();
return;
}
else
{
connectToProgram( ic, p );
// kick off the player (they'll look at the motd
// & link themselves in when they're ready)
ic.interactWith( p );
p.endConnect();
p = null;
}
}
catch( java.io.IOException e )
{
Log.debug( this, "disconnect from login" );
ic.close();
if( p != null )
{
p.endConnect();
Registry.instance.deleteIfTemporary( p );
p = null;
}
}
catch( NetworkException e )
{
if( ic instanceof SocketIC )
Log.debug( this, "disconnect from login [" + ((SocketIC)ic).getAddress() + "]" );
else
Log.debug( this, "disconnect from login" );
ic.close();
if( p != null )
{
p.endConnect();
Registry.instance.deleteIfTemporary( p );
p = null;
}
}
catch( Exception e )
{
Log.debug( this, e.toString() + " at login prompt" );
ic.send( "\n\nWe apologise, but an error has occurred attempting to connect you. Please mail forest@realm.progsoc.uts.edu.au if you are continually having problems. Detail: " + e.toString() );
ic.flush();
ic.close();
if( p != null )
{
p.endConnect();
Registry.instance.deleteIfTemporary( p );
p = null;
}
}
finally
{
Key.getLatencyCache().removeFromCache( this );
}
}
/**
* If a players name is not matched to a character on
* the program, this routine leads them through the
* operations in creating a character of their own.
*
* @return null if an error or cancellation occurs
*/
protected Player connectGuest( InteractiveConnection ic, String name ) throws IOException
{
Container online = Key.instance().getOnline();
Container shortcuts = (Container) Key.shortcuts();
ConnectionConfiguration connectConfig = (ConnectionConfiguration) Key.instance().getConfig().getElement( "connection" );
Container players = Key.instance().getResidents();
// check to ensure there isn't a newbie ban on this
// site.
if( !ic.newbiesAllowed() )
{
Screen newbieban = (Screen) online.getElement( "newbieban" );
if( newbieban == null )
ic.sendFeedback( "New players are not currently permitted to connect from this site" );
else
ic.send( newbieban.text, false );
ic.flush();
ic.close();
return null;
}
// check that the name is not in the reserved list
// (names that aren't permitted to log in)
StringSet ss = (StringSet) online.getElement( "reservedNames" );
if( ss != null && ss.get( name ) != null )
{
ic.send( "'" + name + "' means something else here, please choose another." );
ic.blankLine();
ic.blankLine();
return( null );
}
// need to create a new player & link it in
ic.send( " '" + name + "' not found in player database...\n" );
if( guestConfirmName( ic, name ) )
{
Player created = null;
try
{
created = (Player) Factory.makeAtom( Player.class, name );
created.setKey( name );
created.beginConnect();
players.add( created );
Rank nr = Key.instance().getInitialRank();
if( nr != null )
nr.add( created );
Atom a = (Atom) shortcuts.getExactElement( name );
if( a != null )
{
Paragraph disclaimer = (Paragraph) connectConfig.getProperty( "disclaimer" );
if( disclaimer != null && !disclaimer.isEmpty() )
{
ic.send( disclaimer, false );
if( !Grammar.getYesNo( "Do you accept these conditions? ", true, ic ) )
{
ic.flush();
ic.close();
if( created != null )
{
players.remove( created );
Registry.instance.deleteIfTemporary( created );
created.endConnect();
return null;
}
}
}
Paragraph gender = (Paragraph) connectConfig.getProperty( "genderRequest" );
if( gender != null && !gender.isEmpty() )
{
ic.blankLine();
ic.send( gender, false );
ic.blankLine();
boolean genderOk;
do
{
genderOk = true;
String in = ic.input( "Enter 'm' or 'f': " );
if( in.equalsIgnoreCase( "m" ) )
created.setGender( Gender.MALE_GENDER );
else if( in.equalsIgnoreCase( "f" ) )
created.setGender( Gender.FEMALE_GENDER );
else
{
genderOk = false;
if( in.startsWith( "\'" ) )
ic.send( "\nDo not type the quotes, just the letter m or f.\n" );
else
ic.send( "\nPlease enter, literally: 'm' if you are male, or 'f' if you are female.\n" );
}
} while( !genderOk );
}
Paragraph newbieScreen = (Paragraph) connectConfig.getProperty( "newbieA" );
if( newbieScreen != null && !newbieScreen.isEmpty() )
{
ic.send( newbieScreen, false );
Grammar.getReturn( ic );
}
newbieScreen = (Paragraph) connectConfig.getProperty( "newbieB" );
if( newbieScreen != null && !newbieScreen.isEmpty() )
{
ic.send( newbieScreen, false );
Grammar.getReturn( ic );
}
Log.log( "newbie", name + " from " + ((SocketIC)ic).getAddress() );
return( created );
}
else
ic.send( "Cannot create a player with this name" );
}
catch( BadKeyException e )
{
ic.send( "Your name must only contain alphabetical characters" );
}
catch( NonUniqueKeyException e )
{
ic.send( "There is already another player using this name" );
}
catch( IOException e )
{
if( created != null )
{
try
{
players.remove( created );
}
catch( NonUniqueKeyException ex )
{
}
catch( BadKeyException ex )
{
}
created.endConnect();
Registry.instance.deleteIfTemporary( created );
created = null;
}
}
}
else
{
ic.blankLine();
ic.blankLine();
ic.send( " Perhaps someone else, then..." );
ic.blankLine();
}
return( null );
}
protected Player connectResident( InteractiveConnection ic, Player p ) throws IOException
{
p.beginConnect();
// now check if the player has been banished
if( p.isBanished() )
{
Screen banished = (Screen) Key.instance().getOnline().getElement( "playerBanished" );
if ( banished == null )
{
ic.sendFeedback( "Your character is currently banished." );
}
else
ic.send( banished.text, false );
p.endConnect();
p = null;
ic.flush();
ic.close();
return null;
}
int tries = 0;
boolean name_okay = false;
try
{
do
{
if( p.authenticate( ic ) )
{
name_okay = true;
}
else
{
name_okay = false;
}
} while( !name_okay && ++tries < 3 );
}
catch( PasswordEntryCancelled except )
{
ic.send( " Perhaps someone else, then..." );
p.endConnect();
p = null;
return( null );
}
if( name_okay )
{
// the endConnect is handled by the
// level above.
//p.endConnect();
// should return a tag instead?
return( p );
}
else
{
ic.flush();
ic.close();
p.endConnect();
p = null;
return( null );
}
}
protected void connectToProgram( InteractiveConnection ic, Player p ) throws IOException
{
// copyright messages
ic.send( Key.copyright );
ic.sendLine();
// the MOTD
Screen motd = (Screen) Key.instance().getOnline().getElement( "motd" );
if( motd != null && !motd.isEmpty() && (p.loginStats.lastConnection == null || motd.isModifiedAfter( p.loginStats.lastConnection ) ) )
{
ic.send( motd.text, false );
Grammar.getReturn( ic );
}
// the clan motd
{
Clan c;
c = p.getClan();
if( c != null )
{
motd = c.motd;
if( motd != null && !motd.isEmpty() && (p.loginStats.lastConnection == null || motd.isModifiedAfter( p.loginStats.lastConnection ) ) )
{
ic.send( motd.text, false );
Grammar.getReturn( ic );
}
}
}
// link this player into the program
try
{
synchronized( p )
{
if( p.connected() )
{
p.getConnection().sendSystem( "Reconnecting..." );
p.disconnect();
}
p.connectTo( ic );
}
}
catch( NonUniqueKeyException e )
{
ic.send( "This player is already on the program. You will need to enter another name. (Internal error)" );
throw new UnexpectedResult( e.toString() );
}
catch( BadKeyException e )
{
ic.send( "Your name must consist of alphabetical characters only. Please try again. (Internal error)" );
throw new UnexpectedResult( e.toString() );
}
}
/**
* Retrieves a player structure from the entered name. Should
* the name not be acceptable for some reason, null is returned.
* Should a serious error occur, the connection will be closed,
* also.
*
* @return null on failure
*/
protected Player getPlayerFromName( InteractiveConnection ic, String name ) throws IOException
{
Object a = Key.shortcuts().getExactElement( name );
if( a == null )
return( connectGuest( ic, name ) );
else
{
if( a instanceof Player )
{
Player p = (Player) a;
if( p.willSync() )
return( connectResident( ic, p ) );
else
ic.send( "That name is in use by another new player at the moment, and can't be used." );
}
else
ic.send( "That name means something else here." );
}
return( null );
}
/**
* Puts the initial logo on the players screen.
*/
protected void outputLogo()
{
Screen logo = (Screen) Key.instance().getOnline().getElement( "logo" );
if( logo != null )
ic.send( logo.text, false );
}
/**
* This function returns the player's name, as entered at
* the 'Please enter your name:' prompt.
*/
protected String getPlayersName( InteractiveConnection ic ) throws IOException
{
boolean name_okay;
String name;
do
{
name_okay = false;
name = ic.input( "Please enter your name: " );
if( name.length() < Player.MIN_NAME )
{
ic.send( "We require that your name is at least " + Player.MIN_NAME + " characters long. You'll need to choose another." );
continue;
}
else if( name.length() > Player.MAX_NAME )
{
ic.send( "We require that your name is less than " + Player.MAX_NAME + " characters long. You'll need to choose another." );
continue;
}
if( !Grammar.isStringCompletelyAlphabetical( name ) )
{
ic.send( "Your name must only contain alphabetical characters" );
continue;
}
if( name.equalsIgnoreCase( "quit" ) )
{
ic.flush();
ic.close();
return null;
}
else if( name.equalsIgnoreCase( "who" ) )
{
ic.blankLine();
ic.blankLine();
key.commands.Who.displayTable( ic, Key.instance().players() );
ic.blankLine();
}
else
name_okay = true;
} while( !name_okay );
return( name );
}
/**
* This function outputs the 'are you sure you want to use this
* name' prompt, when someone starts to create a new player.
*
* @param ic the connection
* @param name the name the player originally typed
* @return true if the person replied 'yes'
*/
protected boolean guestConfirmName( InteractiveConnection ic, String name ) throws IOException
{
ConnectionConfiguration connectConfig = (ConnectionConfiguration) Key.instance().getConfig().getElement( "connection" );
ic.send( (Paragraph) connectConfig.getProperty( "newbieConfirm" ) );
ic.blankLine();
// output the multiple matches screen
Object o;
o = Key.instance().getResidents().getTrieFor( name.substring( 0, 3 ) );
if( (o != null) )
{
if( o instanceof Trie )
ic.send( new TextParagraph( TextParagraph.CENTERALIGNED, ((Trie)o).contents(), 10, 10, 0, 0 ), false );
else if( o instanceof Reference )
ic.send( new TextParagraph( TextParagraph.CENTERALIGNED, ((Reference)o).getName(), 10, 10, 0, 0 ), false );
}
else
ic.send( new TextParagraph( TextParagraph.CENTERALIGNED, "<no close names found>", 10, 10, 0, 0 ), false );
ic.blankLine();
return( Grammar.getYesNo( "Do you still wish to create a new character of the name '" + name + "'? ", false, ic ) );
}
public InteractiveConnection getInteractiveConnection()
{
return( ic );
}
private boolean mod = true;
public boolean modified()
{
return( mod );
}
public void resetModify()
{
mod = false;
}
public synchronized void deallocate()
{
ic.send( "\n\nYou have taken too long to connect." );
ic.flush();
ic.close();
}
}