Files
cobblemon/utilityscripts/sync_alpha_textures.py
T

333 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Copies alpha eye overlay textures (*_alpha.png) from the cobblemon-assets repo
into the correct locations in the cobblemon textures directory.
Can use either:
- A local clone of cobblemon-assets (fast, file copies)
- GitLab API downloads (slower, no clone needed)
Destination: common/src/main/resources/assets/cobblemon/textures/pokemon/NNNN_species/
Safe to re-run. Will prompt for overwrite behavior on startup.
"""
import os
import sys
import shutil
import urllib.request
import json
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent
TEXTURES_DIR = REPO_ROOT / "common/src/main/resources/assets/cobblemon/textures/pokemon"
GITLAB_PROJECT = "cable-mc%2Fcobblemon-assets"
GITLAB_BRANCH = "master"
GITLAB_RAW_BASE = f"https://gitlab.com/api/v4/projects/{GITLAB_PROJECT}/repository/files"
GITLAB_TREE_BASE = f"https://gitlab.com/api/v4/projects/{GITLAB_PROJECT}/repository/tree"
ASSETS_POKEMON_SUBPATH = "blockbench/pokemon"
# ---------------------------------------------------------------------------
# Prompts
# ---------------------------------------------------------------------------
def prompt_assets_root() -> Path | None:
"""Ask user for local assets clone path. Returns None if blank/invalid."""
print("=" * 60)
print("cobblemon-assets source")
print("=" * 60)
response = input(
"Enter path to your cobblemon-assets clone\n"
"(leave blank to download via GitLab API): "
).strip()
if not response:
return None
path = Path(response)
if not path.exists():
print(f" WARN: Path does not exist: {path}")
print(" Falling back to GitLab API downloads.")
return None
pokemon_path = path / ASSETS_POKEMON_SUBPATH
if not pokemon_path.exists():
print(f" WARN: Expected '{ASSETS_POKEMON_SUBPATH}' not found under {path}")
print(" Is this the correct cobblemon-assets root?")
confirm = input(" Continue anyway? (y/n): ").strip().lower()
if confirm != "y":
print(" Falling back to GitLab API downloads.")
return None
return path
def prompt_overwrite() -> bool:
"""Ask user whether to overwrite existing files."""
print()
print("=" * 60)
print("Overwrite existing files?")
print("=" * 60)
while True:
response = input("Overwrite existing alpha textures? (y/n): ").strip().lower()
if response in ("y", "yes"):
return True
if response in ("n", "no"):
return False
print(" Please enter y or n.")
# ---------------------------------------------------------------------------
# Local file discovery
# ---------------------------------------------------------------------------
def find_alpha_textures_local(assets_root: Path) -> list[Path]:
"""Find all *_alpha.png files under blockbench/pokemon/ in the local clone."""
pokemon_dir = assets_root / ASSETS_POKEMON_SUBPATH
if not pokemon_dir.exists():
print(f"ERROR: {pokemon_dir} does not exist.")
sys.exit(1)
results = list(pokemon_dir.rglob("*_alpha.png"))
print(f" Found {len(results)} alpha textures in local clone.")
return results
# ---------------------------------------------------------------------------
# GitLab API discovery + download
# ---------------------------------------------------------------------------
def gitlab_list_alpha_files() -> list[str]:
"""
List all *_alpha.png paths under blockbench/pokemon/ via GitLab API.
Handles pagination automatically.
"""
print(" Querying GitLab API for alpha textures (this may take a moment)...")
all_files = []
page = 1
while True:
url = (
f"{GITLAB_TREE_BASE}"
f"?path={ASSETS_POKEMON_SUBPATH}"
f"&recursive=true"
f"&ref={GITLAB_BRANCH}"
f"&per_page=100"
f"&page={page}"
)
try:
with urllib.request.urlopen(url) as r:
data = json.load(r)
except Exception as e:
print(f" ERROR querying GitLab API: {e}")
sys.exit(1)
if not data:
break
for item in data:
if item.get("type") == "blob" and item["path"].endswith("_alpha.png"):
all_files.append(item["path"])
page += 1
print(f" Found {len(all_files)} alpha textures via API.")
return all_files
def gitlab_download_file(repo_path: str, dest: Path):
"""Download a single file from GitLab raw API."""
encoded_path = repo_path.replace("/", "%2F")
url = f"{GITLAB_RAW_BASE}/{encoded_path}/raw?ref={GITLAB_BRANCH}"
try:
with urllib.request.urlopen(url) as r:
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(r.read())
except Exception as e:
raise RuntimeError(f"Failed to download {repo_path}: {e}")
# ---------------------------------------------------------------------------
# Destination matching
# ---------------------------------------------------------------------------
def build_folder_map() -> dict[str, Path]:
"""Build a lookup of normalized_species_name -> folder path from TEXTURES_DIR."""
folder_map = {}
for folder in TEXTURES_DIR.iterdir():
if not folder.is_dir():
continue
parts = folder.name.split("_", 1)
if len(parts) == 2:
# Store both the raw name and a normalized version (no hyphens/underscores)
# so ho_oh -> hooh, mr_mime -> mrmime etc. can match 0250_hooh, 0122_mrmime
raw_name = parts[1]
folder_map[raw_name] = folder
normalized = raw_name.replace("_", "").replace("-", "")
if normalized != raw_name:
folder_map[normalized] = folder
return folder_map
def normalize(name: str) -> str:
"""Strip hyphens and underscores for loose matching."""
return name.replace("_", "").replace("-", "")
def find_dest_folder(filename_stem: str, folder_map: dict[str, Path]) -> Path | None:
"""
Given a stem like 'charizard_alpha', find the matching NNNN_species folder.
Tries the full species name first, then progressively strips trailing
segments until a match is found. Each attempt is tried both raw and
normalized (hyphens/underscores stripped) to handle cases like:
ho_oh_alpha -> try ho_oh, then hooh -> matches 0250_hooh
mr_mime_alpha -> try mr_mime, then mrmime -> matches 0122_mrmime
porygon-z_alpha -> try porygon-z, then porygonz -> matches NNNN_porygonz
zorua_hisuian_alpha -> try zorua_hisuian, then zorua -> matches 0570_zorua
"""
if not filename_stem.endswith("_alpha"):
return None
full_species_name = filename_stem[:-len("_alpha")]
segments = full_species_name.split("_")
for i in range(len(segments), 0, -1):
candidate = "_".join(segments[:i])
# Try raw match first
if candidate in folder_map:
return folder_map[candidate]
# Try normalized match
normalized = normalize(candidate)
if normalized in folder_map:
return folder_map[normalized]
return None
# ---------------------------------------------------------------------------
# Main processing
# ---------------------------------------------------------------------------
def process_local(assets_root: Path, overwrite: bool):
alpha_files = find_alpha_textures_local(assets_root)
folder_map = build_folder_map()
stats = {"copied": 0, "skipped_exists": 0, "skipped_no_dest": 0, "errors": 0}
no_dest = []
for src_path in sorted(alpha_files):
stem = src_path.stem
filename = src_path.name
dest_folder = find_dest_folder(stem, folder_map)
if dest_folder is None:
no_dest.append(stem)
stats["skipped_no_dest"] += 1
continue
dest_file = dest_folder / filename
if dest_file.exists() and not overwrite:
stats["skipped_exists"] += 1
continue
try:
shutil.copy2(src_path, dest_file)
stats["copied"] += 1
print(f" COPIED: {filename} -> {dest_folder.name}/")
except OSError as e:
print(f" ERROR copying {filename}: {e}")
stats["errors"] += 1
print_summary(stats, no_dest)
def process_api(overwrite: bool):
alpha_paths = gitlab_list_alpha_files()
folder_map = build_folder_map()
stats = {"copied": 0, "skipped_exists": 0, "skipped_no_dest": 0, "errors": 0}
no_dest = []
for repo_path in sorted(alpha_paths):
filename = Path(repo_path).name
stem = Path(repo_path).stem
dest_folder = find_dest_folder(stem, folder_map)
if dest_folder is None:
no_dest.append(stem)
stats["skipped_no_dest"] += 1
continue
dest_file = dest_folder / filename
if dest_file.exists() and not overwrite:
stats["skipped_exists"] += 1
continue
try:
gitlab_download_file(repo_path, dest_file)
stats["copied"] += 1
print(f" DOWNLOADED: {filename} -> {dest_folder.name}/")
except RuntimeError as e:
print(f" ERROR: {e}")
stats["errors"] += 1
print_summary(stats, no_dest)
def print_summary(stats: dict, no_dest: list):
print()
print("=" * 60)
print("SUMMARY")
print("=" * 60)
print(f" Copied/downloaded : {stats['copied']}")
print(f" Skipped (exists) : {stats['skipped_exists']}")
print(f" No matching dest : {stats['skipped_no_dest']}")
print(f" Errors : {stats['errors']}")
if no_dest:
print()
print(f"No matching texture folder for ({len(no_dest)}):")
for name in sorted(no_dest):
print(f" - {name}")
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main():
print()
print("Alpha Eye Texture Sync")
print()
if not TEXTURES_DIR.exists():
print(f"ERROR: Cobblemon textures directory not found: {TEXTURES_DIR}")
print("Is this script in the cobblemon/utilityscripts/ directory?")
sys.exit(1)
assets_root = prompt_assets_root()
overwrite = prompt_overwrite()
print()
print("=" * 60)
print("Processing")
print("=" * 60)
if assets_root is not None:
print(f" Mode: local clone ({assets_root})")
process_local(assets_root, overwrite)
else:
print(" Mode: GitLab API")
process_api(overwrite)
if __name__ == "__main__":
main()