From 9095d0e9441c4b6a647a77a0db76c88d62e607d0 Mon Sep 17 00:00:00 2001 From: En Passant Date: Tue, 24 Oct 2023 03:31:00 -0600 Subject: [PATCH] New client features and fixes Mod-specific item lists (will be bringing these to main), mods can add their own moveIsNotUseless function, Tradeback search fix --- build-tools/build-indexes | 356 ++++++++++++++------------------------ src/battle-dex-data.ts | 2 + src/battle-dex-search.ts | 147 +++++++++------- src/battle-dex.ts | 16 +- 4 files changed, 229 insertions(+), 292 deletions(-) diff --git a/build-tools/build-indexes b/build-tools/build-indexes index 41c507401..38f69da6e 100755 --- a/build-tools/build-indexes +++ b/build-tools/build-indexes @@ -33,6 +33,7 @@ for (const modid in Dex.dexes) { if ((/gen\d/.test(modid) && modid.length === 4) || modid === 'base') continue; let teambuilderConfig = Dex.dexes[modid].data.Scripts.teambuilderConfig; teambuilderConfig = teambuilderConfig ? teambuilderConfig : {}; + if(teambuilderConfig.moveIsNotUseless) teambuilderConfig.moveIsNotUseless = JSON.stringify(teambuilderConfig.moveIsNotUseless.toString()); ModConfig[modid] = ModConfig[modid] ? ModConfig[modid] : {}; ModConfig[modid] = Object.assign(ModConfig[modid], teambuilderConfig); } catch(err) { @@ -687,8 +688,6 @@ function buildTeambuilderTables() { const specificItems = [['header', "Pokémon-specific items"]]; const poorItems = [['header', "Usually useless items"]]; const badItems = [['header', "Useless items"]]; - const unreleasedItems = []; - if (genNum === 6) unreleasedItems.push(['header', "Unreleased"]); for (const id of itemList) { const item = Dex.mod(gen).items.get(id); if (item.gen > genNum) { @@ -706,190 +705,31 @@ function buildTeambuilderTables() { const banlist = Dex.formats.getRuleTable(Dex.formats.get(gen + 'metronomebattle')); if (banlist.isBanned('item:' + item.id)) continue; } - switch (id) { - case 'leftovers': - case 'lifeorb': - case 'choiceband': - case 'choicescarf': - case 'choicespecs': - case 'eviolite': - case 'assaultvest': - case 'focussash': - case 'powerherb': - case 'rockyhelmet': - case 'heavydutyboots': - case 'expertbelt': - case 'salacberry': + if(item.itemUser || item.megaStone || id === 'boosterenergy'){ + specificItems.push(id); + continue; + } + if(item.isPokeball || item.name.startsWith("TR")){ + badItems.push(id); + continue; + } + switch (item.rating) { + case 3: greatItems.push(id); break; - case 'mentalherb': - if (genNum > 4) greatItems.push(id); - else poorItems.push(id); - break; - case 'lumberry': - if (genNum === 2 || genNum > 6) goodItems.push(id); - else greatItems.push(id); - break; - case 'sitrusberry': - if (genNum > 6) goodItems.push(id); - else if (genNum > 3 && genNum < 7) greatItems.push(id); - else poorItems.push(id); - break; - case 'aguavberry': - case 'figyberry': - case 'iapapaberry': - case 'magoberry': - case 'wikiberry': - if (genNum >= 7) greatItems.push(id); - else poorItems.push(id); - break; - case 'berryjuice': - if (genNum === 2) poorItems.push(id); - else goodItems.push(id); + case 2: + goodItems.push(id); break; - case 'dragonfang': - if (genNum === 2) badItems.push(id); - else goodItems.push(id); - break; - case 'dragonscale': - if (genNum === 2) goodItems.push(id); - else badItems.push(id); - break; - case 'mail': - if (genNum >= 6) unreleasedItems.push(id); - else goodItems.push(id); - break; - // Legendaries - case 'adamantorb': - case 'griseousorb': - case 'lustrousorb': - case 'blueorb': - case 'redorb': - case 'souldew': - // Other - // fallsthrough - case 'stick': - case 'thickclub': - case 'lightball': - case 'luckypunch': - case 'quickpowder': - case 'metalpowder': - case 'deepseascale': - case 'deepseatooth': - specificItems.push(id); + // outclassed items + case 1: + poorItems.push(id); break; // Fling-only - case 'rarebone': - case 'belueberry': - case 'blukberry': - case 'cornnberry': - case 'durinberry': - case 'hondewberry': - case 'magostberry': - case 'nanabberry': - case 'nomelberry': - case 'pamtreberry': - case 'pinapberry': - case 'pomegberry': - case 'qualotberry': - case 'rabutaberry': - case 'razzberry': - case 'spelonberry': - case 'tamatoberry': - case 'watmelberry': - case 'wepearberry': - case 'energypowder': - case 'electirizer': - case 'oldamber': - case 'dawnstone': - case 'dubiousdisc': - case 'duskstone': - case 'firestone': - case 'icestone': - case 'leafstone': - case 'magmarizer': - case 'moonstone': - case 'ovalstone': - case 'prismscale': - case 'protector': - case 'reapercloth': - case 'sachet': - case 'shinystone': - case 'sunstone': - case 'thunderstone': - case 'upgrade': - case 'waterstone': - case 'whippeddream': - case 'bottlecap': - case 'goldbottlecap': - case 'galaricacuff': - case 'chippedpot': - case 'crackedpot': - case 'galaricawreath': + case 0: badItems.push(id); break; - // outclassed items - case 'aspearberry': - case 'bindingband': - case 'cheriberry': - case 'destinyknot': - case 'enigmaberry': - case 'floatstone': - case 'ironball': - case 'jabocaberry': - case 'oranberry': - case 'machobrace': - case 'pechaberry': - case 'persimberry': - case 'poweranklet': - case 'powerband': - case 'powerbelt': - case 'powerbracer': - case 'powerlens': - case 'powerweight': - case 'rawstberry': - case 'ringtarget': - case 'rowapberry': - case 'bigroot': - case 'focusband': - // gen 2 - case 'psncureberry': - case 'przcureberry': - case 'burntberry': - case 'bitterberry': - case 'iceberry': - case 'berry': - poorItems.push(id); - break; default: - if ( - item.name.endsWith(" Ball") || item.name.endsWith(" Fossil") || item.name.startsWith("Fossilized ") || - item.name.endsWith(" Sweet") || item.name.endsWith(" Apple") - ) { - badItems.push(id); - } else if (item.name.startsWith("TR")) { - badItems.push(id); - } else if (item.name.endsWith(" Gem") && item.name !== "Normal Gem") { - if (genNum >= 6) { - unreleasedItems.push(id); - } else if (item.name === "Flying Gem") { - greatItems.push(id); - } else { - goodItems.push(id); - } - } else if (item.name.endsWith(" Drive")) { - specificItems.push(id); - } else if (item.name.endsWith(" Memory")) { - specificItems.push(id); - } else if (item.name.startsWith("Rusted")) { - specificItems.push(id); - } else if (item.itemUser) { - specificItems.push(id); - } else if (item.megaStone) { - specificItems.push(id); - } else { - goodItems.push(id); - } + goodItems.push(id); } } items.push(...greatItems); @@ -897,7 +737,6 @@ function buildTeambuilderTables() { items.push(...specificItems); items.push(...poorItems); items.push(...badItems); - items.push(...unreleasedItems); } // @@ -1126,8 +965,9 @@ function buildTeambuilderTables() { // Client relevant data that should be overriden by past gens and mods const overrideSpeciesKeys = ['abilities', 'baseStats', 'cosmeticFormes', 'isNonstandard', 'requiredItems', 'types', 'unreleasedHidden']; - const overrideMoveKeys = ['accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'noSketch', 'pp', 'priority', 'shortDesc', 'target', 'type']; + const overrideMoveKeys = ['accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'noSketch', 'pp', 'priority', 'shortDesc', 'target', 'type', 'viable']; const overrideAbilityKeys = ['desc', 'isNonstandard', 'rating', 'shortDesc']; + const overrideItemKeys = ['desc', 'isNonstandard', 'rating', 'shortDesc']; // // Past gen table @@ -1170,7 +1010,8 @@ function buildTeambuilderTables() { const baseString = JSON.stringify(baseEntry[key]); if (modString !== baseString) { if (!overrideDexInfo[id]) overrideDexInfo[id] = {}; - try { + if (modString === undefined) overrideDexInfo[id][key] = null; + else try { overrideDexInfo[id][key] = JSON.parse(modString); } catch (e) { console.log(modString + " " + id + " " + key + " parsed an invalid value: " + modString); @@ -1179,7 +1020,6 @@ function buildTeambuilderTables() { } } } - const overrideMoveKeys = ['basePower', 'accuracy', 'category', 'pp', 'isNonstandard', 'shortDesc', 'unviable']; const overrideMoveInfo = {}; BattleTeambuilderTable[gen].overrideMoveInfo = overrideMoveInfo; for (const id in genData.Moves) { @@ -1197,27 +1037,7 @@ function buildTeambuilderTables() { const baseString = JSON.stringify(baseEntry[key]); if (modString !== baseString) { if (!overrideMoveInfo[id]) overrideMoveInfo[id] = {}; - overrideMoveInfo[id][key] = JSON.parse(modString); - } - } - } - - BattleTeambuilderTable[gen].overrideMoveInfo = overrideMoveInfo; - for (const id in genData.Moves) { - const modEntry = genData.Moves[id]; - const baseEntry = Dex.data.Moves[id]; - if (typeof baseEntry === 'undefined') { - overrideMoveInfo[id] = {}; - overrideMoveInfo[id] = modEntry; - overrideMoveInfo[id].exists = true; - continue; - } - for (const key in modEntry) { - if (!overrideMoveKeys.includes(key)) continue; - const modString = JSON.stringify(modEntry[key]); - const baseString = JSON.stringify(baseEntry[key]); - if (modString !== baseString) { - if (!overrideMoveInfo[id]) overrideMoveInfo[id] = {}; + if (modString === undefined) overrideMoveInfo[id][key] = null; overrideMoveInfo[id][key] = JSON.parse(modString); } } @@ -1236,13 +1056,21 @@ function buildTeambuilderTables() { } } - const overrideItemDesc = {}; - BattleTeambuilderTable[gen].overrideItemDesc = overrideItemDesc; + const overrideItemInfo = {}; + BattleTeambuilderTable[gen].overrideItemInfo = overrideItemInfo; for (const id in genData.Items) { const curEntry = genDex.items.get(id); const nextEntry = nextGenDex.items.get(id); - if ((curEntry.shortDesc || curEntry.desc) !== (nextEntry.shortDesc || nextEntry.desc)) { - overrideItemDesc[id] = (curEntry.shortDesc || curEntry.desc); + for (const key in curEntry) { + if (!overrideItemKeys.includes(key)) continue; + const curString = JSON.stringify(curEntry[key]); + const nextString = JSON.stringify(nextEntry[key]); + if (curString !== nextString) { + if (!overrideItemInfo[id]) overrideItemInfo[id] = {}; + if (curString === undefined) overrideItemInfo[id][key] = null; + else overrideItemInfo[id][key] = JSON.parse(curString); + if(key === 'desc' && !curEntry['shortDesc']) overrideItemInfo[id]['shortDesc'] = JSON.parse(curString); + } } } @@ -1318,14 +1146,30 @@ function buildTeambuilderTables() { format.unbans = []; if (!!format.banlist) { for (const name of format.banlist) { - const id = toID(name); + let id = toID(name); + if(name.endsWith('-Base')) id = id.substr(0, id.length - 4); if (id in Dex.data.Pokedex || id in modData.Pokedex) format.bans.push(id); + const formes = modData.Pokedex[id]?.otherFormes; + if(formes && toID(name) === id){ //id wasn't modified for base forme ban, therefore all formes are banned + for(const forme of formes){ + const formeid = toID(forme); + if (formeid in Dex.data.Pokedex || formeid in modData.Pokedex) format.bans.push(formeid); + } + } } if (!!format.banlist && format.banlist.includes("All Pokemon")) { format.bans.push('All Pokemon'); for (const name of format.unbanlist) { - const id = toID(name); + let id = toID(name); + if(name.endsWith('-Base')) id = id.substr(0, id.length - 4); if (id in Dex.data.Pokedex || id in modData.Pokedex) format.unbans.push(id); + const formes = modData.Pokedex[id]?.otherFormes; + if(formes && toID(name) === id){ //id wasn't modified for base forme ban, therefore all formes are banned + for(const forme of formes){ + const formeid = toID(forme); + if (formeid in Dex.data.Pokedex || formeid in modData.Pokedex) format.unbans.push(formeid); + } + } } } } @@ -1451,7 +1295,7 @@ function buildTeambuilderTables() { for (const moveid in learnset) { const newLearnsetEntry = getLearnsetStr(moveid, learnset[moveid], id, modConfigId); let baseLearnsetEntry = learnsets[id][moveid]; - if (modGen === '2' || modGen === '1') baseLearnsetEntry = G2Learnsets[id][moveid]; + if (modGen <= 2 && G2Learnsets[id]) baseLearnsetEntry = G2Learnsets[id][moveid]; const baseLsetNoEarlyGen = baseLearnsetEntry ? baseLearnsetEntry.replace(1, '').replace(2, '') : ''; if (newLearnsetEntry !== baseLearnsetEntry && newLearnsetEntry !== baseLsetNoEarlyGen) { if (!overrideLearnsets[id]) overrideLearnsets[id] = {}; @@ -1465,32 +1309,89 @@ function buildTeambuilderTables() { } } } - //items + //items let items = []; const fullItemName = {}; BattleTeambuilderTable[modConfigId].fullItemName = fullItemName; BattleTeambuilderTable[modConfigId].items = items; - const overrideItemDesc = {}; - BattleTeambuilderTable[modConfigId].overrideItemDesc = overrideItemDesc; - items.push(BattleTeambuilderTable.items[0]); + const overrideItemInfo = {}; + BattleTeambuilderTable[modConfigId].overrideItemInfo = overrideItemInfo; + + const greatItems = []; + const goodItems = []; + const specificItems = []; + const poorItems = []; + const badItems = []; for (const id in modData.Items) { const modEntry = modData.Items[id]; const baseEntry = Dex.data.Items[id]; - const fakeItem = (typeof baseEntry === 'undefined'); - if (fakeItem) { - items.push(id); + if (typeof baseEntry === 'undefined') { + overrideItemInfo[id] = {}; + overrideItemInfo[id] = modEntry; + overrideItemInfo[id].exists = true; fullItemName[id] = modData.Items[id].name; + } else { + if(baseEntry.gen > modGen) continue; + for (const key in modEntry) { + if (!overrideItemKeys.includes(key)) continue; + const modString = JSON.stringify(modEntry[key]); + const baseString = JSON.stringify(baseEntry[key]); + if (modString !== baseString) { + if (!overrideItemInfo[id]) overrideItemInfo[id] = {}; + overrideItemInfo[id][key] = JSON.parse(modString); + if(key === 'desc' && !modEntry['shortDesc']) overrideItemInfo[id]['shortDesc'] = JSON.parse(modString); + } + } + } + + if(modEntry.itemUser || modEntry.megaStone || id === 'boosterenergy'){ + specificItems.push(id); + continue; } - if (fakeItem || (modEntry.shortDesc || modEntry.desc) !== (baseEntry.shortDesc || baseEntry.desc)) { - overrideItemDesc[id] = (modEntry.shortDesc || modEntry.desc); + if(modEntry.isPokeball || modEntry.name.startsWith("TR")){ + badItems.push(id); + continue; + } + switch (modEntry.rating) { + case 3: + greatItems.push(id); + break; + case 2: + goodItems.push(id); + break; + // outclassed items + case 1: + poorItems.push(id); + break; + // Fling-only + case 0: + badItems.push(id); + break; + // Allows mods to manually set Pokemon-specific items + case -1: + specificItems.push(id); + break; + default: + goodItems.push(id); } } - let itemTable = BattleTeambuilderTable.items; - modGen === 9 ? itemTable = BattleTeambuilderTable.items : itemTable = BattleTeambuilderTable['gen' + modGen].items; - items.push(...itemTable.slice(1)); + greatItems.sort(); + greatItems.unshift(['header', "Popular items"]); + items.push(...greatItems); + goodItems.sort(); + goodItems.unshift(['header', "Items"]); + items.push(...goodItems); + specificItems.sort(); + specificItems.unshift(['header', "Pokémon-specific items"]); + items.push(...specificItems); + poorItems.sort(); + poorItems.unshift(['header', "Usually useless items"]); + items.push(...poorItems); + badItems.sort(); + badItems.unshift(['header', "Useless items"]); + items.push(...badItems); //moves const overrideMoveInfo = {}; - const overrideMoveKeys = ['basePower', 'accuracy', 'category', 'pp', 'isNonstandard', 'shortDesc', 'type', 'unviable', 'isViable']; BattleTeambuilderTable[modConfigId].overrideMoveInfo = overrideMoveInfo; for (const id in modData.Moves) { const modEntry = modData.Moves[id]; @@ -1521,6 +1422,15 @@ function buildTeambuilderTables() { } if (overrideMoveInfo[id] && moddedMoveIsOldGen) overrideMoveInfo[id].modMoveFromOldGen = true; } + for(const id in Dex.data.Moves){ //Weed out nulled moves + const modEntry = modData.Moves[id]; + if(!modEntry){ + overrideMoveInfo[id] = {}; + overrideMoveInfo[id].isNonstandard = "Unobtainable"; + overrideMoveInfo[id].exists = false; + continue; + } + } //abilities const fullAbilityName = {}; BattleTeambuilderTable[modConfigId].fullAbilityName = fullAbilityName; diff --git a/src/battle-dex-data.ts b/src/battle-dex-data.ts index 26efc385e..70d2c8cc1 100644 --- a/src/battle-dex-data.ts +++ b/src/battle-dex-data.ts @@ -1046,6 +1046,7 @@ class Item implements Effect { readonly id: ID; readonly name: string; readonly gen: number; + readonly isNonstandard: string; readonly exists: boolean; readonly num: number; @@ -1073,6 +1074,7 @@ class Item implements Effect { this.name = Dex.sanitizeName(name); this.id = id; this.gen = data.gen || 0; + this.isNonstandard = data.isNonstandard || undefined; this.exists = ('exists' in data ? !!data.exists : true); this.num = data.num || 0; diff --git a/src/battle-dex-search.ts b/src/battle-dex-search.ts index 894d40673..d71dc2fec 100644 --- a/src/battle-dex-search.ts +++ b/src/battle-dex-search.ts @@ -415,7 +415,7 @@ class DexSearch { ) continue; else if ( typeIndex === 5 && (!BattleItems[id] || BattleItems[id].exists === false) && - (!table || !table.overrideItemDesc || id in table.overrideItemDesc === false) + (!table || !table.overrideItemInfo || id in table.overrideItemInfo === false) ) continue; else if ( typeIndex === 4 && (!BattleMovedex[id] || BattleMovedex[id].exists === false) && @@ -814,8 +814,6 @@ abstract class BattleTypedSearch { } protected firstLearnsetid(speciesid: ID) { let table = BattleTeambuilderTable; - let learnsets = table.learnsets; - if (speciesid in learnsets) return speciesid; if (this.formatType?.startsWith('bdsp')) table = table['gen8bdsp']; if (this.formatType === 'letsgo') table = table['gen7letsgo']; if (speciesid in table.learnsets) return speciesid; @@ -878,13 +876,18 @@ abstract class BattleTypedSearch { let learnset = table.learnsets[learnsetid]; if (this.mod) { const overrideLearnsets = BattleTeambuilderTable[this.mod].overrideLearnsets; - if (overrideLearnsets[learnsetid] && overrideLearnsets[learnsetid][moveid]) learnset = overrideLearnsets[learnsetid]; + if (overrideLearnsets[learnsetid]) { + if(!learnset) learnset = overrideLearnsets[learnsetid]; //Didn't have learnset and mod gave it one + else { + for (const learnedMove in overrideLearnsets[learnsetid]) learnset[learnedMove] = overrideLearnsets[learnsetid][learnedMove]; + } + } } // Modified this function to account for pet mods with tradebacks enabled const tradebacksMod = ['gen1expansionpack', 'gen1burgundy']; - if (learnset && (moveid in learnset) && (!this.format.startsWith('tradebacks') || !(tradebacksMod.includes(this.mod)) ? learnset[moveid].includes(genChar) : - learnset[moveid].includes(genChar) || - (learnset[moveid].includes(`${gen + 1}`) && move.gen === gen))) { + if (learnset && (moveid in learnset) && (!(this.format.startsWith('tradebacks') || tradebacksMod.includes(this.mod)) ? learnset[moveid].includes(genChar) : + (learnset[moveid].includes(genChar) || + (learnset[moveid].includes(`${gen + 1}`) && move.gen === gen)))) { return true; } learnsetid = this.nextLearnsetid(learnsetid, speciesid); @@ -1322,7 +1325,7 @@ class BattleAbilitySearch extends BattleTypedSearch<'ability'> { class BattleItemSearch extends BattleTypedSearch<'item'> { getTable() { if (!this.mod) return BattleItems; - else return {...BattleTeambuilderTable[this.mod].fullItemName, ...BattleItems}; + else return {...BattleTeambuilderTable[this.mod].overrideItemInfo, ...BattleItems}; } getDefaultResults(): SearchRow[] { let table = BattleTeambuilderTable; @@ -1349,19 +1352,30 @@ class BattleItemSearch extends BattleTypedSearch<'item'> { return table.itemSet; } getBaseResults(): SearchRow[] { - if (!this.species) return this.getDefaultResults(); - const speciesName = this.dex.species.get(this.species).name; const results = this.getDefaultResults(); + const species = this.dex.species.get(this.species); const speciesSpecific: SearchRow[] = []; - for (const row of results) { + for (let i = results.length - 1; i > 0; i--) { + const row = results[i]; if (row[0] !== 'item') continue; - if (this.dex.items.get(row[1]).itemUser?.includes(speciesName)) { + const id = row[1]; + let item = this.dex.items.get(id); + if (!item.exists || item.isNonstandard){ + if(item.isNonstandard != "Past" || this.formatType != "natdex"){ + results.splice(i, 1); + continue; + } + } + if (item.itemUser?.includes(species.name)) { + speciesSpecific.push(row); + } + if(id === 'boosterenergy' && species.tags?.includes('Paradox')) { speciesSpecific.push(row); } } if (speciesSpecific.length) { return [ - ['header', "Specific to " + speciesName], + ['header', "Specific to " + species.name], ...speciesSpecific, ...results, ]; @@ -1395,7 +1409,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { getDefaultResults(): SearchRow[] { let results: SearchRow[] = []; results.push(['header', "Moves"]); - for (let id in BattleMovedex) { + for (let id in this.getTable()) { switch (id) { case 'paleowave': results.push(['header', "CAP moves"]); @@ -1412,6 +1426,12 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { let abilityid: ID = set ? toID(set.ability) : '' as ID; const itemid: ID = set ? toID(set.item) : '' as ID; + + // Check if mod declared forced viability + if (this.mod && id in BattleTeambuilderTable[this.mod].overrideMoveInfo) { + if(BattleTeambuilderTable[this.mod].overrideMoveInfo[id].viable === true) return true; + if(BattleTeambuilderTable[this.mod].overrideMoveInfo[id].viable === false) return false; + } if (dex.gen === 1) { // Usually not useless for Gen 1 @@ -1479,7 +1499,9 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { if (itemid === 'pidgeotite') abilityid = 'noguard' as ID; if (itemid === 'blastoisinite') abilityid = 'megalauncher' as ID; - if (itemid === 'aerodactylite') abilityid = 'toughclaws' as ID; + if (itemid === 'heracronite') abilityid = 'skilllink' as ID; + if (itemid === 'cameruptite') abilityid = 'sheerforce' as ID; + if (itemid === 'aerodactylite' || itemid === 'charizardmegax') abilityid = 'toughclaws' as ID; if (itemid === 'glalitite') abilityid = 'refrigerate' as ID; switch (id) { @@ -1526,17 +1548,15 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { case 'hex': return !moves.includes('infernalparade'); case 'hiddenpowerelectric': - return (dex.gen < 4 && !moves.includes('thunderpunch')) && !moves.includes('thunderbolt'); + return !(moves.includes('thunderbolt') || (dex.gen < 4 && moves.includes('thunderpunch'))); case 'hiddenpowerfighting': - return (dex.gen < 4 && !moves.includes('brickbreak')) && !moves.includes('aurasphere') && !moves.includes('focusblast'); + return !(moves.includes('aurasphere') || moves.includes('focusblast') || (dex.gen < 4 && moves.includes('brickbreak'))); case 'hiddenpowerfire': - return (dex.gen < 4 && !moves.includes('firepunch')) && !moves.includes('flamethrower') && - !moves.includes('mysticalfire') && !moves.includes('burningjealousy'); + return !(moves.includes('flamethrower') || moves.includes('mysticalfire') || (dex.gen < 4 && moves.includes('firepunch'))); case 'hiddenpowergrass': - return !moves.includes('energyball') && !moves.includes('grassknot') && !moves.includes('gigadrain'); + return !(moves.includes('energyball') || moves.includes('grassknot') || moves.includes('gigadrain')); case 'hiddenpowerice': - return !moves.includes('icebeam') && (dex.gen < 4 && !moves.includes('icepunch')) || - (dex.gen > 5 && !moves.includes('aurorabeam') && !moves.includes('glaciate')); + return !(moves.includes('icebeam') || (dex.gen > 5 && (moves.includes('aurorabeam') || moves.includes('glaciate'))) || (dex.gen < 4 && moves.includes('icepunch'))); case 'hiddenpowerflying': return dex.gen < 4 && !moves.includes('drillpeck'); case 'hiddenpowerbug': @@ -1577,7 +1597,7 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { case 'petaldance': return abilityid === 'owntempo'; case 'phantomforce': - return (!moves.includes('poltergeist') && !moves.includes('shadowclaw')) || this.formatType === 'doubles'; + return !(moves.includes('shadowforce') || moves.includes('poltergeist') || moves.includes('shadowclaw')) || this.formatType === 'doubles'; case 'poisonfang': return species.types.includes('Poison') && !moves.includes('gunkshot') && !moves.includes('poisonjab'); case 'relicsong': @@ -1621,26 +1641,11 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { if (this.formatType === 'doubles' && BattleMoveSearch.GOOD_DOUBLES_MOVES.includes(id)) { return true; } - // Custom move added by a mod - if (this.mod && id in BattleTeambuilderTable[this.mod].overrideMoveInfo - && !BattleTeambuilderTable[this.mod].overrideMoveInfo[id].unviable - && !BattleTeambuilderTable[this.mod].overrideMoveInfo[id].modMoveFromOldGen - ) return true; - const modMoveData = BattleMovedex[id]; - if (!modMoveData) return true; - if (modMoveData.category === 'Status') { - return BattleMoveSearch.GOOD_STATUS_MOVES.includes(id); - } const moveData = BattleMovedex[id]; if (!moveData) return true; if (moveData.category === 'Status') { return BattleMoveSearch.GOOD_STATUS_MOVES.includes(id); } - if (moveData.basePower < 75) { - return BattleMoveSearch.GOOD_WEAK_MOVES.includes(id); - } - if (id === 'skydrop') return true; - // strong moves if (moveData.flags?.charge) { return itemid === 'powerherb'; } @@ -1650,13 +1655,16 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { if (moveData.flags?.slicing && abilityid === 'sharpness') { return true; } + if (moveData.basePower < 75) { + return BattleMoveSearch.GOOD_WEAK_MOVES.includes(id); + } return !BattleMoveSearch.BAD_STRONG_MOVES.includes(id); } static readonly GOOD_STATUS_MOVES = [ 'acidarmor', 'agility', 'aromatherapy', 'auroraveil', 'autotomize', 'banefulbunker', 'batonpass', 'bellydrum', 'bulkup', 'calmmind', 'chillyreception', 'clangoroussoul', 'coil', 'cottonguard', 'courtchange', 'curse', 'defog', 'destinybond', 'detect', 'disable', 'dragondance', 'encore', 'extremeevoboost', 'filletaway', 'geomancy', 'glare', 'haze', 'healbell', 'healingwish', 'healorder', 'heartswap', 'honeclaws', 'kingsshield', 'leechseed', 'lightscreen', 'lovelykiss', 'lunardance', 'magiccoat', 'maxguard', 'memento', 'milkdrink', 'moonlight', 'morningsun', 'nastyplot', 'naturesmadness', 'noretreat', 'obstruct', 'painsplit', 'partingshot', 'perishsong', 'protect', 'quiverdance', 'recover', 'reflect', 'reflecttype', 'rest', 'revivalblessing', 'roar', 'rockpolish', 'roost', 'shedtail', 'shellsmash', 'shiftgear', 'shoreup', 'silktrap', 'slackoff', 'sleeppowder', 'sleeptalk', 'softboiled', 'spikes', 'spikyshield', 'spore', 'stealthrock', 'stickyweb', 'strengthsap', 'substitute', 'switcheroo', 'swordsdance', 'synthesis', 'tailglow', 'tailwind', 'taunt', 'thunderwave', 'tidyup', 'toxic', 'transform', 'trick', 'victorydance', 'whirlwind', 'willowisp', 'wish', 'yawn', ] as ID[] as readonly ID[]; static readonly GOOD_WEAK_MOVES = [ - 'accelerock', 'acrobatics', 'aquacutter', 'avalanche', 'barbbarrage', 'bonemerang', 'bouncybubble', 'bulletpunch', 'buzzybuzz', 'ceaselessedge', 'circlethrow', 'clearsmog', 'doubleironbash', 'dragondarts', 'dragontail', 'drainingkiss', 'endeavor', 'facade', 'firefang', 'flipturn', 'flowertrick', 'freezedry', 'frustration', 'geargrind', 'grassknot', 'gyroball', 'icefang', 'iceshard', 'iciclespear', 'infernalparade', 'knockoff', 'lastrespects', 'lowkick', 'machpunch', 'mortalspin', 'mysticalpower', 'naturesmadness', 'nightshade', 'nuzzle', 'pikapapow', 'populationbomb', 'psychocut', 'psyshieldbash', 'pursuit', 'quickattack', 'ragefist', 'rapidspin', 'return', 'rockblast', 'ruination', 'saltcure', 'scorchingsands', 'seismictoss', 'shadowclaw', 'shadowsneak', 'sizzlyslide', 'stoneaxe', 'storedpower', 'stormthrow', 'suckerpunch', 'superfang', 'surgingstrikes', 'tailslap', 'trailblaze', 'tripleaxel', 'tripledive', 'twinbeam', 'uturn', 'veeveevolley', 'voltswitch', 'watershuriken', 'weatherball', + 'accelerock', 'acrobatics', 'aquacutter', 'avalanche', 'barbbarrage', 'bonemerang', 'bouncybubble', 'bulletpunch', 'buzzybuzz', 'ceaselessedge', 'circlethrow', 'clearsmog', 'doubleironbash', 'dragondarts', 'dragontail', 'drainingkiss', 'endeavor', 'facade', 'firefang', 'flipturn', 'flowertrick', 'freezedry', 'frustration', 'geargrind', 'grassknot', 'gyroball', 'icefang', 'iceshard', 'iciclespear', 'infernalparade', 'jetpunch', 'knockoff', 'lastrespects', 'lowkick', 'machpunch', 'mortalspin', 'mysticalpower', 'naturesmadness', 'nightshade', 'nuzzle', 'pikapapow', 'populationbomb', 'psychocut', 'psyshieldbash', 'pursuit', 'quickattack', 'ragefist', 'rapidspin', 'return', 'rockblast', 'ruination', 'saltcure', 'scorchingsands', 'seismictoss', 'shadowclaw', 'shadowsneak', 'sizzlyslide', 'skydrop', 'stoneaxe', 'storedpower', 'stormthrow', 'suckerpunch', 'superfang', 'surgingstrikes', 'tailslap', 'trailblaze', 'tripleaxel', 'tripledive', 'twinbeam', 'uturn', 'veeveevolley', 'voltswitch', 'watershuriken', 'weatherball', ] as ID[] as readonly ID[]; static readonly BAD_STRONG_MOVES = [ 'belch', 'burnup', 'crushclaw', 'dragonrush', 'dreameater', 'eggbomb', 'firepledge', 'flyingpress', 'grasspledge', 'hyperbeam', 'hyperfang', 'hyperspacehole', 'jawlock', 'landswrath', 'megakick', 'megapunch', 'mistyexplosion', 'muddywater', 'nightdaze', 'pollenpuff', 'rockclimb', 'selfdestruct', 'shelltrap', 'skyuppercut', 'slam', 'strength', 'submission', 'synchronoise', 'takedown', 'thrash', 'uproar', 'waterpledge', @@ -1676,7 +1684,20 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { /^battle(spot|stadium|festival)/.test(format) || format.startsWith('vgc') || (dex.gen === 9 && this.formatType !== 'natdex'); // Hoenn Gaiden Baton Pass Gaiden Declaration - const isHoennGaiden = this.modFormat === 'gen3hoenngaiden' || this.modFormat.endsWith('hoenngaiden'); + const isHoennGaiden = this.modFormat.endsWith('hoenngaiden'); + + let hasOwnUsefulCheck = false; + switch(typeof window.ModConfig[this.mod]?.moveIsNotUseless){ + case 'string': + hasOwnUsefulCheck = true; + const usefulCheck = JSON.parse(window.ModConfig[this.mod].moveIsNotUseless); + const checkParameters = usefulCheck.substring(usefulCheck.indexOf('(')+1,usefulCheck.indexOf(')')).split(','); + window.ModConfig[this.mod].moveIsNotUseless = new Function(...checkParameters, usefulCheck.substring(usefulCheck.indexOf('{'))); + break; + case 'function': + hasOwnUsefulCheck = true; + break; + } let learnsetid = this.firstLearnsetid(species.id); let moves: string[] = []; @@ -1691,10 +1712,12 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { while (learnsetid) { let learnset = lsetTable.learnsets[learnsetid]; if (this.mod) { - learnset = JSON.parse(JSON.stringify(learnset)); const overrideLearnsets = BattleTeambuilderTable[this.mod].overrideLearnsets; if (overrideLearnsets[learnsetid]) { - for (const moveid in overrideLearnsets[learnsetid]) learnset[moveid] = overrideLearnsets[learnsetid][moveid]; + if(!learnset) learnset = overrideLearnsets[learnsetid]; //Didn't have learnset and mod gave it one + else { + for (const moveid in overrideLearnsets[learnsetid]) learnset[moveid] = overrideLearnsets[learnsetid][moveid]; + } } } if (learnset) { @@ -1744,22 +1767,22 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { } if (sketch || isHackmons) { if (isHackmons) moves = []; - for (let id in BattleMovedex) { + for (let id in this.getTable()) { if (!format.startsWith('cap') && (id === 'paleowave' || id === 'shadowstrike')) continue; - const move = dex.moves.get(id); - if (move.gen > dex.gen) continue; + let move = dex.moves.get(id); + if (!move.exists || moves.includes(id) || move.gen > dex.gen) continue; if (sketch) { if (move.noSketch || move.isMax || move.isZ) continue; if (move.isNonstandard && move.isNonstandard !== 'Past') continue; if (move.isNonstandard === 'Past' && this.formatType !== 'natdex') continue; - sketchMoves.push(move.id); + sketchMoves.push(id); } else { if (!(dex.gen < 8 || this.formatType === 'natdex') && move.isZ) continue; if (typeof move.isMax === 'string') continue; if (move.isMax && dex.gen > 8) continue; if (move.isNonstandard === 'Past' && this.formatType !== 'natdex') continue; if (move.isNonstandard === 'LGPE' && this.formatType !== 'letsgo') continue; - moves.push(move.id); + moves.push(id); } } } @@ -1817,7 +1840,11 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { let usableMoves: SearchRow[] = []; let uselessMoves: SearchRow[] = []; for (const id of moves) { - const isUsable = this.moveIsNotUseless(id as ID, species, moves, this.set); + let isUsable = this.moveIsNotUseless(id as ID, species, moves, this.set); + if(hasOwnUsefulCheck){ + const modIsUsable = window.ModConfig[this.mod].moveIsNotUseless.apply(window.ModConfig[this.mod], [id as ID, species, moves, this.set]); + if(typeof modIsUsable === 'boolean' && modIsUsable !== isUsable) isUsable = modIsUsable; + } if (isUsable) { if (!usableMoves.length) usableMoves.push(['header', "Moves"]); usableMoves.push(['move', id as ID]); @@ -1831,7 +1858,11 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { uselessMoves.push(['header', "Useless sketched moves"]); } for (const id of sketchMoves) { - const isUsable = this.moveIsNotUseless(id as ID, species, sketchMoves, this.set); + let isUsable = this.moveIsNotUseless(id as ID, species, sketchMoves, this.set); + if(hasOwnUsefulCheck){ + const modIsUsable = window.ModConfig[this.mod].moveIsNotUseless.apply(window.ModConfig[this.mod], [id as ID, species, moves, this.set]); + if(typeof modIsUsable === 'boolean' && modIsUsable !== isUsable) isUsable = modIsUsable; + } if (isUsable) { usableMoves.push(['move', id as ID]); } else { @@ -1872,30 +1903,24 @@ class BattleMoveSearch extends BattleTypedSearch<'move'> { fissure: 1500, horndrill: 1500, guillotine: 1500, }; return results.sort(([rowType1, id1], [rowType2, id2]) => { - const modPow1 = this.mod ? BattleTeambuilderTable[this.mod].overrideBP[id1] : null; - const modPow2 = this.mod ? BattleTeambuilderTable[this.mod].overrideBP[id2] : null; let move1 = this.dex.moves.get(id1); let move2 = this.dex.moves.get(id2); - let pow1 = modPow1 || move1.basePower || powerTable[id1] || (move1.category === 'Status' ? -1 : 1400); - let pow2 = modPow2 || move2.basePower || powerTable[id2] || (move2.category === 'Status' ? -1 : 1400); + let pow1 = move1.basePower || powerTable[id1] || (move1.category === 'Status' ? -1 : 1400); + let pow2 = move2.basePower || powerTable[id2] || (move2.category === 'Status' ? -1 : 1400); return (pow2 - pow1) * sortOrder; }); case 'accuracy': return results.sort(([rowType1, id1], [rowType2, id2]) => { - const modAcc1 = this.mod ? BattleTeambuilderTable[this.mod].overrideAcc[id1] : null; - const modAcc2 = this.mod ? BattleTeambuilderTable[this.mod].overrideAcc[id2] : null; - let accuracy1 = modAcc1 || BattleMovedex[id1].accuracy || 0; - let accuracy2 = modAcc2 || BattleMovedex[id2].accuracy || 0; + let accuracy1 = this.dex.moves.get(id1).accuracy || 0; + let accuracy2 = this.dex.moves.get(id2).accuracy || 0; if (accuracy1 === true) accuracy1 = 101; if (accuracy2 === true) accuracy2 = 101; return (accuracy2 - accuracy1) * sortOrder; }); case 'pp': return results.sort(([rowType1, id1], [rowType2, id2]) => { - const modPP1 = this.mod ? BattleTeambuilderTable[this.mod].overridePP[id1] : null; - const modPP2 = this.mod ? BattleTeambuilderTable[this.mod].overridePP[id2] : null; - let pp1 = modPP1 || BattleMovedex[id1].pp || 0; - let pp2 = modPP2 || BattleMovedex[id2].pp || 0; + let pp1 = this.dex.moves.get(id1).pp || 0; + let pp2 = this.dex.moves.get(id2).pp || 0; return (pp2 - pp1) * sortOrder; }); case 'name': diff --git a/src/battle-dex.ts b/src/battle-dex.ts index 78fc68282..466c4ff43 100644 --- a/src/battle-dex.ts +++ b/src/battle-dex.ts @@ -306,8 +306,8 @@ const Dex = new class implements ModdedDex { basePower: Number(id.slice(11)), }; } - if (!data) data = {exists: false}; + let move = new Move(id, name, data); window.BattleMovedex[id] = move; return move; @@ -974,15 +974,15 @@ class ModdedDex { data.name = table.fullItemName[id]; data.exists = true; } - if (id in table.overrideItemDesc) data.shortDesc = table.overrideItemDesc[id]; - - for (let i = this.gen; i < 9; i++) { - const table = window.BattleTeambuilderTable['gen' + i]; - if (table.overrideItemDesc && id in table.overrideItemDesc) { - data.shortDesc = table.overrideItemDesc[id]; - break; + for(let i = 9; i > this.gen; i--) { + const genTable = window.BattleTeambuilderTable['gen' + (i-1)]; + if (genTable.overrideItemInfo[id]) { + data = {...Dex.items.get(name), ...genTable.overrideItemInfo[id]}; } } + if (this.modid && table.overrideItemInfo[id]) { + data = {...Dex.items.get(name), ...table.overrideItemInfo[id]}; + } const item = new Item(id, name, data); this.cache.Items[id] = item;