diff --git a/modules/rpgmakermvmz.py b/modules/rpgmakermvmz.py index e967db2..650a251 100644 --- a/modules/rpgmakermvmz.py +++ b/modules/rpgmakermvmz.py @@ -85,50 +85,44 @@ LEAVE = False # Config (Default) # FIRSTLINESPEAKERS: Guess speaker from first line. -FIRSTLINESPEAKERS = True +FIRSTLINESPEAKERS = False # INLINE401SPEAKERS: Extract speaker from "Name「dialogue」" inline format on 401 lines. INLINE401SPEAKERS = False # FACENAME101: Map face name -> speaker. -FACENAME101 = False +FACENAME101 = True # Face name -> speaker mapping for FACENAME101. # Matching: if face string contains "_talk", split on it and look up the prefix; # otherwise try startswith against each key (longest key first). FACENAME101_MAP = { - "aglo": "Agro", - "Ai": "AI", - "cron": "Cron", - "diado": "Diad", - "doctor": "Doctor", - "dragon": "Dragon", - "dragonpeaple": "Dragonpeople", - "Eno": "Eno", - "fight": "Fight", - "kajua": "Kajua", - "last_boss": "Last Boss", - "MC": "MC", - "mizel": "Mizel", - "peaple": "People", - "professor": "Professor", - "ReceptionWoman": "ReceptionWoman", - "risa": "Risalue", - "roma": "Romasha", - "romasha": "Romasha", - "spina_dragonewt": "Spina Dragonewt", - "spina": "Spina", - "supi": "Supi", - "TMob": "TMob", - "TMobBlue": "TMobBlue", - "TMobGreen": "TMobGreen", - "TMobOrange": "TMobOrange", - "TMobPink": "TMobPink", - "TMobsyota": "TMobsyota", - "TMobYellow": "TMobYellow", - "TMobZERO": "TMobZERO", - "Trash": "Trash", - "underpeaple": "Underpeople", - "vanila": "Vanilla", - "Yudo": "Yudonge", - "zizi": "Zizi", + "_Daijin": "Minister", + "__HA": "Count Bampton", + "__Labyrinth": "Labyrinth", + "___Nimurda": "Nimurda", + "___Ren": "Ren", + "___prince": "Prince Elliot", + "___princess1": "Lilifa", + "___princess2": "Lilifa", + "___princess2_2": "Lilifa", + "___princess3": "Lilifa", + "___princess4": "Lilifa", + "___princess5": "Lilifa", + "__opHA": "Count Bampton", + "__opRen": "Ren", + "__opλ": "Lambda", + "__λ": "Lambda", + "_girl": "Receptionist", + "_guard_F": "Female Guard", + "_guard_M": "Guard", + "_guard_M1": "Guard 1", + "_guard_M10": "Guard 2", + "_king": "King", + "_knight": "Guard 3", + "_made1": "Maid 3", + "_made2": "Maid 2", + "_made3": "Maid 1", + "_queen": "Queen", + "_toy": "Toy Store Manager", + "made2": "Maid 2", } # Pre-sorted by key length descending so longer prefixes match first. FACENAME101_MAP_SORTED = sorted(FACENAME101_MAP.items(), key=lambda x: len(x[0]), reverse=True) @@ -684,6 +678,33 @@ def _101_speaker_name(raw_name: str) -> str: return plain.group(1).strip() if plain else "" +def _facename101_speaker(cmd) -> str | None: + """Resolve an unnamed code-101 speaker from its face filename.""" + params = cmd.get("parameters") or [] + if len(params) == 0: + return None + + # The face is only a fallback. An explicit name-window value is more + # authoritative, even when the face filename also has a known prefix. + if len(params) > 4 and _101_speaker_name(params[4]): + return None + + face_name = params[0] + if not isinstance(face_name, str) or not face_name: + return None + + if "_talk" in face_name: + prefix = face_name.split("_talk", 1)[0] + matched = FACENAME101_MAP.get(prefix) + if matched is not None: + return matched + + for prefix, name in FACENAME101_MAP_SORTED: + if face_name.startswith(prefix): + return name + return None + + def _entry_orig(entry) -> dict: """Return _original dict on a database entry, or empty dict if absent.""" orig = entry.get("_original") if isinstance(entry, dict) else None @@ -3139,27 +3160,12 @@ def searchCodes(page, pbar, jobList, filename): isVar = False # Check for face name mappings first (before other processing) - if FACENAME101 and len(codeList[i]["parameters"]) > 0: - faceName = codeList[i]["parameters"][0] - if isinstance(faceName, str) and faceName: - matchedSpeaker = None - - # 1) _talk_ pattern: split on "_talk" and exact-match the prefix - if "_talk" in faceName: - prefix = faceName.split("_talk")[0] - matchedSpeaker = FACENAME101_MAP.get(prefix) - - # 2) Longest-prefix startswith match - if matchedSpeaker is None: - for prefix, name in FACENAME101_MAP_SORTED: - if faceName.startswith(prefix): - matchedSpeaker = name - break - - if matchedSpeaker is not None: - speaker = matchedSpeaker - i += 1 - continue + if FACENAME101: + matchedSpeaker = _facename101_speaker(codeList[i]) + if matchedSpeaker is not None: + speaker = matchedSpeaker + i += 1 + continue # Grab String jaString = "" diff --git a/tests/test_mvmz_facename101.py b/tests/test_mvmz_facename101.py new file mode 100644 index 0000000..18a3e01 --- /dev/null +++ b/tests/test_mvmz_facename101.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Regression tests for code-101 face filename speaker mappings.""" + +import unittest + +import modules.rpgmakermvmz as mvmz + + +class FaceName101Tests(unittest.TestCase): + def test_recurring_character_faces_resolve_to_speakers(self): + expected = { + "__HA": "Count Bampton", + "___Nimurda": "Nimurda", + "___Ren": "Ren", + "___prince": "Prince Elliot", + "___princess1": "Lilifa", + "___princess2": "Lilifa", + "___princess2_2": "Lilifa", + "___princess3": "Lilifa", + "___princess4": "Lilifa", + "___princess5": "Lilifa", + "__opHA": "Count Bampton", + "__opRen": "Ren", + "__opλ": "Lambda", + "__λ": "Lambda", + } + + for face_name, speaker in expected.items(): + with self.subTest(face_name=face_name): + command = {"parameters": [face_name, 0, 0, 2, ""]} + self.assertEqual(mvmz._facename101_speaker(command), speaker) + + def test_all_detected_face_filenames_are_mapped(self): + detected = { + "_Daijin", + "__HA", + "__Labyrinth", + "___Nimurda", + "___Ren", + "___prince", + "___princess1", + "___princess2", + "___princess2_2", + "___princess3", + "___princess4", + "___princess5", + "__opHA", + "__opRen", + "__opλ", + "__λ", + "_girl", + "_guard_F", + "_guard_M", + "_guard_M1", + "_guard_M10", + "_king", + "_knight", + "_made1", + "_made2", + "_made3", + "_queen", + "_toy", + "made2", + } + + self.assertEqual(set(mvmz.FACENAME101_MAP), detected) + + def test_longest_prefix_wins_for_overlapping_face_names(self): + command = {"parameters": ["___princess2_2_smile", 0, 0, 2, ""]} + + self.assertEqual(mvmz._facename101_speaker(command), "Lilifa") + + def test_explicit_code101_name_takes_precedence(self): + command = {"parameters": ["___prince", 1, 0, 2, "リリファ"]} + + self.assertIsNone(mvmz._facename101_speaker(command)) + + +if __name__ == "__main__": + unittest.main()