2024-05-02 11:56:57 +00:00
from logging import getLogger
2024-05-26 22:03:39 +00:00
from random import choice
2024-05-19 00:48:18 +00:00
from typing import List , Tuple
2024-05-02 11:56:57 +00:00
from packit . agent import Agent
2024-05-04 22:17:56 +00:00
from packit . loops import loop_retry
2024-05-24 02:57:21 +00:00
from packit . results import enum_result , int_result
2024-05-16 04:12:06 +00:00
from packit . utils import could_be_json
2024-05-25 20:18:40 +00:00
from adventure . context import broadcast , set_current_world , set_system_data
2024-05-16 04:12:06 +00:00
from adventure . game_system import GameSystem
2024-05-19 00:48:18 +00:00
from adventure . models . config import DEFAULT_CONFIG , WorldConfig
2024-05-24 02:57:21 +00:00
from adventure . models . effect import (
EffectPattern ,
FloatEffectPattern ,
IntEffectPattern ,
StringEffectPattern ,
2024-05-16 04:12:06 +00:00
)
2024-05-27 01:32:03 +00:00
from adventure . models . entity import Character , Item , Portal , Room , World , WorldEntity
2024-05-18 21:58:11 +00:00
from adventure . models . event import GenerateEvent
2024-05-19 04:30:17 +00:00
from adventure . utils import try_parse_float , try_parse_int
2024-05-26 22:03:39 +00:00
from adventure . utils . effect import resolve_int_range
2024-05-19 18:09:52 +00:00
from adventure . utils . search import (
2024-05-27 01:32:03 +00:00
list_characters ,
list_characters_in_room ,
2024-05-19 18:09:52 +00:00
list_items ,
2024-05-27 01:32:03 +00:00
list_items_in_character ,
2024-05-19 18:09:52 +00:00
list_items_in_room ,
list_rooms ,
)
from adventure . utils . string import normalize_name
2024-05-02 11:56:57 +00:00
logger = getLogger ( __name__ )
2024-05-19 00:48:18 +00:00
world_config : WorldConfig = DEFAULT_CONFIG . world
2024-05-04 22:57:24 +00:00
2024-05-02 11:56:57 +00:00
2024-05-13 04:33:47 +00:00
def duplicate_name_parser ( existing_names : List [ str ] ) :
2024-05-16 04:12:06 +00:00
def name_parser ( value : str , * * kwargs ) :
2024-05-18 21:58:11 +00:00
logger . debug ( f " validating generated name: { value } " )
2024-05-16 04:12:06 +00:00
if value in existing_names :
raise ValueError ( f ' " { value } " has already been used. ' )
2024-05-14 01:08:19 +00:00
2024-05-16 04:12:06 +00:00
if could_be_json ( value ) :
raise ValueError ( " The name cannot contain JSON or other commands. " )
2024-05-13 04:33:47 +00:00
2024-05-16 04:12:06 +00:00
if ' " ' in value or " : " in value :
2024-05-13 04:33:47 +00:00
raise ValueError ( " The name cannot contain quotes or colons. " )
2024-05-16 04:12:06 +00:00
if len ( value ) > 50 :
2024-05-13 04:33:47 +00:00
raise ValueError ( " The name cannot be longer than 50 characters. " )
2024-05-16 04:12:06 +00:00
return value
2024-05-13 04:33:47 +00:00
return name_parser
2024-05-18 21:58:11 +00:00
def broadcast_generated (
2024-05-13 04:33:47 +00:00
message : str | None = None ,
entity : WorldEntity | None = None ,
) :
if message :
event = GenerateEvent . from_name ( message )
elif entity :
event = GenerateEvent . from_entity ( entity )
else :
raise ValueError ( " Either message or entity must be provided " )
2024-05-18 21:58:11 +00:00
broadcast ( event )
2024-05-13 04:33:47 +00:00
2024-05-19 04:30:17 +00:00
def generate_system_attributes (
agent : Agent , world : World , entity : WorldEntity , systems : List [ GameSystem ]
) - > None :
for system in systems :
if system . generate :
2024-05-25 20:18:40 +00:00
# TODO: pass the whole world
2024-05-19 04:30:17 +00:00
system . generate ( agent , world . theme , entity )
2024-05-04 20:35:42 +00:00
def generate_room (
2024-05-05 14:14:54 +00:00
agent : Agent ,
2024-05-19 04:30:17 +00:00
world : World ,
systems : List [ GameSystem ] ,
2024-05-04 20:35:42 +00:00
) - > Room :
2024-05-19 04:30:17 +00:00
existing_rooms = [ room . name for room in list_rooms ( world ) ]
2024-05-04 22:17:56 +00:00
name = loop_retry (
agent ,
2024-05-02 11:56:57 +00:00
" Generate one room, area, or location that would make sense in the world of {world_theme} . "
2024-05-08 01:39:58 +00:00
" Only respond with the room name in title case, do not include the description or any other text. "
2024-05-02 11:56:57 +00:00
' Do not prefix the name with " the " , do not wrap it in quotes. The existing rooms are: {existing_rooms} ' ,
2024-05-04 22:17:56 +00:00
context = {
2024-05-19 04:30:17 +00:00
" world_theme " : world . theme ,
2024-05-04 22:17:56 +00:00
" existing_rooms " : existing_rooms ,
} ,
2024-05-13 04:33:47 +00:00
result_parser = duplicate_name_parser ( existing_rooms ) ,
2024-05-19 19:06:09 +00:00
toolbox = None ,
2024-05-02 11:56:57 +00:00
)
2024-05-05 14:14:54 +00:00
2024-05-18 21:58:11 +00:00
broadcast_generated ( message = f " Generating room: { name } " )
2024-05-02 11:56:57 +00:00
desc = agent (
" Generate a detailed description of the {name} area. What does it look like? "
" What does it smell like? What can be seen or heard? " ,
name = name ,
)
2024-05-19 00:48:18 +00:00
actions = { }
2024-05-27 01:32:03 +00:00
room = Room ( name = name , description = desc , items = [ ] , characters = [ ] , actions = actions )
2024-05-19 00:48:18 +00:00
2024-05-26 22:03:39 +00:00
item_count = resolve_int_range ( world_config . size . room_items ) or 0
2024-05-19 00:48:18 +00:00
broadcast_generated ( f " Generating { item_count } items for room: { name } " )
2024-05-19 04:30:17 +00:00
for _ in range ( item_count ) :
2024-05-19 00:48:18 +00:00
try :
item = generate_item (
agent ,
2024-05-19 04:30:17 +00:00
world ,
systems = systems ,
dest_room = room ,
2024-05-19 00:48:18 +00:00
)
broadcast_generated ( entity = item )
2024-05-19 04:30:17 +00:00
room . items . append ( item )
2024-05-19 00:48:18 +00:00
except Exception :
logger . exception ( " error generating item " )
2024-05-27 01:32:03 +00:00
character_count = resolve_int_range ( world_config . size . room_characters ) or 0
broadcast_generated (
message = f " Generating { character_count } characters for room: { name } "
)
2024-05-19 00:48:18 +00:00
2024-05-27 01:32:03 +00:00
for _ in range ( character_count ) :
2024-05-19 00:48:18 +00:00
try :
2024-05-27 01:32:03 +00:00
character = generate_character (
2024-05-19 00:48:18 +00:00
agent ,
2024-05-19 04:30:17 +00:00
world ,
systems = systems ,
dest_room = room ,
2024-05-19 00:48:18 +00:00
)
2024-05-27 01:32:03 +00:00
broadcast_generated ( entity = character )
2024-05-19 00:48:18 +00:00
2024-05-27 01:32:03 +00:00
room . characters . append ( character )
2024-05-19 00:48:18 +00:00
except Exception :
2024-05-27 01:32:03 +00:00
logger . exception ( " error generating character " )
2024-05-19 00:48:18 +00:00
continue
2024-05-02 11:56:57 +00:00
2024-05-19 04:30:17 +00:00
return room
2024-05-02 11:56:57 +00:00
2024-05-19 00:48:18 +00:00
def generate_portals (
agent : Agent ,
2024-05-19 04:30:17 +00:00
world : World ,
2024-05-19 00:48:18 +00:00
source_room : Room ,
dest_room : Room ,
2024-05-19 04:30:17 +00:00
systems : List [ GameSystem ] ,
2024-05-19 00:48:18 +00:00
) - > Tuple [ Portal , Portal ] :
existing_source_portals = [ portal . name for portal in source_room . portals ]
existing_dest_portals = [ portal . name for portal in dest_room . portals ]
outgoing_name = loop_retry (
agent ,
" Generate the name of a portal that leads from the {source_room} room to the {dest_room} room and fits the world theme of {world_theme} . "
" Some example portal names are: ' door ' , ' gate ' , ' archway ' , ' staircase ' , ' trapdoor ' , ' mirror ' , and ' magic circle ' . "
" Only respond with the portal 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 create any duplicate portals in the same room. The existing portals are: {existing_portals} " ,
context = {
" source_room " : source_room . name ,
" dest_room " : dest_room . name ,
" existing_portals " : existing_source_portals ,
2024-05-19 04:30:17 +00:00
" world_theme " : world . theme ,
2024-05-19 00:48:18 +00:00
} ,
result_parser = duplicate_name_parser ( existing_source_portals ) ,
2024-05-19 19:06:09 +00:00
toolbox = None ,
2024-05-19 00:48:18 +00:00
)
broadcast_generated ( message = f " Generating portal: { outgoing_name } " )
incoming_name = loop_retry (
agent ,
" Generate the opposite name of the portal that leads from the {dest_room} room to the {source_room} room. "
" The name should be the opposite of the {outgoing_name} portal and should fit the world theme of {world_theme} . "
" Some example portal names are: ' door ' , ' gate ' , ' archway ' , ' staircase ' , ' trapdoor ' , ' mirror ' , and ' magic circle ' . "
" Only respond with the portal 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 create any duplicate portals in the same room. The existing portals are: {existing_portals} " ,
context = {
" source_room " : source_room . name ,
" dest_room " : dest_room . name ,
" existing_portals " : existing_dest_portals ,
2024-05-19 04:30:17 +00:00
" world_theme " : world . theme ,
2024-05-19 00:48:18 +00:00
" outgoing_name " : outgoing_name ,
} ,
result_parser = duplicate_name_parser ( existing_dest_portals ) ,
2024-05-19 19:06:09 +00:00
toolbox = None ,
2024-05-19 00:48:18 +00:00
)
broadcast_generated ( message = f " Linking { outgoing_name } to { incoming_name } " )
outgoing_portal = Portal (
name = outgoing_name ,
description = f " A { outgoing_name } leads to the { dest_room . name } room. " ,
destination = dest_room . name ,
)
2024-05-19 04:30:17 +00:00
generate_system_attributes ( agent , world , outgoing_portal , systems )
2024-05-19 00:48:18 +00:00
incoming_portal = Portal (
name = incoming_name ,
description = f " A { incoming_name } leads to the { source_room . name } room. " ,
destination = source_room . name ,
)
2024-05-19 04:30:17 +00:00
generate_system_attributes ( agent , world , incoming_portal , systems )
2024-05-19 00:48:18 +00:00
return ( outgoing_portal , incoming_portal )
2024-05-02 11:56:57 +00:00
def generate_item (
agent : Agent ,
2024-05-19 04:30:17 +00:00
world : World ,
systems : List [ GameSystem ] ,
dest_room : Room | None = None ,
2024-05-27 01:32:03 +00:00
dest_character : Character | None = None ,
2024-05-02 11:56:57 +00:00
) - > Item :
2024-05-19 04:30:17 +00:00
existing_items = [
item . name
for item in list_items (
2024-05-27 01:32:03 +00:00
world , include_character_inventory = True , include_item_inventory = True
2024-05-19 04:30:17 +00:00
)
]
2024-05-19 18:09:52 +00:00
2024-05-27 01:32:03 +00:00
if dest_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_character ( dest_character )
]
2024-05-02 11:56:57 +00:00
elif dest_room :
2024-05-19 04:30:17 +00:00
dest_note = f " The item will be placed in the { dest_room . name } room "
2024-05-19 18:09:52 +00:00
existing_items + = [ item . name for item in list_items_in_room ( dest_room ) ]
2024-05-02 11:56:57 +00:00
else :
dest_note = " The item will be placed in the world "
2024-05-04 22:17:56 +00:00
name = loop_retry (
agent ,
2024-05-02 11:56:57 +00:00
" Generate one item or object that would make sense in the world of {world_theme} . {dest_note} . "
2024-05-08 01:39:58 +00:00
" Only respond with the item 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 include the name of the room. Use a unique name. '
" Do not create any duplicate items in the same room. Do not give characters any duplicate items. "
" The existing items are: {existing_items} " ,
2024-05-04 22:17:56 +00:00
context = {
" dest_note " : dest_note ,
" existing_items " : existing_items ,
2024-05-19 04:30:17 +00:00
" world_theme " : world . theme ,
2024-05-04 22:17:56 +00:00
} ,
2024-05-13 04:33:47 +00:00
result_parser = duplicate_name_parser ( existing_items ) ,
2024-05-19 19:06:09 +00:00
toolbox = None ,
2024-05-02 11:56:57 +00:00
)
2024-05-05 14:14:54 +00:00
2024-05-18 21:58:11 +00:00
broadcast_generated ( message = f " Generating item: { name } " )
2024-05-02 11:56:57 +00:00
desc = agent (
" Generate a detailed description of the {name} item. What does it look like? What is it made of? What does it do? " ,
name = name ,
)
actions = { }
2024-05-16 04:12:06 +00:00
item = Item ( name = name , description = desc , actions = actions )
2024-05-19 04:30:17 +00:00
generate_system_attributes ( agent , world , item , systems )
2024-05-02 11:56:57 +00:00
2024-05-26 22:03:39 +00:00
effect_count = resolve_int_range ( world_config . size . item_effects ) or 0
2024-05-18 21:58:11 +00:00
broadcast_generated ( message = f " Generating { effect_count } effects for item: { name } " )
2024-05-16 04:12:06 +00:00
2024-05-19 04:30:17 +00:00
for _ in range ( effect_count ) :
2024-05-16 04:12:06 +00:00
try :
2024-05-19 04:30:17 +00:00
effect = generate_effect ( agent , world , entity = item )
item . effects . append ( effect )
2024-05-16 04:12:06 +00:00
except Exception :
logger . exception ( " error generating effect " )
return item
2024-05-02 11:56:57 +00:00
2024-05-27 01:32:03 +00:00
def generate_character (
2024-05-05 14:14:54 +00:00
agent : Agent ,
2024-05-19 04:30:17 +00:00
world : World ,
systems : List [ GameSystem ] ,
dest_room : Room ,
2024-05-27 02:33:44 +00:00
additional_prompt : str = " " ,
detail_prompt : str = " " ,
2024-05-27 01:32:03 +00:00
) - > Character :
existing_characters = [ character . name for character in list_characters ( world ) ] + [
character . name for character in list_characters_in_room ( dest_room )
2024-05-19 18:09:52 +00:00
]
2024-05-04 22:17:56 +00:00
name = loop_retry (
agent ,
2024-05-27 02:33:44 +00:00
" Generate a new character that would make sense in the world of {world_theme} . Characters can be a person, creature, or some other intelligent entity. "
" The character will be placed in the {dest_room} room. {additional_prompt} . "
2024-05-08 01:39:58 +00:00
" 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. '
2024-05-04 04:18:21 +00:00
" Do not include the name of the room. Do not give characters any duplicate names. "
2024-05-27 01:32:03 +00:00
" Do not create any duplicate characters. The existing characters are: {existing_characters} " ,
2024-05-04 22:17:56 +00:00
context = {
2024-05-27 02:33:44 +00:00
" additional_prompt " : additional_prompt ,
2024-05-19 04:30:17 +00:00
" dest_room " : dest_room . name ,
2024-05-27 01:32:03 +00:00
" existing_characters " : existing_characters ,
2024-05-19 04:30:17 +00:00
" world_theme " : world . theme ,
2024-05-04 22:17:56 +00:00
} ,
2024-05-27 01:32:03 +00:00
result_parser = duplicate_name_parser ( existing_characters ) ,
2024-05-19 19:06:09 +00:00
toolbox = None ,
2024-05-02 11:56:57 +00:00
)
2024-05-05 14:14:54 +00:00
2024-05-27 01:32:03 +00:00
broadcast_generated ( message = f " Generating character: { name } " )
2024-05-02 11:56:57 +00:00
description = agent (
2024-05-27 02:33:44 +00:00
" Generate a detailed description of the {name} character. {additional_prompt} . {detail_prompt} . What do they look like? What are they wearing? "
2024-05-03 01:57:11 +00:00
" What are they doing? Describe their appearance from the perspective of an outside observer. "
" Do not include the room or any other characters in the description, because they will move around. " ,
2024-05-27 02:33:44 +00:00
additional_prompt = additional_prompt ,
detail_prompt = detail_prompt ,
2024-05-02 11:56:57 +00:00
name = name ,
)
backstory = agent (
2024-05-27 02:33:44 +00:00
" Generate a backstory for the {name} character. {additional_prompt} . {detail_prompt} . Where are they from? What are they doing here? What are their "
2024-05-02 11:56:57 +00:00
' goals? Make sure to phrase the backstory in the second person, starting with " you are " and speaking directly to {name} . ' ,
2024-05-27 02:33:44 +00:00
additional_prompt = additional_prompt ,
detail_prompt = detail_prompt ,
2024-05-02 11:56:57 +00:00
name = name ,
)
2024-05-27 01:32:03 +00:00
character = Character (
2024-05-19 04:30:17 +00:00
name = name , backstory = backstory , description = description , actions = { } , items = [ ]
)
2024-05-27 01:32:03 +00:00
generate_system_attributes ( agent , world , character , systems )
2024-05-19 04:30:17 +00:00
2024-05-27 01:32:03 +00:00
# generate the character's inventory
item_count = resolve_int_range ( world_config . size . character_items ) or 0
broadcast_generated ( f " Generating { item_count } items for character { name } " )
2024-05-19 00:48:18 +00:00
for k in range ( item_count ) :
try :
item = generate_item (
agent ,
2024-05-19 04:30:17 +00:00
world ,
systems ,
2024-05-27 01:32:03 +00:00
dest_character = character ,
2024-05-19 00:48:18 +00:00
)
2024-05-19 04:30:17 +00:00
generate_system_attributes ( agent , world , item , systems )
2024-05-19 00:48:18 +00:00
broadcast_generated ( entity = item )
2024-05-27 01:32:03 +00:00
character . items . append ( item )
2024-05-19 00:48:18 +00:00
except Exception :
logger . exception ( " error generating item " )
2024-05-27 01:32:03 +00:00
return character
2024-05-19 00:48:18 +00:00
2024-05-16 04:12:06 +00:00
2024-05-24 02:57:21 +00:00
def generate_effect ( agent : Agent , world : World , entity : Item ) - > EffectPattern :
2024-05-19 00:48:18 +00:00
entity_type = entity . type
2024-05-19 04:30:17 +00:00
existing_effects = [ effect . name for effect in entity . effects ]
2024-05-16 04:12:06 +00:00
name = loop_retry (
agent ,
2024-05-18 21:20:47 +00:00
" Generate one effect for an {entity_type} named {entity_name} that would make sense in the world of {theme} . "
2024-05-16 04:12:06 +00:00
" 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 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 ' . " ,
context = {
2024-05-18 21:20:47 +00:00
" entity_name " : entity . name ,
2024-05-16 04:12:06 +00:00
" entity_type " : entity_type ,
" existing_effects " : existing_effects ,
2024-05-19 04:30:17 +00:00
" theme " : world . theme ,
2024-05-16 04:12:06 +00:00
} ,
result_parser = duplicate_name_parser ( existing_effects ) ,
2024-05-19 19:06:09 +00:00
toolbox = None ,
2024-05-16 04:12:06 +00:00
)
2024-05-18 21:58:11 +00:00
broadcast_generated ( message = f " Generating effect: { name } " )
2024-05-16 04:12:06 +00:00
description = agent (
" Generate a detailed description of the {name} effect. What does it look like? What does it do? "
" How does it affect the target? Describe the effect from the perspective of an outside observer. " ,
name = name ,
)
2024-05-27 02:33:44 +00:00
cooldown = loop_retry (
agent ,
f " How many turns should the { name } effect wait before it can be used again? Enter a positive number to set a cooldown, or 0 for no cooldown. "
" Do not include any other text. Do not use JSON. " ,
context = {
" name " : name ,
} ,
result_parser = int_result ,
toolbox = None ,
)
uses = loop_retry (
agent ,
f " How many times can the { name } effect be used before it is exhausted? Enter a positive number to set a limit, or -1 for unlimited uses. "
" Do not include any other text. Do not use JSON. " ,
context = {
" name " : name ,
} ,
result_parser = int_result ,
toolbox = None ,
)
if uses == - 1 :
uses = None
2024-05-16 04:12:06 +00:00
attribute_names = agent (
2024-05-19 04:30:17 +00:00
" Generate a short list of attributes that the {name} effect modifies. Include 1 to 3 attributes. "
2024-05-16 04:12:06 +00:00
" For example, ' heal ' increases the target ' s ' health ' attribute, while ' poison ' decreases it. "
" Use a comma-separated list of attribute names, such as ' health, strength, speed ' . "
" Only include the attribute names, do not include the question or any JSON. " ,
name = name ,
)
attributes = [ ]
for attribute_name in attribute_names . split ( " , " ) :
2024-05-19 18:09:52 +00:00
attribute_name = normalize_name ( attribute_name )
2024-05-16 04:12:06 +00:00
if attribute_name :
value = agent (
f " How much does the { name } effect modify the { attribute_name } attribute? "
2024-05-24 02:57:21 +00:00
" For example, heal might add 10 to the health attribute, while poison might remove -5 from it. "
" Enter a positive number to increase the attribute or a negative number to decrease it. "
" Do not include any other text. Do not use JSON. " ,
2024-05-16 04:12:06 +00:00
name = name ,
attribute_name = attribute_name ,
)
value = value . strip ( )
2024-05-19 00:48:18 +00:00
2024-05-24 02:57:21 +00:00
# TODO: support more than just set: offset and multiply
2024-05-19 00:48:18 +00:00
int_value = try_parse_int ( value )
if int_value is not None :
2024-05-24 02:57:21 +00:00
attribute_effect = IntEffectPattern ( name = attribute_name , set = int_value )
2024-05-16 04:12:06 +00:00
else :
2024-05-19 00:48:18 +00:00
float_value = try_parse_float ( value )
if float_value is not None :
2024-05-24 02:57:21 +00:00
attribute_effect = FloatEffectPattern (
name = attribute_name , set = float_value
2024-05-19 00:48:18 +00:00
)
else :
2024-05-24 02:57:21 +00:00
attribute_effect = StringEffectPattern (
name = attribute_name , set = value
2024-05-19 00:48:18 +00:00
)
2024-05-16 04:12:06 +00:00
attributes . append ( attribute_effect )
2024-05-24 02:57:21 +00:00
duration = loop_retry (
agent ,
f " How many turns does the { name } effect last? Enter a positive number to set a duration, or 0 for an instant effect. "
" Do not include any other text. Do not use JSON. " ,
context = {
" name " : name ,
} ,
result_parser = int_result ,
2024-05-25 20:18:40 +00:00
toolbox = None ,
2024-05-24 02:57:21 +00:00
)
def parse_application ( value : str , * * kwargs ) - > str :
value = enum_result ( value , [ " temporary " , " permanent " ] )
if value :
return value
raise ValueError ( " The application must be ' temporary ' or ' permanent ' . " )
application = loop_retry (
agent ,
(
f " How should the { name } effect be applied? Respond with ' temporary ' for a temporary effect that lasts for a duration, "
" or ' permanent ' for a permanent effect that immediately modifies the target. "
" For example, a healing potion would be a permanent effect that increases health every turn, "
" while bleeding would be a temporary effect that decreases health every turn. "
" A haste potion would be a temporary effect that increases speed for a duration, "
" while a slow spell would be a temporary effect that decreases speed for a duration. "
" Do not include any other text. Do not use JSON. "
) ,
context = {
" name " : name ,
} ,
result_parser = parse_application ,
2024-05-25 20:18:40 +00:00
toolbox = None ,
2024-05-24 02:57:21 +00:00
)
return EffectPattern (
name ,
description ,
application ,
attributes = attributes ,
2024-05-27 02:33:44 +00:00
cooldown = cooldown ,
duration = duration ,
uses = uses ,
2024-05-24 02:57:21 +00:00
)
2024-05-16 04:12:06 +00:00
2024-05-19 18:09:52 +00:00
def link_rooms (
2024-05-04 04:18:21 +00:00
agent : Agent ,
2024-05-19 18:09:52 +00:00
world : World ,
2024-05-19 04:30:17 +00:00
systems : List [ GameSystem ] ,
2024-05-19 18:09:52 +00:00
rooms : List [ Room ] | None = None ,
) - > None :
rooms = rooms or world . rooms
2024-05-02 11:56:57 +00:00
2024-05-19 18:09:52 +00:00
for room in rooms :
2024-05-26 22:03:39 +00:00
num_portals = resolve_int_range ( world_config . size . portals ) or 0
2024-05-12 20:47:18 +00:00
2024-05-19 00:48:18 +00:00
if len ( room . portals ) > = num_portals :
logger . info ( f " room { room . name } already has enough portals " )
continue
2024-05-02 11:56:57 +00:00
2024-05-18 21:58:11 +00:00
broadcast_generated (
2024-05-19 00:48:18 +00:00
message = f " Generating { num_portals } portals for room: { room . name } "
2024-05-12 20:47:18 +00:00
)
2024-05-04 20:35:42 +00:00
2024-05-19 04:30:17 +00:00
for _ in range ( num_portals ) :
2024-05-19 00:48:18 +00:00
previous_destinations = [ portal . destination for portal in room . portals ] + [
room . name
]
2024-05-19 04:30:17 +00:00
remaining_rooms = [
r for r in world . rooms if r . name not in previous_destinations
]
2024-05-19 00:48:18 +00:00
if len ( remaining_rooms ) == 0 :
logger . info ( f " no more rooms to link to from { room . name } " )
break
# TODO: prompt the DM to choose a destination room
dest_room = choice (
2024-05-19 04:30:17 +00:00
[ r for r in world . rooms if r . name not in previous_destinations ]
2024-05-19 00:48:18 +00:00
)
2024-05-13 04:33:47 +00:00
try :
2024-05-19 00:48:18 +00:00
outgoing_portal , incoming_portal = generate_portals (
2024-05-19 04:30:17 +00:00
agent , world , room , dest_room , systems
2024-05-02 11:56:57 +00:00
)
2024-05-12 20:47:18 +00:00
2024-05-19 00:48:18 +00:00
room . portals . append ( outgoing_portal )
dest_room . portals . append ( incoming_portal )
2024-05-13 04:33:47 +00:00
except Exception :
2024-05-19 00:48:18 +00:00
logger . exception ( " error generating portal " )
2024-05-02 11:56:57 +00:00
continue
2024-05-19 18:09:52 +00:00
def generate_world (
agent : Agent ,
name : str ,
theme : str ,
systems : List [ GameSystem ] ,
room_count : int | None = None ,
) - > World :
2024-05-26 22:03:39 +00:00
room_count = room_count or resolve_int_range ( world_config . size . rooms ) or 0
2024-05-19 18:09:52 +00:00
broadcast_generated ( message = f " Generating a { theme } with { room_count } rooms " )
world = World ( name = name , rooms = [ ] , theme = theme , order = [ ] )
set_current_world ( world )
2024-05-25 20:18:40 +00:00
# initialize the systems
for system in systems :
if system . initialize :
data = system . initialize ( world )
set_system_data ( system . name , data )
2024-05-19 18:09:52 +00:00
# generate the rooms
for _ in range ( room_count ) :
try :
room = generate_room ( agent , world , systems )
generate_system_attributes ( agent , world , room , systems )
broadcast_generated ( entity = room )
world . rooms . append ( room )
except Exception :
logger . exception ( " error generating room " )
continue
# generate portals to link the rooms together
link_rooms ( agent , world , systems )
2024-05-27 01:32:03 +00:00
# ensure characters act in a stable order
world . order = [
character . name for room in world . rooms for character in room . characters
]
2024-05-19 04:30:17 +00:00
return world