TeensyMud - 'A ruby mud server.'

Browse repository
back

account.rb

#
# file:: account.rb
# author:: Jon A. Lambert
# version:: 2.9.0
# date:: 03/15/2006
#
# This source code copyright (C) 2005, 2006 by Jon A. Lambert
# All rights reserved.
#
# Released under the terms of the TeensyMUD Public License
# See LICENSE file for additional information.
#
$:.unshift "lib" if !$:.include? "lib"

require 'utility/utility'
require 'utility/log'
require 'utility/publisher'
require 'core/root'

# The Account class handles connection login and passes them to
# character.
class Account < Root
include Publisher
logger 'DEBUG'
property :color, :passwd, :characters
attr_accessor :mode, :echo, :termsize, :terminal, :conn, :character

# Create an Account connection. This is a temporary object that handles
# login for character and gets them connected.
# [+conn+] The session associated with this Account connection.
# [+return+] A handle to the Account object.
def initialize(conn)
super("",nil)
self.passwd = nil
self.color = false
self.characters = []
@conn = conn # Reference to network session (connection)
@mode = :initialize
@echo = false
@termsize = nil
@terminal = nil
@checked = 3 # Login retry counter - on 0 disconnect
@account = nil # used only during sign-in process
@character = nil # reference to the currently played Character.
end

# Receives messages from a Connection being observed and handles login
# state.
#
# [+msg+] The message string
#
# This supports the following:
# [:disconnected] - This symbol from the server informs us that the
# Connection has disconnected.
# [:initdone] - This symbol from the server indicates that the Connection
# is done setting up and done negotiating an initial state.
# It triggers us to start sending output and parsing input.
# [:termsize] - This is sent everytime the terminal size changes (NAWS)
# [String] - A String is assumed to be input from the Session and we
# send it to parse_messages.
#
def update(msg)
case msg
# Handle disconnection from server
# Note that publishing a :quit event (see #disconnect) will return a
# :disconnected event when server has closed the connection.
# Guest accounts and characters are deleted here.
when :disconnected
@conn = nil
unsubscribe_all
Engine.instance.db.makeswap(id)
if @character
world.connected_characters.delete(@character.id)
world.connected_characters.each do |pid|
add_event(@character.id,pid,:show,"#{name} has disconnected.")
end
Engine.instance.db.makeswap(@character.id)
@character.account = nil
if @character.name =~ /Guest/i
world.all_characters.delete(@character.id)
delete_object(@character.id)
end
@character = nil
end
if name =~ /Guest/i
world.all_accounts.delete(id)
delete_object(id)
end
# Issued when a NAWS event occurs
# Currently this clears and resets the screen. Ideally it should attempt
# to redraw it.
when :termsize
@termsize = @conn.query(:termsize)
if vtsupport?
publish("[home #{@termsize[1]},1][clearline][cursave]" +
"[home 1,1][scrreset][clear][scrreg 1,#{@termsize[1]-3}][currest]")
end
# Negotiation with client done. Start talking to it.
when :initdone
@echo = @conn.query(:echo)
@termsize = @conn.query(:termsize)
@terminal = @conn.query(:terminal)
if vtsupport?
publish("[home #{@termsize[1]},1][clearline][cursave]" +
"[home 1,1][scrreset][clear][scrreg 1,#{@termsize[1]-3}][currest]")
sendmsg(LOGO)
end
sendmsg(BANNER)
sendmsg(append_echo("login> "))
@mode = :name
# This is a message from our user
when String
parse_messages(msg)
else
log.error "Account#update unknown message - #{msg.inspect}"
end
rescue
# We squash and print out all exceptions here. There is no reason to
# throw these back at the Connection.
log.error $!
end


