mud/src/mudlib/conversation.py
Jared Miller 67a0290ede
Add talk and reply commands with conversation system
Implements player-NPC dialogue using the dialogue tree data model.
Conversation state tracking manages active conversations and transitions
NPCs to "converse" behavior state during dialogue. Commands support
terminal node cleanup and display formatting with numbered choices.
2026-02-14 14:31:39 -05:00

111 lines
2.9 KiB
Python

"""Conversation state tracking for player-NPC dialogues."""
from dataclasses import dataclass
from mudlib.dialogue import DialogueNode, DialogueTree
from mudlib.entity import Mob
from mudlib.npc_behavior import transition_state
from mudlib.player import Player
@dataclass
class ConversationState:
"""Tracks an active conversation between a player and NPC."""
tree: DialogueTree
current_node: str
npc: Mob
previous_state: str = "idle"
# Global registry of active conversations (keyed by player name)
active_conversations: dict[str, ConversationState] = {}
def start_conversation(player: Player, mob: Mob, tree: DialogueTree) -> DialogueNode:
"""Start a conversation between player and NPC.
Args:
player: The player starting the conversation
mob: The NPC mob to talk to
tree: The dialogue tree to use
Returns:
The root DialogueNode
"""
# Store mob's previous state for restoration
previous_state = mob.behavior_state
# Transition mob to converse state
transition_state(mob, "converse")
# Create conversation state
state = ConversationState(
tree=tree,
current_node=tree.root_node,
npc=mob,
previous_state=previous_state,
)
active_conversations[player.name] = state
return tree.nodes[tree.root_node]
def advance_conversation(player: Player, choice_index: int) -> DialogueNode | None:
"""Advance conversation based on player's choice.
Args:
player: The player making the choice
choice_index: 1-indexed choice number
Returns:
Next DialogueNode (conversation state updated but not ended yet),
or None if invalid choice
"""
conv = active_conversations.get(player.name)
if conv is None:
return None
current = conv.tree.nodes[conv.current_node]
# Validate choice index
if choice_index < 1 or choice_index > len(current.choices):
return None
# Get the next node
choice = current.choices[choice_index - 1]
next_node = conv.tree.nodes[choice.next_node]
# Update conversation state to new node
conv.current_node = next_node.id
return next_node
def end_conversation(player: Player) -> None:
"""End the active conversation and clean up state.
Args:
player: The player whose conversation to end
"""
conv = active_conversations.get(player.name)
if conv is None:
return
# Transition mob back to previous state
transition_state(conv.npc, conv.previous_state)
# Remove conversation state
del active_conversations[player.name]
def get_conversation(player: Player) -> ConversationState | None:
"""Get the active conversation for a player.
Args:
player: The player to look up
Returns:
ConversationState if active, None otherwise
"""
return active_conversations.get(player.name)