Feature: configurable toggle for music console and count of guests

This commit is contained in:
Dejvino 2026-01-04 06:28:28 +00:00
parent dcf12771d6
commit 6b292b32ad
5 changed files with 148 additions and 87 deletions

View File

@ -114,9 +114,43 @@ export class ConfigUI extends SceneFeature {
// Side Screens Toggle // Side Screens Toggle
createToggle('Side Screens', 'sideScreensEnabled'); createToggle('Side Screens', 'sideScreensEnabled');
// Music Console Toggle
createToggle('Music Console', 'consoleEnabled', (enabled) => {
const consoleFeature = sceneFeatureManager.features.find(f => f.constructor.name === 'MusicConsole');
if (consoleFeature && consoleFeature.group) consoleFeature.group.visible = enabled;
});
// Console RGB Toggle // Console RGB Toggle
createToggle('Console RGB Panel', 'consoleRGBEnabled'); createToggle('Console RGB Panel', 'consoleRGBEnabled');
// Guest Count Input
const guestRow = document.createElement('div');
guestRow.style.display = 'flex';
guestRow.style.alignItems = 'center';
guestRow.style.justifyContent = 'space-between';
const guestLabel = document.createElement('label');
guestLabel.innerText = 'Guest Count';
const guestInput = document.createElement('input');
guestInput.type = 'number';
guestInput.min = '0';
guestInput.max = '500';
guestInput.value = state.config.guestCount;
guestInput.style.width = '60px';
guestInput.onchange = (e) => {
const val = parseInt(e.target.value, 10);
state.config.guestCount = val;
saveConfig();
const guestsFeature = sceneFeatureManager.features.find(f => f.constructor.name === 'PartyGuests');
if (guestsFeature) guestsFeature.setGuestCount(val);
};
this.guestInput = guestInput;
guestRow.appendChild(guestLabel);
guestRow.appendChild(guestInput);
rightContainer.appendChild(guestRow);
// DJ Hat Selector // DJ Hat Selector
const hatRow = document.createElement('div'); const hatRow = document.createElement('div');
hatRow.style.display = 'flex'; hatRow.style.display = 'flex';
@ -323,6 +357,8 @@ export class ConfigUI extends SceneFeature {
lasersEnabled: true, lasersEnabled: true,
sideScreensEnabled: true, sideScreensEnabled: true,
consoleRGBEnabled: true, consoleRGBEnabled: true,
consoleEnabled: true,
guestCount: 150,
djHat: 'None' djHat: 'None'
}; };
for (const key in defaults) { for (const key in defaults) {
@ -332,6 +368,7 @@ export class ConfigUI extends SceneFeature {
if (this.toggles[key].callback) this.toggles[key].callback(defaults[key]); if (this.toggles[key].callback) this.toggles[key].callback(defaults[key]);
} }
} }
if (this.guestInput) this.guestInput.value = defaults.guestCount;
if (this.hatSelect) this.hatSelect.value = defaults.djHat; if (this.hatSelect) this.hatSelect.value = defaults.djHat;
this.updateStatus(); this.updateStatus();
}; };

View File

