fix logging, make config optional, fix event attributes
This commit is contained in:
parent
fee406e607
commit
1453038f6d
|
@ -11,7 +11,7 @@
|
||||||
"program": "${file}",
|
"program": "${file}",
|
||||||
"args": [
|
"args": [
|
||||||
"--world",
|
"--world",
|
||||||
"worlds/test-1.json",
|
"worlds/test-3.json",
|
||||||
"--rooms",
|
"--rooms",
|
||||||
"2",
|
"2",
|
||||||
"--server",
|
"--server",
|
||||||
|
|
|
@ -13,7 +13,7 @@ from adventure.context import (
|
||||||
get_current_world,
|
get_current_world,
|
||||||
set_actor_agent,
|
set_actor_agent,
|
||||||
)
|
)
|
||||||
from adventure.models.config import DiscordBotConfig
|
from adventure.models.config import DiscordBotConfig, DEFAULT_CONFIG
|
||||||
from adventure.models.event import (
|
from adventure.models.event import (
|
||||||
ActionEvent,
|
ActionEvent,
|
||||||
GameEvent,
|
GameEvent,
|
||||||
|
@ -36,7 +36,7 @@ from adventure.render_comfy import render_event
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
client = None
|
client = None
|
||||||
bot_config: DiscordBotConfig = DiscordBotConfig(channels=["bots"])
|
bot_config: DiscordBotConfig = DEFAULT_CONFIG.bot.discord
|
||||||
|
|
||||||
active_tasks = set()
|
active_tasks = set()
|
||||||
event_messages: Dict[str, str | GameEvent] = {}
|
event_messages: Dict[str, str | GameEvent] = {}
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
from typing import Callable, Dict, List, Sequence, Tuple
|
from typing import Callable, Dict, List, Sequence, Tuple
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from packit.agent import Agent
|
from packit.agent import Agent
|
||||||
|
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 Actor, Room, World
|
||||||
from adventure.models.event import GameEvent
|
from adventure.models.event import GameEvent
|
||||||
|
|
||||||
|
# TODO: replace with event emitter and a context manager
|
||||||
current_broadcast: Callable[[str | GameEvent], None] | None = None
|
current_broadcast: Callable[[str | GameEvent], None] | None = None
|
||||||
|
|
||||||
|
# world context
|
||||||
|
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_actor: Actor | None = None
|
||||||
current_step = 0
|
|
||||||
dungeon_master: Agent | None = None
|
dungeon_master: Agent | None = None
|
||||||
|
|
||||||
|
# game context
|
||||||
|
event_emitter = EventEmitter()
|
||||||
game_systems: List[GameSystem] = []
|
game_systems: List[GameSystem] = []
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +36,23 @@ def has_dungeon_master():
|
||||||
return dungeon_master is not None
|
return dungeon_master is not None
|
||||||
|
|
||||||
|
|
||||||
|
# region context manager
|
||||||
|
# TODO
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
# region context getters
|
# region context getters
|
||||||
|
def get_action_context() -> Tuple[Room, Actor]:
|
||||||
|
if not current_room:
|
||||||
|
raise ValueError("The current room must be set before calling action functions")
|
||||||
|
if not current_actor:
|
||||||
|
raise ValueError(
|
||||||
|
"The current actor must be set before calling action functions"
|
||||||
|
)
|
||||||
|
|
||||||
|
return (current_room, current_actor)
|
||||||
|
|
||||||
|
|
||||||
def get_current_context() -> Tuple[World, Room, Actor]:
|
def get_current_context() -> Tuple[World, Room, Actor]:
|
||||||
if not current_world:
|
if not current_world:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
|
@ -209,12 +209,13 @@ def generate_effect(
|
||||||
|
|
||||||
name = loop_retry(
|
name = loop_retry(
|
||||||
agent,
|
agent,
|
||||||
"Generate one effect for an {entity_type} named {entity.name} that would make sense in the world of {theme}. "
|
"Generate one effect for an {entity_type} named {entity_name} that would make sense in the world of {theme}. "
|
||||||
"Only respond with the effect name in title case, do not include a description or any other text. "
|
"Only respond with the effect 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. Use a unique name. '
|
'Do not prefix the name with "the", do not wrap it in quotes. Use a unique name. '
|
||||||
"Do not create any duplicate effects on the same item. The existing effects are: {existing_effects}. "
|
"Do not create any duplicate effects on the same item. The existing effects are: {existing_effects}. "
|
||||||
"Some example effects are: 'fire', 'poison', 'frost', 'haste', 'slow', and 'heal'.",
|
"Some example effects are: 'fire', 'poison', 'frost', 'haste', 'slow', and 'heal'.",
|
||||||
context={
|
context={
|
||||||
|
"entity_name": entity.name,
|
||||||
"entity_type": entity_type,
|
"entity_type": entity_type,
|
||||||
"existing_effects": existing_effects,
|
"existing_effects": existing_effects,
|
||||||
"theme": theme,
|
"theme": theme,
|
||||||
|
@ -245,6 +246,7 @@ def generate_effect(
|
||||||
f"How does the {name} effect modify the {attribute_name} attribute? "
|
f"How does the {name} effect modify the {attribute_name} attribute? "
|
||||||
"For example, 'heal' might 'add' to the 'health' attribute, while 'poison' might 'subtract' from it."
|
"For example, 'heal' might 'add' to the 'health' attribute, while 'poison' might 'subtract' from it."
|
||||||
"Another example is 'writing' might 'set' the 'text' attribute, while 'break' might 'set' the 'condition' attribute."
|
"Another example is 'writing' might 'set' the 'text' attribute, while 'break' might 'set' the 'condition' attribute."
|
||||||
|
"Reply with the operation only, without any other text. Give a single word."
|
||||||
"Choose from the following operations: {operations}",
|
"Choose from the following operations: {operations}",
|
||||||
name=name,
|
name=name,
|
||||||
attribute_name=attribute_name,
|
attribute_name=attribute_name,
|
||||||
|
@ -260,8 +262,8 @@ def generate_effect(
|
||||||
)
|
)
|
||||||
value = agent(
|
value = agent(
|
||||||
f"How much does the {name} effect modify the {attribute_name} attribute? "
|
f"How much does the {name} effect modify the {attribute_name} attribute? "
|
||||||
"For example, 'heal' might 'add' 10 to the 'health' attribute, while 'poison' might 'subtract' 5 from it."
|
"For example, heal might add '10' to the health attribute, while poison might subtract '5' from it."
|
||||||
"Enter a positive or negative number, or a string value.",
|
"Enter a positive or negative number, or a string value. Do not include any other text. Do not use JSON.",
|
||||||
name=name,
|
name=name,
|
||||||
attribute_name=attribute_name,
|
attribute_name=attribute_name,
|
||||||
)
|
)
|
||||||
|
@ -283,7 +285,7 @@ def generate_effect(
|
||||||
|
|
||||||
attributes.append(attribute_effect)
|
attributes.append(attribute_effect)
|
||||||
|
|
||||||
return Effect(name=name, description=description, attributes=[])
|
return Effect(name=name, description=description, attributes=attributes)
|
||||||
|
|
||||||
|
|
||||||
def generate_system_attributes(
|
def generate_system_attributes(
|
||||||
|
|
|
@ -6,19 +6,9 @@ from typing import List
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from packit.agent import Agent, agent_easy_connect
|
from packit.agent import Agent, agent_easy_connect
|
||||||
from packit.utils import logger_with_colors
|
from packit.utils import logger_with_colors
|
||||||
|
from pyee.base import EventEmitter
|
||||||
from yaml import Loader, load
|
from yaml import Loader, load
|
||||||
|
|
||||||
from adventure.context import set_current_step, set_dungeon_master
|
|
||||||
from adventure.game_system import GameSystem
|
|
||||||
from adventure.generate import generate_world
|
|
||||||
from adventure.models.config import Config
|
|
||||||
from adventure.models.entity import World, WorldState
|
|
||||||
from adventure.models.event import EventCallback, GameEvent, GenerateEvent
|
|
||||||
from adventure.models.files import PromptFile, WorldPrompt
|
|
||||||
from adventure.plugins import load_plugin
|
|
||||||
from adventure.simulate import simulate_world
|
|
||||||
from adventure.state import create_agents, save_world, save_world_state
|
|
||||||
|
|
||||||
|
|
||||||
def load_yaml(file):
|
def load_yaml(file):
|
||||||
return load(file, Loader=Loader)
|
return load(file, Loader=Loader)
|
||||||
|
@ -42,6 +32,18 @@ logger = logger_with_colors(__name__) # , level="DEBUG")
|
||||||
|
|
||||||
load_dotenv(environ.get("ADVENTURE_ENV", ".env"), override=True)
|
load_dotenv(environ.get("ADVENTURE_ENV", ".env"), override=True)
|
||||||
|
|
||||||
|
if True:
|
||||||
|
from adventure.context import set_current_step, set_dungeon_master
|
||||||
|
from adventure.game_system import GameSystem
|
||||||
|
from adventure.generate import generate_world
|
||||||
|
from adventure.models.config import Config, DEFAULT_CONFIG
|
||||||
|
from adventure.models.entity import World, WorldState
|
||||||
|
from adventure.models.event import EventCallback, GameEvent, GenerateEvent
|
||||||
|
from adventure.models.files import PromptFile, WorldPrompt
|
||||||
|
from adventure.plugins import load_plugin
|
||||||
|
from adventure.simulate import simulate_world
|
||||||
|
from adventure.state import create_agents, save_world, save_world_state
|
||||||
|
|
||||||
|
|
||||||
# start the debugger, if needed
|
# start the debugger, if needed
|
||||||
if environ.get("DEBUG", "false").lower() == "true":
|
if environ.get("DEBUG", "false").lower() == "true":
|
||||||
|
@ -52,6 +54,13 @@ if environ.get("DEBUG", "false").lower() == "true":
|
||||||
debugpy.wait_for_client()
|
debugpy.wait_for_client()
|
||||||
|
|
||||||
|
|
||||||
|
def int_or_inf(value: str) -> float | int:
|
||||||
|
if value == "inf":
|
||||||
|
return float("inf")
|
||||||
|
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
# main
|
# main
|
||||||
def parse_args():
|
def parse_args():
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -68,8 +77,7 @@ def parse_args():
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
type=str,
|
type=str,
|
||||||
default="config.yml",
|
help="The file to load additional configuration from",
|
||||||
help="The file to load the configuration from",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--discord",
|
"--discord",
|
||||||
|
@ -91,7 +99,7 @@ def parse_args():
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--optional-actions",
|
"--optional-actions",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Whether to include optional actions",
|
help="Whether to include optional actions in the simulation",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--player",
|
"--player",
|
||||||
|
@ -101,7 +109,7 @@ def parse_args():
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--render",
|
"--render",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Whether to render the simulation",
|
help="Whether to run the render thread",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--render-generated",
|
"--render-generated",
|
||||||
|
@ -116,7 +124,7 @@ def parse_args():
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--server",
|
"--server",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="The address on which to run the server",
|
help="Whether to run the websocket server",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--state",
|
"--state",
|
||||||
|
@ -125,7 +133,7 @@ def parse_args():
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--steps",
|
"--steps",
|
||||||
type=int,
|
type=int_or_inf,
|
||||||
default=10,
|
default=10,
|
||||||
help="The number of simulation steps to run",
|
help="The number of simulation steps to run",
|
||||||
)
|
)
|
||||||
|
@ -233,8 +241,11 @@ def load_or_generate_world(
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
|
if args.config:
|
||||||
with open(args.config, "r") as f:
|
with open(args.config, "r") as f:
|
||||||
config = Config(**load_yaml(f))
|
config = Config(**load_yaml(f))
|
||||||
|
else:
|
||||||
|
config = DEFAULT_CONFIG
|
||||||
|
|
||||||
players = []
|
players = []
|
||||||
if args.player:
|
if args.player:
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .base import dataclass
|
||||||
class Range:
|
class Range:
|
||||||
min: int
|
min: int
|
||||||
max: int
|
max: int
|
||||||
|
interval: int = 1
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -39,3 +40,21 @@ class RenderConfig:
|
||||||
class Config:
|
class Config:
|
||||||
bot: BotConfig
|
bot: BotConfig
|
||||||
render: RenderConfig
|
render: RenderConfig
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = Config(
|
||||||
|
bot=BotConfig(discord=DiscordBotConfig(channels=["adventure"])),
|
||||||
|
render=RenderConfig(
|
||||||
|
cfg=Range(min=5, max=8),
|
||||||
|
checkpoints=[
|
||||||
|
"diffusion-sdxl-dynavision-0-5-5-7.safetensors",
|
||||||
|
],
|
||||||
|
path="/tmp/adventure-images",
|
||||||
|
sizes={
|
||||||
|
"landscape": Size(width=1024, height=768),
|
||||||
|
"portrait": Size(width=768, height=1024),
|
||||||
|
"square": Size(width=768, height=768),
|
||||||
|
},
|
||||||
|
steps=Range(min=30, max=30),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -17,7 +17,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from adventure.context import broadcast
|
from adventure.context import broadcast
|
||||||
from adventure.models.config import Range, RenderConfig, Size
|
from adventure.models.config import RenderConfig, DEFAULT_CONFIG
|
||||||
from adventure.models.entity import WorldEntity
|
from adventure.models.entity import WorldEntity
|
||||||
from adventure.models.event import (
|
from adventure.models.event import (
|
||||||
ActionEvent,
|
ActionEvent,
|
||||||
|
@ -33,19 +33,7 @@ logger = getLogger(__name__)
|
||||||
|
|
||||||
server_address = environ["COMFY_API"]
|
server_address = environ["COMFY_API"]
|
||||||
client_id = uuid4().hex
|
client_id = uuid4().hex
|
||||||
render_config: RenderConfig = RenderConfig(
|
render_config: RenderConfig = DEFAULT_CONFIG.render
|
||||||
cfg=Range(min=5, max=8),
|
|
||||||
checkpoints=[
|
|
||||||
"diffusion-sdxl-dynavision-0-5-5-7.safetensors",
|
|
||||||
],
|
|
||||||
path="/tmp/adventure-images",
|
|
||||||
sizes={
|
|
||||||
"landscape": Size(width=1024, height=768),
|
|
||||||
"portrait": Size(width=768, height=1024),
|
|
||||||
"square": Size(width=768, height=768),
|
|
||||||
},
|
|
||||||
steps=Range(min=30, max=30),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# requests to generate images for game events
|
# requests to generate images for game events
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
from random import randint
|
||||||
|
from adventure.context import broadcast, get_current_context, get_dungeon_master
|
||||||
|
from adventure.generate import generate_item
|
||||||
|
from adventure.models.entity import Item
|
||||||
|
from adventure.models.base import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Recipe:
|
||||||
|
ingredients: list[str]
|
||||||
|
result: str
|
||||||
|
difficulty: int
|
||||||
|
|
||||||
|
|
||||||
|
recipes = {
|
||||||
|
"potion": Recipe(["herb", "water"], "potion", 5),
|
||||||
|
"sword": Recipe(["metal", "wood"], "sword", 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def action_craft(item_name: str) -> str:
|
||||||
|
"""
|
||||||
|
Craft an item using available recipes and inventory items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_name: The name of the item to craft.
|
||||||
|
"""
|
||||||
|
action_world, _, action_actor = get_current_context()
|
||||||
|
|
||||||
|
if item_name not in recipes:
|
||||||
|
return f"There is no recipe to craft a {item_name}."
|
||||||
|
|
||||||
|
recipe = recipes[item_name]
|
||||||
|
|
||||||
|
# Check if the actor has the required skill level
|
||||||
|
skill = randint(1, 20)
|
||||||
|
if skill < recipe.difficulty:
|
||||||
|
return f"You need a crafting skill level of {recipe.difficulty} to craft {item_name}."
|
||||||
|
|
||||||
|
# Collect inventory items names
|
||||||
|
inventory_items = {item.name for item in action_actor.items}
|
||||||
|
|
||||||
|
# Check for sufficient ingredients
|
||||||
|
missing_items = [item for item in recipe.ingredients if item not in inventory_items]
|
||||||
|
if missing_items:
|
||||||
|
return f"You are missing {' and '.join(missing_items)} to craft {item_name}."
|
||||||
|
|
||||||
|
# Deduct the ingredients from inventory
|
||||||
|
for ingredient in recipe.ingredients:
|
||||||
|
item_to_remove = next(
|
||||||
|
item for item in action_actor.items if item.name == ingredient
|
||||||
|
)
|
||||||
|
action_actor.items.remove(item_to_remove)
|
||||||
|
|
||||||
|
# Create and add the crafted item to inventory
|
||||||
|
result_item = next(
|
||||||
|
(item for item in action_actor.items if item.name == recipe.result), None
|
||||||
|
)
|
||||||
|
if result_item:
|
||||||
|
new_item = Item(**vars(result_item)) # Copying the item
|
||||||
|
else:
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
new_item = generate_item(
|
||||||
|
dungeon_master, action_world.theme
|
||||||
|
) # TODO: pass recipe item
|
||||||
|
|
||||||
|
action_actor.items.append(new_item)
|
||||||
|
|
||||||
|
broadcast(f"{action_actor.name} crafts a {item_name}.")
|
||||||
|
return f"You successfully craft a {item_name}."
|
|
@ -0,0 +1,22 @@
|
||||||
|
from adventure.context import broadcast, get_current_context
|
||||||
|
from adventure.search import find_item_in_actor
|
||||||
|
|
||||||
|
|
||||||
|
def action_read(item_name: str) -> str:
|
||||||
|
"""
|
||||||
|
Read an item like a book or a sign.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_name: The name of the item to read.
|
||||||
|
"""
|
||||||
|
_, _, action_actor = get_current_context()
|
||||||
|
|
||||||
|
item = find_item_in_actor(action_actor, item_name)
|
||||||
|
if not item:
|
||||||
|
return f"You do not have a {item_name} to read."
|
||||||
|
|
||||||
|
if "text" in item.attributes:
|
||||||
|
broadcast(f"{action_actor.name} reads {item_name}")
|
||||||
|
return str(item.attributes["text"])
|
||||||
|
else:
|
||||||
|
return f"The {item_name} has nothing to read."
|
|
@ -0,0 +1,36 @@
|
||||||
|
from random import randint
|
||||||
|
from adventure.context import broadcast, get_current_context, get_dungeon_master
|
||||||
|
from adventure.search import find_actor_in_room
|
||||||
|
|
||||||
|
|
||||||
|
def action_cast(spell: str, target: str) -> str:
|
||||||
|
"""
|
||||||
|
Cast a spell on a target.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spell: The name of the spell to cast.
|
||||||
|
target: The target of the spell.
|
||||||
|
"""
|
||||||
|
_, action_room, action_actor = get_current_context()
|
||||||
|
|
||||||
|
target_actor = find_actor_in_room(action_room, target)
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
|
# Check for spell availability and mana costs
|
||||||
|
if spell not in action_actor.attributes["spells"]:
|
||||||
|
return f"You do not know the spell '{spell}'."
|
||||||
|
if action_actor.attributes["mana"] < action_actor.attributes["spells"][spell]:
|
||||||
|
return "You do not have enough mana to cast this spell."
|
||||||
|
|
||||||
|
action_actor.attributes["mana"] -= action_actor.attributes["spells"][spell]
|
||||||
|
# Get flavor text from the dungeon master
|
||||||
|
flavor_text = dungeon_master(f"Describe the effects of {spell} on {target}.")
|
||||||
|
broadcast(f"{action_actor.name} casts {spell} on {target}. {flavor_text}")
|
||||||
|
|
||||||
|
# Apply effects based on the spell
|
||||||
|
if spell == "heal" and target_actor:
|
||||||
|
heal_amount = randint(10, 30)
|
||||||
|
target_actor.attributes["health"] += heal_amount
|
||||||
|
return f"{target} is healed for {heal_amount} points."
|
||||||
|
|
||||||
|
return f"{spell} was successfully cast on {target}."
|
|
@ -0,0 +1,36 @@
|
||||||
|
from random import randint
|
||||||
|
from adventure.context import broadcast, get_current_context, get_dungeon_master
|
||||||
|
from adventure.search import find_item_in_room
|
||||||
|
|
||||||
|
|
||||||
|
def action_climb(target: str) -> str:
|
||||||
|
"""
|
||||||
|
Climb a structure or natural feature.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target: The object or feature to climb.
|
||||||
|
"""
|
||||||
|
_, action_room, action_actor = get_current_context()
|
||||||
|
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
# Assume 'climbable' is an attribute that marks climbable targets
|
||||||
|
climbable_feature = find_item_in_room(action_room, target)
|
||||||
|
|
||||||
|
if climbable_feature and climbable_feature.attributes.get("climbable", False):
|
||||||
|
climb_difficulty = int(climbable_feature.attributes.get("difficulty", 5))
|
||||||
|
climb_roll = randint(1, 20)
|
||||||
|
|
||||||
|
# Get flavor text for the climb attempt
|
||||||
|
flavor_text = dungeon_master(
|
||||||
|
f"Describe {action_actor.name}'s attempt to climb {target}."
|
||||||
|
)
|
||||||
|
if climb_roll > climb_difficulty:
|
||||||
|
broadcast(
|
||||||
|
f"{action_actor.name} successfully climbs the {target}. {flavor_text}"
|
||||||
|
)
|
||||||
|
return f"You successfully climb the {target}."
|
||||||
|
else:
|
||||||
|
broadcast(f"{action_actor.name} fails to climb the {target}. {flavor_text}")
|
||||||
|
return f"You fail to climb the {target}."
|
||||||
|
else:
|
||||||
|
return f"The {target} is not climbable."
|
|
@ -1,5 +1,7 @@
|
||||||
|
from itertools import count
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import Callable, Sequence
|
from typing import Callable, Sequence
|
||||||
|
from math import inf
|
||||||
|
|
||||||
from packit.loops import loop_retry
|
from packit.loops import loop_retry
|
||||||
from packit.results import multi_function_or_str_result
|
from packit.results import multi_function_or_str_result
|
||||||
|
@ -63,7 +65,7 @@ def world_result_parser(value, agent, **kwargs):
|
||||||
|
|
||||||
def simulate_world(
|
def simulate_world(
|
||||||
world: World,
|
world: World,
|
||||||
steps: int = 10,
|
steps: float | int = inf,
|
||||||
actions: Sequence[Callable[..., str]] = [],
|
actions: Sequence[Callable[..., str]] = [],
|
||||||
callbacks: Sequence[EventCallback] = [],
|
callbacks: Sequence[EventCallback] = [],
|
||||||
systems: Sequence[GameSystem] = [],
|
systems: Sequence[GameSystem] = [],
|
||||||
|
@ -100,9 +102,10 @@ def simulate_world(
|
||||||
action_names = action_tools.list_tools()
|
action_names = action_tools.list_tools()
|
||||||
|
|
||||||
# simulate each actor
|
# simulate each actor
|
||||||
for i in range(steps):
|
for i in count():
|
||||||
current_step = get_current_step()
|
current_step = get_current_step()
|
||||||
logger.info(f"Simulating step {current_step}")
|
logger.info(f"simulating step {i} of {steps} (world step {current_step})")
|
||||||
|
|
||||||
for actor_name in world.order:
|
for actor_name in world.order:
|
||||||
actor, agent = get_actor_agent_for_name(actor_name)
|
actor, agent = get_actor_agent_for_name(actor_name)
|
||||||
if not agent or not actor:
|
if not agent or not actor:
|
||||||
|
@ -179,3 +182,6 @@ def simulate_world(
|
||||||
system.simulate(world, current_step)
|
system.simulate(world, current_step)
|
||||||
|
|
||||||
set_current_step(current_step + 1)
|
set_current_step(current_step + 1)
|
||||||
|
if i > steps:
|
||||||
|
logger.info("reached step limit at world step %s", current_step + 1)
|
||||||
|
break
|
||||||
|
|
Loading…
Reference in New Issue