Ready. Set. Go!

This commit is contained in:
Cheri Dawn 2024-05-21 11:59:26 +03:00
commit 05c000990a
6 changed files with 1190 additions and 0 deletions

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# SETAGE
A **S**imple **E**xtensible **T**ext **A**dventure **G**ame **E**ngine.
## Manifest
see docs.toml
## Example
see test-game/manifest.setage

57
docs.toml Normal file
View file

@ -0,0 +1,57 @@
[credits] # Optional but highly recommended.
author = "who made the manifest for the game"
year = "when it was made"
license = "license for this manifest"
[scenario] # Required.
start = "id" # room where the player starts. any valid room id.
title = "title of the game"
intro = "intro to the game"
prompt = "> " # optional, default shown.
playtested = false # optional, default. displays a warning if this is set to false.
[scenario.win] # Required.
trigger = "item" # one of "item", "room" ("interactable" in future releases).
target = "id of the trigger" # which item, room (or interactable) should trigger the win.
message = "message when the win is triggered"
end = false # optional, default; exit the game after triggering?
[items] # Required.
item.examine = "message when the `item` is examined" # `item` can be any item id.
item.hidden = false # optional, default.
[rooms.id] # Required, id can be any other room id.
description = "description of the room as it appears in the listing of exits from other rooms"
examine = "description of the room when you examine it"
go = "message when you `go` to this room"
[rooms.id.items] # Optional.
item = "description" # item can be any valid id. description is a short description as it appears in the room listing.
[rooms.id.interactables.id] # Optional. Any valid id.
name = "name of the interactable" # appears in the room listing.
type = "bare" # optional, default; options: "bare".
times = "one" # optional, default; options: "one", "change", "remove", "many".
hidden = false # optional, default.
action = "add exit" # any of "add exit", "add exits", "add item", "add items", "reveal interactable".
target_room = "id" # any valid room id; changes will be made to that room.
target_exit.name = "id" # Required if action is "add exit"; name of the exit as it appears in the room listing & id of the room it points to.
# Required if action is "add items"; pick one of the following two:
target_exits = [ # list of inline tables.
{name = "id"}, # similar to a regular exit decl.
]
target_exits.name = "id" # another way to do that.
target_item = {"id" = "description"} # Required if action is "add item"; any valid item id & short description as it appears in the room listing.
# Required if action is "add items"; pick one of the following two:
target_items = [ # list of inline tables.
{id = "description"}, # similar to a regular item decl.
]
target_items.id = "description" # another way to do that.
message = "message after the interactable is activated" # Required.
[rooms.id.exits] # Technically optional.
direction = "id" # direction as it appears in the listing.

43
setage-standalone-gen.py Normal file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from argparse import ArgumentParser, FileType
from base64 import b64encode
__author__ = "Cheri Dawn"
__copyright__ = "Copyright 2024, Cheri Dawn"
__license__ = "GPLv3"
__version__ = "0.2.0"
__maintainer__ = "Cheri Dawn"
__status__ = "Prototype"
if __name__ == "__main__":
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 generate a script for.")
parser.add_argument("template", nargs="?",
default="setage-standalone.py.template",
type=FileType("r"),
help="The template for the script.")
parser.add_argument("output", nargs="?", default="setage-standalone.py",
type=FileType("w"),
help="The output file.")
args = parser.parse_args()
print(f"Reading manifest '{args.file.name}'...")
data = args.file.read()
print(f"Read manifest '{args.file.name}'.")
print(f"Reading template '{args.template.name}'...")
template = args.template.read()
print(f"Read template '{args.template.name}'.")
out = template.replace("{{data}}",
b64encode(data.encode("utf-8")).decode("utf-8"))
print(f"Writing output '{args.output.name}'...")
output = args.output.write(out)
print(f"Wrote output '{args.output.name}'.")

View file

@ -0,0 +1,489 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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"
__license__ = "GPLv3"
__version__ = "0.2.0"
__maintainer__ = "Cheri Dawn"
__status__ = "Prototype"
manifest = {}
scenario = {}
rooms = {}
current_room_id = ""
inventory = []
def print_items(cur_room):
if "items" not in cur_room:
return
items = cur_room["items"]
for k, v in items.items():
if not manifest["items"][k]["hidden"]:
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: "
f"room '{v}' does not have a `description`.")
continue
print(f"Looking [{k}] you see {rooms[v]["description"]}")
class SetageShell(Cmd):
intro = ""
prompt = "> "
ruler = "~"
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.")
for k, v in inventory:
print(f"You have {v} [{k}].")
elif arg in dict(inventory).keys():
if "examine" in manifest["items"][arg]:
print(manifest["items"][arg]["examine"])
else:
print("It doesn't seem to be all that interesting...")
elif arg in cur_room["items"].keys():
manifest["items"][arg]["hidden"] = False
if "examine" in manifest["items"][arg]:
print(manifest["items"][arg]["examine"])
else:
print("It doesn't seem to be all that interesting...")
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"]
items = dict(inventory)
items.update(rooms[current_room_id]["items"])
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]
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
if can_activate:
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)
if not can_activate:
print("> I can't interact with that...")
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}.")
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
elif arg in cur_room["items"].keys():
inventory.append((arg, cur_room["items"].pop(arg)))
print(f"{arg} added to inventory.")
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:
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)
def do_credits(self, arg):
"""Print credits."""
if "credits" not in manifest:
print("No credits :(")
return
cred = manifest["credits"]
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"])}")
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."""
win = scenario["win"]
if win["trigger"] == "item":
if win["target"] in dict(inventory).keys():
print(win["message"])
if win["end"]:
return True
if win["trigger"] == "room":
if current_room_id == win["target"]:
print(win["message"])
if win["end"]:
return True
if __name__ == "__main__":
if version_info < (3, 11):
print(
f"Sorry, this is only usable with python 3.11 as of {__version__}")
exit(1)
print("WARNING: It's a prototype. Beware of bugs. Report them")
data = "{{data}}" # noqa: E501
data = b64decode(data.encode("utf-8")).decode("utf-8")
try:
manifest = toml_loads(data)
except (TOMLDecodeError) as e:
ec = e.__class__
fatal = "FATAL: embedded manifest"
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 "items" in manifest:
for k, v in manifest["items"].items():
if "hidden" not in v:
v.update({"hidden": False})
manifest["items"][k] = v
if "scenario" not in manifest:
print("FATAL: "
"table `scenario` does not exist in manifest.")
exit(2)
if "rooms" not in manifest:
print("FATAL: "
"table `rooms` does not exist in manifest.")
exit(2)
if "title" not in manifest["scenario"]:
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)
if "win" not in manifest["scenario"]:
print("FATAL: "
"table `win` does not exist in scenario.")
exit(2)
if "end" not in manifest["scenario"]["win"]:
manifest["scenario"]["win"]["end"] = False
for k, v in manifest["rooms"].items():
if "exits" not in v:
v.update({"exits": dict()})
scenario = manifest["scenario"]
rooms = manifest["rooms"]
current_room_id = scenario["start"]
SetageShell.intro = scenario["intro"]
if "prompt" in scenario:
SetageShell.prompt = scenario["prompt"]
if "playtested" not in scenario:
print("WARNING: This manifest has not been playtested.")
elif not scenario["playtested"]:
print("WARNING: This manifest has not been playtested.")
print()
print("? for help. Try ls.")
print()
print(scenario["title"])
print()
SetageShell().cmdloop()

501
setage.py Executable file
View file

@ -0,0 +1,501 @@
#!/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"
__license__ = "GPLv3"
__version__ = "0.2.0"
__maintainer__ = "Cheri Dawn"
__status__ = "Prototype"
manifest = {}
scenario = {}
rooms = {}
current_room_id = ""
inventory = []
def print_items(cur_room):
if "items" not in cur_room:
return
items = cur_room["items"]
for k, v in items.items():
if not manifest["items"][k]["hidden"]:
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: "
f"room '{v}' does not have a `description`.")
continue
print(f"Looking [{k}] you see {rooms[v]["description"]}")
class SetageShell(Cmd):
intro = ""
prompt = "> "
ruler = "~"
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.")
for k, v in inventory:
print(f"You have {v} [{k}].")
elif arg in dict(inventory).keys():
if "examine" in manifest["items"][arg]:
print(manifest["items"][arg]["examine"])
else:
print("It doesn't seem to be all that interesting...")
elif arg in cur_room["items"].keys():
manifest["items"][arg]["hidden"] = False
if "examine" in manifest["items"][arg]:
print(manifest["items"][arg]["examine"])
else:
print("It doesn't seem to be all that interesting...")
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"]
items = dict(inventory)
items.update(rooms[current_room_id]["items"])
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]
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
if can_activate:
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)
if not can_activate:
print("> I can't interact with that...")
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}.")
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
elif arg in cur_room["items"].keys():
inventory.append((arg, cur_room["items"].pop(arg)))
print(f"{arg} added to inventory.")
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:
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)
def do_credits(self, arg):
"""Print credits."""
if "credits" not in manifest:
print("No credits :(")
return
cred = manifest["credits"]
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"])}")
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."""
win = scenario["win"]
if win["trigger"] == "item":
if win["target"] in dict(inventory).keys():
print(win["message"])
if win["end"]:
return True
if win["trigger"] == "room":
if current_room_id == win["target"]:
print(win["message"])
if win["end"]:
return True
if __name__ == "__main__":
if version_info < (3, 11):
print(
f"Sorry, this is only usable with python 3.11 as of {__version__}")
exit(1)
print("WARNING: It's a prototype. Beware of bugs. Report them")
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 "items" in manifest:
for k, v in manifest["items"].items():
if "hidden" not in v:
v.update({"hidden": False})
manifest["items"][k] = v
if "scenario" not in manifest:
print("FATAL: "
"table `scenario` does not exist in manifest.")
exit(2)
if "rooms" not in manifest:
print("FATAL: "
"table `rooms` does not exist in manifest.")
exit(2)
if "title" not in manifest["scenario"]:
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)
if "win" not in manifest["scenario"]:
print("FATAL: "
"table `win` does not exist in scenario.")
exit(2)
if "end" not in manifest["scenario"]["win"]:
manifest["scenario"]["win"]["end"] = False
for k, v in manifest["rooms"].items():
if "exits" not in v:
v.update({"exits": dict()})
scenario = manifest["scenario"]
rooms = manifest["rooms"]
current_room_id = scenario["start"]
SetageShell.intro = scenario["intro"]
if "prompt" in scenario:
SetageShell.prompt = scenario["prompt"]
if "playtested" not in scenario:
print("WARNING: This manifest has not been playtested.")
elif not scenario["playtested"]:
print("WARNING: This manifest has not been playtested.")
print()
print("? for help. Try ls.")
print()
print(scenario["title"])
print()
SetageShell().cmdloop()

