fix conversation events, rename look to examine, generate indoor/outdoor attributes
This commit is contained in:
parent
66c2c20031
commit
80e98482e0
|
@ -119,6 +119,32 @@ export function SnapshotEventItem(props: EventItemProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReplyEventItem(props: EventItemProps) {
|
export function ReplyEventItem(props: EventItemProps) {
|
||||||
|
const { event } = props;
|
||||||
|
const { audience, speaker, text } = event;
|
||||||
|
|
||||||
|
return <ListItem alignItems="flex-start" ref={props.focusRef}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar alt="System">
|
||||||
|
<Settings />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary="System"
|
||||||
|
secondary={
|
||||||
|
<Typography
|
||||||
|
sx={{ display: 'block' }}
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
color="text.primary"
|
||||||
|
>
|
||||||
|
{speaker.name} replies to {audience.name}: {text}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatusEventItem(props: EventItemProps) {
|
||||||
const { event } = props;
|
const { event } = props;
|
||||||
const { text } = event;
|
const { text } = event;
|
||||||
|
|
||||||
|
@ -295,8 +321,9 @@ export function EventItem(props: EventItemProps) {
|
||||||
case 'result':
|
case 'result':
|
||||||
return <ActionEventItem {...props} />;
|
return <ActionEventItem {...props} />;
|
||||||
case 'reply':
|
case 'reply':
|
||||||
case 'status': // TODO: should have a different component
|
|
||||||
return <ReplyEventItem {...props} />;
|
return <ReplyEventItem {...props} />;
|
||||||
|
case 'status':
|
||||||
|
return <StatusEventItem {...props} />;
|
||||||
case 'player':
|
case 'player':
|
||||||
return <PlayerEventItem {...props} />;
|
return <PlayerEventItem {...props} />;
|
||||||
case 'render':
|
case 'render':
|
||||||
|
|
|
@ -13,6 +13,7 @@ from taleweave.utils.search import (
|
||||||
find_character_in_room,
|
find_character_in_room,
|
||||||
find_item_in_character,
|
find_item_in_character,
|
||||||
find_item_in_room,
|
find_item_in_room,
|
||||||
|
find_portal_in_room,
|
||||||
find_room,
|
find_room,
|
||||||
)
|
)
|
||||||
from taleweave.utils.string import normalize_name
|
from taleweave.utils.string import normalize_name
|
||||||
|
@ -20,12 +21,12 @@ from taleweave.utils.world import describe_entity
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
MAX_CONVERSATION_STEPS = 3
|
MAX_CONVERSATION_STEPS = 2
|
||||||
|
|
||||||
|
|
||||||
def action_look(target: str) -> str:
|
def action_examine(target: str) -> str:
|
||||||
"""
|
"""
|
||||||
Look at a target in the room or your inventory.
|
Examine the room, a character, or an item (in the room or in your inventory).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
target: The name of the target to look at.
|
target: The name of the target to look at.
|
||||||
|
@ -71,14 +72,7 @@ def action_move(direction: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with world_context() as (action_world, action_room, action_character):
|
with world_context() as (action_world, action_room, action_character):
|
||||||
portal = next(
|
portal = find_portal_in_room(action_room, direction)
|
||||||
(
|
|
||||||
p
|
|
||||||
for p in action_room.portals
|
|
||||||
if normalize_name(p.name) == normalize_name(direction)
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if not portal:
|
if not portal:
|
||||||
raise ActionError(f"You cannot move {direction} from here.")
|
raise ActionError(f"You cannot move {direction} from here.")
|
||||||
|
|
||||||
|
@ -151,7 +145,7 @@ def action_ask(character: str, question: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
action_agent = get_agent_for_character(action_character)
|
action_agent = get_agent_for_character(action_character)
|
||||||
answer = loop_conversation(
|
result = loop_conversation(
|
||||||
action_room,
|
action_room,
|
||||||
[question_character, action_character],
|
[question_character, action_character],
|
||||||
[question_agent, action_agent],
|
[question_agent, action_agent],
|
||||||
|
@ -165,9 +159,8 @@ def action_ask(character: str, question: str) -> str:
|
||||||
max_length=MAX_CONVERSATION_STEPS,
|
max_length=MAX_CONVERSATION_STEPS,
|
||||||
)
|
)
|
||||||
|
|
||||||
if answer:
|
if result:
|
||||||
broadcast(f"{character} responds to {action_character.name}: {answer}")
|
return result
|
||||||
return f"{character} responds: {answer}"
|
|
||||||
|
|
||||||
return f"{character} does not respond."
|
return f"{character} does not respond."
|
||||||
|
|
||||||
|
@ -209,7 +202,7 @@ def action_tell(character: str, message: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
action_agent = get_agent_for_character(action_character)
|
action_agent = get_agent_for_character(action_character)
|
||||||
answer = loop_conversation(
|
result = loop_conversation(
|
||||||
action_room,
|
action_room,
|
||||||
[question_character, action_character],
|
[question_character, action_character],
|
||||||
[question_agent, action_agent],
|
[question_agent, action_agent],
|
||||||
|
@ -223,9 +216,8 @@ def action_tell(character: str, message: str) -> str:
|
||||||
max_length=MAX_CONVERSATION_STEPS,
|
max_length=MAX_CONVERSATION_STEPS,
|
||||||
)
|
)
|
||||||
|
|
||||||
if answer:
|
if result:
|
||||||
broadcast(f"{character} responds to {action_character.name}: {answer}")
|
return result
|
||||||
return f"{character} responds: {answer}"
|
|
||||||
|
|
||||||
return f"{character} does not respond."
|
return f"{character} does not respond."
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,10 @@ def take_note(fact: str):
|
||||||
|
|
||||||
with action_context() as (_, action_character):
|
with action_context() as (_, action_character):
|
||||||
if fact in action_character.planner.notes:
|
if fact in action_character.planner.notes:
|
||||||
raise ActionError("You already have a note about that fact.")
|
raise ActionError(
|
||||||
|
"You already have a note about that fact. You do not need to take duplicate notes. "
|
||||||
|
"If you have too many notes, consider erasing, replacing, or summarizing them."
|
||||||
|
)
|
||||||
|
|
||||||
if len(action_character.planner.notes) >= character_config.note_limit:
|
if len(action_character.planner.notes) >= character_config.note_limit:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
|
@ -103,7 +106,8 @@ def summarize_notes(limit: int) -> str:
|
||||||
"If a newer note contradicts an older note, keep the newer note. "
|
"If a newer note contradicts an older note, keep the newer note. "
|
||||||
"Clean up your notes so you can focus on the most important facts. "
|
"Clean up your notes so you can focus on the most important facts. "
|
||||||
"Respond with one note per line. You can have up to {limit} notes, "
|
"Respond with one note per line. You can have up to {limit} notes, "
|
||||||
"so make sure you reply with less than {limit} lines. "
|
"so make sure you reply with less than {limit} lines. Do not number the lines "
|
||||||
|
"in your response. Do not include any JSON or other information. "
|
||||||
"Your notes are:\n{notes}",
|
"Your notes are:\n{notes}",
|
||||||
limit=limit,
|
limit=limit,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
|
|
|
@ -330,7 +330,7 @@ def embed_from_event(event: GameEvent) -> Embed | None:
|
||||||
|
|
||||||
|
|
||||||
def embed_from_action(event: ActionEvent | ReplyEvent):
|
def embed_from_action(event: ActionEvent | ReplyEvent):
|
||||||
action_embed = Embed(title=event.room.name, description=event.character.name)
|
action_embed = Embed(title=event.room.name, description=event.speaker.name)
|
||||||
|
|
||||||
if isinstance(event, ActionEvent):
|
if isinstance(event, ActionEvent):
|
||||||
action_name = event.action.replace("action_", "").title()
|
action_name = event.action.replace("action_", "").title()
|
||||||
|
|
|
@ -200,11 +200,14 @@ def load_or_initialize_system_data(args, systems: List[GameSystem], world: World
|
||||||
logger.info(f"loading system data from {system_data_file}")
|
logger.info(f"loading system data from {system_data_file}")
|
||||||
data = system.data.load(system_data_file)
|
data = system.data.load(system_data_file)
|
||||||
set_system_data(system.name, data)
|
set_system_data(system.name, data)
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
logger.info(f"no system data found at {system_data_file}")
|
logger.info(f"no system data found at {system_data_file}")
|
||||||
if system.initialize:
|
|
||||||
data = system.initialize(world)
|
if system.initialize:
|
||||||
set_system_data(system.name, data)
|
logger.info(f"initializing system data for {system.name}")
|
||||||
|
data = system.initialize(world)
|
||||||
|
set_system_data(system.name, data)
|
||||||
|
|
||||||
|
|
||||||
def save_system_data(args, systems: List[GameSystem]):
|
def save_system_data(args, systems: List[GameSystem]):
|
||||||
|
|
|
@ -71,22 +71,15 @@ class PromptEvent(BaseModel):
|
||||||
class ReplyEvent(BaseModel):
|
class ReplyEvent(BaseModel):
|
||||||
"""
|
"""
|
||||||
A character has replied with text.
|
A character has replied with text.
|
||||||
|
|
||||||
This is the non-JSON version of an ActionEvent.
|
|
||||||
|
|
||||||
TODO: add the character being replied to.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: str
|
|
||||||
room: Room
|
room: Room
|
||||||
character: Character
|
speaker: Character
|
||||||
|
audience: Character | Room
|
||||||
|
text: str
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["reply"] = "reply"
|
type: Literal["reply"] = "reply"
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_text(text: str, room: Room, character: Character) -> "ReplyEvent":
|
|
||||||
return ReplyEvent(text=text, room=room, character=character)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ResultEvent(BaseModel):
|
class ResultEvent(BaseModel):
|
||||||
|
|
|
@ -193,6 +193,7 @@ class RemotePlayer(BasePlayer):
|
||||||
logger.exception("error getting reply from remote player")
|
logger.exception("error getting reply from remote player")
|
||||||
|
|
||||||
if self.fallback_agent:
|
if self.fallback_agent:
|
||||||
|
logger.info("prompting fallback agent: {self.fallback_agent.name}")
|
||||||
return self.fallback_agent(prompt, **kwargs)
|
return self.fallback_agent(prompt, **kwargs)
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -230,7 +230,7 @@ def get_image_prefix(event: GameEvent | WorldEntity) -> str:
|
||||||
|
|
||||||
if isinstance(event, ReplyEvent):
|
if isinstance(event, ReplyEvent):
|
||||||
return sanitize_name(
|
return sanitize_name(
|
||||||
f"event-reply-{event.character.name}-{fast_hash(event.text)}"
|
f"event-reply-{event.speaker.name}-{fast_hash(event.text)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(event, ResultEvent):
|
if isinstance(event, ResultEvent):
|
||||||
|
|
|
@ -97,7 +97,7 @@ def scene_from_event(event: GameEvent) -> str | None:
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(event, ReplyEvent):
|
if isinstance(event, ReplyEvent):
|
||||||
return f"{event.character.name} replies: {event.text}. {describe_entity(event.character)}. {describe_entity(event.room)}."
|
return f"{event.speaker.name} replies: {event.text}. {describe_entity(event.speaker)}. {describe_entity(event.room)}."
|
||||||
|
|
||||||
if isinstance(event, ResultEvent):
|
if isinstance(event, ResultEvent):
|
||||||
return f"{event.result}. {describe_entity(event.character)}. {describe_entity(event.room)}."
|
return f"{event.result}. {describe_entity(event.character)}. {describe_entity(event.room)}."
|
||||||
|
|
|
@ -8,14 +8,14 @@ from typing import Callable, Sequence
|
||||||
from packit.agent import Agent
|
from packit.agent import Agent
|
||||||
from packit.conditions import condition_or, condition_threshold
|
from packit.conditions import condition_or, condition_threshold
|
||||||
from packit.loops import loop_retry
|
from packit.loops import loop_retry
|
||||||
from packit.results import multi_function_or_str_result
|
from packit.results import function_result
|
||||||
from packit.toolbox import Toolbox
|
from packit.toolbox import Toolbox
|
||||||
from packit.utils import could_be_json
|
from packit.utils import could_be_json
|
||||||
|
|
||||||
from taleweave.actions.base import (
|
from taleweave.actions.base import (
|
||||||
action_ask,
|
action_ask,
|
||||||
|
action_examine,
|
||||||
action_give,
|
action_give,
|
||||||
action_look,
|
|
||||||
action_move,
|
action_move,
|
||||||
action_take,
|
action_take,
|
||||||
action_tell,
|
action_tell,
|
||||||
|
@ -45,11 +45,11 @@ from taleweave.context import (
|
||||||
from taleweave.game_system import GameSystem
|
from taleweave.game_system import GameSystem
|
||||||
from taleweave.models.config import DEFAULT_CONFIG
|
from taleweave.models.config import DEFAULT_CONFIG
|
||||||
from taleweave.models.entity import Character, Room, World
|
from taleweave.models.entity import Character, Room, World
|
||||||
from taleweave.models.event import ActionEvent, ReplyEvent, ResultEvent
|
from taleweave.models.event import ActionEvent, ResultEvent
|
||||||
from taleweave.utils.conversation import make_keyword_condition, summarize_room
|
from taleweave.utils.conversation import make_keyword_condition, summarize_room
|
||||||
from taleweave.utils.effect import expire_effects
|
from taleweave.utils.effect import expire_effects
|
||||||
from taleweave.utils.planning import expire_events, get_upcoming_events
|
from taleweave.utils.planning import expire_events, get_upcoming_events
|
||||||
from taleweave.utils.search import find_room_with_character
|
from taleweave.utils.search import find_containing_room
|
||||||
from taleweave.utils.world import describe_entity, format_attributes
|
from taleweave.utils.world import describe_entity, format_attributes
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
@ -76,7 +76,7 @@ def world_result_parser(value, agent, **kwargs):
|
||||||
set_current_room(current_room)
|
set_current_room(current_room)
|
||||||
set_current_character(current_character)
|
set_current_character(current_character)
|
||||||
|
|
||||||
return multi_function_or_str_result(value, agent=agent, **kwargs)
|
return function_result(value, agent=agent, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def prompt_character_action(
|
def prompt_character_action(
|
||||||
|
@ -94,7 +94,7 @@ def prompt_character_action(
|
||||||
character_items = [item.name for item in character.items]
|
character_items = [item.name for item in character.items]
|
||||||
|
|
||||||
# set up a result parser for the agent
|
# set up a result parser for the agent
|
||||||
def result_parser(value, agent, **kwargs):
|
def result_parser(value, **kwargs):
|
||||||
if not room or not character:
|
if not room or not character:
|
||||||
raise ValueError("Room and character must be set before parsing results")
|
raise ValueError("Room and character must be set before parsing results")
|
||||||
|
|
||||||
|
@ -117,11 +117,12 @@ def prompt_character_action(
|
||||||
if could_be_json(value):
|
if could_be_json(value):
|
||||||
event = ActionEvent.from_json(value, room, character)
|
event = ActionEvent.from_json(value, room, character)
|
||||||
else:
|
else:
|
||||||
event = ReplyEvent.from_text(value, room, character)
|
# TODO: this should be removed and throw
|
||||||
|
event = ResultEvent(value, room, character)
|
||||||
|
|
||||||
broadcast(event)
|
broadcast(event)
|
||||||
|
|
||||||
return world_result_parser(value, agent, **kwargs)
|
return world_result_parser(value, **kwargs)
|
||||||
|
|
||||||
# prompt and act
|
# prompt and act
|
||||||
logger.info("starting turn for character: %s", character.name)
|
logger.info("starting turn for character: %s", character.name)
|
||||||
|
@ -255,7 +256,7 @@ def simulate_world(
|
||||||
[
|
[
|
||||||
action_ask,
|
action_ask,
|
||||||
action_give,
|
action_give,
|
||||||
action_look,
|
action_examine,
|
||||||
action_move,
|
action_move,
|
||||||
action_take,
|
action_take,
|
||||||
action_tell,
|
action_tell,
|
||||||
|
@ -288,7 +289,7 @@ def simulate_world(
|
||||||
logger.error(f"agent or character not found for name {character_name}")
|
logger.error(f"agent or character not found for name {character_name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
room = find_room_with_character(world, character)
|
room = find_containing_room(world, character)
|
||||||
if not room:
|
if not room:
|
||||||
logger.error(f"character {character_name} is not in a room")
|
logger.error(f"character {character_name} is not in a room")
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
|
from functools import partial
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from taleweave.context import get_dungeon_master
|
||||||
from taleweave.models.base import dataclass
|
from taleweave.models.base import dataclass
|
||||||
from taleweave.models.entity import World
|
from taleweave.models.entity import World
|
||||||
from taleweave.systems.logic import load_logic
|
from taleweave.systems.logic import load_logic
|
||||||
from taleweave.game_system import GameSystem
|
from taleweave.game_system import GameSystem
|
||||||
|
from packit.agent import Agent
|
||||||
|
from taleweave.models.entity import Room, WorldEntity
|
||||||
|
from taleweave.utils.string import or_list
|
||||||
|
from packit.results import enum_result
|
||||||
|
from packit.loops import loop_retry
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
LOGIC_FILES = [
|
LOGIC_FILES = [
|
||||||
"./taleweave/systems/weather/weather_logic.yaml",
|
"./taleweave/systems/weather/weather_logic.yaml",
|
||||||
|
@ -39,10 +50,36 @@ def get_time_of_day(turn: int) -> TimeOfDay:
|
||||||
def initialize_weather(world: World):
|
def initialize_weather(world: World):
|
||||||
time_of_day = get_time_of_day(0)
|
time_of_day = get_time_of_day(0)
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
|
logger.info(f"initializing weather for {room.name}")
|
||||||
room.attributes["time"] = time_of_day.name
|
room.attributes["time"] = time_of_day.name
|
||||||
|
|
||||||
|
if "environment" not in room.attributes:
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
generate_room_weather(dungeon_master, world.theme, room)
|
||||||
|
|
||||||
# TODO: generate indoor/outdoor attributes
|
|
||||||
|
def generate_room_weather(agent: Agent, theme: str, entity: Room) -> None:
|
||||||
|
environment_options = ["indoor", "outdoor"]
|
||||||
|
environment_result = partial(enum_result, enum=environment_options)
|
||||||
|
environment = loop_retry(
|
||||||
|
agent,
|
||||||
|
"Is this room indoors or outdoors?"
|
||||||
|
"Reply with a single word: {environment_list}.\n\n"
|
||||||
|
"{description}",
|
||||||
|
context={
|
||||||
|
"environment_list": or_list(environment_options),
|
||||||
|
"description": entity.description,
|
||||||
|
},
|
||||||
|
result_parser=environment_result,
|
||||||
|
)
|
||||||
|
entity.attributes["environment"] = environment
|
||||||
|
logger.info(f"generated environment for {entity.name}: {environment}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_weather(agent: Agent, theme: str, entity: WorldEntity) -> None:
|
||||||
|
if isinstance(entity, Room):
|
||||||
|
if "environment" not in entity.attributes:
|
||||||
|
generate_room_weather(agent, theme, entity)
|
||||||
|
|
||||||
|
|
||||||
def simulate_weather(world: World, turn: int, data: None = None):
|
def simulate_weather(world: World, turn: int, data: None = None):
|
||||||
|
@ -55,5 +92,10 @@ def init():
|
||||||
logic_systems = [load_logic(filename) for filename in LOGIC_FILES]
|
logic_systems = [load_logic(filename) for filename in LOGIC_FILES]
|
||||||
return [
|
return [
|
||||||
*logic_systems,
|
*logic_systems,
|
||||||
GameSystem("weather", initialize=initialize_weather, simulate=simulate_weather),
|
GameSystem(
|
||||||
|
"weather",
|
||||||
|
generate=generate_weather,
|
||||||
|
initialize=initialize_weather,
|
||||||
|
simulate=simulate_weather,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,7 +3,7 @@ rules:
|
||||||
- group: weather
|
- group: weather
|
||||||
match:
|
match:
|
||||||
type: room
|
type: room
|
||||||
outdoor: true
|
environment: outdoor
|
||||||
weather: clear
|
weather: clear
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -12,7 +12,7 @@ rules:
|
||||||
- group: weather
|
- group: weather
|
||||||
match:
|
match:
|
||||||
type: room
|
type: room
|
||||||
outdoor: true
|
environment: outdoor
|
||||||
weather: clouds
|
weather: clouds
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -21,7 +21,7 @@ rules:
|
||||||
- group: weather
|
- group: weather
|
||||||
match:
|
match:
|
||||||
type: room
|
type: room
|
||||||
outdoor: true
|
environment: outdoor
|
||||||
weather: rain
|
weather: rain
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -30,7 +30,7 @@ rules:
|
||||||
- group: weather
|
- group: weather
|
||||||
match:
|
match:
|
||||||
type: room
|
type: room
|
||||||
outdoor: true
|
environment: outdoor
|
||||||
weather: clouds
|
weather: clouds
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -40,7 +40,7 @@ rules:
|
||||||
- group: weather
|
- group: weather
|
||||||
match:
|
match:
|
||||||
type: room
|
type: room
|
||||||
outdoor: true
|
environment: outdoor
|
||||||
rule: |
|
rule: |
|
||||||
"weather" not in attributes
|
"weather" not in attributes
|
||||||
set:
|
set:
|
||||||
|
|
|
@ -148,11 +148,11 @@ def loop_conversation(
|
||||||
response = result_parser(response)
|
response = result_parser(response)
|
||||||
|
|
||||||
logger.info(f"{character.name} responds: {response}")
|
logger.info(f"{character.name} responds: {response}")
|
||||||
reply_event = ReplyEvent.from_text(response, room, character)
|
reply_event = ReplyEvent(room, character, last_character, response)
|
||||||
broadcast(reply_event)
|
broadcast(reply_event)
|
||||||
|
|
||||||
# increment the step counter
|
# increment the step counter
|
||||||
i += 1
|
i += 1
|
||||||
last_character = character
|
last_character = character
|
||||||
|
|
||||||
return response
|
return f"{last_character.name} ends the conversation for now"
|
||||||
|
|
|
@ -23,9 +23,9 @@ def find_room(world: World, room_name: str) -> Room | None:
|
||||||
|
|
||||||
def find_portal(world: World, portal_name: str) -> Portal | None:
|
def find_portal(world: World, portal_name: str) -> Portal | None:
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
for portal in room.portals:
|
portal = find_portal_in_room(room, portal_name)
|
||||||
if normalize_name(portal.name) == normalize_name(portal_name):
|
if portal:
|
||||||
return portal
|
return portal
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -47,6 +47,14 @@ def find_character_in_room(room: Room, character_name: str) -> Character | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_portal_in_room(room: Room, portal_name: str) -> Portal | None:
|
||||||
|
for portal in room.portals:
|
||||||
|
if normalize_name(portal.name) == normalize_name(portal_name):
|
||||||
|
return portal
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# TODO: allow item or str
|
# TODO: allow item or str
|
||||||
def find_item(
|
def find_item(
|
||||||
world: World,
|
world: World,
|
||||||
|
@ -109,15 +117,6 @@ def find_item_in_room(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_room_with_character(world: World, character: Character) -> Room | None:
|
|
||||||
for room in world.rooms:
|
|
||||||
for room_character in room.characters:
|
|
||||||
if normalize_name(character.name) == normalize_name(room_character.name):
|
|
||||||
return room
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def find_containing_room(world: World, entity: Room | Character | Item) -> Room | None:
|
def find_containing_room(world: World, entity: Room | Character | Item) -> Room | None:
|
||||||
if isinstance(entity, Room):
|
if isinstance(entity, Room):
|
||||||
return entity
|
return entity
|
||||||
|
|
Loading…
Reference in New Issue