Skip to content

Commit 60a5533

Browse files
authored
Save story (#46)
* load and save story through json * fix teaparty and tests * fix one more test * revert change * actual save_story command load npc stats
1 parent 9bed64a commit 60a5533

File tree

20 files changed

+660
-123
lines changed

20 files changed

+660
-123
lines changed

stories/teaparty/world.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"story": {
3+
"name": "Test Story Config 1"
4+
},
5+
"zones": {
6+
"Mad Hatters House": {
7+
"name": "Mad Hatters House",
8+
"description": "The Mad Hatter's house. It's a bit of a mess.",
9+
"level": 1,
10+
"mood": 3,
11+
"locations":[
12+
{
13+
"name": "Living Room",
14+
"descr": "The Mad Hatter's cluttered living room. It's set up for a tea party.",
15+
"exits": [],
16+
"world_location": [
17+
"0",
18+
"0",
19+
"0"
20+
]
21+
}
22+
],
23+
"races": [],
24+
"items": []
25+
}
26+
},
27+
"world": {
28+
"creatures": {
29+
"Mad Hatter": {
30+
"name":"Mad Hatter",
31+
"gender":"m",
32+
"race":"human",
33+
"type":"LivingExt",
34+
"title":"Mad Hatter",
35+
"personality":"The Mad Hatter from Lewis Carrol's Alice in Wonderland. He is a bit crazy and has a strange sense of humor.",
36+
"descr":"Wearing a big hat and a colorful costume.",
37+
"short_descr":"A man with a big hat and colorful costume.",
38+
"location":"Living Room"
39+
},
40+
"Duchess": {
41+
"name":"Duchess",
42+
"gender":"f",
43+
"race":"human",
44+
"type":"LivingExt",
45+
"title":"Duchess",
46+
"personality":"The Duchess from Lewis Carrol's Alice in Wonderland. Volatile and aggressive personality.",
47+
"descr":"Very big head. Tall enough to rest her chin upon Alice’s shoulder, uncomfortably sharp chin.",
48+
"short_descr": "Short woman with a very large head.",
49+
"location":"Living Room"
50+
},
51+
"Ace of Spades": {
52+
"name":"Ace of Spades",
53+
"gender":"m",
54+
"race":"human",
55+
"type":"LivingExt",
56+
"title":"Ace of Spades",
57+
"personality":"A drunken ex-rock star. Like a talkative Ozzy Osbourne if he were in Alice in Wonderland.",
58+
"descr":"Thin man with eyeliner and a vacant expression. Wearing a black leather jacket and a black top hat.",
59+
"short_descr": "A man with leather jacket and a black top hat.",
60+
"location":"Living Room"
61+
}
62+
},
63+
"items": {},
64+
"locations": []
65+
}
66+
}

tale/cmds/normal.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import random
1111
from typing import Iterable, List, Dict, Generator, Union, Optional
1212

