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