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
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
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
const hatRow = document.createElement('div');
hatRow.style.display = 'flex';
@ -323,6 +357,8 @@ export class ConfigUI extends SceneFeature {
lasersEnabled: true,
sideScreensEnabled: true,
consoleRGBEnabled: true,
consoleEnabled: true,
guestCount: 150,
djHat: 'None'
};
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.guestInput) this.guestInput.value = defaults.guestCount;
if (this.hatSelect) this.hatSelect.value = defaults.djHat;
this.updateStatus();
};

View File

@ -164,7 +164,7 @@ export class DJ extends SceneFeature {
if (this.armTimer <= 0) {
this.armState = Math.floor(Math.random() * 5);
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;
@ -176,7 +176,7 @@ export class DJ extends SceneFeature {
let targetLeftX = 0;
let targetRightX = 0;
if (this.armState === 4) {
if (this.armState === 4 && state.config.consoleEnabled) {
// Twiddling
targetLeftZ = 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.currentRightAngleX = THREE.MathUtils.lerp(this.currentRightAngleX, targetRightX, deltaTime * 5);
if (this.armState === 4) {
if (this.armState === 4 && state.config.consoleEnabled) {
const t = time * 15;
this.leftArm.rotation.z = -this.currentLeftAngle + Math.cos(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;
if (this.moveTimer <= 0) {
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') {
const speed = 1.5;

View File

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

View File

@ -7,7 +7,6 @@ import sceneFeatureManager from './SceneFeatureManager.js';
const stageHeight = 1.5;
const stageDepth = 5;
const length = 25;
const numGuests = 150;
const moveSpeed = 0.8;
const movementArea = { x: 15, z: length, y: 0, centerZ: -2 };
const jumpChance = 0.01;
@ -25,6 +24,7 @@ export class PartyGuests extends SceneFeature {
constructor() {
super();
this.guests = [];
this.guestPool = []; // Store all created guests to reuse them
sceneFeatureManager.register(this);
}
@ -36,96 +36,115 @@ export class PartyGuests extends SceneFeature {
const headGeo = new THREE.SphereGeometry(0.25, 16, 16);
// Arm: Ellipsoid (Scaled Sphere)
const armGeo = new THREE.SphereGeometry(0.12, 16, 16);
this.geometries = { bodyGeo, headGeo, armGeo };
const createGuests = () => {
for (let i = 0; i < numGuests; i++) {
// Random Color
// Dark gray-blue shades
const color = new THREE.Color().setHSL(
0.6 + (Math.random() * 0.1 - 0.05), // Hue around 0.6 (blue)
0.1 + Math.random() * 0.1, // Low saturation
0.01 + Math.random() * 0.05 // Much darker lightness
);
const material = new THREE.MeshStandardMaterial({
color: color,
roughness: 0.6,
metalness: 0.1,
});
// Initialize with config count
this.setGuestCount(state.config.guestCount);
}
const group = new THREE.Group();
createGuest() {
// Random Color
// Dark gray-blue shades
const color = new THREE.Color().setHSL(
0.6 + (Math.random() * 0.1 - 0.05), // Hue around 0.6 (blue)
0.1 + Math.random() * 0.1, // Low saturation
0.01 + Math.random() * 0.05 // Much darker lightness
);
const material = new THREE.MeshStandardMaterial({
color: color,
roughness: 0.6,
metalness: 0.1,
});
const scale = 0.85 + Math.random() * 0.3;
group.scale.setScalar(scale);
const group = new THREE.Group();
// Body
const body = new THREE.Mesh(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.castShadow = true;
body.receiveShadow = true;
group.add(body);
const scale = 0.85 + Math.random() * 0.3;
group.scale.setScalar(scale);
// Head
const head = new THREE.Mesh(headGeo, material);
head.position.y = 1.55; // Top of body
head.castShadow = true;
head.receiveShadow = true;
group.add(head);
// Body
const body = new THREE.Mesh(this.geometries.bodyGeo, material);
body.position.y = 0.7;
body.castShadow = true;
body.receiveShadow = true;
group.add(body);
// Arms
const createArm = (isLeft) => {
const pivot = new THREE.Group();
// Shoulder position
pivot.position.set(isLeft ? -0.35 : 0.35, 1.3, 0);
const arm = new THREE.Mesh(armGeo, material);
arm.scale.set(1, 3.5, 1); // Ellipsoid
arm.position.y = -0.4; // Hang down from pivot
arm.castShadow = true;
arm.receiveShadow = true;
pivot.add(arm);
return pivot;
};
// Head
const head = new THREE.Mesh(this.geometries.headGeo, material);
head.position.y = 1.55;
head.castShadow = true;
head.receiveShadow = true;
group.add(head);
const leftArm = createArm(true);
const rightArm = createArm(false);
group.add(leftArm);
group.add(rightArm);
// Position
const pos = new THREE.Vector3(
(Math.random() - 0.5) * movementArea.x,
0,
movementArea.centerZ + ( rushIn
? (((Math.random()-0.5) * length * 0.6) - length * 0.3)
: ((Math.random()-0.5) * length))
);
group.position.copy(pos);
group.visible = false;
state.scene.add(group);
this.guests.push({
mesh: group,
leftArm,
rightArm,
state: 'WAITING',
targetPosition: pos.clone(),
waitStartTime: 0,
waitTime: 3 + Math.random() * 4, // Wait longer: 3-7 seconds
isJumping: false,
jumpStartTime: 0,
jumpHeight: 0,
shouldRaiseArms: false,
handsUpTimer: 0,
handsRaisedType: 'BOTH',
randomOffset: Math.random() * 100
});
}
// Arms
const createArm = (isLeft) => {
const pivot = new THREE.Group();
// Shoulder position
pivot.position.set(isLeft ? -0.35 : 0.35, 1.3, 0);
const arm = new THREE.Mesh(this.geometries.armGeo, material);
arm.scale.set(1, 3.5, 1); // Ellipsoid
arm.position.y = -0.4; // Hang down from pivot
arm.castShadow = true;
arm.receiveShadow = true;
pivot.add(arm);
return pivot;
};
createGuests();
const leftArm = createArm(true);
const rightArm = createArm(false);
group.add(leftArm);
group.add(rightArm);
// Position
const pos = new THREE.Vector3(
(Math.random() - 0.5) * movementArea.x,
0,
movementArea.centerZ + ( rushIn
? (((Math.random()-0.5) * length * 0.6) - length * 0.3)
: ((Math.random()-0.5) * length))
);
group.position.copy(pos);
group.visible = false;
state.scene.add(group);
return {
mesh: group,
leftArm,
rightArm,
state: 'WAITING',
targetPosition: pos.clone(),
waitStartTime: 0,
waitTime: 3 + Math.random() * 4,
isJumping: false,
jumpStartTime: 0,
jumpHeight: 0,
shouldRaiseArms: false,
handsUpTimer: 0,
handsRaisedType: 'BOTH',
randomOffset: Math.random() * 100
};
}
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) {
@ -280,7 +299,7 @@ export class PartyGuests extends SceneFeature {
onPartyStart() {
const stageFrontZ = -40 / 2 + 5 + 5; // In front of the stage
this.guests.forEach((guestObj, index) => {
guestObj.mesh.visible = true;
guestObj.mesh.visible = true; // Make sure active guests are visible
// Rush to the stage
guestObj.state = 'MOVING';
if (index % 2 === 0) {

View File

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