Implements import_map.py script that converts YAML zone definitions to
TOML format used by the engine. YAML format supports all zone features
including terrain, portals, spawns, ambient messages, and boundaries.
Mobs with home regions now pathfind back when they've strayed. Each tick,
process_mob_movement() checks all mobs and moves them one tile toward their
home region center using Manhattan distance. Movement is throttled to 3
seconds, respects impassable terrain, skips mobs in combat, and broadcasts
to nearby players.
These commands enable runtime world editing:
- @goto teleports to a named zone's spawn point
- @dig creates a new blank zone with specified dimensions
- @save exports the current zone to TOML
- @place spawns a thing from templates at player position
Implements TDD feature for readable text on Things:
- Added readable_text field to Thing dataclass
- Extended ThingTemplate to parse readable_text from TOML
- Created read command that finds objects by name/alias in inventory or on ground
- Handles edge cases: no target, not found, not readable
UnlockCondition on CombatMove, parsed from [unlock] TOML section.
check_unlocks evaluates kill_count and mob_kills thresholds.
Locked moves rejected with "You haven't learned that yet." in
do_attack/do_defend. New unlocks announced after kills.
LootEntry defines probabilistic item drops with min/max counts.
roll_loot takes a loot table and returns Thing instances.
MobTemplate now has loot field, parsed from TOML [[loot]] sections.
When a mob dies in combat, create_corpse is called to spawn a corpse
at the mob's position with the mob's inventory transferred. This
replaces the direct despawn_mob call, making combat deaths leave
lootable corpses behind.
The fallback to despawn_mob is kept if the mob somehow has no zone.
Corpse is a non-portable Container subclass that holds a deceased mob's
inventory. The create_corpse factory transfers items from the mob to the
corpse, sets a decompose_at timestamp for eventual cleanup, and calls
despawn_mob to remove the mob from the world.
- Update _find_container to use targeting module (prefix + ordinal)
- Update cmd_put to use find_in_inventory directly
- Add 'get all from <container>' support with portable item filtering
- Add comprehensive tests for all container grammar features
Replace local exact-match helpers with targeting module calls for
prefix matching and ordinal disambiguation. Works in get, drop, and
container extraction (get X from Y).
Objects were comparing by value instead of identity, causing
list.remove() to remove the wrong object when moving items with
identical attributes. Set eq=False on all dataclasses to use
identity-based comparison.
Implements a complete alias system allowing players to create command shortcuts.
Aliases are expanded during dispatch with a recursion guard (max 10 levels).
Changes:
- Add aliases field to Player dataclass (dict[str, str])
- Add player_aliases table to database schema
- Add save_aliases() and load_aliases() persistence functions
- Add alias/unalias commands with built-in command protection
- Integrate alias expansion into dispatch() before command resolution
- Add comprehensive test coverage for all features
Moved common test fixtures (mock_writer, mock_reader, test_zone, player,
nearby_player, clear_state) from individual test files into a shared
conftest.py. This eliminates duplication across test_power.py, test_sleep.py,
test_combat_zaxis.py, test_quit.py, test_stamina_cues.py, and
test_stamina_cue_wiring.py.
Some test files override specific fixtures where they need custom behavior
(e.g., test_quit.py adds a close method to mock_writer, stamina tests use
smaller zones and custom player positions).
Allows instant kill of unconscious opponents. Only works in combat on targets with unconscious posture. Ends encounter, handles mob despawn, sends dramatic messages to both parties.
Players become unconscious when PL or stamina drops to 0. While unconscious, both stats slowly recover at 0.1 per tick (1.0 per second). When both reach above 0, player regains consciousness with a message. Recovery runs in the main game loop via process_unconscious.
When altitude differs at resolve time (attacker or defender changed flying state during window phase), attack misses. Treated as successful dodge with zero damage.
Players must be at same altitude (both flying or both grounded) to initiate combat. Attack fails with 'You can't reach them from here!' if altitude differs.
When combat begins, any active power-up task on either the attacker
or defender should be cancelled to prevent background power changes
during combat. This ensures players can't continue charging while
fighting.
The fix checks both entities for a _power_task attribute and cancels
it if present, then clears the reference.
The {s} conjugation check had incorrect operator precedence that
evaluated the ch/sh suffix check independently of the prev_text
existence check. This could lead to confusing logic flow even
though it didn't crash due to len() handling empty strings safely.
Fixed by wrapping both suffix conditions in parentheses so they're
both guarded by the prev_text truthiness check.