Something (#4728)
* [Feature] Add event-based friendship multiplier and update TrainerConfig for event modifiers * [UI][Dev] Improve event banner placement (#4726) * [ui] automatically place event banner and timer in the title screen * add new event banner * ugh * [Refactor] Clean up TrainerConfig and remove unused timed event --------- Co-authored-by: Moka <54149968+MokaStitcher@users.noreply.github.com>
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 85 KiB |
|
@ -203,6 +203,7 @@ export class TrainerConfig {
|
|||
public modifierRewardFuncs: ModifierTypeFunc[] = [];
|
||||
public partyTemplates: TrainerPartyTemplate[];
|
||||
public partyTemplateFunc: PartyTemplateFunc;
|
||||
public eventRewardFuncs: ModifierTypeFunc[] = [];
|
||||
public partyMemberFuncs: PartyMemberFuncs = {};
|
||||
public speciesPools: TrainerTierPools;
|
||||
public speciesFilter: PokemonSpeciesFilter;
|
||||
|
@ -546,6 +547,17 @@ export class TrainerConfig {
|
|||
return this;
|
||||
}
|
||||
|
||||
setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
|
||||
this.eventRewardFuncs = modifierTypeFuncs.map(func => () => {
|
||||
const modifierTypeFunc = func();
|
||||
const modifierType = modifierTypeFunc();
|
||||
modifierType.withIdFromFunc(modifierTypeFunc);
|
||||
return modifierType;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
|
||||
this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => {
|
||||
const modifierTypeFunc = func();
|
||||
|
@ -1828,10 +1840,12 @@ export const trainerConfigs: TrainerConfigs = {
|
|||
|
||||
[TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL)
|
||||
.setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE)
|
||||
.setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM, () => modifierTypes.ABILITY_CHARM)
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE, Species.TREECKO, Species.TORCHIC, Species.MUDKIP, Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP, Species.SNIVY, Species.TEPIG, Species.OSHAWOTT, Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE, Species.ROWLET, Species.LITTEN, Species.POPPLIO, Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE, Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEY, Species.HOOTHOOT, Species.TAILLOW, Species.STARLY, Species.PIDOVE, Species.FLETCHLING, Species.PIKIPEK, Species.ROOKIDEE, Species.WATTREL ], TrainerSlot.TRAINER, true)),
|
||||
[TrainerType.RIVAL_2]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_2)
|
||||
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
|
||||
.setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM)
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.IVYSAUR, Species.CHARMELEON, Species.WARTORTLE, Species.BAYLEEF, Species.QUILAVA, Species.CROCONAW, Species.GROVYLE, Species.COMBUSKEN, Species.MARSHTOMP, Species.GROTLE, Species.MONFERNO, Species.PRINPLUP, Species.SERVINE, Species.PIGNITE, Species.DEWOTT, Species.QUILLADIN, Species.BRAIXEN, Species.FROGADIER, Species.DARTRIX, Species.TORRACAT, Species.BRIONNE, Species.THWACKEY, Species.RABOOT, Species.DRIZZILE, Species.FLORAGATO, Species.CROCALOR, Species.QUAXWELL ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOTTO, Species.HOOTHOOT, Species.TAILLOW, Species.STARAVIA, Species.TRANQUILL, Species.FLETCHINDER, Species.TRUMBEAK, Species.CORVISQUIRE, Species.WATTREL ], TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)),
|
||||
|
|
|
@ -4114,7 +4114,11 @@ export class PlayerPokemon extends Pokemon {
|
|||
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
|
||||
].filter(d => !!d);
|
||||
const amount = new Utils.IntegerHolder(friendship);
|
||||
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||
let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER;
|
||||
if (this.scene.eventManager.isEventActive()) {
|
||||
candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier();
|
||||
}
|
||||
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||
if (amount.value > 0) {
|
||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);
|
||||
|
|
|
@ -246,9 +246,9 @@ export class LoadingScene extends SceneBase {
|
|||
}
|
||||
const availableLangs = [ "en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN" ];
|
||||
if (lang && availableLangs.includes(lang)) {
|
||||
this.loadImage("egg-update_" + lang, "events");
|
||||
this.loadImage("halloween2024-event-" + lang, "events");
|
||||
} else {
|
||||
this.loadImage("egg-update_en", "events");
|
||||
this.loadImage("halloween2024-event-en", "events");
|
||||
}
|
||||
|
||||
this.loadAtlas("statuses", "");
|
||||
|
|
|
@ -1301,13 +1301,12 @@ function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTyp
|
|||
return !(party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex === 199) && (lures.length === 0 || lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) ? weight : 0;
|
||||
};
|
||||
}
|
||||
|
||||
class WeightedModifierType {
|
||||
public modifierType: ModifierType;
|
||||
public weight: integer | WeightedModifierTypeWeightFunc;
|
||||
public maxWeight: integer;
|
||||
public maxWeight: integer | WeightedModifierTypeWeightFunc;
|
||||
|
||||
constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer) {
|
||||
constructor(modifierTypeFunc: ModifierTypeFunc, weight: integer | WeightedModifierTypeWeightFunc, maxWeight?: integer | WeightedModifierTypeWeightFunc) {
|
||||
this.modifierType = modifierTypeFunc();
|
||||
this.modifierType.id = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifierTypeFunc)!; // TODO: is this bang correct?
|
||||
this.weight = weight;
|
||||
|
@ -1694,7 +1693,10 @@ const modifierPool: ModifierPool = {
|
|||
new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => {
|
||||
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
|
||||
}, 8),
|
||||
new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 1 : 0, 1),
|
||||
new WeightedModifierType(modifierTypes.MAP,
|
||||
(party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? party[0].scene.eventManager.isEventActive() ? 2 : 1 : 0,
|
||||
(party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 2 : 1),
|
||||
new WeightedModifierType(modifierTypes.SOOTHE_BELL, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 3 : 0),
|
||||
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
|
||||
new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => {
|
||||
if (!party.find(p => p.getLearnableLevelMoves().length)) {
|
||||
|
@ -1762,7 +1764,7 @@ const modifierPool: ModifierPool = {
|
|||
new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)),
|
||||
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9),
|
||||
new WeightedModifierType(modifierTypes.TM_ULTRA, 11),
|
||||
new WeightedModifierType(modifierTypes.RARER_CANDY, 4),
|
||||
new WeightedModifierType(modifierTypes.RARER_CANDY, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 6 : 4),
|
||||
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)),
|
||||
new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)),
|
||||
new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)),
|
||||
|
@ -1785,7 +1787,7 @@ const modifierPool: ModifierPool = {
|
|||
new WeightedModifierType(modifierTypes.BATON, 2),
|
||||
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
|
||||
//new WeightedModifierType(modifierTypes.OVAL_CHARM, 6),
|
||||
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 4),
|
||||
new WeightedModifierType(modifierTypes.SOOTHE_BELL, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 0 : 4),
|
||||
new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)),
|
||||
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
||||
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
||||
|
|
|
@ -27,6 +27,12 @@ export class TrainerVictoryPhase extends BattlePhase {
|
|||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc));
|
||||
}
|
||||
|
||||
if (this.scene.eventManager.isEventActive()) {
|
||||
for (const rewardFunc of this.scene.currentBattle.trainer?.config.eventRewardFuncs!) {
|
||||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, rewardFunc));
|
||||
}
|
||||
}
|
||||
|
||||
const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
|
||||
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
||||
if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) {
|
||||
|
|
|
@ -5,13 +5,13 @@ import i18next from "i18next";
|
|||
|
||||
export enum EventType {
|
||||
SHINY,
|
||||
GENERIC
|
||||
NO_TIMER_DISPLAY
|
||||
}
|
||||
|
||||
interface EventBanner {
|
||||
bannerKey?: string;
|
||||
xPosition?: number;
|
||||
yPosition?: number;
|
||||
xOffset?: number;
|
||||
yOffset?: number;
|
||||
scale?: number;
|
||||
availableLangs?: string[];
|
||||
}
|
||||
|
@ -20,19 +20,20 @@ interface TimedEvent extends EventBanner {
|
|||
name: string;
|
||||
eventType: EventType;
|
||||
shinyMultiplier?: number;
|
||||
friendshipMultiplier?: number;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}
|
||||
|
||||
const timedEvents: TimedEvent[] = [
|
||||
{
|
||||
name: "Egg Skip Update",
|
||||
eventType: EventType.GENERIC,
|
||||
startDate: new Date(Date.UTC(2024, 8, 8, 0)),
|
||||
endDate: new Date(Date.UTC(2024, 8, 12, 0)),
|
||||
bannerKey: "egg-update",
|
||||
xPosition: 19,
|
||||
yPosition: 120,
|
||||
name: "Halloween Update",
|
||||
eventType: EventType.SHINY,
|
||||
shinyMultiplier: 2,
|
||||
friendshipMultiplier: 2,
|
||||
startDate: new Date(Date.UTC(2024, 9, 25, 0)),
|
||||
endDate: new Date(Date.UTC(2024, 10, 4, 0)),
|
||||
bannerKey: "halloween2024-event-",
|
||||
scale: 0.21,
|
||||
availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN" ]
|
||||
}
|
||||
|
@ -61,6 +62,16 @@ export class TimedEventManager {
|
|||
return activeEvents.length > 0;
|
||||
}
|
||||
|
||||
getFriendshipMultiplier(): number {
|
||||
let multiplier = 1;
|
||||
const friendshipEvents = timedEvents.filter((te) => this.isActive(te));
|
||||
friendshipEvents.forEach((fe) => {
|
||||
multiplier *= fe.friendshipMultiplier ?? 1;
|
||||
});
|
||||
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
getShinyMultiplier(): number {
|
||||
let multiplier = 1;
|
||||
const shinyEvents = timedEvents.filter((te) => te.eventType === EventType.SHINY && this.isActive(te));
|
||||
|
@ -80,42 +91,63 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
|
|||
private event: TimedEvent | nil;
|
||||
private eventTimerText: Phaser.GameObjects.Text;
|
||||
private banner: Phaser.GameObjects.Image;
|
||||
private bannerShadow: Phaser.GameObjects.Rectangle;
|
||||
private availableWidth: number;
|
||||
private eventTimer: NodeJS.Timeout | null;
|
||||
|
||||
constructor(scene: BattleScene, x: number, y: number, event?: TimedEvent) {
|
||||
super(scene, x, y);
|
||||
this.availableWidth = scene.scaledCanvas.width;
|
||||
this.event = event;
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width that can be used to display the event timer and banner. By default
|
||||
* these elements get centered horizontally in that space, in the bottom left of the screen
|
||||
*/
|
||||
setWidth(width: number) {
|
||||
if (width !== this.availableWidth) {
|
||||
this.availableWidth = width;
|
||||
const xPosition = this.availableWidth / 2 + (this.event?.xOffset ?? 0);
|
||||
if (this.banner) {
|
||||
this.banner.x = xPosition;
|
||||
}
|
||||
if (this.eventTimerText) {
|
||||
this.eventTimerText.x = xPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setup() {
|
||||
const lang = i18next.resolvedLanguage;
|
||||
if (this.event && this.event.bannerKey) {
|
||||
let key = this.event.bannerKey;
|
||||
if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) {
|
||||
if (this.event.availableLangs.includes(lang)) {
|
||||
key += "_" + lang;
|
||||
key += lang;
|
||||
} else {
|
||||
key += "_en";
|
||||
key += "en";
|
||||
}
|
||||
}
|
||||
console.log(this.event.bannerKey);
|
||||
this.banner = new Phaser.GameObjects.Image(this.scene, this.event.xPosition ?? 29, this.event.yPosition ?? 64, key);
|
||||
const padding = 5;
|
||||
const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY;
|
||||
const yPosition = this.scene.game.canvas.height / 6 - padding - (showTimer ? 10 : 0) - (this.event.yOffset ?? 0);
|
||||
this.banner = new Phaser.GameObjects.Image(this.scene, this.availableWidth / 2, yPosition - padding, key);
|
||||
this.banner.setName("img-event-banner");
|
||||
this.banner.setOrigin(0.08, -0.35);
|
||||
this.banner.setOrigin(0.5, 1);
|
||||
this.banner.setScale(this.event.scale ?? 0.18);
|
||||
if (this.event.eventType !== EventType.GENERIC) {
|
||||
if (showTimer) {
|
||||
this.eventTimerText = addTextObject(
|
||||
this.scene,
|
||||
this.banner.x + 8,
|
||||
this.banner.y + 100,
|
||||
this.banner.x,
|
||||
this.banner.y + 2,
|
||||
this.timeToGo(this.event.endDate),
|
||||
TextStyle.WINDOW
|
||||
);
|
||||
this.eventTimerText.setName("text-event-timer");
|
||||
this.eventTimerText.setScale(0.15);
|
||||
this.eventTimerText.setOrigin(0, 0);
|
||||
this.eventTimerText.setOrigin(0.5, 0);
|
||||
|
||||
this.add(this.eventTimerText);
|
||||
}
|
||||
|
@ -161,7 +193,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
updateCountdown() {
|
||||
if (this.event && this.event.eventType !== EventType.GENERIC) {
|
||||
if (this.event && this.event.eventType !== EventType.NO_TIMER_DISPLAY) {
|
||||
this.eventTimerText.setText(this.timeToGo(this.event.endDate));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
|
|||
const ui = this.getUi();
|
||||
|
||||
if (this.scene.eventManager.isEventActive()) {
|
||||
this.eventDisplay.setWidth(this.scene.scaledCanvas.width - this.optionSelectBg.width - this.optionSelectBg.x);
|
||||
this.eventDisplay.show();
|
||||
}
|
||||
|
||||
|
|