2010-08-29 18:46:58 +00:00
|
|
|
"""
|
2010-12-07 02:34:59 +00:00
|
|
|
This module implements the main Evennia server process, the core of
|
2011-09-03 10:22:19 +00:00
|
|
|
the game engine.
|
2010-12-07 02:34:59 +00:00
|
|
|
|
|
|
|
|
This module should be started with the 'twistd' executable since it
|
2011-05-28 15:48:50 +00:00
|
|
|
sets up all the networking features. (this is done automatically
|
2011-04-23 11:54:08 +00:00
|
|
|
by game/evennia.py).
|
2010-12-07 02:34:59 +00:00
|
|
|
|
2010-08-29 18:46:58 +00:00
|
|
|
"""
|
|
|
|
|
import time
|
|
|
|
|
import sys
|
|
|
|
|
import os
|
2011-09-03 10:22:19 +00:00
|
|
|
import signal
|
2010-08-29 18:46:58 +00:00
|
|
|
if os.name == 'nt':
|
|
|
|
|
# For Windows batchfile we need an extra path insertion here.
|
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(
|
|
|
|
|
os.path.dirname(os.path.abspath(__file__)))))
|
|
|
|
|
|
|
|
|
|
from twisted.application import internet, service
|
2010-12-12 10:54:33 +00:00
|
|
|
from twisted.internet import protocol, reactor, defer
|
2010-12-07 02:34:59 +00:00
|
|
|
from twisted.web import server, static
|
2011-11-13 23:15:04 +01:00
|
|
|
import django
|
2010-08-29 18:46:58 +00:00
|
|
|
from django.db import connection
|
|
|
|
|
from django.conf import settings
|
2011-09-03 10:22:19 +00:00
|
|
|
|
2011-08-11 21:16:35 +00:00
|
|
|
from src.scripts.models import ScriptDB
|
2011-04-12 21:43:57 +00:00
|
|
|
from src.server.models import ServerConfig
|
2010-08-29 18:46:58 +00:00
|
|
|
from src.server import initial_setup
|
2010-12-07 02:34:59 +00:00
|
|
|
|
2010-08-29 18:46:58 +00:00
|
|
|
from src.utils.utils import get_evennia_version
|
|
|
|
|
from src.comms import channelhandler
|
2011-09-03 10:22:19 +00:00
|
|
|
from src.server.sessionhandler import SESSIONS
|
|
|
|
|
|
|
|
|
|
if os.name == 'nt':
|
|
|
|
|
# For Windows we need to handle pid files manually.
|
|
|
|
|
SERVER_PIDFILE = os.path.join(settings.GAME_DIR, 'server.pid')
|
|
|
|
|
|
|
|
|
|
SERVER_RESTART = os.path.join(settings.GAME_DIR, 'server.restart')
|
2010-08-29 18:46:58 +00:00
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
# i18n
|
|
|
|
|
from django.utils.translation import ugettext as _
|
2010-12-07 02:34:59 +00:00
|
|
|
|
|
|
|
|
#------------------------------------------------------------
|
|
|
|
|
# Evennia Server settings
|
|
|
|
|
#------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
SERVERNAME = settings.SERVERNAME
|
|
|
|
|
VERSION = get_evennia_version()
|
|
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
AMP_ENABLED = True
|
|
|
|
|
AMP_HOST = settings.AMP_HOST
|
|
|
|
|
AMP_PORT = settings.AMP_PORT
|
2010-12-07 02:34:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
#------------------------------------------------------------
|
|
|
|
|
# Evennia Main Server object
|
|
|
|
|
#------------------------------------------------------------
|
|
|
|
|
class Evennia(object):
|
|
|
|
|
|
2010-08-29 18:46:58 +00:00
|
|
|
"""
|
2010-12-07 02:34:59 +00:00
|
|
|
The main Evennia server handler. This object sets up the database and
|
|
|
|
|
tracks and interlinks all the twisted network services that make up
|
|
|
|
|
evennia.
|
2010-08-29 18:46:58 +00:00
|
|
|
"""
|
2010-12-07 02:34:59 +00:00
|
|
|
|
|
|
|
|
def __init__(self, application):
|
|
|
|
|
"""
|
|
|
|
|
Setup the server.
|
2010-08-29 18:46:58 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
application - an instantiated Twisted application
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
sys.path.append('.')
|
2011-09-03 10:22:19 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
# create a store of services
|
|
|
|
|
self.services = service.IServiceCollection(application)
|
2011-09-03 10:22:19 +00:00
|
|
|
self.amp_protocol = None # set by amp factory
|
|
|
|
|
self.sessions = SESSIONS
|
|
|
|
|
self.sessions.server = self
|
|
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
print '\n' + '-'*50
|
2010-08-29 18:46:58 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
# Database-specific startup optimizations.
|
|
|
|
|
self.sqlite3_prep()
|
|
|
|
|
|
|
|
|
|
# Run the initial setup if needed
|
|
|
|
|
self.run_initial_setup()
|
2010-08-29 18:46:58 +00:00
|
|
|
|
|
|
|
|
self.start_time = time.time()
|
|
|
|
|
|
|
|
|
|
# initialize channelhandler
|
|
|
|
|
channelhandler.CHANNELHANDLER.update()
|
2011-09-03 10:22:19 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
# Make info output to the terminal.
|
|
|
|
|
self.terminal_output()
|
2010-08-29 18:46:58 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
print '-'*50
|
|
|
|
|
|
2010-12-12 10:54:33 +00:00
|
|
|
# set a callback if the server is killed abruptly,
|
|
|
|
|
# by Ctrl-C, reboot etc.
|
2011-05-28 15:48:50 +00:00
|
|
|
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True)
|
2010-12-12 10:54:33 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
self.game_running = True
|
2011-09-15 10:46:41 +02:00
|
|
|
|
|
|
|
|
self.run_init_hooks()
|
|
|
|
|
|
2010-08-29 18:46:58 +00:00
|
|
|
# Server startup methods
|
|
|
|
|
|
|
|
|
|
def sqlite3_prep(self):
|
|
|
|
|
"""
|
|
|
|
|
Optimize some SQLite stuff at startup since we
|
|
|
|
|
can't save it to the database.
|
2011-11-13 23:15:04 +01:00
|
|
|
"""
|
|
|
|
|
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3")
|
|
|
|
|
or (hasattr(settings, 'DATABASES')
|
|
|
|
|
and settings.DATABASES.get("default", {}).get('ENGINE', None)
|
|
|
|
|
== 'django.db.backends.sqlite3')):
|
2010-12-07 02:34:59 +00:00
|
|
|
cursor = connection.cursor()
|
|
|
|
|
cursor.execute("PRAGMA cache_size=10000")
|
|
|
|
|
cursor.execute("PRAGMA synchronous=OFF")
|
|
|
|
|
cursor.execute("PRAGMA count_changes=OFF")
|
|
|
|
|
cursor.execute("PRAGMA temp_store=2")
|
|
|
|
|
|
|
|
|
|
def run_initial_setup(self):
|
|
|
|
|
"""
|
|
|
|
|
This attempts to run the initial_setup script of the server.
|
|
|
|
|
It returns if this is not the first time the server starts.
|
|
|
|
|
"""
|
2011-04-12 21:43:57 +00:00
|
|
|
last_initial_setup_step = ServerConfig.objects.conf('last_initial_setup_step')
|
2010-12-07 02:34:59 +00:00
|
|
|
if not last_initial_setup_step:
|
|
|
|
|
# None is only returned if the config does not exist,
|
|
|
|
|
# i.e. this is an empty DB that needs populating.
|
2011-09-03 10:22:19 +00:00
|
|
|
print _(' Server started for the first time. Setting defaults.')
|
2010-12-07 02:34:59 +00:00
|
|
|
initial_setup.handle_setup(0)
|
|
|
|
|
print '-'*50
|
|
|
|
|
elif int(last_initial_setup_step) >= 0:
|
|
|
|
|
# a positive value means the setup crashed on one of its
|
|
|
|
|
# modules and setup will resume from this step, retrying
|
|
|
|
|
# the last failed module. When all are finished, the step
|
|
|
|
|
# is set to -1 to show it does not need to be run again.
|
2011-09-03 10:22:19 +00:00
|
|
|
print _(' Resuming initial setup from step %(last)s.' % \
|
|
|
|
|
{'last': last_initial_setup_step})
|
2010-12-07 02:34:59 +00:00
|
|
|
initial_setup.handle_setup(int(last_initial_setup_step))
|
|
|
|
|
print '-'*50
|
2010-08-29 18:46:58 +00:00
|
|
|
|
2011-09-15 10:46:41 +02:00
|
|
|
def run_init_hooks(self):
|
|
|
|
|
"""
|
|
|
|
|
Called every server start
|
|
|
|
|
"""
|
|
|
|
|
from src.objects.models import ObjectDB
|
|
|
|
|
from src.players.models import PlayerDB
|
|
|
|
|
|
2011-09-20 12:37:45 +02:00
|
|
|
#print "run_init_hooks:", ObjectDB.get_all_cached_instances()
|
2011-10-01 22:00:22 +02:00
|
|
|
[(o.typeclass, o.at_init()) for o in ObjectDB.get_all_cached_instances()]
|
|
|
|
|
[(p.typeclass, p.at_init()) for p in PlayerDB.get_all_cached_instances()]
|
2011-09-03 10:22:19 +00:00
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
def terminal_output(self):
|
|
|
|
|
"""
|
|
|
|
|
Outputs server startup info to the terminal.
|
|
|
|
|
"""
|
2011-11-19 19:34:00 +01:00
|
|
|
print _(' %(servername)s Server (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
|
2011-09-03 10:22:19 +00:00
|
|
|
print ' amp (Portal): %s' % AMP_PORT
|
|
|
|
|
|
|
|
|
|
def set_restart_mode(self, mode=None):
|
2010-08-29 18:46:58 +00:00
|
|
|
"""
|
2011-09-03 10:22:19 +00:00
|
|
|
This manages the flag file that tells the runner if the server is
|
|
|
|
|
reloading, resetting or shutting down. Valid modes are
|
|
|
|
|
'reload', 'reset', 'shutdown' and None.
|
|
|
|
|
If mode is None, no change will be done to the flag file.
|
2010-12-12 10:54:33 +00:00
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
Either way, the active restart setting (Restart=True/False) is
|
|
|
|
|
returned so the server knows which more it's in.
|
|
|
|
|
"""
|
|
|
|
|
if mode == None:
|
|
|
|
|
if os.path.exists(SERVER_RESTART) and 'True' == open(SERVER_RESTART, 'r').read():
|
2011-09-03 15:10:28 +00:00
|
|
|
mode = 'reload'
|
2011-09-03 10:22:19 +00:00
|
|
|
else:
|
|
|
|
|
mode = 'shutdown'
|
|
|
|
|
else:
|
|
|
|
|
restart = mode in ('reload', 'reset')
|
|
|
|
|
f = open(SERVER_RESTART, 'w')
|
|
|
|
|
f.write(str(restart))
|
|
|
|
|
f.close()
|
|
|
|
|
return mode
|
|
|
|
|
|
|
|
|
|
def shutdown(self, mode=None, _abrupt=False):
|
2010-08-29 18:46:58 +00:00
|
|
|
"""
|
2011-09-03 10:22:19 +00:00
|
|
|
Shuts down the server from inside it.
|
|
|
|
|
|
|
|
|
|
mode - sets the server restart mode.
|
|
|
|
|
'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called.
|
|
|
|
|
'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called.
|
|
|
|
|
'shutdown' - like reset, but server will not auto-restart.
|
|
|
|
|
None - keep currently set flag from flag file.
|
|
|
|
|
_abrupt - this is set if server is stopped by a kill command,
|
|
|
|
|
in which case the reactor is dead anyway.
|
|
|
|
|
"""
|
|
|
|
|
mode = self.set_restart_mode(mode)
|
|
|
|
|
|
|
|
|
|
# call shutdown hooks on all cached objects
|
|
|
|
|
|
|
|
|
|
from src.objects.models import ObjectDB
|
|
|
|
|
from src.players.models import PlayerDB
|
|
|
|
|
from src.server.models import ServerConfig
|
|
|
|
|
|
|
|
|
|
if mode == 'reload':
|
|
|
|
|
# call restart hooks
|
2011-10-01 22:00:22 +02:00
|
|
|
[(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
|
|
|
|
|
[(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
|
|
|
|
|
[(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
|
2011-09-03 10:22:19 +00:00
|
|
|
|
|
|
|
|
ServerConfig.objects.conf("server_restart_mode", "reload")
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
if mode == 'reset':
|
|
|
|
|
# don't call disconnect hooks on reset
|
2011-10-01 22:00:22 +02:00
|
|
|
[(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
|
2011-09-03 10:22:19 +00:00
|
|
|
else: # shutdown
|
2011-10-01 22:00:22 +02:00
|
|
|
[(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
|
|
|
|
|
[(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
|
|
|
|
|
[(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
|
2011-09-03 10:22:19 +00:00
|
|
|
|
|
|
|
|
ServerConfig.objects.conf("server_restart_mode", "reset")
|
|
|
|
|
|
2010-12-12 10:54:33 +00:00
|
|
|
if not _abrupt:
|
|
|
|
|
reactor.callLater(0, reactor.stop)
|
2011-09-03 10:22:19 +00:00
|
|
|
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
|
|
|
|
|
# for Windows we need to remove pid files manually
|
|
|
|
|
os.remove(SERVER_PIDFILE)
|
|
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
#------------------------------------------------------------
|
|
|
|
|
#
|
|
|
|
|
# Start the Evennia game server and add all active services
|
|
|
|
|
#
|
|
|
|
|
#------------------------------------------------------------
|
|
|
|
|
|
2011-04-16 22:26:22 +00:00
|
|
|
# Tell the system the server is starting up; some things are not available yet
|
|
|
|
|
ServerConfig.objects.conf("server_starting_mode", True)
|
|
|
|
|
|
2010-12-07 02:34:59 +00:00
|
|
|
# twistd requires us to define the variable 'application' so it knows
|
|
|
|
|
# what to execute from.
|
|
|
|
|
application = service.Application('Evennia')
|
|
|
|
|
|
|
|
|
|
# The main evennia server program. This sets up the database
|
|
|
|
|
# and is where we store all the other services.
|
|
|
|
|
EVENNIA = Evennia(application)
|
|
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
# The AMP protocol handles the communication between
|
|
|
|
|
# the portal and the mud server. Only reason to ever deactivate
|
|
|
|
|
# it would be during testing and debugging.
|
2010-12-07 02:34:59 +00:00
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
if AMP_ENABLED:
|
2010-12-07 02:34:59 +00:00
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
from src.server import amp
|
2010-12-07 02:34:59 +00:00
|
|
|
|
2011-09-03 10:22:19 +00:00
|
|
|
factory = amp.AmpServerFactory(EVENNIA)
|
|
|
|
|
amp_service = internet.TCPServer(AMP_PORT, factory)
|
|
|
|
|
amp_service.setName("EvenniaPortal")
|
|
|
|
|
EVENNIA.services.addService(amp_service)
|
2011-04-16 22:26:22 +00:00
|
|
|
|
|
|
|
|
# clear server startup mode
|
|
|
|
|
ServerConfig.objects.conf("server_starting_mode", delete=True)
|
2011-09-03 10:22:19 +00:00
|
|
|
|
|
|
|
|
if os.name == 'nt':
|
|
|
|
|
# Windows only: Set PID file manually
|
|
|
|
|
f = open(os.path.join(settings.GAME_DIR, 'server.pid'), 'w')
|
|
|
|
|
f.write(str(os.getpid()))
|
|
|
|
|
f.close()
|