Skip to content

Commit 6a19be8

Browse files
authored
Merge pull request #98 from neph1/update-v0.37.0
Update v0.37.0
2 parents 4212f07 + f09b284 commit 6a19be8

15 files changed

+96
-50
lines changed

backend_kobold_cpp.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ JSON_GRAMMAR_KEY: "grammar"
55
STREAM_ENDPOINT: "/api/extra/generate/stream"
66
DATA_ENDPOINT: "/api/extra/generate/check"
77
DEFAULT_BODY: '{"stop_sequence": "", "max_length":1500, "max_context_length":4096, "temperature":0.5, "top_k":120, "top_a":0.0, "top_p":0.85, "typical_p":1.0, "tfs":1.0, "rep_pen":1.2, "rep_pen_range":256, "sampler_order":[6,0,1,3,4,2,5], "seed":-1}'
8-
GENERATION_BODY: '{"stop_sequence": "", "max_length":1500, "max_context_length":4096, "temperature":1.0, "top_k":120, "top_a":0.0, "top_p":0.85, "typical_p":1.0, "tfs":1.0, "rep_pen":1.2, "rep_pen_range":256, "sampler_order":[6,0,1,3,4,2,5], "seed":-1}'
8+
GENERATION_BODY: '{"stop_sequence": "", "max_length":1500, "max_context_length":4096, "temperature":1.0, "top_k":120, "top_a":0.0, "top_p":0.85, "typical_p":1.0, "tfs":1.0, "rep_pen":1.2, "rep_pen_range":256, "sampler_order":[6,0,1,3,4,2,5], "seed":-1}'
9+
API_PASSWORD: "" # if koboldcpp is run with the --password flag, this must be set to the same password

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"]
1919
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. Always follow the instructions given, never acknowledge the task or speak directly to the user or respond with anything besides the request.'
2020
BASE_PROMPT: '<context>{context}</context>\n[USER_START] Rewrite [{input_text}] in your own words. The information inside the <context> tags should be used to ensure it fits the story. 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: '<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}'
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 describe their health status without mentioning numbers: 1.0 is highest, 0.0 is dead. <combat result> {input_text}</combat result>'
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 the blanks 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ def __init__(self, name: str, title: str = "", *, descr: str = "", short_descr:
440440
self.rent = 0.0 # price to keep in store / day
441441
self.weight = 0.0 # some abstract unit
442442
self.takeable = True # can this item be taken/picked up?
443+
self.durability = 100 # how long can this item last?
443444
super().__init__(name, title=title, descr=descr, short_descr=short_descr)
444445

445446
def init(self) -> None:
@@ -459,6 +460,7 @@ def to_dict(self) -> Dict[str, Any]:
459460
"rent": self.rent,
460461
"weight": self.weight,
461462
"takeable": self.takeable,
463+
"durability": self.durability,
462464
"location" : self.location.name if self.location else ''
463465
}
464466

