Start adding MSSP wizard info

This commit is contained in:
Griatch 2019-06-27 12:16:25 +02:00
parent e2bd2b4c5a
commit beffaa4753
5 changed files with 484 additions and 101 deletions

View file

@ -169,6 +169,8 @@
- Evscaperoom - a full puzzle engine for making multiplayer escape rooms in Evennia. Used to make
the entry for the MUD-Coder's Guild's 2019 Game Jam with the theme "One Room", where it ranked #1.
- Evennia game-index client no longer a contrib - moved into server core and configured with new
setting `GAME_INDEX_ENABLED`.
- The `extended_room` contrib saw some backwards-incompatible refactoring:
+ All commands now begin with `CmdExtendedRoom`. So before it was `CmdExtendedLook`, now
it's `CmdExtendedRoomLook` etc.

View file

@ -0,0 +1,410 @@
"""
Link Evennia to external resources (wizard plugin for evennia_launcher)
"""
import sys
import pprint
from django.conf import settings
from evennia.utils.utils import list_to_string
class ConnectionWizard(object):
def __init__(self):
self.data = {}
self.prev_node = None
def display(self, text):
"Show text"
print(text)
def ask_continue(self):
"'Press return to continue'-prompt"
input(" (Press return to continue)")
def ask_node(self, options, prompt="Enter choice: ", default=None):
"""
Retrieve options and jump to different menu nodes
Args:
options (dict): Node options on the form {key: (desc, callback), }
prompt (str, optional): Question to ask
default (str, optional): Default value to use if user hits return.
"""
opt_txt = "\n".join(f" {key}: {desc}" for key, (desc, _, _) in options.items())
self.display(opt_txt)
while True:
resp = input(prompt).strip()
if not resp:
if default:
resp = str(default)
if resp.lower() in options:
self.display(f" Selected '{resp}'.")
desc, callback, kwargs = options[resp.lower()]
callback(self, **kwargs)
elif resp.lower() in ("quit", "q"):
sys.exit()
elif resp:
# input, but nothing was recognized
self.display(" Choose one of: {}".format(list_to_string(list(options))))
def ask_yesno(self, prompt, default="yes"):
"""
Ask a yes/no question inline.
Kwargs:
prompt (str): The prompt to ask.
default (str): "yes" or "no", used if pressing return.
Returns:
reply (str): Either 'yes' or 'no'.
"""
prompt = prompt + (" [Y]/N? " if default == "yes" else " Y/[N]? ")
while True:
resp = input(prompt).lstrip().lower()
if not resp:
resp = default.lower()
if resp in ("yes", "y"):
self.display(" Answered Yes.")
return "yes"
elif resp in ("no", "n"):
self.display(" Answered No.")
return "no"
elif resp.lower() in ("quit", "q"):
sys.exit()
def ask_choice(self, prompt="> ", options=None, default=None):
"""
Ask multiple-choice question, get response inline.
Kwargs:
prompt (str): Input prompt.
options (list): List of options. Will be indexable by sequence number 1...
default (int): The list index+1 of the default choice, if any
Returns:
reply (str): The answered reply.
"""
opt_txt = "\n".join(f" {ind + 1}: {desc}" for ind, desc in enumerate(options))
self.display(opt_txt)
while True:
resp = input(prompt).strip()
if not resp:
if default:
return options[int(default)]
if resp.lower() in ("quit", "q"):
sys.exit()
if resp.isdigit():
resp = int(resp) - 1
if 0 <= resp < len(options):
selection = options[resp]
self.display(f" Selected '{selection}'.")
return selection
self.display(" Select one of the given options.")
def ask_input(self, prompt="> ", default=None, verify=True, max_len=None):
"""
Get arbitrary input inline.
Kwargs:
prompt (str): The display prompt.
default (str, optional): If empty input, use this.
Returns:
inp (str): The input given, or default.
"""
while True:
resp = input(prompt).strip()
if not resp and default:
resp = str(default)
if resp.lower() == 'none':
resp = ''
ok = input("\n Leave blank? [Y]/N: ")
if ok.lower() in ('n', 'no'):
continue
elif ok.lower() in ('q', 'quit'):
sys.exit()
return resp
if verify:
self.display(resp)
if max_len:
nlen = len(resp)
if nlen > max_len:
self.display(f" This text is {nlen} characters long. Max is {max_len}.")
continue
ok = input("\n Is the above looking correct? [Y]/N: ")
if ok.lower() in ("n", "no"):
continue
elif ok.lower() in ('q', 'quit'):
sys.exit()
return resp
def node_start(wizard):
text = """
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.
Use `quit` at any time to abort and throw away any changes.
"""
options = {
"1": ("Add game to Evennia game index (also for closed dev games)",
node_game_index_start, {}),
"2": ("Add MSSP information (for mud-list crawlers)",
node_mssp_start, {}),
"3": ("View and Save created settings",
node_view_and_apply_settings, {}),
}
wizard.display(text)
wizard.ask_node(options)
# Evennia game index
def node_game_index_start(wizard, **kwargs):
text = f"""
The Evennia game index (http://games.evennia.com) lists both active Evennia
games as well as games in various stages of development.
You can put up your game in the index also if you are not (yet) open for
players. If so, put 'None' for the connection details. Just tell us you
are out there and make us excited about your upcoming game!
Please check the listing online first to see that your exact game name is
not colliding with an existing game-name in the list (be nice!).
"""
wizard.display(text)
if wizard.ask_yesno("Continue adding/editing an Index entry?") == 'yes':
node_game_index_fields(wizard)
else:
node_start(wizard)
def node_game_index_fields(wizard, status=None):
# reset the listing if needed
if not hasattr(wizard, "game_index_listing"):
wizard.game_index_listing = settings.GAME_INDEX_LISTING
# game status
status_default = wizard.game_index_listing['game_status']
text = f"""
What is the status of your game?
- pre-alpha: a game in its very early stages, mostly unfinished or unstarted
- alpha: a working concept, probably lots of bugs and incomplete features
- beta: a working game, but expect bugs and changing features
- launched: a full, working game that may still be expanded upon and improved later
Current value:
{status_default}
"""
options = ["pre-alpha", "alpha", "beta", "launched"]
wizard.display(text)
wizard.game_index_listing['game_status'] = \
wizard.ask_choice("Select one: ", options)
# short desc
sdesc_default = wizard.game_index_listing.get('short_description', None)
text = f"""
Enter a short description of your game. Make it snappy and interesting!
This should be at most one or two sentences (255 characters) to display by
'{settings.SERVERNAME}' in the main game list. Line breaks will be ignored.
Current value:
{sdesc_default}
"""
wizard.display(text)
wizard.game_index_listing['short_description'] = \
wizard.ask_input(default=sdesc_default, max_len=255)
# long desc
long_default = wizard.game_index_listing.get("long_description", None)
text = f"""
Enter a longer, full-length description. This will be shown when clicking
on your game's listing. You can use \\n to create line breaks and may use
Markdown formatting like *bold*, _italic_, [linkname](http://link) etc.
Current value:
{long_default}
"""
wizard.display(text)
wizard.game_index_listing['long_description'] = \
wizard.ask_input(default=long_default)
# listing contact
listing_default = wizard.game_index_listing.get("listing_contact", None)
text = f"""
Enter a listing email-contact. This will not be visible in the listing, but
allows us to get in touch with you should there be some listing issue (like
a name collision) or some bug with the listing (us actually using this is
likely to be somewhere between super-rarely and never).
Current value:
{listing_default}
"""
wizard.display(text)
wizard.game_index_listing['listing_contact'] = \
wizard.ask_input(default=listing_default)
# telnet hostname
hostname_default = wizard.game_index_listing.get('telnet_hostname', None)
text = f"""
Enter the hostname to which third-party telnet mud clients can connect to
your game. This would be the name of the server your game is hosted on,
like `coolgame.games.com`, or `mygreatgame.se`.
Write 'None' if you are not offering public telnet connections at this time.
Current value:
{hostname_default}
"""
wizard.display(text)
wizard.game_index_listing['telnet_hostname'] = \
wizard.ask_input(default=hostname_default)
# telnet port
port_default = wizard.game_index_listing.get('telnet_port', None)
text = f"""
Enter the main telnet port. The Evennia default is 4000. You can change
this with the TELNET_PORTS server setting.
Write 'None' if you are not offering public telnet connections at this time.
Current value:
{port_default}
"""
wizard.display(text)
wizard.game_index_listing['telnet_port'] = \
wizard.ask_input(default=port_default)
# website
website_default = wizard.game_index_listing.get('game_website', None)
text = f"""
Evennia is its own web server and runs your game's website. Enter the
URL of the website here, like http://yourwebsite.com, here.
Wtite 'None' if you are not offering a publicly visible website at this time.
Current value:
{website_default}
"""
wizard.display(text)
wizard.game_index_listing['game_website'] = \
wizard.ask_input(default=website_default)
# webclient
webclient_default = wizard.game_index_listing.get('web_client_url', None)
text = f"""
Evennia offers its own native webclient. Normally it will be found from the
game homepage at something like http://yourwebsite.com/webclient. Enter
your specific URL here (when clicking this link you should launch into the
web client)
Wtite 'None' if you don't want to list a publicly accessible webclient.
Current value:
{webclient_default}
"""
wizard.display(text)
wizard.game_index_listing['web_client_url'] = \
wizard.ask_input(default=webclient_default)
if not (wizard.game_index_listing.get('web_client_url') or
(wizard.game_index_listing.get('telnet_host'))):
wizard.display(
"\nNote: You have not specified any connection options. This means "
"your game \nwill be marked as being in 'closed development' in "
"the index.")
wizard.display("\nDon't forget to inspect and save your changes.")
node_start(wizard)
# MSSP
def node_mssp_start(wizard):
text = f"""
MSSP (Mud Server Status Protocol) allows online MUD-listing sites/crawlers
to continuously monitor your game and list information about it. Some of
this, like active player-count, Evennia will automatically add for you,
whereas many fields is info about your game.
To use MSSP you should generally have a publicly open game that external
players can connect to.
"""
wizard.mssp_table
# Admin
def _save_changes(wizard):
"""
Perform the save
"""
print("saving!")
def node_view_and_apply_settings(wizard):
"""
Inspect and save the data gathered in the other nodes
"""
pp = pprint.PrettyPrinter(indent=4)
saves = False
game_index_txt = "No changes to save for Game Index."
if hasattr(wizard, "game_index_listing"):
if wizard.game_index_listing != settings.GAME_INDEX_LISTING:
game_index_txt = "No changes to save for Game Index."
else:
game_index_txt = pp.pformat(wizard.game_index_listing)
saves = True
text = game_index_txt
print("- Game index:\n" + text)
if saves:
if wizard.ask_yesno("Do you want to save these settings?") == 'yes':
_save_changes(wizard)
else:
print("Cancelled. Returning ...")
wizard.ask_continue()
node_start(wizard)

