mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
778 lines
27 KiB
Python
778 lines
27 KiB
Python
"""
|
|
This module implements the main Evennia server process, the core of the game
|
|
engine.
|
|
|
|
This module should be started with the 'twistd' executable since it sets up all
|
|
the networking features. (this is done automatically by
|
|
evennia/server/server_runner.py).
|
|
|
|
"""
|
|
import time
|
|
import sys
|
|
import os
|
|
import traceback
|
|
|
|
from twisted.web import static
|
|
from twisted.application import internet, service
|
|
from twisted.internet import reactor, defer
|
|
from twisted.internet.task import LoopingCall
|
|
from twisted.logger import globalLogPublisher
|
|
|
|
import django
|
|
|
|
django.setup()
|
|
|
|
import evennia
|
|
import importlib
|
|
|
|
evennia._init()
|
|
|
|
from django.db import connection
|
|
from django.db.utils import OperationalError
|
|
from django.conf import settings
|
|
|
|
from evennia.accounts.models import AccountDB
|
|
from evennia.scripts.models import ScriptDB
|
|
from evennia.server.models import ServerConfig
|
|
|
|
from evennia.utils.utils import get_evennia_version, mod_import, make_iter
|
|
from evennia.utils import logger
|
|
from evennia.server.sessionhandler import SESSIONS
|
|
|
|
from django.utils.translation import gettext as _
|
|
|
|
_SA = object.__setattr__
|
|
|
|
# a file with a flag telling the server to restart after shutdown or not.
|
|
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", "server.restart")
|
|
|
|
# modules containing hook methods called during start_stop
|
|
SERVER_STARTSTOP_MODULES = [mod_import(m) for m in make_iter(settings.AT_SERVER_STARTSTOP_MODULE) if isinstance(m, str)]
|
|
|
|
# modules containing plugin services
|
|
SERVER_SERVICES_PLUGIN_MODULES = make_iter(settings.SERVER_SERVICES_PLUGIN_MODULES)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Evennia Server settings
|
|
# ------------------------------------------------------------
|
|
|
|
SERVERNAME = settings.SERVERNAME
|
|
VERSION = get_evennia_version()
|
|
|
|
AMP_ENABLED = True
|
|
AMP_HOST = settings.AMP_HOST
|
|
AMP_PORT = settings.AMP_PORT
|
|
AMP_INTERFACE = settings.AMP_INTERFACE
|
|
|
|
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
|
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
|
|
|
|
GUEST_ENABLED = settings.GUEST_ENABLED
|
|
|
|
# server-channel mappings
|
|
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
|
IRC_ENABLED = settings.IRC_ENABLED
|
|
RSS_ENABLED = settings.RSS_ENABLED
|
|
GRAPEVINE_ENABLED = settings.GRAPEVINE_ENABLED
|
|
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
|
GAME_INDEX_ENABLED = settings.GAME_INDEX_ENABLED
|
|
|
|
INFO_DICT = {
|
|
"servername": SERVERNAME,
|
|
"version": VERSION,
|
|
"amp": "",
|
|
"errors": "",
|
|
"info": "",
|
|
"webserver": "",
|
|
"irc_rss": "",
|
|
}
|
|
|
|
try:
|
|
WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
|
|
except ImportError:
|
|
WEB_PLUGINS_MODULE = None
|
|
INFO_DICT["errors"] = (
|
|
"WARNING: settings.WEB_PLUGINS_MODULE not found - "
|
|
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
|
|
)
|
|
|
|
|
|
# Maintenance function - this is called repeatedly by the server
|
|
|
|
_MAINTENANCE_COUNT = 0
|
|
_FLUSH_CACHE = None
|
|
_IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE
|
|
_GAMETIME_MODULE = None
|
|
|
|
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
|
_LAST_SERVER_TIME_SNAPSHOT = 0
|
|
|
|
|
|
def _server_maintenance():
|
|
"""
|
|
This maintenance function handles repeated checks and updates that
|
|
the server needs to do. It is called every minute.
|
|
"""
|
|
global EVENNIA, _MAINTENANCE_COUNT, _FLUSH_CACHE, _GAMETIME_MODULE
|
|
global _LAST_SERVER_TIME_SNAPSHOT
|
|
|
|
if not _FLUSH_CACHE:
|
|
from evennia.utils.idmapper.models import conditional_flush as _FLUSH_CACHE
|
|
if not _GAMETIME_MODULE:
|
|
from evennia.utils import gametime as _GAMETIME_MODULE
|
|
|
|
_MAINTENANCE_COUNT += 1
|
|
|
|
now = time.time()
|
|
if _MAINTENANCE_COUNT == 1:
|
|
# first call after a reload
|
|
_GAMETIME_MODULE.SERVER_START_TIME = now
|
|
_GAMETIME_MODULE.SERVER_RUNTIME = ServerConfig.objects.conf("runtime", default=0.0)
|
|
_LAST_SERVER_TIME_SNAPSHOT = now
|
|
else:
|
|
# adjust the runtime not with 60s but with the actual elapsed time
|
|
# in case this may varies slightly from 60s.
|
|
_GAMETIME_MODULE.SERVER_RUNTIME += now - _LAST_SERVER_TIME_SNAPSHOT
|
|
_LAST_SERVER_TIME_SNAPSHOT = now
|
|
|
|
# update game time and save it across reloads
|
|
_GAMETIME_MODULE.SERVER_RUNTIME_LAST_UPDATED = now
|
|
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.SERVER_RUNTIME)
|
|
|
|
if _MAINTENANCE_COUNT % 5 == 0:
|
|
# check cache size every 5 minutes
|
|
_FLUSH_CACHE(_IDMAPPER_CACHE_MAXSIZE)
|
|
if _MAINTENANCE_COUNT % (60 * 7) == 0:
|
|
# drop database connection every 7 hrs to avoid default timeouts on MySQL
|
|
# (see https://github.com/evennia/evennia/issues/1376)
|
|
connection.close()
|
|
|
|
# handle idle timeouts
|
|
if _IDLE_TIMEOUT > 0:
|
|
reason = _("idle timeout exceeded")
|
|
to_disconnect = []
|
|
for session in (
|
|
sess for sess in SESSIONS.values() if (now - sess.cmd_last) > _IDLE_TIMEOUT
|
|
):
|
|
if not session.account or not session.account.access(
|
|
session.account, "noidletimeout", default=False
|
|
):
|
|
to_disconnect.append(session)
|
|
|
|
for session in to_disconnect:
|
|
SESSIONS.disconnect(session, reason=reason)
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
# Evennia Main Server object
|
|
# ------------------------------------------------------------
|
|
|
|
|
|
class Evennia:
|
|
|
|
"""
|
|
The main Evennia server handler. This object sets up the database and
|
|
tracks and interlinks all the twisted network services that make up
|
|
evennia.
|
|
|
|
"""
|
|
|
|
def __init__(self, application):
|
|
"""
|
|
Setup the server.
|
|
|
|
application - an instantiated Twisted application
|
|
|
|
"""
|
|
sys.path.insert(1, ".")
|
|
|
|
# create a store of services
|
|
self.services = service.MultiService()
|
|
self.services.setServiceParent(application)
|
|
self.amp_protocol = None # set by amp factory
|
|
self.sessions = SESSIONS
|
|
self.sessions.server = self
|
|
self.process_id = os.getpid()
|
|
|
|
# Database-specific startup optimizations.
|
|
self.sqlite3_prep()
|
|
|
|
self.start_time = time.time()
|
|
|
|
# wrap the SIGINT handler to make sure we empty the threadpool
|
|
# even when we reload and we have long-running requests in queue.
|
|
# this is necessary over using Twisted's signal handler.
|
|
# (see https://github.com/evennia/evennia/issues/1128)
|
|
def _wrap_sigint_handler(*args):
|
|
from twisted.internet.defer import Deferred
|
|
|
|
if hasattr(self, "web_root"):
|
|
d = self.web_root.empty_threadpool()
|
|
d.addCallback(lambda _: self.shutdown("reload", _reactor_stopping=True))
|
|
else:
|
|
d = Deferred(lambda _: self.shutdown("reload", _reactor_stopping=True))
|
|
d.addCallback(lambda _: reactor.stop())
|
|
reactor.callLater(1, d.callback, None)
|
|
|
|
reactor.sigInt = _wrap_sigint_handler
|
|
|
|
# Server startup methods
|
|
|
|
def sqlite3_prep(self):
|
|
"""
|
|
Optimize some SQLite stuff at startup since we
|
|
can't save it to the database.
|
|
"""
|
|
if (
|
|
".".join(str(i) for i in django.VERSION) < "1.2"
|
|
and settings.DATABASES.get("default", {}).get("ENGINE") == "sqlite3"
|
|
) or (
|
|
hasattr(settings, "DATABASES")
|
|
and settings.DATABASES.get("default", {}).get("ENGINE", None)
|
|
== "django.db.backends.sqlite3"
|
|
):
|
|
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 update_defaults(self):
|
|
"""
|
|
We make sure to store the most important object defaults here, so
|
|
we can catch if they change and update them on-objects automatically.
|
|
This allows for changing default cmdset locations and default
|
|
typeclasses in the settings file and have them auto-update all
|
|
already existing objects.
|
|
|
|
"""
|
|
global INFO_DICT
|
|
|
|
# setting names
|
|
settings_names = (
|
|
"CMDSET_CHARACTER",
|
|
"CMDSET_ACCOUNT",
|
|
"BASE_ACCOUNT_TYPECLASS",
|
|
"BASE_OBJECT_TYPECLASS",
|
|
"BASE_CHARACTER_TYPECLASS",
|
|
"BASE_ROOM_TYPECLASS",
|
|
"BASE_EXIT_TYPECLASS",
|
|
"BASE_SCRIPT_TYPECLASS",
|
|
"BASE_CHANNEL_TYPECLASS",
|
|
)
|
|
# get previous and current settings so they can be compared
|
|
settings_compare = list(
|
|
zip(
|
|
[ServerConfig.objects.conf(name) for name in settings_names],
|
|
[settings.__getattr__(name) for name in settings_names],
|
|
)
|
|
)
|
|
mismatches = [
|
|
i for i, tup in enumerate(settings_compare) if tup[0] and tup[1] and tup[0] != tup[1]
|
|
]
|
|
if len(
|
|
mismatches
|
|
): # can't use any() since mismatches may be [0] which reads as False for any()
|
|
# we have a changed default. Import relevant objects and
|
|
# run the update
|
|
from evennia.objects.models import ObjectDB
|
|
from evennia.comms.models import ChannelDB
|
|
|
|
# from evennia.accounts.models import AccountDB
|
|
for i, prev, curr in (
|
|
(i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches
|
|
):
|
|
# update the database
|
|
INFO_DICT[
|
|
"info"
|
|
] = " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." % (
|
|
settings_names[i],
|
|
prev,
|
|
curr,
|
|
)
|
|
if i == 0:
|
|
ObjectDB.objects.filter(db_cmdset_storage__exact=prev).update(
|
|
db_cmdset_storage=curr
|
|
)
|
|
if i == 1:
|
|
AccountDB.objects.filter(db_cmdset_storage__exact=prev).update(
|
|
db_cmdset_storage=curr
|
|
)
|
|
if i == 2:
|
|
AccountDB.objects.filter(db_typeclass_path__exact=prev).update(
|
|
db_typeclass_path=curr
|
|
)
|
|
if i in (3, 4, 5, 6):
|
|
ObjectDB.objects.filter(db_typeclass_path__exact=prev).update(
|
|
db_typeclass_path=curr
|
|
)
|
|
if i == 7:
|
|
ScriptDB.objects.filter(db_typeclass_path__exact=prev).update(
|
|
db_typeclass_path=curr
|
|
)
|
|
if i == 8:
|
|
ChannelDB.objects.filter(db_typeclass_path__exact=prev).update(
|
|
db_typeclass_path=curr
|
|
)
|
|
# store the new default and clean caches
|
|
ServerConfig.objects.conf(settings_names[i], curr)
|
|
ObjectDB.flush_instance_cache()
|
|
AccountDB.flush_instance_cache()
|
|
ScriptDB.flush_instance_cache()
|
|
ChannelDB.flush_instance_cache()
|
|
# if this is the first start we might not have a "previous"
|
|
# setup saved. Store it now.
|
|
[
|
|
ServerConfig.objects.conf(settings_names[i], tup[1])
|
|
for i, tup in enumerate(settings_compare)
|
|
if not tup[0]
|
|
]
|
|
|
|
def run_initial_setup(self):
|
|
"""
|
|
This is triggered by the amp protocol when the connection
|
|
to the portal has been established.
|
|
This attempts to run the initial_setup script of the server.
|
|
It returns if this is not the first time the server starts.
|
|
Once finished the last_initial_setup_step is set to 'done'
|
|
|
|
"""
|
|
global INFO_DICT
|
|
initial_setup = importlib.import_module(settings.INITIAL_SETUP_MODULE)
|
|
last_initial_setup_step = ServerConfig.objects.conf("last_initial_setup_step")
|
|
try:
|
|
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.
|
|
INFO_DICT["info"] = " Server started for the first time. Setting defaults."
|
|
initial_setup.handle_setup()
|
|
elif last_initial_setup_step not in ("done", -1):
|
|
# last step crashed, so we weill resume from this step.
|
|
# modules and setup will resume from this step, retrying
|
|
# the last failed module. When all are finished, the step
|
|
# is set to 'done' to show it does not need to be run again.
|
|
INFO_DICT["info"] = " Resuming initial setup from step '{last}'.".format(
|
|
last=last_initial_setup_step
|
|
)
|
|
initial_setup.handle_setup(last_initial_setup_step)
|
|
except Exception:
|
|
# stop server if this happens.
|
|
print(traceback.format_exc())
|
|
print("Error in initial setup. Stopping Server + Portal.")
|
|
self.sessions.portal_shutdown()
|
|
|
|
def create_default_channels(self):
|
|
"""
|
|
check so default channels exist on every restart, create if not.
|
|
|
|
"""
|
|
|
|
from evennia.comms.models import ChannelDB
|
|
from evennia.accounts.models import AccountDB
|
|
from evennia.utils.create import create_channel
|
|
|
|
superuser = AccountDB.objects.get(id=1)
|
|
# mudinfo
|
|
mudinfo_chan = settings.CHANNEL_MUDINFO
|
|
if mudinfo_chan:
|
|
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
|
channel = create_channel(**mudinfo_chan)
|
|
channel.connect(superuser)
|
|
# connectinfo
|
|
connectinfo_chan = settings.CHANNEL_MUDINFO
|
|
if connectinfo_chan:
|
|
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
|
channel = create_channel(**connectinfo_chan)
|
|
# default channels
|
|
for chan_info in settings.DEFAULT_CHANNELS:
|
|
if not ChannelDB.objects.filter(db_key=chan_info["key"]):
|
|
channel = create_channel(**chan_info)
|
|
channel.connect(superuser)
|
|
|
|
def run_init_hooks(self, mode):
|
|
"""
|
|
Called by the amp client once receiving sync back from Portal
|
|
|
|
Args:
|
|
mode (str): One of shutdown, reload or reset
|
|
|
|
"""
|
|
from evennia.typeclasses.models import TypedObject
|
|
|
|
# start server time and maintenance task
|
|
self.maintenance_task = LoopingCall(_server_maintenance)
|
|
self.maintenance_task.start(60, now=True) # call every minute
|
|
|
|
# update eventual changed defaults
|
|
self.update_defaults()
|
|
|
|
# run at_init() on all cached entities on reconnect
|
|
[
|
|
[entity.at_init() for entity in typeclass_db.get_all_cached_instances()]
|
|
for typeclass_db in TypedObject.__subclasses__()
|
|
]
|
|
|
|
self.at_server_init()
|
|
|
|
# call correct server hook based on start file value
|
|
if mode == "reload":
|
|
logger.log_msg("Server successfully reloaded.")
|
|
self.at_server_reload_start()
|
|
elif mode == "reset":
|
|
# only run hook, don't purge sessions
|
|
self.at_server_cold_start()
|
|
logger.log_msg("Evennia Server successfully restarted in 'reset' mode.")
|
|
elif mode == "shutdown":
|
|
from evennia.objects.models import ObjectDB
|
|
|
|
self.at_server_cold_start()
|
|
# clear eventual lingering session storages
|
|
ObjectDB.objects.clear_all_sessids()
|
|
logger.log_msg("Evennia Server successfully started.")
|
|
|
|
# always call this regardless of start type
|
|
self.at_server_start()
|
|
|
|
@defer.inlineCallbacks
|
|
def shutdown(self, mode="reload", _reactor_stopping=False):
|
|
"""
|
|
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 but sessions will not
|
|
be disconnected.
|
|
- 'shutdown' - like reset, but server will not auto-restart.
|
|
_reactor_stopping - this is set if server is stopped by a kill
|
|
command OR this method was already called
|
|
once - in both cases the reactor is
|
|
dead/stopping already.
|
|
"""
|
|
if _reactor_stopping and hasattr(self, "shutdown_complete"):
|
|
# this means we have already passed through this method
|
|
# once; we don't need to run the shutdown procedure again.
|
|
defer.returnValue(None)
|
|
|
|
from evennia.objects.models import ObjectDB
|
|
from evennia.server.models import ServerConfig
|
|
from evennia.utils import gametime as _GAMETIME_MODULE
|
|
|
|
if mode == "reload":
|
|
# call restart hooks
|
|
ServerConfig.objects.conf("server_restart_mode", "reload")
|
|
yield [o.at_server_reload() for o in ObjectDB.get_all_cached_instances()]
|
|
yield [p.at_server_reload() for p in AccountDB.get_all_cached_instances()]
|
|
yield [
|
|
(s._pause_task(auto_pause=True), s.at_server_reload())
|
|
for s in ScriptDB.get_all_cached_instances()
|
|
if s.id and s.is_active
|
|
]
|
|
yield self.sessions.all_sessions_portal_sync()
|
|
self.at_server_reload_stop()
|
|
# only save monitor state on reload, not on shutdown/reset
|
|
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
|
|
|
MONITOR_HANDLER.save()
|
|
else:
|
|
if mode == "reset":
|
|
# like shutdown but don't unset the is_connected flag and don't disconnect sessions
|
|
yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()]
|
|
yield [p.at_server_shutdown() for p in AccountDB.get_all_cached_instances()]
|
|
if self.amp_protocol:
|
|
yield self.sessions.all_sessions_portal_sync()
|
|
else: # shutdown
|
|
yield [_SA(p, "is_connected", False) for p in AccountDB.get_all_cached_instances()]
|
|
yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()]
|
|
yield [
|
|
(p.unpuppet_all(), p.at_server_shutdown())
|
|
for p in AccountDB.get_all_cached_instances()
|
|
]
|
|
yield ObjectDB.objects.clear_all_sessids()
|
|
yield [
|
|
(s._pause_task(auto_pause=True), s.at_server_shutdown())
|
|
for s in ScriptDB.get_all_cached_instances()
|
|
if s.id and s.is_active
|
|
]
|
|
ServerConfig.objects.conf("server_restart_mode", "reset")
|
|
self.at_server_cold_stop()
|
|
|
|
# tickerhandler state should always be saved.
|
|
from evennia.scripts.tickerhandler import TICKER_HANDLER
|
|
|
|
TICKER_HANDLER.save()
|
|
|
|
# always called, also for a reload
|
|
self.at_server_stop()
|
|
|
|
if hasattr(self, "web_root"): # not set very first start
|
|
yield self.web_root.empty_threadpool()
|
|
|
|
if not _reactor_stopping:
|
|
# kill the server
|
|
self.shutdown_complete = True
|
|
reactor.callLater(1, reactor.stop)
|
|
|
|
# we make sure the proper gametime is saved as late as possible
|
|
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())
|
|
|
|
def get_info_dict(self):
|
|
"""
|
|
Return the server info, for display.
|
|
|
|
"""
|
|
return INFO_DICT
|
|
|
|
# server start/stop hooks
|
|
|
|
def at_server_init(self):
|
|
"""
|
|
This is called first when the server is starting, before any other hooks, regardless of how it's starting.
|
|
"""
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_init"):
|
|
m.at_server_init()
|
|
|
|
def at_server_start(self):
|
|
"""
|
|
This is called every time the server starts up, regardless of
|
|
how it was shut down.
|
|
|
|
"""
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_start"):
|
|
m.at_server_start()
|
|
|
|
def at_server_stop(self):
|
|
"""
|
|
This is called just before a server is shut down, regardless
|
|
of it is fore a reload, reset or shutdown.
|
|
|
|
"""
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_stop"):
|
|
m.at_server_stop()
|
|
|
|
def at_server_reload_start(self):
|
|
"""
|
|
This is called only when server starts back up after a reload.
|
|
|
|
"""
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_reload_start"):
|
|
m.at_server_reload_start()
|
|
|
|
def at_post_portal_sync(self, mode):
|
|
"""
|
|
This is called just after the portal has finished syncing back data to the server
|
|
after reconnecting.
|
|
|
|
Args:
|
|
mode (str): One of 'reload', 'reset' or 'shutdown'.
|
|
|
|
"""
|
|
|
|
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
|
|
|
MONITOR_HANDLER.restore(mode == "reload")
|
|
|
|
from evennia.scripts.tickerhandler import TICKER_HANDLER
|
|
|
|
TICKER_HANDLER.restore(mode == "reload")
|
|
|
|
# Un-pause all scripts, stop non-persistent timers
|
|
ScriptDB.objects.update_scripts_after_server_start()
|
|
|
|
# start the task handler
|
|
from evennia.scripts.taskhandler import TASK_HANDLER
|
|
|
|
TASK_HANDLER.load()
|
|
TASK_HANDLER.create_delays()
|
|
|
|
# create/update channels
|
|
self.create_default_channels()
|
|
|
|
# delete the temporary setting
|
|
ServerConfig.objects.conf("server_restart_mode", delete=True)
|
|
|
|
def at_server_reload_stop(self):
|
|
"""
|
|
This is called only time the server stops before a reload.
|
|
|
|
"""
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_reload_stop"):
|
|
m.at_server_reload_stop()
|
|
|
|
def at_server_cold_start(self):
|
|
"""
|
|
This is called only when the server starts "cold", i.e. after a
|
|
shutdown or a reset.
|
|
|
|
"""
|
|
# We need to do this just in case the server was killed in a way where
|
|
# the normal cleanup operations did not have time to run.
|
|
from evennia.objects.models import ObjectDB
|
|
|
|
ObjectDB.objects.clear_all_sessids()
|
|
|
|
# Remove non-persistent scripts
|
|
from evennia.scripts.models import ScriptDB
|
|
|
|
for script in ScriptDB.objects.filter(db_persistent=False):
|
|
script._stop_task()
|
|
|
|
if GUEST_ENABLED:
|
|
for guest in AccountDB.objects.all().filter(
|
|
db_typeclass_path=settings.BASE_GUEST_TYPECLASS
|
|
):
|
|
for character in guest.db._playable_characters:
|
|
if character:
|
|
character.delete()
|
|
guest.delete()
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_cold_start"):
|
|
m.at_server_cold_start()
|
|
|
|
def at_server_cold_stop(self):
|
|
"""
|
|
This is called only when the server goes down due to a shutdown or reset.
|
|
|
|
"""
|
|
for m in SERVER_STARTSTOP_MODULES:
|
|
if hasattr(m, "at_server_cold_stop"):
|
|
m.at_server_cold_stop()
|
|
|
|
|
|
# ------------------------------------------------------------
|
|
#
|
|
# Start the Evennia game server and add all active services
|
|
#
|
|
# ------------------------------------------------------------
|
|
|
|
|
|
# Tell the system the server is starting up; some things are not available yet
|
|
try:
|
|
ServerConfig.objects.conf("server_starting_mode", True)
|
|
except OperationalError:
|
|
print("Server server_starting_mode couldn't be set - database not set up.")
|
|
|
|
|
|
# twistd requires us to define the variable 'application' so it knows
|
|
# what to execute from.
|
|
application = service.Application("Evennia")
|
|
|
|
|
|
if "--nodaemon" not in sys.argv and "test" not in sys.argv:
|
|
# activate logging for interactive/testing mode
|
|
logfile = logger.WeeklyLogFile(
|
|
os.path.basename(settings.SERVER_LOG_FILE),
|
|
os.path.dirname(settings.SERVER_LOG_FILE),
|
|
day_rotation=settings.SERVER_LOG_DAY_ROTATION,
|
|
max_size=settings.SERVER_LOG_MAX_SIZE,
|
|
)
|
|
globalLogPublisher.addObserver(logger.GetServerLogObserver()(logfile))
|
|
|
|
|
|
# The main evennia server program. This sets up the database
|
|
# and is where we store all the other services.
|
|
EVENNIA = Evennia(application)
|
|
|
|
if AMP_ENABLED:
|
|
|
|
# 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.
|
|
|
|
ifacestr = ""
|
|
if AMP_INTERFACE != "127.0.0.1":
|
|
ifacestr = "-%s" % AMP_INTERFACE
|
|
|
|
INFO_DICT["amp"] = "amp %s: %s" % (ifacestr, AMP_PORT)
|
|
|
|
from evennia.server import amp_client
|
|
|
|
factory = amp_client.AMPClientFactory(EVENNIA)
|
|
amp_service = internet.TCPClient(AMP_HOST, AMP_PORT, factory)
|
|
amp_service.setName("ServerAMPClient")
|
|
EVENNIA.services.addService(amp_service)
|
|
|
|
if WEBSERVER_ENABLED:
|
|
|
|
# Start a django-compatible webserver.
|
|
|
|
from evennia.server.webserver import (
|
|
DjangoWebRoot,
|
|
WSGIWebServer,
|
|
Website,
|
|
LockableThreadPool,
|
|
PrivateStaticRoot,
|
|
)
|
|
|
|
# start a thread pool and define the root url (/) as a wsgi resource
|
|
# recognized by Django
|
|
threads = LockableThreadPool(
|
|
minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]),
|
|
maxthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[1]),
|
|
)
|
|
|
|
web_root = DjangoWebRoot(threads)
|
|
# point our media resources to url /media
|
|
web_root.putChild(b"media", PrivateStaticRoot(settings.MEDIA_ROOT))
|
|
# point our static resources to url /static
|
|
web_root.putChild(b"static", PrivateStaticRoot(settings.STATIC_ROOT))
|
|
EVENNIA.web_root = web_root
|
|
|
|
if WEB_PLUGINS_MODULE:
|
|
# custom overloads
|
|
web_root = WEB_PLUGINS_MODULE.at_webserver_root_creation(web_root)
|
|
|
|
web_site = Website(web_root, logPath=settings.HTTP_LOG_FILE)
|
|
web_site.is_portal = False
|
|
|
|
INFO_DICT["webserver"] = ""
|
|
for proxyport, serverport in WEBSERVER_PORTS:
|
|
# create the webserver (we only need the port for this)
|
|
webserver = WSGIWebServer(threads, serverport, web_site, interface="127.0.0.1")
|
|
webserver.setName("EvenniaWebServer%s" % serverport)
|
|
EVENNIA.services.addService(webserver)
|
|
|
|
INFO_DICT["webserver"] += "webserver: %s" % serverport
|
|
|
|
ENABLED = []
|
|
if IRC_ENABLED:
|
|
# IRC channel connections
|
|
ENABLED.append("irc")
|
|
|
|
if RSS_ENABLED:
|
|
# RSS feed channel connections
|
|
ENABLED.append("rss")
|
|
|
|
if GRAPEVINE_ENABLED:
|
|
# Grapevine channel connections
|
|
ENABLED.append("grapevine")
|
|
|
|
if GAME_INDEX_ENABLED:
|
|
from evennia.server.game_index_client.service import EvenniaGameIndexService
|
|
|
|
egi_service = EvenniaGameIndexService()
|
|
EVENNIA.services.addService(egi_service)
|
|
|
|
if ENABLED:
|
|
INFO_DICT["irc_rss"] = ", ".join(ENABLED) + " enabled."
|
|
|
|
for plugin_module in SERVER_SERVICES_PLUGIN_MODULES:
|
|
# external plugin protocols - load here
|
|
plugin_module = mod_import(plugin_module)
|
|
if plugin_module:
|
|
plugin_module.start_plugin_services(EVENNIA)
|
|
else:
|
|
print(f"Could not load plugin module {plugin_module}")
|
|
|
|
# clear server startup mode
|
|
try:
|
|
ServerConfig.objects.conf("server_starting_mode", delete=True)
|
|
except OperationalError:
|
|
print("Server server_starting_mode couldn't unset - db not set up.")
|