arkparser 0.3.1__tar.gz → 0.3.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.3.1 → arkparser-0.3.2}/PKG-INFO +4 -1
- {arkparser-0.3.1 → arkparser-0.3.2}/README.md +3 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/__init__.py +1 -1
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/export.py +93 -2
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser.egg-info/PKG-INFO +4 -1
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser.egg-info/SOURCES.txt +1 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/pyproject.toml +1 -1
- arkparser-0.3.2/tests/test_current_stats.py +133 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/LICENSE +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/__init__.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/binary_reader.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/exceptions.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/map_config.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/normalization.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/types.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/common/version_detection.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/data_models.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/files/__init__.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/files/base.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/files/cloud_inventory.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/files/profile.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/files/tribe.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/files/world_save.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/game_objects/__init__.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/game_objects/container.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/game_objects/game_object.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/game_objects/location.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/properties/__init__.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/properties/base.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/properties/byte_property.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/properties/compound.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/properties/primitives.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/properties/registry.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/__init__.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/base.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/colors.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/misc.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/property_list.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/registry.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser/structs/vectors.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser.egg-info/dependency_links.txt +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser.egg-info/requires.txt +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/arkparser.egg-info/top_level.txt +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/setup.cfg +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_asa_header_position.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_binary_reader.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_binary_reader_layouts.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_cloud_inventory.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_data_models.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_export.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_game_objects.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_profile.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_tribe.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_v13_property_layouts.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_version_detection.py +0 -0
- {arkparser-0.3.1 → arkparser-0.3.2}/tests/test_world_save.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arkparser
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Pure Python parser for ARK: Survival Evolved and ARK: Survival Ascended save files
|
|
5
5
|
Author: Vertyco
|
|
6
6
|
License-Expression: MIT
|
|
@@ -250,6 +250,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
250
250
|
| `last_baby_age_update` | added | ISO 8601 datetime of `LastUpdatedBabyAgeAtTime`. |
|
|
251
251
|
| `last_gestation_update` | added | ISO 8601 datetime of `LastUpdatedGestationAtTime`. |
|
|
252
252
|
| `next_cuddle` | added | ISO 8601 datetime of `BabyNextCuddleTime`. |
|
|
253
|
+
| `current_stats` | added | Live in-world stat values from the dino's status component (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. These are the *current* values (e.g. `hp: 11013.62` = current HP, drops as the dino takes damage). Max values are NOT persisted by ARK — compute downstream from species stat tables + `*-w`/`*-t` points + `imprint` + server multipliers if you need them. `null` when the status component carries no `CurrentStatusValues` entries (e.g. uninitialised baby actor). |
|
|
253
254
|
|
|
254
255
|
#### `ASV_Wild` schema
|
|
255
256
|
|
|
@@ -267,6 +268,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
267
268
|
| `trait` | legacy | first entry of `CreatureTraits` (or empty string) |
|
|
268
269
|
| `traits` | added | full `CreatureTraits` list |
|
|
269
270
|
| `wild_spawn_region` | added | `OriginalNPCVolumeName` — `NPCZoneVolume` the creature spawned in. |
|
|
271
|
+
| `current_stats` | added | Live in-world stat values from the creature's status component (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. Max values are NOT in the save (would need species stat tables). `null` when uninitialised. |
|
|
270
272
|
|
|
271
273
|
#### Player data: `.arkprofile` vs in-world pawn
|
|
272
274
|
|
|
@@ -318,6 +320,7 @@ For the richest output, hand `export_players` **both** — assemble a wrapper fo
|
|
|
318
320
|
| `corpse_destruction` | added | ISO 8601 datetime of `CorpseDestructionTime`. |
|
|
319
321
|
| `chibi_levels` | added | `NumChibiLevelUps` — bonus levels from chibi pets. |
|
|
320
322
|
| `ascensions_scorched` | added | `NumAscensionsScorched` — ASE ascension counter (legacy `ContentPlayer` parses the ASA ascension block differently; this is the ASE-specific field). |
|
|
323
|
+
| `current_stats` | added | Live in-world stat values for the player from the pawn's `MyCharacterStatusComponent` (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. For profile-sourced records the parser joins on `PlayerDataID == LinkedPlayerDataID` to find the spawned pawn in the world save. `null` when the player has no in-world pawn (never spawned this server / corpse cleared) or the status component has no values — only currently / recently spawned characters have live stats. Max values are NOT persisted by ARK. |
|
|
321
324
|
|
|
322
325
|
#### `ASV_Tribes` schema
|
|
323
326
|
|
|
@@ -216,6 +216,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
216
216
|
| `last_baby_age_update` | added | ISO 8601 datetime of `LastUpdatedBabyAgeAtTime`. |
|
|
217
217
|
| `last_gestation_update` | added | ISO 8601 datetime of `LastUpdatedGestationAtTime`. |
|
|
218
218
|
| `next_cuddle` | added | ISO 8601 datetime of `BabyNextCuddleTime`. |
|
|
219
|
+
| `current_stats` | added | Live in-world stat values from the dino's status component (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. These are the *current* values (e.g. `hp: 11013.62` = current HP, drops as the dino takes damage). Max values are NOT persisted by ARK — compute downstream from species stat tables + `*-w`/`*-t` points + `imprint` + server multipliers if you need them. `null` when the status component carries no `CurrentStatusValues` entries (e.g. uninitialised baby actor). |
|
|
219
220
|
|
|
220
221
|
#### `ASV_Wild` schema
|
|
221
222
|
|
|
@@ -233,6 +234,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
233
234
|
| `trait` | legacy | first entry of `CreatureTraits` (or empty string) |
|
|
234
235
|
| `traits` | added | full `CreatureTraits` list |
|
|
235
236
|
| `wild_spawn_region` | added | `OriginalNPCVolumeName` — `NPCZoneVolume` the creature spawned in. |
|
|
237
|
+
| `current_stats` | added | Live in-world stat values from the creature's status component (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. Max values are NOT in the save (would need species stat tables). `null` when uninitialised. |
|
|
236
238
|
|
|
237
239
|
#### Player data: `.arkprofile` vs in-world pawn
|
|
238
240
|
|
|
@@ -284,6 +286,7 @@ For the richest output, hand `export_players` **both** — assemble a wrapper fo
|
|
|
284
286
|
| `corpse_destruction` | added | ISO 8601 datetime of `CorpseDestructionTime`. |
|
|
285
287
|
| `chibi_levels` | added | `NumChibiLevelUps` — bonus levels from chibi pets. |
|
|
286
288
|
| `ascensions_scorched` | added | `NumAscensionsScorched` — ASE ascension counter (legacy `ContentPlayer` parses the ASA ascension block differently; this is the ASE-specific field). |
|
|
289
|
+
| `current_stats` | added | Live in-world stat values for the player from the pawn's `MyCharacterStatusComponent` (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. For profile-sourced records the parser joins on `PlayerDataID == LinkedPlayerDataID` to find the spawned pawn in the world save. `null` when the player has no in-world pawn (never spawned this server / corpse cleared) or the status component has no values — only currently / recently spawned characters have live stats. Max values are NOT persisted by ARK. |
|
|
287
290
|
|
|
288
291
|
#### `ASV_Tribes` schema
|
|
289
292
|
|
|
@@ -203,6 +203,57 @@ def _stat_array(status: t.Any, prop_name: str) -> list[int]:
|
|
|
203
203
|
return out
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
def _current_stat_floats(status: t.Any) -> list[float] | None:
|
|
207
|
+
"""Read ``CurrentStatusValues[0..11]`` from a character status component.
|
|
208
|
+
|
|
209
|
+
Status components persist the live in-world values of all 12 stats. The
|
|
210
|
+
array is indexed by ARK's ``EPrimalCharacterStatusValue`` enum:
|
|
211
|
+
0=hp, 1=stam, 2=torpor, 3=oxy, 4=food, 5=water, 6=temp, 7=weight,
|
|
212
|
+
8=melee, 9=speed, 10=temp-fortitude, 11=crafting.
|
|
213
|
+
|
|
214
|
+
Returns ``None`` when the status component is missing or carries no
|
|
215
|
+
CurrentStatusValues entries (e.g. uninitialised baby actor) so callers
|
|
216
|
+
can distinguish "no data" from "all zeros".
|
|
217
|
+
"""
|
|
218
|
+
if status is None:
|
|
219
|
+
return None
|
|
220
|
+
out: list[float] = [0.0] * 12
|
|
221
|
+
seen = False
|
|
222
|
+
getter = getattr(status, "get_properties_by_name", None)
|
|
223
|
+
if callable(getter):
|
|
224
|
+
# Real GameObject: get_properties_by_name surfaces every indexed
|
|
225
|
+
# entry. Empty result == component carries no CurrentStatusValues
|
|
226
|
+
# at all (uninitialised); return None so callers can distinguish
|
|
227
|
+
# "no data" from "all zeros".
|
|
228
|
+
for prop in getter("CurrentStatusValues"):
|
|
229
|
+
idx = getattr(prop, "index", 0)
|
|
230
|
+
if 0 <= idx < 12:
|
|
231
|
+
out[idx] = _float(getattr(prop, "value", 0.0))
|
|
232
|
+
seen = True
|
|
233
|
+
return out if seen else None
|
|
234
|
+
# Synthetic / cryopod stand-in: per-index get_property_value lookups.
|
|
235
|
+
for i in range(12):
|
|
236
|
+
val = _prop(status, "CurrentStatusValues", default=None, index=i)
|
|
237
|
+
if val is not None:
|
|
238
|
+
out[i] = _float(val)
|
|
239
|
+
seen = True
|
|
240
|
+
return out if seen else None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _current_stats_dict(status: t.Any) -> dict[str, float] | None:
|
|
244
|
+
"""Return current stat values keyed by short stat name.
|
|
245
|
+
|
|
246
|
+
Pre-conditions: ``status`` is the character status component (creature or
|
|
247
|
+
player). May be ``None`` (e.g. offline player with no spawned pawn).
|
|
248
|
+
Post-conditions: returns a dict ``{hp, stam, ..., craft}`` of floats, or
|
|
249
|
+
``None`` when no CurrentStatusValues are persisted on the component.
|
|
250
|
+
"""
|
|
251
|
+
floats = _current_stat_floats(status)
|
|
252
|
+
if floats is None:
|
|
253
|
+
return None
|
|
254
|
+
return {_STAT_NAMES[i]: floats[i] for i in range(12)}
|
|
255
|
+
|
|
256
|
+
|
|
206
257
|
def _flat_stats(points: list[int], suffix: str = "") -> dict[str, int]:
|
|
207
258
|
"""Emit all 12 stats as a flat dict.
|
|
208
259
|
|
|
@@ -674,6 +725,7 @@ def _tamed_dict(
|
|
|
674
725
|
)) is not None
|
|
675
726
|
else None
|
|
676
727
|
),
|
|
728
|
+
"current_stats": _current_stats_dict(status),
|
|
677
729
|
"imprinter_player_id": _int(_prop(obj, "ImprinterPlayerDataID")),
|
|
678
730
|
"imprinter_net_id": _str(_prop(obj, "ImprinterPlayerUniqueNetId")),
|
|
679
731
|
"taming_team_id": _int(_prop(obj, "TamingTeamID")),
|
|
@@ -870,6 +922,7 @@ def _wild_dict(
|
|
|
870
922
|
# CreatureTraits list is exposed alongside as ``traits``.
|
|
871
923
|
"trait": traits[0] if traits else "",
|
|
872
924
|
"traits": traits,
|
|
925
|
+
"current_stats": _current_stats_dict(status),
|
|
873
926
|
"wild_spawn_region": _str(_prop(obj, "OriginalNPCVolumeName")),
|
|
874
927
|
}
|
|
875
928
|
data.update(_gps_payload(obj, map_config))
|
|
@@ -882,13 +935,24 @@ def export_wild(save: t.Any, map_config: MapConfig | None = None) -> list[dict[s
|
|
|
882
935
|
return [_wild_dict(obj, _status_for(obj, lookup), map_config) for obj in objects]
|
|
883
936
|
|
|
884
937
|
|
|
885
|
-
def _player_from_profile(
|
|
938
|
+
def _player_from_profile(
|
|
939
|
+
profile: Profile,
|
|
940
|
+
save: t.Any = None,
|
|
941
|
+
pawn_status_by_id: dict[int, t.Any] | None = None,
|
|
942
|
+
) -> dict[str, t.Any]:
|
|
886
943
|
stat_points = [_int(profile.get_stat(i)["added"]) for i in range(12)]
|
|
887
944
|
gamertag = profile.player_name or ""
|
|
888
945
|
character = profile.character_name or gamertag
|
|
889
946
|
steam_id = profile.unique_id or ""
|
|
890
947
|
active_dt = _approx_real_datetime(profile.last_login_time, save)
|
|
891
948
|
|
|
949
|
+
# Live HP/stam/etc live on the player's in-world pawn's status component,
|
|
950
|
+
# not in the profile. Resolve via PlayerDataID -> pawn -> status. Absent
|
|
951
|
+
# when the player has no spawned body (never spawned / corpse cleared).
|
|
952
|
+
status = None
|
|
953
|
+
if pawn_status_by_id and profile.player_id:
|
|
954
|
+
status = pawn_status_by_id.get(int(profile.player_id))
|
|
955
|
+
|
|
892
956
|
out: dict[str, t.Any] = {
|
|
893
957
|
"playerid": profile.player_id or 0,
|
|
894
958
|
"steam": _str(gamertag),
|
|
@@ -907,6 +971,7 @@ def _player_from_profile(profile: Profile, save: t.Any = None) -> dict[str, t.An
|
|
|
907
971
|
"netAddress": "",
|
|
908
972
|
"engram_points": profile.total_engram_points,
|
|
909
973
|
"experience": _int(profile.experience),
|
|
974
|
+
"current_stats": _current_stats_dict(status),
|
|
910
975
|
}
|
|
911
976
|
if steam_id:
|
|
912
977
|
out["steamid"] = steam_id
|
|
@@ -981,17 +1046,43 @@ def _player_from_object(
|
|
|
981
1046
|
"body_colors": body_colors,
|
|
982
1047
|
"died_at": died_iso,
|
|
983
1048
|
"corpse_destruction": corpse_iso,
|
|
1049
|
+
"current_stats": _current_stats_dict(status),
|
|
984
1050
|
}
|
|
985
1051
|
return _compact(data, LEGACY_PLAYER_KEYS)
|
|
986
1052
|
|
|
987
1053
|
|
|
1054
|
+
def _player_status_by_data_id(save: t.Any, lookup: dict[t.Any, t.Any]) -> dict[int, t.Any]:
|
|
1055
|
+
"""Index ``MyCharacterStatusComponent`` per player by ``LinkedPlayerDataID``.
|
|
1056
|
+
|
|
1057
|
+
Walks every ``PlayerPawnTest_*_C`` / ``PlayerCharacter_*`` in the world
|
|
1058
|
+
save, reads its ``LinkedPlayerDataID``, follows ``MyCharacterStatusComponent``
|
|
1059
|
+
via ``lookup``, and stores the resolved status object. Lets profile-based
|
|
1060
|
+
player exports surface live HP/stamina/food/etc. without legacy ASVPack
|
|
1061
|
+
having to do the same join.
|
|
1062
|
+
"""
|
|
1063
|
+
out: dict[int, t.Any] = {}
|
|
1064
|
+
objects = getattr(save, "objects", None) or []
|
|
1065
|
+
for obj in objects:
|
|
1066
|
+
cn = str(getattr(obj, "class_name", "") or "")
|
|
1067
|
+
if "PlayerPawn" not in cn and "PlayerCharacter" not in cn:
|
|
1068
|
+
continue
|
|
1069
|
+
pid = _int(_prop(obj, "LinkedPlayerDataID"))
|
|
1070
|
+
if not pid:
|
|
1071
|
+
continue
|
|
1072
|
+
status = _status_for(obj, lookup)
|
|
1073
|
+
if status is not None:
|
|
1074
|
+
out[pid] = status
|
|
1075
|
+
return out
|
|
1076
|
+
|
|
1077
|
+
|
|
988
1078
|
def export_players(save: t.Any, map_config: MapConfig | None = None) -> list[dict[str, t.Any]]:
|
|
989
1079
|
profiles = _collection(save, "profiles", Profile)
|
|
990
1080
|
lookup = _save_lookup(save)
|
|
1081
|
+
pawn_status_by_id = _player_status_by_data_id(save, lookup)
|
|
991
1082
|
results: list[dict[str, t.Any]] = []
|
|
992
1083
|
for entry in profiles:
|
|
993
1084
|
if isinstance(entry, Profile):
|
|
994
|
-
results.append(_player_from_profile(entry, save))
|
|
1085
|
+
results.append(_player_from_profile(entry, save, pawn_status_by_id))
|
|
995
1086
|
continue
|
|
996
1087
|
profile_obj = getattr(entry, "profile", None)
|
|
997
1088
|
if profile_obj is None and getattr(entry, "objects", None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arkparser
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Pure Python parser for ARK: Survival Evolved and ARK: Survival Ascended save files
|
|
5
5
|
Author: Vertyco
|
|
6
6
|
License-Expression: MIT
|
|
@@ -250,6 +250,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
250
250
|
| `last_baby_age_update` | added | ISO 8601 datetime of `LastUpdatedBabyAgeAtTime`. |
|
|
251
251
|
| `last_gestation_update` | added | ISO 8601 datetime of `LastUpdatedGestationAtTime`. |
|
|
252
252
|
| `next_cuddle` | added | ISO 8601 datetime of `BabyNextCuddleTime`. |
|
|
253
|
+
| `current_stats` | added | Live in-world stat values from the dino's status component (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. These are the *current* values (e.g. `hp: 11013.62` = current HP, drops as the dino takes damage). Max values are NOT persisted by ARK — compute downstream from species stat tables + `*-w`/`*-t` points + `imprint` + server multipliers if you need them. `null` when the status component carries no `CurrentStatusValues` entries (e.g. uninitialised baby actor). |
|
|
253
254
|
|
|
254
255
|
#### `ASV_Wild` schema
|
|
255
256
|
|
|
@@ -267,6 +268,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
267
268
|
| `trait` | legacy | first entry of `CreatureTraits` (or empty string) |
|
|
268
269
|
| `traits` | added | full `CreatureTraits` list |
|
|
269
270
|
| `wild_spawn_region` | added | `OriginalNPCVolumeName` — `NPCZoneVolume` the creature spawned in. |
|
|
271
|
+
| `current_stats` | added | Live in-world stat values from the creature's status component (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. Max values are NOT in the save (would need species stat tables). `null` when uninitialised. |
|
|
270
272
|
|
|
271
273
|
#### Player data: `.arkprofile` vs in-world pawn
|
|
272
274
|
|
|
@@ -318,6 +320,7 @@ For the richest output, hand `export_players` **both** — assemble a wrapper fo
|
|
|
318
320
|
| `corpse_destruction` | added | ISO 8601 datetime of `CorpseDestructionTime`. |
|
|
319
321
|
| `chibi_levels` | added | `NumChibiLevelUps` — bonus levels from chibi pets. |
|
|
320
322
|
| `ascensions_scorched` | added | `NumAscensionsScorched` — ASE ascension counter (legacy `ContentPlayer` parses the ASA ascension block differently; this is the ASE-specific field). |
|
|
323
|
+
| `current_stats` | added | Live in-world stat values for the player from the pawn's `MyCharacterStatusComponent` (`CurrentStatusValues[0..11]`) as a `{hp, stam, torp, oxy, food, water, temp, weight, melee, speed, fort, craft}` dict of floats. For profile-sourced records the parser joins on `PlayerDataID == LinkedPlayerDataID` to find the spawned pawn in the world save. `null` when the player has no in-world pawn (never spawned this server / corpse cleared) or the status component has no values — only currently / recently spawned characters have live stats. Max values are NOT persisted by ARK. |
|
|
321
324
|
|
|
322
325
|
#### `ASV_Tribes` schema
|
|
323
326
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Unit tests for live CurrentStatusValues extraction.
|
|
2
|
+
|
|
3
|
+
ARK persists every character's instantaneous stat values on the status
|
|
4
|
+
component as ``CurrentStatusValues[0..11]``. ``_current_stats_dict`` reads
|
|
5
|
+
those entries (using either ``get_properties_by_name`` for objects that
|
|
6
|
+
expose it or per-index ``get_property_value`` lookups for synthetic /
|
|
7
|
+
cryopod objects) and returns ``{hp, stam, torp, oxy, food, water, temp,
|
|
8
|
+
weight, melee, speed, fort, craft}``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import typing as t
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
from arkparser.export import _current_stat_floats, _current_stats_dict
|
|
17
|
+
|
|
18
|
+
_STAT_ORDER = (
|
|
19
|
+
"hp", "stam", "torp", "oxy", "food", "water",
|
|
20
|
+
"temp", "weight", "melee", "speed", "fort", "craft",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class _IndexedProp:
|
|
26
|
+
"""Stand-in for an arkparser property with an array index."""
|
|
27
|
+
|
|
28
|
+
name: str
|
|
29
|
+
index: int
|
|
30
|
+
value: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _FakeStatus:
|
|
34
|
+
"""Minimal stand-in for a character status component.
|
|
35
|
+
|
|
36
|
+
Supports the ``get_properties_by_name`` interface used by
|
|
37
|
+
``_current_stat_floats``.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, current: list[tuple[int, float]]) -> None:
|
|
41
|
+
self._props = [_IndexedProp("CurrentStatusValues", idx, val) for idx, val in current]
|
|
42
|
+
|
|
43
|
+
def get_properties_by_name(self, name: str) -> list[_IndexedProp]:
|
|
44
|
+
return [p for p in self._props if p.name == name]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _PerIndexStatus:
|
|
48
|
+
"""Synthetic stand-in (matches ``_SyntheticGameObject`` interface).
|
|
49
|
+
|
|
50
|
+
Stores values as ``CurrentStatusValues_{idx}`` keys, and
|
|
51
|
+
``get_property_value`` returns the indexed value.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, current: dict[int, float]) -> None:
|
|
55
|
+
self._values = current
|
|
56
|
+
|
|
57
|
+
def get_property_value(
|
|
58
|
+
self,
|
|
59
|
+
name: str,
|
|
60
|
+
default: t.Any = None,
|
|
61
|
+
index: int | None = None,
|
|
62
|
+
) -> t.Any:
|
|
63
|
+
if name != "CurrentStatusValues":
|
|
64
|
+
return default
|
|
65
|
+
if index is None:
|
|
66
|
+
return default
|
|
67
|
+
return self._values.get(index, default)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_none_status_returns_none() -> None:
|
|
71
|
+
assert _current_stat_floats(None) is None
|
|
72
|
+
assert _current_stats_dict(None) is None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_empty_status_returns_none() -> None:
|
|
76
|
+
"""Status component carrying no CurrentStatusValues at all -> ``None``."""
|
|
77
|
+
status = _FakeStatus(current=[])
|
|
78
|
+
assert _current_stat_floats(status) is None
|
|
79
|
+
assert _current_stats_dict(status) is None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_sparse_indices_fill_with_zeros() -> None:
|
|
83
|
+
"""Real saves only persist the stats that diverge from defaults; the
|
|
84
|
+
rest get zero. Wyvern test: hp + stam + oxy set, the rest zero."""
|
|
85
|
+
status = _FakeStatus(current=[
|
|
86
|
+
(0, 25238.14), # hp
|
|
87
|
+
(1, 806.40), # stam
|
|
88
|
+
(3, 660.0), # oxy
|
|
89
|
+
])
|
|
90
|
+
result = _current_stats_dict(status)
|
|
91
|
+
assert result is not None
|
|
92
|
+
assert result["hp"] == 25238.14
|
|
93
|
+
assert result["stam"] == 806.40
|
|
94
|
+
assert result["oxy"] == 660.0
|
|
95
|
+
assert result["torp"] == 0.0
|
|
96
|
+
assert result["food"] == 0.0
|
|
97
|
+
assert result["water"] == 0.0
|
|
98
|
+
assert result["weight"] == 0.0
|
|
99
|
+
assert result["melee"] == 0.0
|
|
100
|
+
assert result["speed"] == 0.0
|
|
101
|
+
assert result["temp"] == 0.0
|
|
102
|
+
assert result["fort"] == 0.0
|
|
103
|
+
assert result["craft"] == 0.0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_all_twelve_indices_round_trip() -> None:
|
|
107
|
+
"""Verify every stat slot maps to the correct name (catches off-by-one)."""
|
|
108
|
+
status = _FakeStatus(current=[(i, float(i + 1) * 10) for i in range(12)])
|
|
109
|
+
result = _current_stats_dict(status)
|
|
110
|
+
assert result is not None
|
|
111
|
+
for i, name in enumerate(_STAT_ORDER):
|
|
112
|
+
assert result[name] == float(i + 1) * 10, f"slot {i} ({name}) mismatched"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_synthetic_object_uses_per_index_getter() -> None:
|
|
116
|
+
"""Cryopod-decoded creatures expose status via ``get_property_value``
|
|
117
|
+
with an explicit ``index`` arg, not ``get_properties_by_name``."""
|
|
118
|
+
status = _PerIndexStatus(current={0: 1000.0, 1: 500.0, 4: 3000.0})
|
|
119
|
+
result = _current_stats_dict(status)
|
|
120
|
+
assert result is not None
|
|
121
|
+
assert result["hp"] == 1000.0
|
|
122
|
+
assert result["stam"] == 500.0
|
|
123
|
+
assert result["food"] == 3000.0
|
|
124
|
+
assert result["torp"] == 0.0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_out_of_range_indices_are_ignored() -> None:
|
|
128
|
+
"""Defensive: stray index 12+ entries must not blow past the 12-slot array."""
|
|
129
|
+
status = _FakeStatus(current=[(0, 100.0), (12, 999.0), (15, 999.0)])
|
|
130
|
+
result = _current_stats_dict(status)
|
|
131
|
+
assert result is not None
|
|
132
|
+
assert result["hp"] == 100.0
|
|
133
|
+
assert len(result) == 12
|
|
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
|