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/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..43d2dec --- /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 }}/linklink 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 Archipelago/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 }} 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/.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/data/items.json b/data/items.json deleted file mode 100644 index 0243765..0000000 --- a/data/items.json +++ /dev/null @@ -1,589 +0,0 @@ -[ - { - "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"] - } - }, - { - "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_pkmn.json b/data/items_pkmn.json deleted file mode 100644 index 27725a6..0000000 --- a/data/items_pkmn.json +++ /dev/null @@ -1,144 +0,0 @@ -[ - { - "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 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 diff --git a/hooks/Rules.py b/hooks/Rules.py deleted file mode 100644 index 66fd184..0000000 --- a/hooks/Rules.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Optional -from worlds.AutoWorld import World -from ..Helpers import clamp, get_items_with_value -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|" 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/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" + ] +} diff --git a/linklink/data/Sort-Items-linklink-data.py b/linklink/data/Sort-Items-linklink-data.py new file mode 100644 index 0000000..0e2818a --- /dev/null +++ b/linklink/data/Sort-Items-linklink-data.py @@ -0,0 +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}") 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/linklink/data/items.json b/linklink/data/items.json new file mode 100644 index 0000000..7b43bf1 --- /dev/null +++ b/linklink/data/items.json @@ -0,0 +1,553 @@ +{ + "$schema": "items.schema.json", + "data": [ + { + "count": 4, + "name": "Sword", + "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", + "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)", + "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)", + "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)", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "progression": true, + "linklink": { + "Ocarina of Time": ["Double Defense"], + "The Wind Waker": ["Magic Armor"], + "TUNIC": ["DEF Offering"] + } + }, + { + "count": 1, + "name": "Blue Tunic", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "progression": true, + "linklink": { + "Ocarina of Time": ["Iron Boots"], + "The Wind Waker": ["Iron Boots"] + } + }, + { + "count": 1, + "name": "Feather", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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"] + } + } + ] +} \ No newline at end of file diff --git a/linklink/data/items.schema.json b/linklink/data/items.schema.json new file mode 100644 index 0000000..decbfb1 --- /dev/null +++ b/linklink/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" + } + } + } +} diff --git a/linklink/data/items_kh.json b/linklink/data/items_kh.json new file mode 100644 index 0000000..b144c61 --- /dev/null +++ b/linklink/data/items_kh.json @@ -0,0 +1,435 @@ +{ + "$schema": "items.schema.json", + "data": [ + { + "count": 3, + "name": "Cure", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Magnet Element"], + "Kingdom Hearts Birth by Sleep": ["Magnet", "Magnera", "Magnega"] + } + }, + { + "count": 4, + "name": "High Jump", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Dodge Roll"], + "Kingdom Hearts 2": ["Dodge Roll"], + "Kingdom Hearts Birth by Sleep": ["Thunder Roll", "Firewheel"] + } + }, + { + "count": 4, + "name": "Glide", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Quick Run"], + "Kingdom Hearts Birth by Sleep": ["Air Slide", "Ice Slide"] + } + }, + { + "count": 1, + "name": "Scan", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Scan"], + "Kingdom Hearts 2": ["Scan"] + } + }, + { + "count": 1, + "name": "Leaf Bracer", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Leaf Bracer"], + "Kingdom Hearts 2": ["Leaf Bracer"], + "Kingdom Hearts Birth by Sleep": ["Leaf Bracer"] + } + }, + { + "count": 1, + "name": "Second Chance", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Second Chance"], + "Kingdom Hearts 2": ["Second Chance"], + "Kingdom Hearts Birth by Sleep": ["Second Chance"] + } + }, + { + "count": 1, + "name": "Once More", + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Once More"], + "Kingdom Hearts Birth by Sleep": ["Once More"] + } + }, + { + "count": 1, + "name": "Guard", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Guard+Barrier"], + "Kingdom Hearts 2": ["Guard"], + "Kingdom Hearts Birth by Sleep": ["Renewal Barrier"] + } + }, + { + "count": 1, + "name": "Aerial Recovery", + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Aerial Recovery"], + "Kingdom Hearts Birth by Sleep": ["Aerial Recovery"] + } + }, + { + "count": 1, + "name": "Blitz", + "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", + "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", + "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", + "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", + "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", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Zantetsuken"], + "Kingdom Hearts Birth by Sleep": ["Zantetsuken"], + "Kingdom Hearts RE Chain of Memories": ["Sleight Zantetsuken"] + } + }, + { + "count": 1, + "name": "Simba", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Simba"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Simba"] + } + }, + { + "count": 1, + "name": "Genie", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Genie"], + "Kingdom Hearts 2": ["Genie"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Genie"] + } + }, + { + "count": 1, + "name": "Bambi", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Bambi"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Bambi"] + } + }, + { + "count": 1, + "name": "Dumbo", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Dumbo"], + "Kingdom Hearts RE Chain of Memories": ["Card Set Dumbo"] + } + }, + { + "count": 1, + "name": "Tinker Bell+Peter Pan", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Stitch"], + "Kingdom Hearts Birth by Sleep": ["Experiment 626 D-Link"] + } + }, + { + "count": 2, + "name": "Destiny Islands", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Wonderland"], + "Kingdom Hearts RE Chain of Memories": ["World Card Wonderland", "Key to Rewards Wonderland"] + } + }, + { + "count": 2, + "name": "Olympus Coliseum", + "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", + "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", + "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", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts": ["Atlantica"], + "Kingdom Hearts RE Chain of Memories": ["World Card Atlantica", "Key to Rewards Atlantica"] + } + }, + { + "count": 2, + "name": "Neverland", + "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", + "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", + "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", + "progression": true, + "linklink": { + "Kingdom Hearts 2": ["Disney Castle Key"], + "Kingdom Hearts Birth by Sleep": ["Disney Town", "Mickey D-Link"] + } + }, + { + "count": 5, + "name": "Torn Page", + "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/linklink/data/items_pkmn.json b/linklink/data/items_pkmn.json new file mode 100644 index 0000000..19fbbc8 --- /dev/null +++ b/linklink/data/items_pkmn.json @@ -0,0 +1,133 @@ +{ + "$schema": "items.schema.json", + "data": [ + { + "count": 1, + "name": "HM01 Cut", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM01 Cut"], + "Pokemon Emerald": ["HM01 Cut"], + "Pokemon Red and Blue": ["HM01 Cut"] + } + }, + { + "count": 1, + "name": "HM02 Fly", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM02 Fly"], + "Pokemon Emerald": ["HM02 Fly"], + "Pokemon Red and Blue": ["HM02 Fly"] + } + }, + { + "count": 1, + "name": "HM03 Surf", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM03 Surf"], + "Pokemon Emerald": ["HM03 Surf"], + "Pokemon Red and Blue": ["HM03 Surf"] + } + }, + { + "count": 1, + "name": "HM04 Strength", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM04 Strength"], + "Pokemon Emerald": ["HM04 Strength"], + "Pokemon Red and Blue": ["HM04 Strength"] + } + }, + { + "count": 1, + "name": "HM05 Flash", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM05 Flash"], + "Pokemon Emerald": ["HM05 Flash"], + "Pokemon Red and Blue": ["HM05 Flash"] + } + }, + { + "count": 1, + "name": "HM06 Rock Smash", + "progression": true, + "linklink": { + "Pokemon Crystal": ["TM08 Rock Smash"], + "Pokemon Emerald": ["HM06 Rock Smash"] + } + }, + { + "count": 1, + "name": "HM07 Waterfall", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM07 Waterfall"], + "Pokemon Emerald": ["HM07 Waterfall"] + } + }, + { + "count": 0, + "name": "HM Whirlpool/ Dive", + "progression": true, + "linklink": { + "Pokemon Crystal": ["HM_WHIRLPOOL"], + "Pokemon Emerald": ["HM08 Dive"] + } + }, + { + "name": "Poke Flute", + "progression": true, + "linklink": { + "Pokemon Crystal": ["Poke Flute"], + "Pokemon Red and Blue": ["Poke Flute"] + } + }, + { + "name": "Bicycle", + "progression": true, + "linklink": { + "Pokemon Crystal": ["Bicycle"], + "Pokemon Emerald": ["Mach Bike"], + "Pokemon Red and Blue": ["Bicycle"] + } + }, + { + "name": "S.S. Ticket", + "progression": true, + "linklink": { + "Pokemon Crystal": ["S.S. Ticket"], + "Pokemon Emerald": ["S.S. Ticket"], + "Pokemon Red and Blue": ["S.S. Ticket"] + } + }, + { + "name": "Card Key", + "progression": true, + "linklink": { + "Pokemon Crystal": ["Card Key"], + "Pokemon Red and Blue": ["Card Key"] + } + }, + { + "name": "Scope", + "progression": true, + "linklink": { + "Pokemon Emerald": ["Devon Scope"], + "Pokemon Red and Blue": ["Silph Scope"] + } + }, + { + "name": "Coin Case", + "progression": true, + "linklink": { + "Pokemon Crystal": ["Coin Case"], + "Pokemon Emerald": ["Coin Case"], + "Pokemon Red and Blue": ["Coin Case"] + } + } + ] +} \ No newline at end of file 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 76% rename from hooks/Data.py rename to linklink/hooks/Data.py index 65bc936..f3a440d 100644 --- a/hooks/Data.py +++ b/linklink/hooks/Data.py @@ -1,7 +1,10 @@ +from typing import Any + + 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 +14,13 @@ 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: - from ..Data import load_data_file - item_table.extend(load_data_file('items_pkmn.json')) + for i, extra_file in enumerate(extra_item_files): + 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 + item_table.extend(new_table) for item in item_table: if 'count' not in item: @@ -31,18 +38,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']} {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": "", @@ -55,8 +67,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, @@ -76,7 +89,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 diff --git a/hooks/Helpers.py b/linklink/hooks/Helpers.py similarity index 78% rename from hooks/Helpers.py rename to linklink/hooks/Helpers.py index 5826fd8..9f6df9d 100644 --- a/hooks/Helpers.py +++ b/linklink/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/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/linklink/hooks/Rules.py b/linklink/hooks/Rules.py new file mode 100644 index 0000000..b458adf --- /dev/null +++ b/linklink/hooks/Rules.py @@ -0,0 +1,6 @@ +from typing import Optional +from worlds.AutoWorld import World +from ..Helpers import clamp, get_items_with_value +from BaseClasses import MultiWorld, CollectionState + +import re diff --git a/hooks/World.py b/linklink/hooks/World.py similarity index 78% rename from hooks/World.py rename to linklink/hooks/World.py index 01bf54c..da086be 100644 --- a/hooks/World.py +++ b/linklink/hooks/World.py @@ -1,8 +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 +from BaseClasses import MultiWorld, CollectionState, Item from typing import TYPE_CHECKING +from collections.abc import Iterator + +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 @@ -95,15 +99,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 +171,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 +179,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 +187,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: @@ -210,11 +216,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) @@ -245,19 +251,57 @@ 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 + + if location.parent_region is not None and location.parent_region.name == "Free Items": + continue + + 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(): + 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 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