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
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 = {
BATTLE _READY _TO _START : - 99 ,
PLAYER _ADDED _AND _ACKED : - 98 ,
PLAYER _READDED _AND _ACKED : - 97 ,
} ;
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 ,
} ,
treasurePrefab : {
type : cc . Prefab ,
default : null ,
} ,
trapPrefab : {
type : cc . Prefab ,
default : null ,
} ,
speedShoePrefab : {
type : cc . Prefab ,
default : null ,
} ,
polygonBoundaryBarrierPrefab : {
type : cc . Prefab ,
default : null ,
} ,
polygonBoundaryShelterPrefab : {
type : cc . Prefab ,
default : null ,
} ,
polygonBoundaryShelterZReducerPrefab : {
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
} ,
trapBulletPrefab : {
type : cc . Prefab ,
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
} ,
guardTowerPrefab : {
type : cc . Prefab ,
default : null
} ,
forceBigEndianFloatingNumDecoding : {
default : false ,
} ,
backgroundMapTiledIns : {
type : cc . TiledMap ,
default : null
} ,
2022-09-21 09:22:34 +00:00
rollbackEstimatedDt : {
type : cc . Float ,
default : 1.0 / 60
} ,
2022-09-20 15:50:01 +00:00
} ,
_inputFrameIdDebuggable ( inputFrameId ) {
return ( 0 == inputFrameId % 10 ) ;
} ,
_dumpToFullFrameCache : function ( fullFrame ) {
const self = this ;
while ( self . recentFrameCacheCurrentSize >= self . recentFrameCacheMaxCount ) {
const toDelFrameId = Object . keys ( self . recentFrameCache ) [ 0 ] ;
delete self . recentFrameCache [ toDelFrameId ] ;
-- self . recentFrameCacheCurrentSize ;
}
self . recentFrameCache [ fullFrame . id ] = fullFrame ;
++ self . recentFrameCacheCurrentSize ;
} ,
_dumpToInputCache : function ( inputFrameDownsync ) {
const self = this ;
while ( self . recentInputCacheCurrentSize >= self . recentInputCacheMaxCount ) {
const toDelFrameId = Object . keys ( self . recentInputCache ) [ 0 ] ;
// console.log("Deleting toDelFrameId=", toDelFrameId, " from recentInputCache");
delete self . recentInputCache [ toDelFrameId ] ;
-- self . recentInputCacheCurrentSize ;
}
self . recentInputCache [ inputFrameDownsync . inputFrameId ] = inputFrameDownsync ;
++ self . recentInputCacheCurrentSize ;
} ,
_convertToInputFrameId ( renderFrameId , inputDelayFrames ) {
if ( renderFrameId < inputDelayFrames ) return 0 ;
const inputScaleFrames = 2 ;
return ( ( renderFrameId - inputDelayFrames ) >> inputScaleFrames ) ;
} ,
_convertToRenderFrameId ( inputFrameId , inputDelayFrames ) {
const inputScaleFrames = 2 ;
return ( ( inputFrameId << inputScaleFrames ) + inputDelayFrames ) ;
} ,
_shouldGenerateInputFrameUpsync ( renderFrameId ) {
return ( ( renderFrameId & 3 ) == 0 ) ; // 3 is 0x0011
} ,
_generateInputFrameUpsync ( inputFrameId ) {
const instance = this ;
if (
null == instance . ctrl ||
null == instance . selfPlayerInfo
) {
return [ null , null ] ;
}
const discreteDir = instance . ctrl . getDiscretizedDirection ( ) ;
let prefabbedInputList = null ;
let selfPlayerLastInputFrameInput = 0 ;
if ( 0 == instance . lastLocalInputFrameId ) {
prefabbedInputList = new Array ( Object . keys ( instance . playersNode ) . length ) . fill ( 0 ) ;
} else {
if ( null == instance . recentInputCache || null == instance . recentInputCache [ instance . lastLocalInputFrameId - 1 ] ) {
console . warn ( "_generateInputFrameUpsync: recentInputCache is NOT having inputFrameId=" , instance . lastLocalInputFrameId - 1 , "; recentInputCache=" , instance . _stringifyRecentInputCache ( false ) ) ;
prefabbedInputList = new Array ( Object . keys ( instance . playersNode ) . length ) . fill ( 0 ) ;
} else {
prefabbedInputList = Array . from ( instance . recentInputCache [ instance . lastLocalInputFrameId - 1 ] . inputList ) ;
selfPlayerLastInputFrameInput = prefabbedInputList [ ( instance . selfPlayerInfo . joinIndex - 1 ) ] ; // it's an integer, thus making a copy here, not impacted by later assignments
}
}
prefabbedInputList [ ( instance . selfPlayerInfo . joinIndex - 1 ) ] = discreteDir . encodedIdx ;
const prefabbedInputFrameDownsync = {
inputFrameId : inputFrameId ,
inputList : prefabbedInputList ,
confirmedList : ( 1 << ( instance . selfPlayerInfo . joinIndex - 1 ) )
} ;
instance . _dumpToInputCache ( prefabbedInputFrameDownsync ) ; // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
return [ selfPlayerLastInputFrameInput , discreteDir . encodedIdx ] ;
} ,
_shouldSendInputFrameUpsyncBatch ( prevSelfInput , currSelfInput , lastUpsyncInputFrameId , currInputFrameId ) {
/ *
For a 2 - player - battle , this "shouldUpsyncForEarlyAllConfirmedOnServer" 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 .
* /
if ( null == currSelfInput ) return false ;
const shouldUpsyncForEarlyAllConfirmedOnServer = ( currInputFrameId - lastUpsyncInputFrameId >= this . inputFrameUpsyncDelayTolerance ) ;
return shouldUpsyncForEarlyAllConfirmedOnServer || ( prevSelfInput != currSelfInput ) ;
} ,
_sendInputFrameUpsyncBatch ( inputFrameId ) {
// [WARNING] Why not just send the latest input? Because different player would have a different "inputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"!
const instance = this ;
let inputFrameUpsyncBatch = [ ] ;
for ( let i = instance . lastUpsyncInputFrameId + 1 ; i <= inputFrameId ; ++ i ) {
const inputFrameDownsync = instance . recentInputCache [ i ] ;
if ( null == inputFrameDownsync ) {
console . warn ( "_sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=" , i , "; recentInputCache=" , JSON . stringify ( instance . recentInputCache ) ) ;
} else {
const inputFrameUpsync = {
inputFrameId : i ,
encodedDir : inputFrameDownsync . inputList [ instance . selfPlayerInfo . joinIndex - 1 ] ,
} ;
inputFrameUpsyncBatch . push ( inputFrameUpsync ) ;
}
}
const reqData = window . WsReq . encode ( {
msgId : Date . now ( ) ,
playerId : instance . selfPlayerInfo . id ,
act : window . UPSYNC _MSG _ACT _PLAYER _CMD ,
joinIndex : instance . selfPlayerInfo . joinIndex ,
ackingFrameId : instance . lastRoomDownsyncFrameId ,
ackingInputFrameId : instance . lastDownsyncInputFrameId ,
inputFrameUpsyncBatch : inputFrameUpsyncBatch ,
} ) . finish ( ) ;
window . sendSafely ( reqData ) ;
instance . lastUpsyncInputFrameId = inputFrameId ;
} ,
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 . handleRoomDownsyncFrame ) {
window . handleRoomDownsyncFrame = null ;
}
if ( null != window . handleInputFrameDownsyncBatch ) {
window . handleInputFrameDownsyncBatch = null ;
}
if ( null != window . handleBattleColliderInfo ) {
window . handleBattleColliderInfo = null ;
}
if ( null != window . handleClientSessionCloseOrError ) {
window . handleClientSessionCloseOrError = null ;
}
if ( self . inputControlTimer ) {
clearInterval ( self . inputControlTimer ) ;
}
} ,
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]
for ( let joinIndex in self . playersNode ) {
const node = self . playersNode [ joinIndex ] ;
if ( node . parent ) {
node . parent . removeChild ( node ) ;
}
}
self . playerRichInfoDict = { } ;
// Clearing previous info of all players. [ENDS]
self . lastRoomDownsyncFrameId = 0 ;
self . renderFrameId = 0 ; // After battle started
self . inputDelayFrames = 4 ;
self . inputScaleFrames = 2 ;
self . lastLocalInputFrameId = 0 ;
self . lastDownsyncInputFrameId = - 1 ;
self . lastAllConfirmedInputFrameId = - 1 ;
self . lastUpsyncInputFrameId = - 1 ;
self . inputFrameUpsyncDelayTolerance = 1 ;
self . recentFrameCache = { } ;
self . r ecentFrameCacheCurrentSize = 0 ;
2022-09-21 09:22:34 +00:00
self . recentFrameCacheMaxCount = 1024 ;
2022-09-20 15:50:01 +00:00
self . selfPlayerInfo = null ; // This field is kept for distinguishing "self" and "others".
self . recentInputCache = { } ; // TODO: Use a ringbuf instead
self . recentInputCacheCurrentSize = 0 ;
self . recentInputCacheMaxCount = 1024 ;
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()" ) ;
window . handleClientSessionCloseOrError = function ( ) {
console . warn ( '+++++++ Common handleClientSessionCloseOrError()' ) ;
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) { //如果是游戏时间结束引起的断连
console . log ( "游戏结束引起的断连, 不需要回到登录页面" ) ;
} else {
console . warn ( "意外断连,即将回到登录页面" ) ;
window . clearLocalStorageAndBackToLoginScene ( true ) ;
}
} ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
cc . director . getCollisionManager ( ) . enabled = true ;
cc . director . getCollisionManager ( ) . enabledDebugDraw = CC _DEBUG ;
// 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 = ( ) => {
self . battleState = ALL _BATTLE _STATES . WAITING ;
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
} ;
resultPanelScriptIns . onCloseDelegate = ( ) => {
} ;
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 ( ) ) ;
self . playersNode = { } ;
const player1Node = cc . instantiate ( self . player1Prefab ) ;
const player2Node = cc . instantiate ( self . player2Prefab ) ;
Object . assign ( self . playersNode , {
1 : player1Node
} ) ;
Object . assign ( self . playersNode , {
2 : player2Node
} ) ;
/** Init required prefab ended. */
self . clientUpsyncFps = 60 ;
window . handleBattleColliderInfo = function ( parsedBattleColliderInfo ) {
console . log ( parsedBattleColliderInfo ) ;
self . battleColliderInfo = parsedBattleColliderInfo ;
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 ;
}
/ *
[ WARNING ]
- The order of the following statements is important , because we should have finished "_resetCurrentMatch" before the first "RoomDownsyncFrame" .
- It ' s important to assign new "tmxAsset" before "extractBoundaryObjects => initMapNodeByTiledBoundaries" , to ensure that the correct tilesets are used .
- To ensure clearance , put destruction of the "cc.TiledMap" component preceding that of "mapNode.destroyAllChildren()" .
-- YFLu , 2019 - 09 - 07
* /
tiledMapIns . tmxAsset = null ;
mapNode . removeAllChildren ( ) ;
self . _resetCurrentMatch ( ) ;
tiledMapIns . tmxAsset = tmxAsset ;
const newMapSize = tiledMapIns . getMapSize ( ) ;
const newTileSize = tiledMapIns . getTileSize ( ) ;
self . node . setContentSize ( newMapSize . width * newTileSize . width , newMapSize . height * newTileSize . height ) ;
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 ) {
singleImageLayer . node . opacity = 0 ;
}
const boundaryObjs = tileCollisionManager . extractBoundaryObjects ( self . node ) ;
tileCollisionManager . initMapNodeByTiledBoundaries ( self , mapNode , boundaryObjs ) ;
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 ( ) ;
self . backgroundMapTiledIns . node . setContentSize ( newBackgroundMapSize . width * newBackgroundMapTileSize . width , newBackgroundMapSize . height * newBackgroundMapTileSize . height ) ;
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 ;
let findingPlayerScriptIns = null ;
window . handleRoomDownsyncFrame = function ( rdf ) {
if ( ALL _BATTLE _STATES . WAITING != self . battleState
&& ALL _BATTLE _STATES . IN _BATTLE != self . battleState
&& ALL _BATTLE _STATES . IN _SETTLEMENT != self . battleState ) {
return ;
}
// Right upon establishment of the "PersistentSessionClient", we should receive an initial signal "BattleColliderInfo" earlier than any "RoomDownsyncFrame" containing "PlayerMeta" data.
const refFrameId = rdf . refFrameId ;
switch ( refFrameId ) {
case window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _READY _TO _START :
// 显示倒计时
self . playersMatched ( rdf . playerMetas ) ;
// 隐藏返回按钮
findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . hideExitButton ( ) ;
return ;
case window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . PLAYER _ADDED _AND _ACKED :
self . _initPlayerRichInfoDict ( rdf . players , rdf . playerMetas ) ;
// 显示匹配玩家
findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
if ( ! self . findingPlayerNode . parent ) {
self . showPopupInCanvas ( self . findingPlayerNode ) ;
}
findingPlayerScriptIns . updatePlayersInfo ( rdf . playerMetas ) ;
return ;
case window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . PLAYER _READDED _AND _ACKED :
self . _initPlayerRichInfoDict ( rdf . players , rdf . playerMetas ) ;
// In this case, we're definitely in an active battle, thus the "self.findingPlayerNode" should be hidden if being presented.
if ( self . findingPlayerNode && self . findingPlayerNode . parent ) {
self . findingPlayerNode . parent . removeChild ( self . findingPlayerNode ) ;
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
if ( self . playersInfoNode ) {
for ( let playerId in rdf . playerMetas ) {
const playerMeta = rdf . playerMetas [ playerId ] ;
const playersInfoScriptIns = self . playersInfoNode . getComponent ( "PlayersInfo" ) ;
playersInfoScriptIns . updateData ( playerMeta ) ;
}
}
}
return ;
}
const frameId = rdf . id ;
if ( 0 == self . lastRoomDownsyncFrameId ) {
if ( 1 == frameId ) {
// No need to prompt upon rejoined.
self . popupSimplePressToGo ( i18n . t ( "gameTip.start" ) ) ;
}
2022-09-21 04:21:36 +00:00
self . onBattleStarted ( rdf ) ;
2022-09-20 15:50:01 +00:00
}
self . _dumpToFullFrameCache ( rdf ) ;
self . lastRoomDownsyncFrameId = frameId ;
// TODO: Inject a NetworkDoctor as introduced in https://app.yinxiang.com/shard/s61/nl/13267014/5c575124-01db-419b-9c02-ec81f78c6ddc/.
} ;
window . handleInputFrameDownsyncBatch = function ( batch ) {
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState
&& ALL _BATTLE _STATES . IN _SETTLEMENT != self . battleState ) {
return ;
}
2022-09-21 09:22:34 +00:00
// console.log("Received inputFrameDownsyncBatch=", batch, ", now correspondingLastLocalInputFrame=", self.recentInputCache[batch[batch.length-1].inputFrameId]);
let firstPredictedYetIncorrectInputFrameId = null ;
let firstPredictedYetIncorrectInputFrameJoinIndex = null ;
2022-09-20 15:50:01 +00:00
for ( let k in batch ) {
const inputFrameDownsync = batch [ k ] ;
const inputFrameDownsyncId = inputFrameDownsync . inputFrameId ;
const localInputFrame = self . recentInputCache [ inputFrameDownsyncId ] ;
if ( null == localInputFrame ) {
console . warn ( "handleInputFrameDownsyncBatch: recentInputCache is NOT having inputFrameDownsyncId=" , inputFrameDownsyncId , "; now recentInputCache=" , self . _stringifyRecentInputCache ( false ) ) ;
} else {
2022-09-21 09:22:34 +00:00
if ( null == firstPredictedYetIncorrectInputFrameId ) {
2022-09-20 15:50:01 +00:00
for ( let i in localInputFrame . inputList ) {
if ( localInputFrame . inputList [ i ] != inputFrameDownsync . inputList [ i ] ) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId ;
firstPredictedYetIncorrectInputFrameJoinIndex = ( parseInt ( i ) + 1 ) ;
break ;
}
}
}
}
self . _dumpToInputCache ( inputFrameDownsync ) ;
// [WARNING] Currently "lastDownsyncInputFrameId" and "lastAllConfirmedInputFrameId" are identical, but they (their definitions) are prone to changes in the future
self . lastDownsyncInputFrameId = inputFrameDownsyncId ;
self . lastAllConfirmedInputFrameId = inputFrameDownsyncId ;
}
2022-09-21 09:22:34 +00:00
if ( null != firstPredictedYetIncorrectInputFrameId ) {
2022-09-20 15:50:01 +00:00
const renderFrameId2 = self . renderFrameId ;
const inputFrameId2 = self . _convertToInputFrameId ( renderFrameId2 , self . inputDelayFrames ) ;
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId ;
const renderFrameId1 = self . _convertToRenderFrameId ( inputFrameId1 , self . inputDelayFrames ) ; // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
2022-09-21 09:22:34 +00:00
if ( renderFrameId1 < renderFrameId2 ) {
// No need to rollback when "renderFrameId1 == renderFrameId2", because the "delayedInputFrame for renderFrameId2" is not yet executed by now, it just went through "++self.renderFrameId" in "update(dt)" and js-runtime is mostly single-threaded in our programmable range.
2022-09-20 15:50:01 +00:00
console . warn ( "Mismatched input! inputFrameId1=" , inputFrameId1 , ", renderFrameId1=" , renderFrameId1 , ": at least 1 incorrect input predicted for joinIndex=" , firstPredictedYetIncorrectInputFrameJoinIndex , ", inputFrameId2 = " , inputFrameId2 , ", renderFrameId2=" , renderFrameId2 ) ;
2022-09-21 09:22:34 +00:00
self . _rollbackAndReplay ( inputFrameId1 , renderFrameId1 , inputFrameId2 , renderFrameId2 ) ;
2022-09-20 15:50:01 +00:00
} else {
console . log ( "Mismatched input yet no rollback needed. inputFrameId1=" , inputFrameId1 , ", renderFrameId1=" , renderFrameId1 , ": at least 1 incorrect input predicted for joinIndex=" , firstPredictedYetIncorrectInputFrameJoinIndex , ", inputFrameId2 = " , inputFrameId2 , ", renderFrameId2=" , renderFrameId2 ) ;
}
}
} ;
}
// 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.
self . battleState = ALL _BATTLE _STATES . WAITING ;
window . initPersistentSessionClient ( self . initAfterWSConnected , expectedRoomId ) ;
} else if ( null != boundRoomId ) {
self . disableGameRuleNode ( ) ;
self . battleState = ALL _BATTLE _STATES . WAITING ;
window . initPersistentSessionClient ( self . initAfterWSConnected , expectedRoomId ) ;
} else {
self . showPopupInCanvas ( self . gameRuleNode ) ;
// Deliberately left blank. -- YFLu
}
} ,
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-09-21 04:21:36 +00:00
onBattleStarted ( rdf ) {
// This function is also applicable to "re-joining".
const players = rdf . players ;
const playerMetas = rdf . playerMetas ;
2022-09-20 15:50:01 +00:00
console . log ( 'On battle started!' ) ;
const self = window . mapIns ;
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-09-21 04:21:36 +00:00
self . _applyRoomDownsyncFrameDynamics ( rdf ) ;
self . battleState = ALL _BATTLE _STATES . IN _BATTLE ; // Starts the increment of "self.renderFrameId" in "self.update(dt)"
2022-09-20 15:50:01 +00:00
} ,
logBattleStats ( ) {
const self = this ;
let s = [ ] ;
s . push ( "Battle stats: lastUpsyncInputFrameId=" + self . lastUpsyncInputFrameId + ", lastDownsyncInputFrameId=" + self . lastDownsyncInputFrameId + ", lastAllConfirmedInputFrameId=" + self . lastAllConfirmedInputFrameId + ", lastDownsyncInputFrameId=" + self . lastDownsyncInputFrameId ) ;
for ( let inputFrameDownsyncId in self . recentInputCache ) {
const inputFrameDownsync = self . recentInputCache [ inputFrameDownsyncId ] ;
s . push ( JSON . stringify ( inputFrameDownsync ) ) ;
}
console . log ( s . join ( '\n' ) ) ;
} ,
onBattleStopped ( ) {
const self = this ;
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 ) {
const instance = this ;
const newPlayerNode = instance . playersNode [ joinIndex ] ;
newPlayerNode . setPosition ( cc . v2 ( x , y ) ) ;
newPlayerNode . getComponent ( "SelfPlayer" ) . mapNode = instance . node ;
safelyAddChild ( instance . node , newPlayerNode ) ;
setLocalZOrder ( newPlayerNode , 5 ) ;
newPlayerNode . active = true ;
const playerScriptIns = newPlayerNode . getComponent ( "SelfPlayer" ) ;
playerScriptIns . scheduleNewDirection ( { dx : 0 , dy : 0 } , true ) ;
return [ newPlayerNode , playerScriptIns ] ;
} ,
update ( dt ) {
const self = this ;
try {
let inputs = new Array ( Object . keys ( self . playersNode ) . length ) . fill ( {
dx : 0 ,
dy : 0
} ) ;
if ( ALL _BATTLE _STATES . IN _BATTLE == self . battleState ) {
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 ] ;
}
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 ) ;
}
const delayedInputFrameId = self . _convertToInputFrameId ( self . renderFrameId , self . inputDelayFrames ) ; // The "inputFrameId" to use at current "renderFrameId"
const delayedInputFrameDownsync = self . recentInputCache [ delayedInputFrameId ] ;
if ( null == delayedInputFrameDownsync ) {
console . warn ( "update(dt): recentInputCache is NOT having inputFrameId=" , delayedInputFrameId , "; recentInputCache=" , instance . _stringifyRecentInputCache ( false ) ) ;
} else {
self . _applyInputFrameDownsyncDynamics ( delayedInputFrameDownsync , false ) ;
}
2022-09-21 09:22:34 +00:00
const rdf = self . _createRoomDownsyncFrameLocally ( ) ;
self . _dumpToFullFrameCache ( rdf ) ;
/ *
if ( null != delayedInputFrameDownsync && null != delayedInputFrameDownsync . inputList && 0 < delayedInputFrameDownsync . inputList [ self . selfPlayerInfo . joinIndex - 1 ] ) {
console . log ( "My critical status: renderFrame=" , JSON . stringify ( rdf ) , ", delayedInputFrameDownsync=" , JSON . stringify ( delayedInputFrameDownsync ) ) ;
}
* /
++ self . renderFrameId ; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
2022-09-20 15:50:01 +00:00
}
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
const canvasParentNode = canvasNode . parent ;
if ( null != window . boundRoomId ) {
self . boundRoomIdLabel . string = window . boundRoomId ;
}
// update countdown
if ( null != self . countdownNanos ) {
2022-09-21 09:22:34 +00:00
self . countdownNanos -= self . rollbackEstimatedDt * 1000000000 ;
2022-09-20 15:50:01 +00:00
if ( self . countdownNanos <= 0 ) {
self . onBattleStopped ( self . playerRichInfoDict ) ;
return ;
}
const countdownSeconds = parseInt ( self . countdownNanos / 1000000000 ) ;
if ( isNaN ( countdownSeconds ) ) {
console . warn ( ` countdownSeconds is NaN for countdownNanos == ${ self . countdownNanos } . ` ) ;
}
self . countdownLabel . string = countdownSeconds ;
}
} catch ( err ) {
console . error ( "Error during Map.update" , err ) ;
}
if ( null != self . ctrl ) {
self . ctrl . justifyMapNodePosAndScale ( self . ctrl . linearSpeedBase , self . ctrl . zoomingSpeedBase ) ;
}
} ,
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 ( ) ;
return ;
}
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" ,
data : { intAuthToken : selfPlayerInfo . intAuthToken } ,
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 ;
self . battleState = ALL _BATTLE _STATES . WAITING ;
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 ) ;
} ,
playersMatched ( playerMetas ) {
console . log ( "Calling `playersMatched` with:" , playerMetas ) ;
const self = this ;
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . updatePlayersInfo ( playerMetas ) ;
window . setTimeout ( ( ) => {
if ( null != self . findingPlayerNode . parent ) {
self . findingPlayerNode . parent . removeChild ( self . findingPlayerNode ) ;
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
const playersInfoScriptIns = self . playersInfoNode . getComponent ( "PlayersInfo" ) ;
for ( let i in playerMetas ) {
const playerMeta = playerMetas [ i ] ;
playersInfoScriptIns . updateData ( playerMeta ) ;
}
}
const countDownScriptIns = self . countdownToBeginGameNode . getComponent ( "CountdownToBeginGame" ) ;
countDownScriptIns . setData ( ) ;
self . showPopupInCanvas ( self . countdownToBeginGameNode ) ;
return ;
} , 2000 ) ;
} ,
_createRoomDownsyncFrameLocally ( ) {
const self = this ;
const rdf = {
id : self . renderFrameId ,
refFrameId : self . renderFrameId ,
players : { } ,
countdownNanos : self . countdownNanos
} ;
for ( let playerId in self . playerRichInfoDict ) {
const playerRichInfo = self . playerRichInfoDict [ playerId ] ;
const joinIndex = playerRichInfo . joinIndex ;
const playerNode = playerRichInfo . node ;
const playerScriptIns = playerRichInfo . scriptIns ;
rdf . players [ playerRichInfo . id ] = {
id : playerRichInfo . id ,
x : playerNode . position . x ,
y : playerNode . position . y ,
dir : playerScriptIns . activeDirection ,
speed : playerScriptIns . speed ,
joinIndex : joinIndex
} ;
}
2022-09-21 09:22:34 +00:00
return rdf ;
2022-09-20 15:50:01 +00:00
} ,
_applyRoomDownsyncFrameDynamics ( rdf ) {
const self = this ;
for ( let playerId in self . playerRichInfoDict ) {
const playerRichInfo = self . playerRichInfoDict [ playerId ] ;
const immediatePlayerInfo = rdf . players [ playerId ] ;
playerRichInfo . node . setPosition ( immediatePlayerInfo . x , immediatePlayerInfo . y ) ;
playerRichInfo . scriptIns . scheduleNewDirection ( immediatePlayerInfo . dir , true ) ;
playerRichInfo . scriptIns . updateSpeed ( immediatePlayerInfo . speed ) ;
}
} ,
_applyInputFrameDownsyncDynamics ( inputFrameDownsync , invokeUpdateToo ) {
// This application DOESN'T use a "full physics engine", but only "collider detection" of "box2d", thus when resetting room state, there's no need of resetting "momentums".
const self = this ;
const inputs = inputFrameDownsync . inputList ;
// Update controlled player nodes
for ( let playerId in self . playerRichInfoDict ) {
const playerRichInfo = self . playerRichInfoDict [ playerId ] ;
const joinIndex = playerRichInfo . joinIndex ;
const playerScriptIns = playerRichInfo . scriptIns ;
const decodedInput = self . ctrl . decodeDirection ( inputs [ joinIndex - 1 ] ) ;
playerScriptIns . scheduleNewDirection ( decodedInput , true ) ;
if ( invokeUpdateToo ) {
playerScriptIns . update ( self . rollbackEstimatedDt ) ;
}
}
} ,
2022-09-21 09:22:34 +00:00
_rollbackAndReplay ( inputFrameId1 , renderFrameId1 , inputFrameId2 , renderFrameId2 ) {
2022-09-20 15:50:01 +00:00
const self = this ;
const rdf1 = self . recentFrameCache [ renderFrameId1 ] ;
if ( null == rdf1 ) {
const recentFrameCacheKeys = Object . keys ( self . recentFrameCache ) ;
2022-09-21 09:22:34 +00:00
console . error ( "renderFrameId1=" , renderFrameId1 , "doesn't exist in recentFrameCache " , self . _stringifyRecentFrameCache ( false ) , ": COULDN'T ROLLBACK!" ) ;
2022-09-20 15:50:01 +00:00
return ;
}
self . _applyRoomDownsyncFrameDynamics ( rdf1 ) ;
2022-09-21 09:22:34 +00:00
// DON'T apply inputFrameDownsync dynamics for exactly "renderFrameId2", see the comment around the invocation of "_rollbackAndReplay".
for ( let renderFrameId = renderFrameId1 ; renderFrameId < renderFrameId2 ; ++ renderFrameId ) {
2022-09-20 15:50:01 +00:00
const delayedInputFrameId = self . _convertToInputFrameId ( renderFrameId , self . inputDelayFrames ) ;
2022-09-21 09:22:34 +00:00
const delayedInputFrameDownsync = self . recentInputCache [ delayedInputFrameId ] ;
self . _applyInputFrameDownsyncDynamics ( delayedInputFrameDownsync , true ) ;
// console.log("_rollbackAndReplay, AFTER:", self._stringifyRollbackResult(renderFrameId, delayedInputFrameDownsync));
2022-09-20 15:50:01 +00:00
}
} ,
_initPlayerRichInfoDict ( players , playerMetas ) {
const self = this ;
for ( let k in players ) {
const playerId = parseInt ( k ) ;
if ( self . playerRichInfoDict . hasOwnProperty ( playerId ) ) continue ;
const immediatePlayerInfo = players [ playerId ] ;
const immediatePlayerMeta = playerMetas [ playerId ] ;
const nodeAndScriptIns = self . spawnPlayerNode ( immediatePlayerInfo . joinIndex , immediatePlayerInfo . x , immediatePlayerInfo . y ) ;
self . playerRichInfoDict [ playerId ] = immediatePlayerInfo ;
Object . assign ( self . playerRichInfoDict [ playerId ] , {
node : nodeAndScriptIns [ 0 ] ,
scriptIns : nodeAndScriptIns [ 1 ]
} ) ;
if ( self . selfPlayerInfo . id == playerId ) {
self . selfPlayerInfo = Object . assign ( self . selfPlayerInfo , immediatePlayerInfo ) ;
nodeAndScriptIns [ 1 ] . showArrowTipNode ( ) ;
}
}
} ,
_stringifyRecentFrameCache ( usefullOutput ) {
if ( true == usefullOutput ) {
2022-09-21 09:22:34 +00:00
let s = [ ] ;
for ( let renderFrameId in self . recentFrameCache ) {
const roomDownsyncFrame = self . recentFrameCache [ renderFrameId ] ;
s . push ( JSON . stringify ( roomDownsyncFrame ) ) ;
}
return s . join ( '\n' ) ;
2022-09-20 15:50:01 +00:00
}
2022-09-21 09:22:34 +00:00
const keys = Object . keys ( this . recentFrameCache ) ;
return "[stRenderFrameId=" + keys [ 0 ] + ", edRenderFrameId=" + keys [ keys . length - 1 ] + "]" ;
2022-09-20 15:50:01 +00:00
} ,
_stringifyRecentInputCache ( usefullOutput ) {
if ( true == usefullOutput ) {
return JSON . stringify ( this . recentInputCache ) ;
}
const keys = Object . keys ( this . recentInputCache ) ;
return "[stInputFrameId=" + keys [ 0 ] + ", edInputFrameId=" + keys [ keys . length - 1 ] + "]" ;
} ,
2022-09-21 09:22:34 +00:00
_stringifyRollbackResult ( renderFrameId , delayedInputFrameDownsync ) {
// Slightly different from "_createRoomDownsyncFrameLocally"
const self = this ;
const s = (
null == delayedInputFrameDownsync
?
{
renderFrameId : renderFrameId ,
players : { }
}
:
{
renderFrameId : renderFrameId ,
players : { } ,
delayedInputFrameDownsync : delayedInputFrameDownsync ,
}
) ;
let players = { } ;
for ( let playerId in self . playerRichInfoDict ) {
const playerRichInfo = self . playerRichInfoDict [ playerId ] ;
const joinIndex = playerRichInfo . joinIndex ;
const playerNode = playerRichInfo . node ;
const playerScriptIns = playerRichInfo . scriptIns ;
s . players [ playerRichInfo . id ] = {
id : playerRichInfo . id ,
x : playerNode . position . x ,
y : playerNode . position . y ,
} ;
}
return JSON . stringify ( s ) ;
} ,
2022-09-20 15:50:01 +00:00
} ) ;