# Handles String messages from Connection - called by update.
# This was refactored out of Account#update for length reasons.
#
# [+msg+] The message string
#
# @mode tracks the state changes, The Account is created with the
# initial state of :initialize. The following state transition
# diagram illustrates the possible transitions.
#
# :intialize -> :name Set when Account:update receives :initdone msg
# :name -> :password Sets @login_name and finds @account
# :playing Creates a new character if Guest account
# :password -> :newacct Sets @login_passwd
# -> :menu Good passwd, switches account, if account_system
# option on goes to menu
# -> :playing Good passwd, switches account, loads character
# -> :name Bad passwd
# -> disconnect Bad passwd, exceeds @check attempts
# (see Account#disconnect)
# :newacct -> :menu If account_system option on goes to menu
# -> :playing Creates new character, adds account
# :menu -> parse_menu Redirect message (see Account#parse_menu)
# :playing -> @character Redirect message (see Character#parse)
#
def parse_messages(msg)
case @mode
when :initialize
# ignore everything until negotiation done
when :name
publish("[clearline]") if vtsupport?
@login_name = msg.proper_name
if options['guest_accounts'] && @login_name =~ /Guest/i
self.name = "Guest#{id}"
@character = new_char
put_object(self)
world.all_accounts << id
# make the account non-swappable so we dont lose connection
Engine.instance.db.makenoswap(id)
@conn.set(:color, color)
welcome
@mode = :playing
elsif @login_name.empty?
sendmsg(append_echo("login> "))
@mode = :name
else
acctid = world.all_accounts.find {|a|
@login_name == get_object(a).name
}
@account = get_object(acctid)
sendmsg(append_echo("password> "))
@conn.set(:hide, true)
@mode = :password
end
when :password
@login_passwd = msg
@conn.set(:hide, false)
if @account.nil? # new account
sendmsg(append_echo("Create new user?\n'Y/y' to create, Hit enter to retry login> "))
@mode = :newacct
else
if @login_passwd.is_passwd?(@account.passwd) # good login
# deregister all observers here and on connection
unsubscribe_all
@conn.unsubscribe_all
# reregister all observers to @account
@conn.subscribe(@account.id)
# make the account non-swappable so we dont lose connection
Engine.instance.db.makenoswap(@account.id)
@conn.set(:color, @account.color)
switch_acct(@account)
# Check if this account already logged in
reconnect = false
if @account.subscriber_count > 0
@account.publish(:reconnecting)
@account.unsubscribe_all
reconnect = true
end
@account.subscribe(@conn)
if options['account_system']
@account.sendmsg(append_echo(login_menu))
@account.mode = :menu
else
@character = get_object(@account.characters.first)
# make the character non-swappable so we dont lose references
Engine.instance.db.makenoswap(@character.id)
world.connected_characters << @character.id
@character.account = @account
@account.character = @character
welcome(reconnect)
@account.mode = :playing
end
else # bad login
@checked -= 1
sendmsg(append_echo("Sorry wrong password."))
if @checked < 1
disconnect
else
@mode = :name
sendmsg(append_echo("login> "))
end
end
end
when :newacct
if msg =~ /^y/i
self.name = @login_name
self.passwd = @login_passwd.encrypt
put_object(self)
# make the account non-swappable so we dont lose connection
Engine.instance.db.makenoswap(id)
world.all_accounts << id
@conn.set(:color, color)
if options['account_system']
sendmsg(append_echo(login_menu))
@mode = :menu
else
@character = new_char
welcome
@mode = :playing
end
else
@mode = :name
sendmsg(append_echo("login> "))
end
when :menu, :menucr, :menupl
parse_menu(msg)
when :playing
@character.parse(msg)
else
log.error "Account#parse_messages unknown :mode - #{@mode.inspect}"
end
end

