arkparser 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {arkparser-0.1.1 → arkparser-0.1.2}/PKG-INFO +1 -1
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/files/cloud_inventory.py +29 -13
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser.egg-info/PKG-INFO +1 -1
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser.egg-info/SOURCES.txt +2 -1
- {arkparser-0.1.1 → arkparser-0.1.2}/pyproject.toml +1 -1
- {arkparser-0.1.1 → arkparser-0.1.2}/tests/test_cloud_inventory.py +88 -1
- arkparser-0.1.2/tests/test_world_save.py +252 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/LICENSE +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/README.md +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/common/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/common/binary_reader.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/common/exceptions.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/common/map_config.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/common/types.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/common/version_detection.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/data_models.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/export.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/files/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/files/base.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/files/profile.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/files/tribe.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/files/world_save.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/game_objects/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/game_objects/container.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/game_objects/game_object.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/game_objects/location.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/character.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/creature.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/item.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/player.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/stats.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/structure.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/models/tribe.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/properties/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/properties/base.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/properties/byte_property.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/properties/compound.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/properties/primitives.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/properties/registry.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/py.typed +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/__init__.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/base.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/colors.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/misc.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/property_list.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/registry.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser/structs/vectors.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser.egg-info/dependency_links.txt +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser.egg-info/requires.txt +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/arkparser.egg-info/top_level.txt +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/setup.cfg +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/tests/test_models.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/tests/test_profile.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/tests/test_tribe.py +0 -0
- {arkparser-0.1.1 → arkparser-0.1.2}/tests/test_version_detection.py +0 -0
|
@@ -56,13 +56,19 @@ class CloudInventory(ArkFile):
|
|
|
56
56
|
- Object headers (ASE format)
|
|
57
57
|
- Properties
|
|
58
58
|
|
|
59
|
-
ASA format:
|
|
60
|
-
- Int32 version
|
|
61
|
-
- Int32 unknown1 (extra field)
|
|
62
|
-
- Int32 unknown2 (extra field)
|
|
59
|
+
ASA v7 format:
|
|
60
|
+
- Int32 version (7)
|
|
61
|
+
- Int32 unknown1 (extra field, v7+ only)
|
|
62
|
+
- Int32 unknown2 (extra field, v7+ only)
|
|
63
63
|
- Int32 object_count
|
|
64
64
|
- Object headers (ASA format with GUIDs)
|
|
65
65
|
- Properties
|
|
66
|
+
|
|
67
|
+
ASA v6 format (solo-cluster / cross-ARK transfer files):
|
|
68
|
+
- Int32 version (6)
|
|
69
|
+
- Int32 object_count
|
|
70
|
+
- Object headers (ASA format with GUIDs — no extra header fields)
|
|
71
|
+
- Properties
|
|
66
72
|
"""
|
|
67
73
|
# Read version
|
|
68
74
|
version = reader.read_int32()
|
|
@@ -73,8 +79,8 @@ class CloudInventory(ArkFile):
|
|
|
73
79
|
# Detect ASE vs ASA
|
|
74
80
|
is_asa = cls._detect_asa(reader, version)
|
|
75
81
|
|
|
76
|
-
if is_asa:
|
|
77
|
-
# ASA has extra header fields
|
|
82
|
+
if is_asa and version >= 7:
|
|
83
|
+
# v7+ ASA has two extra header fields before object_count
|
|
78
84
|
_unknown1 = reader.read_int32()
|
|
79
85
|
_unknown2 = reader.read_int32()
|
|
80
86
|
|
|
@@ -88,17 +94,23 @@ class CloudInventory(ArkFile):
|
|
|
88
94
|
objects: list[GameObject] = []
|
|
89
95
|
for i in range(object_count):
|
|
90
96
|
if is_asa:
|
|
91
|
-
obj = cls._read_asa_object_header(reader, obj_id=i)
|
|
97
|
+
obj = cls._read_asa_object_header(reader, obj_id=i, version=version)
|
|
92
98
|
else:
|
|
93
99
|
obj = GameObject.read_header(reader, obj_id=i, is_asa=False)
|
|
94
100
|
objects.append(obj)
|
|
95
101
|
|
|
96
|
-
# Load properties for each object
|
|
102
|
+
# Load properties for each object.
|
|
103
|
+
# Version 6 ASA (cross-ARK / solecluster) uses ASA-style object headers
|
|
104
|
+
# but ASE-style (is_asa=False) properties. Only v7+ uses ASA properties.
|
|
105
|
+
properties_is_asa = version >= 7
|
|
97
106
|
properties_block_offset = 0
|
|
98
107
|
for i, obj in enumerate(objects):
|
|
99
108
|
next_obj = objects[i + 1] if i + 1 < len(objects) else None
|
|
100
109
|
obj.load_properties(
|
|
101
|
-
reader,
|
|
110
|
+
reader,
|
|
111
|
+
properties_block_offset=properties_block_offset,
|
|
112
|
+
is_asa=properties_is_asa,
|
|
113
|
+
next_object=next_obj,
|
|
102
114
|
)
|
|
103
115
|
|
|
104
116
|
# Build container with lookups
|
|
@@ -113,7 +125,7 @@ class CloudInventory(ArkFile):
|
|
|
113
125
|
)
|
|
114
126
|
|
|
115
127
|
@classmethod
|
|
116
|
-
def _read_asa_object_header(cls, reader: BinaryReader, obj_id: int) -> GameObject:
|
|
128
|
+
def _read_asa_object_header(cls, reader: BinaryReader, obj_id: int, version: int = 7) -> GameObject:
|
|
117
129
|
"""
|
|
118
130
|
Read an ASA object header.
|
|
119
131
|
|
|
@@ -123,7 +135,10 @@ class CloudInventory(ArkFile):
|
|
|
123
135
|
- Field1 (int32)
|
|
124
136
|
- Field2 (int32)
|
|
125
137
|
- Instance name (string)
|
|
126
|
-
- Padding (21 bytes)
|
|
138
|
+
- Padding (21 bytes for v7+, 20 bytes for v6)
|
|
139
|
+
|
|
140
|
+
Version 6 (cross-ARK / solecluster files) uses a slightly older format
|
|
141
|
+
with one fewer padding byte before the properties block.
|
|
127
142
|
"""
|
|
128
143
|
obj = GameObject(id=obj_id)
|
|
129
144
|
|
|
@@ -142,8 +157,9 @@ class CloudInventory(ArkFile):
|
|
|
142
157
|
instance_name = reader.read_string()
|
|
143
158
|
obj.names = [instance_name] if instance_name else []
|
|
144
159
|
|
|
145
|
-
#
|
|
146
|
-
|
|
160
|
+
# v7+ uses 21 bytes of padding; v6 (cross-ARK / solecluster) uses 20
|
|
161
|
+
padding_size = 21 if version >= 7 else 20
|
|
162
|
+
reader.skip(padding_size)
|
|
147
163
|
|
|
148
164
|
# Properties offset will be set by sequential reading
|
|
149
165
|
obj.properties_offset = reader.position
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Tests for cloud inventory (obelisk) parsing - both ASE and ASA formats.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import pytest
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
8
9
|
from arkparser import CloudInventory
|
|
9
10
|
|
|
10
11
|
|
|
@@ -126,6 +127,7 @@ class TestCloudInventoryStats:
|
|
|
126
127
|
def test_ase_base_stats_object(self, ase_obelisk_path: Path) -> None:
|
|
127
128
|
"""Stats should return a DinoStats object."""
|
|
128
129
|
from arkparser import DinoStats
|
|
130
|
+
|
|
129
131
|
inv = CloudInventory.load(ase_obelisk_path)
|
|
130
132
|
creature = inv.uploaded_creatures[0]
|
|
131
133
|
assert isinstance(creature.stats, DinoStats)
|
|
@@ -133,6 +135,7 @@ class TestCloudInventoryStats:
|
|
|
133
135
|
def test_asa_base_stats_object(self, asa_obelisk_path: Path) -> None:
|
|
134
136
|
"""Stats should return a DinoStats object."""
|
|
135
137
|
from arkparser import DinoStats
|
|
138
|
+
|
|
136
139
|
inv = CloudInventory.load(asa_obelisk_path)
|
|
137
140
|
creature = inv.uploaded_creatures[0]
|
|
138
141
|
assert isinstance(creature.stats, DinoStats)
|
|
@@ -146,3 +149,87 @@ class TestCloudInventoryStats:
|
|
|
146
149
|
assert "level" in d
|
|
147
150
|
assert "stats" in d
|
|
148
151
|
|
|
152
|
+
|
|
153
|
+
class TestASESolecluster:
|
|
154
|
+
"""
|
|
155
|
+
Tests for ASE solecluster (cross-ARK transfer) files.
|
|
156
|
+
|
|
157
|
+
The solecluster directory contains 128 files; 15 are 0-byte empties.
|
|
158
|
+
All non-empty files must parse without errors.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def test_all_parse_without_errors(self, ase_solecluster_dir: Path) -> None:
|
|
162
|
+
"""Every non-empty ASE solecluster file should load without raising."""
|
|
163
|
+
failures: list[str] = []
|
|
164
|
+
for f in sorted(ase_solecluster_dir.iterdir()):
|
|
165
|
+
if f.stat().st_size == 0:
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
CloudInventory.load(f)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
failures.append(f"{f.name}: {e}")
|
|
171
|
+
assert failures == [], "Parse failures:\n" + "\n".join(failures)
|
|
172
|
+
|
|
173
|
+
def test_format_is_ase(self, ase_solecluster_dir: Path) -> None:
|
|
174
|
+
"""All non-empty ASE solecluster files should be identified as ASE."""
|
|
175
|
+
for f in sorted(ase_solecluster_dir.iterdir())[:20]:
|
|
176
|
+
if f.stat().st_size == 0:
|
|
177
|
+
continue
|
|
178
|
+
inv = CloudInventory.load(f)
|
|
179
|
+
assert not inv.is_asa, f"{f.name} was wrongly detected as ASA"
|
|
180
|
+
|
|
181
|
+
def test_have_objects(self, ase_solecluster_dir: Path) -> None:
|
|
182
|
+
"""Every non-empty ASE solecluster file should contain at least 1 object."""
|
|
183
|
+
for f in sorted(ase_solecluster_dir.iterdir())[:20]:
|
|
184
|
+
if f.stat().st_size == 0:
|
|
185
|
+
continue
|
|
186
|
+
inv = CloudInventory.load(f)
|
|
187
|
+
assert len(inv.objects) >= 1, f"{f.name} has no objects"
|
|
188
|
+
|
|
189
|
+
def test_nonempty_count(self, ase_solecluster_dir: Path) -> None:
|
|
190
|
+
"""Sanity check: at least 100 non-empty files in the ASE solecluster dir."""
|
|
191
|
+
nonempty = [f for f in ase_solecluster_dir.iterdir() if f.stat().st_size > 0]
|
|
192
|
+
assert len(nonempty) >= 100
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TestASASolecluster:
|
|
196
|
+
"""
|
|
197
|
+
Tests for ASA solecluster (cross-ARK transfer) files.
|
|
198
|
+
|
|
199
|
+
These are version-6 ASA files: GUID-based object headers but ASE-style
|
|
200
|
+
properties. The solecluster directory contains 173 files; 35 are 0-byte empties.
|
|
201
|
+
All non-empty files must parse without errors.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def test_all_parse_without_errors(self, asa_solecluster_dir: Path) -> None:
|
|
205
|
+
"""Every non-empty ASA solecluster file should load without raising."""
|
|
206
|
+
failures: list[str] = []
|
|
207
|
+
for f in sorted(asa_solecluster_dir.iterdir()):
|
|
208
|
+
if f.stat().st_size == 0:
|
|
209
|
+
continue
|
|
210
|
+
try:
|
|
211
|
+
CloudInventory.load(f)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
failures.append(f"{f.name}: {e}")
|
|
214
|
+
assert failures == [], "Parse failures:\n" + "\n".join(failures)
|
|
215
|
+
|
|
216
|
+
def test_format_is_asa(self, asa_solecluster_dir: Path) -> None:
|
|
217
|
+
"""All non-empty ASA solecluster files should be identified as ASA."""
|
|
218
|
+
for f in sorted(asa_solecluster_dir.iterdir())[:20]:
|
|
219
|
+
if f.stat().st_size == 0:
|
|
220
|
+
continue
|
|
221
|
+
inv = CloudInventory.load(f)
|
|
222
|
+
assert inv.is_asa, f"{f.name} was wrongly detected as ASE"
|
|
223
|
+
|
|
224
|
+
def test_have_objects(self, asa_solecluster_dir: Path) -> None:
|
|
225
|
+
"""Every non-empty ASA solecluster file should contain at least 1 object."""
|
|
226
|
+
for f in sorted(asa_solecluster_dir.iterdir())[:20]:
|
|
227
|
+
if f.stat().st_size == 0:
|
|
228
|
+
continue
|
|
229
|
+
inv = CloudInventory.load(f)
|
|
230
|
+
assert len(inv.objects) >= 1, f"{f.name} has no objects"
|
|
231
|
+
|
|
232
|
+
def test_nonempty_count(self, asa_solecluster_dir: Path) -> None:
|
|
233
|
+
"""Sanity check: at least 130 non-empty files in the ASA solecluster dir."""
|
|
234
|
+
nonempty = [f for f in asa_solecluster_dir.iterdir() if f.stat().st_size > 0]
|
|
235
|
+
assert len(nonempty) >= 130
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive tests for WorldSave parsing across multiple maps and formats.
|
|
3
|
+
|
|
4
|
+
Baselines (verified against example save files):
|
|
5
|
+
ASE Extinction – objects=77392, tamed=37, wild=30074, pawns=65
|
|
6
|
+
terminals=32, artifacts=3, resources=37
|
|
7
|
+
ASE Ragnarok – objects=93194, tamed=1, wild=1, pawns=1245
|
|
8
|
+
terminals=3, artifacts=11, resources=118
|
|
9
|
+
ASA Extinction – objects=128751, tamed=181, wild=32438, pawns=67
|
|
10
|
+
terminals=31, artifacts=3, resources=18
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from arkparser import WorldSave
|
|
18
|
+
from arkparser.game_objects.game_object import GameObject
|
|
19
|
+
from arkparser.game_objects.location import LocationData
|
|
20
|
+
|
|
21
|
+
_EXAMPLES = Path(__file__).parent.parent / "references" / "examples"
|
|
22
|
+
_ASE_EXTINCTION = _EXAMPLES / "ase" / "maps" / "extinction" / "Extinction.ark"
|
|
23
|
+
_ASE_RAGNAROK = _EXAMPLES / "ase" / "maps" / "ragnarok" / "Ragnarok.ark"
|
|
24
|
+
_ASA_EXTINCTION = _EXAMPLES / "asa" / "maps" / "extinction" / "Extinction_WP.ark"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Shared fixtures – the world-save objects are expensive to load, so we
|
|
29
|
+
# cache them at module scope via session-scoped fixtures.
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture(scope="session")
|
|
34
|
+
def ase_extinction() -> WorldSave:
|
|
35
|
+
return WorldSave.load(_ASE_EXTINCTION)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture(scope="session")
|
|
39
|
+
def ase_ragnarok() -> WorldSave:
|
|
40
|
+
return WorldSave.load(_ASE_RAGNAROK)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture(scope="session")
|
|
44
|
+
def asa_extinction() -> WorldSave:
|
|
45
|
+
return WorldSave.load(_ASA_EXTINCTION)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# ASE Extinction
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestASEWorldSaveExtinction:
|
|
54
|
+
"""Tests against the ASE Extinction world save (Extinction.ark)."""
|
|
55
|
+
|
|
56
|
+
def test_loads(self, ase_extinction: WorldSave) -> None:
|
|
57
|
+
assert ase_extinction is not None
|
|
58
|
+
|
|
59
|
+
def test_is_not_asa(self, ase_extinction: WorldSave) -> None:
|
|
60
|
+
assert not ase_extinction.is_asa
|
|
61
|
+
|
|
62
|
+
def test_object_count(self, ase_extinction: WorldSave) -> None:
|
|
63
|
+
assert len(ase_extinction.objects) == 77392
|
|
64
|
+
|
|
65
|
+
def test_zero_parse_errors(self, ase_extinction: WorldSave) -> None:
|
|
66
|
+
assert ase_extinction.parse_error_count == 0
|
|
67
|
+
|
|
68
|
+
def test_tamed_creature_count(self, ase_extinction: WorldSave) -> None:
|
|
69
|
+
assert len(ase_extinction.get_tamed_creatures()) == 37
|
|
70
|
+
|
|
71
|
+
def test_wild_creature_count(self, ase_extinction: WorldSave) -> None:
|
|
72
|
+
assert len(ase_extinction.get_wild_creatures()) == 30074
|
|
73
|
+
|
|
74
|
+
def test_player_pawn_count(self, ase_extinction: WorldSave) -> None:
|
|
75
|
+
assert len(ase_extinction.get_player_pawns()) == 65
|
|
76
|
+
|
|
77
|
+
def test_terminal_count(self, ase_extinction: WorldSave) -> None:
|
|
78
|
+
assert len(ase_extinction.get_terminals()) == 32
|
|
79
|
+
|
|
80
|
+
def test_artifact_crate_count(self, ase_extinction: WorldSave) -> None:
|
|
81
|
+
assert len(ase_extinction.get_artifact_crates()) == 3
|
|
82
|
+
|
|
83
|
+
def test_map_resource_count(self, ase_extinction: WorldSave) -> None:
|
|
84
|
+
assert len(ase_extinction.get_map_resources()) == 37
|
|
85
|
+
|
|
86
|
+
def test_supply_drops_empty(self, ase_extinction: WorldSave) -> None:
|
|
87
|
+
"""No active supply drops in this save."""
|
|
88
|
+
assert len(ase_extinction.get_supply_drops()) == 0
|
|
89
|
+
|
|
90
|
+
def test_creatures_have_class_name(self, ase_extinction: WorldSave) -> None:
|
|
91
|
+
for creature in ase_extinction.get_tamed_creatures():
|
|
92
|
+
assert isinstance(creature.class_name, str)
|
|
93
|
+
assert len(creature.class_name) > 0
|
|
94
|
+
|
|
95
|
+
def test_tamed_creatures_have_location(self, ase_extinction: WorldSave) -> None:
|
|
96
|
+
for creature in ase_extinction.get_tamed_creatures():
|
|
97
|
+
assert isinstance(creature.location, LocationData)
|
|
98
|
+
|
|
99
|
+
def test_wild_creature_is_game_object(self, ase_extinction: WorldSave) -> None:
|
|
100
|
+
for c in ase_extinction.get_wild_creatures()[:10]:
|
|
101
|
+
assert isinstance(c, GameObject)
|
|
102
|
+
|
|
103
|
+
def test_terminals_have_class_name(self, ase_extinction: WorldSave) -> None:
|
|
104
|
+
for terminal in ase_extinction.get_terminals():
|
|
105
|
+
cn = terminal.class_name.lower()
|
|
106
|
+
assert "terminal" in cn or "tribute" in cn or "city" in cn
|
|
107
|
+
|
|
108
|
+
def test_artifacts_have_class_name(self, ase_extinction: WorldSave) -> None:
|
|
109
|
+
for art in ase_extinction.get_artifact_crates():
|
|
110
|
+
assert "artifact" in art.class_name.lower()
|
|
111
|
+
|
|
112
|
+
def test_resources_have_class_name(self, ase_extinction: WorldSave) -> None:
|
|
113
|
+
valid_patterns = ("oilvein", "watervein", "gasvein", "chargenode", "elementvein", "beaverdam")
|
|
114
|
+
for r in ase_extinction.get_map_resources():
|
|
115
|
+
cn = r.class_name.lower()
|
|
116
|
+
assert any(p in cn for p in valid_patterns), f"Unexpected resource class: {r.class_name}"
|
|
117
|
+
|
|
118
|
+
def test_version_is_valid_ase(self, ase_extinction: WorldSave) -> None:
|
|
119
|
+
assert ase_extinction.version in (5, 6, 7, 8, 9, 10, 11, 12)
|
|
120
|
+
assert not ase_extinction.is_asa
|
|
121
|
+
|
|
122
|
+
def test_to_dict(self, ase_extinction: WorldSave) -> None:
|
|
123
|
+
d = ase_extinction.to_dict()
|
|
124
|
+
assert isinstance(d, dict)
|
|
125
|
+
assert "version" in d
|
|
126
|
+
assert "is_asa" in d
|
|
127
|
+
assert d["is_asa"] is False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# ASE Ragnarok
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestASEWorldSaveRagnarok:
|
|
136
|
+
"""Tests against the ASE Ragnarok world save (Ragnarok.ark)."""
|
|
137
|
+
|
|
138
|
+
def test_loads(self, ase_ragnarok: WorldSave) -> None:
|
|
139
|
+
assert ase_ragnarok is not None
|
|
140
|
+
|
|
141
|
+
def test_is_not_asa(self, ase_ragnarok: WorldSave) -> None:
|
|
142
|
+
assert not ase_ragnarok.is_asa
|
|
143
|
+
|
|
144
|
+
def test_object_count(self, ase_ragnarok: WorldSave) -> None:
|
|
145
|
+
assert len(ase_ragnarok.objects) == 93194
|
|
146
|
+
|
|
147
|
+
def test_zero_parse_errors(self, ase_ragnarok: WorldSave) -> None:
|
|
148
|
+
assert ase_ragnarok.parse_error_count == 0
|
|
149
|
+
|
|
150
|
+
def test_tamed_creature_count(self, ase_ragnarok: WorldSave) -> None:
|
|
151
|
+
assert len(ase_ragnarok.get_tamed_creatures()) == 1
|
|
152
|
+
|
|
153
|
+
def test_wild_creature_count(self, ase_ragnarok: WorldSave) -> None:
|
|
154
|
+
assert len(ase_ragnarok.get_wild_creatures()) == 1
|
|
155
|
+
|
|
156
|
+
def test_player_pawn_count(self, ase_ragnarok: WorldSave) -> None:
|
|
157
|
+
assert len(ase_ragnarok.get_player_pawns()) == 1245
|
|
158
|
+
|
|
159
|
+
def test_terminal_count(self, ase_ragnarok: WorldSave) -> None:
|
|
160
|
+
assert len(ase_ragnarok.get_terminals()) == 3
|
|
161
|
+
|
|
162
|
+
def test_artifact_crate_count(self, ase_ragnarok: WorldSave) -> None:
|
|
163
|
+
assert len(ase_ragnarok.get_artifact_crates()) == 11
|
|
164
|
+
|
|
165
|
+
def test_map_resource_count(self, ase_ragnarok: WorldSave) -> None:
|
|
166
|
+
assert len(ase_ragnarok.get_map_resources()) == 118
|
|
167
|
+
|
|
168
|
+
def test_supply_drops_empty(self, ase_ragnarok: WorldSave) -> None:
|
|
169
|
+
assert len(ase_ragnarok.get_supply_drops()) == 0
|
|
170
|
+
|
|
171
|
+
def test_version_is_valid_ase(self, ase_ragnarok: WorldSave) -> None:
|
|
172
|
+
assert ase_ragnarok.version in (5, 6, 7, 8, 9, 10, 11, 12)
|
|
173
|
+
assert not ase_ragnarok.is_asa
|
|
174
|
+
|
|
175
|
+
def test_to_dict(self, ase_ragnarok: WorldSave) -> None:
|
|
176
|
+
d = ase_ragnarok.to_dict()
|
|
177
|
+
assert isinstance(d, dict)
|
|
178
|
+
assert d["is_asa"] is False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# ASA Extinction
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class TestASAWorldSaveExtinction:
|
|
187
|
+
"""Tests against the ASA Extinction world save (Extinction_WP.ark)."""
|
|
188
|
+
|
|
189
|
+
def test_loads(self, asa_extinction: WorldSave) -> None:
|
|
190
|
+
assert asa_extinction is not None
|
|
191
|
+
|
|
192
|
+
def test_is_asa(self, asa_extinction: WorldSave) -> None:
|
|
193
|
+
assert asa_extinction.is_asa
|
|
194
|
+
|
|
195
|
+
def test_object_count(self, asa_extinction: WorldSave) -> None:
|
|
196
|
+
assert len(asa_extinction.objects) == 128751
|
|
197
|
+
|
|
198
|
+
def test_zero_parse_errors(self, asa_extinction: WorldSave) -> None:
|
|
199
|
+
assert asa_extinction.parse_error_count == 0
|
|
200
|
+
|
|
201
|
+
def test_tamed_creature_count(self, asa_extinction: WorldSave) -> None:
|
|
202
|
+
assert len(asa_extinction.get_tamed_creatures()) == 181
|
|
203
|
+
|
|
204
|
+
def test_wild_creature_count(self, asa_extinction: WorldSave) -> None:
|
|
205
|
+
assert len(asa_extinction.get_wild_creatures()) == 32438
|
|
206
|
+
|
|
207
|
+
def test_player_pawn_count(self, asa_extinction: WorldSave) -> None:
|
|
208
|
+
assert len(asa_extinction.get_player_pawns()) == 67
|
|
209
|
+
|
|
210
|
+
def test_terminal_count(self, asa_extinction: WorldSave) -> None:
|
|
211
|
+
assert len(asa_extinction.get_terminals()) == 31
|
|
212
|
+
|
|
213
|
+
def test_artifact_crate_count(self, asa_extinction: WorldSave) -> None:
|
|
214
|
+
assert len(asa_extinction.get_artifact_crates()) == 3
|
|
215
|
+
|
|
216
|
+
def test_map_resource_count(self, asa_extinction: WorldSave) -> None:
|
|
217
|
+
assert len(asa_extinction.get_map_resources()) == 18
|
|
218
|
+
|
|
219
|
+
def test_supply_drops_empty(self, asa_extinction: WorldSave) -> None:
|
|
220
|
+
assert len(asa_extinction.get_supply_drops()) == 0
|
|
221
|
+
|
|
222
|
+
def test_creatures_have_class_name(self, asa_extinction: WorldSave) -> None:
|
|
223
|
+
for creature in asa_extinction.get_tamed_creatures()[:10]:
|
|
224
|
+
assert isinstance(creature.class_name, str)
|
|
225
|
+
assert len(creature.class_name) > 0
|
|
226
|
+
|
|
227
|
+
def test_tamed_creatures_have_location(self, asa_extinction: WorldSave) -> None:
|
|
228
|
+
for creature in asa_extinction.get_tamed_creatures()[:10]:
|
|
229
|
+
assert isinstance(creature.location, LocationData)
|
|
230
|
+
|
|
231
|
+
def test_terminals_have_class_name(self, asa_extinction: WorldSave) -> None:
|
|
232
|
+
for terminal in asa_extinction.get_terminals():
|
|
233
|
+
cn = terminal.class_name.lower()
|
|
234
|
+
assert "terminal" in cn or "tribute" in cn or "city" in cn
|
|
235
|
+
|
|
236
|
+
def test_artifacts_have_class_name(self, asa_extinction: WorldSave) -> None:
|
|
237
|
+
for art in asa_extinction.get_artifact_crates():
|
|
238
|
+
assert "artifact" in art.class_name.lower()
|
|
239
|
+
|
|
240
|
+
def test_resources_have_class_name(self, asa_extinction: WorldSave) -> None:
|
|
241
|
+
valid_patterns = ("oilvein", "watervein", "gasvein", "chargenode", "elementvein", "beaverdam")
|
|
242
|
+
for r in asa_extinction.get_map_resources():
|
|
243
|
+
cn = r.class_name.lower()
|
|
244
|
+
assert any(p in cn for p in valid_patterns), f"Unexpected resource class: {r.class_name}"
|
|
245
|
+
|
|
246
|
+
def test_version_is_asa(self, asa_extinction: WorldSave) -> None:
|
|
247
|
+
assert asa_extinction.version >= 7
|
|
248
|
+
|
|
249
|
+
def test_to_dict(self, asa_extinction: WorldSave) -> None:
|
|
250
|
+
d = asa_extinction.to_dict()
|
|
251
|
+
assert isinstance(d, dict)
|
|
252
|
+
assert d["is_asa"] is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|