synth-ai 0.2.4.dev4__py3-none-any.whl → 0.2.4.dev5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synth_ai/environments/examples/__init__.py +1 -0
- synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
- synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
- synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
- synth_ai/environments/examples/crafter_classic/engine.py +575 -0
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +266 -0
- synth_ai/environments/examples/crafter_classic/environment.py +364 -0
- synth_ai/environments/examples/crafter_classic/taskset.py +233 -0
- synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +229 -0
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +298 -0
- synth_ai/environments/examples/crafter_custom/__init__.py +4 -0
- synth_ai/environments/examples/crafter_custom/crafter/__init__.py +7 -0
- synth_ai/environments/examples/crafter_custom/crafter/config.py +182 -0
- synth_ai/environments/examples/crafter_custom/crafter/constants.py +8 -0
- synth_ai/environments/examples/crafter_custom/crafter/engine.py +269 -0
- synth_ai/environments/examples/crafter_custom/crafter/env.py +266 -0
- synth_ai/environments/examples/crafter_custom/crafter/objects.py +418 -0
- synth_ai/environments/examples/crafter_custom/crafter/recorder.py +187 -0
- synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +119 -0
- synth_ai/environments/examples/crafter_custom/dataset_builder.py +373 -0
- synth_ai/environments/examples/crafter_custom/environment.py +312 -0
- synth_ai/environments/examples/crafter_custom/run_dataset.py +305 -0
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
- synth_ai/environments/examples/enron/engine.py +291 -0
- synth_ai/environments/examples/enron/environment.py +165 -0
- synth_ai/environments/examples/enron/taskset.py +112 -0
- synth_ai/environments/examples/minigrid/__init__.py +48 -0
- synth_ai/environments/examples/minigrid/engine.py +589 -0
- synth_ai/environments/examples/minigrid/environment.py +274 -0
- synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
- synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
- synth_ai/environments/examples/minigrid/taskset.py +583 -0
- synth_ai/environments/examples/nethack/__init__.py +7 -0
- synth_ai/environments/examples/nethack/achievements.py +337 -0
- synth_ai/environments/examples/nethack/engine.py +738 -0
- synth_ai/environments/examples/nethack/environment.py +255 -0
- synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
- synth_ai/environments/examples/nethack/taskset.py +323 -0
- synth_ai/environments/examples/red/__init__.py +7 -0
- synth_ai/environments/examples/red/config_logging.py +110 -0
- synth_ai/environments/examples/red/engine.py +693 -0
- synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
- synth_ai/environments/examples/red/environment.py +235 -0
- synth_ai/environments/examples/red/taskset.py +77 -0
- synth_ai/environments/examples/sokoban/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine.py +675 -0
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
- synth_ai/environments/examples/sokoban/environment.py +228 -0
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
- synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
- synth_ai/environments/examples/sokoban/taskset.py +425 -0
- synth_ai/environments/examples/tictactoe/__init__.py +1 -0
- synth_ai/environments/examples/tictactoe/engine.py +368 -0
- synth_ai/environments/examples/tictactoe/environment.py +239 -0
- synth_ai/environments/examples/tictactoe/taskset.py +214 -0
- synth_ai/environments/examples/verilog/__init__.py +10 -0
- synth_ai/environments/examples/verilog/engine.py +328 -0
- synth_ai/environments/examples/verilog/environment.py +349 -0
- synth_ai/environments/examples/verilog/taskset.py +418 -0
- {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev5.dist-info}/METADATA +1 -1
- {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev5.dist-info}/RECORD +104 -6
- {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev5.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev5.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev5.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
import importlib
|
2
|
+
import numpy as np
|
3
|
+
from typing import Any, Dict
|
4
|
+
|
5
|
+
# Minimal attributes to serialize per object type
|
6
|
+
BASIC_ATTRS: Dict[str, list] = {
|
7
|
+
"Player": [
|
8
|
+
"pos",
|
9
|
+
"facing",
|
10
|
+
"health",
|
11
|
+
"inventory",
|
12
|
+
"achievements",
|
13
|
+
"action",
|
14
|
+
"sleeping",
|
15
|
+
"_last_health",
|
16
|
+
"_hunger",
|
17
|
+
"_thirst",
|
18
|
+
"_fatigue",
|
19
|
+
"_recover",
|
20
|
+
],
|
21
|
+
"Cow": ["pos", "health"],
|
22
|
+
"Zombie": ["pos", "health", "cooldown"],
|
23
|
+
"Skeleton": ["pos", "health", "reload"],
|
24
|
+
"Arrow": ["pos", "facing"],
|
25
|
+
"Plant": ["pos", "health", "grown"],
|
26
|
+
"Stone": ["pos"],
|
27
|
+
"Table": ["pos"],
|
28
|
+
"Furnace": ["pos"],
|
29
|
+
# Add other types as needed
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
def serialize_world_object(obj: Any) -> Dict[str, Any]:
|
34
|
+
"""Convert a crafter object into a JSON-friendly dict."""
|
35
|
+
cls_name = obj.__class__.__name__
|
36
|
+
fields = BASIC_ATTRS.get(cls_name, ["pos"])
|
37
|
+
payload: Dict[str, Any] = {}
|
38
|
+
for field in fields:
|
39
|
+
val = getattr(obj, field)
|
40
|
+
if isinstance(val, np.ndarray):
|
41
|
+
payload[field] = val.tolist()
|
42
|
+
else:
|
43
|
+
payload[field] = val
|
44
|
+
return {
|
45
|
+
"type": f"{obj.__class__.__module__}.{cls_name}",
|
46
|
+
"state": payload,
|
47
|
+
}
|
48
|
+
|
49
|
+
|
50
|
+
def deserialize_world_object(blob: Dict[str, Any], world: Any) -> Any:
|
51
|
+
"""Reconstruct a crafter object from its serialized dict."""
|
52
|
+
type_str = blob.get("type", "")
|
53
|
+
module_name, cls_name = type_str.rsplit(".", 1)
|
54
|
+
module = importlib.import_module(module_name)
|
55
|
+
cls = getattr(module, cls_name)
|
56
|
+
# Bypass __init__; create empty instance
|
57
|
+
state = blob.get("state", {})
|
58
|
+
obj = cls.__new__(cls)
|
59
|
+
# Initialize required base attributes
|
60
|
+
obj.world = world
|
61
|
+
obj.random = world.random
|
62
|
+
obj.removed = False
|
63
|
+
# Ensure inventory exists for health setter
|
64
|
+
obj.inventory = {"health": 0}
|
65
|
+
# Set attributes from state
|
66
|
+
for field, value in state.items():
|
67
|
+
if field == "pos":
|
68
|
+
# restore position as numpy array
|
69
|
+
obj.pos = np.array(value)
|
70
|
+
else:
|
71
|
+
# restore other attributes (including property setters)
|
72
|
+
# convert lists back to arrays only for known ndarray fields
|
73
|
+
setattr(obj, field, value)
|
74
|
+
return obj
|
@@ -0,0 +1,266 @@
|
|
1
|
+
"""
|
2
|
+
Enhanced monkey patch to add save/load functionality to crafter.Env
|
3
|
+
This version handles player references for Zombie and Skeleton objects.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import collections
|
7
|
+
import pickle
|
8
|
+
import numpy as np
|
9
|
+
import crafter
|
10
|
+
from crafter import objects
|
11
|
+
from typing import Dict, Any, Optional, Set
|
12
|
+
|
13
|
+
print("[PATCH] Attempting to apply Crafter serialization patch v3...")
|
14
|
+
|
15
|
+
# Check if already patched
|
16
|
+
if not hasattr(crafter.Env, "save"):
|
17
|
+
print("[PATCH] Adding enhanced save/load methods to crafter.Env...")
|
18
|
+
|
19
|
+
def _save(self) -> Dict[str, Any]:
|
20
|
+
"""Save complete environment state including all details."""
|
21
|
+
# Save complete world state
|
22
|
+
world_state = {
|
23
|
+
"area": self._world.area,
|
24
|
+
"daylight": self._world.daylight,
|
25
|
+
"_mat_map": self._world._mat_map.tolist(),
|
26
|
+
"_obj_map": self._world._obj_map.tolist(),
|
27
|
+
"_mat_names": dict(self._world._mat_names),
|
28
|
+
"_mat_ids": dict(self._world._mat_ids),
|
29
|
+
"_chunk_size": self._world._chunk_size,
|
30
|
+
"random_state": pickle.dumps(self._world.random.get_state()),
|
31
|
+
}
|
32
|
+
|
33
|
+
# Save all objects with complete state
|
34
|
+
objects_data = []
|
35
|
+
player_idx = None
|
36
|
+
|
37
|
+
for i, obj in enumerate(self._world._objects):
|
38
|
+
if obj is None:
|
39
|
+
objects_data.append(None)
|
40
|
+
else:
|
41
|
+
# Get all object attributes
|
42
|
+
obj_data = {
|
43
|
+
"type": f"{obj.__class__.__module__}.{obj.__class__.__name__}",
|
44
|
+
"_index": i,
|
45
|
+
"pos": obj.pos.tolist() if hasattr(obj, "pos") else None,
|
46
|
+
"removed": getattr(obj, "removed", False),
|
47
|
+
}
|
48
|
+
|
49
|
+
# Save ALL attributes based on object type
|
50
|
+
if isinstance(obj, objects.Player):
|
51
|
+
player_idx = i
|
52
|
+
obj_data.update(
|
53
|
+
{
|
54
|
+
"facing": list(obj.facing) if hasattr(obj, "facing") else None,
|
55
|
+
"health": obj.health,
|
56
|
+
"inventory": dict(obj.inventory),
|
57
|
+
"achievements": dict(obj.achievements),
|
58
|
+
"_hunger": obj._hunger,
|
59
|
+
"_thirst": obj._thirst,
|
60
|
+
"_fatigue": obj._fatigue,
|
61
|
+
"_recover": obj._recover,
|
62
|
+
"action": obj.action,
|
63
|
+
"sleeping": obj.sleeping,
|
64
|
+
"_last_health": obj._last_health,
|
65
|
+
}
|
66
|
+
)
|
67
|
+
elif isinstance(obj, objects.Zombie):
|
68
|
+
obj_data.update(
|
69
|
+
{
|
70
|
+
"health": obj.health,
|
71
|
+
"cooldown": getattr(obj, "cooldown", 0),
|
72
|
+
}
|
73
|
+
)
|
74
|
+
elif isinstance(obj, objects.Skeleton):
|
75
|
+
obj_data.update(
|
76
|
+
{
|
77
|
+
"health": obj.health,
|
78
|
+
"reload": getattr(obj, "reload", 0),
|
79
|
+
}
|
80
|
+
)
|
81
|
+
elif isinstance(obj, objects.Arrow):
|
82
|
+
obj_data.update(
|
83
|
+
{
|
84
|
+
"facing": list(obj.facing) if hasattr(obj, "facing") else None,
|
85
|
+
}
|
86
|
+
)
|
87
|
+
elif isinstance(obj, objects.Plant):
|
88
|
+
obj_data.update(
|
89
|
+
{
|
90
|
+
"health": getattr(obj, "health", 1),
|
91
|
+
"grown": getattr(obj, "grown", 0),
|
92
|
+
"kind": getattr(obj, "kind", "unknown"),
|
93
|
+
}
|
94
|
+
)
|
95
|
+
elif hasattr(obj, "health"):
|
96
|
+
obj_data["health"] = obj.health
|
97
|
+
|
98
|
+
objects_data.append(obj_data)
|
99
|
+
|
100
|
+
# Save chunks with proper references
|
101
|
+
chunks_data = {}
|
102
|
+
for key, chunk_objs in self._world._chunks.items():
|
103
|
+
chunk_indices = []
|
104
|
+
for obj in chunk_objs:
|
105
|
+
if obj is not None:
|
106
|
+
try:
|
107
|
+
idx = self._world._objects.index(obj)
|
108
|
+
chunk_indices.append(idx)
|
109
|
+
except ValueError:
|
110
|
+
pass
|
111
|
+
chunks_data[key] = chunk_indices
|
112
|
+
|
113
|
+
# Add chunks data to world state
|
114
|
+
world_state["chunks"] = chunks_data
|
115
|
+
world_state["objects"] = objects_data
|
116
|
+
|
117
|
+
return {
|
118
|
+
"step": self._step,
|
119
|
+
"seed": self._seed,
|
120
|
+
"length": self._length,
|
121
|
+
"episode": self._episode,
|
122
|
+
"player_idx": player_idx,
|
123
|
+
"world": world_state,
|
124
|
+
}
|
125
|
+
|
126
|
+
def _load(self, state: Dict[str, Any]) -> None:
|
127
|
+
"""Load environment state from saved data, preserving everything."""
|
128
|
+
# Restore basic attributes
|
129
|
+
self._step = state["step"]
|
130
|
+
self._seed = state["seed"]
|
131
|
+
self._length = state["length"]
|
132
|
+
self._episode = state["episode"]
|
133
|
+
|
134
|
+
# Restore world state
|
135
|
+
world_data = state["world"]
|
136
|
+
self._world.area = world_data["area"]
|
137
|
+
self._world.daylight = world_data["daylight"]
|
138
|
+
self._world._mat_map = np.array(world_data["_mat_map"])
|
139
|
+
self._world._obj_map = np.array(world_data["_obj_map"])
|
140
|
+
self._world._mat_names = world_data.get("_mat_names", {})
|
141
|
+
self._world._mat_ids = world_data.get("_mat_ids", {})
|
142
|
+
self._world._chunk_size = world_data.get("_chunk_size", (16, 16))
|
143
|
+
|
144
|
+
# Clear existing objects
|
145
|
+
self._world._objects = []
|
146
|
+
self._world._chunks.clear()
|
147
|
+
|
148
|
+
# First pass: Create player object first if it exists
|
149
|
+
player_obj = None
|
150
|
+
player_idx = state.get("player_idx")
|
151
|
+
|
152
|
+
if player_idx is not None:
|
153
|
+
player_data = world_data["objects"][player_idx]
|
154
|
+
if player_data and "Player" in player_data["type"]:
|
155
|
+
player_obj = objects.Player(self._world, np.array(player_data["pos"]))
|
156
|
+
player_obj.facing = (
|
157
|
+
tuple(player_data["facing"]) if player_data.get("facing") else (0, 1)
|
158
|
+
)
|
159
|
+
player_obj.health = player_data["health"]
|
160
|
+
player_obj.inventory = collections.Counter(player_data["inventory"])
|
161
|
+
player_obj.achievements = dict(player_data["achievements"])
|
162
|
+
player_obj._hunger = player_data["_hunger"]
|
163
|
+
player_obj._thirst = player_data["_thirst"]
|
164
|
+
player_obj._fatigue = player_data["_fatigue"]
|
165
|
+
player_obj._recover = player_data["_recover"]
|
166
|
+
player_obj.action = player_data["action"]
|
167
|
+
player_obj.sleeping = player_data["sleeping"]
|
168
|
+
player_obj._last_health = player_data["_last_health"]
|
169
|
+
self._player = player_obj
|
170
|
+
|
171
|
+
# Second pass: Create all objects
|
172
|
+
objects_by_idx = {}
|
173
|
+
|
174
|
+
for obj_data in world_data["objects"]:
|
175
|
+
if obj_data is None:
|
176
|
+
self._world._objects.append(None)
|
177
|
+
else:
|
178
|
+
idx = obj_data["_index"]
|
179
|
+
|
180
|
+
# Skip player as we already created it
|
181
|
+
if idx == player_idx and player_obj is not None:
|
182
|
+
while len(self._world._objects) <= idx:
|
183
|
+
self._world._objects.append(None)
|
184
|
+
self._world._objects[idx] = player_obj
|
185
|
+
objects_by_idx[idx] = player_obj
|
186
|
+
continue
|
187
|
+
|
188
|
+
# Create object based on type
|
189
|
+
type_str = obj_data["type"]
|
190
|
+
module_name, cls_name = type_str.rsplit(".", 1)
|
191
|
+
|
192
|
+
# Special handling for each object type
|
193
|
+
if cls_name == "Zombie":
|
194
|
+
obj = objects.Zombie(self._world, np.array(obj_data["pos"]), player_obj)
|
195
|
+
obj.health = obj_data.get("health", 3)
|
196
|
+
obj.cooldown = obj_data.get("cooldown", 0)
|
197
|
+
elif cls_name == "Skeleton":
|
198
|
+
obj = objects.Skeleton(self._world, np.array(obj_data["pos"]), player_obj)
|
199
|
+
obj.health = obj_data.get("health", 2)
|
200
|
+
obj.reload = obj_data.get("reload", 0)
|
201
|
+
elif cls_name == "Cow":
|
202
|
+
obj = objects.Cow(self._world, np.array(obj_data["pos"]))
|
203
|
+
obj.health = obj_data.get("health", 3)
|
204
|
+
elif cls_name == "Plant":
|
205
|
+
obj = objects.Plant(self._world, np.array(obj_data["pos"]))
|
206
|
+
obj.health = obj_data.get("health", 1)
|
207
|
+
obj.grown = obj_data.get("grown", 0)
|
208
|
+
obj.kind = obj_data.get("kind", "tree")
|
209
|
+
elif cls_name == "Arrow":
|
210
|
+
obj = objects.Arrow(
|
211
|
+
self._world,
|
212
|
+
np.array(obj_data["pos"]),
|
213
|
+
tuple(obj_data.get("facing", [0, 1])),
|
214
|
+
)
|
215
|
+
elif cls_name in ["Stone", "Coal", "Iron", "Diamond"]:
|
216
|
+
# These are material objects
|
217
|
+
cls = getattr(objects, cls_name)
|
218
|
+
obj = cls(self._world, np.array(obj_data["pos"]))
|
219
|
+
elif cls_name in ["Table", "Furnace"]:
|
220
|
+
cls = getattr(objects, cls_name)
|
221
|
+
obj = cls(self._world, np.array(obj_data["pos"]))
|
222
|
+
else:
|
223
|
+
# Generic object creation
|
224
|
+
import importlib
|
225
|
+
|
226
|
+
module = importlib.import_module(module_name)
|
227
|
+
cls = getattr(module, cls_name)
|
228
|
+
obj = cls(self._world, np.array(obj_data["pos"]))
|
229
|
+
if hasattr(obj, "health") and "health" in obj_data:
|
230
|
+
obj.health = obj_data["health"]
|
231
|
+
|
232
|
+
obj.removed = obj_data.get("removed", False)
|
233
|
+
|
234
|
+
# Ensure list is long enough
|
235
|
+
while len(self._world._objects) <= idx:
|
236
|
+
self._world._objects.append(None)
|
237
|
+
|
238
|
+
self._world._objects[idx] = obj
|
239
|
+
objects_by_idx[idx] = obj
|
240
|
+
|
241
|
+
# Restore chunks
|
242
|
+
for key, obj_indices in world_data["chunks"].items():
|
243
|
+
# Convert string key back to tuple if needed
|
244
|
+
if isinstance(key, str):
|
245
|
+
key = eval(key) # Safe here as we control the format
|
246
|
+
|
247
|
+
chunk_objects = set()
|
248
|
+
for idx in obj_indices:
|
249
|
+
if idx in objects_by_idx:
|
250
|
+
chunk_objects.add(objects_by_idx[idx])
|
251
|
+
if chunk_objects:
|
252
|
+
self._world._chunks[key] = chunk_objects
|
253
|
+
|
254
|
+
# Restore random state
|
255
|
+
random_state = pickle.loads(world_data["random_state"])
|
256
|
+
self._world.random.set_state(random_state)
|
257
|
+
|
258
|
+
# Attach methods to Env class
|
259
|
+
crafter.Env.save = _save
|
260
|
+
crafter.Env.load = _load
|
261
|
+
|
262
|
+
print("[PATCH] crafter.Env.save() and load() methods added (v3).")
|
263
|
+
else:
|
264
|
+
print("[PATCH] crafter.Env already has save/load methods.")
|
265
|
+
|
266
|
+
print("[PATCH] Crafter serialization patch v3 complete.")
|