#
# 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