From aa91ae4c49ed5d003ce95b34861f070c537ef047 Mon Sep 17 00:00:00 2001 From: Yentis Date: Sat, 18 May 2024 00:28:56 +0200 Subject: [PATCH] type hints --- src/battle-scene.ts | 1 + src/field/pokemon.ts | 78 ++++++++++++++++++++++++++++++++++---- src/phases.ts | 8 ++++ src/system/settings.ts | 12 ++++-- src/ui/battle-info.ts | 4 ++ src/ui/fight-ui-handler.ts | 77 ++++++++++++++++++++++++++++++++++--- src/ui/party-ui-handler.ts | 3 ++ 7 files changed, 167 insertions(+), 16 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 0f75447a500..87bc3b39f2b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -119,6 +119,7 @@ export default class BattleScene extends SceneBase { public fusionPaletteSwaps: boolean = true; public enableTouchControls: boolean = false; public enableVibration: boolean = false; + public typeHints: integer = 0; public abSwapped: boolean = false; public disableMenu: boolean = false; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0a5e0a6a991..69cda81a6a7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -943,6 +943,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return !this.isOfType(Type.FLYING, true) && !this.hasAbility(Abilities.LEVITATE); } + isStabMove(move: Move): boolean { + if (move.category === MoveCategory.STATUS) return false; + + const type = move.type; + const types = this.getTypes(); + const teraType = this.getTeraType(); + const matchesSourceType = types[0] === type || (types.length > 1 && types[1] === type); + + return (teraType === Type.UNKNOWN && matchesSourceType) || (teraType !== Type.UNKNOWN && teraType === type); + } + + getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined { + if (move.getMove().category === MoveCategory.STATUS) return undefined; + return this.getAttackMoveEffectiveness(source, move); + } + getAttackMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { const typeless = !!move.getMove().getAttrs(TypelessAttr).length; const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.getMove().type, source)); @@ -1096,11 +1112,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let shinyThreshold = new Utils.IntegerHolder(32); if (thresholdOverride === undefined) { - if (!this.hasTrainer()) { - if (new Date() < new Date('2024-05-21')) - shinyThreshold.value *= 3; + if (!this.hasTrainer()) this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); - } } else shinyThreshold.value = thresholdOverride; @@ -1376,6 +1389,57 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.battleInfo.updateInfo(this, instant); } + updateNameColor() { + const nameColor = this.getNameColor(); + if (nameColor === undefined) return; + + this.battleInfo.updateNameColor(nameColor); + } + + getNameColor(): string | undefined { + const typeHints = this.scene.typeHints; + if (typeHints === 0) return undefined; + const opponents = this.getOpponents(); + + const opponentMoveEffectivenessList = opponents.map((opponent) => { + return opponent.getMoveset().map((move) => { + return this.getMoveEffectiveness(opponent, move); + }); + }).flat().filter((effectiveness) => effectiveness !== undefined); + + const moveEffectivenessList = opponents.map((opponent) => { + return this.getMoveset().map((move) => { + return opponent.getMoveEffectiveness(this, move); + }); + }).flat().filter((effectiveness) => effectiveness !== undefined); + + const fullHints = typeHints === 2; + + if (fullHints && opponentMoveEffectivenessList.some((effectiveness) => effectiveness === 8)) { + return 'darkred'; + } else if (fullHints && opponentMoveEffectivenessList.some((effectiveness) => effectiveness === 4)) { + return 'red'; + } else if (fullHints && opponentMoveEffectivenessList.some((effectiveness) => effectiveness === 2)) { + return 'crimson'; + } else if (moveEffectivenessList.some((effectiveness) => effectiveness === 8)) { + return 'darkgreen'; + } else if (moveEffectivenessList.some((effectiveness) => effectiveness === 4)) { + return 'green'; + } else if (moveEffectivenessList.some((effectiveness) => effectiveness === 2)) { + return 'lightgreen'; + } else if (fullHints && opponentMoveEffectivenessList.every((effectiveness) => effectiveness === 0)) { + return 'yellow'; + } else if (fullHints && opponentMoveEffectivenessList.every((effectiveness) => effectiveness === 0.125)) { + return 'darkblue'; + } else if (fullHints && opponentMoveEffectivenessList.every((effectiveness) => effectiveness === 0.25)) { + return 'blue'; + } else if (fullHints && opponentMoveEffectivenessList.every((effectiveness) => effectiveness === 0.5)) { + return 'lightblue'; + } + + return 'white'; + } + toggleStats(visible: boolean): void { this.battleInfo.toggleStats(visible); } @@ -1526,11 +1590,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; const sourceTypes = source.getTypes(); const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type); + let stabMultiplier = new Utils.NumberHolder(1); - if (sourceTeraType === Type.UNKNOWN && matchesSourceType) - stabMultiplier.value += 0.5; - else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type) + if (source.isStabMove(move)) { stabMultiplier.value += 0.5; + } applyAbAttrs(StabBoostAbAttr, source, null, stabMultiplier); diff --git a/src/phases.ts b/src/phases.ts index 1137c85afa6..794a1d2d48f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1572,6 +1572,10 @@ export class CheckSwitchPhase extends BattlePhase { super.start(); const pokemon = this.scene.getPlayerField()[this.fieldIndex]; + + this.scene.getParty().forEach((pokemon) => { + pokemon.updateNameColor(); + }); if (this.scene.field.getAll().indexOf(pokemon) === -1) { this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); @@ -1651,6 +1655,10 @@ export class TurnInitPhase extends FieldPhase { } }); + this.scene.getParty().forEach((pokemon) => { + pokemon.updateNameColor(); + }); + this.scene.pushPhase(new TurnStartPhase(this.scene)); this.end(); diff --git a/src/system/settings.ts b/src/system/settings.ts index 15c1f19aa04..c9a7cff0c75 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -28,7 +28,8 @@ export enum Setting { Gamepad_Support = "GAMEPAD_SUPPORT", Swap_A_and_B = "SWAP_A_B", // Swaps which gamepad button handles ACTION and CANCEL Touch_Controls = "TOUCH_CONTROLS", - Vibration = "VIBRATION" + Vibration = "VIBRATION", + Type_Hints = "TYPE_HINTS" } export interface SettingOptions { @@ -61,7 +62,8 @@ export const settingOptions: SettingOptions = { [Setting.Gamepad_Support]: ['Auto', 'Disabled'], [Setting.Swap_A_and_B]: ['Enabled', 'Disabled'], [Setting.Touch_Controls]: ['Auto', 'Disabled'], - [Setting.Vibration]: ['Auto', 'Disabled'] + [Setting.Vibration]: ['Auto', 'Disabled'], + [Setting.Type_Hints]: ['Off', 'Partial', 'Full'] }; export const settingDefaults: SettingDefaults = { @@ -86,7 +88,8 @@ export const settingDefaults: SettingDefaults = { [Setting.Gamepad_Support]: 0, [Setting.Swap_A_and_B]: 1, // Set to 'Disabled' by default [Setting.Touch_Controls]: 0, - [Setting.Vibration]: 0 + [Setting.Vibration]: 0, + [Setting.Type_Hints]: 0, }; export const reloadSettings: Setting[] = [Setting.UI_Theme, Setting.Language, Setting.Sprite_Set]; @@ -171,6 +174,9 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) case Setting.Vibration: scene.enableVibration = settingOptions[setting][value] !== 'Disabled' && hasTouchscreen(); break; + case Setting.Type_Hints: + scene.typeHints = value; + break; case Setting.Language: if (value) { if (scene.ui) { diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 88bc3230ce3..43561641925 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -551,6 +551,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.lastName = pokemon.name; } + updateNameColor(color: string) { + this.nameText.setColor(color); + } + updatePokemonExp(pokemon: Pokemon, instant?: boolean, levelDurationMultiplier: number = 1): Promise { return new Promise(resolve => { const levelUp = this.lastLevel < pokemon.level; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 084337b4086..8f98055afca 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -1,6 +1,6 @@ import BattleScene from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; -import { Type } from "../data/type"; +import { Type, TypeDamageMultiplier } from "../data/type"; import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; @@ -9,6 +9,7 @@ import { CommandPhase } from "../phases"; import { MoveCategory } from "#app/data/move.js"; import i18next from '../plugins/i18n'; import {Button} from "../enums/buttons"; +import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -189,15 +190,79 @@ export default class FightUiHandler extends UiHandler { } displayMoves() { - const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset(); - for (let m = 0; m < 4; m++) { - const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, '-', TextStyle.WINDOW); - if (m < moveset.length) - moveText.setText(moveset[m].getName()); + const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const moveset = pokemon.getMoveset(); + + for (let moveIndex = 0; moveIndex < 4; moveIndex++) { + const moveText = addTextObject(this.scene, moveIndex % 2 === 0 ? 0 : 100, moveIndex < 2 ? 0 : 16, '-', TextStyle.WINDOW); + + if (moveIndex < moveset.length) { + const pokemonMove = moveset[moveIndex]; + moveText.setText(pokemonMove.getName()); + + if (this.scene.typeHints > 0) { + this.setTypeHints(pokemon, pokemonMove, moveText); + } + } + this.movesContainer.add(moveText); } } + private setTypeHints(pokemon: Pokemon, pokemonMove: PokemonMove, moveText: Phaser.GameObjects.Text) { + const opponents = pokemon.getOpponents(); + if (opponents.length < 1) return; + + const move = pokemonMove.getMove(); + const moveEffectivenessList = opponents.map((opponent) => opponent.getMoveEffectiveness(pokemon, pokemonMove)); + + let text = moveText.text; + const effectivenessTextList = moveEffectivenessList.map((effectiveness) => this.getMoveEffectivenessText(effectiveness)); + + if (effectivenessTextList.every((text) => text === effectivenessTextList[0])) { + if (moveEffectivenessList[0] !== 1) { + text += effectivenessTextList[0]; + } + } else { + moveEffectivenessList.forEach((effectiveness) => { + text += this.getMoveEffectivenessText(effectiveness); + }); + } + moveText.setText(text); + + const stab = pokemon.isStabMove(move); + if (stab) moveText.setFontStyle('bold'); + + const moveColors = moveEffectivenessList.sort((a, b) => b - a).map((effectiveness) => this.getMoveColor(effectiveness)); + moveText.setColor(moveColors[0]); + } + + private getMoveEffectivenessText(moveEffectiveness?: TypeDamageMultiplier): string { + if (moveEffectiveness === undefined) return ''; + return ` ${moveEffectiveness}x`; + } + + private getMoveColor(moveEffectiveness?: TypeDamageMultiplier): string { + switch (moveEffectiveness) { + case 0: + return 'black'; + case 0.125: + return 'darkred'; + case 0.25: + return 'red'; + case 0.5: + return 'crimson'; + case 2: + return 'lightgreen'; + case 4: + return 'green'; + case 8: + return 'darkgreen'; + } + + return 'white'; + } + clear() { super.clear(); this.clearMoves(); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 8b497655a17..0886dbda1a1 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -870,6 +870,9 @@ class PartySlot extends Phaser.GameObjects.Container { slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10); slotName.setOrigin(0, 0); + const color = this.pokemon.getNameColor(); + if (color !== undefined) slotName.setColor(color); + const slotLevelLabel = this.scene.add.image(0, 0, 'party_slot_overlay_lv'); slotLevelLabel.setPositionRelative(slotName, 8, 12); slotLevelLabel.setOrigin(0, 0);