sm64coopdx/autogen/convert_constants.py
Isaac0-dev c9e4efdb31
custom level fixes and fixes from other pr (#483)
clean up custom level code
    fixed a bug where custom level course numbers weren't used by dynos warps
    removed a bunch of unused dynos code
    fix demos triggering incorrectly
    allowed the right Ctrl key to be used when opening the in game console
    fixed a softlock that was possible to experience when talking to the snowman in CCM
    fixed the bug where you can permanently lose your cap (bug created by my own PR from beta 32)
    fix the moderator feature I made a while back; I am amazed it even worked at all before
    fixed dynos warp initial actions being skipped (read ec8aabc for explanation)
    completely changed the way star names and course names work
2023-10-27 16:42:27 -07:00

377 lines
10 KiB
Python

import os
from xml.etree.ElementInclude import include
from common import *
from extract_constants import *
in_filename = 'autogen/lua_constants/built-in.lua'
out_filename = 'src/pc/lua/smlua_constants_autogen.c'
out_filename_docs = 'docs/lua/constants.md'
out_filename_defs = 'autogen/lua_definitions/constants.lua'
in_files = [
"include/types.h",
"include/sm64.h",
"src/pc/lua/smlua_hooks.h",
"src/game/area.h",
"src/game/camera.h",
"include/mario_animation_ids.h",
"include/sounds.h",
"src/game/characters.h",
"src/pc/network/network.h",
"src/pc/network/network_player.h",
"include/PR/os_cont.h",
"src/game/interaction.c",
"src/game/interaction.h",
"src/pc/djui/djui_hud_utils.h",
"include/behavior_table.h",
"src/pc/lua/utils/smlua_model_utils.h",
"src/pc/lua/utils/smlua_misc_utils.h",
"include/object_constants.h",
"include/mario_geo_switch_case_ids.h",
"src/game/object_list_processor.h",
"src/engine/graph_node.h",
"levels/level_defines.h",
"src/game/obj_behaviors.c",
"src/game/save_file.h",
"src/game/obj_behaviors_2.h",
"include/dialog_ids.h",
"include/seq_ids.h",
"include/surface_terrains.h",
"src/game/level_update.h",
"src/pc/network/version.h",
"include/geo_commands.h",
"include/level_commands.h",
"src/audio/external.h",
"src/game/envfx_snow.h",
"src/pc/mods/mod_storage.cpp.h"
]
exclude_constants = {
'*': [ '^MAXCONTROLLERS$', '^AREA_[^T].*', '^AREA_T[HTO]', '^CONT_ERR.*', '^READ_MASK$', '^SIGN_RANGE$', ],
'src/game/obj_behaviors.c': ['^o$'],
}
include_constants = {
'include/geo_commands.h': ['BACKGROUND'],
'include/level_commands.h': [ "WARP_CHECKPOINT", "WARP_NO_CHECKPOINT" ],
'src/audio/external.h': [ "SEQ_PLAYER" ],
'src/pc/mods/mod_storage.cpp.h': [ "MAX_KEYS", "MAX_KEY_VALUE_LENGTH" ]
}
pretend_find = [
'SOUND_ARG_LOAD'
]
############################################################################
seen_constants = []
totalConstants = 0
############################################################################
def validate_identifiers(built_files):
all_identifiers = [x.group()[1:] for x in re.finditer(r'[(, ][A-Z_][A-Z0-9_]*', built_files)]
all_identifiers = set(all_identifiers)
for ident in all_identifiers:
if ident in pretend_find:
continue
if ident + ' = ' not in built_files:
print('COULD NOT FIND ' + ident)
############################################################################
def saw_constant(identifier):
if identifier in seen_constants:
print("SAW DUPLICATE CONSTANT: " + identifier)
return True
else:
global totalConstants
totalConstants += 1
seen_constants.append(identifier)
return False
def allowed_identifier(filename, ident):
exclude_list = exclude_constants['*']
if filename in exclude_constants:
exclude_list.extend(exclude_constants[filename])
for exclude in exclude_list:
if re.search(exclude, ident) != None:
return False
if filename in include_constants:
for include in include_constants[filename]:
if re.search(include, ident) != None:
return True
return False
return True
def process_enum(filename, line):
_, ident, val = line.split(' ', 2)
if '{' not in val or '}' not in val:
#print('UNRECOGNIZED ENUM: ' + line)
return None
# grab inside body
val = val.split('{', 1)[-1].rsplit('}', 1)[0]
ret = {}
ret['identifier'] = ident
constants = []
set_to = None
index = 0
fields = val.split(',')
for field in fields:
field = field.strip()
if len(field) == 0:
continue
if '=' in field:
ident, val = field.split('=', 2)
constants.append([ident.strip(), val.strip()])
set_to = ident
index = 1
continue
if set_to is not None:
constants.append([field, '((%s) + %d)' % (set_to, index)])
index += 1
continue
if allowed_identifier(filename, field):
constants.append([field, str(index)])
if saw_constant(field):
print('>>> ' + line)
index += 1
ret['constants'] = constants
return ret
def process_define(filename, line):
_, ident, val = line.split(' ', 2)
val = val.replace('(u8)', '')
val = val.replace('(u64)', '')
for p in val.split(' '):
if p.startswith('0x'):
continue
p = re.sub(r'0x[a-fA-F0-9]+', '', p)
if re.search('[a-z]', p) != None and 'VERSION_TEXT' not in line:
if 'gCurrentObject' not in line and 'gNetworkType' not in line:
print('UNRECOGNIZED DEFINE: ' + line)
return None
if not allowed_identifier(filename, ident):
return None
if saw_constant(ident):
print('>>> ' + line)
return [ident, val]
def process_line(filename, line):
if line.startswith('enum '):
return process_enum(filename, line)
elif line.startswith('#define '):
return process_define(filename, line)
else:
print("UNRECOGNIZED LINE: " + line)
return None
def process_file(filename):
processed_file = {}
processed_file['filename'] = filename.replace('\\', '/').split('/')[-1]
constants = []
lines = extract_constants(get_path(filename)).splitlines()
for line in lines:
c = process_line(filename, line)
if c != None:
constants.append(c)
processed_file['constants'] = constants
return processed_file
def process_files():
seen_constants = []
processed_files = []
files = sorted(in_files, key=lambda d: d.split('/')[-1])
for f in files:
processed_files.append(process_file(f))
return processed_files
############################################################################
def build_constant(processed_constant):
constants = processed_constant
s = ''
is_enum = 'identifier' in processed_constant
if is_enum:
constants = processed_constant['constants']
else:
constants = [processed_constant]
for c in constants:
s += '%s = %s\n' % (c[0], c[1].replace('"', "'"))
return s
def build_file(processed_file):
s = ''
for c in processed_file['constants']:
s += build_constant(c)
return s
def build_files(processed_files):
s = ''
for file in processed_files:
s += build_file(file)
return s
def build_to_c(built_files):
txt = ''
with open(get_path(in_filename), 'r', newline='\n') as f:
txt = f.read()
txt += '\n' + built_files
while ('\n\n' in txt):
txt = txt.replace('\n\n', '\n')
lines = txt.splitlines()
txt = 'char gSmluaConstants[] = ""\n'
for line in lines:
txt += '"%s\\n"\n' % line
txt += ';'
return txt
############################################################################
def doc_constant_index(processed_files):
s = '# Supported Constants\n'
for processed_file in processed_files:
s += '- [%s](#%s)\n' % (processed_file['filename'], processed_file['filename'].replace('.', ''))
constants = [x for x in processed_file['constants'] if 'identifier' in x]
constants = sorted(constants, key=lambda d: d['identifier'])
for c in constants:
s += ' - [enum %s](#enum-%s)\n' % (c['identifier'], c['identifier'])
s += '\n<br />\n\n'
return s
def doc_constant(processed_constant):
constants = processed_constant
s = ''
is_enum = 'identifier' in processed_constant
if is_enum:
constants = processed_constant['constants']
if len(constants) == 0:
return ''
enum = 'enum ' + processed_constant['identifier']
s += '\n### [%s](#%s)\n' % (enum, processed_constant['identifier'])
s += '| Identifier | Value |\n'
s += '| :--------- | :---- |\n'
for c in constants:
s += '| %s | %s |\n' % (c[0], c[1])
return s
for c in [processed_constant]:
s += '- %s\n' % (c[0])
return s
def doc_file(processed_file):
s = '## [%s](#%s)\n' % (processed_file['filename'], processed_file['filename'])
constants = sorted(processed_file['constants'], key=lambda d: 'zzz' + d['identifier'] if 'identifier' in d else d[0])
for c in constants:
s += doc_constant(c)
s += '\n[:arrow_up_small:](#)\n'
s += '\n<br />\n\n'
return s
def doc_files(processed_files):
s = '## [:rewind: Lua Reference](lua.md)\n\n'
s += doc_constant_index(processed_files)
for file in processed_files:
s += doc_file(file)
return s
############################################################################
def def_constant(processed_constant):
constants = processed_constant
s = ''
is_enum = 'identifier' in processed_constant
if is_enum:
s += '\n--- @class %s\n' % translate_to_def(processed_constant['identifier'])
constants = processed_constant['constants']
if len(constants) == 0:
return ''
for c in constants:
s += '\n--- @type %s\n' % translate_to_def(processed_constant['identifier'])
s += '%s = %s\n' % (c[0], c[1])
return s
for c in [processed_constant]:
if '"' in c[1]:
s += '\n--- @type string\n'
else:
s += '\n--- @type integer\n'
s += '%s = %s\n' % (c[0], c[1])
return s
def build_to_def(processed_files):
s = '-- AUTOGENERATED FOR CODE EDITORS -- \n--- @meta\n--- @diagnostic disable\n\n'
with open(get_path(in_filename), 'r', newline='\n') as f:
s += f.read()
s += '\n'
for file in processed_files:
constants = sorted(file['constants'], key=lambda d: 'zzz' + d['identifier'] if 'identifier' in d else d[0])
for c in constants:
s += def_constant(c)
return s
############################################################################
def main():
processed_files = process_files()
built_files = build_files(processed_files)
validate_identifiers(built_files)
built_c = build_to_c(built_files)
with open(get_path(out_filename), 'w', newline='\n') as out:
out.write(built_c)
doc = doc_files(processed_files)
with open(get_path(out_filename_docs), 'w', newline='\n') as out:
out.write(doc)
defs = build_to_def(processed_files)
with open(get_path(out_filename_defs), 'w', newline='\n') as out:
out.write(defs)
global totalConstants
print("Total constants: " + str(totalConstants))
main()