Skip to content

Commit 8c79743

Browse files
authored
Merge pull request #81 from neph1/update-v0.29.1
Update v0.29.1
2 parents 19f2b37 + 2a19dfb commit 8c79743

File tree

7 files changed

+116
-88
lines changed

7 files changed

+116
-88
lines changed

llm_config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ ITEM_TYPES: ["Weapon", "Wearable", "Health", "Money", "Trash", "Food", "Drink",
1919
PRE_PROMPT: 'You are a creative game keeper for a role playing game (RPG). You craft detailed worlds and interesting characters with unique and deep personalities for the player to interact with.'
2020
BASE_PROMPT: '<context>{context}</context>\n[USER_START]Rewrite [{input_text}] in your own words using the information found inside the <context> tags to create a background for your text. Use about {max_words} words.'
2121
DIALOGUE_PROMPT: '<context>{context}</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 <context> 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}'
22-
COMBAT_PROMPT: 'The following is a combat scene between user {attacker} and {victim} in {location} into a vivid description. [USER_START] Rewrite the following combat result in about 150 words, using the characters weapons and their health status: 1.0 is highest, 0.0 is lowest. Combat Result: {input_text}'
22+
COMBAT_PROMPT: '<context>{context}</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}'
2323
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.'
2424
CREATE_CHARACTER_PROMPT: '<context>{context}</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}'
2525
CREATE_LOCATION_PROMPT: '<context>{context}</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 <context> 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.'

tale/base.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,39 +1423,43 @@ def start_attack(self, defender: 'Living', target_body_part: wearable.WearLocati
14231423
"""Starts attacking the given living for one round."""
14241424
attacker_name = lang.capital(self.title)
14251425
victim_name = lang.capital(defender.title)
1426-
c = combat.Combat(self, defender, target_body_part=target_body_part)
1427-
result, damage_to_attacker, damage_to_defender = c.resolve_attack()
1426+
attackers = [self]
1427+
defenders = [defender]
1428+
for living in defender.location.livings:
1429+
if living.following is self:
1430+
attackers.append(living)
1431+
elif living.following is defender:
1432+
defenders.append(living)
1433+
1434+
c = combat.Combat(attackers, defenders, target_body_part=target_body_part)
1435+
result = c.resolve_attack()
14281436

14291437
room_msg = "%s attacks %s! %s" % (attacker_name, victim_name, result)
14301438
victim_msg = "%s attacks you. %s" % (attacker_name, result)
14311439
attacker_msg = "You attack %s! %s" % (victim_name, result)
14321440
#victim.tell(victim_msg, evoke=True, short_len=False)
14331441

1434-
combat_prompt, attacker_msg = mud_context.driver.prepare_combat_prompt(attacker=self,
1435-
defender=defender,
1442+
combat_prompt, attacker_msg = mud_context.driver.prepare_combat_prompt(attackers=attackers,
1443+
defenders=defenders,
14361444
location_title = self.location.title,
14371445
combat_result = result,
14381446
attacker_msg = attacker_msg)
14391447

1440-
combat_context = CombatContext(attacker_name=self.name,
1441-
attacker_health=self.stats.hp / self.stats.max_hp,
1442-
attacker_weapon=self.wielding.name,
1443-
defender_name=defender.name,
1444-
defender_health=defender.stats.hp / defender.stats.max_hp,
1445-
defender_weapon=defender.wielding.name,
1448+
combat_context = CombatContext(attackers=attackers,
1449+
defenders=defenders,
14461450
location_description=self.location.description)
14471451

14481452
defender.location.tell(room_msg,
14491453
evoke=True,
14501454
short_len=False,
14511455
alt_prompt=combat_prompt,
14521456
extra_context=combat_context.to_prompt_string())
1453-
self.stats.hp -= damage_to_attacker
1454-
defender.stats.hp -= damage_to_defender
1455-
if self.stats.hp < 1:
1456-
mud_context.driver.defer(0.1, self.do_on_death)
1457-
if defender.stats.hp < 1:
1458-
mud_context.driver.defer(0.1, defender.do_on_death)
1457+
for attacker in attackers:
1458+
if attacker.stats.hp < 1:
1459+
mud_context.driver.defer(0.1, attacker.do_on_death)
1460+
for defender in defenders:
1461+
if defender.stats.hp < 1:
1462+
mud_context.driver.defer(0.1, defender.do_on_death)
14591463
return c
14601464

14611465
def allow_give_money(self, amount: float, actor: Optional['Living']) -> None:

tale/combat.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import random
8+
from typing import List, Tuple
89
from tale import weapon_type
910
import tale.base as base
1011
from tale.wearable import WearLocation, body_parts_for_bodytype
@@ -15,9 +16,9 @@
1516

1617
class Combat():
1718

18-
def __init__(self, attacker: 'base.Living', defender: 'base.Living', target_body_part: WearLocation = None) -> None:
19-
self.attacker = attacker
20-
self.defender = defender
19+
def __init__(self, attackers: List['base.Living'], defenders: List['base.Living'], target_body_part: WearLocation = None) -> None:
20+
self.attackers = attackers
21+
self.defenders = defenders
2122
self.target_body_part = target_body_part
2223

2324
def _calculate_attack_success(self, actor: 'base.Living') -> int:
@@ -83,37 +84,41 @@ def create_probability_distribution(self, locations, size_factor: float = 1.0, t
8384
normalized_distribution = {location: probability / total_probability for location, probability in probability_distribution.items()}
8485
return normalized_distribution
8586

86-
def resolve_attack(self) -> (str, int, int):
87+
def resolve_attack(self) -> str:
8788
""" Both attacker and defender attack each other once.
8889
They get a chance to block, unless it's a critical hit, 5/100.
8990
Returns a textual representation of the combat and the damage done to each actor.
9091
"""
9192
texts = []
92-
damage_to_defender = 0
93-
damage_to_attacker = 0
9493

95-
text_result, damage_to_defender = self._round(self.attacker, self.defender)
96-
texts.extend(text_result)
94+
for attacker in self.attackers:
95+
random_defender = random.choice(self.defenders)
96+
text_result, damage_to_defender = self._round(attacker, random_defender)
97+
texts.extend(text_result)
98+
random_defender.stats.hp -= damage_to_defender
99+
if random_defender.stats.hp < 1:
100+
texts.append(f'{random_defender.title} dies')
97101

98-
text_result, damage_to_attacker = self._round(self.defender, self.attacker)
99-
texts.extend(text_result)
102+
for defender in self.defenders:
103+
random_attacker = random.choice(self.attackers)
104+
text_result, damage_to_attacker = self._round(defender, random_attacker)
105+
texts.extend(text_result)
100106

101-
if self.defender.stats.hp - damage_to_defender < 0:
102-
texts.append(f'{self.defender.title} dies')
103-
if self.attacker.stats.hp - damage_to_attacker < 0:
104-
texts.append(f'{self.attacker.title} dies')
107+
random_attacker.stats.hp -= damage_to_attacker
108+
if random_attacker.stats.hp < 1:
109+
texts.append(f'{random_attacker.title} dies')
105110

106-
return ', '.join(texts), damage_to_attacker, damage_to_defender
111+
return ', '.join(texts)
107112

108-
def _round(self, actor1: 'base.Living', actor2: 'base.Living') -> ([str], int):
113+
def _round(self, actor1: 'base.Living', actor2: 'base.Living') -> Tuple[List[str], int]:
109114
attack_result = self._calculate_attack_success(actor1)
110115
texts = []
111116
if attack_result < 0:
112117
if attack_result < -actor1.stats.get_weapon_skill(actor1.wielding.type) + 5:
113-
texts.append(f'{actor1.title} performs a critical hit')
118+
texts.append(f'{actor1.title} performs a critical hit on {actor2.title}')
114119
block_result = 100
115120
else:
116-
texts.append(f'{actor1.title} hits')
121+
texts.append(f'{actor1.title} hits {actor2.title}')
117122
block_result = self._calculate_block_success(actor1, actor2)
118123

119124
if block_result < 0:
@@ -129,9 +134,9 @@ def _round(self, actor1: 'base.Living', actor2: 'base.Living') -> ([str], int):
129134
texts.append(f', {actor2.title} is unharmed')
130135
return texts, damage_to_defender
131136
elif attack_result > 50:
132-
texts.append(f'{actor1.title} misses completely')
137+
texts.append(f'{actor1.title} misses {actor2.title} completely')
133138
elif attack_result > 25:
134-
texts.append(f'{actor1.title} misses')
139+
texts.append(f'{actor1.title} misses {actor2.title}')
135140
else:
136-
texts.append(f'{actor1.title} barely misses')
141+
texts.append(f'{actor1.title} barely misses {actor2.title}')
137142
return texts, 0

tale/driver.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -885,29 +885,32 @@ def uptime(self) -> Tuple[int, int, int]:
885885
return int(hours), int(minutes), int(seconds)
886886

887887
def prepare_combat_prompt(self,
888-
attacker: base.Living,
889-
defender: base.Living,
888+
attackers: List[base.Living],
889+
defenders: List[base.Living],
890890
location_title: str,
891891
combat_result: str,
892892
attacker_msg: str):
893893
""" TODO: A bad work around. Need to find a better way to do this."""
894-
895-
attacker_name = lang.capital(attacker.title)
896-
victim_name = lang.capital(defender.title)
897-
898-
if isinstance(self, player.Player):
899-
attacker_msg.replace(attacker_name, "you")
900-
attacker_name += " (as 'You')"
901-
if isinstance(defender, player.Player):
902-
attacker_msg.replace(victim_name, "you")
903-
victim_name += " (as 'You')"
894+
attacker_names = []
895+
defender_titles = []
896+
for attacker in attackers:
897+
attacker_title = lang.capital(attacker.title)
898+
if isinstance(attacker, player.Player):
899+
attacker_title = attacker_title + " (as 'You')"
900+
attacker_names.append(attacker_title)
901+
902+
for defender in defenders:
903+
defender_title = defender.title
904+
if isinstance(defender, player.Player):
905+
defender_title = defender_title + " (as 'You')"
906+
defender_titles.append(defender_title)
904907

905908

906-
return self.llm_util.combat_prompt.format(attacker=attacker_name,
907-
victim=victim_name,
909+
return self.llm_util.combat_prompt.format(attackers=attacker_names,
910+
defenders=defender_titles,
908911
location=location_title,
909912
input_text=combat_result,
910-
context=''), attacker_msg
913+
context=self.story.config.context if self.story else ''), attacker_msg
911914

912915
def build_location(self, targetLocation: base.Location, zone: Zone, player: player.Player):
913916
dynamic_story = typing.cast(DynamicStory, self.story)

tale/llm/LivingNpc.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,6 @@ def notify_action(self, parsed: ParseResult, actor: Living) -> None:
8888
self._observed_events.append(event_hash)
8989
# TODO: should llm decide sentiment?
9090
self.sentiments[actor.title] = 'hostile'
91-
elif parsed.verb == 'attack' and self.following == actor:
92-
target = self.location.search_living(parsed.who_1) if parsed.who_1 else None
93-
if target:
94-
self.start_attack(target)
9591
elif parsed.verb == 'request_to_follow' and targeted:
9692
result = mud_context.driver.llm_util.request_follow(actor=actor,
9793
character_name=self.title,

tale/llm/contexts/CombatContext.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
class CombatContext():
1+
from typing import List
22

3-
def __init__(self, attacker_name: str, attacker_health: float, attacker_weapon: str, defender_name: str, defender_health: float, defender_weapon: str, location_description: str) -> None:
4-
self.victim_info = {"name": defender_name,
5-
"health": defender_health,
6-
"weapon": defender_weapon}
3+
from tale import base
4+
class CombatContext():
75

8-
self.attacker_info = {"name": attacker_name,
9-
"health": attacker_health,
10-
"weapon": attacker_weapon}
6+
def __init__(self, attackers: List['base.Living'], defenders: List['base.Living'], location_description: str) -> None:
7+
self.attackers = attackers
8+
self.defenders = defenders
119
self.location_description = location_description
1210

1311
def to_prompt_string(self) -> str:
14-
return f"Attacker: {self.attacker_info['name']} ({self.attacker_info['health']}); Defender: {self.victim_info['name']} ({self.victim_info['health']}); Location: {self.location_description}"
12+
attackers_info = []
13+
for attacker in self.attackers:
14+
attackers_info.append(f"{attacker.name}: Health:({str(attacker.stats.hp / attacker.stats.max_hp)}). Weapon: {attacker.wielding.name}.")
15+
defenders_info = []
16+
for defender in self.defenders:
17+
defenders_info.append(f"{defender.name}: Health:({str(defender.stats.hp / defender.stats.max_hp)}). Weapon: {defender.wielding.name}.")
18+
return f"Attackers: {attackers_info}; Defenders: {defenders_info}; Location: {self.location_description}."

0 commit comments

Comments
 (0)