znmud-0.0.1/benchmark/
znmud-0.0.1/cmd/
znmud-0.0.1/cmd/emotes/
znmud-0.0.1/cmd/objects/
znmud-0.0.1/cmd/tiny/
znmud-0.0.1/doc/
znmud-0.0.1/farts/
znmud-0.0.1/lib/
znmud-0.0.1/lib/combat/
znmud-0.0.1/lib/core/bodytypes/
znmud-0.0.1/lib/engine/
znmud-0.0.1/lib/farts/
znmud-0.0.1/logs/
#
# file::    character.rb
# author::  Jon A. Lambert
# version:: 2.10.0
# date::    06/25/2006
#
# Additional Contributor: Craig Smith
#
# 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 'gettext'
require 'core/gameobject'
require 'core/body'
require 'storage/properties'

# The Character class is the mother of all characters.
# Who's their daddy?
#
class Character < GameObject
  include GetText
  bindtextdomain("core")

  # Constants
  MAX_HUNGER=4500
  MAX_THIRST=3600
  MAX_TIRED=9000
  MAX_IDLE=72

  logger 'DEBUG'

  # The acctid object this character is associated with.
  property :acctid, :maxhp, :maxmp, :bodyid, :cmdaliases, :stats,
	   :skills, :lastsavedlocation, :mailbox, :race
  attr_accessor :account # The reference to the account object
                         # (nil if not logged in)
  attr_accessor :position, :mode, :recoverFreq, :olc, :followed_by, :group_members,
	   :group_leader, :idle_ticks, :dirhint, :combatants, :following
  attr_reader :body

  # Create a new Character object
  # IMPORTANT :Character objects must be marked nonswappable while connected!!!
  #       Otherwise we risk losing the contants of @account
  # [+name+]    The displayed name of the character.
  # [+acctid+]  The account id this character belongs to.
  # [+return+]  A handle to the new Character.
  def initialize(name,acctid,location=nil)
    if location
	start = location	# NPCs will specify a location
    else
	start = options['home'] || 1
    end
    super(name, nil, start)
    self.acctid = acctid if acctid   # NPCs do not have acctid
    self.position = :standing
    self.lastsavedlocation = start	# Last time at a saved spot
    self.stats = {}		# Contains all chracters stats
    self.skills = {}		# Skills
    self.bodyid = 0		# No body yet...spooky
    self.mailbox = []		# Internal MUD Mail
    self.race = :human		# Default race is human
    @olc = nil			# Online Creator
    @body = nil			# Placeholder for body object
    @combatants = []		# People currently in battle with us
    @followed_by = []		# Ids of people following this player
    @following = nil		# Person id you are following
    @group_members = []		# Members of a group
    @group_leader = nil		# Leader of a group
    @targetattack = nil		# Target a specific ID when attacking
    @recoverFreq = 15		# Rate of recovery in gamticks
    @recoverTicks = 0
    @recoverMVTicks = 0
    @idleTicks = 0
    @dirhint = nil		# Hint of what direction to go
    self.cmdaliases = {}		# Custom aliases
    @account = nil              # reference to the Account.  If nil this
                                # character is not logged in.
                                # We could use get_object(acctid) but
                                # holding the reference is faster
    initstats			# Set initial stats
  end

  def makebody(maxhp)
	@recoverFreq = 15 if not @recoverFreq
	@recoverTicks = 0
	@recoverMVTicks = 0
	if @body
		if not @body.type == race # Happens for any non-human mobile
			@body.unused = true
			@body = Body.new(race, id)
			@body.assignhp(maxhp)
			self.bodyid = @body.id
		end
		return
	end
	if bodyid > 0  # We have a body id but no body...
		@body = get_object(bodyid)
		return
	end
	# Try and find a discarded body to use
	ObjectSpace.each_object(Body) do |b|
		if b.unused and b.type == race
			@body = b
			@body.unused = false
			@body.assignhp(maxhp)
			self.bodyid = @body.id
			return
		end
	end
	# If none can be found use a new one
	@body = Body.new(race, id) if not @body
	@body.assignhp(maxhp)
	self.bodyid = @body.id
  end

  # Sets default stats and skills
  # This is used to set default values if the object doesn't have them already
  # Useful for adding new features w/o corrupting the older databases
  def initstats
	self.stats = {} if not stats
        # Player abilities
	self.stats[:strength] = 15 + rand(5) if not stats.has_key? :strength
	self.stats[:endurance] = 15 + rand(5) if not stats.has_key? :endurance
	self.stats[:intel] = 15 + rand(5) if not stats.has_key? :intel

	self.stats[:maxhp] = 100 + (stats[:endurance] - 15) if not stats.has_key? :maxhp
	self.stats[:maxmp] = 20 + rand(5) + (stats[:endurance] - 15) if not stats.has_key? :maxmp
	self.stats[:mp] = self.stats[:maxmp] if not stats.has_key? :mp
	self.stats[:exp] = 1 if not stats.has_key? :exp
	self.stats[:kills] = 0 if not stats.has_key? :kills
	self.stats[:level] = 1 if not stats.has_key? :level
	self.stats[:maxinv] = 25 if not stats.has_key? :maxinv
	self.stats[:maxweight] = 50 +( stats[:strength] - 15) if not stats.has_key? :maxweight
	self.stats[:hunger] = 0 if not stats.has_key? :hunger
	self.stats[:thirst] = 0 if not stats.has_key? :thirst
	self.stats[:tired] = 0 if not stats.has_key? :tired
	self.stats[:infection] = 0 if not stats.has_key? :infection
	self.stats[:cash] = 0 if not stats.has_key? :cash
	self.stats[:bank] = 0 if not stats.has_key? :bank
	self.stats[:debugmode] = false if not stats.has_key? :debugmode
	self.stats[:afk] = false if not stats.has_key? :afk
	self.stats[:idle] = false if not stats.has_key? :idle
	self.stats[:chatchannel] = true if not stats.has_key? :chatchannel
  end

  # [+return+] He, She, It
  def pronoun
	m = _("male")
	f = _("female")
	gender = :it
	if get_stat(:gender).is_a? String
		gender = get_stat(:gender)
	elsif @account
		gender = @account.gender
	end
	case gender
	when :male, m
		return _("he")
	when :female, f
		return _("she")
	end
	return _("it")
  end

  # [+return+] His, Her, Its
  def pos_pronoun
	m = _("male")
	f = _("female")
	gender = _("its")
	if get_stat(:gender).is_a? String
		gender = get_stat(:gender)
	elsif @account
		gender = @account.gender
	end
	case gender
	when m, :male
		return _("his")
	when f, :female
		return _("her")
	end
	return _("its")
  end

  # Reset the character.  Note: This does not reset health
  # This is called when connecting and can be used to init
  # new features to the game engine w/o killing the db
  # [+restore+] set to true to restore health
  # [+return+] undefined
  def reset(restore=nil)
	initstats
	self.race = :human if not race
	self.mailbox = [] if not mailbox
	self.skills = {} if not skills
        makebody(get_stat(:maxhp)) if not @body or not @body.type == race
	location = options['home'] || 1 if not location
	@combatants = []
	@followed_by = []
	@group_members = []
	@group_leader = nil
	if restore
		set_stat(:hunger, 0)
		set_stat(:thirst, 0)
		set_stat(:tired, 0)
		set_stat(:infection, 0)
		set_stat(:mp, get_stat(:maxmp))
		if not self.is_a? Zombie
			del_attribute :zombie if has_attribute? :zombie
			del_attribute :infected if has_attribute? :infected
		end
		if @body.severed
			@body.reset if @body.severed.size > 0
		end
		@body.assignhp(get_stat(:maxhp))
		@body.bodyparts.each {|bpid| get_object(bpid).crippled = false }
	end
  end

  # Sends a message to the character if they are connected.
  # [+s+]      The message string
  # [+return+] Undefined.
  def sendto(s)
    if s.is_a? Msg
	s.cansee = cansee? :dark
	s.cansee_invisible = cansee? :invisible
	return if not cansee? and not s.system
    	if @account and s.to_s
		if @mode == :edit or @mode == :olc
			# If in a menu it must be a system message to see it
	    		@account.sendmsg(s.to_s+"\n") if s.system
		else
	    		@account.sendmsg(s.to_s+"\n")
		end
	end
    else
	log.info "sendto Warning: ^ character in String" if s=~/\^/
    	@account.sendmsg(s+"\n") if @account
    end
  end

  # Sends a message to everybody else in the room
  # [+s+]      The message string
  # [+return+] Undefined.
  def sendroom(s)
    return if not location
    return if location == 0
    if not s.is_a? Msg
	s = Msg.new(s)
	s.broadcast = true
    end
    get_object(location).say(s, id)
  end

  # Sends a message to everobyd else in the room except id2
  # [+s+]      The message string
  # [+id2+]    ID of the other player not to send the message to
  # [+return+] Undefined
  def sendrest(s, id2)
    room = get_object(location)
    return if not room
    room.sayrest(s, id, id2) if room.respond_to? "sayrest"
  end

  # Checks if name or aliases match
  # [+n+] Name to check
  # [+return+] True if name or aliases is n
  def named?(n)
	return true if name=~/^#{n}$/i
	aliases.each do |al|
		return true if al=~/^#{n}$/i
	end
	false
  end

  # Checks if character is wearing or wielding a light
  # [+return+] the light or nil if they do not have one
  def has_light?
	@body.wearing.each do |oid|
		o = get_object(oid)
		if o.has_attribute? :light
			if o.respond_to? "has_power"
				return nil if not o.has_power
			end
			return o
		end
	end
	@body.wielding?.each do |oid|
		o = get_object(oid)
		if o.has_attribute? :light
			if o.respond_to? "has_power"
				return nil if not o.has_power
			end
			return o
		end
	end
	nil
  end

  # Totals up and returns the total for any modifier with key
  # [+key+] Key modifier to add up
  # [+return+] Sum of values that matched key
  def get_modifier(key)
	total = 0
	total += modifier[key] if has_modifier? key
	@body.wearing.each do |oid|
		o = get_object(oid)
		total += o.get_modifier(key) if o.has_modifier? key
	end
	@body.wielding?.each do |oid|
		o = get_object(oid)
		total += o.get_modifier(key) if o.has_modifier? key
	end
	total
  end

  # Checks the players mode to determine if they can see a message
  # [+cond+] option condition
  # [+return+] boolean
  def cansee? cond=nil
	case @mode
	when :playing, :resting
		case cond
		when :dark   # In regards to darkness
			loc = get_object(location)
			return true if world.can_build? id
			return true if loc.has_attribute? :bright
			if loc.has_attribute? :dark
				return true if has_light?
				return true if has_attribute? :night_vision
				return false
			end
			# Here we know the room isn't specifically dark or bright
			return true if loc.has_attribute? :inside
			# Here we know the person is not inside so check for night
			return true if not world.climate.night 
			# Here we know outside and night time
			return true if has_light?
			return true if has_attribute? :night_vision
			return false
		when :invisible  # See invisible things
			# Currently only true if you are an admin
			return true if world.is_admin?(id)
			return true if has_attribute? :see_invisible
			return false
		end
		return true
	else
		return false
	end
  end

  # Returns health
  # [+return+] Health points
  def health
	makebody(stats[:maxhp]) if not @body
	return @body.health
  end

  # Converts characters health percentage to an appearance
  # [+detailed+] Optional.  Set to true for a detailed response
  # [+return+] A string describing their health
  def describe_health(detailed = nil)
	if has_attribute? :zombie
		return _("%{name} looks like a walking corpse!" % {:name => name})
	end
	status = (health.to_f / stats[:maxhp].to_f) * 100
	case status.to_i
	when 100
		msg = _("%{name} looks perfectly healthy" % {:name => name})
	when 80..99
		msg = _("%{name} has a few scratches" % {:name => name})
	when 50..79
		msg = _("%{name} has several cuts and bruises" % {:name => name})
	when 25..49
		msg = _("%{name} is badly injured and in need of medical attention" % {:name => name})
	when 10..24
		msg = _("%{name} is covered in blood and looks badly damaged" % {:name => name})
	when 1..9
		msg = _("%{name} is almost dead." % {:name => name})
	when 0
		msg = _("%{name} spits out blood." % {:name => name})
	else
		msg = "#{health}/#{get_stat(:maxhp)}"
	end
	if detailed
	    @body.bodyparts.each do |bpid|
		bp = get_object(bpid)
		if bp.crippled
			msg << _("\nThe %{bp} is crippled." % {:bp => bp.name})
		end
            end
	end
	if has_attribute? :poison or get_stat(:infection) > 60
		msg << _("\n%{pronoun} is covered in sweat." % {:pronoun => pronoun.ucfirst})
	end
	msg.ucfirst
  end

  # This gets called at a set frequency by the world game loop.
  # This is the same as mobiles
  # [+e+] Event data (ignored)
  def idle(e)
	return if world.can_build? id
	recoverHP
	recoverMP
	recoverStunned
	manage_idleticks
	if has_attribute? :zombie
		victimid = find_zombie_victim
		if victimid
			parse(_("kill %{name}" % {:name => get_object(victimid).name}))
		end
		manage_hunger
	else
		manage_sleep
		manage_hunger
		manage_thirst
		manage_infections
	end
  end

  # Manages idle ticks
  def manage_idleticks
	return if @mode == :olc
	return if @position == :fighting
	return if get_stat(:afk) == true
	@idle_ticks += 1
	if @idle_ticks > MAX_IDLE
		if get_stat(:idle) == false
			set_stat(:idle, true)
			sendto _("You have gone idle...")
			sendroom _("%{name} has gone idle." % {:name => name})
		end
	end
  end

  # Manages how hungry a person is
  def manage_hunger
	# Zombies have a fierce appitite
	if has_attribute? :zombie or self.is_a? Zombie
		adjust_stat(:hunger, 30) if get_stat(:hunger) < MAX_HUNGER
	else
		adjust_stat(:hunger, 1) if get_stat(:hunger) < MAX_HUNGER
	end
	sendto(_("You are hungry.")) if get_stat(:hunger) > MAX_HUNGER-150 and rand(1)
  end

  # Manages how thirsty a person is
  def manage_thirst
	loc = get_object(location)
	adjust_stat(:thirst, 1) if get_stat(:thirst) < MAX_THIRST
	# We get more thirsty if we are outside and it's > 95 degrees
	adjust_stat(:thirst, 2) if world.climate.temp > 95 and not loc.has_attribute? :inside
	set_stat(:thirst, MAX_THIRST) if get_stat(:thirst) > MAX_THIRST
	sendto(_("You are thirsty.")) if get_stat(:thirst) > MAX_THIRST-150 and rand(1)
	# TODO: Dehydrate when over 3600 (health -= 1)
  end

  # Manages how tired a person is
  def manage_sleep
	if not @position == :sleeping
		adjust_stat(:tired, 1)
		if get_stat(:tired) > 8850 and get_stat(:tired) < MAX_TIRED
			sendto(_("You are feeling very tired.")) if rand(1)
		elsif get_stat(:tired) > MAX_TIRED
			set_stat(:tired, MAX_TIRED)
			sendto(_("You pass out from exhaustion."))
			@position = :sleeping
		end
	else
		adjust_stat(:tired, -5)
		set_stat(:tired,0) if get_stat(:tired) < 1
	end
  end

  def tired?
	return true if get_stat(:tired) > MAX_TIRED - 50
	false
  end

  # Manages infections
  def manage_infections
	return if not has_attribute? :infected
	adjust_stat(:infection, 1)
	if get_stat(:infection) >= 100
		set_stat(:infection, 100)
		make_zombie
	end
  end

  def hungry?
	return true if get_stat(:hunger) > MAX_HUNGER-150
	false
  end

  def thirsty?
	return true if get_stat(:thirst) > MAX_THIRST-150
	false
  end

  # Recovers 10-25% additional health based on players resting position
  # and an additional random 0-10% on chance
  # [+return+] undefined
  def recoverHP
	@recoverFreq = 15 if not @recoverFreq
	@recoverTicks = 0 if not @recoverTicks
	@recoverTicks += 1
	if @recoverTicks < @recoverFreq
		return
	else
		@recoverTicks = 0
	end
  	set_stat(:maxhp, 100) if not stats.has_key? :maxhp

	if has_attribute? :poison
		if rand(100) > 90 - get_stat(:endurance) # Chance to heal
			delete_attribute? :poison
			sendto(_("You start to feel better."))
		else
			sendto _("You feel very ill.")
			@body.damage(nil, rand(3) + 1)
			make_corpse if health < 1
			return
		end
	end
	return if health >= get_stat(:maxhp)

  	variant = get_stat(:maxhp) / 10
  	case @position
	when :sitting
		modifier = 18
	when :sleeping
		modifier = 25
	when :fighting
		modifier = 5
	else
		modifier = 10
	end
	modifier -= 5 if hungry?
	modifier = 1 if modifier == 0
	health_bonus = (get_stat(:maxhp) / modifier + rand(variant)).to_i
	@body.heal(health_bonus)
  end

  # Recovers 10-25% additional movement points based on players resting position
  # and an additional random 0-10% on chance
  # [+return+] undefined
  def recoverMP
	@recoverFreq = 15 if not @recoverFreq
	@recoverMVTicks = 0 if not @recoverMVTicks
	@recoverMVTicks += 1
	if @recoverMVTicks < @recoverFreq
		return
	else
		@recoverMVTicks = 0
	end
  	stats[:maxmp] = 20 if not stats.has_key? :maxmp # Catch legacy
	stats[:mp] = stats[:maxmp] if not stats.has_key? :mp

  	variant = (stats[:maxmp] * 0.10).to_i
  	case @position
	when :sitting
		modifier = 0.18
	when :sleeping
		modifier = 0.25
	when :fighting
		modifier = 0.01
	else
		modifier = 0.10
	end
	modifier -= 0.10 if tired?
	modifier = 0 if modifier < 0
	stats[:mp] += (stats[:maxmp] * modifier).to_i + rand(variant)
	stats[:mp] = stats[:maxmp] if stats[:mp] > stats[:maxmp]
  end

  # Check if the player is stunned then roll to see if they recover
  # [+return+] Undefined
  def recoverStunned
	return if not has_attribute? :stunned
	#75% to recover
	if rand(4) > 0
		del_attribute :stunned
		sendto _("You are no longer stunned.")
	end
  end

  # [+return+] True if characters has a ceratin skill
  def has_skill?(skill)
	skill = skill.to_s if skill.is_a? Symbol
	if skills.has_key? skill
		return true if skills[skill] > 0
	end
	false
  end

  # Increase skill level by one
  # [+skill+] String of skill to increase
  # [+return+] Current skill total
  def add_skill(skill)
	skill = skill.to_s if skill.is_a? Symbol
	if has_skill? skill
		self.skills[skill] += 1
	else
		self.skills[skill] = 1
	end
	skills[skill]
  end

  # Lowers a skill level
  # [+skill+] skill to lower one point
  # [+return+] current skill level
  def lower_skill(skill)
	skill = skill.to_s if skill.is_a? Symbol
	if has_skill? skill
		self.skills[skill] -= 1
	else
		return 0
	end
	skills[skill]
   end

   # Gets a current skill level.  0 if no skill.
   # [+return+] Returns skill level or 0 if none
   def get_skill(skill)
	skill = skill.to_s if skill.is_a? Symbol
	if has_skill? skill
		return skills[skill]
	end
	0
   end

   # Sets skill level to a specified value
   # [+return+] true on success
   def set_skill_level(skill, level)
	skill = skill.to_s if skill.is_a? Symbol
	if has_skill? skill
		skills[skill] = level
	else
		return false
	end
	true
   end

  # Get a stat value, returns 0 if skill is not set (NOTE: not nil!)
  # [+stat+] Stat to retrieve
  # [+return+] Stat value or 0
  def get_stat(stat)
	stat = stat.to_sym if stat.is_a? String
	return stats[stat] if stats.has_key? stat
	0
  end

  # Sets a stat
  # [+stat+] Stat to set
  # [+val+] value to set stat to
  def set_stat(stat, val)
	stat = stat.to_sym if stat.is_a? String
	self.stats[stat] = val
  end

  # Adjust a stat up or down
  # [+stat+] Stat to adjust
  # [+amt+] Amount to adjust stat
  # [+return+] new adjusted amount
  def adjust_stat(stat, amt)
	stat = stat.to_sym if stat.is_a? String
	set_stat(stat, get_stat(stat) + amt)
	get_stat(stat)
  end

  # Adds a follower to you
  # [+stalkerid+] Id of person following you
  def add_follower(stalkerid)
	@followed_by = [] if not @followed_by
	@followed_by << stalkerid if not @followed_by.include? stalkerid
  end

  # Removes a follower
  # [+stalkerid+] Id to remove
  def del_follower(stalkerid)
	return if not @followed_by
	@followed_by.delete(stalkerid) if @followed_by.include? stalkerid
  end

  # Lets you know if there are any unread messages
  # [+return+] the number of unread messages or 0 if none
  def newmail?
	count = 0
	mailbox.each do |msgid|
		mail = get_object(msgid)
		count += 1 if not mail.read
	end
	count
  end

  # Find a player in the current room by name
  # [+name+] Player to find name
  # [+return+] Array of matching people
  def peopleinroom(pname)
	ppl = []
	nth = 1
	found = 0
	if pname=~/^(\d+)\.(.*)/
		nth = $1.to_i
		pname = $2
	elsif pname=~/^all\.(.*)/
		nth = nil
		pname = $2
	end
	get_object(location).people(id).each do |p|
		gotname = false
		if p.name =~/^#{pname}$/i
			gotname = true
		elsif p.aliases.size > 0
			p.aliases.each do |a|
				gotname = true if a=~/^#{pname}$/i
			end
		end
		if gotname
			found += 1
			if not nth
				ppl << p
			elsif found == nth
				ppl << p
			end
		end
	end
	ppl
  end

  # Case insensitive search of inventory.  Supports 2.obj and all.obj syntax
  # [+what+] name of obj in inventory
  # [+return+] Array of matching objects
  def find_inv(what)
	objs = []
	nth = 1
	found = 0
	if what=~/^(\d+)\.(.*)/
		nth = $1.to_i
		what = $2
	elsif what=~/^all\.(.*)/
		nth = nil
		what = $1
	end
	objects.each do |o|
		gotname = false
		if o.name =~/^#{what}$/i
			gotname = true
		elsif o.aliases.size > 0
			o.aliases.each do |a|
				gotname = true if a=~/^#{what}$/
			end
		end
		if gotname
			found += 1
			if not nth
				objs << o
			elsif found == nth
				objs << o
			end
		end
	end
	objs
  end

  # Returns the weight the player is carrying
  # [+return+] weight the player is carrying
  def carry_weight
  	weight = 0
	contents.each { |o| weight += get_object(o).weight }
	if @body
		@body.wearing.each { |o| weight += get_object(o).weight }
		@body.wielding?.each { |o| weight += get_object(o).weight }
	end
	weight
  end

  # Reports if a player can carry a new item of given weight
  # [+weight+] weight of the new item to carry
  # [+return+] true if they can pick it up
  def can_carry?(weight)
  	return true if world.is_builder? id
	stats[:maxweight] = 50 if not stats.has_key? :maxweight
	stats[:maxinv] = 25 if not stats.has_key? :maxinv
	return false if contents.size + 1 >= stats[:maxinv]
	return false if carry_weight + weight >= stats[:maxweight]
	true
  end

  # Starts a fight with players ID
  # [+id+] of player to fight
  # [+return+] Undefined
  def add_combatant(pid)
	@combatants = [] if not @combatants
	if @position == :sitting
		add_event(id, id, :show, _("You clammer to your feet."))
		msg = Msg.new _("^p1 clammers to %{pos} feet." % {:pos => pos_pronoun})
		msg.p1 = name
		add_event(id, id, :roomsay, msg)
	elsif @position == :sleeping
		add_event(id, id, :show, _("You are attacked and jump up from your sleep."))
		msg = Msg.new _("^p1 wakes and jumps to %{pos} feet." % {:pos => pos_pronoun})
		msg.p1 = name
		add_event(id, id, :roomsay, msg)
	end
	if not @combatants.include? pid
		@combatants << pid
		plyr = get_object(pid)
		plyr.add_combatant(id)
		@position = :fighting
	end
  end

  # Stops fighting with players ID
  # [+id+] of player to fight
  # [+return+] Undefined
  def delete_combatant(pid)
	if @combatants.include? pid
		@combatants.delete pid
		plyr = get_object(pid)
		plyr.delete_combatant(id)
		@position = :standing if @combatants.size < 1
	end
  end

  # Check to see if a command can be executed by user
  # [+cmd+] Command object to verify
  # [+return+] true if user has ability or a msg of why if not
  def can_exec_cmd?(cmd)
	return false if not cmd.kind_of? Command
	return false if has_attribute? :zombie or self.is_a? Zombie and not cmd.zombie
	# Check if command is a results of a skill
	if cmd.skill
		if not has_skill? cmd.skill and not world.is_admin? id
			return false
		end
	end
	cmd.pos = :sitting if cmd.is_emote?
    	# Check position / mode
	case @position
	when :sleeping
		case cmd.pos
		when :any, :sleeping
			# Do nothing... all is good
		else
			return _("Perhaps you should wakeup first.")
		end
	when :sitting
		case cmd.pos
		when :any, :sitting, :sleeping
			# Do nothing
		else
			return _("You need to get to your feet first.")
		end
	when :fighting
		case cmd.pos
		when :any, :fighting
			# Fair game in a fight
		else
			return _("Not now!  You are fighting for your life!")
		end
	end
	# Check permissions
	case cmd.perm
	when "builder"
      		return if not world.can_build? id
	when "admin"
      		return if not world.is_admin? id
	end
	true
  end

  # All command input routed through here and parsed.
  # [+m+]      The input message to be parsed
  # [+return+] Undefined.
  def parse(m)
    # handle edit mode
    if @mode == :edit
      edit_parser m
      return
    elsif @mode == :olc
	if @olc
		@olc.parse(m)
	else
		@mode = :playing
	end
	return
    end

    # match legal command
    m=~/([A-Za-z0-9_@?"'#!\]\[]+)(.*)/
    cmd=$1
    arg=$2
    arg.strip! if arg
    if !cmd
      sendto _("Huh?")
      @account.prompt if @account
      return
    end

    # Idle and AFK checks
    set_stat(:idle, false)
    @idle_ticks = 0
    if get_stat(:afk) == true and not arg == "afk"
	set_stat(:afk, false)
	sendto _("Back from AFK")
	sendroom _("%{name} is back from AFK" % {:name => name})
    end

    # You can not use commands when stunned
    if has_attribute? :stunned
	sendto _("You are STUNNED!")
	return
    end

    # Expand any aliases the user may have
    if cmdaliases
      if cmdaliases.has_key? cmd
	m = cmdaliases[cmd]
        m=~/([A-Za-z0-9_@?"'#!\]\[]+)(.*)/
        cmd=$1
        aliasarg=$2
	aliasarg.gsub!(/%x/,arg) if arg
	arg = aliasarg
        arg.strip! if arg
        if !cmd
      	   sendto("Huh?")
           @account.prompt if @account
           return
        end
      end
    end

    # look for a command in our spanking new table
    c = world.cmds.find(cmd)

    # add any exits to our command list
    # escape certain characters in cmd
    check = cmd.gsub(/\?/,"\\?")
    check.gsub!(/\#/,"\\#")
    check.gsub!(/\[/,"\\[")
    check.gsub!(/\]/,"\\]")
    room = get_object(location)
    if not room or location == 0
	log.error "#{id} location not a valid object"
	return
    end
    get_object(location).exits.each do |exid|
      ext = get_object(exid)
      ext.name.split(/;/).grep(/^#{check}/).each do |ex|
        excmd = Command.new(:cmd_go,"go #{ex}",nil)
	excmd.zombie = true # Allow zombies to use exits
	c << excmd
        arg = ex
      end
    end
    log.debug "parse commands - '#{c.inspect}', arguments - '#{arg}', check - '#{check}'"

    # there are three possibilities here
    case c.size
    when 0   # no commands found
      sendto("Huh?")
    when 1   # command found
		reason = can_exec_cmd? c[0]
		if reason
			if reason == true
				# Check for an emote
				if c[0].is_emote?
					self.send(c[0].cmd, arg, c[0].i, c[0].me, c[0].you, c[0].room, c[0].rest)
				else # Standard command
			      		self.send(c[0].cmd, arg)
				end
			else
				if reason.size > 0
					sendto(reason)
				else
					sendto("Huh?")
				end
			end
		else
			sendto("Huh?")
		end
    else	# check for directions and other shortcuts
		found = false
		c.each do |x|
			if x.name =~ /north|south|east|west|up|down|look/i or
			   x.name == m
				self.send(x.cmd, arg)
				found = true
				break
			end
		end
		if !found	# ambiguous command - tell luser about them.
			# Sanitize results
			sanitized = []
			c.each {|x| sanitized << x if can_exec_cmd?(x) == true }
			if sanitized.size > 0
				ln = _("Which did you mean, ")
				sanitized.each do |x|
					ln += "\'" + x.name + "\'"
					x.name == sanitized.last.name ? ln += "?" : ln += _(" or ")
				end
				sendto(ln)
			else
				sendto(_("Huh?"))
			end
		end
	end
    @account.prompt if @account

  rescue Exception
    # keep character alive after exceptions
    log.fatal $!
  end

  # add experience points. TODO: Check for level up
  # [+pts+] Amount of points to add
  # [+return+] Undefined
  def add_exp(pts)
	adjust_stat(:exp, pts)
  end

  # Record a kill.
  # [+id+] Id of person killed
  # [+return+] Undefined
  def add_kill(id)
	victim = get_object(id)
	add_attribute(:PK) if not victim.kind_of? Mobile and not has_attribute? :zombie
	adjust_stat(:kills, 1)
	# Experience is based on difference of level + amount of kills the victim had
	# TODO: Distribute during group combat
	exp = 1
	level_diff = victim.get_stat(:level) - get_stat(:level)
	exp += 100 * level_diff if level_diff > 0
	exp += victim.get_stat(:kills)
	add_exp(exp)
  end

  # Turns a person into a zombie
  def make_zombie
	add_attribute :zombie
	set_stat(:hunger, MAX_HUNGER)
	# Drop what they are holding in their hands
	loc = get_object(location)
	@body.wielding?.each do |oid|
		@body.unwield(oid)
		loc.add_contents(oid)
		obj = get_object(oid)
		add_event(id, id, :roomsay, _("%{person} drops %{weapon}." % {:person => name, :weapon => obj.shortname}))
	end
	add_skill(:bash)
	add_skill(:moan)
	sendto _("[color Red]Braaaaaains!!![/color]")
	msg = Msg.new _("^p1 vomits blood and begins snarling!")
	msg.p1 = name
	sendroom(msg)
  end

  # Finds a non-zombie character or mobile
  # [+return+] Random OID of a non-zombie character or mobile
  def find_zombie_victim
	possible_victims = []
	loc = get_object(location)
	return if not loc # Shouldn't happen
	loc.contents.each do |oid|
		o = get_object(oid)
		if o.is_a? Character
			if not o.is_a? Zombie and not world.can_build? o.id
				possible_victims << oid if not o.has_attribute? :zombie
			end
		end
	end
	if possible_victims.size > 0
		return possible_victims[rand(possible_victims.size)]
	else
		return nil
	end
  end

  # Creates a corpse object from the players body
  # [+return+] Undefined
  def make_corpse
	health  # This doesn't do anything except ensure a body exists
	c = world.find_objects(_("corpse"))
	if c.size < 1
		corpse = Corpse.new(_("corpse"),id,location)
	else
		corpse = world.load_object(c[0].id)
		corpse.owner = id
		corpse.location = location
		corpse.unused = false
	end
	# Transfer inventory
	contents.each do |i| 
		corpse.add_contents(i)
		get_object(i).location = corpse.id
	end
	contents.clear 
	# Transfer clothing
	@body.wearing.each do |oid|
		@body.remove oid
		corpse.add_contents(oid)
		get_object(oid).location = corpse.id
	end
	# Transfer weapons
	@body.wielding?.each do |oid|
		@body.unwield(oid)
		corpse.add_contents(oid)
		get_object(oid).location = corpse.id
	end
	# Transfer cash
	money = get_stat(:cash)
	if money > 0
		cash = world.find_unused_object_type(Cash)
		if not cash
			cash = Cash.new(_("cash"),id,corpse.id)
		else
			cash.owner = id
			cash.location = corpse.id
			cash.unused = false
		end
		# Adjsut 10 %
		mod = (money * 0.1).to_i
		money += rand(1) ? mod : -mod
		cash.cost = money
		set_stat(:cash, 0)
		corpse.add_contents(cash.id)
	end
	loc = get_object(location)
	loc.add_contents(corpse.id)
	loc.delete_contents(id)
	# Zombie deaths are boring :)
	if has_attribute? :zombie or self.is_a? Zombie
		# Remove bash bonus
		lower_skill(:bash)
		lower_skill(:moan)
		msg = Msg.new _("^p1's rotting body collapses to the floor.")
		msg.p1 = name
		sendroom(msg)
	else # Death Cry
		msg = Msg.new _("^p1 releases a blood curlding death cry")
		msg.p1 = name
		sendroom(msg)
		loc.exits.each do |exid|
			ex = get_object(exid)
			get_object(ex.to_room).say(_("You hear a blood curdling death cry"))
		end
	end
	if @account
		del_attribute :infected if has_attribute? :infected
		self.stats[:infection] = 0
		@body.reset if @body.severed.size > 0
		@body.set_hp(3)  # Give just a bit of health
		self.location = lastsavedlocation
		if has_attribute? :zombie
			del_attribute :zombie
			stats[:hunger] = 0
			@account.disconnect(_("You are dead...again."))
		else
			@account.disconnect(_("You are dead."))
		end
	else
		self.location = nil
		self.unused = true
	end	
  end

  # Event :describe
  # [+e+]      The event
  # [+return+] Undefined
  def describe(e)
    ch = get_object(e.from)
    flags = []
    if world.is_admin? e.from
		flags << "Inv" if has_attribute? :invisible
    end
    flags << "AFK" if get_stat(:afk) == true
    msg = "[COLOR Cyan]"
    msg << "(#{id}) " if ch.get_stat(:debugmode) == true
    pname = name.ucfirst
    case self.class.to_s
    when "Character"
	msg << mxptag("Player '#{pname}'") + pname + mxptag("/Player")
    when "Zombie"
	msg << mxptag("Monster '#{pname}'") + pname + mxptag("/Monster")
    when "Merchant"
	msg << mxptag("Merchant '#{pname}'") + pname + mxptag("/Merchant")
    when "Mobile"
	msg << mxptag("NPC '#{pname}'") + pname + mxptag("/NPC")
    else
    	msg << pname
    end
    msg << " (#{flags.to_s})" if flags.size > 0
    case @position
    when :standing, :sitting, :sleeping
	    msg << _(" is %{position} here." % {:position => @position})
    when :fighting
	    msg << _(" is in a fight.")
    when "", nil
	    msg << _(" is standing here.") 
	    @position = :standing
    else
	    log.warn "ID #{id} has unknown position type #{position}"
	    msg << _(" is here.")
    end
    msg << "[/COLOR]"
    msg << " [color Blue][OLC][/color]" if @mode == :olc
    msg << " [color Blue](Idle...)[/color]" if get_stat(:idle) == true
    add_event(id,e.from,:show,msg)
  end

  # Event :show
  # [+e+]      The event
  # [+return+] Undefined
  def show(e)
    sendto(e.msg)
  end

  # Event :roomshow
  # Sends a message to all players in that room
  # [+e+]      The message string
  # [+return+] Undefined.
  def roomshow(e)
      plyr = get_object(e.to)
      get_object(plyr.location).characters.each do |p|
        add_event(id,p.id,:show,"#{e.msg}")
      end
  end

  # Event :showrest
  # Shows a message to everoby bot e.to and e.from
  # [+e+]      The message string
  # [+return+] Undefined.
  def showrest(e)
	sendrest(e.msg, e.from)
  end

  # Event :roomsay
  # Sends a message to all players in that room but 'to.id' or 'from.id'
  # [+e+]      The message string
  # [+return+] Undefined.
  def roomsay(e)
      get_object(e.to).sendroom("#{e.msg}")
  end

  # Event :wield
  # You can wield anythign :)
  # [+e+] Event info
  # [+return+] Undefined
  def wield(e)
	if @body.wield(e.from)
		delete_contents(e.from)
		o = get_object(e.from)
		sendto(_("You wield %{weapon}" % {:weapon => o.shortname}))
		msg = Msg.new _("^p1 wields ^o1")
		msg.p1 = name
		msg.o1 = o.shortname
		sendroom(msg)
		if o.has_val? :powered_from
			req = get_object(o.power_requirement)
			if not o.has_power?
				if req.is_a? Liquid
					sendto(_("You need %{fuel} to power the %{weapon}." % {:fuel => req.shortname, :weapon => o.name}))
				elsif req.is_a? Battery
					sendto _("The %{obj} is dead." % {:obj => o.name})
				end
			elsif req.is_a? Battery
				sendto _("You switch on the %{obj}." % {:obj => o.name})
			end
		end
	else
		sendto(_("You are unable to wield that"))
	end
  end

  # Event :eat
  # Attempt to eat e.from object
  # [+e+] Event info
  # [+return+] Undefined
  def eat(e)
	food = get_object(e.from)
	if not food.is_a? Food
		sendto(_("You can not eat that!"))
		return
	end
	sendto(_("You eat %{food}" % {:food => food.shortname}))
	msg = Msg.new _("^p1 eats ^o1.")
	msg.p1 = name
	msg.o1 = food.shortname
	sendroom(msg)
	adjust_stat(:hunger, -food.has_val?(:hunger))
	if get_stat(:hunger) < 1
		set_stat(:hunger, 0)
		sendto(_("You feel full."))
	end
	if food.has_attribute? :poison
		# Handle poison
		add_attribute(:poison)
	end
	delete_contents(food.id)
	food.unused = true
	food.location = nil
  end

  # Event :wear
  # Attempts to wear e.from object
  # [+e+] Event info
  # [+return+] Undefiened
  def wear(e)
	# Double check they still have the item in case they were pick pocketed
	# Or something between the command and the event...
	o = nil
	objects.each { |obj| o = obj if obj.id == e.from }
	if o == nil
		sendto(_("You do not seem to be carring this item anymore."))
		return
	end
	if o.val.has_key? "worn"
		if @body.wear(o.id)
			delete_contents(o.id)
			sendto(_("You wear %{worn}" % {:worn => o.shortname}))
			msg = Msg.new _("^p1 wears ^o1.")
			msg.p1 = name
			msg.o1 = o.shortname
			sendroom(msg)
		else
			sendto(_("You can not put that on."))
		end			
	else
		sendto(_("You can not wear that."))
	end
  end

  # Event :remove
  # Removes an article of clothing or armor
  # [+e+] event of what to remove
  # [+return+] Undefined
  def remove(e)
	o = get_object(e.from)
	if @body.remove(o.id)
		add_contents(o.id)
		sendto(_("You remove %{worn}" % {:worn => o.shortname}))
		msg = Msg.new _("^p1 stops wearing ^o1.")
		msg.p1 = name
		msg.o1 = o.shortname
		sendroom(msg)
	else
		sendto(_("Unable to remove %{worn}" % {:worn => o.shortname}))
	end
  end

  # Event :drink
  # Drink from somethine
  # [+e+] e.from = drink from what
  # [+return+] Undefined
  def drink(e)
	from = get_object(e.from)
	if from.has_val? :fountain
		liq = get_object(from.val["fountain"])
		if liq
			if liq.is_a? Liquid
				sendto(_("You drink the %{what} from the %{where}." % {:what => liq.name, :where => from.name}))
				msg = Msg.new _("^p1 drinks ^o1 from ^o2.")
				msg.p1 = name
				msg.o1 = liq.name
				msg.o2 = from.name
				sendroom(msg)
				self.stats[:thirst] = 0
				sendto(_("You are full."))
				add_attribute(:poison) if liq.has_attribute? :poison
			else
				sendto(_("You can not drink what is in that."))
			end
		else
			sendto(_("It is empty."))
		end
	elsif from.kind_of? Container
		if from.has_type? "Liquid"
			if from.liq_amt > 0
				liq = get_object(from.contents[0])
				sendto(_("You drink the %{what} from %{where}." % {:what => liq.name, :where => from.shortname}))
				msg = Msg.new _("^p1 drinks ^o1 from ^o2.")
				msg.p1 = name
				msg.o1 = liq.name
				msg.o2 = from.shortname
				sendroom(msg)
				self.stats[:thirst] -= liq.val["thirst"] if liq.has_val? :thirst
				from.liq_amt -= 1
				if from.liq_amt < 1
					from.contents.clear
					from.liq_amt = 0
				end
				if stats[:thirst] < 1
					sendto(_("You are full."))
					self.stats[:thirst] = 0
				end
			else
				sendto(_("It is empty."))
			end
		else
			sendto(_("You can not drink what is in that."))
		end
	else
		sendto(_("You can not drink from that."))
	end		
  end

  # Event :give
  # Give something to somebody.  WARNING doesn't verify event info
  # [+e+] e.from = giver, e.to = reciver, e.msg = object
  # [+return+] Undefined
  def give(e)
	msg = ""
	from = get_object(e.from)
	to = get_object(e.to)
	obj = get_object(e.msg)
	if not to.can_carry?(obj.weight)
		add_event(to.id, id, :show, _("%{pronoun} is carrying too much already" % {:pronoun => pronoun.ucfirst}))
		return
	end
	return if not obj or not to or not from
	to.add_contents(obj.id)
	from.delete_contents(obj.id)
	get_object(to.location).characters.each do |p|
		if p.account
			case p.id
			when to.id
				msg = Msg.new _("^p2 gives you %{obj}." % {:obj => obj.shortname})
				msg.p2 = from.name
			when from.id
				msg = Msg.new _("You gave %{to} %{obj}." % {:to => to.name, :obj => obj.shortname})
			else
				msg = Msg.new _("^p2 gives ^p1 ^o1.")
				msg.p1 = to.name
				msg.p2 = from.name
				msg.o1 = obj.shortname
			end
			add_event(p.id,p.id,:show,msg)
		end
	end
  end

  # Event :fight - Continue an in progress fight
  # [+e+] Event info
  # [+return+] Undefined
  def fight(e)
	# this routine is typically just called from the world heartbeat
	targetid = nil
	if @targetattack
		targetid = @targetattack
	else
		targetid = @combatants[rand(@combatants.size)]
	end
	target = get_object(targetid)
	return if not target # This can happen if the target disappears
	# Make sure that the person is still in the room!
	if not target.location == location
			add_event(targetid, id, :show, _("You go to attack %{target} but they are no longer in front of you!" % {:target => target.name}))
			delete_combatant(targetid)
			return
	end
	battle = Combat.new(id, targetid)
	hit = battle.attack
	sendto(target.describe_health) if hit and not target.is_a? Zombie
	# Check to see if anybody died (including us)
	if health < 1
		make_corpse
		return
	end
	@combatants.each do |c|
		victim = get_object(c)
		if victim.health < 1
			add_event(victim.id, id, :show, _("You killed %{victim}!!" % {:victim => victim.name}))
			msg = Msg.new _("^p1 killed you!!")
			msg.p1 = name
			add_event(id, victim.id, :show, msg)
			msg = Msg.new _("^p1 killed ^p2!!")
			msg.p1 = name
			msg.p2 = victim.name
			add_event(victim.id, id, :showrest, msg)
			delete_combatant(c)
			victim.make_corpse
			add_kill(c) if victim.id == targetid
		end
	end
		
	# these two events shouldn't happen.  They are just here for safety
	@combatants = [] if not @combatants
	@postion = :standing if @combatants.size < 1
  end

  # Describe the spawn point
  # [+e+] Event info
  # [+return+] A string w/ the message
  def describespawn(e)
	s = get_object(e.from)
	msg = "Spawn ID ##{s.id} Loads ##{s.targetid} - #{get_object(s.targetid).name}\n"
	msg << "  Max: #{s.max}, Max in Room: #{s.max_in_room}, Frequency: #{s.frequency} ticks\n"
	s.inv.each do |i|
		msg << "  Inv: ##{i} - #{get_object(i).name}\n"
	end
	s.wear.each do |w|
		msg << "  Wear: ##{w} - #{get_object(w).name}\n"
	end
	s.wield.each do |w|
		msg << "  Wield: ##{w} - #{get_ojbect(w).name}\n"
	end
	sendto(msg)
  end

  # Event :wake
  # [+e+] Event info
  # [+return+] Undefined
  def wake(e)
	ch = get_object(e.from)
	if @position == :sleeping
		@position = :sitting
		@mode = :resting
		msg = Msg.new _("^p1 wakes you up.")
		msg.p1 = ch.name
		sendto(msg)
		add_event(id, ch.id, :show, _("You wake up %{name}." % {:name => name}))
	else
		msg = Msg.new _("^m1 is not asleep.")
		msg.ch1 = self
		add_event(id, ch.id, :show, msg)
	end
  end

end