rename actor to character, add step limits to config
This commit is contained in:
parent
fc07ccd9df
commit
c81d2ae3f2
|
@ -3,15 +3,15 @@ from logging import getLogger
|
||||||
from adventure.context import (
|
from adventure.context import (
|
||||||
action_context,
|
action_context,
|
||||||
broadcast,
|
broadcast,
|
||||||
get_actor_agent_for_name,
|
get_agent_for_character,
|
||||||
get_agent_for_actor,
|
get_character_agent_for_name,
|
||||||
world_context,
|
world_context,
|
||||||
)
|
)
|
||||||
from adventure.errors import ActionError
|
from adventure.errors import ActionError
|
||||||
from adventure.utils.conversation import loop_conversation
|
from adventure.utils.conversation import loop_conversation
|
||||||
from adventure.utils.search import (
|
from adventure.utils.search import (
|
||||||
find_actor_in_room,
|
find_character_in_room,
|
||||||
find_item_in_actor,
|
find_item_in_character,
|
||||||
find_item_in_room,
|
find_item_in_room,
|
||||||
find_room,
|
find_room,
|
||||||
)
|
)
|
||||||
|
@ -31,31 +31,31 @@ def action_look(target: str) -> str:
|
||||||
target: The name of the target to look at.
|
target: The name of the target to look at.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
broadcast(f"{action_actor.name} looks at {target}")
|
broadcast(f"{action_character.name} looks at {target}")
|
||||||
|
|
||||||
if normalize_name(target) == normalize_name(action_room.name):
|
if normalize_name(target) == normalize_name(action_room.name):
|
||||||
broadcast(f"{action_actor.name} saw the {action_room.name} room")
|
broadcast(f"{action_character.name} saw the {action_room.name} room")
|
||||||
return describe_entity(action_room)
|
return describe_entity(action_room)
|
||||||
|
|
||||||
target_actor = find_actor_in_room(action_room, target)
|
target_character = find_character_in_room(action_room, target)
|
||||||
if target_actor:
|
if target_character:
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} saw the {target_actor.name} actor in the {action_room.name} room"
|
f"{action_character.name} saw the {target_character.name} character in the {action_room.name} room"
|
||||||
)
|
)
|
||||||
return describe_entity(target_actor)
|
return describe_entity(target_character)
|
||||||
|
|
||||||
target_item = find_item_in_room(action_room, target)
|
target_item = find_item_in_room(action_room, target)
|
||||||
if target_item:
|
if target_item:
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} saw the {target_item.name} item in the {action_room.name} room"
|
f"{action_character.name} saw the {target_item.name} item in the {action_room.name} room"
|
||||||
)
|
)
|
||||||
return describe_entity(target_item)
|
return describe_entity(target_item)
|
||||||
|
|
||||||
target_item = find_item_in_actor(action_actor, target)
|
target_item = find_item_in_character(action_character, target)
|
||||||
if target_item:
|
if target_item:
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} saw the {target_item.name} item in their inventory"
|
f"{action_character.name} saw the {target_item.name} item in their inventory"
|
||||||
)
|
)
|
||||||
return describe_entity(target_item)
|
return describe_entity(target_item)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ def action_move(direction: str) -> str:
|
||||||
direction: The direction to move in.
|
direction: The direction to move in.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with world_context() as (action_world, action_room, action_actor):
|
with world_context() as (action_world, action_room, action_character):
|
||||||
portal = next(
|
portal = next(
|
||||||
(
|
(
|
||||||
p
|
p
|
||||||
|
@ -87,10 +87,10 @@ def action_move(direction: str) -> str:
|
||||||
raise ActionError(f"The {portal.destination} room does not exist.")
|
raise ActionError(f"The {portal.destination} room does not exist.")
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} moves through {direction} to {destination_room.name}"
|
f"{action_character.name} moves through {direction} to {destination_room.name}"
|
||||||
)
|
)
|
||||||
action_room.actors.remove(action_actor)
|
action_room.characters.remove(action_character)
|
||||||
destination_room.actors.append(action_actor)
|
destination_room.characters.append(action_character)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f"You move through the {direction} and arrive at {destination_room.name}."
|
f"You move through the {direction} and arrive at {destination_room.name}."
|
||||||
|
@ -104,14 +104,14 @@ def action_take(item: str) -> str:
|
||||||
Args:
|
Args:
|
||||||
item: The name of the item to take.
|
item: The name of the item to take.
|
||||||
"""
|
"""
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
action_item = find_item_in_room(action_room, item)
|
action_item = find_item_in_room(action_room, item)
|
||||||
if not action_item:
|
if not action_item:
|
||||||
raise ActionError(f"The {item} item is not in the room.")
|
raise ActionError(f"The {item} item is not in the room.")
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} takes the {item} item")
|
broadcast(f"{action_character.name} takes the {item} item")
|
||||||
action_room.items.remove(action_item)
|
action_room.items.remove(action_item)
|
||||||
action_actor.items.append(action_item)
|
action_character.items.append(action_item)
|
||||||
return f"You take the {item} item and put it in your inventory."
|
return f"You take the {item} item and put it in your inventory."
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,39 +123,39 @@ def action_ask(character: str, question: str) -> str:
|
||||||
character: The name of the character to ask. You cannot ask yourself questions.
|
character: The name of the character to ask. You cannot ask yourself questions.
|
||||||
question: The question to ask them.
|
question: The question to ask them.
|
||||||
"""
|
"""
|
||||||
# capture references to the current actor and room, because they will be overwritten
|
# capture references to the current character and room, because they will be overwritten
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
# sanity checks
|
# sanity checks
|
||||||
question_actor, question_agent = get_actor_agent_for_name(character)
|
question_character, question_agent = get_character_agent_for_name(character)
|
||||||
if question_actor == action_actor:
|
if question_character == action_character:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
"You cannot ask yourself a question. Stop talking to yourself. Try another action."
|
"You cannot ask yourself a question. Stop talking to yourself. Try another action."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not question_actor:
|
if not question_character:
|
||||||
raise ActionError(f"The {character} character is not in the room.")
|
raise ActionError(f"The {character} character is not in the room.")
|
||||||
|
|
||||||
if not question_agent:
|
if not question_agent:
|
||||||
raise ActionError(f"The {character} character does not exist.")
|
raise ActionError(f"The {character} character does not exist.")
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} asks {character}: {question}")
|
broadcast(f"{action_character.name} asks {character}: {question}")
|
||||||
first_prompt = (
|
first_prompt = (
|
||||||
"{last_actor.name} asks you: {response}\n"
|
"{last_character.name} asks you: {response}\n"
|
||||||
"Reply with your response to them. Reply with 'END' to end the conversation. "
|
"Reply with your response to them. Reply with 'END' to end the conversation. "
|
||||||
"Do not include the question or any JSON. Only include your answer for {last_actor.name}."
|
"Do not include the question or any JSON. Only include your answer for {last_character.name}."
|
||||||
)
|
)
|
||||||
reply_prompt = (
|
reply_prompt = (
|
||||||
"{last_actor.name} continues the conversation with you. They reply: {response}\n"
|
"{last_character.name} continues the conversation with you. They reply: {response}\n"
|
||||||
"Reply with your response to them. Reply with 'END' to end the conversation. "
|
"Reply with your response to them. Reply with 'END' to end the conversation. "
|
||||||
"Do not include the question or any JSON. Only include your answer for {last_actor.name}."
|
"Do not include the question or any JSON. Only include your answer for {last_character.name}."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_agent = get_agent_for_actor(action_actor)
|
action_agent = get_agent_for_character(action_character)
|
||||||
answer = loop_conversation(
|
answer = loop_conversation(
|
||||||
action_room,
|
action_room,
|
||||||
[question_actor, action_actor],
|
[question_character, action_character],
|
||||||
[question_agent, action_agent],
|
[question_agent, action_agent],
|
||||||
action_actor,
|
action_character,
|
||||||
first_prompt,
|
first_prompt,
|
||||||
reply_prompt,
|
reply_prompt,
|
||||||
question,
|
question,
|
||||||
|
@ -166,7 +166,7 @@ def action_ask(character: str, question: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
if answer:
|
if answer:
|
||||||
broadcast(f"{character} responds to {action_actor.name}: {answer}")
|
broadcast(f"{character} responds to {action_character.name}: {answer}")
|
||||||
return f"{character} responds: {answer}"
|
return f"{character} responds: {answer}"
|
||||||
|
|
||||||
return f"{character} does not respond."
|
return f"{character} does not respond."
|
||||||
|
@ -180,40 +180,40 @@ def action_tell(character: str, message: str) -> str:
|
||||||
character: The name of the character to tell. You cannot talk to yourself.
|
character: The name of the character to tell. You cannot talk to yourself.
|
||||||
message: The message to tell them.
|
message: The message to tell them.
|
||||||
"""
|
"""
|
||||||
# capture references to the current actor and room, because they will be overwritten
|
# capture references to the current character and room, because they will be overwritten
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
# sanity checks
|
# sanity checks
|
||||||
question_actor, question_agent = get_actor_agent_for_name(character)
|
question_character, question_agent = get_character_agent_for_name(character)
|
||||||
if question_actor == action_actor:
|
if question_character == action_character:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
"You cannot tell yourself a message. Stop talking to yourself. Try another action."
|
"You cannot tell yourself a message. Stop talking to yourself. Try another action."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not question_actor:
|
if not question_character:
|
||||||
raise ActionError(f"The {character} character is not in the room.")
|
raise ActionError(f"The {character} character is not in the room.")
|
||||||
|
|
||||||
if not question_agent:
|
if not question_agent:
|
||||||
raise ActionError(f"The {character} character does not exist.")
|
raise ActionError(f"The {character} character does not exist.")
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} tells {character}: {message}")
|
broadcast(f"{action_character.name} tells {character}: {message}")
|
||||||
first_prompt = (
|
first_prompt = (
|
||||||
"{last_actor.name} starts a conversation with you. They say: {response}\n"
|
"{last_character.name} starts a conversation with you. They say: {response}\n"
|
||||||
"Reply with your response to them. "
|
"Reply with your response to them. "
|
||||||
"Do not include the message or any JSON. Only include your reply to {last_actor.name}."
|
"Do not include the message or any JSON. Only include your reply to {last_character.name}."
|
||||||
)
|
)
|
||||||
reply_prompt = (
|
reply_prompt = (
|
||||||
"{last_actor.name} continues the conversation with you. They reply: {response}\n"
|
"{last_character.name} continues the conversation with you. They reply: {response}\n"
|
||||||
"Reply with your response to them. "
|
"Reply with your response to them. "
|
||||||
"Do not include the message or any JSON. Only include your reply to {last_actor.name}."
|
"Do not include the message or any JSON. Only include your reply to {last_character.name}."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_agent = get_agent_for_actor(action_actor)
|
action_agent = get_agent_for_character(action_character)
|
||||||
answer = loop_conversation(
|
answer = loop_conversation(
|
||||||
action_room,
|
action_room,
|
||||||
[question_actor, action_actor],
|
[question_character, action_character],
|
||||||
[question_agent, action_agent],
|
[question_agent, action_agent],
|
||||||
action_actor,
|
action_character,
|
||||||
first_prompt,
|
first_prompt,
|
||||||
reply_prompt,
|
reply_prompt,
|
||||||
message,
|
message,
|
||||||
|
@ -224,7 +224,7 @@ def action_tell(character: str, message: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
if answer:
|
if answer:
|
||||||
broadcast(f"{character} responds to {action_actor.name}: {answer}")
|
broadcast(f"{character} responds to {action_character.name}: {answer}")
|
||||||
return f"{character} responds: {answer}"
|
return f"{character} responds: {answer}"
|
||||||
|
|
||||||
return f"{character} does not respond."
|
return f"{character} does not respond."
|
||||||
|
@ -238,23 +238,23 @@ def action_give(character: str, item: str) -> str:
|
||||||
character: The name of the character to give the item to.
|
character: The name of the character to give the item to.
|
||||||
item: The name of the item to give.
|
item: The name of the item to give.
|
||||||
"""
|
"""
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
destination_actor = find_actor_in_room(action_room, character)
|
destination_character = find_character_in_room(action_room, character)
|
||||||
if not destination_actor:
|
if not destination_character:
|
||||||
raise ActionError(f"The {character} character is not in the room.")
|
raise ActionError(f"The {character} character is not in the room.")
|
||||||
|
|
||||||
if destination_actor == action_actor:
|
if destination_character == action_character:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
"You cannot give an item to yourself. Try another action."
|
"You cannot give an item to yourself. Try another action."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_item = find_item_in_actor(action_actor, item)
|
action_item = find_item_in_character(action_character, item)
|
||||||
if not action_item:
|
if not action_item:
|
||||||
raise ActionError(f"You do not have the {item} item in your inventory.")
|
raise ActionError(f"You do not have the {item} item in your inventory.")
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} gives {character} the {item} item.")
|
broadcast(f"{action_character.name} gives {character} the {item} item.")
|
||||||
action_actor.items.remove(action_item)
|
action_character.items.remove(action_item)
|
||||||
destination_actor.items.append(action_item)
|
destination_character.items.append(action_item)
|
||||||
|
|
||||||
return f"You give the {item} item to {character}."
|
return f"You give the {item} item to {character}."
|
||||||
|
|
||||||
|
@ -267,13 +267,13 @@ def action_drop(item: str) -> str:
|
||||||
item: The name of the item to drop.
|
item: The name of the item to drop.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
action_item = find_item_in_actor(action_actor, item)
|
action_item = find_item_in_character(action_character, item)
|
||||||
if not action_item:
|
if not action_item:
|
||||||
raise ActionError(f"You do not have the {item} item in your inventory.")
|
raise ActionError(f"You do not have the {item} item in your inventory.")
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} drops the {item} item")
|
broadcast(f"{action_character.name} drops the {item} item")
|
||||||
action_actor.items.remove(action_item)
|
action_character.items.remove(action_item)
|
||||||
action_room.items.append(action_item)
|
action_room.items.append(action_item)
|
||||||
|
|
||||||
return f"You drop the {item} item."
|
return f"You drop the {item} item."
|
||||||
|
|
|
@ -6,7 +6,7 @@ from packit.agent import Agent, agent_easy_connect
|
||||||
from adventure.context import (
|
from adventure.context import (
|
||||||
action_context,
|
action_context,
|
||||||
broadcast,
|
broadcast,
|
||||||
get_agent_for_actor,
|
get_agent_for_character,
|
||||||
get_dungeon_master,
|
get_dungeon_master,
|
||||||
get_game_systems,
|
get_game_systems,
|
||||||
has_dungeon_master,
|
has_dungeon_master,
|
||||||
|
@ -16,9 +16,9 @@ from adventure.context import (
|
||||||
from adventure.errors import ActionError
|
from adventure.errors import ActionError
|
||||||
from adventure.generate import generate_item, generate_room, link_rooms
|
from adventure.generate import generate_item, generate_room, link_rooms
|
||||||
from adventure.utils.effect import apply_effects
|
from adventure.utils.effect import apply_effects
|
||||||
from adventure.utils.search import find_actor_in_room
|
from adventure.utils.search import find_character_in_room
|
||||||
from adventure.utils.string import normalize_name
|
from adventure.utils.string import normalize_name
|
||||||
from adventure.utils.world import describe_actor, describe_entity
|
from adventure.utils.world import describe_character, describe_entity
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ def action_explore(direction: str) -> str:
|
||||||
direction: The direction to explore. For example: inside, outside, upstairs, downstairs, trapdoor, portal, etc.
|
direction: The direction to explore. For example: inside, outside, upstairs, downstairs, trapdoor, portal, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with world_context() as (action_world, action_room, action_actor):
|
with world_context() as (action_world, action_room, action_character):
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
if direction in action_room.portals:
|
if direction in action_room.portals:
|
||||||
|
@ -62,7 +62,7 @@ def action_explore(direction: str) -> str:
|
||||||
link_rooms(dungeon_master, action_world, systems, [new_room])
|
link_rooms(dungeon_master, action_world, systems, [new_room])
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} explores {direction} of {action_room.name} and finds a new room: {new_room.name}"
|
f"{action_character.name} explores {direction} of {action_room.name} and finds a new room: {new_room.name}"
|
||||||
)
|
)
|
||||||
return f"You explore {direction} and find a new room: {new_room.name}"
|
return f"You explore {direction} and find a new room: {new_room.name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -75,7 +75,7 @@ def action_search(unused: bool) -> str:
|
||||||
Search the room for hidden items.
|
Search the room for hidden items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with world_context() as (action_world, action_room, action_actor):
|
with world_context() as (action_world, action_room, action_character):
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
if len(action_room.items) > 2:
|
if len(action_room.items) > 2:
|
||||||
|
@ -94,7 +94,7 @@ def action_search(unused: bool) -> str:
|
||||||
action_room.items.append(new_item)
|
action_room.items.append(new_item)
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} searches {action_room.name} and finds a new item: {new_item.name}"
|
f"{action_character.name} searches {action_room.name} and finds a new item: {new_item.name}"
|
||||||
)
|
)
|
||||||
return f"You search the room and find a new item: {new_item.name}"
|
return f"You search the room and find a new item: {new_item.name}"
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -110,13 +110,13 @@ def action_use(item: str, target: str) -> str:
|
||||||
item: The name of the item to use.
|
item: The name of the item to use.
|
||||||
target: The name of the character to use the item on, or "self" to use the item on yourself.
|
target: The name of the character to use the item on, or "self" to use the item on yourself.
|
||||||
"""
|
"""
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
action_item = next(
|
action_item = next(
|
||||||
(
|
(
|
||||||
search_item
|
search_item
|
||||||
for search_item in (action_actor.items + action_room.items)
|
for search_item in (action_character.items + action_room.items)
|
||||||
if search_item.name == item
|
if search_item.name == item
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
|
@ -125,17 +125,17 @@ def action_use(item: str, target: str) -> str:
|
||||||
raise ActionError(f"The {item} item is not available to use.")
|
raise ActionError(f"The {item} item is not available to use.")
|
||||||
|
|
||||||
if target == "self":
|
if target == "self":
|
||||||
target_actor = action_actor
|
target_character = action_character
|
||||||
target = action_actor.name
|
target = action_character.name
|
||||||
else:
|
else:
|
||||||
# TODO: allow targeting the room itself and items in the room
|
# TODO: allow targeting the room itself and items in the room
|
||||||
target_actor = find_actor_in_room(action_room, target)
|
target_character = find_character_in_room(action_room, target)
|
||||||
if not target_actor:
|
if not target_character:
|
||||||
return f"The {target} character is not in the room."
|
return f"The {target} character is not in the room."
|
||||||
|
|
||||||
effect_names = [effect.name for effect in action_item.effects]
|
effect_names = [effect.name for effect in action_item.effects]
|
||||||
chosen_name = dungeon_master(
|
chosen_name = dungeon_master(
|
||||||
f"{action_actor.name} uses {item} on {target}. "
|
f"{action_character.name} uses {item} on {target}. "
|
||||||
f"{item} has the following effects: {effect_names}. "
|
f"{item} has the following effects: {effect_names}. "
|
||||||
"Which effect should be applied? Specify the name of the effect to apply."
|
"Which effect should be applied? Specify the name of the effect to apply."
|
||||||
"Do not include the question or any JSON. Only include the name of the effect to apply."
|
"Do not include the question or any JSON. Only include the name of the effect to apply."
|
||||||
|
@ -155,7 +155,7 @@ def action_use(item: str, target: str) -> str:
|
||||||
raise ValueError(f"The {chosen_name} effect is not available to apply.")
|
raise ValueError(f"The {chosen_name} effect is not available to apply.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apply_effects(target_actor, [chosen_effect])
|
apply_effects(target_character, [chosen_effect])
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("error applying effect: %s", chosen_effect)
|
logger.exception("error applying effect: %s", chosen_effect)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -163,11 +163,11 @@ def action_use(item: str, target: str) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} uses the {chosen_name} effect of {item} on {target}"
|
f"{action_character.name} uses the {chosen_name} effect of {item} on {target}"
|
||||||
)
|
)
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} uses the {chosen_name} effect of {item} on {target}. "
|
f"{action_character.name} uses the {chosen_name} effect of {item} on {target}. "
|
||||||
f"{describe_actor(action_actor)}. {describe_actor(target_actor)}. {describe_entity(action_item)}. "
|
f"{describe_character(action_character)}. {describe_character(target_character)}. {describe_entity(action_item)}. "
|
||||||
f"What happens? How does {target} react? Be creative with the results. The outcome can be good, bad, or neutral."
|
f"What happens? How does {target} react? Be creative with the results. The outcome can be good, bad, or neutral."
|
||||||
"Decide based on the characters involved and the item being used."
|
"Decide based on the characters involved and the item being used."
|
||||||
"Specify the outcome of the action. Do not include the question or any JSON. Only include the outcome of the action."
|
"Specify the outcome of the action. Do not include the question or any JSON. Only include the outcome of the action."
|
||||||
|
@ -175,7 +175,7 @@ def action_use(item: str, target: str) -> str:
|
||||||
broadcast(f"The action resulted in: {outcome}")
|
broadcast(f"The action resulted in: {outcome}")
|
||||||
|
|
||||||
# make sure both agents remember the outcome
|
# make sure both agents remember the outcome
|
||||||
target_agent = get_agent_for_actor(target_actor)
|
target_agent = get_agent_for_character(target_character)
|
||||||
if target_agent and target_agent.memory:
|
if target_agent and target_agent.memory:
|
||||||
target_agent.memory.append(outcome)
|
target_agent.memory.append(outcome)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from adventure.context import action_context, get_agent_for_actor, get_current_step
|
from adventure.context import action_context, get_agent_for_character, get_current_step
|
||||||
from adventure.errors import ActionError
|
from adventure.errors import ActionError
|
||||||
from adventure.models.config import DEFAULT_CONFIG
|
from adventure.models.config import DEFAULT_CONFIG
|
||||||
from adventure.models.planning import CalendarEvent
|
from adventure.models.planning import CalendarEvent
|
||||||
from adventure.utils.planning import get_recent_notes
|
from adventure.utils.planning import get_recent_notes
|
||||||
|
|
||||||
actor_config = DEFAULT_CONFIG.world.actor
|
character_config = DEFAULT_CONFIG.world.character
|
||||||
|
|
||||||
|
|
||||||
def take_note(fact: str):
|
def take_note(fact: str):
|
||||||
|
@ -16,16 +16,16 @@ def take_note(fact: str):
|
||||||
fact: The fact to remember.
|
fact: The fact to remember.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
if fact in action_actor.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.")
|
||||||
|
|
||||||
if len(action_actor.planner.notes) >= actor_config.note_limit:
|
if len(action_character.planner.notes) >= character_config.note_limit:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
"You have reached the limit of notes you can take. Please erase, replace, or summarize some notes."
|
"You have reached the limit of notes you can take. Please erase, replace, or summarize some notes."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_actor.planner.notes.append(fact)
|
action_character.planner.notes.append(fact)
|
||||||
|
|
||||||
return "You make a note of that fact."
|
return "You make a note of that fact."
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ def read_notes(unused: bool, count: int = 10):
|
||||||
count: The number of recent notes to read. 10 is usually a good number.
|
count: The number of recent notes to read. 10 is usually a good number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
facts = get_recent_notes(action_actor, count=count)
|
facts = get_recent_notes(action_character, count=count)
|
||||||
return "\n".join(facts)
|
return "\n".join(facts)
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,15 +51,15 @@ def erase_notes(prefix: str) -> str:
|
||||||
prefix: The prefix to match notes against.
|
prefix: The prefix to match notes against.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
matches = [
|
matches = [
|
||||||
note for note in action_actor.planner.notes if note.startswith(prefix)
|
note for note in action_character.planner.notes if note.startswith(prefix)
|
||||||
]
|
]
|
||||||
if not matches:
|
if not matches:
|
||||||
return "No notes found with that prefix."
|
return "No notes found with that prefix."
|
||||||
|
|
||||||
action_actor.planner.notes[:] = [
|
action_character.planner.notes[:] = [
|
||||||
note for note in action_actor.planner.notes if note not in matches
|
note for note in action_character.planner.notes if note not in matches
|
||||||
]
|
]
|
||||||
return f"Erased {len(matches)} notes."
|
return f"Erased {len(matches)} notes."
|
||||||
|
|
||||||
|
@ -73,12 +73,12 @@ def replace_note(old: str, new: str) -> str:
|
||||||
new: The new note to replace it with.
|
new: The new note to replace it with.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
if old not in action_actor.planner.notes:
|
if old not in action_character.planner.notes:
|
||||||
return "Note not found."
|
return "Note not found."
|
||||||
|
|
||||||
action_actor.planner.notes[:] = [
|
action_character.planner.notes[:] = [
|
||||||
new if note == old else note for note in action_actor.planner.notes
|
new if note == old else note for note in action_character.planner.notes
|
||||||
]
|
]
|
||||||
return "Note replaced."
|
return "Note replaced."
|
||||||
|
|
||||||
|
@ -91,12 +91,12 @@ def summarize_notes(limit: int) -> str:
|
||||||
limit: The maximum number of notes to keep.
|
limit: The maximum number of notes to keep.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
notes = action_actor.planner.notes
|
notes = action_character.planner.notes
|
||||||
action_agent = get_agent_for_actor(action_actor)
|
action_agent = get_agent_for_character(action_character)
|
||||||
|
|
||||||
if not action_agent:
|
if not action_agent:
|
||||||
raise ActionError("Agent missing for actor {action_actor.name}")
|
raise ActionError("Agent missing for character {action_character.name}")
|
||||||
|
|
||||||
summary = action_agent(
|
summary = action_agent(
|
||||||
"Please summarize your notes. Remove any duplicates and combine similar notes. "
|
"Please summarize your notes. Remove any duplicates and combine similar notes. "
|
||||||
|
@ -110,12 +110,12 @@ def summarize_notes(limit: int) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
new_notes = [note.strip() for note in summary.split("\n") if note.strip()]
|
new_notes = [note.strip() for note in summary.split("\n") if note.strip()]
|
||||||
if len(new_notes) > actor_config.note_limit:
|
if len(new_notes) > character_config.note_limit:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
f"Too many notes. You can only have up to {actor_config.note_limit} notes."
|
f"Too many notes. You can only have up to {character_config.note_limit} notes."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_actor.planner.notes[:] = new_notes
|
action_character.planner.notes[:] = new_notes
|
||||||
return "Notes were summarized successfully."
|
return "Notes were summarized successfully."
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,10 +130,10 @@ def schedule_event(name: str, turns: int):
|
||||||
turns: The number of turns until the event happens.
|
turns: The number of turns until the event happens.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
# TODO: check for existing events with the same name
|
# TODO: check for existing events with the same name
|
||||||
event = CalendarEvent(name, turns)
|
event = CalendarEvent(name, turns)
|
||||||
action_actor.planner.calendar.events.append(event)
|
action_character.planner.calendar.events.append(event)
|
||||||
return f"{name} is scheduled to happen in {turns} turns."
|
return f"{name} is scheduled to happen in {turns} turns."
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,8 +144,8 @@ def check_calendar(unused: bool, count: int = 10):
|
||||||
|
|
||||||
current_turn = get_current_step()
|
current_turn = get_current_step()
|
||||||
|
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
events = action_actor.planner.calendar.events[:count]
|
events = action_character.planner.calendar.events[:count]
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
f"{event.name} will happen in {event.turn - current_turn} turns"
|
f"{event.name} will happen in {event.turn - current_turn} turns"
|
||||||
|
|
|
@ -3,56 +3,56 @@ from adventure.systems.quest import (
|
||||||
QUEST_SYSTEM,
|
QUEST_SYSTEM,
|
||||||
complete_quest,
|
complete_quest,
|
||||||
get_active_quest,
|
get_active_quest,
|
||||||
get_quests_for_actor,
|
get_quests_for_character,
|
||||||
set_active_quest,
|
set_active_quest,
|
||||||
)
|
)
|
||||||
from adventure.utils.search import find_actor_in_room
|
from adventure.utils.search import find_character_in_room
|
||||||
|
|
||||||
|
|
||||||
def accept_quest(actor: str, quest: str) -> str:
|
def accept_quest(character: str, quest: str) -> str:
|
||||||
"""
|
"""
|
||||||
Accept and start a quest being given by another character.
|
Accept and start a quest being given by another character.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
quests = get_system_data(QUEST_SYSTEM)
|
quests = get_system_data(QUEST_SYSTEM)
|
||||||
if not quests:
|
if not quests:
|
||||||
return "No quests available."
|
return "No quests available."
|
||||||
|
|
||||||
target_actor = find_actor_in_room(action_room, actor)
|
target_character = find_character_in_room(action_room, character)
|
||||||
if not target_actor:
|
if not target_character:
|
||||||
return f"{actor} is not in the room."
|
return f"{character} is not in the room."
|
||||||
|
|
||||||
available_quests = get_quests_for_actor(quests, target_actor)
|
available_quests = get_quests_for_character(quests, target_character)
|
||||||
|
|
||||||
for available_quest in available_quests:
|
for available_quest in available_quests:
|
||||||
if available_quest.name == quest:
|
if available_quest.name == quest:
|
||||||
set_active_quest(quests, action_actor, available_quest)
|
set_active_quest(quests, action_character, available_quest)
|
||||||
return f"You have accepted the quest: {quest}"
|
return f"You have accepted the quest: {quest}"
|
||||||
|
|
||||||
return f"{actor} does not have the quest: {quest}"
|
return f"{character} does not have the quest: {quest}"
|
||||||
|
|
||||||
|
|
||||||
def submit_quest(actor: str) -> str:
|
def submit_quest(character: str) -> str:
|
||||||
"""
|
"""
|
||||||
Submit your active quest to the quest giver. If you have completed the quest, you will be rewarded.
|
Submit your active quest to the quest giver. If you have completed the quest, you will be rewarded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
quests = get_system_data(QUEST_SYSTEM)
|
quests = get_system_data(QUEST_SYSTEM)
|
||||||
if not quests:
|
if not quests:
|
||||||
return "No quests available."
|
return "No quests available."
|
||||||
|
|
||||||
active_quest = get_active_quest(quests, action_actor)
|
active_quest = get_active_quest(quests, action_character)
|
||||||
if not active_quest:
|
if not active_quest:
|
||||||
return "You do not have an active quest."
|
return "You do not have an active quest."
|
||||||
|
|
||||||
target_actor = find_actor_in_room(action_room, actor)
|
target_character = find_character_in_room(action_room, character)
|
||||||
if not target_actor:
|
if not target_character:
|
||||||
return f"{actor} is not in the room."
|
return f"{character} is not in the room."
|
||||||
|
|
||||||
if active_quest.giver.actor == target_actor.name:
|
if active_quest.giver.character == target_character.name:
|
||||||
complete_quest(quests, action_actor, active_quest)
|
complete_quest(quests, action_character, active_quest)
|
||||||
return f"You have completed the quest: {active_quest.name}"
|
return f"You have completed the quest: {active_quest.name}"
|
||||||
|
|
||||||
return f"{actor} is not the quest giver for your active quest."
|
return f"{character} is not the quest giver for your active quest."
|
||||||
|
|
|
@ -9,9 +9,9 @@ from discord import Client, Embed, File, Intents
|
||||||
|
|
||||||
from adventure.context import (
|
from adventure.context import (
|
||||||
broadcast,
|
broadcast,
|
||||||
get_actor_agent_for_name,
|
get_character_agent_for_name,
|
||||||
get_current_world,
|
get_current_world,
|
||||||
set_actor_agent,
|
set_character_agent,
|
||||||
subscribe,
|
subscribe,
|
||||||
)
|
)
|
||||||
from adventure.models.config import DEFAULT_CONFIG, DiscordBotConfig
|
from adventure.models.config import DEFAULT_CONFIG, DiscordBotConfig
|
||||||
|
@ -101,15 +101,15 @@ class AdventureClient(Client):
|
||||||
await channel.send(f"{character_name} has already been taken!")
|
await channel.send(f"{character_name} has already been taken!")
|
||||||
return
|
return
|
||||||
|
|
||||||
actor, agent = get_actor_agent_for_name(character_name)
|
character, agent = get_character_agent_for_name(character_name)
|
||||||
if not actor:
|
if not character:
|
||||||
await channel.send(f"Character `{character_name}` not found!")
|
await channel.send(f"Character `{character_name}` not found!")
|
||||||
return
|
return
|
||||||
|
|
||||||
def prompt_player(event: PromptEvent):
|
def prompt_player(event: PromptEvent):
|
||||||
logger.info(
|
logger.info(
|
||||||
"append prompt for character %s (user %s) to queue: %s",
|
"append prompt for character %s (user %s) to queue: %s",
|
||||||
event.actor.name,
|
event.character.name,
|
||||||
user_name,
|
user_name,
|
||||||
event.prompt,
|
event.prompt,
|
||||||
)
|
)
|
||||||
|
@ -118,12 +118,12 @@ class AdventureClient(Client):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
player = RemotePlayer(
|
player = RemotePlayer(
|
||||||
actor.name, actor.backstory, prompt_player, fallback_agent=agent
|
character.name, character.backstory, prompt_player, fallback_agent=agent
|
||||||
)
|
)
|
||||||
set_actor_agent(character_name, actor, player)
|
set_character_agent(character_name, character, player)
|
||||||
set_player(user_name, player)
|
set_player(user_name, player)
|
||||||
|
|
||||||
logger.info(f"{user_name} has joined the game as {actor.name}!")
|
logger.info(f"{user_name} has joined the game as {character.name}!")
|
||||||
join_event = PlayerEvent("join", character_name, user_name)
|
join_event = PlayerEvent("join", character_name, user_name)
|
||||||
return broadcast(join_event)
|
return broadcast(join_event)
|
||||||
|
|
||||||
|
@ -133,10 +133,12 @@ class AdventureClient(Client):
|
||||||
remove_player(user_name)
|
remove_player(user_name)
|
||||||
|
|
||||||
# revert to LLM agent
|
# revert to LLM agent
|
||||||
actor, _ = get_actor_agent_for_name(player.name)
|
character, _ = get_character_agent_for_name(player.name)
|
||||||
if actor and player.fallback_agent:
|
if character and player.fallback_agent:
|
||||||
logger.info("restoring LLM agent for %s", player.name)
|
logger.info("restoring LLM agent for %s", player.name)
|
||||||
set_actor_agent(actor.name, actor, player.fallback_agent)
|
set_character_agent(
|
||||||
|
character.name, character, player.fallback_agent
|
||||||
|
)
|
||||||
|
|
||||||
# broadcast leave event
|
# broadcast leave event
|
||||||
logger.info("disconnecting player %s from %s", user_name, player.name)
|
logger.info("disconnecting player %s from %s", user_name, player.name)
|
||||||
|
@ -324,7 +326,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.actor.name)
|
action_embed = Embed(title=event.room.name, description=event.character.name)
|
||||||
|
|
||||||
if isinstance(event, ActionEvent):
|
if isinstance(event, ActionEvent):
|
||||||
action_name = event.action.replace("action_", "").title()
|
action_name = event.action.replace("action_", "").title()
|
||||||
|
@ -350,7 +352,7 @@ def embed_from_result(event: ResultEvent):
|
||||||
if len(text) > 1000:
|
if len(text) > 1000:
|
||||||
text = text[:1000] + "..."
|
text = text[:1000] + "..."
|
||||||
|
|
||||||
result_embed = Embed(title=event.room.name, description=event.actor.name)
|
result_embed = Embed(title=event.room.name, description=event.character.name)
|
||||||
result_embed.add_field(name="Result", value=text)
|
result_embed.add_field(name="Result", value=text)
|
||||||
return result_embed
|
return result_embed
|
||||||
|
|
||||||
|
@ -369,7 +371,7 @@ def embed_from_player(event: PlayerEvent):
|
||||||
|
|
||||||
def embed_from_prompt(event: PromptEvent):
|
def embed_from_prompt(event: PromptEvent):
|
||||||
# TODO: ping the player
|
# TODO: ping the player
|
||||||
prompt_embed = Embed(title=event.room.name, description=event.actor.name)
|
prompt_embed = Embed(title=event.room.name, description=event.character.name)
|
||||||
prompt_embed.add_field(name="Prompt", value=event.prompt)
|
prompt_embed.add_field(name="Prompt", value=event.prompt)
|
||||||
return prompt_embed
|
return prompt_embed
|
||||||
|
|
||||||
|
@ -377,7 +379,7 @@ def embed_from_prompt(event: PromptEvent):
|
||||||
def embed_from_status(event: StatusEvent):
|
def embed_from_status(event: StatusEvent):
|
||||||
status_embed = Embed(
|
status_embed = Embed(
|
||||||
title=event.room.name if event.room else "",
|
title=event.room.name if event.room else "",
|
||||||
description=event.actor.name if event.actor else "",
|
description=event.character.name if event.character else "",
|
||||||
)
|
)
|
||||||
status_embed.add_field(name="Status", value=event.text)
|
status_embed.add_field(name="Status", value=event.text)
|
||||||
return status_embed
|
return status_embed
|
||||||
|
|
|
@ -18,7 +18,7 @@ from packit.agent import Agent
|
||||||
from pyee.base import EventEmitter
|
from pyee.base import EventEmitter
|
||||||
|
|
||||||
from adventure.game_system import GameSystem
|
from adventure.game_system import GameSystem
|
||||||
from adventure.models.entity import Actor, Room, World
|
from adventure.models.entity import Character, Room, World
|
||||||
from adventure.models.event import GameEvent
|
from adventure.models.event import GameEvent
|
||||||
from adventure.utils.string import normalize_name
|
from adventure.utils.string import normalize_name
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ logger = getLogger(__name__)
|
||||||
current_step = 0
|
current_step = 0
|
||||||
current_world: World | None = None
|
current_world: World | None = None
|
||||||
current_room: Room | None = None
|
current_room: Room | None = None
|
||||||
current_actor: Actor | None = None
|
current_character: Character | None = None
|
||||||
dungeon_master: Agent | None = None
|
dungeon_master: Agent | None = None
|
||||||
|
|
||||||
# game context
|
# game context
|
||||||
|
@ -38,7 +38,7 @@ system_data: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
|
||||||
# TODO: where should this one go?
|
# TODO: where should this one go?
|
||||||
actor_agents: Dict[str, Tuple[Actor, Agent]] = {}
|
character_agents: Dict[str, Tuple[Character, Agent]] = {}
|
||||||
|
|
||||||
STRING_EVENT_TYPE = "message"
|
STRING_EVENT_TYPE = "message"
|
||||||
|
|
||||||
|
@ -88,44 +88,44 @@ def has_dungeon_master():
|
||||||
# region context manager
|
# region context manager
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def action_context():
|
def action_context():
|
||||||
room, actor = get_action_context()
|
room, character = get_action_context()
|
||||||
yield room, actor
|
yield room, character
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def world_context():
|
def world_context():
|
||||||
world, room, actor = get_world_context()
|
world, room, character = get_world_context()
|
||||||
yield world, room, actor
|
yield world, room, character
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
# region context getters
|
# region context getters
|
||||||
def get_action_context() -> Tuple[Room, Actor]:
|
def get_action_context() -> Tuple[Room, Character]:
|
||||||
if not current_room:
|
if not current_room:
|
||||||
raise ValueError("The current room must be set before calling action functions")
|
raise ValueError("The current room must be set before calling action functions")
|
||||||
if not current_actor:
|
if not current_character:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"The current actor must be set before calling action functions"
|
"The current character must be set before calling action functions"
|
||||||
)
|
)
|
||||||
|
|
||||||
return (current_room, current_actor)
|
return (current_room, current_character)
|
||||||
|
|
||||||
|
|
||||||
def get_world_context() -> Tuple[World, Room, Actor]:
|
def get_world_context() -> Tuple[World, Room, Character]:
|
||||||
if not current_world:
|
if not current_world:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"The current world must be set before calling action functions"
|
"The current world must be set before calling action functions"
|
||||||
)
|
)
|
||||||
if not current_room:
|
if not current_room:
|
||||||
raise ValueError("The current room must be set before calling action functions")
|
raise ValueError("The current room must be set before calling action functions")
|
||||||
if not current_actor:
|
if not current_character:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"The current actor must be set before calling action functions"
|
"The current character must be set before calling action functions"
|
||||||
)
|
)
|
||||||
|
|
||||||
return (current_world, current_room, current_actor)
|
return (current_world, current_room, current_character)
|
||||||
|
|
||||||
|
|
||||||
def get_current_world() -> World | None:
|
def get_current_world() -> World | None:
|
||||||
|
@ -136,8 +136,8 @@ def get_current_room() -> Room | None:
|
||||||
return current_room
|
return current_room
|
||||||
|
|
||||||
|
|
||||||
def get_current_actor() -> Actor | None:
|
def get_current_character() -> Character | None:
|
||||||
return current_actor
|
return current_character
|
||||||
|
|
||||||
|
|
||||||
def get_current_step() -> int:
|
def get_current_step() -> int:
|
||||||
|
@ -175,9 +175,9 @@ def set_current_room(room: Room | None):
|
||||||
current_room = room
|
current_room = room
|
||||||
|
|
||||||
|
|
||||||
def set_current_actor(actor: Actor | None):
|
def set_current_character(character: Character | None):
|
||||||
global current_actor
|
global current_character
|
||||||
current_actor = actor
|
current_character = character
|
||||||
|
|
||||||
|
|
||||||
def set_current_step(step: int):
|
def set_current_step(step: int):
|
||||||
|
@ -185,8 +185,8 @@ def set_current_step(step: int):
|
||||||
current_step = step
|
current_step = step
|
||||||
|
|
||||||
|
|
||||||
def set_actor_agent(name, actor, agent):
|
def set_character_agent(name, character, agent):
|
||||||
actor_agents[name] = (actor, agent)
|
character_agents[name] = (character, agent)
|
||||||
|
|
||||||
|
|
||||||
def set_dungeon_master(agent):
|
def set_dungeon_master(agent):
|
||||||
|
@ -207,41 +207,41 @@ def set_system_data(system: str, data: Any):
|
||||||
|
|
||||||
|
|
||||||
# region search functions
|
# region search functions
|
||||||
def get_actor_for_agent(agent):
|
def get_character_for_agent(agent):
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
inner_actor
|
inner_character
|
||||||
for inner_actor, inner_agent in actor_agents.values()
|
for inner_character, inner_agent in character_agents.values()
|
||||||
if inner_agent == agent
|
if inner_agent == agent
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_agent_for_actor(actor):
|
def get_agent_for_character(character):
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
inner_agent
|
inner_agent
|
||||||
for inner_actor, inner_agent in actor_agents.values()
|
for inner_character, inner_agent in character_agents.values()
|
||||||
if inner_actor == actor
|
if inner_character == character
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_actor_agent_for_name(name):
|
def get_character_agent_for_name(name):
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
(actor, agent)
|
(character, agent)
|
||||||
for actor, agent in actor_agents.values()
|
for character, agent in character_agents.values()
|
||||||
if normalize_name(actor.name) == normalize_name(name)
|
if normalize_name(character.name) == normalize_name(name)
|
||||||
),
|
),
|
||||||
(None, None),
|
(None, None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_all_actor_agents():
|
def get_all_character_agents():
|
||||||
return list(actor_agents.values())
|
return list(character_agents.values())
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -16,15 +16,15 @@ from adventure.models.effect import (
|
||||||
IntEffectPattern,
|
IntEffectPattern,
|
||||||
StringEffectPattern,
|
StringEffectPattern,
|
||||||
)
|
)
|
||||||
from adventure.models.entity import Actor, Item, Portal, Room, World, WorldEntity
|
from adventure.models.entity import Character, Item, Portal, Room, World, WorldEntity
|
||||||
from adventure.models.event import GenerateEvent
|
from adventure.models.event import GenerateEvent
|
||||||
from adventure.utils import try_parse_float, try_parse_int
|
from adventure.utils import try_parse_float, try_parse_int
|
||||||
from adventure.utils.effect import resolve_int_range
|
from adventure.utils.effect import resolve_int_range
|
||||||
from adventure.utils.search import (
|
from adventure.utils.search import (
|
||||||
list_actors,
|
list_characters,
|
||||||
list_actors_in_room,
|
list_characters_in_room,
|
||||||
list_items,
|
list_items,
|
||||||
list_items_in_actor,
|
list_items_in_character,
|
||||||
list_items_in_room,
|
list_items_in_room,
|
||||||
list_rooms,
|
list_rooms,
|
||||||
)
|
)
|
||||||
|
@ -107,7 +107,7 @@ def generate_room(
|
||||||
)
|
)
|
||||||
|
|
||||||
actions = {}
|
actions = {}
|
||||||
room = Room(name=name, description=desc, items=[], actors=[], actions=actions)
|
room = Room(name=name, description=desc, items=[], characters=[], actions=actions)
|
||||||
|
|
||||||
item_count = resolve_int_range(world_config.size.room_items) or 0
|
item_count = resolve_int_range(world_config.size.room_items) or 0
|
||||||
broadcast_generated(f"Generating {item_count} items for room: {name}")
|
broadcast_generated(f"Generating {item_count} items for room: {name}")
|
||||||
|
@ -126,22 +126,24 @@ def generate_room(
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("error generating item")
|
logger.exception("error generating item")
|
||||||
|
|
||||||
actor_count = resolve_int_range(world_config.size.room_actors) or 0
|
character_count = resolve_int_range(world_config.size.room_characters) or 0
|
||||||
broadcast_generated(message=f"Generating {actor_count} actors for room: {name}")
|
broadcast_generated(
|
||||||
|
message=f"Generating {character_count} characters for room: {name}"
|
||||||
|
)
|
||||||
|
|
||||||
for _ in range(actor_count):
|
for _ in range(character_count):
|
||||||
try:
|
try:
|
||||||
actor = generate_actor(
|
character = generate_character(
|
||||||
agent,
|
agent,
|
||||||
world,
|
world,
|
||||||
systems=systems,
|
systems=systems,
|
||||||
dest_room=room,
|
dest_room=room,
|
||||||
)
|
)
|
||||||
broadcast_generated(entity=actor)
|
broadcast_generated(entity=character)
|
||||||
|
|
||||||
room.actors.append(actor)
|
room.characters.append(character)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("error generating actor")
|
logger.exception("error generating character")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return room
|
return room
|
||||||
|
@ -218,18 +220,20 @@ def generate_item(
|
||||||
world: World,
|
world: World,
|
||||||
systems: List[GameSystem],
|
systems: List[GameSystem],
|
||||||
dest_room: Room | None = None,
|
dest_room: Room | None = None,
|
||||||
dest_actor: Actor | None = None,
|
dest_character: Character | None = None,
|
||||||
) -> Item:
|
) -> Item:
|
||||||
existing_items = [
|
existing_items = [
|
||||||
item.name
|
item.name
|
||||||
for item in list_items(
|
for item in list_items(
|
||||||
world, include_actor_inventory=True, include_item_inventory=True
|
world, include_character_inventory=True, include_item_inventory=True
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
if dest_actor:
|
if dest_character:
|
||||||
dest_note = f"The item will be held by the {dest_actor.name} character"
|
dest_note = f"The item will be held by the {dest_character.name} character"
|
||||||
existing_items += [item.name for item in list_items_in_actor(dest_actor)]
|
existing_items += [
|
||||||
|
item.name for item in list_items_in_character(dest_character)
|
||||||
|
]
|
||||||
elif dest_room:
|
elif dest_room:
|
||||||
dest_note = f"The item will be placed in the {dest_room.name} room"
|
dest_note = f"The item will be placed in the {dest_room.name} room"
|
||||||
existing_items += [item.name for item in list_items_in_room(dest_room)]
|
existing_items += [item.name for item in list_items_in_room(dest_room)]
|
||||||
|
@ -275,14 +279,14 @@ def generate_item(
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def generate_actor(
|
def generate_character(
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
world: World,
|
world: World,
|
||||||
systems: List[GameSystem],
|
systems: List[GameSystem],
|
||||||
dest_room: Room,
|
dest_room: Room,
|
||||||
) -> Actor:
|
) -> Character:
|
||||||
existing_actors = [actor.name for actor in list_actors(world)] + [
|
existing_characters = [character.name for character in list_characters(world)] + [
|
||||||
actor.name for actor in list_actors_in_room(dest_room)
|
character.name for character in list_characters_in_room(dest_room)
|
||||||
]
|
]
|
||||||
|
|
||||||
name = loop_retry(
|
name = loop_retry(
|
||||||
|
@ -292,17 +296,17 @@ def generate_actor(
|
||||||
"Only respond with the character name in title case, do not include a description or any other text. "
|
"Only respond with the character name in title case, do not include a description or any other text. "
|
||||||
'Do not prefix the name with "the", do not wrap it in quotes. '
|
'Do not prefix the name with "the", do not wrap it in quotes. '
|
||||||
"Do not include the name of the room. Do not give characters any duplicate names."
|
"Do not include the name of the room. Do not give characters any duplicate names."
|
||||||
"Do not create any duplicate characters. The existing characters are: {existing_actors}",
|
"Do not create any duplicate characters. The existing characters are: {existing_characters}",
|
||||||
context={
|
context={
|
||||||
"dest_room": dest_room.name,
|
"dest_room": dest_room.name,
|
||||||
"existing_actors": existing_actors,
|
"existing_characters": existing_characters,
|
||||||
"world_theme": world.theme,
|
"world_theme": world.theme,
|
||||||
},
|
},
|
||||||
result_parser=duplicate_name_parser(existing_actors),
|
result_parser=duplicate_name_parser(existing_characters),
|
||||||
toolbox=None,
|
toolbox=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
broadcast_generated(message=f"Generating actor: {name}")
|
broadcast_generated(message=f"Generating character: {name}")
|
||||||
description = agent(
|
description = agent(
|
||||||
"Generate a detailed description of the {name} character. What do they look like? What are they wearing? "
|
"Generate a detailed description of the {name} character. What do they look like? What are they wearing? "
|
||||||
"What are they doing? Describe their appearance from the perspective of an outside observer."
|
"What are they doing? Describe their appearance from the perspective of an outside observer."
|
||||||
|
@ -310,19 +314,19 @@ def generate_actor(
|
||||||
name=name,
|
name=name,
|
||||||
)
|
)
|
||||||
backstory = agent(
|
backstory = agent(
|
||||||
"Generate a backstory for the {name} actor. Where are they from? What are they doing here? What are their "
|
"Generate a backstory for the {name} character. Where are they from? What are they doing here? What are their "
|
||||||
'goals? Make sure to phrase the backstory in the second person, starting with "you are" and speaking directly to {name}.',
|
'goals? Make sure to phrase the backstory in the second person, starting with "you are" and speaking directly to {name}.',
|
||||||
name=name,
|
name=name,
|
||||||
)
|
)
|
||||||
|
|
||||||
actor = Actor(
|
character = Character(
|
||||||
name=name, backstory=backstory, description=description, actions={}, items=[]
|
name=name, backstory=backstory, description=description, actions={}, items=[]
|
||||||
)
|
)
|
||||||
generate_system_attributes(agent, world, actor, systems)
|
generate_system_attributes(agent, world, character, systems)
|
||||||
|
|
||||||
# generate the actor's inventory
|
# generate the character's inventory
|
||||||
item_count = resolve_int_range(world_config.size.actor_items) or 0
|
item_count = resolve_int_range(world_config.size.character_items) or 0
|
||||||
broadcast_generated(f"Generating {item_count} items for actor {name}")
|
broadcast_generated(f"Generating {item_count} items for character {name}")
|
||||||
|
|
||||||
for k in range(item_count):
|
for k in range(item_count):
|
||||||
try:
|
try:
|
||||||
|
@ -330,16 +334,16 @@ def generate_actor(
|
||||||
agent,
|
agent,
|
||||||
world,
|
world,
|
||||||
systems,
|
systems,
|
||||||
dest_actor=actor,
|
dest_character=character,
|
||||||
)
|
)
|
||||||
generate_system_attributes(agent, world, item, systems)
|
generate_system_attributes(agent, world, item, systems)
|
||||||
broadcast_generated(entity=item)
|
broadcast_generated(entity=item)
|
||||||
|
|
||||||
actor.items.append(item)
|
character.items.append(item)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("error generating item")
|
logger.exception("error generating item")
|
||||||
|
|
||||||
return actor
|
return character
|
||||||
|
|
||||||
|
|
||||||
def generate_effect(agent: Agent, world: World, entity: Item) -> EffectPattern:
|
def generate_effect(agent: Agent, world: World, entity: Item) -> EffectPattern:
|
||||||
|
@ -534,6 +538,8 @@ def generate_world(
|
||||||
# generate portals to link the rooms together
|
# generate portals to link the rooms together
|
||||||
link_rooms(agent, world, systems)
|
link_rooms(agent, world, systems)
|
||||||
|
|
||||||
# ensure actors act in a stable order
|
# ensure characters act in a stable order
|
||||||
world.order = [actor.name for room in world.rooms for actor in room.actors]
|
world.order = [
|
||||||
|
character.name for room in world.rooms for character in room.characters
|
||||||
|
]
|
||||||
return world
|
return world
|
||||||
|
|
|
@ -41,7 +41,7 @@ class ServerConfig:
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WorldActorConfig:
|
class WorldCharacterConfig:
|
||||||
conversation_limit: int
|
conversation_limit: int
|
||||||
event_limit: int
|
event_limit: int
|
||||||
note_limit: int
|
note_limit: int
|
||||||
|
@ -49,18 +49,26 @@ class WorldActorConfig:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WorldSizeConfig:
|
class WorldSizeConfig:
|
||||||
actor_items: int | IntRange
|
character_items: int | IntRange
|
||||||
item_effects: int | IntRange
|
item_effects: int | IntRange
|
||||||
portals: int | IntRange
|
portals: int | IntRange
|
||||||
room_actors: int | IntRange
|
room_characters: int | IntRange
|
||||||
room_items: int | IntRange
|
room_items: int | IntRange
|
||||||
rooms: int | IntRange
|
rooms: int | IntRange
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WorldStepConfig:
|
||||||
|
action_retries: int
|
||||||
|
planning_steps: int
|
||||||
|
planning_retries: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WorldConfig:
|
class WorldConfig:
|
||||||
actor: WorldActorConfig
|
character: WorldCharacterConfig
|
||||||
size: WorldSizeConfig
|
size: WorldSizeConfig
|
||||||
|
step: WorldStepConfig
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -88,18 +96,23 @@ DEFAULT_CONFIG = Config(
|
||||||
),
|
),
|
||||||
server=ServerConfig(websocket=WebsocketServerConfig(host="localhost", port=8001)),
|
server=ServerConfig(websocket=WebsocketServerConfig(host="localhost", port=8001)),
|
||||||
world=WorldConfig(
|
world=WorldConfig(
|
||||||
actor=WorldActorConfig(
|
character=WorldCharacterConfig(
|
||||||
conversation_limit=2,
|
conversation_limit=2,
|
||||||
event_limit=5,
|
event_limit=5,
|
||||||
note_limit=10,
|
note_limit=10,
|
||||||
),
|
),
|
||||||
size=WorldSizeConfig(
|
size=WorldSizeConfig(
|
||||||
actor_items=IntRange(min=0, max=2),
|
character_items=IntRange(min=0, max=2),
|
||||||
item_effects=IntRange(min=1, max=1),
|
item_effects=IntRange(min=1, max=1),
|
||||||
portals=IntRange(min=1, max=3),
|
portals=IntRange(min=1, max=3),
|
||||||
rooms=IntRange(min=3, max=6),
|
rooms=IntRange(min=3, max=6),
|
||||||
room_actors=IntRange(min=1, max=3),
|
room_characters=IntRange(min=1, max=3),
|
||||||
room_items=IntRange(min=1, max=3),
|
room_items=IntRange(min=1, max=3),
|
||||||
),
|
),
|
||||||
|
step=WorldStepConfig(
|
||||||
|
action_retries=5,
|
||||||
|
planning_steps=3,
|
||||||
|
planning_retries=3,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Item(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Actor(BaseModel):
|
class Character(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
backstory: str
|
backstory: str
|
||||||
description: str
|
description: str
|
||||||
|
@ -33,7 +33,7 @@ class Actor(BaseModel):
|
||||||
attributes: Attributes = Field(default_factory=dict)
|
attributes: Attributes = Field(default_factory=dict)
|
||||||
items: List[Item] = Field(default_factory=list)
|
items: List[Item] = Field(default_factory=list)
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["actor"] = "actor"
|
type: Literal["character"] = "character"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -51,7 +51,7 @@ class Portal(BaseModel):
|
||||||
class Room(BaseModel):
|
class Room(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
actors: List[Actor] = Field(default_factory=list)
|
characters: List[Character] = Field(default_factory=list)
|
||||||
actions: Actions = Field(default_factory=dict)
|
actions: Actions = Field(default_factory=dict)
|
||||||
active_effects: List[EffectResult] = Field(default_factory=list)
|
active_effects: List[EffectResult] = Field(default_factory=list)
|
||||||
attributes: Attributes = Field(default_factory=dict)
|
attributes: Attributes = Field(default_factory=dict)
|
||||||
|
@ -80,12 +80,12 @@ class WorldState(BaseModel):
|
||||||
type: Literal["world_state"] = "world_state"
|
type: Literal["world_state"] = "world_state"
|
||||||
|
|
||||||
|
|
||||||
WorldEntity = Room | Actor | Item | Portal
|
WorldEntity = Room | Character | Item | Portal
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EntityReference:
|
class EntityReference:
|
||||||
actor: str | None = None
|
character: str | None = None
|
||||||
item: str | None = None
|
item: str | None = None
|
||||||
portal: str | None = None
|
portal: str | None = None
|
||||||
room: str | None = None
|
room: str | None = None
|
||||||
|
|
|
@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, List, Literal, Union
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from .base import BaseModel, dataclass, uuid
|
from .base import BaseModel, dataclass, uuid
|
||||||
from .entity import Actor, Item, Room, WorldEntity
|
from .entity import Character, Item, Room, WorldEntity
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -30,26 +30,26 @@ class GenerateEvent(BaseModel):
|
||||||
@dataclass
|
@dataclass
|
||||||
class ActionEvent(BaseModel):
|
class ActionEvent(BaseModel):
|
||||||
"""
|
"""
|
||||||
An actor has taken an action.
|
A character has taken an action.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
action: str
|
action: str
|
||||||
parameters: Dict[str, bool | float | int | str]
|
parameters: Dict[str, bool | float | int | str]
|
||||||
|
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
item: Item | None = None
|
item: Item | None = None
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["action"] = "action"
|
type: Literal["action"] = "action"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_json(json: str, room: Room, actor: Actor) -> "ActionEvent":
|
def from_json(json: str, room: Room, character: Character) -> "ActionEvent":
|
||||||
openai_json = loads(json)
|
openai_json = loads(json)
|
||||||
return ActionEvent(
|
return ActionEvent(
|
||||||
action=openai_json["function"],
|
action=openai_json["function"],
|
||||||
parameters=openai_json["parameters"],
|
parameters=openai_json["parameters"],
|
||||||
room=room,
|
room=room,
|
||||||
actor=actor,
|
character=character,
|
||||||
item=None,
|
item=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,12 +57,12 @@ class ActionEvent(BaseModel):
|
||||||
@dataclass
|
@dataclass
|
||||||
class PromptEvent(BaseModel):
|
class PromptEvent(BaseModel):
|
||||||
"""
|
"""
|
||||||
A prompt for an actor to take an action.
|
A prompt for a character to take an action.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prompt: str
|
prompt: str
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["prompt"] = "prompt"
|
type: Literal["prompt"] = "prompt"
|
||||||
|
|
||||||
|
@ -70,22 +70,22 @@ class PromptEvent(BaseModel):
|
||||||
@dataclass
|
@dataclass
|
||||||
class ReplyEvent(BaseModel):
|
class ReplyEvent(BaseModel):
|
||||||
"""
|
"""
|
||||||
An actor has replied with text.
|
A character has replied with text.
|
||||||
|
|
||||||
This is the non-JSON version of an ActionEvent.
|
This is the non-JSON version of an ActionEvent.
|
||||||
|
|
||||||
TODO: add the actor being replied to.
|
TODO: add the character being replied to.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["reply"] = "reply"
|
type: Literal["reply"] = "reply"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_text(text: str, room: Room, actor: Actor) -> "ReplyEvent":
|
def from_text(text: str, room: Room, character: Character) -> "ReplyEvent":
|
||||||
return ReplyEvent(text=text, room=room, actor=actor)
|
return ReplyEvent(text=text, room=room, character=character)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -96,7 +96,7 @@ class ResultEvent(BaseModel):
|
||||||
|
|
||||||
result: str
|
result: str
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["result"] = "result"
|
type: Literal["result"] = "result"
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ class StatusEvent(BaseModel):
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
room: Room | None = None
|
room: Room | None = None
|
||||||
actor: Actor | None = None
|
character: Character | None = None
|
||||||
id: str = Field(default_factory=uuid)
|
id: str = Field(default_factory=uuid)
|
||||||
type: Literal["status"] = "status"
|
type: Literal["status"] = "status"
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ class SnapshotEvent(BaseModel):
|
||||||
A snapshot of the world state.
|
A snapshot of the world state.
|
||||||
|
|
||||||
This one is slightly unusual, because the world has already been dumped to a JSON-compatible dictionary.
|
This one is slightly unusual, because the world has already been dumped to a JSON-compatible dictionary.
|
||||||
That is especially important for the memory, which is a dictionary of actor names to lists of messages.
|
That is especially important for the memory, which is a dictionary of character names to lists of messages.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
world: Dict[str, Any]
|
world: Dict[str, Any]
|
||||||
|
|
|
@ -178,9 +178,9 @@ class RemotePlayer(BasePlayer):
|
||||||
formatted_prompt = prompt.format(**kwargs)
|
formatted_prompt = prompt.format(**kwargs)
|
||||||
self.memory.append(HumanMessage(content=formatted_prompt))
|
self.memory.append(HumanMessage(content=formatted_prompt))
|
||||||
|
|
||||||
with action_context() as (current_room, current_actor):
|
with action_context() as (current_room, current_character):
|
||||||
prompt_event = PromptEvent(
|
prompt_event = PromptEvent(
|
||||||
prompt=formatted_prompt, room=current_room, actor=current_actor
|
prompt=formatted_prompt, room=current_room, character=current_character
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -226,14 +226,16 @@ def fast_hash(text: str) -> str:
|
||||||
|
|
||||||
def get_image_prefix(event: GameEvent | WorldEntity) -> str:
|
def get_image_prefix(event: GameEvent | WorldEntity) -> str:
|
||||||
if isinstance(event, ActionEvent):
|
if isinstance(event, ActionEvent):
|
||||||
return sanitize_name(f"event-action-{event.actor.name}-{event.action}")
|
return sanitize_name(f"event-action-{event.character.name}-{event.action}")
|
||||||
|
|
||||||
if isinstance(event, ReplyEvent):
|
if isinstance(event, ReplyEvent):
|
||||||
return sanitize_name(f"event-reply-{event.actor.name}-{fast_hash(event.text)}")
|
return sanitize_name(
|
||||||
|
f"event-reply-{event.character.name}-{fast_hash(event.text)}"
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(event, ResultEvent):
|
if isinstance(event, ResultEvent):
|
||||||
return sanitize_name(
|
return sanitize_name(
|
||||||
f"event-result-{event.actor.name}-{fast_hash(event.result)}"
|
f"event-result-{event.character.name}-{fast_hash(event.result)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(event, StatusEvent):
|
if isinstance(event, StatusEvent):
|
||||||
|
|
|
@ -12,7 +12,7 @@ from adventure.models.event import (
|
||||||
ResultEvent,
|
ResultEvent,
|
||||||
StatusEvent,
|
StatusEvent,
|
||||||
)
|
)
|
||||||
from adventure.utils.search import find_actor_in_room, find_item_in_room, find_room
|
from adventure.utils.search import find_character_in_room, find_item_in_room, find_room
|
||||||
from adventure.utils.world import describe_entity
|
from adventure.utils.world import describe_entity
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
@ -28,11 +28,11 @@ def prompt_from_parameters(
|
||||||
# look up the character
|
# look up the character
|
||||||
character_name = str(parameters["character"])
|
character_name = str(parameters["character"])
|
||||||
logger.debug("searching for parameter character: %s", character_name)
|
logger.debug("searching for parameter character: %s", character_name)
|
||||||
target_actor = find_actor_in_room(action_room, character_name)
|
target_character = find_character_in_room(action_room, character_name)
|
||||||
if target_actor:
|
if target_character:
|
||||||
logger.debug("adding actor to prompt: %s", target_actor.name)
|
logger.debug("adding character to prompt: %s", target_character.name)
|
||||||
pre.append(f"with {target_actor.name}")
|
pre.append(f"with {target_character.name}")
|
||||||
post.append(describe_entity(target_actor))
|
post.append(describe_entity(target_character))
|
||||||
|
|
||||||
if "item" in parameters:
|
if "item" in parameters:
|
||||||
# look up the item
|
# look up the item
|
||||||
|
@ -41,7 +41,7 @@ def prompt_from_parameters(
|
||||||
target_item = find_item_in_room(
|
target_item = find_item_in_room(
|
||||||
action_room,
|
action_room,
|
||||||
item_name,
|
item_name,
|
||||||
include_actor_inventory=True,
|
include_character_inventory=True,
|
||||||
include_item_inventory=True,
|
include_item_inventory=True,
|
||||||
)
|
)
|
||||||
if target_item:
|
if target_item:
|
||||||
|
@ -50,7 +50,7 @@ def prompt_from_parameters(
|
||||||
post.append(describe_entity(target_item))
|
post.append(describe_entity(target_item))
|
||||||
|
|
||||||
if "target" in parameters:
|
if "target" in parameters:
|
||||||
# could be a room, actor, or item
|
# could be a room, character, or item
|
||||||
target_name = str(parameters["target"])
|
target_name = str(parameters["target"])
|
||||||
logger.debug("searching for parameter target: %s", target_name)
|
logger.debug("searching for parameter target: %s", target_name)
|
||||||
|
|
||||||
|
@ -62,16 +62,16 @@ def prompt_from_parameters(
|
||||||
pre.append(f"in the {target_room.name}")
|
pre.append(f"in the {target_room.name}")
|
||||||
post.append(describe_entity(target_room))
|
post.append(describe_entity(target_room))
|
||||||
|
|
||||||
target_actor = find_actor_in_room(action_room, target_name)
|
target_character = find_character_in_room(action_room, target_name)
|
||||||
if target_actor:
|
if target_character:
|
||||||
logger.debug("adding actor to prompt: %s", target_actor.name)
|
logger.debug("adding character to prompt: %s", target_character.name)
|
||||||
pre.append(f"with {target_actor.name}")
|
pre.append(f"with {target_character.name}")
|
||||||
post.append(describe_entity(target_actor))
|
post.append(describe_entity(target_character))
|
||||||
|
|
||||||
target_item = find_item_in_room(
|
target_item = find_item_in_room(
|
||||||
action_room,
|
action_room,
|
||||||
target_name,
|
target_name,
|
||||||
include_actor_inventory=True,
|
include_character_inventory=True,
|
||||||
include_item_inventory=True,
|
include_item_inventory=True,
|
||||||
)
|
)
|
||||||
if target_item:
|
if target_item:
|
||||||
|
@ -92,20 +92,20 @@ def scene_from_event(event: GameEvent) -> str | None:
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f"{event.actor.name} uses the {action_name} action {parameter_pre}. "
|
f"{event.character.name} uses the {action_name} action {parameter_pre}. "
|
||||||
"{describe_entity(event.actor)}. {describe_entity(event.room)}. {parameter_post}."
|
"{describe_entity(event.character)}. {describe_entity(event.room)}. {parameter_post}."
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(event, ReplyEvent):
|
if isinstance(event, ReplyEvent):
|
||||||
return f"{event.actor.name} replies: {event.text}. {describe_entity(event.actor)}. {describe_entity(event.room)}."
|
return f"{event.character.name} replies: {event.text}. {describe_entity(event.character)}. {describe_entity(event.room)}."
|
||||||
|
|
||||||
if isinstance(event, ResultEvent):
|
if isinstance(event, ResultEvent):
|
||||||
return f"{event.result}. {describe_entity(event.actor)}. {describe_entity(event.room)}."
|
return f"{event.result}. {describe_entity(event.character)}. {describe_entity(event.room)}."
|
||||||
|
|
||||||
if isinstance(event, StatusEvent):
|
if isinstance(event, StatusEvent):
|
||||||
if event.room:
|
if event.room:
|
||||||
if event.actor:
|
if event.character:
|
||||||
return f"{event.text}. {describe_entity(event.actor)}. {describe_entity(event.room)}."
|
return f"{event.text}. {describe_entity(event.character)}. {describe_entity(event.room)}."
|
||||||
|
|
||||||
return f"{event.text}. {describe_entity(event.room)}."
|
return f"{event.text}. {describe_entity(event.room)}."
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,13 @@ from pydantic import RootModel
|
||||||
|
|
||||||
from adventure.context import (
|
from adventure.context import (
|
||||||
broadcast,
|
broadcast,
|
||||||
get_actor_agent_for_name,
|
get_character_agent_for_name,
|
||||||
get_current_world,
|
get_current_world,
|
||||||
set_actor_agent,
|
set_character_agent,
|
||||||
subscribe,
|
subscribe,
|
||||||
)
|
)
|
||||||
from adventure.models.config import DEFAULT_CONFIG, WebsocketServerConfig
|
from adventure.models.config import DEFAULT_CONFIG, WebsocketServerConfig
|
||||||
from adventure.models.entity import Actor, Item, Room, World
|
from adventure.models.entity import Character, Item, Room, World
|
||||||
from adventure.models.event import (
|
from adventure.models.event import (
|
||||||
GameEvent,
|
GameEvent,
|
||||||
PlayerEvent,
|
PlayerEvent,
|
||||||
|
@ -38,7 +38,7 @@ from adventure.player import (
|
||||||
)
|
)
|
||||||
from adventure.render.comfy import render_entity, render_event
|
from adventure.render.comfy import render_entity, render_event
|
||||||
from adventure.state import snapshot_world, world_json
|
from adventure.state import snapshot_world, world_json
|
||||||
from adventure.utils.search import find_actor, find_item, find_portal, find_room
|
from adventure.utils.search import find_character, find_item, find_portal, find_room
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ async def handler(websocket):
|
||||||
def sync_turn(event: PromptEvent) -> bool:
|
def sync_turn(event: PromptEvent) -> bool:
|
||||||
# TODO: nothing about this is good
|
# TODO: nothing about this is good
|
||||||
player = get_player(id)
|
player = get_player(id)
|
||||||
if player and player.name == event.actor.name:
|
if player and player.name == event.character.name:
|
||||||
asyncio.run(next_turn(event.actor.name, event.prompt))
|
asyncio.run(next_turn(event.character.name, event.prompt))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -137,9 +137,11 @@ async def handler(websocket):
|
||||||
# TODO: should this always remove?
|
# TODO: should this always remove?
|
||||||
remove_player(id)
|
remove_player(id)
|
||||||
|
|
||||||
actor, llm_agent = get_actor_agent_for_name(character_name)
|
character, llm_agent = get_character_agent_for_name(
|
||||||
if not actor:
|
character_name
|
||||||
logger.error(f"Failed to find actor {character_name}")
|
)
|
||||||
|
if not character:
|
||||||
|
logger.error(f"Failed to find character {character_name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# prevent any recursive fallback bugs
|
# prevent any recursive fallback bugs
|
||||||
|
@ -150,8 +152,8 @@ async def handler(websocket):
|
||||||
llm_agent = llm_agent.fallback_agent
|
llm_agent = llm_agent.fallback_agent
|
||||||
|
|
||||||
player = RemotePlayer(
|
player = RemotePlayer(
|
||||||
actor.name,
|
character.name,
|
||||||
actor.backstory,
|
character.backstory,
|
||||||
sync_turn,
|
sync_turn,
|
||||||
fallback_agent=llm_agent,
|
fallback_agent=llm_agent,
|
||||||
)
|
)
|
||||||
|
@ -161,7 +163,7 @@ async def handler(websocket):
|
||||||
)
|
)
|
||||||
|
|
||||||
# swap out the LLM agent
|
# swap out the LLM agent
|
||||||
set_actor_agent(actor.name, actor, player)
|
set_character_agent(character.name, character, player)
|
||||||
|
|
||||||
# notify all clients that this character is now active
|
# notify all clients that this character is now active
|
||||||
broadcast_player_event(character_name, player_name, "join")
|
broadcast_player_event(character_name, player_name, "join")
|
||||||
|
@ -195,10 +197,10 @@ async def handler(websocket):
|
||||||
broadcast_player_event(player.name, player_name, "leave")
|
broadcast_player_event(player.name, player_name, "leave")
|
||||||
broadcast_player_list()
|
broadcast_player_list()
|
||||||
|
|
||||||
actor, _ = get_actor_agent_for_name(player.name)
|
character, _ = get_character_agent_for_name(player.name)
|
||||||
if actor and player.fallback_agent:
|
if character and player.fallback_agent:
|
||||||
logger.info("restoring LLM agent for %s", player.name)
|
logger.info("restoring LLM agent for %s", player.name)
|
||||||
set_actor_agent(player.name, actor, player.fallback_agent)
|
set_character_agent(player.name, character, player.fallback_agent)
|
||||||
|
|
||||||
logger.info("client disconnected: %s", id)
|
logger.info("client disconnected: %s", id)
|
||||||
|
|
||||||
|
@ -220,17 +222,20 @@ def render_input(data):
|
||||||
render_event(event)
|
render_event(event)
|
||||||
else:
|
else:
|
||||||
logger.error(f"failed to find event {event_id}")
|
logger.error(f"failed to find event {event_id}")
|
||||||
elif "actor" in data:
|
elif "character" in data:
|
||||||
actor_name = data["actor"]
|
character_name = data["character"]
|
||||||
actor = find_actor(world, actor_name)
|
character = find_character(world, character_name)
|
||||||
if actor:
|
if character:
|
||||||
render_entity(actor)
|
render_entity(character)
|
||||||
else:
|
else:
|
||||||
logger.error(f"failed to find actor {actor_name}")
|
logger.error(f"failed to find character {character_name}")
|
||||||
elif "item" in data:
|
elif "item" in data:
|
||||||
item_name = data["item"]
|
item_name = data["item"]
|
||||||
item = find_item(
|
item = find_item(
|
||||||
world, item_name, include_actor_inventory=True, include_item_inventory=True
|
world,
|
||||||
|
item_name,
|
||||||
|
include_character_inventory=True,
|
||||||
|
include_item_inventory=True,
|
||||||
)
|
)
|
||||||
if item:
|
if item:
|
||||||
render_entity(item)
|
render_entity(item)
|
||||||
|
@ -258,7 +263,7 @@ socket_thread = None
|
||||||
|
|
||||||
|
|
||||||
def server_json(obj):
|
def server_json(obj):
|
||||||
if isinstance(obj, (Actor, Item, Room)):
|
if isinstance(obj, (Character, Item, Room)):
|
||||||
return obj.name
|
return obj.name
|
||||||
|
|
||||||
return world_json(obj)
|
return world_json(obj)
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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_reduce, loop_retry
|
from packit.loops import loop_retry
|
||||||
from packit.results import multi_function_or_str_result
|
from packit.results import multi_function_or_str_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
|
||||||
|
@ -32,28 +32,32 @@ from adventure.actions.planning import (
|
||||||
)
|
)
|
||||||
from adventure.context import (
|
from adventure.context import (
|
||||||
broadcast,
|
broadcast,
|
||||||
get_actor_agent_for_name,
|
get_character_agent_for_name,
|
||||||
get_actor_for_agent,
|
get_character_for_agent,
|
||||||
get_current_step,
|
get_current_step,
|
||||||
get_current_world,
|
get_current_world,
|
||||||
set_current_actor,
|
set_current_character,
|
||||||
set_current_room,
|
set_current_room,
|
||||||
set_current_step,
|
set_current_step,
|
||||||
set_current_world,
|
set_current_world,
|
||||||
set_game_systems,
|
set_game_systems,
|
||||||
)
|
)
|
||||||
from adventure.game_system import GameSystem
|
from adventure.game_system import GameSystem
|
||||||
from adventure.models.entity import Actor, Room, World
|
from adventure.models.config import DEFAULT_CONFIG
|
||||||
|
from adventure.models.entity import Character, Room, World
|
||||||
from adventure.models.event import ActionEvent, ReplyEvent, ResultEvent
|
from adventure.models.event import ActionEvent, ReplyEvent, ResultEvent
|
||||||
from adventure.utils.conversation import make_keyword_condition, summarize_room
|
from adventure.utils.conversation import make_keyword_condition, summarize_room
|
||||||
from adventure.utils.effect import expire_effects
|
from adventure.utils.effect import expire_effects
|
||||||
from adventure.utils.planning import expire_events, get_upcoming_events
|
from adventure.utils.planning import expire_events, get_upcoming_events
|
||||||
from adventure.utils.search import find_room_with_actor
|
from adventure.utils.search import find_room_with_character
|
||||||
from adventure.utils.world import describe_entity, format_attributes
|
from adventure.utils.world import describe_entity, format_attributes
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
step_config = DEFAULT_CONFIG.world.step
|
||||||
|
|
||||||
|
|
||||||
def world_result_parser(value, agent, **kwargs):
|
def world_result_parser(value, agent, **kwargs):
|
||||||
current_world = get_current_world()
|
current_world = get_current_world()
|
||||||
if not current_world:
|
if not current_world:
|
||||||
|
@ -63,35 +67,36 @@ def world_result_parser(value, agent, **kwargs):
|
||||||
|
|
||||||
logger.debug(f"parsing action for {agent.name}: {value}")
|
logger.debug(f"parsing action for {agent.name}: {value}")
|
||||||
|
|
||||||
current_actor = get_actor_for_agent(agent)
|
current_character = get_character_for_agent(agent)
|
||||||
current_room = next(
|
current_room = next(
|
||||||
(room for room in current_world.rooms if current_actor in room.actors), None
|
(room for room in current_world.rooms if current_character in room.characters),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
set_current_room(current_room)
|
set_current_room(current_room)
|
||||||
set_current_actor(current_actor)
|
set_current_character(current_character)
|
||||||
|
|
||||||
return multi_function_or_str_result(value, agent=agent, **kwargs)
|
return multi_function_or_str_result(value, agent=agent, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def prompt_actor_action(
|
def prompt_character_action(
|
||||||
room, actor, agent, action_names, action_toolbox, current_turn
|
room, character, agent, action_names, action_toolbox, current_turn
|
||||||
) -> str:
|
) -> str:
|
||||||
# collect data for the prompt
|
# collect data for the prompt
|
||||||
notes_prompt, events_prompt = get_notes_events(actor, current_turn)
|
notes_prompt, events_prompt = get_notes_events(character, current_turn)
|
||||||
|
|
||||||
room_actors = [actor.name for actor in room.actors]
|
room_characters = [character.name for character in room.characters]
|
||||||
room_items = [item.name for item in room.items]
|
room_items = [item.name for item in room.items]
|
||||||
room_directions = [portal.name for portal in room.portals]
|
room_directions = [portal.name for portal in room.portals]
|
||||||
|
|
||||||
actor_attributes = format_attributes(actor)
|
character_attributes = format_attributes(character)
|
||||||
# actor_effects = [effect.name for effect in actor.active_effects]
|
# character_effects = [effect.name for effect in character.active_effects]
|
||||||
actor_items = [item.name for item in actor.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, agent, **kwargs):
|
||||||
if not room or not actor:
|
if not room or not character:
|
||||||
raise ValueError("Room and actor must be set before parsing results")
|
raise ValueError("Room and character must be set before parsing results")
|
||||||
|
|
||||||
# trim suffixes that are used elsewhere
|
# trim suffixes that are used elsewhere
|
||||||
value = value.removesuffix("END").strip()
|
value = value.removesuffix("END").strip()
|
||||||
|
@ -110,23 +115,23 @@ def prompt_actor_action(
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if could_be_json(value):
|
if could_be_json(value):
|
||||||
event = ActionEvent.from_json(value, room, actor)
|
event = ActionEvent.from_json(value, room, character)
|
||||||
else:
|
else:
|
||||||
event = ReplyEvent.from_text(value, room, actor)
|
event = ReplyEvent.from_text(value, room, character)
|
||||||
|
|
||||||
broadcast(event)
|
broadcast(event)
|
||||||
|
|
||||||
return world_result_parser(value, agent, **kwargs)
|
return world_result_parser(value, agent, **kwargs)
|
||||||
|
|
||||||
# prompt and act
|
# prompt and act
|
||||||
logger.info("starting turn for actor: %s", actor.name)
|
logger.info("starting turn for character: %s", character.name)
|
||||||
result = loop_retry(
|
result = loop_retry(
|
||||||
agent,
|
agent,
|
||||||
(
|
(
|
||||||
"You are currently in the {room_name} room. {room_description}. {attributes}. "
|
"You are currently in the {room_name} room. {room_description}. {attributes}. "
|
||||||
"The room contains the following characters: {visible_actors}. "
|
"The room contains the following characters: {visible_characters}. "
|
||||||
"The room contains the following items: {visible_items}. "
|
"The room contains the following items: {visible_items}. "
|
||||||
"Your inventory contains the following items: {actor_items}."
|
"Your inventory contains the following items: {character_items}."
|
||||||
"You can take the following actions: {actions}. "
|
"You can take the following actions: {actions}. "
|
||||||
"You can move in the following directions: {directions}. "
|
"You can move in the following directions: {directions}. "
|
||||||
"{notes_prompt} {events_prompt}"
|
"{notes_prompt} {events_prompt}"
|
||||||
|
@ -135,12 +140,12 @@ def prompt_actor_action(
|
||||||
),
|
),
|
||||||
context={
|
context={
|
||||||
"actions": action_names,
|
"actions": action_names,
|
||||||
"actor_items": actor_items,
|
"character_items": character_items,
|
||||||
"attributes": actor_attributes,
|
"attributes": character_attributes,
|
||||||
"directions": room_directions,
|
"directions": room_directions,
|
||||||
"room_name": room.name,
|
"room_name": room.name,
|
||||||
"room_description": describe_entity(room),
|
"room_description": describe_entity(room),
|
||||||
"visible_actors": room_actors,
|
"visible_characters": room_characters,
|
||||||
"visible_items": room_items,
|
"visible_items": room_items,
|
||||||
"notes_prompt": notes_prompt,
|
"notes_prompt": notes_prompt,
|
||||||
"events_prompt": events_prompt,
|
"events_prompt": events_prompt,
|
||||||
|
@ -149,7 +154,7 @@ def prompt_actor_action(
|
||||||
toolbox=action_toolbox,
|
toolbox=action_toolbox,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"{actor.name} step result: {result}")
|
logger.debug(f"{character.name} step result: {result}")
|
||||||
if agent.memory:
|
if agent.memory:
|
||||||
# TODO: make sure this is not duplicating memories and wasting space
|
# TODO: make sure this is not duplicating memories and wasting space
|
||||||
agent.memory.append(result)
|
agent.memory.append(result)
|
||||||
|
@ -157,9 +162,9 @@ def prompt_actor_action(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_notes_events(actor: Actor, current_turn: int):
|
def get_notes_events(character: Character, current_turn: int):
|
||||||
recent_notes = get_recent_notes(actor)
|
recent_notes = get_recent_notes(character)
|
||||||
upcoming_events = get_upcoming_events(actor, current_turn)
|
upcoming_events = get_upcoming_events(character, current_turn)
|
||||||
|
|
||||||
if len(recent_notes) > 0:
|
if len(recent_notes) > 0:
|
||||||
notes = "\n".join(recent_notes)
|
notes = "\n".join(recent_notes)
|
||||||
|
@ -181,19 +186,30 @@ def get_notes_events(actor: Actor, current_turn: int):
|
||||||
return notes_prompt, events_prompt
|
return notes_prompt, events_prompt
|
||||||
|
|
||||||
|
|
||||||
def prompt_actor_think(
|
def prompt_character_think(
|
||||||
room: Room, actor: Actor, agent: Agent, planner_toolbox: Toolbox, current_turn: int
|
room: Room,
|
||||||
|
character: Character,
|
||||||
|
agent: Agent,
|
||||||
|
planner_toolbox: Toolbox,
|
||||||
|
current_turn: int,
|
||||||
|
max_steps: int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
notes_prompt, events_prompt = get_notes_events(actor, current_turn)
|
max_steps = max_steps or step_config.planning_steps
|
||||||
|
|
||||||
event_count = len(actor.planner.calendar.events)
|
notes_prompt, events_prompt = get_notes_events(character, current_turn)
|
||||||
note_count = len(actor.planner.notes)
|
|
||||||
|
|
||||||
logger.info("starting planning for actor: %s", actor.name)
|
event_count = len(character.planner.calendar.events)
|
||||||
|
note_count = len(character.planner.notes)
|
||||||
|
|
||||||
|
logger.info("starting planning for character: %s", character.name)
|
||||||
_, condition_end, result_parser = make_keyword_condition("You are done planning.")
|
_, condition_end, result_parser = make_keyword_condition("You are done planning.")
|
||||||
stop_condition = condition_or(condition_end, partial(condition_threshold, max=3))
|
stop_condition = condition_or(
|
||||||
|
condition_end, partial(condition_threshold, max=max_steps)
|
||||||
|
)
|
||||||
|
|
||||||
result = loop_reduce(
|
i = 0
|
||||||
|
while not stop_condition(current=i):
|
||||||
|
result = loop_retry(
|
||||||
agent,
|
agent,
|
||||||
"You are about to start your turn. Plan your next action carefully. Take notes and schedule events to help keep track of your goals. "
|
"You are about to start your turn. Plan your next action carefully. Take notes and schedule events to help keep track of your goals. "
|
||||||
"You can check your notes for important facts or check your calendar for upcoming events. You have {note_count} notes. "
|
"You can check your notes for important facts or check your calendar for upcoming events. You have {note_count} notes. "
|
||||||
|
@ -209,7 +225,7 @@ def prompt_actor_think(
|
||||||
"events_prompt": events_prompt,
|
"events_prompt": events_prompt,
|
||||||
"note_count": note_count,
|
"note_count": note_count,
|
||||||
"notes_prompt": notes_prompt,
|
"notes_prompt": notes_prompt,
|
||||||
"room_summary": summarize_room(room, actor),
|
"room_summary": summarize_room(room, character),
|
||||||
},
|
},
|
||||||
result_parser=result_parser,
|
result_parser=result_parser,
|
||||||
stop_condition=stop_condition,
|
stop_condition=stop_condition,
|
||||||
|
@ -219,6 +235,8 @@ def prompt_actor_think(
|
||||||
if agent.memory:
|
if agent.memory:
|
||||||
agent.memory.append(result)
|
agent.memory.append(result)
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -259,44 +277,46 @@ def simulate_world(
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# simulate each actor
|
# simulate each character
|
||||||
for i in count():
|
for i in count():
|
||||||
current_step = get_current_step()
|
current_step = get_current_step()
|
||||||
logger.info(f"simulating step {i} of {steps} (world step {current_step})")
|
logger.info(f"simulating step {i} of {steps} (world step {current_step})")
|
||||||
|
|
||||||
for actor_name in world.order:
|
for character_name in world.order:
|
||||||
actor, agent = get_actor_agent_for_name(actor_name)
|
character, agent = get_character_agent_for_name(character_name)
|
||||||
if not agent or not actor:
|
if not agent or not character:
|
||||||
logger.error(f"agent or actor not found for name {actor_name}")
|
logger.error(f"agent or character not found for name {character_name}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
room = find_room_with_actor(world, actor)
|
room = find_room_with_character(world, character)
|
||||||
if not room:
|
if not room:
|
||||||
logger.error(f"actor {actor_name} is not in a room")
|
logger.error(f"character {character_name} is not in a room")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# prep context
|
# prep context
|
||||||
set_current_room(room)
|
set_current_room(room)
|
||||||
set_current_actor(actor)
|
set_current_character(character)
|
||||||
|
|
||||||
# decrement effects on the actor and remove any that have expired
|
# decrement effects on the character and remove any that have expired
|
||||||
expire_effects(actor)
|
expire_effects(character)
|
||||||
expire_events(actor, current_step)
|
expire_events(character, current_step)
|
||||||
|
|
||||||
# give the actor a chance to think and check their planner
|
# give the character a chance to think and check their planner
|
||||||
if agent.memory and len(agent.memory) > 0:
|
if agent.memory and len(agent.memory) > 0:
|
||||||
try:
|
try:
|
||||||
thoughts = prompt_actor_think(
|
thoughts = prompt_character_think(
|
||||||
room, actor, agent, planner_toolbox, current_step
|
room, character, agent, planner_toolbox, current_step
|
||||||
)
|
)
|
||||||
logger.debug(f"{actor.name} thinks: {thoughts}")
|
logger.debug(f"{character.name} thinks: {thoughts}")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(f"error during planning for actor {actor.name}")
|
logger.exception(
|
||||||
|
f"error during planning for character {character.name}"
|
||||||
result = prompt_actor_action(
|
|
||||||
room, actor, agent, action_names, action_tools, current_step
|
|
||||||
)
|
)
|
||||||
result_event = ResultEvent(result=result, room=room, actor=actor)
|
|
||||||
|
result = prompt_character_action(
|
||||||
|
room, character, agent, action_names, action_tools, current_step
|
||||||
|
)
|
||||||
|
result_event = ResultEvent(result=result, room=room, character=character)
|
||||||
broadcast(result_event)
|
broadcast(result_event)
|
||||||
|
|
||||||
for system in systems:
|
for system in systems:
|
||||||
|
|
|
@ -7,7 +7,7 @@ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, System
|
||||||
from packit.agent import Agent, agent_easy_connect
|
from packit.agent import Agent, agent_easy_connect
|
||||||
from pydantic import RootModel
|
from pydantic import RootModel
|
||||||
|
|
||||||
from adventure.context import get_all_actor_agents, set_actor_agent
|
from adventure.context import get_all_character_agents, set_character_agent
|
||||||
from adventure.models.entity import World
|
from adventure.models.entity import World
|
||||||
from adventure.player import LocalPlayer
|
from adventure.player import LocalPlayer
|
||||||
|
|
||||||
|
@ -17,19 +17,19 @@ def create_agents(
|
||||||
memory: Dict[str, List[str | Dict[str, str]]] = {},
|
memory: Dict[str, List[str | Dict[str, str]]] = {},
|
||||||
players: List[str] = [],
|
players: List[str] = [],
|
||||||
):
|
):
|
||||||
# set up agents for each actor
|
# set up agents for each character
|
||||||
llm = agent_easy_connect()
|
llm = agent_easy_connect()
|
||||||
|
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
if actor.name in players:
|
if character.name in players:
|
||||||
agent = LocalPlayer(actor.name, actor.backstory)
|
agent = LocalPlayer(character.name, character.backstory)
|
||||||
agent_memory = restore_memory(memory.get(actor.name, []))
|
agent_memory = restore_memory(memory.get(character.name, []))
|
||||||
agent.load_history(agent_memory)
|
agent.load_history(agent_memory)
|
||||||
else:
|
else:
|
||||||
agent = Agent(actor.name, actor.backstory, {}, llm)
|
agent = Agent(character.name, character.backstory, {}, llm)
|
||||||
agent.memory = restore_memory(memory.get(actor.name, []))
|
agent.memory = restore_memory(memory.get(character.name, []))
|
||||||
set_actor_agent(actor.name, actor, agent)
|
set_character_agent(character.name, character, agent)
|
||||||
|
|
||||||
|
|
||||||
def graph_world(world: World, step: int):
|
def graph_world(world: World, step: int):
|
||||||
|
@ -38,8 +38,8 @@ def graph_world(world: World, step: int):
|
||||||
graph_name = f"{path.basename(world.name)}-{step}"
|
graph_name = f"{path.basename(world.name)}-{step}"
|
||||||
graph = graphviz.Digraph(graph_name, format="png")
|
graph = graphviz.Digraph(graph_name, format="png")
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
actors = [actor.name for actor in room.actors]
|
characters = [character.name for character in room.characters]
|
||||||
room_label = "\n".join([room.name, *actors])
|
room_label = "\n".join([room.name, *characters])
|
||||||
graph.node(room.name, room_label)
|
graph.node(room.name, room_label)
|
||||||
for portal in room.portals:
|
for portal in room.portals:
|
||||||
graph.edge(room.name, portal.destination, label=portal.name)
|
graph.edge(room.name, portal.destination, label=portal.name)
|
||||||
|
@ -54,8 +54,8 @@ def snapshot_world(world: World, step: int):
|
||||||
|
|
||||||
json_memory = {}
|
json_memory = {}
|
||||||
|
|
||||||
for actor, agent in get_all_actor_agents():
|
for character, agent in get_all_character_agents():
|
||||||
json_memory[actor.name] = list(agent.memory or [])
|
json_memory[character.name] = list(agent.memory or [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"world": json_world,
|
"world": json_world,
|
||||||
|
|
|
@ -138,9 +138,9 @@ def update_logic(
|
||||||
) -> None:
|
) -> None:
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
update_attributes(room, rules=rules, triggers=triggers)
|
update_attributes(room, rules=rules, triggers=triggers)
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
update_attributes(actor, rules=rules, triggers=triggers)
|
update_attributes(character, rules=rules, triggers=triggers)
|
||||||
for item in actor.items:
|
for item in character.items:
|
||||||
update_attributes(item, rules=rules, triggers=triggers)
|
update_attributes(item, rules=rules, triggers=triggers)
|
||||||
for item in room.items:
|
for item in room.items:
|
||||||
update_attributes(item, rules=rules, triggers=triggers)
|
update_attributes(item, rules=rules, triggers=triggers)
|
||||||
|
|
|
@ -8,7 +8,7 @@ from adventure.context import get_system_data
|
||||||
from adventure.game_system import GameSystem, SystemData
|
from adventure.game_system import GameSystem, SystemData
|
||||||
from adventure.models.base import Attributes, dataclass, uuid
|
from adventure.models.base import Attributes, dataclass, uuid
|
||||||
from adventure.models.entity import (
|
from adventure.models.entity import (
|
||||||
Actor,
|
Character,
|
||||||
EntityReference,
|
EntityReference,
|
||||||
Item,
|
Item,
|
||||||
Room,
|
Room,
|
||||||
|
@ -35,8 +35,8 @@ class QuestGoalContains:
|
||||||
Quest goal for any kind of fetch quest, including delivery and escort quests.
|
Quest goal for any kind of fetch quest, including delivery and escort quests.
|
||||||
|
|
||||||
Valid combinations are:
|
Valid combinations are:
|
||||||
- container: Room and items: List[Actor | Item]
|
- container: Room and items: List[Character | Item]
|
||||||
- container: Actor and items: List[Item]
|
- container: Character and items: List[Item]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
container: EntityReference
|
container: EntityReference
|
||||||
|
@ -98,7 +98,7 @@ def is_quest_complete(world: World, quest: Quest) -> bool:
|
||||||
if content.item:
|
if content.item:
|
||||||
if not find_item_in_room(container, content.item):
|
if not find_item_in_room(container, content.item):
|
||||||
return False
|
return False
|
||||||
elif isinstance(container, (Actor, Item)):
|
elif isinstance(container, (Character, Item)):
|
||||||
if content.item:
|
if content.item:
|
||||||
if not find_item_in_container(container, content.item):
|
if not find_item_in_container(container, content.item):
|
||||||
return False
|
return False
|
||||||
|
@ -122,41 +122,41 @@ def is_quest_complete(world: World, quest: Quest) -> bool:
|
||||||
|
|
||||||
|
|
||||||
# region state management
|
# region state management
|
||||||
def get_quests_for_actor(quests: QuestData, actor: Actor) -> List[Quest]:
|
def get_quests_for_character(quests: QuestData, character: Character) -> List[Quest]:
|
||||||
"""
|
"""
|
||||||
Get all quests for the given actor.
|
Get all quests for the given character.
|
||||||
"""
|
"""
|
||||||
return quests.available.get(actor.name, [])
|
return quests.available.get(character.name, [])
|
||||||
|
|
||||||
|
|
||||||
def set_active_quest(quests: QuestData, actor: Actor, quest: Quest) -> None:
|
def set_active_quest(quests: QuestData, character: Character, quest: Quest) -> None:
|
||||||
"""
|
"""
|
||||||
Set the active quest for the given actor.
|
Set the active quest for the given character.
|
||||||
"""
|
"""
|
||||||
quests.active[actor.name] = quest
|
quests.active[character.name] = quest
|
||||||
|
|
||||||
|
|
||||||
def get_active_quest(quests: QuestData, actor: Actor) -> Quest | None:
|
def get_active_quest(quests: QuestData, character: Character) -> Quest | None:
|
||||||
"""
|
"""
|
||||||
Get the active quest for the given actor.
|
Get the active quest for the given character.
|
||||||
"""
|
"""
|
||||||
return quests.active.get(actor.name)
|
return quests.active.get(character.name)
|
||||||
|
|
||||||
|
|
||||||
def complete_quest(quests: QuestData, actor: Actor, quest: Quest) -> None:
|
def complete_quest(quests: QuestData, character: Character, quest: Quest) -> None:
|
||||||
"""
|
"""
|
||||||
Complete the given quest for the given actor.
|
Complete the given quest for the given character.
|
||||||
"""
|
"""
|
||||||
if quest in quests.available.get(actor.name, []):
|
if quest in quests.available.get(character.name, []):
|
||||||
quests.available[actor.name].remove(quest)
|
quests.available[character.name].remove(quest)
|
||||||
|
|
||||||
if quest == quests.active.get(actor.name, None):
|
if quest == quests.active.get(character.name, None):
|
||||||
del quests.active[actor.name]
|
del quests.active[character.name]
|
||||||
|
|
||||||
if actor.name not in quests.completed:
|
if character.name not in quests.completed:
|
||||||
quests.completed[actor.name] = []
|
quests.completed[character.name] = []
|
||||||
|
|
||||||
quests.completed[actor.name].append(quest)
|
quests.completed[character.name].append(quest)
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -180,8 +180,8 @@ def generate_quests(agent: Agent, theme: str, entity: WorldEntity) -> None:
|
||||||
if not quests:
|
if not quests:
|
||||||
raise ValueError("Quest data is required for quest generation")
|
raise ValueError("Quest data is required for quest generation")
|
||||||
|
|
||||||
if isinstance(entity, Actor):
|
if isinstance(entity, Character):
|
||||||
available_quests = get_quests_for_actor(quests, entity)
|
available_quests = get_quests_for_character(quests, entity)
|
||||||
if len(available_quests) == 0:
|
if len(available_quests) == 0:
|
||||||
logger.info(f"generating new quest for {entity.name}")
|
logger.info(f"generating new quest for {entity.name}")
|
||||||
# TODO: generate one new quest
|
# TODO: generate one new quest
|
||||||
|
@ -201,13 +201,17 @@ def simulate_quests(world: World, step: int, data: QuestData | None = None) -> N
|
||||||
raise ValueError("Quest data is required for simulation")
|
raise ValueError("Quest data is required for simulation")
|
||||||
|
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
active_quest = get_active_quest(quests, actor)
|
active_quest = get_active_quest(quests, character)
|
||||||
if active_quest:
|
if active_quest:
|
||||||
logger.info(f"simulating quest for {actor.name}: {active_quest.name}")
|
logger.info(
|
||||||
|
f"simulating quest for {character.name}: {active_quest.name}"
|
||||||
|
)
|
||||||
if is_quest_complete(world, active_quest):
|
if is_quest_complete(world, active_quest):
|
||||||
logger.info(f"quest complete for {actor.name}: {active_quest.name}")
|
logger.info(
|
||||||
complete_quest(quests, actor, active_quest)
|
f"quest complete for {character.name}: {active_quest.name}"
|
||||||
|
)
|
||||||
|
complete_quest(quests, character, active_quest)
|
||||||
|
|
||||||
|
|
||||||
def load_quest_data(file: str) -> QuestData:
|
def load_quest_data(file: str) -> QuestData:
|
||||||
|
|
|
@ -31,19 +31,19 @@ def action_craft(item: str) -> str:
|
||||||
Args:
|
Args:
|
||||||
item: The name of the item to craft.
|
item: The name of the item to craft.
|
||||||
"""
|
"""
|
||||||
with world_context() as (action_world, _, action_actor):
|
with world_context() as (action_world, _, action_character):
|
||||||
if item not in recipes:
|
if item not in recipes:
|
||||||
return f"There is no recipe to craft a {item}."
|
return f"There is no recipe to craft a {item}."
|
||||||
|
|
||||||
recipe = recipes[item]
|
recipe = recipes[item]
|
||||||
|
|
||||||
# Check if the actor has the required skill level
|
# Check if the character has the required skill level
|
||||||
skill = randint(1, 20)
|
skill = randint(1, 20)
|
||||||
if skill < recipe.difficulty:
|
if skill < recipe.difficulty:
|
||||||
return f"You need a crafting skill level of {recipe.difficulty} to craft {item}."
|
return f"You need a crafting skill level of {recipe.difficulty} to craft {item}."
|
||||||
|
|
||||||
# Collect inventory items names
|
# Collect inventory items names
|
||||||
inventory_items = {item.name for item in action_actor.items}
|
inventory_items = {item.name for item in action_character.items}
|
||||||
|
|
||||||
# Check for sufficient ingredients
|
# Check for sufficient ingredients
|
||||||
missing_items = [
|
missing_items = [
|
||||||
|
@ -55,13 +55,14 @@ def action_craft(item: str) -> str:
|
||||||
# Deduct the ingredients from inventory
|
# Deduct the ingredients from inventory
|
||||||
for ingredient in recipe.ingredients:
|
for ingredient in recipe.ingredients:
|
||||||
item_to_remove = next(
|
item_to_remove = next(
|
||||||
item for item in action_actor.items if item.name == ingredient
|
item for item in action_character.items if item.name == ingredient
|
||||||
)
|
)
|
||||||
action_actor.items.remove(item_to_remove)
|
action_character.items.remove(item_to_remove)
|
||||||
|
|
||||||
# Create and add the crafted item to inventory
|
# Create and add the crafted item to inventory
|
||||||
result_item = next(
|
result_item = next(
|
||||||
(item for item in action_actor.items if item.name == recipe.result), None
|
(item for item in action_character.items if item.name == recipe.result),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
if result_item:
|
if result_item:
|
||||||
new_item = Item(**vars(result_item)) # Copying the item
|
new_item = Item(**vars(result_item)) # Copying the item
|
||||||
|
@ -72,7 +73,7 @@ def action_craft(item: str) -> str:
|
||||||
dungeon_master, action_world, systems
|
dungeon_master, action_world, systems
|
||||||
) # TODO: pass crafting recipe and generate from that
|
) # TODO: pass crafting recipe and generate from that
|
||||||
|
|
||||||
action_actor.items.append(new_item)
|
action_character.items.append(new_item)
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} crafts a {item}.")
|
broadcast(f"{action_character.name} crafts a {item}.")
|
||||||
return f"You successfully craft a {item}."
|
return f"You successfully craft a {item}."
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from adventure.context import action_context, broadcast
|
from adventure.context import action_context, broadcast
|
||||||
from adventure.utils.search import find_item_in_actor
|
from adventure.utils.search import find_item_in_character
|
||||||
|
|
||||||
|
|
||||||
def action_read(item: str) -> str:
|
def action_read(item: str) -> str:
|
||||||
|
@ -9,13 +9,13 @@ def action_read(item: str) -> str:
|
||||||
Args:
|
Args:
|
||||||
item: The name of the item to read.
|
item: The name of the item to read.
|
||||||
"""
|
"""
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
action_item = find_item_in_actor(action_actor, item)
|
action_item = find_item_in_character(action_character, item)
|
||||||
if not action_item:
|
if not action_item:
|
||||||
return f"You do not have a {item} to read."
|
return f"You do not have a {item} to read."
|
||||||
|
|
||||||
if "text" in action_item.attributes:
|
if "text" in action_item.attributes:
|
||||||
broadcast(f"{action_actor.name} reads {item}")
|
broadcast(f"{action_character.name} reads {item}")
|
||||||
return str(action_item.attributes["text"])
|
return str(action_item.attributes["text"])
|
||||||
|
|
||||||
return f"The {item} has nothing to read."
|
return f"The {item} has nothing to read."
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
from adventure.context import action_context, broadcast, get_dungeon_master
|
from adventure.context import action_context, broadcast, get_dungeon_master
|
||||||
from adventure.utils.search import find_actor_in_room
|
from adventure.utils.search import find_character_in_room
|
||||||
|
|
||||||
|
|
||||||
def action_cast(spell: str, target: str) -> str:
|
def action_cast(spell: str, target: str) -> str:
|
||||||
|
@ -12,25 +12,30 @@ def action_cast(spell: str, target: str) -> str:
|
||||||
spell: The name of the spell to cast.
|
spell: The name of the spell to cast.
|
||||||
target: The target of the spell.
|
target: The target of the spell.
|
||||||
"""
|
"""
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
target_actor = find_actor_in_room(action_room, target)
|
target_character = find_character_in_room(action_room, target)
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
# Check for spell availability and mana costs
|
# Check for spell availability and mana costs
|
||||||
if spell not in action_actor.attributes["spells"]:
|
if spell not in action_character.attributes["spells"]:
|
||||||
return f"You do not know the spell '{spell}'."
|
return f"You do not know the spell '{spell}'."
|
||||||
if action_actor.attributes["mana"] < action_actor.attributes["spells"][spell]:
|
if (
|
||||||
|
action_character.attributes["mana"]
|
||||||
|
< action_character.attributes["spells"][spell]
|
||||||
|
):
|
||||||
return "You do not have enough mana to cast this spell."
|
return "You do not have enough mana to cast this spell."
|
||||||
|
|
||||||
action_actor.attributes["mana"] -= action_actor.attributes["spells"][spell]
|
action_character.attributes["mana"] -= action_character.attributes["spells"][
|
||||||
|
spell
|
||||||
|
]
|
||||||
# Get flavor text from the dungeon master
|
# Get flavor text from the dungeon master
|
||||||
flavor_text = dungeon_master(f"Describe the effects of {spell} on {target}.")
|
flavor_text = dungeon_master(f"Describe the effects of {spell} on {target}.")
|
||||||
broadcast(f"{action_actor.name} casts {spell} on {target}. {flavor_text}")
|
broadcast(f"{action_character.name} casts {spell} on {target}. {flavor_text}")
|
||||||
|
|
||||||
# Apply effects based on the spell
|
# Apply effects based on the spell
|
||||||
if spell == "heal" and target_actor:
|
if spell == "heal" and target_character:
|
||||||
heal_amount = randint(10, 30)
|
heal_amount = randint(10, 30)
|
||||||
target_actor.attributes["health"] += heal_amount
|
target_character.attributes["health"] += heal_amount
|
||||||
return f"{target} is healed for {heal_amount} points."
|
return f"{target} is healed for {heal_amount} points."
|
||||||
|
|
||||||
return f"{spell} was successfully cast on {target}."
|
return f"{spell} was successfully cast on {target}."
|
||||||
|
|
|
@ -11,7 +11,7 @@ def action_climb(target: str) -> str:
|
||||||
Args:
|
Args:
|
||||||
target: The object or feature to climb.
|
target: The object or feature to climb.
|
||||||
"""
|
"""
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
# Assume 'climbable' is an attribute that marks climbable targets
|
# Assume 'climbable' is an attribute that marks climbable targets
|
||||||
climbable_feature = find_item_in_room(action_room, target)
|
climbable_feature = find_item_in_room(action_room, target)
|
||||||
|
@ -22,16 +22,16 @@ def action_climb(target: str) -> str:
|
||||||
|
|
||||||
# Get flavor text for the climb attempt
|
# Get flavor text for the climb attempt
|
||||||
flavor_text = dungeon_master(
|
flavor_text = dungeon_master(
|
||||||
f"Describe {action_actor.name}'s attempt to climb {target}."
|
f"Describe {action_character.name}'s attempt to climb {target}."
|
||||||
)
|
)
|
||||||
if climb_roll > climb_difficulty:
|
if climb_roll > climb_difficulty:
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} successfully climbs the {target}. {flavor_text}"
|
f"{action_character.name} successfully climbs the {target}. {flavor_text}"
|
||||||
)
|
)
|
||||||
return f"You successfully climb the {target}."
|
return f"You successfully climb the {target}."
|
||||||
else:
|
else:
|
||||||
broadcast(
|
broadcast(
|
||||||
f"{action_actor.name} fails to climb the {target}. {flavor_text}"
|
f"{action_character.name} fails to climb the {target}. {flavor_text}"
|
||||||
)
|
)
|
||||||
return f"You fail to climb the {target}."
|
return f"You fail to climb the {target}."
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from adventure.context import (
|
from adventure.context import (
|
||||||
action_context,
|
action_context,
|
||||||
broadcast,
|
broadcast,
|
||||||
get_agent_for_actor,
|
get_agent_for_character,
|
||||||
get_dungeon_master,
|
get_dungeon_master,
|
||||||
)
|
)
|
||||||
from adventure.utils.search import find_actor_in_room, find_item_in_room
|
from adventure.utils.search import find_character_in_room, find_item_in_room
|
||||||
from adventure.utils.world import describe_entity
|
from adventure.utils.world import describe_entity
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,30 +16,32 @@ def action_attack(target: str) -> str:
|
||||||
target: The name of the character or item to attack.
|
target: The name of the character or item to attack.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
# make sure the target is in the room
|
# make sure the target is in the room
|
||||||
target_actor = find_actor_in_room(action_room, target)
|
target_character = find_character_in_room(action_room, target)
|
||||||
target_item = find_item_in_room(action_room, target)
|
target_item = find_item_in_room(action_room, target)
|
||||||
|
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
if target_actor:
|
if target_character:
|
||||||
target_agent = get_agent_for_actor(target_actor)
|
target_agent = get_agent_for_character(target_character)
|
||||||
if not target_agent:
|
if not target_agent:
|
||||||
raise ValueError(f"no agent found for actor {target_actor.name}")
|
raise ValueError(
|
||||||
|
f"no agent found for character {target_character.name}"
|
||||||
|
)
|
||||||
|
|
||||||
reaction = target_agent(
|
reaction = target_agent(
|
||||||
f"{action_actor.name} is attacking you in the {action_room.name}. How do you react?"
|
f"{action_character.name} is attacking you in the {action_room.name}. How do you react?"
|
||||||
"Respond with 'fighting', 'fleeing', or 'surrendering'."
|
"Respond with 'fighting', 'fleeing', or 'surrendering'."
|
||||||
)
|
)
|
||||||
|
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} attacks {target} in the {action_room.name}. {describe_entity(action_room)}."
|
f"{action_character.name} attacks {target} in the {action_room.name}. {describe_entity(action_room)}."
|
||||||
f"{describe_entity(action_actor)}. {describe_entity(target_actor)}."
|
f"{describe_entity(action_character)}. {describe_entity(target_character)}."
|
||||||
f"{target} reacts by {reaction}. What is the outcome of the attack? Describe the result in detail."
|
f"{target} reacts by {reaction}. What is the outcome of the attack? Describe the result in detail."
|
||||||
)
|
)
|
||||||
|
|
||||||
description = (
|
description = (
|
||||||
f"{action_actor.name} attacks the {target} in the {action_room.name}."
|
f"{action_character.name} attacks the {target} in the {action_room.name}."
|
||||||
f"{target} reacts by {reaction}. {outcome}"
|
f"{target} reacts by {reaction}. {outcome}"
|
||||||
)
|
)
|
||||||
broadcast(description)
|
broadcast(description)
|
||||||
|
@ -47,12 +49,12 @@ def action_attack(target: str) -> str:
|
||||||
|
|
||||||
if target_item:
|
if target_item:
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} attacks {target} in the {action_room.name}. {describe_entity(action_room)}."
|
f"{action_character.name} attacks {target} in the {action_room.name}. {describe_entity(action_room)}."
|
||||||
f"{describe_entity(action_actor)}. {describe_entity(target_item)}."
|
f"{describe_entity(action_character)}. {describe_entity(target_item)}."
|
||||||
f"What is the outcome of the attack? Describe the result in detail."
|
f"What is the outcome of the attack? Describe the result in detail."
|
||||||
)
|
)
|
||||||
|
|
||||||
description = f"{action_actor.name} attacks the {target} in the {action_room.name}. {outcome}"
|
description = f"{action_character.name} attacks the {target} in the {action_room.name}. {outcome}"
|
||||||
broadcast(description)
|
broadcast(description)
|
||||||
return description
|
return description
|
||||||
|
|
||||||
|
@ -68,21 +70,21 @@ def action_cast(target: str, spell: str) -> str:
|
||||||
spell: The name of the spell to cast.
|
spell: The name of the spell to cast.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
# make sure the target is in the room
|
# make sure the target is in the room
|
||||||
target_actor = find_actor_in_room(action_room, target)
|
target_character = find_character_in_room(action_room, target)
|
||||||
target_item = find_item_in_room(action_room, target)
|
target_item = find_item_in_room(action_room, target)
|
||||||
|
|
||||||
if not target_actor and not target_item:
|
if not target_character and not target_item:
|
||||||
return f"{target} is not in the {action_room.name}."
|
return f"{target} is not in the {action_room.name}."
|
||||||
|
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} casts {spell} on {target} in the {action_room.name}. {describe_entity(action_room)}."
|
f"{action_character.name} casts {spell} on {target} in the {action_room.name}. {describe_entity(action_room)}."
|
||||||
f"{describe_entity(action_actor)}. {describe_entity(target_actor) if target_actor else describe_entity(target_item)}."
|
f"{describe_entity(action_character)}. {describe_entity(target_character) if target_character else describe_entity(target_item)}."
|
||||||
f"What is the outcome of the spell? Describe the result in detail."
|
f"What is the outcome of the spell? Describe the result in detail."
|
||||||
)
|
)
|
||||||
|
|
||||||
description = f"{action_actor.name} casts {spell} on the {target} in the {action_room.name}. {outcome}"
|
description = f"{action_character.name} casts {spell} on the {target} in the {action_room.name}. {outcome}"
|
||||||
broadcast(description)
|
broadcast(description)
|
||||||
return description
|
return description
|
||||||
|
|
|
@ -2,7 +2,7 @@ rules:
|
||||||
# wet/dry logic
|
# wet/dry logic
|
||||||
- group: environment-moisture
|
- group: environment-moisture
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
wet: true
|
wet: true
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -10,7 +10,7 @@ rules:
|
||||||
|
|
||||||
- group: environment-moisture
|
- group: environment-moisture
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
wet: true
|
wet: true
|
||||||
temperature: hot
|
temperature: hot
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
|
@ -33,7 +33,7 @@ rules:
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
wet: true
|
wet: true
|
||||||
backstory: You are soaking wet.
|
backstory: You are soaking wet.
|
||||||
description: They are soaking wet and dripping water.
|
description: They are soaking wet and dripping water.
|
||||||
|
|
|
@ -3,21 +3,21 @@ from adventure.models.entity import Attributes, Room
|
||||||
|
|
||||||
def hot_room(room: Room, attributes: Attributes):
|
def hot_room(room: Room, attributes: Attributes):
|
||||||
"""
|
"""
|
||||||
If the room is hot, actors should get hotter.
|
If the room is hot, characters should get hotter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
actor.attributes["hot"] = "hot"
|
character.attributes["hot"] = "hot"
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
|
||||||
def cold_room(room: Room, attributes: Attributes):
|
def cold_room(room: Room, attributes: Attributes):
|
||||||
"""
|
"""
|
||||||
If the room is cold, actors should get colder.
|
If the room is cold, characters should get colder.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
actor.attributes["cold"] = "cold"
|
character.attributes["cold"] = "cold"
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from adventure.context import action_context
|
from adventure.context import action_context
|
||||||
from adventure.utils.search import find_item_in_actor
|
from adventure.utils.search import find_item_in_character
|
||||||
|
|
||||||
|
|
||||||
def action_cook(item: str) -> str:
|
def action_cook(item: str) -> str:
|
||||||
|
@ -9,8 +9,8 @@ def action_cook(item: str) -> str:
|
||||||
Args:
|
Args:
|
||||||
item: The name of the item to cook.
|
item: The name of the item to cook.
|
||||||
"""
|
"""
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
target_item = find_item_in_actor(action_actor, item)
|
target_item = find_item_in_character(action_character, item)
|
||||||
if target_item is None:
|
if target_item is None:
|
||||||
return "You don't have the item to cook."
|
return "You don't have the item to cook."
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ def action_eat(item: str) -> str:
|
||||||
Args:
|
Args:
|
||||||
item: The name of the item to eat.
|
item: The name of the item to eat.
|
||||||
"""
|
"""
|
||||||
with action_context() as (_, action_actor):
|
with action_context() as (_, action_character):
|
||||||
target_item = find_item_in_actor(action_actor, item)
|
target_item = find_item_in_character(action_character, item)
|
||||||
if target_item is None:
|
if target_item is None:
|
||||||
return "You don't have the item to eat."
|
return "You don't have the item to eat."
|
||||||
|
|
||||||
|
@ -56,12 +56,12 @@ def action_eat(item: str) -> str:
|
||||||
if spoiled:
|
if spoiled:
|
||||||
return "You can't eat that item, it is rotten."
|
return "You can't eat that item, it is rotten."
|
||||||
|
|
||||||
# Check if the actor is hungry
|
# Check if the character is hungry
|
||||||
hunger = action_actor.attributes.get("hunger", None)
|
hunger = action_character.attributes.get("hunger", None)
|
||||||
if hunger != "hungry":
|
if hunger != "hungry":
|
||||||
return "You're not hungry."
|
return "You're not hungry."
|
||||||
|
|
||||||
# Eat the item
|
# Eat the item
|
||||||
action_actor.items.remove(target_item)
|
action_character.items.remove(target_item)
|
||||||
action_actor.attributes["hunger"] = "full"
|
action_character.attributes["hunger"] = "full"
|
||||||
return f"You eat the {item}."
|
return f"You eat the {item}."
|
||||||
|
|
|
@ -21,7 +21,7 @@ rules:
|
||||||
# hunger logic
|
# hunger logic
|
||||||
- group: hunger
|
- group: hunger
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
hunger: full
|
hunger: full
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -37,7 +37,7 @@ rules:
|
||||||
# thirst logic
|
# thirst logic
|
||||||
- group: thirst
|
- group: thirst
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
thirst: hydrated
|
thirst: hydrated
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -77,27 +77,27 @@ labels:
|
||||||
backstory: You are rotten and inedible.
|
backstory: You are rotten and inedible.
|
||||||
description: This item is rotten and inedible.
|
description: This item is rotten and inedible.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
spoiled: false
|
spoiled: false
|
||||||
backstory: You are fresh and edible.
|
backstory: You are fresh and edible.
|
||||||
description: This item is fresh and edible.
|
description: This item is fresh and edible.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hunger: full
|
hunger: full
|
||||||
backstory: You are have eaten recently and are full.
|
backstory: You are have eaten recently and are full.
|
||||||
description: ~
|
description: ~
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hunger: hungry
|
hunger: hungry
|
||||||
backstory: You are hungry and need to eat.
|
backstory: You are hungry and need to eat.
|
||||||
description: They look hungry.
|
description: They look hungry.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
thirst: hydrated
|
thirst: hydrated
|
||||||
backstory: You are hydrated.
|
backstory: You are hydrated.
|
||||||
description: ~
|
description: ~
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
thirst: thirsty
|
thirst: thirsty
|
||||||
backstory: You are thirsty and need to drink.
|
backstory: You are thirsty and need to drink.
|
||||||
description: They look thirsty.
|
description: They look thirsty.
|
|
@ -7,15 +7,15 @@ def action_wash(unused: bool) -> str:
|
||||||
Wash yourself.
|
Wash yourself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
hygiene = action_actor.attributes.get("hygiene", "clean")
|
hygiene = action_character.attributes.get("hygiene", "clean")
|
||||||
|
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} washes themselves in the {action_room.name}. {describe_entity(action_room)}. {describe_entity(action_actor)}"
|
f"{action_character.name} washes themselves in the {action_room.name}. {describe_entity(action_room)}. {describe_entity(action_character)}"
|
||||||
f"{action_actor.name} was {hygiene} to start with. How clean are they after washing? Respond with 'clean' or 'dirty'."
|
f"{action_character.name} was {hygiene} to start with. How clean are they after washing? Respond with 'clean' or 'dirty'."
|
||||||
"If the room has a shower or running water, they should be cleaner. If the room is dirty, they should end up dirtier."
|
"If the room has a shower or running water, they should be cleaner. If the room is dirty, they should end up dirtier."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_actor.attributes["clean"] = outcome.strip().lower()
|
action_character.attributes["clean"] = outcome.strip().lower()
|
||||||
return f"You wash yourself in the {action_room.name} and feel {outcome}"
|
return f"You wash yourself in the {action_room.name} and feel {outcome}"
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
rules:
|
rules:
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hygiene: clean
|
hygiene: clean
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
hygiene: dirty
|
hygiene: dirty
|
||||||
|
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hygiene: dirty
|
hygiene: dirty
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -21,17 +21,17 @@ rules:
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hygiene: clean
|
hygiene: clean
|
||||||
backstory: You are clean and smell fresh.
|
backstory: You are clean and smell fresh.
|
||||||
description: They look freshly washed and smell clean.
|
description: They look freshly washed and smell clean.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hygiene: dirty
|
hygiene: dirty
|
||||||
backstory: You are dirty and smell bad.
|
backstory: You are dirty and smell bad.
|
||||||
description: They look dirty and smell bad.
|
description: They look dirty and smell bad.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
hygiene: filthy
|
hygiene: filthy
|
||||||
backstory: You are filthy and smell terrible.
|
backstory: You are filthy and smell terrible.
|
||||||
description: They look filthy and smell terrible.
|
description: They look filthy and smell terrible.
|
||||||
|
|
|
@ -2,7 +2,7 @@ rules:
|
||||||
# mood logic
|
# mood logic
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: happy
|
mood: happy
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -10,7 +10,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: happy
|
mood: happy
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -18,7 +18,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: angry
|
mood: angry
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -26,7 +26,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: neutral
|
mood: neutral
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -34,7 +34,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: neutral
|
mood: neutral
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -42,7 +42,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: sad
|
mood: sad
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -50,7 +50,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: sad
|
mood: sad
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -59,7 +59,7 @@ rules:
|
||||||
# mood interactions with other systems
|
# mood interactions with other systems
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: sad
|
mood: sad
|
||||||
sleep: rested
|
sleep: rested
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
|
@ -68,7 +68,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
hunger: hungry
|
hunger: hungry
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
set:
|
set:
|
||||||
|
@ -76,7 +76,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: angry
|
mood: angry
|
||||||
hunger: full
|
hunger: full
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
|
@ -85,7 +85,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: neutral
|
mood: neutral
|
||||||
hunger: full
|
hunger: full
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
|
@ -94,7 +94,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: happy
|
mood: happy
|
||||||
hunger: hungry
|
hunger: hungry
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
|
@ -103,7 +103,7 @@ rules:
|
||||||
|
|
||||||
- group: mood
|
- group: mood
|
||||||
match:
|
match:
|
||||||
type: actor
|
type: character
|
||||||
mood: neutral
|
mood: neutral
|
||||||
sleep: tired
|
sleep: tired
|
||||||
chance: 0.2
|
chance: 0.2
|
||||||
|
@ -119,17 +119,17 @@ rules:
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
mood: happy
|
mood: happy
|
||||||
backstory: You are feeling happy.
|
backstory: You are feeling happy.
|
||||||
description: They look happy.
|
description: They look happy.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
mood: sad
|
mood: sad
|
||||||
backstory: You are feeling sad.
|
backstory: You are feeling sad.
|
||||||
description: They look sad.
|
description: They look sad.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
mood: angry
|
mood: angry
|
||||||
backstory: You are feeling angry.
|
backstory: You are feeling angry.
|
||||||
description: They look angry.
|
description: They look angry.
|
||||||
|
|
|
@ -7,12 +7,12 @@ def action_sleep(unused: bool) -> str:
|
||||||
Sleep until you are rested.
|
Sleep until you are rested.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with action_context() as (action_room, action_actor):
|
with action_context() as (action_room, action_character):
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} sleeps in the {action_room.name}. {describe_entity(action_room)}. {describe_entity(action_actor)}"
|
f"{action_character.name} sleeps in the {action_room.name}. {describe_entity(action_room)}. {describe_entity(action_character)}"
|
||||||
"How rested are they? Respond with 'rested' or 'tired'."
|
"How rested are they? Respond with 'rested' or 'tired'."
|
||||||
)
|
)
|
||||||
|
|
||||||
action_actor.attributes["rested"] = outcome
|
action_character.attributes["rested"] = outcome
|
||||||
return f"You sleep in the {action_room.name} and wake up feeling {outcome}"
|
return f"You sleep in the {action_room.name} and wake up feeling {outcome}"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
rules:
|
rules:
|
||||||
# sleeping logic
|
# sleeping logic
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
sleep: rested
|
sleep: rested
|
||||||
chance: 0.1
|
chance: 0.1
|
||||||
set:
|
set:
|
||||||
|
@ -15,12 +15,12 @@ rules:
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
sleep: rested
|
sleep: rested
|
||||||
backstory: You are well-rested.
|
backstory: You are well-rested.
|
||||||
description: They look well-rested.
|
description: They look well-rested.
|
||||||
- match:
|
- match:
|
||||||
type: actor
|
type: character
|
||||||
sleep: tired
|
sleep: tired
|
||||||
backstory: You are tired.
|
backstory: You are tired.
|
||||||
description: They look tired.
|
description: They look tired.
|
||||||
|
|
|
@ -10,7 +10,7 @@ from packit.utils import could_be_json
|
||||||
|
|
||||||
from adventure.context import broadcast
|
from adventure.context import broadcast
|
||||||
from adventure.models.config import DEFAULT_CONFIG
|
from adventure.models.config import DEFAULT_CONFIG
|
||||||
from adventure.models.entity import Actor, Room
|
from adventure.models.entity import Character, Room
|
||||||
from adventure.models.event import ReplyEvent
|
from adventure.models.event import ReplyEvent
|
||||||
|
|
||||||
from .string import normalize_name
|
from .string import normalize_name
|
||||||
|
@ -18,7 +18,7 @@ from .string import normalize_name
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
actor_config = DEFAULT_CONFIG.world.actor
|
character_config = DEFAULT_CONFIG.world.character
|
||||||
|
|
||||||
|
|
||||||
def make_keyword_condition(end_message: str, keywords=["end", "stop"]):
|
def make_keyword_condition(end_message: str, keywords=["end", "stop"]):
|
||||||
|
@ -85,19 +85,23 @@ def or_list(items: List[str]) -> str:
|
||||||
return f"{', '.join(items[:-1])}, or {items[-1]}"
|
return f"{', '.join(items[:-1])}, or {items[-1]}"
|
||||||
|
|
||||||
|
|
||||||
def summarize_room(room: Room, player: Actor) -> str:
|
def summarize_room(room: Room, player: Character) -> str:
|
||||||
"""
|
"""
|
||||||
Summarize a room for the player.
|
Summarize a room for the player.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
actor_names = and_list(
|
character_names = and_list(
|
||||||
[actor.name for actor in room.actors if actor.name != player.name]
|
[
|
||||||
|
character.name
|
||||||
|
for character in room.characters
|
||||||
|
if character.name != player.name
|
||||||
|
]
|
||||||
)
|
)
|
||||||
item_names = and_list([item.name for item in room.items])
|
item_names = and_list([item.name for item in room.items])
|
||||||
inventory_names = and_list([item.name for item in player.items])
|
inventory_names = and_list([item.name for item in player.items])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f"You are in the {room.name} room with {actor_names}. "
|
f"You are in the {room.name} room with {character_names}. "
|
||||||
f"You see the {item_names} around the room. "
|
f"You see the {item_names} around the room. "
|
||||||
f"You are carrying the {inventory_names}."
|
f"You are carrying the {inventory_names}."
|
||||||
)
|
)
|
||||||
|
@ -105,9 +109,9 @@ def summarize_room(room: Room, player: Actor) -> str:
|
||||||
|
|
||||||
def loop_conversation(
|
def loop_conversation(
|
||||||
room: Room,
|
room: Room,
|
||||||
actors: List[Actor],
|
characters: List[Character],
|
||||||
agents: List[Agent],
|
agents: List[Agent],
|
||||||
first_actor: Actor,
|
first_character: Character,
|
||||||
first_prompt: str,
|
first_prompt: str,
|
||||||
reply_prompt: str,
|
reply_prompt: str,
|
||||||
first_message: str,
|
first_message: str,
|
||||||
|
@ -117,14 +121,14 @@ def loop_conversation(
|
||||||
max_length: int | None = None,
|
max_length: int | None = None,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Loop through a conversation between a series of agents, using metadata from their actors.
|
Loop through a conversation between a series of agents, using metadata from their characters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if max_length is None:
|
if max_length is None:
|
||||||
max_length = actor_config.conversation_limit
|
max_length = character_config.conversation_limit
|
||||||
|
|
||||||
if len(actors) != len(agents):
|
if len(characters) != len(agents):
|
||||||
raise ValueError("The number of actors and agents must match.")
|
raise ValueError("The number of characters and agents must match.")
|
||||||
|
|
||||||
# set up the keyword or length-limit compound condition
|
# set up the keyword or length-limit compound condition
|
||||||
_, condition_end, parse_end = make_keyword_condition(end_message)
|
_, condition_end, parse_end = make_keyword_condition(end_message)
|
||||||
|
@ -145,34 +149,36 @@ def loop_conversation(
|
||||||
|
|
||||||
# prepare the loop state
|
# prepare the loop state
|
||||||
i = 0
|
i = 0
|
||||||
last_actor = first_actor
|
last_character = first_character
|
||||||
response = first_message
|
response = first_message
|
||||||
|
|
||||||
while not stop_condition(current=i):
|
while not stop_condition(current=i):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
logger.debug(f"starting conversation with {first_actor.name}")
|
logger.debug(f"starting conversation with {first_character.name}")
|
||||||
prompt = first_prompt
|
prompt = first_prompt
|
||||||
else:
|
else:
|
||||||
logger.debug(f"continuing conversation with {last_actor.name} on step {i}")
|
logger.debug(
|
||||||
|
f"continuing conversation with {last_character.name} on step {i}"
|
||||||
|
)
|
||||||
prompt = reply_prompt
|
prompt = reply_prompt
|
||||||
|
|
||||||
# loop through the actors and agents
|
# loop through the characters and agents
|
||||||
actor = actors[i % len(actors)]
|
character = characters[i % len(characters)]
|
||||||
agent = agents[i % len(agents)]
|
agent = agents[i % len(agents)]
|
||||||
|
|
||||||
# summarize the room and present the last response
|
# summarize the room and present the last response
|
||||||
summary = summarize_room(room, actor)
|
summary = summarize_room(room, character)
|
||||||
response = agent(
|
response = agent(
|
||||||
prompt, response=response, summary=summary, last_actor=last_actor
|
prompt, response=response, summary=summary, last_character=last_character
|
||||||
)
|
)
|
||||||
response = result_parser(response)
|
response = result_parser(response)
|
||||||
|
|
||||||
logger.info(f"{actor.name} responds: {response}")
|
logger.info(f"{character.name} responds: {response}")
|
||||||
reply_event = ReplyEvent.from_text(response, room, actor)
|
reply_event = ReplyEvent.from_text(response, room, character)
|
||||||
broadcast(reply_event)
|
broadcast(reply_event)
|
||||||
|
|
||||||
# increment the step counter
|
# increment the step counter
|
||||||
i += 1
|
i += 1
|
||||||
last_actor = actor
|
last_character = character
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -13,7 +13,7 @@ from adventure.models.effect import (
|
||||||
StringEffectPattern,
|
StringEffectPattern,
|
||||||
StringEffectResult,
|
StringEffectResult,
|
||||||
)
|
)
|
||||||
from adventure.models.entity import Actor, Attributes
|
from adventure.models.entity import Attributes, Character
|
||||||
from adventure.utils.attribute import (
|
from adventure.utils.attribute import (
|
||||||
add_value,
|
add_value,
|
||||||
append_value,
|
append_value,
|
||||||
|
@ -252,9 +252,9 @@ def apply_permanent_effects(
|
||||||
return apply_permanent_results(attributes, results)
|
return apply_permanent_results(attributes, results)
|
||||||
|
|
||||||
|
|
||||||
def apply_effects(target: Actor, effects: List[EffectPattern]) -> None:
|
def apply_effects(target: Character, effects: List[EffectPattern]) -> None:
|
||||||
"""
|
"""
|
||||||
Apply a set of effects to an actor and their attributes.
|
Apply a set of effects to a character and their attributes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permanent_effects = [
|
permanent_effects = [
|
||||||
|
@ -270,9 +270,9 @@ def apply_effects(target: Actor, effects: List[EffectPattern]) -> None:
|
||||||
target.active_effects.extend(temporary_effects)
|
target.active_effects.extend(temporary_effects)
|
||||||
|
|
||||||
|
|
||||||
def expire_effects(target: Actor) -> None:
|
def expire_effects(target: Character) -> None:
|
||||||
"""
|
"""
|
||||||
Decrement the duration of effects on an actor and remove any that have expired.
|
Decrement the duration of effects on a character and remove any that have expired.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for effect in target.active_effects:
|
for effect in target.active_effects:
|
||||||
|
|
|
@ -1,34 +1,36 @@
|
||||||
from adventure.models.entity import Actor
|
from adventure.models.entity import Character
|
||||||
|
|
||||||
|
|
||||||
def expire_events(actor: Actor, current_turn: int):
|
def expire_events(character: Character, current_turn: int):
|
||||||
"""
|
"""
|
||||||
Expire events that have already happened.
|
Expire events that have already happened.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
events = actor.planner.calendar.events
|
events = character.planner.calendar.events
|
||||||
expired_events = [event for event in events if event.turn < current_turn]
|
expired_events = [event for event in events if event.turn < current_turn]
|
||||||
actor.planner.calendar.events[:] = [
|
character.planner.calendar.events[:] = [
|
||||||
event for event in events if event not in expired_events
|
event for event in events if event not in expired_events
|
||||||
]
|
]
|
||||||
|
|
||||||
return expired_events
|
return expired_events
|
||||||
|
|
||||||
|
|
||||||
def get_recent_notes(actor: Actor, count: int = 3):
|
def get_recent_notes(character: Character, count: int = 3):
|
||||||
"""
|
"""
|
||||||
Get the most recent facts from your notes.
|
Get the most recent facts from your notes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return actor.planner.notes[-count:]
|
return character.planner.notes[-count:]
|
||||||
|
|
||||||
|
|
||||||
def get_upcoming_events(actor: Actor, current_turn: int, upcoming_turns: int = 3):
|
def get_upcoming_events(
|
||||||
|
character: Character, current_turn: int, upcoming_turns: int = 3
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get a list of upcoming events within a certain number of turns.
|
Get a list of upcoming events within a certain number of turns.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
calendar = actor.planner.calendar
|
calendar = character.planner.calendar
|
||||||
# TODO: sort events by turn
|
# TODO: sort events by turn
|
||||||
return [
|
return [
|
||||||
event
|
event
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Any, Generator
|
from typing import Any, Generator
|
||||||
|
|
||||||
from adventure.models.entity import (
|
from adventure.models.entity import (
|
||||||
Actor,
|
Character,
|
||||||
EntityReference,
|
EntityReference,
|
||||||
Item,
|
Item,
|
||||||
Portal,
|
Portal,
|
||||||
|
@ -30,19 +30,19 @@ def find_portal(world: World, portal_name: str) -> Portal | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_actor(world: World, actor_name: str) -> Actor | None:
|
def find_character(world: World, character_name: str) -> Character | None:
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
actor = find_actor_in_room(room, actor_name)
|
character = find_character_in_room(room, character_name)
|
||||||
if actor:
|
if character:
|
||||||
return actor
|
return character
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_actor_in_room(room: Room, actor_name: str) -> Actor | None:
|
def find_character_in_room(room: Room, character_name: str) -> Character | None:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
if normalize_name(actor.name) == normalize_name(actor_name):
|
if normalize_name(character.name) == normalize_name(character_name):
|
||||||
return actor
|
return character
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -51,12 +51,12 @@ def find_actor_in_room(room: Room, actor_name: str) -> Actor | None:
|
||||||
def find_item(
|
def find_item(
|
||||||
world: World,
|
world: World,
|
||||||
item_name: str,
|
item_name: str,
|
||||||
include_actor_inventory=False,
|
include_character_inventory=False,
|
||||||
include_item_inventory=False,
|
include_item_inventory=False,
|
||||||
) -> Item | None:
|
) -> Item | None:
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
item = find_item_in_room(
|
item = find_item_in_room(
|
||||||
room, item_name, include_actor_inventory, include_item_inventory
|
room, item_name, include_character_inventory, include_item_inventory
|
||||||
)
|
)
|
||||||
if item:
|
if item:
|
||||||
return item
|
return item
|
||||||
|
@ -64,14 +64,14 @@ def find_item(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_item_in_actor(
|
def find_item_in_character(
|
||||||
actor: Actor, item_name: str, include_item_inventory=False
|
character: Character, item_name: str, include_item_inventory=False
|
||||||
) -> Item | None:
|
) -> Item | None:
|
||||||
return find_item_in_container(actor, item_name, include_item_inventory)
|
return find_item_in_container(character, item_name, include_item_inventory)
|
||||||
|
|
||||||
|
|
||||||
def find_item_in_container(
|
def find_item_in_container(
|
||||||
container: Actor | Item, item_name: str, include_item_inventory=False
|
container: Character | Item, item_name: str, include_item_inventory=False
|
||||||
) -> Item | None:
|
) -> Item | None:
|
||||||
for item in container.items:
|
for item in container.items:
|
||||||
if normalize_name(item.name) == normalize_name(item_name):
|
if normalize_name(item.name) == normalize_name(item_name):
|
||||||
|
@ -88,7 +88,7 @@ def find_item_in_container(
|
||||||
def find_item_in_room(
|
def find_item_in_room(
|
||||||
room: Room,
|
room: Room,
|
||||||
item_name: str,
|
item_name: str,
|
||||||
include_actor_inventory=False,
|
include_character_inventory=False,
|
||||||
include_item_inventory=False,
|
include_item_inventory=False,
|
||||||
) -> Item | None:
|
) -> Item | None:
|
||||||
for item in room.items:
|
for item in room.items:
|
||||||
|
@ -100,30 +100,30 @@ def find_item_in_room(
|
||||||
if item:
|
if item:
|
||||||
return item
|
return item
|
||||||
|
|
||||||
if include_actor_inventory:
|
if include_character_inventory:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
item = find_item_in_actor(actor, item_name, include_item_inventory)
|
item = find_item_in_character(character, item_name, include_item_inventory)
|
||||||
if item:
|
if item:
|
||||||
return item
|
return item
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_room_with_actor(world: World, actor: Actor) -> Room | None:
|
def find_room_with_character(world: World, character: Character) -> Room | None:
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
for room_actor in room.actors:
|
for room_character in room.characters:
|
||||||
if normalize_name(actor.name) == normalize_name(room_actor.name):
|
if normalize_name(character.name) == normalize_name(room_character.name):
|
||||||
return room
|
return room
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_containing_room(world: World, entity: Room | Actor | 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
|
||||||
|
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
if entity in room.actors or entity in room.items:
|
if entity in room.characters or entity in room.items:
|
||||||
return room
|
return room
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -139,8 +139,8 @@ def find_entity_reference(
|
||||||
if reference.room:
|
if reference.room:
|
||||||
return find_room(world, reference.room)
|
return find_room(world, reference.room)
|
||||||
|
|
||||||
if reference.actor:
|
if reference.character:
|
||||||
return find_actor(world, reference.actor)
|
return find_character(world, reference.character)
|
||||||
|
|
||||||
if reference.item:
|
if reference.item:
|
||||||
return find_item(world, reference.item)
|
return find_item(world, reference.item)
|
||||||
|
@ -162,14 +162,14 @@ def list_portals(world: World) -> Generator[Portal, Any, None]:
|
||||||
yield portal
|
yield portal
|
||||||
|
|
||||||
|
|
||||||
def list_actors(world: World) -> Generator[Actor, Any, None]:
|
def list_characters(world: World) -> Generator[Character, Any, None]:
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
yield actor
|
yield character
|
||||||
|
|
||||||
|
|
||||||
def list_items(
|
def list_items(
|
||||||
world: World, include_actor_inventory=True, include_item_inventory=True
|
world: World, include_character_inventory=True, include_item_inventory=True
|
||||||
) -> Generator[Item, Any, None]:
|
) -> Generator[Item, Any, None]:
|
||||||
|
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
|
@ -179,21 +179,21 @@ def list_items(
|
||||||
if include_item_inventory:
|
if include_item_inventory:
|
||||||
yield from list_items_in_container(item)
|
yield from list_items_in_container(item)
|
||||||
|
|
||||||
if include_actor_inventory:
|
if include_character_inventory:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
for item in actor.items:
|
for item in character.items:
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
|
|
||||||
def list_actors_in_room(room: Room) -> Generator[Actor, Any, None]:
|
def list_characters_in_room(room: Room) -> Generator[Character, Any, None]:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
yield actor
|
yield character
|
||||||
|
|
||||||
|
|
||||||
def list_items_in_actor(
|
def list_items_in_character(
|
||||||
actor: Actor, include_item_inventory=True
|
character: Character, include_item_inventory=True
|
||||||
) -> Generator[Item, Any, None]:
|
) -> Generator[Item, Any, None]:
|
||||||
for item in actor.items:
|
for item in character.items:
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
if include_item_inventory:
|
if include_item_inventory:
|
||||||
|
@ -212,7 +212,7 @@ def list_items_in_container(
|
||||||
|
|
||||||
def list_items_in_room(
|
def list_items_in_room(
|
||||||
room: Room,
|
room: Room,
|
||||||
include_actor_inventory=True,
|
include_character_inventory=True,
|
||||||
include_item_inventory=True,
|
include_item_inventory=True,
|
||||||
) -> Generator[Item, Any, None]:
|
) -> Generator[Item, Any, None]:
|
||||||
for item in room.items:
|
for item in room.items:
|
||||||
|
@ -221,7 +221,7 @@ def list_items_in_room(
|
||||||
if include_item_inventory:
|
if include_item_inventory:
|
||||||
yield from list_items_in_container(item)
|
yield from list_items_in_container(item)
|
||||||
|
|
||||||
if include_actor_inventory:
|
if include_character_inventory:
|
||||||
for actor in room.actors:
|
for character in room.characters:
|
||||||
for item in actor.items:
|
for item in character.items:
|
||||||
yield item
|
yield item
|
||||||
|
|
|
@ -2,25 +2,26 @@ from logging import getLogger
|
||||||
|
|
||||||
from adventure.context import get_game_systems
|
from adventure.context import get_game_systems
|
||||||
from adventure.game_system import FormatPerspective
|
from adventure.game_system import FormatPerspective
|
||||||
from adventure.models.entity import Actor, WorldEntity
|
from adventure.models.entity import Character, WorldEntity
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def describe_actor(
|
def describe_character(
|
||||||
actor: Actor, perspective: FormatPerspective = FormatPerspective.SECOND_PERSON
|
character: Character,
|
||||||
|
perspective: FormatPerspective = FormatPerspective.SECOND_PERSON,
|
||||||
) -> str:
|
) -> str:
|
||||||
attribute_descriptions = format_attributes(actor, perspective=perspective)
|
attribute_descriptions = format_attributes(character, perspective=perspective)
|
||||||
logger.info("describing actor: %s, %s", actor, attribute_descriptions)
|
logger.info("describing character: %s, %s", character, attribute_descriptions)
|
||||||
|
|
||||||
if perspective == FormatPerspective.SECOND_PERSON:
|
if perspective == FormatPerspective.SECOND_PERSON:
|
||||||
actor_description = actor.backstory
|
character_description = character.backstory
|
||||||
elif perspective == FormatPerspective.THIRD_PERSON:
|
elif perspective == FormatPerspective.THIRD_PERSON:
|
||||||
actor_description = actor.description
|
character_description = character.description
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Perspective {perspective} is not implemented")
|
raise ValueError(f"Perspective {perspective} is not implemented")
|
||||||
|
|
||||||
return f"{actor_description} {attribute_descriptions}"
|
return f"{character_description} {attribute_descriptions}"
|
||||||
|
|
||||||
|
|
||||||
def describe_static(entity: WorldEntity) -> str:
|
def describe_static(entity: WorldEntity) -> str:
|
||||||
|
@ -32,8 +33,8 @@ def describe_entity(
|
||||||
entity: WorldEntity,
|
entity: WorldEntity,
|
||||||
perspective: FormatPerspective = FormatPerspective.SECOND_PERSON,
|
perspective: FormatPerspective = FormatPerspective.SECOND_PERSON,
|
||||||
) -> str:
|
) -> str:
|
||||||
if isinstance(entity, Actor):
|
if isinstance(entity, Character):
|
||||||
return describe_actor(entity, perspective)
|
return describe_character(entity, perspective)
|
||||||
|
|
||||||
return describe_static(entity)
|
return describe_static(entity)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import useWebSocketModule from 'react-use-websocket';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
|
||||||
import { HistoryPanel } from './history.js';
|
import { HistoryPanel } from './history.js';
|
||||||
import { Actor } from './models.js';
|
import { Character } from './models.js';
|
||||||
import { PlayerPanel } from './player.js';
|
import { PlayerPanel } from './player.js';
|
||||||
import { Statusbar } from './status.js';
|
import { Statusbar } from './status.js';
|
||||||
import { StoreState, store } from './store.js';
|
import { StoreState, store } from './store.js';
|
||||||
|
@ -52,15 +52,15 @@ export function App(props: AppProps) {
|
||||||
sendMessage(JSON.stringify({ type: 'render', event }));
|
sendMessage(JSON.stringify({ type: 'render', event }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPlayer(actor: Maybe<Actor>) {
|
function setPlayer(character: Maybe<Character>) {
|
||||||
// do not call setCharacter until the server confirms the player change
|
// do not call setCharacter until the server confirms the player change
|
||||||
if (doesExist(actor)) {
|
if (doesExist(character)) {
|
||||||
sendMessage(JSON.stringify({ type: 'player', become: actor.name }));
|
sendMessage(JSON.stringify({ type: 'player', become: character.name }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendInput(input: string) {
|
function sendInput(input: string) {
|
||||||
const { character, setActiveTurn } = store.getState();
|
const { playerCharacter: character, setActiveTurn } = store.getState();
|
||||||
if (doesExist(character)) {
|
if (doesExist(character)) {
|
||||||
sendMessage(JSON.stringify({ type: 'input', input }));
|
sendMessage(JSON.stringify({ type: 'input', input }));
|
||||||
setActiveTurn(false);
|
setActiveTurn(false);
|
||||||
|
@ -80,7 +80,7 @@ export function App(props: AppProps) {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { setClientId, setActiveTurn, setPlayers, appendEvent, setWorld, world, clientId, setCharacter } = store.getState();
|
const { setClientId, setActiveTurn, setPlayers, appendEvent, setWorld, world, clientId, setPlayerCharacter: setCharacter } = store.getState();
|
||||||
if (doesExist(lastMessage)) {
|
if (doesExist(lastMessage)) {
|
||||||
const event = JSON.parse(lastMessage.data);
|
const event = JSON.parse(lastMessage.data);
|
||||||
|
|
||||||
|
@ -98,8 +98,8 @@ export function App(props: AppProps) {
|
||||||
case 'player':
|
case 'player':
|
||||||
if (event.status === 'join' && doesExist(world) && event.client === clientId) {
|
if (event.status === 'join' && doesExist(world) && event.client === clientId) {
|
||||||
const { character: characterName } = event;
|
const { character: characterName } = event;
|
||||||
const actor = world.rooms.flatMap((room) => room.actors).find((a) => a.name === characterName);
|
const character = world.rooms.flatMap((room) => room.characters).find((a) => a.name === characterName);
|
||||||
setCharacter(actor);
|
setCharacter(character);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'players':
|
case 'players':
|
||||||
|
|
|
@ -21,11 +21,11 @@ import {
|
||||||
import { instance as graphviz } from '@viz-js/viz';
|
import { instance as graphviz } from '@viz-js/viz';
|
||||||
import React, { Fragment, useEffect } from 'react';
|
import React, { Fragment, useEffect } from 'react';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
import { Actor, Attributes, Item, Portal, Room, World } from './models';
|
import { Character, Attributes, Item, Portal, Room, World } from './models';
|
||||||
import { StoreState, store } from './store';
|
import { StoreState, store } from './store';
|
||||||
|
|
||||||
export interface EntityDetailsProps {
|
export interface EntityDetailsProps {
|
||||||
entity: Maybe<Item | Actor | Portal | Room>;
|
entity: Maybe<Item | Character | Portal | Room>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onRender: (type: string, entity: string) => void;
|
onRender: (type: string, entity: string) => void;
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,10 @@ export function EntityDetails(props: EntityDetailsProps) {
|
||||||
let attributes: Attributes = {};
|
let attributes: Attributes = {};
|
||||||
let planner;
|
let planner;
|
||||||
|
|
||||||
if (type === 'actor') {
|
if (type === 'character') {
|
||||||
const actor = entity as Actor;
|
const character = entity as Character;
|
||||||
attributes = actor.attributes;
|
attributes = character.attributes;
|
||||||
planner = actor.planner;
|
planner = character.planner;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'item') {
|
if (type === 'item') {
|
||||||
|
@ -155,7 +155,7 @@ export function DetailDialog(props: DetailDialogProps) {
|
||||||
>{details}</Dialog>;
|
>{details}</Dialog>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWorld(entity: Maybe<Item | Actor | Portal | Room | World>): entity is World {
|
export function isWorld(entity: Maybe<Item | Character | Portal | Room | World>): entity is World {
|
||||||
return doesExist(entity) && doesExist(Object.getOwnPropertyDescriptor(entity, 'theme'));
|
return doesExist(entity) && doesExist(Object.getOwnPropertyDescriptor(entity, 'theme'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Maybe, doesExist } from '@apextoaster/js-utils';
|
||||||
import { Camera, Settings } from '@mui/icons-material';
|
import { Camera, Settings } from '@mui/icons-material';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
import { formatters } from './format.js';
|
import { formatters } from './format.js';
|
||||||
import { Actor } from './models.js';
|
import { Character } from './models.js';
|
||||||
import { StoreState, store } from './store.js';
|
import { StoreState, store } from './store.js';
|
||||||
|
|
||||||
export function openImage(image: string) {
|
export function openImage(image: string) {
|
||||||
|
@ -32,11 +32,11 @@ export interface EventItemProps {
|
||||||
|
|
||||||
export function characterSelector(state: StoreState) {
|
export function characterSelector(state: StoreState) {
|
||||||
return {
|
return {
|
||||||
character: state.character,
|
playerCharacter: state.playerCharacter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sameCharacter(a: Maybe<Actor>, b: Maybe<Actor>): boolean {
|
export function sameCharacter(a: Maybe<Character>, b: Maybe<Character>): boolean {
|
||||||
if (doesExist(a) && doesExist(b)) {
|
if (doesExist(a) && doesExist(b)) {
|
||||||
return a.name === b.name;
|
return a.name === b.name;
|
||||||
}
|
}
|
||||||
|
@ -46,13 +46,13 @@ export function sameCharacter(a: Maybe<Actor>, b: Maybe<Actor>): boolean {
|
||||||
|
|
||||||
export function ActionEventItem(props: EventItemProps) {
|
export function ActionEventItem(props: EventItemProps) {
|
||||||
const { event, renderEvent } = props;
|
const { event, renderEvent } = props;
|
||||||
const { id, actor, room, type } = event;
|
const { id, character, room, type } = event;
|
||||||
const content = formatters[type](event);
|
const content = formatters[type](event);
|
||||||
|
|
||||||
const state = useStore(store, characterSelector);
|
const state = useStore(store, characterSelector);
|
||||||
const { character } = state;
|
const { playerCharacter } = state;
|
||||||
|
|
||||||
const playerAction = sameCharacter(actor, character);
|
const playerAction = sameCharacter(character, playerCharacter);
|
||||||
const typographyProps = {
|
const typographyProps = {
|
||||||
color: playerAction ? 'success.text' : 'primary.text',
|
color: playerAction ? 'success.text' : 'primary.text',
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,7 @@ export function ActionEventItem(props: EventItemProps) {
|
||||||
variant="body2"
|
variant="body2"
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
>
|
>
|
||||||
{actor.name}
|
{character.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
{content}
|
{content}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -220,7 +220,7 @@ export function PromptEventItem(props: EventItemProps) {
|
||||||
const { character, prompt } = event;
|
const { character, prompt } = event;
|
||||||
|
|
||||||
const state = useStore(store, characterSelector);
|
const state = useStore(store, characterSelector);
|
||||||
const { character: playerCharacter } = state;
|
const { playerCharacter: playerCharacter } = state;
|
||||||
|
|
||||||
const playerPrompt = sameCharacter(playerCharacter, character);
|
const playerPrompt = sameCharacter(playerCharacter, character);
|
||||||
const typographyProps = {
|
const typographyProps = {
|
||||||
|
|
|
@ -17,8 +17,8 @@ export interface Item {
|
||||||
attributes: Attributes;
|
attributes: Attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Actor {
|
export interface Character {
|
||||||
type: 'actor';
|
type: 'character';
|
||||||
name: string;
|
name: string;
|
||||||
backstory: string;
|
backstory: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -38,7 +38,7 @@ export interface Room {
|
||||||
type: 'room';
|
type: 'room';
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
actors: Array<Actor>;
|
characters: Array<Character>;
|
||||||
items: Array<Item>;
|
items: Array<Item>;
|
||||||
portals: Array<Portal>;
|
portals: Array<Portal>;
|
||||||
attributes: Attributes;
|
attributes: Attributes;
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface PlayerPanelProps {
|
||||||
|
|
||||||
export function playerStateSelector(s: StoreState) {
|
export function playerStateSelector(s: StoreState) {
|
||||||
return {
|
return {
|
||||||
character: s.character,
|
character: s.playerCharacter,
|
||||||
activeTurn: s.activeTurn,
|
activeTurn: s.activeTurn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { createStore, StateCreator } from 'zustand';
|
||||||
import { doesExist, Maybe } from '@apextoaster/js-utils';
|
import { doesExist, Maybe } from '@apextoaster/js-utils';
|
||||||
import { PaletteMode } from '@mui/material';
|
import { PaletteMode } from '@mui/material';
|
||||||
import { ReadyState } from 'react-use-websocket';
|
import { ReadyState } from 'react-use-websocket';
|
||||||
import { Actor, GameEvent, Item, Portal, Room, World } from './models';
|
import { Character, GameEvent, Item, Portal, Room, World } from './models';
|
||||||
|
|
||||||
export type LayoutMode = 'horizontal' | 'vertical';
|
export type LayoutMode = 'horizontal' | 'vertical';
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ export interface ClientState {
|
||||||
autoScroll: boolean;
|
autoScroll: boolean;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientName: string;
|
clientName: string;
|
||||||
detailEntity: Maybe<Item | Actor | Portal | Room | World>;
|
detailEntity: Maybe<Item | Character | Portal | Room | World>;
|
||||||
eventHistory: Array<GameEvent>;
|
eventHistory: Array<GameEvent>;
|
||||||
layoutMode: LayoutMode;
|
layoutMode: LayoutMode;
|
||||||
readyState: ReadyState;
|
readyState: ReadyState;
|
||||||
|
@ -22,7 +22,7 @@ export interface ClientState {
|
||||||
setAutoScroll: (autoScroll: boolean) => void;
|
setAutoScroll: (autoScroll: boolean) => void;
|
||||||
setClientId: (clientId: string) => void;
|
setClientId: (clientId: string) => void;
|
||||||
setClientName: (name: string) => void;
|
setClientName: (name: string) => void;
|
||||||
setDetailEntity: (entity: Maybe<Item | Actor | Portal | Room | World>) => void;
|
setDetailEntity: (entity: Maybe<Item | Character | Portal | Room | World>) => void;
|
||||||
setLayoutMode: (mode: LayoutMode) => void;
|
setLayoutMode: (mode: LayoutMode) => void;
|
||||||
setReadyState: (state: ReadyState) => void;
|
setReadyState: (state: ReadyState) => void;
|
||||||
setThemeMode: (mode: PaletteMode) => void;
|
setThemeMode: (mode: PaletteMode) => void;
|
||||||
|
@ -44,11 +44,11 @@ export interface WorldState {
|
||||||
|
|
||||||
export interface PlayerState {
|
export interface PlayerState {
|
||||||
activeTurn: boolean;
|
activeTurn: boolean;
|
||||||
character: Maybe<Actor>;
|
playerCharacter: Maybe<Character>;
|
||||||
|
|
||||||
// setters
|
// setters
|
||||||
setActiveTurn: (activeTurn: boolean) => void;
|
setActiveTurn: (activeTurn: boolean) => void;
|
||||||
setCharacter: (character: Maybe<Actor>) => void;
|
setPlayerCharacter: (character: Maybe<Character>) => void;
|
||||||
|
|
||||||
// misc helpers
|
// misc helpers
|
||||||
isPlaying: () => boolean;
|
isPlaying: () => boolean;
|
||||||
|
@ -114,11 +114,11 @@ export function createWorldStore(): StateCreator<WorldState> {
|
||||||
export function createPlayerStore(): StateCreator<PlayerState> {
|
export function createPlayerStore(): StateCreator<PlayerState> {
|
||||||
return (set) => ({
|
return (set) => ({
|
||||||
activeTurn: false,
|
activeTurn: false,
|
||||||
character: undefined,
|
playerCharacter: undefined,
|
||||||
setActiveTurn: (activeTurn: boolean) => set({ activeTurn }),
|
setActiveTurn: (activeTurn: boolean) => set({ activeTurn }),
|
||||||
setCharacter: (character: Maybe<Actor>) => set({ character }),
|
setPlayerCharacter: (character: Maybe<Character>) => set({ playerCharacter: character }),
|
||||||
isPlaying() {
|
isPlaying() {
|
||||||
return doesExist(this.character);
|
return doesExist(this.playerCharacter);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ import React from 'react';
|
||||||
|
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
import { StoreState, store } from './store';
|
import { StoreState, store } from './store';
|
||||||
import { Actor, Item, Portal, Room } from './models';
|
import { Character, Item, Portal, Room } from './models';
|
||||||
|
|
||||||
export type SetDetails = (entity: Maybe<Item | Actor | Room>) => void;
|
export type SetDetails = (entity: Maybe<Item | Character | Room>) => void;
|
||||||
export type SetPlayer = (actor: Maybe<Actor>) => void;
|
export type SetPlayer = (character: Maybe<Character>) => void;
|
||||||
|
|
||||||
export interface BaseEntityItemProps {
|
export interface BaseEntityItemProps {
|
||||||
setPlayer: SetPlayer;
|
setPlayer: SetPlayer;
|
||||||
|
@ -25,14 +25,14 @@ export function formatLabel(name: string, active = false): string {
|
||||||
|
|
||||||
export function itemStateSelector(s: StoreState) {
|
export function itemStateSelector(s: StoreState) {
|
||||||
return {
|
return {
|
||||||
character: s.character,
|
playerCharacter: s.playerCharacter,
|
||||||
setDetailEntity: s.setDetailEntity,
|
setDetailEntity: s.setDetailEntity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function actorStateSelector(s: StoreState) {
|
export function characterStateSelector(s: StoreState) {
|
||||||
return {
|
return {
|
||||||
character: s.character,
|
playerCharacter: s.playerCharacter,
|
||||||
players: s.players,
|
players: s.players,
|
||||||
setDetailEntity: s.setDetailEntity,
|
setDetailEntity: s.setDetailEntity,
|
||||||
};
|
};
|
||||||
|
@ -65,33 +65,33 @@ export function ItemItem(props: { item: Item } & BaseEntityItemProps) {
|
||||||
</TreeItem>;
|
</TreeItem>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActorItem(props: { actor: Actor } & BaseEntityItemProps) {
|
export function CharacterItem(props: { character: Character } & BaseEntityItemProps) {
|
||||||
const { actor, setPlayer } = props;
|
const { character, setPlayer } = props;
|
||||||
const state = useStore(store, actorStateSelector);
|
const state = useStore(store, characterStateSelector);
|
||||||
const { character, players, setDetailEntity } = state;
|
const { playerCharacter, players, setDetailEntity } = state;
|
||||||
|
|
||||||
const activeSelf = doesExist(character) && actor.name === character.name;
|
const activeSelf = doesExist(playerCharacter) && character.name === playerCharacter.name;
|
||||||
const activeOther = Object.values(players).some((it) => it === actor.name); // TODO: are these the keys or the values?
|
const activeOther = Object.values(players).some((it) => it === character.name); // TODO: are these the keys or the values?
|
||||||
const label = formatLabel(actor.name, activeSelf);
|
const label = formatLabel(character.name, activeSelf);
|
||||||
|
|
||||||
let playButton;
|
let playButton;
|
||||||
if (activeSelf) {
|
if (activeSelf) {
|
||||||
playButton = <TreeItem itemId={`${actor.name}-stop`} label="Stop playing" onClick={() => setPlayer(undefined)} />;
|
playButton = <TreeItem itemId={`${character.name}-stop`} label="Stop playing" onClick={() => setPlayer(undefined)} />;
|
||||||
} else {
|
} else {
|
||||||
if (activeOther) {
|
if (activeOther) {
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
const player = Object.entries(players).find((it) => it[1] === actor.name)?.[0];
|
const player = Object.entries(players).find((it) => it[1] === character.name)?.[0];
|
||||||
playButton = <TreeItem itemId={`${actor.name}-taken`} label={`Played by ${player}`} />;
|
playButton = <TreeItem itemId={`${character.name}-taken`} label={`Played by ${player}`} />;
|
||||||
} else {
|
} else {
|
||||||
playButton = <TreeItem itemId={`${actor.name}-play`} label="Play!" onClick={() => setPlayer(actor)} />;
|
playButton = <TreeItem itemId={`${character.name}-play`} label="Play!" onClick={() => setPlayer(character)} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <TreeItem itemId={actor.name} label={label}>
|
return <TreeItem itemId={character.name} label={label}>
|
||||||
{playButton}
|
{playButton}
|
||||||
<TreeItem itemId={`${actor.name}-details`} label="Details" onClick={() => setDetailEntity(actor)} />
|
<TreeItem itemId={`${character.name}-details`} label="Details" onClick={() => setDetailEntity(character)} />
|
||||||
<TreeItem itemId={`${actor.name}-items`} label="Items">
|
<TreeItem itemId={`${character.name}-items`} label="Items">
|
||||||
{actor.items.map((item) => <ItemItem key={item.name} item={item} setPlayer={setPlayer} />)}
|
{character.items.map((item) => <ItemItem key={item.name} item={item} setPlayer={setPlayer} />)}
|
||||||
</TreeItem>
|
</TreeItem>
|
||||||
</TreeItem>;
|
</TreeItem>;
|
||||||
}
|
}
|
||||||
|
@ -99,15 +99,15 @@ export function ActorItem(props: { actor: Actor } & BaseEntityItemProps) {
|
||||||
export function RoomItem(props: { room: Room } & BaseEntityItemProps) {
|
export function RoomItem(props: { room: Room } & BaseEntityItemProps) {
|
||||||
const { room, setPlayer } = props;
|
const { room, setPlayer } = props;
|
||||||
const state = useStore(store, itemStateSelector);
|
const state = useStore(store, itemStateSelector);
|
||||||
const { character, setDetailEntity } = state;
|
const { playerCharacter, setDetailEntity } = state;
|
||||||
|
|
||||||
const active = doesExist(character) && room.actors.some((it) => it.name === character.name);
|
const active = doesExist(playerCharacter) && room.characters.some((it) => it.name === playerCharacter.name);
|
||||||
const label = formatLabel(room.name, active);
|
const label = formatLabel(room.name, active);
|
||||||
|
|
||||||
return <TreeItem itemId={room.name} label={label}>
|
return <TreeItem itemId={room.name} label={label}>
|
||||||
<TreeItem itemId={`${room.name}-details`} label="Details" onClick={() => setDetailEntity(room)} />
|
<TreeItem itemId={`${room.name}-details`} label="Details" onClick={() => setDetailEntity(room)} />
|
||||||
<TreeItem itemId={`${room.name}-actors`} label="Actors">
|
<TreeItem itemId={`${room.name}-characters`} label="Characters">
|
||||||
{room.actors.map((actor) => <ActorItem key={actor.name} actor={actor} setPlayer={setPlayer} />)}
|
{room.characters.map((character) => <CharacterItem key={character.name} character={character} setPlayer={setPlayer} />)}
|
||||||
</TreeItem>
|
</TreeItem>
|
||||||
<TreeItem itemId={`${room.name}-items`} label="Items">
|
<TreeItem itemId={`${room.name}-items`} label="Items">
|
||||||
{room.items.map((item) => <ItemItem key={item.name} item={item} setPlayer={setPlayer} />)}
|
{room.items.map((item) => <ItemItem key={item.name} item={item} setPlayer={setPlayer} />)}
|
||||||
|
|
|
@ -28,7 +28,7 @@ server:
|
||||||
port: 8001
|
port: 8001
|
||||||
world:
|
world:
|
||||||
size:
|
size:
|
||||||
actor_items:
|
character_items:
|
||||||
min: 0
|
min: 0
|
||||||
max: 3
|
max: 3
|
||||||
item_effects:
|
item_effects:
|
||||||
|
@ -40,7 +40,7 @@ world:
|
||||||
rooms:
|
rooms:
|
||||||
min: 3
|
min: 3
|
||||||
max: 6
|
max: 6
|
||||||
room_actors:
|
room_characters:
|
||||||
min: 1
|
min: 1
|
||||||
max: 3
|
max: 3
|
||||||
room_items:
|
room_items:
|
||||||
|
|
|
@ -45,5 +45,5 @@ additional configuration from a YAML or JSON file.
|
||||||
|
|
||||||
- figure out the human input syntax for actions
|
- figure out the human input syntax for actions
|
||||||
- make an admin panel in web UI
|
- make an admin panel in web UI
|
||||||
- store long-term memory for actors in a vector DB (RAG and all that)
|
- store long-term memory for characters in a vector DB (RAG and all that)
|
||||||
- generate and simulate should probably be async
|
- generate and simulate should probably be async
|
||||||
|
|
|
@ -30,10 +30,10 @@ mechanics to suit different types of adventures and narrative styles.
|
||||||
|
|
||||||
### What kinds of entities exist in the world?
|
### What kinds of entities exist in the world?
|
||||||
|
|
||||||
In the immersive world of TaleWeave AI, entities are categorized into Rooms, Actors, and Items, each playing a vital
|
In the immersive world of TaleWeave AI, entities are categorized into Rooms, Characters, and Items, each playing a vital
|
||||||
role in crafting the narrative and gameplay experience. Rooms serve as the fundamental spatial units where the story
|
role in crafting the narrative and gameplay experience. Rooms serve as the fundamental spatial units where the story
|
||||||
unfolds, each containing various Actors and potentially multiple Items. Actors, representing characters in the game,
|
unfolds, each containing various Characters and potentially multiple Items. Characters, representing characters in the game,
|
||||||
possess inventories that hold Items, which are objects that can be interacted with or utilized by the Actors. Currently,
|
possess inventories that hold Items, which are objects that can be interacted with or utilized by the Characters. Currently,
|
||||||
TaleWeave AI does not support Containers—Items that can hold other Items—but the structure is designed to support
|
TaleWeave AI does not support Containers—Items that can hold other Items—but the structure is designed to support
|
||||||
complex interactions within and between these entity types, laying the groundwork for a deeply interactive environment.
|
complex interactions within and between these entity types, laying the groundwork for a deeply interactive environment.
|
||||||
|
|
||||||
|
@ -41,15 +41,15 @@ complex interactions within and between these entity types, laying the groundwor
|
||||||
|
|
||||||
Actions in TaleWeave AI are defined as Python functions that enable both human players and AI-driven characters to
|
Actions in TaleWeave AI are defined as Python functions that enable both human players and AI-driven characters to
|
||||||
interact with the game world. These actions, which include behaviors like taking an item or moving between rooms, are
|
interact with the game world. These actions, which include behaviors like taking an item or moving between rooms, are
|
||||||
integral to advancing the gameplay and affecting the state of the world. Each actor is permitted one action per round,
|
integral to advancing the gameplay and affecting the state of the world. Each character is permitted one action per round,
|
||||||
which can significantly alter the attributes of entities, reposition entities between rooms or actors, or modify the
|
which can significantly alter the attributes of entities, reposition entities between rooms or characters, or modify the
|
||||||
game world by adding or removing entities. This framework ensures that every turn is meaningful and that players'
|
game world by adding or removing entities. This framework ensures that every turn is meaningful and that players'
|
||||||
decisions have direct consequences on the game's progression and outcome.
|
decisions have direct consequences on the game's progression and outcome.
|
||||||
|
|
||||||
### What are attributes?
|
### What are attributes?
|
||||||
|
|
||||||
Attributes in TaleWeave AI are key-value pairs that define the properties of an entity. These attributes can be of
|
Attributes in TaleWeave AI are key-value pairs that define the properties of an entity. These attributes can be of
|
||||||
various types—boolean, number, or string—such as an actor’s mood being "happy," their health being quantified as 10, or
|
various types—boolean, number, or string—such as a character's mood being "happy," their health being quantified as 10, or
|
||||||
an item's quality described as "broken" or quantified with a remaining usage of 3. Attributes play a crucial role in the
|
an item's quality described as "broken" or quantified with a remaining usage of 3. Attributes play a crucial role in the
|
||||||
game's logic system by influencing how entities react under different conditions. They are actively used to trigger
|
game's logic system by influencing how entities react under different conditions. They are actively used to trigger
|
||||||
specific rules within the game, and their labels are included in prompts to guide language model players in making
|
specific rules within the game, and their labels are included in prompts to guide language model players in making
|
||||||
|
|
|
@ -77,7 +77,7 @@ added to the world.
|
||||||
```yaml
|
```yaml
|
||||||
type: "generate"
|
type: "generate"
|
||||||
name: string
|
name: string
|
||||||
entity: Room | Actor | Item | None
|
entity: Room | Character | Item | None
|
||||||
```
|
```
|
||||||
|
|
||||||
Two `generate` events will be fired for each entity. The first event will *not* have an `entity` set, only the `name`.
|
Two `generate` events will be fired for each entity. The first event will *not* have an `entity` set, only the `name`.
|
||||||
|
@ -88,14 +88,14 @@ more frequent progress updates when generating with slow models.
|
||||||
|
|
||||||
### Action Events
|
### Action Events
|
||||||
|
|
||||||
The action event is fired after player or actor input has been processed and any JSON function calls have been parsed.
|
The action event is fired after player or character input has been processed and any JSON function calls have been parsed.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
type: "action"
|
type: "action"
|
||||||
action: string
|
action: string
|
||||||
parameters: dict
|
parameters: dict
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
item: Item | None
|
item: Item | None
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ The prompt event is fired when a character's turn starts and their input is need
|
||||||
type: "prompt"
|
type: "prompt"
|
||||||
prompt: string
|
prompt: string
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reply Events
|
### Reply Events
|
||||||
|
@ -118,7 +118,7 @@ The reply event is fired when a character has been asked a question or told a me
|
||||||
type: "reply"
|
type: "reply"
|
||||||
text: string
|
text: string
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
```
|
```
|
||||||
|
|
||||||
### Result Events
|
### Result Events
|
||||||
|
@ -129,10 +129,10 @@ The result event is fired after a character has taken an action and contains the
|
||||||
type: "result"
|
type: "result"
|
||||||
result: string
|
result: string
|
||||||
room: Room
|
room: Room
|
||||||
actor: Actor
|
character: Character
|
||||||
```
|
```
|
||||||
|
|
||||||
The result is related to the most recent action for the same actor, although not every action will have a result - they
|
The result is related to the most recent action for the same character, although not every action will have a result - they
|
||||||
may have a reply or error instead.
|
may have a reply or error instead.
|
||||||
|
|
||||||
### Status Events
|
### Status Events
|
||||||
|
@ -143,7 +143,7 @@ The status event is fired for general events in the world and messages about oth
|
||||||
type: "status"
|
type: "status"
|
||||||
text: string
|
text: string
|
||||||
room: Room | None
|
room: Room | None
|
||||||
actor: Actor | None
|
character: Character | None
|
||||||
```
|
```
|
||||||
|
|
||||||
### Snapshot Events
|
### Snapshot Events
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
- [Player leaves the game during their turn](#player-leaves-the-game-during-their-turn)
|
- [Player leaves the game during their turn](#player-leaves-the-game-during-their-turn)
|
||||||
- [Spectator renders a recent event](#spectator-renders-a-recent-event)
|
- [Spectator renders a recent event](#spectator-renders-a-recent-event)
|
||||||
- [User Stories for Developers](#user-stories-for-developers)
|
- [User Stories for Developers](#user-stories-for-developers)
|
||||||
|
- [ML Engineer collects prompts and actions to fine-tune a character model](#ml-engineer-collects-prompts-and-actions-to-fine-tune-a-character-model)
|
||||||
|
- [ML Engineer collects character movement and conversations to chart emergent behavior](#ml-engineer-collects-character-movement-and-conversations-to-chart-emergent-behavior)
|
||||||
- [Mod Developer creates a new system for the game](#mod-developer-creates-a-new-system-for-the-game)
|
- [Mod Developer creates a new system for the game](#mod-developer-creates-a-new-system-for-the-game)
|
||||||
- [Project Contributor fixes a bug in the engine](#project-contributor-fixes-a-bug-in-the-engine)
|
- [Project Contributor fixes a bug in the engine](#project-contributor-fixes-a-bug-in-the-engine)
|
||||||
|
|
||||||
|
@ -174,9 +176,20 @@ Skills:
|
||||||
|
|
||||||
### User Stories for Developers
|
### User Stories for Developers
|
||||||
|
|
||||||
|
#### ML Engineer collects prompts and actions to fine-tune a character model
|
||||||
|
|
||||||
|
> As an ML engineer, I want to collect data from the game history, especially prompts, actions, and their results, so
|
||||||
|
> that I can fine-tune an LLM to be a better model for characters.
|
||||||
|
|
||||||
|
#### ML Engineer collects character movement and conversations to chart emergent behavior
|
||||||
|
|
||||||
|
> As an ML engineer, I want to collect data from the game history, especially character movement, interactions, and
|
||||||
|
> conversations, so that I can graph their movements and discover any emergent behavior in the game world.
|
||||||
|
|
||||||
#### Mod Developer creates a new system for the game
|
#### Mod Developer creates a new system for the game
|
||||||
|
|
||||||
> As a Mod Developer, I want to write a Python module with a new game system and load it into a test world, so that I can develop custom features.
|
> As a Mod Developer, I want to write a Python module with a new game system and load it into a test world, so that I
|
||||||
|
> can develop custom features.
|
||||||
|
|
||||||
#### Project Contributor fixes a bug in the engine
|
#### Project Contributor fixes a bug in the engine
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue