sovereignx/tools/learnset_helpers/teachable.py
Bassoonian 5d5cc76a2c
Teachable learnset helper mechanics (#3856)
* Teachable learnset helper mechanics

* Rename folder and python script

* Some teachable learnset work

* Update PoryMoves file labels

* Add header and make custom json

* Include found moves in output file

* Update SV file to latest version

* Don't run if there are no jsons to be found

* Add Basculin duplication in json

* Add universal move support to

* Ignore and skip Mew

* Integrate tool in Makefile

* Condense Basculin learnsets

* Split Oinkologne for easier generation

* Add Deoxys' XD move tutor data

* Add missing Darumaka/Yamask Galarian SwSh TMs

* Add TID species to sv.json

* Update sv.json to The Indigo Disk data

* Add Python install instructions

* Fix Makefile

* Expand header with more information

* Add config to allow disabling the learnset helper

* Update include/config/pokemon.h

Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>

* Don't crash if the config is missing

---------

Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-02-08 11:32:48 -03:00

205 lines
8.2 KiB
Python

import glob
import re
import json
import os
# before all else, abort if the config is off
with open("./include/config/pokemon.h", "r") as file:
learnset_config = re.findall("#define P_LEARNSET_HELPER_TEACHABLE *([^ ]*)", file.read())
if len(learnset_config) != 1:
quit()
if learnset_config[0] != "TRUE":
quit()
def parse_mon_name(name):
return re.sub('(?!^)([A-Z]+)', r'_\1', name).upper()
tm_moves = []
tutor_moves = []
# scan incs
incs_to_check = glob.glob('./data/scripts/*.inc') # all .incs in the script folder
incs_to_check += glob.glob('./data/maps/*/scripts.inc') # all map scripts
if len(incs_to_check) == 0: # disabled if no jsons present
quit()
for file in incs_to_check:
with open(file, 'r') as f2:
raw = f2.read()
if 'special ChooseMonForMoveTutor' in raw:
for x in re.findall('setvar VAR_0x8005, (MOVE_.*)', raw):
if not x in tutor_moves:
tutor_moves.append(x)
# scan TMs and HMs
with open("./include/constants/tms_hms.h", 'r') as file:
for x in re.findall('F\((.*)\)', file.read()):
if not 'MOVE_' + x in tm_moves:
tm_moves.append('MOVE_' + x)
# look up universal moves to exclude them
universal_moves = []
with open("./src/pokemon.c", "r") as file:
for x in re.findall("static const u16 sUniversalMoves\[\] =(.|\n)*?{((.|\n)*?)};", file.read())[0]:
x = x.replace("\n", "")
for y in x.split(","):
y = y.strip()
if y == "":
continue
universal_moves.append(y)
# get compatibility from jsons
def construct_compatibility_dict(force_custom_check):
dict_out = {}
for pth in glob.glob('./tools/learnset_helpers/porymoves_files/*.json'):
f = open(pth, 'r')
data = json.load(f)
for mon in data.keys():
if not mon in dict_out:
dict_out[mon] = []
for move in data[mon]['LevelMoves']:
if not move['Move'] in dict_out[mon]:
dict_out[mon].append(move['Move'])
#for move in data[mon]['PreEvoMoves']:
# if not move in dict_out[mon]:
# dict_out[mon].append(move)
for move in data[mon]['TMMoves']:
if not move in dict_out[mon]:
dict_out[mon].append(move)
for move in data[mon]['EggMoves']:
if not move in dict_out[mon]:
dict_out[mon].append(move)
for move in data[mon]['TutorMoves']:
if not move in dict_out[mon]:
dict_out[mon].append(move)
# if the file was not previously generated, check if there is custom data there that needs to be preserved
with open("./src/data/pokemon/teachable_learnsets.h", 'r') as file:
raw = file.read()
if not "// DO NOT MODIFY THIS FILE!" in raw and force_custom_check == True:
custom_teachable_compatibilities = {}
for entry in re.findall("static const u16 s(.*)TeachableLearnset\[\] = {\n((.|\n)*?)\n};", raw):
monname = parse_mon_name(entry[0])
if monname == "NONE":
continue
compatibility = entry[1].split("\n")
if not monname in custom_teachable_compatibilities:
custom_teachable_compatibilities[monname] = []
if not monname in dict_out:
# this mon is unknown, so all data needs to be preserved
for move in compatibility:
move = move.replace(",", "").strip()
if move == "" or move == "MOVE_UNAVAILABLE":
continue
custom_teachable_compatibilities[monname].append(move)
else:
# this mon is known, so check if the moves in the old teachable_learnsets.h are not in the jsons
for move in compatibility:
move = move.replace(",", "").strip()
if move == "" or move == "MOVE_UNAVAILABLE":
continue
if not move in dict_out[monname]:
custom_teachable_compatibilities[monname].append(move)
# actually store the data in custom.json
if os.path.exists("./tools/learnset_helpers/porymoves_files/custom.json"):
f2 = open("./tools/learnset_helpers/porymoves_files/custom.json", "r")
custom_json = json.load(f2)
f2.close()
else:
custom_json = {}
for x in custom_teachable_compatibilities:
if len(custom_teachable_compatibilities[x]) == 0:
continue
if not x in custom_json:
custom_json[x] = {"LevelMoves": [], "PreEvoMoves": [], "TMMoves": [], "EggMoves": [], "TutorMoves": []}
for move in custom_teachable_compatibilities[x]:
custom_json[x]["TutorMoves"].append(move)
f2 = open("./tools/learnset_helpers/porymoves_files/custom.json", "w")
f2.write(json.dumps(custom_json, indent=2))
f2.close()
print("FIRST RUN: Updated custom.json with teachable_learnsets.h's data")
# rerun the process
dict_out = construct_compatibility_dict(False)
return dict_out
compatibility_dict = construct_compatibility_dict(True)
# actually prepare the file
with open("./src/data/pokemon/teachable_learnsets.h", 'r') as file:
out = file.read()
list_of_mons = re.findall('static const u16 s(.*)TeachableLearnset', out)
for mon in list_of_mons:
mon_parsed = parse_mon_name(mon)
tm_learnset = []
tutor_learnset = []
if mon_parsed == "NONE" or mon_parsed == "MEW":
continue
if not mon_parsed in compatibility_dict:
print("Unable to find %s in json" % mon)
continue
for move in tm_moves:
if move in universal_moves:
continue
if move in tm_learnset:
continue
if move in compatibility_dict[mon_parsed]:
tm_learnset.append(move)
continue
for move in tutor_moves:
if move in universal_moves:
continue
if move in tutor_learnset:
continue
if move in compatibility_dict[mon_parsed]:
tutor_learnset.append(move)
continue
tm_learnset.sort()
tutor_learnset.sort()
tm_learnset += tutor_learnset
repl = "static const u16 s%sTeachableLearnset[] = {\n " % mon
if len(tm_learnset) > 0:
repl += ",\n ".join(tm_learnset) + ",\n "
repl += "MOVE_UNAVAILABLE,\n};"
newout = re.sub('static const u16 s%sTeachableLearnset\[\] = {[\s\S]*?};' % mon, repl, out)
if newout != out:
out = newout
print("Updated %s" % mon)
# add/update header
header = "//\n// DO NOT MODIFY THIS FILE! It is auto-generated from tools/learnset_helpers/teachable.py\n//\n\n"
longest_move_name = 0
for move in tm_moves + tutor_moves:
if len(move) > longest_move_name:
longest_move_name = len(move)
longest_move_name += 2 # + 2 for a hyphen and a space
if longest_move_name < len("Found near-universal moves:"):
longest_move_name = len("Found near-universal moves:")
def header_print(str):
global header
header += "// " + str + " " * (longest_move_name - len(str)) + " //\n"
header += "// " + longest_move_name * "*" + " //\n"
header_print("Found TM/HM moves:")
for move in tm_moves:
header_print("- " + move)
header += "// " + longest_move_name * "*" + " //\n"
header_print("Found tutor moves:")
tutor_moves.sort() # alphabetically sort tutor moves for easier referencing
for move in tutor_moves:
header_print("- " + move)
header += "// " + longest_move_name * "*" + " //\n"
header_print("Found near-universal moves:")
universal_moves.sort() # alphabetically sort near-universal moves for easier referencing
for move in universal_moves:
header_print("- " + move)
header += "// " + longest_move_name * "*" + " //\n\n"
if not "// DO NOT MODIFY THIS FILE!" in out:
out = header + out
else:
out = re.sub("\/\/\n\/\/ DO NOT MODIFY THIS FILE!(.|\n)*\* \/\/\n\n", header, out)
with open("./src/data/pokemon/teachable_learnsets.h", 'w') as file:
file.write(out)