Skip to content

Commit

Permalink
update client to threejs v167 modules and add basic visuals for volum…
Browse files Browse the repository at this point in the history
…etric smokes
  • Loading branch information
starswaitforus committed Aug 26, 2024
1 parent 508b65e commit 6782093
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 105 deletions.
3 changes: 3 additions & 0 deletions www/assets/js/Enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const EventList = {
ThrowEvent: 11,
DropEvent: 12,
GrillEvent: 13,
SmokeEvent: 14,
}

// server/src/Enum/GameOverReason.php
Expand Down Expand Up @@ -106,6 +107,8 @@ const SoundType = {
ITEM_DROP_LAND: 21,
FLAME_EXTINGUISH: 22,
FLAME_PLAYER_HIT: 23,
SMOKE_SPAWN: 24,
SMOKE_FADE: 25,
}

// server/src/Enum/ArmorType.php
Expand Down
6 changes: 5 additions & 1 deletion www/assets/js/EventProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ export class EventProcessor {
}

eventsCallback[EventList.GrillEvent] = function (data) {
game.grillStart(data.position, data.maxTime, data.maxFlames)
game.grillStart(data.id, data.position, data.size, data.time, data.count)
}

eventsCallback[EventList.SmokeEvent] = function (data) {
game.smokeStart(data.id, data.position, data.size, data.time, data.count)
}

