Compare commits

..

No commits in common. "9b22b06d08072e08abd426a730cf3bbd22695dfc" and "415496b3a25dc50387129980ff68d16c476f517a" have entirely different histories.

5 changed files with 28 additions and 103 deletions

View File

View File

@ -34,19 +34,18 @@ export class ExplosionManager {
// Default configuration // Default configuration
private static readonly DEFAULT_CONFIG: Required<ExplosionConfig> = { private static readonly DEFAULT_CONFIG: Required<ExplosionConfig> = {
duration: 2000, duration: 1000,
explosionForce: 10, explosionForce: 5,
frameRate: 60 frameRate: 60
}; };
constructor(scene: Scene, config?: ExplosionConfig) { constructor(scene: Scene, config?: ExplosionConfig) {
this.scene = scene; this.scene = scene;
this.config = { ...ExplosionManager.DEFAULT_CONFIG, ...config }; this.config = { ...ExplosionManager.DEFAULT_CONFIG, ...config };
debugLog(this.config);
this._debrisBaseMesh = MeshBuilder.CreateIcoSphere( this._debrisBaseMesh = MeshBuilder.CreateIcoSphere(
'debrisBase', 'debrisBase',
{ {
radius: 1, radius: .2,
subdivisions: 2 subdivisions: 2
}, DefaultScene.MainScene }, DefaultScene.MainScene
); );
@ -131,13 +130,14 @@ export class ExplosionManager {
// Attach spatial sound to the node // Attach spatial sound to the node
sound.spatial.attach(explosionNode); sound.spatial.attach(explosionNode);
sound.play(); sound.play();
sound.onEndedObservable.addOnce(() => {
//Cleanup after sound ends. // Cleanup after explosion duration (synchronized with visual effect)
sound.spatial.detach(); setTimeout(() => {
if (sound.spatial) {
sound.spatial.detach();
}
explosionNode.dispose(); explosionNode.dispose();
}) }, this.config.duration);
} catch (error) { } catch (error) {
debugLog("ExplosionManager: Error playing explosion audio", error); debugLog("ExplosionManager: Error playing explosion audio", error);
explosionNode.dispose(); explosionNode.dispose();

View File

@ -48,8 +48,8 @@ export class RockFactory {
this._orbitCenter = new PhysicsAggregate(node, PhysicsShapeType.SPHERE, {radius: .1, mass: 1000}, DefaultScene.MainScene ); this._orbitCenter = new PhysicsAggregate(node, PhysicsShapeType.SPHERE, {radius: .1, mass: 1000}, DefaultScene.MainScene );
this._orbitCenter.body.setMotionType(PhysicsMotionType.ANIMATED); this._orbitCenter.body.setMotionType(PhysicsMotionType.ANIMATED);
this._explosionManager = new ExplosionManager(DefaultScene.MainScene, { this._explosionManager = new ExplosionManager(DefaultScene.MainScene, {
duration: 2000, duration: 800,
explosionForce: 150.0, explosionForce: 20.0,
frameRate: 60 frameRate: 60
}); });
await this._explosionManager.initialize(); await this._explosionManager.initialize();
@ -102,6 +102,7 @@ export class RockFactory {
const body = agg.body; const body = agg.body;
const constraint = new DistanceConstraint(Vector3.Distance(position, this._orbitCenter.body.transformNode.position), DefaultScene.MainScene); const constraint = new DistanceConstraint(Vector3.Distance(position, this._orbitCenter.body.transformNode.position), DefaultScene.MainScene);
body.addConstraint(this._orbitCenter.body, constraint); body.addConstraint(this._orbitCenter.body, constraint);
body.setLinearDamping(0) body.setLinearDamping(0)
body.setMotionType(PhysicsMotionType.DYNAMIC); body.setMotionType(PhysicsMotionType.DYNAMIC);
body.setCollisionCallbackEnabled(true); body.setCollisionCallbackEnabled(true);

View File

@ -14,14 +14,12 @@ export enum VoiceMessagePriority {
/** /**
* Voice message to be queued * Voice message to be queued
*
*/ */
interface VoiceMessage { interface VoiceMessage {
sounds: string[]; // Array of sound names to play in sequence sounds: string[]; // Array of sound names to play in sequence
priority: VoiceMessagePriority; priority: VoiceMessagePriority;
interrupt: boolean; // If true, interrupt current playback interrupt: boolean; // If true, interrupt current playback
repeatInterval?: number; // Milliseconds between repeats (0 or undefined = no repeat)
lastPlayedTime?: number; // Timestamp when message was last played (for repeat timing)
stateKey?: string; // Warning state key (e.g., "warning_fuel") to track ownership
} }
/** /**
@ -110,40 +108,28 @@ export class VoiceAudioSystem {
const maxValue = 1; const maxValue = 1;
const percentage = maxValue > 0 ? newValue / maxValue : 0; const percentage = maxValue > 0 ? newValue / maxValue : 0;
debugLog(event); debugLog(event);
// Only trigger on decreases
// Clear warning states if resources increase above thresholds if (delta >= 0) {
if (delta > 0) { return;
if (percentage >= 0.3) {
this.clearWarningState(`warning_${statusType}`);
}
if (percentage >= 0.1) {
this.clearWarningState(`danger_${statusType}`);
}
if (newValue > 0) {
this.clearWarningState(`empty_${statusType}`);
}
return; // Don't trigger warnings on increases
} }
// Critical danger (< 10%)
if (percentage < 0.2 && !this._warningStates.has(`danger_${statusType}`)) { if (percentage < 0.1 && !this._warningStates.has(`danger_${statusType}`)) {
debugLog(`VoiceAudioSystem: DANGER warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`); debugLog(`VoiceAudioSystem: DANGER warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`);
this._warningStates.add(`danger_${statusType}`); this._warningStates.add(`danger_${statusType}`);
// Clear warning state if it exists (danger supersedes warning) this.queueMessage(['danger', statusType], VoiceMessagePriority.HIGH, false);
this.clearWarningState(`warning_${statusType}`);
this.queueMessage(['danger', statusType], VoiceMessagePriority.HIGH, false, 2000, `danger_${statusType}`);
} }
// Warning (10% <= x < 30%) - repeat every 4 seconds ONLY if not in danger // Warning (< 30%)
else if (percentage >= 0.2 && percentage < 0.5 && !this._warningStates.has(`warning_${statusType}`) && !this._warningStates.has(`danger_${statusType}`)) { else if (percentage < 0.3 && !this._warningStates.has(`warning_${statusType}`)) {
debugLog(`VoiceAudioSystem: Warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`); debugLog(`VoiceAudioSystem: Warning triggered for ${statusType} (${(percentage * 100).toFixed(1)}%)`);
this._warningStates.add(`warning_${statusType}`); this._warningStates.add(`warning_${statusType}`);
this.queueMessage(['warning', statusType], VoiceMessagePriority.NORMAL, false, 4000, `warning_${statusType}`); this.queueMessage(['warning', statusType], VoiceMessagePriority.NORMAL, false);
} }
// Empty (= 0) - no repeat // Empty (= 0)
else if (newValue === 0 && !this._warningStates.has(`empty_${statusType}`)) { else if (newValue === 0 && !this._warningStates.has(`empty_${statusType}`)) {
debugLog(`VoiceAudioSystem: EMPTY warning triggered for ${statusType}`); debugLog(`VoiceAudioSystem: EMPTY warning triggered for ${statusType}`);
this._warningStates.add(`empty_${statusType}`); this._warningStates.add(`empty_${statusType}`);
this.queueMessage([statusType, 'empty'], VoiceMessagePriority.HIGH, false, 0, `empty_${statusType}`); this.queueMessage([statusType, 'empty'], VoiceMessagePriority.HIGH, false);
} }
} }
@ -151,25 +137,13 @@ export class VoiceAudioSystem {
/** /**
* Queue a voice message to be played * Queue a voice message to be played
*/ */
public queueMessage( public queueMessage(sounds: string[], priority: VoiceMessagePriority = VoiceMessagePriority.NORMAL, interrupt: boolean = false): void {
sounds: string[],
priority: VoiceMessagePriority = VoiceMessagePriority.NORMAL,
interrupt: boolean = false,
repeatInterval: number = 0,
stateKey?: string
): void {
if (!this._audioEngine) { if (!this._audioEngine) {
debugLog('VoiceAudioSystem: Cannot queue message - audio not initialized'); debugLog('VoiceAudioSystem: Cannot queue message - audio not initialized');
return; return;
} }
const message: VoiceMessage = { const message: VoiceMessage = { sounds, priority, interrupt };
sounds,
priority,
interrupt,
repeatInterval: repeatInterval > 0 ? repeatInterval : undefined,
stateKey
};
// If interrupt flag is set, stop current playback and clear queue // If interrupt flag is set, stop current playback and clear queue
if (interrupt) { if (interrupt) {
@ -185,8 +159,7 @@ export class VoiceAudioSystem {
this._queue.splice(insertIndex, 0, message); this._queue.splice(insertIndex, 0, message);
} }
const repeatInfo = repeatInterval > 0 ? ` (repeat every ${repeatInterval}ms)` : ''; debugLog(`VoiceAudioSystem: Queued message [${sounds.join(', ')}] with priority ${priority}`);
debugLog(`VoiceAudioSystem: Queued message [${sounds.join(', ')}] with priority ${priority}${repeatInfo}`);
} }
/** /**
@ -224,22 +197,6 @@ export class VoiceAudioSystem {
} else { } else {
// Sequence complete // Sequence complete
debugLog('VoiceAudioSystem: Sequence complete'); debugLog('VoiceAudioSystem: Sequence complete');
// Check if this message should repeat
if (this._currentMessage.repeatInterval && this._currentMessage.repeatInterval > 0) {
// Only re-queue if the warning state still exists
if (!this._currentMessage.stateKey || this._warningStates.has(this._currentMessage.stateKey)) {
// Record the time this message finished
this._currentMessage.lastPlayedTime = performance.now();
// Re-queue the message for repeat
this._queue.push({ ...this._currentMessage });
debugLog(`VoiceAudioSystem: Message re-queued for repeat in ${this._currentMessage.repeatInterval}ms`);
} else {
debugLog(`VoiceAudioSystem: Message NOT re-queued - warning state '${this._currentMessage.stateKey}' cleared`);
}
}
this._isPlaying = false; this._isPlaying = false;
this._currentMessage = null; this._currentMessage = null;
this._currentSoundIndex = 0; this._currentSoundIndex = 0;
@ -250,19 +207,6 @@ export class VoiceAudioSystem {
// If not playing and queue has messages, start next message // If not playing and queue has messages, start next message
if (!this._isPlaying && this._queue.length > 0) { if (!this._isPlaying && this._queue.length > 0) {
// Check if the first message in queue needs to wait for repeat interval
const nextMessage = this._queue[0];
if (nextMessage.lastPlayedTime && nextMessage.repeatInterval) {
const now = performance.now();
const timeSinceLastPlay = now - nextMessage.lastPlayedTime;
if (timeSinceLastPlay < nextMessage.repeatInterval) {
// Not enough time has passed, skip this frame
return;
}
}
// Ready to play - dequeue and start
this._currentMessage = this._queue.shift()!; this._currentMessage = this._queue.shift()!;
this._currentSoundIndex = 0; this._currentSoundIndex = 0;
this._isPlaying = true; this._isPlaying = true;
@ -318,26 +262,6 @@ export class VoiceAudioSystem {
debugLog('VoiceAudioSystem: Queue cleared'); debugLog('VoiceAudioSystem: Queue cleared');
} }
/**
* Clear a specific warning state and remove matching messages from queue
*/
public clearWarningState(key: string): void {
if (this._warningStates.has(key)) {
this._warningStates.delete(key);
// Remove all messages with this state key from queue
const originalLength = this._queue.length;
this._queue = this._queue.filter(msg => msg.stateKey !== key);
const removed = originalLength - this._queue.length;
if (removed > 0) {
debugLog(`VoiceAudioSystem: Cleared warning state '${key}' and purged ${removed} queued message(s)`);
} else {
debugLog(`VoiceAudioSystem: Cleared warning state '${key}'`);
}
}
}
/** /**
* Reset warning states (useful when starting new level or respawning) * Reset warning states (useful when starting new level or respawning)
*/ */