#!ruby
#
# RocketMUD was written by Jon Lambert, 2006.
# It is based on SocketMUD(tm) written by Brian Graversen.
# This code is released to the public domain.
#
#
require 'socket'
require 'fcntl'
require 'yaml'
require 'singleton'
require 'pp'
require 'constants'
##############################
# End of standard definitons #
##############################
#############################
# New structures #
#############################
# the actual structures
class Help
attr_accessor :load_time, :keyword, :text
def initialize k, t
@load_time = Time.now.to_i
@keyword = k
@text = t
end
end
class Command
attr_accessor :cmd_name, :cmd_funct, :level
def initialize n, f, l
@cmd_name = n
@cmd_funct = f
@level = l
end
end
#############################
# End of new structures #
#############################
####################
# Global Variables #
####################
$dsock_list = [] # the linked list of active sockets
$dmobile_list = [] # the mobile list of active mobiles
$shut_down = false # used for shutdown
$current_time = Time.now # let's cut down on calls to time()
$fSet = [] # the socket list for polling
$help_list = [] # the linked list of help files
$tabCmd = [] # the command table
$greeting = "" # the welcome greeting
$motd = "" # the MOTD help file
# the color table...
$ansi_table = [
[ ?d, "30", false ],
[ ?D, "30", true ],
[ ?r, "31", false ],
[ ?R, "31", true ],
[ ?g, "32", false ],
[ ?G, "32", true ],
[ ?y, "33", false ],
[ ?Y, "33", true ],
[ ?b, "34", false ],
[ ?B, "34", true ],
[ ?p, "35", false ],
[ ?P, "35", true ],
[ ?c, "36", false ],
[ ?C, "36", true ],
[ ?w, "37", false ],
[ ?W, "37", true ]
]
#
# The command table, very simple, but easy to extend.
#
$tabCmd = [
# command function Req. Level
# ---------------------------------------------
Command.new("commands", :cmd_commands, LEVEL_GUEST),
Command.new("help", :cmd_help, LEVEL_GUEST),
Command.new("linkdead", :cmd_linkdead, LEVEL_ADMIN),
Command.new("say", :cmd_say, LEVEL_GUEST),
Command.new("save", :cmd_save, LEVEL_GUEST),
Command.new("shutdown", :cmd_shutdown, LEVEL_GOD),
Command.new("quit", :cmd_quit, LEVEL_GUEST),
Command.new("who", :cmd_who, LEVEL_GUEST)
]
###########################
# End of Global Variables #
###########################
require 'utils'
#require 'help'
#require 'event'
#require 'sockdesc'
#require 'mobile'
def load_control
loaded = false
Dir.entries(".").each do |entry|
begin
next if entry =~ /rocket|constants/ || entry !~ /\.rb/
if last_modified(entry) > $load_time.to_i
Kernel::load(entry)
log_string "Loaded #{entry}"
loaded = true
end
rescue
log_string "Unable to load #{entry}"
log_string $!.to_s
end
end
if loaded
$load_time = Time.now
end
end
def game_loop control
tv = 0.0
rFd = []
# set this for the first loop
last_time = Time.now
# clear out the file socket set
$fSet = []
# add control to the set
$fSet << control
# do this untill the program is shutdown
while !$shut_down
# set current_time
$current_time = Time.now
# copy the socket set
rFd = $fSet.dup
# wait for something to happen
# Poll our socket interest set
rFd, dummy, dummy = select(rFd, nil, nil, tv)
# check for new connections
if rFd && rFd.include?(control)
newConnection = control.accept
if newConnection
new_socket newConnection
end
end
# poll sockets in the socket list
$dsock_list.each do |dsock|
#
# Close sockects we are unable to read from.
if rFd && rFd.include?(dsock.control) && !dsock.read_from_socket
dsock.close_socket false
next
end
# Ok, check for a new command
dsock.next_cmd_from_buffer
# Is there a new command pending ?
if !dsock.next_command.empty?
# figure out how to deal with the incoming command
case dsock.state
when :state_new_name, :state_new_password, :state_verify_password, :state_ask_password
dsock.handle_new_connections dsock.next_command
when :state_playing
dsock.handle_cmd_input dsock.next_command
else
bug "Descriptor in bad state."
end
dsock.next_command = ''
end
# if the player quits or get's disconnected
next if dsock.state == :state_closed
# Send all new data to the socket and close it if any errors occour
if !dsock.flush_output
dsock.close_socket false
end
end
# call the event queue
heartbeat
#
# Here we sleep out the rest of the pulse, thus forcing
# RocketMud to run at PULSES_PER_SECOND pulses each second.
# get the time right now, and calculate how long we should sleep
sleep_time = last_time - Time.now + (1.0 / PULSES_PER_SECOND)
# if secs < 0 we don't sleep, since we have encountered a laghole
if sleep_time > 0
sleep sleep_time
next
end
load_control
# reset the last time we where sleeping
last_time = Time.now
# recycle sockets
$dsock_list.each do |dsock|
next if dsock.state != :state_closed
# remove the socket from the socket list
$dsock_list.delete dsock
# close the socket
dsock.control.close
end
end # while
end
#
# New_socket()
#
# Initializes a new socket, get's the hostname
# and puts it in the active socket_list.
def new_socket sock
#
# allocate some memory for a new socket if
# there is no free socket in the free_list
sock_new = SockDesc.new sock
# attach the new connection to the socket list
$fSet << sock
# set the socket as non-blocking
sock.fcntl Fcntl::F_SETFL, Fcntl::O_NONBLOCK unless RUBY_PLATFORM =~ /win32/
# update the linked list of sockets
$dsock_list << sock_new
# send the greeting
sock_new.text_to_buffer $greeting
sock_new.text_to_buffer "What is your name? "
# initialize socket events
init_events_socket sock_new
# everything went as it was supposed to
return true
end
def termguard(sig)
bug "The server is shutting down, attempting to close MUD."
buf = sprintf("\r\nThe server is shutting down!\r\n");
# inform all players and save them
$dsock_list.each do |dsock|
dsock.text_to_socket buf
if dsock.state == :state_playing && dsock.player
save_player dsock.player
end
end
# close MUD
exit 1
end
if __FILE__ == $0
Signal.trap("INT", method(:termguard))
Signal.trap("TERM", method(:termguard))
Signal.trap("KILL", method(:termguard))
load_control
# note that we are booting up
log_string "Program starting."
# initialize the event queue - part 1
init_event_queue 1
# initialize the socket
servsock = TCPServer.new '0.0.0.0', MUDPORT
servsock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) unless RUBY_PLATFORM =~ /cygwin/
servsock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [0,0].pack('ii'))
servsock.fcntl Fcntl::F_SETFL, Fcntl::O_NONBLOCK unless RUBY_PLATFORM =~ /win32/
# load all external data
load_muddata
# initialize the event queue - part 2
init_event_queue 2
# main game loop
game_loop servsock
# close down the socket
servsock.close
# terminated without errors
log_string "Program terminated without errors."
exit 0
end