From 49e6126c511d09136fbdc3ac3b9f9293467aa4cb Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 26 Apr 2024 23:40:23 +0200 Subject: [PATCH] feat: implemented move effectiveness color --- package-lock.json | 4 +- src/battle-scene.ts | 6 +- src/field/pokemon.ts | 3 +- src/phases.ts | 13 ++-- src/system/settings.ts | 6 ++ src/ui/fight-ui-handler.ts | 98 ++++++++++++++++++++++++------ src/ui/target-select-ui-handler.ts | 77 ++++++++++++++++++++--- src/ui/text.ts | 18 +++++- src/ui/ui.ts | 4 ++ 9 files changed, 193 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5521dcc86a8..ecaadf378ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.0.1", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.0.1", + "version": "1.0.3", "dependencies": { "@material/material-color-utilities": "^0.2.7", "crypto-js": "^4.2.0", diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 73fa000d840..e6ef7bbb541 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -122,6 +122,7 @@ export default class BattleScene extends SceneBase { public bgmVolume: number = 1; public seVolume: number = 1; public gameSpeed: integer = 1; + public showEffectiveness: boolean = false; public damageNumbersMode: integer = 0; public showLevelUpStats: boolean = true; public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; @@ -172,6 +173,8 @@ export default class BattleScene extends SceneBase { public pokeballCounts: PokeballCounts; public money: integer; public pokemonInfoContainer: PokemonInfoContainer; + public selectedTarget: Pokemon; + public newEncounter: boolean = true; private party: PlayerPokemon[]; private waveCountText: Phaser.GameObjects.Text; private moneyText: Phaser.GameObjects.Text; @@ -1423,7 +1426,8 @@ export default class BattleScene extends SceneBase { if (this.ui?.getMode() === Mode.SETTINGS) (this.ui.getHandler() as SettingsUiHandler).show([]); } - } else + } + else return; if (inputSuccess && this.enableVibration && typeof navigator.vibrate !== 'undefined') navigator.vibrate(vibrationLength || 10); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index af97a4e65db..0ea5a997ad6 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -696,12 +696,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const ret = !ignoreOverride && this.summonData?.moveset ? this.summonData.moveset : this.moveset; - if (MOVE_OVERRIDE && this.isPlayer()) this.moveset[0] = new PokemonMove(MOVE_OVERRIDE, Math.min(this.moveset[0].ppUsed, allMoves[MOVE_OVERRIDE].pp)); else if (OPP_MOVE_OVERRIDE && !this.isPlayer()) this.moveset[0] = new PokemonMove(OPP_MOVE_OVERRIDE, Math.min(this.moveset[0].ppUsed, allMoves[OPP_MOVE_OVERRIDE].pp)); - + return ret; } diff --git a/src/phases.ts b/src/phases.ts index 535a6c2b2c3..05b514aaa1c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -666,7 +666,6 @@ export class EncounterPhase extends BattlePhase { constructor(scene: BattleScene, loaded?: boolean) { super(scene); - this.loaded = !!loaded; } @@ -674,6 +673,7 @@ export class EncounterPhase extends BattlePhase { super.start(); this.scene.initSession(); + this.scene.newEncounter = true; const loadEnemyAssets = []; @@ -1680,8 +1680,10 @@ export class CommandPhase extends FieldPhase { console.log(moveTargets, playerPokemon.name); if (moveTargets.targets.length <= 1 || moveTargets.multiple) turnCommand.move.targets = moveTargets.targets; - else - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + else{ + this.scene.ui.clearText(); + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; success = true; } else if (cursor < playerPokemon.getMoveset().length) { @@ -1902,16 +1904,18 @@ export class SelectTargetPhase extends PokemonPhase { start() { super.start(); - + const turnCommand = this.scene.currentBattle.turnCommands[this.fieldIndex]; const move = turnCommand.move?.move; this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (cursor: integer) => { + turnCommand.targets = [ cursor ]; this.scene.ui.setMode(Mode.MESSAGE); if (cursor === -1) { this.scene.currentBattle.turnCommands[this.fieldIndex] = null; this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); } else turnCommand.targets = [ cursor ]; + this.scene.newEncounter=false; if (turnCommand.command === Command.BALL && this.fieldIndex) this.scene.currentBattle.turnCommands[this.fieldIndex - 1].skip = true; this.end(); @@ -3210,6 +3214,7 @@ export class VictoryPhase extends PokemonPhase { const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; const expPartyMembers = party.filter(p => p.hp && p.level < this.scene.getMaxExpLevel()); const partyMemberExp = []; + this.scene.selectedTarget=null; if (participantIds.size) { let expValue = this.getPokemon().getExpValue(); diff --git a/src/system/settings.ts b/src/system/settings.ts index df4f894c949..47077531596 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -12,6 +12,7 @@ export enum Setting { BGM_Volume = "BGM_VOLUME", SE_Volume = "SE_VOLUME", Language = "LANGUAGE", + Move_Effectiveness = "MOVE_EFFECTIVENESS", Damage_Numbers = "DAMAGE_NUMBERS", UI_Theme = "UI_THEME", Window_Type = "WINDOW_TYPE", @@ -43,6 +44,7 @@ export const settingOptions: SettingOptions = { [Setting.BGM_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), [Setting.SE_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), [Setting.Language]: [ 'English', 'Change' ], + [Setting.Move_Effectiveness]:['Off','On'], [Setting.Damage_Numbers]: [ 'Off', 'Simple', 'Fancy' ], [Setting.UI_Theme]: [ 'Default', 'Legacy' ], [Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()), @@ -66,6 +68,7 @@ export const settingDefaults: SettingDefaults = { [Setting.BGM_Volume]: 10, [Setting.SE_Volume]: 10, [Setting.Language]: 0, + [Setting.Move_Effectiveness]:0, [Setting.Damage_Numbers]: 0, [Setting.UI_Theme]: 0, [Setting.Window_Type]: 0, @@ -102,6 +105,9 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) scene.seVolume = value ? parseInt(settingOptions[setting][value]) * 0.01 : 0; scene.updateSoundVolume(); break; + case Setting.Move_Effectiveness: + scene.showEffectiveness = settingOptions[setting][value] === 'On'; + break; case Setting.Damage_Numbers: scene.damageNumbersMode = value; break; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 1a7a8bef597..b5a6b754ea5 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -8,6 +8,8 @@ import * as Utils from "../utils"; import { CommandPhase } from "../phases"; import { MoveCategory } from "#app/data/move.js"; import i18next from '../plugins/i18n'; +import Pokemon from "../field/pokemon.js"; +import { Moves } from "../data/enums/moves.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -72,6 +74,19 @@ export default class FightUiHandler extends UiHandler { messageHandler.commandWindow.setVisible(false); messageHandler.movesWindowContainer.setVisible(true); this.setCursor(this.getCursor()); + this.displayMoves(this.scene.selectedTarget); + + return true; + } + + showTargettedMoves(args: any[]): boolean { + super.show(args); + + this.fieldIndex = args.length ? args[0] as integer : 0; + + const messageHandler = this.getUi().getMessageHandler(); + messageHandler.commandWindow.setVisible(false); + messageHandler.movesWindowContainer.setVisible(true); this.displayMoves(); return true; @@ -141,41 +156,63 @@ export default class FightUiHandler extends UiHandler { ui.add(this.cursorObj); } + const activePokemon =(this.scene.getCurrentPhase() as CommandPhase).getPokemon() const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset(); - const hasMove = cursor < moveset.length; - if (hasMove) { - const pokemonMove = moveset[cursor]; - this.typeIcon.setTexture('types', Type[pokemonMove.getMove().type].toLowerCase()).setScale(0.8); - this.moveCategoryIcon.setTexture('categories', MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); - - const power = pokemonMove.getMove().power; - const maxPP = pokemonMove.getMovePp(); - const pp = maxPP - pokemonMove.ppUsed; - - this.ppText.setText(`${Utils.padInt(pp, 2, ' ')}/${Utils.padInt(maxPP, 2, ' ')}`); - this.powerText.setText(`${power >= 0 ? power : '---'}`); - } - this.typeIcon.setVisible(hasMove); this.ppLabel.setVisible(hasMove); this.ppText.setVisible(hasMove); this.powerLabel.setVisible(hasMove); this.powerText.setVisible(hasMove); this.moveCategoryIcon.setVisible(hasMove); - this.cursorObj.setPosition(13 + (cursor % 2 === 1 ? 100 : 0), -31 + (cursor >= 2 ? 15 : 0)); - + + if (hasMove) { + this.updateMovesWindowContainer(activePokemon,cursor) + } return changed; } - displayMoves() { - const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset(); + displayMoves(pokemon?: Pokemon,move?:Moves) { + const actingPokemon =(this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const targetPokemon = pokemon || this.scene.getEnemyPokemon(); + this.setMoveColor(targetPokemon === actingPokemon ? this.scene.getEnemyPokemon() : targetPokemon, actingPokemon,move); + } + + setMoveColor(targetPokemon:Pokemon,actingPokemon:Pokemon,move?:Moves){ + const moveset = actingPokemon.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) + + this.typeIcon.setVisible(true); + this.ppText.setVisible(true); + this.moveCategoryIcon.setVisible(true); + + if (m < moveset.length){ + const pokemonMove = moveset[m]; + if (pokemonMove.moveId===move) { + this.updateMovesWindowContainer(actingPokemon,m) + } + moveText.setText(moveset[m].getName()); + const effectiveness = (targetPokemon.getAttackMoveEffectiveness(targetPokemon,pokemonMove)) + + if (this.scene.showEffectiveness) { + if (effectiveness === 0) { + moveText.setColor(this.getTextColor(TextStyle.ZERO_X_EFFECT)); // No effect + } else if (effectiveness === 4) { + moveText.setColor(this.getTextColor(TextStyle.FOUR_X_EFFECT)); // x4 Super effective + } else if (effectiveness === 2) { + moveText.setColor(this.getTextColor(TextStyle.TWO_X_EFFECT)); // x2 effective + } else if (effectiveness === 0.5) { + moveText.setColor(this.getTextColor(TextStyle.HALF_X_EFFECT)); // x0.5 Not very effective + } else if (effectiveness === 0.25) { + moveText.setColor(this.getTextColor(TextStyle.QUARTER_X_EFFECT)); // x0.25 Not very effective + } + } + + } this.movesContainer.add(moveText); } } @@ -201,4 +238,27 @@ export default class FightUiHandler extends UiHandler { this.cursorObj.destroy(); this.cursorObj = null; } + + setVisible(){ + this.typeIcon.setVisible(true); + this.ppLabel.setVisible(true); + this.ppText.setVisible(true); + this.powerLabel.setVisible(true); + this.powerText.setVisible(true); + this.moveCategoryIcon.setVisible(true); + } + + updateMovesWindowContainer(actingPokemon:Pokemon,m:integer){ + const pokemonMove = actingPokemon.getMoveset()[m]; + this.typeIcon.setTexture('types', Type[pokemonMove.getMove().type].toLowerCase()).setScale(0.8); + this.moveCategoryIcon.setTexture('categories', MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); + + const power = pokemonMove.getMove().power; + const maxPP = pokemonMove.getMovePp(); + const pp = maxPP - pokemonMove.ppUsed; + + this.ppText.setText(`${Utils.padInt(pp, 2, ' ')}/${Utils.padInt(maxPP, 2, ' ')}`); + this.powerText.setText(`${power >= 0 ? power : '---'}`); + } + } \ No newline at end of file diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index f8a7c9d28a3..77355925f3e 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -5,24 +5,37 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; import { getMoveTargets } from "../data/move"; +import FightUiHandler from "./fight-ui-handler"; +import { CommandPhase } from "../phases.js"; +import Pokemon from "../field/pokemon.js"; export type TargetSelectCallback = (cursor: integer) => void; export default class TargetSelectUiHandler extends UiHandler { - private fieldIndex: integer; private move: Moves; private targetSelectCallback: TargetSelectCallback; + private fightUiHandler: FightUiHandler; + private cursorObj: Phaser.GameObjects.Image; + protected fieldIndex: integer; + protected cursor2: integer; private targets: BattlerIndex[]; private targetFlashTween: Phaser.Tweens.Tween; constructor(scene: BattleScene) { super(scene, Mode.TARGET_SELECT); - this.cursor = -1; } - setup(): void { } + setup(): void { + const ui = this.getUi(); + + if (!this.cursorObj) { + this.cursorObj = this.scene.add.image(0, 0, 'cursor'); + this.cursorObj.setVisible(false) + ui.add(this.cursorObj); + } + } show(args: any[]): boolean { if (args.length < 3) @@ -32,23 +45,53 @@ export default class TargetSelectUiHandler extends UiHandler { this.fieldIndex = args[0] as integer; this.move = args[1] as Moves; - this.targetSelectCallback = args[2] as TargetSelectCallback; + this.targetSelectCallback = args[2]; + + if (this.scene.newEncounter===true){ + this.cursor=-1 + } this.targets = getMoveTargets(this.scene.getPlayerField()[this.fieldIndex], this.move).targets; + const messageHandler = this.getUi().getMessageHandler(); + messageHandler.movesWindowContainer.setVisible(true); + + this.fightUiHandler = this.getUi().getFightUiHandler(); + this.fightUiHandler.setVisible(); + this.displayCursor() + if (!this.targets.length) return false; this.setCursor(this.targets.indexOf(this.cursor) > -1 ? this.cursor : this.targets[0]); + const target = this.scene.getField()[this.cursor] + this.showTargetEffectiveness(target); return true; } + showTargetEffectiveness(target:Pokemon){ + const fieldPokemon = this.scene.getField(); + // Create a mapping from Pokemon name to action + const actionMap = fieldPokemon.reduce((map, pokemon, index) => { + map[pokemon.name] = () => { + this.clearMoves(); + this.scene.selectedTarget = fieldPokemon[index]; + this.fightUiHandler.displayMoves(fieldPokemon[index], this.move); + }; + return map; + }, {}); + + if (actionMap[target.name]) { + actionMap[target.name](); + } + } + processInput(button: Button): boolean { const ui = this.getUi(); let success = false; - + if (button === Button.ACTION || button === Button.CANCEL) { this.targetSelectCallback(button === Button.ACTION ? this.cursor : -1); success = true; @@ -81,7 +124,7 @@ export default class TargetSelectUiHandler extends UiHandler { setCursor(cursor: integer): boolean { const lastCursor = this.cursor; - + const ret = super.setCursor(cursor); if (this.targetFlashTween) { @@ -92,7 +135,8 @@ export default class TargetSelectUiHandler extends UiHandler { } const target = this.scene.getField()[cursor]; - + this.showTargetEffectiveness(target); + this.targetFlashTween = this.scene.tweens.add({ targets: [ target ], alpha: 0, @@ -122,5 +166,24 @@ export default class TargetSelectUiHandler extends UiHandler { clear() { super.clear(); this.eraseCursor(); + this.clearMoves(); + this.fightUiHandler.clear() + this.cursorObj.setVisible(false) + } + + displayCursor() { + const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset(); + + for (let m = 0; m < moveset.length; m++) { + const pokemonMove = moveset[m]; + if (pokemonMove.moveId===this.move) { + this.cursorObj.setPosition(13 + (m % 2 === 1 ? 100 : 0), -31 + (m >= 2 ? 15 : 0)); + this.cursorObj.setVisible(true) + } + } + } + + clearMoves() { + this.fightUiHandler.clearMoves() } } \ No newline at end of file diff --git a/src/ui/text.ts b/src/ui/text.ts index bfb02d06e27..31cd1f22b39 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -23,7 +23,12 @@ export enum TextStyle { SETTINGS_LABEL, SETTINGS_SELECTED, TOOLTIP_TITLE, - TOOLTIP_CONTENT + TOOLTIP_CONTENT, + ZERO_X_EFFECT, + FOUR_X_EFFECT, + TWO_X_EFFECT, + HALF_X_EFFECT, + QUARTER_X_EFFECT }; export function addTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): Phaser.GameObjects.Text { @@ -164,6 +169,17 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui return !shadow ? '#f8b050' : '#c07800'; case TextStyle.SETTINGS_SELECTED: return !shadow ? '#f88880' : '#f83018'; + case TextStyle.ZERO_X_EFFECT: + return !shadow ? '#a0a0a0' : '#6b5a73'; + case TextStyle.FOUR_X_EFFECT: + return !shadow ? '#2ecc71' : '#6b5a73'; + case TextStyle.TWO_X_EFFECT: + return !shadow ? '#229954' : '#6b5a73'; + case TextStyle.HALF_X_EFFECT: + return !shadow ? '#922b21' : '#6b5a73'; + case TextStyle.QUARTER_X_EFFECT: + return !shadow ? '#e74c3c' : '#6b5a73'; + } } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 6e20b2cb8b8..d813c61ab83 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -198,6 +198,10 @@ export default class UI extends Phaser.GameObjects.Container { return this.handlers[Mode.MESSAGE] as BattleMessageUiHandler; } + getFightUiHandler(): FightUiHandler { + return this.handlers[Mode.FIGHT] as FightUiHandler; + } + processInput(button: Button): boolean { if (this.overlayActive) return false;