[Bug] Fix return in applyChosenModifier; prevent race condition in modifier-ui-handler (#6099)

* Fix return in applyChosenModifier

* Stop race condition in modifier-select-ui

* Fix null errors inside the tween's onUpdate method

* Minor cleanup of tween chains

* Minor cleanup of tween chains

* Minor fixup
This commit is contained in:
Sirz Benjie 2025-07-16 11:25:58 -06:00 committed by GitHub
parent 3d42e976b7
commit b3f4005a16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 101 additions and 81 deletions

View File

@ -179,7 +179,7 @@ export class SelectModifierPhase extends BattlePhase {
} else {
this.applyModifier(modifierType.newModifier()!);
}
return !cost;
return cost === -1;
}
// Reroll rewards

View File

@ -269,13 +269,22 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler {
globalScene.updateBiomeWaveText();
globalScene.updateMoneyText();
// DO NOT REMOVE: Fixes bug which allows action input to be processed before the UI is shown,
// causing errors if reroll is selected
this.awaitingActionInput = false;
// TODO: Replace with `Promise.withResolvers` when possible.
let tweenResolve: () => void;
const tweenPromise = new Promise<void>(resolve => (tweenResolve = resolve));
let i = 0;
// TODO: Rework this bespoke logic for animating the modifier options.
globalScene.tweens.addCounter({
ease: "Sine.easeIn",
duration: 1250,
onUpdate: t => {
const value = t.getValue();
// The bang here is safe, as `getValue()` only returns null if the tween has been destroyed (which obviously isn't the case inside onUpdate)
const value = t.getValue()!;
const index = Math.floor(value * typeOptions.length);
if (index > i && index <= typeOptions.length) {
const option = this.options[i];
@ -286,67 +295,77 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler {
i++;
}
},
onComplete: () => {
tweenResolve();
},
});
globalScene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => {
for (const shopOption of this.shopOptionsRows.flat()) {
shopOption.show(0, 0);
}
let shopResolve: () => void;
const shopPromise = new Promise<void>(resolve => (shopResolve = resolve));
tweenPromise.then(() => {
globalScene.time.delayedCall(1000, () => {
for (const shopOption of this.shopOptionsRows.flat()) {
shopOption.show(0, 0);
}
shopResolve();
});
});
globalScene.time.delayedCall(4000 + maxUpgradeCount * 2000, () => {
if (partyHasHeldItem) {
this.transferButtonContainer.setAlpha(0);
this.transferButtonContainer.setVisible(true);
shopPromise.then(() => {
globalScene.time.delayedCall(500, () => {
if (partyHasHeldItem) {
this.transferButtonContainer.setAlpha(0);
this.transferButtonContainer.setVisible(true);
globalScene.tweens.add({
targets: this.transferButtonContainer,
alpha: 1,
duration: 250,
});
}
this.rerollButtonContainer.setAlpha(0);
this.checkButtonContainer.setAlpha(0);
this.lockRarityButtonContainer.setAlpha(0);
this.continueButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(true);
this.checkButtonContainer.setVisible(true);
this.continueButtonContainer.setVisible(this.rerollCost < 0);
this.lockRarityButtonContainer.setVisible(canLockRarities);
globalScene.tweens.add({
targets: this.transferButtonContainer,
targets: [this.checkButtonContainer, this.continueButtonContainer],
alpha: 1,
duration: 250,
});
}
this.rerollButtonContainer.setAlpha(0);
this.checkButtonContainer.setAlpha(0);
this.lockRarityButtonContainer.setAlpha(0);
this.continueButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(true);
this.checkButtonContainer.setVisible(true);
this.continueButtonContainer.setVisible(this.rerollCost < 0);
this.lockRarityButtonContainer.setVisible(canLockRarities);
globalScene.tweens.add({
targets: [this.rerollButtonContainer, this.lockRarityButtonContainer],
alpha: this.rerollCost < 0 ? 0.5 : 1,
duration: 250,
});
globalScene.tweens.add({
targets: [this.checkButtonContainer, this.continueButtonContainer],
alpha: 1,
duration: 250,
});
const updateCursorTarget = () => {
if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) {
this.setRowCursor(0);
this.setCursor(2);
} else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) {
this.setRowCursor(ShopCursorTarget.REWARDS);
this.setCursor(0);
} else {
this.setRowCursor(globalScene.shopCursorTarget);
this.setCursor(0);
}
};
globalScene.tweens.add({
targets: [this.rerollButtonContainer, this.lockRarityButtonContainer],
alpha: this.rerollCost < 0 ? 0.5 : 1,
duration: 250,
});
updateCursorTarget();
const updateCursorTarget = () => {
if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) {
this.setRowCursor(0);
this.setCursor(2);
} else if (globalScene.shopCursorTarget === ShopCursorTarget.SHOP && globalScene.gameMode.hasNoShop) {
this.setRowCursor(ShopCursorTarget.REWARDS);
this.setCursor(0);
} else {
this.setRowCursor(globalScene.shopCursorTarget);
this.setCursor(0);
}
};
updateCursorTarget();
handleTutorial(Tutorial.Select_Item).then(res => {
if (res) {
updateCursorTarget();
}
this.awaitingActionInput = true;
this.onActionInput = args[2];
handleTutorial(Tutorial.Select_Item).then(res => {
if (res) {
updateCursorTarget();
}
this.awaitingActionInput = true;
this.onActionInput = args[2];
});
});
});
@ -687,7 +706,11 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler {
scale: 0.01,
duration: 250,
ease: "Cubic.easeIn",
onComplete: () => options.forEach(o => o.destroy()),
onComplete: () => {
options.forEach(o => {
o.destroy();
});
},
});
[
@ -819,7 +842,7 @@ class ModifierOption extends Phaser.GameObjects.Container {
if (!globalScene) {
return;
}
const value = t.getValue();
const value = t.getValue()!;
if (!bounce && value > lastValue) {
globalScene.playSound("se/pb_bounce_1", {
volume: 1 / ++bounceCount,
@ -832,41 +855,38 @@ class ModifierOption extends Phaser.GameObjects.Container {
},
});
// TODO: Figure out proper delay between chains and then convert this into a single tween chain
// rather than starting multiple tween chains.
for (let u = 0; u < this.modifierTypeOption.upgradeCount; u++) {
const upgradeIndex = u;
globalScene.time.delayedCall(
remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (upgradeIndex + 1 + upgradeCountOffset)),
() => {
globalScene.playSound("se/upgrade", {
rate: 1 + 0.25 * upgradeIndex,
});
this.pbTint.setPosition(this.pb.x, this.pb.y);
this.pbTint.setTintFill(0xffffff);
this.pbTint.setAlpha(0);
this.pbTint.setVisible(true);
globalScene.tweens.add({
globalScene.tweens.chain({
tweens: [
{
delay: remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (u + 1 + upgradeCountOffset)),
onStart: () => {
globalScene.playSound("se/upgrade", {
rate: 1 + 0.25 * u,
});
this.pbTint.setPosition(this.pb.x, this.pb.y).setTintFill(0xffffff).setVisible(true).setAlpha(0);
},
targets: this.pbTint,
alpha: 1,
duration: 1000,
ease: "Sine.easeIn",
onComplete: () => {
this.pb.setTexture(
"pb",
this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount + (upgradeIndex + 1)),
);
globalScene.tweens.add({
targets: this.pbTint,
alpha: 0,
duration: 750,
ease: "Sine.easeOut",
onComplete: () => {
this.pbTint.setVisible(false);
},
});
this.pb.setTexture("pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount + (u + 1)));
},
});
},
);
},
{
targets: this.pbTint,
alpha: 0,
duration: 750,
ease: "Sine.easeOut",
onComplete: () => {
this.pbTint.setVisible(false);
},
},
],
});
}
}