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