Files
timmy-academy/commands/command.py
Allegro 67d91291d3 build: second pass — rich descriptions, custom commands, README
- 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)
2026-03-31 16:24:18 +00:00

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.")