[Accessibility] Text to Speech (#2487)

This commit is contained in:
David Chavez 2023-03-02 09:27:28 +01:00 committed by GitHub
parent 72777a0eb2
commit 21466192e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2279 additions and 28 deletions

View File

@ -141,14 +141,21 @@ static void ExporterProgramEnd()
}
}
if(item.find("accessibility") != std::string::npos) {
std::string extension = splitPath.at(splitPath.size() - 1);
splitPath.pop_back();
if(extension == "json"){
auto fileData = File::ReadAllBytes(item);
printf("Adding accessibility texts %s\n", StringHelper::Split(item, "texts/")[1].c_str());
otrArchive->AddFile(StringHelper::Split(item, "Extract/assets/")[1], (uintptr_t)fileData.data(), fileData.size());
}
continue;
}
auto fileData = File::ReadAllBytes(item);
printf("otrArchive->AddFile(%s)\n", StringHelper::Split(item, "Extract/")[1].c_str());
otrArchive->AddFile(StringHelper::Split(item, "Extract/")[1], (uintptr_t)fileData.data(), fileData.size());
}
//otrArchive->AddFile("Audiobank", (uintptr_t)Globals::Instance->GetBaseromFile("Audiobank").data(), Globals::Instance->GetBaseromFile("Audiobank").size());
//otrArchive->AddFile("Audioseq", (uintptr_t)Globals::Instance->GetBaseromFile("Audioseq").data(), Globals::Instance->GetBaseromFile("Audioseq").size());
//otrArchive->AddFile("Audiotable", (uintptr_t)Globals::Instance->GetBaseromFile("Audiotable").data(), Globals::Instance->GetBaseromFile("Audiotable").size());
}
}

View File

@ -0,0 +1,16 @@
{
"file1": "File 1",
"file2": "File 2",
"file3": "File 3",
"options": "Options",
"copy": "Copy",
"erase": "Erase",
"quit": "Quit",
"confirm": "Yes",
"audio_stereo": "Sound - Stereo",
"audio_mono": "Sound - Mono",
"audio_headset": "Sound - Headset",
"audio_surround": "Sound - Surround",
"target_switch": "Targetting Mode - Switch",
"target_hold": "Targetting Mode - Hold"
}

View File

@ -0,0 +1,16 @@
{
"file1": "Fichier 1",
"file2": "Fichier 2",
"file3": "Fichier 3",
"options": "Options",
"copy": "Copier",
"erase": "Effacer",
"quit": "Retour",
"confirm": "Oui",
"audio_stereo": "Son - Stéréo",
"audio_mono": "Son - Mono",
"audio_headset": "Son - Casque",
"audio_surround": "Son - Surround",
"target_switch": "Visée - Fixe",
"target_hold": "Visée - Maintenue"
}

View File

@ -0,0 +1,16 @@
{
"file1": "Datei 1",
"file2": "Datei 2",
"file3": "Datei 3",
"options": "Optionen",
"copy": "Kopieren",
"erase": "Löschen",
"quit": "Zurück",
"confirm": "Ja",
"audio_stereo": "Sound - Stereo",
"audio_mono": "Sound - Mono",
"audio_headset": "Sound - Kopfhörer",
"audio_surround": "Sound - Surround",
"target_switch": "Zielerfassung - Einmal drücken",
"target_hold": "Zielerfassung - Trigger halten"
}

View File

@ -0,0 +1,219 @@
{
"health": "health $0",
"magic": "magic $0",
"rupees": "rupees $0",
"0": "Deku Stick $0",
"1": "Deku Nut $0",
"2": "Bomb $0",
"3": "Fairy Bow $0",
"4": "Fire Arrow",
"5": "Din's Fire",
"6": "Fairy Slingshot $0",
"7": "Fairy Ocarina",
"8": "Ocarina of Time",
"9": "Bombchu $0",
"10": "Hookshot",
"11": "Longshot",
"12": "Ice Arrow",
"13": "Farore's Wind",
"14": "Boomerang",
"15": "Lens of Truth",
"16": "Magic Beans $0",
"17": "Megaton Hammer",
"18": "Light Arrow",
"19": "Nayru's Love",
"20": "Empty Bottle",
"21": "Red Potion",
"22": "Green Potion",
"23": "Blue Potion",
"24": "Fairy",
"25": "Fish",
"26": "Milk Bottle",
"27": "Ruto's Letter",
"28": "Blue Fire",
"29": "Bugs",
"30": "Big Poe",
"31": "Milk Bottle (Half)",
"32": "Poe",
"33": "Weird Egg",
"34": "Chicken",
"35": "Zelda's Letter",
"36": "Keaton Mask",
"37": "Skull Mask",
"38": "Spooky Mask",
"39": "Bunny Mask",
"40": "Goron Mask",
"41": "Zora Mask",
"42": "Gerudo Mask",
"43": "Mask of Truth",
"44": "Sold Out",
"45": "Pocket Egg",
"46": "Pocket Cucco",
"47": "Cojiro",
"48": "Odd Mushroom",
"49": "Odd Potion",
"50": "Saw",
"51": "Broken Sword",
"52": "Prescription",
"53": "Eyeball Frog",
"54": "Eyedrops",
"55": "Claim Check",
"56": "Bow Fire Arrow",
"57": "Bow Ice Arrow",
"58": "Bow Light Arrow",
"59": "Kokiri Sword",
"60": "Master Sword",
"61": "Giant's Knife",
"62": "Deku Shield",
"63": "Hylian Shield",
"64": "Mirror Shield",
"65": "Kokiri Tunic",
"66": "Goron Tunic",
"67": "Zora Tunic",
"68": "Kokiri Boots",
"69": "Iron Boots",
"70": "Hover Boots",
"71": "Bullet Bag (Holds 30)",
"72": "Bullet Bag (Holds 40)",
"73": "Bullet Bag (Holds 50)",
"74": "Quiver (Holds 30)",
"75": "Quiver (Holds 40)",
"76": "Quiver (Holds 50)",
"77": "Bomb Bag (Holds 20)",
"78": "Bomb Bag (Holds 30)",
"79": "Bomb Bag (Holds 40)",
"80": "Goron's Bracelet",
"81": "Silver Gauntlets",
"82": "Golden Gauntlets",
"83": "Silver Scale",
"84": "Golden Scale",
"85": "Giant's Knife (Broken)",
"86": "WALLET ADULT",
"87": "Giant's Wallet",
"88": "Deku Seeds",
"89": "Fishing Pole",
"90": "Minuet of Forest",
"91": "Bolero of Fire",
"92": "Serenade of Water",
"93": "Requiem of Spirit",
"94": "Nocturne of Shadow",
"95": "Prelude of Light",
"96": "Zelda's Lullaby",
"97": "Epona's Song",
"98": "Saria's Song",
"99": "Sun's Song",
"100": "Song of Time",
"101": "Song of Storms",
"102": "Forest Medallion",
"103": "Fire Medallion",
"104": "Water Medallion",
"105": "Spirit Medallion",
"106": "Shadow Medallion",
"107": "Light Medallion",
"108": "Kokiri's Emerald",
"109": "Goron's Ruby",
"110": "Zora Sapphire",
"111": "Stone of Agony",
"112": "Gerudo's Card",
"113": "Skulltula Token $0",
"114": "Heart Container $0",
"115": "Piece of Heart",
"116": "Boss Key",
"117": "Compass",
"118": "Dungeon Map",
"119": "Small Key",
"120": "MAGIC SMALL",
"121": "MAGIC LARGE",
"122": "PIECE OF HEART 2",
"123": "INVALID 1",
"124": "INVALID 2",
"125": "INVALID 3",
"126": "INVALID 4",
"127": "INVALID 5",
"128": "INVALID 6",
"129": "INVALID 7",
"130": "Milk",
"131": "Recovery Heart",
"132": "Green Rupee",
"133": "Blue Rupee",
"134": "Red Rupee",
"135": "Purple Rupee",
"136": "Gold Rupee",
"137": "INVALID 8",
"138": "STICKS 5",
"139": "STICKS 10",
"140": "NUTS 5",
"141": "NUTS 10",
"142": "BOMBS 5",
"143": "BOMBS 10",
"144": "BOMBS 20",
"145": "BOMBS 30",
"146": "ARROWS SMALL",
"147": "ARROWS MEDIUM",
"148": "ARROWS LARGE",
"149": "SEEDS 30",
"150": "BOMBCHUS 5",
"151": "BOMBCHUS 20",
"152": "STICK UPGRADE 20",
"153": "STICK UPGRADE 30",
"154": "NUT UPGRADE 30",
"155": "NUT UPGRADE 40",
"256": "Haunted Wasteland",
"257": "Gerudos Fortress",
"258": "Gerudo Valley",
"259": "Hylia Lakeside",
"260": "Lon Lon Ranch",
"261": "Market",
"262": "Hyrule Field",
"263": "Death Mountain",
"264": "Kakariko Village",
"265": "Lost Woods",
"266": "Kokiri Forest",
"267": "Zoras Domain",
"268": "",
"269": "",
"270": "",
"271": "",
"272": "",
"273": "",
"274": "",
"275": "",
"276": "",
"277": "",
"278": "",
"279": "",
"280": "",
"281": "",
"282": "",
"283": "",
"284": "",
"285": "",
"286": "",
"287": "",
"288": "",
"289": "",
"290": "",
"291": "",
"292": "Hyrule Field",
"293": "Kakariko Village",
"294": "Graveyard",
"295": "Zoras River",
"296": "Kokiri Forest",
"297": "Sacred Forest Meadow",
"298": "Lake Hylia",
"299": "Zoras Domain",
"300": "Zoras Fountain",
"301": "Gerudo Valley",
"302": "Lost Woods",
"303": "Desert Colossus",
"304": "Gerudo's Fortress",
"305": "Haunted Wasteland",
"306": "Market",
"307": "Hyrule Castle",
"308": "Death Mountain Trail",
"309": "Death Mountain Crater",
"310": "Goron City",
"311": "Lon Lon Ranch",
"312": "Question Mark",
"313": "Ganon's Castle"
}

View File

