Add setting for showing type effectiveness hints (#1061)
* type hints * fix overwritten change * don't set color to white, just leave it unchanged * remove unrelated code * don't show hints if no opponents, use type effectiveness instead of move effectiveness * fix color not going back to white when new opponent is sent * move effectiveness to move info container * add effectiveness overlay, partial hints only show move effectiveness, improve colors * lint * docs * remove full hints, move container to right of enemy info box * hide effectiveness while flyout is visible * move setting to display, use default style color instead of white
This commit is contained in:
parent
5e5ece868c
commit
3f9eaf4a5d
|
@ -155,6 +155,13 @@ export default class BattleScene extends SceneBase {
|
|||
*/
|
||||
public battleStyle: integer = 0;
|
||||
|
||||
/**
|
||||
* Defines whether or not to show type effectiveness hints
|
||||
* - true: No hints
|
||||
* - false: Show hints for moves
|
||||
*/
|
||||
public typeHints: boolean = false;
|
||||
|
||||
public disableMenu: boolean = false;
|
||||
|
||||
public gameData: GameData;
|
||||
|
|
|
@ -501,6 +501,52 @@ export function getTypeDamageMultiplier(attackType: integer, defType: integer):
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the color corresponding to a specific damage multiplier
|
||||
* @returns A color or undefined if the default color should be used
|
||||
*/
|
||||
export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined {
|
||||
if (side === "offense") {
|
||||
switch (multiplier) {
|
||||
case 0:
|
||||
return "#929292";
|
||||
case 0.125:
|
||||
return "#FF5500";
|
||||
case 0.25:
|
||||
return "#FF7400";
|
||||
case 0.5:
|
||||
return "#FE8E00";
|
||||
case 1:
|
||||
return undefined;
|
||||
case 2:
|
||||
return "#4AA500";
|
||||
case 4:
|
||||
return "#4BB400";
|
||||
case 8:
|
||||
return "#52C200";
|
||||
}
|
||||
} else if (side === "defense") {
|
||||
switch (multiplier) {
|
||||
case 0:
|
||||
return "#B1B100";
|
||||
case 0.125:
|
||||
return "#2DB4FF";
|
||||
case 0.25:
|
||||
return "#00A4FF";
|
||||
case 0.5:
|
||||
return "#0093FF";
|
||||
case 1:
|
||||
return undefined;
|
||||
case 2:
|
||||
return "#FE8E00";
|
||||
case 4:
|
||||
return "#FF7400";
|
||||
case 8:
|
||||
return "#FF5500";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getTypeRgb(type: Type): [ integer, integer, integer ] {
|
||||
switch (type) {
|
||||
case Type.NORMAL:
|
||||
|
|
|
@ -1069,6 +1069,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
return !this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The type damage multiplier or undefined if it's a status move
|
||||
*/
|
||||
getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined {
|
||||
if (move.getMove().category === MoveCategory.STATUS) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.getAttackMoveEffectiveness(source, move);
|
||||
}
|
||||
|
||||
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove): TypeDamageMultiplier {
|
||||
const move = pokemonMove.getMove();
|
||||
const typeless = move.hasAttr(TypelessAttr);
|
||||
|
@ -1588,11 +1599,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
return this.battleInfo.updateInfo(this, instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide the type effectiveness multiplier window
|
||||
* Passing undefined will hide the window
|
||||
*/
|
||||
updateEffectiveness(effectiveness?: string) {
|
||||
this.battleInfo.updateEffectiveness(effectiveness);
|
||||
}
|
||||
|
||||
toggleStats(visible: boolean): void {
|
||||
this.battleInfo.toggleStats(visible);
|
||||
}
|
||||
|
||||
toggleFlyout(visible: boolean): void {
|
||||
this.battleInfo.flyoutMenu?.toggleFlyout(visible);
|
||||
this.battleInfo.toggleFlyout(visible);
|
||||
}
|
||||
|
||||
addExp(exp: integer) {
|
||||
|
|
|
@ -64,6 +64,7 @@ export const SettingKeys = {
|
|||
Sprite_Set: "SPRITE_SET",
|
||||
Fusion_Palette_Swaps: "FUSION_PALETTE_SWAPS",
|
||||
Player_Gender: "PLAYER_GENDER",
|
||||
Type_Hints: "TYPE_HINTS",
|
||||
Master_Volume: "MASTER_VOLUME",
|
||||
BGM_Volume: "BGM_VOLUME",
|
||||
SE_Volume: "SE_VOLUME",
|
||||
|
@ -268,6 +269,13 @@ export const Setting: Array<Setting> = [
|
|||
default: 0,
|
||||
type: SettingType.DISPLAY
|
||||
},
|
||||
{
|
||||
key: SettingKeys.Type_Hints,
|
||||
label: "Type hints",
|
||||
options: OFF_ON,
|
||||
default: 0,
|
||||
type: SettingType.DISPLAY
|
||||
},
|
||||
{
|
||||
key: SettingKeys.Master_Volume,
|
||||
label: "Master Volume",
|
||||
|
@ -447,6 +455,9 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
|
|||
case SettingKeys.Vibration:
|
||||
scene.enableVibration = Setting[index].options[value] !== "Disabled" && hasTouchscreen();
|
||||
break;
|
||||
case SettingKeys.Type_Hints:
|
||||
scene.typeHints = Setting[index].options[value] === "On";
|
||||
break;
|
||||
case SettingKeys.Language:
|
||||
if (value) {
|
||||
if (scene.ui) {
|
||||
|
|
|
@ -55,6 +55,9 @@ export default class BattleFlyout extends Phaser.GameObjects.Container {
|
|||
/** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */
|
||||
private moveInfo: MoveInfo[] = new Array();
|
||||
|
||||
/** Current state of the flyout's visibility */
|
||||
public flyoutVisible: boolean = false;
|
||||
|
||||
// Stores callbacks in a variable so they can be unsubscribed from when destroyed
|
||||
private readonly onMoveUsedEvent = (event: Event) => this.onMoveUsed(event);
|
||||
private readonly onBerryUsedEvent = (event: Event) => this.onBerryUsed(event);
|
||||
|
@ -170,6 +173,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container {
|
|||
|
||||
/** Animates the flyout to either show or hide it by applying a fade and translation */
|
||||
toggleFlyout(visible: boolean): void {
|
||||
this.flyoutVisible = visible;
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this.flyoutParent,
|
||||
x: visible ? this.anchorX : this.anchorX - this.translationX,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Type, getTypeRgb } from "../data/type";
|
|||
import { getVariantTint } from "#app/data/variant";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import BattleFlyout from "./battle-flyout";
|
||||
import { WindowVariant, addWindow } from "./ui-theme";
|
||||
|
||||
const battleStatOrder = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD ];
|
||||
|
||||
|
@ -52,6 +53,13 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
private type3Icon: Phaser.GameObjects.Sprite;
|
||||
private expBar: Phaser.GameObjects.Image;
|
||||
|
||||
// #region Type effectiveness hint objects
|
||||
private effectivenessContainer: Phaser.GameObjects.Container;
|
||||
private effectivenessWindow: Phaser.GameObjects.NineSlice;
|
||||
private effectivenessText: Phaser.GameObjects.Text;
|
||||
private currentEffectiveness?: string;
|
||||
// #endregion
|
||||
|
||||
public expMaskRect: Phaser.GameObjects.Graphics;
|
||||
|
||||
private statsContainer: Phaser.GameObjects.Container;
|
||||
|
@ -59,7 +67,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
private statValuesContainer: Phaser.GameObjects.Container;
|
||||
private statNumbers: Phaser.GameObjects.Sprite[];
|
||||
|
||||
public flyoutMenu: BattleFlyout;
|
||||
public flyoutMenu?: BattleFlyout;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
|
||||
super(scene, x, y);
|
||||
|
@ -250,6 +258,19 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
this.type3Icon.setName("icon_type_3");
|
||||
this.type3Icon.setOrigin(0, 0);
|
||||
this.add(this.type3Icon);
|
||||
|
||||
if (!this.player) {
|
||||
this.effectivenessContainer = this.scene.add.container(0, 0);
|
||||
this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4);
|
||||
this.effectivenessContainer.setVisible(false);
|
||||
this.add(this.effectivenessContainer);
|
||||
|
||||
this.effectivenessText = addTextObject(this.scene, 5, 4.5, "", TextStyle.BATTLE_INFO);
|
||||
this.effectivenessWindow = addWindow((this.scene as BattleScene), 0, 0, 0, 20, false, false, null, null, WindowVariant.XTHIN);
|
||||
|
||||
this.effectivenessContainer.add(this.effectivenessWindow);
|
||||
this.effectivenessContainer.add(this.effectivenessText);
|
||||
}
|
||||
}
|
||||
|
||||
initInfo(pokemon: Pokemon) {
|
||||
|
@ -711,6 +732,39 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary
|
||||
*/
|
||||
toggleFlyout(visible: boolean): void {
|
||||
this.flyoutMenu?.toggleFlyout(visible);
|
||||
|
||||
if (visible) {
|
||||
this.effectivenessContainer?.setVisible(false);
|
||||
} else {
|
||||
this.updateEffectiveness(this.currentEffectiveness);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide the type effectiveness multiplier window
|
||||
* Passing undefined will hide the window
|
||||
*/
|
||||
updateEffectiveness(effectiveness?: string) {
|
||||
if (this.player) {
|
||||
return;
|
||||
}
|
||||
this.currentEffectiveness = effectiveness;
|
||||
|
||||
if (!(this.scene as BattleScene).typeHints || effectiveness === undefined || this.flyoutMenu.flyoutVisible) {
|
||||
this.effectivenessContainer.setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.effectivenessText.setText(effectiveness);
|
||||
this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth;
|
||||
this.effectivenessContainer.setVisible(true);
|
||||
}
|
||||
|
||||
getBaseY(): number {
|
||||
return this.baseY;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import BattleScene from "../battle-scene";
|
||||
import { addTextObject, TextStyle } from "./text";
|
||||
import { Type } from "../data/type";
|
||||
import { getTypeDamageMultiplierColor, Type } 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;
|
||||
|
@ -162,7 +163,8 @@ export default class FightUiHandler extends UiHandler {
|
|||
ui.add(this.cursorObj);
|
||||
}
|
||||
|
||||
const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
|
||||
const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon();
|
||||
const moveset = pokemon.getMoveset();
|
||||
|
||||
const hasMove = cursor < moveset.length;
|
||||
|
||||
|
@ -179,6 +181,10 @@ export default class FightUiHandler extends UiHandler {
|
|||
this.ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`);
|
||||
this.powerText.setText(`${power >= 0 ? power : "---"}`);
|
||||
this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`);
|
||||
|
||||
pokemon.getOpponents().forEach((opponent) => {
|
||||
opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove));
|
||||
});
|
||||
}
|
||||
|
||||
this.typeIcon.setVisible(hasMove);
|
||||
|
@ -195,17 +201,60 @@ export default class FightUiHandler extends UiHandler {
|
|||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets multiplier text for a pokemon's move against a specific opponent
|
||||
* Returns undefined if it's a status move
|
||||
*/
|
||||
private getEffectivenessText(pokemon: Pokemon, opponent: Pokemon, pokemonMove: PokemonMove): string | undefined {
|
||||
const effectiveness = opponent.getMoveEffectiveness(pokemon, pokemonMove);
|
||||
if (effectiveness === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `${effectiveness}x`;
|
||||
}
|
||||
|
||||
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());
|
||||
moveText.setColor(this.getMoveColor(pokemon, pokemonMove) ?? moveText.style.color);
|
||||
}
|
||||
|
||||
this.movesContainer.add(moveText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific move's color based on its type effectiveness against opponents
|
||||
* If there are multiple opponents, the highest effectiveness' color is returned
|
||||
* @returns A color or undefined if the default color should be used
|
||||
*/
|
||||
private getMoveColor(pokemon: Pokemon, pokemonMove: PokemonMove): string | undefined {
|
||||
if (!this.scene.typeHints) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const opponents = pokemon.getOpponents();
|
||||
if (opponents.length <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const moveColors = opponents.map((opponent) => {
|
||||
return opponent.getMoveEffectiveness(pokemon, pokemonMove);
|
||||
}).sort((a, b) => b - a).map((effectiveness) => {
|
||||
return getTypeDamageMultiplierColor(effectiveness, "offense");
|
||||
});
|
||||
|
||||
return moveColors[0];
|
||||
}
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
this.clearMoves();
|
||||
|
@ -222,6 +271,11 @@ export default class FightUiHandler extends UiHandler {
|
|||
|
||||
clearMoves() {
|
||||
this.movesContainer.removeAll(true);
|
||||
|
||||
const opponents = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents();
|
||||
opponents.forEach((opponent) => {
|
||||
opponent.updateEffectiveness(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
eraseCursor() {
|
||||
|
|
Loading…
Reference in New Issue