HollowBond/Public/ecma/Objects/Game/ElevatorObjectGame.ecma.js

263 lines
9.9 KiB
JavaScript

"use strict";
const ElevatorObjectGame = (function(){
const STATES = {
IDLE_CLOSED: "idle_closed",
WAITING_ARRIVAL: "waiting_arrival", // Pequeña espera tras pulsar llamada
OPENING: "opening",
IDLE_OPEN: "idle_open", // Abierto esperando entrada
CLOSING: "closing",
TRANSIT: "transit" // Viaje a oscuras
};
const ROTATIONS = {
south: 0,
north: Math.PI,
east: Math.PI * 0.5,
west: -Math.PI * 0.5
};
// Función de interpolación suave (aceleración y deceleración)
const smoothstep = t => t * t * (3 - 2 * t);
const ElevatorObjectGame = function(hollow_bond, model, material_factory, geometry_factory){
const self = this;
this.model = model;
this.state = STATES.IDLE_CLOSED;
this.door_openness = 0.0; // 0 = cerrada, 1 = abierta
this.timer = 0.0;
const DOOR_TRANSITION_TIME = 1.25; // Duración de apertura/cierre en segundos
const geom_fac = geometry_factory
|| (hollow_bond.geometries && hollow_bond.geometries.elevator)
|| new ElevatorGeometryGame(hollow_bond);
const mat_fac = material_factory
|| (hollow_bond.materials && hollow_bond.materials.elevator)
|| new ElevatorMaterialGame(hollow_bond);
// 1. Contenedor Raíz
this.mesh = new THREE.Group();
const base_x = model.x + (model.width * 0.5);
const base_y = model.y * hollow_bond.room_height;
const base_z = model.z + (model.depth * 0.5);
this.mesh.position.set(base_x, base_y, base_z);
this.mesh.rotation.y = ROTATIONS[model.facing] || 0;
// 2. Malla de la Cabina
const cabin_geo = geom_fac.get_cabin_geometry(model);
const cabin_mat = mat_fac.get_cabin_material();
this.cabin_mesh = new THREE.Mesh(cabin_geo, cabin_mat);
this.mesh.add(this.cabin_mesh);
// 3. Plafón Fluorescente
const light_geo = geom_fac.get_light_geometry(model);
this.light_mat = mat_fac.get_light_material();
this.light_mesh = new THREE.Mesh(light_geo, this.light_mat);
this.mesh.add(this.light_mesh);
// 4. Puertas Correderas (Hojas Izquierda y Derecha)
const door_geo = geom_fac.get_door_leaf_geometry(model);
const hd = (model.depth || hollow_bond.elevator_size || 2.0) * 0.5;
this.door_left = new THREE.Mesh(door_geo, cabin_mat);
this.door_left.position.set(-0.28, 0, hd - 0.04);
this.mesh.add(this.door_left);
this.door_right = new THREE.Mesh(door_geo, cabin_mat);
this.door_right.position.set(0.28, 0, hd - 0.04);
this.mesh.add(this.door_right);
// 5. Marco Frontal
const frame_geom_fac = (hollow_bond.geometries && hollow_bond.geometries.elevator_frame)
|| new ElevatorFrameGeometryGame(hollow_bond);
const frame_geo = frame_geom_fac.get_for_elevator(model);
this.frame_mesh = new THREE.Mesh(frame_geo, [cabin_mat, this.light_mat]);
this.frame_mesh.position.set(0, 0, hd);
this.mesh.add(this.frame_mesh);
// 6. Collider físico dinámico
this.collider = new ElevatorCollider(hollow_bond, model, self);
/**
* Comprueba si el rayo de la cámara apunta al ascensor (marco, puertas o botón)
*/
this.try_click_button = raycaster => {
const hits = raycaster.intersectObject(self.mesh, true);
// 1. Detección por rayo directo a cualquier parte de la cabina o puertas
if(hits.length > 0 && hits[0].distance <= 3.2){
return self.trigger_interaction();
}
// 2. Fallback por proximidad: si estás frente a la puerta (< 2.2m) y haces click
const dungeon = hollow_bond.dungeon;
if(dungeon && dungeon.camera){
const p_pos = dungeon.camera.position;
const dist_sq = (p_pos.x - base_x) * (p_pos.x - base_x) + (p_pos.z - base_z) * (p_pos.z - base_z);
const feet_y = p_pos.y - 1.75;
if(dist_sq <= (2.4 * 2.4) && Math.abs(feet_y - base_y) < 1.0){
return self.trigger_interaction();
}
}
return false;
};
/**
* Transición de estados al pulsar el botón
*/
this.trigger_interaction = () => {
const dungeon = hollow_bond.dungeon;
if(!dungeon || !dungeon.camera) return false;
const is_in = self.collider.is_inside(dungeon.camera.position.x, dungeon.camera.position.z);
// Botón exterior (llamada)
if(!is_in && self.state === STATES.IDLE_CLOSED){
self.state = STATES.WAITING_ARRIVAL;
self.timer = 1.5; // Breve espera con sonido/parpadeo de llegada
return true;
}
// Botón interior (cierre y despegue)
if(is_in && (self.state === STATES.IDLE_OPEN || self.state === STATES.IDLE_CLOSED)){
self.state = STATES.CLOSING;
self.timer = 0.0;
return true;
}
return false;
};
const execute_transit = () => {
const map = hollow_bond.map;
if(!map || !map.elevators) return;
const group_cabins = [];
for(const eid in map.elevators){
const el = map.elevators[eid];
if(el.group_id === model.group_id){
group_cabins.push(el);
}
}
if(group_cabins.length <= 1) return;
group_cabins.sort((a, b) => a.floor - b.floor);
const cur_index = group_cabins.findIndex(c => c.id === model.id);
const next_index = (cur_index + 1) % group_cabins.length;
const target_model = group_cabins[next_index];
const dungeon = hollow_bond.dungeon;
if(!dungeon || !dungeon.camera) return;
const target_cx = target_model.x + (target_model.width * 0.5);
const target_cz = target_model.z + (target_model.depth * 0.5);
const target_by = target_model.y * hollow_bond.room_height;
const eye_h = (hollow_bond.controls && hollow_bond.controls.game) ? hollow_bond.controls.game.current_height : 1.75;
dungeon.camera.position.set(target_cx, target_by + eye_h, target_cz);
const angle_delta = (ROTATIONS[target_model.facing] || 0) - (ROTATIONS[model.facing] || 0);
if(hollow_bond.controls && hollow_bond.controls.game){
hollow_bond.controls.game.yaw = (hollow_bond.controls.game.yaw || 0) + angle_delta;
}
dungeon.check_room_transition && dungeon.check_room_transition();
dungeon.update_rooms_streaming && dungeon.update_rooms_streaming();
const target_obj = dungeon.objects.elevator ? dungeon.objects.elevator[target_model.id] : null;
if(target_obj){
target_obj.state = STATES.OPENING;
target_obj.timer = 0.0;
}
self.state = STATES.IDLE_CLOSED;
self.door_openness = 0.0;
};
/**
* Actualización continua de estados y animación con Smoothstep
*/
this.update = delta => {
switch(self.state){
case STATES.WAITING_ARRIVAL:
self.timer -= delta;
if(self.timer <= 0){
self.state = STATES.OPENING;
self.timer = 0.0;
}
break;
case STATES.OPENING:
self.timer += delta;
const open_t = Math.min(1.0, self.timer / DOOR_TRANSITION_TIME);
self.door_openness = smoothstep(open_t);
if(open_t >= 1.0){
self.state = STATES.IDLE_OPEN;
self.timer = 5.0; // Se queda abierto 5 segundos
}
break;
case STATES.IDLE_OPEN:
self.timer -= delta;
if(self.timer <= 0){
self.state = STATES.CLOSING;
self.timer = 0.0;
}
break;
case STATES.CLOSING:
self.timer += delta;
const close_t = Math.min(1.0, self.timer / DOOR_TRANSITION_TIME);
self.door_openness = 1.0 - smoothstep(close_t);
if(close_t >= 1.0){
self.door_openness = 0.0;
const dungeon = hollow_bond.dungeon;
const is_in = dungeon && dungeon.camera && self.collider.is_inside(dungeon.camera.position.x, dungeon.camera.position.z);
if(is_in){
self.state = STATES.TRANSIT;
self.timer = 2.4;
} else {
self.state = STATES.IDLE_CLOSED;
}
}
break;
case STATES.TRANSIT:
self.timer -= delta;
if(self.light_mesh){
self.light_mesh.visible = (Math.random() > 0.07);
}
if(self.timer <= 0){
if(self.light_mesh) self.light_mesh.visible = true;
execute_transit();
}
break;
}
// Desplazamiento visual de las hojas con holgura en el carril
const slide = self.door_openness * 0.52;
this.door_left.position.x = -0.28 - slide;
this.door_right.position.x = 0.28 + slide;
};
this.destroy = () => {
if(self.mesh && self.mesh.parent){
self.mesh.parent.remove(self.mesh);
}
};
};
return ElevatorObjectGame;
})();