2022-09-20 23:50:01 +08:00
const i18n = require ( 'LanguageData' ) ;
i18n . init ( window . language ) ; // languageID should be equal to the one we input in New Language ID input field
2022-09-23 16:42:44 +08:00
const collisions = require ( './modules/Collisions' ) ;
2022-09-24 12:01:50 +08:00
const RingBuffer = require ( './RingBuffer' ) ;
2022-09-23 16:42:44 +08:00
2022-09-20 23:50:01 +08:00
window . ALL _MAP _STATES = {
VISUAL : 0 , // For free dragging & zooming.
EDITING _BELONGING : 1 ,
SHOWING _MODAL _POPUP : 2 ,
} ;
window . ALL _BATTLE _STATES = {
WAITING : 0 ,
IN _BATTLE : 1 ,
IN _SETTLEMENT : 2 ,
IN _DISMISSAL : 3 ,
} ;
window . MAGIC _ROOM _DOWNSYNC _FRAME _ID = {
2022-09-24 12:01:50 +08:00
BATTLE _READY _TO _START : - 1 ,
2022-10-01 23:54:48 +08:00
BATTLE _START : 0
2022-09-20 23:50:01 +08:00
} ;
2022-10-04 11:24:47 +08:00
window . PlayerBattleState = {
ADDED _PENDING _BATTLE _COLLIDER _ACK : 0 ,
READDED _PENDING _BATTLE _COLLIDER _ACK : 1 ,
ACTIVE : 2 ,
DISCONNECTED : 3 ,
LOST : 4 ,
EXPELLED _DURING _GAME : 5 ,
EXPELLED _IN _DISMISSAL : 6
} ;
2022-09-20 23:50:01 +08:00
cc . Class ( {
extends : cc . Component ,
properties : {
canvasNode : {
type : cc . Node ,
default : null ,
} ,
tiledAnimPrefab : {
type : cc . Prefab ,
default : null ,
} ,
player1Prefab : {
type : cc . Prefab ,
default : null ,
} ,
player2Prefab : {
type : cc . Prefab ,
default : null ,
} ,
polygonBoundaryBarrierPrefab : {
type : cc . Prefab ,
default : null ,
} ,
keyboardInputControllerNode : {
type : cc . Node ,
default : null
} ,
joystickInputControllerNode : {
type : cc . Node ,
default : null
} ,
confirmLogoutPrefab : {
type : cc . Prefab ,
default : null
} ,
simplePressToGoDialogPrefab : {
type : cc . Prefab ,
default : null
} ,
boundRoomIdLabel : {
type : cc . Label ,
default : null
} ,
countdownLabel : {
type : cc . Label ,
default : null
} ,
resultPanelPrefab : {
type : cc . Prefab ,
default : null
} ,
gameRulePrefab : {
type : cc . Prefab ,
default : null
} ,
findingPlayerPrefab : {
type : cc . Prefab ,
default : null
} ,
countdownToBeginGamePrefab : {
type : cc . Prefab ,
default : null
} ,
playersInfoPrefab : {
type : cc . Prefab ,
default : null
} ,
forceBigEndianFloatingNumDecoding : {
default : false ,
} ,
backgroundMapTiledIns : {
type : cc . TiledMap ,
default : null
} ,
2022-10-04 11:24:47 +08:00
renderFrameIdLagTolerance : {
type : cc . Integer ,
default : 4 // implies (renderFrameIdLagTolerance >> inputScaleFrames) count of inputFrameIds
} ,
2022-10-10 14:33:04 +08:00
teleportEps1D : {
type : cc . Float ,
default : 1e-3
} ,
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
2022-09-20 23:50:01 +08:00
_inputFrameIdDebuggable ( inputFrameId ) {
2022-10-03 00:22:05 +08:00
return ( 0 == inputFrameId % 10 ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
dumpToRenderCache : function ( roomDownsyncFrame ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-10-03 00:22:05 +08:00
const minToKeepRenderFrameId = self . lastAllConfirmedRenderFrameId ;
2022-09-26 10:36:46 +08:00
while ( 0 < self . recentRenderCache . cnt && self . recentRenderCache . stFrameId < minToKeepRenderFrameId ) {
2022-09-24 12:01:50 +08:00
self . recentRenderCache . pop ( ) ;
2022-09-22 17:09:49 +08:00
}
2022-10-03 00:22:05 +08:00
const ret = self . recentRenderCache . setByFrameId ( roomDownsyncFrame , roomDownsyncFrame . id ) ;
return ret ;
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
dumpToInputCache : function ( inputFrameDownsync ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-10-16 10:38:38 +08:00
let minToKeepInputFrameId = self . _convertToInputFrameId ( self . lastAllConfirmedRenderFrameId , self . inputDelayFrames ) ; // [WARNING] This could be different from "self.lastAllConfirmedInputFrameId". We'd like to keep the corresponding delayedInputFrame for "self.lastAllConfirmedRenderFrameId" such that a rollback could place "self.chaserRenderFrameId = self.lastAllConfirmedRenderFrameId" for the worst case incorrect prediction.
2022-10-03 00:22:05 +08:00
if ( minToKeepInputFrameId > self . lastAllConfirmedInputFrameId ) {
minToKeepInputFrameId = self . lastAllConfirmedInputFrameId ;
}
2022-09-26 10:36:46 +08:00
while ( 0 < self . recentInputCache . cnt && self . recentInputCache . stFrameId < minToKeepInputFrameId ) {
2022-09-24 12:01:50 +08:00
self . recentInputCache . pop ( ) ;
2022-09-22 17:09:49 +08:00
}
2022-10-03 00:22:05 +08:00
const ret = self . recentInputCache . setByFrameId ( inputFrameDownsync , inputFrameDownsync . inputFrameId ) ;
if ( - 1 < self . lastAllConfirmedInputFrameId && self . recentInputCache . stFrameId > self . lastAllConfirmedInputFrameId ) {
console . error ( "Invalid input cache dumped! lastAllConfirmedRenderFrameId=" , self . lastAllConfirmedRenderFrameId , ", lastAllConfirmedInputFrameId=" , self . lastAllConfirmedInputFrameId , ", recentRenderCache=" , self . _stringifyRecentRenderCache ( false ) , ", recentInputCache=" , self . _stringifyRecentInputCache ( false ) ) ;
2022-09-20 23:50:01 +08:00
}
2022-10-03 00:22:05 +08:00
return ret ;
2022-09-20 23:50:01 +08:00
} ,
_convertToInputFrameId ( renderFrameId , inputDelayFrames ) {
if ( renderFrameId < inputDelayFrames ) return 0 ;
2022-09-21 23:59:05 +08:00
return ( ( renderFrameId - inputDelayFrames ) >> this . inputScaleFrames ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-29 12:21:04 +08:00
_convertToFirstUsedRenderFrameId ( inputFrameId , inputDelayFrames ) {
2022-09-21 23:59:05 +08:00
return ( ( inputFrameId << this . inputScaleFrames ) + inputDelayFrames ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-29 12:21:04 +08:00
shouldGenerateInputFrameUpsync ( renderFrameId ) {
2022-10-03 00:22:05 +08:00
return ( ( renderFrameId & ( ( 1 << this . inputScaleFrames ) - 1 ) ) == 0 ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-24 12:01:50 +08:00
_allConfirmed ( confirmedList ) {
2022-10-03 00:22:05 +08:00
return ( confirmedList + 1 ) == ( 1 << this . playerRichInfoDict . size ) ;
} ,
2022-09-24 12:01:50 +08:00
2022-09-20 23:50:01 +08:00
_generateInputFrameUpsync ( inputFrameId ) {
2022-09-24 12:01:50 +08:00
const self = this ;
2022-09-20 23:50:01 +08:00
if (
2022-09-24 12:01:50 +08:00
null == self . ctrl ||
null == self . selfPlayerInfo
2022-09-20 23:50:01 +08:00
) {
return [ null , null ] ;
}
2022-09-24 12:01:50 +08:00
const joinIndex = self . selfPlayerInfo . joinIndex ;
const discreteDir = self . ctrl . getDiscretizedDirection ( ) ;
2022-10-03 00:22:05 +08:00
const previousInputFrameDownsyncWithPrediction = self . getCachedInputFrameDownsyncWithPrediction ( inputFrameId ) ;
const prefabbedInputList = ( null == previousInputFrameDownsyncWithPrediction ? new Array ( self . playerRichInfoDict . size ) . fill ( 0 ) : previousInputFrameDownsyncWithPrediction . inputList . slice ( ) ) ;
prefabbedInputList [ ( joinIndex - 1 ) ] = discreteDir . encodedIdx ;
const prefabbedInputFrameDownsync = {
2022-09-24 12:01:50 +08:00
inputFrameId : inputFrameId ,
2022-10-03 00:22:05 +08:00
inputList : prefabbedInputList ,
confirmedList : ( 1 << ( self . selfPlayerInfo . joinIndex - 1 ) )
2022-09-20 23:50:01 +08:00
} ;
2022-10-03 00:22:05 +08:00
self . dumpToInputCache ( prefabbedInputFrameDownsync ) ; // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
2022-09-20 23:50:01 +08:00
2022-10-03 00:22:05 +08:00
const previousSelfInput = ( null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction . inputList [ joinIndex - 1 ] ) ;
2022-09-24 12:01:50 +08:00
return [ previousSelfInput , discreteDir . encodedIdx ] ;
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
2022-09-24 12:01:50 +08:00
shouldSendInputFrameUpsyncBatch ( prevSelfInput , currSelfInput , lastUpsyncInputFrameId , currInputFrameId ) {
2022-09-20 23:50:01 +08:00
/*
2022-10-01 15:14:05 +08:00
For a 2-player-battle, this "shouldUpsyncForEarlyAllConfirmedOnBackend" can be omitted, however for more players in a same battle, to avoid a "long time non-moving player" jamming the downsync of other moving players, we should use this flag.
When backend implements the "force confirmation" feature, we can have "false == shouldUpsyncForEarlyAllConfirmedOnBackend" all the time as well!
2022-09-20 23:50:01 +08:00
*/
if ( null == currSelfInput ) return false ;
2022-10-01 15:14:05 +08:00
2022-10-03 00:22:05 +08:00
const shouldUpsyncForEarlyAllConfirmedOnBackend = ( currInputFrameId - lastUpsyncInputFrameId >= this . inputFrameUpsyncDelayTolerance ) ;
2022-10-01 15:14:05 +08:00
return shouldUpsyncForEarlyAllConfirmedOnBackend || ( prevSelfInput != currSelfInput ) ;
2022-10-03 00:22:05 +08:00
} ,
2022-09-20 23:50:01 +08:00
2022-10-03 00:22:05 +08:00
sendInputFrameUpsyncBatch ( latestLocalInputFrameId ) {
// [WARNING] Why not just send the latest input? Because different player would have a different "latestLocalInputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"!
2022-09-24 12:01:50 +08:00
const self = this ;
2022-09-20 23:50:01 +08:00
let inputFrameUpsyncBatch = [ ] ;
2022-10-03 00:22:05 +08:00
let batchInputFrameIdSt = self . lastUpsyncInputFrameId + 1 ;
if ( batchInputFrameIdSt < self . recentInputCache . stFrameId ) {
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
batchInputFrameIdSt = self . recentInputCache . stFrameId ;
}
for ( let i = batchInputFrameIdSt ; i <= latestLocalInputFrameId ; ++ i ) {
2022-09-24 12:01:50 +08:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( i ) ;
if ( null == inputFrameDownsync ) {
2022-10-03 00:22:05 +08:00
console . error ( "sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=" , i , ": latestLocalInputFrameId=" , latestLocalInputFrameId , ", recentInputCache=" , self . _stringifyRecentInputCache ( false ) ) ;
2022-09-24 12:01:50 +08:00
} else {
const inputFrameUpsync = {
inputFrameId : i ,
2022-10-03 00:22:05 +08:00
encodedDir : inputFrameDownsync . inputList [ self . selfPlayerInfo . joinIndex - 1 ] ,
2022-09-24 12:01:50 +08:00
} ;
inputFrameUpsyncBatch . push ( inputFrameUpsync ) ;
}
2022-10-03 00:22:05 +08:00
}
2022-09-20 23:50:01 +08:00
const reqData = window . WsReq . encode ( {
msgId : Date . now ( ) ,
2022-09-24 12:01:50 +08:00
playerId : self . selfPlayerInfo . id ,
2022-09-20 23:50:01 +08:00
act : window . UPSYNC _MSG _ACT _PLAYER _CMD ,
2022-09-24 12:01:50 +08:00
joinIndex : self . selfPlayerInfo . joinIndex ,
ackingFrameId : self . lastAllConfirmedRenderFrameId ,
ackingInputFrameId : self . lastAllConfirmedInputFrameId ,
2022-09-20 23:50:01 +08:00
inputFrameUpsyncBatch : inputFrameUpsyncBatch ,
} ) . finish ( ) ;
window . sendSafely ( reqData ) ;
2022-10-03 00:22:05 +08:00
self . lastUpsyncInputFrameId = latestLocalInputFrameId ;
2022-09-20 23:50:01 +08:00
} ,
onEnable ( ) {
cc . log ( "+++++++ Map onEnable()" ) ;
} ,
onDisable ( ) {
cc . log ( "+++++++ Map onDisable()" ) ;
} ,
onDestroy ( ) {
const self = this ;
console . warn ( "+++++++ Map onDestroy()" ) ;
if ( null == self . battleState || ALL _BATTLE _STATES . WAITING == self . battleState ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
}
if ( null != window . handleBattleColliderInfo ) {
window . handleBattleColliderInfo = null ;
}
2022-10-25 23:02:39 +08:00
if ( null != window . handleClientSessionError ) {
window . handleClientSessionError = null ;
2022-09-20 23:50:01 +08:00
}
} ,
popupSimplePressToGo ( labelString , hideYesButton ) {
const self = this ;
self . state = ALL _MAP _STATES . SHOWING _MODAL _POPUP ;
const canvasNode = self . canvasNode ;
const simplePressToGoDialogNode = cc . instantiate ( self . simplePressToGoDialogPrefab ) ;
simplePressToGoDialogNode . setPosition ( cc . v2 ( 0 , 0 ) ) ;
simplePressToGoDialogNode . setScale ( 1 / canvasNode . scale ) ;
const simplePressToGoDialogScriptIns = simplePressToGoDialogNode . getComponent ( "SimplePressToGoDialog" ) ;
const yesButton = simplePressToGoDialogNode . getChildByName ( "Yes" ) ;
const postDismissalByYes = ( ) => {
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
canvasNode . removeChild ( simplePressToGoDialogNode ) ;
}
simplePressToGoDialogNode . getChildByName ( "Hint" ) . getComponent ( cc . Label ) . string = labelString ;
yesButton . once ( "click" , simplePressToGoDialogScriptIns . dismissDialog . bind ( simplePressToGoDialogScriptIns , postDismissalByYes ) ) ;
yesButton . getChildByName ( "Label" ) . getComponent ( cc . Label ) . string = "OK" ;
if ( true == hideYesButton ) {
yesButton . active = false ;
}
self . transitToState ( ALL _MAP _STATES . SHOWING _MODAL _POPUP ) ;
safelyAddChild ( self . widgetsAboveAllNode , simplePressToGoDialogNode ) ;
setLocalZOrder ( simplePressToGoDialogNode , 20 ) ;
return simplePressToGoDialogNode ;
} ,
alertForGoingBackToLoginScene ( labelString , mapIns , shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
const millisToGo = 3000 ;
mapIns . popupSimplePressToGo ( cc . js . formatStr ( "%s will logout in %s seconds." , labelString , millisToGo / 1000 ) ) ;
setTimeout ( ( ) => {
mapIns . logout ( false , shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) ;
} , millisToGo ) ;
} ,
_resetCurrentMatch ( ) {
const self = this ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
self . countdownLabel . string = "" ;
self . countdownNanos = null ;
// Clearing previous info of all players. [BEGINS]
2022-09-24 12:01:50 +08:00
self . collisionPlayerIndexPrefix = ( 1 << 17 ) ; // For tracking the movements of players
2022-09-22 12:45:17 +08:00
if ( null != self . playerRichInfoDict ) {
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
if ( playerRichInfo . node . parent ) {
playerRichInfo . node . parent . removeChild ( playerRichInfo . node ) ;
2022-09-20 23:50:01 +08:00
}
2022-09-22 12:45:17 +08:00
} ) ;
2022-10-03 00:22:05 +08:00
}
2022-09-22 12:45:17 +08:00
self . playerRichInfoDict = new Map ( ) ;
2022-09-20 23:50:01 +08:00
// Clearing previous info of all players. [ENDS]
self . renderFrameId = 0 ; // After battle started
2022-09-24 12:01:50 +08:00
self . lastAllConfirmedRenderFrameId = - 1 ;
self . lastAllConfirmedInputFrameId = - 1 ;
2022-09-20 23:50:01 +08:00
self . lastUpsyncInputFrameId = - 1 ;
2022-10-03 00:22:05 +08:00
self . chaserRenderFrameId = - 1 ; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
2022-09-20 23:50:01 +08:00
2022-09-24 12:01:50 +08:00
self . recentRenderCache = new RingBuffer ( 1024 ) ;
2022-09-20 23:50:01 +08:00
self . selfPlayerInfo = null ; // This field is kept for distinguishing "self" and "others".
2022-10-03 00:22:05 +08:00
self . recentInputCache = new RingBuffer ( 1024 ) ;
2022-09-23 16:42:44 +08:00
self . latestCollisionSys = new collisions . Collisions ( ) ;
self . chaserCollisionSys = new collisions . Collisions ( ) ;
2022-09-24 12:01:50 +08:00
self . collisionBarrierIndexPrefix = ( 1 << 16 ) ; // For tracking the movements of barriers, though not yet actually used
self . latestCollisionSysMap = new Map ( ) ;
self . chaserCollisionSysMap = new Map ( ) ;
2022-10-03 00:22:05 +08:00
2022-09-20 23:50:01 +08:00
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
self . battleState = ALL _BATTLE _STATES . WAITING ;
if ( self . findingPlayerNode ) {
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . init ( ) ;
}
safelyAddChild ( self . widgetsAboveAllNode , self . playersInfoNode ) ;
safelyAddChild ( self . widgetsAboveAllNode , self . findingPlayerNode ) ;
} ,
onLoad ( ) {
const self = this ;
window . mapIns = self ;
window . forceBigEndianFloatingNumDecoding = self . forceBigEndianFloatingNumDecoding ;
console . warn ( "+++++++ Map onLoad()" ) ;
2022-10-25 23:02:39 +08:00
window . handleClientSessionError = function ( ) {
console . warn ( '+++++++ Common handleClientSessionError()' ) ;
2022-09-20 23:50:01 +08:00
2022-10-04 11:24:47 +08:00
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
console . log ( "Battled ended by settlement" ) ;
2022-09-20 23:50:01 +08:00
} else {
2022-10-04 11:24:47 +08:00
console . warn ( "Connection lost, going back to login page" ) ;
2022-09-20 23:50:01 +08:00
window . clearLocalStorageAndBackToLoginScene ( true ) ;
}
} ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
2022-09-23 16:42:44 +08:00
cc . director . getCollisionManager ( ) . enabled = false ;
2022-09-20 23:50:01 +08:00
// self.musicEffectManagerScriptIns = self.node.getComponent("MusicEffectManager");
self . musicEffectManagerScriptIns = null ;
/** Init required prefab started. */
self . confirmLogoutNode = cc . instantiate ( self . confirmLogoutPrefab ) ;
self . confirmLogoutNode . getComponent ( "ConfirmLogout" ) . mapNode = self . node ;
// Initializes Result panel.
self . resultPanelNode = cc . instantiate ( self . resultPanelPrefab ) ;
self . resultPanelNode . width = self . canvasNode . width ;
self . resultPanelNode . height = self . canvasNode . height ;
const resultPanelScriptIns = self . resultPanelNode . getComponent ( "ResultPanel" ) ;
resultPanelScriptIns . mapScriptIns = self ;
resultPanelScriptIns . onAgainClicked = ( ) => {
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 23:50:01 +08:00
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
} ;
2022-10-03 00:22:05 +08:00
resultPanelScriptIns . onCloseDelegate = ( ) => { } ;
2022-09-20 23:50:01 +08:00
self . gameRuleNode = cc . instantiate ( self . gameRulePrefab ) ;
self . gameRuleNode . width = self . canvasNode . width ;
self . gameRuleNode . height = self . canvasNode . height ;
self . gameRuleScriptIns = self . gameRuleNode . getComponent ( "GameRule" ) ;
self . gameRuleScriptIns . mapNode = self . node ;
self . findingPlayerNode = cc . instantiate ( self . findingPlayerPrefab ) ;
self . findingPlayerNode . width = self . canvasNode . width ;
self . findingPlayerNode . height = self . canvasNode . height ;
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . init ( ) ;
self . playersInfoNode = cc . instantiate ( self . playersInfoPrefab ) ;
self . countdownToBeginGameNode = cc . instantiate ( self . countdownToBeginGamePrefab ) ;
self . countdownToBeginGameNode . width = self . canvasNode . width ;
self . countdownToBeginGameNode . height = self . canvasNode . height ;
self . mainCameraNode = canvasNode . getChildByName ( "Main Camera" ) ;
self . mainCamera = self . mainCameraNode . getComponent ( cc . Camera ) ;
for ( let child of self . mainCameraNode . children ) {
child . setScale ( 1 / self . mainCamera . zoomRatio ) ;
}
self . widgetsAboveAllNode = self . mainCameraNode . getChildByName ( "WidgetsAboveAll" ) ;
self . mainCameraNode . setPosition ( cc . v2 ( ) ) ;
/** Init required prefab ended. */
window . handleBattleColliderInfo = function ( parsedBattleColliderInfo ) {
2022-10-03 00:22:05 +08:00
self . inputDelayFrames = parsedBattleColliderInfo . inputDelayFrames ;
2022-10-02 11:33:40 +08:00
self . inputScaleFrames = parsedBattleColliderInfo . inputScaleFrames ;
self . inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo . inputFrameUpsyncDelayTolerance ;
2022-10-10 14:33:04 +08:00
self . battleDurationNanos = parsedBattleColliderInfo . battleDurationNanos ;
2022-10-10 12:17:23 +08:00
self . rollbackEstimatedDt = parsedBattleColliderInfo . rollbackEstimatedDt ;
2022-10-10 14:33:04 +08:00
self . rollbackEstimatedDtMillis = parsedBattleColliderInfo . rollbackEstimatedDtMillis ;
self . rollbackEstimatedDtNanos = parsedBattleColliderInfo . rollbackEstimatedDtNanos ;
2022-10-03 00:22:05 +08:00
self . rollbackEstimatedDtToleranceMillis = self . rollbackEstimatedDtMillis / 1000.0 ;
2022-10-02 11:33:40 +08:00
self . maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo . maxChasingRenderFramesPerUpdate ;
2022-09-20 23:50:01 +08:00
const tiledMapIns = self . node . getComponent ( cc . TiledMap ) ;
const fullPathOfTmxFile = cc . js . formatStr ( "map/%s/map" , parsedBattleColliderInfo . stageName ) ;
cc . loader . loadRes ( fullPathOfTmxFile , cc . TiledMapAsset , ( err , tmxAsset ) => {
if ( null != err ) {
console . error ( err ) ;
return ;
}
2022-10-03 00:22:05 +08:00
2022-09-20 23:50:01 +08:00
/*
[WARNING]
- The order of the following statements is important, because we should have finished "_resetCurrentMatch" before the first "RoomDownsyncFrame".
2022-09-24 12:01:50 +08:00
- It's important to assign new "tmxAsset" before "extractBoundaryObjects", to ensure that the correct tilesets are used.
2022-09-20 23:50:01 +08:00
- To ensure clearance, put destruction of the "cc.TiledMap" component preceding that of "mapNode.destroyAllChildren()".
*/
tiledMapIns . tmxAsset = null ;
mapNode . removeAllChildren ( ) ;
2022-10-03 00:22:05 +08:00
self . _resetCurrentMatch ( ) ;
2022-09-20 23:50:01 +08:00
tiledMapIns . tmxAsset = tmxAsset ;
const newMapSize = tiledMapIns . getMapSize ( ) ;
const newTileSize = tiledMapIns . getTileSize ( ) ;
2022-10-03 00:22:05 +08:00
self . node . setContentSize ( newMapSize . width * newTileSize . width , newMapSize . height * newTileSize . height ) ;
2022-09-20 23:50:01 +08:00
self . node . setPosition ( cc . v2 ( 0 , 0 ) ) ;
/*
* Deliberately hiding "ImageLayer"s. This dirty fix is specific to "CocosCreator v2.2.1", where it got back the rendering capability of "ImageLayer of Tiled", yet made incorrectly. In this game our "markers of ImageLayers" are rendered by dedicated prefabs with associated colliders.
*
* -- YFLu, 2020-01-23
*/
const existingImageLayers = tiledMapIns . getObjectGroups ( ) ;
for ( let singleImageLayer of existingImageLayers ) {
2022-10-03 00:22:05 +08:00
singleImageLayer . node . opacity = 0 ;
2022-09-20 23:50:01 +08:00
}
2022-09-24 12:01:50 +08:00
let barrierIdCounter = 0 ;
2022-09-20 23:50:01 +08:00
const boundaryObjs = tileCollisionManager . extractBoundaryObjects ( self . node ) ;
2022-09-24 12:01:50 +08:00
for ( let boundaryObj of boundaryObjs . barriers ) {
2022-10-03 00:22:05 +08:00
const x0 = boundaryObj [ 0 ] . x ,
y0 = boundaryObj [ 0 ] . y ;
2022-09-24 12:01:50 +08:00
let pts = [ ] ;
// TODO: Simplify this redundant coordinate conversion within "extractBoundaryObjects", but since this routine is only called once per battle, not urgent.
for ( let i = 0 ; i < boundaryObj . length ; ++ i ) {
2022-10-03 00:22:05 +08:00
pts . push ( [ boundaryObj [ i ] . x - x0 , boundaryObj [ i ] . y - y0 ] ) ;
}
2022-09-24 12:01:50 +08:00
const newBarrierLatest = self . latestCollisionSys . createPolygon ( x0 , y0 , pts ) ;
2022-10-25 09:52:38 +08:00
// console.log("Created barrier: ", newBarrierLatest);
2022-09-24 12:01:50 +08:00
const newBarrierChaser = self . chaserCollisionSys . createPolygon ( x0 , y0 , pts ) ;
++ barrierIdCounter ;
2022-10-03 00:22:05 +08:00
const collisionBarrierIndex = ( self . collisionBarrierIndexPrefix + barrierIdCounter ) ;
self . latestCollisionSysMap . set ( collisionBarrierIndex , newBarrierLatest ) ;
self . chaserCollisionSysMap . set ( collisionBarrierIndex , newBarrierChaser ) ;
2022-09-24 12:01:50 +08:00
}
2022-09-20 23:50:01 +08:00
self . selfPlayerInfo = JSON . parse ( cc . sys . localStorage . getItem ( 'selfPlayer' ) ) ;
Object . assign ( self . selfPlayerInfo , {
id : self . selfPlayerInfo . playerId
} ) ;
const fullPathOfBackgroundMapTmxFile = cc . js . formatStr ( "map/%s/BackgroundMap/map" , parsedBattleColliderInfo . stageName ) ;
cc . loader . loadRes ( fullPathOfBackgroundMapTmxFile , cc . TiledMapAsset , ( err , backgroundMapTmxAsset ) => {
if ( null != err ) {
console . error ( err ) ;
return ;
}
self . backgroundMapTiledIns . tmxAsset = null ;
self . backgroundMapTiledIns . node . removeAllChildren ( ) ;
self . backgroundMapTiledIns . tmxAsset = backgroundMapTmxAsset ;
const newBackgroundMapSize = self . backgroundMapTiledIns . getMapSize ( ) ;
const newBackgroundMapTileSize = self . backgroundMapTiledIns . getTileSize ( ) ;
2022-10-03 00:22:05 +08:00
self . backgroundMapTiledIns . node . setContentSize ( newBackgroundMapSize . width * newBackgroundMapTileSize . width , newBackgroundMapSize . height * newBackgroundMapTileSize . height ) ;
2022-09-20 23:50:01 +08:00
self . backgroundMapTiledIns . node . setPosition ( cc . v2 ( 0 , 0 ) ) ;
const reqData = window . WsReq . encode ( {
msgId : Date . now ( ) ,
act : window . UPSYNC _MSG _ACT _PLAYER _COLLIDER _ACK ,
} ) . finish ( ) ;
window . sendSafely ( reqData ) ;
} ) ;
} ) ;
} ;
self . initAfterWSConnected = ( ) => {
const self = window . mapIns ;
self . hideGameRuleNode ( ) ;
self . transitToState ( ALL _MAP _STATES . WAITING ) ;
self . _inputControlEnabled = false ;
}
// The player is now viewing "self.gameRuleNode" with button(s) to start an actual battle. -- YFLu
const expectedRoomId = window . getExpectedRoomIdSync ( ) ;
const boundRoomId = window . getBoundRoomIdFromPersistentStorage ( ) ;
console . warn ( "Map.onLoad, expectedRoomId == " , expectedRoomId , ", boundRoomId == " , boundRoomId ) ;
if ( null != expectedRoomId ) {
self . disableGameRuleNode ( ) ;
// The player is now possibly viewing "self.gameRuleNode" with no button, and should wait for `self.initAfterWSConnected` to be called.
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 23:50:01 +08:00
window . initPersistentSessionClient ( self . initAfterWSConnected , expectedRoomId ) ;
} else if ( null != boundRoomId ) {
self . disableGameRuleNode ( ) ;
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-10-04 11:24:47 +08:00
window . initPersistentSessionClient ( self . initAfterWSConnected , boundRoomId ) ;
2022-09-20 23:50:01 +08:00
} else {
self . showPopupInCanvas ( self . gameRuleNode ) ;
2022-10-03 00:22:05 +08:00
// Deliberately left blank. -- YFLu
2022-09-20 23:50:01 +08:00
}
} ,
disableGameRuleNode ( ) {
const self = window . mapIns ;
if ( null == self . gameRuleNode ) {
return ;
}
if ( null == self . gameRuleScriptIns ) {
return ;
}
if ( null == self . gameRuleScriptIns . modeButton ) {
return ;
}
self . gameRuleScriptIns . modeButton . active = false ;
} ,
hideGameRuleNode ( ) {
const self = window . mapIns ;
if ( null == self . gameRuleNode ) {
return ;
}
self . gameRuleNode . active = false ;
} ,
enableInputControls ( ) {
this . _inputControlEnabled = true ;
} ,
disableInputControls ( ) {
this . _inputControlEnabled = false ;
} ,
2022-10-03 11:42:19 +08:00
onRoomDownsyncFrame ( rdf ) {
2022-09-21 12:21:36 +08:00
// This function is also applicable to "re-joining".
2022-09-20 23:50:01 +08:00
const self = window . mapIns ;
2022-10-03 11:42:19 +08:00
if ( rdf . id < self . lastAllConfirmedRenderFrameId ) {
return window . RING _BUFF _FAILED _TO _SET ;
}
2022-10-03 00:22:05 +08:00
const dumpRenderCacheRet = self . dumpToRenderCache ( rdf ) ;
if ( window . RING _BUFF _FAILED _TO _SET == dumpRenderCacheRet ) {
console . error ( "Something is wrong while setting the RingBuffer by frameId!" ) ;
return dumpRenderCacheRet ;
}
if ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START < rdf . id && window . RING _BUFF _CONSECUTIVE _SET == dumpRenderCacheRet ) {
2022-10-03 11:42:19 +08:00
/*
Don't change
- lastAllConfirmedRenderFrameId, it's updated only in "rollbackAndChase > _createRoomDownsyncFrameLocally" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
- chaserRenderFrameId, it's updated only in "onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
*/
2022-10-03 00:22:05 +08:00
return dumpRenderCacheRet ;
}
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
console . log ( 'On battle started or resynced! renderFrameId=' , rdf . id ) ;
2022-10-01 15:14:05 +08:00
self . renderFrameId = rdf . id ;
2022-10-03 00:22:05 +08:00
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2022-10-03 11:42:19 +08:00
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
2022-10-03 00:22:05 +08:00
self . lastAllConfirmedRenderFrameId = rdf . id ;
2022-10-01 15:14:05 +08:00
self . chaserRenderFrameId = rdf . id ;
2022-09-26 10:36:46 +08:00
const players = rdf . players ;
const playerMetas = rdf . playerMetas ;
self . _initPlayerRichInfoDict ( players , playerMetas ) ;
// Show the top status indicators for IN_BATTLE
const playersInfoScriptIns = self . playersInfoNode . getComponent ( "PlayersInfo" ) ;
for ( let i in playerMetas ) {
const playerMeta = playerMetas [ i ] ;
playersInfoScriptIns . updateData ( playerMeta ) ;
}
2022-09-21 12:21:36 +08:00
if ( null != rdf . countdownNanos ) {
self . countdownNanos = rdf . countdownNanos ;
}
2022-09-20 23:50:01 +08:00
if ( null != self . musicEffectManagerScriptIns ) {
self . musicEffectManagerScriptIns . playBGM ( ) ;
}
const canvasNode = self . canvasNode ;
self . ctrl = canvasNode . getComponent ( "TouchEventsManager" ) ;
self . enableInputControls ( ) ;
if ( self . countdownToBeginGameNode . parent ) {
self . countdownToBeginGameNode . parent . removeChild ( self . countdownToBeginGameNode ) ;
}
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . IN _BATTLE ;
2022-09-24 12:01:50 +08:00
self . applyRoomDownsyncFrameDynamics ( rdf ) ;
2022-10-03 00:22:05 +08:00
return dumpRenderCacheRet ;
} ,
equalInputLists ( lhs , rhs ) {
if ( null == lhs || null == rhs ) return false ;
if ( lhs . length != rhs . length ) return false ;
for ( let i in lhs ) {
if ( lhs [ i ] == rhs [ i ] ) continue ;
return false ;
}
return true ;
} ,
2022-10-16 10:38:38 +08:00
onInputFrameDownsyncBatch ( batch ) {
2022-10-03 00:22:05 +08:00
const self = this ;
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState
&& ALL _BATTLE _STATES . IN _SETTLEMENT != self . battleState ) {
return ;
}
let firstPredictedYetIncorrectInputFrameId = null ;
for ( let k in batch ) {
const inputFrameDownsync = batch [ k ] ;
const inputFrameDownsyncId = inputFrameDownsync . inputFrameId ;
2022-10-03 11:42:19 +08:00
if ( inputFrameDownsyncId < self . lastAllConfirmedInputFrameId ) {
continue ;
}
2022-10-16 10:38:38 +08:00
const localInputFrame = self . recentInputCache . getByFrameId ( inputFrameDownsyncId ) ;
if ( null != localInputFrame
&&
null == firstPredictedYetIncorrectInputFrameId
&&
! self . equalInputLists ( localInputFrame . inputList , inputFrameDownsync . inputList )
) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId ;
2022-10-03 00:22:05 +08:00
}
self . lastAllConfirmedInputFrameId = inputFrameDownsyncId ;
self . dumpToInputCache ( inputFrameDownsync ) ;
}
2022-10-16 10:38:38 +08:00
if ( null == firstPredictedYetIncorrectInputFrameId ) return ;
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId ;
const renderFrameId1 = self . _convertToFirstUsedRenderFrameId ( inputFrameId1 , self . inputDelayFrames ) ; // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
if ( renderFrameId1 >= self . renderFrameId ) return ; // No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId1" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range.
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
if ( renderFrameId1 >= self . chaserRenderFrameId ) return ;
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
/*
A typical case is as follows.
--------------------------------------------------------
[self.lastAllConfirmedRenderFrameId] : 22
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
<renderFrameId1> : 36
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
<self.chaserRenderFrameId> : 62
[self.renderFrameId] : 64
--------------------------------------------------------
*/
// The actual rollback-and-chase would later be executed in update(dt).
console . warn ( "Mismatched input detected, resetting chaserRenderFrameId: inputFrameId1:" , inputFrameId1 , ", renderFrameId1:" , renderFrameId1 , ", chaserRenderFrameId before reset: " , self . chaserRenderFrameId ) ;
self . chaserRenderFrameId = renderFrameId1 ;
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
onPlayerAdded ( rdf ) {
const self = this ;
// Update the "finding player" GUI and show it if not previously present
if ( ! self . findingPlayerNode . parent ) {
self . showPopupInCanvas ( self . findingPlayerNode ) ;
}
let findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . updatePlayersInfo ( rdf . playerMetas ) ;
} ,
2022-09-20 23:50:01 +08:00
logBattleStats ( ) {
const self = this ;
let s = [ ] ;
2022-10-04 11:24:47 +08:00
s . push ( "Battle stats: renderFrameId=" + self . renderFrameId + ", lastAllConfirmedRenderFrameId=" + self . lastAllConfirmedRenderFrameId + ", lastUpsyncInputFrameId=" + self . lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self . lastAllConfirmedInputFrameId ) ;
2022-10-03 00:22:05 +08:00
2022-09-25 20:48:09 +08:00
for ( let i = self . recentInputCache . stFrameId ; i < self . recentInputCache . edFrameId ; ++ i ) {
2022-10-03 00:22:05 +08:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( i ) ;
2022-09-22 17:09:49 +08:00
s . push ( JSON . stringify ( inputFrameDownsync ) ) ;
2022-10-03 00:22:05 +08:00
}
2022-09-20 23:50:01 +08:00
console . log ( s . join ( '\n' ) ) ;
} ,
onBattleStopped ( ) {
const self = this ;
2022-10-03 00:22:05 +08:00
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState ) {
return ;
}
2022-09-20 23:50:01 +08:00
self . countdownNanos = null ;
self . logBattleStats ( ) ;
if ( self . musicEffectManagerScriptIns ) {
self . musicEffectManagerScriptIns . stopAllMusic ( ) ;
}
const canvasNode = self . canvasNode ;
const resultPanelNode = self . resultPanelNode ;
const resultPanelScriptIns = resultPanelNode . getComponent ( "ResultPanel" ) ;
resultPanelScriptIns . showPlayerInfo ( self . playerRichInfoDict ) ;
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
self . battleState = ALL _BATTLE _STATES . IN _SETTLEMENT ;
self . showPopupInCanvas ( resultPanelNode ) ;
// Clear player info
self . playersInfoNode . getComponent ( "PlayersInfo" ) . clearInfo ( ) ;
} ,
spawnPlayerNode ( joinIndex , x , y ) {
2022-09-24 12:01:50 +08:00
const self = this ;
const newPlayerNode = 1 == joinIndex ? cc . instantiate ( self . player1Prefab ) : cc . instantiate ( self . player2Prefab ) ; // hardcoded for now, car color determined solely by joinIndex
2022-09-20 23:50:01 +08:00
newPlayerNode . setPosition ( cc . v2 ( x , y ) ) ;
2022-09-24 12:01:50 +08:00
newPlayerNode . getComponent ( "SelfPlayer" ) . mapNode = self . node ;
const currentSelfColliderCircle = newPlayerNode . getComponent ( cc . CircleCollider ) ;
2022-10-21 22:39:08 +08:00
const r = currentSelfColliderCircle . radius , d = 2 * r ;
// The collision box of an individual player is a polygon instead of a circle, because the backend collision engine doesn't handle circle alignment well.
const x0 = x - r , y0 = y - r ;
let pts = [ [ 0 , 0 ] , [ d , 0 ] , [ d , d ] , [ 0 , d ] ] ;
2022-09-20 23:50:01 +08:00
2022-10-21 22:39:08 +08:00
const newPlayerColliderLatest = self . latestCollisionSys . createPolygon ( x0 , y0 , pts ) ;
const newPlayerColliderChaser = self . chaserCollisionSys . createPolygon ( x0 , y0 , pts ) ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
self . latestCollisionSysMap . set ( collisionPlayerIndex , newPlayerColliderLatest ) ;
self . chaserCollisionSysMap . set ( collisionPlayerIndex , newPlayerColliderChaser ) ;
safelyAddChild ( self . node , newPlayerNode ) ;
2022-09-20 23:50:01 +08:00
setLocalZOrder ( newPlayerNode , 5 ) ;
newPlayerNode . active = true ;
const playerScriptIns = newPlayerNode . getComponent ( "SelfPlayer" ) ;
2022-10-03 00:22:05 +08:00
playerScriptIns . scheduleNewDirection ( {
dx : 0 ,
dy : 0
} , true ) ;
2022-09-20 23:50:01 +08:00
return [ newPlayerNode , playerScriptIns ] ;
} ,
update ( dt ) {
const self = this ;
2022-09-25 20:48:09 +08:00
if ( ALL _BATTLE _STATES . IN _BATTLE == self . battleState ) {
2022-10-01 23:54:48 +08:00
const elapsedMillisSinceLastFrameIdTriggered = performance . now ( ) - self . lastRenderFrameIdTriggeredAt ;
2022-10-03 00:22:05 +08:00
if ( elapsedMillisSinceLastFrameIdTriggered < ( self . rollbackEstimatedDtMillis ) ) {
2022-10-01 23:54:48 +08:00
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
2022-10-01 20:45:38 +08:00
return ;
}
2022-09-25 20:48:09 +08:00
try {
2022-10-03 00:22:05 +08:00
let st = performance . now ( ) ;
let prevSelfInput = null ,
currSelfInput = null ;
const noDelayInputFrameId = self . _convertToInputFrameId ( self . renderFrameId , 0 ) ; // It's important that "inputDelayFrames == 0" here
if ( self . shouldGenerateInputFrameUpsync ( self . renderFrameId ) ) {
const prevAndCurrInputs = self . _generateInputFrameUpsync ( noDelayInputFrameId ) ;
prevSelfInput = prevAndCurrInputs [ 0 ] ;
currSelfInput = prevAndCurrInputs [ 1 ] ;
}
2022-09-24 12:01:50 +08:00
2022-10-03 00:22:05 +08:00
let t0 = performance . now ( ) ;
if ( self . shouldSendInputFrameUpsyncBatch ( prevSelfInput , currSelfInput , self . lastUpsyncInputFrameId , noDelayInputFrameId ) ) {
// TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously?
self . sendInputFrameUpsyncBatch ( noDelayInputFrameId ) ;
}
let t1 = performance . now ( ) ;
// Use "fractional-frame-chasing" to guarantee that "self.update(dt)" is not jammed by a "large range of frame-chasing". See `<proj-root>/ConcerningEdgeCases.md` for the motivation.
const prevChaserRenderFrameId = self . chaserRenderFrameId ;
let nextChaserRenderFrameId = ( prevChaserRenderFrameId + self . maxChasingRenderFramesPerUpdate ) ;
2022-10-03 11:42:19 +08:00
if ( nextChaserRenderFrameId > self . renderFrameId ) {
2022-10-03 00:22:05 +08:00
nextChaserRenderFrameId = self . renderFrameId ;
2022-10-03 11:42:19 +08:00
}
2022-10-03 00:22:05 +08:00
self . rollbackAndChase ( prevChaserRenderFrameId , nextChaserRenderFrameId , self . chaserCollisionSys , self . chaserCollisionSysMap ) ;
self . chaserRenderFrameId = nextChaserRenderFrameId ; // Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
let t2 = performance . now ( ) ;
2022-10-16 10:38:38 +08:00
// Inside the following "self.rollbackAndChase" (which actually ROLLS FORWARD), the "self.latestCollisionSys" is ALWAYS "ROLLED BACK" to "self.recentRenderCache.get(self.renderFrameId)" before being applied dynamics from corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
2022-10-03 00:22:05 +08:00
const rdf = self . rollbackAndChase ( self . renderFrameId , self . renderFrameId + 1 , self . latestCollisionSys , self . latestCollisionSysMap ) ;
2022-10-10 14:33:04 +08:00
/*
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
if (nonTrivialChaseEnded) {
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
}
*/
2022-10-03 00:22:05 +08:00
self . applyRoomDownsyncFrameDynamics ( rdf ) ;
let t3 = performance . now ( ) ;
2022-09-25 20:48:09 +08:00
} catch ( err ) {
console . error ( "Error during Map.update" , err ) ;
} finally {
// Update countdown
if ( null != self . countdownNanos ) {
2022-10-16 10:38:38 +08:00
self . countdownNanos = self . battleDurationNanos - self . renderFrameId * self . rollbackEstimatedDtNanos ;
2022-09-25 20:48:09 +08:00
if ( self . countdownNanos <= 0 ) {
self . onBattleStopped ( self . playerRichInfoDict ) ;
return ;
}
2022-09-20 23:50:01 +08:00
2022-09-25 20:48:09 +08:00
const countdownSeconds = parseInt ( self . countdownNanos / 1000000000 ) ;
if ( isNaN ( countdownSeconds ) ) {
console . warn ( ` countdownSeconds is NaN for countdownNanos == ${ self . countdownNanos } . ` ) ;
}
self . countdownLabel . string = countdownSeconds ;
2022-09-20 23:50:01 +08:00
}
2022-09-25 20:48:09 +08:00
++ self . renderFrameId ; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
2022-10-01 23:54:48 +08:00
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2022-09-20 23:50:01 +08:00
}
}
} ,
transitToState ( s ) {
const self = this ;
self . state = s ;
} ,
logout ( byClick /* The case where this param is "true" will be triggered within `ConfirmLogout.js`.*/ , shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
const self = this ;
const localClearance = ( ) => {
window . clearLocalStorageAndBackToLoginScene ( shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) ;
}
const selfPlayerStr = cc . sys . localStorage . getItem ( "selfPlayer" ) ;
if ( null == selfPlayerStr ) {
localClearance ( ) ;
2022-10-03 00:22:05 +08:00
return ;
2022-09-20 23:50:01 +08:00
}
const selfPlayerInfo = JSON . parse ( selfPlayerStr ) ;
try {
NetworkUtils . ajax ( {
url : backendAddress . PROTOCOL + '://' + backendAddress . HOST + ':' + backendAddress . PORT + constants . ROUTE _PATH . API + constants . ROUTE _PATH . PLAYER + constants . ROUTE _PATH . VERSION + constants . ROUTE _PATH . INT _AUTH _TOKEN + constants . ROUTE _PATH . LOGOUT ,
type : "POST" ,
2022-10-03 00:22:05 +08:00
data : {
intAuthToken : selfPlayerInfo . intAuthToken
} ,
2022-09-20 23:50:01 +08:00
success : function ( res ) {
if ( res . ret != constants . RET _CODE . OK ) {
console . log ( "Logout failed: " , res ) ;
}
localClearance ( ) ;
} ,
error : function ( xhr , status , errMsg ) {
localClearance ( ) ;
} ,
timeout : function ( ) {
localClearance ( ) ;
}
} ) ;
} catch ( e ) { } finally {
// For Safari (both desktop and mobile).
localClearance ( ) ;
}
} ,
onLogoutClicked ( evt ) {
const self = this ;
self . showPopupInCanvas ( self . confirmLogoutNode ) ;
} ,
onLogoutConfirmationDismissed ( ) {
const self = this ;
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
const canvasNode = self . canvasNode ;
canvasNode . removeChild ( self . confirmLogoutNode ) ;
self . enableInputControls ( ) ;
} ,
onGameRule1v1ModeClicked ( evt , cb ) {
const self = this ;
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 23:50:01 +08:00
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
self . hideGameRuleNode ( ) ;
} ,
showPopupInCanvas ( toShowNode ) {
const self = this ;
self . disableInputControls ( ) ;
self . transitToState ( ALL _MAP _STATES . SHOWING _MODAL _POPUP ) ;
safelyAddChild ( self . widgetsAboveAllNode , toShowNode ) ;
setLocalZOrder ( toShowNode , 10 ) ;
} ,
2022-10-04 11:24:47 +08:00
hideFindingPlayersGUI ( ) {
const self = this ;
if ( null == self . findingPlayerNode . parent ) return ;
self . findingPlayerNode . parent . removeChild ( self . findingPlayerNode ) ;
} ,
onBattleReadyToStart ( playerMetas ) {
2022-09-26 10:36:46 +08:00
console . log ( "Calling `onBattleReadyToStart` with:" , playerMetas ) ;
2022-09-20 23:50:01 +08:00
const self = this ;
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
2022-09-26 10:36:46 +08:00
findingPlayerScriptIns . hideExitButton ( ) ;
2022-09-20 23:50:01 +08:00
findingPlayerScriptIns . updatePlayersInfo ( playerMetas ) ;
2022-09-26 10:36:46 +08:00
2022-10-04 11:24:47 +08:00
// Delay to hide the "finding player" GUI, then show a countdown clock
window . setTimeout ( ( ) => {
self . hideFindingPlayersGUI ( ) ;
const countDownScriptIns = self . countdownToBeginGameNode . getComponent ( "CountdownToBeginGame" ) ;
countDownScriptIns . setData ( ) ;
self . showPopupInCanvas ( self . countdownToBeginGameNode ) ;
} , 1500 ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-24 12:01:50 +08:00
_createRoomDownsyncFrameLocally ( renderFrameId , collisionSys , collisionSysMap ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-10-03 00:22:05 +08:00
const prevRenderFrameId = renderFrameId - 1 ;
2022-09-29 12:21:04 +08:00
const inputFrameAppliedOnPrevRenderFrame = (
2022-10-03 00:22:05 +08:00
0 > prevRenderFrameId
?
2022-09-24 12:01:50 +08:00
null
:
self . getCachedInputFrameDownsyncWithPrediction ( self . _convertToInputFrameId ( prevRenderFrameId , self . inputDelayFrames ) )
) ;
// TODO: Find a better way to assign speeds instead of using "speedRefRenderFrameId".
const speedRefRenderFrameId = prevRenderFrameId ;
const speedRefRenderFrame = (
2022-10-03 00:22:05 +08:00
0 > speedRefRenderFrameId
?
2022-09-24 12:01:50 +08:00
null
:
2022-09-29 12:21:04 +08:00
self . recentRenderCache . getByFrameId ( speedRefRenderFrameId )
2022-09-24 12:01:50 +08:00
) ;
2022-09-20 23:50:01 +08:00
const rdf = {
2022-09-24 12:01:50 +08:00
id : renderFrameId ,
refFrameId : renderFrameId ,
2022-10-03 00:22:05 +08:00
players : { }
2022-09-20 23:50:01 +08:00
} ;
2022-09-22 12:45:17 +08:00
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-03 00:22:05 +08:00
const joinIndex = playerRichInfo . joinIndex ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-10-21 22:39:08 +08:00
const currentSelfColliderCircle = playerRichInfo . node . getComponent ( cc . CircleCollider ) ;
const r = currentSelfColliderCircle . radius ;
2022-09-20 23:50:01 +08:00
rdf . players [ playerRichInfo . id ] = {
id : playerRichInfo . id ,
2022-10-21 22:39:08 +08:00
x : playerCollider . x + r , // [WARNING] the (x, y) of "playerCollider" is offset to the anchor (i.e. first point of all points) of the polygon shape
y : playerCollider . y + r ,
2022-10-03 00:22:05 +08:00
dir : self . ctrl . decodeDirection ( null == inputFrameAppliedOnPrevRenderFrame ? 0 : inputFrameAppliedOnPrevRenderFrame . inputList [ joinIndex - 1 ] ) ,
2022-09-24 12:01:50 +08:00
speed : ( null == speedRefRenderFrame ? playerRichInfo . speed : speedRefRenderFrame . players [ playerRichInfo . id ] . speed ) ,
2022-10-03 00:22:05 +08:00
joinIndex : joinIndex
2022-09-20 23:50:01 +08:00
} ;
2022-09-22 12:45:17 +08:00
} ) ;
2022-09-24 12:01:50 +08:00
if (
2022-10-03 00:22:05 +08:00
null != inputFrameAppliedOnPrevRenderFrame && self . _allConfirmed ( inputFrameAppliedOnPrevRenderFrame . confirmedList )
&&
2022-09-24 12:01:50 +08:00
self . lastAllConfirmedRenderFrameId >= prevRenderFrameId
&&
rdf . id > self . lastAllConfirmedRenderFrameId
) {
2022-10-03 00:22:05 +08:00
self . lastAllConfirmedRenderFrameId = rdf . id ;
2022-10-02 16:19:54 +08:00
self . chaserRenderFrameId = rdf . id ; // it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId"
2022-09-24 12:01:50 +08:00
}
2022-10-03 00:22:05 +08:00
self . dumpToRenderCache ( rdf ) ;
2022-09-21 17:22:34 +08:00
return rdf ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-24 12:01:50 +08:00
applyRoomDownsyncFrameDynamics ( rdf ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-09-22 12:45:17 +08:00
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-09-20 23:50:01 +08:00
const immediatePlayerInfo = rdf . players [ playerId ] ;
2022-10-16 10:38:38 +08:00
const dx = ( immediatePlayerInfo . x - playerRichInfo . node . x ) ;
const dy = ( immediatePlayerInfo . y - playerRichInfo . node . y ) ;
const selfJiggling = ( playerId == self . selfPlayerInfo . playerId && ( 0 != dx && self . teleportEps1D >= Math . abs ( dx ) && 0 != dy && self . teleportEps1D >= Math . abs ( dy ) ) ) ;
2022-10-10 14:33:04 +08:00
if ( ! selfJiggling ) {
playerRichInfo . node . setPosition ( immediatePlayerInfo . x , immediatePlayerInfo . y ) ;
} else {
console . log ( "selfJiggling: dx = " , dx , ", dy = " , dy ) ;
}
playerRichInfo . scriptIns . scheduleNewDirection ( immediatePlayerInfo . dir , false ) ;
2022-10-03 00:22:05 +08:00
playerRichInfo . scriptIns . updateSpeed ( immediatePlayerInfo . speed ) ;
2022-09-22 12:45:17 +08:00
} ) ;
2022-10-03 00:22:05 +08:00
} ,
2022-09-20 23:50:01 +08:00
2022-09-24 12:01:50 +08:00
getCachedInputFrameDownsyncWithPrediction ( inputFrameId ) {
2022-09-22 12:45:17 +08:00
const self = this ;
2022-09-24 12:01:50 +08:00
let inputFrameDownsync = self . recentInputCache . getByFrameId ( inputFrameId ) ;
2022-09-25 20:48:09 +08:00
if ( null != inputFrameDownsync && - 1 != self . lastAllConfirmedInputFrameId && inputFrameId > self . lastAllConfirmedInputFrameId ) {
2022-10-03 00:22:05 +08:00
const lastAllConfirmedInputFrame = self . recentInputCache . getByFrameId ( self . lastAllConfirmedInputFrameId ) ;
2022-09-22 12:45:17 +08:00
for ( let i = 0 ; i < inputFrameDownsync . inputList . length ; ++ i ) {
2022-10-03 00:22:05 +08:00
if ( i == self . selfPlayerInfo . joinIndex - 1 ) continue ;
inputFrameDownsync . inputList [ i ] = lastAllConfirmedInputFrame . inputList [ i ] ;
2022-09-22 12:45:17 +08:00
}
2022-10-03 00:22:05 +08:00
}
2022-09-22 12:45:17 +08:00
return inputFrameDownsync ;
} ,
2022-09-25 23:21:12 +08:00
rollbackAndChase ( renderFrameIdSt , renderFrameIdEd , collisionSys , collisionSysMap ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-10-03 11:42:19 +08:00
let latestRdf = self . recentRenderCache . getByFrameId ( renderFrameIdSt ) ; // typed "RoomDownsyncFrame"
if ( null == latestRdf ) {
2022-10-03 00:22:05 +08:00
console . error ( "Couldn't find renderFrameId=" , renderFrameIdSt , " to rollback, lastAllConfirmedRenderFrameId=" , self . lastAllConfirmedRenderFrameId , ", lastAllConfirmedInputFrameId=" , self . lastAllConfirmedInputFrameId , ", recentRenderCache=" , self . _stringifyRecentRenderCache ( false ) , ", recentInputCache=" , self . _stringifyRecentInputCache ( false ) ) ;
2022-09-26 10:36:46 +08:00
}
2022-10-03 11:42:19 +08:00
if ( renderFrameIdSt >= renderFrameIdEd ) {
return latestRdf ;
}
2022-09-24 12:01:50 +08:00
/*
2022-10-03 11:42:19 +08:00
Reset "position" of players in "collisionSys" according to "renderFrameIdSt". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
2022-09-24 12:01:50 +08:00
*/
2022-09-22 12:45:17 +08:00
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-03 00:22:05 +08:00
const joinIndex = playerRichInfo . joinIndex ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-10-03 11:42:19 +08:00
const player = latestRdf . players [ playerId ] ;
2022-10-21 22:39:08 +08:00
const currentSelfColliderCircle = playerRichInfo . node . getComponent ( cc . CircleCollider ) ;
const r = currentSelfColliderCircle . radius ;
playerCollider . x = player . x - r ;
playerCollider . y = player . y - r ;
2022-09-22 12:45:17 +08:00
} ) ;
2022-09-21 23:59:05 +08:00
2022-09-24 12:01:50 +08:00
/*
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd".
*/
2022-09-25 20:48:09 +08:00
for ( let i = renderFrameIdSt ; i < renderFrameIdEd ; ++ i ) {
2022-09-24 12:01:50 +08:00
const renderFrame = self . recentRenderCache . getByFrameId ( i ) ; // typed "RoomDownsyncFrame"
const j = self . _convertToInputFrameId ( i , self . inputDelayFrames ) ;
2022-10-03 00:22:05 +08:00
const inputFrameDownsync = self . getCachedInputFrameDownsyncWithPrediction ( j ) ;
if ( null == inputFrameDownsync ) {
console . error ( "Failed to get cached inputFrameDownsync for renderFrameId=" , i , ", inputFrameId=" , j , "lastAllConfirmedRenderFrameId=" , self . lastAllConfirmedRenderFrameId , ", lastAllConfirmedInputFrameId=" , self . lastAllConfirmedInputFrameId , ", recentRenderCache=" , self . _stringifyRecentRenderCache ( false ) , ", recentInputCache=" , self . _stringifyRecentInputCache ( false ) ) ;
}
const inputList = inputFrameDownsync . inputList ;
2022-10-03 11:42:19 +08:00
// [WARNING] Traverse in the order of joinIndices to guarantee determinism.
2022-09-29 12:21:04 +08:00
for ( let j in self . playerRichInfoArr ) {
2022-10-03 00:22:05 +08:00
const joinIndex = parseInt ( j ) + 1 ;
const playerId = self . playerRichInfoArr [ j ] . id ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
const player = renderFrame . players [ playerId ] ;
2022-10-03 00:22:05 +08:00
const encodedInput = inputList [ joinIndex - 1 ] ;
2022-09-25 20:48:09 +08:00
const decodedInput = self . ctrl . decodeDirection ( encodedInput ) ;
2022-10-03 00:22:05 +08:00
const baseChange = player . speed * self . rollbackEstimatedDt * decodedInput . speedFactor ;
playerCollider . x += baseChange * decodedInput . dx ;
playerCollider . y += baseChange * decodedInput . dy ;
2022-09-29 12:21:04 +08:00
}
2022-09-24 12:01:50 +08:00
collisionSys . update ( ) ;
2022-09-26 10:36:46 +08:00
const result = collisionSys . createResult ( ) ; // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle?
2022-10-03 00:22:05 +08:00
2022-09-26 11:16:18 +08:00
for ( let i in self . playerRichInfoArr ) {
2022-10-03 00:22:05 +08:00
const joinIndex = parseInt ( i ) + 1 ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
const potentials = playerCollider . potentials ( ) ;
for ( const barrier of potentials ) {
// Test if the player collides with the wall
if ( ! playerCollider . collides ( barrier , result ) ) continue ;
// Push the player out of the wall
playerCollider . x -= result . overlap * result . overlap _x ;
playerCollider . y -= result . overlap * result . overlap _y ;
}
2022-09-26 11:16:18 +08:00
}
2022-10-03 11:42:19 +08:00
2022-10-04 11:24:47 +08:00
latestRdf = self . _createRoomDownsyncFrameLocally ( i + 1 , collisionSys , collisionSysMap ) ;
2022-09-21 23:59:05 +08:00
}
2022-10-03 00:22:05 +08:00
2022-10-03 11:42:19 +08:00
return latestRdf ;
2022-10-03 00:22:05 +08:00
} ,
2022-09-20 23:50:01 +08:00
_initPlayerRichInfoDict ( players , playerMetas ) {
const self = this ;
for ( let k in players ) {
const playerId = parseInt ( k ) ;
2022-09-22 12:45:17 +08:00
if ( self . playerRichInfoDict . has ( playerId ) ) continue ; // Skip already put keys
2022-09-20 23:50:01 +08:00
const immediatePlayerInfo = players [ playerId ] ;
const immediatePlayerMeta = playerMetas [ playerId ] ;
const nodeAndScriptIns = self . spawnPlayerNode ( immediatePlayerInfo . joinIndex , immediatePlayerInfo . x , immediatePlayerInfo . y ) ;
2022-10-03 00:22:05 +08:00
self . playerRichInfoDict . set ( playerId , immediatePlayerInfo ) ;
2022-09-20 23:50:01 +08:00
2022-09-22 12:45:17 +08:00
Object . assign ( self . playerRichInfoDict . get ( playerId ) , {
2022-09-20 23:50:01 +08:00
node : nodeAndScriptIns [ 0 ] ,
scriptIns : nodeAndScriptIns [ 1 ]
} ) ;
if ( self . selfPlayerInfo . id == playerId ) {
2022-10-03 00:22:05 +08:00
self . selfPlayerInfo = Object . assign ( self . selfPlayerInfo , immediatePlayerInfo ) ;
2022-09-20 23:50:01 +08:00
nodeAndScriptIns [ 1 ] . showArrowTipNode ( ) ;
}
}
2022-09-26 11:16:18 +08:00
self . playerRichInfoArr = new Array ( self . playerRichInfoDict . size ) ;
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-03 00:22:05 +08:00
self . playerRichInfoArr [ playerRichInfo . joinIndex - 1 ] = playerRichInfo ;
} ) ;
2022-09-20 23:50:01 +08:00
} ,
_stringifyRecentInputCache ( usefullOutput ) {
2022-09-26 10:36:46 +08:00
const self = this ;
2022-09-20 23:50:01 +08:00
if ( true == usefullOutput ) {
2022-09-22 17:09:49 +08:00
let s = [ ] ;
2022-09-26 10:36:46 +08:00
for ( let i = self . recentInputCache . stFrameId ; i < self . recentInputCache . edFrameId ; ++ i ) {
s . push ( JSON . stringify ( self . recentInputCache . getByFrameId ( i ) ) ) ;
}
2022-09-22 17:09:49 +08:00
return s . join ( '\n' ) ;
2022-09-20 23:50:01 +08:00
}
2022-09-26 10:36:46 +08:00
return "[stInputFrameId=" + self . recentInputCache . stFrameId + ", edInputFrameId=" + self . recentInputCache . edFrameId + ")" ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-21 17:22:34 +08:00
2022-09-26 10:36:46 +08:00
_stringifyRecentRenderCache ( usefullOutput ) {
2022-09-21 17:22:34 +08:00
const self = this ;
2022-09-26 10:36:46 +08:00
if ( true == usefullOutput ) {
let s = [ ] ;
for ( let i = self . recentRenderCache . stFrameId ; i < self . recentRenderCache . edFrameId ; ++ i ) {
s . push ( JSON . stringify ( self . recentRenderCache . getByFrameId ( i ) ) ) ;
2022-09-21 17:22:34 +08:00
}
2022-09-26 10:36:46 +08:00
return s . join ( '\n' ) ;
}
return "[stRenderFrameId=" + self . recentRenderCache . stFrameId + ", edRenderFrameId=" + self . recentRenderCache . edFrameId + ")" ;
2022-09-21 17:22:34 +08:00
} ,
2022-09-26 10:36:46 +08:00
2022-09-20 23:50:01 +08:00
} ) ;