# Handles message while in the login menu - called by parse_messages.
# This was refactored out of Account#parse_messages for length reasons.
#
# [+msg+] The message string
#
# @mode tracks the state changes, This routine is entered by any @modes
# staring with :menu.
#
# The following state transition diagram illustrates the possible transitions.
#
# :menu -> :menucr Create a character
# -> :menupl Play a character
# :menucr -> :playing Get character name, create character and play
#
def parse_menu(msg)
case @mode
when :menu
case msg
when /^1/i
sendmsg(append_echo("Enter character name> "))
@mode = :menucr
when /^2/i
if characters.size == 0
sendmsg(append_echo(login_menu))
@mode = :menu
else
sendmsg(append_echo(character_menu))
@mode = :menupl
end
when /^Q/i
disconnect
else # Any other key
sendmsg(append_echo(login_menu))
@mode = :menu
end
when :menucr
if msg.proper_name.empty?
sendmsg(append_echo(login_menu))
@mode = :menu
else
@character = new_char(msg.proper_name)
@conn.set(:color, color)
welcome
@mode = :playing
end
when :menupl
case msg
when /(\d+)/
if $1.to_i >= characters.size
sendmsg(append_echo(character_menu))
else
@character = get_object(characters[$1.to_i])
# make the character non-swappable so we dont lose references
Engine.instance.db.makenoswap(@character.id)
world.connected_characters << @character.id
@character.account = self
welcome
@mode = :playing
end
else
sendmsg(append_echo(login_menu))
@mode = :menu
end
else
log.error "Account#parse_menu unknown :mode - #{@mode.inspect}"
end
end

# If echo hasn't been negotiated, we want to leave the cursor after
# the message prompt, so we prepend linefeeds in front of messages.
# This is hackish.
def append_echo(msg)
@echo ? msg : "\n" + msg
end

def sendmsg(msg)
publish("[cursave][home #{@termsize[1]-3},1]") if vtsupport?
publish(msg)
publish("[currest]") if vtsupport?
prompt
end

def prompt
if vtsupport?
=begin
publish("[cursave][home #{@termsize[1]-2},1]" +
"[color Yellow on Red]#{" "*@termsize[0]}[/color]" +
"[home #{@termsize[1]-1},1][clearline][color Magenta](#{name})[#{@mode}][/color]" +
"[currest][clearline]> ")
=end

publish("[home #{@termsize[1]-2},1]" +
"[color Yellow on Red]#{" "*@termsize[0]}[/color]" +
"[home #{@termsize[1]-1},1][clearline][color Magenta](#{name})[#{@mode}][/color]" +
"[home #{@termsize[1]},1][clearline]> ")
else
# publish("> ")
end
end

def status_rept
str = "Terminal: #{@terminal}\n"
str << "Terminal size: #{@termsize[0]} X #{@termsize[1]}\n"
str << "Colors toggled #{@color ? '[COLOR Magenta]ON[/COLOR]' : 'OFF' }\n"
str << "Echo is #{@echo ? 'ON' : 'OFF' }\n"
str << "ZMP is #{@conn.query(:zmp) ? 'ON' : 'OFF' }\n"
end

def toggle_color
color ? self.color = false : self.color = true
@conn.set(:color,color)
"Colors toggled #{color ? '[COLOR Magenta]ON[/COLOR]' : 'OFF' }\n"
end


# Disconnects this account
def disconnect(msg=nil)
publish("[home 1,1][scrreset][clear]") if vtsupport?
publish(msg + "\n") if msg
publish("Bye!\n")
publish(:quit)
unsubscribe_all
end

def character_menu
str = '[color Yellow]'
characters.each_index do |i|
str << "#{i}) #{get_object(characters[i]).name}\n"
end
str << "Pick a character>[/color] "
end

def login_menu
"[color Yellow]1) Create a character\n2) Play\nQ) Quit\n>[/color] "
end

def vtsupport?
@terminal =~ /^vt|xterm/
end
private
def new_char(nm=nil)
if nm.nil?
ch = Character.new(name,id)
else
ch = Character.new(nm,id)
end
self.characters << ch.id
world.all_characters << ch.id
ch.account = self
get_object(options['home'] || 1).add_contents(ch.id)
put_object(ch)
Engine.instance.db.makenoswap(ch.id)
world.connected_characters << ch.id
ch
end

def switch_acct(acct)
acct.conn = @conn
acct.echo = @echo
acct.termsize = @termsize
acct.terminal = @terminal
acct.character = @character
end

def welcome(reconnect=false)
rstr = reconnect ? 'reconnected' : 'connected'
@character.sendto(append_echo("Welcome #{@character.name}@#{@conn.query(:host)}!"))
world.connected_characters.each do |pid|
if pid != @character.id
add_event(@character.id,pid,:show,"#{@character.name} has #{rstr}.")
end
end
@character.parse('look')
end

end