@ -0,0 +1,219 @@
{
"health": "vie $0",
"magic": "magie $0",
"rupees": "rubis $0",
"0": "Bâton Mojo $0",
"1": "Noix Mojo $0",
"2": "Bombes $0",
"3": "Arc des Fées $0",
"4": "Flèche de Feu",
"5": "Feu de Din",
"6": "Lance-Pierre des Fées $0",
"7": "Ocarina des Fées",
"8": "Ocarina of Temps",
"9": "Missiles Teigneux $0",
"10": "Grappin",
"11": "Super Grappin",
"12": "Flèche de Glace",
"13": "Vent de Farore",
"14": "Boomerang",
"15": "Monocle de Vérité",
"16": "Haricot Magique $0",
"17": "Masse des Titans",
"18": "Flèche de Lumière",
"19": "Amour de Nayru",
"20": "Bouteille Vide",
"21": "Potion Rouge",
"22": "Potion Verte",
"23": "Potion Bleue",
"24": "Fée",
"25": "Poisson",
"26": "Lait de Lon Lon",
"27": "Lettre de Ruto",
"28": "Flammme Bleue",
"29": "Insectes",
"30": "Âme",
"31": "Lait de Lon Lon (moitié)",
"32": "Esprit",
"33": "Oeuf Curieux",
"34": "Poulet",
"35": "Lettre de Zelda",
"36": "Masque du Renard",
"37": "Masque de Mort",
"38": "Masque d'Effroi",
"39": "Masque du Lapin",
"40": "Masque de Goron",
"41": "Masque de Zora",
"42": "Masque de Gerudo",
"43": "Masque de Vérité",
"44": "VENDU",
"45": "Oeuf de Poche",
"46": "Cocotte de poche",
"47": "P'tit Poulet",
"48": "Champignon suspect",
"49": "Mixture suspecte",
"50": "Scie du chasseur",
"51": "Épée de Goron (brisée)",
"52": "Ordonnance",
"53": "Crapaud-qui-louche",
"54": "Gouttes",
"55": "Certificat",
"56": "Arc et Flèche de Feu",
"57": "Arc et Flèche de Glace",
"58": "Arc et Flèche de Lumière",
"59": "Épée Kokiri",
"60": "Épée de Légende",
"61": "Lame des Géants",
"62": "Bouclier Mojo",
"63": "Bouclier Hylien",
"64": "Bouclier Miroir",
"65": "Tunique Kokiri",
"66": "Tunique Goron",
"67": "Tunique Zora",
"68": "Bottes Kokiri",
"69": "Bottes de plomb",
"70": "Bottes des airs",
"71": "Sac de graines (Contient 30)",
"72": "Sac de graines (Contient 40)",
"73": "Sac de graines (Contient 50)",
"74": "Carquois (Contient 30)",
"75": "Carquois (Contient 40)",
"76": "Carquois (Contient 50)",
"77": "Sac de bombes (Contient 20)",
"78": "Sac de bombes (Contient 30)",
"79": "Sac de bombes (Contient 40)",
"80": "Bracelet Goron",
"81": "Gantelets d'argent",
"82": "Gentelets d'or",
"83": "Écaille d'argent",
"84": "Écaille d'or",
"85": "Lame des Géants (Brisée)",
"86": "GRANDE BOURSE",
"87": "Bourse de Géant",
"88": "Deku Seeds",
"89": "Canne à pèche",
"90": "Menuet des Bois",
"91": "Boléro du Feu",
"92": "Sérénade de l'Eau",
"93": "Requiem des Esprits",
"94": "Nocturne de l'Ombre",
"95": "Prélude de la Lumière",
"96": "Berceuse de Zelda",
"97": "Chant d'Epona",
"98": "Chant de Saria",
"99": "Chant du Soleil",
"100": "Chant du Temps",
"101": "Chant des Tempêtes",
"102": "Médaillon de la Forêt",
"103": "Médaillon du Feu",
"104": "Médaillon de l'Eau",
"105": "Médaillon de l'Esprit",
"106": "Médaillon de l'Ombre",
"107": "Médaillon de la Lumière",
"108": "Émeraude Kokiri",
"109": "Rubis Goron",
"110": "Saphir Zora",
"111": "Pierre de Souffrance",
"112": "Carte Gerudo",
"113": "Skulltula d'or $0",
"114": "Coeur d'Énergie $0",
"115": "Quart de Coeur",
"116": "Clé d'or",
"117": "Boussole",
"118": "Carte du Donjon",
"119": "Petite Clé",
"120": "PETITE BOUTEILLE DE MAGIE",
"121": "GRANDE BOUTEILLE DE MAGIE",
"122": "QUART DE COEUR 2",
"123": "INVALIDE 1",
"124": "INVALIDE 2",
"125": "INVALIDE 3",
"126": "INVALIDE 4",
"127": "INVALIDE 5",
"128": "INVALIDE 6",
"129": "INVALIDE 7",
"130": "Lait de Lon Lon",
"131": "Coeur de Vie",
"132": "Rubis Vert",
"133": "Rubis Bleu",
"134": "Rubis Rouge",
"135": "Rubis Pourpre",
"136": "Énorme Rubis",
"137": "INVALIDE 8",
"138": "BÂTON MOJO 5",
"139": "BÂTON MOJO 10",
"140": "NOIX MOJO 5",
"141": "NOIX MOJO 10",
"142": "BOMBES 5",
"143": "BOMBES 10",
"144": "BOMBES 20",
"145": "BOMBES 30",
"146": "ARROWS SMALL",
"147": "ARROWS MEDIUM",
"148": "ARROWS LARGE",
"149": "GRAINES MOJO 30",
"150": "MISSILES TEIGNEUX 5",
"151": "MISSILES TEIGNEUX 20",
"152": "AMÉLIORATION BÂTON MOJO 20",
"153": "AMÉLIORATION BÂTON MOJO 30",
"154": "AMÉLIORATION NOIX MOJO 30",
"155": "AMÉLIORATION NOIX MOJO 40",
"256": "Désert Hanté",
"257": "Forteresse Gerudo",
"258": "Vallée Gerudo",
"259": "Laboratoire du Lac",
"260": "Ranch Lon Lon",
"261": "Place du Marché",
"262": "Plaine d'Hyrule",
"263": "Montagne du Péril",
"264": "Village Cocorico",
"265": "Bois Perdus",
"266": "Forêt Kokiri",
"267": "Domaine Zora",
"268": "",
"269": "",
"270": "",
"271": "",
"272": "",
"273": "",
"274": "",
"275": "",
"276": "",
"277": "",
"278": "",
"279": "",
"280": "",
"281": "",
"282": "",
"283": "",
"284": "",
"285": "",
"286": "",
"287": "",
"288": "",
"289": "",
"290": "",
"291": "",
"292": "Plaine d'Hyrule",
"293": "Village Cocorico",
"294": "Cimetière",
"295": "Rivière Zora",
"296": "Forêt Kokiri",
"297": "Bosquet Sacré",
"298": "Lac Hylia",
"299": "Domaine Zora",
"300": "Fountaine Zora",
"301": "Vallée Gerudo",
"302": "Bois Perdus",
"303": "Colosse du Désert",
"304": "Forteresse Gerudo",
"305": "Désert Hanté",
"306": "Place du Marché",
"307": "Château d'Hyrule",
"308": "Chemin du Péril",
"309": "Cratère du Péril",
"310": "Village Goron",
"311": "Ranch Lon Lon",
"312": "Point d'interrogation",
"313": "Château de Ganon"
}

View File

@ -0,0 +1,219 @@
{
"health": "Energie $0",
"magic": "Magie $0",
"rupees": "Rubine $0",
"0": "Deku-Stab $0",
"1": "Deku-Nuß $0",
"2": "Bombe $0",
"3": "Feen-Bogen $0",
"4": "Feuer-Pfeil",
"5": "Dins Feuerinferno",
"6": "Feen-Schleuder $0",
"7": "Feen-Okarina",
"8": "Okarina der Zeit",
"9": "Krabbelmine $0",
"10": "Fanghaken",
"11": "Enterhaken",
"12": "Eis-Pfeil",
"13": "Farores Donnersturm",
"14": "Bumerang",
"15": "Auge der Wahrheit",
"16": "Wundererbsen $0",
"17": "Stahlhammer",
"18": "Licht-Pfeil",
"19": "Nayrus Umarmung",
"20": "Flasche",
"21": "Rotes Elixier",
"22": "Grünes Elixier",
"23": "Blaues Elixier",
"24": "Fee",
"25": "Fisch",
"26": "Milch",
"27": "Brief",
"28": "Blaues Feuer",
"29": "Käfer",
"30": "Nachtschwärmer",
"31": "Milch (1/2)",
"32": "Irrlicht",
"33": "Seltsames Ei",
"34": "Huhn",
"35": "Zeldas Brief",
"36": "Fuchs-Maske",
"37": "Schädel-Maske",
"38": "Geister-Maske",
"39": "Hasenohren",
"40": "Goronen-Maske",
"41": "Zora-Maske",
"42": "Gerudo-Maske",
"43": "Maske des Wissens",
"44": "Verkauft",
"45": "Ei",
"46": "Kiki",
"47": "Henni",
"48": "Schimmelpilz",
"49": "Modertrank",
"50": "Säge",
"51": "Goronen-Schwert (zerbrochen)",
"52": "Rezept",
"53": "Glotzfrosch",
"54": "Augentropfen",
"55": "Zertifikat",
"56": "Bogen Feuer-Pfeil",
"57": "Bogen Eis-Pfeil",
"58": "Bogen Licht-Pfeil",
"59": "Kokiri-Schwert",
"60": "Master-Schwert",
"61": "Langschwert",
"62": "Deku-schild",
"63": "Hylia-Schild",
"64": "Spiegel-Schild",
"65": "Kokiri-Rüstung",
"66": "Goronen-Rüstung",
"67": "Zora-Rüstung",
"68": "Lederstiefel",
"69": "Eisenstiefel",
"70": "Gleitstiefel",
"71": "Munitionstasche (30)",
"72": "Munitionstasche (40)",
"73": "Munitionstasche (50)",
"74": "Köcher (30)",
"75": "Köcher (40)",
"76": "Köcher (50)",
"77": "Bombentasche (20)",
"78": "Bombentasche (30)",
"79": "Bombentasche (40)",
"80": "Goronen-Armband",
"81": "Krafthandschuh",
"82": "Titanhandschuh",
"83": "Silberschuppe",
"84": "Goldschuppe",
"85": "Langschwert (gebrochen)",
"86": "Große Börse",
"87": "Riesenbörse",
"88": "Deku-Kerne",
"89": "Angel",
"90": "Menuett des Waldes",
"91": "Bolero des Feuers",
"92": "Serenade des Wassers",
"93": "Requiem der Geister",
"94": "Nocturne des Schattens",
"95": "Kantate des Lichts",
"96": "Zeldas Wiegenlied",
"97": "Eponas Lied",
"98": "Salias Lied",
"99": "Hymne der Sonne",
"100": "Hymne der Zeit",
"101": "Song of Storms",
"102": "Amulett des Waldes",
"103": "Amulett des Feuers",
"104": "Amulett des Wassers",
"105": "Amulett der Geister",
"106": "Amulett des Schattens",
"107": "Amulett des Lichts",
"108": "Kokiri-Smaragd",
"109": "Goronen-Opal",
"110": "Zora-Saphir",
"111": "Stein des Wissens",
"112": "Gerudo-Paß",
"113": "Skulltula-Symbol $0",
"114": "Herzcontainer $0",
"115": "Herzteil",
"116": "Master-Schlüssel",
"117": "Kompaß",
"118": "Labyrinth-Karte",
"119": "Kleiner Schlüssel",
"120": "MAGIE KLEIN",
"121": "MAGIE GROß",
"122": "HERZTEIL 2",
"123": "UNGÜLTIG 1",
"124": "UNGÜLTIG 2",
"125": "UNGÜLTIG 3",
"126": "UNGÜLTIG 4",
"127": "UNGÜLTIG 5",
"128": "UNGÜLTIG 6",
"129": "UNGÜLTIG 7",
"130": "Milch",
"131": "Herz",
"132": "ein Rubin",
"133": "5 Rubine",
"134": "20 Rubine",
"135": "50 Rubine",
"136": "200 Rubine",
"137": "UNGÜLTIG 8",
"138": "STÄBE 5",
"139": "STÄBE 10",
"140": "NÜSSE 5",
"141": "NÜSSE 10",
"142": "BOMBEN 5",
"143": "BOMBEN 10",
"144": "BOMBEN 20",
"145": "BOMBEN 30",
"146": "PFEILE KLEIN",
"147": "PFEILE MITTEL",
"148": "PFEILE GROß",
"149": "KERNE 30",
"150": "KRABBELMINEN 5",
"151": "KRABBELMINEN 20",
"152": "STAB UPGRADE 20",
"153": "STAB UPGRADE 30",
"154": "NUß UPGRADE 30",
"155": "NUß UPGRADE 40",
"256": "Gespensterwüste",
"257": "Gerudo-Festung",
"258": "Gerudotal",
"259": "Hylia-See",
"260": "Lon Lon-Farm",
"261": "Marktplatz",
"262": "Hylianische Steppe",
"263": "Todesberg",
"264": "Kakariko",
"265": "Verlorene Wälder",
"266": "Kokiri-Wald",
"267": "Zoras Reich",
"268": "",
"269": "",
"270": "",
"271": "",
"272": "",
"273": "",
"274": "",
"275": "",
"276": "",
"277": "",
"278": "",
"279": "",
"280": "",
"281": "",
"282": "",
"283": "",
"284": "",
"285": "",
"286": "",
"287": "",
"288": "",
"289": "",
"290": "",
"291": "",
"292": "Hylianische Steppe",
"293": "Kakariko",
"294": "Friedhof",
"295": "Zora-Fluss",
"296": "Kokiri-Wald",
"297": "Heilige Lichtung",
"298": "Hylia-See",
"299": "Zoras Reich",
"300": "Zoras Quelle",
"301": "Gerudotal",
"302": "Verlorene Wälder",
"303": "Wüstenkoloss",
"304": "Gerudo-Festung",
"305": "Gespensterwüste",
"306": "Marktplatz",
"307": "Schloß Hyrule",
"308": "Pfad zum Todesberg",
"309": "Todeskrater",
"310": "Goronia",
"311": "Lon Lon-Farm",
"312": "Fragezeichen",
"313": "Teufelsturm"
}

