Change the credits system

It is now possible to have longer credit texts than the original.
This commit is contained in:
Kevin Cathcart 2017-11-05 00:06:00 -04:00
parent 5b3d4449c1
commit 4ca1d8b62f
2 changed files with 247 additions and 63 deletions

53
Rom.py
View File

@ -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]])

257
Text.py
View File

@ -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