View file

@ -1823,6 +1823,16 @@ def run_dummyrunner(number_of_dummies):
pass
def run_connect_wizard():
"""
Run the linking wizard, for adding new external connections.
"""
from .connection_wizard import ConnectionWizard, node_start
wizard = ConnectionWizard()
node_start(wizard)
def list_settings(keys):
"""
Display the server settings. We only display the Evennia specific
@ -2082,7 +2092,7 @@ def main():
init_game_directory(CURRENT_DIR, check_db=True)
run_menu()
elif option in ('status', 'info', 'start', 'istart', 'ipstart', 'reload', 'restart', 'reboot',
'reset', 'stop', 'sstop', 'kill', 'skill', 'sstart'):
'reset', 'stop', 'sstop', 'kill', 'skill', 'sstart', 'connections'):
# operate the server directly
if not SERVER_LOGFILE:
init_game_directory(CURRENT_DIR, check_db=True)
@ -2120,6 +2130,9 @@ def main():
print("This option is not supported on Windows.")
else:
kill(SERVER_PIDFILE, 'Server')
elif option == 'connections':
run_connect_wizard()
elif option != "noop":
# pass-through to django manager, but set things up first
check_db = False

View file

@ -19,10 +19,6 @@ MSSP_VAR = b'\x01'
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):
"""
Implements the MSSP protocol. Add this to a variable on the telnet
@ -86,109 +82,34 @@ class Mssp(object):
# Required fields
"NAME": "Evennia",
"NAME": settings.SERVERNAME,
"PLAYERS": self.get_player_count,
"UPTIME": self.get_uptime,
# Generic
"PORT": list(reversed(settings.TELNET_PORTS)), # most important port should be last in list
# Evennia auto-filled
"CRAWL DELAY": "-1",
"HOSTNAME": "", # current or new hostname
"PORT": ["4000"], # most important port should be last in list
"CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud
"CREATED": "", # year MUD was created
"ICON": "", # url to icon 32x32 or larger; <32kb.
"IP": "", # current or new IP address
"LANGUAGE": "", # name of language used, e.g. English
"LOCATION": "", # full English name of server country
"MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "www.evennia.com",
# Categorisation
"FAMILY": "Custom", # evennia goes under 'Custom'
"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": "Open Beta", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available.
# World
"AREAS": "0",
"HELPFILES": "0",
"MOBILES": "0",
"OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less
"CLASSES": "0", # use 0 if class-less
"LEVELS": "0", # use 0 if level-less
"RACES": "0", # use 0 if race-less
"SKILLS": "0", # use 0 if skill-less
# Protocols set to 1 or 0)
"CODEBASE": utils.get_evennia_version(mode='pretty'),
"FAMILY": "Custom",
"ANSI": "1",
"GMCP": "0",
"GMCP": "1" if settings.TELNET_OOB_ENABLED else "0",
"ATCP": "0",
"MCCP": "0",
"MCCP": "1",
"MCP": "0",
"MSDP": "0",
"MSDP": "1" if settings.TELNET_OOB_ENABLED else "0",
"MSP": "0",
"MXP": "0",
"MXP": "1",
"PUEBLO": "0",
"SSL": "1",
"SSL": "1" if settings.SSL_ENABLED else "0",
"UTF-8": "1",
"ZMP": "0",
"VT100": "0",
"XTERM 256 COLORS": "0",
# Commercial set to 1 or 0)
"PAY TO PLAY": "0",
"PAY FOR PERKS": "0",
# Hiring set to 1 or 0)
"HIRING BUILDERS": "0",
"HIRING CODERS": "0",
# Extended variables
# World
"DBSIZE": "0",
"EXITS": "0",
"EXTRA DESCRIPTIONS": "0",
"MUDPROGS": "0",
"MUDTRIGS": "0",
"RESETS": "0",
# Game (set to 1, 0 or one of the given alternatives)
"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"
"VT100": "1",
"XTERM 256 COLORS": "1",
}
# update the static table with the custom one
if MSSPTable_CUSTOM:
self.mssp_table.update(MSSPTable_CUSTOM)
self.mssp_table.update(settings.MSSP_TABLE)
varlist = ''
for variable, value in self.mssp_table.items():
@ -196,7 +117,8 @@ class Mssp(object):
value = value()
if utils.is_iter(value):
for partval in value:
varlist += MSSP_VAR + bytes(variable, 'utf-8') + MSSP_VAL + bytes(partval, 'utf-8')
varlist += (MSSP_VAR + bytes(variable, 'utf-8') +
MSSP_VAL + bytes(partval, 'utf-8'))
else:
varlist += MSSP_VAR + bytes(variable, 'utf-8') + MSSP_VAL + bytes(value, 'utf-8')

View file

@ -343,9 +343,6 @@ SERVER_SERVICES_PLUGIN_MODULES = ["server.conf.server_services_plugins"]
# main Evennia Portal application when the Portal is initiated.
# It will be called last in the startup sequence.
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.
WEB_PLUGINS_MODULE = "server.conf.web_plugins"
# Tuple of modules implementing lock functions. All callable functions
@ -682,6 +679,10 @@ DEFAULT_CHANNELS = [
# While the MudInfo channel will also receieve this, this channel is meant for non-staffers.
CHANNEL_CONNECTINFO = None
######################################################################
# External Connections
######################################################################
# 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.
GAME_INDEX_ENABLED = False
@ -692,15 +693,50 @@ GAME_INDEX_LISTING = {
'long_description': '',
'listing_contact': '', # email
'telnet_hostname': '', # mygame.com
'telnet_port': 1234,
'telnet_port': '', # 1234
'game_website': '', # http://mygame.com
'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",
######################################################################
# External Channel connections
######################################################################
"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