{ Summary: Main server class ## $Id: server.pas,v 1.16 2004/04/15 17:47:22 druid Exp $ } unit server; interface uses dtypes; type GServerShutdownTypes = (SHUTDOWNTYPE_REBOOT, SHUTDOWNTYPE_COPYOVER, SHUTDOWNTYPE_HALT); GServerEvent = procedure() of object; GServer = class(GSingleton) private listenSockets : GDLinkedList; running : boolean; shutdownType : GServerShutdownTypes; shutdownDelay : integer; lastReportedDelay : integer; { Called for every iteration in GServer.gameLoop() } FOnTick : GServerEvent; procedure openListenSockets(); procedure processShutdownDelay(); public constructor actualCreate(); override; destructor actualDestroy(); override; published function gameLoop() : GServerShutdownTypes; procedure init(); procedure cleanup(); procedure shutdown(shutdownType : GServerShutdownTypes; delay : integer = 0); function isRunning() : boolean; function getShutdownDelay() : integer; function getShutdownType() : GServerShutdownTypes; property OnTick : GServerEvent read FOnTick write FOnTick; end; var serverBooted : boolean = false; implementation uses Math, SysUtils, conns, console, player, socket, constants, clan, mudsystem, timers, update, fight, fsys, modules, commands, NameGen, mudhelp, skills, clean, chars, Channels, Bulletinboard, progs, events, area, debug, race; const { Number of milliseconds to sleep each iteration } SERVER_PULSE_SLEEP = 25; { Resolution of timer } SERVER_PULSE_RES = 1000 div 25; { GServer constructor } constructor GServer.actualCreate(); begin inherited actualCreate(); listenSockets := GDLinkedList.Create(); running := false; shutdownType := SHUTDOWNTYPE_HALT; shutdownDelay := -1; end; { GServer destructor } destructor GServer.actualDestroy(); begin listenSockets.clear(); listenSockets.Free(); inherited actualDestroy(); end; { Opens listening sockets } procedure GServer.openListenSockets(); var socket : GSocket; begin listenSockets := GDLinkedList.Create(); if (isSupported(SOCKTYPE_IPV4)) then begin socket := createSocket(SOCKTYPE_IPV4); try socket.openPort(system_info.port); listenSockets.add(socket); except socket.Free(); end; end; if (isSupported(SOCKTYPE_IPV6)) then begin socket := createSocket(SOCKTYPE_IPV6); try socket.openPort(system_info.port6); listenSockets.add(socket); except socket.Free(); end; end; end; { Initializes the server } procedure GServer.init(); begin writeConsole(version_info + ', ' + version_number + '.'); writeConsole(version_copyright + '.'); writeConsole('Initializing memory pool...'); try initProgs(); initClans(); initCommands(); initHelp(); initChannels(); initChars(); initPlayers(); initSkills(); initAreas(); initTimers(); initRaces(); initNotes(); initSystem(); initEvents(); writeConsole('Booting server...'); loadSystem(); writeConsole('Booting "' + system_info.mud_name + '" database, ' + FormatDateTime('ddddd', Now()) + '.'); writeConsole('Loading skills...'); load_skills(); writeConsole('Loading races...'); loadRaces(); writeConsole('Loading clans...'); load_clans; writeConsole('Loading channels...'); load_channels(); writeConsole('Loading areas...'); loadAreas(); writeConsole('Loading help...'); loadHelp('help.dat'); writeConsole('Loading namegenerator data...'); loadNameTables(NameTablesDataFile); writeConsole('Loading noteboards...'); load_notes('boards.dat'); writeConsole('Loading modules...'); loadModules(); writeConsole('Loading texts...'); loadCommands(); loadSocials(); loadDamage(); writeConsole('Loading mud state...'); BootTime := Now; bg_info.count := -1; update_time; time_info.day := 1; time_info.month := 1; time_info.year := 1; loadMudState(); randomize(); resetAreas(); openListenSockets(); registerTimer('teleports', update_teleports, 1, true); registerTimer('fighting', update_fighting, CPULSE_VIOLENCE, true); registerTimer('battleground', update_battleground, CPULSE_VIOLENCE, true); registerTimer('objects', update_objects, CPULSE_TICK, true); registerTimer('characters', update_chars, CPULSE_TICK, true); registerTimer('gametime', update_time, CPULSE_GAMETIME, true); timer_thread := GTimerThread.Create(); clean_thread := GCleanThread.Create(); calculateonline(); except on E : Exception do begin reportException(E, 'GServer.init()'); writeConsole('Server boot failed, halting!'); Halt(1); end; end; end; procedure GServer.cleanup(); var node : GListNode; begin try writeConsole('Terminating threads...'); timer_thread.Terminate(); clean_thread.Terminate(); Sleep(100); writeConsole('Saving mudstate...'); saveMudState(); writeConsole('Unloading modules...'); unloadModules(); writeConsole('Releasing allocated memory...'); node := char_list.tail; while (node <> nil) do begin GCharacter(node.element).extract(true); node := char_list.tail; end; writeConsole('Cleaning channels...'); cleanupChannels(); writeConsole('Cleaning players...'); cleanupPlayers(); writeConsole('Cleaning chars...'); cleanupChars(); writeConsole('Cleaning clans...'); cleanupClans(); writeConsole('Cleaning commands...'); cleanupCommands(); writeConsole('Cleaning help...'); cleanupHelp(); writeConsole('Cleaning skills...'); cleanupSkills(); writeConsole('Cleaning areas...'); cleanupAreas(); writeConsole('Cleaning timers...'); cleanupTimers(); writeConsole('Cleaning races...'); cleanupRaces(); writeConsole('Cleaning system...'); cleanupSystem(); writeConsole('Cleaning notes...'); cleanupNotes(); writeConsole('Cleaning events...'); cleanupEvents(); except on E : Exception do reportException(E, 'GServer.cleanup()'); end; writeConsole('Cleanup complete.'); end; { Decreases shutdownDelay (if > 0), reports shutdown times to channels/console } procedure GServer.processShutdownDelay(); var delay_sec : integer; begin if (shutdownDelay > 0) then begin delay_sec := getShutdownDelay(); if (lastReportedDelay <> delay_sec) then begin lastReportedDelay := delay_sec; case delay_sec of 60,30,20,10,5 : case shutdownType of SHUTDOWNTYPE_HALT:begin writeConsole('Starting shutdown in ' + IntToStr(delay_sec) + ' seconds...'); to_channel(nil, '$B$1 ---- Server $3shutdown$1 in $7' + IntToStr(delay_sec) + '$1 seconds! ----',CHANNEL_ALL,AT_REPORT); end; SHUTDOWNTYPE_REBOOT:begin writeConsole('Starting reboot in ' + IntToStr(delay_sec) + ' seconds...'); to_channel(nil, '$B$1 ---- Server $3reboot$1 in $7' + IntToStr(delay_sec) + '$1 seconds! ----',CHANNEL_ALL,AT_REPORT); end; SHUTDOWNTYPE_COPYOVER:begin writeConsole('Starting copyover in ' + IntToStr(delay_sec) + ' seconds...'); to_channel(nil, '$B$1 ---- Server $3copyover$1 in $7' + IntToStr(delay_sec) + '$1 seconds! ----',CHANNEL_ALL,AT_REPORT); end; end; end; end; dec(shutdownDelay); end else if (shutdownDelay = 0) then begin running := false; case shutdownType of SHUTDOWNTYPE_HALT:begin writeConsole('Starting shutdown now...'); to_channel(nil, '$B$1 ---- Server will $3shutdown $7NOW!$1 ----',CHANNEL_ALL,AT_REPORT); end; SHUTDOWNTYPE_REBOOT:begin writeConsole('Starting reboot now...'); to_channel(nil, '$B$1 ---- Server will $3reboot $7NOW!$1 ----',CHANNEL_ALL,AT_REPORT); end; SHUTDOWNTYPE_COPYOVER:begin writeConsole('Starting copyover now...'); to_channel(nil, '$B$1 ---- Server will $3copyover $7NOW!$1 ----',CHANNEL_ALL,AT_REPORT); end; end; end; end; { Gameloop, call this from main program or TService.Execute } function GServer.gameLoop() : GServerShutdownTypes; var iterator : GIterator; socket : GSocket; begin running := true; serverBooted := true; while (running) do begin try iterator := listenSockets.iterator(); while (iterator.hasNext()) do begin socket := GSocket(iterator.next()); if (socket.canRead()) then acceptConnection(socket); end; iterator.Free(); Sleep(SERVER_PULSE_SLEEP); if (Assigned(FOnTick)) then FOnTick(); processShutdownDelay(); except {$IFDEF LINUX} on E : EQuit do break; {$ENDIF} on E : EControlC do break; on E : Exception do reportException(E, 'GServer.gameLoop()'); end; end; running := false; serverBooted := false; Result := shutdownType; end; { Initiate a shutdown/reboot/copyover procedure, delay is in seconds } procedure GServer.shutdown(shutdownType : GServerShutdownTypes; delay : integer = 0); begin { Internal shutdownDelay is one tick every 1/40th of a second (25 msec) } Self.shutdownDelay := delay * SERVER_PULSE_RES; Self.shutdownType := shutdownType; end; { Returns the shutdown type } function GServer.getShutdownType() : GServerShutdownTypes; begin Result := shutdownType; end; { Returns the shutdown delay (divided by 40 to return to seconds) } function GServer.getShutdownDelay() : integer; begin Result := Ceil(shutdownDelay / SERVER_PULSE_RES); end; { Returns true if server is running } function GServer.isRunning() : boolean; begin Result := running; end; end.