@ -164,7 +164,7 @@ export class DJ extends SceneFeature {
if (this.armTimer <= 0) { if (this.armTimer <= 0) {
this.armState = Math.floor(Math.random() * 5); this.armState = Math.floor(Math.random() * 5);
this.armTimer = 2 + Math.random() * 4; this.armTimer = 2 + Math.random() * 4;
if (Math.random() < 0.3) this.armState = 4; // Twiddling some more if (Math.random() < 0.3 && state.config.consoleEnabled) this.armState = 4; // Twiddling some more
} }
const upAngle = Math.PI * 0.85; const upAngle = Math.PI * 0.85;
@ -176,7 +176,7 @@ export class DJ extends SceneFeature {
let targetLeftX = 0; let targetLeftX = 0;
let targetRightX = 0; let targetRightX = 0;
if (this.armState === 4) { if (this.armState === 4 && state.config.consoleEnabled) {
// Twiddling // Twiddling
targetLeftZ = 0.2; targetLeftZ = 0.2;
targetRightZ = 0.2; targetRightZ = 0.2;
@ -192,7 +192,7 @@ export class DJ extends SceneFeature {
this.currentLeftAngleX = THREE.MathUtils.lerp(this.currentLeftAngleX, targetLeftX, deltaTime * 5); this.currentLeftAngleX = THREE.MathUtils.lerp(this.currentLeftAngleX, targetLeftX, deltaTime * 5);
this.currentRightAngleX = THREE.MathUtils.lerp(this.currentRightAngleX, targetRightX, deltaTime * 5); this.currentRightAngleX = THREE.MathUtils.lerp(this.currentRightAngleX, targetRightX, deltaTime * 5);
if (this.armState === 4) { if (this.armState === 4 && state.config.consoleEnabled) {
const t = time * 15; const t = time * 15;
this.leftArm.rotation.z = -this.currentLeftAngle + Math.cos(t) * 0.05; this.leftArm.rotation.z = -this.currentLeftAngle + Math.cos(t) * 0.05;
this.rightArm.rotation.z = this.currentRightAngle + Math.sin(t) * 0.05; this.rightArm.rotation.z = this.currentRightAngle + Math.sin(t) * 0.05;
@ -215,7 +215,8 @@ export class DJ extends SceneFeature {
this.moveTimer -= deltaTime; this.moveTimer -= deltaTime;
if (this.moveTimer <= 0) { if (this.moveTimer <= 0) {
this.state = 'MOVING'; this.state = 'MOVING';
this.targetX = (Math.random() - 0.5) * 2.5; const range = state.config.consoleEnabled ? 2.5 : 10.0;
this.targetX = (Math.random() - 0.5) * range;
} }
} else if (this.state === 'MOVING') { } else if (this.state === 'MOVING') {
const speed = 1.5; const speed = 1.5;

View File

@ -21,6 +21,8 @@ export class MusicConsole extends SceneFeature {
// Position on stage, centered // Position on stage, centered
group.position.set(0, stageY, -16.5); group.position.set(0, stageY, -16.5);
state.scene.add(group); state.scene.add(group);
this.group = group;
this.group.visible = state.config.consoleEnabled;
// 1. The Stand/Table Body // 1. The Stand/Table Body
const standGeo = new THREE.BoxGeometry(consoleWidth, consoleHeight, consoleDepth); const standGeo = new THREE.BoxGeometry(consoleWidth, consoleHeight, consoleDepth);

View File

@ -7,7 +7,6 @@ import sceneFeatureManager from './SceneFeatureManager.js';
const stageHeight = 1.5; const stageHeight = 1.5;
const stageDepth = 5; const stageDepth = 5;
const length = 25; const length = 25;
const numGuests = 150;
const moveSpeed = 0.8; const moveSpeed = 0.8;
const movementArea = { x: 15, z: length, y: 0, centerZ: -2 }; const movementArea = { x: 15, z: length, y: 0, centerZ: -2 };
const jumpChance = 0.01; const jumpChance = 0.01;
@ -25,6 +24,7 @@ export class PartyGuests extends SceneFeature {
constructor() { constructor() {
super(); super();
this.guests = []; this.guests = [];
this.guestPool = []; // Store all created guests to reuse them
sceneFeatureManager.register(this); sceneFeatureManager.register(this);
} }
@ -37,8 +37,13 @@ export class PartyGuests extends SceneFeature {
// Arm: Ellipsoid (Scaled Sphere) // Arm: Ellipsoid (Scaled Sphere)
const armGeo = new THREE.SphereGeometry(0.12, 16, 16); const armGeo = new THREE.SphereGeometry(0.12, 16, 16);
const createGuests = () => { this.geometries = { bodyGeo, headGeo, armGeo };
for (let i = 0; i < numGuests; i++) {
// Initialize with config count
this.setGuestCount(state.config.guestCount);
}
createGuest() {
// Random Color // Random Color
// Dark gray-blue shades // Dark gray-blue shades
const color = new THREE.Color().setHSL( const color = new THREE.Color().setHSL(
@ -58,15 +63,15 @@ export class PartyGuests extends SceneFeature {
group.scale.setScalar(scale); group.scale.setScalar(scale);
// Body // Body
const body = new THREE.Mesh(bodyGeo, material); const body = new THREE.Mesh(this.geometries.bodyGeo, material);
body.position.y = 0.7; // Center of capsule (0.8 length + 0.3*2 radius = 1.4 total height. Center at 0.7) body.position.y = 0.7;
body.castShadow = true; body.castShadow = true;
body.receiveShadow = true; body.receiveShadow = true;
group.add(body); group.add(body);
// Head // Head
const head = new THREE.Mesh(headGeo, material); const head = new THREE.Mesh(this.geometries.headGeo, material);
head.position.y = 1.55; // Top of body head.position.y = 1.55;
head.castShadow = true; head.castShadow = true;
head.receiveShadow = true; head.receiveShadow = true;
group.add(head); group.add(head);
@ -77,7 +82,7 @@ export class PartyGuests extends SceneFeature {
// Shoulder position // Shoulder position
pivot.position.set(isLeft ? -0.35 : 0.35, 1.3, 0); pivot.position.set(isLeft ? -0.35 : 0.35, 1.3, 0);
const arm = new THREE.Mesh(armGeo, material); const arm = new THREE.Mesh(this.geometries.armGeo, material);
arm.scale.set(1, 3.5, 1); // Ellipsoid arm.scale.set(1, 3.5, 1); // Ellipsoid
arm.position.y = -0.4; // Hang down from pivot arm.position.y = -0.4; // Hang down from pivot
arm.castShadow = true; arm.castShadow = true;
@ -106,14 +111,14 @@ export class PartyGuests extends SceneFeature {
group.visible = false; group.visible = false;
state.scene.add(group); state.scene.add(group);
this.guests.push({ return {
mesh: group, mesh: group,
leftArm, leftArm,
rightArm, rightArm,
state: 'WAITING', state: 'WAITING',
targetPosition: pos.clone(), targetPosition: pos.clone(),
waitStartTime: 0, waitStartTime: 0,
waitTime: 3 + Math.random() * 4, // Wait longer: 3-7 seconds waitTime: 3 + Math.random() * 4,
isJumping: false, isJumping: false,
jumpStartTime: 0, jumpStartTime: 0,
jumpHeight: 0, jumpHeight: 0,
@ -121,11 +126,25 @@ export class PartyGuests extends SceneFeature {
handsUpTimer: 0, handsUpTimer: 0,
handsRaisedType: 'BOTH', handsRaisedType: 'BOTH',
randomOffset: Math.random() * 100 randomOffset: Math.random() * 100
});
}
}; };
}
createGuests(); setGuestCount(count) {
// Ensure we have enough guests in the pool
while (this.guestPool.length < count) {
this.guestPool.push(this.createGuest());
}
// Update visibility and active list
this.guests = [];
this.guestPool.forEach((guest, index) => {
if (index < count) {
guest.mesh.visible = state.partyStarted; // Only visible if party started
this.guests.push(guest);
} else {
guest.mesh.visible = false;
}
});
} }
update(deltaTime) { update(deltaTime) {
@ -280,7 +299,7 @@ export class PartyGuests extends SceneFeature {
onPartyStart() { onPartyStart() {
const stageFrontZ = -40 / 2 + 5 + 5; // In front of the stage const stageFrontZ = -40 / 2 + 5 + 5; // In front of the stage
this.guests.forEach((guestObj, index) => { this.guests.forEach((guestObj, index) => {
guestObj.mesh.visible = true; guestObj.mesh.visible = true; // Make sure active guests are visible
// Rush to the stage // Rush to the stage
guestObj.state = 'MOVING'; guestObj.state = 'MOVING';
if (index % 2 === 0) { if (index % 2 === 0) {

View File

@ -8,6 +8,8 @@ export function initState() {
lasersEnabled: true, lasersEnabled: true,
sideScreensEnabled: true, sideScreensEnabled: true,
consoleRGBEnabled: true, consoleRGBEnabled: true,
consoleEnabled: true,
guestCount: 150,
djHat: 'None' // 'None', 'Santa', 'Top Hat' djHat: 'None' // 'None', 'Santa', 'Top Hat'
}; };
try { try {