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
|
||||
inventory tables for quality and localization keys. Appearance-table IDs
|
||||
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
|
||||
`DT_GashaponLotteryGlobal`. No event IDs are hard-coded, so future rotations
|
||||
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.
|
||||
- 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.
|
||||
- Character-awakening and character-vehicle illustration entries are not
|
||||
independent pull rewards and are excluded.
|
||||
- Character-awakening illustration entries are not independent pull rewards
|
||||
and are excluded.
|
||||
- Orange, purple, and blue item qualities map to `S`, `A`, and `B`.
|
||||
|
||||
## UID compatibility boundary
|
||||
|
|
|
|||
|
|
@ -324,9 +324,16 @@ def _build_item_mapping(
|
|||
candidate_id.isdigit()
|
||||
or folded.startswith("fork_")
|
||||
or folded.startswith("characterawaken_")
|
||||
or folded.startswith("fashion_vehicle_")
|
||||
):
|
||||
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)
|
||||
match = next((source.get(folded) for source in sources if folded in source), 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])))
|
||||
|
||||
|
||||
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(
|
||||
rows: dict[str, Any],
|
||||
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": {
|
||||
"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("OldTicket", result.mappings["items.json"])
|
||||
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"]["deletions_allowed"], True)
|
||||
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("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"]))
|
||||
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):
|
||||
assets = load_assets(assets_root=SAMPLE_ASSETS)
|
||||
|
|
@ -113,7 +117,7 @@ class MappingUpdateTests(unittest.TestCase):
|
|||
tables["localization"]["ST_Item"].update(
|
||||
{"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))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue