Expand achievement exports with progress and metadata
This commit is contained in:
parent
35918f24f1
commit
40b44521aa
17 changed files with 898 additions and 52 deletions
22
tests/fixtures/nte_assets/DataTable/DT_AchievementConfigInfo.json
vendored
Normal file
22
tests/fixtures/nte_assets/DataTable/DT_AchievementConfigInfo.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"Rows": {
|
||||
"battle_25": {
|
||||
"Amout": 50,
|
||||
"AchievementMainType": "AchievementMainType::MainTypeFight",
|
||||
"AwardInfo": [
|
||||
{"ItemID": "Annulith", "Amount": 10}
|
||||
],
|
||||
"TitleId": {
|
||||
"TableId": "/Game/Text/ST_Achievement.ST_Achievement",
|
||||
"Key": "battle_25_Title"
|
||||
},
|
||||
"ContentID": {
|
||||
"TableId": "/Game/Text/ST_Achievement.ST_Achievement",
|
||||
"Key": "battle_25_Content"
|
||||
},
|
||||
"Quality": "EHTAchievementQuality::High"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
{
|
||||
"ST_Achievement": {
|
||||
"battle_25_Title": "Devil Within II",
|
||||
"battle_25_Content": "Trigger Hexed ×50."
|
||||
},
|
||||
"ST_Player": {
|
||||
"character_1003": "Old Character Renamed",
|
||||
"character_1099": "New Character"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import unittest
|
|||
from nte_history_exporter.decoder.achievement import (
|
||||
ACHIEVEMENT_RECORD_MARKER,
|
||||
extract_achievement_ids,
|
||||
extract_achievement_records,
|
||||
reassemble_tcp_segments,
|
||||
)
|
||||
|
||||
|
|
@ -22,6 +23,13 @@ class AchievementPacketTests(unittest.TestCase):
|
|||
self.assertEqual(extract_achievement_ids(stream), ["Battle_30"])
|
||||
self.assertEqual(extract_achievement_ids(b"\x01\0\0\0\0"), [])
|
||||
|
||||
records = extract_achievement_records(stream)
|
||||
self.assertEqual(len(records), 2)
|
||||
self.assertEqual(records[0].achievement_id, "Battle_30")
|
||||
self.assertTrue(records[0].completed)
|
||||
self.assertEqual(records[1].achievement_id, "Playstation_035")
|
||||
self.assertEqual(records[1].status, "not_started")
|
||||
|
||||
def test_reassembles_tcp_data(self):
|
||||
self.assertEqual(
|
||||
reassemble_tcp_segments([(104, b"bar"), (100, b"foobar"), (100, b"foobar")]),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
from tests.support import * # noqa: F401,F403
|
||||
from nte_history_exporter import __version__
|
||||
from nte_history_exporter.decoder.achievement import AchievementRecord
|
||||
from nte_history_exporter.export.json_export import build_achievement_export_json
|
||||
from nte_history_exporter.live_capture.runner import _achievement_path
|
||||
|
||||
|
||||
class ExportContractTests(unittest.TestCase):
|
||||
|
|
@ -117,6 +120,83 @@ class ExportContractTests(unittest.TestCase):
|
|||
r"^218216016349_Limited_\d{8}_\d{6}(?:_\d+)?\.json$",
|
||||
)
|
||||
|
||||
def test_achievement_path_includes_user_uid_and_timestamp(self):
|
||||
path = _achievement_path("218216016349")
|
||||
|
||||
self.assertRegex(
|
||||
path.name,
|
||||
r"^218216016349_Achievements_\d{8}_\d{6}(?:_\d+)?\.json$",
|
||||
)
|
||||
|
||||
def test_achievement_export_has_versioned_metadata_and_records(self):
|
||||
export = build_achievement_export_json(
|
||||
[
|
||||
AchievementRecord("Battle_30", 10, 639_136_215_846_370_000),
|
||||
AchievementRecord("Battle_25", 20, 0),
|
||||
AchievementRecord("Playstation_017", 0, 0),
|
||||
],
|
||||
source="live_capture",
|
||||
capture_source="npcap",
|
||||
user_uid="218216016349",
|
||||
server_id="23003",
|
||||
)
|
||||
|
||||
self.assertEqual(export["format"], "nte-achievement-export")
|
||||
self.assertEqual(export["format_version"], 1)
|
||||
self.assertEqual(export["capture_source"], "npcap")
|
||||
self.assertEqual(export["user_uid"], "218216016349")
|
||||
self.assertEqual(export["account_region"], "EU")
|
||||
self.assertEqual(
|
||||
export["scan"],
|
||||
{
|
||||
"in_game": {
|
||||
"total_achievements": 2,
|
||||
"completed_achievements": 1,
|
||||
"in_progress_achievements": 1,
|
||||
"not_started_achievements": 0,
|
||||
},
|
||||
"playstation": {
|
||||
"total_achievements": 1,
|
||||
"completed_achievements": 0,
|
||||
"in_progress_achievements": 0,
|
||||
"not_started_achievements": 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
battle_30, battle_25 = export["categories"]["battle"]
|
||||
self.assertEqual(battle_30["name"], "Death Nova I")
|
||||
self.assertEqual(battle_30["completed_at"], "2026-05-05 23:46:24")
|
||||
self.assertEqual(battle_25["name"], "Devil Within II")
|
||||
self.assertEqual(battle_25["description"], "Trigger Hexed ×50.")
|
||||
self.assertEqual(battle_25["progress"], 20)
|
||||
self.assertEqual(battle_25["target"], 50)
|
||||
self.assertEqual(battle_25["quality"], "high")
|
||||
self.assertEqual(
|
||||
battle_25["rewards"], [{"item_id": "Annulith", "amount": 10}]
|
||||
)
|
||||
self.assertEqual(
|
||||
export["categories"]["playstation"][0]["name"], "Speed Above All"
|
||||
)
|
||||
|
||||
def test_unmapped_achievement_still_exports_capture_data(self):
|
||||
export = build_achievement_export_json(
|
||||
[AchievementRecord("FutureCategory_999", 7, 0)]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
export["categories"]["futurecategory"],
|
||||
[
|
||||
{
|
||||
"id": "FutureCategory_999",
|
||||
"platform": "in_game",
|
||||
"status": "in_progress",
|
||||
"progress": 7,
|
||||
"completed": False,
|
||||
"completed_at": None,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def test_extracts_user_uid_from_record_context(self):
|
||||
payload = (
|
||||
b"\x00" * 24
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ def sample_current():
|
|||
"DiceNormal": {"type": "item", "name": "Fabricated Dice", "rank": "S"},
|
||||
"OldTicket": {"type": "item", "name": "Retired Ticket", "rank": "B"},
|
||||
},
|
||||
"achievements.json": {},
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -54,11 +55,22 @@ class MappingUpdateTests(unittest.TestCase):
|
|||
self.assertNotIn("fork_retired", result.mappings["arcs.json"])
|
||||
self.assertNotIn("OldTicket", result.mappings["items.json"])
|
||||
self.assertNotEqual(result.mappings["characters.json"]["1003"], current["characters.json"]["1003"])
|
||||
self.assertEqual(result.report["changes"], {"additions": 4, "updates": 2, "deletions": 3})
|
||||
self.assertEqual(result.report["changes"], {"additions": 5, "updates": 2, "deletions": 3})
|
||||
self.assertIs(result.report["safety"]["authoritative_snapshot"], True)
|
||||
self.assertIs(result.report["safety"]["deletions_allowed"], True)
|
||||
self.assertIs(result.report["safety"]["uid_inputs_touched"], False)
|
||||
self.assertIs(result.report["safety"]["pool_mappings_touched"], False)
|
||||
self.assertEqual(
|
||||
result.mappings["achievements.json"]["battle_25"],
|
||||
{
|
||||
"name": "Devil Within II",
|
||||
"description": "Trigger Hexed ×50.",
|
||||
"category": "fight",
|
||||
"quality": "high",
|
||||
"target": 50,
|
||||
"rewards": [{"item_id": "Annulith", "amount": 10}],
|
||||
},
|
||||
)
|
||||
|
||||
def test_items_use_illustrations_as_filter_and_asset_tables_as_authority(self):
|
||||
_current, result = build_sample_update()
|
||||
|
|
@ -71,6 +83,7 @@ class MappingUpdateTests(unittest.TestCase):
|
|||
self.assertNotIn("DIceNormal", result.mappings["items.json"])
|
||||
self.assertNotIn("UnusedItem", result.mappings["items.json"])
|
||||
self.assertFalse(any(key.startswith("Characterawaken_") for key in result.mappings["items.json"]))
|
||||
self.assertFalse(any(key.startswith("Fashion_vehicle_") for key in result.mappings["items.json"]))
|
||||
|
||||
def test_future_mystery_box_pool_is_discovered_without_hard_coded_event_id(self):
|
||||
assets = load_assets(assets_root=SAMPLE_ASSETS)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from tests.support import * # noqa: F401,F403
|
||||
from nte_history_exporter.mappings import ACHIEVEMENTS
|
||||
|
||||
|
||||
class MappingAndUpdateTests(unittest.TestCase):
|
||||
|
|
@ -79,6 +80,14 @@ class MappingAndUpdateTests(unittest.TestCase):
|
|||
self.assertIn("name", info)
|
||||
self.assertIn(info.get("rank"), ("S", "A"))
|
||||
|
||||
self.assertTrue(ACHIEVEMENTS)
|
||||
battle_25 = ACHIEVEMENTS["battle_25"]
|
||||
self.assertEqual(battle_25["name"], "Devil Within II")
|
||||
self.assertEqual(battle_25["target"], 50)
|
||||
self.assertEqual(
|
||||
battle_25["rewards"], [{"item_id": "Annulith", "amount": 10}]
|
||||
)
|
||||
|
||||
self.assertTrue(ITEMS)
|
||||
for item_id, info in ITEMS.items():
|
||||
with self.subTest(item_id=item_id):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue