Archipelago/worlds/smz3/TotalSMZ3/Text/Dialog.py

324 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 ],
}