Last active
September 5, 2024 17:11
-
-
Save UserUnknownFactor/c8e87ac3f70a30cfafb38fe56c7186cf to your computer and use it in GitHub Desktop.
Python tool to strip unused assets (images/audio) form RPG Maker MV/MZ games.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json, io, logging, re, sys | |
from pathlib import Path | |
from enum import Enum | |
# Logging setup | |
logger = logging.getLogger(__name__) | |
logger.setLevel(logging.INFO) | |
handler = logging.StreamHandler(sys.stdout) | |
handler.setFormatter(logging.Formatter('%(message)s')) | |
logger.addHandler(handler) | |
# Define enums for resource types | |
class ResourceTypeImage(Enum): | |
ANIMATIONS = 'animations' | |
BATTLEBACK1 = 'battlebacks1' | |
BATTLEBACK2 = 'battlebacks2' | |
CHARACTERS = 'characters' | |
ENEMIES = 'enemies' | |
FACES = 'faces' | |
PARALLAX = 'parallaxes' | |
PICTURES = 'pictures' | |
SV_ACTORS = 'sv_actors' | |
SV_ENEMIES = 'sv_enemies' | |
TILESETS = 'tilesets' | |
TITLES1 = 'titles1' | |
TITLES2 = 'titles2' | |
class ResourceTypeAudio(Enum): | |
BGM = 'bgm' | |
BGS = 'bgs' | |
ME = 'me' | |
SE = 'se' | |
class SetEncoder(json.JSONEncoder): | |
def default(self, obj): | |
if isinstance(obj, set): | |
return dict(__set=list(obj)) | |
else: | |
return json.JSONEncoder.default(self, obj) | |
@staticmethod | |
def as_set(dct): | |
""" decoded = json.loads(encoded, object_hook=json_as_python_set) | |
json set: {'__set': [1,2,3]} to set([1,2,3]) | |
""" | |
if '__set' in dct: | |
return set(dct['__set']) | |
return dct | |
# Global dictionaries to store resource usage | |
tileset_map = {} | |
animation_map = {} | |
image_keep_map = {resource_type.value: set() for resource_type in ResourceTypeImage} | |
audio_keep_map = {resource_type.value: set() for resource_type in ResourceTypeAudio} | |
def register_image_keep(resource_name, resource_type): | |
logger.debug(f"Inserting {resource_name} {resource_type}...") | |
if resource_name: | |
image_keep_map[resource_type.value].add(resource_name) | |
def register_audio_keep(resource_name, resource_type): | |
logger.debug(f"Inserting {resource_name} {resource_type}...") | |
if resource_name: | |
audio_keep_map[resource_type.value].add(resource_name) | |
def parse_tileset_map(tilesets): | |
logger.debug("Parsing tilesets...") | |
for tileset in tilesets: | |
if not tileset: continue | |
for map_name in tileset["tilesetNames"]: | |
if map_name not in tileset_map.get(tileset["id"], []): | |
tileset_map.setdefault(tileset["id"], []).append(map_name) | |
def parse_animations(data): | |
logger.debug("Parsing animations...") | |
for anim in data: | |
if anim: | |
register_animation(anim['id'], anim['animation1Name']) | |
register_animation(anim['id'], anim['animation2Name']) | |
for item in anim['timings']: | |
if item['se']: | |
register_animation(anim['id'], item['se']['name'], 'se') | |
def register_animation(index, animation_name, anim_type='img'): | |
if animation_name: | |
animation_map.setdefault(index, {'img': [], 'se': []})[anim_type].append(animation_name) | |
# Function to parse commands and register resource usage | |
def parse_command(command, check_scripts): | |
code = command["code"] | |
parameters = command["parameters"] | |
if code == 245: # Play BGS | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.BGS) | |
elif code == 241: # Play BGM | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.BGM) | |
elif code == 249: # Play ME | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.ME) | |
elif code == 250: # Play SE | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.SE) | |
elif code == 132: # Change Battle BGM | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.BGM) | |
elif code == 133: # Change Victory ME | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.ME) | |
elif code == 139: # Change Defeat ME | |
register_audio_keep(parameters[0]["name"], ResourceTypeAudio.ME) | |
elif code == 140: # Change Vehicle BGM | |
register_audio_keep(parameters[1]["name"], ResourceTypeAudio.BGM) | |
elif code == 323: # Vehicle Image Change | |
register_image_keep(parameters[1], ResourceTypeImage.CHARACTERS) | |
elif code == 322: # Character Image Change | |
register_image_keep(parameters[1], ResourceTypeImage.FACES) | |
register_image_keep(parameters[3], ResourceTypeImage.CHARACTERS) | |
register_image_keep(parameters[5], ResourceTypeImage.SV_ACTORS) | |
elif code == 284: # Parallax Change | |
register_image_keep(parameters[0], ResourceTypeImage.PARALLAX) | |
elif code == 283: # Battleback Change | |
register_image_keep(parameters[0], ResourceTypeImage.BATTLEBACK1) | |
register_image_keep(parameters[1], ResourceTypeImage.BATTLEBACK2) | |
elif code == 231: # Show Picture | |
if parameters[1] == "test": | |
pass | |
register_image_keep(parameters[1], ResourceTypeImage.PICTURES) | |
elif code == 282: # Change Tileset | |
tileset_id = parameters[0] | |
if tileset_id in tileset_map: | |
for key in tileset_map[tileset_id]: | |
register_image_keep(key, ResourceTypeImage.TILESETS) | |
elif code in (337, 212): # Show Battle Animation / Show Animation | |
anim_id = parameters[2] if code == 337 else parameters[1] | |
if anim_id in animation_map: | |
for file_se in animation_map[anim_id]['se']: | |
register_audio_keep(file_se, ResourceTypeAudio.SE) | |
for file_img in animation_map[anim_id]['img']: | |
register_image_keep(file_img, ResourceTypeImage.ANIMATIONS) | |
elif check_scripts and code in (356, 355): # MV Script | |
result = re.search(r"([\"\'])((?:\\\1|.)*?)\1", parameters[0]) | |
if result: | |
result = result.group(2) | |
register_image_keep(result, ResourceTypeImage.PICTURES) | |
register_audio_keep(result, ResourceTypeAudio.BGS) | |
register_audio_keep(result, ResourceTypeAudio.SE) | |
elif check_scripts and code == 357: # MZ Script | |
if len(parameters) >= 4: | |
pass # TODO: needs a specific parameters for each | |
def parse_events(data, check_scripts=False): | |
logger.debug(f"Parsing common events...") | |
for event in data['events']: | |
if not event: continue | |
for page in event['pages']: | |
image_char_index = page['image'].get('characterName', None) | |
if image_char_index: | |
register_image_keep(image_char_index, ResourceTypeImage.CHARACTERS) | |
for command in page['list']: | |
parse_command(command, check_scripts) | |
def parse_common_events(data, check_scripts=False): | |
logger.debug("Parsing common events...") | |
for c_event in data: | |
if not c_event: continue | |
[parse_command(command, check_scripts) for command in c_event['list']] | |
def parse_map(data): | |
register_image_keep(data["battleback1Name"], ResourceTypeImage.BATTLEBACK1) | |
register_image_keep(data["battleback2Name"], ResourceTypeImage.BATTLEBACK2) | |
register_image_keep(data["parallaxName"], ResourceTypeImage.PARALLAX) | |
register_audio_keep(data["bgs"]["name"], ResourceTypeAudio.BGS) | |
register_audio_keep(data["bgm"]["name"], ResourceTypeAudio.BGM) | |
tileset_id = data["tilesetId"] | |
if tileset_id in tileset_map: | |
for key in tileset_map[tileset_id]: | |
register_image_keep(key, ResourceTypeImage.TILESETS) | |
def parse_system(data): | |
logger.debug("Parsing system...") | |
register_image_keep(data['battleback1Name'], ResourceTypeImage.BATTLEBACK1) | |
register_image_keep(data['battleback2Name'], ResourceTypeImage.BATTLEBACK2) | |
register_image_keep(data['battlerName'], ResourceTypeImage.SV_ACTORS) | |
register_image_keep(data['title1Name'], ResourceTypeImage.TITLES1) | |
register_image_keep(data['title2Name'], ResourceTypeImage.TITLES2) | |
register_audio_keep(data['titleBgm']['name'], ResourceTypeAudio.BGM) | |
register_audio_keep(data['battleBgm']['name'], ResourceTypeAudio.BGM) | |
register_audio_keep(data['defeatMe']['name'], ResourceTypeAudio.ME) | |
register_audio_keep(data['gameoverMe']['name'], ResourceTypeAudio.ME) | |
register_audio_keep(data['victoryMe']['name'], ResourceTypeAudio.ME) | |
register_audio_keep(data['airship']['bgm']['name'], ResourceTypeAudio.BGM) | |
register_image_keep(data['airship']['characterName'], ResourceTypeImage.CHARACTERS) | |
register_audio_keep(data['boat']['bgm']['name'], ResourceTypeAudio.BGM) | |
register_image_keep(data['boat']['characterName'], ResourceTypeImage.CHARACTERS) | |
register_audio_keep(data['ship']['bgm']['name'], ResourceTypeAudio.BGM) | |
register_image_keep(data['ship']['characterName'], ResourceTypeImage.CHARACTERS) | |
for sound in data['sounds']: | |
register_audio_keep(sound['name'], ResourceTypeAudio.SE) | |
def parse_actors(data): | |
logger.debug("Parsing actors...") | |
for actor in data: | |
if not actor: continue | |
register_image_keep(actor['characterName'], ResourceTypeImage.CHARACTERS) | |
register_image_keep(actor['faceName'], ResourceTypeImage.FACES) | |
register_image_keep(actor['battlerName'], ResourceTypeImage.SV_ACTORS) | |
def parse_enemies(data): | |
logger.debug("Parsing enemies...") | |
for enemy in data: | |
if not enemy: continue | |
register_image_keep(enemy['battlerName'], ResourceTypeImage.SV_ENEMIES) | |
register_image_keep(enemy['battlerName'], ResourceTypeImage.ENEMIES) | |
def parse_data_for_animations(data): | |
logger.debug("Parsing animations...") | |
for item in data: | |
if not item: continue | |
anim_id = item['animationId'] | |
if anim_id >= 1: | |
for file_se in animation_map[anim_id]['se']: | |
register_audio_keep(file_se, ResourceTypeAudio.SE) | |
for file_img in animation_map[anim_id]['img']: | |
register_image_keep(file_img, ResourceTypeImage.ANIMATIONS) | |
def list_rpgm_files(project_path, json_prefix='rtp', save=False): | |
logger.debug(f"Loading RPG MV/MZ data...") | |
all_rtp_img = {resource_type.value: set() for resource_type in ResourceTypeImage} | |
all_rtp_audio = {resource_type.value: set() for resource_type in ResourceTypeAudio} | |
imgdir = project_path.absolute() / 'img' | |
audiodir = project_path.absolute() / 'audio' | |
for subdir in all_rtp_img: | |
cur_dir = imgdir / subdir | |
if not cur_dir.is_dir(): continue | |
for f in cur_dir.iterdir(): | |
if f.is_file() and f.stem not in all_rtp_img[subdir]: | |
all_rtp_img[subdir].add(f.stem) | |
for subdir in all_rtp_audio: | |
cur_dir = audiodir / subdir | |
if not cur_dir.is_dir(): continue | |
for f in (audiodir / subdir).iterdir(): | |
if f.is_file() and f.stem not in all_rtp_audio[subdir]: | |
all_rtp_audio[subdir].add(f.stem) | |
if save: | |
with open(f'{json_prefix}_imgs_list.json', 'w', encoding='utf-8') as f: | |
json.dump(all_rtp_img, f, indent=2, cls=SetEncoder) | |
with open(f'{json_prefix}_audio_list.json', 'w', encoding='utf-8') as f: | |
json.dump(all_rtp_audio, f, indent=2, cls=SetEncoder) | |
return all_rtp_img, all_rtp_audio | |
def load_rtp_list(json_prefix='rtp'): | |
logger.debug(f"Loading RPG MV/MZ RTP data...") | |
imgs_to_delete = {} | |
audio_to_delete = {} | |
try: | |
with open(f'{json_prefix}_imgs_list.json', 'r', encoding='utf-8') as f: | |
imgs_to_delete = json.load(f, object_hook=SetEncoder.as_set) | |
except FileNotFoundError: | |
pass | |
try: | |
with open(f'{json_prefix}_audio_list.json', 'r', encoding='utf-8') as f: | |
audio_to_delete = json.load(f, object_hook=SetEncoder.as_set) | |
except FileNotFoundError: | |
pass | |
return imgs_to_delete, audio_to_delete | |
# Function to physically move unused resource files to a "removed" directory | |
def remove_files(remove_dict, base_path, base_remove_path): | |
logger.debug(f"Moving unused RPG MV/MZ files...") | |
rdir = Path(base_remove_path) | |
for _, dir_items in remove_dict.items(): | |
for file_basename in dir_items: | |
for filename in Path(base_path).rglob(f'{file_basename}.*'): | |
new_path = rdir / filename.relative_to(base_path) | |
new_path.parent.mkdir(parents=True, exist_ok=True) | |
filename.rename(rdir / filename.relative_to(base_path)) | |
def scan_js_files(project_path, imgs_to_delete, audio_to_delete): | |
js_folder = project_path / 'js' | |
if not js_folder.is_dir(): | |
return | |
for js_file in js_folder.rglob('*.js'): | |
try: | |
with open(js_file, 'r', encoding='utf-8') as f: | |
content = f.read() | |
for img_type, img_names in imgs_to_delete.items(): | |
for img_name in list(img_names): | |
if img_name in content: | |
imgs_to_delete[img_type].discard(img_name) | |
for audio_type, audio_names in audio_to_delete.items(): | |
for audio_name in list(audio_names): | |
if audio_name in content: | |
audio_to_delete[audio_type].discard(audio_name) | |
except Exception as e: | |
logger.error(f"Error reading or processing {js_file}: {e}") | |
def run_parser(project_path, exclude_folders=None, strip_only_rtp=True, check_scripts=False, test_orphans=False, print_removed=False): | |
project_path = Path(project_path) | |
if not project_path.is_dir(): | |
logger.error(f'Directory "{project_path}" not found, check your input path (-i parameter)') | |
return False | |
data_path = project_path / "data" | |
if not data_path.is_dir(): | |
logger.error(f'Directory "{data_path}" not found, check your input path (-i parameter)') | |
return False | |
# Parse game data files | |
# NOTE: Some games don't use Armors/Items/Animations/etc you can reset them | |
# manually (to [ null ] for lists and to {} for dicts) in the JSONs beforehand. | |
parse_tileset_map(json.loads((data_path / "Tilesets.json").read_text(encoding='utf-8'))) | |
parse_animations(json.loads((data_path / "Animations.json").read_text(encoding='utf-8'))) | |
parse_system(json.loads((data_path / "System.json").read_text(encoding='utf-8'))) | |
parse_enemies(json.loads((data_path / "Enemies.json").read_text(encoding='utf-8'))) | |
parse_actors(json.loads((data_path / "Actors.json").read_text(encoding='utf-8'))) | |
parse_common_events(json.loads((data_path / "CommonEvents.json").read_text(encoding='utf-8')), check_scripts) | |
parse_data_for_animations(json.loads((data_path / "Skills.json").read_text(encoding='utf-8'))) | |
parse_data_for_animations(json.loads((data_path / "Items.json").read_text(encoding='utf-8'))) | |
parse_data_for_animations(json.loads((data_path / "Weapons.json").read_text(encoding='utf-8'))) | |
logger.debug("Parsing map info and tiles...") | |
for map_file in data_path.glob('Map*'): | |
if re.match(r'Map\d+\b', map_file.stem): | |
data = json.loads(map_file.read_text(encoding='utf-8')) | |
if not isinstance(data, dict): | |
logger.error(f"{data} is not a dict in {map_file.stem}") | |
return False | |
data.pop('data', None) | |
parse_map(data) | |
parse_events(data, check_scripts) | |
# Load or generate resource removal lists | |
imgs_from_rtp, audio_from_rtp = load_rtp_list() | |
imgs_to_delete, audio_to_delete = list_rpgm_files(project_path) | |
# Exclude specified folders from removal | |
for folder in exclude_folders: | |
imgs_to_delete.pop(folder, None) | |
audio_to_delete.pop(folder, None) | |
def keep_unused(base: dict, keep: dict, rtp: dict): | |
result = dict(base) | |
for key, dir_items in base.items(): | |
keep_set = keep.get(key, set()) | |
if test_orphans: | |
missing_set = keep_set - (dir_items & keep_set) | |
if missing_set: | |
missing_str = '\n'.join(missing_set) | |
print(f"{key} items declared, but not on disk: \n{missing_str}") | |
if strip_only_rtp: | |
keep_set = keep_set & rtp.get(key, set()) | |
diff_set = dir_items & keep_set | |
result[key] = dir_items - diff_set | |
return result | |
# Scan JS files for resource usage since they can access the images | |
if check_scripts: | |
scan_js_files(project_path, imgs_to_delete, audio_to_delete) | |
if test_orphans: | |
print('======== Orphan references ========') | |
# Remove used resources from removal lists | |
imgs_to_delete = keep_unused(imgs_to_delete, image_keep_map, imgs_from_rtp) | |
audio_to_delete = keep_unused(audio_to_delete, audio_keep_map, audio_from_rtp) | |
if test_orphans: | |
return False | |
if print_removed: | |
print('======== Images to delete ========') | |
for key, dir_items in imgs_to_delete.items(): | |
if dir_items: | |
print(f'==== Directory {project_path / key} ====') | |
for i in sorted(dir_items): | |
print(i) | |
print('======== Audio to delete ========') | |
for key, dir_items in audio_to_delete.items(): | |
if dir_items: | |
print(f'==== Directory {project_path / key} ====') | |
for i in sorted(dir_items): | |
print(i) | |
return False | |
# Move unused resource files | |
remove_files(imgs_to_delete, project_path / 'img', project_path / 'removed' / 'img') | |
remove_files(audio_to_delete, project_path / 'audio', project_path / 'removed' / 'audio') | |
return True | |
if __name__ == '__main__': | |
import argparse, os | |
self_name = os.path.basename(__file__) | |
parser = argparse.ArgumentParser( | |
formatter_class=argparse.RawTextHelpFormatter, | |
description=( | |
'Scans and strips an RPG Maker game of unused files.\n' | |
'They are moved to the "removed" folder in the project directory.\n' | |
'\n' | |
'usage examples:\n' | |
f' {self_name} --input-directory "." -p\n' | |
f' {self_name} -i "www" -e "pictures,bgm" -p\n' | |
f' {self_name} -s -p\n' | |
f' {self_name} -i "NewData" -g\n' | |
) | |
) | |
def comma_separated(string: str): | |
if string: | |
return [i.strip() for i in string.split(',') if i] | |
return [] | |
parser.add_argument('-i', '--input-directory', type=Path, default='www', help='Path to the RPG Maker project directory (default: www)') #, required=True) # if we omit default= | |
parser.add_argument('-e', '--exclude-folders', type=comma_separated, nargs='+', default=[], help='Comma-separated list of folders to exclude from stripping', metavar='DIRECTORY,') | |
parser.add_argument('-s', '--strip-only-rtp', action='store_true', help='Strip only RTP resources, otherwise everything unused') | |
parser.add_argument('-c', '--check-scripts-not', action='store_true', help="Don't check script commands and .js files for resources (naive approach)") | |
group = parser.add_mutually_exclusive_group(required=True) | |
group.add_argument('-o', '--orphans-list', action='store_true', help='Find resources declared in JSONs but missing on disk') | |
group.add_argument('-t', '--test-parse-jsons', action='store_true', help='Only print unused game resources to the console') | |
group.add_argument('-g', '--generate-lists', action='store_true', help='Generate JSON resource lists (to dump file lists of RPGM RTP)') | |
group.add_argument('-p', '--parse-jsons', action='store_true', help='Run stripping of the unused game resources') | |
args = parser.parse_args(args=None if sys.argv[1:] else ['--help']) | |
if args.generate_lists: | |
list_rpgm_files(args.input_directory, save=True) | |
print('Resource JSON files generated successfully.') | |
elif run_parser( | |
args.input_directory, | |
args.exclude_folders, | |
args.strip_only_rtp, | |
not args.check_scripts_not, | |
args.orphans_list, | |
args.test_parse_jsons): | |
print('Unused resources moved to the "removed" folder.') | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"bgm": {"__set": [ | |
"Battle1", | |
"Battle2", | |
"Battle3", | |
"Castle1", | |
"Castle2", | |
"Dungeon1", | |
"Dungeon2", | |
"Dungeon3", | |
"Field1", | |
"Field2", | |
"Ship1", | |
"Ship2", | |
"Ship3", | |
"Theme1", | |
"Theme2", | |
"Theme6", | |
"Town1", | |
"Town2" | |
]}, | |
"bgs": {"__set": [ | |
"City", | |
"Darkness", | |
"Drips", | |
"Night", | |
"Quake", | |
"River", | |
"Sea", | |
"Storm1", | |
"Storm2", | |
"Wind" | |
]}, | |
"me": {"__set": [ | |
"Curse1", | |
"Curse2", | |
"Defeat1", | |
"Defeat2", | |
"Fanfare1", | |
"Fanfare2", | |
"Gameover1", | |
"Gameover2", | |
"Inn", | |
"Musical1", | |
"Musical2", | |
"Musical3", | |
"Mystery", | |
"Organ", | |
"Shock1", | |
"Shock2", | |
"Victory1", | |
"Victory2" | |
]}, | |
"se": {"__set": [ | |
"Absorb1", | |
"Absorb2", | |
"Applause1", | |
"Applause2", | |
"Attack1", | |
"Attack2", | |
"Attack3", | |
"Battle1", | |
"Battle2", | |
"Battle3", | |
"Bell1", | |
"Bell2", | |
"Bell3", | |
"Blind", | |
"Blow1", | |
"Blow2", | |
"Blow3", | |
"Book1", | |
"Book2", | |
"Break", | |
"Buzzer1", | |
"Buzzer2", | |
"Cancel1", | |
"Cancel2", | |
"Cat", | |
"Chest1", | |
"Chest2", | |
"Close1", | |
"Close2", | |
"Close3", | |
"Coin", | |
"Collapse1", | |
"Collapse2", | |
"Collapse3", | |
"Collapse4", | |
"Computer", | |
"Cow", | |
"Crash", | |
"Crossbow", | |
"Crow", | |
"Cursor1", | |
"Cursor2", | |
"Damage1", | |
"Damage2", | |
"Damage3", | |
"Damage4", | |
"Damage5", | |
"Darkness1", | |
"Darkness2", | |
"Darkness3", | |
"Darkness4", | |
"Darkness5", | |
"Decision1", | |
"Decision2", | |
"Devil1", | |
"Devil2", | |
"Disappointment", | |
"Dive", | |
"Dog", | |
"Door1", | |
"Door2", | |
"Door3", | |
"Door4", | |
"Down1", | |
"Down2", | |
"Earth1", | |
"Earth2", | |
"Earth3", | |
"Earth4", | |
"Earth5", | |
"Electrocardiogram", | |
"Equip1", | |
"Equip2", | |
"Evasion1", | |
"Evasion2", | |
"Explosion1", | |
"Explosion2", | |
"Fall", | |
"Fire1", | |
"Fire2", | |
"Fire3", | |
"Flash1", | |
"Flash2", | |
"Frog", | |
"Growl", | |
"Gun1", | |
"Gun2", | |
"Hammer", | |
"Heal1", | |
"Heal2", | |
"Heal3", | |
"Horn", | |
"Horse", | |
"Ice1", | |
"Ice2", | |
"Ice3", | |
"Ice4", | |
"Ice5", | |
"Item1", | |
"Item2", | |
"Item3", | |
"Jump1", | |
"Jump2", | |
"Key", | |
"Knock", | |
"Laser1", | |
"Laser2", | |
"Laugh", | |
"Launch", | |
"Leakage", | |
"Liquid", | |
"Load", | |
"Machine", | |
"Magic1", | |
"Magic2", | |
"Magic3", | |
"Magic4", | |
"Miss", | |
"Monster1", | |
"Monster2", | |
"Monster3", | |
"Monster4", | |
"Monster5", | |
"Move1", | |
"Move2", | |
"Move3", | |
"Move4", | |
"Move5", | |
"Neon", | |
"Noise", | |
"Open1", | |
"Open2", | |
"Open3", | |
"Open4", | |
"Open5", | |
"Paralyze1", | |
"Paralyze2", | |
"Paralyze3", | |
"Parry", | |
"Phone", | |
"Poison", | |
"Pollen", | |
"Powerup", | |
"Push", | |
"Raise1", | |
"Raise2", | |
"Recovery", | |
"Reflection", | |
"Run", | |
"Saint1", | |
"Saint2", | |
"Saint3", | |
"Saint4", | |
"Saint5", | |
"Sand", | |
"Save", | |
"Scream", | |
"Sheep", | |
"Shop1", | |
"Shop2", | |
"Shot1", | |
"Shot2", | |
"Shot3", | |
"Silence", | |
"Siren", | |
"Skill1", | |
"Skill2", | |
"Skill3", | |
"Slash1", | |
"Slash2", | |
"Slash3", | |
"Slash4", | |
"Slash5", | |
"Sleep", | |
"Sound1", | |
"Sound2", | |
"Sound3", | |
"Splash", | |
"Stare", | |
"Starlight", | |
"Switch1", | |
"Switch2", | |
"Switch3", | |
"Sword1", | |
"Sword2", | |
"Sword3", | |
"Sword4", | |
"Sword5", | |
"Teleport", | |
"Thunder1", | |
"Thunder10", | |
"Thunder2", | |
"Thunder3", | |
"Thunder4", | |
"Thunder5", | |
"Thunder6", | |
"Thunder7", | |
"Thunder8", | |
"Thunder9", | |
"Transceiver", | |
"Twine", | |
"Up1", | |
"Up2", | |
"Up3", | |
"Up4", | |
"Water1", | |
"Water2", | |
"Water3", | |
"Water4", | |
"Water5", | |
"Wind1", | |
"Wind2", | |
"Wind3", | |
"Wind4", | |
"Wind5", | |
"Wind6", | |
"Wind7", | |
"Wolf" | |
]} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"animations": {"__set": [ | |
"Absorb", | |
"ArrowSpecial", | |
"Blow", | |
"Breath", | |
"Claw", | |
"ClawPhoton", | |
"ClawSpecial1", | |
"ClawSpecial2", | |
"Cure1", | |
"Cure2", | |
"Cure3", | |
"Cure4", | |
"Curse", | |
"Darkness1", | |
"Darkness2", | |
"Darkness3", | |
"Darkness4", | |
"Darkness5", | |
"Earth1", | |
"Earth2", | |
"Earth3", | |
"Earth4", | |
"Earth5", | |
"Explosion1", | |
"Explosion2", | |
"Fire1", | |
"Fire2", | |
"Fire3", | |
"Flash", | |
"Gun1", | |
"Gun2", | |
"Gun3", | |
"Hit1", | |
"Hit2", | |
"HitFire", | |
"HitIce", | |
"HitPhoton", | |
"HitSpecial1", | |
"HitSpecial2", | |
"HitThunder", | |
"Holy1", | |
"Holy2", | |
"Holy3", | |
"Holy4", | |
"Holy5", | |
"Howl", | |
"Ice1", | |
"Ice2", | |
"Ice3", | |
"Ice4", | |
"Ice5", | |
"Laser1", | |
"Laser2", | |
"Light1", | |
"Light2", | |
"Light3", | |
"Light4", | |
"Magic1", | |
"Magic2", | |
"Meteor", | |
"Mist", | |
"Pollen", | |
"PreSpecial1", | |
"PreSpecial2", | |
"PreSpecial3", | |
"Recovery1", | |
"Recovery2", | |
"Recovery3", | |
"Recovery4", | |
"Recovery5", | |
"Revival1", | |
"Revival2", | |
"Slash", | |
"SlashFire", | |
"SlashIce", | |
"SlashPhoton", | |
"SlashSpecial1", | |
"SlashSpecial2", | |
"SlashSpecial3", | |
"SlashThunder", | |
"Song", | |
"Sonic", | |
"Special1", | |
"Special2", | |
"Special3", | |
"StateChaos", | |
"StateDark", | |
"StateDeath", | |
"StateDown1", | |
"StateDown2", | |
"StateDown3", | |
"StateParalys", | |
"StatePoison", | |
"StateSilent", | |
"StateSleep", | |
"StateUp1", | |
"StateUp2", | |
"Stick", | |
"StickPhoton", | |
"StickSpecial1", | |
"StickSpecial2", | |
"StickSpecial3", | |
"Thunder1", | |
"Thunder2", | |
"Thunder3", | |
"Thunder4", | |
"Thunder5", | |
"Water1", | |
"Water2", | |
"Water3", | |
"Water4", | |
"Water5", | |
"Wind1", | |
"Wind2", | |
"Wind3", | |
"Wind4", | |
"Wind5" | |
]}, | |
"battlebacks1": {"__set": [ | |
"Castle1", | |
"Castle2", | |
"Clouds", | |
"Cobblestones1", | |
"Cobblestones2", | |
"Cobblestones3", | |
"Cobblestones4", | |
"Cobblestones5", | |
"CobblestonesPool", | |
"Crystal", | |
"DarkSpace", | |
"DecorativeTile", | |
"DemonCastle1", | |
"DemonCastle2", | |
"DemonicWorld", | |
"Desert", | |
"Dirt1", | |
"Dirt2", | |
"DirtField", | |
"FaceTile", | |
"Factory", | |
"Grassland", | |
"GrassMaze", | |
"GrassMazePool", | |
"IceCave", | |
"InBody", | |
"Lava1", | |
"Lava2", | |
"LavaCave", | |
"Meadow", | |
"PoisonSwamp", | |
"Road1", | |
"Road2", | |
"Road3", | |
"RockCave", | |
"Ruins1", | |
"Ruins2", | |
"Ruins3", | |
"Ruins4", | |
"Ruins5", | |
"Sand", | |
"Ship", | |
"Sky", | |
"Snowfield", | |
"Tent", | |
"Translucent", | |
"Wasteland", | |
"WireMesh", | |
"Wood1", | |
"Wood2" | |
]}, | |
"battlebacks2": {"__set": [ | |
"Brick", | |
"Bridge", | |
"Castle1", | |
"Castle2", | |
"Castle3", | |
"Cliff", | |
"Clouds", | |
"Crystal", | |
"DarkSpace", | |
"DemonCastle1", | |
"DemonCastle2", | |
"DemonCastle3", | |
"DemonicWorld", | |
"Desert", | |
"DirtCave", | |
"Forest", | |
"Fort1", | |
"Fort2", | |
"Grassland", | |
"GrassMaze", | |
"IceCave", | |
"IceMaze", | |
"InBody", | |
"Lava", | |
"LavaCave", | |
"Metal", | |
"Mine", | |
"PoisonSwamp", | |
"Port", | |
"RockCave", | |
"Room1", | |
"Room2", | |
"Room3", | |
"Ruins1", | |
"Ruins2", | |
"Ship", | |
"Sky", | |
"Snowfield", | |
"Stone1", | |
"Stone2", | |
"Stone3", | |
"Temple", | |
"Tent", | |
"Tower", | |
"Town1", | |
"Town2", | |
"Town3", | |
"Town4", | |
"Town5", | |
"Wasteland" | |
]}, | |
"characters": {"__set": [ | |
"!$Gate1", | |
"!$Gate2", | |
"!Chest", | |
"!Crystal", | |
"!Door1", | |
"!Door2", | |
"!Flame", | |
"!Other1", | |
"!Other2", | |
"!SF_Door1", | |
"!SF_Door2", | |
"!Switch1", | |
"!Switch2", | |
"$BigMonster1", | |
"$BigMonster2", | |
"Actor1", | |
"Actor2", | |
"Actor3", | |
"Damage1", | |
"Damage2", | |
"Damage3", | |
"Evil", | |
"Monster", | |
"Nature", | |
"People1", | |
"People2", | |
"People3", | |
"People4", | |
"Vehicle" | |
]}, | |
"enemies": {"__set": [ | |
"Actor1_3", | |
"Actor1_4", | |
"Actor1_5", | |
"Actor1_6", | |
"Actor1_7", | |
"Actor2_1", | |
"Actor2_2", | |
"Actor2_3", | |
"Actor2_4", | |
"Actor2_5", | |
"Actor2_6", | |
"Actor3_1", | |
"Actor3_2", | |
"Actor3_5", | |
"Actor3_6", | |
"Angel", | |
"Assassin", | |
"Bat", | |
"Behemoth", | |
"Captain", | |
"Cerberus", | |
"Chimera", | |
"Cockatrice", | |
"Darklord-final", | |
"Darklord", | |
"Death", | |
"Demon", | |
"Dragon", | |
"Earthspirit", | |
"Evilgod", | |
"Fairy", | |
"Fanatic", | |
"Firespirit", | |
"Gargoyle", | |
"Garuda", | |
"Gazer", | |
"General_f", | |
"General_m", | |
"Ghost", | |
"God", | |
"Goddess", | |
"Hornet", | |
"Imp", | |
"Irongiant", | |
"Jellyfish", | |
"Lamia", | |
"Mage", | |
"Mimic", | |
"Minotaur", | |
"Ogre", | |
"Orc", | |
"Plant", | |
"Puppet", | |
"Rat", | |
"Rogue", | |
"Sahuagin", | |
"Scorpion", | |
"Skeleton", | |
"Slime", | |
"Snake", | |
"Soldier", | |
"Spider", | |
"Succubus", | |
"Swordsman", | |
"Vampire", | |
"Waterspirit", | |
"Werewolf", | |
"Willowisp", | |
"Windspirit", | |
"Zombie" | |
]}, | |
"faces": {"__set": [ | |
"Actor1", | |
"Actor2", | |
"Actor3", | |
"Evil", | |
"Monster", | |
"Nature", | |
"People1", | |
"People2", | |
"People3", | |
"People4" | |
]}, | |
"parallaxes": {"__set": [ | |
"BlueSky", | |
"CloudySky1", | |
"CloudySky2", | |
"DarkSpace1", | |
"DarkSpace2", | |
"Mountains1", | |
"Mountains2", | |
"Mountains3", | |
"Mountains4", | |
"Mountains5", | |
"Ocean1", | |
"Ocean2", | |
"SeaofClouds", | |
"StarlitSky", | |
"Sunset" | |
]}, | |
"pictures": {"__set": []}, | |
"sv_actors": {"__set": [ | |
"Actor1_1", | |
"Actor1_2", | |
"Actor1_3", | |
"Actor1_4", | |
"Actor1_5", | |
"Actor1_6", | |
"Actor1_7", | |
"Actor1_8", | |
"Actor2_1", | |
"Actor2_2", | |
"Actor2_3", | |
"Actor2_4", | |
"Actor2_5", | |
"Actor2_6", | |
"Actor2_7", | |
"Actor2_8", | |
"Actor3_5", | |
"Actor3_6", | |
"Actor3_7", | |
"Actor3_8" | |
]}, | |
"sv_enemies": {"__set": [ | |
"Actor1_3", | |
"Actor1_4", | |
"Actor1_5", | |
"Actor1_6", | |
"Actor1_7", | |
"Actor2_1", | |
"Actor2_2", | |
"Actor2_3", | |
"Actor2_4", | |
"Actor2_5", | |
"Actor2_6", | |
"Actor3_1", | |
"Actor3_2", | |
"Actor3_5", | |
"Actor3_6", | |
"Angel", | |
"Assassin", | |
"Bat", | |
"Behemoth", | |
"Captain", | |
"Cerberus", | |
"Chimera", | |
"Cockatrice", | |
"Darklord-final", | |
"Darklord", | |
"Death", | |
"Demon", | |
"Dragon", | |
"Earthspirit", | |
"Evilgod", | |
"Fairy", | |
"Fanatic", | |
"Firespirit", | |
"Gargoyle", | |
"Garuda", | |
"Gazer", | |
"General_f", | |
"General_m", | |
"Ghost", | |
"God", | |
"Goddess", | |
"Hornet", | |
"Imp", | |
"Irongiant", | |
"Jellyfish", | |
"Lamia", | |
"Mage", | |
"Mimic", | |
"Minotaur", | |
"Ogre", | |
"Orc", | |
"Plant", | |
"Puppet", | |
"Rat", | |
"Rogue", | |
"Sahuagin", | |
"Scorpion", | |
"Skeleton", | |
"Slime", | |
"Snake", | |
"Soldier", | |
"Spider", | |
"Succubus", | |
"Swordsman", | |
"Vampire", | |
"Waterspirit", | |
"Werewolf", | |
"Willowisp", | |
"Windspirit", | |
"Zombie" | |
]}, | |
"tilesets": {"__set": [ | |
"Dungeon_A1", | |
"Dungeon_A2", | |
"Dungeon_A4", | |
"Dungeon_A5", | |
"Dungeon_B", | |
"Dungeon_C", | |
"Inside_A1", | |
"Inside_A2", | |
"Inside_A4", | |
"Inside_A5", | |
"Inside_B", | |
"Inside_C", | |
"Outside_A1", | |
"Outside_A2", | |
"Outside_A3", | |
"Outside_A4", | |
"Outside_A5", | |
"Outside_B", | |
"Outside_C", | |
"SF_Inside_A4", | |
"SF_Inside_B", | |
"SF_Inside_C", | |
"SF_Outside_A3", | |
"SF_Outside_A4", | |
"SF_Outside_A5", | |
"SF_Outside_B", | |
"SF_Outside_C", | |
"World_A1", | |
"World_A2", | |
"World_B", | |
"World_C" | |
]}, | |
"titles1": {"__set": [ | |
"Book", | |
"Castle", | |
"CrossedSwords", | |
"Crystal", | |
"DemonCastle", | |
"Devil", | |
"Dragon", | |
"Fountain", | |
"Gates", | |
"Hexagram", | |
"Island", | |
"Night", | |
"Plain", | |
"Sword", | |
"Tower1", | |
"Tower2", | |
"Universe", | |
"Volcano", | |
"World", | |
"WorldMap" | |
]}, | |
"titles2": {"__set": [ | |
"Floral", | |
"Medieval" | |
]} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment