Add four-way cannon and stimulant bonuses with XP multiplier and healing ticks

This commit is contained in:
Brian Fertig 2026-03-08 19:33:59 -06:00
parent 145341d992
commit 26d2dad489
6 changed files with 55 additions and 13 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

View File

@ -30,8 +30,10 @@ export class Player {
this._baseFireInterval = 250; // ms between shots
this._isMoving = false;
this._isShooting = false;
this.weaponMode = 'default'; // 'default' | 'shotgun' | 'rocket'
this.weaponMode = 'default'; // 'default' | 'shotgun' | 'rocket' | 'fourway' | 'stimulant'
this.weaponTimeLeft = 0; // ms remaining for timed weapon
this.weaponDuration = 0; // initial duration of current weapon (for HUD ratio)
this._stimulantTickTimer = 0;
this._createAnims();
this._buildSprite(x, y);
@ -82,6 +84,13 @@ export class Player {
this._updateRockets();
if (this.weaponTimeLeft > 0) {
this.weaponTimeLeft -= delta;
if (this.weaponMode === 'stimulant') {
this._stimulantTickTimer -= delta;
if (this._stimulantTickTimer <= 0) {
this._stimulantTickTimer = 3000;
this.hp = Math.min(this.hp + this.stats.maxHp * 0.1, this.stats.maxHp);
}
}
if (this.weaponTimeLeft <= 0) {
this.weaponTimeLeft = 0;
this.weaponMode = 'default';
@ -118,6 +127,7 @@ export class Player {
this._fireCooldown = this._baseFireInterval / this.stats.fireRate;
if (this.weaponMode === 'shotgun') this._spawnShotgunBurst();
else if (this.weaponMode === 'rocket') this._spawnRocket();
else if (this.weaponMode === 'fourway') this._spawnFourWayBurst();
else this._spawnBullet();
}
}
@ -172,6 +182,20 @@ export class Player {
});
}
_spawnFourWayBurst() {
this.scene.sound.play('sfx-shoot', { volume: 0.4 });
[0, Math.PI / 2, Math.PI, Math.PI * 1.5].forEach(offset => {
const angle = this.facing + offset;
const bx = this.x + Math.cos(angle) * GUN_TIP_DIST;
const by = this.y + Math.sin(angle) * GUN_TIP_DIST;
const bullet = this.scene.add.circle(bx, by, BULLET_SIZE, 0xffff00);
this.scene.physics.add.existing(bullet);
bullet.body.setVelocity(Math.cos(angle) * BULLET_SPEED, Math.sin(angle) * BULLET_SPEED);
bullet.damage = this.stats.damage;
this.bullets.add(bullet);
});
}
_spawnRocket() {
this.scene.sound.play('sfx-shoot', { volume: 0.5 });
const bx = this.x + Math.cos(this.facing) * GUN_TIP_DIST;
@ -193,15 +217,30 @@ export class Player {
});
}
get xpMultiplier() {
return this.weaponMode === 'stimulant' ? 2 : 1;
}
applyBonus(type) {
if (type === 'medpack') {
this.hp = Math.min(this.hp + this.stats.maxHp * 0.5, this.stats.maxHp);
} else if (type === 'shotgun') {
this.weaponMode = 'shotgun';
this.weaponTimeLeft = 20000;
this.weaponDuration = 20000;
} else if (type === 'rocket') {
this.weaponMode = 'rocket';
this.weaponTimeLeft = 20000;
this.weaponDuration = 20000;
} else if (type === 'fourway') {
this.weaponMode = 'fourway';
this.weaponTimeLeft = 20000;
this.weaponDuration = 20000;
} else if (type === 'stimulant') {
this.weaponMode = 'stimulant';
this.weaponTimeLeft = 15000;
this.weaponDuration = 15000;
this._stimulantTickTimer = 3000;
}
}

View File

@ -72,7 +72,7 @@ export class GameScene extends Phaser.Scene {
this._enemyProjectiles = [];
// Event wiring
this.events.on('enemy-killed', ({ xp }) => this.xpSystem.addXP(xp));
this.events.on('enemy-killed', ({ xp }) => this.xpSystem.addXP(xp * this.player.xpMultiplier));
this.events.on('enemy-projectile-spawned', proj => this._enemyProjectiles.push(proj));
this.events.on('level-up', level => this._showLevelUp(level));
this.events.on('game-over', () => this._onGameOver());

View File

@ -1,13 +1,13 @@
const BONUS_LIFETIME_MS = 15000;
const BLINK_START_MS = 10000; // blink when < 5s left (at 10s elapsed of 15s)
const COOLDOWN_PICKUP = 20000;
const COOLDOWN_PICKUP = 10000;
const COOLDOWN_EXPIRE = 10000;
const FIRST_SPAWN_MS = 20000;
const PICKUP_RADIUS = 32;
const BONUS_TYPES = ['medpack', 'shotgun', 'rocket'];
const BONUS_TYPES = ['medpack', 'shotgun', 'rocket', 'fourway', 'stimulant'];
const FRAME_INDEX = { medpack: 0, shotgun: 1, rocket: 2 };
const GLOW_COLOR = { medpack: 0x44ff44, shotgun: 0xff9900, rocket: 0xff3300 };
const FRAME_INDEX = { medpack: 0, shotgun: 1, rocket: 2, fourway: 3, stimulant: 4 };
const GLOW_COLOR = { medpack: 0x44ff44, shotgun: 0xff9900, rocket: 0xff3300, fourway: 0x00ffff, stimulant: 0xaa44ff };
export class BonusManager {
constructor(scene, player) {
@ -19,8 +19,10 @@ export class BonusManager {
update(delta) {
if (!this._active) {
this._countdown -= delta;
if (this._countdown <= 0) this._spawn();
if (this._player.weaponMode === 'default') {
this._countdown -= delta;
if (this._countdown <= 0) this._spawn();
}
return;
}
@ -57,7 +59,7 @@ export class BonusManager {
}
_spawn() {
const type = BONUS_TYPES[Phaser.Math.Between(0, 2)];
const type = BONUS_TYPES[Phaser.Math.Between(0, BONUS_TYPES.length - 1)];
const cell = this._findSpawnCell();
if (!cell) {
// No valid cell found — retry in 2 seconds

View File

@ -82,11 +82,12 @@ export class HUD {
this._weaponBarFill.setVisible(hasWeapon);
this._weaponLabel.setVisible(hasWeapon);
if (hasWeapon) {
const ratio = Math.max(0, this.player.weaponTimeLeft / 20000);
const ratio = Math.max(0, this.player.weaponTimeLeft / this.player.weaponDuration);
this._weaponBarFill.width = 200 * ratio;
const color = this.player.weaponMode === 'rocket' ? 0xff3300 : 0xff9900;
this._weaponBarFill.setFillStyle(color);
this._weaponLabel.setText(this.player.weaponMode === 'rocket' ? 'ROCKET LAUNCHER' : 'SHOTGUN');
const colorMap = { shotgun: 0xff9900, rocket: 0xff3300, fourway: 0x00ffff, stimulant: 0xaa44ff };
const labelMap = { shotgun: 'SHOTGUN', rocket: 'ROCKET LAUNCHER', fourway: 'FOUR-WAY CANNON', stimulant: 'STIMULANT' };
this._weaponBarFill.setFillStyle(colorMap[this.player.weaponMode] ?? 0xffffff);
this._weaponLabel.setText(labelMap[this.player.weaponMode] ?? '');
}
}