Compare commits
2 Commits
415496b3a2
...
9b22b06d08
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b22b06d08 | |||
| b523d5e31a |
0
public/levels/1.json
Normal file
0
public/levels/1.json
Normal file
0
public/levels/directory.json
Normal file
0
public/levels/directory.json
Normal file
@ -34,18 +34,19 @@ export class ExplosionManager {
|
|||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
private static readonly DEFAULT_CONFIG: Required<ExplosionConfig> = {
|
private static readonly DEFAULT_CONFIG: Required<ExplosionConfig> = {
|
||||||
duration: 1000,
|
duration: 2000,
|
||||||
explosionForce: 5,
|
explosionForce: 10,
|
||||||
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: .2,
|
radius: 1,
|
||||||
subdivisions: 2
|
subdivisions: 2
|
||||||
}, DefaultScene.MainScene
|
}, DefaultScene.MainScene
|
||||||
);
|
);
|
||||||
@ -130,14 +131,13 @@ 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 explosion duration (synchronized with visual effect)
|
//Cleanup after sound ends.
|
||||||
setTimeout(() => {
|
sound.spatial.detach();
|
||||||
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();
|
||||||
|
|||||||
@ -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: 800,
|
duration: 2000,
|
||||||
explosionForce: 20.0,
|
explosionForce: 150.0,
|
||||||
frameRate: 60
|
frameRate: 60
|
||||||
});
|
});
|
||||||
await this._explosionManager.initialize();
|
await this._explosionManager.initialize();
|
||||||
@ -102,7 +102,6 @@ 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);
|
||||||
|
|||||||
@ -14,12 +14,14 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,28 +110,40 @@ 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
|
|
||||||
if (delta >= 0) {
|
// Clear warning states if resources increase above thresholds
|
||||||
return;
|
if (delta > 0) {
|
||||||
|
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.1 && !this._warningStates.has(`danger_${statusType}`)) {
|
if (percentage < 0.2 && !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}`);
|
||||||
this.queueMessage(['danger', statusType], VoiceMessagePriority.HIGH, false);
|
// Clear warning state if it exists (danger supersedes warning)
|
||||||
|
this.clearWarningState(`warning_${statusType}`);
|
||||||
|
this.queueMessage(['danger', statusType], VoiceMessagePriority.HIGH, false, 2000, `danger_${statusType}`);
|
||||||
}
|
}
|
||||||
// Warning (< 30%)
|
// Warning (10% <= x < 30%) - repeat every 4 seconds ONLY if not in danger
|
||||||
else if (percentage < 0.3 && !this._warningStates.has(`warning_${statusType}`)) {
|
else if (percentage >= 0.2 && percentage < 0.5 && !this._warningStates.has(`warning_${statusType}`) && !this._warningStates.has(`danger_${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);
|
this.queueMessage(['warning', statusType], VoiceMessagePriority.NORMAL, false, 4000, `warning_${statusType}`);
|
||||||
}
|
}
|
||||||
// Empty (= 0)
|
// Empty (= 0) - no repeat
|
||||||
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);
|
this.queueMessage([statusType, 'empty'], VoiceMessagePriority.HIGH, false, 0, `empty_${statusType}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,13 +151,25 @@ export class VoiceAudioSystem {
|
|||||||
/**
|
/**
|
||||||
* Queue a voice message to be played
|
* Queue a voice message to be played
|
||||||
*/
|
*/
|
||||||
public queueMessage(sounds: string[], priority: VoiceMessagePriority = VoiceMessagePriority.NORMAL, interrupt: boolean = false): void {
|
public queueMessage(
|
||||||
|
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 = { sounds, priority, interrupt };
|
const message: VoiceMessage = {
|
||||||
|
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) {
|
||||||
@ -159,7 +185,8 @@ export class VoiceAudioSystem {
|
|||||||
this._queue.splice(insertIndex, 0, message);
|
this._queue.splice(insertIndex, 0, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog(`VoiceAudioSystem: Queued message [${sounds.join(', ')}] with priority ${priority}`);
|
const repeatInfo = repeatInterval > 0 ? ` (repeat every ${repeatInterval}ms)` : '';
|
||||||
|
debugLog(`VoiceAudioSystem: Queued message [${sounds.join(', ')}] with priority ${priority}${repeatInfo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,6 +224,22 @@ 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;
|
||||||
@ -207,6 +250,19 @@ 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;
|
||||||
@ -262,6 +318,26 @@ 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)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user