Fix enemy ships reviving with stuck white hit-flash tint
- Add Fx.releaseCraft() to settle a craft's pending white tintFlash immediately on kill/destroy, so sprites never die mid-flash - Add exported settleCraftTint() helper that resets tint mode and tint to identity; call it on revive (EnemyShip), respawn, and reuse seams - Guard restore callbacks against destroyed sprites with try/catch so a half-restored craft can't leak white state - Update combat screenshots
This commit is contained in:
parent
716f9270ae
commit
db75222ca2
Binary file not shown.
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 186 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 47 KiB |
|
|
@ -10,7 +10,7 @@ import { Ship } from '../entities/Ship.js';
|
|||
import { EnemyShip } from './EnemyShip.js';
|
||||
import { enemySheetList } from './EnemyTypes.js';
|
||||
import { Bullet } from './Bullet.js';
|
||||
import { Fx, FX_COLORS } from './Fx.js';
|
||||
import { Fx, FX_COLORS, settleCraftTint } from './Fx.js';
|
||||
import { StarDrift } from './StarDrift.js';
|
||||
import { applyDamage, handleFeel, pickTarget } from './Combat.js';
|
||||
import { combatSfx, combatSfxEnemyHit, loadCombatSfx } from './CombatSfx.js';
|
||||
|
|
@ -414,6 +414,9 @@ export class CombatScene extends Phaser.Scene {
|
|||
killEnemy(e) {
|
||||
if (e._killFx) return; // idempotent — damage() sets e.dead first, so
|
||||
e._killFx = true; // the dead flag can't be the "already counted" guard
|
||||
// Settle any pending white hit-flash NOW — the craft dies in its
|
||||
// normal colors, never mid-flash (the reuse seam's edge case).
|
||||
this.fx.releaseCraft(e);
|
||||
this.kills += 1;
|
||||
this.fx.explode(e.x, e.y, 0xff5d6f);
|
||||
combatShake(this, 'kill'); // the world kick (the bar stays still)
|
||||
|
|
@ -681,6 +684,7 @@ export class CombatScene extends Phaser.Scene {
|
|||
killPlayer() {
|
||||
this.playerDead = true;
|
||||
this.hull = 0;
|
||||
this.fx.releaseCraft(this.ship); // out in normal colors, never mid-flash
|
||||
this.fx.down(this.ship.x, this.ship.y); // the DOWN cue (burst + hold)
|
||||
combatSfx(this, 'down');
|
||||
this.ship.stop();
|
||||
|
|
@ -699,6 +703,7 @@ export class CombatScene extends Phaser.Scene {
|
|||
this.ship.y = this.h / 2;
|
||||
this.ship.rotation = 0;
|
||||
this.ship.stop();
|
||||
settleCraftTint(this.ship); // never reappear holding a white hit-flash
|
||||
this.ship.setVisible(true);
|
||||
this.playerDead = false;
|
||||
this.clearBullets();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { config } from '../config/Config.js';
|
|||
import { EnemyShip } from './EnemyShip.js';
|
||||
import { enemyTypeConfig } from './EnemyTypes.js';
|
||||
import { Bullet } from './Bullet.js';
|
||||
import { Fx, FX_COLORS } from './Fx.js';
|
||||
import { Fx, FX_COLORS, settleCraftTint } from './Fx.js';
|
||||
import { applyDamage, pickTarget } from './Combat.js';
|
||||
import { combatSfx, combatSfxEnemyHit } from './CombatSfx.js';
|
||||
import { combatShake } from './CamShake.js';
|
||||
|
|
@ -256,7 +256,10 @@ export class CombatSystem {
|
|||
}
|
||||
if (destroy) {
|
||||
for (const e of this.enemies) {
|
||||
if (e.wave && !e.destroyed) e.destroy();
|
||||
if (e.wave && !e.destroyed) {
|
||||
this.fx.releaseCraft(e); // out in normal colors, never mid-flash
|
||||
e.destroy();
|
||||
}
|
||||
}
|
||||
this.enemies = this.enemies.filter((e) => !e.wave);
|
||||
}
|
||||
|
|
@ -699,6 +702,7 @@ export class CombatSystem {
|
|||
this.playerDown = true;
|
||||
this.hull = 0;
|
||||
if (this.ship) {
|
||||
this.fx.releaseCraft(this.ship); // out in normal colors, never mid-flash
|
||||
this.fx.down(this.ship.x, this.ship.y); // the DOWN cue (burst + hold)
|
||||
this.ship.stop();
|
||||
this.ship.setVisible(false);
|
||||
|
|
@ -717,7 +721,10 @@ export class CombatSystem {
|
|||
this.shields = this.maxShields;
|
||||
this.lastHitAt = 0;
|
||||
this.respawnAt = 0;
|
||||
if (this.ship) this.ship.setVisible(true);
|
||||
if (this.ship) {
|
||||
settleCraftTint(this.ship); // never reappear holding a white hit-flash
|
||||
this.ship.setVisible(true);
|
||||
}
|
||||
if (this.ship) this.fx.respawn(this.ship.x, this.ship.y); // the REFORMED cue
|
||||
combatSfx(this.scene, 'respawn');
|
||||
if (this.onPlayerUp) this.onPlayerUp();
|
||||
|
|
@ -733,6 +740,9 @@ export class CombatSystem {
|
|||
killEnemy(e, time) {
|
||||
if (e._killFx) return;
|
||||
e._killFx = true;
|
||||
// Settle any pending white hit-flash NOW — the craft dies in its
|
||||
// normal colors, never mid-flash (the reuse seam's edge case).
|
||||
this.fx.releaseCraft(e);
|
||||
this.kills += 1;
|
||||
this.fx.explode(e.x, e.y, '#ff5d6f');
|
||||
combatShake(this.scene, 'kill'); // the world kick (the bar stays still)
|
||||
|
|
@ -802,7 +812,10 @@ export class CombatSystem {
|
|||
/** Tear the combat down (scene destroy). */
|
||||
destroy() {
|
||||
this.waveRun = null;
|
||||
for (const e of this.enemies) e.destroy();
|
||||
for (const e of this.enemies) {
|
||||
this.fx.releaseCraft(e); // settle flashes before the craft goes
|
||||
e.destroy();
|
||||
}
|
||||
for (const b of this.bullets) if (!b.dead) b.destroy();
|
||||
this.enemies = [];
|
||||
this.bullets = [];
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Phaser from '../vendor/phaser.js';
|
|||
import { toColor } from '../utils/Color.js';
|
||||
import { assignWorld } from '../visuals/UiCameras.js';
|
||||
import { enemyTypeConfig } from './EnemyTypes.js';
|
||||
import { settleCraftTint } from './Fx.js';
|
||||
import { applyDamage, headingOf, orbitStep } from './Combat.js';
|
||||
|
||||
const FALLBACK_W = 76; // the procedural wedge (it faces east)
|
||||
|
|
@ -239,6 +240,11 @@ export class EnemyShip extends Phaser.Physics.Arcade.Sprite {
|
|||
this.hull = this.maxHull;
|
||||
this.shields = this.maxShields;
|
||||
this.dead = false;
|
||||
// THE REUSE SEAM: this is the SAME sprite that died (the range and
|
||||
// the main game both revive in place). If a white hit-flash was
|
||||
// still pending when it went down, settle the tint here too — a
|
||||
// revived craft must never come back hot white.
|
||||
settleCraftTint(this);
|
||||
if (this.body) {
|
||||
this.body.velocity.x = 0;
|
||||
this.body.velocity.y = 0;
|
||||
|
|
|
|||
|
|
@ -432,9 +432,11 @@ export class Fx {
|
|||
t0: 0,
|
||||
life,
|
||||
kind: 'tintFlash',
|
||||
// Each write is guarded on its own — a throw in one must not
|
||||
// skip the other (a half-restored craft is a white craft).
|
||||
restore: () => {
|
||||
ship.setTintMode(mode);
|
||||
ship.setTint(tint);
|
||||
try { ship.setTintMode(mode); } catch { /* gone */ }
|
||||
try { ship.setTint(tint); } catch { /* gone */ }
|
||||
},
|
||||
});
|
||||
return ship;
|
||||
|
|
@ -446,6 +448,27 @@ export class Fx {
|
|||
return this.flash(ship.x, ship.y, 0xffffff, r * 2.6, life);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settle a craft's pending white flash RIGHT NOW — the kill/destroy
|
||||
* seam. A craft must never die, or be hidden and reused, while still
|
||||
* holding the white-fill state: the hit is over (the kill burst or the
|
||||
* reformed craft supersedes it), and the craft goes out in its normal
|
||||
* colors (a sprite that dies mid-tint can leak the white state into
|
||||
* its reuse — the "revived enemies are hot white and stay that way"
|
||||
* edge case). Idempotent; safe on a destroyed craft.
|
||||
*/
|
||||
releaseCraft(ship) {
|
||||
if (!ship) return;
|
||||
for (let i = this.items.length - 1; i >= 0; i--) {
|
||||
const it = this.items[i];
|
||||
if (it.kind !== 'tintFlash' || it.img !== ship) continue;
|
||||
if (it.restore) {
|
||||
try { it.restore(); } catch { /* the craft may already be gone */ }
|
||||
}
|
||||
this.items.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------ the player's shield
|
||||
|
||||
/** The shield-sphere tuning (data/combat.json → combat.fx.shield). */
|
||||
|
|
@ -663,3 +686,18 @@ export class Fx {
|
|||
this.items.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a craft's tint to identity (white corners + multiply mode).
|
||||
* The reuse seams (revive/respawn) call this so a craft can NEVER
|
||||
* reappear while still holding the white hit-flash state — the last
|
||||
* line of defense for the "revived enemies are hot white" edge case.
|
||||
* Safe on stubs and destroyed sprites. (Neither craft class tints for
|
||||
* coloring — their colors are baked into the art — so identity is the
|
||||
* always-correct default.)
|
||||
*/
|
||||
export function settleCraftTint(ship) {
|
||||
if (!ship) return;
|
||||
try { if (typeof ship.setTintMode === 'function') ship.setTintMode(0); } catch { /* stub */ }
|
||||
try { if (typeof ship.setTint === 'function') ship.setTint(0xffffff); } catch { /* stub */ }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue