- world/rebuild_world.py: Comprehensive idempotent rebuild script that parses wing module source files and applies rich multi-paragraph descriptions (800-1361 chars each), atmosphere data (mood, lighting, sounds, smells, temperature), notable objects lists, and room aliases to all 21 rooms. Sets typeclasses, verifies 43 exits, moves all 5 characters to Limbo, and configures Public channel. - commands/command.py: Added 5 new custom commands: @status - Agent status (location, wing, online users, uptime) @map - ASCII map of current wing @academy - Overview of all 4 wings with room counts smell/sniff - Sensory command using room atmosphere data listen/hear - Sensory command using room atmosphere data - commands/default_cmdsets.py: Registered all new commands in the CharacterCmdSet - README.md: Complete rewrite with project description, connection info, ASCII room maps, agent accounts, rebuild instructions, tech stack, and future plans (Gitea bridge, crisis training, Nexus integration)
368 lines
12 KiB
Python
368 lines
12 KiB
Python
"""
|
|
Timmy Academy - Custom Commands
|
|
|
|
Includes:
|
|
- CmdExamine: Examine objects/rooms in detail
|
|
- CmdRooms: List available rooms
|
|
- CmdStatus: Show current agent status
|
|
- CmdMap: Show ASCII map of current wing
|
|
- CmdAcademy: Show overview of all 4 wings
|
|
- CmdSmell: Use atmosphere data for scents
|
|
- CmdListen: Use atmosphere data for sounds
|
|
"""
|
|
|
|
from evennia.commands.command import Command as BaseCommand
|
|
from evennia import default_cmds
|
|
|
|
|
|
class Command(BaseCommand):
|
|
"""Base command class for Timmy Academy."""
|
|
pass
|
|
|
|
|
|
# ============================================================
|
|
# Original commands (from first pass)
|
|
# ============================================================
|
|
|
|
class CmdExamine(BaseCommand):
|
|
"""
|
|
Examine an object, character, or detail in the room.
|
|
|
|
Usage:
|
|
examine [<object>]
|
|
ex [<object>]
|
|
|
|
If no object is given, examines the current room in detail.
|
|
"""
|
|
key = "examine"
|
|
aliases = ["ex", "exam"]
|
|
locks = "cmd:all()"
|
|
help_category = "General"
|
|
|
|
def func(self):
|
|
if not self.args:
|
|
loc = self.caller.location
|
|
self.caller.msg(f"|c{loc.key}|n")
|
|
self.caller.msg(f"{loc.db.desc}")
|
|
# Show atmosphere if available
|
|
atmo = loc.db.atmosphere
|
|
if atmo:
|
|
self.caller.msg("\n|wAtmosphere:|n")
|
|
for key, val in atmo.items():
|
|
self.caller.msg(f" |w{key.capitalize()}:|n {val}")
|
|
# Show notable objects
|
|
objs = loc.db.objects
|
|
if objs:
|
|
self.caller.msg("\n|wNotable features:|n")
|
|
for obj in objs:
|
|
self.caller.msg(f" - {obj}")
|
|
# Show contents
|
|
contents = [obj.key for obj in loc.contents if obj != self.caller]
|
|
if contents:
|
|
self.caller.msg(f"\n|wPresent:|n {', '.join(contents)}")
|
|
return
|
|
|
|
target = self.caller.search(self.args.strip())
|
|
if not target:
|
|
return
|
|
self.caller.msg(f"|c{target.key}|n")
|
|
if hasattr(target, 'db') and target.db.desc:
|
|
self.caller.msg(f"{target.db.desc}")
|
|
else:
|
|
self.caller.msg("You see nothing special.")
|
|
|
|
|
|
class CmdRooms(BaseCommand):
|
|
"""
|
|
List available rooms in the game.
|
|
|
|
Usage:
|
|
rooms
|
|
"""
|
|
key = "rooms"
|
|
locks = "cmd:all()"
|
|
help_category = "General"
|
|
|
|
def func(self):
|
|
from evennia.objects.models import ObjectDB
|
|
rooms = ObjectDB.objects.filter(
|
|
db_typeclass_path__icontains='room'
|
|
).order_by('id')
|
|
self.caller.msg("|c=== Timmy Academy Rooms ===|n")
|
|
for room in rooms:
|
|
wing = room.attributes.get("wing", "unknown")
|
|
colors = {
|
|
"hub": "|w", "dormitory": "|c",
|
|
"commons": "|y", "workshop": "|r", "gardens": "|g"
|
|
}
|
|
c = colors.get(wing, "|n")
|
|
marker = "*" if self.caller.location == room else " "
|
|
self.caller.msg(f" {marker} {c}{room.db_key}|n [{wing}]")
|
|
|
|
|
|
# ============================================================
|
|
# New commands (second pass)
|
|
# ============================================================
|
|
|
|
class CmdStatus(BaseCommand):
|
|
"""
|
|
Show your current status: location, wing, who's online, uptime.
|
|
|
|
Usage:
|
|
@status
|
|
"""
|
|
key = "@status"
|
|
aliases = ["status"]
|
|
locks = "cmd:all()"
|
|
help_category = "Academy"
|
|
|
|
def func(self):
|
|
caller = self.caller
|
|
loc = caller.location
|
|
wing = loc.db.wing if loc else "unknown"
|
|
mood = ""
|
|
if loc and loc.db.atmosphere:
|
|
mood = loc.db.atmosphere.get("mood", "")
|
|
|
|
# Who is online
|
|
from evennia.accounts.models import AccountDB
|
|
online = AccountDB.objects.filter(db_is_connected=True)
|
|
online_names = [a.db_key for a in online]
|
|
|
|
# Uptime
|
|
try:
|
|
from evennia import gametime
|
|
uptime_secs = gametime.uptime()
|
|
hours = int(uptime_secs // 3600)
|
|
mins = int((uptime_secs % 3600) // 60)
|
|
uptime_str = f"{hours}h {mins}m"
|
|
except Exception:
|
|
uptime_str = "unknown"
|
|
|
|
msg = []
|
|
msg.append("|c============= Agent Status =============|n")
|
|
msg.append(f" |wAgent:|n {caller.key}")
|
|
msg.append(f" |wLocation:|n {loc.key if loc else 'Void'}")
|
|
msg.append(f" |wWing:|n {wing}")
|
|
if mood:
|
|
msg.append(f" |wMood:|n {mood}")
|
|
msg.append(f" |wOnline:|n {', '.join(online_names) if online_names else 'nobody'} ({len(online_names)})")
|
|
msg.append(f" |wUptime:|n {uptime_str}")
|
|
msg.append("|c=========================================|n")
|
|
self.caller.msg("\n".join(msg))
|
|
|
|
|
|
class CmdMap(BaseCommand):
|
|
"""
|
|
Show an ASCII map of the current wing or the academy hub.
|
|
|
|
Usage:
|
|
@map
|
|
"""
|
|
key = "@map"
|
|
aliases = ["map"]
|
|
locks = "cmd:all()"
|
|
help_category = "Academy"
|
|
|
|
WING_MAPS = {
|
|
"hub": (
|
|
"\n|c============= Academy Central Hub =============|n\n"
|
|
"\n"
|
|
" |c[Dormitory Wing]|n\n"
|
|
" |\n"
|
|
" north\n"
|
|
" |\n"
|
|
" |g[Gardens Wing]|n --west-- |w[ LIMBO ]|n --east-- |y[Commons Wing]|n\n"
|
|
" |\n"
|
|
" south\n"
|
|
" |\n"
|
|
" |r[Workshop Wing]|n\n"
|
|
"\n|c=================================================|n"
|
|
),
|
|
"dormitory": (
|
|
"\n|c============= Dormitory Wing =============|n\n"
|
|
"\n"
|
|
" |c[Master Suites]|n --w-- |c[Corridor]|n --e-- |c[Novice Hall]|n\n"
|
|
" |\n"
|
|
" south\n"
|
|
" |\n"
|
|
" |c[Res. Services]|n --n-- |c[Dorm Entrance]|n\n"
|
|
" |\n"
|
|
" south\n"
|
|
" |\n"
|
|
" |w[LIMBO/Hub]|n\n"
|
|
"\n|c=============================================|n"
|
|
),
|
|
"commons": (
|
|
"\n|y============= Commons Wing =============|n\n"
|
|
"\n"
|
|
" |y[Upper Balcony]|n\n"
|
|
" |\n"
|
|
" up\n"
|
|
" |\n"
|
|
" |y[Scholar's]|n --e-- |y[Grand Commons]|n --s-- |y[Entertainment]|n\n"
|
|
" |\n"
|
|
" north\n"
|
|
" |\n"
|
|
" |y[Hearthside]|n\n"
|
|
" |\n"
|
|
" |w[LIMBO/Hub]|n\n"
|
|
"\n|y=============================================|n"
|
|
),
|
|
"workshop": (
|
|
"\n|r============= Workshop Wing =============|n\n"
|
|
"\n"
|
|
" |r[Woodworking]|n --e-- |r[Alchemy Labs]|n\n"
|
|
" |\n"
|
|
" north\n"
|
|
" |\n"
|
|
" |r[Great Smithy]|n --e-- |r[Workshop Entrance]|n\n"
|
|
" |\n"
|
|
" down | north\n"
|
|
" |\n"
|
|
" |r[Artificing]|n |w[LIMBO/Hub]|n\n"
|
|
"\n|r=============================================|n"
|
|
),
|
|
"gardens": (
|
|
"\n|g============= Gardens Wing =============|n\n"
|
|
"\n"
|
|
" |g[Sacred Grove]|n\n"
|
|
" |\n"
|
|
" up\n"
|
|
" |\n"
|
|
" |g[Greenhouse]|n --n-- |g[Enchanted Grove]|n\n"
|
|
" |\n"
|
|
" west\n"
|
|
" |\n"
|
|
" |g[Herb Gardens]|n --s-- |g[Gardens Entrance]|n\n"
|
|
" |\n"
|
|
" |w[LIMBO/Hub]|n\n"
|
|
"\n|g=============================================|n"
|
|
),
|
|
}
|
|
|
|
def func(self):
|
|
loc = self.caller.location
|
|
wing = loc.db.wing if loc else "hub"
|
|
if not wing or wing not in self.WING_MAPS:
|
|
wing = "hub"
|
|
self.caller.msg(self.WING_MAPS[wing])
|
|
self.caller.msg(f" |wYou are in:|n {loc.key}")
|
|
|
|
|
|
class CmdAcademy(BaseCommand):
|
|
"""
|
|
Show an overview of all academy wings and room counts.
|
|
|
|
Usage:
|
|
@academy
|
|
"""
|
|
key = "@academy"
|
|
aliases = ["academy"]
|
|
locks = "cmd:all()"
|
|
help_category = "Academy"
|
|
|
|
def func(self):
|
|
from evennia.objects.models import ObjectDB
|
|
|
|
msg = []
|
|
msg.append("|c" + "=" * 52 + "|n")
|
|
msg.append("|c TIMMY ACADEMY - Agent Training Grounds|n")
|
|
msg.append("|c" + "=" * 52 + "|n")
|
|
msg.append("")
|
|
msg.append(' |x"Together we learn, together we grow."|n')
|
|
msg.append("")
|
|
|
|
wing_config = {
|
|
"hub": {"name": "Central Hub", "color": "|w", "ids": [2]},
|
|
"dormitory": {"name": "Dormitory Wing", "color": "|c", "ids": [3,4,5,6,7]},
|
|
"commons": {"name": "Commons Wing", "color": "|y", "ids": [8,9,10,11,12]},
|
|
"workshop": {"name": "Workshop Wing", "color": "|r", "ids": [13,14,15,16,17]},
|
|
"gardens": {"name": "Gardens Wing", "color": "|g", "ids": [18,19,20,21,22]},
|
|
}
|
|
|
|
total_rooms = 0
|
|
for key, info in wing_config.items():
|
|
rooms = ObjectDB.objects.filter(id__in=info["ids"])
|
|
count = rooms.count()
|
|
total_rooms += count
|
|
c = info["color"]
|
|
msg.append(f" {c}{info['name']:20s}|n {count} rooms")
|
|
for room in rooms.order_by('id'):
|
|
marker = "*" if self.caller.location == room else " "
|
|
msg.append(f" {marker} {c}{room.db_key}|n")
|
|
|
|
exits = ObjectDB.objects.filter(db_typeclass_path__icontains="exit")
|
|
chars = ObjectDB.objects.filter(db_typeclass_path__icontains="character")
|
|
|
|
msg.append("")
|
|
msg.append(f" |wTotal:|n {total_rooms} rooms, {exits.count()} exits, {chars.count()} characters")
|
|
msg.append("")
|
|
msg.append(" |wConnect:|n telnet 167.99.126.228 4000")
|
|
msg.append(" |wWeb:|n http://167.99.126.228:4001")
|
|
msg.append("|c" + "=" * 52 + "|n")
|
|
self.caller.msg("\n".join(msg))
|
|
|
|
|
|
class CmdSmell(BaseCommand):
|
|
"""
|
|
Use your sense of smell to perceive the room.
|
|
|
|
Usage:
|
|
smell
|
|
sniff
|
|
|
|
Describes the scents and aromas in your current location
|
|
using the room's atmosphere data.
|
|
"""
|
|
key = "smell"
|
|
aliases = ["sniff"]
|
|
locks = "cmd:all()"
|
|
help_category = "Academy"
|
|
|
|
def func(self):
|
|
loc = self.caller.location
|
|
if not loc:
|
|
self.caller.msg("You smell... nothing. The void has no scent.")
|
|
return
|
|
|
|
atmo = loc.db.atmosphere
|
|
if atmo and "smells" in atmo:
|
|
self.caller.msg(f"|wYou breathe deeply in {loc.key}...|n")
|
|
self.caller.msg(f"\n {atmo['smells']}")
|
|
if "temperature" in atmo:
|
|
self.caller.msg(f"\n The air feels: {atmo['temperature']}")
|
|
else:
|
|
self.caller.msg("You sniff the air but detect nothing noteworthy.")
|
|
|
|
|
|
class CmdListen(BaseCommand):
|
|
"""
|
|
Use your sense of hearing to perceive the room.
|
|
|
|
Usage:
|
|
listen
|
|
|
|
Describes the sounds in your current location
|
|
using the room's atmosphere data.
|
|
"""
|
|
key = "listen"
|
|
aliases = ["hear"]
|
|
locks = "cmd:all()"
|
|
help_category = "Academy"
|
|
|
|
def func(self):
|
|
loc = self.caller.location
|
|
if not loc:
|
|
self.caller.msg("You listen... but silence stretches forever in the void.")
|
|
return
|
|
|
|
atmo = loc.db.atmosphere
|
|
if atmo and "sounds" in atmo:
|
|
self.caller.msg(f"|wYou pause and listen in {loc.key}...|n")
|
|
self.caller.msg(f"\n {atmo['sounds']}")
|
|
if "mood" in atmo:
|
|
self.caller.msg(f"\n The mood here is: {atmo['mood']}")
|
|
else:
|
|
self.caller.msg("You listen carefully but hear nothing unusual.")
|