2022-08-13 12:40:42 +02:00
import { EventProcessor } from "./EventProcessor.js" ;
2022-10-06 20:35:28 +02:00
import { Player } from "./Player.js" ;
2022-11-06 15:37:41 +01:00
import { InventorySlot , SoundType } from "./Enums.js" ;
2022-10-21 12:35:24 +02:00
import { SoundRepository } from "./SoundRepository.js" ;
2022-08-13 12:40:42 +02:00
export class Game {
2022-10-22 21:35:14 +02:00
# world
# hud
# stats
# pointer
2022-12-14 18:30:12 +01:00
# shouldRenderInsideTick
2022-08-13 12:40:42 +02:00
# round = 1
2022-11-21 15:34:08 +01:00
# roundHalfTime = 2
2022-10-06 20:35:28 +02:00
# paused = false
2022-08-13 12:40:42 +02:00
# started = false
2022-11-23 15:37:36 +01:00
# options = null
2022-08-13 12:40:42 +02:00
# readyCallback
# endCallback
2022-10-21 12:35:24 +02:00
# soundRepository
2022-10-13 17:26:52 +02:00
# hudDebounceTicks = 1
2022-10-30 15:07:24 +01:00
# bombTimerId = null ;
2022-11-23 15:37:36 +01:00
# eventProcessor
2022-10-06 20:35:28 +02:00
score = null
2022-11-16 18:12:16 +01:00
bombDropPosition = null
2022-10-06 20:35:28 +02:00
alivePlayers = [ 0 , 0 ]
2022-10-09 13:28:35 +02:00
buyList = [ ]
2022-08-13 12:40:42 +02:00
players = [ ]
2022-10-06 20:35:28 +02:00
playerMe = null
2022-11-02 17:29:35 +01:00
playerSpectate = null
2022-11-20 14:38:40 +01:00
# playerSlotsVisibleModels = [ InventorySlot . SLOT _KNIFE , InventorySlot . SLOT _BOMB , InventorySlot . SLOT _PRIMARY , InventorySlot . SLOT _SECONDARY ]
2022-08-13 12:40:42 +02:00
2022-10-05 16:09:55 +02:00
constructor ( world , hud , stats ) {
2022-08-13 12:40:42 +02:00
this . # world = world
this . # hud = hud
2022-10-05 16:09:55 +02:00
this . # stats = stats
2022-11-23 15:37:36 +01:00
this . # eventProcessor = new EventProcessor ( this )
this . # soundRepository = new SoundRepository ( ( ... args ) => world . playSound ( ... args ) )
2022-08-13 12:40:42 +02:00
}
2022-10-06 20:35:28 +02:00
pause ( msg , score , timeMs ) {
2022-11-23 15:37:36 +01:00
this . # paused = true
2022-10-06 20:35:28 +02:00
console . log ( "Pause: " + msg + " for " + timeMs + "ms" )
2022-11-03 14:51:13 +01:00
clearInterval ( this . # bombTimerId )
2022-11-05 17:12:25 +01:00
this . # world . reset ( )
2022-11-03 14:51:13 +01:00
2022-09-24 19:28:57 +02:00
const game = this
this . players . forEach ( function ( player ) {
2022-11-15 12:16:18 +01:00
if ( player . getId ( ) === game . playerMe . getId ( ) ) { // reset spectate camera to our player
const camera = game . # world . getCamera ( )
camera . rotation . set ( 0 , serverHorizontalRotationToThreeRadian ( player . data . look . horizontal ) , 0 )
if ( game . # pointer ) {
game . # pointer . reset ( )
2022-11-02 17:29:35 +01:00
}
2022-11-15 12:16:18 +01:00
player . get3DObject ( ) . getObjectByName ( 'head' ) . add ( camera )
game . playerSpectate = game . playerMe
game . requestPointerLock ( )
2022-11-02 17:29:35 +01:00
} else {
2022-11-26 19:05:47 +01:00
player . get3DObject ( ) . getObjectByName ( 'figure' ) . visible = true
2022-09-24 19:28:57 +02:00
}
2022-11-26 19:05:47 +01:00
player . respawn ( )
2022-09-24 19:28:57 +02:00
} )
2022-10-21 12:35:24 +02:00
if ( ! this . # started ) {
2022-11-03 14:51:13 +01:00
this . # gameStartOrHalfTimeOrEnd ( )
2022-10-21 12:35:24 +02:00
this . # started = true
}
2022-11-21 15:34:08 +01:00
if ( this . # roundHalfTime === this . # round + 1 ) {
2022-11-22 15:53:07 +01:00
this . # world . playSound ( 'voice/blanka-last_round_of_half.mp3' , null , true )
2022-11-21 15:34:08 +01:00
}
2022-10-06 20:35:28 +02:00
this . score = score
2022-08-13 12:40:42 +02:00
this . # hud . pause ( msg , timeMs )
2022-10-07 15:44:35 +02:00
this . # hud . requestFullScoreBoardUpdate ( this . score )
2022-08-13 12:40:42 +02:00
}
unpause ( ) {
this . # paused = false
this . # hud . clearTopMessage ( )
console . log ( "Game unpause" )
}
end ( msg ) {
console . log ( 'Game ended' )
2022-11-03 14:51:13 +01:00
this . # gameStartOrHalfTimeOrEnd ( )
2022-08-13 12:40:42 +02:00
if ( this . # endCallback ) {
this . # endCallback ( msg )
}
}
roundStart ( aliveAttackers , aliveDefenders ) {
console . log ( "Starting round " + this . # round )
2022-10-06 20:35:28 +02:00
this . alivePlayers [ 0 ] = aliveDefenders
this . alivePlayers [ 1 ] = aliveAttackers
2022-08-13 12:40:42 +02:00
this . # hud . clearAlerts ( )
this . # hud . roundStart ( this . # options . setting . round _time _ms )
}
2022-10-06 20:35:28 +02:00
roundEnd ( attackersWins , newRoundNumber , score ) {
2022-08-13 12:40:42 +02:00
let winner = attackersWins ? 'Attackers' : 'Defenders'
console . log ( "Round " + this . # round + " ended. Round wins: " + winner )
2022-10-06 20:35:28 +02:00
this . score = score ;
2022-10-05 20:08:33 +02:00
this . # round = newRoundNumber
2022-08-13 12:40:42 +02:00
this . # hud . displayTopMessage ( winner + ' wins' )
2022-10-07 15:44:35 +02:00
this . # hud . requestFullScoreBoardUpdate ( this . score )
2022-08-13 12:40:42 +02:00
}
2022-11-03 14:51:13 +01:00
halfTime ( ) {
this . # gameStartOrHalfTimeOrEnd ( )
}
# gameStartOrHalfTimeOrEnd ( ) {
2022-10-19 12:23:08 +02:00
this . # world . playSound ( '538422__rosa-orenes256__referee-whistle-sound.wav' , null , true )
}
2022-11-23 15:37:36 +01:00
processSound ( data ) {
const spectatorId = this . playerSpectate . getId ( )
if ( data . type === SoundType . ITEM _ATTACK && data . player === spectatorId ) {
2022-11-02 17:29:35 +01:00
this . attackFeedback ( data . item )
}
2022-11-05 17:12:25 +01:00
if ( data . type === SoundType . ITEM _PICKUP ) {
2022-11-26 19:05:47 +01:00
this . # world . itemPickup ( data . position , data . item , ( spectatorId === data . player ) )
2022-11-05 17:12:25 +01:00
}
2022-12-10 16:54:41 +01:00
if ( data . type === SoundType . BULLET _HIT ) {
if ( data . player ) {
this . # world . bulletPlayerHit ( data . position , false )
} else if ( data . surface && ( data . item . slot === InventorySlot . SLOT _PRIMARY || data . item . slot === InventorySlot . SLOT _SECONDARY ) ) {
this . # world . bulletWallHit ( data . position , data . surface , ( data . item . slot === InventorySlot . SLOT _PRIMARY ? 1.2 : 0.8 ) )
}
} else if ( data . type === SoundType . BULLET _HIT _HEADSHOT ) {
this . # world . bulletPlayerHit ( data . position , true )
2022-11-06 13:29:50 +01:00
}
2022-11-05 17:12:25 +01:00
if ( data . type === SoundType . ITEM _DROP ) {
this . # world . itemDrop ( data . position , data . item )
2022-11-23 15:37:36 +01:00
if ( data . player === spectatorId ) {
2022-11-05 17:12:25 +01:00
this . dropFeedback ( data . item )
}
2022-11-16 18:12:16 +01:00
if ( data . item . slot === InventorySlot . SLOT _BOMB ) {
this . bombDropPosition = data . position
}
2022-11-05 17:12:25 +01:00
}
2022-11-10 17:06:08 +01:00
if ( data . type === SoundType . BOMB _DEFUSED ) {
2022-11-23 15:37:36 +01:00
clearInterval ( this . # bombTimerId )
2022-10-17 14:30:50 +02:00
}
2022-10-18 12:20:51 +02:00
2022-11-23 15:37:36 +01:00
this . # soundRepository . play ( data , spectatorId )
2022-10-17 14:30:50 +02:00
}
2022-10-30 15:07:24 +01:00
bombPlanted ( timeMs , position ) {
const world = this . # world
2022-11-02 17:29:35 +01:00
world . spawnBomb ( position )
2022-11-21 15:34:08 +01:00
this . bombDropPosition = position
2022-10-30 15:07:24 +01:00
2022-11-03 14:51:13 +01:00
const bombSecCount = Math . round ( timeMs / 1000 )
this . # hud . bombPlanted ( bombSecCount )
const tenSecWarningSecCount = Math . round ( timeMs / 1000 - 10 )
2022-10-30 15:07:24 +01:00
let tickSecondsCount = 0 ;
2022-11-03 14:51:13 +01:00
let bombTimerId = setInterval ( function ( ) {
if ( tickSecondsCount === bombSecCount ) {
clearInterval ( bombTimerId )
}
2022-10-30 15:07:24 +01:00
if ( tickSecondsCount === tenSecWarningSecCount ) {
world . playSound ( '88532__northern87__woosh-northern87.wav' , null , true )
}
world . playSound ( '536422__rudmer-rotteveel__setting-electronic-timer-1-beep.wav' , position , false )
tickSecondsCount ++ ;
} , 1000 )
2022-11-03 14:51:13 +01:00
this . # bombTimerId = bombTimerId
2022-10-30 15:07:24 +01:00
}
2022-10-21 12:35:24 +02:00
isPaused ( ) {
return this . # paused
2022-10-10 14:54:48 +02:00
}
2022-08-13 12:40:42 +02:00
isPlaying ( ) {
return this . # started
}
onReady ( callback ) {
this . # readyCallback = callback
}
onEnd ( callback ) {
this . # endCallback = callback
}
2022-10-21 12:35:24 +02:00
gameStart ( options ) {
2022-08-13 12:40:42 +02:00
this . # options = options
2022-11-21 15:34:08 +01:00
this . # roundHalfTime = Math . floor ( options . setting . max _rounds / 2 ) + 1
2022-08-13 12:40:42 +02:00
this . # hud . startWarmup ( options . warmupSec * 1000 )
const playerId = options . playerId
if ( this . players [ playerId ] ) {
throw new Error ( "My Player is already set!" )
}
2022-10-06 20:35:28 +02:00
this . playerMe = new Player ( options . player , this . # world . createPlayerMe ( ) )
this . players [ playerId ] = this . playerMe ;
2022-11-02 17:29:35 +01:00
this . playerSpectate = this . playerMe
2022-08-13 12:40:42 +02:00
if ( this . # readyCallback ) {
this . # readyCallback ( this . # options )
}
}
playerKilled ( playerIdDead , playerIdCulprit , wasHeadshot , killItemId ) {
2022-11-02 17:29:35 +01:00
const culpritPlayer = this . players [ playerIdCulprit ]
const deadPlayer = this . players [ playerIdDead ]
2022-10-07 15:44:35 +02:00
2022-11-03 14:51:13 +01:00
deadPlayer . died ( )
2022-10-06 20:35:28 +02:00
this . alivePlayers [ deadPlayer . getTeamIndex ( ) ] --
2022-08-13 12:40:42 +02:00
this . # hud . showKill (
2022-10-07 15:44:35 +02:00
culpritPlayer . data ,
2022-10-06 20:35:28 +02:00
deadPlayer . data ,
2022-08-13 12:40:42 +02:00
wasHeadshot ,
2022-11-03 14:51:13 +01:00
this . playerMe . data ,
2022-08-13 12:40:42 +02:00
killItemId
)
2022-11-02 17:29:35 +01:00
if ( playerIdDead === this . playerSpectate . getId ( ) ) {
2022-11-09 12:33:06 +01:00
this . requestPointerUnLock ( )
2022-11-02 17:29:35 +01:00
this . spectatePlayer ( )
}
}
spectatePlayer ( directionNext = true ) {
if ( this . playerMe . isAlive ( ) || this . alivePlayers [ this . playerMe . getTeamIndex ( ) ] === 0 ) {
return
}
2022-11-09 12:33:06 +01:00
const myId = this . playerSpectate . getId ( )
let aliveAvailableSpectateMates = this . getMyTeamPlayers ( ) . filter ( ( player ) => player . isAlive ( ) && myId !== player . getId ( ) )
if ( aliveAvailableSpectateMates . length === 0 ) {
return ;
}
let ids = aliveAvailableSpectateMates . map ( ( player ) => player . getId ( ) ) . sort ( )
2022-11-02 17:29:35 +01:00
if ( ! directionNext ) {
2022-11-09 12:33:06 +01:00
ids . reverse ( )
2022-11-02 17:29:35 +01:00
}
2022-11-09 12:33:06 +01:00
let playerId = ids . find ( ( id ) => myId > id )
if ( ! playerId ) {
playerId = ids . shift ( )
2022-11-02 17:29:35 +01:00
}
const camera = this . # world . getCamera ( )
2022-11-09 12:33:06 +01:00
camera . rotation . set ( 0 , degreeToRadian ( - 90 ) , 0 )
const player = this . players [ playerId ]
player . get3DObject ( ) . getObjectByName ( 'head' ) . add ( camera )
2022-11-23 17:10:23 +01:00
player . get3DObject ( ) . getObjectByName ( 'figure' ) . visible = false
2022-11-09 15:14:54 +01:00
if ( this . playerSpectate . isAlive ( ) ) {
2022-11-23 17:10:23 +01:00
this . playerSpectate . get3DObject ( ) . getObjectByName ( 'figure' ) . visible = true
2022-11-09 15:14:54 +01:00
}
2022-11-09 12:33:06 +01:00
this . playerSpectate = player
2022-11-23 17:10:23 +01:00
this . equip ( player . getEquippedSlotId ( ) )
2022-08-13 12:40:42 +02:00
}
2022-10-06 20:35:28 +02:00
createPlayer ( data ) {
2022-10-23 21:03:13 +02:00
const player = new Player ( data , this . # world . spawnPlayer ( data . color , this . playerMe . isAttacker ( ) !== data . isAttacker ) )
2022-10-06 20:35:28 +02:00
if ( this . players [ data . id ] ) {
throw new Error ( 'Player already exist with id ' + data . id )
2022-08-13 12:40:42 +02:00
}
2022-10-06 20:35:28 +02:00
this . players [ data . id ] = player
return player
2022-08-13 12:40:42 +02:00
}
2022-11-02 17:29:35 +01:00
attackFeedback ( item ) {
if ( this . playerSpectate . data . ammo > 0 ) {
2022-11-05 17:12:25 +01:00
this . # hud . showShot ( item )
2022-10-19 12:23:08 +02:00
}
2022-10-05 20:08:33 +02:00
}
2022-11-05 17:12:25 +01:00
dropFeedback ( item ) {
this . # hud . showDropAnimation ( item )
}
2022-08-13 12:40:42 +02:00
equip ( slotId ) {
2022-11-02 17:29:35 +01:00
if ( ! this . playerSpectate . data . slots [ slotId ] ) {
2022-08-13 12:40:42 +02:00
return false
}
2022-11-02 17:29:35 +01:00
this . playerSpectate . equip ( slotId )
2022-11-23 17:10:23 +01:00
const item = this . playerSpectate . data . slots [ slotId ]
const povItems = this . # world . getCamera ( ) . getObjectByName ( 'pov-item' )
povItems . children . forEach ( ( mesh ) => mesh . visible = false )
let model = povItems . getObjectByName ( ` item- ${ item . id } ` )
if ( ! model ) {
model = this . # world . getModelForItem ( item )
povItems . add ( model )
}
model . position . set ( 0 , 0 , 0 )
model . rotation . set ( 0 , 0 , 0 )
model . visible = true
2022-11-02 17:29:35 +01:00
this . # hud . equip ( slotId , this . playerSpectate . data . slots )
2022-08-13 12:40:42 +02:00
return true
}
tick ( state ) {
2022-10-21 12:35:24 +02:00
this . # stats . begin ( )
2022-08-13 12:40:42 +02:00
const game = this
2022-11-23 15:37:36 +01:00
if ( this . # options !== null ) {
2022-11-15 12:16:18 +01:00
state . players . forEach ( function ( serverState ) {
let player = game . players [ serverState . id ]
if ( player === undefined ) {
player = game . createPlayer ( serverState )
}
game . updatePlayerData ( player , serverState )
} )
}
2022-10-07 15:44:35 +02:00
state . events . forEach ( function ( event ) {
2022-11-23 15:37:36 +01:00
game . # eventProcessor . process ( event )
2022-10-07 15:44:35 +02:00
} )
2022-08-13 12:40:42 +02:00
2022-12-15 15:39:05 +01:00
this . # render ( )
2022-10-21 12:35:24 +02:00
this . # stats . end ( )
2022-08-13 12:40:42 +02:00
}
2022-10-07 15:44:35 +02:00
updatePlayerData ( player , serverState ) {
2022-11-03 14:51:13 +01:00
player . get3DObject ( ) . getObjectByName ( 'head' ) . position . y = serverState . heightSight
player . get3DObject ( ) . position . set ( serverState . position . x , serverState . position . y , - serverState . position . z )
2022-10-07 15:44:35 +02:00
if ( player . data . isAttacker === this . playerMe . data . isAttacker ) { // if player on my team
if ( player . data . money !== serverState . money ) {
this . # hud . updateMyTeamPlayerMoney ( player . data , serverState . money )
}
player . updateData ( serverState )
} else {
player . data . item = serverState . item
player . data . isAttacker = serverState . isAttacker
}
2022-10-23 13:02:58 +02:00
2022-11-02 17:29:35 +01:00
if ( this . playerMe . getId ( ) === serverState . id || this . playerSpectate . getId ( ) === serverState . id ) {
if ( this . playerSpectate . isInventoryChanged ( serverState ) ) {
2022-10-23 13:02:58 +02:00
this . equip ( serverState . item . slot )
}
2022-11-09 12:33:06 +01:00
}
if ( this . playerMe . getId ( ) !== serverState . id ) {
2022-11-20 14:38:40 +01:00
this . updateOtherPlayersModels ( player , serverState )
2022-10-23 13:02:58 +02:00
}
2022-10-07 15:44:35 +02:00
}
2022-11-20 14:38:40 +01:00
updateOtherPlayersModels ( player , data ) {
const playerObject = player . get3DObject ( )
2022-10-18 15:14:38 +02:00
playerObject . rotation . y = serverHorizontalRotationToThreeRadian ( data . look . horizontal )
2022-12-07 12:04:07 +01:00
const rotationVertical = this . playerMe . isAlive ( ) ? Math . max ( Math . min ( data . look . vertical , 50 ) , - 50 ) : data . look . vertical // cap visual rotation if playerMe is alive to not see broken neck
const rotationVerticalThree = serverVerticalRotationToThreeRadian ( rotationVertical )
playerObject . getObjectByName ( 'head' ) . rotation . x = rotationVerticalThree
const hand = playerObject . getObjectByName ( 'hand' )
if ( hand . children . length ) {
hand . children [ 0 ] . rotation . y = rotationVerticalThree
}
2022-10-07 15:44:35 +02:00
const body = playerObject . getObjectByName ( 'body' )
if ( body . position . y !== data . heightBody ) { // update body height position if changed
body . position . y = data . heightBody
}
2022-11-20 14:38:40 +01:00
if ( player . isInventoryChanged ( data ) ) {
this . # otherPlayersInventoryChanged ( player , data )
2022-11-30 12:13:27 +01:00
player . equip ( data . item . slot )
2022-11-20 14:38:40 +01:00
}
}
# otherPlayersInventoryChanged ( player , data ) {
const world = this . # world
const hand = player . get3DObject ( ) . getObjectByName ( 'hand' )
const belt = player . get3DObject ( ) . getObjectByName ( 'belt' ) ;
2022-11-30 12:13:27 +01:00
if ( hand . children . length === 1 ) {
const lastHandItemModel = hand . children [ 0 ]
belt . getObjectByName ( ` slot- ${ lastHandItemModel . userData . slot } ` ) . add ( lastHandItemModel )
} else if ( hand . children . length > 1 ) {
throw new Error ( "Too many item in hands?" )
2022-11-20 14:38:40 +01:00
}
this . # playerSlotsVisibleModels . forEach ( function ( slotId ) {
2022-11-21 15:34:08 +01:00
const item = data . slots [ slotId ]
2022-11-20 14:38:40 +01:00
const beltSlot = belt . getObjectByName ( ` slot- ${ slotId } ` )
beltSlot . children . forEach ( ( model ) => model . visible = false )
if ( ! item ) { // do not have slotID filled
return
}
let itemModel = beltSlot . getObjectByName ( ` item- ${ item . id } ` )
if ( ! itemModel ) {
itemModel = world . getModelForItem ( item )
beltSlot . add ( itemModel )
}
itemModel . position . set ( 0 , 0 , 0 )
itemModel . rotation . set ( 0 , 0 , 0 )
itemModel . visible = true
} )
const modelInHand = belt . getObjectByName ( ` slot- ${ data . item . slot } ` ) . getObjectByName ( ` item- ${ data . item . id } ` )
hand . add ( modelInHand )
modelInHand . position . set ( 0 , 0 , 0 )
modelInHand . rotation . set ( 0 , 0 , 0 )
modelInHand . visible = true
2022-11-30 12:13:27 +01:00
modelInHand . userData . slot = data . item . slot
2022-08-13 12:40:42 +02:00
}
2022-10-21 12:35:24 +02:00
getMyTeamPlayers ( ) {
let meIsAttacker = this . playerMe . isAttacker ( )
return this . players . filter ( ( player ) => player . isAttacker ( ) === meIsAttacker )
}
meIsAlive ( ) {
return this . playerMe . isAlive ( )
}
2022-11-02 17:29:35 +01:00
meIsSpectating ( ) {
return ( ! this . meIsAlive ( ) )
}
2022-12-14 18:30:12 +01:00
setDependency ( pointer , renderWorldInsideTick ) {
2022-10-22 21:35:14 +02:00
this . # pointer = pointer
2022-12-14 18:30:12 +01:00
this . # shouldRenderInsideTick = renderWorldInsideTick
2022-10-22 21:35:14 +02:00
}
getPlayerMeRotation ( ) {
return threeRotationToServer ( this . # pointer . getObject ( ) . rotation )
}
2022-11-02 17:29:35 +01:00
getPlayerSpectateRotation ( ) {
if ( this . playerSpectate . getId ( ) === this . playerMe . getId ( ) ) {
return this . getPlayerMeRotation ( )
}
return [ this . playerSpectate . data . look . horizontal , this . playerSpectate . data . look . vertical ]
}
2022-10-22 21:35:14 +02:00
requestPointerLock ( ) {
2022-11-09 12:33:06 +01:00
if ( this . # pointer . isLocked || ( this . playerMe && this . playerMe . getId ( ) !== this . playerSpectate . getId ( ) ) ) {
2022-10-22 21:35:14 +02:00
return
}
this . # pointer . lock ( )
}
requestPointerUnLock ( ) {
if ( ! this . # pointer . isLocked ) {
return
}
this . # pointer . unlock ( )
}
2022-10-21 12:35:24 +02:00
# render ( ) {
2022-10-13 17:26:52 +02:00
if ( this . # started && -- this . # hudDebounceTicks === 0 ) {
this . # hudDebounceTicks = 4
2022-11-02 17:29:35 +01:00
this . # hud . updateHud ( this . playerSpectate . data )
2022-10-06 20:35:28 +02:00
}
2022-12-15 15:39:05 +01:00
if ( this . # shouldRenderInsideTick ) {
this . # world . render ( )
}
2022-08-13 12:40:42 +02:00
}
}