Resolve vehicle livery mappings from matching illustration assets
This commit is contained in:
parent
7f7ad80b46
commit
593b23e1e2
5 changed files with 50 additions and 7 deletions
|
|
@ -80,6 +80,9 @@ from the committed mappings. It still writes the staged artifacts.
|
||||||
- Other pull rewards are selected by `GachaIllustrate` and resolved to the
|
- Other pull rewards are selected by `GachaIllustrate` and resolved to the
|
||||||
inventory tables for quality and localization keys. Appearance-table IDs
|
inventory tables for quality and localization keys. Appearance-table IDs
|
||||||
provide canonical casing for glider rewards.
|
provide canonical casing for glider rewards.
|
||||||
|
- Vehicle liveries are resolved from their illustration name and icon. Rows
|
||||||
|
whose icon identifies a different reward are treated as unreleased upstream
|
||||||
|
placeholders and skipped until the assets are corrected.
|
||||||
- Mystery Box rewards are selected from every row in
|
- Mystery Box rewards are selected from every row in
|
||||||
`DT_GashaponLotteryGlobal`. No event IDs are hard-coded, so future rotations
|
`DT_GashaponLotteryGlobal`. No event IDs are hard-coded, so future rotations
|
||||||
are included automatically. Rewards resolve through the inventory, capital,
|
are included automatically. Rewards resolve through the inventory, capital,
|
||||||
|
|
@ -89,8 +92,8 @@ from the committed mappings. It still writes the staged artifacts.
|
||||||
currencies, are categorized as items.
|
currencies, are categorized as items.
|
||||||
- Names are resolved strictly through `Localization/en/game.json`. A missing or
|
- Names are resolved strictly through `Localization/en/game.json`. A missing or
|
||||||
ambiguous key fails the update instead of falling back to a DT value.
|
ambiguous key fails the update instead of falling back to a DT value.
|
||||||
- Character-awakening and character-vehicle illustration entries are not
|
- Character-awakening illustration entries are not independent pull rewards
|
||||||
independent pull rewards and are excluded.
|
and are excluded.
|
||||||
- Orange, purple, and blue item qualities map to `S`, `A`, and `B`.
|
- Orange, purple, and blue item qualities map to `S`, `A`, and `B`.
|
||||||
|
|
||||||
## UID compatibility boundary
|
## UID compatibility boundary
|
||||||
|
|
|
||||||
|
|
@ -324,9 +324,16 @@ def _build_item_mapping(
|
||||||
candidate_id.isdigit()
|
candidate_id.isdigit()
|
||||||
or folded.startswith("fork_")
|
or folded.startswith("fork_")
|
||||||
or folded.startswith("characterawaken_")
|
or folded.startswith("characterawaken_")
|
||||||
or folded.startswith("fashion_vehicle_")
|
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
if folded.startswith("fashion_vehicle_"):
|
||||||
|
illustration = tables["illustrations"].get(candidate_id)
|
||||||
|
if illustration is not None:
|
||||||
|
vehicle_meta = _normalise_vehicle_livery(candidate_id, illustration, translations)
|
||||||
|
if vehicle_meta is None:
|
||||||
|
continue
|
||||||
|
result[candidate_id] = vehicle_meta
|
||||||
|
continue
|
||||||
sources = (inventory, capital, appearances, vehicle_inventory)
|
sources = (inventory, capital, appearances, vehicle_inventory)
|
||||||
match = next((source.get(folded) for source in sources if folded in source), None)
|
match = next((source.get(folded) for source in sources if folded in source), None)
|
||||||
if match is None:
|
if match is None:
|
||||||
|
|
@ -343,6 +350,31 @@ def _build_item_mapping(
|
||||||
return dict(sorted(result.items(), key=lambda pair: (pair[0].casefold(), pair[0])))
|
return dict(sorted(result.items(), key=lambda pair: (pair[0].casefold(), pair[0])))
|
||||||
|
|
||||||
|
|
||||||
|
def _normalise_vehicle_livery(
|
||||||
|
item_id: str,
|
||||||
|
row: Any,
|
||||||
|
translations: dict[str, list[tuple[str, str]]],
|
||||||
|
) -> dict[str, str] | None:
|
||||||
|
"""Resolve a livery from its illustration, ignoring mismatched placeholder rows."""
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
raise MappingUpdateError(f"NTE_Assets row {item_id} must be an object")
|
||||||
|
head_icon = row.get("HeadIcon")
|
||||||
|
asset_path = head_icon.get("AssetPathName") if isinstance(head_icon, dict) else None
|
||||||
|
if not isinstance(asset_path, str) or not asset_path:
|
||||||
|
raise MappingUpdateError(f"NTE_Assets vehicle livery row {item_id} has no HeadIcon")
|
||||||
|
icon_id = asset_path.rsplit("/", 1)[-1].split(".", 1)[0]
|
||||||
|
if icon_id.casefold() != item_id.casefold():
|
||||||
|
return None
|
||||||
|
item_name = row.get("ItemName_Override")
|
||||||
|
if not isinstance(item_name, dict):
|
||||||
|
raise MappingUpdateError(f"NTE_Assets vehicle livery row {item_id} has no ItemName_Override")
|
||||||
|
return {
|
||||||
|
"type": "cosmetic",
|
||||||
|
"name": _translate_name(item_id, item_name, translations).strip(),
|
||||||
|
"rank": "S",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _build_achievement_mapping(
|
def _build_achievement_mapping(
|
||||||
rows: dict[str, Any],
|
rows: dict[str, Any],
|
||||||
translations: dict[str, list[tuple[str, str]]],
|
translations: dict[str, list[tuple[str, str]]],
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
[{"Rows":{"1003":{},"1099":{},"fork_alpha":{},"fork_new":{},"DIceNormal":{},"Dice_ticket_03":{},"Fashion_glide_2000":{},"Characterawaken_1003":{}}}]
|
[{"Rows":{"1003":{},"1099":{},"fork_alpha":{},"fork_new":{},"DIceNormal":{},"Dice_ticket_03":{},"Fashion_glide_2000":{},"Characterawaken_1003":{},"Fashion_vehicle_1010_V008":{"HeadIcon":{"AssetPathName":"/Game/UI/UI_Icon/Item/Fashion_vehicle_1010_V008.Fashion_vehicle_1010_V008"},"ItemName_Override":{"TableId":"/Game/Text/ST_VehicleData.ST_VehicleData","Key":"vehicle_1010_livery"}},"Fashion_vehicle_1072_V006":{"HeadIcon":{"AssetPathName":"/Game/UI/UI_Icon/Item/Fashion_vehicle_1036_V043.Fashion_vehicle_1036_V043"},"ItemName_Override":{"TableId":"/Game/Text/ST_VehicleData.ST_VehicleData","Key":"vehicle_placeholder_livery"}}}}]
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,9 @@
|
||||||
},
|
},
|
||||||
"ST_Appearance": {
|
"ST_Appearance": {
|
||||||
"glider_2000": "Inventory Glider"
|
"glider_2000": "Inventory Glider"
|
||||||
|
},
|
||||||
|
"ST_VehicleData": {
|
||||||
|
"vehicle_1010_livery": "Tiger Incoming! - Livery",
|
||||||
|
"vehicle_placeholder_livery": "Sandy Mirage - Livery"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class MappingUpdateTests(unittest.TestCase):
|
||||||
self.assertNotIn("fork_retired", result.mappings["arcs.json"])
|
self.assertNotIn("fork_retired", result.mappings["arcs.json"])
|
||||||
self.assertNotIn("OldTicket", result.mappings["items.json"])
|
self.assertNotIn("OldTicket", result.mappings["items.json"])
|
||||||
self.assertNotEqual(result.mappings["characters.json"]["1003"], current["characters.json"]["1003"])
|
self.assertNotEqual(result.mappings["characters.json"]["1003"], current["characters.json"]["1003"])
|
||||||
self.assertEqual(result.report["changes"], {"additions": 5, "updates": 2, "deletions": 3})
|
self.assertEqual(result.report["changes"], {"additions": 6, "updates": 2, "deletions": 3})
|
||||||
self.assertIs(result.report["safety"]["authoritative_snapshot"], True)
|
self.assertIs(result.report["safety"]["authoritative_snapshot"], True)
|
||||||
self.assertIs(result.report["safety"]["deletions_allowed"], True)
|
self.assertIs(result.report["safety"]["deletions_allowed"], True)
|
||||||
self.assertIs(result.report["safety"]["uid_inputs_touched"], False)
|
self.assertIs(result.report["safety"]["uid_inputs_touched"], False)
|
||||||
|
|
@ -83,7 +83,11 @@ class MappingUpdateTests(unittest.TestCase):
|
||||||
self.assertNotIn("DIceNormal", result.mappings["items.json"])
|
self.assertNotIn("DIceNormal", result.mappings["items.json"])
|
||||||
self.assertNotIn("UnusedItem", 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("Characterawaken_") for key in result.mappings["items.json"]))
|
||||||
self.assertFalse(any(key.startswith("Fashion_vehicle_") for key in result.mappings["items.json"]))
|
self.assertEqual(
|
||||||
|
result.mappings["items.json"]["Fashion_vehicle_1010_V008"],
|
||||||
|
{"type": "cosmetic", "name": "Tiger Incoming! - Livery", "rank": "S"},
|
||||||
|
)
|
||||||
|
self.assertNotIn("Fashion_vehicle_1072_V006", result.mappings["items.json"])
|
||||||
|
|
||||||
def test_future_mystery_box_pool_is_discovered_without_hard_coded_event_id(self):
|
def test_future_mystery_box_pool_is_discovered_without_hard_coded_event_id(self):
|
||||||
assets = load_assets(assets_root=SAMPLE_ASSETS)
|
assets = load_assets(assets_root=SAMPLE_ASSETS)
|
||||||
|
|
@ -113,7 +117,7 @@ class MappingUpdateTests(unittest.TestCase):
|
||||||
tables["localization"]["ST_Item"].update(
|
tables["localization"]["ST_Item"].update(
|
||||||
{"wow_ticket": "Wowzers Ticket", "wow_frame": "Wowzers Frame"}
|
{"wow_ticket": "Wowzers Ticket", "wow_frame": "Wowzers Frame"}
|
||||||
)
|
)
|
||||||
tables["localization"]["ST_VehicleData"] = {"vehicle_wow": "Wowmobile"}
|
tables["localization"]["ST_VehicleData"]["vehicle_wow"] = "Wowmobile"
|
||||||
|
|
||||||
result = build_mapping_update(sample_current(), replace(assets, tables=tables))
|
result = build_mapping_update(sample_current(), replace(assets, tables=tables))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue