waves/public/assets/g/burgerandfrights/js/entities/car.class.js
2025-04-09 17:11:14 -05:00

445 lines
15 KiB
JavaScript

/**
* @file car.class.js
* @version 1.0.0
*/
import AudioSystem from '../framework/audiosystem.class.js';
import Entity from '../framework/entity.class.js';
import ModelManager from '../framework/modelmanager.class.js';
import Animator from '../framework/three.js/animator.class.js';
import Utility from '../game/utility.class.js';
import { SkeletonUtils } from '../lib/three.js/SkeletonUtils.js';
import * as THREE from '../lib/three.js/three.module.js';
export default class Car extends Entity {
constructor(context, scene, options = Object.create(null)) {
options.updatePriority = 4;
super(context, scene, options);
const tScene = scene.getThreeScene();
const gltf = ModelManager.getModel('characters');
const animations = Animator.filterAnimations(gltf.animations, [
'DriverCalm',
'DriverScreaming',
'DriverDead',
], null, null, true, false, true);
this._rig = SkeletonUtils.clone(gltf.scene.getObjectByName('Man'));
this._rig.getObjectByName('CyclistMesh').removeFromParent();
this._rig.getObjectByName('CyclistBloodyMesh').removeFromParent();
this._animator = new Animator(this._rig, animations);
this._group = new THREE.Group();
this._group.name = 'Car';
this._group.add(this._rig);
this._group.add(gltf.scene.getObjectByName('Car'));
this._group.add(gltf.scene.getObjectByName('CarMonster'));
tScene.add(this._group);
this._group.traverse(obj => {
obj.matrixAutoUpdate = true;
obj.frustumCulled = false;
obj.material = Utility.replaceMaterial(obj.material);
});
this._player = scene.findEntityOfType('Player');
this._road = scene.findEntityOfType('Road');
this._overlay = scene.findEntityOfType('Overlay');
this._sceneName = null;
this._sceneSeconds = null;
this._stopping = null;
this._playerFallSeconds = null;
this._soundPlayed = null;
this._secondsUntilClonk = null;
this._roadPosition = new THREE.Vector2();
this._lightPoint = null;
this._lightSpot = null;
const listener = this._player.getListener();
this._soundCar = new THREE.PositionalAudio(listener);
this._soundCar.name = 'SoundCar';
this._soundCar.matrixAutoUpdate = true;
this._soundCar.setBuffer(AudioSystem.getSoundBuffer('effect__clonk'));
this._group.add(this._soundCar);
}
reset(context) {
this._animator.stopActions();
this._sceneName = null;
this._stopping = false;
this._playerFallSeconds = 0;
this._soundPlayed = false;
this._secondsUntilClonk = null;
this._roadPosition.set(0, 0);
if (this._lightPoint !== null) {
this._lightPoint.pool();
this._lightPoint = null;
}
if (this._lightSpot !== null) {
this._lightSpot.pool();
this._lightSpot = null;
}
this._group.visible = false;
this._rig.getObjectByName('DriverCalmMesh').visible = false;
this._rig.getObjectByName('DriverScreamingMesh').visible = false;
this._rig.getObjectByName('DriverDeadMesh').visible = false;
this._group.getObjectByName('Car').visible = false;
this._group.getObjectByName('CarMonster').visible = false;
}
update(context) {
super.update(context);
if (this._sceneName === null) {
return;
}
const dt = context.time.elapsedSeconds;
const firstUpdate = this._sceneSeconds === 0;
this._sceneSeconds += dt;
const distanceMin = this._road.getRoadDistanceMin();
const distanceMax = this._road.getRoadDistanceMax();
const type = this._road.getRoadType(this._roadPosition.y);
const laneCenter = type.laneCenter;
const laneOffset = type.laneOffset;
const playerRoadPosition = this._road.getPlayerRoadPosition();
const playerBiking = this._player.getBiking();
const pdx = this._roadPosition.x - playerRoadPosition.x;
const pdd = this._road.getRoadDistanceBetween(playerRoadPosition.y, this._roadPosition.y);
const chaseVelocity = pdd / 20;
let targetX = THREE.MathUtils.clamp(
this._roadPosition.x,
playerRoadPosition.x - 0.5,
playerRoadPosition.x + 0.5);
let t;
let againstPlayer = false;
switch (this._sceneName) {
case 'drive_by_calm':
case 'drive_by_screaming':
case 'drive_by_dead':
againstPlayer = true;
targetX = -laneOffset + 0.2;
this._roadPosition.y -= 0.15 * dt;
this._lightSpot.distance = Math.min(13, this._lightSpot.distance + dt * 5);
if (pdd > 2 && pdd < 9) {
this._player.slowDown(1);
}
if (pdd < 15 && !this._soundPlayed) {
this._soundPlayed = true;
AudioSystem.play('effect__car_drive_by');
if (this._sceneName === 'drive_by_dead') {
AudioSystem.play('ambiance__creepy_violin', 1, true, 'violin');
context.time.setTimeout(() => {
AudioSystem.stop('violin', 5);
}, 4000);
}
}
if (playerBiking && Math.abs(pdx) < 1.3 && Math.abs(pdd) < 2.5) {
this._player.playScene(context, 'crash');
}
if (this._sceneName === 'drive_by_screaming') {
this._secondsUntilClonk -= dt;
if (this._secondsUntilClonk <= 0) {
this._secondsUntilClonk += 0.875;
this._soundCar.play();
}
}
if (this._roadPosition.y <= distanceMin) {
this.reset(context);
return;
}
break;
case 'chase':
t = Math.min(1, this._sceneSeconds / 4);
this._roadPosition.y = Math.min(this._roadPosition.y - chaseVelocity * dt, playerRoadPosition.y - 0.5);
this._lightSpot.distance = 0.01 + t * 20;
if (this._sceneSeconds > 4 &&
this._player.getLookAngleTowards(this._group) < Math.PI * 0.5 || this._sceneSeconds > 15) {
AudioSystem.stop('ambiance', 0.5);
context.time.setTimeout(() => {
AudioSystem.play('effect__car_reveal');
}, 2000);
this._player.lookAt(this._group, 8, false);
this._overlay.playScene(context, 'letterbox', 7);
this._overlay.setContrast(0.5, 0.55, 0.55, 20);
this._sceneName = 'chase_reveal';
this._sceneSeconds = 0;
}
break;
case 'chase_reveal':
t = Math.min(1, Math.max(0, (this._sceneSeconds - 2) / 6));
this._lightPoint.distance = 1 + t * 0.5;
this._lightPoint.intensity = 0.5 + t;
this._lightSpot.distance = 15;
this._lightSpot.position.z = 2.5 - 3 * t;
this._roadPosition.y = THREE.MathUtils.clamp(
this._roadPosition.y - chaseVelocity * dt,
playerRoadPosition.y - 1,
playerRoadPosition.y - 0.1);
if (this._sceneSeconds > 8) {
AudioSystem.play('ambiance__car_chase', 1, true, 'ambiance');
this._roadPosition.y = playerRoadPosition.y - 0.5;
this._sceneName = 'chase_started';
this._sceneSeconds = 0;
}
break;
case 'chase_started':
t = Math.min(1, this._sceneSeconds / 2);
this._lightSpot.distance = Math.max(this._lightSpot.distance, 0.1 + t * 15);
if (!this._stopping) {
if (this._player.getFallen()) {
this._playerFallSeconds += dt;
this._roadPosition.y = Math.max(this._roadPosition.y - dt, playerRoadPosition.y - 0.5 + this._playerFallSeconds * 0.1);
} else {
this._roadPosition.y = THREE.MathUtils.clamp(
this._roadPosition.y - chaseVelocity * dt,
playerRoadPosition.y - 0.5,
playerRoadPosition.y);
}
}
if (playerBiking && Math.abs(pdd) < 2.5) {
AudioSystem.play('effect__car_reveal');
this._player.playScene(context, 'eaten_by_car');
this._sceneName = 'chase_ended';
this._sceneSeconds = 0;
}
break;
case 'chase_ended':
t = Math.min(1, this._sceneSeconds / 6);
this._roadPosition.y = playerRoadPosition.y - 0.3 + t * t * 0.25;
break;
case 'chicken_race':
againstPlayer = true;
t = Math.min(1, this._sceneSeconds / 2);
this._lightSpot.distance = 0.1 + t * 20;
if (pdd < 6) {
this._roadPosition.y -= 0.2 * dt;
this._player.pause(1);
} else {
this._roadPosition.y -= 0.6 * dt;
}
if (playerRoadPosition.y > this._roadPosition.y - 0.12) {
AudioSystem.stop('ambiance');
AudioSystem.play('effect__bike_crash');
AudioSystem.play('effect__game_over_scream');
AudioSystem.play('effect__game_over_splatter');
this._player.pause(5);
this._overlay.playScene(context, 'fade_out_red_splatter', 5, () => {
this.getScene().findEntityOfType('Director').resetInTuonela(context);
this._player.pause(2);
this._overlay.playScene(context, 'fade_in_black', 10);
});
this._sceneName = 'collided';
this._sceneSeconds = 0;
}
break;
case 'collided':
againstPlayer = true;
this._roadPosition.y = playerRoadPosition.y + 0.12;
break;
}
const steerT = firstUpdate ? 1 : 1 - 0.1 ** dt;
const lastX = this._roadPosition.x;
if (this._sceneName !== 'chase_ended') {
const laneWidth = laneOffset === 0 ? 0.5 : Math.abs(laneOffset) + 1;
targetX = THREE.MathUtils.clamp(targetX, laneCenter - laneWidth, laneCenter + laneWidth);
}
this._roadPosition.set(
this._roadPosition.x * (1 - steerT) + targetX * steerT,
THREE.MathUtils.clamp(this._roadPosition.y, distanceMin, distanceMax));
const dx = dt === 0 ? 0 : (this._roadPosition.x - lastX) / dt;
this._road.roadToWorldPosition(this._roadPosition, this._group.position);
this._group.position.x += Math.random() * 0.015;
this._group.position.y += Math.random() * 0.015;
this._group.position.z += Math.random() * 0.015;
this._group.rotation.set(0, 0, 0);
this._group.rotateY(-this._road.getRoadAngle(this._roadPosition.y) - dx * 0.1
+ Math.sin(context.time.totalSeconds * 2) * 0.03 + againstPlayer * Math.PI);
this._group.rotateX(THREE.MathUtils.clamp(-this._road.getForwardSlopeAngle(this._group), -0.5, 0.5));
this._animator.update(dt);
}
playScene(context, sceneName) {
this.reset(context);
const environment = this.getScene().findEntityOfType('Environment');
const isChase = sceneName === 'chase' || sceneName === 'chase_started' || sceneName === 'chicken_race';
this._sceneName = sceneName;
this._sceneSeconds = 0;
this._lightPoint = environment.getPointLight(0xfcf095, 1.5, 1, 0.8, 0, 1.5, 0.2);
this._lightSpot = environment.getSpotLight(0xf1a134, 4, 10, 0.4, 0.5, 0.2, 0, 0.5, 2.4, 0, isChase ? 0.5 : -0.5, 5);
this._group.add(this._lightPoint);
this._group.add(this._lightSpot);
this._group.add(this._lightSpot.target);
this._group.visible = true;
switch (sceneName) {
case 'drive_by_calm':
this._rig.getObjectByName('DriverCalmMesh').visible = true;
this._group.getObjectByName('Car').visible = true;
this._animator.playAction('DriverCalm', Infinity);
this._roadPosition.set(0, this._road.getRoadDistanceMax());
break;
case 'drive_by_screaming':
this._rig.getObjectByName('DriverScreamingMesh').visible = true;
this._group.getObjectByName('Car').visible = true;
this._animator.playAction('DriverScreaming', Infinity);
this._roadPosition.set(0, this._road.getRoadDistanceMax());
this._secondsUntilClonk = 0.28;
break;
case 'drive_by_dead':
this._rig.getObjectByName('DriverDeadMesh').visible = true;
this._group.getObjectByName('Car').visible = true;
this._animator.playAction('DriverDead', Infinity);
this._roadPosition.set(0, this._road.getRoadDistanceMax());
break;
case 'chicken_race':
case 'chase':
case 'chase_started':
AudioSystem.play(this._sceneName === 'chase_started' ? 'effect__car_reveal' : 'effect__car_drive_by');
this._rig.getObjectByName('DriverDeadMesh').visible = true;
this._group.getObjectByName('CarMonster').visible = true;
this._animator.playAction(sceneName === 'chase' ? 'DriverDead' : 'DriverCalm', Infinity);
this._lightPoint.color.setHex(0xff6666);
this._lightPoint.intensity = sceneName === 'chase' ? 0.2 : 1;
this._lightPoint.distance = sceneName === 'chase' ? 1 : 3;
this._lightSpot.color.setHex(0xff6666);
this._lightSpot.distance = 0.1;
this._lightSpot.penumbra = 0.4;
this._lightSpot.position.y = 1.5;
this._lightSpot.position.z = 2.5;
this._lightSpot.target.y = 0.5;
this._roadPosition.set(0, this._road.getRoadDistanceMin());
if (sceneName === 'chase_started') {
this._player.lookAt(this._group, 3, false);
} else if (sceneName === 'chicken_race') {
this._roadPosition.set(0, this._road.getRoadDistanceMax() - 1);
}
break;
default:
throw new Error(`Car scene "${sceneName}" does not exist`);
}
}
stop() {
this._stopping = true;
}
}
Car.p_register();