View File

@ -0,0 +1,18 @@
{
"minutes_plural" : "$0 minutes",
"minutes_singular" : "$0 minute",
"seconds_plural" : "$0 seconds",
"seconds_singular" : "$0 second",
"input_button_a": "the A button",
"input_button_b": "the B button",
"input_button_c": "the C button",
"input_button_l": "the L button",
"input_button_r": "the R button",
"input_button_z": "the Z button",
"input_button_c_up": "C Up",
"input_button_c_down": "C Down",
"input_button_c_left": "C Left",
"input_button_c_right": "C Right",
"input_analog_stick": "the Analog Stick",
"input_d_pad": "the D-Pad"
}

View File

@ -0,0 +1,18 @@
{
"minutes_plural" : "$0 minutes",
"minutes_singular" : "$0 minute",
"seconds_plural" : "$0 secondes",
"seconds_singular" : "$0 seconde",
"input_button_a": "le bouton A",
"input_button_b": "le bouton B",
"input_button_c": "le bouton C",
"input_button_l": "le bouton L",
"input_button_r": "le bouton R",
"input_button_z": "le bouton Z",
"input_button_c_up": "C Haut",
"input_button_c_down": "C Bas",
"input_button_c_left": "C Gauche",
"input_button_c_right": "C Droit",
"input_analog_stick": "le Stick Analogique",
"input_d_pad": "D-Pad"
}

View File

@ -0,0 +1,18 @@
{
"minutes_plural" : "$0 Minuten",
"minutes_singular" : "eine Minute",
"seconds_plural" : "$0 Sekunden",
"seconds_singular" : "eine Sekunde",
"input_button_a": "den A-Knopf",
"input_button_b": "den B-Knopf",
"input_button_c": "den C-Knopf",
"input_button_l": "den L-Knopf",
"input_button_r": "den R-Knopf",
"input_button_z": "den Z-Knopf",
"input_button_c_up": "C Oben",
"input_button_c_down": "C Unten",
"input_button_c_left": "C Links",
"input_button_c_right": "C Rechts",
"input_analog_stick": "den Analog-Stick",
"input_d_pad": "das Steuerkreuz"
}

View File

@ -0,0 +1,112 @@
{
"0": "Inside the Deku Tree",
"1": "Dodongo's Cavern",
"2": "Inside Jabu-Jabu's Belly",
"3": "Forest Temple",
"4": "Fire Temple",
"5": "Water Temple",
"6": "Spirit Temple",
"7": "Shadow Temple",
"8": "Bottom of The Well",
"9": "Ice Cavern",
"10": "", // Stairs to Ganondorf's Lair (No title card)
"11": "Gerudo Training Ground",
"12": "Thieves' Hideout",
"13": "Ganon's Castle",
"14": "", // Escape from Ganon's Castle (No title card)
"15": "", // Escape from Ganon's Castle 5 (No title card)x
"16": "Treasure Box Shop",
"17": "Parasitic Armored Arachnid - Gohma",
"18": "Infernal Dinosaur - King Dodongo",
"19": "Bio-electric Anemone - Barinade",
"20": "Evil Spirit from Beyond - Phantom Ganon",
"21": "Subterranean Lava Dragon - Volvagia",
"22": "Giant Aquatic Amoeba - Morpha",
"23": "Sorceress Sisters - Twinrova",
"24": "Phantom Shadow Beast - Bongo Bongo",
"25": "Great King of Evil - Ganondorf",
"26": "",
"27": "", // Entrance to Market (No title card)
"28": "",
"29": "",
"30": "Back Alley",
"31": "Back Alley",
"32": "Market",
"33": "Market",
"34": "Market",
"35": "", // Temple of Time Exterior (No title card)
"36": "SCENE_SHRINE_N",
"37": "SCENE_SHRINE_R",
"38": "", // House of the Know-it-All Brothers (No title card)
"39": "", // House of Twins (No title card)
"40": "", // House of the Great Mido (No title card)
"41": "", // Saria's House (No title card)
"42": "", // Kakariko House 1 (No title card)
"43": "", // Back Alley House 1 (No title card)
"44": "Bazaar",
"45": "Kokiri Shop",
"46": "Goron Shop",
"47": "Zora Shop",
"48": "", // Closed Shop (No title card)
"49": "Potion Shop",
"50": "", // Bombchu Shop (No title card)
"51": "Happy Mask Shop",
"52": "", // Link's House (No title card)
"53": "", // Dog Lady's House (No title card)
"54": "Stable",
"55": "", // Impa's House (No title card)
"56": "Lakeside Laboratory",
"57": "", // Running Man's Tent (No title card)
"58": "Gravekeepers Hut",
"59": "Great Fairy's Fountain",
"60": "Fairy's Fountain",
"61": "Great Fairy's Fountain",
"62": "", // Grottos (No title card)
"63": "", // Tomb 1 (No title card)
"64": "", // Tomb 2 (No title card)
"65": "Royal Family's Tomb",
"66": "Shooting Gallery",
"67": "Temple of Time",
"68": "Chamber of The Sages",
"69": "Castle Courtyard",
"70": "Castle Courtyard",
"71": "", // Goddesses Cutscene (No title card)
"72": "Unknown Place",
"73": "Fishing Pond",
"74": "Castle Courtyard",
"75": "Bombchu Bowling Alley",
"76": "", // Lon Lon Ranch House/Silo (No title card)
"77": "", // Guard House (No title card)
"78": "", // Potion Shop (No title card)
"79": "Ganon",
"80": "House of Skulltula",
"81": "Hyrule Field",
"82": "Kakariko Village",
"83": "Graveyard",
"84": "Zora's River",
"85": "Kokiri Forest",
"86": "Sacred Forest Meadow",
"87": "Lake Hylia",
"88": "Zoras Domain",
"89": "Zoras Fountain",
"90": "Gerudo Valley",
"91": "Lost Woods",
"92": "Desert Colossus",
"93": "Gerudo's Fortress",
"94": "Haunted Wasteland",
"95": "Hyrule Castle",
"96": "Death Mountain Trail",
"97": "Death Mountain Crater",
"98": "Goron City",
"99": "Lon Lon Ranch",
"100": "",
"101": "", // Debug: Test Map (No title card)
"102": "", // Debug: Test Room (No title card)
"103": "", // Debug: Depth Test (No title card)
"104": "", // Debug: Stalfos Miniboss Room (No title card)
"105": "", // Debug: Stalfos Boss Room (No title card)
"106": "", // Debug: Dark Link Room (No title card)
"107": "",
"108": "", // Debug: SRD Room (No title card)
"109": "" // Debug: Treasure Chest Warp (No title card)
}

View File

@ -0,0 +1,112 @@
{
"0": "Abre Mojo",
"1": "Caverne Dodongo",
"2": "Ventre de Jabu-Jabu",
"3": "Temple de la Forêt",
"4": "Temple du Feu",
"5": "Temple de l'Eau",
"6": "Temple de l'Esprit",
"7": "Temple de l'Ombre",
"8": "Puits",
"9": "Caverne Polaire",
"10": "", // Escaliers vers le Repaire de Ganondorf (No title card)
"11": "Gymnase Gerudo",
"12": "Repaire des Voleurs",
"13": "Tour de Ganon",
"14": "", // Fuite du Château de Ganon (No title card)
"15": "", // Fuite du Château de Ganon 5 (No title card)
"16": "Chasse aux Trésors",
"17": "Monstre Insectoide Géant - Gohma",
"18": "Dinosaure Infernal - King Dodongo",
"19": "Anémone Bio-Electrique - Barinade",
"20": "Esprit Maléfique de l'Au-Delà - Ganon Spectral",
"21": "Dragon des Profondeurs - Volcania",
"22": "Amibe Aquatique Géante - Morpha",
"23": "Sorcières Jumelles - Duo Maléfique",
"24": "Monstre de l'Ombre - Bongo Bongo",
"25": "Seigneur du Malin - Ganondorf",
"26": "",
"27": "", // Entrée vers le Marché (No title card)
"28": "",
"29": "",
"30": "Ruelle",
"31": "Ruelle",
"32": "Place du Marché",
"33": "Place du Marché",
"34": "Place du Marché",
"35": "", // Extérieur du Temple du Temps (No title card)
"36": "SCENE_SHRINE_N",
"37": "SCENE_SHRINE_R",
"38": "", // Cabane des Frères Je-Sais-Tout (No title card)
"39": "", // Cabane des Jumelles (No title card)
"40": "", // Cabane du Grand Mido (No title card)
"41": "", // Cabane de Saria (No title card)
"42": "", // Maison du Village Cocorico 1 (No title card)
"43": "", // Maison de la Ruelle 1 (No title card)
"44": "Bazar",
"45": "Boutique Kokiri",
"46": "Boutique Goron",
"47": "Boutique Zora",
"48": "", // Magasin Fermé (No title card)
"49": "Apothicaire",
"50": "", // Magasin de Missiles (No title card)
"51": "Foire aux Masques",
"52": "", // Cabane de Link (No title card)
"53": "", // Dog Lady's House (No title card)
"54": "Étable",
"55": "", // Maison d'Impa (No title card)
"56": "Laboratoire du Lac",
"57": "", // Tente du Marathonien (No title card)
"58": "Cabane du fossoyeur",
"59": "Fountaine Royale des Fées",
"60": "Fountaine des Fées",
"61": "Fountaine Royale des Fées",
"62": "", // Grottes (No title card)
"63": "", // Tombe 1 (No title card)
"64": "", // Tombe 2 (No title card)
"65": "Tombe Royale",
"66": "Jeu d'adresse",
"67": "Temple du Temps",
"68": "Sanctuaire des Sages",
"69": "Cour du Château",
"70": "Cour du Château",
"71": "", // Goddesses Cutscene (No title card)
"72": "Endroit Inconnu",
"73": "Étang",
"74": "Cour du Château",
"75": "Bowling Teigneux",
"76": "", // Lon Lon Ranch House/Silo (No title card)
"77": "", // Guard House (No title card)
"78": "", // Potion Shop (No title card)
"79": "Ganon",
"80": "Maison des Araignées",
"81": "Plaine d'Hyrule",
"82": "Village Cocorico",
"83": "Cimetière",
"84": "Fleuve Zora",
"85": "Forêt Kokiri",
"86": "Bosquet Sacré",
"87": "Lac Hylia",
"88": "Domaine Zora",
"89": "Fontaine Zora",
"90": "Vallée Gerudo",
"91": "Bois Perdu",
"92": "Colosse du Désert",
"93": "Forteresse Gerudo",
"94": "Désert Hanté",
"95": "Château d'Hyrule",
"96": "Chemin du Péril",
"97": "Cratère du Péril",
"98": "Village Goron",
"99": "Ranch Lon Lon",
"100": "",
"101": "", // Debug: Test Map (No title card)
"102": "", // Debug: Test Room (No title card)
"103": "", // Debug: Depth Test (No title card)
"104": "", // Debug: Stalfos Miniboss Room (No title card)
"105": "", // Debug: Stalfos Boss Room (No title card)
"106": "", // Debug: Dark Link Room (No title card)
"107": "",
"108": "", // Debug: SRD Room (No title card)
"109": "" // Debug: Treasure Chest Warp (No title card)
}

