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-24 04:01:50 +00:00
const RingBuffer = require ( './RingBuffer' ) ;
2023-01-21 14:53:41 +00:00
const NetworkDoctor = require ( './NetworkDoctor' ) ;
2023-01-11 10:09:18 +00:00
const PriorityQueue = require ( "./PriorityQueue" ) ;
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 = {
2022-12-20 08:01:44 +00:00
NONE : - 1 ,
2022-09-20 15:50:01 +00:00
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
} ;
2023-01-26 12:19:45 +00:00
window . onUdpMessage = ( args ) => {
const self = window . mapIns ;
2023-01-28 09:47:48 +00:00
const ui8Arr = args ;
2023-01-30 01:21:06 +00:00
//cc.log(`#1 Js called back by CPP: onUdpMessage: args=${args}, typeof(args)=${typeof (args)}, argslen=${args.length}, ui8Arr=${ui8Arr}`);
2023-01-28 02:52:52 +00:00
const req = window . pb . protos . WsReq . decode ( ui8Arr ) ;
if ( req ) {
2023-01-30 01:21:06 +00:00
//cc.log(`#2 Js called back by CPP for upsync: onUdpMessage: ${JSON.stringify(req)}`);
2023-01-28 02:52:52 +00:00
if ( req . act && window . UPSYNC _MSG _ACT _PLAYER _CMD == req . act ) {
2023-01-28 09:47:48 +00:00
let effCnt = 0 ;
2023-01-28 02:52:52 +00:00
const peerJoinIndex = req . joinIndex ;
2023-02-16 00:17:50 +00:00
if ( peerJoinIndex == self . selfPlayerInfo . joinIndex ) return ;
2023-01-28 02:52:52 +00:00
const batch = req . inputFrameUpsyncBatch ;
2023-02-01 09:43:15 +00:00
self . onPeerInputFrameUpsync ( peerJoinIndex , batch , true ) ;
2023-01-26 12:19:45 +00:00
}
}
} ;
2022-09-20 15:50:01 +00:00
cc . Class ( {
extends : cc . Component ,
properties : {
canvasNode : {
type : cc . Node ,
default : null ,
} ,
2022-11-19 12:58:07 +00:00
controlledCharacterPrefab : {
type : cc . Prefab ,
default : null ,
} ,
2023-01-11 10:09:18 +00:00
fireballPrefab : {
type : cc . Prefab ,
default : null ,
} ,
2022-09-20 15:50:01 +00:00
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
} ,
forceBigEndianFloatingNumDecoding : {
default : false ,
} ,
2022-10-04 03:24:47 +00:00
renderFrameIdLagTolerance : {
type : cc . Integer ,
default : 4 // implies (renderFrameIdLagTolerance >> inputScaleFrames) count of inputFrameIds
} ,
2023-02-13 02:34:56 +00:00
inputFrameFrontLabel : {
type : cc . Label ,
default : null
} ,
2023-01-21 14:53:41 +00:00
sendingQLabel : {
type : cc . Label ,
default : null
2022-10-10 06:33:04 +00:00
} ,
2023-01-21 14:53:41 +00:00
inputFrameDownsyncQLabel : {
type : cc . Label ,
default : null
} ,
peerInputFrameUpsyncQLabel : {
type : cc . Label ,
default : null
2022-11-23 14:11:28 +00:00
} ,
2023-01-21 14:53:41 +00:00
rollbackFramesLabel : {
type : cc . Label ,
default : null
2022-12-20 15:51:53 +00:00
} ,
2023-01-22 03:34:02 +00:00
skippedRenderFrameCntLabel : {
type : cc . Label ,
default : null
}
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-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
2023-01-31 23:27:10 +00:00
getOrPrefabInputFrameUpsync ( inputFrameId , canConfirmSelf ) {
2022-12-20 15:51:53 +00:00
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
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
) {
2022-11-29 13:32:18 +00:00
throw ` noDelayInputFrameId= ${ inputFrameId } couldn't be generated: recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
2022-09-20 15:50:01 +00:00
}
2022-12-01 03:35:56 +00:00
let previousSelfInput = null ,
currSelfInput = null ;
2023-02-16 00:17:50 +00:00
const joinIndex = self . selfPlayerInfo . joinIndex ;
2023-01-31 23:27:10 +00:00
const selfJoinIndexMask = ( 1 << ( joinIndex - 1 ) ) ;
2023-02-16 14:23:12 +00:00
const existingInputFrame = gopkgs . GetInputFrameDownsync ( self . recentInputCache , inputFrameId ) ;
const previousInputFrameDownsync = gopkgs . GetInputFrameDownsync ( self . recentInputCache , ( inputFrameId - 1 ) ) ;
previousSelfInput = ( null == previousInputFrameDownsync ? null : gopkgs . GetInput ( previousInputFrameDownsync , joinIndex - 1 ) ) ;
2023-01-31 23:27:10 +00:00
if (
null != existingInputFrame
&&
( true != canConfirmSelf )
) {
2022-12-20 15:51:53 +00:00
// This could happen upon either [type#1] or [type#2] forceConfirmation, where "refRenderFrame" is accompanied by some "inputFrameDownsyncs". The check here also guarantees that we don't override history
2023-01-17 04:07:41 +00:00
//console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`);
2023-02-16 14:23:12 +00:00
return [ previousSelfInput , gopkgs . GetInput ( existingInputFrame , joinIndex - 1 ) ] ;
2022-12-20 15:51:53 +00:00
}
2022-12-29 04:36:57 +00:00
const prefabbedInputList = new Array ( self . playerRichInfoDict . size ) . fill ( 0 ) ;
// the returned "gopkgs.NewInputFrameDownsync.InputList" is immutable, thus we can only modify the values in "prefabbedInputList"
2023-02-01 09:43:15 +00:00
for ( let k = 0 ; k < window . boundRoomCapacity ; ++ k ) {
2023-01-31 23:27:10 +00:00
if ( null != existingInputFrame ) {
// When "null != existingInputFrame", it implies that "true == canConfirmSelf" here, we just have to assign "prefabbedInputList[(joinIndex-1)]" specifically and copy all others
2023-02-16 14:23:12 +00:00
prefabbedInputList [ k ] = gopkgs . GetInput ( existingInputFrame , k ) ;
2023-01-31 23:27:10 +00:00
} else if ( self . lastIndividuallyConfirmedInputFrameId [ k ] <= inputFrameId ) {
prefabbedInputList [ k ] = self . lastIndividuallyConfirmedInputList [ k ] ;
// Don't predict "btnA & btnB"!
prefabbedInputList [ k ] = ( prefabbedInputList [ k ] & 15 ) ;
} else if ( null != previousInputFrameDownsync ) {
// When "self.lastIndividuallyConfirmedInputFrameId[k] > inputFrameId", don't use it to predict a historical input!
2023-02-16 14:23:12 +00:00
prefabbedInputList [ k ] = gopkgs . GetInput ( previousInputFrameDownsync , k ) ;
2023-01-31 23:27:10 +00:00
// Don't predict "btnA & btnB"!
prefabbedInputList [ k ] = ( prefabbedInputList [ k ] & 15 ) ;
}
}
2023-02-17 10:54:51 +00:00
// [WARNING] Do not blindly use "selfJoinIndexMask" here, as the "actuallyUsedInput for self" couldn't be confirmed while prefabbing, otherwise we'd have confirmed a wrong self input by "_markConfirmationIfApplicable()"!
2023-01-31 23:27:10 +00:00
let initConfirmedList = 0 ;
if ( null != existingInputFrame ) {
// When "null != existingInputFrame", it implies that "true == canConfirmSelf" here
2023-02-16 14:23:12 +00:00
initConfirmedList = ( existingInputFrame . GetConfirmedList ( ) | selfJoinIndexMask ) ;
2022-12-29 04:36:57 +00:00
}
2022-12-20 15:51:53 +00:00
currSelfInput = self . ctrl . getEncodedInput ( ) ; // When "null == existingInputFrame", it'd be safe to say that the realtime "self.ctrl.getEncodedInput()" is for the requested "inputFrameId"
prefabbedInputList [ ( joinIndex - 1 ) ] = currSelfInput ;
2023-02-16 00:17:50 +00:00
while ( self . recentInputCache . GetEdFrameId ( ) <= inputFrameId ) {
2022-12-20 15:51:53 +00:00
// Fill the gap
2023-02-17 10:54:51 +00:00
const gapInputFrameId = self . recentInputCache . GetEdFrameId ( ) ;
self . recentInputCache . DryPut ( ) ;
let ifdHolder = gopkgs . GetInputFrameDownsync ( self . recentInputCache , gapInputFrameId ) ;
if ( null == ifdHolder ) {
// Lazy heap alloc, calling "gopkgs.NewInputFrameDownsync" would trigger not only heap alloc but also "gopherjs $externalize", neither is efficient T_T
const prefabbedInputFrameDownsync = gopkgs . NewInputFrameDownsync ( gapInputFrameId , prefabbedInputList , initConfirmedList ) ;
// console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.GetInputFrameId()}`);
self . recentInputCache . SetByFrameId ( prefabbedInputFrameDownsync , gapInputFrameId ) ;
} else {
gopkgs . SetInputFrameId ( ifdHolder , gapInputFrameId ) ;
for ( let k = 0 ; k < window . boundRoomCapacity ; ++ k ) {
gopkgs . SetInput ( ifdHolder , k , prefabbedInputList [ k ] ) ;
}
gopkgs . SetConfirmedList ( ifdHolder , initConfirmedList ) ;
}
2022-11-29 13:32:18 +00:00
}
2022-11-20 10:53:33 +00:00
return [ previousSelfInput , currSelfInput ] ;
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
2023-01-30 01:21:06 +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 ;
2023-02-16 00:17:50 +00:00
if ( batchInputFrameIdSt < self . recentInputCache . GetStFrameId ( ) ) {
2022-10-02 16:22:05 +00:00
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
2023-02-16 00:17:50 +00:00
batchInputFrameIdSt = self . recentInputCache . GetStFrameId ( ) ;
2022-10-02 16:22:05 +00:00
}
2023-01-21 14:53:41 +00:00
self . networkDoctor . logSending ( batchInputFrameIdSt , latestLocalInputFrameId ) ;
2022-10-02 16:22:05 +00:00
for ( let i = batchInputFrameIdSt ; i <= latestLocalInputFrameId ; ++ i ) {
2022-12-28 10:06:05 +00:00
const inputFrameDownsync = self . recentInputCache . GetByFrameId ( i ) ;
2022-09-24 04:01:50 +00:00
if ( null == inputFrameDownsync ) {
2022-11-29 13:32:18 +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 ,
2023-02-16 00:17:50 +00:00
encoded : 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-11-20 16:23:01 +00:00
2022-11-21 09:27:32 +00:00
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
2022-11-09 15:46:11 +00:00
const reqData = window . pb . protos . WsReq . encode ( {
2022-09-20 15:50:01 +00:00
msgId : Date . now ( ) ,
2023-02-16 00:17:50 +00:00
playerId : self . selfPlayerInfo . id ,
2022-09-20 15:50:01 +00:00
act : window . UPSYNC _MSG _ACT _PLAYER _CMD ,
2023-02-16 00:17:50 +00:00
joinIndex : self . selfPlayerInfo . joinIndex ,
2022-09-24 04:01:50 +00:00
ackingInputFrameId : self . lastAllConfirmedInputFrameId ,
2022-09-20 15:50:01 +00:00
inputFrameUpsyncBatch : inputFrameUpsyncBatch ,
2023-01-29 16:20:43 +00:00
authKey : self . selfPlayerInfo . udpTunnelAuthKey ,
2022-09-20 15:50:01 +00:00
} ) . finish ( ) ;
2023-01-26 12:19:45 +00:00
if ( cc . sys . isNative ) {
2023-02-16 00:17:50 +00:00
DelayNoMore . UdpSession . broadcastInputFrameUpsync ( reqData , window . boundRoomCapacity , self . selfPlayerInfo . joinIndex ) ;
2023-01-26 12:19:45 +00:00
}
2022-09-20 15:50:01 +00:00
window . sendSafely ( reqData ) ;
2022-10-02 16:22:05 +00:00
self . lastUpsyncInputFrameId = latestLocalInputFrameId ;
2023-02-16 00:17:50 +00:00
if ( self . lastUpsyncInputFrameId >= self . recentInputCache . GetEdFrameId ( ) ) {
2022-11-29 13:32:18 +00:00
throw ` noDelayInputFrameId= ${ self . lastUpsyncInputFrameId } == latestLocalInputFrameId= ${ latestLocalInputFrameId } seems not properly dumped #2: recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
}
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()" ) ;
2022-12-20 08:01:44 +00:00
if ( null == self . battleState || ALL _BATTLE _STATES . IN _BATTLE != self . battleState ) {
2022-09-20 15:50:01 +00:00
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
}
if ( null != window . handleBattleColliderInfo ) {
window . handleBattleColliderInfo = null ;
}
2022-12-20 08:01:44 +00:00
} ,
2023-02-19 05:42:25 +00:00
popupSimplePressToGo ( labelString , hideYesButton , additionalOnDismissalCb ) {
2022-09-20 15:50:01 +00:00
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 ) ;
2023-02-19 05:42:25 +00:00
if ( additionalOnDismissalCb ) {
additionalOnDismissalCb ( ) ;
}
2022-09-20 15:50:01 +00:00
}
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 ;
2022-12-09 09:22:04 +00:00
2022-09-20 15:50:01 +00:00
// 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 ) => {
2023-01-02 15:35:56 +00:00
if ( playerRichInfo . node && playerRichInfo . node . parent ) {
2022-09-22 04:45:17 +00:00
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]
2023-01-11 10:09:18 +00:00
// Clearing cached fireball rendering nodes [BEGINS]
if ( null != self . cachedFireballs ) {
while ( ! self . cachedFireballs . isEmpty ( ) ) {
const v = self . cachedFireballs . pop ( ) ;
if ( v && v . node && v . node . parent ) {
v . node . parent . removeChild ( v . node ) ;
}
}
} else {
self . cachedFireballs = new PriorityQueue ( ) ;
}
for ( let k = 0 ; k < 1000 ; k ++ ) {
const newFireballNode = cc . instantiate ( self . fireballPrefab ) ;
const newFireball = newFireballNode . getComponent ( "Fireball" ) ;
newFireballNode . setPosition ( cc . v2 ( Number . MAX _VALUE , Number . MAX _VALUE ) ) ;
safelyAddChild ( self . node , newFireballNode ) ;
2023-01-20 03:29:27 +00:00
setLocalZOrder ( newFireballNode , 10 ) ;
2023-01-11 10:09:18 +00:00
newFireball . lastUsed = - 1 ;
newFireball . bulletLocalId = - 1 ;
const initLookupKey = - ( k + 1 ) ; // there's definitely no suck "bulletLocalId"
self . cachedFireballs . push ( newFireball . lastUsed , newFireball , initLookupKey ) ;
}
// Clearing cached fireball rendering nodes [ENDS]
2022-09-20 15:50:01 +00:00
self . renderFrameId = 0 ; // After battle started
2022-11-22 09:12:51 +00:00
self . bulletBattleLocalIdCounter = 0 ;
2022-09-24 04:01:50 +00:00
self . lastAllConfirmedInputFrameId = - 1 ;
2022-09-20 15:50:01 +00:00
self . lastUpsyncInputFrameId = - 1 ;
2022-12-22 03:35:18 +00:00
self . chaserRenderFrameId = - 1 ; // at any moment, "chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
2022-09-20 15:50:01 +00:00
2023-01-31 14:39:21 +00:00
self . lastIndividuallyConfirmedInputFrameId = new Array ( window . boundRoomCapacity ) . fill ( - 1 ) ;
self . lastIndividuallyConfirmedInputList = new Array ( window . boundRoomCapacity ) . fill ( 0 ) ;
2023-02-16 04:51:46 +00:00
self . collisionHolder = gopkgs . NewCollisionHolder ( ) ;
2023-02-17 07:38:37 +00:00
// [WARNING] For "effPushbacks", "hardPushbackNormsArr" and "jumpedOrNotList", use array literal instead of "new Array" for compliance when passing into "gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs"!
self . effPushbacks = [ ] ;
for ( let i = 0 ; i < window . boundRoomCapacity ; i ++ ) self . effPushbacks . push ( gopkgs . NewVec2DJs ( 0 , 0 ) ) ;
self . hardPushbackNormsArr = [ ] ;
2023-02-16 04:51:46 +00:00
for ( let i = 0 ; i < window . boundRoomCapacity ; i ++ ) {
const single = [ ] ;
for ( let j = 0 ; j < 5 ; j ++ ) {
single . push ( gopkgs . NewVec2DJs ( 0 , 0 ) ) ;
}
2023-02-17 07:38:37 +00:00
self . hardPushbackNormsArr . push ( single ) ;
2023-02-16 04:51:46 +00:00
}
2023-02-17 07:38:37 +00:00
self . jumpedOrNotList = [ ] ;
for ( let i = 0 ; i < window . boundRoomCapacity ; i ++ ) self . jumpedOrNotList . push ( false ) ;
2023-02-17 04:26:07 +00:00
self . dynamicRectangleColliders = gopkgs . NewDynamicRectangleColliders ( 64 ) ;
2023-02-16 04:51:46 +00:00
self . recentRenderCache = gopkgs . NewRingBufferJs ( self . renderCacheSize ) ;
2022-09-20 15:50:01 +00:00
2022-12-28 10:06:05 +00:00
self . recentInputCache = gopkgs . NewRingBufferJs ( ( self . renderCacheSize >> 1 ) + 1 ) ;
2022-09-23 08:42:44 +00:00
2023-01-01 07:43:25 +00:00
self . gopkgsCollisionSys = gopkgs . NewCollisionSpaceJs ( ( self . spaceOffsetX << 1 ) , ( self . spaceOffsetY << 1 ) , self . collisionMinStep , self . collisionMinStep ) ;
2022-12-25 12:17:22 +00:00
self . gopkgsCollisionSysMap = { } ; // [WARNING] Don't use "JavaScript Map" which could cause loss of type information when passing through Golang transpiled functions!
2022-09-24 04:01:50 +00:00
self . collisionBarrierIndexPrefix = ( 1 << 16 ) ; // For tracking the movements of barriers, though not yet actually used
2022-11-22 09:12:51 +00:00
self . collisionBulletIndexPrefix = ( 1 << 15 ) ; // For tracking the movements of bullets
2022-10-02 16:22:05 +00:00
2022-12-01 03:35:56 +00:00
console . log ( ` collisionSys & collisionSysMap reset ` ) ;
2022-09-20 15:50:01 +00:00
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-12-20 15:51:53 +00:00
self . othersForcedDownsyncRenderFrameDict = new Map ( ) ;
2022-12-26 10:25:20 +00:00
self . rdfIdToActuallyUsedInput = new Map ( ) ;
2022-12-20 15:51:53 +00:00
2023-01-21 14:53:41 +00:00
self . networkDoctor = new NetworkDoctor ( 20 ) ;
2023-02-13 07:37:13 +00:00
self . allowSkippingRenderFrameFlag = true ;
2023-01-22 03:34:02 +00:00
self . skipRenderFrameFlag = false ;
2023-01-21 14:53:41 +00:00
2023-02-03 14:06:03 +00:00
self . allowRollbackOnPeerUpsync = true ;
2022-11-23 14:11:28 +00:00
self . countdownNanos = null ;
2022-09-20 15:50:01 +00:00
} ,
2023-01-04 15:48:00 +00:00
initDebugDrawers ( ) {
const self = this ;
if ( self . showCriticalCoordinateLabels ) {
const drawer1 = new cc . Node ( ) ;
drawer1 . setPosition ( cc . v2 ( 0 , 0 ) )
safelyAddChild ( self . node , drawer1 ) ;
setLocalZOrder ( drawer1 , 999 ) ;
const g1 = drawer1 . addComponent ( cc . Graphics ) ;
g1 . lineWidth = 2 ;
self . g1 = g1 ;
const collisionSpaceObjs = gopkgs . GetCollisionSpaceObjsJs ( self . gopkgsCollisionSys ) ; // This step is slow according to Chrome profiling, and we only need draw it once for those static barriers
for ( let k in collisionSpaceObjs ) {
const body = collisionSpaceObjs [ k ] ;
let padding = 0 ;
2023-02-17 14:44:21 +00:00
if ( null != body . GetData ( ) && null != body . GetData ( ) . GetJoinIndex ) {
2023-01-04 15:48:00 +00:00
// character
2023-02-17 14:44:21 +00:00
if ( 1 == body . GetData ( ) . GetJoinIndex ( ) ) {
2023-01-04 15:48:00 +00:00
g1 . strokeColor = cc . Color . BLUE ;
} else {
g1 . strokeColor = cc . Color . RED ;
}
padding = self . snapIntoPlatformOverlap ;
} else {
// barrier
g1 . strokeColor = cc . Color . WHITE ;
}
2023-02-16 00:17:50 +00:00
const points = body . GetShape ( ) . Points ;
const [ bodyX , bodyY ] = body . Position ( ) ;
const wpos = [ bodyX - self . spaceOffsetX , bodyY - self . spaceOffsetY ] ;
2023-01-04 15:48:00 +00:00
g1 . moveTo ( wpos [ 0 ] , wpos [ 1 ] ) ;
const cnt = points . length ;
for ( let j = 0 ; j < cnt ; j += 1 ) {
const x = wpos [ 0 ] + points [ j ] [ 0 ] ,
y = wpos [ 1 ] + points [ j ] [ 1 ] ;
g1 . lineTo ( x , y ) ;
}
g1 . lineTo ( wpos [ 0 ] , wpos [ 1 ] ) ;
g1 . stroke ( ) ;
}
const drawer2 = new cc . Node ( ) ;
drawer2 . setPosition ( cc . v2 ( 0 , 0 ) )
safelyAddChild ( self . node , drawer2 ) ;
setLocalZOrder ( drawer2 , 999 ) ;
const g2 = drawer2 . addComponent ( cc . Graphics ) ;
g2 . lineWidth = 2 ;
self . g2 = g2 ;
}
} ,
2022-09-20 15:50:01 +00:00
onLoad ( ) {
2023-02-17 06:35:42 +00:00
cc . game . setFrameRate ( 59.9 ) ;
2023-01-17 15:29:05 +00:00
cc . view . setOrientation ( cc . macro . ORIENTATION _LANDSCAPE ) ;
cc . view . enableAutoFullScreen ( true ) ;
2022-09-20 15:50:01 +00:00
const self = this ;
window . mapIns = self ;
window . forceBigEndianFloatingNumDecoding = self . forceBigEndianFloatingNumDecoding ;
2023-01-13 03:25:20 +00:00
self . showCriticalCoordinateLabels = false ;
2023-01-21 14:53:41 +00:00
self . showNetworkDoctorInfo = true ;
2022-11-12 12:34:38 +00:00
2022-09-20 15:50:01 +00:00
console . warn ( "+++++++ Map onLoad()" ) ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
// 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 */ ) ;
} ;
2023-03-16 09:35:47 +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" ) ;
2023-01-25 15:47:54 +00:00
findingPlayerScriptIns . init ( self ) ;
2022-09-20 15:50:01 +00:00
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-12-01 03:35:56 +00:00
console . log ( ` Received parsedBattleColliderInfo via ws ` ) ;
2022-11-29 13:32:18 +00:00
// TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE".
2022-11-22 09:12:51 +00:00
Object . assign ( self , parsedBattleColliderInfo ) ;
2023-01-30 01:21:06 +00:00
self . inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo . inputFrameUpsyncDelayTolerance ;
2022-11-09 10:13:53 +00:00
2022-09-20 15:50:01 +00:00
const tiledMapIns = self . node . getComponent ( cc . TiledMap ) ;
2022-11-16 14:11:56 +00:00
// It's easier to just use the "barrier"s extracted by the backend (all anchor points in world coordinates), but I'd like to verify frontend tmx parser logic as well.
2022-09-20 15:50:01 +00:00
const fullPathOfTmxFile = cc . js . formatStr ( "map/%s/map" , parsedBattleColliderInfo . stageName ) ;
cc . loader . loadRes ( fullPathOfTmxFile , cc . TiledMapAsset , ( err , tmxAsset ) => {
if ( null != err ) {
2023-01-25 10:26:13 +00:00
console . error ( ` Error occurred when loading tiled stage ${ parsedBattleColliderInfo . stageName } ` , err ) ;
2022-09-20 15:50:01 +00:00
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 ( ) ;
2023-02-13 02:34:56 +00:00
if ( self . countdownLabel ) {
self . countdownLabel . string = "" ;
}
self . hideGameRuleNode ( ) ;
self . showFindingPlayerGUI ( null ) ;
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-12-25 12:17:22 +00:00
const boundaryObjs = tileCollisionManager . extractBoundaryObjects ( self . node ) ;
for ( let boundaryObj of boundaryObjs . barriers ) {
const gopkgsBoundaryAnchor = gopkgs . NewVec2DJs ( boundaryObj . anchor . x , boundaryObj . anchor . y ) ;
const gopkgsBoundaryPts = Array . from ( boundaryObj , p => {
return gopkgs . NewVec2DJs ( p . x , p . y ) ;
} ) ;
const gopkgsBoundary = gopkgs . NewPolygon2DJs ( gopkgsBoundaryAnchor , gopkgsBoundaryPts ) ;
const gopkgsBarrier = gopkgs . NewBarrierJs ( gopkgsBoundary ) ;
const newBarrierCollider = gopkgs . GenerateConvexPolygonColliderJs ( gopkgsBoundary , self . spaceOffsetX , self . spaceOffsetY , gopkgsBarrier , "Barrier" ) ;
self . gopkgsCollisionSys . Add ( newBarrierCollider ) ;
2022-09-24 04:01:50 +00:00
++ barrierIdCounter ;
2022-10-02 16:22:05 +00:00
const collisionBarrierIndex = ( self . collisionBarrierIndexPrefix + barrierIdCounter ) ;
2022-12-25 12:17:22 +00:00
self . gopkgsCollisionSysMap [ collisionBarrierIndex ] = newBarrierCollider ;
2022-09-24 04:01:50 +00:00
}
2023-01-04 15:48:00 +00:00
self . initDebugDrawers ( ) ;
2022-11-16 14:11:56 +00:00
const reqData = window . pb . protos . WsReq . encode ( {
msgId : Date . now ( ) ,
act : window . UPSYNC _MSG _ACT _PLAYER _COLLIDER _ACK ,
} ) . finish ( ) ;
window . sendSafely ( reqData ) ;
2022-12-01 03:35:56 +00:00
console . log ( ` Sent UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK via ws ` ) ;
2022-09-20 15:50:01 +00:00
} ) ;
} ;
self . initAfterWSConnected = ( ) => {
const self = window . mapIns ;
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-11-25 05:24:03 +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 ;
}
2023-02-13 02:34:56 +00:00
//self.gameRuleNode.active = false;
self . gameRuleNode . setPosition ( cc . v2 ( Number . MAX _VALUE , Number . MAX _VALUE ) ) ;
2022-09-20 15:50:01 +00:00
} ,
enableInputControls ( ) {
this . _inputControlEnabled = true ;
} ,
disableInputControls ( ) {
this . _inputControlEnabled = false ;
} ,
2022-12-25 12:17:22 +00:00
onRoomDownsyncFrame ( pbRdf /* pb.RoomDownsyncFrame */ , accompaniedInputFrameDownsyncBatch /* pb.InputFrameDownsyncBatch */ ) {
2023-01-10 04:08:15 +00:00
const jsPlayersArr = new Array ( pbRdf . playersArr . length ) . fill ( null ) ;
for ( let k = 0 ; k < pbRdf . playersArr . length ; ++ k ) {
2022-12-25 12:17:22 +00:00
const pbPlayer = pbRdf . playersArr [ k ] ;
2023-02-08 08:15:05 +00:00
const jsPlayer = gopkgs . NewPlayerDownsyncJs ( pbPlayer . id , pbPlayer . virtualGridX , pbPlayer . virtualGridY , pbPlayer . dirX , pbPlayer . dirY , pbPlayer . velX , pbPlayer . velY , pbPlayer . framesToRecover , pbPlayer . framesInChState , pbPlayer . activeSkillId , pbPlayer . activeSkillHit , pbPlayer . framesInvinsible , pbPlayer . speed , pbPlayer . battleState , pbPlayer . characterState , pbPlayer . joinIndex , pbPlayer . hp , pbPlayer . maxHp , pbPlayer . colliderRadius , pbPlayer . inAir , pbPlayer . onWall , pbPlayer . onWallNormX , pbPlayer . onWallNormY , pbPlayer . capturedByInertia , pbPlayer . bulletTeamId , pbPlayer . chCollisionTeamId , pbPlayer . revivalVirtualGridX , pbPlayer . revivalVirtualGridY ) ;
2022-12-25 12:17:22 +00:00
jsPlayersArr [ k ] = jsPlayer ;
}
2023-01-10 04:08:15 +00:00
const jsMeleeBulletsArr = new Array ( pbRdf . meleeBullets . length ) . fill ( null ) ;
for ( let k = 0 ; k < pbRdf . meleeBullets . length ; ++ k ) {
2022-12-25 12:17:22 +00:00
const pbBullet = pbRdf . meleeBullets [ k ] ;
2023-01-15 05:11:19 +00:00
const jsMeleeBullet = gopkgs . NewMeleeBulletJs ( pbBullet . bulletLocalId , pbBullet . originatedRenderFrameId , pbBullet . offenderJoinIndex , pbBullet . startupFrames , pbBullet . cancellableStFrame , pbBullet . cancellableEdFrame , pbBullet . activeFrames , pbBullet . hitStunFrames , pbBullet . blockStunFrames , pbBullet . pushbackVelX , pbBullet . pushbackVelY , pbBullet . damage , pbBullet . selfLockVelX , pbBullet . selfLockVelY , pbBullet . hitboxOffsetX , pbBullet . hitboxOffsetY , pbBullet . hitboxSizeX , pbBullet . hitboxSizeY , pbBullet . blowUp , pbBullet . teamId , pbBullet . blState , pbBullet . framesInBlState , pbBullet . explosionFrames , pbBullet . speciesId ) ;
2023-01-10 04:08:15 +00:00
jsMeleeBulletsArr [ k ] = jsMeleeBullet ;
}
const jsFireballBulletsArr = new Array ( pbRdf . fireballBullets . length ) . fill ( null ) ;
for ( let k = 0 ; k < pbRdf . fireballBullets . length ; ++ k ) {
const pbBullet = pbRdf . fireballBullets [ k ] ;
2023-01-15 05:11:19 +00:00
const jsFireballBullet = gopkgs . NewFireballBulletJs ( pbBullet . bulletLocalId , pbBullet . originatedRenderFrameId , pbBullet . offenderJoinIndex , pbBullet . startupFrames , pbBullet . cancellableStFrame , pbBullet . cancellableEdFrame , pbBullet . activeFrames , pbBullet . hitStunFrames , pbBullet . blockStunFrames , pbBullet . pushbackVelX , pbBullet . pushbackVelY , pbBullet . damage , pbBullet . selfLockVelX , pbBullet . selfLockVelY , pbBullet . hitboxOffsetX , pbBullet . hitboxOffsetY , pbBullet . hitboxSizeX , pbBullet . hitboxSizeY , pbBullet . blowUp , pbBullet . teamId , pbBullet . virtualGridX , pbBullet . virtualGridY , pbBullet . dirX , pbBullet . dirY , pbBullet . velX , pbBullet . velY , pbBullet . speed , pbBullet . blState , pbBullet . framesInBlState , pbBullet . explosionFrames , pbBullet . speciesId ) ;
2023-01-10 04:08:15 +00:00
jsFireballBulletsArr [ k ] = jsFireballBullet ;
2022-12-25 12:17:22 +00:00
}
2022-09-21 04:21:36 +00:00
// This function is also applicable to "re-joining".
2023-02-17 10:54:51 +00:00
const rdf = gopkgs . NewRoomDownsyncFrameJs ( pbRdf . id , jsPlayersArr , pbRdf . bulletLocalIdCounter , jsMeleeBulletsArr , jsFireballBulletsArr ) ; // TODO: Check whether a "proper" preallocated rdf is available and reuse it to avoid redundant heap alloc. By "proper" I mean "pbRdf.id" should yield a "non window.RING_BUFF_FAILED_TO_SET" result, yet currently it's a bit difficult to sort out the following codes for efficient reuse, thus I'm keeping it as-is.
2022-09-20 15:50:01 +00:00
const self = window . mapIns ;
2022-12-22 03:35:18 +00:00
self . onInputFrameDownsyncBatch ( accompaniedInputFrameDownsyncBatch ) ; // Important to do this step before setting IN_BATTLE
2022-11-29 13:32:18 +00:00
if ( ! self . recentRenderCache ) {
return ;
}
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
return ;
2022-10-03 03:42:19 +00:00
}
2023-02-16 04:51:46 +00:00
const rdfId = rdf . GetId ( ) ;
const shouldForceDumping1 = ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START == rdfId ) ;
let shouldForceDumping2 = ( rdfId >= self . renderFrameId + self . renderFrameIdLagTolerance ) ;
2022-12-26 07:11:35 +00:00
let shouldForceResync = pbRdf . shouldForceResync ;
2023-02-16 00:17:50 +00:00
const notSelfUnconfirmed = ( 0 == ( pbRdf . backendUnconfirmedMask & ( 1 << ( self . selfPlayerInfo . joinIndex - 1 ) ) ) ) ;
2022-12-20 15:51:53 +00:00
if ( notSelfUnconfirmed ) {
shouldForceDumping2 = false ;
shouldForceResync = false ;
2023-03-10 07:40:45 +00:00
self . othersForcedDownsyncRenderFrameDict . set ( rdfId , [ pbRdf , rdf ] ) ;
2023-02-27 03:09:22 +00:00
if ( CC _DEBUG ) {
2023-03-10 07:40:45 +00:00
console . warn ( ` Someone else is forced to resync! renderFrameId= ${ rdfId }
2023-02-27 03:09:22 +00:00
backendUnconfirmedMask = $ { pbRdf . backendUnconfirmedMask }
accompaniedInputFrameDownsyncBatchRange = [ $ { null == accompaniedInputFrameDownsyncBatch ? null : accompaniedInputFrameDownsyncBatch [ 0 ] . inputFrameId } , $ { null == accompaniedInputFrameDownsyncBatch ? null : accompaniedInputFrameDownsyncBatch [ accompaniedInputFrameDownsyncBatch . length - 1 ] . inputFrameId } ] ` );
}
2022-12-20 15:51:53 +00:00
}
2022-12-19 11:51:55 +00:00
/ *
TODO
If "BackendUnconfirmedMask" is non - all - 1 and contains the current player , show a label / button to hint manual reconnection . Note that the continuity of "recentInputCache" is not a good indicator , because due to network delay upon a [ type # 1 forceConfirmation ] a player might just lag in upsync networking and have all consecutive inputFrameIds locally .
* /
2022-11-08 13:38:23 +00:00
2023-02-16 04:51:46 +00:00
const [ dumpRenderCacheRet , oldStRenderFrameId , oldEdRenderFrameId ] = ( shouldForceDumping1 || shouldForceDumping2 || shouldForceResync ) ? self . recentRenderCache . SetByFrameId ( rdf , rdfId ) : [ window . RING _BUFF _CONSECUTIVE _SET , null , null ] ;
2022-10-02 16:22:05 +00:00
if ( window . RING _BUFF _FAILED _TO _SET == dumpRenderCacheRet ) {
2023-02-17 14:44:21 +00:00
throw ` Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.GetId()= ${ rdfId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } ; recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
2022-10-02 16:22:05 +00:00
}
2023-02-16 04:51:46 +00:00
if ( ! shouldForceResync && ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START < rdfId && window . RING _BUFF _CONSECUTIVE _SET == dumpRenderCacheRet ) ) {
2022-10-03 03:42:19 +00:00
/ *
Don ' t change
2022-11-11 05:27:48 +00:00
- chaserRenderFrameId , it ' s updated only in "rollbackAndChase & onInputFrameDownsyncBatch" ( except for when RING _BUFF _NON _CONSECUTIVE _SET )
2022-10-03 03:42:19 +00:00
* /
2022-10-02 16:22:05 +00:00
return dumpRenderCacheRet ;
}
2023-02-16 04:51:46 +00:00
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdfId || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
2023-01-01 07:43:25 +00:00
if ( null == pbRdf . speciesIdList ) {
console . error ( ` pbRdf.speciesIdList is required for starting or resyncing battle! ` ) ;
}
2022-12-31 07:47:45 +00:00
self . chConfigsOrderedByJoinIndex = gopkgs . GetCharacterConfigsOrderedByJoinIndex ( pbRdf . speciesIdList ) ;
2023-02-16 14:23:12 +00:00
self . _initPlayerRichInfoDict ( rdf ) ;
2022-09-26 02:36:46 +00:00
2022-12-20 15:51:53 +00:00
if ( shouldForceDumping1 || shouldForceDumping2 || shouldForceResync ) {
2023-02-16 04:51:46 +00:00
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdfId", but here we double check and log the anomaly
2022-11-29 13:32:18 +00:00
2023-02-16 04:51:46 +00:00
if ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START == rdfId ) {
2023-02-27 03:09:22 +00:00
console . log ( ` On battle started! renderFrameId= ${ rdfId } ` ) ;
2022-12-26 04:27:05 +00:00
} else {
2022-12-26 07:11:35 +00:00
self . hideFindingPlayersGUI ( ) ;
2023-02-27 03:09:22 +00:00
if ( CC _DEBUG ) {
console . warn ( ` On battle resynced! renderFrameId= ${ rdf . GetId ( ) }
accompaniedInputFrameDownsyncBatchRange = [ $ { accompaniedInputFrameDownsyncBatch [ 0 ] . inputFrameId } , $ { accompaniedInputFrameDownsyncBatch [ accompaniedInputFrameDownsyncBatch . length - 1 ] . inputFrameId } ] ` );
} else {
console . warn ( ` On battle resynced! renderFrameId= ${ rdf . GetId ( ) } ` ) ;
}
2022-11-29 13:32:18 +00:00
}
2023-02-05 12:24:09 +00:00
2023-02-16 04:51:46 +00:00
self . renderFrameId = rdfId ;
2022-11-25 05:24:03 +00:00
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2023-02-16 04:51:46 +00:00
// In this case it must be true that "rdfId > chaserRenderFrameId".
self . chaserRenderFrameId = rdfId ;
2023-01-22 03:34:02 +00:00
self . networkDoctor . logRollbackFrames ( 0 ) ;
2022-11-25 05:24:03 +00:00
const canvasNode = self . canvasNode ;
self . ctrl = canvasNode . getComponent ( "TouchEventsManager" ) ;
self . enableInputControls ( ) ;
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
2023-02-05 12:24:09 +00:00
2023-02-16 00:17:50 +00:00
const selfPlayerRichInfo = self . playerRichInfoDict . get ( self . selfPlayerInfo . id ) ;
2023-02-05 12:24:09 +00:00
const newMapPos = cc . v2 ( ) . sub ( selfPlayerRichInfo . node . position ) ;
self . node . setPosition ( newMapPos ) ;
2022-11-25 05:24:03 +00:00
self . battleState = ALL _BATTLE _STATES . IN _BATTLE ;
2022-12-25 12:17:22 +00:00
}
2022-11-09 15:46:11 +00:00
2023-02-05 10:44:37 +00:00
// [WARNING] "cc.Node.removeChild" would trigger massive update of rendering nodes, thus a performance impact at the beginning of battle, avoid it by just moving the widget to infinitely far away!
2022-12-25 12:17:22 +00:00
if ( self . countdownToBeginGameNode && self . countdownToBeginGameNode . parent ) {
2023-02-05 10:44:37 +00:00
self . countdownToBeginGameNode . setPosition ( cc . v2 ( Number . MAX _VALUE , Number . MAX _VALUE ) ) ;
2022-12-25 12:17:22 +00:00
}
2022-11-25 05:24:03 +00:00
2022-12-25 12:17:22 +00:00
if ( null != self . musicEffectManagerScriptIns ) {
self . musicEffectManagerScriptIns . playBGM ( ) ;
2022-09-20 15:50:01 +00:00
}
2022-10-02 16:22:05 +00:00
2022-11-25 05:24:03 +00:00
// [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics"
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-12-20 15:51:53 +00:00
equalPlayers ( lhs , rhs ) {
if ( null == lhs || null == rhs ) return false ;
2023-01-11 23:22:14 +00:00
if ( null == lhs && null != rhs ) return false ;
if ( null != lhs && null == rhs ) return false ;
2023-02-17 14:44:21 +00:00
if ( lhs . GetVirtualGridX ( ) != rhs . GetVirtualGridX ( ) ) return false ;
if ( lhs . GetVirtualGridY ( ) != rhs . GetVirtualGridY ( ) ) return false ;
if ( lhs . GetDirX ( ) != rhs . GetDirX ( ) ) return false ;
if ( lhs . GetDirY ( ) != rhs . GetDirY ( ) ) return false ;
if ( lhs . GetVelX ( ) != rhs . GetVelX ( ) ) return false ;
if ( lhs . GetVelY ( ) != rhs . GetVelY ( ) ) return false ;
if ( lhs . GetSpeed ( ) != rhs . GetSpeed ( ) ) return false ;
if ( lhs . GetHp ( ) != rhs . GetHp ( ) ) return false ;
if ( lhs . GetMaxHp ( ) != rhs . GetMaxHp ( ) ) return false ;
if ( lhs . GetCharacterState ( ) != rhs . GetCharacterState ( ) ) return false ;
if ( lhs . GetInAir ( ) != rhs . GetInAir ( ) ) return false ;
if ( lhs . GetOnWall ( ) != rhs . GetOnWall ( ) ) return false ;
if ( lhs . GetFramesToRecover ( ) != rhs . GetFramesToRecover ( ) ) return false ;
if ( lhs . GetFramesInChState ( ) != rhs . GetFramesInChState ( ) ) return false ;
2022-12-20 15:51:53 +00:00
return true ;
} ,
equalMeleeBullets ( lhs , rhs ) {
if ( null == lhs || null == rhs ) return false ;
2023-01-11 23:22:14 +00:00
if ( null == lhs && null != rhs ) return false ;
if ( null != lhs && null == rhs ) return false ;
2023-02-17 14:44:21 +00:00
if ( lhs . GetBulletLocalId ( ) != rhs . GetBulletLocalId ( ) ) return false ;
if ( lhs . GetOffenderJoinIndex ( ) != rhs . GetOffenderJoinIndex ( ) ) return false ;
if ( lhs . GetOriginatedRenderFrameId ( ) != rhs . GetOriginatedRenderFrameId ( ) ) return false ;
2023-01-11 23:22:14 +00:00
return true ;
} ,
equalFireballBullets ( lhs , rhs ) {
if ( null == lhs || null == rhs ) return false ;
if ( null == lhs && null != rhs ) return false ;
if ( null != lhs && null == rhs ) return false ;
2023-02-17 14:44:21 +00:00
if ( lhs . GetBulletLocalId ( ) != rhs . GetBulletLocalId ( ) ) return false ;
if ( lhs . GetOffenderJoinIndex ( ) != rhs . GetOffenderJoinIndex ( ) ) return false ;
if ( lhs . GetOriginatedRenderFrameId ( ) != rhs . GetOriginatedRenderFrameId ( ) ) return false ;
if ( lhs . GetVirtualGridX ( ) != rhs . GetVirtualGridX ( ) ) return false ;
if ( lhs . GetVirtualGridY ( ) != rhs . GetVirtualGridY ( ) ) return false ;
if ( lhs . GetDirX ( ) != rhs . GetDirX ( ) ) return false ;
if ( lhs . GetDirY ( ) != rhs . GetDirY ( ) ) return false ;
if ( lhs . GetVelX ( ) != rhs . GetVelX ( ) ) return false ;
if ( lhs . GetVelY ( ) != rhs . GetVelY ( ) ) return false ;
if ( lhs . GetSpeed ( ) != rhs . GetSpeed ( ) ) return false ;
2023-01-13 06:55:56 +00:00
2022-12-20 15:51:53 +00:00
return true ;
} ,
equalRoomDownsyncFrames ( lhs , rhs ) {
if ( null == lhs || null == rhs ) return false ;
2023-02-17 14:44:21 +00:00
for ( let k = 0 ; k < window . boundRoomCapacity ; k ++ ) {
const lp = gopkgs . GetPlayer ( lhs , k ) ;
const rp = gopkgs . GetPlayer ( rhs , k ) ;
if ( ! this . equalPlayers ( lp , rp ) ) return false ;
2022-12-20 15:51:53 +00:00
}
2023-02-17 14:44:21 +00:00
for ( let k = 0 ; ; k ++ ) {
const lblt = gopkgs . GetMeleeBullet ( lhs , k ) ;
const rblt = gopkgs . GetMeleeBullet ( rhs , k ) ;
if ( null == lblt && null == rblt ) break ;
if ( ! this . equalMeleeBullets ( lblt , rblt ) ) return false ;
2022-12-20 15:51:53 +00:00
}
2023-02-17 14:44:21 +00:00
for ( let k = 0 ; ; k ++ ) {
const lblt = gopkgs . GetFireballBullet ( lhs , k ) ;
const rblt = gopkgs . GetFireballBullet ( rhs , k ) ;
if ( null == lblt && null == rblt ) break ;
if ( ! this . equalFireballBullets ( lblt , rblt ) ) return false ;
2023-01-11 23:22:14 +00:00
}
2022-12-20 15:51:53 +00:00
return true ;
} ,
2023-01-31 14:39:21 +00:00
_markConfirmationIfApplicable ( ) {
const self = this ;
2023-01-31 23:27:10 +00:00
let newAllConfirmedCnt = 0 ;
2023-02-26 15:25:47 +00:00
let candidateInputFrameId = ( self . lastAllConfirmedInputFrameId + 1 ) ;
if ( candidateInputFrameId < self . recentInputCache . GetStFrameId ( ) ) {
candidateInputFrameId = self . recentInputCache . GetStFrameId ( ) ;
}
while ( self . recentInputCache . GetStFrameId ( ) <= candidateInputFrameId && candidateInputFrameId < self . recentInputCache . GetEdFrameId ( ) ) {
const inputFrameDownsync = gopkgs . GetInputFrameDownsync ( self . recentInputCache , candidateInputFrameId ) ;
2023-01-31 14:39:21 +00:00
if ( null == inputFrameDownsync ) break ;
2023-02-27 03:09:22 +00:00
if ( false == self . _allConfirmed ( inputFrameDownsync . GetConfirmedList ( ) ) ) break ;
2023-02-26 15:25:47 +00:00
++ candidateInputFrameId ;
2023-01-31 23:27:10 +00:00
++ newAllConfirmedCnt ;
2023-01-31 14:39:21 +00:00
}
2023-02-26 15:25:47 +00:00
if ( 0 < newAllConfirmedCnt ) {
2023-02-27 03:09:22 +00:00
self . lastAllConfirmedInputFrameId = candidateInputFrameId - 1 ;
2023-02-26 15:25:47 +00:00
}
2023-01-31 23:27:10 +00:00
return newAllConfirmedCnt ;
2023-01-31 14:39:21 +00:00
} ,
2022-12-28 10:06:05 +00:00
onInputFrameDownsyncBatch ( batch /* []*pb.InputFrameDownsync */ ) {
2022-12-20 15:51:53 +00:00
// TODO: find some kind of synchronization mechanism against "getOrPrefabInputFrameUpsync"!
2022-12-26 04:27:05 +00:00
if ( null == batch ) {
return ;
}
2022-10-02 16:22:05 +00:00
const self = this ;
2022-11-29 13:32:18 +00:00
if ( ! self . recentInputCache ) {
return ;
}
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
2022-10-02 16:22:05 +00:00
return ;
}
2023-01-21 14:53:41 +00:00
self . networkDoctor . logInputFrameDownsync ( batch [ 0 ] . inputFrameId , batch [ batch . length - 1 ] . inputFrameId ) ;
2022-10-02 16:22:05 +00:00
let firstPredictedYetIncorrectInputFrameId = null ;
for ( let k in batch ) {
const inputFrameDownsync = batch [ k ] ;
const inputFrameDownsyncId = inputFrameDownsync . inputFrameId ;
2022-12-22 03:35:18 +00:00
if ( inputFrameDownsyncId <= self . lastAllConfirmedInputFrameId ) {
2022-10-03 03:42:19 +00:00
continue ;
}
2023-01-31 23:27:10 +00:00
// [WARNING] Now that "inputFrameDownsyncId > self.lastAllConfirmedInputFrameId", we should make an update immediately because unlike its backend counterpart "Room.LastAllConfirmedInputFrameId", the frontend "mapIns.lastAllConfirmedInputFrameId" might inevitably get gaps among discrete values due to "either type#1 or type#2 forceConfirmation" -- and only "onInputFrameDownsyncBatch" can catch this!
2023-02-27 03:09:22 +00:00
self . lastAllConfirmedInputFrameId = inputFrameDownsyncId ;
2023-02-26 15:25:47 +00:00
const localInputFrame = gopkgs . GetInputFrameDownsync ( self . recentInputCache , inputFrameDownsyncId ) ;
2022-10-16 02:38:38 +00:00
if ( null != localInputFrame
&&
null == firstPredictedYetIncorrectInputFrameId
&&
2023-02-26 15:25:47 +00:00
! self . equalInputLists ( localInputFrame . GetInputList ( ) , inputFrameDownsync . inputList )
2022-10-16 02:38:38 +00:00
) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId ;
2022-10-02 16:22:05 +00:00
}
2023-01-31 14:39:21 +00:00
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
2023-02-26 15:25:47 +00:00
inputFrameDownsync . confirmedList = ( 1 << window . boundRoomCapacity ) - 1 ;
2022-12-29 04:21:01 +00:00
const inputFrameDownsyncLocal = gopkgs . NewInputFrameDownsync ( inputFrameDownsync . inputFrameId , inputFrameDownsync . inputList , inputFrameDownsync . confirmedList ) ; // "battle.InputFrameDownsync" in "jsexport"
2023-01-31 14:39:21 +00:00
for ( let j in self . playerRichInfoArr ) {
const jj = parseInt ( j ) ;
if ( inputFrameDownsync . inputFrameId > self . lastIndividuallyConfirmedInputFrameId [ jj ] ) {
self . lastIndividuallyConfirmedInputFrameId [ jj ] = inputFrameDownsync . inputFrameId ;
self . lastIndividuallyConfirmedInputList [ jj ] = inputFrameDownsync . inputList [ jj ] ;
}
}
2022-12-26 10:25:20 +00:00
//console.log(`Confirmed inputFrameId=${inputFrameDownsync.inputFrameId}`);
2022-12-29 04:21:01 +00:00
const [ ret , oldStFrameId , oldEdFrameId ] = self . recentInputCache . SetByFrameId ( inputFrameDownsyncLocal , inputFrameDownsync . inputFrameId ) ;
2022-12-01 03:35:56 +00:00
if ( window . RING _BUFF _FAILED _TO _SET == ret ) {
2022-12-22 03:35:18 +00:00
throw ` Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId= ${ inputFrameDownsync . inputFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } ; recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
2022-12-01 03:35:56 +00:00
}
2022-10-02 16:22:05 +00:00
}
2023-02-01 09:43:15 +00:00
self . _markConfirmationIfApplicable ( ) ;
2023-02-01 13:35:00 +00:00
self . _handleIncorrectlyRenderedPrediction ( firstPredictedYetIncorrectInputFrameId , batch , false ) ;
} ,
2022-10-02 16:22:05 +00:00
2023-02-01 13:35:00 +00:00
_handleIncorrectlyRenderedPrediction ( firstPredictedYetIncorrectInputFrameId , batch , fromUDP ) {
2022-10-16 02:38:38 +00:00
if ( null == firstPredictedYetIncorrectInputFrameId ) return ;
2023-02-01 13:35:00 +00:00
const self = this ;
2023-03-10 07:40:45 +00:00
const renderFrameId1 = gopkgs . ConvertToFirstUsedRenderFrameId ( firstPredictedYetIncorrectInputFrameId ) ;
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 .
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
< 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).
2023-02-13 07:37:13 +00:00
if ( CC _DEBUG ) {
// Printing of this message might induce a performance impact.
console . log ( ` Mismatched input detected, resetting chaserRenderFrameId: ${ self . chaserRenderFrameId } -> ${ renderFrameId1 } by
2023-02-01 13:35:00 +00:00
firstPredictedYetIncorrectInputFrameId : $ { firstPredictedYetIncorrectInputFrameId }
2022-12-26 04:27:05 +00:00
lastAllConfirmedInputFrameId = $ { self . lastAllConfirmedInputFrameId }
recentInputCache = $ { self . _stringifyRecentInputCache ( false ) }
2023-02-27 06:35:19 +00:00
batchInputFrameIdRange = [ $ { null == batch ? null : batch [ 0 ] . inputFrameId } , $ { null == batch ? null : batch [ batch . length - 1 ] . inputFrameId } ]
2023-02-01 13:35:00 +00:00
fromUDP = $ { fromUDP } ` );
2023-02-13 07:37:13 +00:00
}
2022-10-16 02:38:38 +00:00
self . chaserRenderFrameId = renderFrameId1 ;
2023-02-01 13:35:00 +00:00
let rollbackFrames = ( self . renderFrameId - self . chaserRenderFrameId ) ;
2023-02-05 12:24:09 +00:00
if ( 0 > rollbackFrames ) {
2023-02-01 13:35:00 +00:00
rollbackFrames = 0 ;
2023-02-05 12:24:09 +00:00
}
2023-02-01 13:35:00 +00:00
self . networkDoctor . logRollbackFrames ( rollbackFrames ) ;
2022-09-20 15:50:01 +00:00
} ,
2023-01-31 14:39:21 +00:00
onPeerInputFrameUpsync ( peerJoinIndex , batch , fromUDP ) {
2023-01-18 07:36:04 +00:00
// TODO: find some kind of synchronization mechanism against "getOrPrefabInputFrameUpsync"!
// See `<proj-root>/ConcerningEdgeCases.md` for why this method exists.
if ( null == batch ) {
return ;
}
const self = this ;
if ( ! self . recentInputCache ) {
return ;
}
2023-01-30 01:21:06 +00:00
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState ) {
2023-01-18 07:36:04 +00:00
return ;
}
2023-01-22 03:34:02 +00:00
let effCnt = 0 ;
2023-01-18 14:43:10 +00:00
//console.log(`Received peer inputFrameUpsync batch w/ inputFrameId in [${batch[0].inputFrameId}, ${batch[batch.length - 1].inputFrameId}] for prediction assistance`);
2023-02-01 13:35:00 +00:00
let firstPredictedYetIncorrectInputFrameId = null ;
2023-01-24 02:08:34 +00:00
const renderedInputFrameIdUpper = gopkgs . ConvertToDelayedInputFrameId ( self . renderFrameId ) ;
2023-01-18 07:36:04 +00:00
for ( let k in batch ) {
2023-01-31 14:39:21 +00:00
const inputFrame = batch [ k ] ; // could be either "pb.InputFrameDownsync" or "pb.InputFrameUpsync", depending on "fromUDP"
const inputFrameId = inputFrame . inputFrameId ;
2023-02-01 13:35:00 +00:00
const peerEncodedInput = ( true == fromUDP ? inputFrame . encoded : inputFrame . inputList [ peerJoinIndex - 1 ] ) ;
2023-02-03 14:06:03 +00:00
if ( false == self . allowRollbackOnPeerUpsync && inputFrameId <= renderedInputFrameIdUpper ) {
2023-04-17 22:57:00 +00:00
// [WARNING] Avoid obfuscating already rendered history, even at "inputFrameId == renderedInputFrameIdUpper", due to the use of "INPUT_SCALE_FRAMES" some previous render frames might've already been rendered with "inputFrameId"!
2023-01-24 02:08:34 +00:00
continue ;
}
2023-01-31 14:39:21 +00:00
if ( inputFrameId <= self . lastAllConfirmedInputFrameId ) {
// [WARNING] Don't reject it by "inputFrameId <= self.lastIndividuallyConfirmedInputFrameId[peerJoinIndex-1]", the arrival of UDP packets might not reserve their sending order!
2023-01-18 07:36:04 +00:00
continue ;
}
2023-02-01 09:43:15 +00:00
const peerJoinIndexMask = ( 1 << ( peerJoinIndex - 1 ) ) ;
self . getOrPrefabInputFrameUpsync ( inputFrameId , false ) ; // Make sure that inputFrame exists locally
2023-02-27 03:09:22 +00:00
const existingInputFrame = gopkgs . GetInputFrameDownsync ( self . recentInputCache , inputFrameId ) ;
2023-02-27 04:02:01 +00:00
const existingConfirmedList = existingInputFrame . GetConfirmedList ( ) ;
if ( 0 < ( existingConfirmedList & peerJoinIndexMask ) ) {
2023-01-28 09:47:48 +00:00
continue ;
}
2023-01-31 14:39:21 +00:00
if ( inputFrameId > self . lastIndividuallyConfirmedInputFrameId [ peerJoinIndex - 1 ] ) {
self . lastIndividuallyConfirmedInputFrameId [ peerJoinIndex - 1 ] = inputFrameId ;
self . lastIndividuallyConfirmedInputList [ peerJoinIndex - 1 ] = peerEncodedInput ;
}
2023-01-28 09:47:48 +00:00
effCnt += 1 ;
2023-04-17 22:57:00 +00:00
// the returned "gopkgs.NewInputFrameDownsync.InputList" is immutable, thus we can only modify the value in "newInputList"!
2023-02-27 04:02:01 +00:00
const existingInputList = existingInputFrame . GetInputList ( ) ;
2023-02-27 03:09:22 +00:00
let newInputList = existingInputFrame . GetInputList ( ) . slice ( ) ;
2023-01-31 14:39:21 +00:00
newInputList [ peerJoinIndex - 1 ] = peerEncodedInput ;
2023-04-17 22:57:00 +00:00
const newInputFrameDownsyncLocal = gopkgs . NewInputFrameDownsync ( inputFrameId , newInputList , existingConfirmedList ) ;
2023-02-01 13:35:00 +00:00
//console.log(`Updated encoded input of peerJoinIndex=${peerJoinIndex} to ${peerEncodedInput} for inputFrameId=${inputFrameId}/renderedInputFrameIdUpper=${renderedInputFrameIdUpper} from ${JSON.stringify(inputFrame)}; newInputFrameDownsyncLocal=${self.gopkgsInputFrameDownsyncStr(newInputFrameDownsyncLocal)}; existingInputFrame=${self.gopkgsInputFrameDownsyncStr(existingInputFrame)}`);
2023-01-31 14:39:21 +00:00
self . recentInputCache . SetByFrameId ( newInputFrameDownsyncLocal , inputFrameId ) ;
2023-02-03 14:06:03 +00:00
2023-02-13 07:37:13 +00:00
if ( true == self . allowRollbackOnPeerUpsync ) {
2023-02-03 14:06:03 +00:00
// Reaching here implies that "true == self.allowRollbackOnPeerUpsync".
// Shall we update the "chaserRenderFrameId" if the rendered history was wrong? It doesn't seem to impact eventual correctness if we allow the update of "chaserRenderFrameId" upon "inputFrameId <= renderedInputFrameIdUpper" here, however UDP upsync doesn't reserve order from a same sender and there might be multiple other senders, hence it might result in unnecessarily frequent chasing.
if (
null == firstPredictedYetIncorrectInputFrameId
&&
2023-02-27 04:02:01 +00:00
existingInputList [ peerJoinIndex - 1 ] != peerEncodedInput
2023-02-03 14:06:03 +00:00
) {
firstPredictedYetIncorrectInputFrameId = inputFrameId ;
}
}
2023-01-18 07:36:04 +00:00
}
2023-01-22 03:34:02 +00:00
if ( 0 < effCnt ) {
2023-01-31 23:27:10 +00:00
//self._markConfirmationIfApplicable();
2023-01-22 03:34:02 +00:00
self . networkDoctor . logPeerInputFrameUpsync ( batch [ 0 ] . inputFrameId , batch [ batch . length - 1 ] . inputFrameId ) ;
}
2023-02-03 14:06:03 +00:00
if ( true == self . allowRollbackOnPeerUpsync ) {
2023-04-17 22:57:00 +00:00
/ *
[ WARNING ]
Deliberately NOT setting "existingInputFrame.ConfirmedList = (existingConfirmedList | peerJoinIndexMask)" , thus NOT helping the move of "lastAllConfirmedInputFrameId" in "_markConfirmationIfApplicable()" .
The edge case of concern here is "type#1 forceConfirmation" . Assume that there is a battle among [ P _u , P _v , P _x , P _y ] where [ P _x ] is being an "ActiveSlowerTicker" , then for [ P _u , P _v , P _y ] there might ' ve been some "inputFrameUpsync" s received from [ P _x ] by UDP peer - to - peer transmission EARLIER THAN BUT CONFLICTING WITH the "accompaniedInputFrameDownsyncBatch of type#1 forceConfirmation" -- in such case the latter should be respected -- by "conflicting" , the backend actually ignores those "inputFrameUpsync" s from [ P _x ] by "forceConfirmation" .
However , we should still call "_handleIncorrectlyRenderedPrediction(...)" here to break rollbacks into smaller chunks , because even if not used for "inputFrameDownsync.ConfirmedList" , a "UDP inputFrameUpsync" is still more accurate than the locally predicted inputs .
* /
2023-02-03 14:06:03 +00:00
self . _handleIncorrectlyRenderedPrediction ( firstPredictedYetIncorrectInputFrameId , batch , fromUDP ) ;
}
2023-01-18 07:36:04 +00:00
} ,
2022-12-25 12:17:22 +00:00
onPlayerAdded ( rdf /* pb.RoomDownsyncFrame */ ) {
2022-10-02 16:22:05 +00:00
const self = this ;
2023-02-13 02:34:56 +00:00
self . showFindingPlayerGUI ( rdf ) ;
2022-10-02 16:22:05 +00:00
} ,
2022-09-20 15:50:01 +00:00
onBattleStopped ( ) {
const self = this ;
2022-10-02 16:22:05 +00:00
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState ) {
return ;
}
2023-01-25 10:26:13 +00:00
window . closeWSConnection ( constants . RET _CODE . BATTLE _STOPPED , "" ) ;
2022-12-20 08:01:44 +00:00
self . battleState = ALL _BATTLE _STATES . IN _SETTLEMENT ;
2022-09-20 15:50:01 +00:00
self . countdownNanos = null ;
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 . showPopupInCanvas ( resultPanelNode ) ;
} ,
2022-11-23 14:11:28 +00:00
spawnPlayerNode ( joinIndex , vx , vy , playerDownsyncInfo ) {
2022-09-24 04:01:50 +00:00
const self = this ;
2022-11-19 14:59:12 +00:00
const newPlayerNode = cc . instantiate ( self . controlledCharacterPrefab )
2022-11-19 12:58:07 +00:00
const playerScriptIns = newPlayerNode . getComponent ( "ControlledCharacter" ) ;
2023-01-01 07:43:25 +00:00
const chConfig = self . chConfigsOrderedByJoinIndex [ joinIndex - 1 ] ;
2023-02-16 00:17:50 +00:00
playerScriptIns . setSpecies ( chConfig . GetSpeciesName ( ) ) ;
2022-11-19 14:59:12 +00:00
2023-01-01 12:18:35 +00:00
if ( 1 == joinIndex ) {
2023-01-02 08:36:17 +00:00
newPlayerNode . color = cc . Color . RED ;
2023-01-01 12:18:35 +00:00
} else {
2023-01-02 08:36:17 +00:00
newPlayerNode . color = cc . Color . BLUE ;
2023-01-01 12:18:35 +00:00
}
2023-01-01 07:43:25 +00:00
const [ wx , wy ] = gopkgs . VirtualGridToWorldPos ( vx , vy ) ;
2022-11-25 03:20:05 +00:00
newPlayerNode . setPosition ( wx , wy ) ;
2022-11-19 12:58:07 +00:00
playerScriptIns . mapNode = self . node ;
2022-09-20 15:50:01 +00:00
2023-01-02 08:36:17 +00:00
console . log ( ` Created new player node: joinIndex= ${ joinIndex } ` ) ;
2022-12-01 03:35:56 +00:00
2022-09-24 04:01:50 +00:00
safelyAddChild ( self . node , newPlayerNode ) ;
2022-09-20 15:50:01 +00:00
setLocalZOrder ( newPlayerNode , 5 ) ;
newPlayerNode . active = true ;
2022-11-25 03:20:05 +00:00
playerScriptIns . updateCharacterAnim ( playerDownsyncInfo , null , 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 ) {
2023-01-18 02:06:27 +00:00
/ *
[ WARNING ] Different devices might differ in the rate of calling "update(dt)" , and the game engine is responsible of keeping this rate statistically constant .
Significantly different rates of calling "update(dt)" among players in a same battle would result in frequent [ type # 1 forceConfirmation ] , if you have any doubt on troubles caused by this , sample the FPS curve from all players in that battle .
Kindly note that Significantly different network bandwidths or delay fluctuations would result in frequent [ type # 1 forceConfirmation ] too , but CAUSE FROM DIFFERENT LOCAL "update(dt)" RATE SHOULD BE THE FIRST TO INVESTIGATE AND ELIMINATE -- because we have control on it , but no one has control on the internet .
* /
2023-02-13 07:37:13 +00:00
if ( self . allowSkippingRenderFrameFlag && self . skipRenderFrameFlag ) {
2023-01-22 03:34:02 +00:00
self . networkDoctor . logSkippedRenderFrameCnt ( ) ;
self . skipRenderFrameFlag = false ;
return ;
}
2022-09-25 12:48:09 +00:00
try {
2022-10-02 16:22:05 +00:00
let st = performance . now ( ) ;
2023-02-09 02:18:23 +00:00
if ( cc . sys . isNative ) {
DelayNoMore . UdpSession . pollUdpRecvRingBuff ( ) ;
}
2023-01-04 15:48:00 +00:00
const noDelayInputFrameId = gopkgs . ConvertToNoDelayInputFrameId ( self . renderFrameId ) ;
2023-01-17 05:07:26 +00:00
let prevSelfInput = null ,
currSelfInput = null ;
if ( gopkgs . ShouldGenerateInputFrameUpsync ( self . renderFrameId ) ) {
2023-01-31 23:27:10 +00:00
[ prevSelfInput , currSelfInput ] = self . getOrPrefabInputFrameUpsync ( noDelayInputFrameId , true ) ;
2023-02-13 02:34:56 +00:00
self . networkDoctor . logInputFrameIdFront ( noDelayInputFrameId ) ;
2023-01-17 05:07:26 +00:00
}
2022-09-24 04:01:50 +00:00
2023-01-17 09:38:18 +00:00
const delayedInputFrameId = gopkgs . ConvertToDelayedInputFrameId ( self . renderFrameId ) ;
if ( null == self . recentInputCache . GetByFrameId ( delayedInputFrameId ) ) {
2023-01-20 12:49:26 +00:00
// Possible edge case after resync, kindly note that it's OK to prefab a "future inputFrame" here, because "sendInputFrameUpsyncBatch" would be capped by "noDelayInputFrameId from self.renderFrameId".
2023-01-31 23:27:10 +00:00
self . getOrPrefabInputFrameUpsync ( delayedInputFrameId , false ) ;
2023-01-17 09:38:18 +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-12-20 15:51:53 +00:00
if ( prevChaserRenderFrameId < nextChaserRenderFrameId ) {
// Do not execute "rollbackAndChase" when "prevChaserRenderFrameId == nextChaserRenderFrameId", otherwise if "nextChaserRenderFrameId == self.renderFrameId" we'd be wasting computing power once.
2022-12-25 12:17:22 +00:00
self . rollbackAndChase ( prevChaserRenderFrameId , nextChaserRenderFrameId , self . gopkgsCollisionSys , self . gopkgsCollisionSysMap , true ) ;
2022-12-20 15:51:53 +00:00
}
2022-10-02 16:22:05 +00:00
let t2 = performance . now ( ) ;
2022-11-11 05:27:48 +00:00
// Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now.
2022-12-25 12:17:22 +00:00
const latestRdfResults = self . rollbackAndChase ( self . renderFrameId , self . renderFrameId + 1 , self . gopkgsCollisionSys , self . gopkgsCollisionSysMap , false ) ;
2023-02-05 12:24:09 +00:00
let rollbackFrames = ( self . renderFrameId - self . chaserRenderFrameId ) ;
if ( 0 > rollbackFrames ) {
rollbackFrames = 0 ;
}
self . networkDoctor . logRollbackFrames ( rollbackFrames ) ;
2023-03-10 07:40:45 +00:00
let prevRdf = latestRdfResults [ 0 ] , // Having "prevRdf.Id == self.renderFrameId"
rdf = latestRdfResults [ 1 ] ; // Having "rdf.Id == self.renderFrameId+1"
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 ) ;
}
* /
2023-02-17 14:44:21 +00:00
if ( self . othersForcedDownsyncRenderFrameDict . has ( rdf . GetId ( ) ) ) {
2023-03-10 07:40:45 +00:00
const [ pbOthersForcedDownsyncRenderFrame , othersForcedDownsyncRenderFrame ] = self . othersForcedDownsyncRenderFrameDict . get ( rdf . GetId ( ) ) ;
2022-12-22 03:35:18 +00:00
if ( self . lastAllConfirmedInputFrameId >= delayedInputFrameId && ! self . equalRoomDownsyncFrames ( othersForcedDownsyncRenderFrame , rdf ) ) {
2023-02-27 03:09:22 +00:00
if ( CC _DEBUG ) {
console . warn ( ` Mismatched render frame@rdf.id= ${ rdf . GetId ( ) } w/ inputFrameId= ${ delayedInputFrameId } :
rdf = $ { self . _stringifyGopkgRdfForFrameDataLogging ( rdf ) }
othersForcedDownsyncRenderFrame = $ { self . _stringifyGopkgRdfForFrameDataLogging ( othersForcedDownsyncRenderFrame ) } ` );
}
2023-03-10 07:40:45 +00:00
// [WARNING] When this happens, something is intrinsically wrong -- to avoid having an inconsistent history in the "recentRenderCache", thus a wrong prediction all the way from here on, clear the history!
pbOthersForcedDownsyncRenderFrame . backendUnconfirmedMask = ( ( 1 << window . boundRoomCapacity ) - 1 ) ;
self . onRoomDownsyncFrame ( pbOthersForcedDownsyncRenderFrame , null ) ;
2023-02-17 14:44:21 +00:00
self . othersForcedDownsyncRenderFrameDict . delete ( rdf . GetId ( ) ) ;
2022-12-20 15:51:53 +00:00
}
}
2022-11-25 03:20:05 +00:00
self . applyRoomDownsyncFrameDynamics ( rdf , prevRdf ) ;
2022-12-12 15:17:54 +00:00
self . showDebugBoundaries ( rdf ) ;
2022-11-29 13:32:18 +00:00
++ self . renderFrameId ; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2022-10-02 16:22:05 +00:00
let t3 = performance . now ( ) ;
2023-02-13 03:52:47 +00:00
const [ skipRenderFrameFlag , inputFrameIdFront , sendingFps , srvDownsyncFps , peerUpsyncFps , doctorRollbackFrames , skippedRenderFrameCnt ] = self . networkDoctor . isTooFast ( self ) ;
2023-02-13 07:37:13 +00:00
if ( self . allowSkippingRenderFrameFlag ) {
self . skipRenderFrameFlag = skipRenderFrameFlag ;
}
2023-02-13 03:52:47 +00:00
if ( self . showNetworkDoctorInfo ) {
self . showNetworkDoctorLabels ( inputFrameIdFront , sendingFps , srvDownsyncFps , peerUpsyncFps , doctorRollbackFrames , skippedRenderFrameCnt ) ;
}
2022-09-25 12:48:09 +00:00
} catch ( err ) {
console . error ( "Error during Map.update" , err ) ;
2022-11-29 13:32:18 +00:00
self . onBattleStopped ( ) ; // TODO: Popup to ask player to refresh browser
2022-09-25 12:48:09 +00:00
} finally {
2022-11-25 09:57:10 +00:00
const countdownSeconds = parseInt ( self . countdownNanos / 1000000000 ) ;
if ( isNaN ( countdownSeconds ) ) {
console . warn ( ` countdownSeconds is NaN for countdownNanos == ${ self . countdownNanos } . ` ) ;
}
if ( null != self . countdownLabel ) {
2022-09-25 12:48:09 +00:00
self . countdownLabel . string = countdownSeconds ;
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 = ( ) => {
2023-01-25 15:47:54 +00:00
window . closeWSConnection ( constants . RET _CODE . BATTLE _STOPPED , "" ) ;
2022-09-20 15:50:01 +00:00
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 ( ) ;
} ,
2023-02-12 15:04:20 +00:00
onGameRule1v1ModeClicked ( chosenSpeciesId ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-10-02 16:22:05 +00:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2023-02-12 15:04:20 +00:00
window . chosenSpeciesId = chosenSpeciesId ; // TODO: Find a better way to pass it into "self.initAfterWSConnected"!
2022-09-20 15:50:01 +00:00
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
} ,
showPopupInCanvas ( toShowNode ) {
const self = this ;
2022-12-20 08:01:44 +00:00
toShowNode . active = true ;
2022-09-20 15:50:01 +00:00
self . disableInputControls ( ) ;
self . transitToState ( ALL _MAP _STATES . SHOWING _MODAL _POPUP ) ;
safelyAddChild ( self . widgetsAboveAllNode , toShowNode ) ;
setLocalZOrder ( toShowNode , 10 ) ;
} ,
2023-02-13 02:34:56 +00:00
showFindingPlayerGUI ( rdf ) {
const self = this ;
// Update the "finding player" GUI and show it if not previously present
if ( ! self . findingPlayerNode . parent ) {
self . showPopupInCanvas ( self . findingPlayerNode ) ;
}
if ( null != rdf ) {
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . updatePlayersInfo ( rdf . playersArr ) ;
}
} ,
2022-11-13 03:37:30 +00:00
hideFindingPlayersGUI ( rdf ) {
2022-10-04 03:24:47 +00:00
const self = this ;
2023-02-05 10:44:37 +00:00
// [WARNING] "cc.Node.removeChild" would trigger massive update of rendering nodes, thus a performance impact at the beginning of battle, avoid it by just moving the widget to infinitely far away!
if ( self . findingPlayerNode && self . findingPlayerNode . parent ) {
self . findingPlayerNode . setPosition ( cc . v2 ( Number . MAX _VALUE , Number . MAX _VALUE ) ) ;
}
2022-10-04 03:24:47 +00:00
} ,
2022-12-25 12:17:22 +00:00
onBattleReadyToStart ( rdf /* pb.RoomDownsyncFrame */ ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2022-12-25 12:17:22 +00:00
const players = rdf . playersArr ;
2022-11-09 15:46:11 +00:00
2022-11-21 09:27:32 +00:00
console . log ( "Calling `onBattleReadyToStart` with:" , players ) ;
2022-11-19 12:58:07 +00:00
if ( self . findingPlayerNode ) {
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . hideExitButton ( ) ;
2022-11-21 09:27:32 +00:00
findingPlayerScriptIns . updatePlayersInfo ( players ) ;
2022-11-19 12:58:07 +00:00
}
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
2022-11-19 12:58:07 +00:00
if ( self . countdownToBeginGameNode ) {
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-11-25 03:20:05 +00:00
applyRoomDownsyncFrameDynamics ( rdf , prevRdf ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2023-02-16 14:23:12 +00:00
for ( let k = 0 ; k < window . boundRoomCapacity ; k ++ ) {
const currPlayerDownsync = gopkgs . GetPlayer ( rdf , k ) ;
const prevRdfPlayer = ( null == prevRdf ? null : gopkgs . GetPlayer ( prevRdf , k ) ) ;
2022-12-31 07:47:45 +00:00
const chConfig = self . chConfigsOrderedByJoinIndex [ k ] ;
2023-02-16 14:23:12 +00:00
const [ wx , wy ] = gopkgs . VirtualGridToWorldPos ( currPlayerDownsync . GetVirtualGridX ( ) , currPlayerDownsync . GetVirtualGridY ( ) ) ;
2022-12-25 12:17:22 +00:00
const playerRichInfo = self . playerRichInfoArr [ k ] ;
2022-11-25 03:20:05 +00:00
playerRichInfo . node . setPosition ( wx , wy ) ;
2023-02-16 14:23:12 +00:00
playerRichInfo . scriptIns . updateSpeed ( currPlayerDownsync . GetSpeed ( ) ) ;
2022-12-31 07:47:45 +00:00
playerRichInfo . scriptIns . updateCharacterAnim ( currPlayerDownsync , prevRdfPlayer , false , chConfig ) ;
2023-02-16 14:23:12 +00:00
playerRichInfo . scriptIns . hpBar . progress = ( currPlayerDownsync . GetHp ( ) * 1.0 ) / currPlayerDownsync . GetMaxHp ( ) ;
2022-11-25 03:20:05 +00:00
}
2022-12-26 07:11:35 +00:00
2023-01-11 10:09:18 +00:00
// Move all to infinitely far away first
for ( let k in self . cachedFireballs . list ) {
const pqNode = self . cachedFireballs . list [ k ] ;
const fireball = pqNode . value ;
fireball . node . setPosition ( cc . v2 ( Number . MAX _VALUE , Number . MAX _VALUE ) ) ;
}
2023-02-16 14:23:12 +00:00
for ( let k = 0 ; ; k ++ ) {
const meleeBullet = gopkgs . GetMeleeBullet ( rdf , k ) ;
if ( null == meleeBullet ) {
break ;
}
const isExploding = ( window . BULLET _STATE . Exploding == meleeBullet . GetBlState ( ) && meleeBullet . GetFramesInBlState ( ) < meleeBullet . GetExplosionFrames ( ) ) ;
2023-01-15 05:11:19 +00:00
if ( isExploding ) {
2023-02-16 14:23:12 +00:00
let pqNode = self . cachedFireballs . popAny ( meleeBullet . GetBulletLocalId ( ) ) ;
2023-01-15 05:11:19 +00:00
let speciesName = ` MeleeExplosion ` ;
2023-02-16 14:23:12 +00:00
let animName = ` MeleeExplosion ${ meleeBullet . GetSpeciesId ( ) } ` ;
2023-01-15 05:11:19 +00:00
2023-02-16 14:23:12 +00:00
const offender = gopkgs . GetPlayer ( rdf , meleeBullet . GetOffenderJoinIndex ( ) - 1 ) ;
2023-01-15 05:11:19 +00:00
let xfac = 1 ; // By now, straight Punch offset doesn't respect "y-axis"
2023-02-16 14:23:12 +00:00
if ( 0 > offender . GetDirX ( ) ) {
2023-01-15 05:11:19 +00:00
xfac = - 1 ;
}
2023-02-16 14:23:12 +00:00
const [ wx , wy ] = gopkgs . VirtualGridToWorldPos ( offender . GetVirtualGridX ( ) + xfac * meleeBullet . GetHitboxOffsetX ( ) , offender . GetVirtualGridY ( ) ) ;
2023-01-15 05:11:19 +00:00
if ( null == pqNode ) {
pqNode = self . cachedFireballs . pop ( ) ;
2023-02-17 14:44:21 +00:00
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, using a new fireball node for rendering for bulletLocalId=${meleeBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
2023-01-15 05:11:19 +00:00
} else {
2023-02-17 14:44:21 +00:00
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, using a cached fireball node for rendering for bulletLocalId=${meleeBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
2023-01-15 05:11:19 +00:00
}
const cachedFireball = pqNode . value ;
cachedFireball . setSpecies ( speciesName , meleeBullet , rdf ) ;
2023-02-16 14:23:12 +00:00
const newAnimIdx = meleeBullet . GetSpeciesId ( ) - 1 ;
cachedFireball . updateAnim ( animName , meleeBullet . GetFramesInBlState ( ) , offender . GetDirX ( ) , false , rdf , newAnimIdx ) ;
2023-01-15 05:11:19 +00:00
cachedFireball . lastUsed = self . renderFrameId ;
2023-02-16 14:23:12 +00:00
cachedFireball . bulletLocalId = meleeBullet . GetBulletLocalId ( ) ;
2023-01-15 05:11:19 +00:00
cachedFireball . node . setPosition ( cc . v2 ( wx , wy ) ) ;
2023-02-16 14:23:12 +00:00
self . cachedFireballs . push ( cachedFireball . lastUsed , cachedFireball , meleeBullet . GetBulletLocalId ( ) ) ;
2023-01-15 05:11:19 +00:00
} else {
2023-02-17 14:44:21 +00:00
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${meleeBullet.GetOriginatedRenderFrameId()}, startupFrames=${meleeBullet.GetStartupFrames()}, activeFrames=${meleeBullet.GetActiveFrames()}, not rendering melee node for bulletLocalId=${meleeBullet.GetBulletLocalId()}`);
2023-01-15 05:11:19 +00:00
}
}
2023-02-16 14:23:12 +00:00
for ( let k = 0 ; ; k ++ ) {
const fireballBullet = gopkgs . GetFireballBullet ( rdf , k ) ;
if ( null == fireballBullet ) {
break ;
}
const isExploding = ( window . BULLET _STATE . Exploding == fireballBullet . GetBlState ( ) ) ;
if ( gopkgs . IsGeneralBulletActive ( fireballBullet . GetBlState ( ) , fireballBullet . GetOriginatedRenderFrameId ( ) , fireballBullet . GetStartupFrames ( ) , fireballBullet . GetActiveFrames ( ) , rdf . GetId ( ) ) || isExploding ) {
let pqNode = self . cachedFireballs . popAny ( fireballBullet . GetBulletLocalId ( ) ) ;
let speciesName = ` Fireball ${ fireballBullet . GetSpeciesId ( ) } ` ;
let animName = ( isExploding ? ` Fireball ${ fireballBullet . GetSpeciesId ( ) } Explosion ` : speciesName ) ;
2023-01-15 05:11:19 +00:00
2023-02-16 14:23:12 +00:00
const [ wx , wy ] = gopkgs . VirtualGridToWorldPos ( fireballBullet . GetVirtualGridX ( ) , fireballBullet . GetVirtualGridY ( ) ) ;
2023-01-13 03:25:20 +00:00
if ( null == pqNode ) {
pqNode = self . cachedFireballs . pop ( ) ;
2023-02-17 14:44:21 +00:00
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, using a new fireball node for rendering for bulletLocalId=${fireballBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
2023-01-13 03:25:20 +00:00
} else {
2023-02-17 14:44:21 +00:00
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, using a cached fireball node for rendering for bulletLocalId=${fireballBullet.GetBulletLocalId()} at wpos=(${wx},${wy})`);
2023-01-13 03:25:20 +00:00
}
const cachedFireball = pqNode . value ;
cachedFireball . setSpecies ( speciesName , fireballBullet , rdf ) ;
2023-01-15 05:11:19 +00:00
const spontaneousLooping = ! isExploding ;
const newAnimIdx = ( spontaneousLooping ? 0 : 1 ) ;
2023-02-16 14:23:12 +00:00
cachedFireball . updateAnim ( animName , fireballBullet . GetFramesInBlState ( ) , fireballBullet . GetDirX ( ) , spontaneousLooping , rdf , newAnimIdx ) ;
2023-01-13 03:25:20 +00:00
cachedFireball . lastUsed = self . renderFrameId ;
2023-02-16 14:23:12 +00:00
cachedFireball . bulletLocalId = fireballBullet . GetBulletLocalId ( ) ;
2023-01-13 03:25:20 +00:00
cachedFireball . node . setPosition ( cc . v2 ( wx , wy ) ) ;
2023-02-16 14:23:12 +00:00
self . cachedFireballs . push ( cachedFireball . lastUsed , cachedFireball , fireballBullet . GetBulletLocalId ( ) ) ;
2023-01-13 06:55:56 +00:00
} else {
2023-02-17 14:44:21 +00:00
//console.log(`@rdf.GetId()=${rdf.GetId()}, origRdfId=${fireballBullet.GetOriginatedRenderFrameId()}, startupFrames=${fireballBullet.GetStartupFrames()}, activeFrames=${fireballBullet.GetActiveFrames()}, not rendering fireball node for bulletLocalId=${fireballBullet.GetBulletLocalId()}`);
2023-01-11 14:24:31 +00:00
}
2023-01-11 10:09:18 +00:00
}
2022-12-26 07:11:35 +00:00
// Update countdown
self . countdownNanos = self . battleDurationNanos - self . renderFrameId * self . rollbackEstimatedDtNanos ;
if ( self . countdownNanos <= 0 ) {
self . onBattleStopped ( self . playerRichInfoDict ) ;
}
2022-12-12 11:11:59 +00:00
} ,
2022-11-11 05:27:48 +00:00
rollbackAndChase ( renderFrameIdSt , renderFrameIdEd , collisionSys , collisionSysMap , isChasing ) {
2022-11-10 16:01:53 +00:00
const self = this ;
2022-12-14 13:30:01 +00:00
let prevLatestRdf = null ,
2022-11-29 13:32:18 +00:00
latestRdf = null ;
2022-12-14 13:30:01 +00:00
for ( let i = renderFrameIdSt ; i < renderFrameIdEd ; i ++ ) {
2023-02-16 14:23:12 +00:00
const currRdf = gopkgs . GetRoomDownsyncFrame ( self . recentRenderCache , i ) ; // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
2022-12-22 03:35:18 +00:00
if ( null == currRdf ) {
throw ` Couldn't find renderFrame for i= ${ i } to rollback (are you using Firefox?), self.renderFrameId= ${ self . renderFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } , might've been interruptted by onRoomDownsyncFrame ` ;
2022-11-10 16:01:53 +00:00
}
2023-01-02 12:42:23 +00:00
const j = gopkgs . ConvertToDelayedInputFrameId ( i ) ;
2023-02-16 14:23:12 +00:00
const delayedInputFrame = gopkgs . GetInputFrameDownsync ( self . recentInputCache , j ) ;
2022-12-25 12:17:22 +00:00
2023-03-10 07:40:45 +00:00
const allowUpdateInputFrameInPlaceUponDynamics = ( ! isChasing ) ; // [WARNING] Input mutation could trigger chasing on frontend, thus don't trigger mutation when chasing to avoid confusing recursion.
2023-02-27 06:35:19 +00:00
const hasInputFrameUpdatedOnDynamics = gopkgs . ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs ( self . recentInputCache , i , collisionSys , collisionSysMap , self . spaceOffsetX , self . spaceOffsetY , self . chConfigsOrderedByJoinIndex , self . recentRenderCache , self . collisionHolder , self . effPushbacks , self . hardPushbackNormsArr , self . jumpedOrNotList , self . dynamicRectangleColliders , self . lastIndividuallyConfirmedInputFrameId , self . lastIndividuallyConfirmedInputList , allowUpdateInputFrameInPlaceUponDynamics , self . selfPlayerInfo . joinIndex ) ;
if ( hasInputFrameUpdatedOnDynamics ) {
const ii = gopkgs . ConvertToFirstUsedRenderFrameId ( j ) ;
if ( ii < i ) {
2023-03-16 09:35:47 +00:00
/ *
[ WARNING ]
If we don 't rollback at this spot, when the mutated "delayedInputFrame.inputList" a.k.a. "inputFrame#j" matches the later downsynced version, rollback WOULDN' T be triggered for the incorrectly rendered "renderFrame#(ii+1)" , and it would STAY IN HISTORY FOREVER -- as the history becomes incorrect , EVERY LATEST renderFrame since "inputFrame#j" was mutated would be ALWAYS incorrectly rendering too !
* /
2023-02-27 06:35:19 +00:00
self . _handleIncorrectlyRenderedPrediction ( j , null , false ) ;
}
}
2022-12-27 02:09:53 +00:00
if ( self . frameDataLoggingEnabled ) {
2023-02-27 03:09:22 +00:00
// [WARNING] The "inputList" of "delayedInputFrame" could be mutated in "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs", thus should clone after dynamics is applied!
const actuallyUsedInputClone = delayedInputFrame . GetInputList ( ) . slice ( ) ;
2022-12-27 02:09:53 +00:00
const inputFrameDownsyncClone = {
2023-02-16 04:51:46 +00:00
inputFrameId : j ,
2022-12-27 02:09:53 +00:00
inputList : actuallyUsedInputClone ,
2022-12-28 10:06:05 +00:00
confirmedList : delayedInputFrame . ConfirmedList ,
2022-12-27 02:09:53 +00:00
} ;
2023-02-16 04:51:46 +00:00
self . rdfIdToActuallyUsedInput . set ( i , inputFrameDownsyncClone ) ;
2022-12-27 02:09:53 +00:00
}
2023-02-17 07:38:37 +00:00
const nextRdf = gopkgs . GetRoomDownsyncFrame ( self . recentRenderCache , i + 1 ) ;
2022-11-11 05:27:48 +00:00
if ( true == isChasing ) {
2022-12-22 03:35:18 +00:00
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
2023-02-16 14:23:12 +00:00
self . chaserRenderFrameId = nextRdf . GetId ( ) ;
} else if ( nextRdf . GetId ( ) == self . chaserRenderFrameId + 1 ) {
self . chaserRenderFrameId = nextRdf . GetId ( ) ; // To avoid redundant calculation
2022-11-11 05:27:48 +00:00
}
2022-12-22 03:35:18 +00:00
prevLatestRdf = currRdf ;
latestRdf = nextRdf ;
2022-12-14 13:30:01 +00:00
}
2022-10-02 16:22:05 +00:00
2022-11-25 03:20:05 +00:00
return [ prevLatestRdf , latestRdf ] ;
2022-10-02 16:22:05 +00:00
} ,
2023-02-16 14:23:12 +00:00
_initPlayerRichInfoDict ( rdf ) {
2022-09-20 15:50:01 +00:00
const self = this ;
2023-02-16 14:23:12 +00:00
for ( let k = 0 ; k < window . boundRoomCapacity ; k ++ ) {
const immediatePlayerInfo = gopkgs . GetPlayer ( rdf , k ) ;
const playerId = immediatePlayerInfo . GetId ( ) ;
2022-09-22 04:45:17 +00:00
if ( self . playerRichInfoDict . has ( playerId ) ) continue ; // Skip already put keys
2022-10-02 16:22:05 +00:00
self . playerRichInfoDict . set ( playerId , immediatePlayerInfo ) ;
2023-02-16 14:23:12 +00:00
const joinIndex = immediatePlayerInfo . GetJoinIndex ( ) ;
const vx = immediatePlayerInfo . GetVirtualGridX ( ) ;
const vy = immediatePlayerInfo . GetVirtualGridY ( ) ;
2022-12-25 12:17:22 +00:00
const nodeAndScriptIns = self . spawnPlayerNode ( joinIndex , vx , vy , immediatePlayerInfo ) ;
2022-09-20 15:50:01 +00:00
2022-09-22 04:45:17 +00:00
Object . assign ( self . playerRichInfoDict . get ( playerId ) , {
2022-12-16 05:55:43 +00:00
node : nodeAndScriptIns [ 0 ] ,
scriptIns : nodeAndScriptIns [ 1 ] ,
2022-09-20 15:50:01 +00:00
} ) ;
2023-02-16 00:17:50 +00:00
const selfPlayerId = self . selfPlayerInfo . id ;
2022-12-25 12:17:22 +00:00
if ( selfPlayerId == playerId ) {
2023-02-16 14:23:12 +00:00
self . selfPlayerInfo . joinIndex = joinIndex ; // Update here in case of any change during WAITING phase
2022-12-16 05:55:43 +00:00
nodeAndScriptIns [ 1 ] . showArrowTipNode ( ) ;
2022-09-20 15:50:01 +00:00
}
}
2022-09-26 03:16:18 +00:00
self . playerRichInfoArr = new Array ( self . playerRichInfoDict . size ) ;
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2023-02-16 14:23:12 +00:00
self . playerRichInfoArr [ playerRichInfo . GetJoinIndex ( ) - 1 ] = playerRichInfo ;
2022-10-02 16:22:05 +00:00
} ) ;
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 = [ ] ;
2023-02-16 00:17:50 +00:00
for ( let i = self . recentInputCache . GetStFrameId ( ) ; i < self . recentInputCache . GetEdFrameId ( ) ; ++ i ) {
2022-12-28 10:06:05 +00:00
s . push ( JSON . stringify ( self . recentInputCache . GetByFrameId ( i ) ) ) ;
2022-09-26 02:36:46 +00:00
}
2022-09-22 09:09:49 +00:00
return s . join ( '\n' ) ;
2022-09-20 15:50:01 +00:00
}
2023-02-16 00:17:50 +00:00
return ` [stInputFrameId= ${ self . recentInputCache . GetStFrameId ( ) } , edInputFrameId= ${ self . recentInputCache . GetEdFrameId ( ) } ) ` ;
2022-09-20 15:50:01 +00:00
} ,
2022-09-21 09:22:34 +00:00
2022-12-26 04:27:05 +00:00
_stringifyGopkgRoomDownsyncFrame ( rdf ) {
let s = [ ] ;
s . push ( ` { ` ) ;
2023-02-17 14:44:21 +00:00
s . push ( ` id: ${ rdf . GetId ( ) } ` ) ;
2022-12-26 04:27:05 +00:00
s . push ( ` players: [ ` ) ;
2023-02-16 00:17:50 +00:00
for ( let k in rdf . GetPlayersArr ( ) ) {
const player = rdf . GetPlayersArr ( ) [ k ] ;
2023-02-17 14:44:21 +00:00
s . push ( ` {joinIndex: ${ player . GetJoinIndex ( ) } , id: ${ player . Id } , vx: ${ player . GetVirtualGridX ( ) } , vy: ${ player . GetVirtualGridY ( ) } , velX: ${ player . GetVelX ( ) } , velY: ${ player . GetVelY ( ) } } ` ) ;
2022-12-26 04:27:05 +00:00
}
s . push ( ` ] ` ) ;
s . push ( ` } ` ) ;
return s . join ( "\n" ) ;
} ,
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 = [ ] ;
2023-02-16 04:51:46 +00:00
for ( let i = self . recentRenderCache . GetStFrameId ( ) ; i < self . recentRenderCache . GetEdFrameId ( ) ; ++ i ) {
const rdf = self . recentRenderCache . GetByFrameId ( i ) ;
2022-12-26 04:27:05 +00:00
s . push ( self . _stringifyGopkgRoomDownsyncFrame ( rdf ) ) ;
2022-09-21 09:22:34 +00:00
}
2022-12-26 04:27:05 +00:00
return s . join ( "\n" ) ;
2022-09-26 02:36:46 +00:00
}
2023-02-16 04:51:46 +00:00
return ` [stRenderFrameId= ${ self . recentRenderCache . GetStFrameId ( ) } , edRenderFrameId= ${ self . recentRenderCache . GetEdFrameId ( ) } ) ` ;
2022-09-21 09:22:34 +00:00
} ,
2022-09-26 02:36:46 +00:00
2022-12-26 12:37:40 +00:00
playerDownsyncStr ( playerDownsync ) {
if ( null == playerDownsync ) return "" ;
2023-02-17 14:44:21 +00:00
return ` { ${ playerDownsync . GetJoinIndex ( ) } , ${ playerDownsync . GetVirtualGridX ( ) } , ${ playerDownsync . GetVirtualGridY ( ) } , ${ playerDownsync . GetVelX ( ) } , ${ playerDownsync . GetVelY ( ) } , ${ playerDownsync . GetFramesToRecover ( ) } , ${ playerDownsync . GetInAir ( ) ? 1 : 0 } , ${ playerDownsync . GetOnWall ( ) ? 1 : 0 } } ` ;
2023-01-13 03:25:20 +00:00
} ,
fireballDownsyncStr ( fireball ) {
if ( null == fireball ) return "" ;
2023-02-17 14:44:21 +00:00
return ` { ${ fireball . GetBulletLocalId ( ) } , ${ fireball . GetOriginatedRenderFrameId ( ) } , ${ fireball . GetOffenderJoinIndex ( ) } , ${ fireball . GetVirtualGridX ( ) } , ${ fireball . GetVirtualGridY ( ) } , ${ fireball . GetVelX ( ) } , ${ fireball . GetVelY ( ) } , ${ fireball . GetDirX ( ) } , ${ fireball . GetDirY ( ) } , ${ fireball . GetHitboxSizeX ( ) } , ${ fireball . GetHitboxSizeY ( ) } } ` ;
2022-12-26 12:37:40 +00:00
} ,
2022-12-26 10:25:20 +00:00
inputFrameDownsyncStr ( inputFrameDownsync ) {
if ( null == inputFrameDownsync ) return "" ;
2022-12-22 13:27:14 +00:00
const self = this ;
let s = [ ] ;
2022-12-26 10:25:20 +00:00
s . push ( ` InputFrameId: ${ inputFrameDownsync . inputFrameId } ` ) ;
let ss = [ ] ;
for ( let k in inputFrameDownsync . inputList ) {
ss . push ( ` " ${ inputFrameDownsync . inputList [ k ] } " ` ) ;
}
s . push ( ` InputList:[ ${ ss . join ( ',' ) } ] ` ) ;
// The "confirmedList" is not worth comparing, because frontend might actually use a non-all-confirmed inputFrame during its history, as long as it's correctly predicted.
//s.push(`ConfirmedList:${inputFrameDownsync.confirmedList}`);
2022-12-22 13:27:14 +00:00
2022-12-26 10:25:20 +00:00
return s . join ( ',' ) ;
} ,
2023-02-01 09:43:15 +00:00
gopkgsInputFrameDownsyncStr ( inputFrameDownsync ) {
if ( null == inputFrameDownsync ) return "{}" ;
const self = this ;
let s = [ ] ;
2023-02-27 03:09:22 +00:00
s . push ( ` InputFrameId: ${ inputFrameDownsync . GetInputFrameId ( ) } ` ) ;
2023-02-01 09:43:15 +00:00
let ss = [ ] ;
for ( let k = 0 ; k < window . boundRoomCapacity ; ++ k ) {
2023-02-27 03:09:22 +00:00
ss . push ( ` " ${ inputFrameDownsync . GetInputList ( ) [ k ] } " ` ) ;
2023-02-01 09:43:15 +00:00
}
s . push ( ` InputList:[ ${ ss . join ( ',' ) } ] ` ) ;
2023-02-27 03:09:22 +00:00
s . push ( ` ConfirmedList: ${ inputFrameDownsync . GetConfirmedList ( ) } ` ) ;
2023-02-01 09:43:15 +00:00
return ` { ${ s . join ( ',' ) } } ` ;
} ,
2023-02-27 03:09:22 +00:00
_stringifyGopkgRdfForFrameDataLogging ( rdf ) {
const playersStrBldr = [ ] ;
for ( let k = 0 ; k < window . boundRoomCapacity ; k ++ ) {
playersStrBldr . push ( this . playerDownsyncStr ( gopkgs . GetPlayer ( rdf , k ) ) ) ;
}
const fireballsStrBldr = [ ] ;
for ( let k = 0 ; ; k ++ ) {
const fireball = gopkgs . GetFireballBullet ( rdf , k ) ;
if ( null == fireball ) break ;
fireballsStrBldr . push ( this . fireballDownsyncStr ( fireball ) ) ;
}
return ` rdfId: ${ rdf . GetId ( ) }
players : [ $ { playersStrBldr . join ( ',' ) } ]
fireballs : [ $ { fireballsStrBldr . join ( ',' ) } ] ` ;
} ,
2022-12-26 10:25:20 +00:00
_stringifyRdfIdToActuallyUsedInput ( ) {
const self = this ;
let s = [ ] ;
2023-02-16 04:51:46 +00:00
for ( let i = self . recentRenderCache . GetStFrameId ( ) ; i < self . recentRenderCache . GetEdFrameId ( ) ; i ++ ) {
2022-12-26 10:25:20 +00:00
const actuallyUsedInputClone = self . rdfIdToActuallyUsedInput . get ( i ) ;
2023-02-27 03:09:22 +00:00
const rdf = gopkgs . GetRoomDownsyncFrame ( self . recentRenderCache , i ) ;
const rdfStr = self . _stringifyGopkgRdfForFrameDataLogging ( rdf ) ;
s . push ( ` ${ rdfStr }
2022-12-26 10:25:20 +00:00
actuallyUsedinputList : { $ { self . inputFrameDownsyncStr ( actuallyUsedInputClone ) } } ` );
2022-12-22 13:27:14 +00:00
}
2022-12-26 10:25:20 +00:00
2022-12-22 13:27:14 +00:00
return s . join ( '\n' ) ;
} ,
2022-12-25 12:17:22 +00:00
stringifyColliderCenterInWorld ( playerCollider , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding ) {
return ` { ${ ( playerCollider . x + leftPadding + halfBoundingW ) . toFixed ( 2 ) } , ${ ( playerCollider . y + bottomPadding + halfBoundingH ) . toFixed ( 2 ) } } ` ;
2022-11-09 10:13:53 +00:00
} ,
2022-12-25 12:17:22 +00:00
showDebugBoundaries ( rdf ) {
2022-11-09 10:13:53 +00:00
const self = this ;
2023-01-02 08:36:17 +00:00
// Hardcoded paddings for now
const leftPadding = 0.1 ,
rightPadding = 0.1 ,
topPadding = 0.1 ,
bottomPadding = 0.1 ;
2023-01-04 15:48:00 +00:00
if ( self . showCriticalCoordinateLabels && self . g2 ) {
let g2 = self . g2 ;
g2 . clear ( ) ;
2023-01-02 08:36:17 +00:00
2023-02-17 14:44:21 +00:00
for ( let k = 0 ; k < window . boundRoomCapacity ; k ++ ) {
const player = gopkgs . GetPlayer ( rdf , k ) ;
if ( 1 == player . GetJoinIndex ( ) ) {
2023-01-04 15:48:00 +00:00
g2 . strokeColor = cc . Color . BLUE ;
2023-01-02 08:36:17 +00:00
} else {
2023-01-04 15:48:00 +00:00
g2 . strokeColor = cc . Color . RED ;
2023-01-02 08:36:17 +00:00
}
2023-02-17 14:44:21 +00:00
let [ colliderWidth , colliderHeight ] = [ player . GetColliderRadius ( ) * 2 , player . GetColliderRadius ( ) * 4 ] ;
switch ( player . GetCharacterState ( ) ) {
2023-01-04 15:48:00 +00:00
case ATK _CHARACTER _STATE . LayDown1 [ 0 ] :
2023-02-17 14:44:21 +00:00
[ colliderWidth , colliderHeight ] = [ player . GetColliderRadius ( ) * 4 , player . GetColliderRadius ( ) * 2 ] ;
2023-01-02 08:36:17 +00:00
break ;
2023-01-04 15:48:00 +00:00
case ATK _CHARACTER _STATE . BlownUp1 [ 0 ] :
case ATK _CHARACTER _STATE . InAirIdle1NoJump [ 0 ] :
case ATK _CHARACTER _STATE . InAirIdle1ByJump [ 0 ] :
2023-01-11 23:22:14 +00:00
case ATK _CHARACTER _STATE . OnWall [ 0 ] :
2023-02-17 14:44:21 +00:00
[ colliderWidth , colliderHeight ] = [ player . GetColliderRadius ( ) * 2 , player . GetColliderRadius ( ) * 2 ] ;
2023-01-02 08:36:17 +00:00
break ;
2023-01-04 15:48:00 +00:00
}
2023-01-02 08:36:17 +00:00
2023-01-04 15:48:00 +00:00
const [ halfColliderWidth , halfColliderHeight ] = gopkgs . VirtualGridToWorldPos ( ( colliderWidth >> 1 ) , ( colliderHeight >> 1 ) ) ;
2023-01-02 08:36:17 +00:00
2023-02-17 14:44:21 +00:00
const [ wx , wy ] = gopkgs . VirtualGridToWorldPos ( player . GetVirtualGridX ( ) , player . GetVirtualGridY ( ) ) ;
2023-01-02 08:36:17 +00:00
const [ cx , cy ] = gopkgs . WorldToPolygonColliderBLPos ( wx , wy , halfColliderWidth , halfColliderHeight , topPadding , bottomPadding , leftPadding , rightPadding , 0 , 0 ) ;
2023-01-04 15:48:00 +00:00
const pts = [ [ 0 , 0 ] , [ leftPadding + halfColliderWidth * 2 + rightPadding , 0 ] , [ leftPadding + halfColliderWidth * 2 + rightPadding , bottomPadding + halfColliderHeight * 2 + topPadding ] , [ 0 , bottomPadding + halfColliderHeight * 2 + topPadding ] ] ;
2023-01-02 08:36:17 +00:00
2023-01-04 15:48:00 +00:00
g2 . moveTo ( cx , cy ) ;
2023-01-02 08:36:17 +00:00
for ( let j = 0 ; j < pts . length ; j += 1 ) {
2023-01-04 15:48:00 +00:00
g2 . lineTo ( pts [ j ] [ 0 ] + cx , pts [ j ] [ 1 ] + cy ) ;
2023-01-02 08:36:17 +00:00
}
2023-01-04 15:48:00 +00:00
g2 . lineTo ( cx , cy ) ;
g2 . stroke ( ) ;
2023-01-02 08:36:17 +00:00
}
2023-02-17 14:44:21 +00:00
for ( let k = 0 ; ; k ++ ) {
const meleeBullet = gopkgs . GetMeleeBullet ( rdf , k ) ;
if ( null == meleeBullet ) {
break ;
}
if ( gopkgs . IsGeneralBulletActive ( meleeBullet . GetBlState ( ) , meleeBullet . GetOriginatedRenderFrameId ( ) , meleeBullet . GetStartupFrames ( ) , meleeBullet . GetActiveFrames ( ) , rdf . GetId ( ) ) ) {
const offender = gopkgs . GetPlayer ( rdf , meleeBullet . GetOffenderJoinIndex ( ) - 1 ) ;
if ( 1 == offender . GetJoinIndex ( ) ) {
2023-01-04 15:48:00 +00:00
g2 . strokeColor = cc . Color . BLUE ;
2023-01-02 08:36:17 +00:00
} else {
2023-01-04 15:48:00 +00:00
g2 . strokeColor = cc . Color . RED ;
2023-01-02 08:36:17 +00:00
}
let xfac = 1 ; // By now, straight Punch offset doesn't respect "y-axis"
2023-02-17 14:44:21 +00:00
if ( 0 > offender . GetDirX ( ) ) {
2023-01-02 08:36:17 +00:00
xfac = - 1 ;
}
2023-02-17 14:44:21 +00:00
const [ bulletWx , bulletWy ] = gopkgs . VirtualGridToWorldPos ( offender . GetVirtualGridX ( ) + xfac * meleeBullet . GetHitboxOffsetX ( ) , offender . GetVirtualGridY ( ) ) ;
const [ halfColliderWidth , halfColliderHeight ] = gopkgs . VirtualGridToWorldPos ( ( meleeBullet . GetHitboxSizeX ( ) >> 1 ) , ( meleeBullet . GetHitboxSizeY ( ) >> 1 ) ) ;
2023-01-02 08:36:17 +00:00
const [ bulletCx , bulletCy ] = gopkgs . WorldToPolygonColliderBLPos ( bulletWx , bulletWy , halfColliderWidth , halfColliderHeight , topPadding , bottomPadding , leftPadding , rightPadding , 0 , 0 ) ;
2023-01-04 15:48:00 +00:00
const pts = [ [ 0 , 0 ] , [ leftPadding + halfColliderWidth * 2 + rightPadding , 0 ] , [ leftPadding + halfColliderWidth * 2 + rightPadding , bottomPadding + halfColliderHeight * 2 + topPadding ] , [ 0 , bottomPadding + halfColliderHeight * 2 + topPadding ] ] ;
2023-01-02 08:36:17 +00:00
2023-01-04 15:48:00 +00:00
g2 . moveTo ( bulletCx , bulletCy ) ;
2023-01-02 08:36:17 +00:00
for ( let j = 0 ; j < pts . length ; j += 1 ) {
2023-01-04 15:48:00 +00:00
g2 . lineTo ( pts [ j ] [ 0 ] + bulletCx , pts [ j ] [ 1 ] + bulletCy ) ;
2023-01-02 08:36:17 +00:00
}
2023-01-04 15:48:00 +00:00
g2 . lineTo ( bulletCx , bulletCy ) ;
g2 . stroke ( ) ;
2023-01-02 08:36:17 +00:00
}
}
2023-01-10 04:08:15 +00:00
2023-02-17 14:44:21 +00:00
for ( let k = 0 ; ; k ++ ) {
const fireballBullet = gopkgs . GetFireballBullet ( rdf , k ) ;
if ( null == fireballBullet ) {
break ;
}
if ( gopkgs . IsGeneralBulletActive ( fireballBullet . GetBlState ( ) , fireballBullet . GetOriginatedRenderFrameId ( ) , fireballBullet . GetStartupFrames ( ) , fireballBullet . GetActiveFrames ( ) , rdf . GetId ( ) ) ) {
const offender = gopkgs . GetPlayer ( rdf , fireballBullet . GetOffenderJoinIndex ( ) - 1 ) ;
if ( 1 == offender . GetJoinIndex ( ) ) {
2023-01-10 04:08:15 +00:00
g2 . strokeColor = cc . Color . BLUE ;
} else {
g2 . strokeColor = cc . Color . RED ;
}
2023-02-17 14:44:21 +00:00
const [ bulletWx , bulletWy ] = gopkgs . VirtualGridToWorldPos ( fireballBullet . GetVirtualGridX ( ) , fireballBullet . GetVirtualGridY ( ) ) ;
const [ halfColliderWidth , halfColliderHeight ] = gopkgs . VirtualGridToWorldPos ( ( fireballBullet . GetHitboxSizeX ( ) >> 1 ) , ( fireballBullet . GetHitboxSizeY ( ) >> 1 ) ) ;
2023-01-10 04:08:15 +00:00
const [ bulletCx , bulletCy ] = gopkgs . WorldToPolygonColliderBLPos ( bulletWx , bulletWy , halfColliderWidth , halfColliderHeight , topPadding , bottomPadding , leftPadding , rightPadding , 0 , 0 ) ;
const pts = [ [ 0 , 0 ] , [ leftPadding + halfColliderWidth * 2 + rightPadding , 0 ] , [ leftPadding + halfColliderWidth * 2 + rightPadding , bottomPadding + halfColliderHeight * 2 + topPadding ] , [ 0 , bottomPadding + halfColliderHeight * 2 + topPadding ] ] ;
g2 . moveTo ( bulletCx , bulletCy ) ;
for ( let j = 0 ; j < pts . length ; j += 1 ) {
g2 . lineTo ( pts [ j ] [ 0 ] + bulletCx , pts [ j ] [ 1 ] + bulletCy ) ;
}
g2 . lineTo ( bulletCx , bulletCy ) ;
g2 . stroke ( ) ;
}
}
2022-12-12 15:17:54 +00:00
}
} ,
2023-01-21 14:53:41 +00:00
2023-02-13 03:52:47 +00:00
showNetworkDoctorLabels ( inputFrameIdFront , sendingFps , srvDownsyncFps , peerUpsyncFps , rollbackFrames , skippedRenderFrameCnt ) {
2023-01-21 14:53:41 +00:00
const self = this ;
2023-02-13 02:34:56 +00:00
if ( self . inputFrameFrontLabel ) {
2023-02-13 03:52:47 +00:00
self . inputFrameFrontLabel . string = ` inputFrameId front: ${ inputFrameIdFront } ` ;
2023-02-13 02:34:56 +00:00
}
2023-01-22 03:34:02 +00:00
if ( self . sendingQLabel ) {
2023-02-13 03:52:47 +00:00
self . sendingQLabel . string = ` fps sending: ${ sendingFps } ` ;
2023-01-22 03:34:02 +00:00
if ( sendingFps < self . networkDoctor . inputRateThreshold ) {
self . sendingQLabel . node . color = cc . Color . RED ;
} else {
self . sendingQLabel . node . color = cc . Color . WHITE ;
}
}
if ( self . inputFrameDownsyncQLabel ) {
2023-02-13 03:52:47 +00:00
self . inputFrameDownsyncQLabel . string = ` fps srv-downsync: ${ srvDownsyncFps } ` ;
2023-01-22 03:34:02 +00:00
if ( srvDownsyncFps < self . networkDoctor . inputRateThreshold ) {
self . inputFrameDownsyncQLabel . node . color = cc . Color . RED ;
} else {
self . inputFrameDownsyncQLabel . node . color = cc . Color . WHITE ;
}
}
if ( self . peerInputFrameUpsyncQLabel ) {
2023-02-13 03:52:47 +00:00
self . peerInputFrameUpsyncQLabel . string = ` fps peer-upsync: ${ peerUpsyncFps } ` ;
2023-01-22 03:34:02 +00:00
if ( peerUpsyncFps > self . networkDoctor . peerUpsyncFps ) {
self . peerInputFrameUpsyncQLabel . node . color = cc . Color . RED ;
} else {
self . peerInputFrameUpsyncQLabel . node . color = cc . Color . WHITE ;
}
}
if ( self . rollbackFramesLabel ) {
self . rollbackFramesLabel . string = ` rollbackFrames: ${ rollbackFrames } `
if ( rollbackFrames > self . networkDoctor . rollbackFramesThreshold ) {
self . rollbackFramesLabel . node . color = cc . Color . RED ;
} else {
self . rollbackFramesLabel . node . color = cc . Color . WHITE ;
}
}
if ( self . skippedRenderFrameCntLabel ) {
2023-02-13 03:52:47 +00:00
self . skippedRenderFrameCntLabel . string = ` frames skipped: ${ skippedRenderFrameCnt } `
2023-01-22 03:34:02 +00:00
}
2023-01-21 14:53:41 +00:00
} ,
2022-09-20 15:50:01 +00:00
} ) ;