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

329 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)
character = re.compile(r"(?P<digit>[0-9])|(?P<upper>[A-Z])|(?P<lower>[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 < len(lines)):
if (lineIndex % 3 == 0):
bytes.append(0x7E) # pause for input
if (lineIndex >= 3):
bytes.append(0x73) # scroll
return bytes[:maxBytes - 1].append(0x7F)
@staticmethod
def Compiled(text: str):
maxBytes = 2046
wrap = 19
if (Dialog.invalid.match(text)):
raise Exception("Dialog commands must be placed on separate lines", text)
padOut = False
pause = True
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
if (match.string == "{NOPAUSE}"):
pause = False
continue
result = Dialog.CommandBytesFor(match.string)
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 > 0):
bytes.append(0xF8 if lineIndex == 1 else #// row 2
0xF9 if lineIndex == 2 else #// row 3
0xF6) #// scroll
#// 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) #// pause for input
return bytes[:maxBytes]
@staticmethod
def CommandBytesFor(text: str):
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 ],
}
return bytesMap.get(text, None)
@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):
match = Dialog.character.match(c)
if match is None:
value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ]
elif match.group("digit") != None: return [(ord(c) - ord('0') + 0xA0) ]
elif match.group("upper") != None: return [ (ord(c) - ord('A') + 0xAA) ]
elif match.group("lower") != None: return [ (ord(c) - ord('a') + 0x30) ]
else:
value = Dialog.letters.get(c, None)
return value if value else [ 0xFF ]
#regions 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 ],
}