View File

@ -0,0 +1,112 @@
{
"0": "Im Deku-Baum",
"1": "Dodongos Höhle",
"2": "Jabu-Jabus Bauch",
"3": "Waldtempel",
"4": "Feuertempel",
"5": "Wassertempel",
"6": "Geistertempel",
"7": "Schattentempel",
"8": "Grund des Brunnens",
"9": "Eishöhle",
"10": "", // Treppe zu Ganondorfs Verließ (Keine Title-Card)
"11": "Gerudo-Arena",
"12": "Diebesversteck",
"13": "Ganons Schloß",
"14": "", // Flucht aus Ganons Schloß (Keine Title-Card)
"15": "", // Flucht aus Ganons Schloß 5 (Keine Title-Card)
"16": "Truhenlotterie",
"17": "Gepanzerter Spinnenparasit - Gohma",
"18": "Infernosaurus - King Dodongo",
"19": "Elektroterristrisches Biotentakel - Barinade",
"20": "Reitendes Unheil - Phantom-Ganon",
"21": "Subterraner Lavadrachoid - Volvagia",
"22": "Aquamöbes Wassertentakel - Morpha",
"23": "Höllische Hexenarmada - Killa Ohmaz",
"24": "Bestialische Schattenmonstrosität - Bongo Bongo",
"25": "Großmeister des Bösen - Ganondorf",
"26": "",
"27": "", // Eingang zum Marktplatz (Keine Title-Card)
"28": "",
"29": "",
"30": "Seitenstraße",
"31": "Seitenstraße",
"32": "Marktplatz",
"33": "Marktplatz",
"34": "Marktplatz",
"35": "", // Vor der Zitadelle der Zeit (Keine Title-Card)
"36": "SCENE_SHRINE_N",
"37": "SCENE_SHRINE_R",
"38": "", // Haus der Allwissenden Brüder (Keine Title-Card)
"39": "", // Haus der Zwillinge (Keine Title-Card)
"40": "", // Midos Haus (Keine Title-Card)
"41": "", // Salias Haus (Keine Title-Card)
"42": "", // Kakariko Haus 1 (Keine Title-Card)
"43": "", // Steinstraßen Haus 1 (Keine Title-Card)
"44": "Basar",
"45": "Kokiri-Laden",
"46": "Goronen-Laden",
"47": "Zora-Laden",
"48": "", // Geschlossener Laden (Keine Title-Card)
"49": "Magie-Laden",
"50": "", // Krabbelminen-Laden (Keine Title-Card)
"51": "Maskenhändler",
"52": "", // Links Haus (Keine Title-Card)
"53": "", // Haus der Hunde-Dame (Keine Title-Card)
"54": "Stall",
"55": "", // Impas Haus (Keine Title-Card)
"56": "Hylia-See Laboratorium",
"57": "", // Zelt des Rennläufers (Keine Title-Card)
"58": "Hütte des Totengräbers",
"59": "Feen-Quelle",
"60": "Feen-Brunnen",
"61": "Feen-Quelle",
"62": "", // Grotten (Keine Title-Card)
"63": "", // Grab 1 (Keine Title-Card)
"64": "", // Grab 2 (Keine Title-Card)
"65": "Königsgrab",
"66": "Schießbude",
"67": "Zitadelle der Zeit",
"68": "Halle der Weisen",
"69": "Burghof",
"70": "Burghof",
"71": "", // Göttinnen Cutscene (Keine Title-Card)
"72": "Unbekannter Ort",
"73": "Fischweiher",
"74": "Burghof",
"75": "Minenbowlingbahn",
"76": "", // Lon Lon-Farm Haus/Silo (Keine Title-Card)
"77": "", // Wachposten (Keine Title-Card)
"78": "", // Magie-Laden (Keine Title-Card)
"79": "Ganon",
"80": "Skulltulas Haus",
"81": "Hylianische Steppe",
"82": "Kakariko",
"83": "Friedhof",
"84": "Zora-Fluß",
"85": "Kokiri-Wald",
"86": "Waldlichtung",
"87": "Hylia-See",
"88": "Zoras Reich",
"89": "Zoras Quelle",
"90": "Gerudotal",
"91": "Verlorene Wälder",
"92": "Wüstenkoloss",
"93": "Gerudo-Festung",
"94": "Geisterwüste",
"95": "Schloß Hyrule",
"96": "Pfad zum Todesberg",
"97": "Todeskrater",
"98": "Goronia",
"99": "Lon Lon-Farm",
"100": "",
"101": "", // Debug: Test Karte (Keine Title-Card)
"102": "", // Debug: Test Raum (Keine Title-Card)
"103": "", // Debug: Tiefen Test (Keine Title-Card)
"104": "", // Debug: Stalfos-Ritter Miniboss Raum (Keine Title-Card)
"105": "", // Debug: Stalfos-Ritter Boss Raum (Keine Title-Card)
"106": "", // Debug: Schwarzer Link Raum (Keine Title-Card)
"107": "",
"108": "", // Debug: SRD Raum (Keine Title-Card)
"109": "" // Debug: Schatzkisten Teleport (Keine Title-Card)
}

View File

@ -5,6 +5,12 @@ set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE)
project(soh LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use")
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
enable_language(OBJCXX)
set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -fobjc-arc")
set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} -fobjc-arc")
endif()
set (BUILD_UTILS OFF CACHE STRING "no utilities")
set (BUILD_SHARED_LIBS OFF CACHE STRING "install/link shared instead of static libs")
@ -116,6 +122,10 @@ source_group("include" FILES ${Header_Files__include})
file(GLOB soh__ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "soh/*.c" "soh/*.cpp" "soh/*.h")
source_group("soh" FILES ${soh__})
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set_source_files_properties(soh/OTRGlobals.cpp PROPERTIES COMPILE_FLAGS "/utf-8")
endif()
# soh/enhancements {{{
file(GLOB_RECURSE soh__Enhancements RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
"soh/Enhancements/*.c"
@ -123,13 +133,25 @@ file(GLOB_RECURSE soh__Enhancements RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
"soh/Enhancements/*.h"
"soh/Enhancements/*.hpp"
"soh/Enhancements/*_extern.inc"
"soh/Enhancements/*.mm"
)
list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/gamecommand.h")
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/gfx.*")
# handle crowd control removals
list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/crowd-control/soh.cs")
if (!BUILD_CROWD_CONTROL)
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/crowd-control/.*")
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/crowd-control/*")
endif()
# handle speechsynthesizer removals
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/Darwin*")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/SAPI*")
else()
list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/(Darwin|SAPI).*")
endif()
source_group("soh\\Enhancements" REGULAR_EXPRESSION "soh/Enhancements/*")
@ -145,6 +167,12 @@ source_group("soh\\Enhancements\\randomizer" REGULAR_EXPRESSION "soh/Enhancement
source_group("soh\\Enhancements\\randomizer\\3drando" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/*")
source_group("soh\\Enhancements\\randomizer\\3drando\\hint_list" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/hint_list/*")
source_group("soh\\Enhancements\\randomizer\\3drando\\location_access" REGULAR_EXPRESSION "soh/Enhancements/randomizer/3drando/location_access/*")
source_group("soh\\Enhancements\\speechsynthesizer" REGULAR_EXPRESSION "soh/Enhancements/speechsynthesizer/*")
source_group("soh\\Enhancements\\tts" REGULAR_EXPRESSION "soh/Enhancements/tts/*")
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set_source_files_properties(soh/Enhancements/tts/tts.cpp PROPERTIES COMPILE_FLAGS "/utf-8")
endif()
# }}}
# soh/resource {{{
@ -225,7 +253,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set_target_properties(${PROJECT_NAME} PROPERTIES
OUTPUT_NAME "soh-macos"
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
OUTPUT_NAME "soh-macos"
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set_target_properties(${PROJECT_NAME} PROPERTIES

View File

@ -4,7 +4,6 @@
#define GameInteractor_h
#include "GameInteractionEffect.h"
#include "z64.h"
typedef enum {
/* 0x00 */ GI_LINK_SIZE_NORMAL,
@ -87,13 +86,29 @@ public:
DEFINE_HOOK(OnLoadGame, void(int32_t fileNum));
DEFINE_HOOK(OnExitGame, void(int32_t fileNum));
DEFINE_HOOK(OnGameFrameUpdate, void());
DEFINE_HOOK(OnReceiveItem, void(u8 item));
DEFINE_HOOK(OnSceneInit, void(s16 sceneNum));
DEFINE_HOOK(OnReceiveItem, void(uint8_t item));
DEFINE_HOOK(OnSceneInit, void(int16_t sceneNum));
DEFINE_HOOK(OnPlayerUpdate, void());
DEFINE_HOOK(OnSaveFile, void(int32_t fileNum));
DEFINE_HOOK(OnLoadFile, void(int32_t fileNum));
DEFINE_HOOK(OnDeleteFile, void(int32_t fileNum));
DEFINE_HOOK(OnDialogMessage, void());
DEFINE_HOOK(OnPresentTitleCard, void());
DEFINE_HOOK(OnInterfaceUpdate, void());
DEFINE_HOOK(OnKaleidoscopeUpdate, void(int16_t inDungeonScene));
DEFINE_HOOK(OnPresentFileSelect, void());
DEFINE_HOOK(OnUpdateFileSelectSelection, void(uint16_t optionIndex));
DEFINE_HOOK(OnUpdateFileCopySelection, void(uint16_t optionIndex));
DEFINE_HOOK(OnUpdateFileCopyConfirmationSelection, void(uint16_t optionIndex));
DEFINE_HOOK(OnUpdateFileEraseSelection, void(uint16_t optionIndex));
DEFINE_HOOK(OnUpdateFileEraseConfirmationSelection, void(uint16_t optionIndex));
DEFINE_HOOK(OnUpdateFileAudioSelection, void(uint8_t optionIndex));
DEFINE_HOOK(OnUpdateFileTargetSelection, void(uint8_t optionIndex));
DEFINE_HOOK(OnSetGameLanguage, void());
// Helpers
static bool IsSaveLoaded();

View File

@ -1,9 +1,5 @@
#include "GameInteractor_Hooks.h"
extern "C" {
extern PlayState* gPlayState;
}
// MARK: - Gameplay
void GameInteractor_ExecuteOnLoadGame(int32_t fileNum) {
@ -18,11 +14,11 @@ void GameInteractor_ExecuteOnGameFrameUpdate() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameFrameUpdate>();
}
void GameInteractor_ExecuteOnReceiveItemHooks(u8 item) {
void GameInteractor_ExecuteOnReceiveItemHooks(uint8_t item) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnReceiveItem>(item);
}
void GameInteractor_ExecuteOnSceneInitHooks(s16 sceneNum) {
void GameInteractor_ExecuteOnSceneInitHooks(int16_t sceneNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSceneInit>(sceneNum);
}
@ -43,3 +39,61 @@ void GameInteractor_ExecuteOnLoadFile(int32_t fileNum) {
void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnDeleteFile>(fileNum);
}
// MARK: - Dialog
void GameInteractor_ExecuteOnDialogMessage() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnDialogMessage>();
}
void GameInteractor_ExecuteOnPresentTitleCard() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPresentTitleCard>();
}
void GameInteractor_ExecuteOnInterfaceUpdate() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnInterfaceUpdate>();
}
void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnKaleidoscopeUpdate>(inDungeonScene);
}
// MARK: - Main Menu
void GameInteractor_ExecuteOnPresentFileSelect() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPresentFileSelect>();
}
void GameInteractor_ExecuteOnUpdateFileSelectSelection(uint16_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileSelectSelection>(optionIndex);
}
void GameInteractor_ExecuteOnUpdateFileCopySelection(uint16_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileCopySelection>(optionIndex);
}
void GameInteractor_ExecuteOnUpdateFileCopyConfirmationSelection(uint16_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileCopyConfirmationSelection>(optionIndex);
}
void GameInteractor_ExecuteOnUpdateFileEraseSelection(uint16_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileEraseSelection>(optionIndex);
}
void GameInteractor_ExecuteOnUpdateFileEraseConfirmationSelection(uint16_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileEraseConfirmationSelection>(optionIndex);
}
void GameInteractor_ExecuteOnUpdateFileAudioSelection(uint8_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileAudioSelection>(optionIndex);
}
void GameInteractor_ExecuteOnUpdateFileTargetSelection(uint8_t optionIndex) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnUpdateFileTargetSelection>(optionIndex);
}
// MARK: - Game
void GameInteractor_ExecuteOnSetGameLanguage() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
}

