diff --git a/public/images/ui/biome_select_window_3.png b/public/images/ui/biome_select_window_3.png deleted file mode 100644 index e8dc0459e67..00000000000 Binary files a/public/images/ui/biome_select_window_3.png and /dev/null differ diff --git a/public/images/ui/option_select_window_1.png b/public/images/ui/option_select_window_1.png new file mode 100644 index 00000000000..429be5b2958 Binary files /dev/null and b/public/images/ui/option_select_window_1.png differ diff --git a/public/images/ui/biome_select_window_2.png b/public/images/ui/option_select_window_2.png similarity index 58% rename from public/images/ui/biome_select_window_2.png rename to public/images/ui/option_select_window_2.png index f3c66d1286a..c0ca520cd1b 100644 Binary files a/public/images/ui/biome_select_window_2.png and b/public/images/ui/option_select_window_2.png differ diff --git a/public/images/ui/option_select_window_3.png b/public/images/ui/option_select_window_3.png new file mode 100644 index 00000000000..d10d044a850 Binary files /dev/null and b/public/images/ui/option_select_window_3.png differ diff --git a/public/images/ui/party_message_options_wide.png b/public/images/ui/party_message_options_wide.png new file mode 100644 index 00000000000..fba84a08bc6 Binary files /dev/null and b/public/images/ui/party_message_options_wide.png differ diff --git a/public/images/ui/party_options_wide_bottom.png b/public/images/ui/party_options_wide_bottom.png new file mode 100644 index 00000000000..42a993b89f2 Binary files /dev/null and b/public/images/ui/party_options_wide_bottom.png differ diff --git a/public/images/ui/party_options_wide_center.png b/public/images/ui/party_options_wide_center.png new file mode 100644 index 00000000000..ef75c43fa27 Binary files /dev/null and b/public/images/ui/party_options_wide_center.png differ diff --git a/public/images/ui/party_options_wide_top.png b/public/images/ui/party_options_wide_top.png new file mode 100644 index 00000000000..f392b041d2f Binary files /dev/null and b/public/images/ui/party_options_wide_top.png differ diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 9d56952f332..9bfbf68d0e5 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -16,7 +16,7 @@ import { EvolutionPhase } from "./evolution-phase"; import { BattlePhase } from "./battle-phase"; import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat"; import { Biome, biomeLinks } from "./data/biome"; -import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getPlayerModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; +import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getEnemyModifierTypesForWave, getPlayerModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { BattleTagLapseType, BattleTagType, HideSpriteTag as HiddenTag } from "./data/battle-tag"; import { getPokemonMessage } from "./messages"; @@ -118,7 +118,7 @@ export class EncounterPhase extends BattlePhase { if (this.scene.getEnemyPokemon().shiny) this.scene.unshiftPhase(new ShinySparklePhase(this.scene, false)); - // TODO: Remove + // TODO: Remove //this.scene.unshiftPhase(new SelectModifierPhase(this.scene)); super.end(); @@ -1838,6 +1838,37 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.setMode(Mode.MESSAGE); super.end(); return; + } else if (cursor >= typeOptions.length) { + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, (fromSlotIndex: integer, itemIndex: integer, toSlotIndex: integer) => { + if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { + this.scene.ui.setMode(Mode.MODIFIER_SELECT); + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; + const itemModifier = itemModifiers[itemIndex]; + const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; + newItemModifier.pokemonId = party[toSlotIndex].id; + const matchingModifier = party[toSlotIndex].scene.findModifier(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).matchType(itemModifier)) as PokemonHeldItemModifier; + let removeOld = true; + if (matchingModifier) { + const newStackCount = matchingModifier.stackCount + itemModifier.stackCount; + const maxStackCount = matchingModifier.getMaxStackCount(); + if (newStackCount > maxStackCount) { + itemModifier.stackCount = newStackCount - maxStackCount; + newItemModifier.stackCount = maxStackCount; + removeOld = !itemModifier.stackCount; + } + } + if (!removeOld || this.scene.removeModifier(itemModifier)) { + this.scene.addModifier(newItemModifier, true).then(() => super.end()); + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE); + return; + } + } + this.scene.ui.setMode(Mode.MODIFIER_SELECT, typeOptions, modifierSelectCallback); + }, PartyUiHandler.FilterItemMaxStacks); + return; } const modifierType = typeOptions[cursor].type; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5d9fb9e9841..a13138cf138 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -21,6 +21,7 @@ import { Moves } from './data/move'; import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; const enableAuto = true; +const quickStart = false; export const startingLevel = 5; export const startingWave = 1; export const startingBiome = Biome.PLAINS; @@ -45,7 +46,7 @@ export enum Button { export default class BattleScene extends Phaser.Scene { public auto: boolean; public gameSpeed: integer = 1; - public quickStart: boolean; + public quickStart: boolean = quickStart; public gameData: GameData; @@ -160,9 +161,13 @@ export default class BattleScene extends Phaser.Scene { this.loadImage('party_message', 'ui'); this.loadImage('party_message_large', 'ui'); this.loadImage('party_message_options', 'ui'); + this.loadImage('party_message_options_wide', 'ui'); this.loadImage('party_options_top', 'ui'); this.loadImage('party_options_center', 'ui'); this.loadImage('party_options_bottom', 'ui'); + this.loadImage('party_options_wide_top', 'ui'); + this.loadImage('party_options_wide_center', 'ui'); + this.loadImage('party_options_wide_bottom', 'ui'); this.loadAtlas('party_cancel', 'ui'); this.loadImage('summary_bg', 'ui'); @@ -174,8 +179,8 @@ export default class BattleScene extends Phaser.Scene { this.loadImage('summary_moves_overlay_pp', 'ui'); this.loadAtlas('summary_moves_cursor', 'ui'); - this.loadImage('biome_select_window_2', 'ui'); - this.loadImage('biome_select_window_3', 'ui'); + for (let o = 1; o <= 3; o++) + this.loadImage(`option_select_window_${o}`, 'ui'); this.loadImage('starter_select_bg', 'ui'); this.loadImage('starter_select_message', 'ui'); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 7cff27b20b2..34b19f5415a 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -69,16 +69,16 @@ export abstract class PersistentModifier extends Modifier { public stackCount: integer; public virtualStackCount: integer; - constructor(type: ModifierType) { + constructor(type: ModifierType, stackCount: integer) { super(type); - this.stackCount = 1; + this.stackCount = stackCount === undefined ? 1 : stackCount; this.virtualStackCount = 0; } add(modifiers: PersistentModifier[], virtual: boolean): boolean { for (let modifier of modifiers) { if (this.match(modifier)) - return modifier.incrementStack(virtual); + return modifier.incrementStack(modifier.stackCount, virtual); } if (virtual) { @@ -91,12 +91,12 @@ export abstract class PersistentModifier extends Modifier { abstract clone(): PersistentModifier; - incrementStack(virtual: boolean): boolean { - if (this.getStackCount() < this.getMaxStackCount()) { + incrementStack(amount: integer, virtual: boolean): boolean { + if (this.getStackCount() + amount <= this.getMaxStackCount()) { if (!virtual) - this.stackCount++; + this.stackCount += amount; else - this.virtualStackCount++; + this.virtualStackCount += amount; return true; } @@ -193,15 +193,15 @@ export class TempBattleStatBoosterModifier extends PersistentModifier { private tempBattleStat: TempBattleStat; private battlesLeft: integer; - constructor(type: ModifierTypes.TempBattleStatBoosterModifierType, tempBattleStat: TempBattleStat) { - super(type); + constructor(type: ModifierTypes.TempBattleStatBoosterModifierType, tempBattleStat: TempBattleStat, stackCount?: integer) { + super(type, stackCount); this.tempBattleStat = tempBattleStat; this.battlesLeft = 5; } clone(): TempBattleStatBoosterModifier { - return new TempBattleStatBoosterModifier(this.type as ModifierTypes.TempBattleStatBoosterModifierType, this.tempBattleStat); + return new TempBattleStatBoosterModifier(this.type as ModifierTypes.TempBattleStatBoosterModifierType, this.tempBattleStat, this.stackCount); } apply(args: any[]): boolean { @@ -236,12 +236,18 @@ export class TempBattleStatBoosterModifier extends PersistentModifier { export abstract class PokemonHeldItemModifier extends PersistentModifier { public pokemonId: integer; - constructor(type: ModifierType, pokemonId: integer) { - super(type); + constructor(type: ModifierType, pokemonId: integer, stackCount: integer) { + super(type, stackCount); this.pokemonId = pokemonId; } + abstract matchType(_modifier: Modifier): boolean; + + match(modifier: Modifier) { + return this.matchType(modifier) && (modifier as PokemonHeldItemModifier).pokemonId === this.pokemonId; + } + shouldApply(args: any[]): boolean { return super.shouldApply(args) && args.length && args[0] instanceof Pokemon && (this.pokemonId === -1 || (args[0] as Pokemon).id === this.pokemonId); } @@ -282,21 +288,19 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { export class PokemonBaseStatModifier extends PokemonHeldItemModifier { protected stat: Stat; - constructor(type: ModifierTypes.PokemonBaseStatBoosterModifierType, pokemonId: integer, stat: Stat) { - super(type, pokemonId); + constructor(type: ModifierTypes.PokemonBaseStatBoosterModifierType, pokemonId: integer, stat: Stat, stackCount?: integer) { + super(type, pokemonId, stackCount); this.stat = stat; } - match(modifier: Modifier): boolean { - if (modifier instanceof PokemonBaseStatModifier) { - const pokemonStatModifier = modifier as PokemonBaseStatModifier; - return pokemonStatModifier.pokemonId === this.pokemonId && pokemonStatModifier.stat === this.stat; - } + matchType(modifier: Modifier): boolean { + if (modifier instanceof PokemonBaseStatModifier) + return (modifier as PokemonBaseStatModifier).stat === this.stat; return false; } clone(): PersistentModifier { - return new PokemonBaseStatModifier(this.type as ModifierTypes.PokemonBaseStatBoosterModifierType, this.pokemonId, this.stat); + return new PokemonBaseStatModifier(this.type as ModifierTypes.PokemonBaseStatBoosterModifierType, this.pokemonId, this.stat, this.stackCount); } shouldApply(args: any[]): boolean { @@ -314,19 +318,22 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { private moveType: Type; private boostMultiplier: number; - constructor(type: ModifierType, pokemonId: integer, moveType: Type, boostPercent: integer) { - super(type, pokemonId); + constructor(type: ModifierType, pokemonId: integer, moveType: Type, boostPercent: integer, stackCount?: integer) { + super(type, pokemonId, stackCount); this.moveType = moveType; this.boostMultiplier = boostPercent * 0.01; } - match(modifier: Modifier) { - return modifier instanceof AttackTypeBoosterModifier; + matchType(modifier: Modifier) { + if (modifier instanceof AttackTypeBoosterModifier) { + const attackTypeBoosterModifier = modifier as AttackTypeBoosterModifier; + return attackTypeBoosterModifier.moveType === this.moveType && attackTypeBoosterModifier.boostMultiplier === this.boostMultiplier; + } } clone() { - return new AttackTypeBoosterModifier(this.type, this.pokemonId, this.moveType, this.boostMultiplier * 100); + return new AttackTypeBoosterModifier(this.type, this.pokemonId, this.moveType, this.boostMultiplier * 100, this.stackCount); } shouldApply(args: any[]): boolean { @@ -341,16 +348,16 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { } export class TurnHealModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer) { - super(type, pokemonId); + constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + super(type, pokemonId, stackCount); } - match(modifier: Modifier) { + matchType(modifier: Modifier) { return modifier instanceof TurnHealModifier; } clone() { - return new TurnHealModifier(this.type, this.pokemonId); + return new TurnHealModifier(this.type, this.pokemonId, this.stackCount); } apply(args: any[]): boolean { @@ -370,16 +377,16 @@ export class TurnHealModifier extends PokemonHeldItemModifier { } export class HitHealModifier extends PokemonHeldItemModifier { - constructor(type: ModifierType, pokemonId: integer) { - super(type, pokemonId); + constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { + super(type, pokemonId, stackCount); } - match(modifier: Modifier) { + matchType(modifier: Modifier) { return modifier instanceof HitHealModifier; } clone() { - return new HitHealModifier(this.type, this.pokemonId); + return new HitHealModifier(this.type, this.pokemonId, this.stackCount); } apply(args: any[]): boolean { @@ -402,19 +409,19 @@ export class BerryModifier extends PokemonHeldItemModifier { public berryType: BerryType; public consumed: boolean; - constructor(type: ModifierType, pokemonId: integer, berryType: BerryType) { - super(type, pokemonId); + constructor(type: ModifierType, pokemonId: integer, berryType: BerryType, stackCount?: integer) { + super(type, pokemonId, stackCount); this.berryType = berryType; this.consumed = false; } - match(modifier: Modifier) { - return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType && modifier.pokemonId === this.pokemonId; + matchType(modifier: Modifier) { + return modifier instanceof BerryModifier && (modifier as BerryModifier).berryType === this.berryType; } clone() { - return new BerryModifier(this.type, this.pokemonId, this.berryType); + return new BerryModifier(this.type, this.pokemonId, this.berryType, this.stackCount); } shouldApply(args: any[]): boolean { @@ -436,8 +443,8 @@ export class BerryModifier extends PokemonHeldItemModifier { } export class PreserveBerryModifier extends PersistentModifier { - constructor(type: ModifierType) { - super(type); + constructor(type: ModifierType, stackCount?: integer) { + super(type, stackCount); } match(modifier: Modifier) { @@ -445,7 +452,7 @@ export class PreserveBerryModifier extends PersistentModifier { } clone() { - return new PreserveBerryModifier(this.type); + return new PreserveBerryModifier(this.type, this.stackCount); } shouldApply(args: any[]): boolean { @@ -628,8 +635,8 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { } export class PartyShareModifier extends PersistentModifier { - constructor(type: ModifierType) { - super(type); + constructor(type: ModifierType, stackCount?: integer) { + super(type, stackCount); } match(modifier: Modifier) { @@ -637,7 +644,7 @@ export class PartyShareModifier extends PersistentModifier { } clone(): PartyShareModifier { - return new PartyShareModifier(this.type); + return new PartyShareModifier(this.type, this.stackCount); } shouldApply(args: any[]): boolean { @@ -675,8 +682,8 @@ export class PartyShareModifier extends PersistentModifier { export class HealingBoosterModifier extends PersistentModifier { private multiplier: number; - constructor(type: ModifierType, multiplier: number) { - super(type); + constructor(type: ModifierType, multiplier: number, stackCount?: integer) { + super(type, stackCount); this.multiplier = multiplier; } @@ -686,7 +693,7 @@ export class HealingBoosterModifier extends PersistentModifier { } clone(): HealingBoosterModifier { - return new HealingBoosterModifier(this.type, this.multiplier); + return new HealingBoosterModifier(this.type, this.multiplier, this.stackCount); } apply(args: any[]): boolean { @@ -702,8 +709,8 @@ export class HealingBoosterModifier extends PersistentModifier { export class ExpBoosterModifier extends PersistentModifier { private boostMultiplier: integer; - constructor(type: ModifierType, boostPercent: integer) { - super(type); + constructor(type: ModifierType, boostPercent: integer, stackCount?: integer) { + super(type, stackCount); this.boostMultiplier = boostPercent * 0.01; } @@ -717,7 +724,7 @@ export class ExpBoosterModifier extends PersistentModifier { } clone(): ExpBoosterModifier { - return new ExpBoosterModifier(this.type, this.boostMultiplier * 100); + return new ExpBoosterModifier(this.type, this.boostMultiplier * 100, this.stackCount); } apply(args: any[]): boolean { @@ -730,21 +737,21 @@ export class ExpBoosterModifier extends PersistentModifier { export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { private boostMultiplier: integer; - constructor(type: ModifierTypes.PokemonExpBoosterModifierType, pokemonId: integer, boostPercent: integer) { - super(type, pokemonId); + constructor(type: ModifierTypes.PokemonExpBoosterModifierType, pokemonId: integer, boostPercent: integer, stackCount?: integer) { + super(type, pokemonId, stackCount); this.boostMultiplier = boostPercent * 0.01; } - match(modifier: Modifier): boolean { + matchType(modifier: Modifier): boolean { if (modifier instanceof PokemonExpBoosterModifier) { const pokemonExpModifier = modifier as PokemonExpBoosterModifier; - return pokemonExpModifier.pokemonId === this.pokemonId && pokemonExpModifier.boostMultiplier === this.boostMultiplier; + return pokemonExpModifier.boostMultiplier === this.boostMultiplier; } return false; } clone(): PersistentModifier { - return new PokemonExpBoosterModifier(this.type as ModifierTypes.PokemonExpBoosterModifierType, this.pokemonId, this.boostMultiplier * 100); + return new PokemonExpBoosterModifier(this.type as ModifierTypes.PokemonExpBoosterModifierType, this.pokemonId, this.boostMultiplier * 100, this.stackCount); } shouldApply(args: any[]): boolean { @@ -759,8 +766,8 @@ export class PokemonExpBoosterModifier extends PokemonHeldItemModifier { } export class ExpShareModifier extends PersistentModifier { - constructor(type: ModifierType) { - super(type); + constructor(type: ModifierType, stackCount?: integer) { + super(type, stackCount); } match(modifier: Modifier): boolean { @@ -772,7 +779,7 @@ export class ExpShareModifier extends PersistentModifier { } clone(): ExpShareModifier { - return new ExpShareModifier(this.type); + return new ExpShareModifier(this.type, this.stackCount); } getMaxStackCount(): integer { @@ -781,8 +788,8 @@ export class ExpShareModifier extends PersistentModifier { } export class ExpBalanceModifier extends PersistentModifier { - constructor(type: ModifierType) { - super(type); + constructor(type: ModifierType, stackCount?: integer) { + super(type, stackCount); } match(modifier: Modifier): boolean { @@ -794,7 +801,7 @@ export class ExpBalanceModifier extends PersistentModifier { } clone(): ExpBalanceModifier { - return new ExpBalanceModifier(this.type); + return new ExpBalanceModifier(this.type, this.stackCount); } getMaxStackCount(): integer { @@ -803,8 +810,8 @@ export class ExpBalanceModifier extends PersistentModifier { } export class ShinyRateBoosterModifier extends PersistentModifier { - constructor(type: ModifierType) { - super(type); + constructor(type: ModifierType, stackCount?: integer) { + super(type, stackCount); } match(modifier: Modifier): boolean { @@ -812,7 +819,7 @@ export class ShinyRateBoosterModifier extends PersistentModifier { } clone(): ShinyRateBoosterModifier { - return new ShinyRateBoosterModifier(this.type); + return new ShinyRateBoosterModifier(this.type, this.stackCount); } apply(args: any[]): boolean { @@ -827,8 +834,8 @@ export class ShinyRateBoosterModifier extends PersistentModifier { } export class ExtraModifierModifier extends PersistentModifier { - constructor(type: ModifierType) { - super(type); + constructor(type: ModifierType, stackCount?: integer) { + super(type, stackCount); } match(modifier: Modifier): boolean { @@ -836,7 +843,7 @@ export class ExtraModifierModifier extends PersistentModifier { } clone(): ExtraModifierModifier { - return new ExtraModifierModifier(this.type); + return new ExtraModifierModifier(this.type, this.stackCount); } apply(args: any[]): boolean { diff --git a/src/ui/biome-select-ui-handler.ts b/src/ui/biome-select-ui-handler.ts index 0ce72eeaeaa..efac5753625 100644 --- a/src/ui/biome-select-ui-handler.ts +++ b/src/ui/biome-select-ui-handler.ts @@ -25,7 +25,7 @@ export default class BiomeSelectUiHandler extends UiHandler { this.biomeSelectContainer.setVisible(false); ui.add(this.biomeSelectContainer); - this.biomeSelectBg = this.scene.add.image(0, 0, 'biome_select_window_2'); + this.biomeSelectBg = this.scene.add.image(0, 0, 'option_select_window_2'); this.biomeSelectBg.setOrigin(0, 1); this.biomeSelectContainer.add(this.biomeSelectBg); @@ -42,7 +42,7 @@ export default class BiomeSelectUiHandler extends UiHandler { return; this.biomeChoices = biomeLinks[args[0]] as Biome[]; - this.biomeSelectBg.setTexture(`biome_select_window_${this.biomeChoices.length}`) + this.biomeSelectBg.setTexture(`option_select_window_${this.biomeChoices.length}`) this.biomesText.setText(this.biomeChoices.map(b => getBiomeName(b)).join('\n')); this.biomesText.setPositionRelative(this.biomeSelectBg, 16, 9); this.biomeSelectHandler = args[1] as Function; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 0d1435e2933..074a1a2eeea 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -1,13 +1,18 @@ import BattleScene, { Button } from "../battle-scene"; -import { ModifierTier, ModifierType, ModifierTypeOption } from "../modifier/modifier-type"; +import { ModifierTier, ModifierTypeOption } from "../modifier/modifier-type"; import { getPokeballAtlasKey, PokeballType } from "../data/pokeball"; import { addTextObject, TextStyle } from "./text"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { Mode } from "./ui"; +import { PokemonHeldItemModifier } from "../modifier/modifier"; export default class ModifierSelectUiHandler extends AwaitableUiHandler { private overlayBg: Phaser.GameObjects.Rectangle; private modifierContainer: Phaser.GameObjects.Container; + private transferButtonContainer: Phaser.GameObjects.Container; + + private lastCursor: integer = 0; + public options: ModifierOption[]; private cursorObj: Phaser.GameObjects.Image; @@ -30,6 +35,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.modifierContainer = this.scene.add.container(0, 0); ui.add(this.modifierContainer); + + this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -64); + this.transferButtonContainer.setVisible(false); + ui.add(this.transferButtonContainer); + + const transferButtonText = addTextObject(this.scene, -4, -2, 'Transfer', TextStyle.PARTY); + transferButtonText.setOrigin(1, 0); + this.transferButtonContainer.add(transferButtonText); } show(args: any[]) { @@ -48,6 +61,11 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.getUi().clearText(); + const partyHasHeldItem = !!this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier).length; + + this.transferButtonContainer.setVisible(false); + this.transferButtonContainer.setAlpha(0); + const typeOptions = args[0] as ModifierTypeOption[]; for (let m = 0; m < typeOptions.length; m++) { const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2); @@ -83,6 +101,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { }); this.scene.time.delayedCall(4000 + (hasUpgrade ? 2000 : 0), () => { + if (partyHasHeldItem) { + this.transferButtonContainer.setAlpha(0); + this.transferButtonContainer.setVisible(true); + this.scene.tweens.add({ + targets: this.transferButtonContainer, + alpha: 1, + duration: 250 + }); + } + this.setCursor(0); this.awaitingActionInput = true; this.onActionInput = args[1]; @@ -115,12 +143,20 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } else { switch (button) { + case Button.UP: + if (this.cursor === this.options.length) + success = this.setCursor(this.lastCursor); + break; + case Button.DOWN: + if (this.cursor < this.options.length && this.transferButtonContainer.visible) + success = this.setCursor(this.options.length); + break; case Button.LEFT: if (this.cursor) success = this.setCursor(this.cursor - 1); break; case Button.RIGHT: - if (this.cursor < this.options.length - 1) + if (this.cursor < this.options.length - (this.transferButtonContainer.visible ? 0 : 1)) success = this.setCursor(this.cursor + 1); break; } @@ -131,18 +167,29 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } setCursor(cursor: integer): boolean { + const lastCursor = this.cursor; + const ui = this.getUi(); const ret = super.setCursor(cursor); + if (ret) + this.lastCursor = lastCursor; + if (!this.cursorObj) { this.cursorObj = this.scene.add.image(0, 0, 'cursor'); - this.cursorObj.setScale(2); this.modifierContainer.add(this.cursorObj); } - const sliceWidth = (this.scene.game.canvas.width / 6) / (this.options.length + 2); - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, -this.scene.game.canvas.height / 12 - 20); - ui.showText(this.options[this.cursor].modifierTypeOption.type.description); + this.cursorObj.setScale(cursor < this.options.length ? 2 : 1); + + if (cursor < this.options.length) { + const sliceWidth = (this.scene.game.canvas.width / 6) / (this.options.length + 2); + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, -this.scene.game.canvas.height / 12 - 20); + ui.showText(this.options[this.cursor].modifierTypeOption.type.description); + } else { + this.cursorObj.setPosition((this.scene.game.canvas.width / 6) - 50, -60); + ui.showText('Transfer a held item from one POKéMON to another instead of selecting an item'); + } return ret; } @@ -165,12 +212,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { targets: this.options, scale: 0.01, duration: 250, - east: 'Elastic.easeIn', + ease: 'Elastic.easeIn', onComplete: () => { this.options.forEach(o => o.destroy()); this.options.splice(0, this.options.length); } }); + if (this.transferButtonContainer.visible) { + this.scene.tweens.add({ + targets: this.transferButtonContainer, + alpha: 0, + duration: 250, + ease: 'Cubic.easeIn', + onComplete: () => this.transferButtonContainer.setVisible(false) + }) + } } eraseCursor() { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 47a6a84407e..d08ad2fabda 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,4 @@ -import { CommandPhase, SummonMissingPhase } from "../battle-phases"; +import { CommandPhase } from "../battle-phases"; import BattleScene, { Button } from "../battle-scene"; import { PlayerPokemon, PokemonMove } from "../pokemon"; import { addTextObject, TextStyle } from "./text"; @@ -6,6 +6,7 @@ import { Command } from "./command-ui-handler"; import MessageUiHandler from "./message-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; +import { PokemonHeldItemModifier } from "../modifier/modifier"; const defaultMessage = 'Choose a Pokémon.'; @@ -15,24 +16,28 @@ export enum PartyUiMode { POST_BATTLE_SWITCH, MODIFIER, MOVE_MODIFIER, + MODIFIER_TRANSFER, RELEASE } export enum PartyOption { + CANCEL = -1, SHIFT, SEND_OUT, APPLY, + TRANSFER, SUMMARY, RELEASE, MOVE_1, MOVE_2, MOVE_3, - MOVE_4, - CANCEL + MOVE_4 } export type PartySelectCallback = (cursor: integer, option: PartyOption) => void; +export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, toCursor?: integer) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string; +export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => string; export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string; export default class PartyUiHandler extends MessageUiHandler { @@ -49,10 +54,14 @@ export default class PartyUiHandler extends MessageUiHandler { private optionsContainer: Phaser.GameObjects.Container; private optionsCursorObj: Phaser.GameObjects.Image; private options: integer[]; + + private transferMode: boolean; + private transferOptionCursor: integer; + private transferCursor: integer; private lastCursor: integer = 0; - private selectCallback: PartySelectCallback; - private selectFilter: PokemonSelectFilter; + private selectCallback: PartySelectCallback | PartyModifierTransferSelectCallback; + private selectFilter: PokemonSelectFilter | PokemonModifierTransferSelectFilter; private moveSelectFilter: PokemonMoveSelectFilter; private static FilterAll = (_pokemon: PlayerPokemon) => null; @@ -65,6 +74,13 @@ export default class PartyUiHandler extends MessageUiHandler { private static FilterAllMoves = (_pokemonMove: PokemonMove) => null; + public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { + const matchingModifier = pokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).matchType(modifier)) as PokemonHeldItemModifier; + if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) + return `${pokemon.name} has too many\nof this item!`; + return null; + }; + public static NoEffectMessage = 'It won\'t have any effect.'; constructor(scene: BattleScene) { @@ -166,15 +182,29 @@ export default class PartyUiHandler extends MessageUiHandler { if (button === Button.ACTION) { const option = this.options[this.optionsCursor]; const pokemon = this.scene.getParty()[this.cursor]; - if ((option !== PartyOption.SUMMARY && option !== PartyOption.RELEASE && option !== PartyOption.CANCEL) + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { + this.startTransfer(); + this.clearOptions(); + ui.playSelect(); + } else if ((option !== PartyOption.SUMMARY && option !== PartyOption.RELEASE && option !== PartyOption.CANCEL) || (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) { - let filterResult: string = this.selectFilter(pokemon); - if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) - filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); + let filterResult: string; + if (option !== PartyOption.TRANSFER) { + filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); + if (filterResult === null && this.partyUiMode === PartyUiMode.MOVE_MODIFIER) + filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]); + } else { + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).pokemonId === pokemon.id) as PokemonHeldItemModifier[]; + filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)(pokemon, itemModifiers[this.transferOptionCursor]); + } if (filterResult === null) { this.clearOptions(); if (this.selectCallback) { - if (option === PartyOption.RELEASE) + if (option === PartyOption.TRANSFER) { + (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.cursor); + this.clearTransfer(); + } else if (option === PartyOption.RELEASE) this.doRelease(this.cursor); else { const selectCallback = this.selectCallback; @@ -241,7 +271,10 @@ export default class PartyUiHandler extends MessageUiHandler { this.processInput(Button.CANCEL); return; } else if (button === Button.CANCEL) { - if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH) { + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && this.transferMode) { + this.clearTransfer(); + ui.playSelect(); + } else if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH) { if (this.selectCallback) { const selectCallback = this.selectCallback; this.selectCallback = null; @@ -309,7 +342,8 @@ export default class PartyUiHandler extends MessageUiHandler { this.optionsCursorObj.setOrigin(0, 0); this.optionsContainer.add(this.optionsCursorObj); } - this.optionsCursorObj.setPosition(-86, -19 - (16 * ((this.options.length - 1) - this.optionsCursor))); + const wideOptions = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER; + this.optionsCursorObj.setPosition(-86 - (wideOptions ? 50 : 0), -19 - (16 * ((this.options.length - 1) - this.optionsCursor))); } else { changed = this.cursor !== cursor; if (changed) { @@ -335,16 +369,35 @@ export default class PartyUiHandler extends MessageUiHandler { this.optionsMode = true; - this.partyMessageBox.setTexture('party_message_options'); - this.message.setText('Do what with this Pokémon?'); + const wideOptions = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER; - const optionsBottom = this.scene.add.image(0, 0, 'party_options_bottom'); + this.partyMessageBox.setTexture(`party_message_options${wideOptions ? '_wide' : ''}`); + + let optionsMessage = 'Do what with this Pokémon?'; + + switch (this.partyUiMode) { + case PartyUiMode.MOVE_MODIFIER: + optionsMessage = 'Select a move.'; + break; + case PartyUiMode.MODIFIER_TRANSFER: + if (!this.transferMode) + optionsMessage = 'Select a held item to transfer.'; + break; + } + + this.message.setText(optionsMessage); + + const optionsBottom = this.scene.add.image(0, 0, `party_options${wideOptions ? '_wide' : ''}_bottom`); optionsBottom.setOrigin(1, 1); this.optionsContainer.add(optionsBottom); const pokemon = this.scene.getParty()[this.cursor]; - if (this.partyUiMode !== PartyUiMode.MOVE_MODIFIER) { + const itemModifiers = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER + ? this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemon.id) as PokemonHeldItemModifier[] + : null; + + if (this.partyUiMode !== PartyUiMode.MOVE_MODIFIER && (this.transferMode || this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER)) { switch (this.partyUiMode) { case PartyUiMode.SWITCH: if (this.cursor) @@ -358,6 +411,9 @@ export default class PartyUiHandler extends MessageUiHandler { case PartyUiMode.MODIFIER: this.options.push(PartyOption.APPLY); break; + case PartyUiMode.MODIFIER_TRANSFER: + this.options.push(PartyOption.TRANSFER); + break; case PartyUiMode.RELEASE: this.options.push(PartyOption.RELEASE); break; @@ -367,9 +423,12 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode === PartyUiMode.SWITCH) this.options.push(PartyOption.RELEASE); - } else { + } else if (this.partyUiMode === PartyUiMode.MOVE_MODIFIER) { for (let m = 0; m < pokemon.moveset.length; m++) this.options.push(PartyOption.MOVE_1 + m); + } else { + for (let im = 0; im < itemModifiers.length; im++) + this.options.push(im); } this.options.push(PartyOption.CANCEL); @@ -377,21 +436,28 @@ export default class PartyUiHandler extends MessageUiHandler { for (let o = 0; o < this.options.length; o++) { const option = this.options[this.options.length - (o + 1)]; let optionName: string; - switch (option) { - case PartyOption.MOVE_1: - case PartyOption.MOVE_2: - case PartyOption.MOVE_3: - case PartyOption.MOVE_4: - optionName = pokemon.moveset[option - PartyOption.MOVE_1].getName(); - break; - default: - optionName = PartyOption[option].replace(/\_/g, ' '); - break; + if (this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER || this.transferMode || option === PartyOption.CANCEL) { + switch (option) { + case PartyOption.MOVE_1: + case PartyOption.MOVE_2: + case PartyOption.MOVE_3: + case PartyOption.MOVE_4: + optionName = pokemon.moveset[option - PartyOption.MOVE_1].getName(); + break; + default: + optionName = PartyOption[option].replace(/\_/g, ' '); + break; + } + } else { + const itemModifier = itemModifiers[option]; + optionName = itemModifier.type.name; + if (itemModifier.stackCount > 1) + optionName += ` (${itemModifier.stackCount})`; } const yCoord = -6 - 16 * o; - const optionBg = this.scene.add.image(0, yCoord, 'party_options_center'); - const optionText = addTextObject(this.scene, -79, yCoord - 16, optionName, TextStyle.WINDOW); + const optionBg = this.scene.add.image(0, yCoord, `party_options${wideOptions ? '_wide' : ''}_center`); + const optionText = addTextObject(this.scene, -79 - (wideOptions ? 50 : 0), yCoord - 16, optionName, TextStyle.WINDOW); optionBg.setOrigin(1, 1); optionText.setOrigin(0, 0); @@ -400,13 +466,26 @@ export default class PartyUiHandler extends MessageUiHandler { this.optionsContainer.add(optionText); } - const optionsTop = this.scene.add.image(0, -6 - 16 * this.options.length, 'party_options_top'); + const optionsTop = this.scene.add.image(0, -6 - 16 * this.options.length, `party_options${wideOptions ? '_wide' : ''}_top`); optionsTop.setOrigin(1, 1); this.optionsContainer.add(optionsTop); this.setCursor(0); } + startTransfer(): void { + this.transferMode = true; + this.transferCursor = this.cursor; + this.transferOptionCursor = this.optionsCursor; + + this.partySlots[this.transferCursor].setTransfer(true); + } + + clearTransfer(): void { + this.transferMode = false; + this.partySlots[this.transferCursor].setTransfer(false); + } + doRelease(slotIndex: integer): void { this.showText(this.getReleaseMessage(this.scene.getParty()[slotIndex].name), null, () => { this.clearPartySlots(); @@ -479,6 +558,7 @@ export default class PartyUiHandler extends MessageUiHandler { class PartySlot extends Phaser.GameObjects.Container { private selected: boolean; + private transfer: boolean; private slotIndex: integer; private pokemon: PlayerPokemon; @@ -550,25 +630,38 @@ class PartySlot extends Phaser.GameObjects.Container { this.slotHpOverlay = slotHpOverlay; } - select() { + select(): void { if (this.selected) return; this.selected = true; - this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`, `party_slot${this.slotIndex ? '' : '_main'}${this.pokemon.hp ? '' : '_fnt'}_sel`); + this.updateSlotTexture(); this.slotPb.setFrame('party_pb_sel'); } - deselect() { + deselect(): void { if (!this.selected) return; this.selected = false; - this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`, `party_slot${this.slotIndex ? '' : '_main'}${this.pokemon.hp ? '' : '_fnt'}`); + this.updateSlotTexture(); this.slotPb.setFrame('party_pb'); } + + setTransfer(transfer: boolean): void { + if (this.transfer === transfer) + return; + + this.transfer = transfer; + this.updateSlotTexture(); + } + + private updateSlotTexture(): void { + this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`, + `party_slot${this.slotIndex ? '' : '_main'}${this.transfer ? '_swap' : this.pokemon.hp ? '' : '_fnt'}${this.selected ? '_sel' : ''}`); + } } class PartyCancelButton extends Phaser.GameObjects.Container {