2024-05-21 04:59:26 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
from argparse import ArgumentParser, FileType
|
|
|
|
from sys import version_info
|
|
|
|
from tomllib import loads as toml_loads
|
|
|
|
from tomllib import TOMLDecodeError
|
|
|
|
from cmd import Cmd
|
|
|
|
from base64 import b64decode
|
|
|
|
|
|
|
|
__author__ = "Cheri Dawn"
|
|
|
|
__copyright__ = "Copyright 2024, Cheri Dawn"
|
2024-05-21 05:09:29 -04:00
|
|
|
__license__ = "GPL-3.0-only"
|
2024-09-02 15:29:51 -04:00
|
|
|
__version__ = "0.4.2.0"
|
2024-05-21 04:59:26 -04:00
|
|
|
__maintainer__ = "Cheri Dawn"
|
2024-09-02 15:29:51 -04:00
|
|
|
__status__ = "Alpha"
|
2024-05-21 04:59:26 -04:00
|
|
|
|
|
|
|
manifest = {}
|
2024-05-22 19:16:13 -04:00
|
|
|
metadata = {}
|
2024-05-21 04:59:26 -04:00
|
|
|
scenario = {}
|
|
|
|
rooms = {}
|
|
|
|
current_room_id = ""
|
2024-05-23 06:58:41 -04:00
|
|
|
inventory = {}
|
2024-05-21 04:59:26 -04:00
|
|
|
|
|
|
|
|
2024-05-22 14:25:22 -04:00
|
|
|
def parse_version(ver):
|
|
|
|
if len(ver) < 1:
|
|
|
|
raise ValueError("empty version string")
|
|
|
|
if ver[0] == "v":
|
|
|
|
ver = ver[1:]
|
|
|
|
try:
|
|
|
|
ver = [int(x) for x in ver.split(".")]
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError("non integer version")
|
|
|
|
return tuple(ver)
|
|
|
|
|
|
|
|
|
|
|
|
VERSION = parse_version(__version__)
|
|
|
|
|
|
|
|
|
2024-05-21 04:59:26 -04:00
|
|
|
def print_items(cur_room):
|
|
|
|
if "items" not in cur_room:
|
|
|
|
return
|
|
|
|
items = cur_room["items"]
|
|
|
|
for k, v in items.items():
|
2024-05-23 06:25:02 -04:00
|
|
|
if k not in manifest["items"]:
|
|
|
|
print(f"You notice {v} [{k}].")
|
|
|
|
elif not manifest["items"][k]["hidden"]:
|
2024-05-21 04:59:26 -04:00
|
|
|
print(f"You notice {v} [{k}].")
|
|
|
|
|
|
|
|
|
|
|
|
def print_interactables(cur_room):
|
|
|
|
if "interactables" not in cur_room:
|
|
|
|
return
|
|
|
|
inters = cur_room["interactables"]
|
|
|
|
if len(inters) == 0:
|
|
|
|
return
|
|
|
|
for inter in inters:
|
|
|
|
inter_dict = cur_room["interactables"][inter]
|
|
|
|
if "hidden" not in inter_dict:
|
|
|
|
if "name" not in inter_dict:
|
|
|
|
print("FATAL: "
|
|
|
|
"`name` doesn't exist "
|
|
|
|
f"in interactable '{inter}'.")
|
|
|
|
exit(2)
|
|
|
|
print(f"You see {inter_dict["name"]} [{inter}].")
|
|
|
|
elif not inter_dict["hidden"]:
|
|
|
|
print(f"You see {inter_dict["name"]} [{inter}].")
|
|
|
|
|
|
|
|
|
|
|
|
def print_exits(cur_room):
|
|
|
|
if "exits" not in cur_room:
|
|
|
|
return
|
|
|
|
exits = cur_room["exits"]
|
|
|
|
for k, v in exits.items():
|
|
|
|
if v not in rooms:
|
|
|
|
print("FATAL: "
|
|
|
|
f"room '{v}' does not exist.")
|
|
|
|
exit(2)
|
|
|
|
if "description" not in rooms[v]:
|
|
|
|
print("WARNING: "
|
2024-05-21 09:21:39 -04:00
|
|
|
f"room '{v}' ({k}) does not have a `description`.")
|
2024-05-21 04:59:26 -04:00
|
|
|
continue
|
|
|
|
print(f"Looking [{k}] you see {rooms[v]["description"]}")
|
|
|
|
|
|
|
|
|
|
|
|
class SetageShell(Cmd):
|
|
|
|
intro = ""
|
|
|
|
prompt = "> "
|
|
|
|
ruler = "~"
|
2024-08-31 22:48:34 -04:00
|
|
|
interacted_with = set()
|
|
|
|
rooms_been_in = set()
|
2024-05-21 04:59:26 -04:00
|
|
|
|
|
|
|
def cmdloop(self, intro=None):
|
|
|
|
print(self.intro)
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
super().cmdloop(intro="")
|
|
|
|
break
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print("^C")
|
|
|
|
|
|
|
|
def emptyline(self):
|
|
|
|
return
|
|
|
|
|
|
|
|
def do_EOF(self, arg):
|
|
|
|
"""Exit the game (does NOT save state)."""
|
|
|
|
return True
|
|
|
|
|
|
|
|
def do_exit(self, arg):
|
|
|
|
"""Exit the game (does NOT save state)."""
|
|
|
|
return True
|
|
|
|
|
|
|
|
def default(self, arg):
|
|
|
|
print("> I don't know how to do that...")
|
|
|
|
|
|
|
|
def do_examine(self, arg):
|
|
|
|
"""Examine a <thing>"""
|
|
|
|
global current_room_id
|
|
|
|
if current_room_id not in rooms:
|
|
|
|
print("FATAL: "
|
|
|
|
f"room '{current_room_id}' does not exist.")
|
|
|
|
exit(2)
|
|
|
|
cur_room = rooms[current_room_id]
|
|
|
|
if arg == "":
|
|
|
|
print("> What should I examine?")
|
|
|
|
return
|
|
|
|
elif arg in ("room", "this room"):
|
|
|
|
if "examine" not in cur_room:
|
|
|
|
print("This room doesn't seem to be all that interesting...")
|
|
|
|
return
|
|
|
|
print(cur_room["examine"])
|
|
|
|
print_items(cur_room)
|
|
|
|
print_interactables(cur_room)
|
|
|
|
print_exits(cur_room)
|
|
|
|
elif arg in ("inv", "inventory"):
|
|
|
|
if len(inventory) == 0:
|
|
|
|
print("Your inventory is empty.")
|
2024-05-23 06:58:41 -04:00
|
|
|
for k, v in inventory.items():
|
2024-05-21 04:59:26 -04:00
|
|
|
print(f"You have {v} [{k}].")
|
2024-05-23 06:58:41 -04:00
|
|
|
elif arg in inventory.keys():
|
2024-05-21 04:59:26 -04:00
|
|
|
if "examine" in manifest["items"][arg]:
|
|
|
|
print(manifest["items"][arg]["examine"])
|
|
|
|
else:
|
|
|
|
print("It doesn't seem to be all that interesting...")
|
2024-05-21 16:35:44 -04:00
|
|
|
elif arg:
|
|
|
|
if "items" not in cur_room and "interactables" not in cur_room:
|
|
|
|
print("> I don't see anything to examine here...")
|
|
|
|
return
|
|
|
|
if "items" in cur_room:
|
2024-05-23 04:02:22 -04:00
|
|
|
if arg in cur_room["items"].keys():
|
|
|
|
manifest["items"][arg]["hidden"] = False
|
|
|
|
if "examine" in manifest["items"][arg]:
|
|
|
|
print(manifest["items"][arg]["examine"])
|
2024-05-23 06:01:21 -04:00
|
|
|
return
|
2024-05-23 04:02:22 -04:00
|
|
|
else:
|
|
|
|
print("It doesn't seem to be all that interesting...")
|
2024-05-23 06:01:21 -04:00
|
|
|
return
|
2024-05-21 16:35:44 -04:00
|
|
|
if "interactables" in cur_room:
|
2024-05-23 04:02:22 -04:00
|
|
|
if arg in cur_room["interactables"].keys():
|
|
|
|
cur_room["interactables"][arg]["hidden"] = False
|
|
|
|
if "examine" in cur_room["interactables"][arg]:
|
|
|
|
print(cur_room["interactables"][arg]["examine"])
|
2024-05-23 06:01:21 -04:00
|
|
|
return
|
2024-05-23 04:02:22 -04:00
|
|
|
else:
|
|
|
|
print("It doesn't seem to be all that interesting...")
|
2024-05-23 06:01:21 -04:00
|
|
|
return
|
2024-08-23 04:36:46 -04:00
|
|
|
print("> I don't see anything like that...")
|
2024-05-21 04:59:26 -04:00
|
|
|
else:
|
|
|
|
print("> I don't see anything like that...")
|
|
|
|
|
|
|
|
def complete_examine(self, text, line, bidx, eidx):
|
|
|
|
global current_room_id
|
|
|
|
all_items = manifest["items"]
|
2024-05-22 19:16:13 -04:00
|
|
|
if "interactables" in rooms[current_room_id]:
|
|
|
|
all_items.update(rooms[current_room_id]["interactables"])
|
2024-05-23 06:58:41 -04:00
|
|
|
items = inventory.copy()
|
2024-05-22 19:16:13 -04:00
|
|
|
if "items" in rooms[current_room_id]:
|
|
|
|
items.update(rooms[current_room_id]["items"])
|
|
|
|
if "interactables" in rooms[current_room_id]:
|
|
|
|
items.update(rooms[current_room_id]["interactables"])
|
2024-05-21 04:59:26 -04:00
|
|
|
items = [x for x in items if (not all_items[x]["hidden"])]
|
|
|
|
if not text:
|
|
|
|
return list(items)
|
|
|
|
else:
|
|
|
|
return [comp for comp in items if comp.startswith(text)]
|
|
|
|
|
|
|
|
def do_ex(self, arg):
|
|
|
|
"""Examine a <thing>"""
|
|
|
|
self.do_examine(arg)
|
|
|
|
|
|
|
|
def complete_ex(self, text, line, bidx, eidx):
|
|
|
|
return self.complete_examine(text, line, bidx, eidx)
|
|
|
|
|
|
|
|
def do_ls(self, arg):
|
|
|
|
"""Examine the current room"""
|
|
|
|
self.do_examine("room")
|
|
|
|
|
|
|
|
def do_interact(self, arg):
|
|
|
|
"""Interact with a <thing>"""
|
|
|
|
global current_room_id, manifest
|
|
|
|
cur_room = manifest["rooms"][current_room_id]
|
2024-05-21 16:08:58 -04:00
|
|
|
if "interactables" not in cur_room:
|
|
|
|
print("> Nothing to interact with here...")
|
|
|
|
return
|
2024-05-21 04:59:26 -04:00
|
|
|
inters = cur_room["interactables"]
|
|
|
|
if arg in inters.keys():
|
|
|
|
inter = inters[arg]
|
|
|
|
can_activate = False
|
|
|
|
if "type" not in inter:
|
|
|
|
inter["type"] = "bare"
|
|
|
|
if inter["type"] == "bare":
|
|
|
|
if "interactable" in inter:
|
|
|
|
if inter["interactable"]:
|
|
|
|
can_activate = True
|
|
|
|
else:
|
|
|
|
can_activate = True
|
2024-05-23 06:58:41 -04:00
|
|
|
elif inter["type"] == "item check":
|
|
|
|
if "check" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"`check` id not in interactable with type `item` "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if inter["check"] in inventory.keys():
|
|
|
|
can_activate = True
|
|
|
|
else:
|
|
|
|
can_activate = False
|
|
|
|
elif inter["type"] == "item take":
|
|
|
|
if "check" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"`check` id not in interactable with type `item` "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if inter["check"] in inventory.keys():
|
|
|
|
can_activate = True
|
|
|
|
inventory.pop(inter["check"])
|
|
|
|
else:
|
|
|
|
can_activate = False
|
|
|
|
elif inter["type"] == "input":
|
|
|
|
if "check" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"`check` string not in interactable "
|
|
|
|
"with type `input` "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if "prompt" not in inter:
|
|
|
|
prompt = "> "
|
|
|
|
else:
|
|
|
|
prompt = inter["prompt"]
|
|
|
|
try:
|
|
|
|
i = input(prompt).strip().casefold()
|
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
|
|
print()
|
|
|
|
if i == inter["check"]:
|
|
|
|
can_activate = True
|
|
|
|
elif inter["type"] == "exact input":
|
|
|
|
if "check" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"`check` string not in interactable "
|
|
|
|
"with type `exact input` "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if "prompt" not in inter:
|
|
|
|
prompt = "> "
|
|
|
|
else:
|
|
|
|
prompt = inter["prompt"]
|
|
|
|
try:
|
|
|
|
i = input(prompt)
|
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
|
|
print()
|
|
|
|
if i == inter["check"]:
|
|
|
|
can_activate = True
|
|
|
|
else:
|
|
|
|
print("FATAL: "
|
|
|
|
f"invalid `type` '{inter["type"]}' "
|
|
|
|
f"in interactable '{arg}'")
|
2024-05-21 04:59:26 -04:00
|
|
|
if can_activate:
|
2024-08-31 22:48:34 -04:00
|
|
|
self.interacted_with.add(arg)
|
2024-05-21 04:59:26 -04:00
|
|
|
if "target_room" not in inter:
|
|
|
|
target_room = current_room_id
|
|
|
|
else:
|
|
|
|
target_room = inter["target_room"]
|
|
|
|
|
|
|
|
if "action" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"`action` doesn't exist "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
match inter["action"]:
|
|
|
|
case "add exit":
|
|
|
|
if "target_exit" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `target_exit` doesn't exist "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if not isinstance(inter["target_exit"], dict):
|
|
|
|
print("FATAL: "
|
|
|
|
"`target_exit` is not a table "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
if "exits" not in manifest["rooms"][target_room]:
|
|
|
|
manifest["rooms"][target_room]["exits"] = dict()
|
|
|
|
manifest["rooms"][target_room]["exits"].update(
|
|
|
|
inter["target_exit"])
|
|
|
|
|
|
|
|
case "add exits":
|
|
|
|
if "target_exits" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `target_exits` doesn't exist "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if not (isinstance(inter["target_exits"], dict)
|
|
|
|
or isinstance(inter["target_exits"], list)):
|
|
|
|
print("FATAL: "
|
|
|
|
"`target_exits` is not a table or a list "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
if "exits" not in manifest["rooms"][target_room]:
|
|
|
|
manifest["rooms"][target_room]["exits"] = dict()
|
|
|
|
if isinstance(inter["target_exits"], dict):
|
|
|
|
manifest["rooms"][target_room]["exits"].update(
|
|
|
|
inter["target_exits"])
|
|
|
|
elif isinstance(inter["target_exits"], list):
|
|
|
|
for ex in inter["target_exits"]:
|
|
|
|
manifest["rooms"][target_room]["exits"].update(
|
|
|
|
ex)
|
|
|
|
|
|
|
|
case "add item":
|
|
|
|
if "target_item" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `target_item` doesn't exist "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if not isinstance(inter["target_item"], dict):
|
|
|
|
print("FATAL: "
|
|
|
|
"`target_item` is not a table "
|
|
|
|
f"interactable '{arg}'")
|
|
|
|
if "items" not in manifest["rooms"][target_room]:
|
|
|
|
manifest["rooms"][target_room]["items"] = dict()
|
|
|
|
manifest["rooms"][target_room]["items"].update(
|
|
|
|
inter["target_item"])
|
|
|
|
|
|
|
|
case "add items":
|
|
|
|
if "target_items" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `target_items` doesn't exist "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
if not (isinstance(inter["target_items"], dict)
|
|
|
|
or isinstance(inter["target_items"], list)):
|
|
|
|
print("FATAL: "
|
|
|
|
"`target_items` is not a table or a list "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
if "items" not in manifest["rooms"][target_room]:
|
|
|
|
manifest["rooms"][target_room]["items"] = dict()
|
|
|
|
if isinstance(inter["target_items"], dict):
|
|
|
|
manifest["rooms"][target_room]["items"].update(
|
|
|
|
inter["target_items"])
|
|
|
|
elif isinstance(inter["target_items"], list):
|
|
|
|
for item in inter["target_items"]:
|
|
|
|
manifest["rooms"][target_room]["items"].update(
|
|
|
|
item)
|
|
|
|
|
|
|
|
case "reveal interactable":
|
|
|
|
if "target_interactable" not in inter:
|
|
|
|
print("FATAL: "
|
|
|
|
"id `target_interactable` doesn't exist "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
exit(2)
|
|
|
|
target_interactable = inter["target_interactable"]
|
|
|
|
if not isinstance(target_interactable, str):
|
|
|
|
print("FATAL: "
|
|
|
|
"`target_interactable` is not a string id "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
if target_interactable not in \
|
|
|
|
manifest["rooms"][target_room]["interactables"]:
|
|
|
|
print("FATAL: "
|
|
|
|
f"'{target_interactable}' does not exist "
|
|
|
|
f"in target_room '{target_room}' "
|
|
|
|
f"in interactable '{arg}'")
|
|
|
|
trg = manifest["rooms"][target_room]
|
|
|
|
trg["interactables"][target_interactable]["hidden"] = \
|
|
|
|
False
|
|
|
|
case _ as a:
|
|
|
|
print(f"ERROR: no such action exists: '{a}'")
|
|
|
|
|
|
|
|
if "message" not in inter:
|
|
|
|
print("WARNING: no message defined for this action.")
|
|
|
|
else:
|
|
|
|
print(inter["message"])
|
|
|
|
if "times" not in inter:
|
|
|
|
inter["times"] = "one"
|
|
|
|
if inter["times"] == "one":
|
|
|
|
cur_room = manifest["rooms"][current_room_id]
|
|
|
|
cur_room["interactables"][arg]["interactable"] = False
|
|
|
|
elif inter["times"] == "remove":
|
|
|
|
cur_room = manifest["rooms"][current_room_id]
|
|
|
|
cur_room["interactables"].pop(arg)
|
2024-05-23 06:01:21 -04:00
|
|
|
else:
|
2024-05-23 06:58:41 -04:00
|
|
|
if "message_failed" in inter:
|
|
|
|
print(inter["message_failed"])
|
|
|
|
else:
|
2024-08-26 10:28:02 -04:00
|
|
|
print(
|
|
|
|
"> I don't seem to be able to do anything with that..."
|
|
|
|
)
|
|
|
|
return self.check_win(arg)
|
2024-05-21 04:59:26 -04:00
|
|
|
elif arg == "":
|
|
|
|
print("> What should I interact with?")
|
|
|
|
else:
|
|
|
|
print("> I don't see anything like that")
|
|
|
|
|
|
|
|
def complete_interact(self, text, line, bidx, eidx):
|
|
|
|
global current_room_id
|
|
|
|
if "interactables" not in rooms[current_room_id]:
|
|
|
|
return
|
|
|
|
inters = rooms[current_room_id]["interactables"]
|
|
|
|
if not text:
|
|
|
|
return list(inters.keys())
|
|
|
|
else:
|
|
|
|
return [comp for comp in inters.keys() if comp.startswith(text)]
|
|
|
|
|
|
|
|
def do_int(self, arg):
|
|
|
|
"""Interact with a <thing>"""
|
|
|
|
self.do_interact(arg)
|
|
|
|
|
|
|
|
def complete_int(self, text, line, bidx, eidx):
|
|
|
|
return self.complete_interact(text, line, bidx, eidx)
|
|
|
|
|
|
|
|
def do_go(self, arg):
|
|
|
|
"""Go to another <room>"""
|
|
|
|
global current_room_id
|
|
|
|
cur_room = rooms[current_room_id]
|
|
|
|
if arg == "":
|
|
|
|
print("> Where should I go?")
|
|
|
|
return
|
|
|
|
elif arg in cur_room["exits"].keys():
|
|
|
|
current_room_id = cur_room["exits"][arg]
|
|
|
|
if "go" in rooms[current_room_id]:
|
|
|
|
print(rooms[current_room_id]["go"])
|
|
|
|
else:
|
|
|
|
print(f"You went {arg}.")
|
2024-08-31 22:48:34 -04:00
|
|
|
self.rooms_been_in.add(current_room_id)
|
2024-05-21 04:59:26 -04:00
|
|
|
else:
|
|
|
|
print("> I can't go there...")
|
|
|
|
return self.check_win()
|
|
|
|
|
|
|
|
def complete_go(self, text, line, bidx, eidx):
|
|
|
|
global current_room_id
|
|
|
|
exits = rooms[current_room_id]["exits"]
|
|
|
|
if not text:
|
|
|
|
return list(exits.keys())
|
|
|
|
else:
|
|
|
|
return [comp for comp in exits.keys() if comp.startswith(text)]
|
|
|
|
|
|
|
|
def do_take(self, arg):
|
|
|
|
"""Take an <item> from the current room."""
|
|
|
|
global current_room_id
|
|
|
|
cur_room = rooms[current_room_id]
|
|
|
|
if arg == "":
|
|
|
|
print("> What should I take?")
|
|
|
|
return
|
2024-08-31 22:55:02 -04:00
|
|
|
elif len(arg.split(",")) > 1:
|
|
|
|
for item in [a.strip() for a in arg.split(",")]:
|
|
|
|
if "items" not in cur_room:
|
|
|
|
print("> I don't see anything that I could take...")
|
|
|
|
return
|
|
|
|
if len(cur_room["items"]) == 0:
|
|
|
|
print("> I don't see anything that I could take...")
|
|
|
|
return
|
|
|
|
if item in cur_room["items"].keys():
|
|
|
|
inventory.update({item: cur_room["items"].pop(item)})
|
|
|
|
print(f"{item} added to inventory.")
|
|
|
|
else:
|
|
|
|
print("> I don't see anything like that...")
|
2024-05-21 16:35:44 -04:00
|
|
|
elif arg:
|
|
|
|
if "items" not in cur_room:
|
|
|
|
print("> I don't see anything that I could take...")
|
|
|
|
return
|
|
|
|
if len(cur_room["items"]) == 0:
|
|
|
|
print("> I don't see anything that I could take...")
|
|
|
|
return
|
|
|
|
if arg in cur_room["items"].keys():
|
2024-05-23 06:58:41 -04:00
|
|
|
inventory.update({arg: cur_room["items"].pop(arg)})
|
2024-05-21 16:35:44 -04:00
|
|
|
print(f"{arg} added to inventory.")
|
|
|
|
else:
|
|
|
|
print("> I don't see anything like that...")
|
2024-05-21 04:59:26 -04:00
|
|
|
else:
|
|
|
|
print("> I don't see anything like that...")
|
|
|
|
return self.check_win()
|
|
|
|
|
|
|
|
def complete_take(self, text, line, bidx, eidx):
|
|
|
|
global current_room_id
|
|
|
|
all_items = manifest["items"]
|
|
|
|
items = [x for x in rooms[current_room_id]["items"]
|
|
|
|
if (not all_items[x]["hidden"])]
|
|
|
|
if not text:
|
|
|
|
return list(items)
|
|
|
|
else:
|
2024-08-31 22:55:02 -04:00
|
|
|
# FIXME: handle multiple items
|
2024-05-21 04:59:26 -04:00
|
|
|
return [comp for comp in items if comp.startswith(text)]
|
|
|
|
|
|
|
|
def do_inventory(self, arg):
|
|
|
|
"""Examine your inventory."""
|
|
|
|
self.do_examine("inventory")
|
|
|
|
|
|
|
|
def do_inv(self, arg):
|
|
|
|
"""Check your inventory."""
|
|
|
|
self.do_inventory(arg)
|
|
|
|
|
2024-08-23 05:18:57 -04:00
|
|
|
def do_combine(self, arg):
|
|
|
|
"""Combine items: combine item1,item2"""
|
|
|
|
recipes = manifest["recipes"]
|
|
|
|
items = [a.strip() for a in arg.split(",")]
|
|
|
|
if len(items) < 2:
|
2024-08-26 09:49:11 -04:00
|
|
|
print("> Not enough items to combine...")
|
2024-08-23 05:18:57 -04:00
|
|
|
return
|
|
|
|
for item in items:
|
|
|
|
if item not in inventory:
|
|
|
|
print(f"> {item} not in inventory...")
|
|
|
|
return
|
|
|
|
for recipe in recipes.items():
|
2024-08-26 09:49:11 -04:00
|
|
|
rec = recipe[1]
|
2024-08-31 22:48:34 -04:00
|
|
|
can = True
|
|
|
|
if rec["lock"]:
|
|
|
|
can = False
|
|
|
|
if rec["lock"] == "item":
|
|
|
|
if rec["lock_target"] in inventory:
|
|
|
|
can = True
|
|
|
|
elif rec["lock"] == "interactable":
|
|
|
|
if rec["lock_target"] in self.interacted_with:
|
|
|
|
can = True
|
|
|
|
elif rec["lock"] == "room":
|
|
|
|
if rec["lock_target"] in self.rooms_been_in:
|
|
|
|
can = True
|
2024-08-26 09:49:11 -04:00
|
|
|
else:
|
2024-08-31 22:48:34 -04:00
|
|
|
print(f"ERROR: Invalid lock type for recipe `{recipe[0]}`")
|
|
|
|
return True
|
|
|
|
if can:
|
|
|
|
# FIXME: multiple recipes with same ingredients???
|
|
|
|
if set(items) == set(rec["ingredients"]):
|
|
|
|
print(rec["text"])
|
|
|
|
for item in items:
|
|
|
|
inventory.pop(item)
|
|
|
|
if "result_ids" in rec:
|
|
|
|
for idx, _id in enumerate(rec["result_ids"]):
|
|
|
|
inventory[_id] = rec["result_texts"][idx]
|
|
|
|
else:
|
|
|
|
inventory[rec["result_id"]] = rec["result_text"]
|
|
|
|
return self.check_win()
|
|
|
|
print("> I can't combine these...")
|
2024-08-23 05:18:57 -04:00
|
|
|
|
2024-05-21 04:59:26 -04:00
|
|
|
def do_credits(self, arg):
|
|
|
|
"""Print credits."""
|
2024-05-22 19:16:13 -04:00
|
|
|
if "credits" not in metadata:
|
2024-05-21 04:59:26 -04:00
|
|
|
print("No credits :(")
|
|
|
|
return
|
2024-05-22 19:16:13 -04:00
|
|
|
cred = metadata["credits"]
|
2024-05-21 04:59:26 -04:00
|
|
|
print("Manifest credits:")
|
|
|
|
if "author" in cred and "year" in cred:
|
|
|
|
print(f"\tMade by {cred["author"]} in {cred["year"]}.")
|
|
|
|
elif "author" in cred and "year" not in cred:
|
|
|
|
print(f"\tMade by {cred["author"]}.")
|
|
|
|
|
|
|
|
if "contributors" in cred:
|
|
|
|
print(f"\tWith contributions by {", ".join(cred["contributors"])}")
|
|
|
|
|
2024-05-21 16:37:12 -04:00
|
|
|
if "version" in cred:
|
2024-05-22 19:16:13 -04:00
|
|
|
print(f"\tManifest version: {cred["version"]}")
|
2024-05-21 16:37:12 -04:00
|
|
|
|
2024-05-21 04:59:26 -04:00
|
|
|
if "license" in cred:
|
|
|
|
print(f"\tLicense: {cred["license"]}")
|
|
|
|
print("\nSETAGE credits:")
|
|
|
|
print(f"\tMade by {__author__}")
|
|
|
|
print(f"\t{__copyright__}")
|
|
|
|
print(f"\tLicense: {__license__}")
|
|
|
|
|
|
|
|
def check_win(self, arg=""):
|
|
|
|
"""Check if you have won."""
|
2024-05-21 09:21:39 -04:00
|
|
|
endings = scenario["endings"]
|
|
|
|
for ending in endings:
|
|
|
|
end = endings[ending]
|
|
|
|
if end["trigger"] == "item":
|
2024-05-23 06:58:41 -04:00
|
|
|
if end["target"] in inventory.keys():
|
2024-05-21 09:21:39 -04:00
|
|
|
if "message" not in end:
|
|
|
|
print(f"WARNING: no message for ending '{ending}'")
|
|
|
|
else:
|
|
|
|
print(end["message"])
|
2024-05-22 19:16:13 -04:00
|
|
|
if "title" in end:
|
|
|
|
print(end["title"])
|
2024-05-21 09:21:39 -04:00
|
|
|
if "exit" not in end:
|
|
|
|
continue
|
|
|
|
if end["exit"]:
|
|
|
|
return True
|
|
|
|
if end["trigger"] == "room":
|
|
|
|
if current_room_id == end["target"]:
|
|
|
|
if "message" not in end:
|
|
|
|
print(f"WARNING: no message for ending '{ending}'")
|
|
|
|
else:
|
|
|
|
print(end["message"])
|
2024-05-22 19:16:13 -04:00
|
|
|
if "title" in end:
|
|
|
|
print(end["title"])
|
2024-05-21 09:21:39 -04:00
|
|
|
if "exit" not in end:
|
|
|
|
continue
|
|
|
|
if end["exit"]:
|
|
|
|
return True
|
2024-08-26 10:28:02 -04:00
|
|
|
if end["trigger"] == "interactable":
|
|
|
|
if arg == end["target"]:
|
|
|
|
if "message" not in end:
|
|
|
|
print(f"WARNING: no message for ending '{ending}'")
|
|
|
|
else:
|
|
|
|
print(end["message"])
|
|
|
|
if "title" in end:
|
|
|
|
print(end["title"])
|
|
|
|
if "exit" not in end:
|
|
|
|
continue
|
|
|
|
if end["exit"]:
|
|
|
|
return True
|
2024-05-21 04:59:26 -04:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if version_info < (3, 11):
|
|
|
|
print(
|
|
|
|
f"Sorry, this is only usable with python 3.11 as of {__version__}")
|
|
|
|
exit(1)
|
2024-05-21 16:44:47 -04:00
|
|
|
print("WARNING: It's a prototype. Beware of bugs. Report them.")
|
2024-05-21 04:59:26 -04:00
|
|
|
parser = ArgumentParser(
|
|
|
|
description="SETAGE, the Simple Extensible Text Adventure Game Engine")
|
|
|
|
parser.add_argument("file", nargs="?", default="manifest.setage",
|
|
|
|
type=FileType("r"),
|
|
|
|
help="The manifest.setage file to use.")
|
|
|
|
parser.add_argument("--obfuscated", "-b", action="store_true",
|
|
|
|
help="Treat file as an obfuscated file.")
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
data = args.file.read()
|
|
|
|
|
|
|
|
if args.obfuscated:
|
|
|
|
data = b64decode(data.encode("utf-8")).decode("utf-8")
|
|
|
|
|
|
|
|
try:
|
|
|
|
manifest = toml_loads(data)
|
|
|
|
except (TOMLDecodeError) as e:
|
|
|
|
ec = e.__class__
|
|
|
|
fatal = f"FATAL: file '{args.file.name}'"
|
|
|
|
if ec == TOMLDecodeError:
|
|
|
|
print(f"{fatal} isn't a valid toml file. "
|
|
|
|
"Try running with `-b` flag.")
|
|
|
|
print(f"FATAL: the following error occured: {e}")
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
if "scenario" not in manifest:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `scenario` does not exist in manifest.")
|
|
|
|
exit(2)
|
2024-05-22 19:16:13 -04:00
|
|
|
if "metadata" not in manifest:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `metadata` does not exist in manifest.")
|
|
|
|
exit(2)
|
|
|
|
if "min_version" not in manifest["metadata"]:
|
2024-05-22 14:25:22 -04:00
|
|
|
print("WARNING: no minimum SETAGE version specified in manifest.")
|
|
|
|
else:
|
2024-05-22 19:16:13 -04:00
|
|
|
if VERSION < parse_version(manifest["metadata"]["min_version"]):
|
2024-05-22 14:25:22 -04:00
|
|
|
print("FATAL: SETAGE version doesn't match required version.")
|
2024-09-02 16:11:04 -04:00
|
|
|
exit(2)
|
2024-05-21 04:59:26 -04:00
|
|
|
if "rooms" not in manifest:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `rooms` does not exist in manifest.")
|
|
|
|
exit(2)
|
|
|
|
|
2024-05-22 19:16:13 -04:00
|
|
|
if "title" not in manifest["metadata"]:
|
2024-05-21 04:59:26 -04:00
|
|
|
print("FATAL: "
|
|
|
|
"string `title` does not exist in scenario.")
|
|
|
|
exit(2)
|
|
|
|
if "intro" not in manifest["scenario"]:
|
|
|
|
print("FATAL: "
|
|
|
|
"string `intro` does not exist in scenario.")
|
|
|
|
exit(2)
|
|
|
|
if "start" not in manifest["scenario"]:
|
|
|
|
print("FATAL: "
|
|
|
|
"room id `start` does not exist in scenario.")
|
|
|
|
exit(2)
|
2024-05-21 09:21:39 -04:00
|
|
|
if "endings" not in manifest["scenario"]:
|
|
|
|
print("FATAL: "
|
|
|
|
"table `endings` does not exist in scenario.")
|
|
|
|
exit(2)
|
|
|
|
|
|
|
|
if not isinstance(manifest["rooms"], dict):
|
|
|
|
print("FATAL: "
|
|
|
|
"`rooms` is not a table in scenario.")
|
|
|
|
exit(2)
|
2024-05-22 19:16:13 -04:00
|
|
|
if "items" in manifest:
|
|
|
|
if not isinstance(manifest["items"], dict):
|
|
|
|
print("FATAL: "
|
|
|
|
"`items` is not a table in scenario.")
|
|
|
|
exit(2)
|
2024-05-21 09:21:39 -04:00
|
|
|
if not isinstance(manifest["scenario"]["endings"], dict):
|
2024-05-21 04:59:26 -04:00
|
|
|
print("FATAL: "
|
2024-05-21 09:21:39 -04:00
|
|
|
"`endings` is not a table in scenario.")
|
2024-05-21 04:59:26 -04:00
|
|
|
exit(2)
|
|
|
|
|
2024-05-21 09:21:39 -04:00
|
|
|
if "items" in manifest:
|
|
|
|
for k, v in manifest["items"].items():
|
|
|
|
if "hidden" not in v:
|
|
|
|
v.update({"hidden": False})
|
|
|
|
manifest["items"][k] = v
|
2024-05-21 04:59:26 -04:00
|
|
|
|
|
|
|
for k, v in manifest["rooms"].items():
|
|
|
|
if "exits" not in v:
|
|
|
|
v.update({"exits": dict()})
|
2024-05-21 09:21:39 -04:00
|
|
|
for room in manifest["rooms"]:
|
|
|
|
if "interactables" in manifest["rooms"][room]:
|
|
|
|
for k, v in manifest["rooms"][room]["interactables"].items():
|
|
|
|
if "hidden" not in v:
|
|
|
|
v.update({"hidden": False})
|
2024-05-21 04:59:26 -04:00
|
|
|
|
2024-05-22 19:16:13 -04:00
|
|
|
metadata = manifest["metadata"]
|
2024-05-21 04:59:26 -04:00
|
|
|
scenario = manifest["scenario"]
|
|
|
|
rooms = manifest["rooms"]
|
|
|
|
current_room_id = scenario["start"]
|
|
|
|
|
|
|
|
SetageShell.intro = scenario["intro"]
|
|
|
|
|
|
|
|
if "prompt" in scenario:
|
|
|
|
SetageShell.prompt = scenario["prompt"]
|
|
|
|
|
2024-05-22 19:16:13 -04:00
|
|
|
if "playtested" not in metadata:
|
2024-05-21 04:59:26 -04:00
|
|
|
print("WARNING: This manifest has not been playtested.")
|
2024-05-22 19:16:13 -04:00
|
|
|
elif not metadata["playtested"]:
|
2024-05-21 04:59:26 -04:00
|
|
|
print("WARNING: This manifest has not been playtested.")
|
|
|
|
|
|
|
|
print()
|
|
|
|
print("? for help. Try ls.")
|
|
|
|
print()
|
2024-05-22 19:16:13 -04:00
|
|
|
print(metadata["title"])
|
2024-05-21 04:59:26 -04:00
|
|
|
print()
|
|
|
|
|
|
|
|
SetageShell().cmdloop()
|