"""
Start menu
This is started from the `evscaperoom` command.
Here player user can set their own description as well as select to create a
new room (to start from scratch) or join an existing room (with other players).
"""
from evennia import EvMenu
from evennia.utils import create, justify, list_to_string, logger
from evennia.utils.evmenu import list_node
from .room import EvscapeRoom
from .utils import create_fantasy_word
# ------------------------------------------------------------
# Main menu
# ------------------------------------------------------------
_START_TEXT = """
|mEv|rScape|mRoom|n
|x- an escape-room experience using Evennia|n
You are |c{name}|n - {desc}|n.
Make a selection below.
"""
_CREATE_ROOM_TEXT = """
This will create a |ynew, empty room|n to challenge you.
Other players can be thrown in there at any time.
Remember that if you give up and are the last person to leave, that particular
room will be gone!
|yDo you want to create (and automatically join) a new room?|n")
"""
_JOIN_EXISTING_ROOM_TEXT = """
This will have you join an existing room ({roomname}).
This is {percent}% complete and has {nplayers} player(s) in it already:
{players}
|yDo you want to join this room?|n
"""
def _move_to_room(caller, raw_string, **kwargs):
"""
Helper to move a user to a room
"""
room = kwargs["room"]
room.msg_char(caller, f"Entering room |c'{room.name}'|n ...")
room.msg_room(caller, f"~You |c~were just tricked in here too!|n")
# we do a manual move since we don't want all hooks to fire.
old_location = caller.location
caller.location = room
room.at_object_receive(caller, old_location)
return "node_quit", {"quiet": True}
def _create_new_room(caller, raw_string, **kwargs):
# create a random name, retrying until we find
# a unique one
key = create_fantasy_word(length=5, capitalize=True)
while EvscapeRoom.objects.filter(db_key=key):
key = create_fantasy_word(length=5, capitalize=True)
room = create.create_object(EvscapeRoom, key=key)
# we must do this once manually for the new room
room.statehandler.init_state()
_move_to_room(caller, "", room=room)
nrooms = EvscapeRoom.objects.all().count()
logger.log_info(
f"Evscaperoom: {caller.key} created room '{key}' (#{room.id}). Now {nrooms} room(s) active."
)
room.log(f"JOIN: {caller.key} created and joined room")
return "node_quit", {"quiet": True}
def _get_all_rooms(caller):
"""
Get a list of all available rooms and store the mapping
between option and room so we get to it later.
"""
room_option_descs = []
room_map = {}
for room in EvscapeRoom.objects.all():
if not room.pk or room.db.deleting:
continue
stats = room.db.stats or {"progress": 0}
progress = int(stats["progress"])
nplayers = len(room.get_all_characters())
desc = (
f"Join room |c'{room.get_display_name(caller)}'|n "
f"(complete: {progress}%, players: {nplayers})"
)
room_map[desc] = room
room_option_descs.append(desc)
caller.ndb._menutree.room_map = room_map
return room_option_descs
def _select_room(caller, menuchoice, **kwargs):
"""
Get a room from the selection using the mapping we created earlier.
"""
room = caller.ndb._menutree.room_map[menuchoice]
return "node_join_room", {"room": room}
@list_node(_get_all_rooms, _select_room)
def node_start(caller, raw_string, **kwargs):
text = _START_TEXT.strip()
text = text.format(name=caller.key, desc=caller.db.desc)
# build a list of available rooms
options = (
{
"key": (
"|y[s]et your description|n",
"set your description",
"set",
"desc",
"description",
"s",
),
"goto": "node_set_desc",
},
{
"key": ("|y[c]reate/join a new room|n", "create a new room", "create", "c"),
"goto": "node_create_room",
},
{"key": ("|r[q]uit the challenge", "quit", "q"), "goto": "node_quit"},
)
return text, options
[docs]def node_set_desc(caller, raw_string, **kwargs):
current_desc = kwargs.get("desc", caller.db.desc)
text = (
"Your current description is\n\n " f' "{current_desc}"' "\n\nEnter your new description!"
)
def _temp_description(caller, raw_string, **kwargs):
desc = raw_string.strip()
if 5 < len(desc) < 40:
return None, {"desc": raw_string.strip()}
else:
caller.msg("|rYour description must be 5-40 characters long.|n")
return None
def _set_description(caller, raw_string, **kwargs):
caller.db.desc = kwargs.get("desc")
caller.msg("You set your description!")
return "node_start"
options = (
{"key": "_default", "goto": _temp_description},
{"key": ("|g[a]ccept", "a"), "goto": (_set_description, {"desc": current_desc})},
{"key": ("|r[c]ancel", "c"), "goto": "node_start"},
)
return text, options
[docs]def node_create_room(caller, raw_string, **kwargs):
text = _CREATE_ROOM_TEXT
options = (
{"key": ("|g[c]reate new room and start game|n", "c"), "goto": _create_new_room},
{"key": ("|r[a]bort and go back|n", "a"), "goto": "node_start"},
)
return text, options
[docs]def node_join_room(caller, raw_string, **kwargs):
room = kwargs["room"]
stats = room.db.stats or {"progress": 0}
players = [char.key for char in room.get_all_characters()]
text = _JOIN_EXISTING_ROOM_TEXT.format(
roomname=room.get_display_name(caller),
percent=int(stats["progress"]),
nplayers=len(players),
players=list_to_string(players),
)
options = (
{"key": ("|g[a]ccept|n (default)", "a"), "goto": (_move_to_room, kwargs)},
{"key": ("|r[c]ancel|n", "c"), "goto": "node_start"},
{"key": "_default", "goto": (_move_to_room, kwargs)},
)
return text, options
[docs]def node_quit(caller, raw_string, **kwargs):
quiet = kwargs.get("quiet")
text = ""
if not quiet:
text = "Goodbye for now!\n"
# we check an Attribute on the caller to see if we should
# leave the game entirely when leaving
if caller.db.evscaperoom_standalone:
from evennia import default_cmds
from evennia.commands import cmdhandler
cmdhandler.cmdhandler(
caller.ndb._menutree._session, "", cmdobj=default_cmds.CmdQuit(), cmdobj_key="@quit"
)
return text, None # empty options exit the menu
# access function
# ------------------------------------------------------------
# In-game Options menu
# ------------------------------------------------------------
def _set_thing_style(caller, raw_string, **kwargs):
room = caller.location
options = caller.attributes.get("options", category=room.tagcategory, default={})
options["things_style"] = kwargs.get("value", 2)
caller.attributes.add("options", options, category=room.tagcategory)
return None, kwargs # rerun node
def _toggle_screen_reader(caller, raw_string, **kwargs):
session = kwargs["session"]
# flip old setting
session.protocol_flags["SCREENREADER"] = not session.protocol_flags.get("SCREENREADER", False)
# sync setting with portal
session.sessionhandler.session_portal_sync(session)
return None, kwargs # rerun node
[docs]def node_options(caller, raw_string, **kwargs):
text = "|cOption menu|n\n('|wq|nuit' to return)"
room = caller.location
options = caller.attributes.get("options", category=room.tagcategory, default={})
things_style = options.get("things_style", 2)
session = kwargs["session"] # we give this as startnode_input when starting menu
screenreader = session.protocol_flags.get("SCREENREADER", False)
options = (
{
"desc": "{}No item markings (hard mode)".format(
"|g(*)|n " if things_style == 0 else "( ) "
),
"goto": (_set_thing_style, {"value": 0, "session": session}),
},
{
"desc": "{}Items marked as |yitem|n (with color)".format(
"|g(*)|n " if things_style == 1 else "( ) "
),
"goto": (_set_thing_style, {"value": 1, "session": session}),
},
{
"desc": "{}Items are marked as |y[item]|n (screenreader friendly)".format(
"|g(*)|n " if things_style == 2 else "( ) "
),
"goto": (_set_thing_style, {"value": 2, "session": session}),
},
{
"desc": "{}Screenreader mode".format("(*) " if screenreader else "( ) "),
"goto": (_toggle_screen_reader, kwargs),
},
)
return text, options
# access function