- server.py: broadcast IF output to spectators after each input (skip :: escape commands)
- server.py: broadcast leave message when player exits IF mode
- play.py: broadcast game intro text when player starts a game
Spectators at the same x,y coordinates now see formatted output with
[PlayerName's terminal] header and game text.
Helper function sends messages to all players at the same x,y coordinates
as the source player, skipping the source player themselves. Used for IF
spectator broadcasting.
Tests verify:
- Spectators at same location see IF output with player name header
- Spectators at different locations see nothing
- Game start intro broadcasts to spectators
- broadcast_to_spectators skips the playing player
- Multiple spectators all receive messages
Tests currently fail as broadcast_to_spectators not yet implemented.
TDD implementation of IFSession that manages a dfrotz subprocess.
IFResponse dataclass follows the editor pattern with output/done fields.
IFSession handles spawning dfrotz, routing input, and detecting the prompt.
Escape commands (::quit, ::help) are handled without sending to dfrotz.
Phase 6: spawn command creates mobs at player position from loaded
templates. Server loads mob templates from content/mobs/ at startup,
injects world into combat/commands module, and runs process_mobs()
each game loop tick after process_combat().
Phase 5: process_mobs() runs each tick, handling mob attack and defense
decisions. Mobs pick random attacks from their move list when IDLE,
swap roles if needed, and attempt defense during TELEGRAPH/WINDOW with
a 40% chance of correct counter. 1-second cooldown between actions.
Training dummies with empty moves never fight back.
Phase 4: when combat ends, determine winner/loser. If the loser is a
Mob, despawn it and send a victory message to the winner. If the loser
is a Player fighting a Mob, send a defeat message instead.
Phase 3: look command now collects alive mob positions using the same
wrapping-aware relative position calc as players, and renders them as *
with the same priority as other players (after @ but before effects).
Phase 2: do_attack now searches the mobs registry after players dict
when resolving a target name. Players always take priority over mobs
with the same name. World instance injected into combat/commands module
for wrapping-aware mob proximity checks.
Phase 1 of fightable mobs: MobTemplate dataclass loaded from TOML,
global mobs list, spawn_mob/despawn_mob/get_nearby_mob with
wrapping-aware distance. Mob entity gets moves and next_action_at fields.
The variant handler now supports prefix matching for directional variants.
This allows 'pa hi' to match 'parry high', 'pa lo' to match 'parry low', etc.
Implementation:
- First tries exact match on variant key
- Falls back to prefix matching if no exact match
- Returns unique match if exactly one variant starts with the prefix
- Shows disambiguation message if multiple variants match
- Shows error with valid options if no variants match
Tests cover exact match, prefix match, ambiguous prefix, no match,
single-char prefix, case-insensitivity, and preservation of target args.
Defense moves now asyncio.sleep for timing_window_ms instead of using
a cooldown field. Input queues naturally since the per-player loop is
sequential. Outside combat shows "parry the air!" flavor text.