arkparser 0.1.0__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.
- arkparser/__init__.py +117 -0
- arkparser/common/__init__.py +72 -0
- arkparser/common/binary_reader.py +402 -0
- arkparser/common/exceptions.py +99 -0
- arkparser/common/map_config.py +166 -0
- arkparser/common/types.py +249 -0
- arkparser/common/version_detection.py +195 -0
- arkparser/data_models.py +801 -0
- arkparser/export.py +485 -0
- arkparser/files/__init__.py +25 -0
- arkparser/files/base.py +309 -0
- arkparser/files/cloud_inventory.py +259 -0
- arkparser/files/profile.py +205 -0
- arkparser/files/tribe.py +155 -0
- arkparser/files/world_save.py +699 -0
- arkparser/game_objects/__init__.py +32 -0
- arkparser/game_objects/container.py +180 -0
- arkparser/game_objects/game_object.py +273 -0
- arkparser/game_objects/location.py +87 -0
- arkparser/models/__init__.py +29 -0
- arkparser/models/character.py +227 -0
- arkparser/models/creature.py +642 -0
- arkparser/models/item.py +207 -0
- arkparser/models/player.py +263 -0
- arkparser/models/stats.py +226 -0
- arkparser/models/structure.py +176 -0
- arkparser/models/tribe.py +291 -0
- arkparser/properties/__init__.py +77 -0
- arkparser/properties/base.py +329 -0
- arkparser/properties/byte_property.py +230 -0
- arkparser/properties/compound.py +1125 -0
- arkparser/properties/primitives.py +803 -0
- arkparser/properties/registry.py +236 -0
- arkparser/py.typed +0 -0
- arkparser/structs/__init__.py +60 -0
- arkparser/structs/base.py +63 -0
- arkparser/structs/colors.py +108 -0
- arkparser/structs/misc.py +133 -0
- arkparser/structs/property_list.py +101 -0
- arkparser/structs/registry.py +140 -0
- arkparser/structs/vectors.py +221 -0
- arkparser-0.1.0.dist-info/METADATA +833 -0
- arkparser-0.1.0.dist-info/RECORD +46 -0
- arkparser-0.1.0.dist-info/WHEEL +5 -0
- arkparser-0.1.0.dist-info/licenses/LICENSE +21 -0
- arkparser-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Creature model classes - TamedCreature and WildCreature.
|
|
3
|
+
|
|
4
|
+
Wraps GameObject with intuitive attribute access for creature data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import typing as t
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from .stats import CreatureStats, Location
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Creature:
|
|
17
|
+
"""
|
|
18
|
+
Base creature class with common attributes.
|
|
19
|
+
|
|
20
|
+
This is the base class for both tamed and wild creatures.
|
|
21
|
+
It wraps a GameObject and provides intuitive property access.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
class_name: Blueprint class name (e.g., "Dodo_Character_BP_C").
|
|
25
|
+
guid: Unique identifier (ASA only, ASE will be empty).
|
|
26
|
+
is_female: True if the creature is female.
|
|
27
|
+
is_baby: True if the creature is a baby.
|
|
28
|
+
is_neutered: True if neutered/spayed.
|
|
29
|
+
colors: List of 6 color region indices.
|
|
30
|
+
base_level: Wild/base level of the creature.
|
|
31
|
+
base_stats: Wild stat points (before taming).
|
|
32
|
+
location: World position.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_game_object: t.Any = field(default=None, repr=False)
|
|
36
|
+
_status_object: t.Any = field(default=None, repr=False)
|
|
37
|
+
|
|
38
|
+
# Cached values
|
|
39
|
+
_class_name: str | None = field(default=None, repr=False)
|
|
40
|
+
_colors: list[int] | None = field(default=None, repr=False)
|
|
41
|
+
_base_stats: CreatureStats | None = field(default=None, repr=False)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def class_name(self) -> str:
|
|
45
|
+
"""Blueprint class name (e.g., 'Dodo_Character_BP_C')."""
|
|
46
|
+
if self._class_name is None:
|
|
47
|
+
self._class_name = self._game_object.class_name if self._game_object else ""
|
|
48
|
+
return self._class_name or ""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def guid(self) -> str:
|
|
52
|
+
"""Unique identifier (ASA only)."""
|
|
53
|
+
return self._game_object.guid if self._game_object else ""
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def dino_id(self) -> int:
|
|
57
|
+
"""
|
|
58
|
+
Unique dino ID (combination of DinoID1 and DinoID2).
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
64-bit dino ID, or 0 if not available.
|
|
62
|
+
"""
|
|
63
|
+
if not self._game_object:
|
|
64
|
+
return 0
|
|
65
|
+
id1 = self._game_object.get_property_value("DinoID1", default=0)
|
|
66
|
+
id2 = self._game_object.get_property_value("DinoID2", default=0)
|
|
67
|
+
if id1 and id2:
|
|
68
|
+
return (int(id1) << 32) | (int(id2) & 0xFFFFFFFF)
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_female(self) -> bool:
|
|
73
|
+
"""True if the creature is female."""
|
|
74
|
+
if not self._game_object:
|
|
75
|
+
return False
|
|
76
|
+
return self._game_object.get_property_value("bIsFemale", default=False)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def gender(self) -> str:
|
|
80
|
+
"""Gender as string ('Female' or 'Male')."""
|
|
81
|
+
return "Female" if self.is_female else "Male"
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def is_baby(self) -> bool:
|
|
85
|
+
"""True if the creature is a baby."""
|
|
86
|
+
if not self._game_object:
|
|
87
|
+
return False
|
|
88
|
+
return self._game_object.get_property_value("bIsBaby", default=False)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def is_neutered(self) -> bool:
|
|
92
|
+
"""True if the creature is neutered/spayed."""
|
|
93
|
+
if not self._game_object:
|
|
94
|
+
return False
|
|
95
|
+
return self._game_object.get_property_value("bNeutered", default=False)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def colors(self) -> list[int]:
|
|
99
|
+
"""
|
|
100
|
+
Color region indices (6 values).
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
List of 6 color indices for regions 0-5.
|
|
104
|
+
"""
|
|
105
|
+
if self._colors is None:
|
|
106
|
+
self._colors = []
|
|
107
|
+
if self._game_object:
|
|
108
|
+
for i in range(6):
|
|
109
|
+
color = self._game_object.get_property_value("ColorSetIndices", index=i, default=0)
|
|
110
|
+
self._colors.append(int(color) if color else 0)
|
|
111
|
+
else:
|
|
112
|
+
self._colors = [0] * 6
|
|
113
|
+
return self._colors
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def base_level(self) -> int:
|
|
117
|
+
"""Wild/base level (before any tamed levels)."""
|
|
118
|
+
if self._status_object:
|
|
119
|
+
return self._status_object.get_property_value("BaseCharacterLevel", default=1)
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def base_stats(self) -> CreatureStats:
|
|
124
|
+
"""
|
|
125
|
+
Wild stat points (points applied at spawn).
|
|
126
|
+
|
|
127
|
+
These are the stat points the creature had before taming.
|
|
128
|
+
"""
|
|
129
|
+
if self._base_stats is None:
|
|
130
|
+
points = []
|
|
131
|
+
if self._status_object:
|
|
132
|
+
for i in range(12):
|
|
133
|
+
val = self._status_object.get_property_value("NumberOfLevelUpPointsApplied", index=i, default=0)
|
|
134
|
+
points.append(int(val) if val else 0)
|
|
135
|
+
self._base_stats = CreatureStats.from_array(points)
|
|
136
|
+
return self._base_stats
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def location(self) -> Location | None:
|
|
140
|
+
"""World position and rotation."""
|
|
141
|
+
if self._game_object and self._game_object.location:
|
|
142
|
+
loc = self._game_object.location
|
|
143
|
+
return Location(
|
|
144
|
+
x=loc.x,
|
|
145
|
+
y=loc.y,
|
|
146
|
+
z=loc.z,
|
|
147
|
+
pitch=getattr(loc, "pitch", 0.0),
|
|
148
|
+
yaw=getattr(loc, "yaw", 0.0),
|
|
149
|
+
roll=getattr(loc, "roll", 0.0),
|
|
150
|
+
)
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def wild_scale(self) -> float:
|
|
155
|
+
"""Wild random scale factor (size variation)."""
|
|
156
|
+
if not self._game_object:
|
|
157
|
+
return 1.0
|
|
158
|
+
return self._game_object.get_property_value("WildRandomScale", default=1.0)
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def maturation(self) -> float:
|
|
162
|
+
"""
|
|
163
|
+
Baby maturation progress (0.0 - 1.0).
|
|
164
|
+
|
|
165
|
+
Only meaningful for babies. Returns 1.0 for adults.
|
|
166
|
+
"""
|
|
167
|
+
if not self._game_object or not self.is_baby:
|
|
168
|
+
return 1.0
|
|
169
|
+
baby_age = self._game_object.get_property_value("BabyAge", default=1.0)
|
|
170
|
+
return float(baby_age) if baby_age else 1.0
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def maturation_percent(self) -> str:
|
|
174
|
+
"""Baby maturation as a percentage string (e.g., '75' or '100')."""
|
|
175
|
+
return str(int(self.maturation * 100))
|
|
176
|
+
|
|
177
|
+
def get_property(self, name: str, index: int = 0, default: t.Any = None) -> t.Any:
|
|
178
|
+
"""
|
|
179
|
+
Get a raw property value from the underlying game object.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
name: Property name.
|
|
183
|
+
index: Array index for repeated properties.
|
|
184
|
+
default: Value to return if not found.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
The property value.
|
|
188
|
+
"""
|
|
189
|
+
if self._game_object:
|
|
190
|
+
return self._game_object.get_property_value(name, default=default, index=index)
|
|
191
|
+
return default
|
|
192
|
+
|
|
193
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
194
|
+
"""Convert to dictionary matching C# ASV export format."""
|
|
195
|
+
result: dict[str, t.Any] = {
|
|
196
|
+
"id": self.dino_id,
|
|
197
|
+
"creature": self.class_name,
|
|
198
|
+
"sex": self.gender,
|
|
199
|
+
"base": self.base_level,
|
|
200
|
+
"colors": self.colors,
|
|
201
|
+
"c0": self.colors[0] if len(self.colors) > 0 else 0,
|
|
202
|
+
"c1": self.colors[1] if len(self.colors) > 1 else 0,
|
|
203
|
+
"c2": self.colors[2] if len(self.colors) > 2 else 0,
|
|
204
|
+
"c3": self.colors[3] if len(self.colors) > 3 else 0,
|
|
205
|
+
"c4": self.colors[4] if len(self.colors) > 4 else 0,
|
|
206
|
+
"c5": self.colors[5] if len(self.colors) > 5 else 0,
|
|
207
|
+
"dinoid": str(self.dino_id),
|
|
208
|
+
"base_stats": self.base_stats.to_dict(),
|
|
209
|
+
# Flat stat fields matching C# wild export
|
|
210
|
+
"hp": self.base_stats.health,
|
|
211
|
+
"stam": self.base_stats.stamina,
|
|
212
|
+
"melee": self.base_stats.melee,
|
|
213
|
+
"weight": self.base_stats.weight,
|
|
214
|
+
"speed": self.base_stats.speed,
|
|
215
|
+
"food": self.base_stats.food,
|
|
216
|
+
"oxy": self.base_stats.oxygen,
|
|
217
|
+
"craft": self.base_stats.crafting,
|
|
218
|
+
}
|
|
219
|
+
if self.location:
|
|
220
|
+
result["location"] = self.location.to_dict()
|
|
221
|
+
result["ccc"] = self.location.ccc
|
|
222
|
+
if self.location.latitude is not None:
|
|
223
|
+
result["lat"] = self.location.latitude
|
|
224
|
+
if self.location.longitude is not None:
|
|
225
|
+
result["lon"] = self.location.longitude
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass
|
|
230
|
+
class TamedCreature(Creature):
|
|
231
|
+
"""
|
|
232
|
+
A tamed creature with full attribute access.
|
|
233
|
+
|
|
234
|
+
Extends Creature with taming-specific attributes like name,
|
|
235
|
+
tribe, imprint quality, tamed stats, and mutations.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
>>> creature = TamedCreature.from_game_object(obj, status_obj)
|
|
239
|
+
>>> print(f"{creature.name} - Level {creature.level}")
|
|
240
|
+
>>> print(f"Imprint: {creature.imprint_quality:.1%}")
|
|
241
|
+
>>> print(f"HP: {creature.base_stats.health} + {creature.tamed_stats.health}")
|
|
242
|
+
|
|
243
|
+
Attributes:
|
|
244
|
+
name: Tamed name given by player.
|
|
245
|
+
tribe_name: Name of the owning tribe.
|
|
246
|
+
tamer_name: Name of the player who tamed it.
|
|
247
|
+
level: Total level (base + extra).
|
|
248
|
+
imprint_quality: Imprint percentage (0.0 - 1.0).
|
|
249
|
+
imprinter_name: Name of the player who imprinted.
|
|
250
|
+
tamed_stats: Tamed stat points (added after taming).
|
|
251
|
+
is_clone: True if this is a cloned creature.
|
|
252
|
+
is_cryo: True if stored in a cryopod.
|
|
253
|
+
mutations_female: Number of mutations from female line.
|
|
254
|
+
mutations_male: Number of mutations from male line.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
# Cached values
|
|
258
|
+
_tamed_stats: CreatureStats | None = field(default=None, repr=False)
|
|
259
|
+
_mutated_stats: CreatureStats | None = field(default=None, repr=False)
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def from_game_object(
|
|
263
|
+
cls,
|
|
264
|
+
game_object: t.Any,
|
|
265
|
+
status_object: t.Any = None,
|
|
266
|
+
) -> TamedCreature:
|
|
267
|
+
"""
|
|
268
|
+
Create a TamedCreature from a GameObject.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
game_object: The creature's main game object.
|
|
272
|
+
status_object: The creature's status component (for stats).
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
A TamedCreature instance.
|
|
276
|
+
"""
|
|
277
|
+
return cls(_game_object=game_object, _status_object=status_object)
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def name(self) -> str:
|
|
281
|
+
"""Tamed name given by player."""
|
|
282
|
+
if not self._game_object:
|
|
283
|
+
return ""
|
|
284
|
+
return self._game_object.get_property_value("TamedName", default="") or ""
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def tribe_name(self) -> str:
|
|
288
|
+
"""Name of the owning tribe."""
|
|
289
|
+
if not self._game_object:
|
|
290
|
+
return ""
|
|
291
|
+
return self._game_object.get_property_value("TribeName", default="") or ""
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def tamer_name(self) -> str:
|
|
295
|
+
"""Name of the player who tamed this creature."""
|
|
296
|
+
if not self._game_object:
|
|
297
|
+
return ""
|
|
298
|
+
return self._game_object.get_property_value("TamerString", default="") or ""
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def extra_level(self) -> int:
|
|
302
|
+
"""Extra levels gained after taming."""
|
|
303
|
+
if self._status_object:
|
|
304
|
+
val = self._status_object.get_property_value("ExtraCharacterLevel", default=0)
|
|
305
|
+
return int(val) if val else 0
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def level(self) -> int:
|
|
310
|
+
"""Total level (base level + extra levels)."""
|
|
311
|
+
return self.base_level + self.extra_level
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def imprint_quality(self) -> float:
|
|
315
|
+
"""
|
|
316
|
+
Imprint percentage (0.0 to 1.0).
|
|
317
|
+
|
|
318
|
+
A value of 1.0 means 100% imprinted.
|
|
319
|
+
"""
|
|
320
|
+
if self._status_object:
|
|
321
|
+
val = self._status_object.get_property_value("DinoImprintingQuality", default=0.0)
|
|
322
|
+
return float(val) if val else 0.0
|
|
323
|
+
return 0.0
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def imprinter_name(self) -> str:
|
|
327
|
+
"""Name of the player who imprinted this creature."""
|
|
328
|
+
if not self._game_object:
|
|
329
|
+
return ""
|
|
330
|
+
return self._game_object.get_property_value("ImprinterName", default="") or ""
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def imprinter_id(self) -> int:
|
|
334
|
+
"""Player ID of the imprinter."""
|
|
335
|
+
if not self._game_object:
|
|
336
|
+
return 0
|
|
337
|
+
val = self._game_object.get_property_value("ImprinterPlayerDataID", default=0)
|
|
338
|
+
return int(val) if val else 0
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def tamed_stats(self) -> CreatureStats:
|
|
342
|
+
"""
|
|
343
|
+
Tamed stat points (points added after taming).
|
|
344
|
+
|
|
345
|
+
These are the stat points allocated by the player.
|
|
346
|
+
"""
|
|
347
|
+
if self._tamed_stats is None:
|
|
348
|
+
points = []
|
|
349
|
+
if self._status_object:
|
|
350
|
+
for i in range(12):
|
|
351
|
+
val = self._status_object.get_property_value(
|
|
352
|
+
"NumberOfLevelUpPointsAppliedTamed", index=i, default=0
|
|
353
|
+
)
|
|
354
|
+
points.append(int(val) if val else 0)
|
|
355
|
+
self._tamed_stats = CreatureStats.from_array(points)
|
|
356
|
+
return self._tamed_stats
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def mutated_stats(self) -> CreatureStats:
|
|
360
|
+
"""
|
|
361
|
+
Mutated stat points (NumberOfMutationsAppliedTamed).
|
|
362
|
+
|
|
363
|
+
These are the stat points gained through mutations.
|
|
364
|
+
"""
|
|
365
|
+
if self._mutated_stats is None:
|
|
366
|
+
points = []
|
|
367
|
+
if self._status_object:
|
|
368
|
+
for i in range(12):
|
|
369
|
+
val = self._status_object.get_property_value("NumberOfMutationsAppliedTamed", index=i, default=0)
|
|
370
|
+
points.append(int(val) if val else 0)
|
|
371
|
+
self._mutated_stats = CreatureStats.from_array(points)
|
|
372
|
+
return self._mutated_stats
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def experience(self) -> float:
|
|
376
|
+
"""Current experience points."""
|
|
377
|
+
if self._status_object:
|
|
378
|
+
val = self._status_object.get_property_value("ExperiencePoints", default=0.0)
|
|
379
|
+
return float(val) if val else 0.0
|
|
380
|
+
return 0.0
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def is_clone(self) -> bool:
|
|
384
|
+
"""True if this creature was cloned."""
|
|
385
|
+
if not self._game_object:
|
|
386
|
+
return False
|
|
387
|
+
is_clone = self._game_object.get_property_value("bIsClone", default=False)
|
|
388
|
+
if is_clone:
|
|
389
|
+
return True
|
|
390
|
+
return self._game_object.get_property_value("bIsCloneDino", default=False)
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def is_cryo(self) -> bool:
|
|
394
|
+
"""True if this creature is stored in a cryopod."""
|
|
395
|
+
if not self._game_object:
|
|
396
|
+
return False
|
|
397
|
+
return self._game_object.get_property_value("IsInCryo", default=False)
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def is_wandering(self) -> bool:
|
|
401
|
+
"""True if wandering is enabled."""
|
|
402
|
+
if not self._game_object:
|
|
403
|
+
return False
|
|
404
|
+
return self._game_object.get_property_value("bEnableTamedWandering", default=False)
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def is_mating(self) -> bool:
|
|
408
|
+
"""True if mating is enabled."""
|
|
409
|
+
if not self._game_object:
|
|
410
|
+
return False
|
|
411
|
+
return self._game_object.get_property_value("bEnableTamedMating", default=False)
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def mutations_female(self) -> int:
|
|
415
|
+
"""Number of mutations from the female line."""
|
|
416
|
+
if not self._game_object:
|
|
417
|
+
return 0
|
|
418
|
+
val = self._game_object.get_property_value("RandomMutationsFemale", default=0)
|
|
419
|
+
return int(val) if val else 0
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def mutations_male(self) -> int:
|
|
423
|
+
"""Number of mutations from the male line."""
|
|
424
|
+
if not self._game_object:
|
|
425
|
+
return 0
|
|
426
|
+
val = self._game_object.get_property_value("RandomMutationsMale", default=0)
|
|
427
|
+
return int(val) if val else 0
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def total_mutations(self) -> int:
|
|
431
|
+
"""Total mutations (female + male)."""
|
|
432
|
+
return self.mutations_female + self.mutations_male
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def targeting_team(self) -> int:
|
|
436
|
+
"""Targeting team ID (tribe ID)."""
|
|
437
|
+
if not self._game_object:
|
|
438
|
+
return 0
|
|
439
|
+
val = self._game_object.get_property_value("TargetingTeam", default=0)
|
|
440
|
+
return int(val) if val else 0
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def tamed_server(self) -> str:
|
|
444
|
+
"""Server name where the creature was tamed."""
|
|
445
|
+
if not self._game_object:
|
|
446
|
+
return ""
|
|
447
|
+
return self._game_object.get_property_value("TamedOnServerName", default="") or ""
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def uploaded_server(self) -> str:
|
|
451
|
+
"""Server name from which the creature was uploaded."""
|
|
452
|
+
if not self._game_object:
|
|
453
|
+
return ""
|
|
454
|
+
return self._game_object.get_property_value("UploadedFromServerName", default="") or ""
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def father_id(self) -> int | None:
|
|
458
|
+
"""
|
|
459
|
+
Father's dino ID, if bred.
|
|
460
|
+
|
|
461
|
+
Parses DinoAncestors struct array to extract the father's
|
|
462
|
+
combined DinoID1/DinoID2 values.
|
|
463
|
+
"""
|
|
464
|
+
ancestors = self._get_ancestors("DinoAncestors")
|
|
465
|
+
if ancestors:
|
|
466
|
+
return self._extract_dino_id(ancestors[0])
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def mother_id(self) -> int | None:
|
|
471
|
+
"""
|
|
472
|
+
Mother's dino ID, if bred.
|
|
473
|
+
|
|
474
|
+
Parses DinoAncestorsMale struct array to extract the mother's
|
|
475
|
+
combined DinoID1/DinoID2 values.
|
|
476
|
+
"""
|
|
477
|
+
ancestors = self._get_ancestors("DinoAncestorsMale")
|
|
478
|
+
if ancestors:
|
|
479
|
+
return self._extract_dino_id(ancestors[0])
|
|
480
|
+
return None
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def father_name(self) -> str:
|
|
484
|
+
"""Father's name, if bred."""
|
|
485
|
+
ancestors = self._get_ancestors("DinoAncestors")
|
|
486
|
+
if ancestors:
|
|
487
|
+
return self._extract_ancestor_name(ancestors[0])
|
|
488
|
+
return ""
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def mother_name(self) -> str:
|
|
492
|
+
"""Mother's name, if bred."""
|
|
493
|
+
ancestors = self._get_ancestors("DinoAncestorsMale")
|
|
494
|
+
if ancestors:
|
|
495
|
+
return self._extract_ancestor_name(ancestors[0])
|
|
496
|
+
return ""
|
|
497
|
+
|
|
498
|
+
def _get_ancestors(self, prop_name: str) -> list[t.Any]:
|
|
499
|
+
"""Get ancestor list from a DinoAncestors property."""
|
|
500
|
+
if not self._game_object:
|
|
501
|
+
return []
|
|
502
|
+
val = self._game_object.get_property_value(prop_name, default=None)
|
|
503
|
+
if isinstance(val, list) and val:
|
|
504
|
+
return val
|
|
505
|
+
return []
|
|
506
|
+
|
|
507
|
+
def _extract_dino_id(self, ancestor: t.Any) -> int | None:
|
|
508
|
+
"""Extract dino ID from an ancestor struct/dict."""
|
|
509
|
+
if isinstance(ancestor, dict):
|
|
510
|
+
id1 = ancestor.get("DinoID1", 0)
|
|
511
|
+
id2 = ancestor.get("DinoID2", 0)
|
|
512
|
+
if id1 or id2:
|
|
513
|
+
return (int(id1) << 32) | (int(id2) & 0xFFFFFFFF)
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
def _extract_ancestor_name(self, ancestor: t.Any) -> str:
|
|
517
|
+
"""Extract name from an ancestor struct/dict."""
|
|
518
|
+
if isinstance(ancestor, dict):
|
|
519
|
+
name = ancestor.get("DinoName", "") or ancestor.get("MaleName", "")
|
|
520
|
+
return str(name) if name else ""
|
|
521
|
+
return ""
|
|
522
|
+
|
|
523
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
524
|
+
"""Convert to dictionary matching C# ASV_Tamed export format."""
|
|
525
|
+
result = super().to_dict()
|
|
526
|
+
result.update(
|
|
527
|
+
{
|
|
528
|
+
"name": self.name,
|
|
529
|
+
"tribeid": self.targeting_team,
|
|
530
|
+
"tribe": self.tribe_name or None,
|
|
531
|
+
"tamer": self.tamer_name,
|
|
532
|
+
"imprinter": self.imprinter_name,
|
|
533
|
+
"imprint": self.imprint_quality,
|
|
534
|
+
"lvl": self.level,
|
|
535
|
+
"extra_level": self.extra_level,
|
|
536
|
+
"tamed_stats": self.tamed_stats.to_dict(),
|
|
537
|
+
# Flat tamed stat fields matching C# export
|
|
538
|
+
"hp-w": self.base_stats.health,
|
|
539
|
+
"stam-w": self.base_stats.stamina,
|
|
540
|
+
"melee-w": self.base_stats.melee,
|
|
541
|
+
"weight-w": self.base_stats.weight,
|
|
542
|
+
"speed-w": self.base_stats.speed,
|
|
543
|
+
"food-w": self.base_stats.food,
|
|
544
|
+
"oxy-w": self.base_stats.oxygen,
|
|
545
|
+
"craft-w": self.base_stats.crafting,
|
|
546
|
+
"hp-m": self.mutated_stats.health,
|
|
547
|
+
"stam-m": self.mutated_stats.stamina,
|
|
548
|
+
"melee-m": self.mutated_stats.melee,
|
|
549
|
+
"weight-m": self.mutated_stats.weight,
|
|
550
|
+
"speed-m": self.mutated_stats.speed,
|
|
551
|
+
"food-m": self.mutated_stats.food,
|
|
552
|
+
"oxy-m": self.mutated_stats.oxygen,
|
|
553
|
+
"craft-m": self.mutated_stats.crafting,
|
|
554
|
+
"hp-t": self.tamed_stats.health,
|
|
555
|
+
"stam-t": self.tamed_stats.stamina,
|
|
556
|
+
"melee-t": self.tamed_stats.melee,
|
|
557
|
+
"weight-t": self.tamed_stats.weight,
|
|
558
|
+
"speed-t": self.tamed_stats.speed,
|
|
559
|
+
"food-t": self.tamed_stats.food,
|
|
560
|
+
"oxy-t": self.tamed_stats.oxygen,
|
|
561
|
+
"craft-t": self.tamed_stats.crafting,
|
|
562
|
+
"mut-f": self.mutations_female,
|
|
563
|
+
"mut-m": self.mutations_male,
|
|
564
|
+
"cryo": self.is_cryo,
|
|
565
|
+
"isMating": self.is_mating,
|
|
566
|
+
"isNeutered": self.is_neutered,
|
|
567
|
+
"isClone": self.is_clone,
|
|
568
|
+
"tamedServer": self.tamed_server,
|
|
569
|
+
"uploadedServer": self.uploaded_server,
|
|
570
|
+
"maturation": self.maturation_percent,
|
|
571
|
+
"experience": self.experience,
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
return result
|
|
575
|
+
|
|
576
|
+
def __repr__(self) -> str:
|
|
577
|
+
name = self.name or self.class_name
|
|
578
|
+
return f"TamedCreature({name!r}, level={self.level}, gender={self.gender!r})"
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@dataclass
|
|
582
|
+
class WildCreature(Creature):
|
|
583
|
+
"""
|
|
584
|
+
A wild (untamed) creature.
|
|
585
|
+
|
|
586
|
+
Wild creatures have simpler attributes than tamed ones.
|
|
587
|
+
|
|
588
|
+
Attributes:
|
|
589
|
+
level: Same as base_level for wild creatures.
|
|
590
|
+
"""
|
|
591
|
+
|
|
592
|
+
@classmethod
|
|
593
|
+
def from_game_object(
|
|
594
|
+
cls,
|
|
595
|
+
game_object: t.Any,
|
|
596
|
+
status_object: t.Any = None,
|
|
597
|
+
) -> WildCreature:
|
|
598
|
+
"""
|
|
599
|
+
Create a WildCreature from a GameObject.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
game_object: The creature's main game object.
|
|
603
|
+
status_object: The creature's status component (for stats).
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
A WildCreature instance.
|
|
607
|
+
"""
|
|
608
|
+
return cls(_game_object=game_object, _status_object=status_object)
|
|
609
|
+
|
|
610
|
+
@property
|
|
611
|
+
def level(self) -> int:
|
|
612
|
+
"""Creature level (same as base level for wild)."""
|
|
613
|
+
return self.base_level
|
|
614
|
+
|
|
615
|
+
@property
|
|
616
|
+
def tameable(self) -> bool:
|
|
617
|
+
"""
|
|
618
|
+
Whether this creature can be tamed.
|
|
619
|
+
|
|
620
|
+
Checks for the RequiredTameAffinity property — if present and > 0,
|
|
621
|
+
the creature is tameable.
|
|
622
|
+
"""
|
|
623
|
+
if not self._game_object:
|
|
624
|
+
return False
|
|
625
|
+
val = self._game_object.get_property_value("RequiredTameAffinity", default=None)
|
|
626
|
+
if val is not None:
|
|
627
|
+
return float(val) > 0
|
|
628
|
+
if self._status_object:
|
|
629
|
+
val = self._status_object.get_property_value("RequiredTameAffinity", default=None)
|
|
630
|
+
if val is not None:
|
|
631
|
+
return float(val) > 0
|
|
632
|
+
return False
|
|
633
|
+
|
|
634
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
635
|
+
"""Convert to dictionary matching C# ASV_Wild export format."""
|
|
636
|
+
result = super().to_dict()
|
|
637
|
+
result["lvl"] = self.level
|
|
638
|
+
result["tameable"] = self.tameable
|
|
639
|
+
return result
|
|
640
|
+
|
|
641
|
+
def __repr__(self) -> str:
|
|
642
|
+
return f"WildCreature({self.class_name!r}, level={self.level}, gender={self.gender!r})"
|