From 8f5603e96eb6dd26bc09c42f5c70eda29dc04855 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 16 Jan 2025 22:26:22 -0800 Subject: [PATCH 01/11] star piece initial impl --- src/config/backpackConfig.js | 9 ++++++++- src/config/npcConfig.js | 5 +++++ src/embeds/battleEmbeds.js | 12 ++++++++++-- src/services/raid.js | 9 ++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/config/backpackConfig.js b/src/config/backpackConfig.js index 50a6adbb..e6c0962c 100644 --- a/src/config/backpackConfig.js +++ b/src/config/backpackConfig.js @@ -16,6 +16,7 @@ const backpackItems = Object.freeze({ WILLPOWER_SHARD: "6", MINT: "7", RAID_PASS: "8", + STAR_PIECE: "9", }); const backpackCategoryConfig = Object.freeze({ @@ -28,7 +29,7 @@ const backpackCategoryConfig = Object.freeze({ [backpackCategories.MATERIALS]: { name: "Materials", emoji: "<:materials:1112557472759160852>", - description: "Used to upgrade Pokemon and equipment!", + description: "Used to upgrade Pokemon, craft items, and as currency!", }, [backpackCategories.CONSUMABLES]: { name: "Consumables", @@ -90,6 +91,12 @@ const backpackItemConfig = Object.freeze({ description: "Used to change a Pokemon's nature!", category: backpackCategories.MATERIALS, }, + [backpackItems.STAR_PIECE]: { + name: "Star Piece", + emoji: "<:starpiece:1329630164526829709>", + description: "Obtained from raids; used to make wishes!", + category: backpackCategories.MATERIALS, + }, [backpackItems.RAID_PASS]: { name: "Raid Pass", emoji: "<:raidpass:1150161526297206824>", diff --git a/src/config/npcConfig.js b/src/config/npcConfig.js index c02c596e..ffdff577 100644 --- a/src/config/npcConfig.js +++ b/src/config/npcConfig.js @@ -2649,6 +2649,7 @@ const raids = Object.freeze({ const RAID_SHINY_CHANCE = 0.0033; // process.env.STAGE === stageNames.ALPHA ? 0.8 : 0.0033; const BASE_RAID_MONEY = 500; +const BASE_STAR_PIECE = 1; const raidConfig = Object.freeze({ [raids.ARMORED_MEWTWO]: { @@ -2698,6 +2699,7 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE, moneyPerPercent: BASE_RAID_MONEY, + starPiecePerPercent: BASE_STAR_PIECE, ttl: 1000 * 60 * 60 * 2, }, }, @@ -2749,6 +2751,7 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE, moneyPerPercent: BASE_RAID_MONEY, + starPiecePerPercent: BASE_STAR_PIECE, ttl: 1000 * 60 * 60 * 2, }, }, @@ -2805,6 +2808,7 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE * 2, moneyPerPercent: BASE_RAID_MONEY * 1.25 * 2, + starPiecePerPercent: BASE_STAR_PIECE * 1.25 * 2, ttl: 1000 * 60 * 60 * 2, }, }, @@ -2862,6 +2866,7 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE * 2, moneyPerPercent: BASE_RAID_MONEY * 1.25 * 2, + starPiecePerPercent: BASE_STAR_PIECE * 1.25 * 2, ttl: 1000 * 60 * 60 * 2, }, }, diff --git a/src/embeds/battleEmbeds.js b/src/embeds/battleEmbeds.js index 5fb6a413..c40b2b18 100644 --- a/src/embeds/battleEmbeds.js +++ b/src/embeds/battleEmbeds.js @@ -40,7 +40,9 @@ const { getFullUsername, getRewardsString, flattenRewards, + flattenCategories, } = require("../utils/trainerUtils"); +const { backpackItemConfig } = require("../config/backpackConfig"); /** * Handles building the party embedded instructions for building a party. @@ -693,13 +695,19 @@ const buildRaidWinEmbed = (raid, rewards) => { const participantRewards = rewards[participantId]; if (!participantRewards) continue; const { money, backpack, shiny } = participantRewards; + const backpackRewards = flattenCategories(backpack); const rewardsForTrainer = []; if (money) { rewardsForTrainer.push(formatMoney(money)); } - if (backpack) { - // TODO + if (Object.keys(backpackRewards)) { + let backpackString = ""; + for (const itemId in backpackRewards) { + const itemData = backpackItemConfig[itemId]; + backpackString += `${itemData.emoji} x${backpackRewards[itemId]} `; + } + rewardsForTrainer.push(backpackString); } if (shiny) { const shinyData = pokemonConfig[shiny]; diff --git a/src/services/raid.js b/src/services/raid.js index e9e94802..7f831fca 100644 --- a/src/services/raid.js +++ b/src/services/raid.js @@ -6,6 +6,7 @@ const { buildIdConfigSelectRow } = require("../components/idConfigSelectRow"); const { backpackItems, backpackItemConfig, + backpackCategories, } = require("../config/backpackConfig"); const { collectionNames } = require("../config/databaseConfig"); const { eventNames } = require("../config/eventConfig"); @@ -237,7 +238,13 @@ const onRaidWin = async (raid) => { const rewardsForTrainer = { money: Math.floor(difficultyData.moneyPerPercent * percentDamage * 100), - backpack: {}, // TODO + backpack: { + [backpackCategories.MATERIALS]: { + [backpackItems.STAR_PIECE]: Math.floor( + difficultyData.starPiecePerPercent * percentDamage * 100 + ), + }, + }, }; addRewards(trainer.data, rewardsForTrainer); rewards[userId] = rewardsForTrainer; From 8ab9e72c7978c01cceb50b3e23ec7f369dd98736 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 16 Jan 2025 22:51:28 -0800 Subject: [PATCH 02/11] finish star piece impl --- src/config/npcConfig.js | 55 +++++++++++++++++++++++++++++++++++----- src/config/types.js | 1 + src/services/raid.js | 29 +++++++++++++-------- src/utils/battleUtils.js | 19 ++++++++++++-- 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/src/config/npcConfig.js b/src/config/npcConfig.js index ffdff577..4d259455 100644 --- a/src/config/npcConfig.js +++ b/src/config/npcConfig.js @@ -2651,7 +2651,33 @@ const RAID_SHINY_CHANCE = 0.0033; // process.env.STAGE === stageNames.ALPHA ? 0. const BASE_RAID_MONEY = 500; const BASE_STAR_PIECE = 1; -const raidConfig = Object.freeze({ +/** + * @typedef {object} RaidConfigData + * @property {string} name + * @property {string} sprite + * @property {string} emoji + * @property {string} description + * @property {string} boss + * @property {string[]} shinyRewards + * @property {PartialRecord>; + * ttl: number; + * }>} difficulties + */ + +/** + * @type {Record} + */ +const raidConfigRaw = { [raids.ARMORED_MEWTWO]: { name: "Armored Mewtwo", sprite: @@ -2699,7 +2725,11 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE, moneyPerPercent: BASE_RAID_MONEY, - starPiecePerPercent: BASE_STAR_PIECE, + backpackPerPercent: { + [backpackCategories.MATERIALS]: { + [backpackItems.STAR_PIECE]: BASE_STAR_PIECE, + }, + }, ttl: 1000 * 60 * 60 * 2, }, }, @@ -2751,7 +2781,11 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE, moneyPerPercent: BASE_RAID_MONEY, - starPiecePerPercent: BASE_STAR_PIECE, + backpackPerPercent: { + [backpackCategories.MATERIALS]: { + [backpackItems.STAR_PIECE]: BASE_STAR_PIECE, + }, + }, ttl: 1000 * 60 * 60 * 2, }, }, @@ -2808,7 +2842,11 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE * 2, moneyPerPercent: BASE_RAID_MONEY * 1.25 * 2, - starPiecePerPercent: BASE_STAR_PIECE * 1.25 * 2, + backpackPerPercent: { + [backpackCategories.MATERIALS]: { + [backpackItems.STAR_PIECE]: BASE_STAR_PIECE, + }, + }, ttl: 1000 * 60 * 60 * 2, }, }, @@ -2866,12 +2904,17 @@ const raidConfig = Object.freeze({ ], shinyChance: RAID_SHINY_CHANCE * 2, moneyPerPercent: BASE_RAID_MONEY * 1.25 * 2, - starPiecePerPercent: BASE_STAR_PIECE * 1.25 * 2, + backpackPerPercent: { + [backpackCategories.MATERIALS]: { + [backpackItems.STAR_PIECE]: BASE_STAR_PIECE * 1.25 * 2, + }, + }, ttl: 1000 * 60 * 60 * 2, }, }, }, -}); +}; +const raidConfig = Object.freeze(raidConfigRaw); const difficultyConfig = Object.freeze({ [difficulties.VERY_EASY]: { diff --git a/src/config/types.js b/src/config/types.js index 2ccd3750..6acc843e 100644 --- a/src/config/types.js +++ b/src/config/types.js @@ -11,6 +11,7 @@ /** @typedef {import("./npcConfig").NpcEnum} NpcEnum */ /** @typedef {import("./npcConfig").DungeonEnum} DungeonEnum */ /** @typedef {import("./npcConfig").RaidEnum} RaidEnum */ +/** @typedef {import("./npcConfig").RaidConfigData} RaidConfigData */ /** @typedef {import("./npcConfig").NpcDifficultyEnum} NpcDifficultyEnum */ /** @typedef {import("./backpackConfig").BackpackItemEnum} BackpackItemEnum */ /** @typedef {import("./backpackConfig").BackpackCategoryEnum} BackpackCategoryEnum */ diff --git a/src/services/raid.js b/src/services/raid.js index 7f831fca..bf8440ab 100644 --- a/src/services/raid.js +++ b/src/services/raid.js @@ -211,11 +211,11 @@ const onRaidStart = async ({ stateId = null, user = null } = {}) => { const onRaidWin = async (raid) => { // ensure raid is valid const { raidId } = raid; - const raidData = raidConfig[raidId]; + const raidData = /** @type {RaidConfigData?} */ (raidConfig[raidId]); if (!raidData) { return; } - const { difficulty } = raid; + const { difficulty } = /** @type {{difficulty: NpcDifficultyEnum}} */ (raid); const difficultyData = raidData.difficulties[difficulty]; if (!difficultyData) { return; @@ -236,15 +236,24 @@ const onRaidWin = async (raid) => { } const percentDamage = damage / raid.boss.stats[0]; + const backpack = {}; + for (const [backpackCategory, items] of Object.entries( + difficultyData.backpackPerPercent + )) { + backpack[backpackCategory] = {}; + for (const [itemId, count] of Object.entries(items)) { + backpack[backpackCategory][itemId] = Math.max( + Math.floor(count * percentDamage * 100), + 1 + ); + } + } const rewardsForTrainer = { - money: Math.floor(difficultyData.moneyPerPercent * percentDamage * 100), - backpack: { - [backpackCategories.MATERIALS]: { - [backpackItems.STAR_PIECE]: Math.floor( - difficultyData.starPiecePerPercent * percentDamage * 100 - ), - }, - }, + money: Math.max( + Math.floor(difficultyData.moneyPerPercent * percentDamage * 100), + 1 + ), + backpack, }; addRewards(trainer.data, rewardsForTrainer); rewards[userId] = rewardsForTrainer; diff --git a/src/utils/battleUtils.js b/src/utils/battleUtils.js index 6d81b0f3..861b1036 100644 --- a/src/utils/battleUtils.js +++ b/src/utils/battleUtils.js @@ -7,9 +7,14 @@ const { statusConditions, targetPatterns } = require("../config/battleConfig"); const { difficultyConfig } = require("../config/npcConfig"); const { pokemonConfig, typeConfig } = require("../config/pokemonConfig"); -const { getRewardsString, flattenRewards } = require("./trainerUtils"); +const { + getRewardsString, + flattenRewards, + flattenCategories, +} = require("./trainerUtils"); const { getPBar, formatMoney } = require("./utils"); const { getEffect } = require("../battle/data/effectRegistry"); +const { backpackItemConfig } = require("../config/backpackConfig"); const plus = "┼"; const plusEmph = "*"; @@ -534,7 +539,17 @@ const buildRaidDifficultyString = (difficulty, raidDifficultyData) => { difficultyString += `**Shiny Chance:** ${shinyChance}% • **Money/%:** ${formatMoney( raidDifficultyData.moneyPerPercent - )} • **Time:** ${raidDifficultyData.ttl / (1000 * 60 * 60)} hours`; + )} • **Time:** ${raidDifficultyData.ttl / (1000 * 60 * 60)} hours\n`; + + if (raidDifficultyData.backpackPerPercent) { + difficultyString += "**Items/%: ** "; + for (const [item, itemPerPercent] of Object.entries( + flattenCategories(raidDifficultyData.backpackPerPercent) + )) { + const itemData = backpackItemConfig[item]; + difficultyString += `${itemData.emoji} x${itemPerPercent} `; + } + } return { difficultyHeader, From 474119ba5f9b517c34b48cb12b43031f0847d089 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 16 Jan 2025 22:53:33 -0800 Subject: [PATCH 03/11] improve typing --- src/utils/battleUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/battleUtils.js b/src/utils/battleUtils.js index 861b1036..2cd14ff7 100644 --- a/src/utils/battleUtils.js +++ b/src/utils/battleUtils.js @@ -515,7 +515,7 @@ const buildDungeonDifficultyString = (difficulty, dungeonDifficultyData) => { /** * @param {NpcDifficultyEnum} difficulty - * @param {any} raidDifficultyData + * @param {RaidConfigData["difficulties"][NpcDifficultyEnum]} raidDifficultyData * @returns {{difficultyHeader: string, difficultyString: string}} */ const buildRaidDifficultyString = (difficulty, raidDifficultyData) => { From 09cd4caccb04b68f2fe993e3d0a9b96322c794dc Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Fri, 17 Jan 2025 18:03:06 -0800 Subject: [PATCH 04/11] new move effect impl --- src/battle/data/abilities.js | 11 ++++ src/battle/data/moves.js | 97 ++++++++++++++++++++++++++++++ src/battle/engine/BattlePokemon.js | 4 ++ src/config/battleConfig.js | 14 ----- src/enums/battleEnums.js | 2 + 5 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/battle/data/abilities.js b/src/battle/data/abilities.js index b3e86199..d2256fee 100644 --- a/src/battle/data/abilities.js +++ b/src/battle/data/abilities.js @@ -174,6 +174,17 @@ const abilitiesToRegister = Object.freeze({ battle.unregisterListener(properties.listenerId); }, }), + [abilityIdEnum.SERENE_GRACE]: new Ability({ + id: abilityIdEnum.SERENE_GRACE, + name: "Serene Grace", + description: + "Most moves have twice the chance to apply effects and status.", + // effect is hard-coded in moves.js > genericApplySingleStatus and genericApplySingleEffect + abilityAdd() { + return {}; + }, + abilityRemove() {}, + }), [abilityIdEnum.ANGER_POINT]: new Ability({ id: abilityIdEnum.ANGER_POINT, name: "Anger Point", diff --git a/src/battle/data/moves.js b/src/battle/data/moves.js index 26bef313..992dd7f1 100644 --- a/src/battle/data/moves.js +++ b/src/battle/data/moves.js @@ -166,6 +166,71 @@ class Move { }); } } + + // eslint-disable-next-line class-methods-use-this + genericApplySingleEffect({ + source, + target, + // eslint-disable-next-line no-unused-vars + primaryTarget, + // eslint-disable-next-line no-unused-vars + allTargets, + missedTargets = [], + effectId, + duration, + initialArgs = {}, + probablity = 1, + }) { + let shouldApplyEffect = false; + if (!missedTargets.includes(target)) { + if (Math.random() < probablity) { + shouldApplyEffect = true; + } + } + + if (shouldApplyEffect) { + return target.applyEffect(effectId, source, duration, initialArgs); + } + return false; + } + + /** + * @template {EffectIdEnum} K + * @param {object} param0 + * @param {BattlePokemon} param0.source + * @param {BattlePokemon} param0.primaryTarget + * @param {Array} param0.allTargets + * @param {Array=} param0.missedTargets + * @param {K} param0.effectId + * @param {number} param0.duration + * @param {EffectInitialArgsTypeFromId=} param0.initialArgs + * @param {number=} param0.probablity + */ + genericApplyAllEffects({ + source, + primaryTarget, + allTargets, + missedTargets = [], + effectId, + duration, + // @ts-ignore + initialArgs = {}, + probablity = 1, + }) { + for (const target of allTargets) { + this.genericApplySingleEffect({ + source, + target, + primaryTarget, + allTargets, + missedTargets, + effectId, + duration, + initialArgs, + probablity, + }); + } + } } const movesToRegister = Object.freeze({ @@ -222,6 +287,38 @@ const movesToRegister = Object.freeze({ }); }, }), + [moveIdEnum.PSYCHIC]: new Move({ + id: moveIdEnum.PSYCHIC, + name: "Psychic", + type: pokemonTypes.PSYCHIC, + power: 65, + accuracy: 90, + cooldown: 3, + targetType: targetTypes.ENEMY, + targetPosition: targetPositions.FRONT, + targetPattern: targetPatterns.ROW, + tier: moveTiers.POWER, + damageType: damageTypes.SPECIAL, + description: + "The target is hit by a strong telekinetic force. This has a 60% chance to lower the targets' Special Defense for 2 turns.", + execute({ source, primaryTarget, allTargets, missedTargets }) { + this.genericDealAllDamage({ + source, + primaryTarget, + allTargets, + missedTargets, + }); + this.genericApplyAllEffects({ + source, + primaryTarget, + allTargets, + missedTargets, + effectId: "spdDown", + duration: 2, + probablity: 0.6, + }); + }, + }), [moveIdEnum.AQUA_IMPACT]: new Move({ id: moveIdEnum.AQUA_IMPACT, name: "Aqua Impact", diff --git a/src/battle/engine/BattlePokemon.js b/src/battle/engine/BattlePokemon.js index 6dd44896..74cf35dd 100644 --- a/src/battle/engine/BattlePokemon.js +++ b/src/battle/engine/BattlePokemon.js @@ -1245,6 +1245,10 @@ class BattlePokemon { } } + hasAbility(abilityId) { + return this.ability?.abilityId === abilityId; + } + /** * @param {number} heal * @param {BattlePokemon} target diff --git a/src/config/battleConfig.js b/src/config/battleConfig.js index fcb5dd83..b28d5988 100644 --- a/src/config/battleConfig.js +++ b/src/config/battleConfig.js @@ -3164,20 +3164,6 @@ const moveConfig = Object.freeze({ description: "The target is hit by a weak telekinetic force. This has a 25% chance to confuse the target for 1 turn.", }, - m94: { - name: "Psychic", - type: pokemonTypes.PSYCHIC, - power: 65, - accuracy: 90, - cooldown: 3, - targetType: targetTypes.ENEMY, - targetPosition: targetPositions.FRONT, - targetPattern: targetPatterns.ROW, - tier: moveTiers.POWER, - damageType: damageTypes.SPECIAL, - description: - "The target is hit by a strong telekinetic force. This has a 60% chance to lower the targets' Special Defense for 2 turns.", - }, m97: { name: "Agility", type: pokemonTypes.PSYCHIC, diff --git a/src/enums/battleEnums.js b/src/enums/battleEnums.js index edc1fb8b..402f7d77 100644 --- a/src/enums/battleEnums.js +++ b/src/enums/battleEnums.js @@ -21,6 +21,7 @@ const moveIdEnum = Object.freeze({ TEST_MOVE2: "998", FIRE_PUNCH: "m7", VINE_WHIP: "m22", + PSYCHIC: "m94", AQUA_IMPACT: "m618-1", MAGMA_IMPACT: "m619-1", FLAME_BALL: "m780-1", @@ -34,6 +35,7 @@ const moveIdEnum = Object.freeze({ const abilityIdEnum = Object.freeze({ TEST_ABILITY: "testAbility", AQUA_POWER: "2-1", + SERENE_GRACE: "32", MAGMA_POWER: "70-1", ANGER_POINT: "83", REGENERATOR: "144", From 59256f0fb5d69c636bd380502b994a3cef850478 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Fri, 17 Jan 2025 19:10:28 -0800 Subject: [PATCH 05/11] base jirachi impl --- src/battle/data/effects.js | 40 +++++++++- src/battle/data/moves.js | 123 +++++++++++++++++++++++++---- src/battle/engine/BattlePokemon.js | 21 ++--- src/battle/types.js | 2 + src/config/battleConfig.js | 49 +----------- src/config/pokemonConfig.js | 25 ++++++ src/enums/battleEnums.js | 4 + src/enums/pokemonEnums.js | 1 + src/services/mythic.js | 4 +- 9 files changed, 193 insertions(+), 76 deletions(-) diff --git a/src/battle/data/effects.js b/src/battle/data/effects.js index 7276ab80..73a8b27c 100644 --- a/src/battle/data/effects.js +++ b/src/battle/data/effects.js @@ -1,8 +1,13 @@ /* eslint-disable no-param-reassign */ const { effectTypes, statToBattleStat } = require("../../config/battleConfig"); -const { effectIdEnum, battleEventEnum } = require("../../enums/battleEnums"); +const { + effectIdEnum, + battleEventEnum, + moveIdEnum, +} = require("../../enums/battleEnums"); const { getIsTargetPokemonCallback } = require("../engine/eventConditions"); const { getEffect } = require("./effectRegistry"); +const { getMove } = require("./moveRegistry"); /** * @template T @@ -157,6 +162,39 @@ const effectsToRegister = Object.freeze({ target[statToIncrease] -= baseStatValue; }, }), + [effectIdEnum.DOOM_DESIRE]: new Effect({ + id: effectIdEnum.DOOM_DESIRE, + name: "Doom Desire", + description: "The target will take damage when the effect is removed.", + type: effectTypes.DEBUFF, + dispellable: false, + /** + * @param {EffectAddBasicArgs & {initialArgs: any}} args + */ + effectAdd({ battle, target, source }) { + battle.addToLog( + `${source.name} is foreseeing an attack against ${target.name}!` + ); + return {}; + }, + effectRemove({ battle, target, source }) { + battle.addToLog( + `${target.name} was hit by ${source.name}'s Doom Desire!` + ); + const damageToDeal = source.calculateMoveDamage({ + move: getMove(moveIdEnum.DOOM_DESIRE), + target, + primaryTarget: target, + allTargets: [target], + offTargetDamageMultiplier: 1, + backTargetDamageMultiplier: 1, + }); + source.dealDamage(damageToDeal, target, { + type: "move", + moveId: moveIdEnum.DOOM_DESIRE, + }); + }, + }), }); module.exports = { diff --git a/src/battle/data/moves.js b/src/battle/data/moves.js index 992dd7f1..e54b5102 100644 --- a/src/battle/data/moves.js +++ b/src/battle/data/moves.js @@ -8,7 +8,11 @@ const { statusConditions, } = require("../../config/battleConfig"); const { getMove } = require("./moveRegistry"); -const { moveIdEnum } = require("../../enums/battleEnums"); +const { + moveIdEnum, + abilityIdEnum, + effectIdEnum, +} = require("../../enums/battleEnums"); const { drawIterable } = require("../../utils/gachaUtils"); class Move { @@ -128,7 +132,21 @@ class Move { options, probablity = 1, }) { - if (!missedTargets.includes(target) && Math.random() < probablity) { + let shouldApplyStatus = false; + if (!missedTargets.includes(target)) { + const roll = Math.random(); + if (roll < probablity) { + shouldApplyStatus = true; + } else if ( + source.hasAbility(abilityIdEnum.SERENE_GRACE) && + roll < 2 * probablity + ) { + source.battle.addToLog(`${source.name}'s Serene Grace activates!`); + shouldApplyStatus = true; + } + } + + if (shouldApplyStatus) { return target.applyStatus(statusId, source, options); } return false; @@ -183,13 +201,20 @@ class Move { }) { let shouldApplyEffect = false; if (!missedTargets.includes(target)) { - if (Math.random() < probablity) { + const roll = Math.random(); + if (roll < probablity) { + shouldApplyEffect = true; + } else if ( + source.hasAbility(abilityIdEnum.SERENE_GRACE) && + roll < 2 * probablity + ) { + source.battle.addToLog(`${source.name}'s Serene Grace activates!`); shouldApplyEffect = true; } } if (shouldApplyEffect) { - return target.applyEffect(effectId, source, duration, initialArgs); + return target.applyEffect(effectId, duration, source, initialArgs); } return false; } @@ -287,6 +312,30 @@ const movesToRegister = Object.freeze({ }); }, }), + [moveIdEnum.CONFUSION]: new Move({ + id: moveIdEnum.CONFUSION, + name: "Confusion", + type: pokemonTypes.PSYCHIC, + power: 50, + accuracy: 100, + cooldown: 0, + targetType: targetTypes.ENEMY, + targetPosition: targetPositions.FRONT, + targetPattern: targetPatterns.SINGLE, + tier: moveTiers.BASIC, + damageType: damageTypes.SPECIAL, + description: + "The target is hit by a weak telekinetic force. This has a 25% chance to confuse the target for 1 turn.", + execute(args) { + this.genericDealAllDamage(args); + this.genericApplyAllEffects({ + ...args, + effectId: "confused", + duration: 1, + probablity: 0.25, + }); + }, + }), [moveIdEnum.PSYCHIC]: new Move({ id: moveIdEnum.PSYCHIC, name: "Psychic", @@ -301,24 +350,68 @@ const movesToRegister = Object.freeze({ damageType: damageTypes.SPECIAL, description: "The target is hit by a strong telekinetic force. This has a 60% chance to lower the targets' Special Defense for 2 turns.", - execute({ source, primaryTarget, allTargets, missedTargets }) { - this.genericDealAllDamage({ - source, - primaryTarget, - allTargets, - missedTargets, - }); + execute(args) { + this.genericDealAllDamage(args); this.genericApplyAllEffects({ - source, - primaryTarget, - allTargets, - missedTargets, + ...args, effectId: "spdDown", duration: 2, probablity: 0.6, }); }, }), + [moveIdEnum.DOOM_DESIRE]: new Move({ + id: moveIdEnum.DOOM_DESIRE, + name: "Doom Desire", + type: pokemonTypes.STEEL, + power: 120, + accuracy: 100, + cooldown: 5, + targetType: targetTypes.ENEMY, + targetPosition: targetPositions.ANY, + targetPattern: targetPatterns.SQUARE, + tier: moveTiers.ULTIMATE, + damageType: damageTypes.SPECIAL, + description: + "Two turns after this move is used, the user's strikes the target with a concentrated bundle of light (undispellable). This move also has a 10% chance to apply Perish Song.", + execute(args) { + this.genericApplyAllEffects({ + ...args, + effectId: effectIdEnum.DOOM_DESIRE, + duration: 2, + }); + this.genericApplyAllEffects({ + ...args, + effectId: "perishSong", + duration: 3, + probablity: 0.1, + }); + }, + }), + [moveIdEnum.IRON_HEAD]: new Move({ + id: moveIdEnum.IRON_HEAD, + name: "Iron Head", + type: pokemonTypes.STEEL, + power: 80, + accuracy: 100, + cooldown: 3, + targetType: targetTypes.ENEMY, + targetPosition: targetPositions.FRONT, + targetPattern: targetPatterns.SINGLE, + tier: moveTiers.POWER, + damageType: damageTypes.PHYSICAL, + description: + "The target is struck with a hard head made of iron. This has a 50% chance to flinch.", + execute(args) { + this.genericDealAllDamage(args); + this.genericApplyAllEffects({ + ...args, + effectId: "flinched", + duration: 1, + probablity: 0.5, + }); + }, + }), [moveIdEnum.AQUA_IMPACT]: new Move({ id: moveIdEnum.AQUA_IMPACT, name: "Aqua Impact", diff --git a/src/battle/engine/BattlePokemon.js b/src/battle/engine/BattlePokemon.js index 74cf35dd..faabf485 100644 --- a/src/battle/engine/BattlePokemon.js +++ b/src/battle/engine/BattlePokemon.js @@ -1510,7 +1510,8 @@ class BattlePokemon { */ removeEffect(effectId) { // if effect doesn't exist, do nothing - if (!this.effectIds[effectId]) { + const effectInstance = this.getEffectInstance(effectId); + if (!effectInstance) { return false; } const effect = getEffect(effectId); @@ -1523,28 +1524,30 @@ class BattlePokemon { // @ts-ignore effect.effectRemove({ battle: this.battle, + source: effectInstance.source, + duration: effectInstance.duration, target: this, - properties: this.effectIds[effectId].args, - initialArgs: this.effectIds[effectId].initialArgs, + properties: effectInstance.args, + initialArgs: effectInstance.initialArgs, }); } else { const legacyEffect = /** @type {any} */ (effect); legacyEffect.effectRemove( this.battle, this, - this.effectIds[effectId].args, - this.effectIds[effectId].initialArgs + effectInstance.args, + effectInstance.initialArgs ); } if (this.effectIds[effectId] !== undefined) { const afterRemoveArgs = { target: this, - source: this.effectIds[effectId].source, + source: effectInstance.source, effectId, - duration: this.effectIds[effectId].duration, - initialArgs: this.effectIds[effectId].initialArgs, - args: this.effectIds[effectId].args, + duration: effectInstance.duration, + initialArgs: effectInstance.initialArgs, + args: effectInstance.args, }; this.battle.eventHandler.emit( battleEventEnum.AFTER_EFFECT_REMOVE, diff --git a/src/battle/types.js b/src/battle/types.js index 6f3c96e7..a91e959c 100644 --- a/src/battle/types.js +++ b/src/battle/types.js @@ -116,6 +116,8 @@ * @param {object} param0 * @param {Battle} param0.battle * @param {BattlePokemon} param0.target + * @param {BattlePokemon} param0.source + * @param {number} param0.duration * @param {T} param0.initialArgs * @param {U} param0.properties */ diff --git a/src/config/battleConfig.js b/src/config/battleConfig.js index b28d5988..4593d1e6 100644 --- a/src/config/battleConfig.js +++ b/src/config/battleConfig.js @@ -3150,20 +3150,7 @@ const moveConfig = Object.freeze({ description: "A move that leaves the targets badly poisoned. Its poison damage worsens every turn. If the user is Poison type and the target isn't Steel type, ignore miss on the primary target.", }, - m93: { - name: "Confusion", - type: pokemonTypes.PSYCHIC, - power: 50, - accuracy: 100, - cooldown: 0, - targetType: targetTypes.ENEMY, - targetPosition: targetPositions.FRONT, - targetPattern: targetPatterns.SINGLE, - tier: moveTiers.BASIC, - damageType: damageTypes.SPECIAL, - description: - "The target is hit by a weak telekinetic force. This has a 25% chance to confuse the target for 1 turn.", - }, + m97: { name: "Agility", type: pokemonTypes.PSYCHIC, @@ -7385,40 +7372,6 @@ const moveExecutes = { } } }, - m93(_battle, source, _primaryTarget, allTargets, missedTargets) { - const moveId = "m93"; - const moveData = getMove(moveId); - for (const target of allTargets) { - const miss = missedTargets.includes(target); - const damageToDeal = calculateDamage(moveData, source, target, miss); - source.dealDamage(damageToDeal, target, { - type: "move", - moveId, - }); - - // if not miss, 25% to confuse - if (!miss && Math.random() < 0.25) { - target.applyEffect("confused", 1, source); - } - } - }, - m94(_battle, source, _primaryTarget, allTargets, missedTargets) { - const moveId = "m94"; - const moveData = getMove(moveId); - for (const target of allTargets) { - const miss = missedTargets.includes(target); - const damageToDeal = calculateDamage(moveData, source, target, miss); - source.dealDamage(damageToDeal, target, { - type: "move", - moveId, - }); - - // if not miss, 60% chance to spd down 2 turn - if (!miss && Math.random() < 0.6) { - target.applyEffect("spdDown", 2, source); - } - } - }, m97(_battle, source, _primaryTarget, allTargets) { for (const target of allTargets) { // apply greaterSpeUp buff diff --git a/src/config/pokemonConfig.js b/src/config/pokemonConfig.js index 2b466fe5..f99aabce 100644 --- a/src/config/pokemonConfig.js +++ b/src/config/pokemonConfig.js @@ -7321,6 +7321,31 @@ const pokemonConfigRaw = { growthRate: growthRates.SLOW, noGacha: true, }, + [pokemonIdEnum.JIRACHI]: { + name: "Jirachi", + emoji: "<:385:1132497393431105588>", + description: + "Jirachi will awaken from its sleep of a thousand years if you sing to it in a voice of purity. It is said to make true any wish that people desire.", + type: [types.STEEL, types.PSYCHIC], + baseStats: [100, 100, 100, 100, 100, 100], + sprite: + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/385.png", + shinySprite: + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/385.png", + abilities: { + [abilityIdEnum.SERENE_GRACE]: 1, + }, + moveIds: [ + moveIdEnum.CONFUSION, + moveIdEnum.PSYCHIC, + moveIdEnum.IRON_HEAD, + moveIdEnum.DOOM_DESIRE, + ], + battleEligible: true, + rarity: rarities.MYTHICAL, + growthRate: growthRates.SLOW, + noGacha: true, + }, 386: { name: "Deoxys", emoji: "<:386:1132497394739712010>", diff --git a/src/enums/battleEnums.js b/src/enums/battleEnums.js index 402f7d77..632715f8 100644 --- a/src/enums/battleEnums.js +++ b/src/enums/battleEnums.js @@ -9,6 +9,7 @@ const effectIdEnum = Object.freeze({ SHIELD: "shield", DEBUFF_IMMUNITY: "debuffImmunity", AQUA_BLESSING: "aquaBlessing", + DOOM_DESIRE: "doomDesire", }); /** @@ -21,7 +22,10 @@ const moveIdEnum = Object.freeze({ TEST_MOVE2: "998", FIRE_PUNCH: "m7", VINE_WHIP: "m22", + CONFUSION: "m93", PSYCHIC: "m94", + DOOM_DESIRE: "m353", + IRON_HEAD: "m442", AQUA_IMPACT: "m618-1", MAGMA_IMPACT: "m619-1", FLAME_BALL: "m780-1", diff --git a/src/enums/pokemonEnums.js b/src/enums/pokemonEnums.js index f38c15ce..3d7ad180 100644 --- a/src/enums/pokemonEnums.js +++ b/src/enums/pokemonEnums.js @@ -269,6 +269,7 @@ const pokemonIdEnum = Object.freeze({ KYOGRE: "382", GROUDON: "383", RAYQUAZA: "384", + JIRACHI: "385", DEOXYS: "386", GARYS_BLASTOISE: "9-1", AAABAAAJSS: "18-1", diff --git a/src/services/mythic.js b/src/services/mythic.js index b5441fe4..453bdef6 100644 --- a/src/services/mythic.js +++ b/src/services/mythic.js @@ -2,8 +2,6 @@ /** * @file * @author Elvis Wei - * @date 2023 - * @section Description * * mythic.js Creates all mythic pokemon information, moves etc. */ @@ -405,7 +403,7 @@ const getCelebi = async (trainer) => { // set locked to true celebi.locked = true; // set nature to 0 - celebi.natureId = 0; + celebi.natureId = "0"; // recalculate stats calculatePokemonStats(celebi, celebiData); modified = true; From 4448c4c419e7c2f2e5eb5d41bcd6157b6d4ecfcf Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Sun, 19 Jan 2025 23:45:31 -0800 Subject: [PATCH 06/11] improve mythic --- src/battle/data/moves.js | 2 +- src/services/mythic.js | 143 +++++++++++++++++---------------------- src/services/pokemon.js | 5 +- 3 files changed, 66 insertions(+), 84 deletions(-) diff --git a/src/battle/data/moves.js b/src/battle/data/moves.js index e54b5102..48836b9f 100644 --- a/src/battle/data/moves.js +++ b/src/battle/data/moves.js @@ -392,7 +392,7 @@ const movesToRegister = Object.freeze({ id: moveIdEnum.IRON_HEAD, name: "Iron Head", type: pokemonTypes.STEEL, - power: 80, + power: 90, accuracy: 100, cooldown: 3, targetType: targetTypes.ENEMY, diff --git a/src/services/mythic.js b/src/services/mythic.js index 453bdef6..6db92f2c 100644 --- a/src/services/mythic.js +++ b/src/services/mythic.js @@ -81,6 +81,52 @@ const getMythic = async (trainer, speciesId) => { return { data: pokemon.data }; }; +/** + * @param {Trainer} trainer + * @param {PokemonIdEnum} speciesId + */ +const generateMythic = (trainer, speciesId) => { + const speciesData = pokemonConfig[speciesId]; + const mythic = generateRandomPokemon(trainer.userId, speciesId, 1); + // set ivs to 31 + mythic.ivs = [31, 31, 31, 31, 31, 31]; + // set shiny to false + mythic.shiny = false; + // set locked to true + mythic.locked = true; + // recalculate stats + calculatePokemonStats(mythic, speciesData); + + return mythic; +}; + +/** + * @param {Trainer} trainer + * @param {Pokemon} mythic + * @returns {Promise<{err?: string, id?: any}>} + */ +const upsertMythic = async (trainer, mythic) => { + const mythicData = pokemonConfig[mythic.speciesId]; + try { + const query = new QueryBuilder(collectionNames.USER_POKEMON) + .setFilter({ userId: mythic.userId, speciesId: mythic.speciesId }) + .setUpsert({ $set: mythic }); + const res = await query.upsertOne(); + + if (res.upsertedCount !== 1) { + logger.warn( + `Error updating ${mythicData.name} for ${trainer.user.username}` + ); + } else { + logger.info(`Updated ${mythicData.name} for ${trainer.user.username}`); + } + return { id: res.upsertedId }; + } catch (err) { + logger.error(err); + return { err: `Error updating ${mythicData.name}` }; + } +}; + const validateMewMoves = (mew, mewData) => { const { mythicConfig } = mewData; @@ -136,15 +182,7 @@ const getMew = async (trainer) => { }; } - mew = generateRandomPokemon(trainer.userId, speciesId, 1); - // set ivs to 31 - mew.ivs = [31, 31, 31, 31, 31, 31]; - // set shiny to false - mew.shiny = false; - // set locked to true - mew.locked = true; - // recalculate stats - calculatePokemonStats(mew, mewData); + mew = generateMythic(trainer, speciesId); modified = true; } @@ -156,24 +194,11 @@ const getMew = async (trainer) => { // update mew if modified if (modified) { - try { - const query = new QueryBuilder(collectionNames.USER_POKEMON) - .setFilter({ userId: mew.userId, speciesId }) - .setUpsert({ $set: mew }); - const res = await query.upsertOne(); - - if (res.upsertedCount !== 1) { - logger.warn(`Error updating Mew for ${trainer.user.username}`); - // return { err: "Error updating Mew" }; - } - if (res.upsertedId) { - mew._id = res.upsertedId; - } - logger.info(`Updated Mew for ${trainer.user.username}`); - } catch (err) { - logger.error(err); - return { err: "Error updating Mew" }; + const { err, id } = await upsertMythic(trainer, mew); + if (err) { + return { err }; } + mew._id = id || mew._id; } return { data: mew }; @@ -370,7 +395,6 @@ const buildMewSend = async ({ user = null, tab = "basic" } = {}) => { const getCelebi = async (trainer) => { const speciesId = "251"; - const celebiData = pokemonConfig[speciesId]; const celebiRes = await getMythic(trainer, speciesId); if (celebiRes.err) { @@ -395,17 +419,7 @@ const getCelebi = async (trainer) => { }; } - celebi = generateRandomPokemon(trainer.userId, speciesId, 1); - // set ivs to 31 - celebi.ivs = [31, 31, 31, 31, 31, 31]; - // set shiny to false - celebi.shiny = false; - // set locked to true - celebi.locked = true; - // set nature to 0 - celebi.natureId = "0"; - // recalculate stats - calculatePokemonStats(celebi, celebiData); + celebi = generateMythic(trainer, speciesId); modified = true; } @@ -419,23 +433,11 @@ const getCelebi = async (trainer) => { // update celebi if modified if (modified) { - try { - const query = new QueryBuilder(collectionNames.USER_POKEMON) - .setFilter({ userId: celebi.userId, speciesId }) - .setUpsert({ $set: celebi }); - const res = await query.upsertOne(); - - if (res.upsertedCount !== 1) { - logger.warn(`Error updating Celebi for ${trainer.user.username}`); - } - if (res.upsertedId) { - celebi._id = res.upsertedId; - } - logger.info(`Updated Celebi for ${trainer.user.username}`); - } catch (err) { - logger.error(err); - return { err: "Error updating Celebi" }; + const { err, id } = await upsertMythic(trainer, celebi); + if (err) { + return { err }; } + celebi._id = id || celebi._id; } return { data: celebi }; @@ -572,7 +574,6 @@ const getDeoxys = async (trainer) => { let modified = false; const speciesId = (deoxys && deoxys.speciesId) || DEOXYS_SPECIES_IDS[0]; - const deoxysData = pokemonConfig[speciesId]; if (!deoxys) { // check if trainer has beat battle tower 20 if (!trainer.defeatedNPCs[getIdFromTowerStage(20)]) { @@ -581,39 +582,17 @@ const getDeoxys = async (trainer) => { }; } - deoxys = generateRandomPokemon(trainer.userId, speciesId, 1); - // set ivs to 31 - deoxys.ivs = [31, 31, 31, 31, 31, 31]; - // set shiny to false - deoxys.shiny = false; - // set locked to true - deoxys.locked = true; - // set nature to 0 - deoxys.natureId = 0; - // recalculate stats - calculatePokemonStats(deoxys, deoxysData); + deoxys = generateMythic(trainer, speciesId); modified = true; } // update deoxys if modified if (modified) { - try { - const query = new QueryBuilder(collectionNames.USER_POKEMON) - .setFilter({ userId: deoxys.userId, speciesId }) - .setUpsert({ $set: deoxys }); - const res = await query.upsertOne(); - - if (res.upsertedCount !== 1) { - logger.warn(`Error updating Deoxys for ${trainer.user.username}`); - } - if (res.upsertedId) { - deoxys._id = res.upsertedId; - } - logger.info(`Updated Deoxys for ${trainer.user.username}`); - } catch (err) { - logger.error(err); - return { err: "Error updating Deoxys" }; + const { err, id } = await upsertMythic(trainer, deoxys); + if (err) { + return { err }; } + deoxys._id = id || deoxys._id; } return { data: deoxys }; diff --git a/src/services/pokemon.js b/src/services/pokemon.js index 3b3a10eb..acde6806 100644 --- a/src/services/pokemon.js +++ b/src/services/pokemon.js @@ -1377,7 +1377,10 @@ const canRelease = async (trainer, pokemonIds) => { // see if any pokemon are mythical for (const pokemon of toRelease.data) { - if (pokemon.rarity === rarities.MYTHICAL) { + if ( + pokemon.rarity === rarities.MYTHICAL && + process.env.STAGE !== stageNames.ALPHA + ) { return { err: `You can't release or trade ${pokemon.name} (${pokemon._id}) because it's mythical!`, }; From fd602462acb117b581c7b5cd8b6cce270fab1040 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Mon, 20 Jan 2025 00:03:48 -0800 Subject: [PATCH 07/11] basic mythic jirachi --- src/config/trainerConfig.js | 10 ++++++ src/services/mythic.js | 65 +++++++++++++++++++++++++++++++++++++ types.js | 2 ++ 3 files changed, 77 insertions(+) diff --git a/src/config/trainerConfig.js b/src/config/trainerConfig.js index db63e90c..8d9c0f55 100644 --- a/src/config/trainerConfig.js +++ b/src/config/trainerConfig.js @@ -407,15 +407,25 @@ const trainerFields = { pokemonIds: [], }, }, + // TODO: move mythics to its own nested object? hasCelebi: { type: "boolean", default: false, }, + hasJirachi: { + type: "boolean", + default: false, + }, usedTimeTravel: { type: "boolean", default: false, refreshInterval: timeEnum.DAY, }, + usedWish: { + type: "boolean", + default: false, + refreshInterval: timeEnum.WEEK, + }, lastTowerStage: { type: "number", default: 0, diff --git a/src/services/mythic.js b/src/services/mythic.js index 6db92f2c..0e461d20 100644 --- a/src/services/mythic.js +++ b/src/services/mythic.js @@ -684,6 +684,70 @@ const onFormSelect = async (user, speciesId) => { return { err: null }; }; +/** + * @param {WithId} trainer + * @returns {Promise<{err?: string, data?: WithId}>} + */ +const getJirachi = async (trainer) => { + const speciesId = "385"; + + const jirachiRes = await getMythic(trainer, speciesId); + if (jirachiRes.err) { + return { err: jirachiRes.err }; + } + + let jirachi = jirachiRes.data; + let modified = false; + if (!jirachi) { + let metRequirements = true; + // check star piece + if (getItems(trainer, backpackItems.STAR_PIECE) < 200) { + metRequirements = false; + } + // check for non-original-trainer pokemon + const pokemonsRes = await listPokemons(trainer, { + pageSize: 3, + filter: { originalOwner: { $ne: trainer.userId } }, + }); + if (pokemonsRes.err) { + return { err: pokemonsRes.err }; + } + if (pokemonsRes.data.length < 3) { + metRequirements = false; + } + if (!metRequirements) { + return { + err: `Jirachi wants you to work with others before granting your wishes! You must obtain 200x ${ + backpackItemConfig[backpackItems.STAR_PIECE].emoji + } Star Pieces from raids, and have at least 3 traded Pokemon!`, + }; + } + + // @ts-ignore + jirachi = generateMythic(trainer, speciesId); + modified = true; + } + + if (!trainer.hasCelebi) { + trainer.hasCelebi = true; + const trainerRes = await updateTrainer(trainer); + if (trainerRes.err) { + return { err: trainerRes.err }; + } + } + + // update jirachi if modified + if (modified) { + const { err, id } = await upsertMythic(trainer, jirachi); + if (err) { + return { err }; + } + jirachi._id = id || jirachi._id; + } + + return { data: jirachi }; +}; + module.exports = { getMew, updateMew, @@ -694,4 +758,5 @@ module.exports = { getDeoxys, buildDeoxysSend, onFormSelect, + getJirachi, }; diff --git a/types.js b/types.js index 19aaddc3..9c08c306 100644 --- a/types.js +++ b/types.js @@ -108,7 +108,9 @@ * * Mythic Pokemon * @property {boolean} hasCelebi + * @property {boolean} hasJirachi * @property {boolean} usedTimeTravel + * @property {boolean} usedWish * * Misc * @property {PartialRecord} upsellData From 730c636387b54159c4db9cdcfbccb12935f378b1 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 23 Jan 2025 00:04:39 -0800 Subject: [PATCH 08/11] command config --- src/commands/pokemon/jirachi.js | 32 ++++++++++++++++++++++++++++++++ src/config/commandConfig.js | 16 +++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/commands/pokemon/jirachi.js diff --git a/src/commands/pokemon/jirachi.js b/src/commands/pokemon/jirachi.js new file mode 100644 index 00000000..f40b0a13 --- /dev/null +++ b/src/commands/pokemon/jirachi.js @@ -0,0 +1,32 @@ +/** + * @file + * @author Elvis Wei + * + * jirachi.js jirachi. + */ +const { buildDeoxysSend } = require("../../services/mythic"); + +const deoxys = async (user) => await buildDeoxysSend(user); + +const deoxysMessageCommand = async (message) => { + const { send, err } = await deoxys(message.author); + if (err) { + await message.channel.send(`${err}`); + return { err }; + } + await message.channel.send(send); +}; + +const deoxysSlashCommand = async (interaction) => { + const { send, err } = await deoxys(interaction.user); + if (err) { + await interaction.reply(`${err}`); + return { err }; + } + await interaction.reply(send); +}; + +module.exports = { + message: deoxysMessageCommand, + slash: deoxysSlashCommand, +}; diff --git a/src/config/commandConfig.js b/src/config/commandConfig.js index a0c02af2..2d6916ec 100644 --- a/src/config/commandConfig.js +++ b/src/config/commandConfig.js @@ -52,6 +52,7 @@ const commandCategoryConfigRaw = { "mew", "celebi", "deoxys", + "jirachi", // "togglespawn", ], }, @@ -957,7 +958,7 @@ const commandConfigRaw = { name: "Mythic", aliases: ["mythic"], description: "Entry point for Mythical Pokemon", - subcommands: ["mew", "celebi", "deoxys"], + subcommands: ["mew", "celebi", "deoxys", "jirachi"], args: {}, stages: [stageNames.ALPHA, stageNames.BETA, stageNames.PROD], }, @@ -999,6 +1000,19 @@ const commandConfigRaw = { money: 10, parent: "mythic", }, + jirachi: { + name: "Jirachi", + aliases: ["jirachi"], + description: "View your Jirachi and make a wish!", + longDescription: + "View your Jirachi and make a wish! Jirachi is a special Pokemon that can grant wishes and increases your shiny Pokemon odds!", + execute: "jirachi.js", + args: {}, + stages: [stageNames.ALPHA, stageNames.BETA, stageNames.PROD], + exp: 5, + money: 10, + parent: "mythic", + }, tutorial: { name: "Tutorial", aliases: ["tutorial"], From 909ab5356181c7df83ac2c5ec8ec5b4f46c85971 Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 23 Jan 2025 00:21:05 -0800 Subject: [PATCH 09/11] useTrainer hook --- src/elements/quest/TutorialList.js | 18 ++++-------------- src/hooks/useTrainer.js | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 src/hooks/useTrainer.js diff --git a/src/elements/quest/TutorialList.js b/src/elements/quest/TutorialList.js index c44ff45d..94c3badd 100644 --- a/src/elements/quest/TutorialList.js +++ b/src/elements/quest/TutorialList.js @@ -1,16 +1,12 @@ -const { - useAwaitedMemo, - useState, - createElement, -} = require("../../deact/deact"); +const { createElement } = require("../../deact/deact"); const usePaginationAndSelection = require("../../hooks/usePaginationAndSelection"); const { newTutorialStages, newTutorialConfig, } = require("../../config/questConfig"); const { buildTutorialListEmbed } = require("../../embeds/questEmbeds"); -const { getTrainer } = require("../../services/trainer"); const TutorialStage = require("./TutorialStage"); +const useTrainer = require("../../hooks/useTrainer"); const PAGE_SIZE = 10; @@ -24,11 +20,7 @@ const PAGE_SIZE = 10; */ module.exports = async (ref, { user, initialStagePage }) => { // TODO: got to current tutorial stage - const { data: initialTrainer, err } = await useAwaitedMemo( - async () => getTrainer(user), - [], - ref - ); + const { trainer, setTrainer, err } = await useTrainer(user, ref); if (err) { return { err }; } @@ -47,7 +39,7 @@ module.exports = async (ref, { user, initialStagePage }) => { initialItem: /** @type {TutorialStageEnum} */ ( initialStagePage && newTutorialStages[initialStagePage - 1] ? newTutorialStages[initialStagePage - 1] - : initialTrainer.tutorialData.currentTutorialStage + : trainer.tutorialData.currentTutorialStage ), selectionPlaceholder: "Select a tutorial stage", itemConfig: newTutorialConfig, @@ -58,8 +50,6 @@ module.exports = async (ref, { user, initialStagePage }) => { ref ); - const [trainer, setTrainer] = useState(initialTrainer, ref); - if (currentStage) { return { elements: [ diff --git a/src/hooks/useTrainer.js b/src/hooks/useTrainer.js new file mode 100644 index 00000000..fb90304c --- /dev/null +++ b/src/hooks/useTrainer.js @@ -0,0 +1,25 @@ +const { useAwaitedMemo, useState } = require("../deact/deact"); +const { getTrainer } = require("../services/trainer"); + +/** + * @param {DiscordUser} user + * @param {DeactElement} ref + * @returns {Promise<{ + * trainer: WithId, + * setTrainer: (trainer: Trainer) => void, + * err?: string + * }>} + */ +const useTrainer = async (user, ref) => { + // TODO: got to current tutorial stage + const { data: initialTrainer, err } = await useAwaitedMemo( + async () => getTrainer(user), + [], + ref + ); + const [trainer, setTrainer] = useState(initialTrainer, ref); + + return { trainer, setTrainer, err }; +}; + +module.exports = useTrainer; From a45704205d9c56d662bd159bba564f1bb9989a4e Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 23 Jan 2025 00:33:38 -0800 Subject: [PATCH 10/11] basic jirachi --- changelog.md | 1 + src/commands/pokemon/jirachi.js | 36 +++++++++++++---------------- src/elements/pokemon/Jirachi.js | 32 +++++++++++++++++++++++++ src/elements/pokemon/PokemonList.js | 1 + src/services/mythic.js | 8 ++++--- 5 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 src/elements/pokemon/Jirachi.js diff --git a/changelog.md b/changelog.md index 3d0172cf..da1878d5 100644 --- a/changelog.md +++ b/changelog.md @@ -21,6 +21,7 @@ TODO: - Make party add/remove easier - Add Pokemon Emojis wherever possible - Fix spawn type bug +- Fix search with apostrophe **Stretch** diff --git a/src/commands/pokemon/jirachi.js b/src/commands/pokemon/jirachi.js index f40b0a13..f8f6df28 100644 --- a/src/commands/pokemon/jirachi.js +++ b/src/commands/pokemon/jirachi.js @@ -4,29 +4,25 @@ * * jirachi.js jirachi. */ -const { buildDeoxysSend } = require("../../services/mythic"); +const { createRoot } = require("../../deact/deact"); +const Jirachi = require("../../elements/pokemon/Jirachi"); +const { getUserFromInteraction } = require("../../utils/utils"); -const deoxys = async (user) => await buildDeoxysSend(user); +const jirachi = async (interaction) => + await createRoot( + Jirachi, + { + user: getUserFromInteraction(interaction), + }, + interaction, + { ttl: 180 } + ); -const deoxysMessageCommand = async (message) => { - const { send, err } = await deoxys(message.author); - if (err) { - await message.channel.send(`${err}`); - return { err }; - } - await message.channel.send(send); -}; +const jirachiMessageCommand = async (message) => await jirachi(message); -const deoxysSlashCommand = async (interaction) => { - const { send, err } = await deoxys(interaction.user); - if (err) { - await interaction.reply(`${err}`); - return { err }; - } - await interaction.reply(send); -}; +const jirachiSlashCommand = async (interaction) => await jirachi(interaction); module.exports = { - message: deoxysMessageCommand, - slash: deoxysSlashCommand, + message: jirachiMessageCommand, + slash: jirachiSlashCommand, }; diff --git a/src/elements/pokemon/Jirachi.js b/src/elements/pokemon/Jirachi.js new file mode 100644 index 00000000..17d9069f --- /dev/null +++ b/src/elements/pokemon/Jirachi.js @@ -0,0 +1,32 @@ +const { buildPokemonEmbed } = require("../../embeds/pokemonEmbeds"); +const { useAwaitedMemo } = require("../../deact/deact"); +const useTrainer = require("../../hooks/useTrainer"); +const { getJirachi } = require("../../services/mythic"); + +/** + * @param {DeactElement} ref + * @param {object} param1 + * @param {DiscordUser} param1.user + */ +const Jirachi = async (ref, { user }) => { + const { trainer, err: trainerErr } = await useTrainer(user, ref); + if (trainerErr) { + return { err: trainerErr }; + } + + const { data: jirachi, err: jirachiErr } = await useAwaitedMemo( + async () => getJirachi(trainer), + [], + ref + ); + if (jirachiErr) { + return { err: jirachiErr }; + } + + return { + content: "", + embeds: [buildPokemonEmbed(trainer, jirachi)], + }; +}; + +module.exports = Jirachi; diff --git a/src/elements/pokemon/PokemonList.js b/src/elements/pokemon/PokemonList.js index 28c3a099..cd8c73ca 100644 --- a/src/elements/pokemon/PokemonList.js +++ b/src/elements/pokemon/PokemonList.js @@ -180,6 +180,7 @@ module.exports = async ( // get list of pokemon const pokemonsRes = await useAwaitedMemo( () => + // @ts-ignore listPokemons(trainer, computeListOptions(page, filter, sort)).then( (res) => { if (res.data && !res.err) { diff --git a/src/services/mythic.js b/src/services/mythic.js index 0e461d20..04c1305b 100644 --- a/src/services/mythic.js +++ b/src/services/mythic.js @@ -42,6 +42,7 @@ const { } = require("./pokemon"); const { getTrainer, updateTrainer } = require("./trainer"); const { getMoves } = require("../battle/data/moveRegistry"); +const { pokemonIdEnum } = require("../enums/pokemonEnums"); /** * @param {Trainer} trainer @@ -689,7 +690,7 @@ const onFormSelect = async (user, speciesId) => { * @returns {Promise<{err?: string, data?: WithId}>} */ const getJirachi = async (trainer) => { - const speciesId = "385"; + const speciesId = pokemonIdEnum.JIRACHI; const jirachiRes = await getMythic(trainer, speciesId); if (jirachiRes.err) { @@ -708,6 +709,7 @@ const getJirachi = async (trainer) => { const pokemonsRes = await listPokemons(trainer, { pageSize: 3, filter: { originalOwner: { $ne: trainer.userId } }, + allowNone: true, }); if (pokemonsRes.err) { return { err: pokemonsRes.err }; @@ -728,8 +730,8 @@ const getJirachi = async (trainer) => { modified = true; } - if (!trainer.hasCelebi) { - trainer.hasCelebi = true; + if (!trainer.hasJirachi) { + trainer.hasJirachi = true; const trainerRes = await updateTrainer(trainer); if (trainerRes.err) { return { err: trainerRes.err }; From 674bcc563adcdbdc20b04550cd31db96e362512e Mon Sep 17 00:00:00 2001 From: Elvis Wei Date: Thu, 23 Jan 2025 00:42:56 -0800 Subject: [PATCH 11/11] improve jirachi embed --- src/elements/pokemon/Jirachi.js | 5 +++-- src/embeds/pokemonEmbeds.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/elements/pokemon/Jirachi.js b/src/elements/pokemon/Jirachi.js index 17d9069f..2693e6ac 100644 --- a/src/elements/pokemon/Jirachi.js +++ b/src/elements/pokemon/Jirachi.js @@ -7,6 +7,7 @@ const { getJirachi } = require("../../services/mythic"); * @param {DeactElement} ref * @param {object} param1 * @param {DiscordUser} param1.user + * @returns {Promise} */ const Jirachi = async (ref, { user }) => { const { trainer, err: trainerErr } = await useTrainer(user, ref); @@ -24,8 +25,8 @@ const Jirachi = async (ref, { user }) => { } return { - content: "", - embeds: [buildPokemonEmbed(trainer, jirachi)], + contents: [jirachi._id.toString()], + embeds: [buildPokemonEmbed(trainer, jirachi, "info")], }; }; diff --git a/src/embeds/pokemonEmbeds.js b/src/embeds/pokemonEmbeds.js index c828093a..f5961835 100644 --- a/src/embeds/pokemonEmbeds.js +++ b/src/embeds/pokemonEmbeds.js @@ -306,7 +306,7 @@ const buildPokemonListEmbed = (trainer, pokemons, page) => { /** * @param {Trainer} trainer * @param {Pokemon} pokemon - * @param {string=} tab + * @param {("info" | "battle" | "equipment" | "all")=} tab * @param {Pokemon=} oldPokemon * @param {string=} originalOwnerId * @returns {EmbedBuilder}