diff --git a/.github/workflows/sync-loldle-data.yml b/.github/workflows/sync-loldle-data.yml new file mode 100644 index 0000000..c3ca9f2 --- /dev/null +++ b/.github/workflows/sync-loldle-data.yml @@ -0,0 +1,50 @@ +name: Sync loldle data + +on: + repository_dispatch: + types: [loldle-data-updated] + workflow_dispatch: + schedule: + # Safety net: runs Friday 07:00 UTC (~25h after loldle-data's Thursday job) + # in case the dispatch event was missed. + - cron: "0 7 * * 5" + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Download latest champions.json + run: | + curl -fsSL -o src/modules/loldle/champions.json.new \ + https://raw.githubusercontent.com/tiennm99/loldle-data/main/champions.json + + - name: Compare and replace + id: diff + run: | + if cmp -s src/modules/loldle/champions.json src/modules/loldle/champions.json.new; then + echo "No changes." + rm src/modules/loldle/champions.json.new + echo "changed=false" >> "$GITHUB_OUTPUT" + else + mv src/modules/loldle/champions.json.new src/modules/loldle/champions.json + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Regenerate champions-data.js + if: steps.diff.outputs.changed == 'true' + run: node scripts/build-loldle-data.js + + - name: Commit and push + if: steps.diff.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add src/modules/loldle/champions.json src/modules/loldle/champions-data.js + git commit -m "chore(loldle): sync champions.json from loldle-data" + git push diff --git a/package.json b/package.json index 4d0611b..ed3380c 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ }, "scripts": { "dev": "wrangler dev", - "deploy": "wrangler deploy && npm run db:migrate && npm run register", + "build:loldle-data": "node scripts/build-loldle-data.js", + "deploy": "npm run build:loldle-data && wrangler deploy && npm run db:migrate && npm run register", "db:migrate": "node scripts/migrate.js", - "register": "node --env-file=.env.deploy scripts/register.js", - "register:dry": "node --env-file=.env.deploy scripts/register.js --dry-run", + "register": "node --env-file-if-exists=.env.deploy scripts/register.js", + "register:dry": "node --env-file-if-exists=.env.deploy scripts/register.js --dry-run", "lint": "biome check src tests scripts && eslint src", "format": "biome format --write src tests scripts", "test": "vitest run" diff --git a/scripts/build-loldle-data.js b/scripts/build-loldle-data.js new file mode 100644 index 0000000..d3ba172 --- /dev/null +++ b/scripts/build-loldle-data.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node +/** + * @file build-loldle-data — wraps src/modules/loldle/champions.json + * as an ES module export to sidestep Node/esbuild disagreement on + * JSON import attributes (Node 24 requires `with { type: "json" }`, + * wrangler's bundled esbuild doesn't parse it yet). + * + * Run after any update to champions.json. + */ + +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const root = resolve(import.meta.dirname, ".."); +const src = resolve(root, "src/modules/loldle/champions.json"); +const dst = resolve(root, "src/modules/loldle/champions-data.js"); + +const json = readFileSync(src, "utf8").trimEnd(); +const out = [ + "// Auto-generated from champions.json — do NOT edit by hand.", + "// Regenerate with: node scripts/build-loldle-data.js", + `export default ${json};`, + "", +].join("\n"); + +writeFileSync(dst, out); +console.log(`wrote ${dst}`); diff --git a/scripts/migrate.js b/scripts/migrate.js index 85b6063..4a111a5 100644 --- a/scripts/migrate.js +++ b/scripts/migrate.js @@ -2,7 +2,7 @@ /** * @file migrate — custom D1 migration runner for per-module SQL files. * - * Discovers all `src/modules/*/ migrations; /*.sql` files, sorts them + * Discovers all SQL files under `src/modules//migrations/`, sorts them * deterministically (by `{moduleName}/{filename}`), then applies each NEW * migration via `wrangler d1 execute miti99bot-db --remote --file=`. * diff --git a/src/modules/loldle/champions-data.js b/src/modules/loldle/champions-data.js new file mode 100644 index 0000000..7d35ebb --- /dev/null +++ b/src/modules/loldle/champions-data.js @@ -0,0 +1,3784 @@ +// Auto-generated from champions.json — do NOT edit by hand. +// Regenerate with: node scripts/build-loldle-data.js +export default [ + { + "id": "Aatrox", + "name": "Aatrox", + "title": "the Darkin Blade", + "resource": "Blood Well", + "genre": "Fighter", + "skinCount": 41, + "image": { + "full": "Aatrox.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "runeterra", + "lane": "top" + }, + { + "id": "Ahri", + "name": "Ahri", + "title": "the Nine-Tailed Fox", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 95, + "image": { + "full": "Ahri.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "ionia", + "lane": "mid" + }, + { + "id": "Akali", + "name": "Akali", + "title": "the Rogue Assassin", + "resource": "Energy", + "genre": "Assassin", + "skinCount": 101, + "image": { + "full": "Akali.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Akshan", + "name": "Akshan", + "title": "the Rogue Sentinel", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 21, + "image": { + "full": "Akshan.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2021, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Alistar", + "name": "Alistar", + "title": "the Minotaur", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 46, + "image": { + "full": "Alistar.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Ambessa", + "name": "Ambessa", + "title": "Matriarch of War", + "resource": "Energy", + "genre": "Fighter,Assassin", + "skinCount": 8, + "image": { + "full": "Ambessa.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2024, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Amumu", + "name": "Amumu", + "title": "the Sad Mummy", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 62, + "image": { + "full": "Amumu.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "Anivia", + "name": "Anivia", + "title": "the Cryophoenix", + "resource": "Mana", + "genre": "Mage", + "skinCount": 55, + "image": { + "full": "Anivia.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "freljord", + "lane": "mid" + }, + { + "id": "Annie", + "name": "Annie", + "title": "the Dark Child", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 58, + "image": { + "full": "Annie.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "mid" + }, + { + "id": "Aphelios", + "name": "Aphelios", + "title": "the Weapon of the Faithful", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 40, + "image": { + "full": "Aphelios.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2019, + "region": "mount-targon", + "lane": "bottom" + }, + { + "id": "Ashe", + "name": "Ashe", + "title": "the Frost Archer", + "resource": "Mana", + "genre": "Marksman,Support", + "skinCount": 93, + "image": { + "full": "Ashe.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "freljord", + "lane": "bottom,support" + }, + { + "id": "AurelionSol", + "name": "Aurelion Sol", + "title": "The Star Forger", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "AurelionSol.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2016, + "region": "mount-targon", + "lane": "mid" + }, + { + "id": "Aurora", + "name": "Aurora", + "title": "the Witch Between Worlds", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 20, + "image": { + "full": "Aurora.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2024, + "region": "freljord", + "lane": "top,mid" + }, + { + "id": "Azir", + "name": "Azir", + "title": "the Emperor of the Sands", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 28, + "image": { + "full": "Azir.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2014, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Bard", + "name": "Bard", + "title": "the Wandering Caretaker", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 46, + "image": { + "full": "Bard.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2015, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Belveth", + "name": "Bel'Veth", + "title": "the Empress of the Void", + "resource": "", + "genre": "Fighter", + "skinCount": 29, + "image": { + "full": "Belveth.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2022, + "region": "void", + "lane": "jungle" + }, + { + "id": "Blitzcrank", + "name": "Blitzcrank", + "title": "the Great Steam Golem", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 74, + "image": { + "full": "Blitzcrank.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "support" + }, + { + "id": "Brand", + "name": "Brand", + "title": "the Burning Vengeance", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 53, + "image": { + "full": "Brand.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "runeterra", + "lane": "jungle,support" + }, + { + "id": "Braum", + "name": "Braum", + "title": "the Heart of the Freljord", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 61, + "image": { + "full": "Braum.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2014, + "region": "freljord", + "lane": "support" + }, + { + "id": "Briar", + "name": "Briar", + "title": "the Restrained Hunger", + "resource": "Fury", + "genre": "Fighter,Assassin", + "skinCount": 29, + "image": { + "full": "Briar.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2023, + "region": "noxus", + "lane": "jungle" + }, + { + "id": "Caitlyn", + "name": "Caitlyn", + "title": "the Sheriff of Piltover", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 63, + "image": { + "full": "Caitlyn.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "piltover", + "lane": "bottom" + }, + { + "id": "Camille", + "name": "Camille", + "title": "the Steel Shadow", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 53, + "image": { + "full": "Camille.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2016, + "region": "piltover", + "lane": "top" + }, + { + "id": "Cassiopeia", + "name": "Cassiopeia", + "title": "the Serpent's Embrace", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "Cassiopeia.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Chogath", + "name": "Cho'Gath", + "title": "the Terror of the Void", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 42, + "image": { + "full": "Chogath.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "divers", + "attackType": "close", + "releaseDate": 2009, + "region": "void", + "lane": "top" + }, + { + "id": "Corki", + "name": "Corki", + "title": "the Daring Bombardier", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 45, + "image": { + "full": "Corki.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "mid" + }, + { + "id": "Darius", + "name": "Darius", + "title": "the Hand of Noxus", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 73, + "image": { + "full": "Darius.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "noxus", + "lane": "top" + }, + { + "id": "Diana", + "name": "Diana", + "title": "Scorn of the Moon", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 75, + "image": { + "full": "Diana.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "mount-targon", + "lane": "jungle,mid" + }, + { + "id": "DrMundo", + "name": "Dr. Mundo", + "title": "the Madman of Zaun", + "resource": "None", + "genre": "Tank,Fighter", + "skinCount": 30, + "image": { + "full": "DrMundo.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "top" + }, + { + "id": "Draven", + "name": "Draven", + "title": "the Glorious Executioner", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 78, + "image": { + "full": "Draven.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "noxus", + "lane": "bottom" + }, + { + "id": "Ekko", + "name": "Ekko", + "title": "the Boy Who Shattered Time", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 59, + "image": { + "full": "Ekko.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2015, + "region": "zaun", + "lane": "jungle,mid" + }, + { + "id": "Elise", + "name": "Elise", + "title": "the Spider Queen", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 36, + "image": { + "full": "Elise.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Evelynn", + "name": "Evelynn", + "title": "Agony's Embrace", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 65, + "image": { + "full": "Evelynn.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Ezreal", + "name": "Ezreal", + "title": "the Prodigal Explorer", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 75, + "image": { + "full": "Ezreal.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "piltover", + "lane": "bottom" + }, + { + "id": "Fiddlesticks", + "name": "Fiddlesticks", + "title": "the Ancient Fear", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 55, + "image": { + "full": "Fiddlesticks.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Fiora", + "name": "Fiora", + "title": "the Grand Duelist", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 100, + "image": { + "full": "Fiora.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "demacia", + "lane": "top" + }, + { + "id": "Fizz", + "name": "Fizz", + "title": "the Tidal Trickster", + "resource": "Mana", + "genre": "Assassin,Fighter", + "skinCount": 44, + "image": { + "full": "Fizz.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bilgewater", + "lane": "mid" + }, + { + "id": "Galio", + "name": "Galio", + "title": "the Colossus", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 47, + "image": { + "full": "Galio.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "mid" + }, + { + "id": "Gangplank", + "name": "Gangplank", + "title": "the Saltwater Scourge", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 42, + "image": { + "full": "Gangplank.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Garen", + "name": "Garen", + "title": "The Might of Demacia", + "resource": "None", + "genre": "Fighter,Tank", + "skinCount": 55, + "image": { + "full": "Garen.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "top" + }, + { + "id": "Gnar", + "name": "Gnar", + "title": "the Missing Link", + "resource": "Rage", + "genre": "Fighter,Tank", + "skinCount": 44, + "image": { + "full": "Gnar.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2014, + "region": "freljord", + "lane": "top" + }, + { + "id": "Gragas", + "name": "Gragas", + "title": "the Rabble Rouser", + "resource": "Mana", + "genre": "Fighter,Mage", + "skinCount": 48, + "image": { + "full": "Gragas.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Graves", + "name": "Graves", + "title": "the Outlaw", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 54, + "image": { + "full": "Graves.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bilgewater", + "lane": "jungle" + }, + { + "id": "Gwen", + "name": "Gwen", + "title": "The Hallowed Seamstress", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 48, + "image": { + "full": "Gwen.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "top" + }, + { + "id": "Hecarim", + "name": "Hecarim", + "title": "the Shadow of War", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 50, + "image": { + "full": "Hecarim.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Heimerdinger", + "name": "Heimerdinger", + "title": "the Revered Inventor", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 27, + "image": { + "full": "Heimerdinger.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "piltover", + "lane": "support" + }, + { + "id": "Hwei", + "name": "Hwei", + "title": "the Visionary", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 21, + "image": { + "full": "Hwei.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2023, + "region": "ionia", + "lane": "mid,support" + }, + { + "id": "Illaoi", + "name": "Illaoi", + "title": "the Kraken Priestess", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 37, + "image": { + "full": "Illaoi.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2015, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Irelia", + "name": "Irelia", + "title": "the Blade Dancer", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 63, + "image": { + "full": "Irelia.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Ivern", + "name": "Ivern", + "title": "the Green Father", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 30, + "image": { + "full": "Ivern.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2016, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Janna", + "name": "Janna", + "title": "the Storm's Fury", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 67, + "image": { + "full": "Janna.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "zaun", + "lane": "support" + }, + { + "id": "JarvanIV", + "name": "Jarvan IV", + "title": "the Exemplar of Demacia", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 55, + "image": { + "full": "JarvanIV.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Jax", + "name": "Jax", + "title": "Grandmaster at Arms", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 42, + "image": { + "full": "Jax.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "top,jungle" + }, + { + "id": "Jayce", + "name": "Jayce", + "title": "the Defender of Tomorrow", + "resource": "Mana", + "genre": "Fighter,Marksman", + "skinCount": 47, + "image": { + "full": "Jayce.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "piltover", + "lane": "top" + }, + { + "id": "Jhin", + "name": "Jhin", + "title": "the Virtuoso", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 54, + "image": { + "full": "Jhin.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2016, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Jinx", + "name": "Jinx", + "title": "the Loose Cannon", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 64, + "image": { + "full": "Jinx.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "KSante", + "name": "K'Sante", + "title": "the Pride of Nazumah", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 18, + "image": { + "full": "KSante.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2022, + "region": "shurima", + "lane": "top" + }, + { + "id": "Kaisa", + "name": "Kai'Sa", + "title": "Daughter of the Void", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 78, + "image": { + "full": "Kaisa.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2018, + "region": "void", + "lane": "bottom" + }, + { + "id": "Kalista", + "name": "Kalista", + "title": "the Spear of Vengeance", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 33, + "image": { + "full": "Kalista.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2014, + "region": "shadow-isles", + "lane": "bottom" + }, + { + "id": "Karma", + "name": "Karma", + "title": "the Enlightened One", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 76, + "image": { + "full": "Karma.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "ionia", + "lane": "mid,support" + }, + { + "id": "Karthus", + "name": "Karthus", + "title": "the Deathsinger", + "resource": "Mana", + "genre": "Mage", + "skinCount": 44, + "image": { + "full": "Karthus.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Kassadin", + "name": "Kassadin", + "title": "the Void Walker", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 32, + "image": { + "full": "Kassadin.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "void", + "lane": "mid" + }, + { + "id": "Katarina", + "name": "Katarina", + "title": "the Sinister Blade", + "resource": "None", + "genre": "Assassin,Mage", + "skinCount": 78, + "image": { + "full": "Katarina.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Kayle", + "name": "Kayle", + "title": "the Righteous", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 88, + "image": { + "full": "Kayle.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "demacia", + "lane": "top" + }, + { + "id": "Kayn", + "name": "Kayn", + "title": "the Shadow Reaper", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 31, + "image": { + "full": "Kayn.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Kennen", + "name": "Kennen", + "title": "the Heart of the Tempest", + "resource": "Energy", + "genre": "Mage", + "skinCount": 34, + "image": { + "full": "Kennen.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "ionia", + "lane": "top" + }, + { + "id": "Khazix", + "name": "Kha'Zix", + "title": "the Voidreaver", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 58, + "image": { + "full": "Khazix.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "void", + "lane": "jungle" + }, + { + "id": "Kindred", + "name": "Kindred", + "title": "The Eternal Hunters", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 43, + "image": { + "full": "Kindred.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2015, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Kled", + "name": "Kled", + "title": "the Cantankerous Cavalier", + "resource": "Courage", + "genre": "Fighter", + "skinCount": 27, + "image": { + "full": "Kled.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2016, + "region": "noxus", + "lane": "top" + }, + { + "id": "KogMaw", + "name": "Kog'Maw", + "title": "the Mouth of the Abyss", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 65, + "image": { + "full": "KogMaw.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "void", + "lane": "bottom" + }, + { + "id": "Leblanc", + "name": "LeBlanc", + "title": "the Deceiver", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 59, + "image": { + "full": "Leblanc.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "LeeSin", + "name": "Lee Sin", + "title": "the Blind Monk", + "resource": "Energy", + "genre": "Fighter,Assassin", + "skinCount": 79, + "image": { + "full": "LeeSin.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Leona", + "name": "Leona", + "title": "the Radiant Dawn", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 64, + "image": { + "full": "Leona.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Lillia", + "name": "Lillia", + "title": "the Bashful Bloom", + "resource": "Mana", + "genre": "Fighter,Mage", + "skinCount": 53, + "image": { + "full": "Lillia.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Lissandra", + "name": "Lissandra", + "title": "the Ice Witch", + "resource": "Mana", + "genre": "Mage", + "skinCount": 52, + "image": { + "full": "Lissandra.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "freljord", + "lane": "mid" + }, + { + "id": "Lucian", + "name": "Lucian", + "title": "the Purifier", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 73, + "image": { + "full": "Lucian.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2013, + "region": "demacia", + "lane": "bottom" + }, + { + "id": "Lulu", + "name": "Lulu", + "title": "the Fae Sorceress", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 56, + "image": { + "full": "Lulu.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "bandle-city", + "lane": "support" + }, + { + "id": "Lux", + "name": "Lux", + "title": "the Lady of Luminosity", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 81, + "image": { + "full": "Lux.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "demacia", + "lane": "mid,support" + }, + { + "id": "Malphite", + "name": "Malphite", + "title": "Shard of the Monolith", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 57, + "image": { + "full": "Malphite.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "ixtal", + "lane": "top" + }, + { + "id": "Malzahar", + "name": "Malzahar", + "title": "the Prophet of the Void", + "resource": "Mana", + "genre": "Mage", + "skinCount": 58, + "image": { + "full": "Malzahar.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "void", + "lane": "mid" + }, + { + "id": "Maokai", + "name": "Maokai", + "title": "the Twisted Treant", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 36, + "image": { + "full": "Maokai.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shadow-isles", + "lane": "support" + }, + { + "id": "MasterYi", + "name": "Master Yi", + "title": "the Wuju Bladesman", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 90, + "image": { + "full": "MasterYi.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Mel", + "name": "Mel", + "title": "the Soul's Reflection", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 12, + "image": { + "full": "Mel.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2025, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Milio", + "name": "Milio", + "title": "The Gentle Flame", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 29, + "image": { + "full": "Milio.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2023, + "region": "ixtal", + "lane": "support" + }, + { + "id": "MissFortune", + "name": "Miss Fortune", + "title": "the Bounty Hunter", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 69, + "image": { + "full": "MissFortune.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "bilgewater", + "lane": "bottom" + }, + { + "id": "Mordekaiser", + "name": "Mordekaiser", + "title": "the Iron Revenant", + "resource": "Shield", + "genre": "Fighter,Mage", + "skinCount": 57, + "image": { + "full": "Mordekaiser.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "noxus", + "lane": "top" + }, + { + "id": "Morgana", + "name": "Morgana", + "title": "the Fallen", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 82, + "image": { + "full": "Morgana.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "demacia", + "lane": "support" + }, + { + "id": "Naafiri", + "name": "Naafiri", + "title": "the Hound of a Hundred Bites", + "resource": "Mana", + "genre": "Assassin,Fighter", + "skinCount": 38, + "image": { + "full": "Naafiri.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2023, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Nami", + "name": "Nami", + "title": "the Tidecaller", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 68, + "image": { + "full": "Nami.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Nasus", + "name": "Nasus", + "title": "the Curator of the Sands", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 63, + "image": { + "full": "Nasus.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "top" + }, + { + "id": "Nautilus", + "name": "Nautilus", + "title": "the Titan of the Depths", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 46, + "image": { + "full": "Nautilus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "bilgewater", + "lane": "support" + }, + { + "id": "Neeko", + "name": "Neeko", + "title": "the Curious Chameleon", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 50, + "image": { + "full": "Neeko.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2018, + "region": "ixtal", + "lane": "mid,support" + }, + { + "id": "Nidalee", + "name": "Nidalee", + "title": "the Bestial Huntress", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 67, + "image": { + "full": "Nidalee.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "ixtal", + "lane": "jungle" + }, + { + "id": "Nilah", + "name": "Nilah", + "title": "the Joy Unbound", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 30, + "image": { + "full": "Nilah.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2022, + "region": "runeterra", + "lane": "bottom" + }, + { + "id": "Nocturne", + "name": "Nocturne", + "title": "the Eternal Nightmare", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 36, + "image": { + "full": "Nocturne.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "divers", + "attackType": "close", + "releaseDate": 2011, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Nunu", + "name": "Nunu & Willump", + "title": "the Boy and His Yeti", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 54, + "image": { + "full": "Nunu.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Olaf", + "name": "Olaf", + "title": "the Berserker", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 53, + "image": { + "full": "Olaf.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "top" + }, + { + "id": "Orianna", + "name": "Orianna", + "title": "the Lady of Clockwork", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 40, + "image": { + "full": "Orianna.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "piltover", + "lane": "mid" + }, + { + "id": "Ornn", + "name": "Ornn", + "title": "The Fire below the Mountain", + "resource": "Mana", + "genre": "Tank", + "skinCount": 38, + "image": { + "full": "Ornn.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "freljord", + "lane": "top" + }, + { + "id": "Pantheon", + "name": "Pantheon", + "title": "the Unbreakable Spear", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 47, + "image": { + "full": "Pantheon.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "mount-targon", + "lane": "top,support" + }, + { + "id": "Poppy", + "name": "Poppy", + "title": "Keeper of the Hammer", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 51, + "image": { + "full": "Poppy.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "top,jungle" + }, + { + "id": "Pyke", + "name": "Pyke", + "title": "the Bloodharbor Ripper", + "resource": "Mana", + "genre": "Support,Assassin", + "skinCount": 85, + "image": { + "full": "Pyke.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2018, + "region": "bilgewater", + "lane": "support" + }, + { + "id": "Qiyana", + "name": "Qiyana", + "title": "Empress of the Elements", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 51, + "image": { + "full": "Qiyana.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2019, + "region": "ixtal", + "lane": "mid" + }, + { + "id": "Quinn", + "name": "Quinn", + "title": "Demacia's Wings", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 24, + "image": { + "full": "Quinn.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "demacia", + "lane": "top" + }, + { + "id": "Rakan", + "name": "Rakan", + "title": "The Charmer", + "resource": "Mana", + "genre": "Support", + "skinCount": 58, + "image": { + "full": "Rakan.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "ionia", + "lane": "support" + }, + { + "id": "Rammus", + "name": "Rammus", + "title": "the Armordillo", + "resource": "Mana", + "genre": "Tank", + "skinCount": 35, + "image": { + "full": "Rammus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "RekSai", + "name": "Rek'Sai", + "title": "the Void Burrower", + "resource": "Rage", + "genre": "Fighter,Tank", + "skinCount": 36, + "image": { + "full": "RekSai.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2014, + "region": "void", + "lane": "jungle" + }, + { + "id": "Rell", + "name": "Rell", + "title": "the Iron Maiden", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 31, + "image": { + "full": "Rell.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2020, + "region": "noxus", + "lane": "support" + }, + { + "id": "Renata", + "name": "Renata Glasc", + "title": "the Chem-Baroness", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 32, + "image": { + "full": "Renata.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2022, + "region": "zaun", + "lane": "support" + }, + { + "id": "Renekton", + "name": "Renekton", + "title": "the Butcher of the Sands", + "resource": "Fury", + "genre": "Fighter,Tank", + "skinCount": 57, + "image": { + "full": "Renekton.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shurima", + "lane": "top" + }, + { + "id": "Rengar", + "name": "Rengar", + "title": "the Pridestalker", + "resource": "Ferocity", + "genre": "Assassin,Fighter", + "skinCount": 49, + "image": { + "full": "Rengar.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "ixtal", + "lane": "jungle" + }, + { + "id": "Riven", + "name": "Riven", + "title": "the Exile", + "resource": "None", + "genre": "Fighter,Assassin", + "skinCount": 73, + "image": { + "full": "Riven.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "noxus", + "lane": "top" + }, + { + "id": "Rumble", + "name": "Rumble", + "title": "the Mechanized Menace", + "resource": "Heat", + "genre": "Fighter,Mage", + "skinCount": 32, + "image": { + "full": "Rumble.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bandle-city", + "lane": "top" + }, + { + "id": "Ryze", + "name": "Ryze", + "title": "the Rune Mage", + "resource": "Mana", + "genre": "Mage", + "skinCount": 39, + "image": { + "full": "Ryze.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "mid" + }, + { + "id": "Samira", + "name": "Samira", + "title": "the Desert Rose", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 42, + "image": { + "full": "Samira.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2020, + "region": "noxus", + "lane": "bottom" + }, + { + "id": "Sejuani", + "name": "Sejuani", + "title": "Fury of the North", + "resource": "Mana", + "genre": "Tank", + "skinCount": 57, + "image": { + "full": "Sejuani.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Senna", + "name": "Senna", + "title": "the Redeemer", + "resource": "Mana", + "genre": "Support,Marksman", + "skinCount": 80, + "image": { + "full": "Senna.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2019, + "region": "runeterra", + "lane": "bottom,support" + }, + { + "id": "Seraphine", + "name": "Seraphine", + "title": "the Starry-Eyed Songstress", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 68, + "image": { + "full": "Seraphine.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2020, + "region": "piltover", + "lane": "bottom,support" + }, + { + "id": "Sett", + "name": "Sett", + "title": "the Boss", + "resource": "Grit", + "genre": "Fighter,Tank", + "skinCount": 76, + "image": { + "full": "Sett.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "top" + }, + { + "id": "Shaco", + "name": "Shaco", + "title": "the Demon Jester", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 71, + "image": { + "full": "Shaco.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Shen", + "name": "Shen", + "title": "the Eye of Twilight", + "resource": "Energy", + "genre": "Tank", + "skinCount": 53, + "image": { + "full": "Shen.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top" + }, + { + "id": "Shyvana", + "name": "Shyvana", + "title": "the Half-Dragon", + "resource": "Fury", + "genre": "Fighter,Tank", + "skinCount": 26, + "image": { + "full": "Shyvana.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Singed", + "name": "Singed", + "title": "the Mad Chemist", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 29, + "image": { + "full": "Singed.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "top" + }, + { + "id": "Sion", + "name": "Sion", + "title": "The Undead Juggernaut", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 50, + "image": { + "full": "Sion.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "noxus", + "lane": "top" + }, + { + "id": "Sivir", + "name": "Sivir", + "title": "the Battle Mistress", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 81, + "image": { + "full": "Sivir.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "shurima", + "lane": "bottom" + }, + { + "id": "Skarner", + "name": "Skarner", + "title": "the Primordial Sovereign", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 15, + "image": { + "full": "Skarner.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ixtal", + "lane": "top,jungle" + }, + { + "id": "Smolder", + "name": "Smolder", + "title": "the Fiery Fledgling", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 20, + "image": { + "full": "Smolder.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2024, + "region": "runeterra", + "lane": "bottom" + }, + { + "id": "Sona", + "name": "Sona", + "title": "Maven of the Strings", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 77, + "image": { + "full": "Sona.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "demacia", + "lane": "support" + }, + { + "id": "Soraka", + "name": "Soraka", + "title": "the Starchild", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 63, + "image": { + "full": "Soraka.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Swain", + "name": "Swain", + "title": "the Noxian Grand General", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 51, + "image": { + "full": "Swain.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "support" + }, + { + "id": "Sylas", + "name": "Sylas", + "title": "the Unshackled", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 54, + "image": { + "full": "Sylas.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2019, + "region": "demacia", + "lane": "mid" + }, + { + "id": "Syndra", + "name": "Syndra", + "title": "the Dark Sovereign", + "resource": "Mana", + "genre": "Mage", + "skinCount": 74, + "image": { + "full": "Syndra.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "ionia", + "lane": "mid" + }, + { + "id": "TahmKench", + "name": "Tahm Kench", + "title": "The River King", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 47, + "image": { + "full": "TahmKench.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2015, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Taliyah", + "name": "Taliyah", + "title": "the Stoneweaver", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 25, + "image": { + "full": "Taliyah.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2016, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "Talon", + "name": "Talon", + "title": "the Blade's Shadow", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 60, + "image": { + "full": "Talon.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Taric", + "name": "Taric", + "title": "the Shield of Valoran", + "resource": "Mana", + "genre": "Support,Tank", + "skinCount": 36, + "image": { + "full": "Taric.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Teemo", + "name": "Teemo", + "title": "the Swift Scout", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 63, + "image": { + "full": "Teemo.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "top" + }, + { + "id": "Thresh", + "name": "Thresh", + "title": "the Chain Warden", + "resource": "Mana", + "genre": "Support,Tank", + "skinCount": 68, + "image": { + "full": "Thresh.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "shadow-isles", + "lane": "support" + }, + { + "id": "Tristana", + "name": "Tristana", + "title": "the Yordle Gunner", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 79, + "image": { + "full": "Tristana.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "bottom" + }, + { + "id": "Trundle", + "name": "Trundle", + "title": "the Troll King", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 30, + "image": { + "full": "Trundle.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "top" + }, + { + "id": "Tryndamere", + "name": "Tryndamere", + "title": "the Barbarian King", + "resource": "Fury", + "genre": "Fighter,Assassin", + "skinCount": 47, + "image": { + "full": "Tryndamere.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "top" + }, + { + "id": "TwistedFate", + "name": "Twisted Fate", + "title": "the Card Master", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 54, + "image": { + "full": "TwistedFate.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bilgewater", + "lane": "mid,bottom" + }, + { + "id": "Twitch", + "name": "Twitch", + "title": "the Plague Rat", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 59, + "image": { + "full": "Twitch.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Udyr", + "name": "Udyr", + "title": "the Spirit Walker", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 12, + "image": { + "full": "Udyr.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "top,jungle" + }, + { + "id": "Urgot", + "name": "Urgot", + "title": "the Dreadnought", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 41, + "image": { + "full": "Urgot.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "zaun", + "lane": "top" + }, + { + "id": "Varus", + "name": "Varus", + "title": "the Arrow of Retribution", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 71, + "image": { + "full": "Varus.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Vayne", + "name": "Vayne", + "title": "the Night Hunter", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 67, + "image": { + "full": "Vayne.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "demacia", + "lane": "bottom" + }, + { + "id": "Veigar", + "name": "Veigar", + "title": "the Tiny Master of Evil", + "resource": "Mana", + "genre": "Mage", + "skinCount": 67, + "image": { + "full": "Veigar.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "mid" + }, + { + "id": "Velkoz", + "name": "Vel'Koz", + "title": "the Eye of the Void", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 29, + "image": { + "full": "Velkoz.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2014, + "region": "void", + "lane": "support" + }, + { + "id": "Vex", + "name": "Vex", + "title": "the Gloomist", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "Vex.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "mid" + }, + { + "id": "Vi", + "name": "Vi", + "title": "the Piltover Enforcer", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 51, + "image": { + "full": "Vi.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "piltover", + "lane": "jungle" + }, + { + "id": "Viego", + "name": "Viego", + "title": "The Ruined King", + "resource": "None", + "genre": "Fighter,Assassin", + "skinCount": 43, + "image": { + "full": "Viego.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Viktor", + "name": "Viktor", + "title": "the Herald of the Arcane", + "resource": "Mana", + "genre": "Mage", + "skinCount": 34, + "image": { + "full": "Viktor.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "zaun", + "lane": "mid" + }, + { + "id": "Vladimir", + "name": "Vladimir", + "title": "the Crimson Reaper", + "resource": "Crimson Rush", + "genre": "Mage,Fighter", + "skinCount": 58, + "image": { + "full": "Vladimir.png", + "sprite": "champion5.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Volibear", + "name": "Volibear", + "title": "the Relentless Storm", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 37, + "image": { + "full": "Volibear.png", + "sprite": "champion5.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "freljord", + "lane": "top" + }, + { + "id": "Warwick", + "name": "Warwick", + "title": "the Uncaged Wrath of Zaun", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 61, + "image": { + "full": "Warwick.png", + "sprite": "champion5.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "jungle" + }, + { + "id": "MonkeyKing", + "name": "Wukong", + "title": "the Monkey King", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 36, + "image": { + "full": "MonkeyKing.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Xayah", + "name": "Xayah", + "title": "the Rebel", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 65, + "image": { + "full": "Xayah.png", + "sprite": "champion5.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2017, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Xerath", + "name": "Xerath", + "title": "the Magus Ascendant", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 33, + "image": { + "full": "Xerath.png", + "sprite": "champion5.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "shurima", + "lane": "mid,support" + }, + { + "id": "XinZhao", + "name": "Xin Zhao", + "title": "the Seneschal of Demacia", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 52, + "image": { + "full": "XinZhao.png", + "sprite": "champion5.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Yasuo", + "name": "Yasuo", + "title": "the Unforgiven", + "resource": "Flow", + "genre": "Fighter,Assassin", + "skinCount": 97, + "image": { + "full": "Yasuo.png", + "sprite": "champion5.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "ionia", + "lane": "top,mid,bottom" + }, + { + "id": "Yone", + "name": "Yone", + "title": "the Unforgotten", + "resource": "Flow", + "genre": "Fighter,Assassin", + "skinCount": 76, + "image": { + "full": "Yone.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Yorick", + "name": "Yorick", + "title": "Shepherd of Souls", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 59, + "image": { + "full": "Yorick.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shadow-isles", + "lane": "top" + }, + { + "id": "Yunara", + "name": "Yunara", + "title": "the Unbroken Faith", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 10, + "image": { + "full": "Yunara.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "region": "ionia" + }, + { + "id": "Yuumi", + "name": "Yuumi", + "title": "the Magical Cat", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 79, + "image": { + "full": "Yuumi.png", + "sprite": "champion5.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2019, + "region": "bandle-city", + "lane": "support" + }, + { + "id": "Zaahen", + "name": "Zaahen", + "title": "The Unsundered", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 10, + "image": { + "full": "Zaahen.png", + "sprite": "champion5.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "region": "runeterra" + }, + { + "id": "Zac", + "name": "Zac", + "title": "the Secret Weapon", + "resource": "None", + "genre": "Tank,Fighter", + "skinCount": 42, + "image": { + "full": "Zac.png", + "sprite": "champion5.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "zaun", + "lane": "jungle" + }, + { + "id": "Zed", + "name": "Zed", + "title": "the Master of Shadows", + "resource": "Energy", + "genre": "Assassin", + "skinCount": 71, + "image": { + "full": "Zed.png", + "sprite": "champion5.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "ionia", + "lane": "mid" + }, + { + "id": "Zeri", + "name": "Zeri", + "title": "The Spark of Zaun", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 47, + "image": { + "full": "Zeri.png", + "sprite": "champion5.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2022, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Ziggs", + "name": "Ziggs", + "title": "the Hexplosives Expert", + "resource": "Mana", + "genre": "Mage", + "skinCount": 52, + "image": { + "full": "Ziggs.png", + "sprite": "champion5.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Zilean", + "name": "Zilean", + "title": "the Chronokeeper", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 33, + "image": { + "full": "Zilean.png", + "sprite": "champion5.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Zoe", + "name": "Zoe", + "title": "the Aspect of Twilight", + "resource": "Mana", + "genre": "Mage", + "skinCount": 45, + "image": { + "full": "Zoe.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2017, + "region": "mount-targon", + "lane": "mid" + }, + { + "id": "Zyra", + "name": "Zyra", + "title": "Rise of the Thorns", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 74, + "image": { + "full": "Zyra.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "ixtal", + "lane": "support" + } +]; diff --git a/src/modules/loldle/champions.json b/src/modules/loldle/champions.json new file mode 100644 index 0000000..c2412df --- /dev/null +++ b/src/modules/loldle/champions.json @@ -0,0 +1,3782 @@ +[ + { + "id": "Aatrox", + "name": "Aatrox", + "title": "the Darkin Blade", + "resource": "Blood Well", + "genre": "Fighter", + "skinCount": 41, + "image": { + "full": "Aatrox.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "runeterra", + "lane": "top" + }, + { + "id": "Ahri", + "name": "Ahri", + "title": "the Nine-Tailed Fox", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 95, + "image": { + "full": "Ahri.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "ionia", + "lane": "mid" + }, + { + "id": "Akali", + "name": "Akali", + "title": "the Rogue Assassin", + "resource": "Energy", + "genre": "Assassin", + "skinCount": 101, + "image": { + "full": "Akali.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Akshan", + "name": "Akshan", + "title": "the Rogue Sentinel", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 21, + "image": { + "full": "Akshan.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2021, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Alistar", + "name": "Alistar", + "title": "the Minotaur", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 46, + "image": { + "full": "Alistar.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Ambessa", + "name": "Ambessa", + "title": "Matriarch of War", + "resource": "Energy", + "genre": "Fighter,Assassin", + "skinCount": 8, + "image": { + "full": "Ambessa.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2024, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Amumu", + "name": "Amumu", + "title": "the Sad Mummy", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 62, + "image": { + "full": "Amumu.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "Anivia", + "name": "Anivia", + "title": "the Cryophoenix", + "resource": "Mana", + "genre": "Mage", + "skinCount": 55, + "image": { + "full": "Anivia.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "freljord", + "lane": "mid" + }, + { + "id": "Annie", + "name": "Annie", + "title": "the Dark Child", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 58, + "image": { + "full": "Annie.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "mid" + }, + { + "id": "Aphelios", + "name": "Aphelios", + "title": "the Weapon of the Faithful", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 40, + "image": { + "full": "Aphelios.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2019, + "region": "mount-targon", + "lane": "bottom" + }, + { + "id": "Ashe", + "name": "Ashe", + "title": "the Frost Archer", + "resource": "Mana", + "genre": "Marksman,Support", + "skinCount": 93, + "image": { + "full": "Ashe.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "freljord", + "lane": "bottom,support" + }, + { + "id": "AurelionSol", + "name": "Aurelion Sol", + "title": "The Star Forger", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "AurelionSol.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2016, + "region": "mount-targon", + "lane": "mid" + }, + { + "id": "Aurora", + "name": "Aurora", + "title": "the Witch Between Worlds", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 20, + "image": { + "full": "Aurora.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2024, + "region": "freljord", + "lane": "top,mid" + }, + { + "id": "Azir", + "name": "Azir", + "title": "the Emperor of the Sands", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 28, + "image": { + "full": "Azir.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2014, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Bard", + "name": "Bard", + "title": "the Wandering Caretaker", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 46, + "image": { + "full": "Bard.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2015, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Belveth", + "name": "Bel'Veth", + "title": "the Empress of the Void", + "resource": "", + "genre": "Fighter", + "skinCount": 29, + "image": { + "full": "Belveth.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2022, + "region": "void", + "lane": "jungle" + }, + { + "id": "Blitzcrank", + "name": "Blitzcrank", + "title": "the Great Steam Golem", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 74, + "image": { + "full": "Blitzcrank.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "support" + }, + { + "id": "Brand", + "name": "Brand", + "title": "the Burning Vengeance", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 53, + "image": { + "full": "Brand.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "runeterra", + "lane": "jungle,support" + }, + { + "id": "Braum", + "name": "Braum", + "title": "the Heart of the Freljord", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 61, + "image": { + "full": "Braum.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2014, + "region": "freljord", + "lane": "support" + }, + { + "id": "Briar", + "name": "Briar", + "title": "the Restrained Hunger", + "resource": "Fury", + "genre": "Fighter,Assassin", + "skinCount": 29, + "image": { + "full": "Briar.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2023, + "region": "noxus", + "lane": "jungle" + }, + { + "id": "Caitlyn", + "name": "Caitlyn", + "title": "the Sheriff of Piltover", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 63, + "image": { + "full": "Caitlyn.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "piltover", + "lane": "bottom" + }, + { + "id": "Camille", + "name": "Camille", + "title": "the Steel Shadow", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 53, + "image": { + "full": "Camille.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2016, + "region": "piltover", + "lane": "top" + }, + { + "id": "Cassiopeia", + "name": "Cassiopeia", + "title": "the Serpent's Embrace", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "Cassiopeia.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Chogath", + "name": "Cho'Gath", + "title": "the Terror of the Void", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 42, + "image": { + "full": "Chogath.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "divers", + "attackType": "close", + "releaseDate": 2009, + "region": "void", + "lane": "top" + }, + { + "id": "Corki", + "name": "Corki", + "title": "the Daring Bombardier", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 45, + "image": { + "full": "Corki.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "mid" + }, + { + "id": "Darius", + "name": "Darius", + "title": "the Hand of Noxus", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 73, + "image": { + "full": "Darius.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "noxus", + "lane": "top" + }, + { + "id": "Diana", + "name": "Diana", + "title": "Scorn of the Moon", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 75, + "image": { + "full": "Diana.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "mount-targon", + "lane": "jungle,mid" + }, + { + "id": "DrMundo", + "name": "Dr. Mundo", + "title": "the Madman of Zaun", + "resource": "None", + "genre": "Tank,Fighter", + "skinCount": 30, + "image": { + "full": "DrMundo.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "top" + }, + { + "id": "Draven", + "name": "Draven", + "title": "the Glorious Executioner", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 78, + "image": { + "full": "Draven.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "noxus", + "lane": "bottom" + }, + { + "id": "Ekko", + "name": "Ekko", + "title": "the Boy Who Shattered Time", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 59, + "image": { + "full": "Ekko.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2015, + "region": "zaun", + "lane": "jungle,mid" + }, + { + "id": "Elise", + "name": "Elise", + "title": "the Spider Queen", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 36, + "image": { + "full": "Elise.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Evelynn", + "name": "Evelynn", + "title": "Agony's Embrace", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 65, + "image": { + "full": "Evelynn.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Ezreal", + "name": "Ezreal", + "title": "the Prodigal Explorer", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 75, + "image": { + "full": "Ezreal.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "piltover", + "lane": "bottom" + }, + { + "id": "Fiddlesticks", + "name": "Fiddlesticks", + "title": "the Ancient Fear", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 55, + "image": { + "full": "Fiddlesticks.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Fiora", + "name": "Fiora", + "title": "the Grand Duelist", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 100, + "image": { + "full": "Fiora.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "demacia", + "lane": "top" + }, + { + "id": "Fizz", + "name": "Fizz", + "title": "the Tidal Trickster", + "resource": "Mana", + "genre": "Assassin,Fighter", + "skinCount": 44, + "image": { + "full": "Fizz.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bilgewater", + "lane": "mid" + }, + { + "id": "Galio", + "name": "Galio", + "title": "the Colossus", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 47, + "image": { + "full": "Galio.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "mid" + }, + { + "id": "Gangplank", + "name": "Gangplank", + "title": "the Saltwater Scourge", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 42, + "image": { + "full": "Gangplank.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Garen", + "name": "Garen", + "title": "The Might of Demacia", + "resource": "None", + "genre": "Fighter,Tank", + "skinCount": 55, + "image": { + "full": "Garen.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "top" + }, + { + "id": "Gnar", + "name": "Gnar", + "title": "the Missing Link", + "resource": "Rage", + "genre": "Fighter,Tank", + "skinCount": 44, + "image": { + "full": "Gnar.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2014, + "region": "freljord", + "lane": "top" + }, + { + "id": "Gragas", + "name": "Gragas", + "title": "the Rabble Rouser", + "resource": "Mana", + "genre": "Fighter,Mage", + "skinCount": 48, + "image": { + "full": "Gragas.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Graves", + "name": "Graves", + "title": "the Outlaw", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 54, + "image": { + "full": "Graves.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bilgewater", + "lane": "jungle" + }, + { + "id": "Gwen", + "name": "Gwen", + "title": "The Hallowed Seamstress", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 48, + "image": { + "full": "Gwen.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "top" + }, + { + "id": "Hecarim", + "name": "Hecarim", + "title": "the Shadow of War", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 50, + "image": { + "full": "Hecarim.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Heimerdinger", + "name": "Heimerdinger", + "title": "the Revered Inventor", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 27, + "image": { + "full": "Heimerdinger.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "piltover", + "lane": "support" + }, + { + "id": "Hwei", + "name": "Hwei", + "title": "the Visionary", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 21, + "image": { + "full": "Hwei.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2023, + "region": "ionia", + "lane": "mid,support" + }, + { + "id": "Illaoi", + "name": "Illaoi", + "title": "the Kraken Priestess", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 37, + "image": { + "full": "Illaoi.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2015, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Irelia", + "name": "Irelia", + "title": "the Blade Dancer", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 63, + "image": { + "full": "Irelia.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Ivern", + "name": "Ivern", + "title": "the Green Father", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 30, + "image": { + "full": "Ivern.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2016, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Janna", + "name": "Janna", + "title": "the Storm's Fury", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 67, + "image": { + "full": "Janna.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "zaun", + "lane": "support" + }, + { + "id": "JarvanIV", + "name": "Jarvan IV", + "title": "the Exemplar of Demacia", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 55, + "image": { + "full": "JarvanIV.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Jax", + "name": "Jax", + "title": "Grandmaster at Arms", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 42, + "image": { + "full": "Jax.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "top,jungle" + }, + { + "id": "Jayce", + "name": "Jayce", + "title": "the Defender of Tomorrow", + "resource": "Mana", + "genre": "Fighter,Marksman", + "skinCount": 47, + "image": { + "full": "Jayce.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "piltover", + "lane": "top" + }, + { + "id": "Jhin", + "name": "Jhin", + "title": "the Virtuoso", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 54, + "image": { + "full": "Jhin.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2016, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Jinx", + "name": "Jinx", + "title": "the Loose Cannon", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 64, + "image": { + "full": "Jinx.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "KSante", + "name": "K'Sante", + "title": "the Pride of Nazumah", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 18, + "image": { + "full": "KSante.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2022, + "region": "shurima", + "lane": "top" + }, + { + "id": "Kaisa", + "name": "Kai'Sa", + "title": "Daughter of the Void", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 78, + "image": { + "full": "Kaisa.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2018, + "region": "void", + "lane": "bottom" + }, + { + "id": "Kalista", + "name": "Kalista", + "title": "the Spear of Vengeance", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 33, + "image": { + "full": "Kalista.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2014, + "region": "shadow-isles", + "lane": "bottom" + }, + { + "id": "Karma", + "name": "Karma", + "title": "the Enlightened One", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 76, + "image": { + "full": "Karma.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "ionia", + "lane": "mid,support" + }, + { + "id": "Karthus", + "name": "Karthus", + "title": "the Deathsinger", + "resource": "Mana", + "genre": "Mage", + "skinCount": 44, + "image": { + "full": "Karthus.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Kassadin", + "name": "Kassadin", + "title": "the Void Walker", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 32, + "image": { + "full": "Kassadin.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "void", + "lane": "mid" + }, + { + "id": "Katarina", + "name": "Katarina", + "title": "the Sinister Blade", + "resource": "None", + "genre": "Assassin,Mage", + "skinCount": 78, + "image": { + "full": "Katarina.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Kayle", + "name": "Kayle", + "title": "the Righteous", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 88, + "image": { + "full": "Kayle.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "demacia", + "lane": "top" + }, + { + "id": "Kayn", + "name": "Kayn", + "title": "the Shadow Reaper", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 31, + "image": { + "full": "Kayn.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Kennen", + "name": "Kennen", + "title": "the Heart of the Tempest", + "resource": "Energy", + "genre": "Mage", + "skinCount": 34, + "image": { + "full": "Kennen.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "ionia", + "lane": "top" + }, + { + "id": "Khazix", + "name": "Kha'Zix", + "title": "the Voidreaver", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 58, + "image": { + "full": "Khazix.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "void", + "lane": "jungle" + }, + { + "id": "Kindred", + "name": "Kindred", + "title": "The Eternal Hunters", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 43, + "image": { + "full": "Kindred.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2015, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Kled", + "name": "Kled", + "title": "the Cantankerous Cavalier", + "resource": "Courage", + "genre": "Fighter", + "skinCount": 27, + "image": { + "full": "Kled.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2016, + "region": "noxus", + "lane": "top" + }, + { + "id": "KogMaw", + "name": "Kog'Maw", + "title": "the Mouth of the Abyss", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 65, + "image": { + "full": "KogMaw.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "void", + "lane": "bottom" + }, + { + "id": "Leblanc", + "name": "LeBlanc", + "title": "the Deceiver", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 59, + "image": { + "full": "Leblanc.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "LeeSin", + "name": "Lee Sin", + "title": "the Blind Monk", + "resource": "Energy", + "genre": "Fighter,Assassin", + "skinCount": 79, + "image": { + "full": "LeeSin.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Leona", + "name": "Leona", + "title": "the Radiant Dawn", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 64, + "image": { + "full": "Leona.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Lillia", + "name": "Lillia", + "title": "the Bashful Bloom", + "resource": "Mana", + "genre": "Fighter,Mage", + "skinCount": 53, + "image": { + "full": "Lillia.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Lissandra", + "name": "Lissandra", + "title": "the Ice Witch", + "resource": "Mana", + "genre": "Mage", + "skinCount": 52, + "image": { + "full": "Lissandra.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "freljord", + "lane": "mid" + }, + { + "id": "Lucian", + "name": "Lucian", + "title": "the Purifier", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 73, + "image": { + "full": "Lucian.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2013, + "region": "demacia", + "lane": "bottom" + }, + { + "id": "Lulu", + "name": "Lulu", + "title": "the Fae Sorceress", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 56, + "image": { + "full": "Lulu.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "bandle-city", + "lane": "support" + }, + { + "id": "Lux", + "name": "Lux", + "title": "the Lady of Luminosity", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 81, + "image": { + "full": "Lux.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "demacia", + "lane": "mid,support" + }, + { + "id": "Malphite", + "name": "Malphite", + "title": "Shard of the Monolith", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 57, + "image": { + "full": "Malphite.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "ixtal", + "lane": "top" + }, + { + "id": "Malzahar", + "name": "Malzahar", + "title": "the Prophet of the Void", + "resource": "Mana", + "genre": "Mage", + "skinCount": 58, + "image": { + "full": "Malzahar.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "void", + "lane": "mid" + }, + { + "id": "Maokai", + "name": "Maokai", + "title": "the Twisted Treant", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 36, + "image": { + "full": "Maokai.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shadow-isles", + "lane": "support" + }, + { + "id": "MasterYi", + "name": "Master Yi", + "title": "the Wuju Bladesman", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 90, + "image": { + "full": "MasterYi.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Mel", + "name": "Mel", + "title": "the Soul's Reflection", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 12, + "image": { + "full": "Mel.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2025, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Milio", + "name": "Milio", + "title": "The Gentle Flame", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 29, + "image": { + "full": "Milio.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2023, + "region": "ixtal", + "lane": "support" + }, + { + "id": "MissFortune", + "name": "Miss Fortune", + "title": "the Bounty Hunter", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 69, + "image": { + "full": "MissFortune.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "bilgewater", + "lane": "bottom" + }, + { + "id": "Mordekaiser", + "name": "Mordekaiser", + "title": "the Iron Revenant", + "resource": "Shield", + "genre": "Fighter,Mage", + "skinCount": 57, + "image": { + "full": "Mordekaiser.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "noxus", + "lane": "top" + }, + { + "id": "Morgana", + "name": "Morgana", + "title": "the Fallen", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 82, + "image": { + "full": "Morgana.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "demacia", + "lane": "support" + }, + { + "id": "Naafiri", + "name": "Naafiri", + "title": "the Hound of a Hundred Bites", + "resource": "Mana", + "genre": "Assassin,Fighter", + "skinCount": 38, + "image": { + "full": "Naafiri.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2023, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Nami", + "name": "Nami", + "title": "the Tidecaller", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 68, + "image": { + "full": "Nami.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Nasus", + "name": "Nasus", + "title": "the Curator of the Sands", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 63, + "image": { + "full": "Nasus.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "top" + }, + { + "id": "Nautilus", + "name": "Nautilus", + "title": "the Titan of the Depths", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 46, + "image": { + "full": "Nautilus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "bilgewater", + "lane": "support" + }, + { + "id": "Neeko", + "name": "Neeko", + "title": "the Curious Chameleon", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 50, + "image": { + "full": "Neeko.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2018, + "region": "ixtal", + "lane": "mid,support" + }, + { + "id": "Nidalee", + "name": "Nidalee", + "title": "the Bestial Huntress", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 67, + "image": { + "full": "Nidalee.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "ixtal", + "lane": "jungle" + }, + { + "id": "Nilah", + "name": "Nilah", + "title": "the Joy Unbound", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 30, + "image": { + "full": "Nilah.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2022, + "region": "runeterra", + "lane": "bottom" + }, + { + "id": "Nocturne", + "name": "Nocturne", + "title": "the Eternal Nightmare", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 36, + "image": { + "full": "Nocturne.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "divers", + "attackType": "close", + "releaseDate": 2011, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Nunu", + "name": "Nunu & Willump", + "title": "the Boy and His Yeti", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 54, + "image": { + "full": "Nunu.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Olaf", + "name": "Olaf", + "title": "the Berserker", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 53, + "image": { + "full": "Olaf.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "top" + }, + { + "id": "Orianna", + "name": "Orianna", + "title": "the Lady of Clockwork", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 40, + "image": { + "full": "Orianna.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "piltover", + "lane": "mid" + }, + { + "id": "Ornn", + "name": "Ornn", + "title": "The Fire below the Mountain", + "resource": "Mana", + "genre": "Tank", + "skinCount": 38, + "image": { + "full": "Ornn.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "freljord", + "lane": "top" + }, + { + "id": "Pantheon", + "name": "Pantheon", + "title": "the Unbreakable Spear", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 47, + "image": { + "full": "Pantheon.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "mount-targon", + "lane": "top,support" + }, + { + "id": "Poppy", + "name": "Poppy", + "title": "Keeper of the Hammer", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 51, + "image": { + "full": "Poppy.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "top,jungle" + }, + { + "id": "Pyke", + "name": "Pyke", + "title": "the Bloodharbor Ripper", + "resource": "Mana", + "genre": "Support,Assassin", + "skinCount": 85, + "image": { + "full": "Pyke.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2018, + "region": "bilgewater", + "lane": "support" + }, + { + "id": "Qiyana", + "name": "Qiyana", + "title": "Empress of the Elements", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 51, + "image": { + "full": "Qiyana.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2019, + "region": "ixtal", + "lane": "mid" + }, + { + "id": "Quinn", + "name": "Quinn", + "title": "Demacia's Wings", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 24, + "image": { + "full": "Quinn.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "demacia", + "lane": "top" + }, + { + "id": "Rakan", + "name": "Rakan", + "title": "The Charmer", + "resource": "Mana", + "genre": "Support", + "skinCount": 58, + "image": { + "full": "Rakan.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "ionia", + "lane": "support" + }, + { + "id": "Rammus", + "name": "Rammus", + "title": "the Armordillo", + "resource": "Mana", + "genre": "Tank", + "skinCount": 35, + "image": { + "full": "Rammus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "RekSai", + "name": "Rek'Sai", + "title": "the Void Burrower", + "resource": "Rage", + "genre": "Fighter,Tank", + "skinCount": 36, + "image": { + "full": "RekSai.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2014, + "region": "void", + "lane": "jungle" + }, + { + "id": "Rell", + "name": "Rell", + "title": "the Iron Maiden", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 31, + "image": { + "full": "Rell.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2020, + "region": "noxus", + "lane": "support" + }, + { + "id": "Renata", + "name": "Renata Glasc", + "title": "the Chem-Baroness", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 32, + "image": { + "full": "Renata.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2022, + "region": "zaun", + "lane": "support" + }, + { + "id": "Renekton", + "name": "Renekton", + "title": "the Butcher of the Sands", + "resource": "Fury", + "genre": "Fighter,Tank", + "skinCount": 57, + "image": { + "full": "Renekton.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shurima", + "lane": "top" + }, + { + "id": "Rengar", + "name": "Rengar", + "title": "the Pridestalker", + "resource": "Ferocity", + "genre": "Assassin,Fighter", + "skinCount": 49, + "image": { + "full": "Rengar.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "ixtal", + "lane": "jungle" + }, + { + "id": "Riven", + "name": "Riven", + "title": "the Exile", + "resource": "None", + "genre": "Fighter,Assassin", + "skinCount": 73, + "image": { + "full": "Riven.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "noxus", + "lane": "top" + }, + { + "id": "Rumble", + "name": "Rumble", + "title": "the Mechanized Menace", + "resource": "Heat", + "genre": "Fighter,Mage", + "skinCount": 32, + "image": { + "full": "Rumble.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bandle-city", + "lane": "top" + }, + { + "id": "Ryze", + "name": "Ryze", + "title": "the Rune Mage", + "resource": "Mana", + "genre": "Mage", + "skinCount": 39, + "image": { + "full": "Ryze.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "mid" + }, + { + "id": "Samira", + "name": "Samira", + "title": "the Desert Rose", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 42, + "image": { + "full": "Samira.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2020, + "region": "noxus", + "lane": "bottom" + }, + { + "id": "Sejuani", + "name": "Sejuani", + "title": "Fury of the North", + "resource": "Mana", + "genre": "Tank", + "skinCount": 57, + "image": { + "full": "Sejuani.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Senna", + "name": "Senna", + "title": "the Redeemer", + "resource": "Mana", + "genre": "Support,Marksman", + "skinCount": 80, + "image": { + "full": "Senna.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2019, + "region": "runeterra", + "lane": "bottom,support" + }, + { + "id": "Seraphine", + "name": "Seraphine", + "title": "the Starry-Eyed Songstress", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 68, + "image": { + "full": "Seraphine.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2020, + "region": "piltover", + "lane": "bottom,support" + }, + { + "id": "Sett", + "name": "Sett", + "title": "the Boss", + "resource": "Grit", + "genre": "Fighter,Tank", + "skinCount": 76, + "image": { + "full": "Sett.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "top" + }, + { + "id": "Shaco", + "name": "Shaco", + "title": "the Demon Jester", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 71, + "image": { + "full": "Shaco.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Shen", + "name": "Shen", + "title": "the Eye of Twilight", + "resource": "Energy", + "genre": "Tank", + "skinCount": 53, + "image": { + "full": "Shen.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top" + }, + { + "id": "Shyvana", + "name": "Shyvana", + "title": "the Half-Dragon", + "resource": "Fury", + "genre": "Fighter,Tank", + "skinCount": 26, + "image": { + "full": "Shyvana.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Singed", + "name": "Singed", + "title": "the Mad Chemist", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 29, + "image": { + "full": "Singed.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "top" + }, + { + "id": "Sion", + "name": "Sion", + "title": "The Undead Juggernaut", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 50, + "image": { + "full": "Sion.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "noxus", + "lane": "top" + }, + { + "id": "Sivir", + "name": "Sivir", + "title": "the Battle Mistress", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 81, + "image": { + "full": "Sivir.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "shurima", + "lane": "bottom" + }, + { + "id": "Skarner", + "name": "Skarner", + "title": "the Primordial Sovereign", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 15, + "image": { + "full": "Skarner.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ixtal", + "lane": "top,jungle" + }, + { + "id": "Smolder", + "name": "Smolder", + "title": "the Fiery Fledgling", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 20, + "image": { + "full": "Smolder.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2024, + "region": "runeterra", + "lane": "bottom" + }, + { + "id": "Sona", + "name": "Sona", + "title": "Maven of the Strings", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 77, + "image": { + "full": "Sona.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "demacia", + "lane": "support" + }, + { + "id": "Soraka", + "name": "Soraka", + "title": "the Starchild", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 63, + "image": { + "full": "Soraka.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Swain", + "name": "Swain", + "title": "the Noxian Grand General", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 51, + "image": { + "full": "Swain.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "support" + }, + { + "id": "Sylas", + "name": "Sylas", + "title": "the Unshackled", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 54, + "image": { + "full": "Sylas.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2019, + "region": "demacia", + "lane": "mid" + }, + { + "id": "Syndra", + "name": "Syndra", + "title": "the Dark Sovereign", + "resource": "Mana", + "genre": "Mage", + "skinCount": 74, + "image": { + "full": "Syndra.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "ionia", + "lane": "mid" + }, + { + "id": "TahmKench", + "name": "Tahm Kench", + "title": "The River King", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 47, + "image": { + "full": "TahmKench.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2015, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Taliyah", + "name": "Taliyah", + "title": "the Stoneweaver", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 25, + "image": { + "full": "Taliyah.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2016, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "Talon", + "name": "Talon", + "title": "the Blade's Shadow", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 60, + "image": { + "full": "Talon.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Taric", + "name": "Taric", + "title": "the Shield of Valoran", + "resource": "Mana", + "genre": "Support,Tank", + "skinCount": 36, + "image": { + "full": "Taric.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Teemo", + "name": "Teemo", + "title": "the Swift Scout", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 63, + "image": { + "full": "Teemo.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "top" + }, + { + "id": "Thresh", + "name": "Thresh", + "title": "the Chain Warden", + "resource": "Mana", + "genre": "Support,Tank", + "skinCount": 68, + "image": { + "full": "Thresh.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "shadow-isles", + "lane": "support" + }, + { + "id": "Tristana", + "name": "Tristana", + "title": "the Yordle Gunner", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 79, + "image": { + "full": "Tristana.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "bottom" + }, + { + "id": "Trundle", + "name": "Trundle", + "title": "the Troll King", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 30, + "image": { + "full": "Trundle.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "top" + }, + { + "id": "Tryndamere", + "name": "Tryndamere", + "title": "the Barbarian King", + "resource": "Fury", + "genre": "Fighter,Assassin", + "skinCount": 47, + "image": { + "full": "Tryndamere.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "top" + }, + { + "id": "TwistedFate", + "name": "Twisted Fate", + "title": "the Card Master", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 54, + "image": { + "full": "TwistedFate.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bilgewater", + "lane": "mid,bottom" + }, + { + "id": "Twitch", + "name": "Twitch", + "title": "the Plague Rat", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 59, + "image": { + "full": "Twitch.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Udyr", + "name": "Udyr", + "title": "the Spirit Walker", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 12, + "image": { + "full": "Udyr.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "top,jungle" + }, + { + "id": "Urgot", + "name": "Urgot", + "title": "the Dreadnought", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 41, + "image": { + "full": "Urgot.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "zaun", + "lane": "top" + }, + { + "id": "Varus", + "name": "Varus", + "title": "the Arrow of Retribution", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 71, + "image": { + "full": "Varus.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Vayne", + "name": "Vayne", + "title": "the Night Hunter", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 67, + "image": { + "full": "Vayne.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "demacia", + "lane": "bottom" + }, + { + "id": "Veigar", + "name": "Veigar", + "title": "the Tiny Master of Evil", + "resource": "Mana", + "genre": "Mage", + "skinCount": 67, + "image": { + "full": "Veigar.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "mid" + }, + { + "id": "Velkoz", + "name": "Vel'Koz", + "title": "the Eye of the Void", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 29, + "image": { + "full": "Velkoz.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2014, + "region": "void", + "lane": "support" + }, + { + "id": "Vex", + "name": "Vex", + "title": "the Gloomist", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "Vex.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "mid" + }, + { + "id": "Vi", + "name": "Vi", + "title": "the Piltover Enforcer", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 51, + "image": { + "full": "Vi.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "piltover", + "lane": "jungle" + }, + { + "id": "Viego", + "name": "Viego", + "title": "The Ruined King", + "resource": "None", + "genre": "Fighter,Assassin", + "skinCount": 43, + "image": { + "full": "Viego.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Viktor", + "name": "Viktor", + "title": "the Herald of the Arcane", + "resource": "Mana", + "genre": "Mage", + "skinCount": 34, + "image": { + "full": "Viktor.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "zaun", + "lane": "mid" + }, + { + "id": "Vladimir", + "name": "Vladimir", + "title": "the Crimson Reaper", + "resource": "Crimson Rush", + "genre": "Mage,Fighter", + "skinCount": 58, + "image": { + "full": "Vladimir.png", + "sprite": "champion5.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Volibear", + "name": "Volibear", + "title": "the Relentless Storm", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 37, + "image": { + "full": "Volibear.png", + "sprite": "champion5.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "freljord", + "lane": "top" + }, + { + "id": "Warwick", + "name": "Warwick", + "title": "the Uncaged Wrath of Zaun", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 61, + "image": { + "full": "Warwick.png", + "sprite": "champion5.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "jungle" + }, + { + "id": "MonkeyKing", + "name": "Wukong", + "title": "the Monkey King", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 36, + "image": { + "full": "MonkeyKing.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Xayah", + "name": "Xayah", + "title": "the Rebel", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 65, + "image": { + "full": "Xayah.png", + "sprite": "champion5.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2017, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Xerath", + "name": "Xerath", + "title": "the Magus Ascendant", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 33, + "image": { + "full": "Xerath.png", + "sprite": "champion5.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "shurima", + "lane": "mid,support" + }, + { + "id": "XinZhao", + "name": "Xin Zhao", + "title": "the Seneschal of Demacia", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 52, + "image": { + "full": "XinZhao.png", + "sprite": "champion5.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Yasuo", + "name": "Yasuo", + "title": "the Unforgiven", + "resource": "Flow", + "genre": "Fighter,Assassin", + "skinCount": 97, + "image": { + "full": "Yasuo.png", + "sprite": "champion5.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "ionia", + "lane": "top,mid,bottom" + }, + { + "id": "Yone", + "name": "Yone", + "title": "the Unforgotten", + "resource": "Flow", + "genre": "Fighter,Assassin", + "skinCount": 76, + "image": { + "full": "Yone.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Yorick", + "name": "Yorick", + "title": "Shepherd of Souls", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 59, + "image": { + "full": "Yorick.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shadow-isles", + "lane": "top" + }, + { + "id": "Yunara", + "name": "Yunara", + "title": "the Unbroken Faith", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 10, + "image": { + "full": "Yunara.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "region": "ionia" + }, + { + "id": "Yuumi", + "name": "Yuumi", + "title": "the Magical Cat", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 79, + "image": { + "full": "Yuumi.png", + "sprite": "champion5.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2019, + "region": "bandle-city", + "lane": "support" + }, + { + "id": "Zaahen", + "name": "Zaahen", + "title": "The Unsundered", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 10, + "image": { + "full": "Zaahen.png", + "sprite": "champion5.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "region": "runeterra" + }, + { + "id": "Zac", + "name": "Zac", + "title": "the Secret Weapon", + "resource": "None", + "genre": "Tank,Fighter", + "skinCount": 42, + "image": { + "full": "Zac.png", + "sprite": "champion5.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "zaun", + "lane": "jungle" + }, + { + "id": "Zed", + "name": "Zed", + "title": "the Master of Shadows", + "resource": "Energy", + "genre": "Assassin", + "skinCount": 71, + "image": { + "full": "Zed.png", + "sprite": "champion5.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "ionia", + "lane": "mid" + }, + { + "id": "Zeri", + "name": "Zeri", + "title": "The Spark of Zaun", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 47, + "image": { + "full": "Zeri.png", + "sprite": "champion5.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2022, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Ziggs", + "name": "Ziggs", + "title": "the Hexplosives Expert", + "resource": "Mana", + "genre": "Mage", + "skinCount": 52, + "image": { + "full": "Ziggs.png", + "sprite": "champion5.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Zilean", + "name": "Zilean", + "title": "the Chronokeeper", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 33, + "image": { + "full": "Zilean.png", + "sprite": "champion5.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Zoe", + "name": "Zoe", + "title": "the Aspect of Twilight", + "resource": "Mana", + "genre": "Mage", + "skinCount": 45, + "image": { + "full": "Zoe.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2017, + "region": "mount-targon", + "lane": "mid" + }, + { + "id": "Zyra", + "name": "Zyra", + "title": "Rise of the Thorns", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 74, + "image": { + "full": "Zyra.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "ixtal", + "lane": "support" + } +] diff --git a/src/modules/loldle/compare.js b/src/modules/loldle/compare.js new file mode 100644 index 0000000..42e3104 --- /dev/null +++ b/src/modules/loldle/compare.js @@ -0,0 +1,119 @@ +/** + * @file Classic-mode champion comparison — ported from tiennm99/loldle + * (lib/classic-mode.js). Pure functions, no DOM/React. + */ + +export const CLASSIC_ATTRIBUTES = [ + { key: "gender", label: "Gender", type: "exact" }, + { key: "genre", label: "Genre", type: "multi" }, + { key: "attackType", label: "Range", type: "exact" }, + { key: "resource", label: "Resource", type: "exact" }, + { key: "region", label: "Region", type: "exact" }, + { key: "lane", label: "Lane", type: "multi" }, + { key: "releaseDate", label: "Year", type: "year" }, +]; + +/** + * Compare a guess champion against the target. + * @param {Record} guess + * @param {Record} target + */ +export function compareChampions(guess, target) { + return CLASSIC_ATTRIBUTES.map((attr) => { + const guessVal = guess[attr.key] ?? ""; + const targetVal = target[attr.key] ?? ""; + + switch (attr.type) { + case "exact": + return { + ...attr, + guessValue: formatValue(attr.key, guessVal), + targetValue: formatValue(attr.key, targetVal), + result: + String(guessVal).toLowerCase() === String(targetVal).toLowerCase() + ? "correct" + : "wrong", + }; + case "multi": + return { + ...attr, + guessValue: formatValue(attr.key, guessVal), + targetValue: formatValue(attr.key, targetVal), + result: compareMultiValue(guessVal, targetVal), + }; + case "year": + return { + ...attr, + guessValue: guessVal || "?", + targetValue: targetVal || "?", + ...compareYear(guessVal, targetVal), + }; + default: + return { ...attr, guessValue: guessVal, targetValue: targetVal, result: "wrong" }; + } + }); +} + +function compareMultiValue(guessStr, targetStr) { + const guessSet = parseSet(guessStr); + const targetSet = parseSet(targetStr); + + if (guessSet.size === 0 && targetSet.size === 0) return "correct"; + if (guessSet.size === 0 || targetSet.size === 0) return "wrong"; + if (setsEqual(guessSet, targetSet)) return "correct"; + for (const val of guessSet) { + if (targetSet.has(val)) return "partial"; + } + return "wrong"; +} + +function compareYear(guessYear, targetYear) { + const g = Number(guessYear); + const t = Number(targetYear); + if (!g || !t) return { result: "wrong" }; + if (g === t) return { result: "correct" }; + return { result: "wrong", direction: g < t ? "up" : "down" }; +} + +function parseSet(str) { + if (!str) return new Set(); + return new Set( + String(str) + .split(",") + .map((s) => s.trim().toLowerCase()) + .filter(Boolean), + ); +} + +function setsEqual(a, b) { + if (a.size !== b.size) return false; + for (const val of a) { + if (!b.has(val)) return false; + } + return true; +} + +function formatValue(key, value) { + if (!value) return "—"; + const str = String(value); + switch (key) { + case "gender": + return capitalize(str); + case "attackType": + return str === "close" ? "Melee" : "Ranged"; + case "region": + return str.split("-").map(capitalize).join(" "); + case "genre": + case "lane": + return str + .split(",") + .map((s) => capitalize(s.trim())) + .join(", "); + default: + return str; + } +} + +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/src/modules/loldle/daily.js b/src/modules/loldle/daily.js new file mode 100644 index 0000000..025e481 --- /dev/null +++ b/src/modules/loldle/daily.js @@ -0,0 +1,33 @@ +/** + * @file Daily puzzle seeding — deterministic per UTC date. + * Uses djb2 hash of YYYY-MM-DD to pick champion index. + */ + +/** UTC date string YYYY-MM-DD. */ +export function todayUtc(now = new Date()) { + return now.toISOString().slice(0, 10); +} + +/** djb2 string hash. */ +function hash(str) { + let h = 5381; + for (let i = 0; i < str.length; i++) { + h = (h * 33) ^ str.charCodeAt(i); + } + return h >>> 0; +} + +/** + * Pick today's target champion deterministically. + * @template T + * @param {T[]} champions + * @param {string} [seed] — defaults to today's UTC date + * @returns {T} + */ +export function pickDaily(champions, seed) { + if (!Array.isArray(champions) || champions.length === 0) { + throw new Error("pickDaily: champions array is empty"); + } + const s = seed ?? todayUtc(); + return champions[hash(s) % champions.length]; +} diff --git a/src/modules/loldle/handlers.js b/src/modules/loldle/handlers.js new file mode 100644 index 0000000..6cab037 --- /dev/null +++ b/src/modules/loldle/handlers.js @@ -0,0 +1,119 @@ +/** + * @file Command handlers for loldle module. + * + * - /loldle → show board / start puzzle + * - /loldle → submit a guess + * - /loldle_giveup → reveal answer, end today's game + * - /loldle_stats → show personal stats + */ + +import championsData from "./champions-data.js"; +import { compareChampions } from "./compare.js"; +import { pickDaily, todayUtc } from "./daily.js"; +import { findChampion } from "./lookup.js"; +import { renderBoard, renderGuess } from "./render.js"; +import { loadGame, loadStats, MAX_GUESSES, recordResult, saveGame } from "./state.js"; + +/** @type {Array>} */ +const champions = championsData; + +function argAfterCommand(text) { + if (!text) return ""; + const idx = text.indexOf(" "); + return idx === -1 ? "" : text.slice(idx + 1).trim(); +} + +async function getOrInitGame(db, userId, date) { + const existing = await loadGame(db, userId, date); + if (existing) return existing; + const target = pickDaily(champions, date); + const fresh = { target: target.id, guesses: [], solved: false }; + await saveGame(db, userId, date, fresh); + return fresh; +} + +/** + * @param {import("grammy").Context} ctx + * @param {import("../../db/kv-store-interface.js").KVStore} db + */ +export async function handleLoldle(ctx, db) { + const userId = ctx.from?.id; + if (!userId) return ctx.reply("Cannot identify user."); + const date = todayUtc(); + const arg = argAfterCommand(ctx.message?.text ?? ""); + + const game = await getOrInitGame(db, userId, date); + + if (!arg) { + const status = game.solved + ? `🎉 Solved in ${game.guesses.length}/${MAX_GUESSES}.` + : game.giveup + ? `🏳️ Gave up. Answer was ${game.target}.` + : `Guess ${game.guesses.length}/${MAX_GUESSES}. Use \`/loldle \`.`; + return ctx.reply(`${status}\n\n${renderBoard(game.guesses)}`); + } + + if (game.solved || game.giveup) { + return ctx.reply(`Today's game is over. Answer was ${game.target}.`); + } + if (game.guesses.length >= MAX_GUESSES) { + return ctx.reply(`Out of guesses. Answer was ${game.target}.`); + } + + const guess = findChampion(champions, arg); + if (!guess) return ctx.reply(`Champion not found: "${arg}".`); + + const target = champions.find((c) => c.id === game.target); + const results = compareChampions(guess, target); + game.guesses.push({ champion: guess.name, results }); + const won = guess.id === target.id; + if (won) game.solved = true; + await saveGame(db, userId, date, game); + + const reply = renderGuess(guess.name, results); + if (won) { + const s = await recordResult(db, userId, date, true); + return ctx.reply(`${reply}\n\n🎉 Solved in ${game.guesses.length}/${MAX_GUESSES}! Streak: ${s.streak}.`); + } + if (game.guesses.length >= MAX_GUESSES) { + await recordResult(db, userId, date, false); + return ctx.reply(`${reply}\n\n❌ Out of guesses. Answer was ${target.name}.`); + } + return ctx.reply(`${reply}\n\nGuess ${game.guesses.length}/${MAX_GUESSES}.`); +} + +/** + * @param {import("grammy").Context} ctx + * @param {import("../../db/kv-store-interface.js").KVStore} db + */ +export async function handleGiveup(ctx, db) { + const userId = ctx.from?.id; + if (!userId) return ctx.reply("Cannot identify user."); + const date = todayUtc(); + const game = await getOrInitGame(db, userId, date); + if (game.solved) return ctx.reply(`Already solved today — ${game.target}.`); + if (game.giveup) return ctx.reply(`Already gave up — ${game.target}.`); + game.giveup = true; + await saveGame(db, userId, date, game); + await recordResult(db, userId, date, false); + const target = champions.find((c) => c.id === game.target); + return ctx.reply(`🏳️ Answer was ${target.name} — ${target.title}.`); +} + +/** + * @param {import("grammy").Context} ctx + * @param {import("../../db/kv-store-interface.js").KVStore} db + */ +export async function handleStats(ctx, db) { + const userId = ctx.from?.id; + if (!userId) return ctx.reply("Cannot identify user."); + const s = await loadStats(db, userId); + const winRate = s.played ? Math.round((s.wins / s.played) * 100) : 0; + return ctx.reply( + `📊 Loldle stats\n` + + `Played: ${s.played}\n` + + `Wins: ${s.wins} (${winRate}%)\n` + + `Current streak: ${s.streak}\n` + + `Best streak: ${s.bestStreak}`, + ); +} diff --git a/src/modules/loldle/index.js b/src/modules/loldle/index.js index 949a403..edd83a6 100644 --- a/src/modules/loldle/index.js +++ b/src/modules/loldle/index.js @@ -1,36 +1,39 @@ /** - * @file loldle module stub — proves the plugin system end-to-end. + * @file Loldle module — classic-mode champion guessing game. * - * One public, one protected, one private slash command. + * Ported from tiennm99/loldle (lib/classic-mode.js). Data sourced from + * tiennm99/loldle-data's champions.json (synced via GH Actions). */ +import { handleGiveup, handleLoldle, handleStats } from "./handlers.js"; + +/** @type {import("../../db/kv-store-interface.js").KVStore | null} */ +let db = null; + /** @type {import("../registry.js").BotModule} */ const loldleModule = { name: "loldle", + init: async ({ db: store }) => { + db = store; + }, commands: [ { name: "loldle", visibility: "public", - description: "Play loldle (stub)", - handler: async (ctx) => { - await ctx.reply("Loldle stub."); - }, + description: "Classic loldle — guess today's champion", + handler: (ctx) => handleLoldle(ctx, db), }, { - name: "lstats", - visibility: "protected", - description: "Loldle stats", - handler: async (ctx) => { - await ctx.reply("loldle stats stub"); - }, + name: "loldle_giveup", + visibility: "public", + description: "Reveal today's loldle answer", + handler: (ctx) => handleGiveup(ctx, db), }, { - name: "ggwp", - visibility: "private", - description: "Easter egg — post-match courtesy", - handler: async (ctx) => { - await ctx.reply("gg well played (stub)"); - }, + name: "loldle_stats", + visibility: "public", + description: "Show your loldle stats (wins, streak)", + handler: (ctx) => handleStats(ctx, db), }, ], }; diff --git a/src/modules/loldle/lookup.js b/src/modules/loldle/lookup.js new file mode 100644 index 0000000..8bb75e0 --- /dev/null +++ b/src/modules/loldle/lookup.js @@ -0,0 +1,30 @@ +/** + * @file Champion name lookup — normalizes user input to a champion record. + * Matches by exact id/name (case/space/punct-insensitive). + * Falls back to prefix match when unique. + */ + +function normalize(s) { + return String(s || "") + .toLowerCase() + .replace(/[^a-z0-9]/g, ""); +} + +/** + * @param {Array>} champions + * @param {string} input + * @returns {Record | null} + */ +export function findChampion(champions, input) { + const q = normalize(input); + if (!q) return null; + + for (const c of champions) { + if (normalize(c.id) === q || normalize(c.name) === q) return c; + } + + const prefixMatches = champions.filter( + (c) => normalize(c.id).startsWith(q) || normalize(c.name).startsWith(q), + ); + return prefixMatches.length === 1 ? prefixMatches[0] : null; +} diff --git a/src/modules/loldle/render.js b/src/modules/loldle/render.js new file mode 100644 index 0000000..8dde4ba --- /dev/null +++ b/src/modules/loldle/render.js @@ -0,0 +1,35 @@ +/** + * @file Render comparison results as a Telegram-friendly plain-text grid. + * Avoids HTML to keep escaping trivial; uses emoji markers: + * ✅ correct · 🟨 partial · ❌ wrong · ⬆ direction-up · ⬇ direction-down + */ + +const MARKER = { correct: "✅", partial: "🟨", wrong: "❌" }; + +/** + * Render a single guess row. + * @param {string} championName + * @param {ReturnType} results + */ +export function renderGuess(championName, results) { + const header = `🎯 ${championName}`; + const lines = results.map((r) => { + const mark = MARKER[r.result] ?? "❌"; + let arrow = ""; + if (r.key === "releaseDate" && r.result !== "correct") { + if (r.direction === "up") arrow = " ⬆"; + else if (r.direction === "down") arrow = " ⬇"; + } + return `${mark} ${r.label}: ${r.guessValue}${arrow}`; + }); + return [header, ...lines].join("\n"); +} + +/** + * Render the current board = all prior guesses stacked. + * @param {Array<{champion:string, results: any[]}>} guesses + */ +export function renderBoard(guesses) { + if (guesses.length === 0) return "No guesses yet. Reply with `/loldle `."; + return guesses.map((g) => renderGuess(g.champion, g.results)).join("\n\n"); +} diff --git a/src/modules/loldle/state.js b/src/modules/loldle/state.js new file mode 100644 index 0000000..9d02a3f --- /dev/null +++ b/src/modules/loldle/state.js @@ -0,0 +1,90 @@ +/** + * @file Per-user daily game state in KV. + * Key layout (inside module-prefixed store): + * game:: -> { target, guesses[], solved, giveup } + * stats: -> { played, wins, streak, bestStreak, lastDate } + */ + +const MAX_GUESSES = 8; + +/** @param {number} userId @param {string} date */ +const gameKey = (userId, date) => `game:${userId}:${date}`; +/** @param {number} userId */ +const statsKey = (userId) => `stats:${userId}`; + +/** + * @typedef {object} GameState + * @property {string} target — champion id + * @property {Array<{champion:string, results:any[]}>} guesses + * @property {boolean} solved + * @property {boolean} [giveup] + */ + +/** + * @param {import("../../db/kv-store-interface.js").KVStore} db + * @param {number} userId + * @param {string} date + * @returns {Promise} + */ +export async function loadGame(db, userId, date) { + return db.getJSON(gameKey(userId, date)); +} + +/** + * @param {import("../../db/kv-store-interface.js").KVStore} db + * @param {number} userId + * @param {string} date + * @param {GameState} state + */ +export async function saveGame(db, userId, date, state) { + // Expire after 3 days — stats are the only long-lived record. + await db.putJSON(gameKey(userId, date), state, { expirationTtl: 60 * 60 * 24 * 3 }); +} + +export { MAX_GUESSES }; + +/** + * @param {import("../../db/kv-store-interface.js").KVStore} db + * @param {number} userId + */ +export async function loadStats(db, userId) { + return ( + (await db.getJSON(statsKey(userId))) ?? { + played: 0, + wins: 0, + streak: 0, + bestStreak: 0, + lastDate: null, + } + ); +} + +/** + * Record a finished game and update streaks. + * @param {import("../../db/kv-store-interface.js").KVStore} db + * @param {number} userId + * @param {string} date + * @param {boolean} won + */ +export async function recordResult(db, userId, date, won) { + const s = await loadStats(db, userId); + s.played += 1; + if (won) { + s.wins += 1; + const prev = s.lastDate; + const yesterday = prevDate(date); + s.streak = prev === yesterday || prev === date ? s.streak + (prev === date ? 0 : 1) : 1; + if (s.streak > s.bestStreak) s.bestStreak = s.streak; + } else { + s.streak = 0; + } + s.lastDate = date; + await db.putJSON(statsKey(userId), s); + return s; +} + +function prevDate(ymd) { + const d = new Date(`${ymd}T00:00:00Z`); + d.setUTCDate(d.getUTCDate() - 1); + return d.toISOString().slice(0, 10); +} diff --git a/tests/modules/dispatcher.test.js b/tests/modules/dispatcher.test.js index d714f75..3542c50 100644 --- a/tests/modules/dispatcher.test.js +++ b/tests/modules/dispatcher.test.js @@ -24,7 +24,7 @@ describe("installDispatcher", () => { const env = { MODULES: "util,wordle,loldle,misc", KV: makeFakeKv() }; const reg = await installDispatcher(bot, env); - // Expect 11 total commands (5 public + 3 protected + 3 private). + // Expect 11 total commands (7 public + 2 protected + 2 private). expect(bot.commandCalls).toHaveLength(11); expect(reg.allCommands.size).toBe(11); @@ -34,7 +34,6 @@ describe("installDispatcher", () => { // Assert private commands ARE registered (the whole point of unified routing). expect(registeredNames).toContain("konami"); - expect(registeredNames).toContain("ggwp"); expect(registeredNames).toContain("fortytwo"); }); diff --git a/tests/modules/loldle/compare.test.js b/tests/modules/loldle/compare.test.js new file mode 100644 index 0000000..2220710 --- /dev/null +++ b/tests/modules/loldle/compare.test.js @@ -0,0 +1,87 @@ +import { describe, expect, it } from "vitest"; +import { CLASSIC_ATTRIBUTES, compareChampions } from "../../../src/modules/loldle/compare.js"; + +const aatrox = { + id: "Aatrox", + gender: "male", + genre: "Fighter", + attackType: "close", + resource: "Blood Well", + region: "runeterra", + lane: "top", + releaseDate: 2013, +}; + +const ahri = { + id: "Ahri", + gender: "female", + genre: "Mage,Assassin", + attackType: "range", + resource: "Mana", + region: "ionia", + lane: "mid", + releaseDate: 2011, +}; + +const akali = { + id: "Akali", + gender: "female", + genre: "Assassin", + attackType: "close", + resource: "Energy", + region: "ionia", + lane: "mid,top", + releaseDate: 2010, +}; + +function byKey(results, key) { + return results.find((r) => r.key === key); +} + +describe("compareChampions", () => { + it("all correct when guess === target", () => { + const r = compareChampions(aatrox, aatrox); + for (const row of r) expect(row.result).toBe("correct"); + }); + + it("exact mismatch is wrong", () => { + const r = compareChampions(aatrox, ahri); + expect(byKey(r, "gender").result).toBe("wrong"); + expect(byKey(r, "attackType").result).toBe("wrong"); + expect(byKey(r, "resource").result).toBe("wrong"); + expect(byKey(r, "region").result).toBe("wrong"); + }); + + it("multi-value partial overlap is partial", () => { + const r = compareChampions(akali, ahri); + expect(byKey(r, "genre").result).toBe("partial"); + expect(byKey(r, "lane").result).toBe("partial"); + }); + + it("multi-value identical sets are correct even if order/case differ", () => { + const r = compareChampions( + { ...akali, genre: "assassin" }, + { ...akali, genre: "Assassin" }, + ); + expect(byKey(r, "genre").result).toBe("correct"); + }); + + it("year direction hints up when guess < target", () => { + const r = compareChampions(akali, aatrox); // 2010 vs 2013 + const y = byKey(r, "releaseDate"); + expect(y.result).toBe("wrong"); + expect(y.direction).toBe("up"); + }); + + it("year direction hints down when guess > target", () => { + const r = compareChampions(aatrox, akali); // 2013 vs 2010 + const y = byKey(r, "releaseDate"); + expect(y.result).toBe("wrong"); + expect(y.direction).toBe("down"); + }); + + it("returns 7 attributes in declared order", () => { + const r = compareChampions(aatrox, ahri); + expect(r.map((x) => x.key)).toEqual(CLASSIC_ATTRIBUTES.map((a) => a.key)); + }); +}); diff --git a/tests/modules/loldle/daily.test.js b/tests/modules/loldle/daily.test.js new file mode 100644 index 0000000..a3e8c6c --- /dev/null +++ b/tests/modules/loldle/daily.test.js @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { pickDaily, todayUtc } from "../../../src/modules/loldle/daily.js"; + +describe("daily", () => { + it("todayUtc returns YYYY-MM-DD", () => { + expect(todayUtc(new Date("2026-04-20T23:30:00Z"))).toBe("2026-04-20"); + expect(todayUtc(new Date("2026-01-01T00:00:00Z"))).toBe("2026-01-01"); + }); + + it("pickDaily is deterministic for same seed", () => { + const champions = [{ id: "A" }, { id: "B" }, { id: "C" }, { id: "D" }]; + const a = pickDaily(champions, "2026-04-20"); + const b = pickDaily(champions, "2026-04-20"); + expect(a).toBe(b); + }); + + it("pickDaily produces different picks over time", () => { + const champions = Array.from({ length: 100 }, (_, i) => ({ id: `C${i}` })); + const picks = new Set(); + for (let d = 1; d <= 30; d++) { + picks.add(pickDaily(champions, `2026-04-${String(d).padStart(2, "0")}`).id); + } + expect(picks.size).toBeGreaterThan(5); + }); + + it("throws on empty list", () => { + expect(() => pickDaily([], "x")).toThrow(); + }); +}); diff --git a/tests/modules/loldle/lookup.test.js b/tests/modules/loldle/lookup.test.js new file mode 100644 index 0000000..05ded10 --- /dev/null +++ b/tests/modules/loldle/lookup.test.js @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import { findChampion } from "../../../src/modules/loldle/lookup.js"; + +const champions = [ + { id: "Aatrox", name: "Aatrox" }, + { id: "Ahri", name: "Ahri" }, + { id: "KhaZix", name: "Kha'Zix" }, + { id: "MissFortune", name: "Miss Fortune" }, +]; + +describe("findChampion", () => { + it("matches by exact id (case-insensitive)", () => { + expect(findChampion(champions, "aatrox").id).toBe("Aatrox"); + expect(findChampion(champions, "AATROX").id).toBe("Aatrox"); + }); + + it("normalizes punctuation and spaces", () => { + expect(findChampion(champions, "kha'zix").id).toBe("KhaZix"); + expect(findChampion(champions, "miss fortune").id).toBe("MissFortune"); + expect(findChampion(champions, "MissFortune").id).toBe("MissFortune"); + }); + + it("returns null for non-matching input", () => { + expect(findChampion(champions, "zzz")).toBeNull(); + expect(findChampion(champions, "")).toBeNull(); + }); + + it("falls back to unique prefix match", () => { + expect(findChampion(champions, "aat").id).toBe("Aatrox"); + }); + + it("prefix match returns null on ambiguity", () => { + const ambig = [...champions, { id: "Aatrox2", name: "Aatrox 2" }]; + expect(findChampion(ambig, "aa")).toBeNull(); + }); +}); diff --git a/wrangler.toml b/wrangler.toml index 6d02d3c..8ae188c 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -14,8 +14,8 @@ MODULES = "util,wordle,loldle,misc,trading" # then paste the returned IDs below. [[kv_namespaces]] binding = "KV" -id = "REPLACE_ME" -preview_id = "REPLACE_ME" +id = "f7f190fcb2fa42eb84a05542911334b0" +preview_id = "0ad72e1c34174f8cadf828e62db099a8" # D1 database for module persistent storage. Each module prefixes its tables # with `{moduleName}_` (e.g. `trading_trades`). Migrations are applied via @@ -25,7 +25,7 @@ preview_id = "REPLACE_ME" [[d1_databases]] binding = "DB" database_name = "miti99bot-db" -database_id = "REPLACE_ME_D1_UUID" +database_id = "261b54e7-0fdb-4fe7-8ed9-2e8a8bcf459c" # Cron Triggers — union of all schedules declared by modules. # When adding a module with cron entries, append its schedule(s) here.