/** * PlayerTank.js * Player-controlled tank with fire level, lives, shield, and respawn logic. */ const Tank = require('./Tank'); const { TANK_TYPE, TANK_CONFIG, FIRE_LEVEL, MAX_BULLETS_BY_LEVEL, DIRECTION, DEFAULT_LIVES, SHIELD_DURATION, INVINCIBLE_BLINK_INTERVAL, TILE_SIZE, MAP_OFFSET_X, MAP_OFFSET_Y, } = require('../base/GameGlobal'); class PlayerTank extends Tank { /** * @param {object} params * @param {number} params.col - Spawn grid column. * @param {number} params.row - Spawn grid row. */ constructor(params) { const cfg = TANK_CONFIG[TANK_TYPE.PLAYER]; const spawnX = MAP_OFFSET_X + params.col * TILE_SIZE + TILE_SIZE / 2; const spawnY = MAP_OFFSET_Y + params.row * TILE_SIZE + TILE_SIZE / 2; super({ x: spawnX, y: spawnY, speed: cfg.speed, hp: cfg.hp, color: cfg.color, size: cfg.size, direction: DIRECTION.UP, }); this.type = TANK_TYPE.PLAYER; this.spawnCol = params.col; this.spawnRow = params.row; // Skin colors (reserved for future use) this._skinColors = null; this._skinId = 'default'; // Fire level system this.fireLevel = FIRE_LEVEL.LV1; // Lives this.lives = DEFAULT_LIVES; // Shield (invincibility) this._shieldTimer = 0; // ms remaining this._shieldBlink = false; this._blinkTimer = 0; // Active bullets count (managed externally) this.activeBullets = 0; // Respawn invincibility (short shield on spawn) this._respawnShieldDuration = 3000; // 3 seconds on respawn } /** * Update player tank state. * @param {number} dt - Delta time in seconds. */ update(dt) { if (!this.alive) return; // Shield timer if (this._shieldTimer > 0) { this._shieldTimer -= dt * 1000; this._blinkTimer += dt * 1000; if (this._blinkTimer >= INVINCIBLE_BLINK_INTERVAL) { this._blinkTimer = 0; this._shieldBlink = !this._shieldBlink; } if (this._shieldTimer <= 0) { this._shieldTimer = 0; this._shieldBlink = false; } } } /** * Override takeDamage to check shield. * @param {number} amount * @returns {boolean} Whether destroyed. */ takeDamage(amount = 1) { if (this._shieldTimer > 0) return false; // invincible return super.takeDamage(amount); } /** * Handle player death: lose a life and respawn, or game over. * @returns {boolean} True if player has lives remaining and respawned. */ die() { this.alive = false; this.lives--; if (this.lives > 0) { this.respawn(); return true; } return false; // game over } /** * Respawn at the spawn point with temporary invincibility. */ respawn() { this.x = MAP_OFFSET_X + this.spawnCol * TILE_SIZE + TILE_SIZE / 2; this.y = MAP_OFFSET_Y + this.spawnRow * TILE_SIZE + TILE_SIZE / 2; this.direction = DIRECTION.UP; this.hp = 1; this.alive = true; this.visible = true; this.fireLevel = FIRE_LEVEL.LV1; this.activeBullets = 0; // Temporary shield on respawn this.activateShield(this._respawnShieldDuration); } /** * Activate shield (invincibility). * @param {number} duration - Duration in ms. */ activateShield(duration) { this._shieldTimer = duration; this._blinkTimer = 0; this._shieldBlink = false; } /** * Upgrade fire level. */ upgradeFireLevel() { if (this.fireLevel < FIRE_LEVEL.LV3) { this.fireLevel++; } } /** * Add a life. */ addLife() { this.lives++; } /** * Check if the player can fire (based on active bullets and fire level). * @returns {boolean} */ canFire() { return this.alive && this.activeBullets < MAX_BULLETS_BY_LEVEL[this.fireLevel]; } /** * Whether the bullet should break steel. * @returns {boolean} */ canBreakSteel() { return this.fireLevel >= FIRE_LEVEL.LV3; } /** * Render player tank with shield effect. * @param {CanvasRenderingContext2D} ctx */ render(ctx) { if (!this.alive) return; // Call base render super.render(ctx); // Draw shield effect if (this._shieldTimer > 0 && this._shieldBlink) { ctx.save(); ctx.strokeStyle = '#00FFFF'; ctx.lineWidth = 2; ctx.globalAlpha = 0.6; ctx.beginPath(); ctx.arc(this.x, this.y, this.halfSize + 4, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); } } /** Whether the player is currently shielded. */ get isShielded() { return this._shieldTimer > 0; } /** Get max bullets allowed on screen. */ get maxBullets() { return MAX_BULLETS_BY_LEVEL[this.fireLevel]; } } module.exports = PlayerTank;