# # file:: world.rb # author:: Jon A. Lambert # version:: 2.8.0 # date:: 01/19/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" $:.unshift "vendor" if !$:.include? "vendor" require 'thread' require 'utility/log' require 'command' require 'occupations' require 'core/root' require 'engine/timer' require 'engine/climate' # The World class is the mother of all worlds. # # It contains world state information, the world timer, utility functions, # and delegates to the Engine. # # [+cmds+] is a handle to the character commands table. # [+ocmds+] is a handle to the object commands table. # [+timer_list+] is a list of all installed timer objects (persistent) # [+all_characters+] is a list of all characters (persistent) # [+timer_list+] is a list of all connected characters class World < Root logger 'DEBUG' property :timer_list, :all_characters, :all_accounts, :builders, :admins, :msgs attr_accessor :cmds, :ocmds, :connected_characters, :climate, :motd, :occupations AUTOSAVE_INTERVAL = 720 # Timer is by game ticks 5second = 1 tick # Create the World. This loads or creates the database depending on # whether it finds it. # [+return+] A handle to the World object. def initialize self.timer_list = [] self.all_characters = [] self.all_accounts = [] self.admins = [] self.builders = [] self.msgs = {} self.climate = nil self.motd = nil @connected_characters = [] @autosave = AUTOSAVE_INTERVAL end def startup srand() @connected_characters = [] @autosave = AUTOSAVE_INTERVAL @cmds, @ocmds = Command.load @occupations = Occupation.load @climate = Climate.new log.info "Cleansing Rooms..." ObjectSpace.each_object(Room) { |r| r.reset } log.info "Starting Timer..." @timer_list_mutex = Mutex.new @timer = Thread.new do begin while true sleep 1.0 @timer_list_mutex.synchronize do timer_list.each do |ti| if ti.fire? add_event(0, ti.id, ti.name, ti.args) ti.reset end end end end rescue Exception log.fatal "Timer thread blew up" log.fatal $! end end # Handle misc tasks. Currently runs every game hour @misc_thread = Thread.new do begin while true sleep 5.0 # 5 seconds == 1 game hour # First handle Weather/Time changes @climate.nextMinute weathermsg = @climate.pump if weathermsg # We pick to.id of #1 because that has a gameobject # and World does not add_event(0, 1, :weathermsg, weathermsg) end # Healing self.all_characters.each do |oid| char = get_object(oid) if char.account add_event(0,char.id, :idle) if char.account.mode == :playing end end # Mobile Idle ObjectSpace.each_object do |m| case m when Mobile if m.location add_event(0,m.id, :idle) if m.location > 0 end end end # Room Spawn points ObjectSpace.each_object(Room) do |r| if r.spawns if r.spawns.size > 0 add_event(0,r.id, :spawn) end end end # Fighting events ObjectSpace.each_object do |o| case o when Character, Mobile add_event(0,o.id,:fight) if o.position == :fighting end end # Rotting corpses, food, etc. ObjectSpace.each_object do |o| add_event(0, o.id, :rot) if o.respond_to? "rot_ticks" and o.unused == false end # Auto-saving @autosave -= 1 if @autosave < 1 @autosave = AUTOSAVE_INTERVAL Engine.instance.db.save log.info "Auto-Saved World" end end rescue Exception log.fatal "Misc Thread Handler crashed" log.fatal $! end end log.info "World initialized." end def shutdown connected_characters.each{|pid| get_object(pid).account.disconnect("Shutdown.")} Thread.kill(@timer) end # Set/add a timer for an object # [+id+] The id of the object that wants to get a timer event # [+name+] The symbolic name of the timer event # [+time+] The interval time in seconds of the timer event def set_timer(id, name, time) @timer_list_mutex.synchronize do timer_list << Timer.new(id, name, time) end end # Unset/remove a timer for an object # [+id+] The id of the object to remove a timer event # [+name+] The symbolic name of the timer event to remove (or nil for all events) def unset_timer(id, name=nil) @timer_list_mutex.synchronize do if name.nil? timer_list.delete_if {|ti| ti.id == id } else timer_list.delete_if {|ti| ti.id == id && ti.name == name } end end end # Is character a builder? # [+oid+] character object id # [+return+] true or false def is_builder? oid builders.include? oid end # Is character an admin? # [+oid+] character object id # [+return+] true or false def is_admin? oid admins.include? oid end # Is character an admin OR a builder? # [+oid+] character object id # [+return+] true or false def can_build? oid admins.include? oid or builders.include? oid end # Make the character an admin # [+oid+] character object id # [+return+] undefined def add_admin oid self.admins << oid if Character && !is_admin?(oid) end # Remove admin priviledges from character # [+oid+] character object id # [+return+] undefined def rem_admin oid self.admins.delete oid end # Make the character a builder # [+oid+] character object id # [+return+] undefined def add_builder oid self.builders << oid if Character && !is_builder?(oid) end # Remove admin priviledges from character # [+oid+] character object id # [+return+] undefined def rem_builder oid self.builders.delete oid end # Does character own the object? # [+pid+] character object id # [+oid+] object id # [+return+] true or false def is_owner?(pid, oid) oid.owner == get_object(pid).owner end # Retrieves all matching objects # [+name+] Object name # [+return+] Array of matching objets def find_objects(name) matching_objects = [] ObjectSpace.each_object do |x| if x.is_a? GameObject matching_objects << x if x.name=~/#{name}/i end end matching_objects end # Retrives unused objects of type # [+type+] Type of object to retrieve # [+return+] First occurance of that object or nil def find_unused_object_type(type) ObjectSpace.each_object(type) do |x| return x if x.isclone and x.unused end nil end # Retrieve all objects with a matching name in the game # [+name+] Room name # [+return+] Array of matching objets def find_objets(name) matching_objects = [] ObjectSpace.each_object do |x| matching_objects << x if x.name=~/^#{name}$/i end matching_objects end # Retrieve all rooms with matching room titles # [+name+] Room name # [+return+] Array of matching objets def find_rooms(name) matching_objects = [] ObjectSpace.each_object do |x| if x.is_a? Room matching_objects << x if x.name=~/#{name}/i end end matching_objects end # Retrieve all mobiles with matching names # [+name+] Mobile name # [+return+] Array of matching objets def find_mobiles(name) matching_objects = [] ObjectSpace.each_object do |x| if x.is_a? Mobile matching_objects << x if x.name=~/^#{name}$/i end end matching_objects end # Retrieve all Characters with matching name # [+name+] Players name # [+return+] Array of matching objets def find_players(name) matching_objects = [] ObjectSpace.each_object do |x| if x.is_a? Character matching_objects << x if x.name=~/^#{name}$/i end end matching_objects end # Returns the next unused object with that matching name or # creates a clone of the target object # [+oid+] Object id to load # [+return+] returns the new object def load_object(oid) o = get_object(oid) if not o log.warn "load_object called with invalid oid ##{oid}" return nil end # First make sure this item is in use and is a clone if o.unused and o.isclone o.unused = false o.reset if o.respond_to? "reset" return o end # Next check for any other object w/ the same parentid that is unused ObjectSpace.each_object do |x| case x when GameObject, Container, Corpse if x.parentid == o.id and x.unused and x.isclone x.reset if x.respond_to? "reset" x.unused = false return x end end end # If we get here we could not find an available object if o.parentid newobj = get_object(o.parentid).clone else newobj = o.clone end if newobj newobj.reset if newobj.respond_to? "reset" newobj.unused = false return newobj else log.error "Could not create/load new object with id #{oid}" end nil end # Given an oid return an array of all oids actively in the game # [+oid+] Oid of non-cloned object to search for # [+return+] Array of matching active objects def find_active_objects(oid) o = get_object(oid) matching_objects = [] if not o log.error "Could not run get_object on ##{oid}" return nil end if o.isclone log.warn "Find Active is looking up a clone id #{oid} checking for parent" o = get_object(o.parentid) end ObjectSpace.each_object(o.class) do |x| if x.location matching_objects << x if not x.unused and x.parentid == o.id and x.location > 1 end end matching_objects end # memstats scans all objects in memory and produces a report # [+return+] a string def memstats # initialize all counters rooms = objs = chars = accounts = scripts = strcount = strsize = ocount = 0 # scan the ObjectSpace counting things ObjectSpace.each_object do |x| case x when String strcount += 1 strsize += x.size when Character chars += 1 when Account accounts += 1 when Room rooms += 1 when GameObject objs += 1 when Script scripts += 1 else ocount += 1 end end # our report : # :NOTE: sprintf would be better memstats=<<EOD [COLOR Cyan] ----* Memory Statistics *---- Rooms - #{rooms} Objects - #{objs} Scripts - #{scripts} Accounts - #{accounts} Characters - #{chars} ----------------------------- Strings - #{strcount} size - #{strsize} bytes Other - #{ocount} ----------------------------- Total Objects - #{rooms+objs+chars+accounts+scripts+strcount+ocount} ----* *---- [/COLOR] EOD end end