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