From 4ca1d8b62f55fe56fe994d04207177c332d0a780 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sun, 5 Nov 2017 00:06:00 -0400 Subject: [PATCH] Change the credits system It is now possible to have longer credit texts than the original. --- Rom.py | 53 ++++++------ Text.py | 257 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 247 insertions(+), 63 deletions(-) diff --git a/Rom.py b/Rom.py index 76dcfdfb..4bcaba6d 100644 --- a/Rom.py +++ b/Rom.py @@ -1,5 +1,5 @@ from Dungeons import dungeon_music_addresses -from Text import string_to_alttp_text, text_addresses, credits_addresses, string_to_credits +from Text import string_to_alttp_text, text_addresses, Credits from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts import random @@ -445,11 +445,6 @@ def write_string_to_rom(rom, target, string): rom.write_bytes(address, string_to_alttp_text(string, maxbytes)) -def write_credits_string_to_rom(rom, target, string): - address, length = credits_addresses[target] - rom.write_bytes(address, string_to_credits(string, length)) - - def write_strings(rom, world): silverarrows = world.find_items('Silver Arrows') silverarrow_hint = (' %s?' % silverarrows[0].hint_text) if silverarrows else '?\nI think not!' @@ -481,7 +476,6 @@ def write_strings(rom, world): pedestal_text = 'Some Hot Air' if pedestalitem is None else pedestalitem.pedestal_hint_text if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' write_string_to_rom(rom, 'Pedestal', pedestal_text) pedestal_credit_text = 'and the Hot Air' if pedestalitem is None else pedestalitem.pedestal_credit_text if pedestalitem.pedestal_credit_text is not None else 'and the Unknown Item' - write_credits_string_to_rom(rom, 'Pedestal', pedestal_credit_text) etheritem = world.get_location('Ether Tablet').item ether_text = 'Some Hot Air' if etheritem is None else etheritem.pedestal_hint_text if etheritem.pedestal_hint_text is not None else 'Unknown Item' @@ -490,30 +484,37 @@ def write_strings(rom, world): bombos_text = 'Some Hot Air' if bombositem is None else bombositem.pedestal_hint_text if bombositem.pedestal_hint_text is not None else 'Unknown Item' write_string_to_rom(rom, 'BombosTablet', bombos_text) - write_credits_string_to_rom(rom, 'KingsReturn', KingsReturn_texts[random.randint(0, len(KingsReturn_texts) - 1)]) - write_credits_string_to_rom(rom, 'Sanctuary', Sanctuary_texts[random.randint(0, len(Sanctuary_texts) - 1)]) - write_credits_string_to_rom(rom, 'Kakariko', Kakariko_texts[random.randint(0, len(Kakariko_texts) - 1)]) - write_credits_string_to_rom(rom, 'Blacksmiths', Blacksmiths_texts[random.randint(0, len(Blacksmiths_texts) - 1)]) - write_credits_string_to_rom(rom, 'DeathMountain', DeathMountain_texts[random.randint(0, len(DeathMountain_texts) - 1)]) - write_credits_string_to_rom(rom, 'LostWoods', LostWoods_texts[random.randint(0, len(LostWoods_texts) - 1)]) - write_credits_string_to_rom(rom, 'WishingWell', WishingWell_texts[random.randint(0, len(WishingWell_texts) - 1)]) - write_credits_string_to_rom(rom, 'DesertPalace', DesertPalace_texts[random.randint(0, len(DesertPalace_texts) - 1)]) - write_credits_string_to_rom(rom, 'MountainTower', MountainTower_texts[random.randint(0, len(MountainTower_texts) - 1)]) - write_credits_string_to_rom(rom, 'LinksHouse', LinksHouse_texts[random.randint(0, len(LinksHouse_texts) - 1)]) - write_credits_string_to_rom(rom, 'Lumberjacks', Lumberjacks_texts[random.randint(0, len(Lumberjacks_texts) - 1)]) + credits = Credits() sickkiditem = world.get_location('Sick Kid').item - sickkiditem_text = SickKid_texts[random.randint(0, len(SickKid_texts) - 1)] if sickkiditem is None or sickkiditem.sickkid_credit_text is None else sickkiditem.sickkid_credit_text - write_credits_string_to_rom(rom, 'SickKid', sickkiditem_text) + sickkiditem_text = random.choice(SickKid_texts) if sickkiditem is None or sickkiditem.sickkid_credit_text is None else sickkiditem.sickkid_credit_text zoraitem = world.get_location('King Zora').item - zoraitem_text = Zora_texts[random.randint(0, len(Zora_texts) - 1)] if zoraitem is None or zoraitem.zora_credit_text is None else zoraitem.zora_credit_text - write_credits_string_to_rom(rom, 'Zora', zoraitem_text) + zoraitem_text = random.choice(Zora_texts) if zoraitem is None or zoraitem.zora_credit_text is None else zoraitem.zora_credit_text magicshopitem = world.get_location('Potion Shop').item - magicshopitem_text = MagicShop_texts[random.randint(0, len(MagicShop_texts) - 1)] if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text - write_credits_string_to_rom(rom, 'MagicShop', magicshopitem_text) + magicshopitem_text = random.choice(MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text fluteboyitem = world.get_location('Stumpy').item - fluteboyitem_text = FluteBoy_texts[random.randint(0, len(FluteBoy_texts) - 1)] if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text - write_credits_string_to_rom(rom, 'FluteBoy', fluteboyitem_text) + fluteboyitem_text = random.choice(FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text + + credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts)) + credits.update_credits_line('sancturary', 0, random.choice(Sanctuary_texts)) + credits.update_credits_line('kakariko', 0, random.choice(Kakariko_texts)) + credits.update_credits_line('desert', 0, random.choice(DesertPalace_texts)) + credits.update_credits_line('hera', 0, random.choice(MountainTower_texts)) + credits.update_credits_line('house', 0, random.choice(LinksHouse_texts)) + credits.update_credits_line('zora', 0, zoraitem_text) + credits.update_credits_line('witch', 0, magicshopitem_text) + credits.update_credits_line('lumberjacks', 0, random.choice(Lumberjacks_texts)) + credits.update_credits_line('grove', 0, fluteboyitem_text) + credits.update_credits_line('well', 0, random.choice(WishingWell_texts)) + credits.update_credits_line('smithy', 0, random.choice(Blacksmiths_texts)) + credits.update_credits_line('kakariko2', 0, sickkiditem_text) + credits.update_credits_line('bridge', 0, random.choice(DeathMountain_texts)) + credits.update_credits_line('woods', 0, random.choice(LostWoods_texts)) + credits.update_credits_line('pedestal', 0, pedestal_credit_text) + + (pointers, data) = credits.get_bytes() + rom.write_bytes(0x181500, data) + rom.write_bytes(0x76CC0, [byte for p in pointers for byte in [p & 0xFF, p >> 8 & 0xFF]]) diff --git a/Text.py b/Text.py index ca6611fa..0f5765ad 100644 --- a/Text.py +++ b/Text.py @@ -15,22 +15,6 @@ text_addresses = {'Pedestal': (0x180300, 256), 'Ganon1Invincible': (0x181100, 256), 'Ganon2Invincible': (0x181200, 256)} -credits_addresses = {'KingsReturn': (0x76928, 22), - 'Sanctuary': (0x76964, 16), - 'Kakariko': (0x76997, 23), - 'DesertPalace': (0x769D4, 24), - 'MountainTower': (0x76A12, 24), - 'LinksHouse': (0x76A52, 19), - 'Zora': (0x76A85, 20), - 'MagicShop': (0x76AC5, 23), - 'Lumberjacks': (0x76AFC, 16), - 'FluteBoy': (0x76B34, 23), - 'WishingWell': (0x76B71, 23), - 'Blacksmiths': (0x76BAC, 23), - 'SickKid': (0x76BDF, 20), - 'DeathMountain': (0x76C19, 16), - 'LostWoods': (0x76C51, 16), - 'Pedestal': (0x76C81, 20)} Uncle_texts = ['Good Luck!\nYou will need it.', 'Forward this message to 10 other people or this seed will be awful.', 'I hope you like your seeds bootless and fluteless.', '10\n9\n8\n7\n6\n5\n4\n3\n2\n1\nGo!', 'I have crippling depression.', 'I\'m off to visit cousin Fritzl.'] @@ -63,23 +47,175 @@ MagicShop_texts = ['Drug deal', 'Shrooms for days'] FluteBoy_texts = ['Stumped'] -def string_to_credits(s, length): - buf = bytearray() +class Credits(object): + def __init__(self): + self.credit_scenes = { + 'castle': [ + SceneSmallCreditLine(19, 'The return of the King'), + SceneLargeCreditLine(23, 'Hyrule Castle'), + ], + 'sancturary': [ + SceneSmallCreditLine(19, 'The loyal priest'), + SceneLargeCreditLine(23, 'Sanctuary'), + ], + 'kakariko': [ + SceneSmallCreditLine(19, "Sahasralah's Homecoming"), + SceneLargeCreditLine(23, 'Kakariko Town'), + ], + 'desert': [ + SceneSmallCreditLine(19, 'vultures rule the desert'), + SceneLargeCreditLine(23, 'Desert Palace'), + ], + 'hera': [ + SceneSmallCreditLine(19, 'the bully makes a friend'), + SceneLargeCreditLine(23, 'Mountain Tower'), + ], + 'house': [ + SceneSmallCreditLine(19, 'your uncle recovers'), + SceneLargeCreditLine(23, 'Your House'), + ], + 'zora': [ + SceneSmallCreditLine(19, 'finger webs for sale'), + SceneLargeCreditLine(23, "Zora's Waterfall"), + ], + 'witch': [ + SceneSmallCreditLine(19, 'the witch and assistant'), + SceneLargeCreditLine(23, 'Magic Shop'), + ], + 'lumberjacks': [ + SceneSmallCreditLine(19, 'twin lumberjacks'), + SceneLargeCreditLine(23, "Woodsmen's Hut"), + ], + 'grove': [ + SceneSmallCreditLine(19, 'ocarina boy plays again'), + SceneLargeCreditLine(23, 'Haunted Grove'), + ], + 'well': [ + SceneSmallCreditLine(19, 'venus, queen of faeries'), + SceneLargeCreditLine(23, 'Wishing Well'), + ], + 'smithy': [ + SceneSmallCreditLine(19, 'the dwarven swordsmiths'), + SceneLargeCreditLine(23, 'Smithery'), + ], + 'kakariko2': [ + SceneSmallCreditLine(19, 'the bug-catching kid'), + SceneLargeCreditLine(23, 'Kakariko Town'), + ], + 'bridge': [ + SceneSmallCreditLine(19, 'the lost old man'), + SceneLargeCreditLine(23, 'Death Mountain'), + ], + 'woods': [ + SceneSmallCreditLine(19, 'the forest thief'), + SceneLargeCreditLine(23, 'Lost Woods'), + ], + 'pedestal': [ + SceneSmallCreditLine(19, 'and the master sword'), + SceneSmallAltCreditLine(21, 'sleeps again...'), + #SceneLargeCreditLine(23, 'Forever!'), + SceneLargeCreditLine(23, 'Forever!!!!!'), + ], + } - if len(s) > length: - s = s[:length] + self.scene_order = ['castle', 'sancturary', 'kakariko', 'desert', 'hera', 'house', 'zora', 'witch', + 'lumberjacks', 'grove', 'well', 'smithy', 'kakariko2', 'bridge', 'woods', 'pedestal'] - padding = length - len(s) - leftpadding = padding // 2 - rightpadding = padding - leftpadding - s = ' '*leftpadding + s + ' '*rightpadding + def update_credits_line(self, scene, line, text, align='center'): + scenes = self.credit_scenes - for char in s.lower(): - buf.append(char_to_credit_char(char)) + text = text[:32] + scenes[scene][line].text = text - return buf + def get_bytes(self): + pointers = [] + data = bytearray() + for scene_name in self.scene_order: + scene = self.credit_scenes[scene_name] + pointers.append(len(data)) + + for part in scene: + data += part.as_bytes() + + pointers.append(len(data)) + return (pointers, data) + +class CreditLine(object): + """Base class of credit lines""" + + def __init__(self, text, align='center'): + self.text = text + self.align = align + + @property + def x(self): + x = 0 + if self.align == 'left': + x = 0 + elif self.align == 'right': + x = 32 - len(self.text) + else: # center + x = (32 - len(self.text)) // 2 + return x + + +class SceneCreditLine(CreditLine): + """Base class for credit lines for the scene portion of the credits""" + def __init__(self, y, text, align='center'): + self.y = y + super().__init__(text,align) + + def header(self, x=None, y=None, length=None): + if x is None: + x = self.x + if y is None: + y = self.y + if length is None: + length = len(self.text) + header = (0x6000 | (y >> 5 << 11) | ((y & 0x1F) << 5) | (x >> 5 << 10) | (x & 0x1F)) << 16 | (length * 2 - 1) + return bytearray([header >> 24 & 0xFF, header >> 16 & 0xFF, header >> 8 & 0xFF, header & 0xFF]) +class SceneSmallCreditLine(SceneCreditLine): + def as_bytes(self): + buf = bytearray() + buf.extend(self.header()) + buf.extend(GoldCreditMapper.convert(self.text)) + + # handle upper half of apostrophe character if present + if "'" in self.text: + apos = "".join([',' if x == "'" else ' ' for x in self.text]) + buf.extend(self.header(self.x + apos.index(','), self.y - 1, len(apos.strip()))) + buf.extend(GoldCreditMapper.convert(apos.strip())) + + # handle lower half of comma character if present + if ',' in self.text: + commas = "".join(["'" if x == ',' else ' ' for x in self.text]) + buf.extend(self.header(self.x + commas.index("'"), self.y + 1, len(commas.strip()))) + buf.extend(GoldCreditMapper.convert(commas.strip())) + + return buf + + +class SceneSmallAltCreditLine(SceneCreditLine): + def as_bytes(self): + buf = bytearray() + buf += self.header() + buf += GreenCreditMapper.convert(self.text) + return buf + + +class SceneLargeCreditLine(SceneCreditLine): + def as_bytes(self): + buf = bytearray() + buf += self.header() + buf += LargeCreditTopMapper.convert(self.text) + + buf += self.header(self.x, self.y + 1) + buf += LargeCreditBottomMapper.convert(self.text) + return buf + + def string_to_alttp_text(s, maxbytes=256): lines = s.upper().split('\n') outbuf = bytearray() @@ -311,12 +447,6 @@ char_map = {' ': 0xFF, 'ェ': 0x9E, 'ォ': 0x9F} -credit_char_map = {' ': 0x9F, - ',': 0x37, - '.': 0x37, - '-': 0x36, - "'": 0x35} - def char_to_alttp_char(char): if 0x30 <= ord(char) <= 0x39: @@ -328,9 +458,62 @@ def char_to_alttp_char(char): return char_map.get(char, 0xFF) -def char_to_credit_char(char): - if 0x61 <= ord(char) <= 0x7A: - return ord(char) - 0x47 +class TextMapper(object): + number_offset = None + @classmethod + def map_char(cls, char): + if cls.number_offset is not None: + if 0x30 <= ord(char) <= 0x39: + return ord(char) + cls.number_offset + if 0x61 <= ord(char) <= 0x7A: + return ord(char) + cls.alpha_offset + return cls.char_map.get(char, cls.char_map[' ']) - return credit_char_map.get(char, 0x9F) + @classmethod + def convert(cls, text): + buf = bytearray() + for char in text.lower(): + buf.append(cls.map_char(char)) + return buf + +class GoldCreditMapper(TextMapper): + char_map = {' ': 0x9F, + ',': 0x34, + '.': 0x37, + '-': 0x36, + "'": 0x35} + alpha_offset = -0x47 + + +class GreenCreditMapper(TextMapper): + char_map = {' ': 0x9F, + '.': 0x52} + alpha_offset = -0x29 + +class RedCreditMapper(TextMapper): + char_map = {' ': 0x9F} #fixme + alpha_offset= -0x61 + +class LargeCreditTopMapper(TextMapper): + char_map = {' ': 0x9F, + "'": 0x77, + '!': 0x78, + '.': 0xA0, + '#': 0xA1, + '/': 0xA2, + ':': 0xA3} + alpha_offset = -0x04 + number_offset = 0x23 + + +class LargeCreditBottomMapper(TextMapper): + char_map = {' ': 0x9F, + "'": 0x9D, + '!': 0x9E, + '.': 0xC0, + '#': 0xC1, + '/': 0xC2, + ':': 0xC3} + alpha_offset = 0x22 + number_offset = 0x49