tale/cmds/wizard.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -904,9 +904,3 @@ def do_create_item(player: Player, parsed: base.ParseResult, ctx: util.Context)
904904
player.tell(item.name + ' added.', evoke=False)
905905
else:
906906
raise ParseError("Item could not be added")
907-
908-
@wizcmd("restart_story")
909-
def do_restart(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
910-
"""Restart the game."""
911-
player.tell("Restarting the game... Please reconnect")
912-
os.execv(sys.executable, ['python3'] + sys.argv)

tale/combat.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ def _calculate_armor_bonus(self, actor: 'base.Living', body_part: WearLocation =
5252
return wearable.ac if wearable else 1
5353
return actor.stats.ac + 1
5454

55+
def _subtract_armor_durability(self, actor: 'base.Living', body_part: WearLocation, amount: int):
56+
wearable = actor.get_wearable(body_part)
57+
if wearable:
58+
wearable.durability -= amount
59+
5560
def resolve_body_part(self, defender: 'base.Living', size_factor: float, target_part: WearLocation = None) -> WearLocation:
5661
""" Resolve the body part that was hit. """
5762
body_parts = body_parts_for_bodytype(defender.stats.bodytype)
@@ -94,49 +99,55 @@ def resolve_attack(self) -> str:
9499
for attacker in self.attackers:
95100
random_defender = random.choice(self.defenders)
96101
text_result, damage_to_defender = self._round(attacker, random_defender)
97-
texts.extend(text_result)
102+
texts.append(text_result)
98103
random_defender.stats.hp -= damage_to_defender
99104
if random_defender.stats.hp < 1:
100-
texts.append(f'{random_defender.title} dies')
105+
texts.append(f'{random_defender.title} dies from their injuries.')
101106

102107
for defender in self.defenders:
108+
if defender.stats.hp < 1:
109+
continue
103110
random_attacker = random.choice(self.attackers)
104111
text_result, damage_to_attacker = self._round(defender, random_attacker)
105-
texts.extend(text_result)
112+
texts.append(text_result)
106113

107114
random_attacker.stats.hp -= damage_to_attacker
108115
if random_attacker.stats.hp < 1:
109-
texts.append(f'{random_attacker.title} dies')
116+
texts.append(f'{random_attacker.title} dies from their injuries.')
110117

111-
return ', '.join(texts)
118+
return '\n'.join(texts)
112119

113120
def _round(self, actor1: 'base.Living', actor2: 'base.Living') -> Tuple[List[str], int]:
114121
attack_result = self._calculate_attack_success(actor1)
115-
texts = []
122+
attack_text = f'{actor1.title} attacks {actor2.title} with their {actor1.wielding.name}'
116123
if attack_result < 0:
117124
if attack_result < -actor1.stats.weapon_skills.get(actor1.wielding.type) + 5:
118-
texts.append(f'{actor1.title} performs a critical hit on {actor2.title}')
125+
attack_text += ' and hits critically'
119126
block_result = 100
120127
else:
121-
texts.append(f'{actor1.title} hits {actor2.title}')
128+
attack_text += ' and hits'
122129
block_result = self._calculate_block_success(actor1, actor2)
123-
130+
124131
if block_result < 0:
125-
texts.append(f'but {actor2.title} blocks')
132+
attack_text += f', but {actor2.title} blocks with their {actor2.wielding.name}'
133+
actor2.wielding.durability -= random.randint(1, 10)
126134
else:
127-
actor1_strength = self._calculate_weapon_bonus(actor1) * actor1.stats.size.order
135+
actor1_attack = self._calculate_weapon_bonus(actor1) * actor1.stats.size.order
128136
body_part = self.resolve_body_part(actor2, actor1.stats.size.order / actor2.stats.size.order, target_part=self.target_body_part)
129-
actor2_strength = self._calculate_armor_bonus(actor2, body_part) * actor2.stats.size.order
130-
damage_to_defender = int(max(0, actor1_strength - actor2_strength))
137+
actor2_defense = self._calculate_armor_bonus(actor2, body_part) * actor2.stats.size.order
138+
damage_to_defender = int(max(0, actor1_attack - actor2_defense))
131139
if damage_to_defender > 0:
132-
texts.append(f', {actor2.title} is injured in the {body_part.name.lower()}')
140+
attack_text += f', and {actor2.title} is injured in the {body_part.name.lower()}.'
141+
elif actor1_attack < actor2_defense:
142+
attack_text += f', but {actor2.title}\' armor protects them.'
143+
self._subtract_armor_durability(actor2, body_part, actor1_attack)
133144
else:
134-
texts.append(f', {actor2.title} is unharmed')
135-
return texts, damage_to_defender
145+
attack_text += f', but {actor2.title} is unharmed.'
146+
return attack_text, damage_to_defender
136147
elif attack_result > 50:
137-
texts.append(f'{actor1.title} misses {actor2.title} completely')
148+
attack_text + f', but misses completely.'
138149
elif attack_result > 25:
139-
texts.append(f'{actor1.title} misses {actor2.title}')
150+
attack_text + f', but misses.'
140151
else:
141-
texts.append(f'{actor1.title} barely misses {actor2.title}')
142-
return texts, 0
152+
attack_text + f', and barely misses.'
153+
return attack_text, 0

tale/json_story.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from tale import load_items
1+
from tale import load_items, wearable
22
from tale.items import generic
33
from tale.llm.dynamic_story import DynamicStory
44
from tale.player import Player
@@ -34,6 +34,8 @@ def init(self, driver) -> None:
3434
self._catalogue._creatures = world['catalogue']['creatures']
3535
if world['catalogue']['items']:
3636
self._catalogue._items = world['catalogue']['items']
37+
if world['catalogue'].get('wearables', None):
38+
wearable.add_story_wearables(world['catalogue']['wearables'])
3739
if world.get('world', None):
3840
if world['world']['items']:
3941
# Keep this so that saved items in worlds will transfer to locations. But don't save them.

tale/llm/io_adapters.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def __init__(self, url: str, stream_endpoint: str, user_start_prompt: str = '',
2222
self.prompt_end = prompt_end
2323

2424
@abstractmethod
25-
def stream_request(self, request_body: dict, io = None, wait: bool = False) -> str:
25+
def stream_request(self, headers: dict, request_body: dict, io = None, wait: bool = False) -> str:
2626
pass
2727

2828
@abstractmethod
@@ -44,8 +44,8 @@ def __init__(self, url: str, stream_endpoint: str, data_endpoint: str, user_star
4444
self.data_endpoint = data_endpoint
4545
self.place_context_in_memory = False
4646

47-
def stream_request(self, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str:
48-
result = asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, request_body))
47+
def stream_request(self, headers: dict, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str:
48+
result = asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, headers, request_body))
4949

5050
try:
5151
if result:
@@ -54,9 +54,10 @@ def stream_request(self, request_body: dict, io: PlayerConnection = None, wait:
5454
print("Error parsing response from backend - ", exc)
5555
return ''
5656

57-
async def _do_stream_request(self, url: str, request_body: dict,) -> bool:
57+
async def _do_stream_request(self, url: str, headers: dict, request_body: dict,) -> bool:
5858
""" Send request to stream endpoint async to not block the main thread"""
5959
async with aiohttp.ClientSession() as session:
60+
session.headers.update(headers)
6061
async with session.post(url, data=json.dumps(request_body)) as response:
6162
if response.status == 200:
6263
return True
@@ -103,14 +104,15 @@ def set_prompt(self, request_body: dict, prompt: str, context: str = '') -> dict
103104

104105
class LlamaCppAdapter(AbstractIoAdapter):
105106

106-
def stream_request(self, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str:
107-
return asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, request_body, io = io))
107+
def stream_request(self, headers: dict, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str:
108+
return asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, headers, request_body, io = io))
108109

109-
async def _do_stream_request(self, url: str, request_body: dict, io: PlayerConnection) -> str:
110+
async def _do_stream_request(self, url: str, headers: dict, request_body: dict, io: PlayerConnection) -> str:
110111
""" Send request to stream endpoint async to not block the main thread"""
111112
request_body['stream'] = True
112113
text = ''
113114
async with aiohttp.ClientSession() as session:
115+
session.headers.update(headers)
114116
async with session.post(url, data=json.dumps(request_body)) as response:
115117
if response.status != 200:
116118
print("Error occurred:", response.status)

tale/llm/llm_io.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ def __init__(self, config: dict = None, backend_config: dict = None):
1313
self.backend = config['BACKEND']
1414
self.url = backend_config['URL']
1515
self.endpoint = backend_config['ENDPOINT']
16+
headers = {}
1617
if self.backend != 'kobold_cpp':
1718
headers = json.loads(backend_config['OPENAI_HEADERS'])
1819
headers['Authorization'] = f"Bearer {backend_config['OPENAI_API_KEY']}"
1920
self.openai_json_format = json.loads(backend_config['OPENAI_JSON_FORMAT'])
2021
self.headers = headers
2122
self.io_adapter = LlamaCppAdapter(self.url, backend_config['STREAM_ENDPOINT'], config.get('USER_START', ''), config.get('USER_END', ''), config.get('SYSTEM_START', ''), config.get('PROMPT_END', ''))
2223
else:
24+
if 'API_PASSWORD' in backend_config and backend_config['API_PASSWORD']:
25+
headers['Authorization'] = f"Bearer {backend_config['API_PASSWORD']}"
26+
self.headers = headers
2327
self.io_adapter = KoboldCppAdapter(self.url, backend_config['STREAM_ENDPOINT'], backend_config['DATA_ENDPOINT'], config.get('USER_START', ''), config.get('USER_END', ''), config.get('SYSTEM_START', ''), config.get('PROMPT_END', ''))
24-
self.headers = {}
2528

2629
self.stream = backend_config['STREAM']
2730

@@ -46,7 +49,7 @@ def asynchronous_request(self, request_body: dict, prompt: str, context: str = '
4649
def stream_request(self, request_body: dict, prompt: str, context: str = '', io = None, wait: bool = False) -> str:
4750
if self.io_adapter:
4851
request_body = self.io_adapter.set_prompt(request_body, prompt, context)
49-
return self.io_adapter.stream_request(request_body, io, wait)
52+
return self.io_adapter.stream_request(self.headers, request_body, io, wait)
5053
# fall back if no io adapter
5154
return self.synchronous_request(request_body=request_body, prompt=prompt, context=context)
5255

tale/wearable.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,8 @@ def load_wearables_from_json(file_path):
3535

3636
wearables_fantasy = load_wearables_from_json('../items/wearables_fantasy.json')
3737
wearables_modern = load_wearables_from_json('../items/wearables_modern.json')
38+
wearbles_story = []
3839

39-
# Disclaimer: Not to limit the player, but to give the generator some hints
40-
female_clothing_modern = {'dress', 'dress_shirt', 'blouse', 'skirt', 'bra', 'panties', 'thong', 'stockings', 'top'}
41-
male_clothing_modern = {'suit', 'boxers', 'briefs', 'shirt'}
42-
neutral_clothing_modern = {'t-shirt', 'shirt', 'jeans', 'sneakers', 'belt', 'dress_shoes', 'hat', 'coveralls', 'sweater', 'socks', 'coat', 'jacket'}
43-
44-
45-
4640
dressable_body_types = [BodyType.HUMANOID, BodyType.SEMI_BIPEDAL, BodyType.WINGED_MAN]
4741

4842
def body_parts_for_bodytype(bodytype: BodyType) -> list:
@@ -53,13 +47,18 @@ def body_parts_for_bodytype(bodytype: BodyType) -> list:
5347
return None
5448

5549
def random_wearable_for_body_part(bodypart: WearLocation, setting: str = 'fantasy', armor_only = False) -> dict:
50+
wearables = []
5651
if setting == 'fantasy':
5752
wearables = wearables_fantasy
58-
else:
53+
elif setting == 'modern' or setting == 'sci-fi' or setting == 'post-apocalyptic':
5954
wearables = wearables_modern
55+
wearables.extend(wearbles_story)
6056
available_wearables = [item for item in wearables if item['location'] == bodypart and (not armor_only or item.get('ac', 0) > 0)]
6157
if not available_wearables:
6258
return None
6359
wearable = random.choice(available_wearables)
6460
wearable['short_descr'] = f"{random.choice(wearable_colors)} {wearable['name']}"
6561
return wearable
62+
63+
def add_story_wearables(wearables: list):
64+
wearbles_story.extend(wearables)

tests/files/world_story/world.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@
202202
"descr": "A wolf",
203203
"type": "Mob"
204204
}
205+
],
206+
"wearables": [
207+
{
208+
"name": "fur jacket",
209+
"short_descr": "A fur jacket",
210+
"descr": "A warm fur jacket",
211+
"location": "TORSO",
212+
"type": "Wearable",
213+
"ac": 1
214+
}
205215
]
206216
}
207217
}

0 commit comments

Comments
 (0)