this.#callbacks = eventsCallback
Expand Down
130 changes: 85 additions & 45 deletions www/assets/js/Game.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as THREE from 'three' // fixme try remove from game, maybe only allow import Vec3...
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js' // todo remove
import {EventProcessor} from "./EventProcessor.js";
import {Player} from "./Player.js";
import {InventorySlot, SoundType} from "./Enums.js";
Expand All @@ -21,12 +23,12 @@ export class Game {
#endCallback
#soundRepository
#hudDebounceTicks = 1
#bombTimerId = null;
#bombTimerId = null
#eventProcessor
#dropItems = {};
#throwables = {};
#flammable = {};
#roundIntervalIds = [];
#dropItems = {}
#throwables = {}
#volumetrics = {}
#roundIntervalIds = []
#roundDamage = {did: {},got: {}}
#playerSlotsVisibleModels = [
InventorySlot.SLOT_KNIFE, InventorySlot.SLOT_PRIMARY, InventorySlot.SLOT_SECONDARY,
Expand Down Expand Up @@ -56,7 +58,8 @@ export class Game {
this.#roundIntervalIds = []
Object.keys(this.#dropItems).forEach((id) => this.itemPickUp(id))
Object.keys(this.#throwables).forEach((id) => this.removeGrenade(id))
Object.keys(this.#flammable).forEach((fireId) => Object.keys(this.#flammable[fireId]).forEach((flameId) => this.destroyFlame(fireId, flameId)))
Object.keys(this.#volumetrics).forEach((groupId) => Object.keys(this.#volumetrics[groupId]).forEach((itemId) => this.#world.destroyObject(this.#volumetrics[groupId][itemId])))
this.#volumetrics = {}
this.#world.reset()
}

Expand Down Expand Up @@ -120,7 +123,7 @@ export class Game {
roundEnd(attackersWins, newRoundNumber, score) {
let winner = attackersWins ? 'Attackers' : 'Defenders'
console.log("Round " + this.#round + " ended. Round wins: " + winner)
this.score = score;
this.score = score
this.#round = newRoundNumber
this.#hud.displayTopMessage(winner + ' wins')
this.#hud.requestFullScoreBoardUpdate(this.score)
Expand Down Expand Up @@ -200,13 +203,19 @@ export class Game {
this.itemPickUp(data.extra.id)
}
if (data.type === SoundType.FLAME_SPAWN) {
this.spawnFlame(data.position, data.extra.size, data.extra.height, data.extra.fire, `${data.position.x}-${data.position.y}-${data.position.z}`)
this.spawnFlame(data.position, data.extra.height, data.extra.id, `${data.position.x}-${data.position.y}-${data.position.z}`)
}
if (data.type === SoundType.SMOKE_SPAWN) {
this.spawnSmoke(data.position, data.extra.height, data.extra.id, `${data.position.x}-${data.position.y}-${data.position.z}`)
}
if (data.type === SoundType.FLAME_EXTINGUISH) {
this.destroyFlame(data.extra.fire, `${data.position.x}-${data.position.y}-${data.position.z}`)
this.destroyFlame(data.extra.id, `${data.position.x}-${data.position.y}-${data.position.z}`)
}
if (data.type === SoundType.SMOKE_FADE) {
this.smokeFade(data.extra.id)
}
if (data.type === SoundType.ITEM_DROP_AIR) {
const item = this.#dropItems[data.extra.id];
const item = this.#dropItems[data.extra.id]
item.rotation.x -= 0.1
item.rotation.y -= 0.1
item.rotation.z -= 0.1
Expand All @@ -216,7 +225,7 @@ export class Game {
if (data.item.slot === InventorySlot.SLOT_BOMB) {
this.bombDropPosition = data.position
}
const item = this.#dropItems[data.extra.id];
const item = this.#dropItems[data.extra.id]
item.rotation.set(0, 0, 0)
item.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), Math.random() * 6.28)
item.position.set(data.position.x, data.position.y, -data.position.z)
Expand All @@ -225,7 +234,7 @@ export class Game {
clearInterval(this.#bombTimerId)
}
if (data.type === SoundType.GRENADE_AIR || data.type === SoundType.GRENADE_BOUNCE || data.type === SoundType.GRENADE_LAND) {
const grenade = this.#throwables[data.extra.id];
const grenade = this.#throwables[data.extra.id]
grenade.rotation.x += 0.1
grenade.rotation.y += 0.1
grenade.rotation.z += 0.1
Expand Down Expand Up @@ -266,33 +275,23 @@ export class Game {
const direction = grenade.getWorldPosition(new THREE.Vector3()).sub(sightPosition).normalize()
if (this.#world.getCamera().getWorldDirection(new THREE.Vector3()).dot(direction) <= 0) { // flash behind spectator
this.removeGrenade(throwableId)
return;
return
}

const ray = new THREE.Raycaster(sightPosition, direction)
const intersects = ray.intersectObjects([grenade, ...this.getMapObjects()], false);
const intersects = ray.intersectObjects([grenade, ...this.getMapObjects()], false)
if (intersects.length >= 1 && intersects[0].object === grenade) {
this.#hud.showFlashBangScreen()
}
this.removeGrenade(throwableId)
return;
}
if (item.slot === InventorySlot.SLOT_GRENADE_SMOKE) {
const grenade = this.#throwables[throwableId]
const smoke = new THREE.Mesh(new THREE.DodecahedronGeometry(300, 1), new THREE.MeshStandardMaterial({color: 0xdadada}))
smoke.material.side = THREE.DoubleSide
smoke.position.y = 150
grenade.add(smoke)
game.#roundIntervalIds.push(setTimeout(() => this.removeGrenade(throwableId), 18000))
return;
}
if (item.slot === InventorySlot.SLOT_GRENADE_MOLOTOV || item.slot === InventorySlot.SLOT_GRENADE_HE) {
this.removeGrenade(throwableId)
return;
return
}
if (item.slot === InventorySlot.SLOT_GRENADE_HE) {
this.removeGrenade(throwableId) // fixme add some cool effect
return
}

console.warn("No handler for grenade: ", item)
game.#roundIntervalIds.push(setTimeout(() => this.removeGrenade(throwableId), 1000)) // todo responsive volumetric smokes, etc.
game.#roundIntervalIds.push(setTimeout(() => this.removeGrenade(throwableId), 500))
}

itemDrop(item, id) {
Expand All @@ -316,26 +315,67 @@ export class Game {
delete this.#throwables[id]
}

grillStart(position, maxTimeMs, maxFlamesCount) {
grillStart(fireId, position, size, maxTimeMs, maxPartCount) {
this.#volumetrics[fireId] = {}
this.#volumetrics[fireId]['size'] = size
this.#world.playSound('338301_4811732-lq.mp3', position, false)
}

spawnFlame(point, size, height, fireId, flameId) {
if (this.#flammable[fireId] === undefined) {
this.#flammable[fireId] = {}
}
height = lerp(height, randomInt(16, 26), Math.min(Math.sqrt(Object.keys(this.#flammable[fireId]).length) / randomInt(7, 9), 1))
spawnFlame(point, height, fireId, partId) {
const size = this.#volumetrics[fireId]['size']
height = lerp(height, randomInt(16, 26), Math.min(Math.sqrt(Object.keys(this.#volumetrics[fireId]).length) / randomInt(7, 9), 1))
const flame = this.#world.spawnFlame(size, height)
this.#flammable[fireId][flameId] = flame
this.#volumetrics[fireId][partId] = flame
flame.position.set(point.x, point.y + (height / 2), -1 * point.z)
flame.rotation.x = randomInt(-10, 10) / 100;
flame.rotation.x = randomInt(-10, 10) / 100
flame.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), Math.random() * 6.28)
}

destroyFlame(fireId, flameId) {
const flame = this.#flammable[fireId][flameId]
const flame = this.#volumetrics[fireId][flameId]
this.#world.destroyObject(flame)
delete this.#flammable[fireId][flameId]
delete this.#volumetrics[fireId][flameId]
}

smokeStart(smokeId, position, size, maxTimeMs, maxPartCount) {
this.#volumetrics[smokeId] = {}
this.#volumetrics[smokeId]['size'] = size
this.#volumetrics[smokeId]['count'] = maxPartCount
this.#volumetrics[smokeId]['geometries'] = []
}

spawnSmoke(point, height, smokeId, partId) {
const size = this.#volumetrics[smokeId]['size']
const geo = new THREE.BoxGeometry(size, height, size)
geo.translate(point.x, point.y + (geo.parameters.height / 2), -point.z)
this.#volumetrics[smokeId]['geometries'].push(geo)

const len = this.#volumetrics[smokeId]['geometries'].length
if (len % 10 !== 0 && len < this.#volumetrics[smokeId]['count']) {
return
}

let geometry= BufferGeometryUtils.mergeGeometries(this.#volumetrics[smokeId]['geometries']);
geometry = BufferGeometryUtils.mergeVertices(geometry, 2);
geometry.computeBoundingBox();
let mesh = this.#volumetrics[smokeId]['mesh']
if (mesh) {
this.#world.destroyObject(mesh)
}

mesh = this.#world.spawnSmoke(geometry)
this.#volumetrics[smokeId]['mesh'] = mesh
if (len === this.#volumetrics[smokeId]['count']) {
this.#volumetrics[smokeId]['range'] = Math.ceil(mesh.geometry.getIndex().count / 50);
mesh.geometry.setDrawRange(0, mesh.geometry.getIndex().count)
}
}

smokeFade(smokeId) {
const range = this.#volumetrics[smokeId]['range']
const mesh = this.#volumetrics[smokeId]['mesh']

this.#roundIntervalIds.push(setInterval(() => mesh.geometry.setDrawRange(0, mesh.geometry.drawRange.count - range), 50))
}

bombPlanted(timeMs, position) {
Expand All @@ -347,7 +387,7 @@ export class Game {
this.#hud.bombPlanted(bombSecCount)

const tenSecWarningSecCount = Math.round(timeMs / 1000 - 10)
let tickSecondsCount = 0;
let tickSecondsCount = 0
let bombTimerId = setInterval(function () {
if (tickSecondsCount === bombSecCount) {
clearInterval(bombTimerId)
Expand All @@ -356,7 +396,7 @@ export class Game {
world.playSound('88532__northern87__woosh-northern87.wav', null, true)
}
world.playSound('536422__rudmer-rotteveel__setting-electronic-timer-1-beep.wav', position, false)
tickSecondsCount++;
tickSecondsCount++
}, 1000)
this.#bombTimerId = bombTimerId
}
Expand Down Expand Up @@ -393,7 +433,7 @@ export class Game {
}

this.playerMe = new Player(options.player, this.#world.createPlayerMe())
this.players[playerId] = this.playerMe;
this.players[playerId] = this.playerMe
this.playerSpectate = this.playerMe

if (this.#readyCallback) {
Expand Down Expand Up @@ -430,7 +470,7 @@ export class Game {
const myId = this.playerSpectate.getId()
let aliveAvailableSpectateMates = this.getMyTeamPlayers().filter((player) => player.isAlive() && myId !== player.getId())
if (aliveAvailableSpectateMates.length === 0) {
return;
return
}

let ids = aliveAvailableSpectateMates.map((player) => player.getId()).sort()
Expand Down Expand Up @@ -565,7 +605,7 @@ export class Game {
#otherPlayersInventoryChanged(player, data) {
const world = this.#world
const hand = player.get3DObject().getObjectByName('hand')
const belt = player.get3DObject().getObjectByName('belt');
const belt = player.get3DObject().getObjectByName('belt')

if (hand.children.length === 1) {
const lastHandItemModel = hand.children[0]
Expand Down
24 changes: 20 additions & 4 deletions www/assets/js/ModelRepository.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import * as THREE from 'three'
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
import {ItemId} from "./Enums.js";

export class ModelRepository {
Expand All @@ -7,6 +10,7 @@ export class ModelRepository {
#meshes = {}
#materials = {
caps: {},
smoke: null,
outfitTeam: null,
outfitOpponent: null,
}
Expand All @@ -16,7 +20,7 @@ export class ModelRepository {
#mapObjects = [];

constructor() {
this.#gltfLoader = new THREE.GLTFLoader()
this.#gltfLoader = new GLTFLoader()
this.#textureLoader = new THREE.TextureLoader()
}

Expand Down Expand Up @@ -53,7 +57,7 @@ export class ModelRepository {
}
})

const sun = new THREE.DirectionalLight(0xffeac2, .9)
const sun = new THREE.DirectionalLight(0xffeac2, 4)
sun.position.set(4000, 4999, -4000)
sun.castShadow = true
sun.shadow.mapSize.width = 4096
Expand All @@ -63,7 +67,7 @@ export class ModelRepository {
sun.shadow.camera.right = 2000
sun.shadow.camera.top = 0
sun.shadow.camera.bottom = -3000
model.scene.add(sun, new THREE.AmbientLight(0xcfe4bb, .4))
model.scene.add(sun, new THREE.AmbientLight(0xcfe4bb, 1))
return model.scene
})
}
Expand All @@ -77,7 +81,7 @@ export class ModelRepository {
}

getPlayer(colorIndex, isOpponent) {
const clone = THREE.SkeletonUtils.clone(this.#models.player)
const clone = SkeletonUtils.clone(this.#models.player)
const player = clone.getObjectByName('player')

const headWear = player.getObjectByName('Wolf3D_Headwear')
Expand All @@ -101,6 +105,10 @@ export class ModelRepository {
return this.#meshes.playerHitMesh.clone()
}

getSmokeMaterial() {
return this.#materials.smoke
}

getModelForItem(item) {
if (item.id === ItemId.Bomb) {
return this.getBomb()
Expand Down Expand Up @@ -235,6 +243,14 @@ export class ModelRepository {
const sprite = new THREE.Sprite(material);
sprite.scale.set(35, 45, 30);


this.#materials.smoke = new THREE.MeshStandardMaterial({ // todo better material with cool displacement map etc.
color: 0x798aa0,
map: texture,
blending: THREE.AdditiveBlending,
side: THREE.FrontSide,
})

this.#meshes.playerHitMesh = sprite
}))

Expand Down
6 changes: 4 additions & 2 deletions www/assets/js/Player.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {AnimationMixer, Vector3} from 'three'

export class Player {
data = {
id: null,
Expand Down Expand Up @@ -45,7 +47,7 @@ export class Player {

setAnimations(animations) {
animations.forEach((clip) => {
const mixer = new THREE.AnimationMixer(this.#threeObject)
const mixer = new AnimationMixer(this.#threeObject)
const action = mixer.clipAction(clip);
if (clip.name === 'crouch') {
action.play()
Expand Down Expand Up @@ -114,7 +116,7 @@ export class Player {
}

getSightPositionThreeVector() {
return new THREE.Vector3(
return new Vector3(
this.data.position.x,
this.data.position.y + this.data.sight,
-this.data.position.z,
Expand Down
2 changes: 1 addition & 1 deletion www/assets/js/SoundRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class SoundRepository {
if (type === SoundType.FLAME_PLAYER_HIT) {
return '512138__beezlefm__item-sound.wav'
}
if (type === SoundType.FLAME_SPAWN || type === SoundType.FLAME_EXTINGUISH) {
if (type === SoundType.FLAME_SPAWN || type === SoundType.FLAME_EXTINGUISH || type === SoundType.SMOKE_SPAWN || type === SoundType.SMOKE_FADE) { // fixme make some noise
return null
}
if (type === SoundType.GRENADE_AIR) {
Expand Down
Loading

0 comments on commit 6782093

Please sign in to comment.