Refactored code into classes for clearer grouping

This commit is contained in:
Kevin Cathcart 2018-03-10 18:45:14 -05:00
parent f7881ea49f
commit a12e04866d
2 changed files with 168 additions and 162 deletions

4
Rom.py
View File

@ -7,7 +7,7 @@ import struct
import random import random
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses
from Text import string_to_alttp_text, text_addresses, Credits from Text import MultiByteTextMapper, 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 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, Sahasrahla_names 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, Sahasrahla_names
from Utils import local_path from Utils import local_path
@ -955,7 +955,7 @@ def write_sprite(rom, sprite):
def write_string_to_rom(rom, target, string): def write_string_to_rom(rom, target, string):
address, maxbytes = text_addresses[target] address, maxbytes = text_addresses[target]
rom.write_bytes(address, string_to_alttp_text(string, maxbytes)) rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
def write_strings(rom, world): def write_strings(rom, world):

128
Text.py
View File

@ -437,9 +437,10 @@ class SceneLargeCreditLine(SceneCreditLine):
buf += LargeCreditBottomMapper.convert(self.text) buf += LargeCreditBottomMapper.convert(self.text)
return buf return buf
class MultiByteTextMapper(object):
def string_to_alttp_text(s, maxbytes=256): @classmethod
outbuf = string_to_alttp_core(s) def convert(cls, text, maxbytes=256):
outbuf = MultiByteCoreTextMapper.convert(text)
# check for max length # check for max length
if len(outbuf) > maxbytes - 2: if len(outbuf) > maxbytes - 2:
@ -452,7 +453,8 @@ def string_to_alttp_text(s, maxbytes=256):
outbuf.append(0x7F) outbuf.append(0x7F)
return outbuf return outbuf
special_commands = { class MultiByteCoreTextMapper(object):
special_commands = {
"{SPEED0}": [0x7A, 0x00], "{SPEED0}": [0x7A, 0x00],
"{SPEED2}": [0x7A, 0x02], "{SPEED2}": [0x7A, 0x02],
"{SPEED6}": [0x7A, 0x06], "{SPEED6}": [0x7A, 0x06],
@ -475,20 +477,21 @@ special_commands = {
"{INTRO}": [0x6E, 0x00, 0x77, 0x07, 0x7A, 0x03, 0x6B, 0x02, 0x67], "{INTRO}": [0x6E, 0x00, 0x77, 0x07, 0x7A, 0x03, 0x6B, 0x02, 0x67],
"{NOTEXT}": [0x6E, 0x00, 0x6B, 0x04], "{NOTEXT}": [0x6E, 0x00, 0x6B, 0x04],
"{IBOX}": [0x6B, 0x02, 0x77, 0x07, 0x7A, 0x03], "{IBOX}": [0x6B, 0x02, 0x77, 0x07, 0x7A, 0x03],
} }
def string_to_alttp_core(s, pause=True, wrap=14): @classmethod
s = s.upper() def convert(cls, text, pause=True, wrap=14):
lines = s.split('\n') text = text.upper()
lines = text.split('\n')
outbuf = bytearray() outbuf = bytearray()
lineindex = 0 lineindex = 0
is_intro = '{INTRO}' in s is_intro = '{INTRO}' in text
while lines: while lines:
linespace = wrap linespace = wrap
line = lines.pop(0) line = lines.pop(0)
if line.startswith('{'): if line.startswith('{'):
outbuf.extend(special_commands[line]) outbuf.extend(cls.special_commands[line])
continue continue
words = line.split(' ') words = line.split(' ')
@ -497,20 +500,20 @@ def string_to_alttp_core(s, pause=True, wrap=14):
while words: while words:
word = words.pop(0) word = words.pop(0)
# sanity check: if the word we have is more than 14 characters, we take as much as we can still fit and push the rest back for later # sanity check: if the word we have is more than 14 characters, we take as much as we can still fit and push the rest back for later
if wordlen(word) > wrap: if cls.wordlen(word) > wrap:
if linespace < wrap: if linespace < wrap:
word = ' ' + word word = ' ' + word
(word_first, word_rest) = splitword(word, linespace) (word_first, word_rest) = cls.splitword(word, linespace)
words.insert(0, word_rest) words.insert(0, word_rest)
lines.insert(0, ' '.join(words)) lines.insert(0, ' '.join(words))
outbuf.extend(RawMBTextMapper.convert(word_first)) outbuf.extend(RawMBTextMapper.convert(word_first))
break break
if wordlen(word) <= (linespace if linespace == wrap else linespace - 1): if cls.wordlen(word) <= (linespace if linespace == wrap else linespace - 1):
if linespace < wrap: if linespace < wrap:
word = ' ' + word word = ' ' + word
linespace -= wordlen(word) linespace -= cls.wordlen(word)
outbuf.extend(RawMBTextMapper.convert(word)) outbuf.extend(RawMBTextMapper.convert(word))
else: else:
# ran out of space, push word and lines back and continue with next line # ran out of space, push word and lines back and continue with next line
@ -530,21 +533,55 @@ def string_to_alttp_core(s, pause=True, wrap=14):
outbuf.append(0x73) outbuf.append(0x73)
return outbuf return outbuf
two_byte_commands = [ @classmethod
def wordlen(cls, word):
l = 0
offset = 0
while offset < len(word):
c_len, offset = cls.charlen(word, offset)
l += c_len
return l
@classmethod
def splitword(cls, word, length):
l = 0
offset = 0
while True:
c_len, new_offset = cls.charlen(word, offset)
if l+c_len > length:
break
l += c_len
offset = new_offset
return (word[0:offset], word[offset:])
@classmethod
def charlen(cls, word, offset):
c = word[offset]
if c in ['>', '¼', '½', '']:
return (2, offset+1)
if c in ['@']:
return (4, offset+1)
if c in ['', '', '', '']:
return (2, offset+1)
return (1, offset+1)
class CompressedTextMapper(object):
two_byte_commands = [
0x6B, 0x6C, 0x6D, 0x6E, 0x6B, 0x6C, 0x6D, 0x6E,
0x77, 0x78, 0x79, 0x7A 0x77, 0x78, 0x79, 0x7A
] ]
specially_coded_commands = { specially_coded_commands = {
0x73: 0xF6, 0x73: 0xF6,
0x74: 0xF7, 0x74: 0xF7,
0x75: 0xF8, 0x75: 0xF8,
0x76: 0xF9, 0x76: 0xF9,
0x7E: 0xFA, 0x7E: 0xFA,
0x7A: 0xFC, 0x7A: 0xFC,
} }
def string_to_alttp_text_compressed(s, pause=True, max_bytes_expanded=0x800, wrap=14): @classmethod
inbuf = string_to_alttp_core(s, pause, wrap) def convert(cls, text, pause=True, max_bytes_expanded=0x800, wrap=14):
inbuf = MultiByteCoreTextMapper.convert(text, pause, wrap)
# Links name will need 8 bytes in the target buffer # Links name will need 8 bytes in the target buffer
# and two will be used by the terminator # and two will be used by the terminator
@ -565,49 +602,18 @@ def string_to_alttp_text_compressed(s, pause=True, max_bytes_expanded=0x800, wra
outbuf.append(0xFD) outbuf.append(0xFD)
outbuf.append(inbuf.pop()) outbuf.append(inbuf.pop())
elif val >= 0x67: elif val >= 0x67:
if val in specially_coded_commands: if val in cls.specially_coded_commands:
outbuf.append(specially_coded_commands[val]) outbuf.append(cls.specially_coded_commands[val])
else: else:
outbuf.append(0xFE) outbuf.append(0xFE)
outbuf.append(val) outbuf.append(val)
if val in two_byte_commands: if val in cls.two_byte_commands:
outbuf.append(inbuf.pop()) outbuf.append(inbuf.pop())
else: else:
raise ValueError("Unexpected byte found in uncompressed string") raise ValueError("Unexpected byte found in uncompressed string")
return outbuf return outbuf
class CharTextMapper(object):
def wordlen(word):
l = 0
offset = 0
while offset < len(word):
c_len, offset = charlen(word, offset)
l += c_len
return l
def splitword(word, length):
l = 0
offset = 0
while True:
c_len, new_offset = charlen(word, offset)
if l+c_len > length:
break
l += c_len
offset = new_offset
return (word[0:offset], word[offset:])
def charlen(word, offset):
c = word[offset]
if c in ['>', '¼', '½', '']:
return (2, offset+1)
if c in ['@']:
return (4, offset+1)
if c in ['', '', '', '']:
return (2, offset+1)
return (1, offset+1)
class TextMapper(object):
number_offset = None number_offset = None
alpha_offset = 0 alpha_offset = 0
char_map = {} char_map = {}
@ -627,7 +633,7 @@ class TextMapper(object):
buf.append(cls.map_char(char)) buf.append(cls.map_char(char))
return buf return buf
class RawMBTextMapper(TextMapper): class RawMBTextMapper(CharTextMapper):
char_map = {' ': 0xFF, char_map = {' ': 0xFF,
'': 0xC4, '': 0xC4,
'': 0xC5, '': 0xC5,
@ -1098,7 +1104,7 @@ class RawMBTextMapper(TextMapper):
return buf return buf
class GoldCreditMapper(TextMapper): class GoldCreditMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
',': 0x34, ',': 0x34,
"'": 0x35, "'": 0x35,
@ -1107,16 +1113,16 @@ class GoldCreditMapper(TextMapper):
alpha_offset = -0x47 alpha_offset = -0x47
class GreenCreditMapper(TextMapper): class GreenCreditMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
'·': 0x52} '·': 0x52}
alpha_offset = -0x29 alpha_offset = -0x29
class RedCreditMapper(TextMapper): class RedCreditMapper(CharTextMapper):
char_map = {' ': 0x9F} char_map = {' ': 0x9F}
alpha_offset = -0x61 alpha_offset = -0x61
class LargeCreditTopMapper(TextMapper): class LargeCreditTopMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
"'": 0x77, "'": 0x77,
'!': 0x78, '!': 0x78,
@ -1137,7 +1143,7 @@ class LargeCreditTopMapper(TextMapper):
number_offset = 0x23 number_offset = 0x23
class LargeCreditBottomMapper(TextMapper): class LargeCreditBottomMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
"'": 0x9D, "'": 0x9D,
'!': 0x9E, '!': 0x9E,