Add evennia connectsions wizard functionality

This commit is contained in:
Griatch 2019-06-27 23:07:35 +02:00
parent c9893c6e65
commit 1172067afc
7 changed files with 126 additions and 81 deletions

View file

@ -35,6 +35,13 @@
- Change webclient from old txws version to use more supported/feature-rich Autobahn websocket library - Change webclient from old txws version to use more supported/feature-rich Autobahn websocket library
#### Evennia game index
- Made Evennia game index client a part of core - now configured from settings file (old configs
need to be moved)
- The `evennia connections` command starts a wizard that helps you connect your game to the game index.
- The game index now accepts games with no public telnet/webclient info (for early prototypes).
#### New golden-layout based Webclient UI (@friarzen) #### New golden-layout based Webclient UI (@friarzen)
- Features - Features
- Much slicker behavior and more professional look - Much slicker behavior and more professional look

View file

@ -3,9 +3,10 @@ Link Evennia to external resources (wizard plugin for evennia_launcher)
""" """
import sys import sys
from os import path
import pprint import pprint
from django.conf import settings from django.conf import settings
from evennia.utils.utils import list_to_string from evennia.utils.utils import list_to_string, mod_import
class ConnectionWizard(object): class ConnectionWizard(object):
@ -33,7 +34,7 @@ class ConnectionWizard(object):
""" """
opt_txt = "\n".join(f" {key}: {desc}" for key, (desc, _, _) in options.items()) opt_txt = "\n".join(f" {key}: {desc}" for key, (desc, _, _) in options.items())
self.display(opt_txt) self.display(opt_txt + "\n")
while True: while True:
resp = input(prompt).strip() resp = input(prompt).strip()
@ -78,7 +79,7 @@ class ConnectionWizard(object):
elif resp.lower() in ("quit", "q"): elif resp.lower() in ("quit", "q"):
sys.exit() sys.exit()
def ask_choice(self, prompt="> ", options=None, default=None): def ask_choice(self, prompt=" > ", options=None, default=None):
""" """
Ask multiple-choice question, get response inline. Ask multiple-choice question, get response inline.
@ -91,7 +92,7 @@ class ConnectionWizard(object):
""" """
opt_txt = "\n".join(f" {ind + 1}: {desc}" for ind, desc in enumerate(options)) opt_txt = "\n".join(f" {ind + 1}: {desc}" for ind, desc in enumerate(options))
self.display(opt_txt) self.display(opt_txt + "\n")
while True: while True:
resp = input(prompt).strip() resp = input(prompt).strip()
@ -109,13 +110,16 @@ class ConnectionWizard(object):
return selection return selection
self.display(" Select one of the given options.") self.display(" Select one of the given options.")
def ask_input(self, prompt="> ", default=None, verify=True, max_len=None): def ask_input(self, prompt=" > ", default=None, validator=None):
""" """
Get arbitrary input inline. Get arbitrary input inline.
Kwargs: Kwargs:
prompt (str): The display prompt. prompt (str): The display prompt.
default (str, optional): If empty input, use this. default (str): If empty input, use this.
validator (callable): If given, the input will be passed
into this callable. It should return True unless validation
fails (and is expected to echo why if so).
Returns: Returns:
inp (str): The input given, or default. inp (str): The input given, or default.
@ -123,11 +127,19 @@ class ConnectionWizard(object):
""" """
while True: while True:
resp = input(prompt).strip() resp = input(prompt).strip()
if not resp and default: if not resp and default:
resp = str(default) resp = str(default)
if resp.lower() in ('q', 'quit'):
sys.exit()
if resp.lower() == 'none': if resp.lower() == 'none':
resp = '' resp = ''
if validator and not validator(resp):
continue
ok = input("\n Leave blank? [Y]/N: ") ok = input("\n Leave blank? [Y]/N: ")
if ok.lower() in ('n', 'no'): if ok.lower() in ('n', 'no'):
continue continue
@ -135,18 +147,15 @@ class ConnectionWizard(object):
sys.exit() sys.exit()
return resp return resp
if verify: if validator and not validator(resp):
self.display(resp) continue
if max_len:
nlen = len(resp) self.display(resp)
if nlen > max_len: ok = input("\n Is this correct? [Y]/N: ")
self.display(f" This text is {nlen} characters long. Max is {max_len}.") if ok.lower() in ("n", "no"):
continue continue
ok = input("\n Is the above looking correct? [Y]/N: ") elif ok.lower() in ('q', 'quit'):
if ok.lower() in ("n", "no"): sys.exit()
continue
elif ok.lower() in ('q', 'quit'):
sys.exit()
return resp return resp
@ -155,10 +164,12 @@ def node_start(wizard):
This wizard helps activate external networks with Evennia. It will create This wizard helps activate external networks with Evennia. It will create
a config that will be attached to the bottom of the game settings file. a config that will be attached to the bottom of the game settings file.
Use `quit` at any time to abort and throw away any changes. Make sure you have at least started the game once before continuing!
Use `quit` at any time to abort and throw away unsaved changes.
""" """
options = { options = {
"1": ("Add game to Evennia game index (also for closed dev games)", "1": ("Add game to Evennia game index (also for closed-dev games)",
node_game_index_start, {}), node_game_index_start, {}),
"2": ("Add MSSP information (for mud-list crawlers)", "2": ("Add MSSP information (for mud-list crawlers)",
node_mssp_start, {}), node_mssp_start, {}),
@ -231,9 +242,18 @@ def node_game_index_fields(wizard, status=None):
{sdesc_default} {sdesc_default}
""" """
def sdesc_validator(inp):
tmax = 255
tlen = len(inp)
if tlen > 255:
print(f"The short desc must be shorter than {tmax} characters (was {tlen}).")
wizard.ask_continue()
return False
return True
wizard.display(text) wizard.display(text)
wizard.game_index_listing['short_description'] = \ wizard.game_index_listing['short_description'] = \
wizard.ask_input(default=sdesc_default, max_len=255) wizard.ask_input(default=sdesc_default, validator=sdesc_validator)
# long desc # long desc
@ -265,9 +285,16 @@ def node_game_index_fields(wizard, status=None):
{listing_default} {listing_default}
""" """
def contact_validator(inp):
if not inp or "@" not in inp:
print("This should be an email and cannot be blank.")
wizard.ask_continue()
return False
return True
wizard.display(text) wizard.display(text)
wizard.game_index_listing['listing_contact'] = \ wizard.game_index_listing['listing_contact'] = \
wizard.ask_input(default=listing_default) wizard.ask_input(default=listing_default, validator=contact_validator)
# telnet hostname # telnet hostname
@ -359,17 +386,29 @@ def node_game_index_fields(wizard, status=None):
def node_mssp_start(wizard): def node_mssp_start(wizard):
mssp_module = mod_import(settings.MSSP_META_MODULE)
filename = mssp_module.__file__
text = f""" text = f"""
MSSP (Mud Server Status Protocol) allows online MUD-listing sites/crawlers MSSP (Mud Server Status Protocol) allows online MUD-listing sites/crawlers
to continuously monitor your game and list information about it. Some of to continuously monitor your game and list information about it. Some of
this, like active player-count, Evennia will automatically add for you, this, like active player-count, Evennia will automatically add for you,
whereas many fields is info about your game. whereas many fields are manually added info about your game.
To use MSSP you should generally have a publicly open game that external To use MSSP you should generally have a publicly open game that external
players can connect to. players can connect to. You also need to register at a MUD listing site to
tell them to list your game.
MSSP has a large number of configuration options and we found it was simply
a lot easier to set them in a file rather than using this wizard. So to
configure MSSP, edit the empty template listing found here:
'{filename}'
""" """
wizard.mssp_table wizard.display(text)
wizard.ask_continue()
node_start(wizard)
# Admin # Admin
@ -378,7 +417,30 @@ def _save_changes(wizard):
""" """
Perform the save Perform the save
""" """
print("saving!")
# add import statement to settings file
import_stanza = "from .connection_settings import *"
setting_module = mod_import("server.conf.settings")
with open(setting_module.__file__, 'r+') as f:
txt = f.read() # moves pointer to end of file
if import_stanza not in txt:
# add to the end of the file
f.write("\n\n"
"try:\n"
" # Created by the `evennia connections` wizard\n"
f" {import_stanza}\n"
"except ImportError:\n"
" pass")
connect_settings_file = path.join(settings.GAME_DIR,
"server", "conf", "connection_settings.py")
with open(connect_settings_file, 'w') as f:
f.write("# This file is auto-generated by the `evennia connections` wizard.\n"
"# Don't edit manually, your changes will be overwritten.\n\n")
f.write(wizard.save_output)
wizard.display(f"saving to {connect_settings_file} ...")
def node_view_and_apply_settings(wizard): def node_view_and_apply_settings(wizard):
""" """
@ -393,18 +455,22 @@ def node_view_and_apply_settings(wizard):
if wizard.game_index_listing != settings.GAME_INDEX_LISTING: if wizard.game_index_listing != settings.GAME_INDEX_LISTING:
game_index_txt = "No changes to save for Game Index." game_index_txt = "No changes to save for Game Index."
else: else:
game_index_txt = pp.pformat(wizard.game_index_listing) game_index_txt = ("GAME_INDEX_ENABLED = True\n"
"GAME_INDEX_LISTING = \\\n" +
pp.pformat(wizard.game_index_listing))
saves = True saves = True
text = game_index_txt text = game_index_txt
print("- Game index:\n" + text) wizard.display(f"Settings to save:\n\n{text}")
if saves: if saves:
if wizard.ask_yesno("Do you want to save these settings?") == 'yes': if wizard.ask_yesno("Do you want to save these settings?") == 'yes':
wizard.save_output = text
_save_changes(wizard) _save_changes(wizard)
wizard.display("... saved!")
else: else:
print("Cancelled. Returning ...") wizard.display("... cancelled.")
wizard.ask_continue() wizard.ask_continue()
node_start(wizard) node_start(wizard)

View file

@ -50,6 +50,8 @@ def check_errors(settings):
if hasattr(settings, "ACCOUNT_TYPECLASS_PATHS"): if hasattr(settings, "ACCOUNT_TYPECLASS_PATHS"):
raise DeprecationWarning(deprstring % "ACCOUNT_TYPECLASS_PATHS") raise DeprecationWarning(deprstring % "ACCOUNT_TYPECLASS_PATHS")
if hasattr(settings, "CHANNEL_TYPECLASS_PATHS"): if hasattr(settings, "CHANNEL_TYPECLASS_PATHS"):
raise DeprecationWarning(deprstring % "CHANNEL_TYPECLASS_PATHS")
if hasattr(settings, "SEARCH_MULTIMATCH_SEPARATOR"):
raise DeprecationWarning( raise DeprecationWarning(
"settings.SEARCH_MULTIMATCH_SEPARATOR was replaced by " "settings.SEARCH_MULTIMATCH_SEPARATOR was replaced by "
"SEARCH_MULTIMATCH_REGEX and SEARCH_MULTIMATCH_TEMPLATE. " "SEARCH_MULTIMATCH_REGEX and SEARCH_MULTIMATCH_TEMPLATE. "

View file

@ -66,6 +66,7 @@ class EvenniaGameIndexClient(object):
'Failed to send game details to Evennia Game Index. HTTP ' 'Failed to send game details to Evennia Game Index. HTTP '
'status code was %s. Message was: %s' % (status_code, response_body) 'status code was %s. Message was: %s' % (status_code, response_body)
) )
if status_code == 400 and self._on_bad_request: if status_code == 400 and self._on_bad_request:
# Improperly formed request. Defer to the callback as far as what # Improperly formed request. Defer to the callback as far as what
# to do. Probably not a great idea to continue attempting to send # to do. Probably not a great idea to continue attempting to send

View file

@ -18,6 +18,9 @@ MSSP = b'\x46'
MSSP_VAR = b'\x01' MSSP_VAR = b'\x01'
MSSP_VAL = b'\x02' MSSP_VAL = b'\x02'
# try to get the customized mssp info, if it exists.
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
class Mssp(object): class Mssp(object):
""" """
@ -109,7 +112,8 @@ class Mssp(object):
} }
# update the static table with the custom one # update the static table with the custom one
self.mssp_table.update(settings.MSSP_TABLE) if MSSPTable_CUSTOM:
self.mssp_table.update(MSSPTable_CUSTOM)
varlist = '' varlist = ''
for variable, value in self.mssp_table.items(): for variable, value in self.mssp_table.items():

