From 807de329af888c08bd16cc51d1577ca53dda0b7a Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:08:34 +1100 Subject: [PATCH 01/15] Add KH items (thanks Twilight for the mappings) --- data/items_kh.json | 476 +++++++++++++++++++++++++++++++++++++++++++++ hooks/Data.py | 8 +- 2 files changed, 481 insertions(+), 3 deletions(-) create mode 100644 data/items_kh.json diff --git a/data/items_kh.json b/data/items_kh.json new file mode 100644 index 0000000..6ad1559 --- /dev/null +++ b/data/items_kh.json @@ -0,0 +1,476 @@ + +[ + { + "count": 3, + "name": "Cure", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Cure"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Cure","Sleight Cura","Sleight Curaga"], + "Kingdom Hearts 2": ["Cure Element"], + "Kingdom Hearts Birth by Sleep": ["Cure","Cura","Curaga"] + } + }, + { + "count": 3, + "name": "Fire", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Fire"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Fire","Sleight Fira","Sleight Firaga"], + "Kingdom Hearts 2": ["Fire Element"], + "Kingdom Hearts Birth by Sleep": ["Fire","Fira","Firaga"] + } + }, + { + "count": 3, + "name": "Blizzard", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Blizzard"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Blizzard","Sleight Blizzara","Sleight Blizzaga"], + "Kingdom Hearts 2": ["Blizzard Element"], + "Kingdom Hearts Birth by Sleep": ["Blizzard","Blizzara","Blizzaga"] + } + }, + { + "count": 3, + "name": "Thunder", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Thunder"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Thunder","Sleight Thundara","Sleight Thundaga"], + "Kingdom Hearts 2": ["Thunder Element"], + "Kingdom Hearts Birth by Sleep": ["Thunder","Thundara","Thundaga"] + } + }, + { + "count": 3, + "name": "Gravity", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Gravity"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Gravity","Sleight Gravira","Sleight Graviga"], + "Kingdom Hearts Birth by Sleep": ["Zero Gravity","Zero Gravira","Zero Graviga"] + } + }, + { + "count": 3, + "name": "Stop", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Stop"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Stop","Sleight Stopra","Sleight Stopga"], + "Kingdom Hearts Birth by Sleep": ["Stop","Stopra","Stopga"] + } + }, + { + "count": 3, + "name": "Aero+Reflect", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Aero"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Aero","Sleight Aerora","Sleight Aeroga"], + "Kingdom Hearts 2": ["Reflect Element"], + "Kingdom Hearts Birth by Sleep": ["Aero","Aerora","Aeroga"] + } + }, + { + "count": 3, + "name": "Magnet", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Magnet Element"], + "Kingdom Hearts Birth by Sleep": ["Magnet","Magnera","Magnega"] + } + }, + { + "count": 4, + "name": "High Jump", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["High Jump"], + "Kingdom Hearts 2": ["High Jump"], + "Kingdom Hearts Birth by Sleep": ["High Jump","Doubleflight"] + } + }, + { + "count": 4, + "name": "Dodge Roll", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Dodge Roll"], + "Kingdom Hearts 2": ["Dodge Roll"], + "Kingdom Hearts Birth by Sleep": ["Thunder Roll","Firewheel"] + } + }, + { + "count": 4, + "name": "Glide", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Glide"], + "Kingdom Hearts 2": ["Glide"], + "Kingdom Hearts Birth by Sleep": ["Glide","Fire Glide","Superglide"] + } + }, + { + "count": 4, + "name": "Quick Run + Slide", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Quick Run"], + "Kingdom Hearts Birth by Sleep": ["Air Slide","Ice Slide"] + } + }, + { + "count": 1, + "name": "Scan", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Scan"], + "Kingdom Hearts 2": ["Scan"], + } + }, + { + "count": 1, + "name": "Leaf Bracer", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Leaf Bracer"], + "Kingdom Hearts 2": ["Leaf Bracer"], + "Kingdom Hearts Birth by Sleep": ["Leaf Bracer"] + } + }, + { + "count": 1, + "name": "Second Chance", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Second Chance"], + "Kingdom Hearts 2": ["Second Chance"], + "Kingdom Hearts Birth by Sleep": ["Second Chance"] + } + }, + { + "count": 1, + "name": "Once More", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Once More"], + "Kingdom Hearts Birth by Sleep": ["Once More"] + } + }, + { + "count": 1, + "name": "Guard", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Guard+Barrier"], + "Kingdom Hearts 2": ["Guard"], + "Kingdom Hearts Birth by Sleep": ["Renewal Barrier"] + } + }, + { + "count": 1, + "name": "Aerial Recovery", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Aerial Recovery"], + "Kingdom Hearts Birth by Sleep": ["Aerial Recovery"] + } + }, + { + "count": 1, + "name": "Blitz", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Blitz"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Blitz"], + "Kingdom Hearts Birth by Sleep": ["Blitz"] + } + }, + { + "count": 1, + "name": "Sliding Dash", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Sliding Dash"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Sliding Dash"], + "Kingdom Hearts 2": ["Slide Dash"], + "Kingdom Hearts Birth by Sleep": ["Sliding Dash"] + } + }, + { + "count": 1, + "name": "Sonic Blade+Limit", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Sonic Blade"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Sonic Blade"], + "Kingdom Hearts 2": ["Limit Form"], + "Kingdom Hearts Birth by Sleep": ["Sonic Blade"] + } + }, + { + "count": 1, + "name": "Ars Arcanum+Valor", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Ars Arcanum"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Ars Arcanum"], + "Kingdom Hearts 2": ["Valor Form"], + "Kingdom Hearts Birth by Sleep": ["Ars Arcanum"] + } + }, + { + "count": 1, + "name": "Strike Raid+Master", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Strike Raid"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Strike Raid"], + "Kingdom Hearts 2": ["Master Form"], + "Kingdom Hearts Birth by Sleep": ["Strike Raid"] + } + }, + { + "count": 1, + "name": "Ragnarok+Wisdom", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Ragnarok"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Ragnarok"], + "Kingdom Hearts 2": ["Wisdom Form"], + "Kingdom Hearts Birth by Sleep": ["Ragnarok"] + } + }, + { + "count": 1, + "name": "Zantetsuken", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Zantetsuken"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Zantetsuken"], + "Kingdom Hearts Birth by Sleep": ["Zantetsuken"] + } + }, + { + "count": 1, + "name": "Simba", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Simba"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Simba"] + } + }, + { + "count": 1, + "name": "Genie", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Genie"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Genie"], + "Kingdom Hearts 2": ["Genie"], + } + }, + { + "count": 1, + "name": "Bambi", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Bambi"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Bambi"] + } + }, + { + "count": 1, + "name": "Dumbo", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Dumbo"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Dumbo"] + } + }, + { + "count": 1, + "name": "Tinker Bell+Peter Pan", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Tinker Bell"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Tinker Bell"], + "Kingdom Hearts 2": ["Peter Pan"], + "Kingdom Hearts Birth by Sleep": ["Peter Pan D-Link"] + } + }, + { + "count": 1, + "name": "Stitch", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Stitch"], + "Kingdom Hearts Birth by Sleep": ["Experiment 626 D-Link"] + } + }, + { + "count": 2, + "name": "Destiny Islands", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Destiny Islands"], + "Kingdom Hearts RE Chain of Memories": ["World Card Destiny Islands","Key to Rewards Destiny Islands"] + } + }, + { + "count": 2, + "name": "Wonderland", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Wonderland"], + "Kingdom Hearts RE Chain of Memories": ["World Card Wonderland","Key to Rewards Wonderland"] + } + }, + { + "count": 2, + "name": "Olympus Coliseum", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Olympus Coliseum"], + "Kingdom Hearts RE Chain of Memories": ["World Card Olympus Coliseum","Key to Rewards Olympus Coliseum"], + "Kingdom Hearts 2": ["Battlefields of War"], + "Kingdom Hearts Birth by Sleep": ["Olympus Coliseum","Zack D-Link"] + } + }, + { + "count": 2, + "name": "Agrabah", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Agrabah"], + "Kingdom Hearts RE Chain of Memories": ["World Card Agrabah","Key to Rewards Agrabah"], + "Kingdom Hearts 2": ["Scimitar"] + } + }, + { + "count": 2, + "name": "Monstro+Mirage Arena", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Monstro"], + "Kingdom Hearts RE Chain of Memories": ["World Card Monstro","Key to Rewards Monstro"], + "Kingdom Hearts Birth by Sleep": ["Mirage Arena"] + } + }, + { + "count": 2, + "name": "Halloween Town", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Halloween Town"], + "Kingdom Hearts RE Chain of Memories": ["World Card Halloween Town","Key to Rewards Halloween Town"], + "Kingdom Hearts 2": ["Bone Fist"] + } + }, + { + "count": 2, + "name": "Atlantica", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Atlantica"], + "Kingdom Hearts RE Chain of Memories": ["World Card Atlantica","Key to Rewards Atlantica"] + } + }, + { + "count": 2, + "name": "Neverland", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Neverland"], + "Kingdom Hearts RE Chain of Memories": ["World Card Neverland","Key to Rewards Neverland"], + "Kingdom Hearts Birth by Sleep": ["Never Land"] + } + }, + { + "count": 2, + "name": "Radiant Garden", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Hollow Bastion"], + "Kingdom Hearts RE Chain of Memories": ["World Card Hollow Bastion","Key to Rewards Hollow Bastion"], + "Kingdom Hearts 2": ["Membership Card"], + "Kingdom Hearts Birth by Sleep": ["Radiant Garden","Maleficent D-Link"] + } + }, + { + "count": 3, + "name": "Twilight Town+ Mysterious Tower", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts RE Chain of Memories": ["World Card Twilight Town","Key to Rewards Twilight Town"], + "Kingdom Hearts 2": ["Ice Cream"], + "Kingdom Hearts Birth by Sleep": ["The Mysterious Tower","Donald D-Link","Goofy D-Link"] + } + }, + { + "count": 2, + "name": "Disney Castle Town", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Disney Castle Key"], + "Kingdom Hearts Birth by Sleep": ["Disney Town","Mickey D-Link"] + } + }, + { + "count": 5, + "name": "Torn Page", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Torn Page"], + "Kingdom Hearts RE Chain of Memories": ["Key to Rewards Traverse Town","Key to Rewards Castle Oblivion","World Card 100 Acre Wood "], + "Kingdom Hearts 2": ["Torn Page"] + } + } +] diff --git a/hooks/Data.py b/hooks/Data.py index 65bc936..be243c3 100644 --- a/hooks/Data.py +++ b/hooks/Data.py @@ -1,7 +1,7 @@ ITEM_TABLE = [] MAX_PLAYERS = 40 FREE_ITEMS = 0 -PKMN = False +extra_item_files = ['items_pkmn.json', 'items_kh.json'] # called after the game.json file has been loaded def after_load_game_file(game_table: dict) -> dict: @@ -11,9 +11,11 @@ def after_load_game_file(game_table: dict) -> dict: # if you need access to the items after processing to add ids, etc., you should use the hooks in World.py def after_load_item_file(item_table: list) -> list: # Store a reference to this - if PKMN: + for i, extra_file in enumerate(extra_item_files): from ..Data import load_data_file - item_table.extend(load_data_file('items_pkmn.json')) + new_table = load_data_file(extra_file) + new_table[0]['id'] += i * 1000 # Plenty of room for expansion + item_table.extend(new_table) for item in item_table: if 'count' not in item: From 839395b23645ebe4c922dcf2d7514d253ae19e66 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Sun, 23 Nov 2025 19:33:31 -0500 Subject: [PATCH 02/15] Made a script to consistently sort the linklink properties in items.json # Conflicts: # data/items.json # data/items.schema.json --- data/Sort-Items-linklink-data.py | 69 ++ data/items.json | 1180 +++++++++++++++--------------- data/items.schema.json | 165 +++++ 3 files changed, 826 insertions(+), 588 deletions(-) create mode 100644 data/Sort-Items-linklink-data.py create mode 100644 data/items.schema.json diff --git a/data/Sort-Items-linklink-data.py b/data/Sort-Items-linklink-data.py new file mode 100644 index 0000000..f5b948f --- /dev/null +++ b/data/Sort-Items-linklink-data.py @@ -0,0 +1,69 @@ +import json, re +from os import path +from typing import cast +def repl_func(match: re.Match): + result = " ".join(match.group().split()) + parts = result.split('",') + current_length = 0 + result_parts: list[str] = [] + indent = " " + for i, part in enumerate(parts): + if i > 0: + if current_length > 100 and i < len(parts) - 1: + current_length = len(part) + 2 + part = f'",\n{indent}' + part + + else: + current_length += len(part) + 2 + part = '",' + part + else: + current_length = len(part) + 1 + result_parts.append(part) + return "".join(result_parts) + +def load_data_file(fname: str) -> dict: + fpath = path.dirname(__file__) + fpath = path.join(fpath, fname) + try: + with open(fpath, 'r', encoding="utf-8-sig") as f: + filedata = json.load(f) + except: + filedata = {} + + return filedata + +def write_data_file(fname: str, data: dict): + fpath = path.dirname(__file__) + fpath = path.join(fpath, fname) + # regex based on answer in https://www.reddit.com/r/learnpython/comments/ymukyr/removing_new_line_inside_square_brackets_in_json/ + json_str = json.dumps(data, indent=4) + json_str = re.sub(r"(?<=\[)[^\[\]]+(?=])", repl_func, json_str) + + with open(file=fpath, mode="w", encoding="utf-8") as f: + f.write(json_str) + +if __name__ == '__main__': + schema = load_data_file('items.schema.json') + itemsjson = load_data_file('items.json') + if schema and itemsjson: + # First get the keys from schema: + schema_linklink = schema.get('definitions', {}).get('linklink', {}).get("properties", None) + if schema_linklink is None: + raise ValueError("items.schema.json doesn't contain a known definition of the linklink data") + schema_linklink = cast(dict[str, dict[str, str | bool]], schema_linklink) + + known_loz_keys: list[str] = [] + for key, value in schema_linklink.items(): + if value.get("loz", False): + known_loz_keys.append(key) + + # Second: sort all the item's linklink properties by placing the zelda ones first + for item in itemsjson.get("data", []): + item = cast(dict[str, dict[str, list[str]]], item) + if not item.get("linklink"): + continue + else: + item["linklink"] = dict(sorted(item["linklink"].items(), key=lambda item: (0 if item[0] in known_loz_keys else 1, item[0]))) + + # Third and finally write the change to the file + write_data_file('items.json', itemsjson) diff --git a/data/items.json b/data/items.json index 0243765..b3b6f88 100644 --- a/data/items.json +++ b/data/items.json @@ -1,589 +1,593 @@ -[ - { - "count": 4, - "name": "Sword", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Sword", "White Sword", "Magical Sword"], - "A Link to the Past": ["Progressive Sword"], - "SMZ3": ["ProgressiveSword"], - "Links Awakening DX": ["Progressive Sword"], - "Ocarina of Time": ["Kokiri Sword", "Giants Knife"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Sword"], - "The Wind Waker": ["Progressive Sword"], - "TUNIC": ["Sword Upgrade"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Sword"], - "Majora's Mask Recompiled": ["Progressive Sword"], - "A Link Between Worlds": ["Progressive Sword"] +{ + "$schema": "items.schema.json", + "data": [ + { + "count": 4, + "name": "Sword", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Progressive Sword"], + "A Link to the Past": ["Progressive Sword"], + "Links Awakening DX": ["Progressive Sword"], + "Majora's Mask Recompiled": ["Progressive Sword"], + "Ocarina of Time": ["Kokiri Sword", "Giants Knife"], + "SMZ3": ["ProgressiveSword"], + "The Legend of Zelda": ["Sword", "White Sword", "Magical Sword"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Sword"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Sword"], + "The Wind Waker": ["Progressive Sword"], + "TUNIC": ["Sword Upgrade"] + } + }, + { + "count": 1, + "name": "Biggoron's Sword", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Progressive Sword"], + "A Link to the Past": ["Progressive Sword"], + "Majora's Mask Recompiled": ["Great Fairy Sword"], + "Ocarina of Time": ["Biggoron Sword"], + "SMZ3": ["ProgressiveSword"], + "The Legend of Zelda - Oracle of Ages": ["Biggoron's Sword"], + "The Legend of Zelda - Oracle of Seasons": ["Biggoron's Sword"], + "The Wind Waker": ["Progressive Sword"], + "TUNIC": ["Sword Upgrade"] + } + }, + { + "count": 1, + "name": "Shield (Basic)", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Hylian Shield"], + "A Link to the Past": ["Progressive Shield", "Blue Shield"], + "Links Awakening DX": ["Progressive Shield"], + "Ocarina of Time": ["Deku Shield"], + "SMZ3": ["ProgressiveShield"], + "The Legend of Zelda": ["Magical Shield"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Shield"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Shield"], + "The Wind Waker": ["Progressive Shield"], + "TUNIC": ["Shield"] + } + }, + { + "count": 1, + "name": "Shield (Intermediate)", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Progressive Shield", "Red Shield"], + "Ocarina of Time": ["Hylian Shield"], + "SMZ3": ["ProgressiveShield"], + "The Legend of Zelda": ["Magical Shield"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Shield"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Shield"] + } + }, + { + "count": 1, + "name": "Shield (Mirror)", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Progressive Shield", "Mirror Shield"], + "Links Awakening DX": ["Progressive Shield"], + "Ocarina of Time": ["Mirror Shield", "Hylian Shield", "Deku Shield"], + "SMZ3": ["ProgressiveShield"], + "The Legend of Zelda": ["Magical Shield"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Shield"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Shield"], + "The Wind Waker": ["Progressive Shield"] + } + }, + { + "count": 2, + "name": "Fire Magic", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Fire Rod"], + "A Link to the Past": ["Fire Rod"], + "Majora's Mask Recompiled": ["Fire Arrow"], + "Ocarina of Time": ["Fire Arrows"], + "SMZ3": ["Firerod"], + "The Wind Waker": ["Progressive Bow"], + "TUNIC": ["Glass Cannon"] + } + }, + { + "count": 2, + "name": "Ice Magic", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Ice Rod"], + "A Link to the Past": ["Ice Rod"], + "Majora's Mask Recompiled": ["Ice Arrow"], + "Ocarina of Time": ["Ice Arrows"], + "SMZ3": ["Icerod"], + "TUNIC": ["Magic Dagger"] + } + }, + { + "count": 1, + "name": "Light Arrows", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Bow of Light"], + "A Link to the Past": ["Silver Bow", "Progressive Bow (Alt)"], + "Majora's Mask Recompiled": ["Light Arrow"], + "Ocarina of Time": ["Light Arrows"], + "SMZ3": ["SilverArrows"], + "The Legend of Zelda": ["Silver Arrow"], + "The Wind Waker": ["Progressive Bow"] + } + }, + { + "count": 3, + "name": "Bow", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Bow"], + "A Link to the Past": ["Progressive Bow", "Bow", "Arrow Upgrade (70)", "Arrow Upgrade (+10)", "Arrow Upgrade (+5)", "Arrows (10)"], + "Links Awakening DX": ["Bow", "Max Arrows Upgrade", "10 Arrows"], + "Majora's Mask Recompiled": ["Progressive Bow"], + "Ocarina of Time": ["Bow"], + "SMZ3": ["Bow", "ArrowUpgrade10", "ArrowUpgrade5"], + "The Legend of Zelda": ["Bow"], + "The Wind Waker": ["Progressive Bow", "Progressive Quiver"] + } + }, + { + "count": 2, + "name": "Boomerang", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Boomerang"], + "A Link to the Past": ["Blue Boomerang", "Red Boomerang"], + "Links Awakening DX": ["Boomerang"], + "Ocarina of Time": ["Boomerang"], + "SMZ3": ["BlueBoomerang", "RedBoomerang"], + "The Legend of Zelda": ["Boomerang", "Magical Boomerang"], + "The Legend of Zelda - Oracle of Ages": ["Boomerang"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Boomerang"], + "The Wind Waker": ["Boomerang"] + } + }, + { + "count": 3, + "name": "Bombs", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Bombs"], + "A Link to the Past": ["Bomb Upgrade (50)", "Bomb Upgrade (+10)", "Bomb Upgrade (+5)", "Bombs (10)"], + "Links Awakening DX": ["Max Bombs Upgrade", "Bombs"], + "Majora's Mask Recompiled": ["Progressive Bomb Bag"], + "Ocarina of Time": ["Bomb Bag"], + "SMZ3": ["BombUpgrade10", "BombUpgrade5"], + "The Legend of Zelda": ["Bombs"], + "The Legend of Zelda - Oracle of Ages": ["Bombs (10)"], + "The Legend of Zelda - Oracle of Seasons": ["Bombs (10)"], + "The Wind Waker": ["Bombs", "Progressive Bomb Bag"], + "TUNIC": ["Firecracker x6", "Firecracker x5", "Firecracker x4", "Firecracker x3"] + } + }, + { + "count": 2, + "name": "Flute", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Bell"], + "A Link to the Past": ["Flute"], + "Links Awakening DX": ["Ocarina"], + "Ocarina of Time": ["Ocarina"], + "SMZ3": ["Flute"], + "The Legend of Zelda": ["Recorder"], + "The Legend of Zelda - Oracle of Ages": ["Moosh's Flute", "Ricky's Flute", "Dimitri's Flute"], + "The Legend of Zelda - Oracle of Seasons": ["Moosh's Flute", "Ricky's Flute", "Dimitri's Flute"], + "The Wind Waker": ["Wind Waker"] + } + }, + { + "count": 1, + "name": "Magic Armor", + "category": [], + "progression": true, + "linklink": { + "Ocarina of Time": ["Double Defense"], + "The Wind Waker": ["Magic Armor"], + "TUNIC": ["DEF Offering"] + } + }, + { + "count": 1, + "name": "Blue Tunic", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Progressive Mail"], + "A Link to the Past": ["Progressive Mail", "Blue Mail"], + "Links Awakening DX": ["Blue Tunic"], + "Ocarina of Time": ["Zora Tunic"], + "SMZ3": ["ProgressiveTunic"], + "The Legend of Zelda": ["Blue Ring"], + "TUNIC": ["DEF Offering"] + } + }, + { + "count": 1, + "name": "Red Tunic", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Progressive Mail"], + "A Link to the Past": ["Progressive Mail", "Red Mail"], + "Links Awakening DX": ["Red Tunic"], + "Ocarina of Time": ["Goron Tunic"], + "SMZ3": ["ProgressiveTunic"], + "The Legend of Zelda": ["Red Ring"], + "TUNIC": ["DEF Offering"] + } + }, + { + "count": 3, + "name": "Strength", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Progressive Glove"], + "A Link to the Past": ["Progressive Glove"], + "Links Awakening DX": ["Progressive Power Bracelet"], + "Majora's Mask Recompiled": ["Goron Mask"], + "Ocarina of Time": ["Progressive Strength Upgrade"], + "SMZ3": ["ProgressiveGlove"], + "The Legend of Zelda": ["Power Bracelet"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Bracelet"], + "The Legend of Zelda - Oracle of Seasons": ["Power Bracelet"], + "The Wind Waker": ["Power Bracelets"], + "TUNIC": ["ATT Offering"] + } + }, + { + "count": 2, + "name": "Hookshot", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Hookshot"], + "A Link to the Past": ["Hookshot"], + "Links Awakening DX": ["Hookshot"], + "Majora's Mask Recompiled": ["Hookshot"], + "Ocarina of Time": ["Progressive Hookshot"], + "SMZ3": ["Hookshot"], + "The Legend of Zelda": ["Stepladder"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Hook"], + "The Legend of Zelda - Oracle of Seasons": ["Magnetic Gloves"], + "The Wind Waker": ["Grappling Hook", "Hookshot"], + "TUNIC": ["Magic Orb"] + } + }, + { + "count": 2, + "name": "Swimming", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Flippers"], + "A Link to the Past": ["Flippers"], + "Links Awakening DX": ["Flippers"], + "Majora's Mask Recompiled": ["Zora Mask"], + "Ocarina of Time": ["Progressive Scale"], + "SMZ3": ["Flippers"], + "The Legend of Zelda": ["Raft"], + "The Legend of Zelda - Oracle of Ages": ["Progressive Flippers"], + "The Legend of Zelda - Oracle of Seasons": ["Flippers", "Swimmer's Ring", "Zora Ring"] + } + }, + { + "count": 2, + "name": "Hammer", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Hammer"], + "A Link to the Past": ["Hammer"], + "Ocarina of Time": ["Megaton Hammer"], + "SMZ3": ["Hammer"], + "The Wind Waker": ["Skull Hammer"] + } + }, + { + "count": 5, + "name": "Bottle", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Premium Milk", "Bottle"], + "A Link to the Past": ["Bottle", "Bottle (Bee)", "Bottle (Blue Potion)", "Bottle (Fairy)", "Bottle (Good Bee)", "Bottle (Green Potion)", "Bottle (Red Potion)"], + "Majora's Mask Recompiled": ["Bottle of Chateau Romani", "Bottle of Red Potion"], + "Ocarina of Time": ["Bottle", "Bottle with Big Poe", "Bottle with Blue Fire", "Bottle with Blue Potion", "Bottle with Bugs", + "Bottle with Fairy", "Bottle with Fish", "Bottle with Green Potion", "Bottle with Milk", "Bottle with Poe", "Bottle with Red Potion"], + "SMZ3": ["Bottle", "BottleWithBee", "BottleWithBluePotion", "BottleWithFairy", "BottleWithGoldBee", "BottleWithGreenPotion", "BottleWithRedPotion"], + "The Wind Waker": ["Empty Bottle"], + "TUNIC": ["Potion Flask"] + } + }, + { + "count": 1, + "name": "Magical Rod", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Cane of Somaria"], + "Links Awakening DX": ["Magic Rod"], + "SMZ3": ["Somaria"], + "The Legend of Zelda": ["Magical Rod"], + "The Legend of Zelda - Oracle of Ages": ["Cane of Somaria"], + "TUNIC": ["Magic Wand"] + } + }, + { + "count": 2, + "name": "Shovel", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Sand Rod"], + "A Link to the Past": ["Shovel"], + "Links Awakening DX": ["Shovel"], + "SMZ3": ["Shovel"], + "The Legend of Zelda - Oracle of Ages": ["Shovel"], + "The Legend of Zelda - Oracle of Seasons": ["Shovel"] + } + }, + { + "count": 1, + "name": "Pegasus Boots", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Pegasus Boots"], + "A Link to the Past": ["Pegasus Boots"], + "Links Awakening DX": ["Pegasus Boots"], + "Ocarina of Time": ["Hover Boots"], + "SMZ3": ["Boots"], + "TUNIC": ["Anklet"] + } + }, + { + "count": 2, + "name": "Magic Meter", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Progressive Bracelet", "Stamina Scroll"], + "A Link to the Past": ["Magic Upgrade (1/2)", "Magic Upgrade (1/4)"], + "Majora's Mask Recompiled": ["Progressive Magic Upgrade"], + "Ocarina of Time": ["Magic Meter"], + "SMZ3": ["HalfMagic"], + "The Wind Waker": ["Progressive Magic Meter"], + "TUNIC": ["MP Offering"] + } + }, + { + "count": 2, + "name": "Lamp", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Lamp"], + "A Link to the Past": ["Lamp"], + "SMZ3": ["Lamp"], + "The Legend of Zelda": ["Candle", "Red Candle"], + "TUNIC": ["Lantern"] + } + }, + { + "count": 1, + "name": "Book", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Book of Mudora"], + "SMZ3": ["Book"], + "The Legend of Zelda": ["Book of Magic"], + "The Legend of Zelda - Oracle of Ages": ["Book of Seals"], + "The Legend of Zelda - Oracle of Seasons": ["Cuccodex"], + "TUNIC": ["Pages 2-3"] + } + }, + { + "count": 1, + "name": "Magic Powder", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Magic Powder"], + "Links Awakening DX": ["Magic Powder"], + "SMZ3": ["Powder"], + "The Legend of Zelda - Oracle of Ages": ["Fairy Powder"] + } + }, + { + "count": 1, + "name": "Mushroom", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Mushroom"], + "Links Awakening DX": ["Toadstool"], + "Majora's Mask Recompiled": ["Mask of Scents"], + "Ocarina of Time": ["Odd Mushroom"], + "SMZ3": ["Mushroom"], + "The Legend of Zelda - Oracle of Seasons": ["Mushroom"], + "TUNIC": ["Hero Relic - MP"] + } + }, + { + "count": 3, + "name": "Magic Spell", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Quake", "Great Spin"], + "A Link to the Past": ["Quake", "Ether", "Bombos"], + "Ocarina of Time": ["Dins Fire", "Farores Wind", "Nayrus Love"], + "SMZ3": ["Quake", "Ether", "Bombos"], + "TUNIC": ["Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"] + } + }, + { + "count": 1, + "name": "Iron Boots", + "category": [], + "progression": true, + "linklink": { + "Ocarina of Time": ["Iron Boots"], + "The Wind Waker": ["Iron Boots"] + } + }, + { + "count": 1, + "name": "Feather", + "category": [], + "progression": true, + "linklink": { + "Links Awakening DX": ["Feather"], + "The Legend of Zelda - Oracle of Ages": ["Feather"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Feather"], + "The Wind Waker": ["Golden Feather"] + } + }, + { + "count": 1, + "name": "Cape", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Cape"], + "SMZ3": ["Cape"], + "The Legend of Zelda": ["Magical Shield"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Feather"] + } + }, + { + "count": 3, + "name": "Slingshot", + "category": [], + "progression": true, + "linklink": { + "Ocarina of Time": ["Slingshot"], + "The Legend of Zelda - Oracle of Ages": ["Seed Shooter"], + "The Legend of Zelda - Oracle of Seasons": ["Progressive Slingshot"], + "TUNIC": ["Gun"] + } + }, + { + "count": 1, + "name": "Membership Card", + "category": [], + "progression": true, + "linklink": { + "Ocarina of Time": ["Gerudo Membership Card"], + "The Legend of Zelda - Oracle of Seasons": ["Member's Card"], + "The Wind Waker": ["Fill-Up Coupon"] + } + }, + { + "count": 3, + "name": "Seed Bag", + "category": [], + "progression": true, + "linklink": { + "Ocarina of Time": ["Deku Nut Capacity"], + "The Legend of Zelda - Oracle of Ages": ["Seed Satchel"], + "The Legend of Zelda - Oracle of Seasons": ["Seed Satchel"], + "The Wind Waker": ["Bait Bag"] + } + }, + { + "count": 1, + "name": "Looking Glass", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Magic Mirror"], + "Links Awakening DX": ["Magnifying Glass"], + "Majora's Mask Recompiled": ["Lens of Truth"], + "Ocarina of Time": ["Lens of Truth"], + "SMZ3": ["Mirror"], + "The Wind Waker": ["Spyglass"] + } + }, + { + "count": 1, + "name": "Letter", + "category": [], + "progression": true, + "linklink": { + "Majora's Mask Recompiled": ["Letter to Kafei"], + "Ocarina of Time": ["Rutos Letter"], + "The Legend of Zelda": ["Letter"], + "The Wind Waker": ["Delivery Bag"], + "TUNIC": ["Pages 0-1"] + } + }, + { + "count": 4, + "name": "Key Jewels", + "category": [], + "progression": true, + "linklink": { + "A Link to the Past": ["Moon Pearl"], + "SMZ3": ["MoonPearl"], + "The Legend of Zelda - Oracle of Ages": ["Slate"], + "The Legend of Zelda - Oracle of Seasons": ["Round Jewel", "Pyramid Jewel", "X-Shaped Jewel", "Square Jewel"], + "The Wind Waker": ["Din's Pearl", "Farore's Pearl", "Nayru's Pearl"], + "TUNIC": ["Red Questagon", "Green Questagon", "Blue Questagon"] + } + }, + { + "count": 3, + "name": "Wallet", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Pouch"], + "Majora's Mask Recompiled": ["Progressive Wallet"], + "Ocarina of Time": ["Progressive Wallet"], + "The Wind Waker": ["Progressive Wallet"], + "TUNIC": ["Effigy"] + } + }, + { + "count": 10, + "name": "Heart Container", + "category": [], + "progression": true, + "linklink": { + "A Link Between Worlds": ["Heart Container", "Piece of Heart", "Heart Piece"], + "A Link to the Past": ["Boss Heart Container", "Piece of Heart"], + "Links Awakening DX": ["Heart Container", "Heart Piece"], + "Majora's Mask Recompiled": ["Heart Container", "Piece of Heart", "Heart Piece"], + "Ocarina of Time": ["Heart Container", "Piece of Heart"], + "SMZ3": ["HeartContainer", "HeartPiece"], + "The Legend of Zelda": ["Heart Container"], + "The Legend of Zelda - Oracle of Ages": ["Heart Container", "Piece of Heart", "Heart Piece"], + "The Legend of Zelda - Oracle of Seasons": ["Heart Container", "Piece of Heart", "Heart Piece"], + "The Wind Waker": ["Heart Container", "Piece of Heart"], + "TUNIC": ["HP Offering", "Potion Flask", "Flask Shard"] + } } - }, - { - "count": 1, - "name": "Biggoron's Sword", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Progressive Sword"], - "SMZ3": ["ProgressiveSword"], - "Ocarina of Time": ["Biggoron Sword"], - "The Legend of Zelda - Oracle of Seasons": ["Biggoron's Sword"], - "The Wind Waker": ["Progressive Sword"], - "TUNIC": ["Sword Upgrade"], - "The Legend of Zelda - Oracle of Ages": ["Biggoron's Sword"], - "Majora's Mask Recompiled": ["Great Fairy Sword"], - "A Link Between Worlds": ["Progressive Sword"] - } - }, - { - "count": 1, - "name": "Shield (Basic)", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Magical Shield"], - "A Link to the Past": ["Progressive Shield", "Blue Shield"], - "SMZ3": ["ProgressiveShield"], - "Links Awakening DX": ["Progressive Shield"], - "Ocarina of Time": ["Deku Shield"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Shield"], - "The Wind Waker": ["Progressive Shield"], - "TUNIC": ["Shield"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Shield"], - "A Link Between Worlds": ["Hylian Shield"] - } - }, - { - "count": 1, - "name": "Shield (Intermediate)", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Magical Shield"], - "A Link to the Past": ["Progressive Shield", "Red Shield"], - "SMZ3": ["ProgressiveShield"], - "Ocarina of Time": ["Hylian Shield"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Shield"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Shield"] - } - }, - { - "count": 1, - "name": "Shield (Mirror)", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Magical Shield"], - "A Link to the Past": ["Progressive Shield", "Mirror Shield"], - "SMZ3": ["ProgressiveShield"], - "Links Awakening DX": ["Progressive Shield"], - "Ocarina of Time": ["Mirror Shield", "Hylian Shield", "Deku Shield"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Shield"], - "The Wind Waker": ["Progressive Shield"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Shield"] - } - }, - { - "count": 2, - "name": "Fire Magic", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Fire Rod"], - "SMZ3": ["Firerod"], - "Ocarina of Time": ["Fire Arrows"], - "The Wind Waker": ["Progressive Bow"], - "TUNIC": ["Glass Cannon"], - "Majora's Mask Recompiled": ["Fire Arrow"], - "A Link Between Worlds": ["Fire Rod"] - } - }, - { - "count": 2, - "name": "Ice Magic", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Ice Rod"], - "SMZ3": ["Icerod"], - "Ocarina of Time": ["Ice Arrows"], - "TUNIC": ["Magic Dagger"], - "Majora's Mask Recompiled": ["Ice Arrow"], - "A Link Between Worlds": ["Ice Rod"] - } - }, - { - "count": 1, - "name": "Light Arrows", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Silver Arrow"], - "A Link to the Past": ["Silver Bow", "Progressive Bow (Alt)"], - "SMZ3": ["SilverArrows"], - "Ocarina of Time": ["Light Arrows"], - "The Wind Waker": ["Progressive Bow"], - "Majora's Mask Recompiled": ["Light Arrow"], - "A Link Between Worlds": ["Bow of Light"] - } - }, - { - "count": 3, - "name": "Bow", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Bow"], - "A Link to the Past": ["Progressive Bow", "Bow", "Arrow Upgrade (70)", "Arrow Upgrade (+10)", "Arrow Upgrade (+5)", "Arrows (10)"], - "SMZ3": ["Bow", "ArrowUpgrade10", "ArrowUpgrade5"], - "Links Awakening DX": ["Bow", "Max Arrows Upgrade", "10 Arrows"], - "Ocarina of Time": ["Bow"], - "The Wind Waker": ["Progressive Bow", "Progressive Quiver"], - "Majora's Mask Recompiled": ["Progressive Bow"], - "A Link Between Worlds": ["Bow"] - } - }, - { - "count": 2, - "name": "Boomerang", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Boomerang", "Magical Boomerang"], - "A Link to the Past": ["Blue Boomerang", "Red Boomerang"], - "SMZ3": ["BlueBoomerang", "RedBoomerang"], - "Links Awakening DX": ["Boomerang"], - "Ocarina of Time": ["Boomerang"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Boomerang"], - "The Wind Waker": ["Boomerang"], - "The Legend of Zelda - Oracle of Ages": ["Boomerang"], - "A Link Between Worlds": ["Boomerang"] - } - }, - { - "count": 3, - "name": "Bombs", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Bombs"], - "A Link to the Past": ["Bomb Upgrade (50)","Bomb Upgrade (+10)", "Bomb Upgrade (+5)", "Bombs (10)"], - "SMZ3": ["BombUpgrade10", "BombUpgrade5"], - "Links Awakening DX": ["Max Bombs Upgrade", "Bombs"], - "Ocarina of Time": ["Bomb Bag"], - "The Legend of Zelda - Oracle of Seasons": ["Bombs (10)"], - "The Wind Waker": ["Bombs", "Progressive Bomb Bag"], - "TUNIC": ["Firecracker x6", "Firecracker x5", "Firecracker x4", "Firecracker x3"], - "The Legend of Zelda - Oracle of Ages": ["Bombs (10)"], - "Majora's Mask Recompiled": ["Progressive Bomb Bag"], - "A Link Between Worlds": ["Bombs"] - } - }, - { - "count": 2, - "name": "Flute", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Recorder"], - "A Link to the Past": ["Flute"], - "SMZ3": ["Flute"], - "Links Awakening DX": ["Ocarina"], - "Ocarina of Time": ["Ocarina"], - "The Legend of Zelda - Oracle of Seasons": ["Moosh's Flute", "Ricky's Flute", "Dimitri's Flute"], - "The Wind Waker": ["Wind Waker"], - "The Legend of Zelda - Oracle of Ages": ["Moosh's Flute", "Ricky's Flute", "Dimitri's Flute"], - "A Link Between Worlds": ["Bell"] - } - }, - { - "count": 1, - "name": "Magic Armor", - "category": [], - "progression": true, - "linklink": { - "Ocarina of Time": ["Double Defense"], - "The Wind Waker": ["Magic Armor"], - "TUNIC": ["DEF Offering"] - } - }, - { - "count": 1, - "name": "Blue Tunic", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Blue Ring"], - "A Link to the Past": ["Progressive Mail", "Blue Mail"], - "SMZ3": ["ProgressiveTunic"], - "Links Awakening DX": ["Blue Tunic"], - "Ocarina of Time": ["Zora Tunic"], - "TUNIC": ["DEF Offering"], - "A Link Between Worlds": ["Progressive Mail"] - } - }, - { - "count": 1, - "name": "Red Tunic", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Red Ring"], - "A Link to the Past": ["Progressive Mail", "Red Mail"], - "SMZ3": ["ProgressiveTunic"], - "Links Awakening DX": ["Red Tunic"], - "Ocarina of Time": ["Goron Tunic"], - "TUNIC": ["DEF Offering"], - "A Link Between Worlds": ["Progressive Mail"] - } - }, - { - "count": 3, - "name": "Strength", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Power Bracelet"], - "A Link to the Past": ["Progressive Glove"], - "SMZ3": ["ProgressiveGlove"], - "Links Awakening DX": ["Progressive Power Bracelet"], - "Ocarina of Time": ["Progressive Strength Upgrade"], - "The Legend of Zelda - Oracle of Seasons": ["Power Bracelet"], - "The Wind Waker": ["Power Bracelets"], - "TUNIC": ["ATT Offering"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Bracelet"], - "Majora's Mask Recompiled": ["Goron Mask"], - "A Link Between Worlds": ["Progressive Glove"] - } - }, - { - "count": 2, - "name": "Hookshot", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Stepladder"], - "A Link to the Past": ["Hookshot"], - "SMZ3": ["Hookshot"], - "Links Awakening DX": ["Hookshot"], - "Ocarina of Time": ["Progressive Hookshot"], - "The Legend of Zelda - Oracle of Seasons": ["Magnetic Gloves"], - "The Wind Waker": ["Grappling Hook", "Hookshot"], - "TUNIC": ["Magic Orb"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Hook"], - "Majora's Mask Recompiled": ["Hookshot"], - "A Link Between Worlds": ["Hookshot"] - } - }, - { - "count": 2, - "name": "Swimming", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Raft"], - "A Link to the Past": ["Flippers"], - "SMZ3": ["Flippers"], - "Links Awakening DX": ["Flippers"], - "Ocarina of Time": ["Progressive Scale"], - "The Legend of Zelda - Oracle of Seasons": ["Flippers", "Swimmer's Ring", "Zora Ring"], - "The Legend of Zelda - Oracle of Ages": ["Progressive Flippers"], - "Majora's Mask Recompiled": ["Zora Mask"], - "A Link Between Worlds": ["Flippers"] - } - }, - { - "count": 2, - "name": "Hammer", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Hammer"], - "SMZ3": ["Hammer"], - "Ocarina of Time": ["Megaton Hammer"], - "The Wind Waker": ["Skull Hammer"], - "A Link Between Worlds": ["Hammer"] - } - }, - { - "count": 5, - "name": "Bottle", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Bottle","Bottle (Bee)","Bottle (Blue Potion)","Bottle (Fairy)","Bottle (Good Bee)","Bottle (Green Potion)","Bottle (Red Potion)"], - "SMZ3": ["Bottle","BottleWithBee","BottleWithBluePotion","BottleWithFairy","BottleWithGoldBee","BottleWithGreenPotion","BottleWithRedPotion"], - "Ocarina of Time": ["Bottle","Bottle with Big Poe","Bottle with Blue Fire","Bottle with Blue Potion","Bottle with Bugs","Bottle with Fairy","Bottle with Fish","Bottle with Green Potion","Bottle with Milk","Bottle with Poe","Bottle with Red Potion"], - "The Wind Waker": ["Empty Bottle"], - "TUNIC": ["Potion Flask"], - "Majora's Mask Recompiled": ["Bottle of Chateau Romani", "Bottle of Red Potion"], - "A Link Between Worlds": ["Premium Milk", "Bottle"] - } - }, - { - "count": 1, - "name": "Magical Rod", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Magical Rod"], - "A Link to the Past": ["Cane of Somaria"], - "SMZ3": ["Somaria"], - "Links Awakening DX": ["Magic Rod"], - "TUNIC": ["Magic Wand"], - "The Legend of Zelda - Oracle of Ages": ["Cane of Somaria"] - } - }, - { - "count": 2, - "name": "Shovel", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Shovel"], - "SMZ3": ["Shovel"], - "Links Awakening DX": ["Shovel"], - "The Legend of Zelda - Oracle of Seasons": ["Shovel"], - "The Legend of Zelda - Oracle of Ages": ["Shovel"], - "A Link Between Worlds": ["Sand Rod"] - } - }, - { - "count": 1, - "name": "Pegasus Boots", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Pegasus Boots"], - "SMZ3": ["Boots"], - "Links Awakening DX": ["Pegasus Boots"], - "Ocarina of Time": ["Hover Boots"], - "TUNIC": ["Anklet"], - "A Link Between Worlds": ["Pegasus Boots"] - } - }, - { - "count": 2, - "name": "Magic Meter", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Magic Upgrade (1/2)", "Magic Upgrade (1/4)"], - "Ocarina of Time": ["Magic Meter"], - "The Wind Waker": ["Progressive Magic Meter"], - "SMZ3": ["HalfMagic"], - "TUNIC": ["MP Offering"], - "Majora's Mask Recompiled": ["Progressive Magic Upgrade"], - "A Link Between Worlds": ["Progressive Bracelet", "Stamina Scroll"] - } - }, - { - "count": 2, - "name": "Lamp", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Candle", "Red Candle"], - "A Link to the Past": ["Lamp"], - "SMZ3": ["Lamp"], - "TUNIC": ["Lantern"], - "A Link Between Worlds": ["Lamp"] - } - }, - { - "count": 1, - "name": "Book", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Book of Magic"], - "A Link to the Past": ["Book of Mudora"], - "SMZ3": ["Book"], - "The Legend of Zelda - Oracle of Seasons": ["Cuccodex"], - "TUNIC": ["Pages 2-3"], - "The Legend of Zelda - Oracle of Ages": ["Book of Seals"] - } - }, - { - "count": 1, - "name": "Magic Powder", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Magic Powder"], - "SMZ3": ["Powder"], - "Links Awakening DX": ["Magic Powder"], - "The Legend of Zelda - Oracle of Ages": ["Fairy Powder"] - } - }, - { - "count": 1, - "name": "Mushroom", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Mushroom"], - "SMZ3": ["Mushroom"], - "Links Awakening DX": ["Toadstool"], - "Ocarina of Time": ["Odd Mushroom"], - "The Legend of Zelda - Oracle of Seasons": ["Mushroom"], - "TUNIC": ["Hero Relic - MP"], - "Majora's Mask Recompiled": ["Mask of Scents"] - } - }, - { - "count": 3, - "name": "Magic Spell", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Quake", "Ether", "Bombos"], - "SMZ3": ["Quake", "Ether", "Bombos"], - "Ocarina of Time": ["Dins Fire", "Farores Wind", "Nayrus Love"], - "TUNIC": ["Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"], - "A Link Between Worlds": ["Quake", "Great Spin"] - } - }, - { - "count": 1, - "name": "Iron Boots", - "category": [], - "progression": true, - "linklink": { - "Ocarina of Time": ["Iron Boots"], - "The Wind Waker": ["Iron Boots"] - } - }, - { - "count": 1, - "name": "Feather", - "category": [], - "progression": true, - "linklink": { - "Links Awakening DX": ["Feather"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Feather"], - "The Wind Waker": ["Golden Feather"], - "The Legend of Zelda - Oracle of Ages": ["Feather"] - } - }, - { - "count": 1, - "name": "Cape", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Magical Shield"], - "A Link to the Past": ["Cape"], - "SMZ3": ["Cape"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Feather"] - } - }, - { - "count": 3, - "name": "Slingshot", - "category": [], - "progression": true, - "linklink": { - "Ocarina of Time": ["Slingshot"], - "The Legend of Zelda - Oracle of Seasons": ["Progressive Slingshot"], - "TUNIC": ["Gun"], - "The Legend of Zelda - Oracle of Ages": ["Seed Shooter"] - } - }, - { - "count": 1, - "name": "Membership Card", - "category": [], - "progression": true, - "linklink": { - "Ocarina of Time": ["Gerudo Membership Card"], - "The Legend of Zelda - Oracle of Seasons": ["Member's Card"], - "The Wind Waker": ["Fill-Up Coupon"] - } - }, - { - "count": 3, - "name": "Seed Bag", - "category": [], - "progression": true, - "linklink": { - "Ocarina of Time": ["Deku Nut Capacity"], - "The Legend of Zelda - Oracle of Seasons": ["Seed Satchel"], - "The Wind Waker": ["Bait Bag"], - "The Legend of Zelda - Oracle of Ages": ["Seed Satchel"] - } - }, - { - "count": 1, - "name": "Looking Glass", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Magic Mirror"], - "SMZ3": ["Mirror"], - "Links Awakening DX": ["Magnifying Glass"], - "Ocarina of Time": ["Lens of Truth"], - "The Wind Waker": ["Spyglass"], - "Majora's Mask Recompiled": ["Lens of Truth"] - } - }, - { - "count": 1, - "name": "Letter", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Letter"], - "Ocarina of Time": ["Rutos Letter"], - "The Wind Waker": ["Delivery Bag"], - "TUNIC": ["Pages 0-1"], - "Majora's Mask Recompiled": ["Letter to Kafei"] - } - }, - { - "count": 4, - "name": "Key Jewels", - "category": [], - "progression": true, - "linklink": { - "A Link to the Past": ["Moon Pearl"], - "SMZ3": ["MoonPearl"], - "The Legend of Zelda - Oracle of Seasons": ["Round Jewel", "Pyramid Jewel", "X-Shaped Jewel", "Square Jewel"], - "The Wind Waker": ["Din's Pearl", "Farore's Pearl", "Nayru's Pearl"], - "TUNIC": ["Red Questagon", "Green Questagon", "Blue Questagon"], - "The Legend of Zelda - Oracle of Ages": ["Slate"] - } - }, - { - "count": 3, - "name": "Wallet", - "category": [], - "progression": true, - "linklink": { - "Ocarina of Time": ["Progressive Wallet"], - "The Wind Waker": ["Progressive Wallet"], - "TUNIC": ["Effigy"], - "Majora's Mask Recompiled": ["Progressive Wallet"], - "A Link Between Worlds": ["Pouch"] - } - }, - { - "count": 10, - "name": "Heart Container", - "category": [], - "progression": true, - "linklink": { - "The Legend of Zelda": ["Heart Container"], - "A Link to the Past": ["Boss Heart Container", "Piece of Heart"], - "SMZ3": ["HeartContainer", "HeartPiece"], - "Links Awakening DX": ["Heart Container", "Heart Piece"], - "Ocarina of Time": ["Heart Container", "Piece of Heart"], - "The Legend of Zelda - Oracle of Seasons": ["Heart Container", "Piece of Heart", "Heart Piece"], - "The Wind Waker": ["Heart Container", "Piece of Heart"], - "TUNIC": ["HP Offering", "Potion Flask", "Flask Shard"], - "The Legend of Zelda - Oracle of Ages": ["Heart Container", "Piece of Heart", "Heart Piece"], - "Majora's Mask Recompiled": ["Heart Container", "Piece of Heart", "Heart Piece"], - "A Link Between Worlds": ["Heart Container", "Piece of Heart", "Heart Piece"] - } - } -] + ] +} diff --git a/data/items.schema.json b/data/items.schema.json new file mode 100644 index 0000000..decbfb1 --- /dev/null +++ b/data/items.schema.json @@ -0,0 +1,165 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/ManualForArchipelago/Manual/main/schemas/Manual.items.schema.json", + "description": "Schema for Manual's items.json", + "type": ["array", "object"], + "items": { + "$ref": "#/definitions/Item" + }, + "properties": { + "$schema": { + "type":"string", + "description": "The schema to verify this document against." + }, + "data": { + "description": "List of Items", + "type": "array", + "items": {"$ref": "#/definitions/Item"} + }, + "_comment": {"$ref": "#/definitions/comment"} + }, + "definitions": { + "linklink": { + "type": "object", + "description": "Name of this options Presets", + "properties": { + "A Link Between Worlds": {"description": "A Link Between Worlds", "$ref": "#/definitions/linklink_list", "loz": true}, + "A Link to the Past": {"description": "A Link to the Past", "$ref": "#/definitions/linklink_list", "loz": true}, + "Links Awakening DX": {"description": "Links Awakening DX", "$ref": "#/definitions/linklink_list", "loz": true}, + "Majora's Mask Recompiled": {"description": "Majora's Mask Recompiled", "$ref": "#/definitions/linklink_list", "loz": true}, + "Ocarina of Time": {"description": "Ocarina of Time", "$ref": "#/definitions/linklink_list", "loz": true}, + "Skyward Sword": {"description": "Skyward Sword", "$ref": "#/definitions/linklink_list", "loz": true}, + "SMZ3": {"description": "SMZ3", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Legend of Zelda - Oracle of Ages": {"description": "The Legend of Zelda - Oracle of Ages", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Legend of Zelda - Oracle of Seasons": {"description": "The Legend of Zelda - Oracle of Seasons", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Legend of Zelda - Phantom Hourglass": {"description": "The Legend of Zelda - Phantom Hourglass", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Legend of Zelda - Spirit Tracks": {"description": "The Legend of Zelda - Spirit Tracks", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Legend of Zelda": {"description": "The Legend of Zelda", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Minish Cap": {"description": "The Minish Cap", "$ref": "#/definitions/linklink_list", "loz": true}, + "The Wind Waker": {"description": "The Wind Waker", "$ref": "#/definitions/linklink_list", "loz": true}, + "Twilight Princess": {"description": "Twilight Princess", "$ref": "#/definitions/linklink_list", "loz": true}, + "A Hat in Time": {"description": "A Hat in Time", "$ref": "#/definitions/linklink_list"}, + "ANIMAL WELL": {"description": "ANIMAL WELL", "$ref": "#/definitions/linklink_list"}, + "Hollow Knight": {"description": "Hollow Knight", "$ref": "#/definitions/linklink_list"}, + "Minecraft": {"description": "Minecraft", "$ref": "#/definitions/linklink_list"}, + "Ori and the Blind Forest": {"description": "Ori and the Blind Forest", "$ref": "#/definitions/linklink_list"}, + "Ori and the Will of the Wisps": {"description": "Ori and the Will of the Wisps", "$ref": "#/definitions/linklink_list"}, + "Outer Wilds": {"description": "Outer Wilds", "$ref": "#/definitions/linklink_list"}, + "TUNIC": {"description": "TUNIC", "$ref": "#/definitions/linklink_list"} + }, + "patternProperties": { + "^.*$": { + "anyOf": [ + { + "description": "A game to be added to the schema", + "type": "array" + } + ] + } + } + }, + "linklink_list": { + "type": "array", + "items": {"type":"string"} + + }, + "Item": { + "type": "object", + "properties": { + "name": { + "description": "The unique name of the item. Do not use () or : in the name", + "type": "string" + }, + "linklink": {"$ref": "#/definitions/linklink"}, + "category": { + "description": "(Optional) A list of categories to be applied to this item.", + "type": ["string", "array"], + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "count": { + "description": "(Optional) Total number of this item that will be in the itempool for randomization.", + "type": ["string", "integer"], + "pattern": "^[0-9]+$", + "default": 1 + }, + "extra": { + "description": "(Optional) Additional copies of the item that will not generate linklink locations.", + "type": ["string", "integer"], + "pattern": "^[0-9]+$", + "default": 0 + }, + "value": { + "description": "(Optional) A dictionary of values this item has in the format {\"name\": int,\"otherName\": int} \nUsed with the {ItemValue(Name: int)} in location requires \neg. \"value\": {\"coins\":10} mean this item is worth 10 coins", + "type": "object", + "patternProperties": { + "^.+$": { + "anyOf": [ + { + "type": "integer", + "description": "A value that this item has must be 'name':integer \neg. \"coins\": 10" + } + ] + } + } + }, + "progression": { + "description": "(Optional) Is this item needed to unlock locations? For more information on item classifications, see: https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#items", + "type": "boolean", + "default": false + }, + "progression_skip_balancing": { + "description": "(Optional) Should this item not get included in progression balance swaps? For more information on item classifications, see: https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#items", + "type": "boolean", + "default": false + }, + "useful": { + "description": "(Optional) Is this item useful to have but not required to complete the game? For more information on item classifications, see: https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#items", + "type": "boolean", + "default": false + }, + "trap": { + "description": "(Optional) Is this item something the player doesn't want to get? For more information on item classifications, see: https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#items", + "type": "boolean", + "default": false + }, + "filler": { + "description": "(Optional) Is this item mostly useless and okay to skip placing sometimes? For more information on item classifications, see: https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#items", + "type": "boolean", + "default": true + }, + "early": { + "description": "(Optional) How many copies of this item are required to be placed somewhere accessible from the start (Sphere 1) \nChoosing 'True' mark all of them to be early", + "type": ["boolean", "integer"], + "default": false + }, + "local": { + "description": "(Optional) Are all copies of this item supposed to be only in your locations (true), or can they be anywhere (false)?", + "type": "boolean", + "default": false + }, + "local_early": { + "description": "(Optional) How many copies of this item (or 'true' if all copies) are supposed to be early and only in your locations. \nCan be used to mark some of the copies of an item to be early and local since 'local' is a toggle between none or all of them.", + "type": ["boolean", "integer"], + "default": false + }, + "id": { + "description": "(Optional) Skips the item ID forward to the given value.\nThis can be used to provide buffer space for future items.", + "type": "integer" + }, + "_comment": {"$ref": "#/definitions/comment"} + }, + "required": ["name"] + }, + "comment": { + "description": "(Optional) Does nothing, Its mainly here for Dev notes for future devs to understand your logic", + "type": ["string", "array"], + "items": { + "description": "A line of comment", + "type": "string" + } + } + } +} From 4cce0e3fee6d7ac54dd1d4dc41882699495d2c80 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:16:37 +1100 Subject: [PATCH 03/15] Sort the other data files --- data/Sort-Items-linklink-data.py | 55 +- data/items.json | 2 +- data/items_kh.json | 952 ++++++++++++++++--------------- data/items_pkmn.json | 289 +++++----- 4 files changed, 656 insertions(+), 642 deletions(-) diff --git a/data/Sort-Items-linklink-data.py b/data/Sort-Items-linklink-data.py index f5b948f..4439c0e 100644 --- a/data/Sort-Items-linklink-data.py +++ b/data/Sort-Items-linklink-data.py @@ -1,6 +1,8 @@ -import json, re +import json +import re from os import path from typing import cast + def repl_func(match: re.Match): result = " ".join(match.group().split()) parts = result.split('",') @@ -27,8 +29,9 @@ def load_data_file(fname: str) -> dict: try: with open(fpath, 'r', encoding="utf-8-sig") as f: filedata = json.load(f) - except: - filedata = {} + except Exception: + import yaml + filedata = yaml.safe_load(open(fpath, 'r', encoding="utf-8-sig")) return filedata @@ -44,26 +47,32 @@ def write_data_file(fname: str, data: dict): if __name__ == '__main__': schema = load_data_file('items.schema.json') - itemsjson = load_data_file('items.json') - if schema and itemsjson: - # First get the keys from schema: - schema_linklink = schema.get('definitions', {}).get('linklink', {}).get("properties", None) - if schema_linklink is None: - raise ValueError("items.schema.json doesn't contain a known definition of the linklink data") - schema_linklink = cast(dict[str, dict[str, str | bool]], schema_linklink) + for filename in ['items.json', 'items_pkmn.json', 'items_kh.json']: + itemsjson = load_data_file(filename) + if isinstance(itemsjson, list): + itemsjson = {"$schema": "items.schema.json","data": itemsjson} - known_loz_keys: list[str] = [] - for key, value in schema_linklink.items(): - if value.get("loz", False): - known_loz_keys.append(key) + if schema and itemsjson: + # First get the keys from schema: + schema_linklink = schema.get('definitions', {}).get('linklink', {}).get("properties", None) + if schema_linklink is None: + raise ValueError("items.schema.json doesn't contain a known definition of the linklink data") + schema_linklink = cast(dict[str, dict[str, str | bool]], schema_linklink) - # Second: sort all the item's linklink properties by placing the zelda ones first - for item in itemsjson.get("data", []): - item = cast(dict[str, dict[str, list[str]]], item) - if not item.get("linklink"): - continue - else: - item["linklink"] = dict(sorted(item["linklink"].items(), key=lambda item: (0 if item[0] in known_loz_keys else 1, item[0]))) + known_loz_keys: list[str] = [] + for key, value in schema_linklink.items(): + if value.get("loz", False): + known_loz_keys.append(key) - # Third and finally write the change to the file - write_data_file('items.json', itemsjson) + # Second: sort all the item's linklink properties by placing the zelda ones first + for item in itemsjson.get("data", []): + item = cast(dict[str, dict[str, list[str]]], item) + if not item.get("linklink"): + continue + else: + item["linklink"] = dict(sorted(item["linklink"].items(), key=lambda item: (0 if item[0] in known_loz_keys else 1, item[0]))) + + # Third and finally write the change to the file + write_data_file(filename, itemsjson) + else: + raise ValueError(f"Failed to load data from items.schema.json or {filename}") diff --git a/data/items.json b/data/items.json index b3b6f88..8157aea 100644 --- a/data/items.json +++ b/data/items.json @@ -590,4 +590,4 @@ } } ] -} +} \ No newline at end of file diff --git a/data/items_kh.json b/data/items_kh.json index 6ad1559..60a335a 100644 --- a/data/items_kh.json +++ b/data/items_kh.json @@ -1,476 +1,478 @@ - -[ - { - "count": 3, - "name": "Cure", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Cure"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Cure","Sleight Cura","Sleight Curaga"], - "Kingdom Hearts 2": ["Cure Element"], - "Kingdom Hearts Birth by Sleep": ["Cure","Cura","Curaga"] - } - }, - { - "count": 3, - "name": "Fire", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Fire"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Fire","Sleight Fira","Sleight Firaga"], - "Kingdom Hearts 2": ["Fire Element"], - "Kingdom Hearts Birth by Sleep": ["Fire","Fira","Firaga"] - } - }, - { - "count": 3, - "name": "Blizzard", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Blizzard"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Blizzard","Sleight Blizzara","Sleight Blizzaga"], - "Kingdom Hearts 2": ["Blizzard Element"], - "Kingdom Hearts Birth by Sleep": ["Blizzard","Blizzara","Blizzaga"] - } - }, - { - "count": 3, - "name": "Thunder", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Thunder"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Thunder","Sleight Thundara","Sleight Thundaga"], - "Kingdom Hearts 2": ["Thunder Element"], - "Kingdom Hearts Birth by Sleep": ["Thunder","Thundara","Thundaga"] - } - }, - { - "count": 3, - "name": "Gravity", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Gravity"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Gravity","Sleight Gravira","Sleight Graviga"], - "Kingdom Hearts Birth by Sleep": ["Zero Gravity","Zero Gravira","Zero Graviga"] - } - }, - { - "count": 3, - "name": "Stop", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Stop"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Stop","Sleight Stopra","Sleight Stopga"], - "Kingdom Hearts Birth by Sleep": ["Stop","Stopra","Stopga"] - } - }, - { - "count": 3, - "name": "Aero+Reflect", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Aero"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Aero","Sleight Aerora","Sleight Aeroga"], - "Kingdom Hearts 2": ["Reflect Element"], - "Kingdom Hearts Birth by Sleep": ["Aero","Aerora","Aeroga"] - } - }, - { - "count": 3, - "name": "Magnet", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts 2": ["Magnet Element"], - "Kingdom Hearts Birth by Sleep": ["Magnet","Magnera","Magnega"] - } - }, - { - "count": 4, - "name": "High Jump", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["High Jump"], - "Kingdom Hearts 2": ["High Jump"], - "Kingdom Hearts Birth by Sleep": ["High Jump","Doubleflight"] - } - }, - { - "count": 4, - "name": "Dodge Roll", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Dodge Roll"], - "Kingdom Hearts 2": ["Dodge Roll"], - "Kingdom Hearts Birth by Sleep": ["Thunder Roll","Firewheel"] - } - }, - { - "count": 4, - "name": "Glide", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Progressive Glide"], - "Kingdom Hearts 2": ["Glide"], - "Kingdom Hearts Birth by Sleep": ["Glide","Fire Glide","Superglide"] - } - }, - { - "count": 4, - "name": "Quick Run + Slide", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts 2": ["Quick Run"], - "Kingdom Hearts Birth by Sleep": ["Air Slide","Ice Slide"] - } - }, - { - "count": 1, - "name": "Scan", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Scan"], - "Kingdom Hearts 2": ["Scan"], - } - }, - { - "count": 1, - "name": "Leaf Bracer", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Leaf Bracer"], - "Kingdom Hearts 2": ["Leaf Bracer"], - "Kingdom Hearts Birth by Sleep": ["Leaf Bracer"] - } - }, - { - "count": 1, - "name": "Second Chance", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Second Chance"], - "Kingdom Hearts 2": ["Second Chance"], - "Kingdom Hearts Birth by Sleep": ["Second Chance"] - } - }, - { - "count": 1, - "name": "Once More", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts 2": ["Once More"], - "Kingdom Hearts Birth by Sleep": ["Once More"] - } - }, - { - "count": 1, - "name": "Guard", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Guard+Barrier"], - "Kingdom Hearts 2": ["Guard"], - "Kingdom Hearts Birth by Sleep": ["Renewal Barrier"] - } - }, - { - "count": 1, - "name": "Aerial Recovery", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts 2": ["Aerial Recovery"], - "Kingdom Hearts Birth by Sleep": ["Aerial Recovery"] - } - }, - { - "count": 1, - "name": "Blitz", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Blitz"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Blitz"], - "Kingdom Hearts Birth by Sleep": ["Blitz"] - } - }, - { - "count": 1, - "name": "Sliding Dash", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Sliding Dash"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Sliding Dash"], - "Kingdom Hearts 2": ["Slide Dash"], - "Kingdom Hearts Birth by Sleep": ["Sliding Dash"] - } - }, - { - "count": 1, - "name": "Sonic Blade+Limit", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Sonic Blade"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Sonic Blade"], - "Kingdom Hearts 2": ["Limit Form"], - "Kingdom Hearts Birth by Sleep": ["Sonic Blade"] - } - }, - { - "count": 1, - "name": "Ars Arcanum+Valor", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Ars Arcanum"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Ars Arcanum"], - "Kingdom Hearts 2": ["Valor Form"], - "Kingdom Hearts Birth by Sleep": ["Ars Arcanum"] - } - }, - { - "count": 1, - "name": "Strike Raid+Master", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Strike Raid"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Strike Raid"], - "Kingdom Hearts 2": ["Master Form"], - "Kingdom Hearts Birth by Sleep": ["Strike Raid"] - } - }, - { - "count": 1, - "name": "Ragnarok+Wisdom", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Ragnarok"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Ragnarok"], - "Kingdom Hearts 2": ["Wisdom Form"], - "Kingdom Hearts Birth by Sleep": ["Ragnarok"] - } - }, - { - "count": 1, - "name": "Zantetsuken", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Zantetsuken"], - "Kingdom Hearts RE Chain of Memories": ["Sleight Zantetsuken"], - "Kingdom Hearts Birth by Sleep": ["Zantetsuken"] - } - }, - { - "count": 1, - "name": "Simba", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Simba"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Simba"] - } - }, - { - "count": 1, - "name": "Genie", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Genie"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Genie"], - "Kingdom Hearts 2": ["Genie"], - } - }, - { - "count": 1, - "name": "Bambi", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Bambi"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Bambi"] - } - }, - { - "count": 1, - "name": "Dumbo", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Dumbo"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Dumbo"] - } - }, - { - "count": 1, - "name": "Tinker Bell+Peter Pan", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Tinker Bell"], - "Kingdom Hearts RE Chain of Memories": ["Card Set Tinker Bell"], - "Kingdom Hearts 2": ["Peter Pan"], - "Kingdom Hearts Birth by Sleep": ["Peter Pan D-Link"] - } - }, - { - "count": 1, - "name": "Stitch", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts 2": ["Stitch"], - "Kingdom Hearts Birth by Sleep": ["Experiment 626 D-Link"] - } - }, - { - "count": 2, - "name": "Destiny Islands", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Destiny Islands"], - "Kingdom Hearts RE Chain of Memories": ["World Card Destiny Islands","Key to Rewards Destiny Islands"] - } - }, - { - "count": 2, - "name": "Wonderland", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Wonderland"], - "Kingdom Hearts RE Chain of Memories": ["World Card Wonderland","Key to Rewards Wonderland"] - } - }, - { - "count": 2, - "name": "Olympus Coliseum", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Olympus Coliseum"], - "Kingdom Hearts RE Chain of Memories": ["World Card Olympus Coliseum","Key to Rewards Olympus Coliseum"], - "Kingdom Hearts 2": ["Battlefields of War"], - "Kingdom Hearts Birth by Sleep": ["Olympus Coliseum","Zack D-Link"] - } - }, - { - "count": 2, - "name": "Agrabah", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Agrabah"], - "Kingdom Hearts RE Chain of Memories": ["World Card Agrabah","Key to Rewards Agrabah"], - "Kingdom Hearts 2": ["Scimitar"] - } - }, - { - "count": 2, - "name": "Monstro+Mirage Arena", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Monstro"], - "Kingdom Hearts RE Chain of Memories": ["World Card Monstro","Key to Rewards Monstro"], - "Kingdom Hearts Birth by Sleep": ["Mirage Arena"] - } - }, - { - "count": 2, - "name": "Halloween Town", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Halloween Town"], - "Kingdom Hearts RE Chain of Memories": ["World Card Halloween Town","Key to Rewards Halloween Town"], - "Kingdom Hearts 2": ["Bone Fist"] - } - }, - { - "count": 2, - "name": "Atlantica", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Atlantica"], - "Kingdom Hearts RE Chain of Memories": ["World Card Atlantica","Key to Rewards Atlantica"] - } - }, - { - "count": 2, - "name": "Neverland", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Neverland"], - "Kingdom Hearts RE Chain of Memories": ["World Card Neverland","Key to Rewards Neverland"], - "Kingdom Hearts Birth by Sleep": ["Never Land"] - } - }, - { - "count": 2, - "name": "Radiant Garden", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Hollow Bastion"], - "Kingdom Hearts RE Chain of Memories": ["World Card Hollow Bastion","Key to Rewards Hollow Bastion"], - "Kingdom Hearts 2": ["Membership Card"], - "Kingdom Hearts Birth by Sleep": ["Radiant Garden","Maleficent D-Link"] - } - }, - { - "count": 3, - "name": "Twilight Town+ Mysterious Tower", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts RE Chain of Memories": ["World Card Twilight Town","Key to Rewards Twilight Town"], - "Kingdom Hearts 2": ["Ice Cream"], - "Kingdom Hearts Birth by Sleep": ["The Mysterious Tower","Donald D-Link","Goofy D-Link"] - } - }, - { - "count": 2, - "name": "Disney Castle Town", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts 2": ["Disney Castle Key"], - "Kingdom Hearts Birth by Sleep": ["Disney Town","Mickey D-Link"] - } - }, - { - "count": 5, - "name": "Torn Page", - "category": [], - "progression": true, - "linklink": { - "Kingdom Hearts": ["Torn Page"], - "Kingdom Hearts RE Chain of Memories": ["Key to Rewards Traverse Town","Key to Rewards Castle Oblivion","World Card 100 Acre Wood "], - "Kingdom Hearts 2": ["Torn Page"] +{ + "$schema": "items.schema.json", + "data": [ + { + "count": 3, + "name": "Cure", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Cure"], + "Kingdom Hearts 2": ["Cure Element"], + "Kingdom Hearts Birth by Sleep": ["Cure", "Cura", "Curaga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Cure", "Sleight Cura", "Sleight Curaga"] + } + }, + { + "count": 3, + "name": "Fire", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Fire"], + "Kingdom Hearts 2": ["Fire Element"], + "Kingdom Hearts Birth by Sleep": ["Fire", "Fira", "Firaga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Fire", "Sleight Fira", "Sleight Firaga"] + } + }, + { + "count": 3, + "name": "Blizzard", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Blizzard"], + "Kingdom Hearts 2": ["Blizzard Element"], + "Kingdom Hearts Birth by Sleep": ["Blizzard", "Blizzara", "Blizzaga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Blizzard", "Sleight Blizzara", "Sleight Blizzaga"] + } + }, + { + "count": 3, + "name": "Thunder", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Thunder"], + "Kingdom Hearts 2": ["Thunder Element"], + "Kingdom Hearts Birth by Sleep": ["Thunder", "Thundara", "Thundaga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Thunder", "Sleight Thundara", "Sleight Thundaga"] + } + }, + { + "count": 3, + "name": "Gravity", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Gravity"], + "Kingdom Hearts Birth by Sleep": ["Zero Gravity", "Zero Gravira", "Zero Graviga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Gravity", "Sleight Gravira", "Sleight Graviga"] + } + }, + { + "count": 3, + "name": "Stop", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Stop"], + "Kingdom Hearts Birth by Sleep": ["Stop", "Stopra", "Stopga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Stop", "Sleight Stopra", "Sleight Stopga"] + } + }, + { + "count": 3, + "name": "Aero+Reflect", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Aero"], + "Kingdom Hearts 2": ["Reflect Element"], + "Kingdom Hearts Birth by Sleep": ["Aero", "Aerora", "Aeroga"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Aero", "Sleight Aerora", "Sleight Aeroga"] + } + }, + { + "count": 3, + "name": "Magnet", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Magnet Element"], + "Kingdom Hearts Birth by Sleep": ["Magnet", "Magnera", "Magnega"] + } + }, + { + "count": 4, + "name": "High Jump", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["High Jump"], + "Kingdom Hearts 2": ["High Jump"], + "Kingdom Hearts Birth by Sleep": ["High Jump", "Doubleflight"] + } + }, + { + "count": 4, + "name": "Dodge Roll", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Dodge Roll"], + "Kingdom Hearts 2": ["Dodge Roll"], + "Kingdom Hearts Birth by Sleep": ["Thunder Roll", "Firewheel"] + } + }, + { + "count": 4, + "name": "Glide", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Progressive Glide"], + "Kingdom Hearts 2": ["Glide"], + "Kingdom Hearts Birth by Sleep": ["Glide", "Fire Glide", "Superglide"] + } + }, + { + "count": 4, + "name": "Quick Run + Slide", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Quick Run"], + "Kingdom Hearts Birth by Sleep": ["Air Slide", "Ice Slide"] + } + }, + { + "count": 1, + "name": "Scan", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Scan"], + "Kingdom Hearts 2": ["Scan"] + } + }, + { + "count": 1, + "name": "Leaf Bracer", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Leaf Bracer"], + "Kingdom Hearts 2": ["Leaf Bracer"], + "Kingdom Hearts Birth by Sleep": ["Leaf Bracer"] + } + }, + { + "count": 1, + "name": "Second Chance", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Second Chance"], + "Kingdom Hearts 2": ["Second Chance"], + "Kingdom Hearts Birth by Sleep": ["Second Chance"] + } + }, + { + "count": 1, + "name": "Once More", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Once More"], + "Kingdom Hearts Birth by Sleep": ["Once More"] + } + }, + { + "count": 1, + "name": "Guard", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Guard+Barrier"], + "Kingdom Hearts 2": ["Guard"], + "Kingdom Hearts Birth by Sleep": ["Renewal Barrier"] + } + }, + { + "count": 1, + "name": "Aerial Recovery", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Aerial Recovery"], + "Kingdom Hearts Birth by Sleep": ["Aerial Recovery"] + } + }, + { + "count": 1, + "name": "Blitz", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Blitz"], + "Kingdom Hearts Birth by Sleep": ["Blitz"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Blitz"] + } + }, + { + "count": 1, + "name": "Sliding Dash", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Sliding Dash"], + "Kingdom Hearts 2": ["Slide Dash"], + "Kingdom Hearts Birth by Sleep": ["Sliding Dash"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Sliding Dash"] + } + }, + { + "count": 1, + "name": "Sonic Blade+Limit", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Sonic Blade"], + "Kingdom Hearts 2": ["Limit Form"], + "Kingdom Hearts Birth by Sleep": ["Sonic Blade"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Sonic Blade"] + } + }, + { + "count": 1, + "name": "Ars Arcanum+Valor", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Ars Arcanum"], + "Kingdom Hearts 2": ["Valor Form"], + "Kingdom Hearts Birth by Sleep": ["Ars Arcanum"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Ars Arcanum"] + } + }, + { + "count": 1, + "name": "Strike Raid+Master", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Strike Raid"], + "Kingdom Hearts 2": ["Master Form"], + "Kingdom Hearts Birth by Sleep": ["Strike Raid"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Strike Raid"] + } + }, + { + "count": 1, + "name": "Ragnarok+Wisdom", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Ragnarok"], + "Kingdom Hearts 2": ["Wisdom Form"], + "Kingdom Hearts Birth by Sleep": ["Ragnarok"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Ragnarok"] + } + }, + { + "count": 1, + "name": "Zantetsuken", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Zantetsuken"], + "Kingdom Hearts Birth by Sleep": ["Zantetsuken"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Zantetsuken"] + } + }, + { + "count": 1, + "name": "Simba", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Simba"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Simba"] + } + }, + { + "count": 1, + "name": "Genie", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Genie"], + "Kingdom Hearts 2": ["Genie"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Genie"] + } + }, + { + "count": 1, + "name": "Bambi", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Bambi"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Bambi"] + } + }, + { + "count": 1, + "name": "Dumbo", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Dumbo"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Dumbo"] + } + }, + { + "count": 1, + "name": "Tinker Bell+Peter Pan", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Tinker Bell"], + "Kingdom Hearts 2": ["Peter Pan"], + "Kingdom Hearts Birth by Sleep": ["Peter Pan D-Link"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Tinker Bell"] + } + }, + { + "count": 1, + "name": "Stitch", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Stitch"], + "Kingdom Hearts Birth by Sleep": ["Experiment 626 D-Link"] + } + }, + { + "count": 2, + "name": "Destiny Islands", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Destiny Islands"], + "Kingdom Hearts RE Chain of Memories": ["World Card Destiny Islands", "Key to Rewards Destiny Islands"] + } + }, + { + "count": 2, + "name": "Wonderland", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Wonderland"], + "Kingdom Hearts RE Chain of Memories": ["World Card Wonderland", "Key to Rewards Wonderland"] + } + }, + { + "count": 2, + "name": "Olympus Coliseum", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Olympus Coliseum"], + "Kingdom Hearts 2": ["Battlefields of War"], + "Kingdom Hearts Birth by Sleep": ["Olympus Coliseum", "Zack D-Link"], + "Kingdom Hearts RE Chain of Memories": ["World Card Olympus Coliseum", "Key to Rewards Olympus Coliseum"] + } + }, + { + "count": 2, + "name": "Agrabah", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Agrabah"], + "Kingdom Hearts 2": ["Scimitar"], + "Kingdom Hearts RE Chain of Memories": ["World Card Agrabah", "Key to Rewards Agrabah"] + } + }, + { + "count": 2, + "name": "Monstro+Mirage Arena", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Monstro"], + "Kingdom Hearts Birth by Sleep": ["Mirage Arena"], + "Kingdom Hearts RE Chain of Memories": ["World Card Monstro", "Key to Rewards Monstro"] + } + }, + { + "count": 2, + "name": "Halloween Town", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Halloween Town"], + "Kingdom Hearts 2": ["Bone Fist"], + "Kingdom Hearts RE Chain of Memories": ["World Card Halloween Town", "Key to Rewards Halloween Town"] + } + }, + { + "count": 2, + "name": "Atlantica", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Atlantica"], + "Kingdom Hearts RE Chain of Memories": ["World Card Atlantica", "Key to Rewards Atlantica"] + } + }, + { + "count": 2, + "name": "Neverland", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Neverland"], + "Kingdom Hearts Birth by Sleep": ["Never Land"], + "Kingdom Hearts RE Chain of Memories": ["World Card Neverland", "Key to Rewards Neverland"] + } + }, + { + "count": 2, + "name": "Radiant Garden", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Hollow Bastion"], + "Kingdom Hearts 2": ["Membership Card"], + "Kingdom Hearts Birth by Sleep": ["Radiant Garden", "Maleficent D-Link"], + "Kingdom Hearts RE Chain of Memories": ["World Card Hollow Bastion", "Key to Rewards Hollow Bastion"] + } + }, + { + "count": 3, + "name": "Twilight Town+ Mysterious Tower", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Ice Cream"], + "Kingdom Hearts Birth by Sleep": ["The Mysterious Tower", "Donald D-Link", "Goofy D-Link"], + "Kingdom Hearts RE Chain of Memories": ["World Card Twilight Town", "Key to Rewards Twilight Town"] + } + }, + { + "count": 2, + "name": "Disney Castle Town", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Disney Castle Key"], + "Kingdom Hearts Birth by Sleep": ["Disney Town", "Mickey D-Link"] + } + }, + { + "count": 5, + "name": "Torn Page", + "category": [], + "progression": true, + "linklink": { + "Kingdom Hearts": ["Torn Page"], + "Kingdom Hearts 2": ["Torn Page"], + "Kingdom Hearts RE Chain of Memories": ["Key to Rewards Traverse Town", "Key to Rewards Castle Oblivion", "World Card 100 Acre Wood"] + } } - } -] + ] +} \ No newline at end of file diff --git a/data/items_pkmn.json b/data/items_pkmn.json index 27725a6..dcf3e87 100644 --- a/data/items_pkmn.json +++ b/data/items_pkmn.json @@ -1,144 +1,147 @@ -[ - { - "count": 1, - "name": "HM01 Cut", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM01 Cut"], - "Pokemon Emerald": ["HM01 Cut"], - "Pokemon Red and Blue": ["HM01 Cut"] +{ + "$schema": "items.schema.json", + "data": [ + { + "count": 1, + "name": "HM01 Cut", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM01 Cut"], + "Pokemon Emerald": ["HM01 Cut"], + "Pokemon Red and Blue": ["HM01 Cut"] + } + }, + { + "count": 1, + "name": "HM02 Fly", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM02 Fly"], + "Pokemon Emerald": ["HM02 Fly"], + "Pokemon Red and Blue": ["HM02 Fly"] + } + }, + { + "count": 1, + "name": "HM03 Surf", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM03 Surf"], + "Pokemon Emerald": ["HM03 Surf"], + "Pokemon Red and Blue": ["HM03 Surf"] + } + }, + { + "count": 1, + "name": "HM04 Strength", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM04 Strength"], + "Pokemon Emerald": ["HM04 Strength"], + "Pokemon Red and Blue": ["HM04 Strength"] + } + }, + { + "count": 1, + "name": "HM05 Flash", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM05 Flash"], + "Pokemon Emerald": ["HM05 Flash"], + "Pokemon Red and Blue": ["HM05 Flash"] + } + }, + { + "count": 1, + "name": "HM06 Rock Smash", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["TM08 Rock Smash"], + "Pokemon Emerald": ["HM06 Rock Smash"] + } + }, + { + "count": 1, + "name": "HM07 Waterfall", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM07 Waterfall"], + "Pokemon Emerald": ["HM07 Waterfall"] + } + }, + { + "count": 0, + "name": "HM Whirlpool/ Dive", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM_WHIRLPOOL"], + "Pokemon Emerald": ["HM08 Dive"] + } + }, + { + "name": "Poke Flute", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["Poke Flute"], + "Pokemon Red and Blue": ["Poke Flute"] + } + }, + { + "name": "Bicycle", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["Bicycle"], + "Pokemon Emerald": ["Mach Bike"], + "Pokemon Red and Blue": ["Bicycle"] + } + }, + { + "name": "S.S. Ticket", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["S.S. Ticket"], + "Pokemon Emerald": ["S.S. Ticket"], + "Pokemon Red and Blue": ["S.S. Ticket"] + } + }, + { + "name": "Card Key", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["Card Key"], + "Pokemon Red and Blue": ["Card Key"] + } + }, + { + "name": "Scope", + "category": [], + "progression": true, + "linklink": { + "Pokemon Emerald": ["Devon Scope"], + "Pokemon Red and Blue": ["Silph Scope"] + } + }, + { + "name": "Coin Case", + "category": [], + "progression": true, + "linklink": { + "Pokemon Crystal": ["Coin Case"], + "Pokemon Emerald": ["Coin Case"], + "Pokemon Red and Blue": ["Coin Case"] + } } - }, - { - "count": 1, - "name": "HM02 Fly", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM02 Fly"], - "Pokemon Emerald": ["HM02 Fly"], - "Pokemon Red and Blue": ["HM02 Fly"] - } - }, - { - "count": 1, - "name": "HM03 Surf", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM03 Surf"], - "Pokemon Emerald": ["HM03 Surf"], - "Pokemon Red and Blue": ["HM03 Surf"] - } - }, - { - "count": 1, - "name": "HM04 Strength", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM04 Strength"], - "Pokemon Emerald": ["HM04 Strength"], - "Pokemon Red and Blue": ["HM04 Strength"] - } - }, - { - "count": 1, - "name": "HM05 Flash", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM05 Flash"], - "Pokemon Emerald": ["HM05 Flash"], - "Pokemon Red and Blue": ["HM05 Flash"] - } - }, - { - "count": 1, - "name": "HM06 Rock Smash", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["TM08 Rock Smash"], - "Pokemon Emerald": ["HM06 Rock Smash"] - } - }, - { - "count": 1, - "name": "HM07 Waterfall", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM07 Waterfall"], - "Pokemon Emerald": ["HM07 Waterfall"] - } - }, - { - "count": 0, - "name": "HM Whirlpool/ Dive", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["HM_WHIRLPOOL"], - "Pokemon Emerald": ["HM08 Dive"] - } - }, - { - "name": "Poke Flute", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["Poke Flute"], - "Pokemon Red and Blue": ["Poke Flute"] - } - }, - { - "name": "Bicycle", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["Bicycle"], - "Pokemon Red and Blue": ["Bicycle"], - "Pokemon Emerald": ["Mach Bike"] - } - }, - { - "name": "S.S. Ticket", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["S.S. Ticket"], - "Pokemon Red and Blue": ["S.S. Ticket"], - "Pokemon Emerald": ["S.S. Ticket"] - } - }, - { - "name": "Card Key", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["Card Key"], - "Pokemon Red and Blue": ["Card Key"] - } - }, - { - "name": "Scope", - "category": [], - "progression": true, - "linklink": { - "Pokemon Emerald": ["Devon Scope"], - "Pokemon Red and Blue": ["Silph Scope"] - } - }, - { - "name": "Coin Case", - "category": [], - "progression": true, - "linklink": { - "Pokemon Crystal": ["Coin Case"], - "Pokemon Red and Blue": ["Coin Case"], - "Pokemon Emerald": ["Coin Case"] - } - } -] \ No newline at end of file + ] +} \ No newline at end of file From 170d906f4fd4d1be9ef29e9d9348a58e40267704 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:26:54 +1100 Subject: [PATCH 04/15] Remove empty categories from items. That's a lot of wasted bytes --- data/Sort-Items-linklink-data.py | 3 +++ data/items.json | 40 ----------------------------- data/items_kh.json | 43 -------------------------------- data/items_pkmn.json | 14 ----------- 4 files changed, 3 insertions(+), 97 deletions(-) diff --git a/data/Sort-Items-linklink-data.py b/data/Sort-Items-linklink-data.py index 4439c0e..2c307ea 100644 --- a/data/Sort-Items-linklink-data.py +++ b/data/Sort-Items-linklink-data.py @@ -71,6 +71,9 @@ def write_data_file(fname: str, data: dict): continue else: item["linklink"] = dict(sorted(item["linklink"].items(), key=lambda item: (0 if item[0] in known_loz_keys else 1, item[0]))) + # Clean up any empty categories + if "category" in item and not item["category"]: + del item["category"] # Third and finally write the change to the file write_data_file(filename, itemsjson) diff --git a/data/items.json b/data/items.json index 8157aea..7b43bf1 100644 --- a/data/items.json +++ b/data/items.json @@ -4,7 +4,6 @@ { "count": 4, "name": "Sword", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Progressive Sword"], @@ -23,7 +22,6 @@ { "count": 1, "name": "Biggoron's Sword", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Progressive Sword"], @@ -40,7 +38,6 @@ { "count": 1, "name": "Shield (Basic)", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Hylian Shield"], @@ -58,7 +55,6 @@ { "count": 1, "name": "Shield (Intermediate)", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Progressive Shield", "Red Shield"], @@ -72,7 +68,6 @@ { "count": 1, "name": "Shield (Mirror)", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Progressive Shield", "Mirror Shield"], @@ -88,7 +83,6 @@ { "count": 2, "name": "Fire Magic", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Fire Rod"], @@ -103,7 +97,6 @@ { "count": 2, "name": "Ice Magic", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Ice Rod"], @@ -117,7 +110,6 @@ { "count": 1, "name": "Light Arrows", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Bow of Light"], @@ -132,7 +124,6 @@ { "count": 3, "name": "Bow", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Bow"], @@ -148,7 +139,6 @@ { "count": 2, "name": "Boomerang", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Boomerang"], @@ -165,7 +155,6 @@ { "count": 3, "name": "Bombs", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Bombs"], @@ -184,7 +173,6 @@ { "count": 2, "name": "Flute", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Bell"], @@ -201,7 +189,6 @@ { "count": 1, "name": "Magic Armor", - "category": [], "progression": true, "linklink": { "Ocarina of Time": ["Double Defense"], @@ -212,7 +199,6 @@ { "count": 1, "name": "Blue Tunic", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Progressive Mail"], @@ -227,7 +213,6 @@ { "count": 1, "name": "Red Tunic", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Progressive Mail"], @@ -242,7 +227,6 @@ { "count": 3, "name": "Strength", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Progressive Glove"], @@ -261,7 +245,6 @@ { "count": 2, "name": "Hookshot", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Hookshot"], @@ -280,7 +263,6 @@ { "count": 2, "name": "Swimming", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Flippers"], @@ -297,7 +279,6 @@ { "count": 2, "name": "Hammer", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Hammer"], @@ -310,7 +291,6 @@ { "count": 5, "name": "Bottle", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Premium Milk", "Bottle"], @@ -326,7 +306,6 @@ { "count": 1, "name": "Magical Rod", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Cane of Somaria"], @@ -340,7 +319,6 @@ { "count": 2, "name": "Shovel", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Sand Rod"], @@ -354,7 +332,6 @@ { "count": 1, "name": "Pegasus Boots", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Pegasus Boots"], @@ -368,7 +345,6 @@ { "count": 2, "name": "Magic Meter", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Progressive Bracelet", "Stamina Scroll"], @@ -383,7 +359,6 @@ { "count": 2, "name": "Lamp", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Lamp"], @@ -396,7 +371,6 @@ { "count": 1, "name": "Book", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Book of Mudora"], @@ -410,7 +384,6 @@ { "count": 1, "name": "Magic Powder", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Magic Powder"], @@ -422,7 +395,6 @@ { "count": 1, "name": "Mushroom", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Mushroom"], @@ -437,7 +409,6 @@ { "count": 3, "name": "Magic Spell", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Quake", "Great Spin"], @@ -450,7 +421,6 @@ { "count": 1, "name": "Iron Boots", - "category": [], "progression": true, "linklink": { "Ocarina of Time": ["Iron Boots"], @@ -460,7 +430,6 @@ { "count": 1, "name": "Feather", - "category": [], "progression": true, "linklink": { "Links Awakening DX": ["Feather"], @@ -472,7 +441,6 @@ { "count": 1, "name": "Cape", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Cape"], @@ -484,7 +452,6 @@ { "count": 3, "name": "Slingshot", - "category": [], "progression": true, "linklink": { "Ocarina of Time": ["Slingshot"], @@ -496,7 +463,6 @@ { "count": 1, "name": "Membership Card", - "category": [], "progression": true, "linklink": { "Ocarina of Time": ["Gerudo Membership Card"], @@ -507,7 +473,6 @@ { "count": 3, "name": "Seed Bag", - "category": [], "progression": true, "linklink": { "Ocarina of Time": ["Deku Nut Capacity"], @@ -519,7 +484,6 @@ { "count": 1, "name": "Looking Glass", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Magic Mirror"], @@ -533,7 +497,6 @@ { "count": 1, "name": "Letter", - "category": [], "progression": true, "linklink": { "Majora's Mask Recompiled": ["Letter to Kafei"], @@ -546,7 +509,6 @@ { "count": 4, "name": "Key Jewels", - "category": [], "progression": true, "linklink": { "A Link to the Past": ["Moon Pearl"], @@ -560,7 +522,6 @@ { "count": 3, "name": "Wallet", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Pouch"], @@ -573,7 +534,6 @@ { "count": 10, "name": "Heart Container", - "category": [], "progression": true, "linklink": { "A Link Between Worlds": ["Heart Container", "Piece of Heart", "Heart Piece"], diff --git a/data/items_kh.json b/data/items_kh.json index 60a335a..b144c61 100644 --- a/data/items_kh.json +++ b/data/items_kh.json @@ -4,7 +4,6 @@ { "count": 3, "name": "Cure", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Cure"], @@ -16,7 +15,6 @@ { "count": 3, "name": "Fire", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Fire"], @@ -28,7 +26,6 @@ { "count": 3, "name": "Blizzard", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Blizzard"], @@ -40,7 +37,6 @@ { "count": 3, "name": "Thunder", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Thunder"], @@ -52,7 +48,6 @@ { "count": 3, "name": "Gravity", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Gravity"], @@ -63,7 +58,6 @@ { "count": 3, "name": "Stop", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Stop"], @@ -74,7 +68,6 @@ { "count": 3, "name": "Aero+Reflect", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Aero"], @@ -86,7 +79,6 @@ { "count": 3, "name": "Magnet", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Magnet Element"], @@ -96,7 +88,6 @@ { "count": 4, "name": "High Jump", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["High Jump"], @@ -107,7 +98,6 @@ { "count": 4, "name": "Dodge Roll", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Dodge Roll"], @@ -118,7 +108,6 @@ { "count": 4, "name": "Glide", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Progressive Glide"], @@ -129,7 +118,6 @@ { "count": 4, "name": "Quick Run + Slide", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Quick Run"], @@ -139,7 +127,6 @@ { "count": 1, "name": "Scan", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Scan"], @@ -149,7 +136,6 @@ { "count": 1, "name": "Leaf Bracer", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Leaf Bracer"], @@ -160,7 +146,6 @@ { "count": 1, "name": "Second Chance", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Second Chance"], @@ -171,7 +156,6 @@ { "count": 1, "name": "Once More", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Once More"], @@ -181,7 +165,6 @@ { "count": 1, "name": "Guard", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Guard+Barrier"], @@ -192,7 +175,6 @@ { "count": 1, "name": "Aerial Recovery", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Aerial Recovery"], @@ -202,7 +184,6 @@ { "count": 1, "name": "Blitz", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Blitz"], @@ -213,7 +194,6 @@ { "count": 1, "name": "Sliding Dash", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Sliding Dash"], @@ -225,7 +205,6 @@ { "count": 1, "name": "Sonic Blade+Limit", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Sonic Blade"], @@ -237,7 +216,6 @@ { "count": 1, "name": "Ars Arcanum+Valor", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Ars Arcanum"], @@ -249,7 +227,6 @@ { "count": 1, "name": "Strike Raid+Master", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Strike Raid"], @@ -261,7 +238,6 @@ { "count": 1, "name": "Ragnarok+Wisdom", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Ragnarok"], @@ -273,7 +249,6 @@ { "count": 1, "name": "Zantetsuken", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Zantetsuken"], @@ -284,7 +259,6 @@ { "count": 1, "name": "Simba", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Simba"], @@ -294,7 +268,6 @@ { "count": 1, "name": "Genie", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Genie"], @@ -305,7 +278,6 @@ { "count": 1, "name": "Bambi", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Bambi"], @@ -315,7 +287,6 @@ { "count": 1, "name": "Dumbo", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Dumbo"], @@ -325,7 +296,6 @@ { "count": 1, "name": "Tinker Bell+Peter Pan", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Tinker Bell"], @@ -337,7 +307,6 @@ { "count": 1, "name": "Stitch", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Stitch"], @@ -347,7 +316,6 @@ { "count": 2, "name": "Destiny Islands", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Destiny Islands"], @@ -357,7 +325,6 @@ { "count": 2, "name": "Wonderland", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Wonderland"], @@ -367,7 +334,6 @@ { "count": 2, "name": "Olympus Coliseum", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Olympus Coliseum"], @@ -379,7 +345,6 @@ { "count": 2, "name": "Agrabah", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Agrabah"], @@ -390,7 +355,6 @@ { "count": 2, "name": "Monstro+Mirage Arena", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Monstro"], @@ -401,7 +365,6 @@ { "count": 2, "name": "Halloween Town", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Halloween Town"], @@ -412,7 +375,6 @@ { "count": 2, "name": "Atlantica", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Atlantica"], @@ -422,7 +384,6 @@ { "count": 2, "name": "Neverland", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Neverland"], @@ -433,7 +394,6 @@ { "count": 2, "name": "Radiant Garden", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Hollow Bastion"], @@ -445,7 +405,6 @@ { "count": 3, "name": "Twilight Town+ Mysterious Tower", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Ice Cream"], @@ -456,7 +415,6 @@ { "count": 2, "name": "Disney Castle Town", - "category": [], "progression": true, "linklink": { "Kingdom Hearts 2": ["Disney Castle Key"], @@ -466,7 +424,6 @@ { "count": 5, "name": "Torn Page", - "category": [], "progression": true, "linklink": { "Kingdom Hearts": ["Torn Page"], diff --git a/data/items_pkmn.json b/data/items_pkmn.json index dcf3e87..19fbbc8 100644 --- a/data/items_pkmn.json +++ b/data/items_pkmn.json @@ -4,7 +4,6 @@ { "count": 1, "name": "HM01 Cut", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM01 Cut"], @@ -15,7 +14,6 @@ { "count": 1, "name": "HM02 Fly", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM02 Fly"], @@ -26,7 +24,6 @@ { "count": 1, "name": "HM03 Surf", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM03 Surf"], @@ -37,7 +34,6 @@ { "count": 1, "name": "HM04 Strength", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM04 Strength"], @@ -48,7 +44,6 @@ { "count": 1, "name": "HM05 Flash", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM05 Flash"], @@ -59,7 +54,6 @@ { "count": 1, "name": "HM06 Rock Smash", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["TM08 Rock Smash"], @@ -69,7 +63,6 @@ { "count": 1, "name": "HM07 Waterfall", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM07 Waterfall"], @@ -79,7 +72,6 @@ { "count": 0, "name": "HM Whirlpool/ Dive", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["HM_WHIRLPOOL"], @@ -88,7 +80,6 @@ }, { "name": "Poke Flute", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["Poke Flute"], @@ -97,7 +88,6 @@ }, { "name": "Bicycle", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["Bicycle"], @@ -107,7 +97,6 @@ }, { "name": "S.S. Ticket", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["S.S. Ticket"], @@ -117,7 +106,6 @@ }, { "name": "Card Key", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["Card Key"], @@ -126,7 +114,6 @@ }, { "name": "Scope", - "category": [], "progression": true, "linklink": { "Pokemon Emerald": ["Devon Scope"], @@ -135,7 +122,6 @@ }, { "name": "Coin Case", - "category": [], "progression": true, "linklink": { "Pokemon Crystal": ["Coin Case"], From 9917ad065563a26b281902405ff8c00883ba3ed3 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:30:05 +1100 Subject: [PATCH 05/15] Pad location numbers --- hooks/Data.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/hooks/Data.py b/hooks/Data.py index be243c3..5257821 100644 --- a/hooks/Data.py +++ b/hooks/Data.py @@ -1,3 +1,6 @@ +from typing import Any + + ITEM_TABLE = [] MAX_PLAYERS = 40 FREE_ITEMS = 0 @@ -33,18 +36,23 @@ def after_load_progressive_item_file(progressive_item_table: list) -> list: def after_load_location_file(location_table: list) -> list: for item in ITEM_TABLE: if 'linklink' in item: - for i in range(1, item['count'] + 1): + count = item['count'] + digit = len(str(count + 1)) + players_digits = len(str(MAX_PLAYERS)) + + for i in range(1, count + 1): for j in range(1, MAX_PLAYERS + 1): location_table.append({ - "name": f"{item['name']} {i} Player {j}", - "region": f"{item['name']} {i}", + "name": f"{item['name']} l$l {str(i).zfill(digit)} Player {str(j).zfill(players_digits)}", + "region": f"{item['name']} {str(i).zfill(digit)}", "category": [item['name']], "requires": "", "linklink": item['linklink'], }) + digit = len(str(FREE_ITEMS + 1)) for i in range(1, FREE_ITEMS + 1): location_table.append({ - "name": f"Free Item {i}", + "name": f"Free Item {str(i).zfill(digit)}", "region": "Free Items", "category": ["Free Items"], "requires": "", @@ -57,8 +65,9 @@ def after_load_location_file(location_table: list) -> list: def after_load_region_file(region_table: dict) -> dict: for item in ITEM_TABLE: if 'linklink' in item: + digit = len(str(item['count'] + 1)) for i in range(1, item['count'] + 1): - name = f"{item['name']} {i}" + name = f"{item['name']} {str(i).zfill(digit)}" if name not in region_table: region_table[name] = { "name": name, @@ -78,7 +87,7 @@ def after_load_meta_file(meta_table: dict) -> dict: # called when an external tool (eg Univeral Tracker) ask for slot data to be read # use this if you want to restore more data # return True if you want to trigger a regeneration if you changed anything -def hook_interpret_slot_data(world, player: int, slot_data: dict[str, any]) -> bool: +def hook_interpret_slot_data(world, player: int, slot_data: dict[str, Any]) -> bool: return False From 0c19396c66b9c615add7dc209f8e7661c13f6369 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:37:37 +1100 Subject: [PATCH 06/15] Release Action --- .github/workflows/release.yaml | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..cc74fc5 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,71 @@ +name: Create APWorld Release + +on: + workflow_dispatch: + +permissions: + actions: read + contents: write + +jobs: + create-release: + name: Release apworld + runs-on: ubuntu-latest + env: + world: manual_linklink_silasary + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Checkout Archipelago + uses: actions/checkout@v3 + with: + repository: ArchipelagoMW/Archipelago + path: Archipelago + fetch-depth: 1 + - name: Set up venv and install dependencies + run: | + ln -s ${{ github.workspace }} Archipelago/worlds/${{ env.world }} + python -m venv venv + source venv/bin/activate + pip install --upgrade pip + pip install -r Archipelago/requirements.txt + python Archipelago/ModuleUpdate.py --yes --force + ls -la Archipelago/worlds/ + + - name: Create apworld file + run: | + source venv/bin/activate + cd Archipelago + python Launcher.py "Build APWorlds" + + - name: Upload apworld as an artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.world }}.apworld + path: Archipelago/build/apworlds/${{ env.world }}.apworld + if-no-files-found: error + - name: Get Version Number + id: get_version + uses: mikefarah/yq@v4.45.1 + with: + cmd: yq .world_version worlds/${{ env.world }}/archipelago.json --unwrapScalar=true + - name: Get Name + id: get_name + uses: mikefarah/yq@v4.45.1 + with: + cmd: yq .game worlds/${{ env.world }}/archipelago.json --unwrapScalar=true + - name: Get Changelog + id: get_changelog + uses: mikefarah/yq@v4.45.1 + with: + cmd: yq '.changelog | join("\n")' worlds/${{ env.world }}/archipelago.json --unwrapScalar=true + + - name: Create release + uses: ncipollo/release-action@v1 + with: + name: LinkLink v${{ steps.get_version.outputs.result }} + tag: ${{ steps.get_version.outputs.result }} + commit: ${{ env.GITHUB_REF }} + artifacts: "${{ env.world }}.apworld" + body: ${{ steps.get_changelog.outputs.result }} From e2b7f5ae3d8f4d2939f0e0185c24c4c39a352584 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:37:59 +1100 Subject: [PATCH 07/15] Cleanup --- .gitattributes | 3 +++ .gitignore | 7 ++++++- hooks/Helpers.py | 9 +++------ hooks/Rules.py | 23 ----------------------- 4 files changed, 12 insertions(+), 30 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cb944b2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto +*.jpg binary +*.png binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index bee8a64..5765dcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -__pycache__ +__pycache__/ +.idea +.vs/ +.vscode/ +/.mypy_cache +/*.code-workspace diff --git a/hooks/Helpers.py b/hooks/Helpers.py index 5826fd8..9f6df9d 100644 --- a/hooks/Helpers.py +++ b/hooks/Helpers.py @@ -1,9 +1,6 @@ -from typing import Optional, TYPE_CHECKING +from typing import Optional, Any from BaseClasses import MultiWorld, Item, Location -if TYPE_CHECKING: - from ..Items import ManualItem - from ..Locations import ManualLocation # Use this if you want to override the default behavior of is_option_enabled # Return True to enable the category, False to disable it, or None to use the default behavior @@ -12,10 +9,10 @@ def before_is_category_enabled(multiworld: MultiWorld, player: int, category_nam # Use this if you want to override the default behavior of is_option_enabled # Return True to enable the item, False to disable it, or None to use the default behavior -def before_is_item_enabled(multiworld: MultiWorld, player: int, item: "ManualItem") -> Optional[bool]: +def before_is_item_enabled(multiworld: MultiWorld, player: int, item: dict[str, Any]) -> Optional[bool]: return None # Use this if you want to override the default behavior of is_option_enabled # Return True to enable the location, False to disable it, or None to use the default behavior -def before_is_location_enabled(multiworld: MultiWorld, player: int, location: "ManualLocation") -> Optional[bool]: +def before_is_location_enabled(multiworld: MultiWorld, player: int, location: dict[str, Any]) -> Optional[bool]: return None diff --git a/hooks/Rules.py b/hooks/Rules.py index 66fd184..b458adf 100644 --- a/hooks/Rules.py +++ b/hooks/Rules.py @@ -4,26 +4,3 @@ from BaseClasses import MultiWorld, CollectionState import re - -# Sometimes you have a requirement that is just too messy or repetitive to write out with boolean logic. -# Define a function here, and you can use it in a requires string with {function_name()}. -def overfishedAnywhere(world: World, multiworld: MultiWorld, state: CollectionState, player: int): - """Has the player collected all fish from any fishing log?""" - for cat, items in world.item_name_groups: - if cat.endswith("Fishing Log") and state.has_all(items, player): - return True - return False - -# You can also pass an argument to your function, like {function_name(15)} -# Note that all arguments are strings, so you'll need to convert them to ints if you want to do math. -def anyClassLevel(world: World, multiworld: MultiWorld, state: CollectionState, player: int, level: str): - """Has the player reached the given level in any class?""" - for item in ["Figher Level", "Black Belt Level", "Thief Level", "Red Mage Level", "White Mage Level", "Black Mage Level"]: - if state.count(item, player) >= int(level): - return True - return False - -# You can also return a string from your function, and it will be evaluated as a requires string. -def requiresMelee(world: World, multiworld: MultiWorld, state: CollectionState, player: int): - """Returns a requires string that checks if the player has unlocked the tank.""" - return "|Figher Level:15| or |Black Belt Level:15| or |Thief Level:15|" From 74cfa7cc799d26d32947b67dc67f845ea997d727 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:38:53 +1100 Subject: [PATCH 08/15] Nicopop's hint data --- hooks/World.py | 67 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/hooks/World.py b/hooks/World.py index 01bf54c..d9e1428 100644 --- a/hooks/World.py +++ b/hooks/World.py @@ -1,8 +1,8 @@ # Object classes from AP core, to represent an entire MultiWorld and this individual World that's part of it import logging from worlds.AutoWorld import World -from BaseClasses import MultiWorld, CollectionState -from typing import TYPE_CHECKING +from BaseClasses import MultiWorld, CollectionState, Item +from typing import TYPE_CHECKING, Iterator # Object classes from Manual -- extending AP core -- representing items and locations that are used in generation from ..Items import ManualItem @@ -245,19 +245,56 @@ def before_write_spoiler(world: World, multiworld: MultiWorld, spoiler_handle) - # This is called when you want to add information to the hint text def before_extend_hint_information(hint_data: dict[int, dict[int, str]], world: World, multiworld: MultiWorld, player: int) -> None: - - ### Example way to use this hook: - # if player not in hint_data: - # hint_data.update({player: {}}) - # for location in multiworld.get_locations(player): - # if not location.address: - # continue - # - # use this section to calculate the hint string - # - # hint_data[player][location.address] = hint_string - - pass + from itertools import groupby + items = [loc.item for loc in multiworld.get_filled_locations() if loc.item is not None and loc.item.player == player] + items.extend(multiworld.precollected_items.get(player, [])) + items = [i for i in items if i.advancement] + + groups: dict[str,list] = {} + def keyfunc(i): + return i.name + + data = sorted(items, key=keyfunc) + for k, g in groupby(data, key=keyfunc): + if k not in groups.keys(): + groups[k] = list(g) + + if player not in hint_data: + hint_data.update({player: {}}) + + iterators: dict[str, dict[str, Iterator]] = {} + next_item: dict[str, dict[str, Item|None]] = {} + # hintsdone: dict[str, list[str]] = {} + for location in multiworld.get_locations(player): + if not location.address: + continue + elif location.parent_region is not None and location.parent_region.name == 'Free Items': + continue + + item_name, rest = location.name.split("l$l") # re.split(r'\d+', location.name)[0].strip() + item_name = item_name.strip() + p_num = str(location.item.player) + if p_num not in iterators.keys(): + iterators[p_num] = {} + next_item[p_num] = {} + # hintsdone[p_num] = [] + + if next_item[p_num].get(item_name, None) is None or item_name not in iterators[p_num].keys(): + ll_keys = list(groups.get(item_name, [])) + world.random.shuffle(ll_keys) + + iterators[p_num][item_name] = iter(ll_keys) + next_item[p_num][item_name] = next(iterators[p_num][item_name], None) + + current_item = next_item[p_num][item_name] + if current_item is not None: + if current_item.location is not None: + hint_data[player][location.address] = f"{str(current_item.location)}" + else: + hint_data[player][location.address] = f"In {multiworld.player_name[player]}'s start inventory" + pass + # hintsdone[p_num].append(f"{rest.strip()}: {hint_data[player][location.address]}") + next_item[p_num][item_name] = next(iterators[p_num][item_name], None) def after_extend_hint_information(hint_data: dict[int, dict[int, str]], world: World, multiworld: MultiWorld, player: int) -> None: pass From 3ab1f175edf02e34738926c9431f69014e8d3d31 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 1 Dec 2025 11:39:19 +1100 Subject: [PATCH 09/15] Don't cull locations during a UT Fake Gen --- hooks/World.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/World.py b/hooks/World.py index d9e1428..e695d70 100644 --- a/hooks/World.py +++ b/hooks/World.py @@ -210,11 +210,11 @@ def remove_nothing(): logging.info(f'Removing surplus {item.name}') multiworld.itempool.remove(item) unplaced_items.remove(item) - for location in multiworld.get_unfilled_locations(player): - if location.name.startswith(f"{item_data['name']} "): - location.parent_region.locations.remove(location) - remove_nothing() - + if not getattr(multiworld, 'generation_is_fake', False): + for location in multiworld.get_unfilled_locations(player): + if location.name.startswith(f"{item_data['name']} "): + location.parent_region.locations.remove(location) + remove_nothing() replace_nothings(world, multiworld, player) From d43d92cf6161b46a3818b4ab71e404933d605f93 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 8 Dec 2025 09:46:44 +1100 Subject: [PATCH 10/15] Put stuff in a subfolder, so I'm not packaging the entirety of the git repo --- .github/workflows/release.yaml | 2 +- .github/workflows/update_manual.yml | 2 +- Data.py => linklink/Data.py | 0 .../DataValidation.py | 0 Game.py => linklink/Game.py | 0 Helpers.py => linklink/Helpers.py | 0 Items.py => linklink/Items.py | 0 Locations.py => linklink/Locations.py | 0 .../MANUAL_VERSION.txt | 0 ManualClient.py => linklink/ManualClient.py | 0 Meta.py => linklink/Meta.py | 0 Options.py => linklink/Options.py | 0 Regions.py => linklink/Regions.py | 0 Rules.py => linklink/Rules.py | 0 __init__.py => linklink/__init__.py | 0 .../data}/Sort-Items-linklink-data.py | 162 +++++++++--------- {data => linklink/data}/game.json | 0 {data => linklink/data}/items.json | 0 {data => linklink/data}/items.schema.json | 0 {data => linklink/data}/items_kh.json | 0 {data => linklink/data}/items_pkmn.json | 0 {data => linklink/data}/locations.json | 0 {data => linklink/data}/regions.json | 0 {docs => linklink/docs}/en_Manual.md | 0 {docs => linklink/docs}/setup_en.md | 0 {hooks => linklink/hooks}/Data.py | 7 +- {hooks => linklink/hooks}/Helpers.py | 0 {hooks => linklink/hooks}/Options.py | 5 +- {hooks => linklink/hooks}/Rules.py | 0 {hooks => linklink/hooks}/World.py | 0 {hooks => linklink/hooks}/__init__.py | 0 manual_test.py => linklink/manual_test.py | 0 32 files changed, 90 insertions(+), 88 deletions(-) rename Data.py => linklink/Data.py (100%) rename DataValidation.py => linklink/DataValidation.py (100%) rename Game.py => linklink/Game.py (100%) rename Helpers.py => linklink/Helpers.py (100%) rename Items.py => linklink/Items.py (100%) rename Locations.py => linklink/Locations.py (100%) rename MANUAL_VERSION.txt => linklink/MANUAL_VERSION.txt (100%) rename ManualClient.py => linklink/ManualClient.py (100%) rename Meta.py => linklink/Meta.py (100%) rename Options.py => linklink/Options.py (100%) rename Regions.py => linklink/Regions.py (100%) rename Rules.py => linklink/Rules.py (100%) rename __init__.py => linklink/__init__.py (100%) rename {data => linklink/data}/Sort-Items-linklink-data.py (97%) rename {data => linklink/data}/game.json (100%) rename {data => linklink/data}/items.json (100%) rename {data => linklink/data}/items.schema.json (100%) rename {data => linklink/data}/items_kh.json (100%) rename {data => linklink/data}/items_pkmn.json (100%) rename {data => linklink/data}/locations.json (100%) rename {data => linklink/data}/regions.json (100%) rename {docs => linklink/docs}/en_Manual.md (100%) rename {docs => linklink/docs}/setup_en.md (100%) rename {hooks => linklink/hooks}/Data.py (94%) rename {hooks => linklink/hooks}/Helpers.py (100%) rename {hooks => linklink/hooks}/Options.py (91%) rename {hooks => linklink/hooks}/Rules.py (100%) rename {hooks => linklink/hooks}/World.py (100%) rename {hooks => linklink/hooks}/__init__.py (100%) rename manual_test.py => linklink/manual_test.py (100%) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cc74fc5..fb87a86 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,7 +25,7 @@ jobs: fetch-depth: 1 - name: Set up venv and install dependencies run: | - ln -s ${{ github.workspace }} Archipelago/worlds/${{ env.world }} + ln -s ${{ github.workspace }}/linklink Archipelago/worlds/${{ env.world }} python -m venv venv source venv/bin/activate pip install --upgrade pip diff --git a/.github/workflows/update_manual.yml b/.github/workflows/update_manual.yml index 463bc2c..dc03ee9 100644 --- a/.github/workflows/update_manual.yml +++ b/.github/workflows/update_manual.yml @@ -18,7 +18,7 @@ jobs: uses: silasary/ap_manuals/update-manual@main with: allow_unstable: 'true' - directory: '.' + directory: linklink - name: Create Pull Request uses: peter-evans/create-pull-request@v7 diff --git a/Data.py b/linklink/Data.py similarity index 100% rename from Data.py rename to linklink/Data.py diff --git a/DataValidation.py b/linklink/DataValidation.py similarity index 100% rename from DataValidation.py rename to linklink/DataValidation.py diff --git a/Game.py b/linklink/Game.py similarity index 100% rename from Game.py rename to linklink/Game.py diff --git a/Helpers.py b/linklink/Helpers.py similarity index 100% rename from Helpers.py rename to linklink/Helpers.py diff --git a/Items.py b/linklink/Items.py similarity index 100% rename from Items.py rename to linklink/Items.py diff --git a/Locations.py b/linklink/Locations.py similarity index 100% rename from Locations.py rename to linklink/Locations.py diff --git a/MANUAL_VERSION.txt b/linklink/MANUAL_VERSION.txt similarity index 100% rename from MANUAL_VERSION.txt rename to linklink/MANUAL_VERSION.txt diff --git a/ManualClient.py b/linklink/ManualClient.py similarity index 100% rename from ManualClient.py rename to linklink/ManualClient.py diff --git a/Meta.py b/linklink/Meta.py similarity index 100% rename from Meta.py rename to linklink/Meta.py diff --git a/Options.py b/linklink/Options.py similarity index 100% rename from Options.py rename to linklink/Options.py diff --git a/Regions.py b/linklink/Regions.py similarity index 100% rename from Regions.py rename to linklink/Regions.py diff --git a/Rules.py b/linklink/Rules.py similarity index 100% rename from Rules.py rename to linklink/Rules.py diff --git a/__init__.py b/linklink/__init__.py similarity index 100% rename from __init__.py rename to linklink/__init__.py diff --git a/data/Sort-Items-linklink-data.py b/linklink/data/Sort-Items-linklink-data.py similarity index 97% rename from data/Sort-Items-linklink-data.py rename to linklink/data/Sort-Items-linklink-data.py index 2c307ea..0e2818a 100644 --- a/data/Sort-Items-linklink-data.py +++ b/linklink/data/Sort-Items-linklink-data.py @@ -1,81 +1,81 @@ -import json -import re -from os import path -from typing import cast - -def repl_func(match: re.Match): - result = " ".join(match.group().split()) - parts = result.split('",') - current_length = 0 - result_parts: list[str] = [] - indent = " " - for i, part in enumerate(parts): - if i > 0: - if current_length > 100 and i < len(parts) - 1: - current_length = len(part) + 2 - part = f'",\n{indent}' + part - - else: - current_length += len(part) + 2 - part = '",' + part - else: - current_length = len(part) + 1 - result_parts.append(part) - return "".join(result_parts) - -def load_data_file(fname: str) -> dict: - fpath = path.dirname(__file__) - fpath = path.join(fpath, fname) - try: - with open(fpath, 'r', encoding="utf-8-sig") as f: - filedata = json.load(f) - except Exception: - import yaml - filedata = yaml.safe_load(open(fpath, 'r', encoding="utf-8-sig")) - - return filedata - -def write_data_file(fname: str, data: dict): - fpath = path.dirname(__file__) - fpath = path.join(fpath, fname) - # regex based on answer in https://www.reddit.com/r/learnpython/comments/ymukyr/removing_new_line_inside_square_brackets_in_json/ - json_str = json.dumps(data, indent=4) - json_str = re.sub(r"(?<=\[)[^\[\]]+(?=])", repl_func, json_str) - - with open(file=fpath, mode="w", encoding="utf-8") as f: - f.write(json_str) - -if __name__ == '__main__': - schema = load_data_file('items.schema.json') - for filename in ['items.json', 'items_pkmn.json', 'items_kh.json']: - itemsjson = load_data_file(filename) - if isinstance(itemsjson, list): - itemsjson = {"$schema": "items.schema.json","data": itemsjson} - - if schema and itemsjson: - # First get the keys from schema: - schema_linklink = schema.get('definitions', {}).get('linklink', {}).get("properties", None) - if schema_linklink is None: - raise ValueError("items.schema.json doesn't contain a known definition of the linklink data") - schema_linklink = cast(dict[str, dict[str, str | bool]], schema_linklink) - - known_loz_keys: list[str] = [] - for key, value in schema_linklink.items(): - if value.get("loz", False): - known_loz_keys.append(key) - - # Second: sort all the item's linklink properties by placing the zelda ones first - for item in itemsjson.get("data", []): - item = cast(dict[str, dict[str, list[str]]], item) - if not item.get("linklink"): - continue - else: - item["linklink"] = dict(sorted(item["linklink"].items(), key=lambda item: (0 if item[0] in known_loz_keys else 1, item[0]))) - # Clean up any empty categories - if "category" in item and not item["category"]: - del item["category"] - - # Third and finally write the change to the file - write_data_file(filename, itemsjson) - else: - raise ValueError(f"Failed to load data from items.schema.json or {filename}") +import json +import re +from os import path +from typing import cast + +def repl_func(match: re.Match): + result = " ".join(match.group().split()) + parts = result.split('",') + current_length = 0 + result_parts: list[str] = [] + indent = " " + for i, part in enumerate(parts): + if i > 0: + if current_length > 100 and i < len(parts) - 1: + current_length = len(part) + 2 + part = f'",\n{indent}' + part + + else: + current_length += len(part) + 2 + part = '",' + part + else: + current_length = len(part) + 1 + result_parts.append(part) + return "".join(result_parts) + +def load_data_file(fname: str) -> dict: + fpath = path.dirname(__file__) + fpath = path.join(fpath, fname) + try: + with open(fpath, 'r', encoding="utf-8-sig") as f: + filedata = json.load(f) + except Exception: + import yaml + filedata = yaml.safe_load(open(fpath, 'r', encoding="utf-8-sig")) + + return filedata + +def write_data_file(fname: str, data: dict): + fpath = path.dirname(__file__) + fpath = path.join(fpath, fname) + # regex based on answer in https://www.reddit.com/r/learnpython/comments/ymukyr/removing_new_line_inside_square_brackets_in_json/ + json_str = json.dumps(data, indent=4) + json_str = re.sub(r"(?<=\[)[^\[\]]+(?=])", repl_func, json_str) + + with open(file=fpath, mode="w", encoding="utf-8") as f: + f.write(json_str) + +if __name__ == '__main__': + schema = load_data_file('items.schema.json') + for filename in ['items.json', 'items_pkmn.json', 'items_kh.json']: + itemsjson = load_data_file(filename) + if isinstance(itemsjson, list): + itemsjson = {"$schema": "items.schema.json","data": itemsjson} + + if schema and itemsjson: + # First get the keys from schema: + schema_linklink = schema.get('definitions', {}).get('linklink', {}).get("properties", None) + if schema_linklink is None: + raise ValueError("items.schema.json doesn't contain a known definition of the linklink data") + schema_linklink = cast(dict[str, dict[str, str | bool]], schema_linklink) + + known_loz_keys: list[str] = [] + for key, value in schema_linklink.items(): + if value.get("loz", False): + known_loz_keys.append(key) + + # Second: sort all the item's linklink properties by placing the zelda ones first + for item in itemsjson.get("data", []): + item = cast(dict[str, dict[str, list[str]]], item) + if not item.get("linklink"): + continue + else: + item["linklink"] = dict(sorted(item["linklink"].items(), key=lambda item: (0 if item[0] in known_loz_keys else 1, item[0]))) + # Clean up any empty categories + if "category" in item and not item["category"]: + del item["category"] + + # Third and finally write the change to the file + write_data_file(filename, itemsjson) + else: + raise ValueError(f"Failed to load data from items.schema.json or {filename}") diff --git a/data/game.json b/linklink/data/game.json similarity index 100% rename from data/game.json rename to linklink/data/game.json diff --git a/data/items.json b/linklink/data/items.json similarity index 100% rename from data/items.json rename to linklink/data/items.json diff --git a/data/items.schema.json b/linklink/data/items.schema.json similarity index 100% rename from data/items.schema.json rename to linklink/data/items.schema.json diff --git a/data/items_kh.json b/linklink/data/items_kh.json similarity index 100% rename from data/items_kh.json rename to linklink/data/items_kh.json diff --git a/data/items_pkmn.json b/linklink/data/items_pkmn.json similarity index 100% rename from data/items_pkmn.json rename to linklink/data/items_pkmn.json diff --git a/data/locations.json b/linklink/data/locations.json similarity index 100% rename from data/locations.json rename to linklink/data/locations.json diff --git a/data/regions.json b/linklink/data/regions.json similarity index 100% rename from data/regions.json rename to linklink/data/regions.json diff --git a/docs/en_Manual.md b/linklink/docs/en_Manual.md similarity index 100% rename from docs/en_Manual.md rename to linklink/docs/en_Manual.md diff --git a/docs/setup_en.md b/linklink/docs/setup_en.md similarity index 100% rename from docs/setup_en.md rename to linklink/docs/setup_en.md diff --git a/hooks/Data.py b/linklink/hooks/Data.py similarity index 94% rename from hooks/Data.py rename to linklink/hooks/Data.py index 5257821..7ddeb05 100644 --- a/hooks/Data.py +++ b/linklink/hooks/Data.py @@ -15,9 +15,10 @@ def after_load_game_file(game_table: dict) -> dict: def after_load_item_file(item_table: list) -> list: # Store a reference to this for i, extra_file in enumerate(extra_item_files): - from ..Data import load_data_file - new_table = load_data_file(extra_file) - new_table[0]['id'] += i * 1000 # Plenty of room for expansion + from ..Data import convert_to_list, load_data_file + + new_table = convert_to_list(load_data_file(extra_file), "data") + new_table[0]["id"] = (i + 1) * 1000 # Plenty of room for expansion item_table.extend(new_table) for item in item_table: diff --git a/hooks/Helpers.py b/linklink/hooks/Helpers.py similarity index 100% rename from hooks/Helpers.py rename to linklink/hooks/Helpers.py diff --git a/hooks/Options.py b/linklink/hooks/Options.py similarity index 91% rename from hooks/Options.py rename to linklink/hooks/Options.py index 4f1d887..9b8f8d9 100644 --- a/hooks/Options.py +++ b/linklink/hooks/Options.py @@ -1,5 +1,6 @@ # Object classes from AP that represent different types of options that you can create -from Options import OptionSet +from typing import Any +from Options import Option, OptionGroup, OptionSet # These helper methods allow you to determine if an option has been set, or what its value is, for any player in the multiworld from ..Helpers import is_option_enabled, get_option_value @@ -46,7 +47,7 @@ def before_options_defined(options: dict) -> dict: def after_options_defined(options: dict) -> dict: return options -def before_option_groups_created(groups: dict[str, list[Type[Option[Any]]]]) -> dict[str, list[Type[Option[Any]]]]: +def before_option_groups_created(groups: dict[str, list[type[Option[Any]]]]) -> dict[str, list[type[Option[Any]]]]: return groups diff --git a/hooks/Rules.py b/linklink/hooks/Rules.py similarity index 100% rename from hooks/Rules.py rename to linklink/hooks/Rules.py diff --git a/hooks/World.py b/linklink/hooks/World.py similarity index 100% rename from hooks/World.py rename to linklink/hooks/World.py diff --git a/hooks/__init__.py b/linklink/hooks/__init__.py similarity index 100% rename from hooks/__init__.py rename to linklink/hooks/__init__.py diff --git a/manual_test.py b/linklink/manual_test.py similarity index 100% rename from manual_test.py rename to linklink/manual_test.py From c0c5da43417c312f852fbb019ffcfbb6d1b266c2 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 8 Dec 2025 09:51:13 +1100 Subject: [PATCH 11/15] Depreciation warning --- linklink/hooks/Data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linklink/hooks/Data.py b/linklink/hooks/Data.py index 7ddeb05..fe1f727 100644 --- a/linklink/hooks/Data.py +++ b/linklink/hooks/Data.py @@ -15,7 +15,8 @@ def after_load_game_file(game_table: dict) -> dict: def after_load_item_file(item_table: list) -> list: # Store a reference to this for i, extra_file in enumerate(extra_item_files): - from ..Data import convert_to_list, load_data_file + from ..Data import convert_to_list + from ..Helpers import load_data_file new_table = convert_to_list(load_data_file(extra_file), "data") new_table[0]["id"] = (i + 1) * 1000 # Plenty of room for expansion From 1768b91a625904db9aefb160cdab20a82302ca11 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 8 Dec 2025 10:17:22 +1100 Subject: [PATCH 12/15] Fix Filler Generation --- linklink/hooks/Data.py | 2 +- linklink/hooks/World.py | 36 +++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/linklink/hooks/Data.py b/linklink/hooks/Data.py index fe1f727..f3a440d 100644 --- a/linklink/hooks/Data.py +++ b/linklink/hooks/Data.py @@ -45,7 +45,7 @@ def after_load_location_file(location_table: list) -> list: for i in range(1, count + 1): for j in range(1, MAX_PLAYERS + 1): location_table.append({ - "name": f"{item['name']} l$l {str(i).zfill(digit)} Player {str(j).zfill(players_digits)}", + "name": f"{item['name']} {str(i).zfill(digit)} Player {str(j).zfill(players_digits)}", "region": f"{item['name']} {str(i).zfill(digit)}", "category": [item['name']], "requires": "", diff --git a/linklink/hooks/World.py b/linklink/hooks/World.py index e695d70..24bfb7f 100644 --- a/linklink/hooks/World.py +++ b/linklink/hooks/World.py @@ -1,9 +1,12 @@ # Object classes from AP core, to represent an entire MultiWorld and this individual World that's part of it import logging +import re from worlds.AutoWorld import World from BaseClasses import MultiWorld, CollectionState, Item from typing import TYPE_CHECKING, Iterator +from worlds.linklink.hooks.Data import MAX_PLAYERS + # Object classes from Manual -- extending AP core -- representing items and locations that are used in generation from ..Items import ManualItem from ..Locations import ManualLocation @@ -95,15 +98,21 @@ def after_create_items(item_pool: list[ManualItem], world: World, multiworld: Mu def replace_nothings(world: World, multiworld: MultiWorld, player: int): # Remove "Nothing" items and replace them with filler items from other players item_pool = [i for i in multiworld.itempool if i.player == player] - item_count = len(item_pool) + my_item_count = len([i for i in item_pool if i.player == player]) location_count = len(multiworld.get_locations(player)) + unfilled_count = len(multiworld.get_unfilled_locations(player)) - filler_blacklist = ["SMZ3", "Links Awakening DX", "Manual_LinkLink_Silasary"] # These games don't have filler items or don't implement them correctly + filler_blacklist = ["Manual_LinkLink_Silasary"] victims = get_victims(multiworld, player) victims = [v for v in victims if v != player and multiworld.worlds[v].game not in filler_blacklist] # Only include players with filler items other_player = None - for item in [i for i in item_pool.copy() if i.name == "Nothing"]: + for item in [i for i in item_pool.copy() if i.name == "Nothing" and i.player == player]: + item_pool.remove(item) + + my_item_count = len([i for i in item_pool if i.player == player]) + needed = location_count - my_item_count + for _ in range(needed): if other_player is None: queue = iter(v for v in victims if v != player) other_player = next(queue) @@ -161,14 +170,7 @@ def before_generate_basic(world: World, multiworld: MultiWorld, player: int) -> # This method is run at the very end of pre-generation, once the place_item options have been handled and before AP generation occurs def after_generate_basic(world: "ManualWorld", multiworld: MultiWorld, player: int): victims = get_victims(multiworld, player) - - def remove_nothing(): - for i in multiworld.itempool.copy(): - if i.name == "Nothing" and i.player == player: - multiworld.itempool.remove(i) - return - logging.error("Could not find Nothing to remove") - return None + players_digits = len(str(MAX_PLAYERS)) unplaced_items = [i for i in multiworld.itempool if i.location is None] for item_data in item_table: @@ -176,6 +178,7 @@ def remove_nothing(): logging.debug(repr(item_data)) linklink: dict[str, list[str]] = item_data['linklink'] item_count = item_data['count'] + digit = len(str(item_count + 1)) for i in range(1, item_count + 1): any_placed = False n = 1 @@ -183,12 +186,14 @@ def remove_nothing(): if j == player or j not in victims: continue placed = False - location = multiworld.get_location(f"{item_data['name']} {i} Player {n}", player) + location_name = f"{item_data['name']} {str(i).zfill(digit)} Player {str(n).zfill(players_digits)}" + location = multiworld.get_location(location_name, player) if location is None: continue if multiworld.worlds[j].game not in linklink: logging.debug(f"Game {multiworld.worlds[j].game} not in linklink for {item_data['name']}") continue + location.ll_item_name = item_data['name'] options = [item for item in unplaced_items if item.name in linklink[multiworld.worlds[j].game] and item.player == j] options.sort(key=lambda x: linklink[multiworld.worlds[j].game].index(x.name)) if i == 1 and len(options) == 0: @@ -214,7 +219,7 @@ def remove_nothing(): for location in multiworld.get_unfilled_locations(player): if location.name.startswith(f"{item_data['name']} "): location.parent_region.locations.remove(location) - remove_nothing() + # remove_nothing() replace_nothings(world, multiworld, player) @@ -268,10 +273,11 @@ def keyfunc(i): for location in multiworld.get_locations(player): if not location.address: continue - elif location.parent_region is not None and location.parent_region.name == 'Free Items': + + if location.parent_region is not None and location.parent_region.name == "Free Items": continue - item_name, rest = location.name.split("l$l") # re.split(r'\d+', location.name)[0].strip() + item_name = location.ll_item_name #re.split(r"\d+", location.name)[0].strip() item_name = item_name.strip() p_num = str(location.item.player) if p_num not in iterators.keys(): From eabdfd108bcc88f5cb4c0e5ae2b514fc623df3bb Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 8 Dec 2025 11:34:55 +1100 Subject: [PATCH 13/15] Fix AP json --- linklink/archipelago.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 linklink/archipelago.json diff --git a/linklink/archipelago.json b/linklink/archipelago.json new file mode 100644 index 0000000..2ec4323 --- /dev/null +++ b/linklink/archipelago.json @@ -0,0 +1,10 @@ +{ + "game": "Manual_LinkLink_Silasary", + "world_version": "4.0.0", + "changelog": [ + "* A bunch of code cleanup and refactoring.", + "* Added Hint improvements (Thanks Nicopop for the code)", + "* Added Kingdom Hearts items (Thanks Twilight for the data)", + "* UT should work again now" + ] +} From 99aaa37d122060fb173a7e97954f2c77c2925dd6 Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 8 Dec 2025 11:36:30 +1100 Subject: [PATCH 14/15] Use relative imports --- linklink/hooks/World.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linklink/hooks/World.py b/linklink/hooks/World.py index 24bfb7f..ab73f47 100644 --- a/linklink/hooks/World.py +++ b/linklink/hooks/World.py @@ -5,7 +5,7 @@ from BaseClasses import MultiWorld, CollectionState, Item from typing import TYPE_CHECKING, Iterator -from worlds.linklink.hooks.Data import MAX_PLAYERS +from .Data import MAX_PLAYERS # Object classes from Manual -- extending AP core -- representing items and locations that are used in generation from ..Items import ManualItem From 020472c6df80efe366ceffdafe96c272bba256ef Mon Sep 17 00:00:00 2001 From: Katelyn Gigante Date: Mon, 8 Dec 2025 11:39:18 +1100 Subject: [PATCH 15/15] Whoops --- .github/workflows/release.yaml | 2 +- linklink/hooks/World.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fb87a86..43d2dec 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -49,7 +49,7 @@ jobs: id: get_version uses: mikefarah/yq@v4.45.1 with: - cmd: yq .world_version worlds/${{ env.world }}/archipelago.json --unwrapScalar=true + cmd: yq .world_version Archipelago/worlds/${{ env.world }}/archipelago.json --unwrapScalar=true - name: Get Name id: get_name uses: mikefarah/yq@v4.45.1 diff --git a/linklink/hooks/World.py b/linklink/hooks/World.py index ab73f47..da086be 100644 --- a/linklink/hooks/World.py +++ b/linklink/hooks/World.py @@ -3,7 +3,8 @@ import re from worlds.AutoWorld import World from BaseClasses import MultiWorld, CollectionState, Item -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING +from collections.abc import Iterator from .Data import MAX_PLAYERS