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.
Files changed (46) hide show
  1. arkparser/__init__.py +117 -0
  2. arkparser/common/__init__.py +72 -0
  3. arkparser/common/binary_reader.py +402 -0
  4. arkparser/common/exceptions.py +99 -0
  5. arkparser/common/map_config.py +166 -0
  6. arkparser/common/types.py +249 -0
  7. arkparser/common/version_detection.py +195 -0
  8. arkparser/data_models.py +801 -0
  9. arkparser/export.py +485 -0
  10. arkparser/files/__init__.py +25 -0
  11. arkparser/files/base.py +309 -0
  12. arkparser/files/cloud_inventory.py +259 -0
  13. arkparser/files/profile.py +205 -0
  14. arkparser/files/tribe.py +155 -0
  15. arkparser/files/world_save.py +699 -0
  16. arkparser/game_objects/__init__.py +32 -0
  17. arkparser/game_objects/container.py +180 -0
  18. arkparser/game_objects/game_object.py +273 -0
  19. arkparser/game_objects/location.py +87 -0
  20. arkparser/models/__init__.py +29 -0
  21. arkparser/models/character.py +227 -0
  22. arkparser/models/creature.py +642 -0
  23. arkparser/models/item.py +207 -0
  24. arkparser/models/player.py +263 -0
  25. arkparser/models/stats.py +226 -0
  26. arkparser/models/structure.py +176 -0
  27. arkparser/models/tribe.py +291 -0
  28. arkparser/properties/__init__.py +77 -0
  29. arkparser/properties/base.py +329 -0
  30. arkparser/properties/byte_property.py +230 -0
  31. arkparser/properties/compound.py +1125 -0
  32. arkparser/properties/primitives.py +803 -0
  33. arkparser/properties/registry.py +236 -0
  34. arkparser/py.typed +0 -0
  35. arkparser/structs/__init__.py +60 -0
  36. arkparser/structs/base.py +63 -0
  37. arkparser/structs/colors.py +108 -0
  38. arkparser/structs/misc.py +133 -0
  39. arkparser/structs/property_list.py +101 -0
  40. arkparser/structs/registry.py +140 -0
  41. arkparser/structs/vectors.py +221 -0
  42. arkparser-0.1.0.dist-info/METADATA +833 -0
  43. arkparser-0.1.0.dist-info/RECORD +46 -0
  44. arkparser-0.1.0.dist-info/WHEEL +5 -0
  45. arkparser-0.1.0.dist-info/licenses/LICENSE +21 -0
  46. arkparser-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,176 @@
1
+ """
2
+ Structure model class - Map structures (buildings, rafts, etc.).
3
+
4
+ Wraps GameObject with intuitive attribute access for structure 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 Location
13
+
14
+
15
+ @dataclass
16
+ class Structure:
17
+ """
18
+ A placed structure (building piece, crafting station, etc.).
19
+
20
+ Wraps a GameObject representing a structure with intuitive property access.
21
+
22
+ Attributes:
23
+ class_name: Blueprint class name.
24
+ owner_tribe_id: ID of the owning tribe.
25
+ owner_tribe_name: Name of the owning tribe.
26
+ location: World position.
27
+ health: Current health.
28
+ max_health: Maximum health.
29
+
30
+ Example:
31
+ >>> for structure in save.structures:
32
+ ... print(f"{structure.class_name} - {structure.owner_tribe_name}")
33
+ """
34
+
35
+ _game_object: t.Any = field(default=None, repr=False)
36
+
37
+ @classmethod
38
+ def from_game_object(cls, game_object: t.Any) -> Structure:
39
+ """
40
+ Create a Structure from a GameObject.
41
+
42
+ Args:
43
+ game_object: The structure's game object.
44
+
45
+ Returns:
46
+ A Structure instance.
47
+ """
48
+ return cls(_game_object=game_object)
49
+
50
+ @property
51
+ def class_name(self) -> str:
52
+ """Blueprint class name."""
53
+ return self._game_object.class_name if self._game_object else ""
54
+
55
+ @property
56
+ def guid(self) -> str:
57
+ """Unique identifier (ASA only)."""
58
+ return self._game_object.guid if self._game_object else ""
59
+
60
+ @property
61
+ def owner_tribe_id(self) -> int:
62
+ """ID of the owning tribe."""
63
+ if not self._game_object:
64
+ return 0
65
+ val = self._game_object.get_property_value("TargetingTeam", default=0)
66
+ return int(val) if val else 0
67
+
68
+ @property
69
+ def owner_tribe_name(self) -> str:
70
+ """Name of the owning tribe."""
71
+ if not self._game_object:
72
+ return ""
73
+ return self._game_object.get_property_value("OwnerName", default="") or ""
74
+
75
+ @property
76
+ def owner_name(self) -> str:
77
+ """Name of the owner (player who placed it)."""
78
+ if not self._game_object:
79
+ return ""
80
+ return self._game_object.get_property_value("OwnerName", default="") or ""
81
+
82
+ @property
83
+ def health(self) -> float:
84
+ """Current health."""
85
+ if not self._game_object:
86
+ return 0.0
87
+ val = self._game_object.get_property_value("Health", default=0.0)
88
+ return float(val) if val else 0.0
89
+
90
+ @property
91
+ def max_health(self) -> float:
92
+ """Maximum health."""
93
+ if not self._game_object:
94
+ return 0.0
95
+ val = self._game_object.get_property_value("MaxHealth", default=0.0)
96
+ return float(val) if val else 0.0
97
+
98
+ @property
99
+ def location(self) -> Location | None:
100
+ """World position and rotation."""
101
+ if self._game_object and self._game_object.location:
102
+ loc = self._game_object.location
103
+ return Location(
104
+ x=loc.x,
105
+ y=loc.y,
106
+ z=loc.z,
107
+ pitch=getattr(loc, "pitch", 0.0),
108
+ yaw=getattr(loc, "yaw", 0.0),
109
+ roll=getattr(loc, "roll", 0.0),
110
+ )
111
+ return None
112
+
113
+ @property
114
+ def is_powered(self) -> bool:
115
+ """True if the structure is powered (for electrical structures)."""
116
+ if not self._game_object:
117
+ return False
118
+ return self._game_object.get_property_value("bIsPowered", default=False)
119
+
120
+ @property
121
+ def is_locked(self) -> bool:
122
+ """True if the structure is locked (for doors, containers)."""
123
+ if not self._game_object:
124
+ return False
125
+ return self._game_object.get_property_value("bIsLocked", default=False)
126
+
127
+ @property
128
+ def decay_time(self) -> float:
129
+ """Time until decay (in seconds)."""
130
+ if not self._game_object:
131
+ return 0.0
132
+ val = self._game_object.get_property_value("LastInAllyRangeTime", default=0.0)
133
+ return float(val) if val else 0.0
134
+
135
+ @property
136
+ def custom_name(self) -> str:
137
+ """Custom name given to the structure (if renamed)."""
138
+ if not self._game_object:
139
+ return ""
140
+ return self._game_object.get_property_value("StructureName", default="") or ""
141
+
142
+ def get_property(self, name: str, index: int = 0, default: t.Any = None) -> t.Any:
143
+ """
144
+ Get a raw property value from the underlying game object.
145
+
146
+ Args:
147
+ name: Property name.
148
+ index: Array index for repeated properties.
149
+ default: Value to return if not found.
150
+
151
+ Returns:
152
+ The property value.
153
+ """
154
+ if self._game_object:
155
+ return self._game_object.get_property_value(name, default=default, index=index)
156
+ return default
157
+
158
+ def to_dict(self) -> dict[str, t.Any]:
159
+ """Convert to dictionary matching C# ASV_Structures export format."""
160
+ result: dict[str, t.Any] = {
161
+ "tribeid": self.owner_tribe_id,
162
+ "tribe": self.owner_tribe_name,
163
+ "struct": self.class_name,
164
+ "name": self.custom_name,
165
+ }
166
+ if self.location:
167
+ result["location"] = self.location.to_dict()
168
+ result["ccc"] = self.location.ccc
169
+ if self.location.latitude is not None:
170
+ result["lat"] = self.location.latitude
171
+ if self.location.longitude is not None:
172
+ result["lon"] = self.location.longitude
173
+ return result
174
+
175
+ def __repr__(self) -> str:
176
+ return f"Structure({self.class_name!r})"
@@ -0,0 +1,291 @@
1
+ """
2
+ Tribe model class - ARK tribe data.
3
+
4
+ Wraps tribe data with intuitive attribute access.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ import typing as t
11
+ from dataclasses import dataclass, field
12
+
13
+
14
+ @dataclass
15
+ class TribeMember:
16
+ """
17
+ A tribe member.
18
+
19
+ Attributes:
20
+ player_id: Unique player ID.
21
+ name: Player name.
22
+ rank: Rank within the tribe.
23
+ """
24
+
25
+ player_id: int = 0
26
+ name: str = ""
27
+ rank: int = 0
28
+
29
+ def to_dict(self) -> dict[str, t.Any]:
30
+ """Convert to dictionary."""
31
+ return {
32
+ "player_id": self.player_id,
33
+ "name": self.name,
34
+ "rank": self.rank,
35
+ }
36
+
37
+
38
+ @dataclass
39
+ class TribeLogEntry:
40
+ """
41
+ A tribe log entry.
42
+
43
+ Log entries follow the format: "Day X, HH:MM:SS: <RichColor ...>message</>"
44
+
45
+ Attributes:
46
+ day: In-game day number.
47
+ time: Time string (HH:MM:SS).
48
+ message: Full raw log message (including rich color tags).
49
+ clean_message: Message with rich color tags stripped.
50
+ """
51
+
52
+ day: int = 0
53
+ time: str = ""
54
+ message: str = ""
55
+
56
+ # Regex to parse "Day X, HH:MM:SS: rest"
57
+ _LOG_PATTERN: t.ClassVar[re.Pattern[str]] = re.compile(r"Day\s+(\d+),?\s+([\d:]+):\s*(.*)", re.DOTALL)
58
+ # Regex to strip RichColor tags
59
+ _RICH_COLOR_PATTERN: t.ClassVar[re.Pattern[str]] = re.compile(r"<RichColor[^>]*>|</>")
60
+
61
+ @classmethod
62
+ def from_string(cls, raw: str) -> TribeLogEntry:
63
+ """
64
+ Parse a tribe log entry from a raw string.
65
+
66
+ Args:
67
+ raw: Raw log string (e.g., "Day 387, 22:35:36: message").
68
+
69
+ Returns:
70
+ A TribeLogEntry with parsed fields.
71
+ """
72
+ match = cls._LOG_PATTERN.match(raw.strip())
73
+ if match:
74
+ return cls(
75
+ day=int(match.group(1)),
76
+ time=match.group(2),
77
+ message=raw.strip(),
78
+ )
79
+ return cls(message=raw.strip())
80
+
81
+ @property
82
+ def clean_message(self) -> str:
83
+ """Message with RichColor XML tags stripped."""
84
+ match = self._LOG_PATTERN.match(self.message)
85
+ body = match.group(3) if match else self.message
86
+ return self._RICH_COLOR_PATTERN.sub("", body).strip()
87
+
88
+ def to_dict(self) -> dict[str, t.Any]:
89
+ """Convert to dictionary."""
90
+ return {
91
+ "day": self.day,
92
+ "time": self.time,
93
+ "message": self.message,
94
+ "clean_message": self.clean_message,
95
+ }
96
+
97
+
98
+ @dataclass
99
+ class Tribe:
100
+ """
101
+ An ARK tribe.
102
+
103
+ Wraps tribe data with intuitive property access.
104
+
105
+ Attributes:
106
+ tribe_id: Unique tribe identifier.
107
+ name: Tribe name.
108
+ owner_id: Player ID of the tribe owner.
109
+ members: List of tribe members.
110
+ log: List of tribe log entries.
111
+
112
+ Example:
113
+ >>> tribe = Tribe.from_game_object(obj)
114
+ >>> print(f"{tribe.name} (ID: {tribe.tribe_id})")
115
+ >>> print(f"Members: {len(tribe.members)}")
116
+ """
117
+
118
+ _game_object: t.Any = field(default=None, repr=False)
119
+
120
+ # Cached values
121
+ _members: list[TribeMember] | None = field(default=None, repr=False)
122
+ _log: list[TribeLogEntry] | None = field(default=None, repr=False)
123
+
124
+ @classmethod
125
+ def from_game_object(cls, game_object: t.Any) -> Tribe:
126
+ """
127
+ Create a Tribe from a GameObject.
128
+
129
+ Args:
130
+ game_object: The tribe's game object.
131
+
132
+ Returns:
133
+ A Tribe instance.
134
+ """
135
+ return cls(_game_object=game_object)
136
+
137
+ @property
138
+ def guid(self) -> str:
139
+ """Unique identifier (ASA only)."""
140
+ return self._game_object.guid if self._game_object else ""
141
+
142
+ @property
143
+ def tribe_id(self) -> int:
144
+ """Unique tribe identifier."""
145
+ if not self._game_object:
146
+ return 0
147
+ val = self._game_object.get_property_value("TribeID", default=0)
148
+ return int(val) if val else 0
149
+
150
+ @property
151
+ def name(self) -> str:
152
+ """Tribe name."""
153
+ if not self._game_object:
154
+ return ""
155
+ return self._game_object.get_property_value("TribeName", default="") or ""
156
+
157
+ @property
158
+ def owner_id(self) -> int:
159
+ """Player ID of the tribe owner."""
160
+ if not self._game_object:
161
+ return 0
162
+ val = self._game_object.get_property_value("OwnerPlayerDataID", default=0)
163
+ return int(val) if val else 0
164
+
165
+ @property
166
+ def owner_name(self) -> str:
167
+ """Name of the tribe owner."""
168
+ if not self._game_object:
169
+ return ""
170
+ return self._game_object.get_property_value("OwnerPlayerName", default="") or ""
171
+
172
+ @property
173
+ def member_count(self) -> int:
174
+ """Number of tribe members (from MembersPlayerDataID count)."""
175
+ if not self._game_object:
176
+ return 0
177
+ count = 0
178
+ while True:
179
+ val = self._game_object.get_property_value("MembersPlayerDataID", index=count, default=None)
180
+ if val is None:
181
+ break
182
+ count += 1
183
+ return max(count, 1) # At least the owner
184
+
185
+ @property
186
+ def members(self) -> list[TribeMember]:
187
+ """List of tribe members."""
188
+ if self._members is None:
189
+ self._members = []
190
+ if self._game_object:
191
+ i = 0
192
+ while True:
193
+ player_id = self._game_object.get_property_value("MembersPlayerDataID", index=i, default=None)
194
+ if player_id is None:
195
+ break
196
+ name = self._game_object.get_property_value("MembersPlayerName", index=i, default="") or ""
197
+ rank = self._game_object.get_property_value("MembersRankGroups", index=i, default=0) or 0
198
+ self._members.append(
199
+ TribeMember(
200
+ player_id=int(player_id),
201
+ name=name,
202
+ rank=int(rank) if rank else 0,
203
+ )
204
+ )
205
+ i += 1
206
+ return self._members
207
+
208
+ @property
209
+ def alliance_ids(self) -> list[int]:
210
+ """IDs of allied tribes."""
211
+ if not self._game_object:
212
+ return []
213
+ ids = []
214
+ i = 0
215
+ while True:
216
+ val = self._game_object.get_property_value("TribeAlliances", index=i, default=None)
217
+ if val is None:
218
+ break
219
+ ids.append(int(val) if val else 0)
220
+ i += 1
221
+ return ids
222
+
223
+ @property
224
+ def log(self) -> list[TribeLogEntry]:
225
+ """
226
+ Tribe log entries.
227
+
228
+ Parses the TribeLog property (array of strings) into
229
+ structured TribeLogEntry objects.
230
+ """
231
+ if self._log is None:
232
+ self._log = []
233
+ if self._game_object:
234
+ log_val = self._game_object.get_property_value("TribeLog", default=None)
235
+ if isinstance(log_val, list):
236
+ for entry in log_val:
237
+ if isinstance(entry, str) and entry.strip():
238
+ self._log.append(TribeLogEntry.from_string(entry))
239
+ else:
240
+ # Try indexed access (some formats store as repeated props)
241
+ i = 0
242
+ while True:
243
+ val = self._game_object.get_property_value("TribeLog", index=i, default=None)
244
+ if val is None:
245
+ break
246
+ if isinstance(val, str) and val.strip():
247
+ self._log.append(TribeLogEntry.from_string(val))
248
+ i += 1
249
+ return self._log
250
+
251
+ @property
252
+ def raw_logs(self) -> list[str]:
253
+ """
254
+ Raw tribe log strings (as stored in the save file).
255
+
256
+ Returns:
257
+ List of raw log message strings.
258
+ """
259
+ return [entry.message for entry in self.log]
260
+
261
+ def get_property(self, name: str, index: int = 0, default: t.Any = None) -> t.Any:
262
+ """
263
+ Get a raw property value from the underlying game object.
264
+
265
+ Args:
266
+ name: Property name.
267
+ index: Array index for repeated properties.
268
+ default: Value to return if not found.
269
+
270
+ Returns:
271
+ The property value.
272
+ """
273
+ if self._game_object:
274
+ return self._game_object.get_property_value(name, default=default, index=index)
275
+ return default
276
+
277
+ def to_dict(self) -> dict[str, t.Any]:
278
+ """Convert to dictionary matching C# ASV_Tribes export format."""
279
+ return {
280
+ "tribeid": self.tribe_id,
281
+ "tribe": self.name,
282
+ "players": self.member_count,
283
+ "members": [m.to_dict() for m in self.members],
284
+ "owner_id": self.owner_id,
285
+ "owner_name": self.owner_name,
286
+ "alliance_ids": self.alliance_ids,
287
+ "logs": self.raw_logs,
288
+ }
289
+
290
+ def __repr__(self) -> str:
291
+ return f"Tribe({self.name!r}, id={self.tribe_id}, members={self.member_count})"
@@ -0,0 +1,77 @@
1
+ """
2
+ ARK Property System.
3
+
4
+ This module provides the property parsing system for ARK save files.
5
+ Properties are the core data storage mechanism for game objects.
6
+
7
+ Usage:
8
+ from arkparser.properties import read_properties, read_properties_as_dict
9
+
10
+ # Read properties from binary data
11
+ properties = read_properties(reader, is_asa=False)
12
+
13
+ # Or as a dictionary for easier access
14
+ props_dict = read_properties_as_dict(reader, is_asa=False)
15
+ health = get_property_value(props_dict, "Health", default=100.0)
16
+ """
17
+
18
+ from .base import Property, PropertyHeader, read_property_header
19
+ from .byte_property import ByteProperty
20
+ from .compound import ArrayProperty, MapProperty, StructProperty
21
+ from .primitives import (
22
+ BoolProperty,
23
+ DoubleProperty,
24
+ FloatProperty,
25
+ Int8Property,
26
+ Int16Property,
27
+ Int64Property,
28
+ IntProperty,
29
+ NameProperty,
30
+ ObjectProperty,
31
+ SoftObjectProperty,
32
+ StrProperty,
33
+ UInt16Property,
34
+ UInt32Property,
35
+ UInt64Property,
36
+ )
37
+ from .registry import (
38
+ PROPERTY_REGISTRY,
39
+ get_property_value,
40
+ read_properties,
41
+ read_properties_as_dict,
42
+ read_property,
43
+ )
44
+
45
+ __all__ = [
46
+ # Base
47
+ "Property",
48
+ "PropertyHeader",
49
+ "read_property_header",
50
+ # Primitives
51
+ "Int8Property",
52
+ "Int16Property",
53
+ "IntProperty",
54
+ "Int64Property",
55
+ "UInt16Property",
56
+ "UInt32Property",
57
+ "UInt64Property",
58
+ "FloatProperty",
59
+ "DoubleProperty",
60
+ "BoolProperty",
61
+ "StrProperty",
62
+ "NameProperty",
63
+ "ObjectProperty",
64
+ "SoftObjectProperty",
65
+ # Byte
66
+ "ByteProperty",
67
+ # Compound
68
+ "ArrayProperty",
69
+ "StructProperty",
70
+ "MapProperty",
71
+ # Registry
72
+ "PROPERTY_REGISTRY",
73
+ "read_property",
74
+ "read_properties",
75
+ "read_properties_as_dict",
76
+ "get_property_value",
77
+ ]