View file

@ -39,7 +39,7 @@ class TestDeprecations(TestCase):
"CHARACTER_DEFAULT_HOME", "OBJECT_TYPECLASS_PATHS", "SCRIPT_TYPECLASS_PATHS", "CHARACTER_DEFAULT_HOME", "OBJECT_TYPECLASS_PATHS", "SCRIPT_TYPECLASS_PATHS",
"ACCOUNT_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS", "SEARCH_MULTIMATCH_SEPARATOR", "ACCOUNT_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS", "SEARCH_MULTIMATCH_SEPARATOR",
"TIME_SEC_PER_MIN", "TIME_MIN_PER_HOUR", "TIME_HOUR_PER_DAY", "TIME_DAY_PER_WEEK", "TIME_SEC_PER_MIN", "TIME_MIN_PER_HOUR", "TIME_HOUR_PER_DAY", "TIME_DAY_PER_WEEK",
"TIME_WEEK_PER_MONTH", "TIME_MONTH_PER_YEAR") "TIME_WEEK_PER_MONTH", "TIME_MONTH_PER_YEAR", "GAME_DIRECTORY_LISTING")
def test_check_errors(self): def test_check_errors(self):
""" """

View file

@ -25,6 +25,9 @@ import sys
# This is the name of your game. Make it catchy! # This is the name of your game. Make it catchy!
SERVERNAME = "Evennia" SERVERNAME = "Evennia"
# Short one-sentence blurb describing your game. Shown under the title
# on the website and could be used in online listings of your game etc.
GAME_SLOGAN = "Python MU* creation system"
# Lockdown mode will cut off the game from any external connections # Lockdown mode will cut off the game from any external connections
# and only allow connections from localhost. Requires a cold reboot. # and only allow connections from localhost. Requires a cold reboot.
LOCKDOWN_MODE = False LOCKDOWN_MODE = False
@ -343,6 +346,9 @@ SERVER_SERVICES_PLUGIN_MODULES = ["server.conf.server_services_plugins"]
# main Evennia Portal application when the Portal is initiated. # main Evennia Portal application when the Portal is initiated.
# It will be called last in the startup sequence. # It will be called last in the startup sequence.
PORTAL_SERVICES_PLUGIN_MODULES = ["server.conf.portal_services_plugins"] PORTAL_SERVICES_PLUGIN_MODULES = ["server.conf.portal_services_plugins"]
# Module holding MSSP meta data. This is used by MUD-crawlers to determine
# what type of game you are running, how many accounts you have etc.
MSSP_META_MODULE = "server.conf.mssp"
# Module for web plugins. # Module for web plugins.
WEB_PLUGINS_MODULE = "server.conf.web_plugins" WEB_PLUGINS_MODULE = "server.conf.web_plugins"
# Tuple of modules implementing lock functions. All callable functions # Tuple of modules implementing lock functions. All callable functions
@ -683,13 +689,19 @@ CHANNEL_CONNECTINFO = None
# External Connections # External Connections
###################################################################### ######################################################################
# Note: You do *not* have to make your MUD open to
# the public to use the external connections, they
# operate as long as you have an internet connection,
# just like stand-alone chat clients.
# The Evennia Game Index is a dynamic listing of Evennia games. You can add your game # The Evennia Game Index is a dynamic listing of Evennia games. You can add your game
# to this list also if it is in closed pre-alpha development. # to this list also if it is in closed pre-alpha development.
GAME_INDEX_ENABLED = False GAME_INDEX_ENABLED = False
# This dict # This dict
GAME_INDEX_LISTING = { GAME_INDEX_LISTING = {
'game_status': 'closed-dev', # closed-dev, pre-alpha, pre-alpha, alpha, beta or launched 'game_name': SERVERNAME,
'short_description': '', 'game_status': 'pre-alpha', # pre-alpha, alpha, beta or launched
'short_description': GAME_SLOGAN,
'long_description': '', 'long_description': '',
'listing_contact': '', # email 'listing_contact': '', # email
'telnet_hostname': '', # mygame.com 'telnet_hostname': '', # mygame.com
@ -697,57 +709,11 @@ GAME_INDEX_LISTING = {
'game_website': '', # http://mygame.com 'game_website': '', # http://mygame.com
'web_client_url': '' # http://mygame.com/webclient 'web_client_url': '' # http://mygame.com/webclient
} }
# MSSP (Mud Server Status Protocol) is used by MUD-crawlers to determine
# what type of game you are running, how many players you have etc. Some of
# this (like server name and current number of players) is handled by Evennia
# automatically, other fields are set by you.
MSSP_TABLE = {
"HOSTNAME": "", "PORT": "", # telnet host/port
"CONTACT": "", "CREATED": "", # email, year of game creation
"IP": "", "ICON": "", # ip address; url to icon 32x32or larger; <32kb.
"LANGUAGE": "English", "LOCATION": "", # server country location, like "Sweden"
"MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "www.evennia.com",
"GENRE": "None", # Adult, Fantasy, Historical, Horror, Modern, None, or Science Fiction
"GAMEPLAY": "None", # Adventure, Educational, Hack and Slash, None,
# Player versus Player, Player versus Environment,
# Roleplaying, Simulation, Social or Strategy
"STATUS": "Alpha", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"SUBGENRE": "None", # Freeform, like LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available.
# use 0 if not applicable or off
"AREAS": "0", "HELPFILES": "0", "MOBILES": "0", "OBJECTS": "0",
"ROOMS": "0", "CLASSES": "0", "LEVELS": "0", "RACES": "0", "SKILLS": "0",
"PAY TO PLAY": "0", "PAY FOR PERKS": "0",
"HIRING BUILDERS": "0", "HIRING CODERS": "0",
"DBSIZE": "0", "EXITS": "0", "EXTRA DESCRIPTIONS": "0",
"MUDPROGS": "0", "MUDTRIGS": "0", "RESETS": "0",
"ADULT MATERIAL": "0", "MULTICLASSING": "0", "NEWBIE FRIENDLY": "0", "PLAYER CITIES": "0",
"PLAYER CLANS": "0", "PLAYER CRAFTING": "0", "PLAYER GUILDS": "0",
"EQUIPMENT SYSTEM": "None", # "None", "Level", "Skill", "Both"
"MULTIPLAYING": "None", # "None", "Restricted", "Full"
"PLAYERKILLING": "None", # "None", "Restricted", "Full"
"QUEST SYSTEM": "None", # "None", "Immortal Run", "Automated", "Integrated"
"ROLEPLAYING": "None", # "None", "Accepted", "Encouraged", "Enforced"
"TRAINING SYSTEM": "None", # "None", "Level", "Skill", "Both"
"WORLD ORIGINALITY": "None", # "All Stock", "Mostly Stock", "Mostly Original", "All Original"
}
# Note: You do *not* have to make your MUD open to
# the public to use the external connections, they
# operate as long as you have an internet connection,
# just like stand-alone chat clients. IRC requires
# that you have twisted.words installed.
# Evennia can connect to external IRC channels and # Evennia can connect to external IRC channels and
# echo what is said on the channel to IRC and vice # echo what is said on the channel to IRC and vice
# versa. Obs - make sure the IRC network allows bots. # versa. Obs - make sure the IRC network allows bots.
# When enabled, command @irc2chan will be available in-game # When enabled, command @irc2chan will be available in-game
# IRC requires that you have twisted.words installed.
IRC_ENABLED = False IRC_ENABLED = False
# RSS allows to connect RSS feeds (from forum updates, blogs etc) to # RSS allows to connect RSS feeds (from forum updates, blogs etc) to
# an in-game channel. The channel will be updated when the rss feed # an in-game channel. The channel will be updated when the rss feed
@ -757,7 +723,6 @@ IRC_ENABLED = False
# http://code.google.com/p/feedparser/) # http://code.google.com/p/feedparser/)
RSS_ENABLED = False RSS_ENABLED = False
RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes
# Grapevine (grapevine.haus) is a network for listing MUDs as well as allow # Grapevine (grapevine.haus) is a network for listing MUDs as well as allow
# users of said MUDs to communicate with each other on shared channels. To use, # users of said MUDs to communicate with each other on shared channels. To use,
# your game must first be registered by logging in and creating a game entry at # your game must first be registered by logging in and creating a game entry at