92
test-game/manifest.setage Normal file
View file

@ -0,0 +1,92 @@
[credits]
author = "Cheri Dawn"
year = "2024"
license = "CC0"
[scenario]
start = "1"
title = "Dust Bunnies"
intro = "You wake up in a very dusty and old-looking room.\nYou think you can get out of here if you find something that could help you remember..."
prompt = ": "
[scenario.win]
trigger = "item"
target = "shiny"
message = "Congrats! You have found the old locket of your grandma."
[items]
brush.examine = "A quite uninteresting toothbrush."
detergent.examine = "A bottle of detergent. Half-filled. Stinks."
shiny.examine = "A heart-shaped shiny locket with a picture of a horse inside."
shiny.hidden = true
note.examine = "The note reads: there is a [shiny] hidden in one of the rooms of this house."
[rooms.1]
description = "a dark room with trash in it."
go = "You walk into a room full of trash, garbage and other junk."
examine = "This room doesn't have much to it, just a lot of dust, old wood trash and a couple torn paintings. This room stinks!"
[rooms.1.items]
[rooms.1.interactables.lever]
name = "a rustic lever"
type = "bare"
times = "one"
hidden = false
action = "add exit"
target_room = "1"
target_exit.down = "4"
message = "Pulling the lever reveals a staircase downwards."
[rooms.1.exits]
north = "1"
south = "2"
[rooms.2]
description = "a bright white shining room."
go = "You walk into a room that is perfectly clean."
examine = "This room has everything neatly organized. Not a spec of dust. This room smells of caustic chemicals."
[rooms.2.interactables.cupboard]
name = "a shiny white cupboard"
times = "remove"
action = "add item"
target_item.id = "detergent"
target_item.description = "a half-filled bottle of laundry detergent"
message = "You open a cupboard, there is something there."
[rooms.2.items]
brush = "a new toothbrush"
[rooms.2.exits]
north = "1"
west = "3"
[rooms.3]
description = "a room lit by a single candle."
go = "You walk into a room lit by a single candle."
examine = "The candle illuminates the room very poorly, only a table and two chairs are visible."
[rooms.3.items]
shiny = "a shiny heart-shaped locket"
[rooms.3.exits]
north = "1"
east = "2"
[rooms.4]
description = "a room with yellowed wallpapers."
go = "You walk into a room with yellowed wallpapers."
examine = "The room has yellowed wallpapers. They stink of old age. There's a faint hum coming from the flourescent lights."
[rooms.4.items]
note = "a torn, yellowed note"
[rooms.4.exits]
up = "1"