View File

@ -4,11 +4,30 @@
extern "C" void GameInteractor_ExecuteOnLoadGame(int32_t fileNum);
extern "C" void GameInteractor_ExecuteOnExitGame(int32_t fileNum);
extern "C" void GameInteractor_ExecuteOnGameFrameUpdate();
extern "C" void GameInteractor_ExecuteOnReceiveItemHooks(u8 item);
extern "C" void GameInteractor_ExecuteOnSceneInit(s16 sceneNum);
extern "C" void GameInteractor_ExecuteOnReceiveItemHooks(uint8_t item);
extern "C" void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum);
extern "C" void GameInteractor_ExecuteOnPlayerUpdate();
// MARK: - Save Files
extern "C" void GameInteractor_ExecuteOnSaveFile(int32_t fileNum);
extern "C" void GameInteractor_ExecuteOnLoadFile(int32_t fileNum);
extern "C" void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum);
// MARK: - Dialog
extern "C" void GameInteractor_ExecuteOnDialogMessage();
extern "C" void GameInteractor_ExecuteOnPresentTitleCard();
extern "C" void GameInteractor_ExecuteOnInterfaceUpdate();
extern "C" void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene);
// MARK: - Main Menu
extern "C" void GameInteractor_ExecuteOnPresentFileSelect();
extern "C" void GameInteractor_ExecuteOnUpdateFileSelectSelection(uint16_t optionIndex);
extern "C" void GameInteractor_ExecuteOnUpdateFileCopySelection(uint16_t optionIndex);
extern "C" void GameInteractor_ExecuteOnUpdateFileCopyConfirmationSelection(uint16_t optionIndex);
extern "C" void GameInteractor_ExecuteOnUpdateFileEraseSelection(uint16_t optionIndex);
extern "C" void GameInteractor_ExecuteOnUpdateFileEraseConfirmationSelection(uint16_t optionIndex);
extern "C" void GameInteractor_ExecuteOnUpdateFileAudioSelection(uint8_t optionIndex);
extern "C" void GameInteractor_ExecuteOnUpdateFileTargetSelection(uint8_t optionIndex);
// MARK: - Game
extern "C" void GameInteractor_ExecuteOnSetGameLanguage();

View File

