Add combat system: NPCs, fights, loot, respawns
NPC class + CombatManager + CombatEncounter 3 NPCs: Shadow Wraith (Bridge), Iron Golem (Forge), Garden Serpent (Garden) 6 endpoints: combat/start, attack, defend, flee, status, npcs Health bars visible to room, loot drops on death
This commit is contained in:
@@ -712,8 +712,382 @@ class InventoryManager:
|
|||||||
|
|
||||||
inventory_manager = InventoryManager()
|
inventory_manager = InventoryManager()
|
||||||
|
|
||||||
|
# ── Combat System ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
COMBAT_FILE = WORLD_DIR / 'combat.json'
|
||||||
|
|
||||||
|
class NPC:
|
||||||
|
"""A non-player character that can be fought."""
|
||||||
|
|
||||||
|
def __init__(self, npc_id: str, name: str, room: str, description: str,
|
||||||
|
health: int, max_health: int, attack: int, defense: int,
|
||||||
|
loot: list[dict], respawn_ticks: int = 5):
|
||||||
|
self.npc_id = npc_id
|
||||||
|
self.name = name
|
||||||
|
self.room = room
|
||||||
|
self.description = description
|
||||||
|
self.health = health
|
||||||
|
self.max_health = max_health
|
||||||
|
self.attack = attack
|
||||||
|
self.defense = defense
|
||||||
|
self.loot = loot # [{name, description, drop_chance}]
|
||||||
|
self.respawn_ticks = respawn_ticks
|
||||||
|
self.alive = True
|
||||||
|
self._dead_tick: int | None = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"npc_id": self.npc_id,
|
||||||
|
"name": self.name,
|
||||||
|
"room": self.room,
|
||||||
|
"description": self.description,
|
||||||
|
"health": self.health,
|
||||||
|
"max_health": self.max_health,
|
||||||
|
"attack": self.attack,
|
||||||
|
"defense": self.defense,
|
||||||
|
"loot": self.loot,
|
||||||
|
"alive": self.alive,
|
||||||
|
"dead_tick": self._dead_tick,
|
||||||
|
"respawn_ticks": self.respawn_ticks,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> 'NPC':
|
||||||
|
npc = cls(
|
||||||
|
data["npc_id"], data["name"], data["room"], data["description"],
|
||||||
|
data["health"], data["max_health"], data["attack"], data["defense"],
|
||||||
|
data["loot"], data.get("respawn_ticks", 5),
|
||||||
|
)
|
||||||
|
npc.alive = data.get("alive", True)
|
||||||
|
npc._dead_tick = data.get("dead_tick")
|
||||||
|
return npc
|
||||||
|
|
||||||
|
def health_bar(self) -> str:
|
||||||
|
"""Return a text health bar like [████████░░] 80/100."""
|
||||||
|
width = 10
|
||||||
|
filled = int((self.health / self.max_health) * width) if self.max_health > 0 else 0
|
||||||
|
bar = "█" * filled + "░" * (width - filled)
|
||||||
|
return f"[{bar}] {self.health}/{self.max_health}"
|
||||||
|
|
||||||
|
def die(self, tick: int):
|
||||||
|
"""Mark NPC as dead."""
|
||||||
|
self.alive = False
|
||||||
|
self.health = 0
|
||||||
|
self._dead_tick = tick
|
||||||
|
|
||||||
|
def respawn(self):
|
||||||
|
"""Respawn the NPC at full health."""
|
||||||
|
self.alive = True
|
||||||
|
self.health = self.max_health
|
||||||
|
self._dead_tick = None
|
||||||
|
|
||||||
|
|
||||||
|
class CombatEncounter:
|
||||||
|
"""An active fight between a player and an NPC."""
|
||||||
|
|
||||||
|
def __init__(self, user_id: str, username: str, room: str, npc_id: str):
|
||||||
|
self.user_id = user_id
|
||||||
|
self.username = username
|
||||||
|
self.room = room
|
||||||
|
self.npc_id = npc_id
|
||||||
|
self.player_hp = 100
|
||||||
|
self.player_max_hp = 100
|
||||||
|
self.player_defending = False
|
||||||
|
self.log: list[str] = []
|
||||||
|
self.active = True
|
||||||
|
self.created_at = datetime.now().isoformat()
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"user_id": self.user_id,
|
||||||
|
"username": self.username,
|
||||||
|
"room": self.room,
|
||||||
|
"npc_id": self.npc_id,
|
||||||
|
"player_hp": self.player_hp,
|
||||||
|
"player_max_hp": self.player_max_hp,
|
||||||
|
"player_defending": self.player_defending,
|
||||||
|
"log": self.log,
|
||||||
|
"active": self.active,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CombatManager:
|
||||||
|
"""Manages NPCs, encounters, and combat resolution."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._npcs: dict[str, NPC] = {} # npc_id -> NPC
|
||||||
|
self._encounters: dict[str, CombatEncounter] = {} # user_id -> encounter
|
||||||
|
self._counter = 0
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
# ── NPC management ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
def create_npc(self, name: str, room: str, description: str,
|
||||||
|
health: int, attack: int, defense: int,
|
||||||
|
loot: list[dict], respawn_ticks: int = 5) -> NPC:
|
||||||
|
with self._lock:
|
||||||
|
self._counter += 1
|
||||||
|
npc_id = f"npc_{self._counter}"
|
||||||
|
npc = NPC(npc_id, name, room, description,
|
||||||
|
health, health, attack, defense, loot, respawn_ticks)
|
||||||
|
self._npcs[npc_id] = npc
|
||||||
|
self._save()
|
||||||
|
return npc
|
||||||
|
|
||||||
|
def get_npc(self, npc_id: str) -> NPC | None:
|
||||||
|
return self._npcs.get(npc_id)
|
||||||
|
|
||||||
|
def get_npcs_in_room(self, room: str) -> list[NPC]:
|
||||||
|
with self._lock:
|
||||||
|
return [n for n in self._npcs.values() if n.room == room and n.alive]
|
||||||
|
|
||||||
|
def get_all_npcs(self) -> list[dict]:
|
||||||
|
with self._lock:
|
||||||
|
return [n.to_dict() for n in self._npcs.values()]
|
||||||
|
|
||||||
|
def check_respawns(self, current_tick: int):
|
||||||
|
"""Called each world tick — respawn NPCs whose timer has elapsed."""
|
||||||
|
with self._lock:
|
||||||
|
for npc in self._npcs.values():
|
||||||
|
if not npc.alive and npc._dead_tick is not None:
|
||||||
|
if current_tick - npc._dead_tick >= npc.respawn_ticks:
|
||||||
|
npc.respawn()
|
||||||
|
self._save()
|
||||||
|
|
||||||
|
# ── Encounter lifecycle ─────────────────────────────────────────
|
||||||
|
|
||||||
|
def start_fight(self, user_id: str, username: str, room: str, npc_id: str) -> dict:
|
||||||
|
"""Begin combat with an NPC."""
|
||||||
|
with self._lock:
|
||||||
|
if user_id in self._encounters and self._encounters[user_id].active:
|
||||||
|
return {"error": "You are already in a fight! Attack or defend."}
|
||||||
|
npc = self._npcs.get(npc_id)
|
||||||
|
if not npc:
|
||||||
|
return {"error": f"NPC '{npc_id}' not found."}
|
||||||
|
if not npc.alive:
|
||||||
|
return {"error": f"{npc.name} is dead. It will respawn later."}
|
||||||
|
if npc.room != room:
|
||||||
|
return {"error": f"{npc.name} is not in this room."}
|
||||||
|
encounter = CombatEncounter(user_id, username, room, npc_id)
|
||||||
|
encounter.log.append(f"Combat started! {username} vs {npc.name}.")
|
||||||
|
self._encounters[user_id] = encounter
|
||||||
|
return {"ok": True, "encounter": encounter.to_dict(), "npc": npc.to_dict()}
|
||||||
|
|
||||||
|
def attack(self, user_id: str) -> dict:
|
||||||
|
"""Player attacks NPC; NPC counter-attacks."""
|
||||||
|
with self._lock:
|
||||||
|
enc = self._encounters.get(user_id)
|
||||||
|
if not enc or not enc.active:
|
||||||
|
return {"error": "No active fight. Start one first."}
|
||||||
|
npc = self._npcs.get(enc.npc_id)
|
||||||
|
if not npc or not npc.alive:
|
||||||
|
enc.active = False
|
||||||
|
return {"error": "The enemy is already dead."}
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Player attack
|
||||||
|
player_dmg = max(1, random.randint(8, 16) - npc.defense // 3)
|
||||||
|
npc.health -= player_dmg
|
||||||
|
enc.log.append(f"You strike {npc.name} for {player_dmg} damage.")
|
||||||
|
|
||||||
|
npc_dmg = 0
|
||||||
|
if npc.health > 0:
|
||||||
|
# NPC counter-attack
|
||||||
|
base_npc_dmg = random.randint(npc.attack // 2, npc.attack)
|
||||||
|
if enc.player_defending:
|
||||||
|
npc_dmg = max(1, base_npc_dmg // 2)
|
||||||
|
enc.log.append(f"{npc.name} attacks for {npc_dmg} (blocked — half damage).")
|
||||||
|
else:
|
||||||
|
npc_dmg = max(1, base_npc_dmg - random.randint(0, 4))
|
||||||
|
enc.log.append(f"{npc.name} attacks you for {npc_dmg} damage.")
|
||||||
|
enc.player_hp -= npc_dmg
|
||||||
|
else:
|
||||||
|
npc.die(self._get_tick())
|
||||||
|
loot_dropped = self._roll_loot(npc)
|
||||||
|
enc.log.append(f"{npc.name} is slain!")
|
||||||
|
if loot_dropped:
|
||||||
|
for item in loot_dropped:
|
||||||
|
inventory_manager.add_room_item(enc.room, item["name"], item["description"], dropped_by=npc.name)
|
||||||
|
enc.log.append(f"Loot dropped: {item['name']}")
|
||||||
|
enc.active = False
|
||||||
|
self._save()
|
||||||
|
|
||||||
|
enc.player_defending = False
|
||||||
|
|
||||||
|
# Check player death
|
||||||
|
if enc.player_hp <= 0:
|
||||||
|
enc.player_hp = 0
|
||||||
|
enc.log.append("You have been defeated!")
|
||||||
|
enc.active = False
|
||||||
|
|
||||||
|
return {"ok": True, "encounter": enc.to_dict(), "npc": npc.to_dict(),
|
||||||
|
"player_dmg": player_dmg, "npc_dmg": npc_dmg,
|
||||||
|
"loot": loot_dropped if not npc.alive else []}
|
||||||
|
|
||||||
|
def defend(self, user_id: str) -> dict:
|
||||||
|
"""Player defends — next NPC attack does half damage."""
|
||||||
|
with self._lock:
|
||||||
|
enc = self._encounters.get(user_id)
|
||||||
|
if not enc or not enc.active:
|
||||||
|
return {"error": "No active fight."}
|
||||||
|
npc = self._npcs.get(enc.npc_id)
|
||||||
|
if not npc or not npc.alive:
|
||||||
|
enc.active = False
|
||||||
|
return {"error": "The enemy is already dead."}
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
enc.player_defending = True
|
||||||
|
enc.log.append("You brace for the next attack (defending).")
|
||||||
|
|
||||||
|
# NPC still attacks
|
||||||
|
base_npc_dmg = random.randint(npc.attack // 2, npc.attack)
|
||||||
|
npc_dmg = max(1, base_npc_dmg // 2)
|
||||||
|
enc.log.append(f"{npc.name} attacks for {npc_dmg} (blocked — half damage).")
|
||||||
|
enc.player_hp -= npc_dmg
|
||||||
|
|
||||||
|
if enc.player_hp <= 0:
|
||||||
|
enc.player_hp = 0
|
||||||
|
enc.log.append("You have been defeated!")
|
||||||
|
enc.active = False
|
||||||
|
|
||||||
|
return {"ok": True, "encounter": enc.to_dict(), "npc": npc.to_dict(), "npc_dmg": npc_dmg}
|
||||||
|
|
||||||
|
def flee(self, user_id: str) -> dict:
|
||||||
|
"""Player flees combat."""
|
||||||
|
with self._lock:
|
||||||
|
enc = self._encounters.get(user_id)
|
||||||
|
if not enc or not enc.active:
|
||||||
|
return {"error": "No active fight."}
|
||||||
|
enc.active = False
|
||||||
|
enc.log.append("You flee from combat!")
|
||||||
|
return {"ok": True, "encounter": enc.to_dict()}
|
||||||
|
|
||||||
|
def get_encounter(self, user_id: str) -> CombatEncounter | None:
|
||||||
|
return self._encounters.get(user_id)
|
||||||
|
|
||||||
|
def get_room_combat_status(self, room: str) -> list[dict]:
|
||||||
|
"""Get all active fights and NPC health in a room (for health bars)."""
|
||||||
|
with self._lock:
|
||||||
|
status = []
|
||||||
|
# NPCs
|
||||||
|
for npc in self._npcs.values():
|
||||||
|
if npc.room == room:
|
||||||
|
status.append({
|
||||||
|
"type": "npc",
|
||||||
|
"name": npc.name,
|
||||||
|
"npc_id": npc.npc_id,
|
||||||
|
"alive": npc.alive,
|
||||||
|
"health_bar": npc.health_bar() if npc.alive else "[dead]",
|
||||||
|
"health": npc.health,
|
||||||
|
"max_health": npc.max_health,
|
||||||
|
})
|
||||||
|
# Active player fights
|
||||||
|
for enc in self._encounters.values():
|
||||||
|
if enc.room == room and enc.active:
|
||||||
|
status.append({
|
||||||
|
"type": "player",
|
||||||
|
"username": enc.username,
|
||||||
|
"user_id": enc.user_id,
|
||||||
|
"hp": enc.player_hp,
|
||||||
|
"max_hp": enc.player_max_hp,
|
||||||
|
"fighting": enc.npc_id,
|
||||||
|
})
|
||||||
|
return status
|
||||||
|
|
||||||
|
def _roll_loot(self, npc: NPC) -> list[dict]:
|
||||||
|
"""Roll for loot drops."""
|
||||||
|
import random
|
||||||
|
dropped = []
|
||||||
|
for item in npc.loot:
|
||||||
|
chance = item.get("drop_chance", 1.0)
|
||||||
|
if random.random() < chance:
|
||||||
|
dropped.append({"name": item["name"], "description": item["description"]})
|
||||||
|
return dropped
|
||||||
|
|
||||||
|
def _get_tick(self) -> int:
|
||||||
|
"""Read current world tick."""
|
||||||
|
state_file = WORLD_DIR / 'world_state.json'
|
||||||
|
if state_file.exists():
|
||||||
|
try:
|
||||||
|
state = json.loads(state_file.read_text())
|
||||||
|
return state.get("tick", 0)
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# ── Persistence ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
try:
|
||||||
|
data = {
|
||||||
|
"counter": self._counter,
|
||||||
|
"npcs": {nid: n.to_dict() for nid, n in self._npcs.items()},
|
||||||
|
}
|
||||||
|
COMBAT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
COMBAT_FILE.write_text(json.dumps(data, indent=2))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[CombatManager] Save failed: {e}")
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
if not COMBAT_FILE.exists():
|
||||||
|
self._seed_npcs()
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
data = json.loads(COMBAT_FILE.read_text())
|
||||||
|
self._counter = data.get("counter", 0)
|
||||||
|
for nid, ndata in data.get("npcs", {}).items():
|
||||||
|
self._npcs[nid] = NPC.from_dict(ndata)
|
||||||
|
print(f"[CombatManager] Loaded {len(self._npcs)} NPCs.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[CombatManager] Load failed: {e}")
|
||||||
|
self._seed_npcs()
|
||||||
|
|
||||||
|
def _seed_npcs(self):
|
||||||
|
"""Seed starter NPCs."""
|
||||||
|
self.create_npc(
|
||||||
|
"Shadow Wraith", "The Bridge",
|
||||||
|
"A swirling mass of darkness with glowing eyes. It haunts the bridge, "
|
||||||
|
"feeding on the fears of those who cross.",
|
||||||
|
health=60, attack=14, defense=4,
|
||||||
|
loot=[
|
||||||
|
{"name": "Wraith Essence", "description": "A vial of shimmering dark energy.", "drop_chance": 0.8},
|
||||||
|
{"name": "Shadow Cloak", "description": "A cloak woven from pure shadow.", "drop_chance": 0.3},
|
||||||
|
],
|
||||||
|
respawn_ticks=5,
|
||||||
|
)
|
||||||
|
self.create_npc(
|
||||||
|
"Iron Golem", "The Forge",
|
||||||
|
"A hulking automaton of iron and fire. It guards the forge with tireless vigilance, "
|
||||||
|
"its joints grinding with every step.",
|
||||||
|
health=100, attack=10, defense=10,
|
||||||
|
loot=[
|
||||||
|
{"name": "Golem Core", "description": "A warm crystal that powered the golem.", "drop_chance": 0.7},
|
||||||
|
{"name": "Iron Shard", "description": "A shard of enchanted iron, still warm.", "drop_chance": 0.5},
|
||||||
|
],
|
||||||
|
respawn_ticks=7,
|
||||||
|
)
|
||||||
|
self.create_npc(
|
||||||
|
"Garden Serpent", "The Garden",
|
||||||
|
"A massive vine serpent camouflaged among the herbs. Its fangs drip with "
|
||||||
|
"a sickly green venom.",
|
||||||
|
health=45, attack=18, defense=2,
|
||||||
|
loot=[
|
||||||
|
{"name": "Serpent Fang", "description": "A curved fang, still coated in venom.", "drop_chance": 0.9},
|
||||||
|
{"name": "Enchanted Vine", "description": "A living vine that moves on its own.", "drop_chance": 0.4},
|
||||||
|
],
|
||||||
|
respawn_ticks=4,
|
||||||
|
)
|
||||||
|
print(f"[CombatManager] Seeded {len(self._npcs)} NPCs.")
|
||||||
|
|
||||||
|
|
||||||
|
combat_manager = CombatManager()
|
||||||
|
|
||||||
# ── Session Management ─────────────────────────────────────────────────
|
# ── Session Management ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
combat_manager.load()
|
||||||
|
|
||||||
class UserSession:
|
class UserSession:
|
||||||
"""Isolated conversation context for one user."""
|
"""Isolated conversation context for one user."""
|
||||||
|
|
||||||
@@ -1238,9 +1612,17 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
|||||||
self._json_response(saved)
|
self._json_response(saved)
|
||||||
else:
|
else:
|
||||||
self._json_response({"error": "no session or saved summary"}, 404)
|
self._json_response({"error": "no session or saved summary"}, 404)
|
||||||
|
elif self.path.startswith('/bridge/combat/status/'):
|
||||||
|
# GET /bridge/combat/status/<room> — NPC health bars + active fights in room
|
||||||
|
room = self.path.split('/bridge/combat/status/')[-1].rstrip('/')
|
||||||
|
status = combat_manager.get_room_combat_status(room)
|
||||||
|
self._json_response({"room": room, "combat_status": status})
|
||||||
|
elif self.path == '/bridge/combat/npcs':
|
||||||
|
# GET /bridge/combat/npcs — list all NPCs
|
||||||
|
self._json_response({"npcs": combat_manager.get_all_npcs()})
|
||||||
else:
|
else:
|
||||||
self._json_response({"error": "not found"}, 404)
|
self._json_response({"error": "not found"}, 404)
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
content_length = int(self.headers.get('Content-Length', 0))
|
content_length = int(self.headers.get('Content-Length', 0))
|
||||||
body = json.loads(self.rfile.read(content_length)) if content_length else {}
|
body = json.loads(self.rfile.read(content_length)) if content_length else {}
|
||||||
@@ -1447,6 +1829,91 @@ class BridgeHandler(BaseHTTPRequestHandler):
|
|||||||
exclude_user=user_id, data=result)
|
exclude_user=user_id, data=result)
|
||||||
self._json_response(result)
|
self._json_response(result)
|
||||||
|
|
||||||
|
elif self.path == '/bridge/combat/start':
|
||||||
|
# POST /bridge/combat/start — begin fight with NPC
|
||||||
|
# Body: { user_id, username, room, npc_id }
|
||||||
|
user_id = body.get('user_id', 'anonymous')
|
||||||
|
username = body.get('username', 'Anonymous')
|
||||||
|
room = body.get('room', 'The Threshold')
|
||||||
|
npc_id = body.get('npc_id', '')
|
||||||
|
if not npc_id:
|
||||||
|
self._json_response({"error": "npc_id required"}, 400)
|
||||||
|
return
|
||||||
|
result = combat_manager.start_fight(user_id, username, room, npc_id)
|
||||||
|
if "error" in result:
|
||||||
|
self._json_response(result, 400)
|
||||||
|
else:
|
||||||
|
npc = result.get("npc", {})
|
||||||
|
notification_manager.broadcast_room(
|
||||||
|
room, "combat_start",
|
||||||
|
f"{username} has engaged {npc.get('name', 'an enemy')} in combat!",
|
||||||
|
exclude_user=user_id, data=result)
|
||||||
|
self._json_response(result)
|
||||||
|
|
||||||
|
elif self.path == '/bridge/combat/attack':
|
||||||
|
# POST /bridge/combat/attack — attack NPC in active fight
|
||||||
|
# Body: { user_id, username, room }
|
||||||
|
user_id = body.get('user_id', 'anonymous')
|
||||||
|
username = body.get('username', 'Anonymous')
|
||||||
|
room = body.get('room', 'The Threshold')
|
||||||
|
result = combat_manager.attack(user_id)
|
||||||
|
if "error" in result:
|
||||||
|
self._json_response(result, 400)
|
||||||
|
else:
|
||||||
|
enc = result.get("encounter", {})
|
||||||
|
npc = result.get("npc", {})
|
||||||
|
loot = result.get("loot", [])
|
||||||
|
# Broadcast combat action to room
|
||||||
|
combat_msg = f"{username} strikes {npc.get('name', 'the enemy')} for {result.get('player_dmg', 0)} damage!"
|
||||||
|
notification_manager.broadcast_room(
|
||||||
|
room, "combat_action", combat_msg,
|
||||||
|
exclude_user=user_id, data=result)
|
||||||
|
# If NPC died, broadcast death
|
||||||
|
if not npc.get("alive", True):
|
||||||
|
death_msg = f"{npc.get('name', 'The enemy')} has been slain by {username}!"
|
||||||
|
if loot:
|
||||||
|
death_msg += f" Loot: {', '.join(i['name'] for i in loot)}"
|
||||||
|
notification_manager.broadcast_room(
|
||||||
|
room, "combat_death", death_msg, data=result)
|
||||||
|
# If player died
|
||||||
|
if enc.get("player_hp", 1) <= 0:
|
||||||
|
notification_manager.broadcast_room(
|
||||||
|
room, "combat_defeat",
|
||||||
|
f"{username} has been defeated in combat!", data=result)
|
||||||
|
self._json_response(result)
|
||||||
|
|
||||||
|
elif self.path == '/bridge/combat/defend':
|
||||||
|
# POST /bridge/combat/defend — defend (half damage next hit)
|
||||||
|
# Body: { user_id, username, room }
|
||||||
|
user_id = body.get('user_id', 'anonymous')
|
||||||
|
username = body.get('username', 'Anonymous')
|
||||||
|
room = body.get('room', 'The Threshold')
|
||||||
|
result = combat_manager.defend(user_id)
|
||||||
|
if "error" in result:
|
||||||
|
self._json_response(result, 400)
|
||||||
|
else:
|
||||||
|
notification_manager.broadcast_room(
|
||||||
|
room, "combat_action",
|
||||||
|
f"{username} braces defensively.",
|
||||||
|
exclude_user=user_id, data=result)
|
||||||
|
self._json_response(result)
|
||||||
|
|
||||||
|
elif self.path == '/bridge/combat/flee':
|
||||||
|
# POST /bridge/combat/flee — flee from combat
|
||||||
|
# Body: { user_id, username, room }
|
||||||
|
user_id = body.get('user_id', 'anonymous')
|
||||||
|
username = body.get('username', 'Anonymous')
|
||||||
|
room = body.get('room', 'The Threshold')
|
||||||
|
result = combat_manager.flee(user_id)
|
||||||
|
if "error" in result:
|
||||||
|
self._json_response(result, 400)
|
||||||
|
else:
|
||||||
|
notification_manager.broadcast_room(
|
||||||
|
room, "combat_flee",
|
||||||
|
f"{username} flees from combat!",
|
||||||
|
exclude_user=user_id, data=result)
|
||||||
|
self._json_response(result)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._json_response({"error": "not found"}, 404)
|
self._json_response({"error": "not found"}, 404)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user