13+
from tale.llm.llm_ext import DynamicStory
14+
1315
from . import abbreviations, cmd, disabled_in_gamemode, disable_notify_action, overrides_soul, no_soul_parse
1416
from .. import base
1517
from .. import lang
@@ -1734,4 +1736,13 @@ def do_wear(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None
17341736
result = player.locate_item(item, include_location=False)
17351737
if not result:
17361738
raise ActionRefused("You don't have that item")
1737-
player.set_wearable(result[0], wear_location=wear_location)
1739+
player.set_wearable(result[0], wear_location=wear_location)
1740+
1741+
@cmd("save_story")
1742+
def do_save(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
1743+
"""Save the current story to file."""
1744+
story = ctx.driver.story
1745+
if isinstance(story, DynamicStory):
1746+
story.save()
1747+
else:
1748+
raise ActionRefused("Not a dynamic story")

tale/coord.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11

22

33
class Coord():
4+
""" Represents a coordinate in 3D space."""
5+
46
def __init__(self, x=0, y=0, z=0):
57
self.x = x
68
self.y = y
79
self.z = z
810

11+
#def __dict__(self):
12+
# return {'x': self.x, 'y': self.y, 'z': self.z}
13+
14+
def as_tuple(self):
15+
return (self.x, self.y, self.z)
16+
917
@classmethod
1018
def from_coord(self, coord: 'Coord'):
1119
return Coord(coord.x, coord.y, coord.z)
@@ -28,8 +36,11 @@ def add(self, other) -> 'Coord':
2836

2937
def multiply(self, value: int) -> 'Coord':
3038
""" Returns the coordinate resulting from multiplying this coordinate by value."""
31-
return Coord(self.x * value, self.y * value, self.z * value)
32-
39+
return Coord(self.x * value, self.y * value, self.z * value)
3340

3441
def __eq__(self, other):
3542
return self.x == other.x and self.y == other.y and self.z == other.z
43+
44+
def valid(self) -> bool:
45+
return self.z != 255
46+

tale/driver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,8 +628,8 @@ def go_through_exit(self, player: player.Player, direction: str, evoke: bool=Tru
628628
new_locations, exits = self.llm_util.build_location(location=xt.target,
629629
exit_location_name=player.location.name,
630630
zone_info=new_zone.get_info(),
631-
world_creatures=dynamic_story.world_creatures,
632-
world_items=dynamic_story.world_items,
631+
world_creatures=dynamic_story.world.creatures,
632+
world_items=dynamic_story.world.items,
633633
neighbors=neighbor_locations,)
634634
if new_locations:
635635
break

tale/json_story.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@ def init(self, driver) -> None:
1919
self.driver = driver
2020
locs = {}
2121
zones = []
22-
for zone in self.config.zones:
23-
zones, exits = parse_utils.load_locations(parse_utils.load_json(self.path +'zones/'+zone + '.json'))
22+
world = parse_utils.load_json(self.path +'world.json')
23+
for zone in world['zones'].values():
24+
zones, exits = parse_utils.load_locations(zone)
2425
if len(zones) < 1:
2526
print("No zones found in story config")
2627
return
2728
for name in zones.keys():
2829
zone = zones[name]
29-
for loc in zone.locations.values():
30-
locs[loc.name] = loc
3130
self.add_zone(zone)
32-
self._locations = locs
31+
for loc in zone.locations.values():
32+
self.add_location(loc, name)
3333

34-
if self.config.npcs:
35-
self._world["creatures"] = parse_utils.load_npcs(parse_utils.load_json(self.path +'npcs/'+self.config.npcs + '.json'), self._zones)
36-
if self.config.items:
37-
self._world["items"] = parse_utils.load_items(parse_utils.load_json(self.path + self.config.items + '.json'), self._zones)
34+
if world['world']['creatures']:
35+
self._world.creatures = parse_utils.load_npcs(world['world']['creatures'].values(), self.locations)
36+
if world['world']['items']:
37+
self._world.items = parse_utils.load_items(world['world']['items'].values(), self.locations)
3838

3939

4040
def welcome(self, player: Player) -> str:

tale/llm/llm_ext.py

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import typing
23
from tale import parse_utils
34
from tale.base import Item, Living, Location
45
from tale.coord import Coord
@@ -10,10 +11,7 @@ class DynamicStory(StoryBase):
1011

1112
def __init__(self) -> None:
1213
self._zones = dict() # type: dict[str, Zone]
13-
self._world = dict() # type: dict[str, any]
14-
self._world["creatures"] = dict()
15-
self._world["items"] = dict()
16-
self._world["locations"] = dict() # type: dict[Coord, Location]
14+
self._world = WorldInfo()
1715

1816
def get_zone(self, name: str) -> Zone:
1917
""" Find a zone by name."""
@@ -48,7 +46,9 @@ def add_location(self, location: Location, zone: str = '') -> bool:
4846
""" Add a location to the story.
4947
If zone is specified, add to that zone, otherwise add to first zone.
5048
"""
51-
self._world["locations"][location.world_location] = location
49+
self._world._locations[location.name] = location
50+
coord = location.world_location
51+
self._world._grid[coord.as_tuple()] = location
5252
if zone:
5353
return self._zones[zone].add_location(location)
5454
for zone in self._zones:
@@ -68,44 +68,77 @@ def zone_info(self, zone_name: str = '', location: str = '') -> dict():
6868
return zone.get_info()
6969

7070
def get_npc(self, npc: str) -> Living:
71-
return self._world["creatures"][npc]
71+
return self._world.creatures[npc]
7272

7373
def get_item(self, item: str) -> Item:
74-
return self._world["items"][item]
74+
return self._world.items[item]
75+
76+
@property
77+
def locations(self) -> dict:
78+
return self._world._locations
79+
80+
@property
81+
def world(self) -> 'WorldInfo':
82+
return self._world
7583

7684

7785
def neighbors_for_location(self, location: Location) -> dict:
7886
""" Return a dict of neighboring locations for a given location."""
7987
neighbors = dict() # type: dict[str, Location]
8088
for dir in ['north', 'east', 'south', 'west', 'up', 'down']:
81-
neighbors[dir] = self._world["locations"][Coord(location.world_location.add(parse_utils.coordinates_from_direction(dir)))]
89+
neighbors[dir] = self._world._grid[Coord(location.world_location.add(parse_utils.coordinates_from_direction(dir))).as_tuple()]
8290
return neighbors
8391

84-
@property
85-
def world_creatures(self) -> dict:
86-
return self._world["creatures"]
87-
88-
@world_creatures.setter
89-
def world_creatures(self, value: dict):
90-
self._world["creatures"] = value
91-
92-
@property
93-
def world_items(self) -> dict:
94-
return self._world["items"]
95-
96-
@world_items.setter
97-
def world_items(self, value: dict):
98-
self._world["items"] = value
99-
10092
def save(self) -> None:
10193
""" Save the story to disk."""
10294
story = dict()
95+
story["story"] = dict()
96+
story["story"]["name"] = self.config.name
97+
10398
story["zones"] = dict()
104-
story["world"] = self._world
99+
story["world"] = self._world.to_json()
105100
for zone in self._zones.values():
106101
story["zones"][zone.name] = zone.get_info()
102+
story["zones"][zone.name]["name"] = zone.name
103+
story["zones"][zone.name]["locations"] = parse_utils.save_locations(zone.locations.values())
104+
print(story)
105+
with open('world.json', "w") as fp:
106+
json.dump(story , fp, indent=4)
107107

108-
with open(self.config.name, "w") as fp:
109-
json.dump(story , fp)
108+
with open('story_config.json', "w") as fp:
109+
json.dump(parse_utils.save_story_config(self.config) , fp, indent=4)
110110

111111

112+
class WorldInfo():
113+
114+
def __init__(self) -> None:
115+
self._creatures = dict()
116+
self._items = dict()
117+
self._locations = dict() # type: dict[str, Location]
118+
self._grid = dict() # type: dict[Coord, Location]
119+
120+
@property
121+
def creatures(self) -> dict:
122+
return self._creatures
123+
124+
@creatures.setter
125+
def creatures(self, value: dict):
126+
self._creatures = value
127+
128+
@property
129+
def items(self) -> dict:
130+
return self._items
131+
132+
@items.setter
133+
def items(self, value: dict):
134+
self._items = value
135+
136+
def get_npc(self, npc: str) -> Living:
137+
return self._creatures[npc]
138+
139+
def get_item(self, item: str) -> Item:
140+
return self._items[item]
141+
142+
def to_json(self) -> dict:
143+
return dict(creatures=parse_utils.save_creatures(self._creatures.values()),
144+
items=parse_utils.save_items(self._items.values()))

tale/llm/llm_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ def generate_random_spawn(self, location: Location, zone_info: dict):
168168
story_type=self.__story.config.type,
169169
story_context=self.__story.config.context,
170170
world_info=self.__story.config.world_info,
171-
world_creatures=self.__story.world_creatures,
172-
world_items=self.__story.world_items)
171+
world_creatures=self.__story.world.creatures,
172+
world_items=self.__story.world.items)
173173

174174
def set_story(self, story: DynamicStory):
175175
""" Set the story object."""

0 commit comments

Comments
 (0)