diff --git a/cards.json b/cards.json index b2f0443..61bc02f 100644 --- a/cards.json +++ b/cards.json @@ -331,8 +331,8 @@ "id": 26000005, "elixirCost": 3, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 3, "duration": null, @@ -514,7 +514,7 @@ "prestigeCost": null }, "hitspeed": 1.1, - "radius": null, + "radius": 1.5, "generationSpeed": 7.0, "generationUnits": 4, "speed": "medium", @@ -712,7 +712,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 0.5, "territory": "restricted", "rarity": "common", @@ -774,7 +774,7 @@ "prestigeCost": null }, "hitspeed": 1.5, - "radius": null, + "radius": 2.0, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -842,7 +842,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 0.5, "territory": "restricted", "rarity": "epic", @@ -904,7 +904,7 @@ "prestigeCost": null }, "hitspeed": 1.8, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1036,7 +1036,7 @@ "prestigeCost": null }, "hitspeed": 1.5, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "fast", @@ -1167,7 +1167,7 @@ "prestigeCost": 1 }, "hitspeed": 1.4, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1366,7 +1366,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "slow", + "speed": "medium", "range": 0.8, "territory": "restricted", "rarity": "epic", @@ -1516,7 +1516,7 @@ "evolution": false, "hero": false, "typeAttack": "splash", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -1756,7 +1756,7 @@ "prestigeCost": null }, "hitspeed": 3.0, - "radius": null, + "radius": 2.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1821,7 +1821,7 @@ "prestigeCost": null }, "hitspeed": 1.3, - "radius": null, + "radius": 1.1, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1966,8 +1966,8 @@ "id": 26000030, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -2018,7 +2018,7 @@ "prestigeCost": null }, "hitspeed": 0.3, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -2032,8 +2032,8 @@ "id": 26000031, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -2084,7 +2084,7 @@ "prestigeCost": null }, "hitspeed": 0.3, - "radius": null, + "radius": 2.3, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -2098,7 +2098,6 @@ "id": 26000032, "elixirCost": 3, "targets": [ - "buildings", "ground" ], "units": 1, @@ -2215,7 +2214,7 @@ "prestigeCost": null }, "hitspeed": 4.0, - "radius": null, + "radius": 1.8, "generationSpeed": null, "generationUnits": null, "speed": "slow", @@ -2280,10 +2279,10 @@ "prestigeCost": null }, "hitspeed": 2.5, - "radius": null, + "radius": 1.8, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "slow", "range": 4.0, "territory": "restricted", "rarity": "epic", @@ -2687,15 +2686,15 @@ "id": 26000041, "elixirCost": 3, "targets": [ - "air", - "ground" + "ground", + "air" ], "units": 6, "duration": null, "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -2760,7 +2759,7 @@ "duration": 0.001, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -2808,7 +2807,7 @@ "radius": 3.0, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "fast", "range": 5.0, "territory": "restricted", "rarity": "legendary", @@ -2873,7 +2872,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 1.2, "territory": "restricted", "rarity": "common", @@ -2936,10 +2935,10 @@ "prestigeCost": null }, "hitspeed": 2.2, - "radius": null, + "radius": 0.07, "generationSpeed": null, "generationUnits": null, - "speed": "slow", + "speed": "medium", "range": 4.0, "territory": "restricted", "rarity": "epic", @@ -3002,7 +3001,7 @@ "prestigeCost": null }, "hitspeed": 0.9, - "radius": null, + "radius": 1.0, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -3070,7 +3069,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 0.75, "territory": "restricted", "rarity": "legendary", @@ -3146,7 +3145,8 @@ "id": 26000048, "elixirCost": 4, "targets": [ - "ground" + "ground", + "air" ], "units": 1, "duration": null, @@ -3200,7 +3200,7 @@ "radius": null, "generationSpeed": 5.0, "generationUnits": 2, - "speed": "fast", + "speed": "medium", "range": 1.6, "territory": "restricted", "rarity": "legendary", @@ -3328,7 +3328,7 @@ "prestigeCost": null }, "hitspeed": 1.8, - "radius": null, + "radius": 1.0, "generationSpeed": null, "generationUnits": null, "speed": "fast", @@ -3343,8 +3343,8 @@ "elixirCost": 5, "targets": [ "buildings", - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -3475,15 +3475,15 @@ "id": 26000053, "elixirCost": 5, "targets": [ - "air", - "ground" + "ground", + "air" ], "units": 3, "duration": null, "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -3657,7 +3657,7 @@ "prestigeCost": null }, "hitspeed": 1.7, - "radius": null, + "radius": 1.3, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -3673,7 +3673,7 @@ "targets": [ "buildings" ], - "units": 7, + "units": 1, "duration": null, "evolution": true, "hero": false, @@ -3725,7 +3725,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": null, "territory": "restricted", "rarity": "common", @@ -3791,7 +3791,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "fast", "range": 6.0, "territory": "restricted", "rarity": "rare", @@ -3853,7 +3853,7 @@ "prestigeCost": null }, "hitspeed": 1.2, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -3921,7 +3921,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "fast", + "speed": "very-fast", "range": 0.75, "territory": "restricted", "rarity": "rare", @@ -3932,7 +3932,9 @@ "id": 26000060, "elixirCost": 6, "targets": [ - "buildings" + "buildings", + "air", + "ground" ], "units": 1, "duration": null, @@ -3985,8 +3987,8 @@ "hitspeed": 1.5, "radius": null, "generationSpeed": null, - "generationUnits": null, - "speed": "fast", + "generationUnits": 2, + "speed": "medium", "range": 1.2, "territory": "restricted", "rarity": "epic", @@ -4114,7 +4116,7 @@ "prestigeCost": null }, "hitspeed": 1.1, - "radius": null, + "radius": 0.25, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -4249,7 +4251,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "fast", "range": 6.0, "territory": "restricted", "rarity": "common", @@ -4396,7 +4398,7 @@ "duration": null, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -4506,7 +4508,7 @@ "prestigeCost": null }, "hitspeed": 1.6, - "radius": null, + "radius": 1.3, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -4575,7 +4577,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "fast", + "speed": "medium", "range": 5.0, "territory": "restricted", "rarity": "champion", @@ -4768,7 +4770,7 @@ "prestigeCost": null }, "hitspeed": 1.9, - "radius": null, + "radius": 0.8, "generationSpeed": null, "generationUnits": null, "speed": "fast", @@ -4848,8 +4850,8 @@ "id": 26000084, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -5034,7 +5036,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "medium", "range": 1.6, "territory": "restricted", "rarity": "legendary", @@ -5048,7 +5050,7 @@ "air", "ground" ], - "units": 2, + "units": 1, "duration": null, "evolution": false, "hero": false, @@ -5161,8 +5163,8 @@ "statsHero": { "prestigeCost": null }, - "hitspeed": 1.2, - "radius": null, + "hitspeed": 1.1, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -5243,7 +5245,7 @@ "targets": [ "buildings" ], - "units": 2, + "units": 1, "duration": 1.0, "evolution": false, "hero": false, @@ -5307,15 +5309,15 @@ "elixirCost": 5, "targets": [ "buildings", - "ground", - "air" + "air", + "ground" ], "units": 2, "duration": null, "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -5699,8 +5701,7 @@ "id": 27000002, "elixirCost": 4, "targets": [ - "ground", - "buildings" + "ground" ], "units": 1, "duration": 30.0, @@ -5751,7 +5752,7 @@ "prestigeCost": null }, "hitspeed": 5.0, - "radius": null, + "radius": 2.0, "generationSpeed": null, "generationUnits": null, "speed": null, @@ -5765,8 +5766,8 @@ "id": 27000003, "elixirCost": 5, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": 30.0, @@ -5882,7 +5883,7 @@ "prestigeCost": null }, "hitspeed": 1.8, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": null, @@ -5948,7 +5949,7 @@ }, "hitspeed": 10.0, "radius": null, - "generationSpeed": 15.0, + "generationSpeed": 14.0, "generationUnits": 3, "speed": null, "range": null, @@ -5961,8 +5962,8 @@ "id": 27000006, "elixirCost": 4, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": 30.0, @@ -6031,7 +6032,7 @@ "duration": 65.0, "evolution": false, "hero": false, - "typeAttack": null, + "typeAttack": "unique", "projectile": false, "suicide": false, "fatalDamage": { @@ -6090,8 +6091,7 @@ "id": 27000008, "elixirCost": 6, "targets": [ - "ground", - "buildings" + "ground" ], "units": 1, "duration": 30.0, @@ -6208,7 +6208,7 @@ }, "hitspeed": 10.0, "radius": null, - "generationSpeed": 4.0, + "generationSpeed": 3.5, "generationUnits": 2, "speed": null, "range": 2.0, @@ -6221,8 +6221,8 @@ "id": 27000010, "elixirCost": 4, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -6276,7 +6276,7 @@ "radius": null, "generationSpeed": 7.0, "generationUnits": 1, - "speed": "slow", + "speed": "medium", "range": 6.0, "territory": "restricted", "rarity": "rare", @@ -6407,7 +6407,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "fast", + "speed": "medium", "range": null, "territory": "wide", "rarity": "epic", @@ -6418,8 +6418,8 @@ "id": 28000000, "elixirCost": 4, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -6484,8 +6484,8 @@ "id": 28000001, "elixirCost": 3, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -6505,14 +6505,14 @@ "level16": null }, "towerDamage": { - "level11": 31, - "level15": 45, - "level16": 49 + "level11": 93, + "level15": 135, + "level16": 147 }, "damage": { - "level11": 123, - "level15": 179, - "level16": 196 + "level11": 366, + "level15": 534, + "level16": 588 }, "hitpoints": { "level11": null, @@ -6616,8 +6616,8 @@ "id": 28000003, "elixirCost": 6, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -6689,7 +6689,7 @@ "duration": null, "evolution": true, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": true, "suicide": false, "fatalDamage": { @@ -6818,7 +6818,7 @@ "duration": null, "evolution": false, "hero": false, - "typeAttack": null, + "typeAttack": "unique", "projectile": false, "suicide": false, "fatalDamage": { @@ -6884,8 +6884,8 @@ "duration": 1.5, "evolution": false, "hero": false, - "typeAttack": "unique", - "projectile": false, + "typeAttack": "splash", + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -7082,7 +7082,7 @@ "duration": 9.0, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -7192,7 +7192,7 @@ "prestigeCost": null }, "hitspeed": null, - "radius": null, + "radius": 1.95, "generationSpeed": null, "generationUnits": null, "speed": null, @@ -7279,7 +7279,7 @@ "duration": 1.0, "evolution": false, "hero": false, - "typeAttack": null, + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -7454,7 +7454,7 @@ "prestigeCost": null }, "hitspeed": 1.3, - "radius": null, + "radius": 1.3, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -7468,8 +7468,8 @@ "id": 28000016, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": 1.0, @@ -7520,7 +7520,7 @@ "prestigeCost": null }, "hitspeed": 0.3, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -7534,8 +7534,8 @@ "id": 28000017, "elixirCost": 2, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -7608,7 +7608,7 @@ "evolution": false, "hero": false, "typeAttack": "splash", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -7631,9 +7631,9 @@ "level16": 699 }, "hitpoints": { - "level11": 547, + "level11": 548, "level15": 796, - "level16": 874 + "level16": 875 }, "statsEvo": { "cycles": null, @@ -7932,8 +7932,8 @@ "id": 159000000, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -7998,8 +7998,8 @@ "id": 159000001, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -8064,8 +8064,8 @@ "id": 159000002, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -8130,8 +8130,8 @@ "id": 159000004, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b6c38bd --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,25 @@ +import js from '@eslint/js'; +import globals from 'globals'; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.node, + ...globals.jest, + }, + }, + rules: { + 'no-unused-vars': 'warn', + 'no-console': 'off', + 'indent': ['error', 4], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'no-undef': 'error', + 'no-use-before-define': ['error', { functions: false, classes: true, variables: true }], + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index b9f5426..5fd0870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "1.8.0", "license": "Apache-2.0", "devDependencies": { + "@eslint/js": "^9.39.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.2", "@types/ajv": "^0.0.5", "@types/jest": "^30.0.0", "ajv": "^8.17.1", + "eslint": "^9.39.2", + "globals": "^17.1.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.0.4", "semantic-release": "^24.2.4" @@ -697,6 +700,307 @@ "tslib": "^2.4.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2101,6 +2405,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2151,6 +2462,13 @@ "parse5": "^7.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", @@ -2475,6 +2793,29 @@ "win32" ] }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -3519,6 +3860,13 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3840,6 +4188,224 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -3854,6 +4420,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3937,6 +4549,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -3980,6 +4599,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4037,6 +4669,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -4207,6 +4860,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", + "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4360,6 +5039,16 @@ "node": ">=0.10.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4515,6 +5204,16 @@ "dev": true, "license": "MIT" }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -4535,6 +5234,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5447,6 +6159,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -5468,6 +6187,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -5494,6 +6220,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5504,6 +6240,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -5606,6 +6356,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -8718,6 +9475,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -9121,6 +9896,16 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", @@ -10574,6 +11359,19 @@ "license": "0BSD", "optional": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -10740,6 +11538,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -10869,6 +11677,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 92f9e4a..4cecc88 100644 --- a/package.json +++ b/package.json @@ -20,18 +20,23 @@ "cards.json" ], "devDependencies": { + "@eslint/js": "^9.39.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.2", "@types/ajv": "^0.0.5", "@types/jest": "^30.0.0", "ajv": "^8.17.1", + "eslint": "^9.39.2", + "globals": "^17.1.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.0.4", "semantic-release": "^24.2.4" }, "scripts": { - "test": "jest /tests/cards.test.js", + "test": "jest tests/cards.test.js", + "lint": "eslint .", + "lint:fix": "eslint . --fix", "buid:update-cards": "node scripts/update-cards.js && node scripts/convert-to-floats.js" } -} +} \ No newline at end of file diff --git a/scripts/update-cards.js b/scripts/update-cards.js index dc3321f..2135605 100644 --- a/scripts/update-cards.js +++ b/scripts/update-cards.js @@ -1,13 +1,11 @@ -/** - * @fileoverview Script to update cards.json with data from galacticapricot API. - * - * Usage: node scripts/update-cards.js - */ - const fs = require('fs'); const path = require('path'); const https = require('https'); +// ───────────────────────────────────────────────────────────────────────────── +// CONSTANTS +// ───────────────────────────────────────────────────────────────────────────── + const API_URL = 'https://humble.galacticapricot.dev/gamedata-v5.json'; const CARDS_FILE = path.join(__dirname, '..', 'cards.json'); @@ -16,18 +14,22 @@ const MULTIPLIERS = { tower: { level11: 2.18, level15: 3.16, level16: 3.46 } }; +const SPEED_MAP = { + 'TID_SPEED_0': 'slow', 'TID_SPEED_1': 'slow', 'TID_SPEED_2': 'slow', + 'TID_SPEED_3': 'medium', 'TID_SPEED_4': 'fast', 'TID_SPEED_5': 'very-fast' +}; + +const TARGETS_MAP = { + 'TID_TARGETS_GROUND': ['ground'], + 'TID_TARGETS_AIR_AND_GROUND': ['air', 'ground'], + 'TID_TARGETS_BUILDINGS': ['buildings'], + 'TID_TARGETS_NONE': [] +}; + const CARD_SKELETON = { - name: null, - id: null, - elixirCost: null, - targets: [], - units: 1, - duration: null, - evolution: false, - hero: false, - typeAttack: null, - projectile: false, - suicide: false, + name: null, id: null, elixirCost: null, targets: [], units: 1, + duration: null, evolution: false, hero: false, typeAttack: null, + projectile: false, suicide: false, fatalDamage: { level11: null, level15: null, level16: null }, chargeDamage: { level11: null, level15: null, level16: null }, towerDamage: { level11: null, level15: null, level16: null }, @@ -38,62 +40,222 @@ const CARD_SKELETON = { damage: { level11: null, level15: null, level16: null }, hitpoints: { level11: null, level15: null, level16: null } }, - statsHero: { - prestigeCost: null - }, - hitspeed: null, - radius: null, - generationSpeed: null, - generationUnits: null, - speed: null, - range: null, - territory: null, - rarity: null, - type: null + statsHero: { prestigeCost: null }, + hitspeed: null, radius: null, generationSpeed: null, generationUnits: null, + speed: null, range: null, territory: null, rarity: null, type: null }; -/** - * Fetch JSON data from URL - */ -function fetchData(url) { - return new Promise((resolve, reject) => { - https.get(url, (res) => { - let data = ''; - res.on('data', (chunk) => data += chunk); - res.on('end', () => { - try { - resolve(JSON.parse(data)); - } catch (e) { - reject(new Error('Failed to parse API response')); - } - }); - }).on('error', (err) => reject(err)); +// ───────────────────────────────────────────────────────────────────────────── +// HELPERS +// ───────────────────────────────────────────────────────────────────────────── + +const fetchData = (url) => new Promise((resolve, reject) => { + https.get(url, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { resolve(JSON.parse(data)); } + catch (e) { reject(new Error('Failed to parse API response: ' + e.message)); } + }); + }).on('error', reject); +}); + +const calcStats = (base, m) => base + ? { level11: Math.round(base * m.level11), level15: Math.round(base * m.level15), level16: Math.round(base * m.level16) } + : { level11: null, level15: null, level16: null }; + +const mergeStats = (computed, existing) => ({ + level11: computed.level11 ?? existing?.level11 ?? null, + level15: computed.level15 ?? existing?.level15 ?? null, + level16: computed.level16 ?? existing?.level16 ?? null +}); + +const getTargets = (tid) => TARGETS_MAP[tid] || []; + +const extractCharData = (item) => { + if (item.summonCharacterData) return item.summonCharacterData; + if (item.statCharacterData) return item.statCharacterData; + const areaSpawn = item.areaEffectObjectData?.onStartingActionData?.spawnDataData; + return areaSpawn || {}; +}; + +const cloneDeep = (obj) => JSON.parse(JSON.stringify(obj)); + +// ───────────────────────────────────────────────────────────────────────────── +// CARD PROCESSING +// ───────────────────────────────────────────────────────────────────────────── + +function collectCharacters(apiItem) { + const chars = []; + if (apiItem.summonCharacterData) chars.push({ data: apiItem.summonCharacterData, count: apiItem.summonNumber || 1 }); + if (apiItem.summonCharacterSecondData) chars.push({ data: apiItem.summonCharacterSecondData, count: apiItem.summonCharacterSecondCount || 1 }); + if (apiItem.summonCharacterThirdData) chars.push({ data: apiItem.summonCharacterThirdData, count: apiItem.summonCharacterThirdCount || 1 }); + return chars; +} + +function aggregateCharacterStats(characters) { + let maxHP = 0, primaryChar = {}, totalDamage = 0, hasProjectile = false; + + characters.forEach(({ data }) => { + const hp = data.hitpoints || 0; + const proj = data.projectileData || {}; + const dmg = data.damage || proj.damage || 0; + + if (hp > maxHP) { maxHP = hp; primaryChar = data; } + if (dmg > totalDamage) totalDamage = dmg; + if (Object.keys(proj).length > 0) hasProjectile = true; + }); + + return { maxHP, primaryChar, totalDamage, hasProjectile }; +} + +function extractAllTargets(apiItem, charData) { + const sources = [ + apiItem.tidTarget, + apiItem.projectileData?.tidTarget, + charData.tidTarget, + charData.spawnCharacterData?.tidTarget + ]; + + ['summonCharacterData', 'summonCharacterSecondData', 'summonCharacterThirdData'].forEach(key => { + const data = apiItem[key]; + if (data) { + sources.push(data.tidTarget, data.spawnCharacterData?.tidTarget); + } }); + + return [...new Set(sources.flatMap(getTargets))]; +} + +function processCard(card, apiItem, multipliers) { + const charData = extractCharData(apiItem); + const areaData = apiItem.areaEffectObjectData || {}; + const projData = apiItem.projectileData || charData.projectileData || areaData.projectileData || {}; + const buffData = areaData.buffData || {}; + const spawnProjData = projData.spawnProjectileData || areaData.projectileData || {}; + const spawnCharData = projData.spawnCharacterData || areaData.spawnCharacterData || {}; + + card.elixirCost = apiItem.manaCost ?? card.elixirCost; + card.rarity = (apiItem.rarity || card.rarity || '').toLowerCase(); + + const allTargets = extractAllTargets(apiItem, charData); + if (allTargets.length > 0) card.targets = allTargets; + + card.units = (apiItem.summonNumber || 0) + (apiItem.summonCharacterSecondCount || 0) + (apiItem.summonCharacterThirdCount || 0) || card.units; + + const characters = collectCharacters(apiItem); + let baseHP, baseDamage; + + if (characters.length > 0) { + const { maxHP, primaryChar, totalDamage, hasProjectile } = aggregateCharacterStats(characters); + card.hitspeed = primaryChar.hitSpeed ? primaryChar.hitSpeed / 1000 : card.hitspeed; + card.range = primaryChar.range ? primaryChar.range / 1000 : card.range; + card.speed = SPEED_MAP[primaryChar.tidSpeed] || card.speed; + card.projectile = hasProjectile || card.projectile; + baseHP = maxHP || charData.hitpoints || spawnCharData.hitpoints; + baseDamage = totalDamage || charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || spawnProjData.damage || spawnCharData.damage; + } else { + card.hitspeed = charData.hitSpeed ? charData.hitSpeed / 1000 : card.hitspeed; + card.range = charData.range ? charData.range / 1000 : card.range; + card.speed = SPEED_MAP[charData.tidSpeed] || card.speed; + baseHP = charData.hitpoints || spawnCharData.hitpoints; + baseDamage = charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || spawnProjData.damage || spawnCharData.damage; + } + + card.generationSpeed = charData.spawnPauseTime ? charData.spawnPauseTime / 1000 : card.generationSpeed; + card.generationUnits = charData.spawnNumber > 1 ? charData.spawnNumber : card.generationUnits; + + const rawRadius = apiItem.radius ?? areaData.radius ?? charData.areaDamageRadius ?? projData.radius ?? projData.customFirstProjectileData?.radius; + if (rawRadius != null) { + card.radius = rawRadius / 1000; + card.typeAttack = 'splash'; + } else { + card.typeAttack = card.typeAttack || 'unique'; + } + + card.projectile = Object.keys(projData).length > 0 || card.projectile; + + const rawDuration = apiItem.lifeTime ?? areaData.lifeDuration; + if (rawDuration != null) card.duration = rawDuration / 1000; + + const baseFatal = charData.deathDamage || charData.deathSpawnCharacterData?.deathDamage; + const baseCharge = charData.damageSpecial; + + const towerDamagePercent = apiItem.crownTowerDamagePercent ?? charData.crownTowerDamagePercent ?? projData.crownTowerDamagePercent ?? + areaData.crownTowerDamagePercent ?? buffData.crownTowerDamagePercent ?? spawnProjData.crownTowerDamagePercent ?? spawnCharData.crownTowerDamagePercent; + const baseTowerDamage = towerDamagePercent !== undefined && baseDamage ? baseDamage * (100 + towerDamagePercent) / 100 : null; + + card.hitpoints = mergeStats(calcStats(baseHP, multipliers), card.hitpoints); + card.damage = mergeStats(calcStats(baseDamage, multipliers), card.damage); + card.fatalDamage = mergeStats(calcStats(baseFatal, multipliers), card.fatalDamage); + card.chargeDamage = mergeStats(calcStats(baseCharge, multipliers), card.chargeDamage); + card.towerDamage = mergeStats(calcStats(baseTowerDamage, multipliers), card.towerDamage); + + processEvolution(card, apiItem, multipliers, baseHP, baseDamage); + extrapolateLevel16(card, multipliers); } -/** - * Calculate stat for level 11 and 15 - */ -function calculateStats(base, multipliers) { - if (base === null || base === undefined || base === 0) return { level11: null, level15: null, level16: null }; - return { - level11: Math.round(base * multipliers.level11), - level15: Math.round(base * multipliers.level15), - level16: Math.round(base * multipliers.level16) +function processEvolution(card, apiItem, multipliers, baseHP, baseDamage) { + if (!apiItem.evolvedSpellsData) return; + + card.evolution = true; + const evoData = apiItem.evolvedSpellsData; + + let evoCharData = evoData.summonCharacterData || {}; + if (Object.keys(evoCharData).length === 0) { + evoCharData = evoData.areaEffectObjectData?.onStartingActionData?.spawnDataData || {}; + } + + const evoProjData = evoData.projectileData || evoCharData.projectileData || {}; + const evoAreaData = evoData.areaEffectObjectData || {}; + const evoBuffData = evoAreaData.buffData || {}; + const evoSpawnProjData = evoProjData.spawnProjectileData || evoAreaData.projectileData || {}; + const evoSpawnCharData = evoProjData.spawnCharacterData || evoAreaData.spawnCharacterData || {}; + + const baseEvoHP = evoCharData.hitpoints || evoSpawnCharData.hitpoints || baseHP; + const baseEvoDmg = evoCharData.damage || evoProjData.damage || evoAreaData.damage || + evoBuffData.damagePerSecond || evoSpawnProjData.damage || evoSpawnCharData.damage || baseDamage; + + card.statsEvo.hitpoints = mergeStats(calcStats(baseEvoHP, multipliers), card.statsEvo.hitpoints); + card.statsEvo.damage = mergeStats(calcStats(baseEvoDmg, multipliers), card.statsEvo.damage); + card.statsEvo.cycles = evoData.cycles ?? card.statsEvo.cycles; +} + +function extrapolateLevel16(card, multipliers) { + const extrapolate = (stats) => { + if (stats && stats.level16 == null && stats.level11 != null) { + stats.level16 = Math.round((stats.level11 / multipliers.level11) * multipliers.level16); + } }; + + ['hitpoints', 'damage', 'fatalDamage', 'chargeDamage', 'towerDamage'].forEach(f => extrapolate(card[f])); + if (card.statsEvo) { + ['hitpoints', 'damage'].forEach(f => extrapolate(card.statsEvo[f])); + } } -/** - * Main update logic - */ +function ensureCardSkeleton(card) { + Object.keys(CARD_SKELETON).forEach(key => { + if (!(key in card)) { + card[key] = cloneDeep(CARD_SKELETON[key]); + } else if (CARD_SKELETON[key] && typeof CARD_SKELETON[key] === 'object' && !Array.isArray(CARD_SKELETON[key])) { + Object.keys(CARD_SKELETON[key]).forEach(nested => { + if (!(nested in card[key])) card[key][nested] = CARD_SKELETON[key][nested]; + }); + } + }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// MAIN +// ───────────────────────────────────────────────────────────────────────────── + async function main() { try { console.log('Fetching game data...'); const apiData = await fetchData(API_URL); - if (!apiData || !apiData.items || !apiData.items.spells) { - throw new Error('Invalid API data structure'); - } + if (!apiData?.items?.spells) throw new Error('Invalid API data structure'); console.log('Reading cards.json...'); const cardsJson = JSON.parse(fs.readFileSync(CARDS_FILE, 'utf8')); @@ -102,14 +264,10 @@ async function main() { cardsJson.cards.forEach(c => existingCardsMap.set(c.id, { card: c, list: cardsJson.cards })); cardsJson.towerCards.forEach(c => existingCardsMap.set(c.id, { card: c, list: cardsJson.towerCards })); - let updatedCount = 0; - let addedCount = 0; + let updatedCount = 0, addedCount = 0; apiData.items.spells.forEach(apiItem => { - // Skip Super cards and event cards - if (apiItem.name.startsWith('Super') || apiItem.notVisible) { - return; - } + if (apiItem.name.startsWith('Super') || apiItem.notVisible) return; const isTower = apiItem.tidType === 'TID_TYPE_TOWER_TROOP' || apiItem.source === 'support_cards'; const multipliers = isTower ? MULTIPLIERS.tower : MULTIPLIERS.standard; @@ -118,168 +276,26 @@ async function main() { let card; if (!entry) { - // New card skeleton - card = JSON.parse(JSON.stringify(CARD_SKELETON)); + card = cloneDeep(CARD_SKELETON); card.id = apiItem.id; card.name = apiItem.englishName || apiItem.name; - card.type = isTower ? 'tower' : 'troop'; // Default, adjusted below - - if (isTower) { - cardsJson.towerCards.push(card); - } else { - cardsJson.cards.push(card); - } + card.type = isTower ? 'tower' : 'troop'; + (isTower ? cardsJson.towerCards : cardsJson.cards).push(card); addedCount++; } else { card = entry.card; updatedCount++; } - // Update basic info if missing or new - card.elixirCost = apiItem.manaCost ?? card.elixirCost; - card.rarity = (apiItem.rarity || card.rarity || '').toLowerCase(); - - // Extract base stats - let charData = apiItem.summonCharacterData || apiItem.statCharacterData || {}; - - // Handle E-Wiz and similar cases where character data is inside area effect - if (Object.keys(charData).length === 0 && apiItem.areaEffectObjectData && apiItem.areaEffectObjectData.onStartingActionData && apiItem.areaEffectObjectData.onStartingActionData.spawnDataData) { - charData = apiItem.areaEffectObjectData.onStartingActionData.spawnDataData; - } - - const projData = apiItem.projectileData || (charData.projectileData) || {}; - const areaData = apiItem.areaEffectObjectData || {}; - const buffData = areaData.buffData || {}; - const spawnProjData = projData.spawnProjectileData || areaData.projectileData || {}; - const spawnCharData = projData.spawnCharacterData || areaData.spawnCharacterData || {}; - - const baseHP = charData.hitpoints || spawnCharData.hitpoints || null; - const baseDamage = charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || spawnProjData.damage || spawnCharData.damage || null; - const baseFatal = charData.deathDamage || (charData.deathSpawnCharacterData ? charData.deathSpawnCharacterData.deathDamage : null) || null; - const baseCharge = charData.damageSpecial || null; - - // Tower damage calculation for spells/troops - let baseTowerDamage = null; - const towerDamagePercent = apiItem.crownTowerDamagePercent ?? charData.crownTowerDamagePercent ?? projData.crownTowerDamagePercent ?? areaData.crownTowerDamagePercent ?? buffData.crownTowerDamagePercent ?? spawnProjData.crownTowerDamagePercent ?? spawnCharData.crownTowerDamagePercent; - if (towerDamagePercent !== undefined && baseDamage) { - baseTowerDamage = baseDamage * (100 + towerDamagePercent) / 100; - } - - // Update stats - const hpStats = calculateStats(baseHP, multipliers); - const dmgStats = calculateStats(baseDamage, multipliers); - const fatalStats = calculateStats(baseFatal, multipliers); - const chargeStats = calculateStats(baseCharge, multipliers); - const towerDmgStats = calculateStats(baseTowerDamage, multipliers); - - card.hitpoints = { - level11: hpStats.level11 ?? card.hitpoints.level11 ?? null, - level15: hpStats.level15 ?? card.hitpoints.level15 ?? null, - level16: hpStats.level16 ?? card.hitpoints.level16 ?? null - }; - card.damage = { - level11: dmgStats.level11 ?? card.damage.level11 ?? null, - level15: dmgStats.level15 ?? card.damage.level15 ?? null, - level16: dmgStats.level16 ?? card.damage.level16 ?? null - }; - card.fatalDamage = { - level11: fatalStats.level11 ?? card.fatalDamage.level11 ?? null, - level15: fatalStats.level15 ?? card.fatalDamage.level15 ?? null, - level16: fatalStats.level16 ?? card.fatalDamage.level16 ?? null - }; - card.chargeDamage = { - level11: chargeStats.level11 ?? card.chargeDamage.level11 ?? null, - level15: chargeStats.level15 ?? card.chargeDamage.level15 ?? null, - level16: chargeStats.level16 ?? card.chargeDamage.level16 ?? null - }; - card.towerDamage = { - level11: towerDmgStats.level11 ?? card.towerDamage.level11 ?? null, - level15: towerDmgStats.level15 ?? card.towerDamage.level15 ?? null, - level16: towerDmgStats.level16 ?? card.towerDamage.level16 ?? null - }; - - // Evolution check - if (apiItem.evolvedSpellsData) { - card.evolution = true; - const evoData = apiItem.evolvedSpellsData; - let evoCharData = evoData.summonCharacterData || {}; - - if (Object.keys(evoCharData).length === 0 && evoData.areaEffectObjectData && evoData.areaEffectObjectData.onStartingActionData && evoData.areaEffectObjectData.onStartingActionData.spawnDataData) { - evoCharData = evoData.areaEffectObjectData.onStartingActionData.spawnDataData; - } - - const evoProjData = evoData.projectileData || (evoCharData.projectileData) || {}; - const evoAreaData = evoData.areaEffectObjectData || {}; - const evoBuffData = evoAreaData.buffData || {}; - const evoSpawnProjData = evoProjData.spawnProjectileData || evoAreaData.projectileData || {}; - const evoSpawnCharData = evoProjData.spawnCharacterData || evoAreaData.spawnCharacterData || {}; - - const baseEvoHP = evoCharData.hitpoints || evoSpawnCharData.hitpoints || baseHP; - const baseEvoDmg = evoCharData.damage || evoProjData.damage || evoAreaData.damage || evoBuffData.damagePerSecond || evoSpawnProjData.damage || evoSpawnCharData.damage || baseDamage; - - const evoHPStats = calculateStats(baseEvoHP, multipliers); - const evoDmgStats = calculateStats(baseEvoDmg, multipliers); - - card.statsEvo.hitpoints = { - level11: evoHPStats.level11 ?? (card.statsEvo.hitpoints ? card.statsEvo.hitpoints.level11 : null), - level15: evoHPStats.level15 ?? (card.statsEvo.hitpoints ? card.statsEvo.hitpoints.level15 : null), - level16: evoHPStats.level16 ?? (card.statsEvo.hitpoints ? card.statsEvo.hitpoints.level16 : null) - }; - card.statsEvo.damage = { - level11: evoDmgStats.level11 ?? (card.statsEvo.damage ? card.statsEvo.damage.level11 : null), - level15: evoDmgStats.level15 ?? (card.statsEvo.damage ? card.statsEvo.damage.level15 : null), - level16: evoDmgStats.level16 ?? (card.statsEvo.damage ? card.statsEvo.damage.level16 : null) - }; - } - - // Hero check - if (apiItem.heroData) { - card.hero = true; - card.statsHero.prestigeCost = apiItem.heroData.prestigeCount ?? null; - } - - // Fallback: Extrapolate missing Level 16 from Level 11 - const statFields = ['hitpoints', 'damage', 'fatalDamage', 'chargeDamage', 'towerDamage']; - statFields.forEach(field => { - if (card[field] && (card[field].level16 === null || card[field].level16 === undefined) && card[field].level11 !== null) { - card[field].level16 = Math.round((card[field].level11 / multipliers.level11) * multipliers.level16); - } - }); - if (card.statsEvo) { - ['hitpoints', 'damage'].forEach(field => { - if (card.statsEvo[field] && (card.statsEvo[field].level16 === null || card.statsEvo[field].level16 === undefined) && card.statsEvo[field].level11 !== null) { - card.statsEvo[field].level16 = Math.round((card.statsEvo[field].level11 / multipliers.level11) * multipliers.level16); - } - }); - } - + processCard(card, apiItem, multipliers); }); - // Final pass: Ensure all cards follow the skeleton (including level16 nested keys) - const allResultCards = [...cardsJson.cards, ...cardsJson.towerCards]; - allResultCards.forEach(card => { - Object.keys(CARD_SKELETON).forEach(key => { - if (!(key in card)) { - card[key] = JSON.parse(JSON.stringify(CARD_SKELETON[key])); - } else if (CARD_SKELETON[key] !== null && typeof CARD_SKELETON[key] === 'object' && !Array.isArray(CARD_SKELETON[key])) { - // Deep check for nested keys (like level16) - Object.keys(CARD_SKELETON[key]).forEach(nestedKey => { - if (!(nestedKey in card[key])) { - card[key][nestedKey] = CARD_SKELETON[key][nestedKey]; - } - }); - } - }); - }); + [...cardsJson.cards, ...cardsJson.towerCards].forEach(ensureCardSkeleton); - // Write back to file console.log('Writing to cards.json...'); fs.writeFileSync(CARDS_FILE, JSON.stringify(cardsJson, null, 4), 'utf8'); - console.log(`Update complete!`); - console.log(`Updated: ${updatedCount} cards`); - console.log(`Added: ${addedCount} new cards`); - + console.log(`Update complete! Updated: ${updatedCount}, Added: ${addedCount}`); } catch (error) { console.error('Error:', error.message); process.exit(1);