@ -1,6 +1,7 @@
#include "mods.h"
#include <libultraship/bridge.h>
#include "game-interactor/GameInteractor.h"
#include "tts/tts.h"
extern "C" {
#include <z64.h>
@ -262,6 +263,7 @@ void RegisterRupeeDash() {
}
void InitMods() {
RegisterTTS();
RegisterInfiniteMoney();
RegisterInfiniteHealth();
RegisterInfiniteAmmo();

View File

@ -0,0 +1,27 @@
//
// DarwinSpeechSynthesizer.h
// libultraship
//
// Created by David Chavez on 22.11.22.
//
#ifndef SOHDarwinSpeechSynthesizer_h
#define SOHDarwinSpeechSynthesizer_h
#include "SpeechSynthesizer.h"
class DarwinSpeechSynthesizer : public SpeechSynthesizer {
public:
DarwinSpeechSynthesizer();
void Speak(const char* text, const char* language);
protected:
bool DoInit(void);
void DoUninitialize(void);
private:
void* mSynthesizer;
};
#endif /* DarwinSpeechSynthesizer_h */

View File

@ -0,0 +1,33 @@
//
// DarwinSpeechSynthesizer.mm
// libultraship
//
// Created by David Chavez on 22.11.22.
//
#include "DarwinSpeechSynthesizer.h"
#import <AVFoundation/AVFoundation.h>
DarwinSpeechSynthesizer::DarwinSpeechSynthesizer() {}
bool DarwinSpeechSynthesizer::DoInit() {
mSynthesizer = (__bridge_retained void*)[[AVSpeechSynthesizer alloc] init];
return true;
}
void DarwinSpeechSynthesizer::DoUninitialize() {
[(__bridge AVSpeechSynthesizer *)mSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
mSynthesizer = nil;
}
void DarwinSpeechSynthesizer::Speak(const char* text, const char* language) {
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@(text)];
[utterance setVoice:[AVSpeechSynthesisVoice voiceWithLanguage:@(language)]];
if (@available(macOS 11.0, *)) {
[utterance setPrefersAssistiveTechnologySettings:YES];
}
[(__bridge AVSpeechSynthesizer *)mSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
[(__bridge AVSpeechSynthesizer *)mSynthesizer speakUtterance:utterance];
}

View File

@ -0,0 +1,56 @@
//
// SAPISpeechSynthesizer.cpp
// libultraship
//
// Created by David Chavez on 22.11.22.
//
#include "SAPISpeechSynthesizer.h"
#include <sapi.h>
#include <thread>
#include <string>
#include <spdlog/fmt/fmt.h>
#include <spdlog/fmt/xchar.h>
ISpVoice* mVoice = NULL;
SAPISpeechSynthesizer::SAPISpeechSynthesizer() {
}
bool SAPISpeechSynthesizer::DoInit() {
CoInitializeEx(NULL, COINIT_MULTITHREADED);
HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&mVoice);
return true;
}
void SAPISpeechSynthesizer::DoUninitialize() {
mVoice->Release();
mVoice = NULL;
CoUninitialize();
}
std::wstring CharToWideString(std::string text) {
int textSize = MultiByteToWideChar(CP_UTF8, 0, &text[0], (int)text.size(), NULL, 0);
std::wstring wstrTo(textSize, 0);
MultiByteToWideChar(CP_UTF8, 0, &text[0], (int)text.size(), &wstrTo[0], textSize);
return wstrTo;
}
void SpeakThreadTask(std::string text, std::string language) {
auto wText = CharToWideString(text);
auto wLanguage = CharToWideString(language);
auto speakText = fmt::format(
L"<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='{}'>{}</speak>", wLanguage, wText);
mVoice->Speak(speakText.c_str(), SPF_IS_XML | SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
}
void SAPISpeechSynthesizer::Speak(const char* text, const char* language) {
// convert to string so char buffers don't have to be kept alive by caller
std::string textStr(text);
std::string languageStr(language);
std::thread t1(SpeakThreadTask, textStr, languageStr);
t1.detach();
}

View File

@ -0,0 +1,25 @@
//
// SAPISpeechSynthesizer.h
// libultraship
//
// Created by David Chavez on 22.11.22.
//
#ifndef SOHSAPISpeechSynthesizer_h
#define SOHSAPISpeechSynthesizer_h
#include "SpeechSynthesizer.h"
#include <stdio.h>
class SAPISpeechSynthesizer : public SpeechSynthesizer {
public:
SAPISpeechSynthesizer();
void Speak(const char* text, const char* language);
protected:
bool DoInit(void);
void DoUninitialize(void);
};
#endif /* SAPISpeechSynthesizer_h */

View File

@ -0,0 +1,32 @@
//
// SpeechSynthesizer.cpp
// libultraship
//
// Created by David Chavez on 22.11.22.
//
#include "SpeechSynthesizer.h"
SpeechSynthesizer::SpeechSynthesizer() : mInitialized(false){};
bool SpeechSynthesizer::Init(void) {
if (mInitialized) {
return true;
}
mInitialized = DoInit();
return mInitialized;
}
void SpeechSynthesizer::Uninitialize(void) {
if (!mInitialized) {
return;
}
DoUninitialize();
mInitialized = false;
}
bool SpeechSynthesizer::IsInitialized(void) {
return mInitialized;
}

View File

@ -0,0 +1,38 @@
//
// SpeechSynthesizer.h
// libultraship
//
// Created by David Chavez on 22.11.22.
//
#ifndef SOHSpeechSynthesizer_h
#define SOHSpeechSynthesizer_h
#include <stdio.h>
class SpeechSynthesizer {
public:
static SpeechSynthesizer* Instance;
SpeechSynthesizer();
bool Init(void);
void Uninitialize(void);
virtual void Speak(const char* text, const char* language) = 0;
bool IsInitialized(void);
protected:
virtual bool DoInit(void) = 0;
virtual void DoUninitialize(void) = 0;
private:
bool mInitialized;
};
#endif /* SpeechSynthesizer_h */
#ifdef _WIN32
#include "SAPISpeechSynthesizer.h"
#elif defined(__APPLE__)
#include "DarwinSpeechSynthesizer.h"
#endif

View File

@ -0,0 +1,724 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h"
#include <OtrFile.h>
#include <libultraship/classes.h>
#include <nlohmann/json.hpp>
#include <spdlog/fmt/fmt.h>
#include "soh/OTRGlobals.h"
#include "message_data_static.h"
#include "overlays/gamestates/ovl_file_choose/file_choose.h"
extern "C" {
extern PlayState* gPlayState;
}
typedef enum {
/* 0x00 */ TEXT_BANK_SCENES,
/* 0x01 */ TEXT_BANK_MISC,
/* 0x02 */ TEXT_BANK_KALEIDO,
/* 0x03 */ TEXT_BANK_FILECHOOSE,
} TextBank;
nlohmann::json sceneMap = nullptr;
nlohmann::json miscMap = nullptr;
nlohmann::json kaleidoMap = nullptr;
nlohmann::json fileChooseMap = nullptr;
// MARK: - Helpers
std::string GetParameritizedText(std::string key, TextBank bank, const char* arg) {
switch (bank) {
case TEXT_BANK_SCENES: {
return sceneMap[key].get<std::string>();
break;
}
case TEXT_BANK_MISC: {
auto value = miscMap[key].get<std::string>();
std::string searchString = "$0";
size_t index = value.find(searchString);
if (index != std::string::npos) {
ASSERT(arg != nullptr);
value.replace(index, searchString.size(), std::string(arg));
return value;
} else {
return value;
}
break;
}
case TEXT_BANK_KALEIDO: {
auto value = kaleidoMap[key].get<std::string>();
std::string searchString = "$0";
size_t index = value.find(searchString);
if (index != std::string::npos) {
ASSERT(arg != nullptr);
value.replace(index, searchString.size(), std::string(arg));
return value;
} else {
return value;
}
break;
}
case TEXT_BANK_FILECHOOSE: {
return fileChooseMap[key].get<std::string>();
break;
}
}
}
const char* GetLanguageCode() {
switch (CVarGetInteger("gLanguages", 0)) {
case LANGUAGE_FRA:
return "fr-FR";
break;
case LANGUAGE_GER:
return "de-DE";
break;
}
return "en-US";
}
// MARK: - Boss Title Cards
std::string NameForSceneId(int16_t sceneId) {
auto key = std::to_string(sceneId);
auto name = GetParameritizedText(key, TEXT_BANK_SCENES, nullptr);
return name;
}
static std::string titleCardText;
void RegisterOnSceneInitHook() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](int16_t sceneNum) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
titleCardText = NameForSceneId(sceneNum);
});
}
void RegisterOnPresentTitleCardHook() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPresentTitleCard>([]() {
if (!CVarGetInteger("gA11yTTS", 0)) return;
SpeechSynthesizer::Instance->Speak(titleCardText.c_str(), GetLanguageCode());
});
}
// MARK: - Interface Updates
void RegisterOnInterfaceUpdateHook() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnInterfaceUpdate>([]() {
if (!CVarGetInteger("gA11yTTS", 0)) return;
static uint32_t prevTimer = 0;
static char ttsAnnounceBuf[32];
uint32_t timer = 0;
if (gSaveContext.timer1State != 0) {
timer = gSaveContext.timer1Value;
} else if (gSaveContext.timer2State != 0) {
timer = gSaveContext.timer2Value;
}
if (timer > 0) {
if (timer > prevTimer || (timer % 30 == 0 && prevTimer != timer)) {
uint32_t minutes = timer / 60;
uint32_t seconds = timer % 60;
char* announceBuf = ttsAnnounceBuf;
char arg[8]; // at least big enough where no s8 string will overflow
if (minutes > 0) {
snprintf(arg, sizeof(arg), "%d", minutes);
auto translation = GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", TEXT_BANK_MISC, arg);
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str());
}
if (seconds > 0) {
snprintf(arg, sizeof(arg), "%d", seconds);
auto translation = GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", TEXT_BANK_MISC, arg);
announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str());
}
ASSERT(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf));
SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode());
prevTimer = timer;
}
}
prevTimer = timer;
if (!GameInteractor::IsSaveLoaded()) return;
static int16_t lostHealth = 0;
static int16_t prevHealth = 0;
if (gSaveContext.health - prevHealth < 0) {
lostHealth += prevHealth - gSaveContext.health;
}
if (gPlayState->state.frames % 7 == 0) {
if (lostHealth >= 16) {
Audio_PlaySoundGeneral(NA_SE_SY_CANCEL, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
lostHealth -= 16;
}
}
prevHealth = gSaveContext.health;
});
}
void RegisterOnKaleidoscopeUpdateHook() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnKaleidoscopeUpdate>([](int16_t inDungeonScene) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
static uint16_t prevCursorIndex = 0;
static uint16_t prevCursorSpecialPos = 0;
static uint16_t prevCursorPoint[5] = { 0 };
PauseContext* pauseCtx = &gPlayState->pauseCtx;
Input* input = &gPlayState->state.input[0];
if (pauseCtx->state != 6) {
//reset cursor index to so it is announced when pause is reopened
prevCursorIndex = -1;
return;
}
if ((pauseCtx->debugState != 1) && (pauseCtx->debugState != 2)) {
char arg[8];
if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) {
snprintf(arg, sizeof(arg), "%d", gSaveContext.health);
auto translation = GetParameritizedText("health", TEXT_BANK_KALEIDO, arg);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
} else if (CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) {
snprintf(arg, sizeof(arg), "%d", gSaveContext.magic);
auto translation = GetParameritizedText("magic", TEXT_BANK_KALEIDO, arg);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
} else if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) {
snprintf(arg, sizeof(arg), "%d", gSaveContext.rupees);
auto translation = GetParameritizedText("rupees", TEXT_BANK_KALEIDO, arg);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
} else if (CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) {
//TODO: announce timer?
}
}
uint16_t cursorIndex = (pauseCtx->pageIndex == PAUSE_MAP && !inDungeonScene) ? PAUSE_WORLD_MAP : pauseCtx->pageIndex;
if (prevCursorIndex == cursorIndex &&
prevCursorSpecialPos == pauseCtx->cursorSpecialPos &&
prevCursorPoint[cursorIndex] == pauseCtx->cursorPoint[cursorIndex]) {
return;
}
prevCursorSpecialPos = pauseCtx->cursorSpecialPos;
if (pauseCtx->cursorSpecialPos > 0) {
return;
}
switch (pauseCtx->pageIndex) {
case PAUSE_ITEM:
{
char arg[8]; // at least big enough where no s8 string will overflow
switch (pauseCtx->cursorItem[PAUSE_ITEM]) {
case ITEM_STICK:
case ITEM_NUT:
case ITEM_BOMB:
case ITEM_BOMBCHU:
case ITEM_SLINGSHOT:
case ITEM_BOW:
snprintf(arg, sizeof(arg), "%d", AMMO(pauseCtx->cursorItem[PAUSE_ITEM]));
break;
case ITEM_BEAN:
snprintf(arg, sizeof(arg), "%d", 0);
break;
default:
arg[0] = '\0';
}
if (pauseCtx->cursorItem[PAUSE_ITEM] == 999) {
return;
}
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_ITEM]);
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, arg);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case PAUSE_MAP:
if (inDungeonScene) {
if (pauseCtx->cursorItem[PAUSE_MAP] != PAUSE_ITEM_NONE) {
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_MAP]);
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
}
} else {
std::string key = std::to_string(0x0100 + pauseCtx->cursorPoint[PAUSE_WORLD_MAP]);
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
SPDLOG_INFO("Item: {}", key);
}
break;
case PAUSE_QUEST:
{
char arg[8]; // at least big enough where no s8 string will overflow
switch (pauseCtx->cursorItem[PAUSE_QUEST]) {
case ITEM_SKULL_TOKEN:
snprintf(arg, sizeof(arg), "%d", gSaveContext.inventory.gsTokens);
break;
case ITEM_HEART_CONTAINER:
snprintf(arg, sizeof(arg), "%d", ((gSaveContext.inventory.questItems & 0xF) & 0xF) >> 0x1C);
break;
default:
arg[0] = '\0';
}
if (pauseCtx->cursorItem[PAUSE_QUEST] == 999) {
return;
}
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_QUEST]);
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, arg);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case PAUSE_EQUIP:
{
std::string key = std::to_string(pauseCtx->cursorItem[PAUSE_EQUIP]);
auto translation = GetParameritizedText(key, TEXT_BANK_KALEIDO, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
prevCursorIndex = cursorIndex;
memcpy(prevCursorPoint, pauseCtx->cursorPoint, sizeof(prevCursorPoint));
});
}
void RegisterOnUpdateMainMenuSelection() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPresentFileSelect>([]() {
if (!CVarGetInteger("gA11yTTS", 0)) return;
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileSelectSelection>([](uint16_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_BTN_MAIN_FILE_1: {
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_MAIN_FILE_2: {
auto translation = GetParameritizedText("file2", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_MAIN_FILE_3: {
auto translation = GetParameritizedText("file3", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_MAIN_OPTIONS: {
auto translation = GetParameritizedText("options", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_MAIN_COPY: {
auto translation = GetParameritizedText("copy", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_MAIN_ERASE: {
auto translation = GetParameritizedText("erase", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileCopySelection>([](uint16_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_BTN_COPY_FILE_1: {
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_COPY_FILE_2: {
auto translation = GetParameritizedText("file2", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_COPY_FILE_3: {
auto translation = GetParameritizedText("file3", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_COPY_QUIT: {
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileCopyConfirmationSelection>([](uint16_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_BTN_CONFIRM_YES: {
auto translation = GetParameritizedText("confirm", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_CONFIRM_QUIT: {
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileEraseSelection>([](uint16_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_BTN_ERASE_FILE_1: {
auto translation = GetParameritizedText("file1", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_ERASE_FILE_2: {
auto translation = GetParameritizedText("file2", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_ERASE_FILE_3: {
auto translation = GetParameritizedText("file3", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_ERASE_QUIT: {
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileEraseConfirmationSelection>([](uint16_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_BTN_CONFIRM_YES: {
auto translation = GetParameritizedText("confirm", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_BTN_CONFIRM_QUIT: {
auto translation = GetParameritizedText("quit", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileAudioSelection>([](uint8_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_AUDIO_STEREO: {
auto translation = GetParameritizedText("audio_stereo", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_AUDIO_MONO: {
auto translation = GetParameritizedText("audio_mono", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_AUDIO_HEADSET: {
auto translation = GetParameritizedText("audio_headset", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_AUDIO_SURROUND: {
auto translation = GetParameritizedText("audio_surround", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnUpdateFileTargetSelection>([](uint8_t optionIndex) {
if (!CVarGetInteger("gA11yTTS", 0)) return;
switch (optionIndex) {
case FS_TARGET_SWITCH: {
auto translation = GetParameritizedText("target_switch", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
case FS_TARGET_HOLD: {
auto translation = GetParameritizedText("target_hold", TEXT_BANK_FILECHOOSE, nullptr);
SpeechSynthesizer::Instance->Speak(translation.c_str(), GetLanguageCode());
break;
}
default:
break;
}
});
}
// MARK: - Dialog Messages
static uint8_t ttsHasMessage;
static uint8_t ttsHasNewMessage;
static int8_t ttsCurrentHighlightedChoice;
std::string remap(uint8_t character) {
switch (character) {
case 0x80: return "À";
case 0x81: return "î";
case 0x82: return "Â";
case 0x83: return "Ä";
case 0x84: return "Ç";
case 0x85: return "È";
case 0x86: return "É";
case 0x87: return "Ê";
case 0x88: return "Ë";
case 0x89: return "Ï";
case 0x8A: return "Ô";
case 0x8B: return "Ö";
case 0x8C: return "Ù";
case 0x8D: return "Û";
case 0x8E: return "Ü";
case 0x8F: return "ß";
case 0x90: return "à";
case 0x91: return "á";
case 0x92: return "â";
case 0x93: return "ä";
case 0x94: return "ç";
case 0x95: return "è";
case 0x96: return "é";
case 0x97: return "ê";
case 0x98: return "ë";
case 0x99: return "ï";
case 0x9A: return "ô";
case 0x9B: return "ö";
case 0x9C: return "ù";
case 0x9D: return "û";
case 0x9E: return "ü";
case 0x9F: return GetParameritizedText("input_button_a", TEXT_BANK_MISC, nullptr);
case 0xA0: return GetParameritizedText("input_button_b", TEXT_BANK_MISC, nullptr);
case 0xA1: return GetParameritizedText("input_button_c", TEXT_BANK_MISC, nullptr);
case 0xA2: return GetParameritizedText("input_button_l", TEXT_BANK_MISC, nullptr);
case 0xA3: return GetParameritizedText("input_button_r", TEXT_BANK_MISC, nullptr);
case 0xA4: return GetParameritizedText("input_button_z", TEXT_BANK_MISC, nullptr);
case 0xA5: return GetParameritizedText("input_button_c_up", TEXT_BANK_MISC, nullptr);
case 0xA6: return GetParameritizedText("input_button_c_down", TEXT_BANK_MISC, nullptr);
case 0xA7: return GetParameritizedText("input_button_c_left", TEXT_BANK_MISC, nullptr);
case 0xA8: return GetParameritizedText("input_button_c_right", TEXT_BANK_MISC, nullptr);
case 0xAA: return GetParameritizedText("input_analog_stick", TEXT_BANK_MISC, nullptr);
case 0xAB: return GetParameritizedText("input_d_pad", TEXT_BANK_MISC, nullptr);
default: return "";
}
}
std::string Message_TTS_Decode(uint8_t* sourceBuf, uint16_t startOfset, uint16_t size) {
std::string output;
uint32_t destWriteIndex = 0;
uint8_t isListingChoices = 0;
for (uint16_t i = 0; i < size; i++) {
uint8_t cchar = sourceBuf[i + startOfset];
if (cchar < ' ') {
switch (cchar) {
case MESSAGE_NEWLINE:
output += (isListingChoices) ? '\n' : ' ';
break;
case MESSAGE_THREE_CHOICE:
case MESSAGE_TWO_CHOICE:
output += '\n';
isListingChoices = 1;
break;
case MESSAGE_COLOR:
case MESSAGE_SHIFT:
case MESSAGE_TEXT_SPEED:
case MESSAGE_BOX_BREAK_DELAYED:
case MESSAGE_FADE:
case MESSAGE_ITEM_ICON:
i++;
break;
case MESSAGE_FADE2:
case MESSAGE_SFX:
case MESSAGE_TEXTID:
i += 2;
break;
default:
break;
}
} else {
if (cchar <= 0x80) {
output += cchar;
} else {
output += remap(cchar);
}
}
}
return output;
}
void RegisterOnDialogMessageHook() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnDialogMessage>([]() {
if (!CVarGetInteger("gA11yTTS", 0)) return;
MessageContext *msgCtx = &gPlayState->msgCtx;
if (msgCtx->msgMode == MSGMODE_TEXT_NEXT_MSG || msgCtx->msgMode == MSGMODE_DISPLAY_SONG_PLAYED_TEXT_BEGIN || (msgCtx->msgMode == MSGMODE_TEXT_CONTINUING && msgCtx->stateTimer == 1)) {
ttsHasNewMessage = 1;
} else if (msgCtx->msgMode == MSGMODE_TEXT_DISPLAYING || msgCtx->msgMode == MSGMODE_TEXT_AWAIT_NEXT || msgCtx->msgMode == MSGMODE_TEXT_DONE || msgCtx->msgMode == MSGMODE_TEXT_DELAYED_BREAK
|| msgCtx->msgMode == MSGMODE_OCARINA_STARTING || msgCtx->msgMode == MSGMODE_OCARINA_PLAYING
|| msgCtx->msgMode == MSGMODE_DISPLAY_SONG_PLAYED_TEXT || msgCtx->msgMode == MSGMODE_DISPLAY_SONG_PLAYED_TEXT || msgCtx->msgMode == MSGMODE_SONG_PLAYED_ACT_BEGIN || msgCtx->msgMode == MSGMODE_SONG_PLAYED_ACT || msgCtx->msgMode == MSGMODE_SONG_PLAYBACK_STARTING || msgCtx->msgMode == MSGMODE_SONG_PLAYBACK || msgCtx->msgMode == MSGMODE_SONG_DEMONSTRATION_STARTING || msgCtx->msgMode == MSGMODE_SONG_DEMONSTRATION_SELECT_INSTRUMENT || msgCtx->msgMode == MSGMODE_SONG_DEMONSTRATION
) {
if (ttsHasNewMessage) {
ttsHasMessage = 1;
ttsHasNewMessage = 0;
ttsCurrentHighlightedChoice = 0;
uint16_t size = msgCtx->decodedTextLen;
auto decodedMsg = Message_TTS_Decode(msgCtx->msgBufDecoded, 0, size);
SpeechSynthesizer::Instance->Speak(decodedMsg.c_str(), GetLanguageCode());
} else if (msgCtx->msgMode == MSGMODE_TEXT_DONE && msgCtx->choiceNum > 0 && msgCtx->choiceIndex != ttsCurrentHighlightedChoice) {
ttsCurrentHighlightedChoice = msgCtx->choiceIndex;
uint16_t startOffset = 0;
while (startOffset < msgCtx->decodedTextLen) {
if (msgCtx->msgBufDecoded[startOffset] == MESSAGE_TWO_CHOICE || msgCtx->msgBufDecoded[startOffset] == MESSAGE_THREE_CHOICE) {
startOffset++;
break;
}
startOffset++;
}
uint16_t endOffset = 0;
if (startOffset < msgCtx->decodedTextLen) {
uint8_t i = msgCtx->choiceIndex;
while (i-- > 0) {
while (startOffset < msgCtx->decodedTextLen) {
if (msgCtx->msgBufDecoded[startOffset] == MESSAGE_NEWLINE) {
startOffset++;
break;
}
startOffset++;
}
}
endOffset = startOffset;
while (endOffset < msgCtx->decodedTextLen) {
if (msgCtx->msgBufDecoded[endOffset] == MESSAGE_NEWLINE) {
break;
}
endOffset++;
}
if (startOffset < msgCtx->decodedTextLen && startOffset != endOffset) {
uint16_t size = endOffset - startOffset;
auto decodedMsg = Message_TTS_Decode(msgCtx->msgBufDecoded, startOffset, size);
SpeechSynthesizer::Instance->Speak(decodedMsg.c_str(), GetLanguageCode());
}
}
}
} else if (ttsHasMessage) {
ttsHasMessage = 0;
ttsHasNewMessage = 0;
if (msgCtx->decodedTextLen < 3 || (msgCtx->msgBufDecoded[msgCtx->decodedTextLen - 2] != MESSAGE_FADE && msgCtx->msgBufDecoded[msgCtx->decodedTextLen - 3] != MESSAGE_FADE2)) {
SpeechSynthesizer::Instance->Speak("", GetLanguageCode()); // cancel current speech (except for faded out messages)
}
}
});
}
// MARK: - Main Registration
void InitTTSBank() {
std::string languageSuffix = "_eng.json";
switch (CVarGetInteger("gLanguages", 0)) {
case LANGUAGE_FRA:
languageSuffix = "_fra.json";
break;
case LANGUAGE_GER:
languageSuffix = "_ger.json";
break;
}
auto sceneFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/scenes" + languageSuffix);
if (sceneFile != nullptr) {
sceneMap = nlohmann::json::parse(sceneFile->Buffer, nullptr, true, true);
}
auto miscFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/misc" + languageSuffix);
if (miscFile != nullptr) {
miscMap = nlohmann::json::parse(miscFile->Buffer, nullptr, true, true);
}
auto kaleidoFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/kaleidoscope" + languageSuffix);
if (kaleidoFile != nullptr) {
kaleidoMap = nlohmann::json::parse(kaleidoFile->Buffer, nullptr, true, true);
}
auto fileChooseFile = OTRGlobals::Instance->context->GetResourceManager()->LoadFile("accessibility/texts/filechoose" + languageSuffix);
if (fileChooseFile != nullptr) {
fileChooseMap = nlohmann::json::parse(fileChooseFile->Buffer, nullptr, true, true);
}
}
void RegisterOnSetGameLanguageHook() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSetGameLanguage>([]() {
InitTTSBank();
});
}
void RegisterTTSModHooks() {
RegisterOnSetGameLanguageHook();
RegisterOnDialogMessageHook();
RegisterOnSceneInitHook();
RegisterOnPresentTitleCardHook();
RegisterOnInterfaceUpdateHook();
RegisterOnKaleidoscopeUpdateHook();
RegisterOnUpdateMainMenuSelection();
}
void RegisterTTS() {
InitTTSBank();
RegisterTTSModHooks();
}

View File

@ -0,0 +1,6 @@
#ifndef TTS_H
#define TTS_H
void RegisterTTS();
#endif

View File

@ -33,6 +33,8 @@
#include "Enhancements/crowd-control/CrowdControl.h"
#endif
#include "Enhancements/game-interactor/GameInteractor.h"
#define EXPERIMENTAL() \
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 50, 50, 255)); \
UIWidgets::Spacer(3.0f); \
@ -297,15 +299,25 @@ namespace GameMenuBar {
if (ImGui::BeginMenu("Languages")) {
UIWidgets::PaddedEnhancementCheckbox("Translate Title Screen", "gTitleScreenTranslation");
UIWidgets::EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG);
UIWidgets::EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER);
UIWidgets::EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA);
if (UIWidgets::EnhancementRadioButton("English", "gLanguages", LANGUAGE_ENG)) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
}
if (UIWidgets::EnhancementRadioButton("German", "gLanguages", LANGUAGE_GER)) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
}
if (UIWidgets::EnhancementRadioButton("French", "gLanguages", LANGUAGE_FRA)) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetGameLanguage>();
}
ImGui::EndMenu();
}
UIWidgets::Spacer(0);
if (ImGui::BeginMenu("Accessibility")) {
#if defined(_WIN32) || defined(__APPLE__)
UIWidgets::PaddedEnhancementCheckbox("Text to Speech", "gA11yTTS");
UIWidgets::Tooltip("Enables text to speech for in game dialog");
#endif
UIWidgets::PaddedEnhancementCheckbox("Disable Idle Camera Re-Centering", "gA11yDisableIdleCam");
UIWidgets::Tooltip("Disables the automatic re-centering of the camera when idle.");

View File

@ -1,4 +1,4 @@
#include "OTRGlobals.h"
#include "OTRGlobals.h"
#include "OTRAudio.h"
#include <iostream>
#include <algorithm>
@ -27,6 +27,7 @@
#define DRWAV_IMPLEMENTATION
#include <dr_libs/wav.h>
#include <AudioPlayer.h>
#include "Enhancements/speechsynthesizer/SpeechSynthesizer.h"
#include "Enhancements/controls/GameControlEditor.h"
#include "Enhancements/cosmetics/CosmeticsEditor.h"
#include "Enhancements/audio/AudioCollection.h"
@ -111,6 +112,7 @@ CustomMessageManager* CustomMessageManager::Instance;
ItemTableManager* ItemTableManager::Instance;
GameInteractor* GameInteractor::Instance;
AudioCollection* AudioCollection::Instance;
SpeechSynthesizer* SpeechSynthesizer::Instance;
extern "C" char** cameraStrings;
std::vector<std::shared_ptr<std::string>> cameraStdStrings;
@ -579,7 +581,14 @@ extern "C" void InitOTR() {
ItemTableManager::Instance = new ItemTableManager();
GameInteractor::Instance = new GameInteractor();
AudioCollection::Instance = new AudioCollection();
#ifdef __APPLE__
SpeechSynthesizer::Instance = new DarwinSpeechSynthesizer();
SpeechSynthesizer::Instance->Init();
#elif defined(_WIN32)
SpeechSynthesizer::Instance = new SAPISpeechSynthesizer();
SpeechSynthesizer::Instance->Init();
#endif
clearMtx = (uintptr_t)&gMtxClear;
OTRMessage_Init();
OTRAudio_Init();
@ -618,6 +627,9 @@ extern "C" void InitOTR() {
extern "C" void DeinitOTR() {
OTRAudio_Exit();
#if defined(_WIN32) || defined(__APPLE__)
SpeechSynthesizerUninitialize();
#endif
#ifdef ENABLE_CROWD_CONTROL
CrowdControl::Instance->Disable();
CrowdControl::Instance->Shutdown();
@ -718,6 +730,10 @@ extern "C" void Graph_StartFrame() {
break;
}
case SDL_SCANCODE_F9: {
// Toggle TTS
CVarSetInteger("gA11yTTS", !CVarGetInteger("gA11yTTS", 0));
}
}
#endif
OTRGlobals::Instance->context->StartFrame();

View File

@ -513,7 +513,7 @@ namespace UIWidgets {
Spacer(0);
}
void EnhancementRadioButton(const char* text, const char* cvarName, int id) {
bool EnhancementRadioButton(const char* text, const char* cvarName, int id) {
/*Usage :
EnhancementRadioButton("My Visible Name","gMyCVarName", MyID);
First arg is the visible name of the Radio button
@ -528,13 +528,17 @@ namespace UIWidgets {
make_invisible += text;
make_invisible += cvarName;
bool ret = false;
int val = CVarGetInteger(cvarName, 0);
if (ImGui::RadioButton(make_invisible.c_str(), id == val)) {
CVarSetInteger(cvarName, id);
SohImGui::RequestCvarSaveOnNextTick();
ret = true;
}
ImGui::SameLine();
ImGui::Text("%s", text);
return ret;
}
bool DrawResetColorButton(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) {

View File

@ -62,7 +62,7 @@ namespace UIWidgets {
bool EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = false, bool disabled = false, const char* disabledTooltipText = "");
void PaddedEnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0, bool PlusMinusButton = false, bool padTop = true, bool padBottom = true, bool disabled = false, const char* disabledTooltipText = "");
bool EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage, bool PlusMinusButton = false, bool disabled = false, const char* disabledTooltipText = "");
void EnhancementRadioButton(const char* text, const char* cvarName, int id);
bool EnhancementRadioButton(const char* text, const char* cvarName, int id);
bool EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha=false, bool TitleSameLine=false);
void DrawFlagArray32(const std::string& name, uint32_t& flags);

View File

@ -1020,8 +1020,6 @@ void TitleCard_InitPlaceName(PlayState* play, TitleCardContext* titleCtx, void*
}
titleCtx->texture = GetResourceDataByName(texture, false);
//titleCtx->texture = texture;
titleCtx->isBossCard = false;
titleCtx->hasTranslation = false;
titleCtx->x = x;
@ -1044,6 +1042,10 @@ void TitleCard_Update(PlayState* play, TitleCardContext* titleCtx) {
}
if (DECR(titleCtx->delayTimer) == 0) {
if (titleCtx->durationTimer == 80) {
GameInteractor_ExecuteOnPresentTitleCard();
}
if (DECR(titleCtx->durationTimer) == 0) {
Math_StepToS(&titleCtx->alpha, 0, 30);
Math_StepToS(&titleCtx->intensityR, 0, 70);

View File

@ -3133,6 +3133,8 @@ void Message_Update(PlayState* play) {
if (msgCtx->msgLength == 0) {
return;
}
GameInteractor_ExecuteOnDialogMessage();
bool isB_Held = CVarGetInteger("gSkipText", 0) != 0 ? CHECK_BTN_ALL(input->cur.button, BTN_B) && !sTextboxSkipped
: CHECK_BTN_ALL(input->press.button, BTN_B);

View File

@ -6064,6 +6064,8 @@ void Interface_Update(PlayState* play) {
Left_HUD_Margin = CVarGetInteger("gHUDMargin_L", 0);
Right_HUD_Margin = CVarGetInteger("gHUDMargin_R", 0);
Bottom_HUD_Margin = CVarGetInteger("gHUDMargin_B", 0);
GameInteractor_ExecuteOnInterfaceUpdate();
if (CHECK_BTN_ALL(debugInput->press.button, BTN_DLEFT)) {
gSaveContext.language = LANGUAGE_ENG;

View File

@ -148,6 +148,11 @@ typedef enum {
/* 3 */ FS_AUDIO_SURROUND
} AudioOption;
typedef enum {
/* 0 */ FS_TARGET_SWITCH,
/* 1 */ FS_TARGET_HOLD,
} TargetOption;
typedef enum {
/* 0 */ FS_CHAR_PAGE_HIRA,
/* 1 */ FS_CHAR_PAGE_KATA,
@ -209,8 +214,8 @@ void FileChoose_DrawNameEntry(GameState* thisx);
void FileChoose_DrawCharacter(GraphicsContext* gfxCtx, void* texture, s16 vtx);
void HandleMouseInput(Input* input);
u8 HandleMouseCursor(FileChooseContext* this, Input* input, int minx, int miny, int maxx, int maxy);
Vec2f HandleMouseCursorSplit(FileChooseContext* this, Input* input, int minx, int miny, int maxx, int maxy, int countx,
u8 HandleMouseCursor(FileChooseContext* thisx, Input* input, int minx, int miny, int maxx, int maxy);
Vec2f HandleMouseCursorSplit(FileChooseContext* thisx, Input* input, int minx, int miny, int maxx, int maxy, int countx,
int county);
extern s16 D_808123F0[];

View File

@ -349,6 +349,7 @@ void FileChoose_FinishFadeIn(GameState* thisx) {
this->controlsAlpha = 255;
this->windowAlpha = 200;
this->configMode = CM_MAIN_MENU;
GameInteractor_ExecuteOnPresentFileSelect();
}
}
@ -478,6 +479,8 @@ void FileChoose_UpdateRandomizer() {
}
}
uint16_t lastFileChooseButtonIndex;
/**
* Update the cursor and wait for the player to select a button to change menus accordingly.
* If an empty file is selected, enter the name entry config mode.
@ -597,6 +600,11 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
} else {
this->warningLabel = FS_WARNING_NONE;
}
if (lastFileChooseButtonIndex != this->buttonIndex) {
GameInteractor_ExecuteOnUpdateFileSelectSelection(this->buttonIndex);
lastFileChooseButtonIndex = this->buttonIndex;
}
}
}

View File

@ -54,6 +54,8 @@ void FileChoose_SetupCopySource(GameState* thisx) {
}
}
uint16_t lastCopyEraseButtonIndex;
/**
* Allow the player to select a file to copy or exit back to the main menu.
* Update function for `CM_SELECT_COPY_SOURCE`
@ -110,6 +112,11 @@ void FileChoose_SelectCopySource(GameState* thisx) {
}
}
}
if (lastCopyEraseButtonIndex != this->buttonIndex) {
GameInteractor_ExecuteOnUpdateFileCopySelection(this->buttonIndex);
lastCopyEraseButtonIndex = this->buttonIndex;
}
}
/**
@ -379,6 +386,11 @@ void FileChoose_CopyConfirm(GameState* thisx) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
this->buttonIndex ^= 1;
}
if (lastCopyEraseButtonIndex != this->buttonIndex) {
GameInteractor_ExecuteOnUpdateFileCopyConfirmationSelection(this->buttonIndex);
lastCopyEraseButtonIndex = this->buttonIndex;
}
}
/**
@ -724,6 +736,11 @@ void FileChoose_EraseSelect(GameState* thisx) {
this->warningLabel = FS_WARNING_NONE;
}
}
if (lastCopyEraseButtonIndex != this->buttonIndex) {
GameInteractor_ExecuteOnUpdateFileEraseSelection(this->buttonIndex);
lastCopyEraseButtonIndex = this->buttonIndex;
}
}
/**
@ -833,6 +850,11 @@ void FileChoose_EraseConfirm(GameState* thisx) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
this->buttonIndex ^= 1;
}
if (lastCopyEraseButtonIndex != this->buttonIndex) {
GameInteractor_ExecuteOnUpdateFileEraseConfirmationSelection(this->buttonIndex);
lastCopyEraseButtonIndex = this->buttonIndex;
}
}
/**

View File

@ -656,6 +656,7 @@ void FileChoose_StartOptions(GameState* thisx) {
}
static u8 sSelectedSetting;
int8_t lastOptionButtonIndex = -1;
/**
* Update the cursor and appropriate settings for the options menu.
@ -718,6 +719,19 @@ void FileChoose_UpdateOptionsMenu(GameState* thisx) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
sSelectedSetting ^= 1;
}
if (sSelectedSetting == FS_SETTING_AUDIO) {
if (lastOptionButtonIndex != gSaveContext.audioSetting) {
GameInteractor_ExecuteOnUpdateFileAudioSelection(gSaveContext.audioSetting);
lastOptionButtonIndex = gSaveContext.audioSetting;
}
} else {
// offset to detect switching between modes
if (lastOptionButtonIndex != FS_BTN_SELECT_QUIT + gSaveContext.zTargetSetting + 1) {
GameInteractor_ExecuteOnUpdateFileTargetSelection(gSaveContext.zTargetSetting);
lastOptionButtonIndex = FS_BTN_SELECT_QUIT + gSaveContext.zTargetSetting + 1;
}
}
}
typedef struct {

View File

@ -4260,4 +4260,6 @@ void KaleidoScope_Update(PlayState* play)
osSyncPrintf(VT_RST);
break;
}
GameInteractor_ExecuteOnKaleidoscopeUpdate(sInDungeonScene);
}