Files
Grinch-AP/worlds/smz3/TotalSMZ3/Text/Dialog.py

324 lines
10 KiB
Python
Raw Normal View History

2022-03-15 08:55:57 -04:00
import re
class Dialog:
command = re.compile(r"^\{[^}]*\}")
invalid = re.compile(r"(?<!^)\{[^}]*\}(?!$)", re.MULTILINE)
digit = re.compile(r"\d")
uppercaseLetter = re.compile(r"[A-Z]")
lowercaseLetter = re.compile(r"[a-z]")
@staticmethod
def Simple(text: str):
maxBytes = 256
wrap = 19
bytes = []
lines = text.split('\n')
lineIndex = 0
for line in lines:
bytes.append(0x74 if 0 else 0x75 if 1 else 0x76)
letters = line[:wrap] if len(line) > wrap else line
for letter in letters:
write = Dialog.LetterToBytes(letter)
if (write[0] == 0xFD):
bytes += write
else:
for b in write:
bytes += [ 0x00, b ]
lineIndex += 1
if (lineIndex % 3 == 0 and lineIndex < len(lines)):
bytes.append(0x7E)
if (lineIndex >= 3 and lineIndex < len(lines)):
bytes.append(0x73)
bytes.append(0x7F)
if (len(bytes) > maxBytes):
return bytes[:maxBytes - 1].append(0x7F)
return bytes
@staticmethod
def Compiled(text: str, pause = True):
maxBytes = 2046
wrap = 19
if (Dialog.invalid.match(text)):
raise Exception("Dialog commands must be placed on separate lines", text)
padOut = False
bytes = [ 0xFB ]
lines = Dialog.Wordwrap(text, wrap)
lineCount = len([l for l in lines if not Dialog.command.match(l)])
lineIndex = 0
for line in lines:
match = Dialog.command.match(line)
if (match is not None):
if (match.string == "{NOTEXT}"):
return [ 0xFB, 0xFE, 0x6E, 0x00, 0xFE, 0x6B, 0x04 ]
if (match.string == "{INTRO}"):
padOut = True
bytesMap = {
"{SPEED0}" : [ 0xFC, 0x00 ],
"{SPEED2}" : [ 0xFC, 0x02 ],
"{SPEED6}" : [ 0xFC, 0x06 ],
"{PAUSE1}" : [ 0xFE, 0x78, 0x01 ],
"{PAUSE3}" : [ 0xFE, 0x78, 0x03 ],
"{PAUSE5}" : [ 0xFE, 0x78, 0x05 ],
"{PAUSE7}" : [ 0xFE, 0x78, 0x07 ],
"{PAUSE9}" : [ 0xFE, 0x78, 0x09 ],
"{INPUT}" : [ 0xFA ],
"{CHOICE}" : [ 0xFE, 0x68 ],
"{ITEMSELECT}" : [ 0xFE, 0x69 ],
"{CHOICE2}" : [ 0xFE, 0x71 ],
"{CHOICE3}" : [ 0xFE, 0x72 ],
"{C:GREEN}" : [ 0xFE, 0x77, 0x07 ],
"{C:YELLOW}" : [ 0xFE, 0x77, 0x02 ],
"{HARP}" : [ 0xFE, 0x79, 0x2D ],
"{MENU}" : [ 0xFE, 0x6D, 0x00 ],
"{BOTTOM}" : [ 0xFE, 0x6D, 0x01 ],
"{NOBORDER}" : [ 0xFE, 0x6B, 0x02 ],
"{CHANGEPIC}" : [ 0xFE, 0x67, 0xFE, 0x67 ],
"{CHANGEMUSIC}" : [ 0xFE, 0x67 ],
"{INTRO}" : [ 0xFE, 0x6E, 0x00, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xFE, 0x6B, 0x02, 0xFE, 0x67 ],
"{IBOX}" : [ 0xFE, 0x6B, 0x02, 0xFE, 0x77, 0x07, 0xFC, 0x03, 0xF7 ],
}
result = bytesMap.get(match.string, None)
if (result is None):
raise Exception(f"Dialog text contained unknown command {match.string}", text)
else:
bytes += result
if (len(bytes) > maxBytes):
raise Exception("Command overflowed maximum byte length", text)
continue
if (lineIndex == 1):
bytes.append(0xF8); #// row 2
elif (lineIndex >= 3 and lineIndex < lineCount):
bytes.append(0xF6); #// scroll
elif (lineIndex >= 2):
bytes.append(0xF9); #// row 3
#// The first box needs to fill the full width with spaces as the palette is loaded weird.
letters = line + (" " * wrap) if padOut and lineIndex < 3 else line
for letter in letters:
bytes += Dialog.LetterToBytes(letter)
lineIndex += 1
if (pause and lineIndex % 3 == 0 and lineIndex < lineCount):
bytes.append(0xFA) #// wait for input
return bytes[:maxBytes]
@staticmethod
def Wordwrap(text: str, width: int):
result = []
for line in text.split('\n'):
line = line.rstrip()
if (len(line) <= width):
result.append(line)
else:
words = line.split(' ')
lines = [ "" ]
for word in words:
line = lines.pop()
if (len(line) + len(word) <= width):
line = f"{line}{word} "
else:
if (len(line) > 0):
lines.append(line)
line = word
while (len(line) > width):
lines.append(line[:width])
line = line[width:]
line = f"{line} "
lines.append(line)
#lines.reverse()
result += [l.strip() for l in lines]
return result
@staticmethod
def LetterToBytes(c: str):
if Dialog.digit.match(c): return [(ord(c) - ord('0') + 0xA0) ]
elif Dialog.uppercaseLetter.match(c): return [ (ord(c) - ord('A') + 0xAA) ]
elif Dialog.lowercaseLetter.match(c): return [ (ord(c) - ord('a') + 0x30) ]
else:
value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ]
#region letter bytes lookup
letters = {
' ' : [ 0x4F ],
'?' : [ 0xC6 ],
'!' : [ 0xC7 ],
',' : [ 0xC8 ],
'-' : [ 0xC9 ],
'' : [ 0xCC ],
'.' : [ 0xCD ],
'~' : [ 0xCE ],
'' : [ 0xCE ],
'\'' : [ 0xD8 ],
'' : [ 0xD8 ],
'"' : [ 0xD8 ],
':' : [ 0x4A ],
'@' : [ 0x4B ],
'#' : [ 0x4C ],
'¤' : [ 0x4D, 0x4E ], #// Morphing ball
'_' : [ 0xFF ], #// Full width space
'£' : [ 0xFE, 0x6A ], #// link's name compressed
'>' : [ 0xD2, 0xD3 ], #// link face
'%' : [ 0xDD ], #// Hylian Bird
'^' : [ 0xDE ], #// Hylian Ankh
'=' : [ 0xDF ], #// Hylian Wavy lines
'' : [ 0xE0 ],
'' : [ 0xE1 ],
'' : [ 0xE2 ],
'' : [ 0xE3 ],
'' : [ 0xE4 ], #// cursor
'¼' : [ 0xE5, 0xE7 ], #// 1/4 heart
'½' : [ 0xE6, 0xE7 ], #// 1/2 heart
'¾' : [ 0xE8, 0xE9 ], #// 3/4 heart
'' : [ 0xEA, 0xEB ], #// full heart
'' : [ 0xFE, 0x6C, 0x00 ], #// var 0
'' : [ 0xFE, 0x6C, 0x01 ], #// var 1
'' : [ 0xFE, 0x6C, 0x02 ], #// var 2
'' : [ 0xFE, 0x6C, 0x03 ], #// var 3
'' : [ 0x00 ],
'' : [ 0x01 ],
'' : [ 0x02 ],
'' : [ 0x03 ],
'' : [ 0x04 ],
'' : [ 0x05 ],
'' : [ 0x06 ],
'' : [ 0x07 ],
'' : [ 0x08 ],
'' : [ 0x09 ],
'' : [ 0x0A ],
'' : [ 0x0B ],
'' : [ 0x0C ],
'' : [ 0x0D ],
'' : [ 0x0E ],
'' : [ 0x0F ],
'' : [ 0x10 ],
'' : [ 0x11 ],
'' : [ 0x12 ],
'' : [ 0x13 ],
'' : [ 0x14 ],
'' : [ 0x15 ],
'' : [ 0x16 ],
'' : [ 0x17 ],
'' : [ 0x18 ],
'' : [ 0x19 ],
'' : [ 0x1A ],
'' : [ 0x1B ],
'' : [ 0x1C ],
'' : [ 0x1D ],
'' : [ 0x1E ],
'' : [ 0x1F ],
'' : [ 0x20 ],
'' : [ 0x21 ],
'' : [ 0x22 ],
'' : [ 0x23 ],
'' : [ 0x24 ],
'' : [ 0x25 ],
'' : [ 0x26 ],
'' : [ 0x27 ],
'' : [ 0x28 ],
'' : [ 0x29 ],
'' : [ 0x2A ],
'' : [ 0x2B ],
'' : [ 0x2C ],
'' : [ 0x2D ],
'' : [ 0x2E ],
'' : [ 0x2F ],
'' : [ 0x50 ],
'' : [ 0x51 ],
'' : [ 0x52 ],
'' : [ 0x53 ],
'' : [ 0x54 ],
'' : [ 0x55 ],
'' : [ 0x56 ],
'' : [ 0x57 ],
'' : [ 0x58 ],
'' : [ 0x59 ],
'' : [ 0x5A ],
'' : [ 0x5B ],
'' : [ 0x5C ],
'' : [ 0x5D ],
'' : [ 0x5E ],
'' : [ 0x5F ],
'' : [ 0x60 ],
'' : [ 0x61 ],
'' : [ 0x62 ],
'' : [ 0x63 ],
'' : [ 0x64 ],
'' : [ 0x65 ],
'' : [ 0x66 ],
'' : [ 0x67 ],
'' : [ 0x68 ],
'' : [ 0x69 ],
'' : [ 0x6A ],
'' : [ 0x6B ],
'' : [ 0x6C ],
'' : [ 0x6D ],
'' : [ 0x6E ],
'' : [ 0x6F ],
'' : [ 0x70 ],
'' : [ 0x71 ],
'' : [ 0x72 ],
'' : [ 0x73 ],
'' : [ 0x74 ],
'' : [ 0x75 ],
'' : [ 0x76 ],
'' : [ 0x77 ],
'' : [ 0x78 ],
'' : [ 0x79 ],
'' : [ 0x7A ],
'' : [ 0x7B ],
'' : [ 0x7C ],
'' : [ 0x7D ],
'' : [ 0x7E ],
'' : [ 0x80 ],
'' : [ 0x81 ],
'' : [ 0x82 ],
'' : [ 0x83 ],
'' : [ 0x84 ],
'' : [ 0x85 ],
'' : [ 0x86 ],
'' : [ 0x87 ],
'' : [ 0x88 ],
'' : [ 0x89 ],
'' : [ 0x8A ],
'' : [ 0x8B ],
'' : [ 0x8C ],
'' : [ 0x8D ],
'' : [ 0x8E ],
'' : [ 0x8F ],
'' : [ 0x90 ],
'' : [ 0x91 ],
'' : [ 0x92 ],
'' : [ 0x93 ],
'' : [ 0x94 ],
'' : [ 0x95 ],
'' : [ 0x96 ],
'' : [ 0x97 ],
'' : [ 0x98 ],
'' : [ 0x99 ],
'' : [ 0x9A ],
'' : [ 0x9B ],
'' : [ 0x9C ],
'' : [ 0x9D ],
'' : [ 0x9E ],
'' : [ 0x9F ],
}