Feature: organized guest modes
This commit is contained in:
parent
eef13e301c
commit
c56e07b083
@ -26,6 +26,45 @@ export class PartyGuests extends SceneFeature {
|
|||||||
this.guests = [];
|
this.guests = [];
|
||||||
this.guestPool = []; // Store all created guests to reuse them
|
this.guestPool = []; // Store all created guests to reuse them
|
||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
|
|
||||||
|
this.organizedMode = null;
|
||||||
|
this.organizedModeTimer = 0;
|
||||||
|
this.wasBlackout = false;
|
||||||
|
this.lastOrganizedLogTime = 0;
|
||||||
|
this.organizedModeMinDuration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
startOrganizedMode() {
|
||||||
|
this.organizedModeTimer = 10 + Math.random() * 10; // 10-20 seconds
|
||||||
|
this.organizedModeMinDuration = 5.0;
|
||||||
|
|
||||||
|
const music = state.music;
|
||||||
|
this.organizedMode = 'RUNNING'; // Default
|
||||||
|
|
||||||
|
if (music) {
|
||||||
|
if (music.beatIntensity > 0.7) {
|
||||||
|
// High intensity: Moshpit or Jumping
|
||||||
|
this.organizedMode = Math.random() < 0.5 ? 'JUMPING' : 'MOSHPIT';
|
||||||
|
} else if (music.loudnessHighs > 0.6) {
|
||||||
|
this.organizedMode = 'HANDS_UP';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random variation
|
||||||
|
if (Math.random() < 0.4) {
|
||||||
|
const modes = ['RUNNING', 'JUMPING', 'HANDS_UP', 'CIRCLE_PIT', 'MOSHPIT'];
|
||||||
|
this.organizedMode = modes[Math.floor(Math.random() * modes.length)];
|
||||||
|
}
|
||||||
|
console.log(`Organized mode started: ${this.organizedMode}`);
|
||||||
|
|
||||||
|
this.guests.forEach(guest => {
|
||||||
|
let chance = 0.1;
|
||||||
|
// Higher chance if in the middle or front
|
||||||
|
if (Math.abs(guest.mesh.position.x) < 5) chance += 0.3;
|
||||||
|
if (guest.mesh.position.z < -5) chance += 0.3;
|
||||||
|
|
||||||
|
guest.isInOrganizedMode = Math.random() < chance;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -125,7 +164,8 @@ export class PartyGuests extends SceneFeature {
|
|||||||
shouldRaiseArms: false,
|
shouldRaiseArms: false,
|
||||||
handsUpTimer: 0,
|
handsUpTimer: 0,
|
||||||
handsRaisedType: 'BOTH',
|
handsRaisedType: 'BOTH',
|
||||||
randomOffset: Math.random() * 100
|
randomOffset: Math.random() * 100,
|
||||||
|
isInOrganizedMode: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +191,46 @@ export class PartyGuests extends SceneFeature {
|
|||||||
if (this.guests.length === 0 || !state.partyStarted) return;
|
if (this.guests.length === 0 || !state.partyStarted) return;
|
||||||
|
|
||||||
const time = state.clock.getElapsedTime();
|
const time = state.clock.getElapsedTime();
|
||||||
|
|
||||||
|
// --- Organized Mode Logic ---
|
||||||
|
const isBlackout = state.blackoutMode;
|
||||||
|
if (!isBlackout && this.wasBlackout) {
|
||||||
|
// Blackout just ended
|
||||||
|
if (Math.random() < 0.2) {
|
||||||
|
this.startOrganizedMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.wasBlackout = isBlackout;
|
||||||
|
|
||||||
|
if (this.organizedMode) {
|
||||||
|
this.organizedModeTimer -= deltaTime;
|
||||||
|
this.organizedModeMinDuration -= deltaTime;
|
||||||
|
if (time - this.lastOrganizedLogTime > 1.0) {
|
||||||
|
const count = this.guests.filter(g => g.isInOrganizedMode).length;
|
||||||
|
console.log(`Organized Event Active: ${this.organizedMode}. Participants: ${count}`);
|
||||||
|
this.lastOrganizedLogTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.organizedModeTimer <= 0 || (isBlackout && this.organizedModeMinDuration <= 0)) {
|
||||||
|
console.log(`Organized mode ended: ${this.organizedMode}`);
|
||||||
|
|
||||||
|
this.guests.forEach(guest => {
|
||||||
|
if (guest.isInOrganizedMode) {
|
||||||
|
guest.state = 'MOVING';
|
||||||
|
guest.targetPosition.set(
|
||||||
|
(Math.random() - 0.5) * movementArea.x,
|
||||||
|
0,
|
||||||
|
movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
|
||||||
|
);
|
||||||
|
guest.moveSpeed = moveSpeed * 1.5;
|
||||||
|
guest.isInOrganizedMode = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.organizedMode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const minDistance = 0.8; // Minimum distance to maintain
|
const minDistance = 0.8; // Minimum distance to maintain
|
||||||
const minDistSq = minDistance * minDistance;
|
const minDistSq = minDistance * minDistance;
|
||||||
|
|
||||||
@ -171,6 +251,35 @@ export class PartyGuests extends SceneFeature {
|
|||||||
this.guests.forEach((guestObj, i) => {
|
this.guests.forEach((guestObj, i) => {
|
||||||
const { mesh, leftArm, rightArm } = guestObj;
|
const { mesh, leftArm, rightArm } = guestObj;
|
||||||
|
|
||||||
|
// Dynamic join/leave logic
|
||||||
|
if (this.organizedMode) {
|
||||||
|
if (guestObj.isInOrganizedMode) {
|
||||||
|
if (Math.random() < 0.001) guestObj.isInOrganizedMode = false;
|
||||||
|
} else {
|
||||||
|
if (Math.random() < 0.001) guestObj.isInOrganizedMode = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
guestObj.isInOrganizedMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentMoveSpeed = moveSpeed;
|
||||||
|
if (this.organizedMode === 'RUNNING' && guestObj.isInOrganizedMode) {
|
||||||
|
currentMoveSpeed = moveSpeed * 2.0;
|
||||||
|
if (guestObj.state === 'WAITING') {
|
||||||
|
guestObj.waitTime = 0;
|
||||||
|
}
|
||||||
|
} else if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode) {
|
||||||
|
currentMoveSpeed = moveSpeed * 2.0;
|
||||||
|
if (guestObj.state === 'WAITING') {
|
||||||
|
guestObj.waitTime = 0;
|
||||||
|
}
|
||||||
|
} else if (this.organizedMode === 'HANDS_UP' && guestObj.isInOrganizedMode) {
|
||||||
|
guestObj.handsUpTimer = 0.5;
|
||||||
|
guestObj.handsRaisedType = 'BOTH';
|
||||||
|
} else if (this.organizedMode && !guestObj.isInOrganizedMode) {
|
||||||
|
currentMoveSpeed = moveSpeed * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Collision Avoidance ---
|
// --- Collision Avoidance ---
|
||||||
let separationX = 0;
|
let separationX = 0;
|
||||||
let separationZ = 0;
|
let separationZ = 0;
|
||||||
@ -207,6 +316,56 @@ export class PartyGuests extends SceneFeature {
|
|||||||
mesh.position.x += separationX * separationStrength * deltaTime;
|
mesh.position.x += separationX * separationStrength * deltaTime;
|
||||||
mesh.position.z += separationZ * separationStrength * deltaTime;
|
mesh.position.z += separationZ * separationStrength * deltaTime;
|
||||||
|
|
||||||
|
if (this.organizedMode === 'CIRCLE_PIT' && guestObj.isInOrganizedMode) {
|
||||||
|
// --- Circle Pit Logic ---
|
||||||
|
const centerZ = movementArea.centerZ;
|
||||||
|
const radius = 3.5;
|
||||||
|
const speed = 5.0;
|
||||||
|
|
||||||
|
const dx = mesh.position.x;
|
||||||
|
const dz = mesh.position.z - centerZ;
|
||||||
|
const dist = Math.sqrt(dx*dx + dz*dz);
|
||||||
|
|
||||||
|
if (dist > radius + 1.0) {
|
||||||
|
// Run towards circle
|
||||||
|
const dirX = -dx / dist;
|
||||||
|
const dirZ = -dz / dist;
|
||||||
|
|
||||||
|
mesh.position.x += dirX * speed * deltaTime;
|
||||||
|
mesh.position.z += dirZ * speed * deltaTime;
|
||||||
|
|
||||||
|
mesh.rotation.y = Math.atan2(dirX, dirZ);
|
||||||
|
} else {
|
||||||
|
// Run in circle
|
||||||
|
// Tangent vector (counter-clockwise)
|
||||||
|
let vx = -dz;
|
||||||
|
let vz = dx;
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
if (dist > 0.001) {
|
||||||
|
vx /= dist;
|
||||||
|
vz /= dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move along tangent
|
||||||
|
mesh.position.x += vx * speed * deltaTime;
|
||||||
|
mesh.position.z += vz * speed * deltaTime;
|
||||||
|
|
||||||
|
// Radius Correction (Pull towards ideal circle)
|
||||||
|
const ratio = 1.0 + (radius - dist) * 2.0 * deltaTime;
|
||||||
|
mesh.position.x *= ratio; // Assumes center X is 0
|
||||||
|
mesh.position.z = centerZ + (mesh.position.z - centerZ) * ratio;
|
||||||
|
|
||||||
|
// Face movement direction
|
||||||
|
mesh.rotation.y = Math.atan2(vx, vz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep state as MOVING so other logic (like jumping) works, but skip standard pathfinding
|
||||||
|
guestObj.state = 'MOVING';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// --- Standard Logic (includes MOSHPIT) ---
|
||||||
|
|
||||||
if (guestObj.state === 'WAITING') {
|
if (guestObj.state === 'WAITING') {
|
||||||
// Face the stage (approx z = -20)
|
// Face the stage (approx z = -20)
|
||||||
const dx = 0 - mesh.position.x;
|
const dx = 0 - mesh.position.x;
|
||||||
@ -226,13 +385,45 @@ export class PartyGuests extends SceneFeature {
|
|||||||
mesh.rotation.z = 0;
|
mesh.rotation.z = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moshpit: Don't wait long
|
||||||
|
if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode) {
|
||||||
|
guestObj.waitTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (time > guestObj.waitStartTime + guestObj.waitTime) {
|
if (time > guestObj.waitStartTime + guestObj.waitTime) {
|
||||||
|
// Gravitate towards stage (negative Z)
|
||||||
|
const minZ = movementArea.centerZ - movementArea.z / 2;
|
||||||
|
const maxZ = movementArea.centerZ + movementArea.z / 2;
|
||||||
|
const zRatio = Math.pow(Math.random(), 1.5); // Bias towards 0 (front)
|
||||||
|
|
||||||
const newTarget = new THREE.Vector3(
|
const newTarget = new THREE.Vector3(
|
||||||
(Math.random() - 0.5) * movementArea.x,
|
(Math.random() - 0.5) * movementArea.x,
|
||||||
0,
|
0,
|
||||||
movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
|
minZ + zRatio * (maxZ - minZ)
|
||||||
);
|
);
|
||||||
guestObj.targetPosition = newTarget;
|
guestObj.targetPosition = newTarget;
|
||||||
|
|
||||||
|
// Moshpit: Target center area more often
|
||||||
|
if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode) {
|
||||||
|
const range = 5.0;
|
||||||
|
guestObj.targetPosition.set(
|
||||||
|
(Math.random() - 0.5) * range,
|
||||||
|
0,
|
||||||
|
movementArea.centerZ + (Math.random() - 0.5) * range
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-participants: Disperse away from center
|
||||||
|
if (this.organizedMode && !guestObj.isInOrganizedMode) {
|
||||||
|
const side = Math.sign(mesh.position.x) || (Math.random() < 0.5 ? 1 : -1);
|
||||||
|
const xOffset = (movementArea.x / 2) * (0.5 + Math.random() * 0.4);
|
||||||
|
guestObj.targetPosition.set(
|
||||||
|
side * xOffset,
|
||||||
|
0,
|
||||||
|
movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
guestObj.state = 'MOVING';
|
guestObj.state = 'MOVING';
|
||||||
}
|
}
|
||||||
} else if (guestObj.state === 'MOVING') {
|
} else if (guestObj.state === 'MOVING') {
|
||||||
@ -240,9 +431,20 @@ export class PartyGuests extends SceneFeature {
|
|||||||
const targetPosFlat = new THREE.Vector3(guestObj.targetPosition.x, 0, guestObj.targetPosition.z);
|
const targetPosFlat = new THREE.Vector3(guestObj.targetPosition.x, 0, guestObj.targetPosition.z);
|
||||||
|
|
||||||
const distance = currentPosFlat.distanceTo(targetPosFlat);
|
const distance = currentPosFlat.distanceTo(targetPosFlat);
|
||||||
|
|
||||||
|
// Moshpit: Change direction frequently
|
||||||
|
if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode && Math.random() < 0.05) {
|
||||||
|
const range = 5.0;
|
||||||
|
guestObj.targetPosition.set(
|
||||||
|
(Math.random() - 0.5) * range,
|
||||||
|
0,
|
||||||
|
movementArea.centerZ + (Math.random() - 0.5) * range
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (distance > 0.1) {
|
if (distance > 0.1) {
|
||||||
const direction = targetPosFlat.sub(currentPosFlat).normalize();
|
const direction = targetPosFlat.sub(currentPosFlat).normalize();
|
||||||
mesh.position.add(direction.multiplyScalar(moveSpeed * deltaTime));
|
mesh.position.add(direction.multiplyScalar(currentMoveSpeed * deltaTime));
|
||||||
|
|
||||||
// If moving away from stage (positive Z), drop hands
|
// If moving away from stage (positive Z), drop hands
|
||||||
if (direction.z > 0.1) {
|
if (direction.z > 0.1) {
|
||||||
@ -267,6 +469,7 @@ export class PartyGuests extends SceneFeature {
|
|||||||
guestObj.waitTime = waitTimeBase + Math.random() * waitTimeVariance;
|
guestObj.waitTime = waitTimeBase + Math.random() * waitTimeVariance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // End else (Standard Logic)
|
||||||
|
|
||||||
// Update hands up timer
|
// Update hands up timer
|
||||||
if (guestObj.handsUpTimer > 0) {
|
if (guestObj.handsUpTimer > 0) {
|
||||||
@ -283,7 +486,9 @@ export class PartyGuests extends SceneFeature {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let currentJumpChance = jumpChance * deltaTime; // Base chance over time
|
let currentJumpChance = jumpChance * deltaTime; // Base chance over time
|
||||||
if (state.music && state.music.isLoudEnough && state.music.beatIntensity > 0.8) {
|
if (this.organizedMode === 'JUMPING' && guestObj.isInOrganizedMode) {
|
||||||
|
currentJumpChance = 5.0 * deltaTime;
|
||||||
|
} else if (state.music && state.music.isLoudEnough && state.music.beatIntensity > 0.8) {
|
||||||
currentJumpChance = 0.1; // High, fixed chance on the beat
|
currentJumpChance = 0.1; // High, fixed chance on the beat
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +497,7 @@ export class PartyGuests extends SceneFeature {
|
|||||||
guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
|
guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
|
||||||
guestObj.jumpStartTime = time;
|
guestObj.jumpStartTime = time;
|
||||||
|
|
||||||
if (Math.random() < 0.5) {
|
if (Math.random() < 0.1) {
|
||||||
guestObj.handsUpTimer = 2.0 + Math.random() * 3.0; // Keep hands up for 2-5 seconds
|
guestObj.handsUpTimer = 2.0 + Math.random() * 3.0; // Keep hands up for 2-5 seconds
|
||||||
const r = Math.random();
|
const r = Math.random();
|
||||||
if (r < 0.33) guestObj.handsRaisedType = 'LEFT';
|
if (r < 0.33) guestObj.handsRaisedType = 'LEFT';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user