2022-09-20 15:50:01 +00: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 08:42:44 +00:00
const collisions = require ( './modules/Collisions' ) ;
2022-09-24 04:01:50 +00:00
const RingBuffer = require ( './RingBuffer' ) ;
2022-09-23 08:42:44 +00:00
2022-09-20 15:50:01 +00: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 04:01:50 +00:00
BATTLE _READY _TO _START : - 1 ,
2022-10-01 15:54:48 +00:00
BATTLE _START : 0
2022-09-20 15:50:01 +00:00
} ;
2022-10-04 03:24:47 +00: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 15:50:01 +00: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 03:24:47 +00:00
renderFrameIdLagTolerance : {
type : cc . Integer ,
default : 4 // implies (renderFrameIdLagTolerance >> inputScaleFrames) count of inputFrameIds
} ,
2022-10-10 06:33:04 +00:00
teleportEps1D : {
type : cc . Float ,
default : 1e-3
} ,
2022-09-20 15:50:01 +00:00
} ,
2022-10-02 16:22:05 +00:00
2022-09-20 15:50:01 +00:00
_inputFrameIdDebuggable ( inputFrameId ) {
2022-10-02 16:22:05 +00:00
return ( 0 == inputFrameId % 10 ) ;
2022-09-20 15:50:01 +00:00
} ,
2022-10-02 16:22:05 +00:00
dumpToRenderCache : function ( roomDownsyncFrame ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-10-02 16:22:05 +00:00
const minToKeepRenderFrameId = self . lastAllConfirmedRenderFrameId ;
2022-09-26 02:36:46 +00:00
while ( 0 < self . recentRenderCache . cnt && self . recentRenderCache . stFrameId < minToKeepRenderFrameId ) {
2022-09-24 04:01:50 +00:00
self . recentRenderCache . pop ( ) ;
2022-09-22 09:09:49 +00:00
}
2022-10-02 16:22:05 +00:00
const ret = self . recentRenderCache . setByFrameId ( roomDownsyncFrame , roomDownsyncFrame . id ) ;
return ret ;
2022-09-20 15:50:01 +00:00
} ,
2022-10-02 16:22:05 +00:00
dumpToInputCache : function ( inputFrameDownsync ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-10-16 02:38:38 +00: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-02 16:22:05 +00:00
if ( minToKeepInputFrameId > self . lastAllConfirmedInputFrameId ) {
minToKeepInputFrameId = self . lastAllConfirmedInputFrameId ;
}
2022-09-26 02:36:46 +00:00
while ( 0 < self . recentInputCache . cnt && self . recentInputCache . stFrameId < minToKeepInputFrameId ) {
2022-09-24 04:01:50 +00:00
self . recentInputCache . pop ( ) ;
2022-09-22 09:09:49 +00:00
}
2022-10-02 16:22:05 +00: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 15:50:01 +00:00
}
2022-10-02 16:22:05 +00:00
return ret ;
2022-09-20 15:50:01 +00:00
} ,
_convertToInputFrameId ( renderFrameId , inputDelayFrames ) {
if ( renderFrameId < inputDelayFrames ) return 0 ;
2022-09-21 15:59:05 +00:00
return ( ( renderFrameId - inputDelayFrames ) >> this . inputScaleFrames ) ;
2022-09-20 15:50:01 +00:00
} ,
2022-09-29 04:21:04 +00:00
_convertToFirstUsedRenderFrameId ( inputFrameId , inputDelayFrames ) {
2022-09-21 15:59:05 +00:00
return ( ( inputFrameId << this . inputScaleFrames ) + inputDelayFrames ) ;
2022-09-20 15:50:01 +00:00
} ,
2022-09-29 04:21:04 +00:00
shouldGenerateInputFrameUpsync ( renderFrameId ) {
2022-10-02 16:22:05 +00:00
return ( ( renderFrameId & ( ( 1 << this . inputScaleFrames ) - 1 ) ) == 0 ) ;
2022-09-20 15:50:01 +00:00
} ,
2022-09-24 04:01:50 +00:00
_allConfirmed ( confirmedList ) {
2022-10-02 16:22:05 +00:00
return ( confirmedList + 1 ) == ( 1 << this . playerRichInfoDict . size ) ;
} ,
2022-09-24 04:01:50 +00:00
2022-09-20 15:50:01 +00:00
_generateInputFrameUpsync ( inputFrameId ) {
2022-09-24 04:01:50 +00:00
const self = this ;
2022-09-20 15:50:01 +00:00
if (
2022-09-24 04:01:50 +00:00
null == self . ctrl ||
null == self . selfPlayerInfo
2022-09-20 15:50:01 +00:00
) {
return [ null , null ] ;
}
2022-09-24 04:01:50 +00:00
const joinIndex = self . selfPlayerInfo . joinIndex ;
const discreteDir = self . ctrl . getDiscretizedDirection ( ) ;
2022-10-02 16:22:05 +00: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 04:01:50 +00:00
inputFrameId : inputFrameId ,
2022-10-02 16:22:05 +00:00
inputList : prefabbedInputList ,
confirmedList : ( 1 << ( self . selfPlayerInfo . joinIndex - 1 ) )
2022-09-20 15:50:01 +00:00
} ;
2022-10-02 16:22:05 +00: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 15:50:01 +00:00
2022-10-02 16:22:05 +00:00
const previousSelfInput = ( null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction . inputList [ joinIndex - 1 ] ) ;
2022-09-24 04:01:50 +00:00
return [ previousSelfInput , discreteDir . encodedIdx ] ;
2022-09-20 15:50:01 +00:00
} ,
2022-10-02 16:22:05 +00:00
2022-09-24 04:01:50 +00:00
shouldSendInputFrameUpsyncBatch ( prevSelfInput , currSelfInput , lastUpsyncInputFrameId , currInputFrameId ) {
2022-09-20 15:50:01 +00:00
/ *
2022-10-01 07:14:05 +00: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 15:50:01 +00:00
* /
if ( null == currSelfInput ) return false ;
2022-10-01 07:14:05 +00:00
2022-10-02 16:22:05 +00:00
const shouldUpsyncForEarlyAllConfirmedOnBackend = ( currInputFrameId - lastUpsyncInputFrameId >= this . inputFrameUpsyncDelayTolerance ) ;
2022-10-01 07:14:05 +00:00
return shouldUpsyncForEarlyAllConfirmedOnBackend || ( prevSelfInput != currSelfInput ) ;
2022-10-02 16:22:05 +00:00
} ,
2022-09-20 15:50:01 +00:00
2022-10-02 16:22:05 +00: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 04:01:50 +00:00
const self = this ;
2022-09-20 15:50:01 +00:00
let inputFrameUpsyncBatch = [ ] ;
2022-10-02 16:22:05 +00: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 04:01:50 +00:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( i ) ;
if ( null == inputFrameDownsync ) {
2022-10-02 16:22:05 +00:00
console . error ( "sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=" , i , ": latestLocalInputFrameId=" , latestLocalInputFrameId , ", recentInputCache=" , self . _stringifyRecentInputCache ( false ) ) ;
2022-09-24 04:01:50 +00:00
} else {
const inputFrameUpsync = {
inputFrameId : i ,
2022-10-02 16:22:05 +00:00
encodedDir : inputFrameDownsync . inputList [ self . selfPlayerInfo . joinIndex - 1 ] ,
2022-09-24 04:01:50 +00:00
} ;
inputFrameUpsyncBatch . push ( inputFrameUpsync ) ;
}
2022-10-02 16:22:05 +00:00
}
2022-09-20 15:50:01 +00:00
const reqData = window . WsReq . encode ( {
msgId : Date . now ( ) ,
2022-09-24 04:01:50 +00:00
playerId : self . selfPlayerInfo . id ,
2022-09-20 15:50:01 +00:00
act : window . UPSYNC _MSG _ACT _PLAYER _CMD ,
2022-09-24 04:01:50 +00:00
joinIndex : self . selfPlayerInfo . joinIndex ,
ackingFrameId : self . lastAllConfirmedRenderFrameId ,
ackingInputFrameId : self . lastAllConfirmedInputFrameId ,
2022-09-20 15:50:01 +00:00
inputFrameUpsyncBatch : inputFrameUpsyncBatch ,
} ) . finish ( ) ;
window . sendSafely ( reqData ) ;
2022-10-02 16:22:05 +00:00
self . lastUpsyncInputFrameId = latestLocalInputFrameId ;
2022-09-20 15:50:01 +00: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 15:02:39 +00:00
if ( null != window . handleClientSessionError ) {
window . handleClientSessionError = null ;
2022-09-20 15:50:01 +00: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 04:01:50 +00:00
self . collisionPlayerIndexPrefix = ( 1 << 17 ) ; // For tracking the movements of players
2022-09-22 04:45:17 +00:00
if ( null != self . playerRichInfoDict ) {
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
if ( playerRichInfo . node . parent ) {
playerRichInfo . node . parent . removeChild ( playerRichInfo . node ) ;
2022-09-20 15:50:01 +00:00
}
2022-09-22 04:45:17 +00:00
} ) ;
2022-10-02 16:22:05 +00:00
}
2022-09-22 04:45:17 +00:00
self . playerRichInfoDict = new Map ( ) ;
2022-09-20 15:50:01 +00:00
// Clearing previous info of all players. [ENDS]
self . renderFrameId = 0 ; // After battle started
2022-09-24 04:01:50 +00:00
self . lastAllConfirmedRenderFrameId = - 1 ;
self . lastAllConfirmedInputFrameId = - 1 ;
2022-09-20 15:50:01 +00:00
self . lastUpsyncInputFrameId = - 1 ;
2022-10-02 16:22:05 +00:00
self . chaserRenderFrameId = - 1 ; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
2022-09-20 15:50:01 +00:00
2022-09-24 04:01:50 +00:00
self . recentRenderCache = new RingBuffer ( 1024 ) ;
2022-09-20 15:50:01 +00:00
self . selfPlayerInfo = null ; // This field is kept for distinguishing "self" and "others".
2022-10-02 16:22:05 +00:00
self . recentInputCache = new RingBuffer ( 1024 ) ;
2022-09-23 08:42:44 +00:00
self . latestCollisionSys = new collisions . Collisions ( ) ;
self . chaserCollisionSys = new collisions . Collisions ( ) ;
2022-09-24 04:01:50 +00: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-02 16:22:05 +00:00
2022-09-20 15:50:01 +00: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 15:02:39 +00:00
window . handleClientSessionError = function ( ) {
console . warn ( '+++++++ Common handleClientSessionError()' ) ;
2022-09-20 15:50:01 +00:00
2022-10-04 03:24:47 +00:00
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
console . log ( "Battled ended by settlement" ) ;
2022-09-20 15:50:01 +00:00
} else {
2022-10-04 03:24:47 +00:00
console . warn ( "Connection lost, going back to login page" ) ;
2022-09-20 15:50:01 +00:00
window . clearLocalStorageAndBackToLoginScene ( true ) ;
}
} ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
2022-09-23 08:42:44 +00:00
cc . director . getCollisionManager ( ) . enabled = false ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 15:50:01 +00:00
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
} ;
2022-10-02 16:22:05 +00:00
resultPanelScriptIns . onCloseDelegate = ( ) => { } ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
self . inputDelayFrames = parsedBattleColliderInfo . inputDelayFrames ;
2022-10-02 03:33:40 +00:00
self . inputScaleFrames = parsedBattleColliderInfo . inputScaleFrames ;
self . inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo . inputFrameUpsyncDelayTolerance ;
2022-10-10 06:33:04 +00:00
self . battleDurationNanos = parsedBattleColliderInfo . battleDurationNanos ;
2022-10-10 04:17:23 +00:00
self . rollbackEstimatedDt = parsedBattleColliderInfo . rollbackEstimatedDt ;
2022-10-10 06:33:04 +00:00
self . rollbackEstimatedDtMillis = parsedBattleColliderInfo . rollbackEstimatedDtMillis ;
self . rollbackEstimatedDtNanos = parsedBattleColliderInfo . rollbackEstimatedDtNanos ;
2022-10-02 16:22:05 +00:00
self . rollbackEstimatedDtToleranceMillis = self . rollbackEstimatedDtMillis / 1000.0 ;
2022-10-02 03:33:40 +00:00
self . maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo . maxChasingRenderFramesPerUpdate ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
2022-09-20 15:50:01 +00:00
/ *
[ WARNING ]
- The order of the following statements is important , because we should have finished "_resetCurrentMatch" before the first "RoomDownsyncFrame" .
2022-09-24 04:01:50 +00:00
- It ' s important to assign new "tmxAsset" before "extractBoundaryObjects" , to ensure that the correct tilesets are used .
2022-09-20 15:50:01 +00:00
- To ensure clearance , put destruction of the "cc.TiledMap" component preceding that of "mapNode.destroyAllChildren()" .
* /
tiledMapIns . tmxAsset = null ;
mapNode . removeAllChildren ( ) ;
2022-10-02 16:22:05 +00:00
self . _resetCurrentMatch ( ) ;
2022-09-20 15:50:01 +00:00
tiledMapIns . tmxAsset = tmxAsset ;
const newMapSize = tiledMapIns . getMapSize ( ) ;
const newTileSize = tiledMapIns . getTileSize ( ) ;
2022-10-02 16:22:05 +00:00
self . node . setContentSize ( newMapSize . width * newTileSize . width , newMapSize . height * newTileSize . height ) ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
singleImageLayer . node . opacity = 0 ;
2022-09-20 15:50:01 +00:00
}
2022-09-24 04:01:50 +00:00
let barrierIdCounter = 0 ;
2022-09-20 15:50:01 +00:00
const boundaryObjs = tileCollisionManager . extractBoundaryObjects ( self . node ) ;
2022-09-24 04:01:50 +00:00
for ( let boundaryObj of boundaryObjs . barriers ) {
2022-10-02 16:22:05 +00:00
const x0 = boundaryObj [ 0 ] . x ,
y0 = boundaryObj [ 0 ] . y ;
2022-09-24 04:01:50 +00: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-02 16:22:05 +00:00
pts . push ( [ boundaryObj [ i ] . x - x0 , boundaryObj [ i ] . y - y0 ] ) ;
}
2022-09-24 04:01:50 +00:00
const newBarrierLatest = self . latestCollisionSys . createPolygon ( x0 , y0 , pts ) ;
2022-10-25 01:52:38 +00:00
// console.log("Created barrier: ", newBarrierLatest);
2022-09-24 04:01:50 +00:00
const newBarrierChaser = self . chaserCollisionSys . createPolygon ( x0 , y0 , pts ) ;
++ barrierIdCounter ;
2022-10-02 16:22:05 +00:00
const collisionBarrierIndex = ( self . collisionBarrierIndexPrefix + barrierIdCounter ) ;
self . latestCollisionSysMap . set ( collisionBarrierIndex , newBarrierLatest ) ;
self . chaserCollisionSysMap . set ( collisionBarrierIndex , newBarrierChaser ) ;
2022-09-24 04:01:50 +00:00
}
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
self . backgroundMapTiledIns . node . setContentSize ( newBackgroundMapSize . width * newBackgroundMapTileSize . width , newBackgroundMapSize . height * newBackgroundMapTileSize . height ) ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 15:50:01 +00:00
window . initPersistentSessionClient ( self . initAfterWSConnected , expectedRoomId ) ;
} else if ( null != boundRoomId ) {
self . disableGameRuleNode ( ) ;
2022-10-02 16:22:05 +00:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-10-04 03:24:47 +00:00
window . initPersistentSessionClient ( self . initAfterWSConnected , boundRoomId ) ;
2022-09-20 15:50:01 +00:00
} else {
self . showPopupInCanvas ( self . gameRuleNode ) ;
2022-10-02 16:22:05 +00:00
// Deliberately left blank. -- YFLu
2022-09-20 15:50:01 +00: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 03:42:19 +00:00
onRoomDownsyncFrame ( rdf ) {
2022-09-21 04:21:36 +00:00
// This function is also applicable to "re-joining".
2022-09-20 15:50:01 +00:00
const self = window . mapIns ;
2022-10-03 03:42:19 +00:00
if ( rdf . id < self . lastAllConfirmedRenderFrameId ) {
return window . RING _BUFF _FAILED _TO _SET ;
}
2022-10-02 16:22:05 +00: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 03:42:19 +00: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-02 16:22:05 +00: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 07:14:05 +00:00
self . renderFrameId = rdf . id ;
2022-10-02 16:22:05 +00:00
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2022-10-03 03:42:19 +00:00
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
2022-10-02 16:22:05 +00:00
self . lastAllConfirmedRenderFrameId = rdf . id ;
2022-10-01 07:14:05 +00:00
self . chaserRenderFrameId = rdf . id ;
2022-09-26 02:36:46 +00: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 04:21:36 +00:00
if ( null != rdf . countdownNanos ) {
self . countdownNanos = rdf . countdownNanos ;
}
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
self . battleState = ALL _BATTLE _STATES . IN _BATTLE ;
2022-09-24 04:01:50 +00:00
self . applyRoomDownsyncFrameDynamics ( rdf ) ;
2022-10-02 16:22:05 +00: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 02:38:38 +00:00
onInputFrameDownsyncBatch ( batch ) {
2022-10-02 16:22:05 +00: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 03:42:19 +00:00
if ( inputFrameDownsyncId < self . lastAllConfirmedInputFrameId ) {
continue ;
}
2022-10-16 02:38:38 +00:00
const localInputFrame = self . recentInputCache . getByFrameId ( inputFrameDownsyncId ) ;
if ( null != localInputFrame
&&
null == firstPredictedYetIncorrectInputFrameId
&&
! self . equalInputLists ( localInputFrame . inputList , inputFrameDownsync . inputList )
) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId ;
2022-10-02 16:22:05 +00:00
}
self . lastAllConfirmedInputFrameId = inputFrameDownsyncId ;
self . dumpToInputCache ( inputFrameDownsync ) ;
}
2022-10-16 02:38:38 +00: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-02 16:22:05 +00:00
2022-10-16 02:38:38 +00:00
if ( renderFrameId1 >= self . chaserRenderFrameId ) return ;
2022-10-02 16:22:05 +00:00
2022-10-16 02:38:38 +00:00
/ *
A typical case is as follows .
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
[ self . lastAllConfirmedRenderFrameId ] : 22
2022-10-02 16:22:05 +00:00
2022-10-16 02:38:38 +00:00
< renderFrameId1 > : 36
2022-10-02 16:22:05 +00:00
2022-10-16 02:38:38 +00: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 15:50:01 +00:00
} ,
2022-10-02 16:22:05 +00: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 15:50:01 +00:00
logBattleStats ( ) {
const self = this ;
let s = [ ] ;
2022-10-04 03:24:47 +00:00
s . push ( "Battle stats: renderFrameId=" + self . renderFrameId + ", lastAllConfirmedRenderFrameId=" + self . lastAllConfirmedRenderFrameId + ", lastUpsyncInputFrameId=" + self . lastUpsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self . lastAllConfirmedInputFrameId ) ;
2022-10-02 16:22:05 +00:00
2022-09-25 12:48:09 +00:00
for ( let i = self . recentInputCache . stFrameId ; i < self . recentInputCache . edFrameId ; ++ i ) {
2022-10-02 16:22:05 +00:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( i ) ;
2022-09-22 09:09:49 +00:00
s . push ( JSON . stringify ( inputFrameDownsync ) ) ;
2022-10-02 16:22:05 +00:00
}
2022-09-20 15:50:01 +00:00
console . log ( s . join ( '\n' ) ) ;
} ,
onBattleStopped ( ) {
const self = this ;
2022-10-02 16:22:05 +00:00
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState ) {
return ;
}
2022-09-20 15:50:01 +00: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 04:01:50 +00: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 15:50:01 +00:00
newPlayerNode . setPosition ( cc . v2 ( x , y ) ) ;
2022-09-24 04:01:50 +00:00
newPlayerNode . getComponent ( "SelfPlayer" ) . mapNode = self . node ;
const currentSelfColliderCircle = newPlayerNode . getComponent ( cc . CircleCollider ) ;
2022-10-21 14:39:08 +00: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 15:50:01 +00:00
2022-10-21 14:39:08 +00:00
const newPlayerColliderLatest = self . latestCollisionSys . createPolygon ( x0 , y0 , pts ) ;
const newPlayerColliderChaser = self . chaserCollisionSys . createPolygon ( x0 , y0 , pts ) ;
2022-09-24 04:01:50 +00:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
self . latestCollisionSysMap . set ( collisionPlayerIndex , newPlayerColliderLatest ) ;
self . chaserCollisionSysMap . set ( collisionPlayerIndex , newPlayerColliderChaser ) ;
safelyAddChild ( self . node , newPlayerNode ) ;
2022-09-20 15:50:01 +00:00
setLocalZOrder ( newPlayerNode , 5 ) ;
newPlayerNode . active = true ;
const playerScriptIns = newPlayerNode . getComponent ( "SelfPlayer" ) ;
2022-10-02 16:22:05 +00:00
playerScriptIns . scheduleNewDirection ( {
dx : 0 ,
dy : 0
} , true ) ;
2022-09-20 15:50:01 +00:00
return [ newPlayerNode , playerScriptIns ] ;
} ,
update ( dt ) {
const self = this ;
2022-09-25 12:48:09 +00:00
if ( ALL _BATTLE _STATES . IN _BATTLE == self . battleState ) {
2022-10-01 15:54:48 +00:00
const elapsedMillisSinceLastFrameIdTriggered = performance . now ( ) - self . lastRenderFrameIdTriggeredAt ;
2022-10-02 16:22:05 +00:00
if ( elapsedMillisSinceLastFrameIdTriggered < ( self . rollbackEstimatedDtMillis ) ) {
2022-10-01 15:54:48 +00:00
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
2022-10-01 12:45:38 +00:00
return ;
}
2022-09-25 12:48:09 +00:00
try {
2022-10-02 16:22:05 +00: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 04:01:50 +00:00
2022-10-02 16:22:05 +00: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 03:42:19 +00:00
if ( nextChaserRenderFrameId > self . renderFrameId ) {
2022-10-02 16:22:05 +00:00
nextChaserRenderFrameId = self . renderFrameId ;
2022-10-03 03:42:19 +00:00
}
2022-10-02 16:22:05 +00: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 02:38:38 +00: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-02 16:22:05 +00:00
const rdf = self . rollbackAndChase ( self . renderFrameId , self . renderFrameId + 1 , self . latestCollisionSys , self . latestCollisionSysMap ) ;
2022-10-10 06:33:04 +00:00
/ *
const nonTrivialChaseEnded = ( prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self . renderFrameId ) ;
if ( nonTrivialChaseEnded ) {
console . debug ( "Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId ) ;
}
* /
2022-10-02 16:22:05 +00:00
self . applyRoomDownsyncFrameDynamics ( rdf ) ;
let t3 = performance . now ( ) ;
2022-09-25 12:48:09 +00:00
} catch ( err ) {
console . error ( "Error during Map.update" , err ) ;
} finally {
// Update countdown
if ( null != self . countdownNanos ) {
2022-10-16 02:38:38 +00:00
self . countdownNanos = self . battleDurationNanos - self . renderFrameId * self . rollbackEstimatedDtNanos ;
2022-09-25 12:48:09 +00:00
if ( self . countdownNanos <= 0 ) {
self . onBattleStopped ( self . playerRichInfoDict ) ;
return ;
}
2022-09-20 15:50:01 +00:00
2022-09-25 12:48:09 +00: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 15:50:01 +00:00
}
2022-09-25 12:48:09 +00:00
++ self . renderFrameId ; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
2022-10-01 15:54:48 +00:00
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
return ;
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
data : {
intAuthToken : selfPlayerInfo . intAuthToken
} ,
2022-09-20 15:50:01 +00: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-02 16:22:05 +00:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 15:50:01 +00: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 03:24:47 +00:00
hideFindingPlayersGUI ( ) {
const self = this ;
if ( null == self . findingPlayerNode . parent ) return ;
self . findingPlayerNode . parent . removeChild ( self . findingPlayerNode ) ;
} ,
onBattleReadyToStart ( playerMetas ) {
2022-09-26 02:36:46 +00:00
console . log ( "Calling `onBattleReadyToStart` with:" , playerMetas ) ;
2022-09-20 15:50:01 +00:00
const self = this ;
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
2022-09-26 02:36:46 +00:00
findingPlayerScriptIns . hideExitButton ( ) ;
2022-09-20 15:50:01 +00:00
findingPlayerScriptIns . updatePlayersInfo ( playerMetas ) ;
2022-09-26 02:36:46 +00:00
2022-10-04 03:24:47 +00: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 15:50:01 +00:00
} ,
2022-09-24 04:01:50 +00:00
_createRoomDownsyncFrameLocally ( renderFrameId , collisionSys , collisionSysMap ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-10-02 16:22:05 +00:00
const prevRenderFrameId = renderFrameId - 1 ;
2022-09-29 04:21:04 +00:00
const inputFrameAppliedOnPrevRenderFrame = (
2022-10-02 16:22:05 +00:00
0 > prevRenderFrameId
?
2022-09-24 04:01:50 +00: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-02 16:22:05 +00:00
0 > speedRefRenderFrameId
?
2022-09-24 04:01:50 +00:00
null
:
2022-09-29 04:21:04 +00:00
self . recentRenderCache . getByFrameId ( speedRefRenderFrameId )
2022-09-24 04:01:50 +00:00
) ;
2022-09-20 15:50:01 +00:00
const rdf = {
2022-09-24 04:01:50 +00:00
id : renderFrameId ,
refFrameId : renderFrameId ,
2022-10-02 16:22:05 +00:00
players : { }
2022-09-20 15:50:01 +00:00
} ;
2022-09-22 04:45:17 +00:00
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-02 16:22:05 +00:00
const joinIndex = playerRichInfo . joinIndex ;
2022-09-24 04:01:50 +00:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-10-21 14:39:08 +00:00
const currentSelfColliderCircle = playerRichInfo . node . getComponent ( cc . CircleCollider ) ;
const r = currentSelfColliderCircle . radius ;
2022-09-20 15:50:01 +00:00
rdf . players [ playerRichInfo . id ] = {
id : playerRichInfo . id ,
2022-10-21 14:39:08 +00: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-02 16:22:05 +00:00
dir : self . ctrl . decodeDirection ( null == inputFrameAppliedOnPrevRenderFrame ? 0 : inputFrameAppliedOnPrevRenderFrame . inputList [ joinIndex - 1 ] ) ,
2022-09-24 04:01:50 +00:00
speed : ( null == speedRefRenderFrame ? playerRichInfo . speed : speedRefRenderFrame . players [ playerRichInfo . id ] . speed ) ,
2022-10-02 16:22:05 +00:00
joinIndex : joinIndex
2022-09-20 15:50:01 +00:00
} ;
2022-09-22 04:45:17 +00:00
} ) ;
2022-09-24 04:01:50 +00:00
if (
2022-10-02 16:22:05 +00:00
null != inputFrameAppliedOnPrevRenderFrame && self . _allConfirmed ( inputFrameAppliedOnPrevRenderFrame . confirmedList )
&&
2022-09-24 04:01:50 +00:00
self . lastAllConfirmedRenderFrameId >= prevRenderFrameId
&&
rdf . id > self . lastAllConfirmedRenderFrameId
) {
2022-10-02 16:22:05 +00:00
self . lastAllConfirmedRenderFrameId = rdf . id ;
2022-10-02 08:19:54 +00:00
self . chaserRenderFrameId = rdf . id ; // it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId"
2022-09-24 04:01:50 +00:00
}
2022-10-02 16:22:05 +00:00
self . dumpToRenderCache ( rdf ) ;
2022-09-21 09:22:34 +00:00
return rdf ;
2022-09-20 15:50:01 +00:00
} ,
2022-09-24 04:01:50 +00:00
applyRoomDownsyncFrameDynamics ( rdf ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-09-22 04:45:17 +00:00
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-09-20 15:50:01 +00:00
const immediatePlayerInfo = rdf . players [ playerId ] ;
2022-10-16 02:38:38 +00: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 06:33:04 +00: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-02 16:22:05 +00:00
playerRichInfo . scriptIns . updateSpeed ( immediatePlayerInfo . speed ) ;
2022-09-22 04:45:17 +00:00
} ) ;
2022-10-02 16:22:05 +00:00
} ,
2022-09-20 15:50:01 +00:00
2022-09-24 04:01:50 +00:00
getCachedInputFrameDownsyncWithPrediction ( inputFrameId ) {
2022-09-22 04:45:17 +00:00
const self = this ;
2022-09-24 04:01:50 +00:00
let inputFrameDownsync = self . recentInputCache . getByFrameId ( inputFrameId ) ;
2022-09-25 12:48:09 +00:00
if ( null != inputFrameDownsync && - 1 != self . lastAllConfirmedInputFrameId && inputFrameId > self . lastAllConfirmedInputFrameId ) {
2022-10-02 16:22:05 +00:00
const lastAllConfirmedInputFrame = self . recentInputCache . getByFrameId ( self . lastAllConfirmedInputFrameId ) ;
2022-09-22 04:45:17 +00:00
for ( let i = 0 ; i < inputFrameDownsync . inputList . length ; ++ i ) {
2022-10-02 16:22:05 +00:00
if ( i == self . selfPlayerInfo . joinIndex - 1 ) continue ;
inputFrameDownsync . inputList [ i ] = lastAllConfirmedInputFrame . inputList [ i ] ;
2022-09-22 04:45:17 +00:00
}
2022-10-02 16:22:05 +00:00
}
2022-09-22 04:45:17 +00:00
return inputFrameDownsync ;
} ,
2022-09-25 15:21:12 +00:00
rollbackAndChase ( renderFrameIdSt , renderFrameIdEd , collisionSys , collisionSysMap ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-10-03 03:42:19 +00:00
let latestRdf = self . recentRenderCache . getByFrameId ( renderFrameIdSt ) ; // typed "RoomDownsyncFrame"
if ( null == latestRdf ) {
2022-10-02 16:22:05 +00: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 02:36:46 +00:00
}
2022-10-03 03:42:19 +00:00
if ( renderFrameIdSt >= renderFrameIdEd ) {
return latestRdf ;
}
2022-09-24 04:01:50 +00:00
/ *
2022-10-03 03:42:19 +00: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 04:01:50 +00:00
* /
2022-09-22 04:45:17 +00:00
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-02 16:22:05 +00:00
const joinIndex = playerRichInfo . joinIndex ;
2022-09-24 04:01:50 +00:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-10-03 03:42:19 +00:00
const player = latestRdf . players [ playerId ] ;
2022-10-21 14:39:08 +00: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 04:45:17 +00:00
} ) ;
2022-09-21 15:59:05 +00:00
2022-09-24 04:01:50 +00:00
/ *
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" .
* /
2022-09-25 12:48:09 +00:00
for ( let i = renderFrameIdSt ; i < renderFrameIdEd ; ++ i ) {
2022-09-24 04:01:50 +00:00
const renderFrame = self . recentRenderCache . getByFrameId ( i ) ; // typed "RoomDownsyncFrame"
const j = self . _convertToInputFrameId ( i , self . inputDelayFrames ) ;
2022-10-02 16:22:05 +00: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 03:42:19 +00:00
// [WARNING] Traverse in the order of joinIndices to guarantee determinism.
2022-09-29 04:21:04 +00:00
for ( let j in self . playerRichInfoArr ) {
2022-10-02 16:22:05 +00:00
const joinIndex = parseInt ( j ) + 1 ;
const playerId = self . playerRichInfoArr [ j ] . id ;
2022-09-24 04:01:50 +00:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
const player = renderFrame . players [ playerId ] ;
2022-10-02 16:22:05 +00:00
const encodedInput = inputList [ joinIndex - 1 ] ;
2022-09-25 12:48:09 +00:00
const decodedInput = self . ctrl . decodeDirection ( encodedInput ) ;
2022-10-02 16:22:05 +00:00
const baseChange = player . speed * self . rollbackEstimatedDt * decodedInput . speedFactor ;
playerCollider . x += baseChange * decodedInput . dx ;
playerCollider . y += baseChange * decodedInput . dy ;
2022-09-29 04:21:04 +00:00
}
2022-09-24 04:01:50 +00:00
collisionSys . update ( ) ;
2022-09-26 02:36:46 +00:00
const result = collisionSys . createResult ( ) ; // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle?
2022-10-02 16:22:05 +00:00
2022-09-26 03:16:18 +00:00
for ( let i in self . playerRichInfoArr ) {
2022-10-02 16:22:05 +00:00
const joinIndex = parseInt ( i ) + 1 ;
2022-09-24 04:01:50 +00: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 03:16:18 +00:00
}
2022-10-03 03:42:19 +00:00
2022-10-04 03:24:47 +00:00
latestRdf = self . _createRoomDownsyncFrameLocally ( i + 1 , collisionSys , collisionSysMap ) ;
2022-09-21 15:59:05 +00:00
}
2022-10-02 16:22:05 +00:00
2022-10-03 03:42:19 +00:00
return latestRdf ;
2022-10-02 16:22:05 +00:00
} ,
2022-09-20 15:50:01 +00:00
_initPlayerRichInfoDict ( players , playerMetas ) {
const self = this ;
for ( let k in players ) {
const playerId = parseInt ( k ) ;
2022-09-22 04:45:17 +00:00
if ( self . playerRichInfoDict . has ( playerId ) ) continue ; // Skip already put keys
2022-09-20 15:50:01 +00:00
const immediatePlayerInfo = players [ playerId ] ;
const immediatePlayerMeta = playerMetas [ playerId ] ;
const nodeAndScriptIns = self . spawnPlayerNode ( immediatePlayerInfo . joinIndex , immediatePlayerInfo . x , immediatePlayerInfo . y ) ;
2022-10-02 16:22:05 +00:00
self . playerRichInfoDict . set ( playerId , immediatePlayerInfo ) ;
2022-09-20 15:50:01 +00:00
2022-09-22 04:45:17 +00:00
Object . assign ( self . playerRichInfoDict . get ( playerId ) , {
2022-09-20 15:50:01 +00:00
node : nodeAndScriptIns [ 0 ] ,
scriptIns : nodeAndScriptIns [ 1 ]
} ) ;
if ( self . selfPlayerInfo . id == playerId ) {
2022-10-02 16:22:05 +00:00
self . selfPlayerInfo = Object . assign ( self . selfPlayerInfo , immediatePlayerInfo ) ;
2022-09-20 15:50:01 +00:00
nodeAndScriptIns [ 1 ] . showArrowTipNode ( ) ;
}
}
2022-09-26 03:16:18 +00:00
self . playerRichInfoArr = new Array ( self . playerRichInfoDict . size ) ;
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-02 16:22:05 +00:00
self . playerRichInfoArr [ playerRichInfo . joinIndex - 1 ] = playerRichInfo ;
} ) ;
2022-09-20 15:50:01 +00:00
} ,
_stringifyRecentInputCache ( usefullOutput ) {
2022-09-26 02:36:46 +00:00
const self = this ;
2022-09-20 15:50:01 +00:00
if ( true == usefullOutput ) {
2022-09-22 09:09:49 +00:00
let s = [ ] ;
2022-09-26 02:36:46 +00:00
for ( let i = self . recentInputCache . stFrameId ; i < self . recentInputCache . edFrameId ; ++ i ) {
s . push ( JSON . stringify ( self . recentInputCache . getByFrameId ( i ) ) ) ;
}
2022-09-22 09:09:49 +00:00
return s . join ( '\n' ) ;
2022-09-20 15:50:01 +00:00
}
2022-09-26 02:36:46 +00:00
return "[stInputFrameId=" + self . recentInputCache . stFrameId + ", edInputFrameId=" + self . recentInputCache . edFrameId + ")" ;
2022-09-20 15:50:01 +00:00
} ,
2022-09-21 09:22:34 +00:00
2022-09-26 02:36:46 +00:00
_stringifyRecentRenderCache ( usefullOutput ) {
2022-09-21 09:22:34 +00:00
const self = this ;
2022-09-26 02:36:46 +00: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 09:22:34 +00:00
}
2022-09-26 02:36:46 +00:00
return s . join ( '\n' ) ;
}
return "[stRenderFrameId=" + self . recentRenderCache . stFrameId + ", edRenderFrameId=" + self . recentRenderCache . edFrameId + ")" ;
2022-09-21 09:22:34 +00:00
} ,
2022-09-26 02:36:46 +00:00
2022-09-20 15:50:01 +00:00
} ) ;