mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-20 14:16:26 -04:00
Added a script to automatically find issues
This commit is contained in:
@@ -24,6 +24,7 @@ eclipse
|
||||
run
|
||||
runClient
|
||||
runServer
|
||||
utilityscripts/implementation/*.txt
|
||||
|
||||
# Files from Forge MDK
|
||||
forge*changelog.txt
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
Hello!
|
||||
This is a script for checking asset implementation.
|
||||
|
||||
There are a few things needed in this folder to get this running:
|
||||
- Create a text document called `assets_directory.txt` and paste in the path to where you have cloned the asset repository, for example `D:\Dev\Cobblemon\Repository\cobblemon-assets`
|
||||
- Create a text document called `sheet_id.txt` and paste in our spreadsheet's identifier, it's found in the link between `../d/` and `/edit..`
|
||||
|
||||
Both text files and are ignored by git and will not get pushed.
|
||||
|
||||
If you don't have Python already, you will need to install it from here: https://www.python.org/downloads/
|
||||
|
||||
Then you will need external libraries, open the terminal (search for `cmd` on Windows) and type in the following: `pip install pandas soundfile`
|
||||
If the terminal says "Requirement already satisfied," but the script doesn't work, try this instead:
|
||||
- Type in `py -0p`, this will show installed versions and highlight your default with `*`
|
||||
- Type in `py -3.14 -m pip install pandas soundfile`, replace `-3.14` with your default version
|
||||
|
||||
If there are any issues, or you need help setting your folder up, poke Sterr on Discord.
|
||||
@@ -0,0 +1,757 @@
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
try: import msvcrt
|
||||
except: print("Could not import msvcrt")
|
||||
import pandas as pd
|
||||
import soundfile as sf
|
||||
|
||||
# Quick class to make getch work on all systems
|
||||
class Getch:
|
||||
def __init__(self):
|
||||
try:
|
||||
self.impl = self._getch_windows
|
||||
except ImportError:
|
||||
self.impl = self._getch_unix
|
||||
|
||||
def _getch_windows(self):
|
||||
return msvcrt.getch()
|
||||
|
||||
def _getch_unix(self):
|
||||
import sys, tty, termios
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
ch = sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
return ch
|
||||
|
||||
def __call__(self): return self.impl()
|
||||
getch = Getch()
|
||||
|
||||
# Colors weeeee
|
||||
def print_color(color):
|
||||
if color == "reset": print("\033[0m", end="")
|
||||
if color == "red": print("\033[31m", end="")
|
||||
if color == "green": print("\033[32m", end="")
|
||||
if color == "yellow": print("\033[33m", end="")
|
||||
if color == "blue": print("\033[34m", end="")
|
||||
if color == "magenta": print("\033[35m", end="")
|
||||
if color == "cyan": print("\033[36m", end="")
|
||||
if color == "white": print("\033[37m", end="")
|
||||
|
||||
# Debug function
|
||||
def debug(string):
|
||||
if (log_debug): print(f"\033[33m" + f"{string}" + "\033[0m")
|
||||
|
||||
# Directories
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
assets_dir_config = os.path.join(script_dir, "assets_directory.txt")
|
||||
sheet_id_config = os.path.join(script_dir, "sheet_id.txt")
|
||||
|
||||
if not os.path.exists(assets_dir_config):
|
||||
print(f"Please create assets_directory.txt and paste your directory path inside\nCheck README.md for more information")
|
||||
getch()
|
||||
quit()
|
||||
|
||||
with open(assets_dir_config, "r") as f:
|
||||
assets_dir = os.path.normpath(f.read().strip().replace('"', ''))
|
||||
|
||||
if not os.path.exists(sheet_id_config):
|
||||
print(f"Please create sheet_id.txt and paste our spreadsheet's identifier inside\nCheck README.md for more information")
|
||||
getch()
|
||||
quit()
|
||||
|
||||
with open(sheet_id_config, "r") as f:
|
||||
sheet_id = os.path.normpath(f.read().strip().replace('"', ''))
|
||||
|
||||
## Script start
|
||||
print(f"Press either of these keys to check Cobblemon assets implementation")
|
||||
print(f"► [1] - Basic")
|
||||
print(f"► [2] - Full")
|
||||
print(f"► [4] - Debug")
|
||||
print(f"► [Esc] Exit")
|
||||
print(f"\n[1] Basic only shows critical issues that will be apparent to players in-game.")
|
||||
print(f"Issues with Pokémon cries show only if a cry is made and should be implemented.")
|
||||
print(f"Some sheet disparities will be shown if a Pokémon's implementation status is incorrect.")
|
||||
print(f"\n[2] Full shows all issues, inconsistencies and disparities.")
|
||||
print(f"Files are compared one to one with the assets repository.")
|
||||
print(f"\nThis script is still in development, but can already be used.")
|
||||
print(f"Report any false positives or issues and feel free to request any features.\n")
|
||||
log_debug = False
|
||||
# Set the operation
|
||||
while True:
|
||||
input = getch()
|
||||
if input == b"1":
|
||||
mode = "basic"
|
||||
break
|
||||
elif input == b"2":
|
||||
mode = "full"
|
||||
break
|
||||
elif input == b"4":
|
||||
mode = "debug"
|
||||
log_debug = True
|
||||
break
|
||||
elif input == b'\x1b':
|
||||
quit()
|
||||
debug("Debug enabled, missile inbound")
|
||||
print(f"Found the Cobblemon assets repository at \"{assets_dir}\"")
|
||||
|
||||
|
||||
|
||||
## Spreadsheet
|
||||
# Individual sheet GIDs
|
||||
gid_implementation = 0
|
||||
gid_cries = 1849522968
|
||||
gid_models = 1841954062
|
||||
# DataFrames
|
||||
print(f"Fetching spreadsheets, this may take a few seconds...")
|
||||
try:
|
||||
df_models = pd.read_csv(f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={gid_models}")
|
||||
df_implementation = pd.read_csv(f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={gid_implementation}")
|
||||
df_cries = pd.read_csv(f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={gid_cries}")
|
||||
except:
|
||||
print("Failed to fetch spreadsheets, make sure the sheet identifier is correct.")
|
||||
getch()
|
||||
quit()
|
||||
print("\n")
|
||||
|
||||
# Clear the name from special characters
|
||||
def sanitize_name(name):
|
||||
# Lower case everything
|
||||
name = name.lower()
|
||||
# Edge cases
|
||||
name = name.replace("mr. mime", "mr_mime") # Mr. Mime
|
||||
name = name.replace("mr. rime", "mr_rime") # Mr. Rime
|
||||
name = name.replace("mr.rime", "mr_rime") # Also Mr. Rime
|
||||
name = name.replace("mime jr.", "mime_jr") # Also Mr. Rime
|
||||
name = re.sub(r"furfrou.*", "furfrou", name) # Furfrou
|
||||
# fefe
|
||||
name = name.replace(" [", "_")
|
||||
name = name.replace("]", "")
|
||||
# Turn signs into letters
|
||||
name = name.replace("♀", "f")
|
||||
name = name.replace("♂", "m")
|
||||
# Turn m and f into full words
|
||||
name = re.sub(r"_m$", "_male", name)
|
||||
name = re.sub(r"_f$", "_female", name)
|
||||
# éééééééééé
|
||||
name = name.replace("é", "e")
|
||||
name = name.replace("\'", "")
|
||||
name = name.replace("-", "_")
|
||||
name = name.replace(" ", "_")
|
||||
# Correct regional names
|
||||
name = name.replace("alola", "alolan")
|
||||
name = name.replace("galar", "galarian")
|
||||
name = name.replace("hisui", "hisuian")
|
||||
name = name.replace("paldea", "paldean")
|
||||
# We go
|
||||
return name
|
||||
|
||||
# Removes regions and special forms from the name
|
||||
def default_name(name):
|
||||
# Remove special forms
|
||||
name = name.replace("_midday", "")
|
||||
name = name.replace("_midnight", "")
|
||||
name = name.replace("_dusk", "")
|
||||
# Remove regions from names
|
||||
name = name.replace("_alolan", "")
|
||||
name = name.replace("_galarian", "")
|
||||
name = name.replace("_hisuian", "")
|
||||
name = name.replace("_paldean", "")
|
||||
# Tauros stuff
|
||||
name = name.replace("paldeancombat", "")
|
||||
name = name.replace("paldeanblaze", "")
|
||||
name = name.replace("paldeanaqua", "")
|
||||
# Turn remove variants
|
||||
name = name.replace("_male", "")
|
||||
name = name.replace("_female", "")
|
||||
# Edge cases
|
||||
name = re.sub(r"unown.*", "unown", name) # Unown
|
||||
# We go
|
||||
return name
|
||||
|
||||
# This is used for unconventional names
|
||||
def incorrect_region_name(name):
|
||||
name = name.replace("_alolan", "_alola")
|
||||
name = name.replace("_galarian", "_galar")
|
||||
name = name.replace("_hisuian", "_hisui")
|
||||
name = name.replace("_paldean", "_paldea")
|
||||
# We go
|
||||
return name
|
||||
|
||||
# Handle all edge cases with different uses for Pokémon names
|
||||
def normalize_name(name, mode):
|
||||
pokemon = name
|
||||
|
||||
## DIRECTORY
|
||||
if mode == "directory":
|
||||
# Remove special forms
|
||||
name = name.replace("_midday", "")
|
||||
name = name.replace("_midnight", "")
|
||||
name = name.replace("_dusk", "")
|
||||
# Remove regions from names
|
||||
name = name.replace("_alolan", "")
|
||||
name = name.replace("_galarian", "")
|
||||
name = name.replace("_hisuian", "")
|
||||
name = name.replace("_paldean", "")
|
||||
# Remove variants
|
||||
if "unown" not in name: name = name.replace("_male", "")
|
||||
if "unown" not in name: name = name.replace("_female", "")
|
||||
## Edge cases
|
||||
# Unown
|
||||
name = re.sub(r"unown.*", "unown", name)
|
||||
# Toxtricity stuff
|
||||
name = name.replace("_amped", "")
|
||||
name = name.replace("_low_key", "")
|
||||
name = name.replace("_lowkey", "")
|
||||
# Tauros stuff
|
||||
if "tauros" in name: return "tauros"
|
||||
# Gimmighoul
|
||||
if "gimmighoul" in name: return "gimmighoul"
|
||||
# Shellos
|
||||
if "shellos" in pokemon: return "shellos"
|
||||
# Cherrim
|
||||
if "cherrim" in pokemon: return "cherrim"
|
||||
# Remove underscores
|
||||
name = name.replace('_', '')
|
||||
# We go
|
||||
return name
|
||||
|
||||
## POSER
|
||||
if mode == "poser":
|
||||
# Unown
|
||||
if "unown" in pokemon:
|
||||
name = name.replace('?', 'question')
|
||||
name = name.replace('!', 'exclamation')
|
||||
return name
|
||||
# Mr. Mime and Mr. Rime
|
||||
if "mr_mime" in pokemon: return "mrmime"
|
||||
if "mr_rime" in pokemon: return "mrrime"
|
||||
# Toxtricity
|
||||
if "toxtricity_low_key" in pokemon: return "toxtricity_lowkey"
|
||||
# Gimmighoul Roaming without an underscore apparently
|
||||
if "gimmighoul_roaming" in pokemon: return "gimmighoulroaming"
|
||||
# Ho-oh
|
||||
if "ho_oh" in pokemon: return "hooh"
|
||||
# Milotic
|
||||
if "milotic_male" in pokemon: return "milotic"
|
||||
# Porygon-Z
|
||||
if "porygon_z" in pokemon: return "porygon-z"
|
||||
|
||||
## ANIMATION
|
||||
if mode == "animation":
|
||||
# Unown
|
||||
if "unown" in pokemon: return re.sub(r"unown.*", "unown", name)
|
||||
# Toxtricity
|
||||
if "toxtricity_low_key" in pokemon: return "toxtricity_lowkey"
|
||||
# Shellos
|
||||
if "shellos" in pokemon: return "shellos"
|
||||
# Jangmo-o line and my depleting sanity
|
||||
if "jangmo" in pokemon: return "jangmo-o"
|
||||
if "hakamo" in pokemon: return "hakamo-o"
|
||||
if "kommo" in pokemon: return "kommo-o"
|
||||
# Unfezant, only male, because it's not named that on the sheet
|
||||
if pokemon == "unfezant": return "unfezant_male"
|
||||
# Milotic
|
||||
if "milotic" in pokemon: return "milotic"
|
||||
# Porygon-Z
|
||||
if "porygon_z" in pokemon: return "porygon-z"
|
||||
|
||||
## CRY
|
||||
if mode == "cry":
|
||||
# Kadabra and Alakazam
|
||||
if "kadabra_" in pokemon: return "kadabra"
|
||||
if "alakazam_" in pokemon: return "alakazam"
|
||||
# Meowstic
|
||||
if "meowstic" in pokemon: return "meowstic"
|
||||
# Unown
|
||||
name = re.sub(r"unown.*", "unown", name)
|
||||
# Toxtricity
|
||||
if "toxtricity_low_key" in pokemon: return "toxtricity_lowkey"
|
||||
# Gimmighoul Roaming without an underscore apparently
|
||||
if "gimmighoul_roaming" in pokemon: return "gimmighoulroaming"
|
||||
# Shellos
|
||||
if "shellos" in pokemon: return "shellos"
|
||||
# Jangmo-o line
|
||||
if "jangmo" in pokemon: return "jangmoo"
|
||||
if "hakamo" in pokemon: return "hakamoo"
|
||||
if "kommo" in pokemon: return "kommoo"
|
||||
# Ho-oh
|
||||
if "ho_oh" in pokemon: return "hooh"
|
||||
# Unfezant, only male, because it's not named that on the sheet
|
||||
if "unfezant" in pokemon: return "unfezant"
|
||||
# Milotic
|
||||
if "milotic" in pokemon: return "milotic"
|
||||
# Pyroar
|
||||
if "pyroar" in pokemon: return "pyroar"
|
||||
# Porygon-Z
|
||||
if "porygon_z" in pokemon: return "porygonz"
|
||||
|
||||
return name
|
||||
|
||||
|
||||
# These Pokémon use their base form for implementation, but have their own row on the sheet
|
||||
use_default_name = {
|
||||
"weezing_galarian",
|
||||
"farfetchd_galarian"
|
||||
}
|
||||
|
||||
def check_implementation(status, name, dex, gen, model_status):
|
||||
debug(f" Checking Implementation for {name} ({sanitize_name(name)})")
|
||||
name = sanitize_name(name)
|
||||
dex = f"{dex:>04}"
|
||||
|
||||
# Check if a poser exists, if not, fallback to Kotlin and check that
|
||||
poser_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/{dex}_{normalize_name(name,'directory')}/{normalize_name(name,'poser')}.json")
|
||||
if poser_path: debug(f" Looking for \"{poser_path}\"")
|
||||
if os.path.isfile(poser_path):
|
||||
status["imp_implemented"] = True
|
||||
debug(" Found JSON poser")
|
||||
|
||||
# If not found, try a different, incorrectly named file
|
||||
else:
|
||||
poser_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/{dex}_{normalize_name(name,'directory')}/{incorrect_region_name(name)}.json")
|
||||
if poser_path: debug(f" Looking for a poser with an incorrect region name instead, \"{poser_path}\"")
|
||||
if os.path.isfile(poser_path):
|
||||
status["imp_implemented"] = True
|
||||
debug(" Found JSON poser")
|
||||
status["imp_unconventional_name"] = os.path.basename(poser_path)
|
||||
debug(" JSON poser is using an incorrect region name (eg. \"_alola\" instead of \"_alolan\")")
|
||||
else:
|
||||
debug(" Could not find any JSON poser")
|
||||
|
||||
# If still not found the poser, try searching for Kotlin
|
||||
if not status.get("imp_implemented"):
|
||||
kotlin_poser_name = name.title().replace("_", "")
|
||||
kotlin_poser_directory = os.path.join(script_dir, "../..", f"common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/pokemon/")
|
||||
debug(f" Searching for Kotlin poser \"{kotlin_poser_name}Model.kt\" in \"{kotlin_poser_directory}\"")
|
||||
|
||||
# Check if a Kotlin poser exists in all gen folders
|
||||
if os.path.exists(kotlin_poser_directory):
|
||||
for root, dirs, files in os.walk(kotlin_poser_directory):
|
||||
if f"{kotlin_poser_name}Model.kt" in files:
|
||||
# Set the new poser path
|
||||
poser_path = os.path.join(root, f"{kotlin_poser_name}Model.kt")
|
||||
# Debug
|
||||
debug(f" Found Kotlin poser in \"{poser_path}\"")
|
||||
break
|
||||
|
||||
# If poser exists, flag the proper status
|
||||
if os.path.isfile(poser_path):
|
||||
status["imp_implemented"] = True
|
||||
status["imp_using_kotlin_poser"] = True
|
||||
else:
|
||||
if model_status == "Implemented":
|
||||
status["sht_erroneous_implemented_flag"] = True
|
||||
debug(" Could not find any poser, not flagging the Pokémon as implemented")
|
||||
debug(" Pokémon is not implemented, but flagged as such on the assets sheet")
|
||||
return
|
||||
else:
|
||||
debug(" Could not find any poser, not flagging the Pokémon as implemented")
|
||||
|
||||
# Check implementation against the Models spreadsheet
|
||||
debug(" Checking if the implementation status on the Models spreadsheet matches...")
|
||||
if ("imp_implemented" in status) and (model_status != "Implemented"):
|
||||
if model_status == "Work in Progress": status["sht_erroneous_work_in_progress_flag"] = True
|
||||
elif model_status == "Not Started": status["sht_erroneous_not_started_flag"] = True
|
||||
elif model_status == "Needs Approval": status["sht_erroneous_needs_approval_flag"] = True
|
||||
elif model_status == "Needs Implementation": status["sht_erroneous_needs_implementation_flag"] = True
|
||||
elif model_status == "Needs Rework": status["sht_erroneous_needs_rework_flag"] = True
|
||||
elif model_status == "Needs Help": status["sht_erroneous_needs_help_flag"] = True
|
||||
debug(" Found paperwork mismatch")
|
||||
else: debug(" Implementation status and mod implementation match")
|
||||
|
||||
|
||||
def scan_animation(status, name, gen, dex):
|
||||
debug(" Scanning the animations")
|
||||
name = sanitize_name(name)
|
||||
dex = f"{dex:>04}"
|
||||
|
||||
if name in use_default_name: name = default_name(name)
|
||||
|
||||
ani_mod_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/{dex}_{normalize_name(name,'directory')}/{normalize_name(name,'animation')}.animation.json")
|
||||
ani_repo_path = os.path.join(assets_dir, f"blockbench/pokemon/gen{gen}/{dex}_{normalize_name(name,'directory')}/{normalize_name(name,'animation')}.animation.json")
|
||||
|
||||
# Check if files exist
|
||||
if not os.path.isfile(ani_mod_path):
|
||||
# Check if an incorrectly named animation exists first
|
||||
ani_mod_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/{dex}_{normalize_name(name,'directory')}/{incorrect_region_name(name)}.animation.json")
|
||||
if not os.path.isfile(ani_mod_path):
|
||||
status["ani_file_missing"] = True
|
||||
debug(f" Animation file does not exist, could not find \"{ani_mod_path}\"")
|
||||
return
|
||||
else:
|
||||
status["ani_unconventional_name"] = os.path.basename(ani_mod_path)
|
||||
debug(f" Could not find the correct animation, but found one using an unconventional name")
|
||||
|
||||
debug(f" Found \"{ani_mod_path}\"")
|
||||
|
||||
with open(ani_mod_path, "r") as file:
|
||||
ani_data = json.load(file)
|
||||
|
||||
# Check for issues with the cry animation
|
||||
if status.get("ani_unconventional_name", False): cry_animation = ani_data.get("animations", {}).get(f"animation.{incorrect_region_name(normalize_name(name,'animation'))}.cry")
|
||||
else: cry_animation = ani_data.get("animations", {}).get(f"animation.{normalize_name(name,'animation')}.cry")
|
||||
|
||||
if (not cry_animation) or cry_animation == None:
|
||||
status["ani_cry_animation_missing"] = True
|
||||
debug(f" Could not find the cry animation")
|
||||
else:
|
||||
debug(f" Scanning \"animation.{normalize_name(name,'animation')}.cry\" for potential issues with the cry")
|
||||
cry_animation_sound_effects = cry_animation.get("sound_effects")
|
||||
if (not cry_animation_sound_effects) or cry_animation_sound_effects == None:
|
||||
status["ani_cry_keyframe_missing"] = True
|
||||
debug(f" Could not find the cry keyframe")
|
||||
else:
|
||||
for key, value in cry_animation_sound_effects.items():
|
||||
if not isinstance(value, dict):
|
||||
debug(f" Omitting {key}")
|
||||
continue
|
||||
effect = value.get("effect")
|
||||
if (not effect) or effect == None:
|
||||
status["ani_cry_keyframe_missing"] = True
|
||||
debug(f" Could not find the cry keyframe in {cry_animation}")
|
||||
elif effect != f"pokemon.{normalize_name(name,'cry')}.cry":
|
||||
status["ani_cry_keyframe_invalid"] = effect
|
||||
debug(f" Found an incorrectly named cry keyframe ({effect})")
|
||||
else: debug(f" Found no issues with the cry implementation")
|
||||
|
||||
# Check for disparity with repository assets
|
||||
debug(f" Checking parity with the assets repository")
|
||||
if not os.path.isfile(ani_repo_path):
|
||||
debug(f" Could not find \"{ani_repo_path}")
|
||||
else:
|
||||
with open(ani_repo_path, "r") as file:
|
||||
repo_data = json.load(file)
|
||||
if ani_data != repo_data:
|
||||
status["ani_file_repo_disparity"] = True
|
||||
debug(f" Animations files do not match")
|
||||
else: debug(f" Animation files are identical")
|
||||
|
||||
def scan_poser(status, name, gen, dex):
|
||||
debug(" Scanning the poser")
|
||||
name = sanitize_name(name)
|
||||
dex = f"{dex:>04}"
|
||||
|
||||
# Return if no JSON poser exists
|
||||
if status.get("imp_using_kotlin_poser", False):
|
||||
debug(f" Cannot check a Kotlin poser")
|
||||
return
|
||||
|
||||
# Get the poser
|
||||
imp_mod_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/bedrock/pokemon/posers/{dex}_{normalize_name(name,'directory')}/{normalize_name(name,'poser')}.json")
|
||||
if not os.path.isfile(imp_mod_path):
|
||||
debug(f" Poser file does not exist, could not find \"{imp_mod_path}\"")
|
||||
return
|
||||
else: debug(f" Found \"{imp_mod_path}\"")
|
||||
|
||||
# Get the animation
|
||||
ani_mod_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/{dex}_{normalize_name(name,'directory')}/{normalize_name(name,'animation')}.animation.json")
|
||||
if not os.path.isfile(ani_mod_path):
|
||||
debug(f" Animation file does not exist, could not find \"{ani_mod_path}\"")
|
||||
return
|
||||
else:
|
||||
debug(f" Found \"{ani_mod_path}\"")
|
||||
|
||||
# Check for special animation implementation
|
||||
possible_targets = ["faint", "cry", "recoil", "physical", "special", "air_physical", "air_special", "status"]
|
||||
with open(ani_mod_path, "r", encoding="utf-8") as file:
|
||||
anim_data = json.load(file)
|
||||
existing_animations = anim_data.get("animations", {}).keys()
|
||||
targets = [t for t in possible_targets if f"animation.{name}.{t}" in existing_animations]
|
||||
if targets:
|
||||
debug(f" Found animations {targets} and checking if they are implemented in the poser...")
|
||||
with open(imp_mod_path, "r", encoding="utf-8") as file:
|
||||
imp_data = json.load(file)
|
||||
animations = imp_data.get("animations", {}).keys()
|
||||
missing_animations = [a for a in targets if a not in animations]
|
||||
if missing_animations:
|
||||
status["imp_missing_animations"] = missing_animations
|
||||
debug(f" Could not find {missing_animations} in the poser")
|
||||
else:
|
||||
debug(f" All aforementioned animations are implemented in the poser")
|
||||
|
||||
def scan_sound(status, name, gen, dex):
|
||||
debug(" Scanning audio stuff")
|
||||
name = sanitize_name(name)
|
||||
dex = f"{dex:>04}"
|
||||
aud_mod_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/sounds/pokemon/{normalize_name(name,'directory')}/{normalize_name(name,'cry')}_cry.ogg")
|
||||
aud_repo_path = os.path.join(assets_dir, f"sounds/pokemon/gen{gen}/{dex}_{normalize_name(name,'directory')}/{normalize_name(name,'cry')}_cry.ogg")
|
||||
|
||||
# Check if files exist
|
||||
debug(f" Searching for cry audio files...")
|
||||
if not os.path.isfile(aud_mod_path):
|
||||
debug(f" Could not find mod's audio file in \"{aud_mod_path}\"")
|
||||
status["aud_cry_file_missing"] = True
|
||||
no_mod_cry_very_sad = True
|
||||
else:
|
||||
debug(f" Found cry in the mod in \"{aud_mod_path}\"")
|
||||
no_mod_cry_very_sad = False
|
||||
if not os.path.isfile(aud_repo_path):
|
||||
debug(f" Could not find repository's audio file \"{aud_repo_path}\"")
|
||||
no_repo_cry_very_sad = True
|
||||
else:
|
||||
debug(f" Found cry on the repository in \"{aud_repo_path}\"")
|
||||
no_repo_cry_very_sad = False
|
||||
|
||||
# If cries exist, check if the files are the same
|
||||
if not (no_mod_cry_very_sad or no_repo_cry_very_sad):
|
||||
# Read the audio data and sample rate
|
||||
mod_audio_data, mod_audio_samplerate = sf.read(aud_mod_path)
|
||||
repo_audio_data, repo_audio_samplerate = sf.read(aud_repo_path)
|
||||
|
||||
# Check if sample rates match
|
||||
if mod_audio_samplerate != repo_audio_samplerate:
|
||||
status["aud_cry_file_disparity"] = True
|
||||
debug(f" Sample rate of audio files does not match")
|
||||
else:
|
||||
debug(f" Sample rate of audio files matches")
|
||||
|
||||
# Check if the Pokémon is in the sounds.json
|
||||
sounds_json_path = os.path.join(script_dir, "../..", f"common/src/main/resources/assets/cobblemon/sounds.json")
|
||||
if not os.path.isfile(sounds_json_path):
|
||||
debug(f" Somehow did not find the sounds.json idk how Sterr can mess up a script this bad")
|
||||
else:
|
||||
with open(sounds_json_path) as file:
|
||||
sounds_json_data = json.load(file)
|
||||
sound_event = f"pokemon.{normalize_name(name,'cry')}.cry"
|
||||
if sound_event in sounds_json_data:
|
||||
debug(f" Found \"{sound_event}\" in sounds.json")
|
||||
else:
|
||||
# Check if an incorrectly named file exists instead
|
||||
unconventional_sound_event = f"pokemon.{incorrect_region_name(name)}.cry"
|
||||
if unconventional_sound_event in sounds_json_data:
|
||||
debug(f" Found \"{unconventional_sound_event}\" in sounds.json")
|
||||
status["aud_cry_sound_event_unconventional_name"] = os.path.basename(unconventional_sound_event)
|
||||
else:
|
||||
status["aud_cry_sound_event_missing"] = True
|
||||
debug(f" Could not find \"{sound_event}\" in sounds.json")
|
||||
|
||||
|
||||
|
||||
|
||||
exclude_from_checks = {
|
||||
"Substitute": "(Always skipped)",
|
||||
"Unown": "(Ignore this! Only Unown with letters are checked and implemented)",
|
||||
"Steelix": "(Specific check is a work in progress)",
|
||||
"Frillish": "(Specific check is a work in progress)",
|
||||
"Jellicent": "(Specific check is a work in progress)",
|
||||
"Rotom": "(Specific check is a work in progress)",
|
||||
"Mime Jr.": "(Specific check is a work in progress)",
|
||||
"Gastrodon": "(Specific check is a work in progress)",
|
||||
"Basculin": "(Specific check is a work in progress)",
|
||||
"Darmanitan [Zen]": "(Specific check is a work in progress)",
|
||||
"Wishiwashi": "(Specific check is a work in progress)",
|
||||
"Aegislash": "(Specific check is a work in progress)",
|
||||
"Clauncher": "(Specific check is a work in progress)",
|
||||
"Clawitzer": "(Specific check is a work in progress)",
|
||||
"Eiscue": "(Specific check is a work in progress)",
|
||||
"Palafin": "(Specific check is a work in progress)",
|
||||
"Dudunsparce": "(Specific check is a work in progress)",
|
||||
"Gimmighoul": "(Specific check is a work in progress)"
|
||||
}
|
||||
|
||||
# These are Pokémon whose gender difference implementation separated their poser
|
||||
# Does not include Pokémon already split into [Male] and [Female] on the assets sheet
|
||||
gender_variant_pokemon ={
|
||||
"Kadabra",
|
||||
"Alakazam",
|
||||
"Milotic", # (Male is missing from the poser name, there is only "milotic" and "milotic_female")
|
||||
"Pyroar",
|
||||
"Indeedee",
|
||||
"Oinkologne"
|
||||
}
|
||||
|
||||
# Exclude specific keywords, only Mega and G-Max for now
|
||||
exclude_keywords = {
|
||||
"Mega",
|
||||
"G-Max"
|
||||
}
|
||||
|
||||
## Main loop
|
||||
for i, row in df_implementation.iterrows():
|
||||
# Skip first three rows
|
||||
if i < 3: continue
|
||||
|
||||
debug(f"Checking {row['Name']}...")
|
||||
|
||||
# Skip keywords
|
||||
for k in exclude_keywords:
|
||||
if re.search(rf"\b{k}\b", (df_implementation.loc[i, "Name"])):
|
||||
debug(f" Keyword \"{k}\" is excluded, skipping!\n")
|
||||
skip = True
|
||||
break
|
||||
else: skip = False
|
||||
if skip: continue
|
||||
|
||||
# Skip if Pokémon is excluded
|
||||
if df_implementation.loc[i, "Name"] in exclude_from_checks:
|
||||
debug(f" {row['Name']} is excluded, skipping!\n")
|
||||
continue
|
||||
for c in exclude_from_checks:
|
||||
if re.search(rf"\b{c}\b", (df_implementation.loc[i, "Name"])):
|
||||
debug(f" Pokémon \"{c}\" is excluded, skipping!\n")
|
||||
skip = True
|
||||
break
|
||||
else: skip = False
|
||||
if skip: continue
|
||||
|
||||
## Set up variables
|
||||
# Gen
|
||||
gen = df_implementation.loc[i,"Gen"]
|
||||
# Dex
|
||||
dex = df_implementation.loc[i,"Dex"]
|
||||
# Name
|
||||
name = df_implementation.loc[i,"Name"]
|
||||
|
||||
# Split the checks if Pokémon has variants with separate implementation
|
||||
if name in gender_variant_pokemon:
|
||||
debug(f" {name} will be split into \"{name} [Male]\" and \"{name} [Female]\"")
|
||||
variants = [f"{name} [Male]", f"{name} [Female]"]
|
||||
else:
|
||||
variants = [name]
|
||||
|
||||
for variant in variants:
|
||||
name = variant
|
||||
|
||||
# Clear error flags
|
||||
try: status.clear()
|
||||
except: status = {}
|
||||
status = {}
|
||||
|
||||
# Check implementation status, skip if unimplemented in the mod
|
||||
check_implementation(status, name, dex, gen, df_models.loc[i,"Model Status"])
|
||||
|
||||
if not status.get("imp_implemented", True):
|
||||
debug(" Pokémon is not implemented in the mod, skipping!")
|
||||
continue
|
||||
|
||||
# Cry sound status
|
||||
if (df_cries.loc[i, "Cry"] == "Approved"): cry_available = True
|
||||
else: cry_available = False
|
||||
|
||||
# Scan for issues
|
||||
if "imp_implemented" in status:
|
||||
scan_poser(status, name, gen, dex)
|
||||
scan_animation(status, name, gen, dex)
|
||||
scan_sound(status, name, gen, dex)
|
||||
|
||||
# Delete non-issues when done
|
||||
del status["imp_implemented"]
|
||||
|
||||
# Exclude checks from basic mode
|
||||
if mode == "basic":
|
||||
if not cry_available:
|
||||
if "ani_cry_animation_missing" in status: del status["ani_cry_animation_missing"]
|
||||
if "ani_cry_keyframe_missing" in status: del status["ani_cry_keyframe_missing"]
|
||||
if "ani_cry_keyframe_invalid" in status: del status["ani_cry_keyframe_invalid"]
|
||||
if "aud_cry_file_disparity" in status: del status["aud_cry_file_disparity"]
|
||||
if "aud_cry_file_missing" in status: del status["aud_cry_file_missing"]
|
||||
if "aud_cry_sound_event_missing" in status: del status["aud_cry_sound_event_missing"]
|
||||
|
||||
if "sht_erroneous_work_in_progress_flag" in status: del status["sht_erroneous_work_in_progress_flag"]
|
||||
if "sht_erroneous_not_started_flag" in status: del status["sht_erroneous_not_started_flag"]
|
||||
if "sht_erroneous_needs_approval_flag" in status: del status["sht_erroneous_needs_approval_flag"]
|
||||
if "sht_erroneous_needs_implementation_flag" in status: del status["sht_erroneous_needs_implementation_flag"]
|
||||
if "sht_erroneous_needs_rework_flag" in status: del status["sht_erroneous_needs_rework_flag"]
|
||||
if "sht_erroneous_needs_help_flag" in status: del status["sht_erroneous_needs_help_flag"]
|
||||
|
||||
if "imp_unconventional_name" in status: del status["imp_unconventional_name"]
|
||||
if "imp_using_kotlin_poser" in status: del status["imp_using_kotlin_poser"]
|
||||
|
||||
if "ani_file_repo_disparity" in status: del status["ani_file_repo_disparity"]
|
||||
|
||||
if "ani_unconventional_name" in status: del status["ani_unconventional_name"]
|
||||
if "aud_cry_sound_event_unconventional_name" in status: del status["aud_cry_sound_event_unconventional_name"]
|
||||
|
||||
|
||||
# Print out issues
|
||||
if any(status.values()):
|
||||
print_color("white")
|
||||
print(f"✘ {name} has the following issues:")
|
||||
if len(variants)>1: print(f"(The male/female split is present only in implementation, not on the spreadsheet)")
|
||||
|
||||
name = sanitize_name(name)
|
||||
|
||||
# Sheet Issues
|
||||
print_color("red")
|
||||
if status.get("sht_erroneous_implemented_flag", False): print(f" ► Pokémon is not implemented, but flagged as such on the Models sheet")
|
||||
if status.get("sht_erroneous_work_in_progress_flag", False): print(f" ► Flagged as Work in Progress on the Models sheet, while implemented in the mod")
|
||||
if status.get("sht_erroneous_not_started_flag", False): print(f" ► Flagged as Not Started on the Models sheet, while implemented in the mod")
|
||||
if status.get("sht_erroneous_needs_approval_flag", False): print(f" ► Flagged as Needs Approval on the Models sheet, while implemented in the mod")
|
||||
if status.get("sht_erroneous_needs_implementation_flag", False): print(f" ► Flagged as Needs Implementation on the Models sheet, while implemented in the mod")
|
||||
if status.get("sht_erroneous_needs_rework_flag", False): print(f" ► Flagged as Needs Rework on the Models sheet, while implemented in the mod")
|
||||
if status.get("sht_erroneous_needs_help_flag", False): print(f" ► Flagged as Needs Help on the Models sheet, while implemented in the mod")
|
||||
if status.get("sht_erroneous_implemented_flag", False) or status.get("sht_erroneous_work_in_progress_flag", False) or status.get("sht_erroneous_not_started_flag", False) or status.get("sht_erroneous_needs_approval_flag", False) or status.get("sht_erroneous_needs_implementation_flag", False) or status.get("sht_erroneous_needs_rework_flag", False) or status.get("sht_erroneous_needs_help_flag", False):
|
||||
print(f" ▲ Check notes and ask approvers, before changing anything on the Models sheet")
|
||||
|
||||
# Implementation Issues
|
||||
print_color("green")
|
||||
if status.get("imp_unconventional_name", False):
|
||||
print(f" ► Poser is using an unconventional name \"{status['imp_unconventional_name']}\"")
|
||||
region_name = status["imp_unconventional_name"]
|
||||
if "_galar" in region_name: print(f" ▲ The convention is to use \"_galarian\" instead of \"_galar\"")
|
||||
if "_alola" in region_name: print(f" ▲ The convention is to use \"_alolan\" instead of \"_alola\"")
|
||||
if "_hisui" in region_name: print(f" ▲ The convention is to use \"_hisuian\" instead of \"_hisui\"")
|
||||
if "_paldea" in region_name: print(f" ▲ The convention is to use \"_paldean\" instead of \"_paldea\"")
|
||||
|
||||
if status.get("imp_using_kotlin_poser", False): print(f" ► Still using a Kotlin poser, the script cannot check for poser issues")
|
||||
if status.get("imp_missing_animations", False): print(f" ► Found animations in \"{normalize_name(name,'animation')}.animation.json\" that are not implemented in the poser: {status['imp_missing_animations']}")
|
||||
|
||||
# Animation Issues
|
||||
print_color("cyan")
|
||||
if status.get("ani_unconventional_name", False):
|
||||
print(f" ► Animation is using an unconventional name \"{status['ani_unconventional_name']}\"")
|
||||
region_name = status["ani_unconventional_name"]
|
||||
if "_galar" in region_name: print(f" ▲ The convention is to use \"_galarian\" instead of \"_galar\"")
|
||||
elif "_alola" in region_name: print(f" ▲ The convention is to use \"_alolan\" instead of \"_alola\"")
|
||||
elif "_hisui" in region_name: print(f" ▲ The convention is to use \"_hisuian\" instead of \"_hisui\"")
|
||||
elif "_paldea" in region_name: print(f" ▲ The convention is to use \"_paldean\" instead of \"_paldea\"")
|
||||
else: print(f" ▲ Whatever triggered this is likely incorrect, report this pls")
|
||||
|
||||
|
||||
if status.get("ani_file_missing", False): print(f" ► Animation file does not exist, could not find \"pokemon/animations/{dex:>04}_{normalize_name(name,'directory')}/{normalize_name(name,'animation')}.animation.json\"")
|
||||
if status.get("ani_file_repo_disparity", False): print(f" ► Animation file \"{sanitize_name(normalize_name(name, 'animation'))}.animation.json\" does not match the Assets Repository")
|
||||
|
||||
if status.get("ani_cry_animation_missing", False): print(f" ► Cry animation is missing, could not find \"animation.{normalize_name(name,'animation')}.cry\"")
|
||||
if status.get("ani_cry_keyframe_missing", False): print(f" ► Cry keyframe is missing in \"animation.{normalize_name(name,'animation')}.cry\"")
|
||||
if status.get("ani_cry_keyframe_invalid", False): print(f" ► Cry keyframe in \"animation.{normalize_name(name,'animation')}.cry\" has an incorrect name, expected \"pokemon.{normalize_name(name,'animation')}.cry\" and found \"{status['ani_cry_keyframe_invalid']}\"")
|
||||
if (status.get("ani_cry_animation_missing", False) or status.get("ani_cry_keyframe_missing", False)) and not cry_available:
|
||||
print(f" ▲ Cry sound doesn't exist, but a cry animation should always be implemented at least as a placeholder")
|
||||
|
||||
# Audio issues
|
||||
print_color("magenta")
|
||||
if status.get("aud_cry_sound_event_unconventional_name", False):
|
||||
print(f" ► Sound event is using an unconventional name \"{status['aud_cry_sound_event_unconventional_name']}\"")
|
||||
region_name = status["aud_cry_sound_event_unconventional_name"]
|
||||
if "_galar" in region_name: print(f" ▲ The convention is to use \"_galarian\" instead of \"_galar\"")
|
||||
if "_alola" in region_name: print(f" ▲ The convention is to use \"_alolan\" instead of \"_alola\"")
|
||||
if "_hisui" in region_name: print(f" ▲ The convention is to use \"_hisuian\" instead of \"_hisui\"")
|
||||
if "_paldea" in region_name: print(f" ▲ The convention is to use \"_paldean\" instead of \"_paldea\"")
|
||||
|
||||
if status.get("aud_cry_sound_event_missing", False): print(f" ► Could not find a sound event \"pokemon.{normalize_name(name,'cry')}.cry\" in sounds.json")
|
||||
if status.get("aud_cry_file_disparity", False): print(f" ► Replace \"{normalize_name(name,'cry')}_cry.ogg\" with the updated file from the asset repository")
|
||||
if status.get("aud_cry_file_missing", False): print(f" ► Cry sound is missing, at least an empty \"{normalize_name(name,'cry')}_cry.ogg\" is needed")
|
||||
|
||||
print_color("reset")
|
||||
if not log_debug: print("\n")
|
||||
if log_debug: print("\n")
|
||||
|
||||
|
||||
|
||||
# We done
|
||||
print("\nWe done!\n")
|
||||
print("The following Pokémon are excluded from this script and must be checked manually:")
|
||||
for c, v in exclude_from_checks.items():
|
||||
print(f"► {c} {v}")
|
||||
print("\nPress [Esc] to exit")
|
||||
while True:
|
||||
input = getch()
|
||||
if input == b'\x1b':
|
||||
quit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user