From d438797d697ea13879a0a6d514eabeccd1292865 Mon Sep 17 00:00:00 2001 From: rickard Date: Sat, 5 Oct 2024 14:37:18 +0200 Subject: [PATCH 1/5] wield and wear equipped items --- tale/equip_npcs.py | 9 +++++++++ tests/test_equip_npc.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/tale/equip_npcs.py b/tale/equip_npcs.py index 7cc272b0..9d8a3bda 100644 --- a/tale/equip_npcs.py +++ b/tale/equip_npcs.py @@ -28,28 +28,34 @@ def equip_npc(npc: LivingNpc, world_items: list[dict], setting: str = 'fantasy') weapon = _get_item_by_name_or_random('Sword', one_handed) if weapon: npc.insert(load_item(weapon), npc) + npc.wielding = npc.locate_item(weapon['name'])[0] else: weapon = _get_item_by_name_or_random('Spear', two_handed) if weapon: npc.insert(load_item(weapon), npc) + npc.wielding = npc.locate_item(weapon['name'])[0] if random.random() > 0.5: helmet = wearable.random_wearable_for_body_part(WearLocation.HEAD, setting, armor_only=True) if helmet: npc.insert(load_item(helmet), npc) + npc.set_wearable(npc.locate_item(helmet['name'])[0], wearable.WearLocation.HEAD) if random.random() > 0.5: torso = wearable.random_wearable_for_body_part(WearLocation.TORSO, setting, armor_only=True) if torso: npc.insert(load_item(torso), npc) + npc.set_wearable(npc.locate_item(torso['name'])[0], wearable.WearLocation.TORSO) return if occupation in ['archer', 'ranger', 'hunter', 'marksman']: # Archer weapon = _get_item_by_name_or_random('Bow', ranged.values) if weapon: npc.insert(load_item(weapon), npc) + npc.wielding = npc.locate_item(weapon['name'])[0] return if occupation in ['mage', 'sorcerer', 'wizard', 'warlock']: # Caster weapon = two_handed.get('Staff', npc) if weapon: npc.insert(load_item(weapon), npc) + npc.wielding = npc.locate_item(weapon['name'])[0] return if occupation in ['healer', 'cleric', 'priest', 'monk']: # Healer potion = random.choice([item for item in world_items if item and item['type'] == 'Health']) @@ -60,12 +66,14 @@ def equip_npc(npc: LivingNpc, world_items: list[dict], setting: str = 'fantasy') weapon = _get_item_by_name_or_random('Dagger', one_handed) if weapon: npc.insert(load_item(weapon), npc) + npc.wielding = npc.locate_item(weapon['name'])[0] return if occupation in ['peasant', 'farmer', 'commoner', 'villager']: if random.random() > 0.3: weapon = _get_item_by_name_or_random('Pitchfork', one_handed) if weapon: npc.insert(load_item(weapon), npc) + npc.wielding = npc.locate_item(weapon['name'])[0] def _get_item_by_name_or_random(name: str, items: dict) -> dict: if not items: @@ -89,4 +97,5 @@ def dress_npc(npc: LivingNpc, setting: str = 'fantasy', max_attempts = 5) -> Non max_attempts -= 1 continue npc.insert(wearable_item, npc) + npc.set_wearable(wearable_item, bodypart) return \ No newline at end of file diff --git a/tests/test_equip_npc.py b/tests/test_equip_npc.py index f39199d1..befda26d 100644 --- a/tests/test_equip_npc.py +++ b/tests/test_equip_npc.py @@ -24,6 +24,7 @@ def test_equip_soldier(self): assert npc.inventory assert npc.money > 0 + assert npc.wielding.name in ['sword', 'spear'] def test_equip_wolf(self): @@ -36,6 +37,7 @@ def test_equip_wolf(self): assert not npc.inventory assert npc.money == 0 + assert npc.wielding.name not in ['sword', 'spear'] def test_equip_centaur(self): driver = IFDriver(screen_delay=99, gui=False, web=True, wizard_override=True) @@ -49,12 +51,15 @@ def test_equip_centaur(self): assert npc.inventory assert npc.money > 0 + assert npc.wielding + assert npc.wielding.name in ['sword', 'spear'] def test_dress_npc_with_wearables(self): npc = LivingNpc('Test', gender='m') setting = 'fantasy' dress_npc(npc, setting, max_attempts=50) assert npc.inventory + assert npc.get_worn_items() def test_get_by_name_or_random(self): items = [{"name": "Sword", "type": "weapon", "value": 100, "weapon_type":"ONE_HANDED"}, {"name": "Spear", "type": "weapon", "value": 100, "weapon_type":"TWO_HANDED"}, {"name": "shield", "type": "armor", "value": 60}, {"name": "boots", "type": "armor", "value": 50}] From 70ecd266bf685a4b186d8d56bc19bbc995e4fa3f Mon Sep 17 00:00:00 2001 From: rickard Date: Sat, 5 Oct 2024 15:08:08 +0200 Subject: [PATCH 2/5] include occupation when generating npcs and characters --- llm_config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llm_config.yaml b/llm_config.yaml index a979f67d..68e85097 100644 --- a/llm_config.yaml +++ b/llm_config.yaml @@ -1,6 +1,6 @@ WORD_LIMIT: 200 # max number of words the model is encoraged to generate. not a hard limit SHORT_WORD_LIMIT: 25 # max number of words when asked to write something short. not a hard limit -BACKEND: "kobold_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template +BACKEND: "llama_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template MEMORY_SIZE: 512 UNLIMITED_REACTS: False DIALOGUE_TEMPLATE: '{{"response":"may be both dialogue and action.", "sentiment":"sentiment based on response", "give":"if any physical item of {character2}s is given as part of the dialogue. Or nothing."}}' @@ -9,11 +9,11 @@ ACTION_TEMPLATE: '{{"goal": reason for action, "thoughts":thoughts about perform ITEM_TEMPLATE: '{{"name":"", "type":"", "short_descr":"", "level":int, "value":int}}' CREATURE_TEMPLATE: '{{"name":"", "body":"", "mass":int(kg), "hp":int, "type":"Npc or Mob", "level":int, "aggressive":bool, "unarmed_attack":One of [FISTS, CLAWS, BITE, TAIL, HOOVES, HORN, TUSKS, BEAK, TALON], "short_descr":""}}' EXIT_TEMPLATE: '{{"direction":"", "name":"name of new location", "short_descr":"exit description"}}' -NPC_TEMPLATE: '{{"name":"", "sentiment":"", "race":"", "gender":"m, f, or n", "level":(int), "aggressive":bool, "description":"25 words", "appearance":"25 words"}}' +NPC_TEMPLATE: '{{"name":"", "sentiment":"", "race":"", "gender":"m, f, or n", "level":(int), "aggressive":bool, "description":"25 words", "occupation":"", "appearance":"25 words"}}' LOCATION_TEMPLATE: '{{"name": "", "description":"", "exits":[], "items":[], "npcs":[], "indoors":"true or false"}}' ZONE_TEMPLATE: '{{"name":name of the room, "description": "75 words", "races":[], "items":[], "mood":"5 to -5, where 5 is extremely friendly and -5 is extremely hostile.", "level":(int)}}' DUNGEON_LOCATION_TEMPLATE: '{"index": (int), "name": "", "description": 25 words}' -CHARACTER_TEMPLATE: '{"name":"", "description": "50 words", "appearance": "25 words", "personality": "50 words", "money":(int), "level":"", "gender":"m/f/n", "age":(int), "race":""}' +CHARACTER_TEMPLATE: '{"name":"", "description": "50 words", "appearance": "25 words", "personality": "50 words", "money":(int), "level":"", "gender":"m/f/n", "age":(int), "race":"", "occupation":""}' FOLLOW_TEMPLATE: '{{"response":"yes or no", "reason":"50 words"}}' ITEM_TYPES: ["Weapon", "Wearable", "Health", "Money", "Trash", "Food", "Drink", "Key"] PRE_PROMPT: 'You are a creative game keeper for an interactive fiction story telling session. You craft detailed worlds and interesting characters with unique and deep personalities for the player to interact with. Do not acknowledge the task or speak directly to the user, or respond with anything besides the request..' From 1f6426e21929518e3204bfbfe10e625c69dc3ca9 Mon Sep 17 00:00:00 2001 From: rickard Date: Sat, 5 Oct 2024 15:18:48 +0200 Subject: [PATCH 3/5] don't sell worn stuff --- tale/shop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tale/shop.py b/tale/shop.py index 4b47784e..e4c50820 100644 --- a/tale/shop.py +++ b/tale/shop.py @@ -182,7 +182,7 @@ def shop_list(self, parsed: ParseResult, actor: Living, brief: bool = False) -> if not brief: actor.tell("%s shows you a list of what is in stock at the moment:" % lang.capital(self.subjective), end=True, evoke=True) txt = ["
    # |
      item |
        price "] - for i, item in enumerate(sorted_by_title(self.inventory), start=1): + for i, item in enumerate(sorted_by_title([item for item in self.inventory if item not in self.get_worn_items()]), start=1): price = item.value * self.shop.sellprofit txt.append("%3d. %-30s %s" % (i, item.title, mud_context.driver.moneyfmt.display(price))) actor.tell( "\n".join(txt), format=False) From 6811e749a7ce31635feaa78bb48ff8eea53f0ddf Mon Sep 17 00:00:00 2001 From: rickard Date: Sat, 5 Oct 2024 15:27:30 +0200 Subject: [PATCH 4/5] revert backend for test --- llm_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm_config.yaml b/llm_config.yaml index 68e85097..405b41cf 100644 --- a/llm_config.yaml +++ b/llm_config.yaml @@ -1,6 +1,6 @@ WORD_LIMIT: 200 # max number of words the model is encoraged to generate. not a hard limit SHORT_WORD_LIMIT: 25 # max number of words when asked to write something short. not a hard limit -BACKEND: "llama_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template +BACKEND: "kobold_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template MEMORY_SIZE: 512 UNLIMITED_REACTS: False DIALOGUE_TEMPLATE: '{{"response":"may be both dialogue and action.", "sentiment":"sentiment based on response", "give":"if any physical item of {character2}s is given as part of the dialogue. Or nothing."}}' From 941a69198a3bfc02c94d3a3a8c8cb4aa816f6bd3 Mon Sep 17 00:00:00 2001 From: rickard Date: Sat, 5 Oct 2024 18:51:39 +0200 Subject: [PATCH 5/5] some wearable fixes --- llm_config.yaml | 2 +- tale/base.py | 2 +- tale/equip_npcs.py | 2 +- tale/wearable.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/llm_config.yaml b/llm_config.yaml index 405b41cf..0629dd06 100644 --- a/llm_config.yaml +++ b/llm_config.yaml @@ -21,7 +21,7 @@ BASE_PROMPT: '{context}\n[USER_START] Rewrite [{input_text}] DIALOGUE_PROMPT: '{context}\nThe following is a conversation between {character1} and {character2}; {character2}s sentiment towards {character1}: {sentiment}. Write a single response as {character2} in third person pov, using {character2} description and other information found inside the tags. If {character2} has a quest active, they will discuss it based on its status. Respond in JSON using this template: """{dialogue_template}""". [USER_START]Continue the following conversation as {character2}: {previous_conversation}' COMBAT_PROMPT: '{context}\nThe following is a combat scene between {attackers} and {defenders} in {location}. [USER_START] Describe the following combat result in about 150 words in vivid language, using the characters weapons and their health status: 1.0 is highest, 0.0 is lowest. Combat Result: {input_text}' PRE_JSON_PROMPT: 'Below is an instruction that describes a task, paired with an input that provides further context. Write a response in valid JSON format that appropriately completes the request.' -CREATE_CHARACTER_PROMPT: '{context}\n[USER_START] Create a diverse character with rich personality that can be interacted with using the story context and keywords. {{quest_prompt}} Do not mention height. keywords: {keywords}. Fill in this JSON template and write nothing else: {character_template}' +CREATE_CHARACTER_PROMPT: '{context}\n[USER_START] Create a diverse character with rich personality that can be interacted with using the story context and keywords. {{quest_prompt}} Do not mention height. Keywords: {keywords}. Fill in the blanks in this JSON template and write nothing else: {character_template}' CREATE_LOCATION_PROMPT: '{context}\nZone info: {zone_info}; Exit json example: {exit_template}; Npc or mob example: {npc_template}. Existing connected locations: {exit_locations}. [USER_START] Using the information supplied inside the tags, describe the following location: {location_name}. {items_prompt} {spawn_prompt} Add a brief description, and one to three additional exits leading to new locations. Fill in this JSON template and do not write anything else: {location_template}. Write the response in valid JSON.' CREATE_ZONE_PROMPT: '{context}\n[USER_START]Using the information supplied inside the tags, create an new area that can be further populated with multiple locations. It is connected in the {direction} to {zone_info}. Add a name and brief description. Choose the names of 5 creatures from the supplied list likely to find in the area. Fill in "items" with the names and types of 5 common items in the area. Fill in this JSON template and do not write anything else: {zone_template}. Write the response in valid JSON.' CREATE_DUNGEON_LOCATIONS: '{context}\n For the supplied list of rooms, generate a name and a one line description for each room, keeping the theme from the Zone. Use the information supplied inside the tags to create a unique and interesting locations. Do not include any creatures. Depth indicates how deep in the dungeon it is. Respond with an array of locations in JSON format and do not write anything else: [{dungeon_location_template},{dungeon_location_template},...].' diff --git a/tale/base.py b/tale/base.py index 6eeef63f..49dc9fc3 100644 --- a/tale/base.py +++ b/tale/base.py @@ -1602,7 +1602,7 @@ def wielding(self, weapon: Optional[Weapon]) -> None: self.tell_others("{Actor} unwields %s." % self.__wielding.title, evoke=True, short_len=True) self.tell("You unwield %s." % self.__wielding.title) - def set_wearable(self, wearable: Optional[Wearable], wear_location: Optional[wearable.WearLocation] = wearable.WearLocation.TORSO) -> None: + def set_wearable(self, wearable: Optional[Wearable], wear_location: Optional[wearable.WearLocation] = None) -> None: """ Wear an item if item is not None, else unwear location""" if wearable: loc = wear_location if wear_location else wearable.wear_location diff --git a/tale/equip_npcs.py b/tale/equip_npcs.py index 9d8a3bda..dd7782b8 100644 --- a/tale/equip_npcs.py +++ b/tale/equip_npcs.py @@ -97,5 +97,5 @@ def dress_npc(npc: LivingNpc, setting: str = 'fantasy', max_attempts = 5) -> Non max_attempts -= 1 continue npc.insert(wearable_item, npc) - npc.set_wearable(wearable_item, bodypart) + npc.set_wearable(wearable_item, wear_location=wearable_item.wear_location) return \ No newline at end of file diff --git a/tale/wearable.py b/tale/wearable.py index 31cd2ac5..0d47276f 100644 --- a/tale/wearable.py +++ b/tale/wearable.py @@ -42,7 +42,7 @@ def random_wearable_for_body_part(bodypart: WearLocation, setting: str = 'fantas # TODO: Fix name wearable = wearables[wearable_name] wearable['name'] = wearable_name - wearable['short_descr'] = f"A {wearable['name']} in {random.choice(wearable_colors)}" + wearable['short_descr'] = f"{random.choice(wearable_colors)} {wearable['name']}" return wearable wearable_colors = ['black', 'green', 'blue', 'red', 'yellow', 'white', 'brown', 'grey', 'purple', 'orange', 'pink', 'cyan', 'magenta']