2022-09-20 23:50:01 +08:00
const i18n = require ( 'LanguageData' ) ;
i18n . init ( window . language ) ; // languageID should be equal to the one we input in New Language ID input field
2022-09-23 16:42:44 +08:00
const collisions = require ( './modules/Collisions' ) ;
2022-09-24 12:01:50 +08:00
const RingBuffer = require ( './RingBuffer' ) ;
2022-09-23 16:42:44 +08:00
2022-09-20 23:50:01 +08:00
window . ALL _MAP _STATES = {
VISUAL : 0 , // For free dragging & zooming.
EDITING _BELONGING : 1 ,
SHOWING _MODAL _POPUP : 2 ,
} ;
window . ALL _BATTLE _STATES = {
WAITING : 0 ,
IN _BATTLE : 1 ,
IN _SETTLEMENT : 2 ,
IN _DISMISSAL : 3 ,
} ;
window . MAGIC _ROOM _DOWNSYNC _FRAME _ID = {
2022-09-24 12:01:50 +08:00
BATTLE _READY _TO _START : - 1 ,
2022-10-01 23:54:48 +08:00
BATTLE _START : 0
2022-09-20 23:50:01 +08:00
} ;
2022-10-04 11:24:47 +08:00
window . PlayerBattleState = {
ADDED _PENDING _BATTLE _COLLIDER _ACK : 0 ,
READDED _PENDING _BATTLE _COLLIDER _ACK : 1 ,
ACTIVE : 2 ,
DISCONNECTED : 3 ,
LOST : 4 ,
EXPELLED _DURING _GAME : 5 ,
EXPELLED _IN _DISMISSAL : 6
} ;
2022-09-20 23:50:01 +08:00
cc . Class ( {
extends : cc . Component ,
properties : {
canvasNode : {
type : cc . Node ,
default : null ,
} ,
2022-11-19 20:58:07 +08:00
controlledCharacterPrefab : {
type : cc . Prefab ,
default : null ,
} ,
2022-09-20 23:50:01 +08: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
} ,
playersInfoPrefab : {
type : cc . Prefab ,
default : null
} ,
forceBigEndianFloatingNumDecoding : {
default : false ,
} ,
2022-10-04 11:24:47 +08:00
renderFrameIdLagTolerance : {
type : cc . Integer ,
default : 4 // implies (renderFrameIdLagTolerance >> inputScaleFrames) count of inputFrameIds
} ,
2022-11-10 18:18:00 +08:00
jigglingEps1D : {
2022-10-10 14:33:04 +08:00
type : cc . Float ,
default : 1e-3
} ,
2022-11-23 22:11:28 +08:00
bulletTriggerEnabled : {
default : false
} ,
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
2022-09-20 23:50:01 +08:00
_inputFrameIdDebuggable ( inputFrameId ) {
2022-10-03 00:22:05 +08:00
return ( 0 == inputFrameId % 10 ) ;
2022-09-20 23:50:01 +08:00
} ,
_convertToInputFrameId ( renderFrameId , inputDelayFrames ) {
if ( renderFrameId < inputDelayFrames ) return 0 ;
2022-09-21 23:59:05 +08:00
return ( ( renderFrameId - inputDelayFrames ) >> this . inputScaleFrames ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-29 12:21:04 +08:00
_convertToFirstUsedRenderFrameId ( inputFrameId , inputDelayFrames ) {
2022-09-21 23:59:05 +08:00
return ( ( inputFrameId << this . inputScaleFrames ) + inputDelayFrames ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-29 12:21:04 +08:00
shouldGenerateInputFrameUpsync ( renderFrameId ) {
2022-10-03 00:22:05 +08:00
return ( ( renderFrameId & ( ( 1 << this . inputScaleFrames ) - 1 ) ) == 0 ) ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-24 12:01:50 +08:00
_allConfirmed ( confirmedList ) {
2022-10-03 00:22:05 +08:00
return ( confirmedList + 1 ) == ( 1 << this . playerRichInfoDict . size ) ;
} ,
2022-09-24 12:01:50 +08:00
2022-09-20 23:50:01 +08:00
_generateInputFrameUpsync ( inputFrameId ) {
2022-09-24 12:01:50 +08:00
const self = this ;
2022-09-20 23:50:01 +08:00
if (
2022-09-24 12:01:50 +08:00
null == self . ctrl ||
null == self . selfPlayerInfo
2022-09-20 23:50:01 +08:00
) {
2022-11-29 21:32:18 +08:00
throw ` noDelayInputFrameId= ${ inputFrameId } couldn't be generated: recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
2022-09-20 23:50:01 +08:00
}
2022-12-01 11:35:56 +08:00
let previousSelfInput = null ,
currSelfInput = null ;
2022-09-24 12:01:50 +08:00
const joinIndex = self . selfPlayerInfo . joinIndex ;
2022-12-01 11:35:56 +08:00
// [WARNING] The while-loop here handles a situation where the "resync rdf & accompaniedInputFrameDownsyncBatch" mismatched and we have to predict some "gap-inputFrames"!
while ( self . recentInputCache . edFrameId <= inputFrameId ) {
// TODO: find some kind of synchronization mechanism against "onInputFrameDownsyncBatch"!
const previousInputFrameDownsyncWithPrediction = self . getCachedInputFrameDownsyncWithPrediction ( inputFrameId - 1 ) ;
previousSelfInput = ( null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction . inputList [ joinIndex - 1 ] ) ;
// If "forceConfirmation" is active on backend, there's a chance that the already downsynced "inputFrameDownsync"s are ahead of a locally generating inputFrameId, in this case we respect the downsynced one.
const existingInputFrame = self . recentInputCache . getByFrameId ( inputFrameId ) ;
if ( null != existingInputFrame && self . _allConfirmed ( existingInputFrame . confirmedList ) ) {
console . log ( ` noDelayInputFrameId= ${ inputFrameId } already exists in recentInputCache and is all-confirmed: recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ) ;
return [ previousSelfInput , existingInputFrame . inputList [ joinIndex - 1 ] ] ;
}
const prefabbedInputList = ( null == previousInputFrameDownsyncWithPrediction ? new Array ( self . playerRichInfoDict . size ) . fill ( 0 ) : previousInputFrameDownsyncWithPrediction . inputList . slice ( ) ) ;
currSelfInput = self . ctrl . getEncodedInput ( ) ;
prefabbedInputList [ ( joinIndex - 1 ) ] = currSelfInput ;
const prefabbedInputFrameDownsync = window . pb . protos . InputFrameDownsync . create ( {
inputFrameId : self . recentInputCache . edFrameId ,
inputList : prefabbedInputList ,
confirmedList : ( 1 << ( self . selfPlayerInfo . joinIndex - 1 ) )
} ) ;
2022-09-20 23:50:01 +08:00
2022-12-01 11:35:56 +08:00
self . recentInputCache . put ( prefabbedInputFrameDownsync ) ; // A prefabbed inputFrame, would certainly be adding a new inputFrame to the cache, because server only downsyncs "all-confirmed inputFrames"
2022-11-29 21:32:18 +08:00
}
2022-11-20 18:53:33 +08:00
return [ previousSelfInput , currSelfInput ] ;
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
2022-09-24 12:01:50 +08:00
shouldSendInputFrameUpsyncBatch ( prevSelfInput , currSelfInput , lastUpsyncInputFrameId , currInputFrameId ) {
2022-09-20 23:50:01 +08:00
/*
2022-10-01 15:14:05 +08:00
For a 2-player-battle, this "shouldUpsyncForEarlyAllConfirmedOnBackend" can be omitted, however for more players in a same battle, to avoid a "long time non-moving player" jamming the downsync of other moving players, we should use this flag.
When backend implements the "force confirmation" feature, we can have "false == shouldUpsyncForEarlyAllConfirmedOnBackend" all the time as well!
2022-09-20 23:50:01 +08:00
*/
if ( null == currSelfInput ) return false ;
2022-10-01 15:14:05 +08:00
2022-10-03 00:22:05 +08:00
const shouldUpsyncForEarlyAllConfirmedOnBackend = ( currInputFrameId - lastUpsyncInputFrameId >= this . inputFrameUpsyncDelayTolerance ) ;
2022-10-01 15:14:05 +08:00
return shouldUpsyncForEarlyAllConfirmedOnBackend || ( prevSelfInput != currSelfInput ) ;
2022-10-03 00:22:05 +08:00
} ,
2022-09-20 23:50:01 +08:00
2022-10-03 00:22:05 +08:00
sendInputFrameUpsyncBatch ( latestLocalInputFrameId ) {
// [WARNING] Why not just send the latest input? Because different player would have a different "latestLocalInputFrameId" of changing its last input, and that could make the server not recognizing any "all-confirmed inputFrame"!
2022-09-24 12:01:50 +08:00
const self = this ;
2022-09-20 23:50:01 +08:00
let inputFrameUpsyncBatch = [ ] ;
2022-10-03 00:22:05 +08:00
let batchInputFrameIdSt = self . lastUpsyncInputFrameId + 1 ;
if ( batchInputFrameIdSt < self . recentInputCache . stFrameId ) {
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
batchInputFrameIdSt = self . recentInputCache . stFrameId ;
}
for ( let i = batchInputFrameIdSt ; i <= latestLocalInputFrameId ; ++ i ) {
2022-09-24 12:01:50 +08:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( i ) ;
if ( null == inputFrameDownsync ) {
2022-11-29 21:32:18 +08:00
console . error ( ` sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId= ${ latestLocalInputFrameId } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ) ;
2022-09-24 12:01:50 +08:00
} else {
const inputFrameUpsync = {
inputFrameId : i ,
2022-11-20 18:53:33 +08:00
encoded : inputFrameDownsync . inputList [ self . selfPlayerInfo . joinIndex - 1 ] ,
2022-09-24 12:01:50 +08:00
} ;
inputFrameUpsyncBatch . push ( inputFrameUpsync ) ;
}
2022-10-03 00:22:05 +08:00
}
2022-11-21 00:23:01 +08:00
2022-11-21 17:27:32 +08:00
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
2022-11-09 23:46:11 +08:00
const reqData = window . pb . protos . WsReq . encode ( {
2022-09-20 23:50:01 +08:00
msgId : Date . now ( ) ,
2022-09-24 12:01:50 +08:00
playerId : self . selfPlayerInfo . id ,
2022-09-20 23:50:01 +08:00
act : window . UPSYNC _MSG _ACT _PLAYER _CMD ,
2022-09-24 12:01:50 +08:00
joinIndex : self . selfPlayerInfo . joinIndex ,
ackingFrameId : self . lastAllConfirmedRenderFrameId ,
ackingInputFrameId : self . lastAllConfirmedInputFrameId ,
2022-09-20 23:50:01 +08:00
inputFrameUpsyncBatch : inputFrameUpsyncBatch ,
} ) . finish ( ) ;
window . sendSafely ( reqData ) ;
2022-10-03 00:22:05 +08:00
self . lastUpsyncInputFrameId = latestLocalInputFrameId ;
2022-11-29 21:32:18 +08:00
if ( self . lastUpsyncInputFrameId >= self . recentInputCache . edFrameId ) {
throw ` noDelayInputFrameId= ${ self . lastUpsyncInputFrameId } == latestLocalInputFrameId= ${ latestLocalInputFrameId } seems not properly dumped #2: recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
}
2022-09-20 23:50:01 +08:00
} ,
onEnable ( ) {
cc . log ( "+++++++ Map onEnable()" ) ;
} ,
onDisable ( ) {
cc . log ( "+++++++ Map onDisable()" ) ;
} ,
onDestroy ( ) {
const self = this ;
console . warn ( "+++++++ Map onDestroy()" ) ;
if ( null == self . battleState || ALL _BATTLE _STATES . WAITING == self . battleState ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
}
if ( null != window . handleBattleColliderInfo ) {
window . handleBattleColliderInfo = null ;
}
2022-10-25 23:02:39 +08:00
if ( null != window . handleClientSessionError ) {
window . handleClientSessionError = null ;
2022-09-20 23:50:01 +08:00
}
} ,
popupSimplePressToGo ( labelString , hideYesButton ) {
const self = this ;
self . state = ALL _MAP _STATES . SHOWING _MODAL _POPUP ;
const canvasNode = self . canvasNode ;
const simplePressToGoDialogNode = cc . instantiate ( self . simplePressToGoDialogPrefab ) ;
simplePressToGoDialogNode . setPosition ( cc . v2 ( 0 , 0 ) ) ;
simplePressToGoDialogNode . setScale ( 1 / canvasNode . scale ) ;
const simplePressToGoDialogScriptIns = simplePressToGoDialogNode . getComponent ( "SimplePressToGoDialog" ) ;
const yesButton = simplePressToGoDialogNode . getChildByName ( "Yes" ) ;
const postDismissalByYes = ( ) => {
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
canvasNode . removeChild ( simplePressToGoDialogNode ) ;
}
simplePressToGoDialogNode . getChildByName ( "Hint" ) . getComponent ( cc . Label ) . string = labelString ;
yesButton . once ( "click" , simplePressToGoDialogScriptIns . dismissDialog . bind ( simplePressToGoDialogScriptIns , postDismissalByYes ) ) ;
yesButton . getChildByName ( "Label" ) . getComponent ( cc . Label ) . string = "OK" ;
if ( true == hideYesButton ) {
yesButton . active = false ;
}
self . transitToState ( ALL _MAP _STATES . SHOWING _MODAL _POPUP ) ;
safelyAddChild ( self . widgetsAboveAllNode , simplePressToGoDialogNode ) ;
setLocalZOrder ( simplePressToGoDialogNode , 20 ) ;
return simplePressToGoDialogNode ;
} ,
alertForGoingBackToLoginScene ( labelString , mapIns , shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
const millisToGo = 3000 ;
mapIns . popupSimplePressToGo ( cc . js . formatStr ( "%s will logout in %s seconds." , labelString , millisToGo / 1000 ) ) ;
setTimeout ( ( ) => {
mapIns . logout ( false , shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) ;
} , millisToGo ) ;
} ,
_resetCurrentMatch ( ) {
const self = this ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
// Clearing previous info of all players. [BEGINS]
2022-09-24 12:01:50 +08:00
self . collisionPlayerIndexPrefix = ( 1 << 17 ) ; // For tracking the movements of players
2022-09-22 12:45:17 +08:00
if ( null != self . playerRichInfoDict ) {
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
if ( playerRichInfo . node . parent ) {
playerRichInfo . node . parent . removeChild ( playerRichInfo . node ) ;
2022-09-20 23:50:01 +08:00
}
2022-09-22 12:45:17 +08:00
} ) ;
2022-10-03 00:22:05 +08:00
}
2022-09-22 12:45:17 +08:00
self . playerRichInfoDict = new Map ( ) ;
2022-09-20 23:50:01 +08:00
// Clearing previous info of all players. [ENDS]
self . renderFrameId = 0 ; // After battle started
2022-11-22 17:12:51 +08:00
self . bulletBattleLocalIdCounter = 0 ;
2022-09-24 12:01:50 +08:00
self . lastAllConfirmedRenderFrameId = - 1 ;
self . lastAllConfirmedInputFrameId = - 1 ;
2022-09-20 23:50:01 +08:00
self . lastUpsyncInputFrameId = - 1 ;
2022-10-03 00:22:05 +08:00
self . chaserRenderFrameId = - 1 ; // at any moment, "lastAllConfirmedRenderFrameId <= chaserRenderFrameId <= renderFrameId", but "chaserRenderFrameId" would fluctuate according to "onInputFrameDownsyncBatch"
2022-09-20 23:50:01 +08:00
2022-11-23 12:30:30 +08:00
self . recentRenderCache = new RingBuffer ( self . renderCacheSize ) ;
2022-09-20 23:50:01 +08:00
self . selfPlayerInfo = null ; // This field is kept for distinguishing "self" and "others".
2022-12-01 00:30:35 +08:00
self . recentInputCache = new RingBuffer ( ( self . renderCacheSize >> 1 ) + 1 ) ;
2022-09-23 16:42:44 +08:00
2022-11-11 13:27:48 +08:00
self . collisionSys = new collisions . Collisions ( ) ;
2022-09-24 12:01:50 +08:00
self . collisionBarrierIndexPrefix = ( 1 << 16 ) ; // For tracking the movements of barriers, though not yet actually used
2022-11-22 17:12:51 +08:00
self . collisionBulletIndexPrefix = ( 1 << 15 ) ; // For tracking the movements of bullets
2022-11-11 13:27:48 +08:00
self . collisionSysMap = new Map ( ) ;
2022-10-03 00:22:05 +08:00
2022-12-01 11:35:56 +08:00
console . log ( ` collisionSys & collisionSysMap reset ` ) ;
2022-09-20 23:50:01 +08:00
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-11-23 22:11:28 +08:00
self . countdownNanos = null ;
2022-11-19 20:58:07 +08:00
if ( self . countdownLabel ) {
self . countdownLabel . string = "" ;
}
2022-09-20 23:50:01 +08:00
if ( self . findingPlayerNode ) {
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . init ( ) ;
}
2022-11-19 20:58:07 +08:00
if ( self . playersInfoNode ) {
safelyAddChild ( self . widgetsAboveAllNode , self . playersInfoNode ) ;
}
if ( self . findingPlayerNode ) {
safelyAddChild ( self . widgetsAboveAllNode , self . findingPlayerNode ) ;
}
2022-09-20 23:50:01 +08:00
} ,
onLoad ( ) {
const self = this ;
window . mapIns = self ;
window . forceBigEndianFloatingNumDecoding = self . forceBigEndianFloatingNumDecoding ;
2022-11-20 18:53:33 +08:00
self . showCriticalCoordinateLabels = false ;
2022-11-12 20:34:38 +08:00
2022-09-20 23:50:01 +08:00
console . warn ( "+++++++ Map onLoad()" ) ;
2022-10-25 23:02:39 +08:00
window . handleClientSessionError = function ( ) {
console . warn ( '+++++++ Common handleClientSessionError()' ) ;
2022-09-20 23:50:01 +08:00
2022-10-04 11:24:47 +08:00
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
console . log ( "Battled ended by settlement" ) ;
2022-09-20 23:50:01 +08:00
} else {
2022-10-04 11:24:47 +08:00
console . warn ( "Connection lost, going back to login page" ) ;
2022-09-20 23:50:01 +08:00
window . clearLocalStorageAndBackToLoginScene ( true ) ;
}
} ;
const mapNode = self . node ;
const canvasNode = mapNode . parent ;
2022-09-23 16:42:44 +08:00
cc . director . getCollisionManager ( ) . enabled = false ;
2022-09-20 23:50:01 +08:00
// self.musicEffectManagerScriptIns = self.node.getComponent("MusicEffectManager");
self . musicEffectManagerScriptIns = null ;
/** Init required prefab started. */
self . confirmLogoutNode = cc . instantiate ( self . confirmLogoutPrefab ) ;
self . confirmLogoutNode . getComponent ( "ConfirmLogout" ) . mapNode = self . node ;
// Initializes Result panel.
self . resultPanelNode = cc . instantiate ( self . resultPanelPrefab ) ;
self . resultPanelNode . width = self . canvasNode . width ;
self . resultPanelNode . height = self . canvasNode . height ;
const resultPanelScriptIns = self . resultPanelNode . getComponent ( "ResultPanel" ) ;
resultPanelScriptIns . mapScriptIns = self ;
resultPanelScriptIns . onAgainClicked = ( ) => {
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 23:50:01 +08:00
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
} ;
2022-12-01 11:35:56 +08:00
resultPanelScriptIns . onCloseDelegate = ( ) => { } ;
2022-09-20 23:50:01 +08:00
self . gameRuleNode = cc . instantiate ( self . gameRulePrefab ) ;
self . gameRuleNode . width = self . canvasNode . width ;
self . gameRuleNode . height = self . canvasNode . height ;
self . gameRuleScriptIns = self . gameRuleNode . getComponent ( "GameRule" ) ;
self . gameRuleScriptIns . mapNode = self . node ;
self . findingPlayerNode = cc . instantiate ( self . findingPlayerPrefab ) ;
self . findingPlayerNode . width = self . canvasNode . width ;
self . findingPlayerNode . height = self . canvasNode . height ;
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . init ( ) ;
self . playersInfoNode = cc . instantiate ( self . playersInfoPrefab ) ;
self . countdownToBeginGameNode = cc . instantiate ( self . countdownToBeginGamePrefab ) ;
self . countdownToBeginGameNode . width = self . canvasNode . width ;
self . countdownToBeginGameNode . height = self . canvasNode . height ;
self . mainCameraNode = canvasNode . getChildByName ( "Main Camera" ) ;
self . mainCamera = self . mainCameraNode . getComponent ( cc . Camera ) ;
for ( let child of self . mainCameraNode . children ) {
child . setScale ( 1 / self . mainCamera . zoomRatio ) ;
}
self . widgetsAboveAllNode = self . mainCameraNode . getChildByName ( "WidgetsAboveAll" ) ;
self . mainCameraNode . setPosition ( cc . v2 ( ) ) ;
/** Init required prefab ended. */
window . handleBattleColliderInfo = function ( parsedBattleColliderInfo ) {
2022-12-01 11:35:56 +08:00
console . log ( ` Received parsedBattleColliderInfo via ws ` ) ;
2022-11-29 21:32:18 +08: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 17:12:51 +08:00
Object . assign ( self , parsedBattleColliderInfo ) ;
2022-11-29 21:32:18 +08:00
self . tooFastDtIntervalMillis = 0.5 * self . rollbackEstimatedDtMillis ;
2022-11-09 18:13:53 +08:00
2022-09-20 23:50:01 +08:00
const tiledMapIns = self . node . getComponent ( cc . TiledMap ) ;
2022-11-16 22:11:56 +08: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 23:50:01 +08:00
const fullPathOfTmxFile = cc . js . formatStr ( "map/%s/map" , parsedBattleColliderInfo . stageName ) ;
cc . loader . loadRes ( fullPathOfTmxFile , cc . TiledMapAsset , ( err , tmxAsset ) => {
if ( null != err ) {
console . error ( err ) ;
return ;
}
2022-10-03 00:22:05 +08:00
2022-09-20 23:50:01 +08:00
/*
[WARNING]
- The order of the following statements is important, because we should have finished "_resetCurrentMatch" before the first "RoomDownsyncFrame".
2022-09-24 12:01:50 +08:00
- It's important to assign new "tmxAsset" before "extractBoundaryObjects", to ensure that the correct tilesets are used.
2022-09-20 23:50:01 +08:00
- To ensure clearance, put destruction of the "cc.TiledMap" component preceding that of "mapNode.destroyAllChildren()".
*/
tiledMapIns . tmxAsset = null ;
mapNode . removeAllChildren ( ) ;
2022-10-03 00:22:05 +08:00
self . _resetCurrentMatch ( ) ;
2022-09-20 23:50:01 +08:00
tiledMapIns . tmxAsset = tmxAsset ;
const newMapSize = tiledMapIns . getMapSize ( ) ;
const newTileSize = tiledMapIns . getTileSize ( ) ;
2022-10-03 00:22:05 +08:00
self . node . setContentSize ( newMapSize . width * newTileSize . width , newMapSize . height * newTileSize . height ) ;
2022-09-20 23:50:01 +08:00
self . node . setPosition ( cc . v2 ( 0 , 0 ) ) ;
/*
* Deliberately hiding "ImageLayer"s. This dirty fix is specific to "CocosCreator v2.2.1", where it got back the rendering capability of "ImageLayer of Tiled", yet made incorrectly. In this game our "markers of ImageLayers" are rendered by dedicated prefabs with associated colliders.
*
* -- YFLu, 2020-01-23
*/
const existingImageLayers = tiledMapIns . getObjectGroups ( ) ;
for ( let singleImageLayer of existingImageLayers ) {
2022-10-03 00:22:05 +08:00
singleImageLayer . node . opacity = 0 ;
2022-09-20 23:50:01 +08:00
}
2022-09-24 12:01:50 +08:00
let barrierIdCounter = 0 ;
2022-09-20 23:50:01 +08:00
const boundaryObjs = tileCollisionManager . extractBoundaryObjects ( self . node ) ;
2022-09-24 12:01:50 +08:00
for ( let boundaryObj of boundaryObjs . barriers ) {
2022-11-16 22:11:56 +08:00
const x0 = boundaryObj . anchor . x ,
y0 = boundaryObj . anchor . y ;
2022-11-19 20:58:07 +08:00
2022-12-01 11:35:56 +08:00
const newBarrierCollider = self . collisionSys . createPolygon ( x0 , y0 , Array . from ( boundaryObj , p => {
2022-11-19 20:58:07 +08:00
return [ p . x , p . y ] ;
} ) ) ;
2022-11-16 22:11:56 +08:00
2022-11-13 11:37:30 +08:00
if ( self . showCriticalCoordinateLabels ) {
2022-11-16 22:11:56 +08:00
for ( let i = 0 ; i < boundaryObj . length ; ++ i ) {
const barrierVertLabelNode = new cc . Node ( ) ;
switch ( i % 4 ) {
case 0 :
barrierVertLabelNode . color = cc . Color . RED ;
break ;
case 1 :
barrierVertLabelNode . color = cc . Color . GRAY ;
break ;
case 2 :
barrierVertLabelNode . color = cc . Color . BLACK ;
break ;
default :
barrierVertLabelNode . color = cc . Color . MAGENTA ;
break ;
}
const wx = boundaryObj . anchor . x + boundaryObj [ i ] . x ,
wy = boundaryObj . anchor . y + boundaryObj [ i ] . y ;
barrierVertLabelNode . setPosition ( cc . v2 ( wx , wy ) ) ;
const barrierVertLabel = barrierVertLabelNode . addComponent ( cc . Label ) ;
barrierVertLabel . fontSize = 12 ;
2022-11-19 20:58:07 +08:00
barrierVertLabel . lineHeight = barrierVertLabel . fontSize + 1 ;
2022-11-16 22:11:56 +08:00
barrierVertLabel . string = ` ( ${ wx . toFixed ( 1 ) } , ${ wy . toFixed ( 1 ) } ) ` ;
safelyAddChild ( self . node , barrierVertLabelNode ) ;
setLocalZOrder ( barrierVertLabelNode , 5 ) ;
barrierVertLabelNode . active = true ;
2022-11-12 20:34:38 +08:00
}
2022-11-16 22:11:56 +08:00
2022-10-03 00:22:05 +08:00
}
2022-09-24 12:01:50 +08:00
++ barrierIdCounter ;
2022-10-03 00:22:05 +08:00
const collisionBarrierIndex = ( self . collisionBarrierIndexPrefix + barrierIdCounter ) ;
2022-12-01 11:35:56 +08:00
self . collisionSysMap . set ( collisionBarrierIndex , newBarrierCollider ) ;
2022-12-01 12:17:30 +08:00
// console.log(`Created new barrier collider: ${collisionBarrierIndex}`);
2022-09-24 12:01:50 +08:00
}
2022-09-20 23:50:01 +08:00
self . selfPlayerInfo = JSON . parse ( cc . sys . localStorage . getItem ( 'selfPlayer' ) ) ;
Object . assign ( self . selfPlayerInfo , {
id : self . selfPlayerInfo . playerId
} ) ;
2022-11-16 22:11:56 +08: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 11:35:56 +08:00
console . log ( ` Sent UPSYNC_MSG_ACT_PLAYER_COLLIDER_ACK via ws ` ) ;
2022-09-20 23:50:01 +08:00
} ) ;
} ;
self . initAfterWSConnected = ( ) => {
const self = window . mapIns ;
self . hideGameRuleNode ( ) ;
self . transitToState ( ALL _MAP _STATES . WAITING ) ;
self . _inputControlEnabled = false ;
}
// The player is now viewing "self.gameRuleNode" with button(s) to start an actual battle. -- YFLu
const expectedRoomId = window . getExpectedRoomIdSync ( ) ;
const boundRoomId = window . getBoundRoomIdFromPersistentStorage ( ) ;
console . warn ( "Map.onLoad, expectedRoomId == " , expectedRoomId , ", boundRoomId == " , boundRoomId ) ;
if ( null != expectedRoomId ) {
self . disableGameRuleNode ( ) ;
// The player is now possibly viewing "self.gameRuleNode" with no button, and should wait for `self.initAfterWSConnected` to be called.
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 23:50:01 +08:00
window . initPersistentSessionClient ( self . initAfterWSConnected , expectedRoomId ) ;
} else if ( null != boundRoomId ) {
self . disableGameRuleNode ( ) ;
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-10-04 11:24:47 +08:00
window . initPersistentSessionClient ( self . initAfterWSConnected , boundRoomId ) ;
2022-09-20 23:50:01 +08:00
} else {
self . showPopupInCanvas ( self . gameRuleNode ) ;
2022-11-25 13:24:03 +08:00
// Deliberately left blank. -- YFLu
2022-09-20 23:50:01 +08:00
}
} ,
disableGameRuleNode ( ) {
const self = window . mapIns ;
if ( null == self . gameRuleNode ) {
return ;
}
if ( null == self . gameRuleScriptIns ) {
return ;
}
if ( null == self . gameRuleScriptIns . modeButton ) {
return ;
}
self . gameRuleScriptIns . modeButton . active = false ;
} ,
hideGameRuleNode ( ) {
const self = window . mapIns ;
if ( null == self . gameRuleNode ) {
return ;
}
self . gameRuleNode . active = false ;
} ,
enableInputControls ( ) {
this . _inputControlEnabled = true ;
} ,
disableInputControls ( ) {
this . _inputControlEnabled = false ;
} ,
2022-12-01 00:30:35 +08:00
onRoomDownsyncFrame ( rdf , accompaniedInputFrameDownsyncBatch ) {
2022-09-21 12:21:36 +08:00
// This function is also applicable to "re-joining".
2022-09-20 23:50:01 +08:00
const self = window . mapIns ;
2022-11-29 21:32:18 +08:00
if ( ! self . recentRenderCache ) {
return ;
}
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
return ;
2022-10-03 11:42:19 +08:00
}
2022-11-29 21:32:18 +08:00
const shouldForceDumping1 = ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START == rdf . id ) ;
const shouldForceDumping2 = ( rdf . id > self . renderFrameId + self . renderFrameIdLagTolerance ) ;
2022-11-08 21:38:23 +08:00
2022-12-01 11:35:56 +08:00
const [ dumpRenderCacheRet , oldStRenderFrameId , oldEdRenderFrameId ] = ( shouldForceDumping1 || shouldForceDumping2 ) ? self . recentRenderCache . setByFrameId ( rdf , rdf . id ) : [ window . RING _BUFF _CONSECUTIVE _SET , null , null ] ;
2022-10-03 00:22:05 +08:00
if ( window . RING _BUFF _FAILED _TO _SET == dumpRenderCacheRet ) {
2022-11-29 21:32:18 +08:00
throw ` Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id= ${ rdf . id } , lastAllConfirmedRenderFrameId= ${ self . lastAllConfirmedRenderFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } ; recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
2022-10-03 00:22:05 +08:00
}
if ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START < rdf . id && window . RING _BUFF _CONSECUTIVE _SET == dumpRenderCacheRet ) {
2022-10-03 11:42:19 +08:00
/*
Don't change
2022-11-11 00:01:53 +08:00
- lastAllConfirmedRenderFrameId, it's updated only in "rollbackAndChase" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
2022-11-11 13:27:48 +08:00
- chaserRenderFrameId, it's updated only in "rollbackAndChase & onInputFrameDownsyncBatch" (except for when RING_BUFF_NON_CONSECUTIVE_SET)
2022-10-03 11:42:19 +08:00
*/
2022-10-03 00:22:05 +08:00
return dumpRenderCacheRet ;
}
2022-11-29 21:32:18 +08:00
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
2022-09-26 10:36:46 +08:00
const players = rdf . players ;
2022-11-21 17:27:32 +08:00
self . _initPlayerRichInfoDict ( players ) ;
2022-09-26 10:36:46 +08:00
// Show the top status indicators for IN_BATTLE
2022-11-19 20:58:07 +08:00
if ( self . playersInfoNode ) {
const playersInfoScriptIns = self . playersInfoNode . getComponent ( "PlayersInfo" ) ;
2022-11-21 17:27:32 +08:00
for ( let i in players ) {
playersInfoScriptIns . updateData ( players [ i ] ) ;
2022-11-19 20:58:07 +08:00
}
2022-09-26 10:36:46 +08:00
}
2022-11-25 13:24:03 +08:00
if ( null == self . renderFrameId || self . renderFrameId <= rdf . id ) {
// In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly
2022-11-29 21:32:18 +08:00
if ( window . MAGIC _ROOM _DOWNSYNC _FRAME _ID . BATTLE _START == rdf . id ) {
console . log ( 'On battle started! renderFrameId=' , rdf . id ) ;
} else {
2022-12-01 11:35:56 +08:00
self . hideFindingPlayersGUI ( rdf ) ;
2022-12-01 00:30:35 +08:00
self . onInputFrameDownsyncBatch ( accompaniedInputFrameDownsyncBatch ) ; // Important to do this step before setting IN_BATTLE
2022-11-29 21:32:18 +08:00
console . warn ( ` Got resync@localRenderFrameId= ${ self . renderFrameId } -> rdf.id= ${ rdf . id } & rdf.backendUnconfirmedMask= ${ rdf . backendUnconfirmedMask } , @lastAllConfirmedRenderFrameId= ${ self . lastAllConfirmedRenderFrameId } , @lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } , @chaserRenderFrameId= ${ self . chaserRenderFrameId } , @localRecentInputCache= ${ mapIns . _stringifyRecentInputCache ( false ) } ` ) ;
}
2022-11-25 13:24:03 +08:00
self . renderFrameId = rdf . id ;
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
// In this case it must be true that "rdf.id > chaserRenderFrameId >= lastAllConfirmedRenderFrameId".
self . lastAllConfirmedRenderFrameId = rdf . id ;
self . chaserRenderFrameId = rdf . id ;
2022-11-29 21:32:18 +08:00
const candidateLastAllConfirmedInputFrame = self . _convertToInputFrameId ( rdf . id - 1 , self . inputDelayFrames ) ;
if ( self . lastAllConfirmedInputFrame < candidateLastAllConfirmedInputFrame ) {
self . lastAllConfirmedInputFrame = candidateLastAllConfirmedInputFrame ;
}
2022-11-25 13:24:03 +08:00
const canvasNode = self . canvasNode ;
self . ctrl = canvasNode . getComponent ( "TouchEventsManager" ) ;
self . enableInputControls ( ) ;
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
self . battleState = ALL _BATTLE _STATES . IN _BATTLE ;
2022-11-09 23:46:11 +08:00
2022-11-25 13:24:03 +08:00
if ( self . countdownToBeginGameNode && self . countdownToBeginGameNode . parent ) {
self . countdownToBeginGameNode . parent . removeChild ( self . countdownToBeginGameNode ) ;
}
if ( null != self . musicEffectManagerScriptIns ) {
self . musicEffectManagerScriptIns . playBGM ( ) ;
}
} else {
2022-11-25 17:57:10 +08:00
console . warn ( ` Anomaly when onRoomDownsyncFrame is called by rdf= ${ JSON . stringify ( rdf ) } , recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ) ;
2022-09-20 23:50:01 +08:00
}
2022-10-03 00:22:05 +08:00
2022-11-25 13:24:03 +08:00
// [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics"
2022-10-03 00:22:05 +08:00
return dumpRenderCacheRet ;
} ,
equalInputLists ( lhs , rhs ) {
if ( null == lhs || null == rhs ) return false ;
if ( lhs . length != rhs . length ) return false ;
for ( let i in lhs ) {
if ( lhs [ i ] == rhs [ i ] ) continue ;
return false ;
}
return true ;
} ,
2022-10-16 10:38:38 +08:00
onInputFrameDownsyncBatch ( batch ) {
2022-12-01 11:35:56 +08:00
// TODO: find some kind of synchronization mechanism against "_generateInputFrameUpsync"!
2022-10-03 00:22:05 +08:00
const self = this ;
2022-11-29 21:32:18 +08:00
if ( ! self . recentInputCache ) {
return ;
}
if ( ALL _BATTLE _STATES . IN _SETTLEMENT == self . battleState ) {
2022-10-03 00:22:05 +08:00
return ;
}
let firstPredictedYetIncorrectInputFrameId = null ;
for ( let k in batch ) {
const inputFrameDownsync = batch [ k ] ;
const inputFrameDownsyncId = inputFrameDownsync . inputFrameId ;
2022-10-03 11:42:19 +08:00
if ( inputFrameDownsyncId < self . lastAllConfirmedInputFrameId ) {
continue ;
}
2022-11-29 21:32:18 +08:00
self . lastAllConfirmedInputFrameId = inputFrameDownsyncId ;
2022-10-16 10:38:38 +08:00
const localInputFrame = self . recentInputCache . getByFrameId ( inputFrameDownsyncId ) ;
if ( null != localInputFrame
&&
null == firstPredictedYetIncorrectInputFrameId
&&
! self . equalInputLists ( localInputFrame . inputList , inputFrameDownsync . inputList )
) {
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId ;
2022-10-03 00:22:05 +08:00
}
2022-11-11 20:10:43 +08:00
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
inputFrameDownsync . confirmedList = ( 1 << self . playerRichInfoDict . size ) - 1 ;
2022-12-01 11:35:56 +08:00
const [ ret , oldStFrameId , oldEdFrameId ] = self . recentInputCache . setByFrameId ( inputFrameDownsync , inputFrameDownsync . inputFrameId ) ;
if ( window . RING _BUFF _FAILED _TO _SET == ret ) {
throw ` Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId= ${ inputFrameDownsync . inputFrameId } , lastAllConfirmedRenderFrameId= ${ self . lastAllConfirmedRenderFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } ; recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
}
2022-10-03 00:22:05 +08:00
}
2022-10-16 10:38:38 +08:00
if ( null == firstPredictedYetIncorrectInputFrameId ) return ;
const inputFrameId1 = firstPredictedYetIncorrectInputFrameId ;
const renderFrameId1 = self . _convertToFirstUsedRenderFrameId ( inputFrameId1 , self . inputDelayFrames ) ; // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId"
if ( renderFrameId1 >= self . renderFrameId ) return ; // No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId1" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range.
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
if ( renderFrameId1 >= self . chaserRenderFrameId ) return ;
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
/*
A typical case is as follows.
--------------------------------------------------------
[self.lastAllConfirmedRenderFrameId] : 22
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
<renderFrameId1> : 36
2022-10-03 00:22:05 +08:00
2022-10-16 10:38:38 +08:00
<self.chaserRenderFrameId> : 62
[self.renderFrameId] : 64
--------------------------------------------------------
*/
// The actual rollback-and-chase would later be executed in update(dt).
2022-11-11 23:43:51 +08:00
console . warn ( ` Mismatched input detected, resetting chaserRenderFrameId: ${ self . chaserRenderFrameId } -> ${ renderFrameId1 } by firstPredictedYetIncorrectInputFrameId: ${ inputFrameId1 } ` ) ;
2022-10-16 10:38:38 +08:00
self . chaserRenderFrameId = renderFrameId1 ;
2022-09-20 23:50:01 +08:00
} ,
2022-10-03 00:22:05 +08:00
onPlayerAdded ( rdf ) {
const self = this ;
// Update the "finding player" GUI and show it if not previously present
if ( ! self . findingPlayerNode . parent ) {
self . showPopupInCanvas ( self . findingPlayerNode ) ;
}
let findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
2022-11-21 17:27:32 +08:00
findingPlayerScriptIns . updatePlayersInfo ( rdf . players ) ;
2022-10-03 00:22:05 +08:00
} ,
2022-09-20 23:50:01 +08:00
logBattleStats ( ) {
const self = this ;
let s = [ ] ;
2022-11-29 21:32:18 +08:00
s . push ( ` Battle stats: renderFrameId= ${ self . renderFrameId } , lastAllConfirmedRenderFrameId= ${ self . lastAllConfirmedRenderFrameId } , lastUpsyncInputFrameId= ${ self . lastUpsyncInputFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } , chaserRenderFrameId= ${ self . chaserRenderFrameId } ; recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ) ;
2022-10-03 00:22:05 +08:00
2022-09-25 20:48:09 +08:00
for ( let i = self . recentInputCache . stFrameId ; i < self . recentInputCache . edFrameId ; ++ i ) {
2022-10-03 00:22:05 +08:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( i ) ;
2022-09-22 17:09:49 +08:00
s . push ( JSON . stringify ( inputFrameDownsync ) ) ;
2022-10-03 00:22:05 +08:00
}
2022-09-20 23:50:01 +08:00
console . log ( s . join ( '\n' ) ) ;
} ,
onBattleStopped ( ) {
const self = this ;
2022-10-03 00:22:05 +08:00
if ( ALL _BATTLE _STATES . IN _BATTLE != self . battleState ) {
return ;
}
2022-09-20 23:50:01 +08:00
self . countdownNanos = null ;
self . logBattleStats ( ) ;
if ( self . musicEffectManagerScriptIns ) {
self . musicEffectManagerScriptIns . stopAllMusic ( ) ;
}
const canvasNode = self . canvasNode ;
const resultPanelNode = self . resultPanelNode ;
const resultPanelScriptIns = resultPanelNode . getComponent ( "ResultPanel" ) ;
resultPanelScriptIns . showPlayerInfo ( self . playerRichInfoDict ) ;
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
self . battleState = ALL _BATTLE _STATES . IN _SETTLEMENT ;
self . showPopupInCanvas ( resultPanelNode ) ;
// Clear player info
self . playersInfoNode . getComponent ( "PlayersInfo" ) . clearInfo ( ) ;
} ,
2022-11-23 22:11:28 +08:00
spawnPlayerNode ( joinIndex , vx , vy , playerDownsyncInfo ) {
2022-09-24 12:01:50 +08:00
const self = this ;
2022-11-19 22:59:12 +08:00
const newPlayerNode = cc . instantiate ( self . controlledCharacterPrefab )
2022-11-19 20:58:07 +08:00
const playerScriptIns = newPlayerNode . getComponent ( "ControlledCharacter" ) ;
2022-11-19 22:59:12 +08:00
if ( 1 == joinIndex ) {
2022-11-22 17:12:51 +08:00
playerScriptIns . setSpecies ( "SoldierWaterGhost" ) ;
2022-11-20 18:53:33 +08:00
} else if ( 2 == joinIndex ) {
playerScriptIns . setSpecies ( "SoldierFireGhost" ) ;
2022-11-19 22:59:12 +08:00
}
2022-11-25 11:20:05 +08:00
const [ wx , wy ] = self . virtualGridToWorldPos ( vx , vy ) ;
newPlayerNode . setPosition ( wx , wy ) ;
2022-11-19 20:58:07 +08:00
playerScriptIns . mapNode = self . node ;
2022-11-29 21:32:18 +08:00
const colliderWidth = playerDownsyncInfo . colliderRadius * 2 ,
colliderHeight = playerDownsyncInfo . colliderRadius * 3 ;
2022-11-27 21:33:34 +08:00
const [ x0 , y0 ] = self . virtualGridToPolygonColliderAnchorPos ( vx , vy , colliderWidth , colliderHeight ) ,
pts = [ [ 0 , 0 ] , [ colliderWidth , 0 ] , [ colliderWidth , colliderHeight ] , [ 0 , colliderHeight ] ] ;
2022-09-20 23:50:01 +08:00
2022-11-11 13:27:48 +08:00
const newPlayerCollider = self . collisionSys . createPolygon ( x0 , y0 , pts ) ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
2022-11-23 22:11:28 +08:00
newPlayerCollider . data = playerDownsyncInfo ;
2022-11-11 13:27:48 +08:00
self . collisionSysMap . set ( collisionPlayerIndex , newPlayerCollider ) ;
2022-09-24 12:01:50 +08:00
2022-12-01 11:35:56 +08:00
console . log ( ` Created new player collider: joinIndex= ${ joinIndex } , colliderRadius= ${ playerDownsyncInfo . colliderRadius } ` ) ;
2022-09-24 12:01:50 +08:00
safelyAddChild ( self . node , newPlayerNode ) ;
2022-09-20 23:50:01 +08:00
setLocalZOrder ( newPlayerNode , 5 ) ;
newPlayerNode . active = true ;
2022-11-25 11:20:05 +08:00
playerScriptIns . updateCharacterAnim ( playerDownsyncInfo , null , true ) ;
2022-09-20 23:50:01 +08:00
return [ newPlayerNode , playerScriptIns ] ;
} ,
update ( dt ) {
const self = this ;
2022-09-25 20:48:09 +08:00
if ( ALL _BATTLE _STATES . IN _BATTLE == self . battleState ) {
2022-10-01 23:54:48 +08:00
const elapsedMillisSinceLastFrameIdTriggered = performance . now ( ) - self . lastRenderFrameIdTriggeredAt ;
2022-11-29 21:32:18 +08:00
if ( elapsedMillisSinceLastFrameIdTriggered < self . tooFastDtIntervalMillis ) {
// [WARNING] We should avoid a frontend ticking too fast to prevent cheating, as well as ticking too slow to cause a "resync avalanche" that impacts user experience!
2022-10-01 23:54:48 +08:00
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
2022-10-01 20:45:38 +08:00
return ;
}
2022-09-25 20:48:09 +08:00
try {
2022-10-03 00:22:05 +08:00
let st = performance . now ( ) ;
let prevSelfInput = null ,
currSelfInput = null ;
const noDelayInputFrameId = self . _convertToInputFrameId ( self . renderFrameId , 0 ) ; // It's important that "inputDelayFrames == 0" here
if ( self . shouldGenerateInputFrameUpsync ( self . renderFrameId ) ) {
2022-11-25 11:20:05 +08:00
[ prevSelfInput , currSelfInput ] = self . _generateInputFrameUpsync ( noDelayInputFrameId ) ;
2022-10-03 00:22:05 +08:00
}
2022-09-24 12:01:50 +08:00
2022-10-03 00:22:05 +08:00
let t0 = performance . now ( ) ;
if ( self . shouldSendInputFrameUpsyncBatch ( prevSelfInput , currSelfInput , self . lastUpsyncInputFrameId , noDelayInputFrameId ) ) {
// TODO: Is the following statement run asynchronously in an implicit manner? Should I explicitly run it asynchronously?
self . sendInputFrameUpsyncBatch ( noDelayInputFrameId ) ;
}
let t1 = performance . now ( ) ;
// Use "fractional-frame-chasing" to guarantee that "self.update(dt)" is not jammed by a "large range of frame-chasing". See `<proj-root>/ConcerningEdgeCases.md` for the motivation.
const prevChaserRenderFrameId = self . chaserRenderFrameId ;
let nextChaserRenderFrameId = ( prevChaserRenderFrameId + self . maxChasingRenderFramesPerUpdate ) ;
2022-10-03 11:42:19 +08:00
if ( nextChaserRenderFrameId > self . renderFrameId ) {
2022-10-03 00:22:05 +08:00
nextChaserRenderFrameId = self . renderFrameId ;
2022-10-03 11:42:19 +08:00
}
2022-11-11 13:27:48 +08:00
self . rollbackAndChase ( prevChaserRenderFrameId , nextChaserRenderFrameId , self . collisionSys , self . collisionSysMap , true ) ;
2022-10-03 00:22:05 +08:00
let t2 = performance . now ( ) ;
2022-11-11 13:27:48 +08: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-11-25 11:20:05 +08:00
const [ prevRdf , rdf ] = self . rollbackAndChase ( self . renderFrameId , self . renderFrameId + 1 , self . collisionSys , self . collisionSysMap , false ) ;
2022-10-10 14:33:04 +08:00
/*
const nonTrivialChaseEnded = (prevChaserRenderFrameId < nextChaserRenderFrameId && nextChaserRenderFrameId == self.renderFrameId);
if (nonTrivialChaseEnded) {
console.debug("Non-trivial chase ended, prevChaserRenderFrameId=" + prevChaserRenderFrameId + ", nextChaserRenderFrameId=" + nextChaserRenderFrameId);
}
*/
2022-11-25 11:20:05 +08:00
// [WARNING] Don't try to get "prevRdf(i.e. renderFrameId == latest-1)" by "self.recentRenderCache.getByFrameId(...)" here, as the cache might have been updated by asynchronous "onRoomDownsyncFrame(...)" calls!
self . applyRoomDownsyncFrameDynamics ( rdf , prevRdf ) ;
2022-11-29 21:32:18 +08:00
++ self . renderFrameId ; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
self . lastRenderFrameIdTriggeredAt = performance . now ( ) ;
2022-10-03 00:22:05 +08:00
let t3 = performance . now ( ) ;
2022-09-25 20:48:09 +08:00
} catch ( err ) {
console . error ( "Error during Map.update" , err ) ;
2022-11-29 21:32:18 +08:00
self . onBattleStopped ( ) ; // TODO: Popup to ask player to refresh browser
2022-09-25 20:48:09 +08:00
} finally {
2022-11-25 17:57:10 +08: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 20:48:09 +08:00
self . countdownLabel . string = countdownSeconds ;
2022-09-20 23:50:01 +08:00
}
}
}
} ,
transitToState ( s ) {
const self = this ;
self . state = s ;
} ,
logout ( byClick /* The case where this param is "true" will be triggered within `ConfirmLogout.js`.*/ , shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
const self = this ;
const localClearance = ( ) => {
window . clearLocalStorageAndBackToLoginScene ( shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) ;
}
const selfPlayerStr = cc . sys . localStorage . getItem ( "selfPlayer" ) ;
if ( null == selfPlayerStr ) {
localClearance ( ) ;
2022-10-03 00:22:05 +08:00
return ;
2022-09-20 23:50:01 +08:00
}
const selfPlayerInfo = JSON . parse ( selfPlayerStr ) ;
try {
NetworkUtils . ajax ( {
url : backendAddress . PROTOCOL + '://' + backendAddress . HOST + ':' + backendAddress . PORT + constants . ROUTE _PATH . API + constants . ROUTE _PATH . PLAYER + constants . ROUTE _PATH . VERSION + constants . ROUTE _PATH . INT _AUTH _TOKEN + constants . ROUTE _PATH . LOGOUT ,
type : "POST" ,
2022-10-03 00:22:05 +08:00
data : {
intAuthToken : selfPlayerInfo . intAuthToken
} ,
2022-09-20 23:50:01 +08:00
success : function ( res ) {
if ( res . ret != constants . RET _CODE . OK ) {
console . log ( "Logout failed: " , res ) ;
}
localClearance ( ) ;
} ,
error : function ( xhr , status , errMsg ) {
localClearance ( ) ;
} ,
timeout : function ( ) {
localClearance ( ) ;
}
} ) ;
} catch ( e ) { } finally {
// For Safari (both desktop and mobile).
localClearance ( ) ;
}
} ,
onLogoutClicked ( evt ) {
const self = this ;
self . showPopupInCanvas ( self . confirmLogoutNode ) ;
} ,
onLogoutConfirmationDismissed ( ) {
const self = this ;
self . transitToState ( ALL _MAP _STATES . VISUAL ) ;
const canvasNode = self . canvasNode ;
canvasNode . removeChild ( self . confirmLogoutNode ) ;
self . enableInputControls ( ) ;
} ,
onGameRule1v1ModeClicked ( evt , cb ) {
const self = this ;
2022-10-03 00:22:05 +08:00
self . battleState = ALL _BATTLE _STATES . WAITING ;
2022-09-20 23:50:01 +08:00
window . initPersistentSessionClient ( self . initAfterWSConnected , null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ ) ;
self . hideGameRuleNode ( ) ;
} ,
showPopupInCanvas ( toShowNode ) {
const self = this ;
self . disableInputControls ( ) ;
self . transitToState ( ALL _MAP _STATES . SHOWING _MODAL _POPUP ) ;
safelyAddChild ( self . widgetsAboveAllNode , toShowNode ) ;
setLocalZOrder ( toShowNode , 10 ) ;
} ,
2022-11-13 11:37:30 +08:00
hideFindingPlayersGUI ( rdf ) {
2022-10-04 11:24:47 +08:00
const self = this ;
if ( null == self . findingPlayerNode . parent ) return ;
self . findingPlayerNode . parent . removeChild ( self . findingPlayerNode ) ;
} ,
2022-11-09 23:46:11 +08:00
onBattleReadyToStart ( rdf ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-11-09 23:46:11 +08:00
const players = rdf . players ;
2022-11-21 17:27:32 +08:00
self . _initPlayerRichInfoDict ( players ) ;
2022-11-09 23:46:11 +08:00
// Show the top status indicators for IN_BATTLE
2022-11-19 20:58:07 +08:00
if ( self . playersInfoNode ) {
const playersInfoScriptIns = self . playersInfoNode . getComponent ( "PlayersInfo" ) ;
2022-11-21 17:27:32 +08:00
for ( let i in players ) {
playersInfoScriptIns . updateData ( players [ i ] ) ;
2022-11-19 20:58:07 +08:00
}
2022-11-09 23:46:11 +08:00
}
2022-11-21 17:27:32 +08:00
console . log ( "Calling `onBattleReadyToStart` with:" , players ) ;
2022-11-19 20:58:07 +08:00
if ( self . findingPlayerNode ) {
const findingPlayerScriptIns = self . findingPlayerNode . getComponent ( "FindingPlayer" ) ;
findingPlayerScriptIns . hideExitButton ( ) ;
2022-11-21 17:27:32 +08:00
findingPlayerScriptIns . updatePlayersInfo ( players ) ;
2022-11-19 20:58:07 +08:00
}
2022-09-26 10:36:46 +08:00
2022-10-04 11:24:47 +08:00
// Delay to hide the "finding player" GUI, then show a countdown clock
2022-11-19 20:58:07 +08:00
if ( self . countdownToBeginGameNode ) {
window . setTimeout ( ( ) => {
self . hideFindingPlayersGUI ( ) ;
const countDownScriptIns = self . countdownToBeginGameNode . getComponent ( "CountdownToBeginGame" ) ;
countDownScriptIns . setData ( ) ;
self . showPopupInCanvas ( self . countdownToBeginGameNode ) ;
} , 1500 ) ;
}
2022-09-20 23:50:01 +08:00
} ,
2022-11-25 11:20:05 +08:00
applyRoomDownsyncFrameDynamics ( rdf , prevRdf ) {
2022-09-20 23:50:01 +08:00
const self = this ;
2022-11-25 11:20:05 +08:00
for ( let [ playerId , playerRichInfo ] of self . playerRichInfoDict . entries ( ) ) {
2022-09-20 23:50:01 +08:00
const immediatePlayerInfo = rdf . players [ playerId ] ;
2022-11-25 11:20:05 +08:00
const prevRdfPlayer = ( null == prevRdf ? null : prevRdf . players [ playerId ] ) ;
const [ wx , wy ] = self . virtualGridToWorldPos ( immediatePlayerInfo . virtualGridX , immediatePlayerInfo . virtualGridY ) ;
//const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y));
playerRichInfo . node . setPosition ( wx , wy ) ;
2022-11-19 22:59:12 +08:00
playerRichInfo . scriptIns . updateSpeed ( immediatePlayerInfo . speed ) ;
2022-11-25 11:20:05 +08:00
playerRichInfo . scriptIns . updateCharacterAnim ( immediatePlayerInfo , prevRdfPlayer , false ) ;
}
2022-11-29 21:32:18 +08:00
// Update countdown
self . countdownNanos = self . battleDurationNanos - self . renderFrameId * self . rollbackEstimatedDtNanos ;
if ( self . countdownNanos <= 0 ) {
self . onBattleStopped ( self . playerRichInfoDict ) ;
}
2022-10-03 00:22:05 +08:00
} ,
2022-09-20 23:50:01 +08:00
2022-09-24 12:01:50 +08:00
getCachedInputFrameDownsyncWithPrediction ( inputFrameId ) {
2022-09-22 12:45:17 +08:00
const self = this ;
2022-11-29 21:32:18 +08:00
const inputFrameDownsync = self . recentInputCache . getByFrameId ( inputFrameId ) ;
const lastAllConfirmedInputFrame = self . recentInputCache . getByFrameId ( self . lastAllConfirmedInputFrameId ) ;
if ( null != inputFrameDownsync && null != lastAllConfirmedInputFrame && inputFrameId > self . lastAllConfirmedInputFrameId ) {
2022-09-22 12:45:17 +08:00
for ( let i = 0 ; i < inputFrameDownsync . inputList . length ; ++ i ) {
2022-11-29 21:32:18 +08:00
if ( i == ( self . selfPlayerInfo . joinIndex - 1 ) ) continue ;
2022-11-24 19:45:48 +08:00
inputFrameDownsync . inputList [ i ] = ( lastAllConfirmedInputFrame . inputList [ i ] & 15 ) ; // Don't predict attack input!
2022-09-22 12:45:17 +08:00
}
2022-10-03 00:22:05 +08:00
}
2022-09-22 12:45:17 +08:00
return inputFrameDownsync ;
} ,
2022-11-11 00:01:53 +08:00
// TODO: Write unit-test for this function to compare with its backend counter part
applyInputFrameDownsyncDynamicsOnSingleRenderFrame ( delayedInputFrame , currRenderFrame , collisionSys , collisionSysMap ) {
2022-11-11 13:27:48 +08:00
const self = this ;
2022-11-23 22:11:28 +08:00
const nextRenderFramePlayers = { } ;
2022-11-11 00:01:53 +08:00
for ( let playerId in currRenderFrame . players ) {
const currPlayerDownsync = currRenderFrame . players [ playerId ] ;
nextRenderFramePlayers [ playerId ] = {
id : playerId ,
virtualGridX : currPlayerDownsync . virtualGridX ,
virtualGridY : currPlayerDownsync . virtualGridY ,
2022-11-23 22:11:28 +08:00
dirX : currPlayerDownsync . dirX ,
dirY : currPlayerDownsync . dirY ,
2022-11-23 15:04:32 +08:00
characterState : currPlayerDownsync . characterState ,
2022-11-11 00:01:53 +08:00
speed : currPlayerDownsync . speed ,
battleState : currPlayerDownsync . battleState ,
score : currPlayerDownsync . score ,
removed : currPlayerDownsync . removed ,
joinIndex : currPlayerDownsync . joinIndex ,
2022-11-23 15:04:32 +08:00
framesToRecover : ( 0 < currPlayerDownsync . framesToRecover ? currPlayerDownsync . framesToRecover - 1 : 0 ) ,
hp : currPlayerDownsync . hp ,
maxHp : currPlayerDownsync . maxHp ,
2022-11-11 00:01:53 +08:00
} ;
2022-09-26 10:36:46 +08:00
}
2022-10-03 11:42:19 +08:00
2022-11-29 21:32:18 +08:00
const nextRenderFrameMeleeBullets = [ ] ;
2022-09-21 23:59:05 +08:00
2022-11-23 15:04:32 +08:00
const bulletPushbacks = new Array ( self . playerRichInfoArr . length ) ; // Guaranteed determinism regardless of traversal order
const effPushbacks = new Array ( self . playerRichInfoArr . length ) ; // Guaranteed determinism regardless of traversal order
2022-11-23 22:11:28 +08:00
// Reset playerCollider position from the "virtual grid position"
2022-11-23 15:04:32 +08:00
for ( let j in self . playerRichInfoArr ) {
const joinIndex = parseInt ( j ) + 1 ;
bulletPushbacks [ joinIndex - 1 ] = [ 0.0 , 0.0 ] ;
effPushbacks [ joinIndex - 1 ] = [ 0.0 , 0.0 ] ;
const playerRichInfo = self . playerRichInfoArr [ j ] ;
const playerId = playerRichInfo . id ;
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-11-23 22:11:28 +08:00
const currPlayerDownsync = currRenderFrame . players [ playerId ] ;
2022-11-23 15:04:32 +08:00
2022-11-23 22:11:28 +08:00
const newVx = currPlayerDownsync . virtualGridX ;
const newVy = currPlayerDownsync . virtualGridY ;
2022-11-25 11:20:05 +08:00
[ playerCollider . x , playerCollider . y ] = self . virtualGridToPolygonColliderAnchorPos ( newVx , newVy , self . playerRichInfoArr [ joinIndex - 1 ] . colliderRadius , self . playerRichInfoArr [ joinIndex - 1 ] . colliderRadius ) ;
2022-11-23 15:04:32 +08:00
}
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
2022-11-23 22:11:28 +08:00
const bulletColliders = new Map ( ) ; // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
2022-11-23 15:04:32 +08:00
const removedBulletsAtCurrFrame = new Set ( ) ;
for ( let k in currRenderFrame . meleeBullets ) {
const meleeBullet = currRenderFrame . meleeBullets [ k ] ;
if (
meleeBullet . originatedRenderFrameId + meleeBullet . startupFrames <= currRenderFrame . id
&&
meleeBullet . originatedRenderFrameId + meleeBullet . startupFrames + meleeBullet . activeFrames > currRenderFrame . id
) {
const collisionBulletIndex = self . collisionBulletIndexPrefix + meleeBullet . battleLocalId ;
const collisionOffenderIndex = self . collisionPlayerIndexPrefix + meleeBullet . offenderJoinIndex ;
const offenderCollider = collisionSysMap . get ( collisionOffenderIndex ) ;
const offender = currRenderFrame . players [ meleeBullet . offenderPlayerId ] ;
2022-11-25 08:21:03 +08:00
let xfac = 1 ; // By now, straight Punch offset doesn't respect "y-axis"
2022-11-23 22:11:28 +08:00
if ( 0 > offender . dirX ) {
xfac = - 1 ;
}
2022-11-25 11:20:05 +08:00
const [ offenderWx , offenderWy ] = self . virtualGridToWorldPos ( offender . virtualGridX , offender . virtualGridY ) ;
const bulletWx = offenderWx + xfac * meleeBullet . hitboxOffset ;
const bulletWy = offenderWy ;
2022-11-25 13:24:03 +08:00
const [ bulletCx , bulletCy ] = self . worldToPolygonColliderAnchorPos ( bulletWx , bulletWy , meleeBullet . hitboxSize . x * 0.5 , meleeBullet . hitboxSize . y * 0.5 ) ,
pts = [ [ 0 , 0 ] , [ meleeBullet . hitboxSize . x , 0 ] , [ meleeBullet . hitboxSize . x , meleeBullet . hitboxSize . y ] , [ 0 , meleeBullet . hitboxSize . y ] ] ;
2022-11-25 11:20:05 +08:00
const newBulletCollider = collisionSys . createPolygon ( bulletCx , bulletCy , pts ) ;
2022-11-23 22:11:28 +08:00
newBulletCollider . data = meleeBullet ;
2022-11-23 15:04:32 +08:00
collisionSysMap . set ( collisionBulletIndex , newBulletCollider ) ;
2022-11-23 22:11:28 +08:00
bulletColliders . set ( collisionBulletIndex , newBulletCollider ) ;
2022-11-25 13:24:03 +08:00
// console.log(`A meleeBullet is added to collisionSys at currRenderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended: ${JSON.stringify(meleeBullet)}`);
2022-11-23 15:04:32 +08:00
}
}
collisionSys . update ( ) ;
const result1 = collisionSys . createResult ( ) ; // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
2022-11-23 22:11:28 +08:00
bulletColliders . forEach ( ( bulletCollider , collisionBulletIndex ) => {
2022-11-23 15:04:32 +08:00
const potentials = bulletCollider . potentials ( ) ;
2022-11-24 17:48:07 +08:00
const offender = currRenderFrame . players [ bulletCollider . data . offenderPlayerId ] ;
2022-11-23 15:04:32 +08:00
let shouldRemove = false ;
for ( const potential of potentials ) {
2022-11-23 22:11:28 +08:00
if ( null != potential . data && potential . data . joinIndex == bulletCollider . data . offenderJoinIndex ) continue ;
2022-11-23 15:04:32 +08:00
if ( ! bulletCollider . collides ( potential , result1 ) ) continue ;
2022-11-23 22:11:28 +08:00
if ( null != potential . data && null !== potential . data . joinIndex ) {
const joinIndex = potential . data . joinIndex ;
2022-11-24 17:48:07 +08:00
let xfac = 1 ;
if ( 0 > offender . dirX ) {
xfac = - 1 ;
}
bulletPushbacks [ joinIndex - 1 ] [ 0 ] += xfac * bulletCollider . data . pushback ; // Only for straight punch, there's no y-pushback
2022-11-23 22:11:28 +08:00
bulletPushbacks [ joinIndex - 1 ] [ 1 ] += 0 ;
const thatAckedPlayerInNextFrame = nextRenderFramePlayers [ potential . data . id ] ;
2022-11-23 15:04:32 +08:00
thatAckedPlayerInNextFrame . characterState = window . ATK _CHARACTER _STATE . Atked1 [ 0 ] ;
2022-11-24 17:48:07 +08:00
const oldFramesToRecover = thatAckedPlayerInNextFrame . framesToRecover ;
thatAckedPlayerInNextFrame . framesToRecover = ( oldFramesToRecover > bulletCollider . data . hitStunFrames ? oldFramesToRecover : bulletCollider . data . hitStunFrames ) ; // In case the hit player is already stun, we extend it
2022-11-23 15:04:32 +08:00
}
shouldRemove = true ;
}
if ( shouldRemove ) {
removedBulletsAtCurrFrame . add ( collisionBulletIndex ) ;
}
} ) ;
2022-11-23 22:11:28 +08:00
// [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback
2022-11-23 15:04:32 +08:00
for ( let k in currRenderFrame . meleeBullets ) {
const meleeBullet = currRenderFrame . meleeBullets [ k ] ;
const collisionBulletIndex = self . collisionBulletIndexPrefix + meleeBullet . battleLocalId ;
if ( collisionSysMap . has ( collisionBulletIndex ) ) {
const bulletCollider = collisionSysMap . get ( collisionBulletIndex ) ;
bulletCollider . remove ( ) ;
collisionSysMap . delete ( collisionBulletIndex ) ;
}
if ( removedBulletsAtCurrFrame . has ( collisionBulletIndex ) ) continue ;
2022-11-29 21:32:18 +08:00
nextRenderFrameMeleeBullets . push ( meleeBullet ) ;
2022-11-23 15:04:32 +08:00
}
2022-11-23 22:11:28 +08:00
// Process player inputs
2022-11-11 00:01:53 +08:00
if ( null != delayedInputFrame ) {
2022-11-23 15:04:32 +08:00
const delayedInputFrameForPrevRenderFrame = self . getCachedInputFrameDownsyncWithPrediction ( self . _convertToInputFrameId ( currRenderFrame . id - 1 , self . inputDelayFrames ) ) ;
2022-11-11 00:01:53 +08:00
const inputList = delayedInputFrame . inputList ;
2022-09-29 12:21:04 +08:00
for ( let j in self . playerRichInfoArr ) {
2022-10-03 00:22:05 +08:00
const joinIndex = parseInt ( j ) + 1 ;
2022-11-11 20:10:43 +08:00
effPushbacks [ joinIndex - 1 ] = [ 0.0 , 0.0 ] ;
2022-11-23 15:04:32 +08:00
const playerRichInfo = self . playerRichInfoArr [ j ] ;
const playerId = playerRichInfo . id ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-11-23 22:11:28 +08:00
const currPlayerDownsync = currRenderFrame . players [ playerId ] ;
const thatPlayerInNextFrame = nextRenderFramePlayers [ playerId ] ;
if ( 0 < thatPlayerInNextFrame . framesToRecover ) {
2022-11-23 15:04:32 +08:00
// No need to process inputs for this player, but there might be bullet pushbacks on this player
2022-11-23 22:11:28 +08:00
playerCollider . x += bulletPushbacks [ joinIndex - 1 ] [ 0 ] ;
playerCollider . y += bulletPushbacks [ joinIndex - 1 ] [ 1 ] ;
2022-11-23 15:04:32 +08:00
if ( 0 != bulletPushbacks [ joinIndex - 1 ] [ 0 ] || 0 != bulletPushbacks [ joinIndex - 1 ] [ 1 ] ) {
2022-11-24 19:45:48 +08:00
console . log ( ` playerId= ${ playerId } , joinIndex= ${ joinIndex } is pushbacked back by ${ bulletPushbacks [ joinIndex - 1 ] } by bullet impacts, now its framesToRecover is ${ thatPlayerInNextFrame . framesToRecover } ` ) ;
2022-11-23 15:04:32 +08:00
}
continue ;
}
2022-11-09 23:53:45 +08:00
2022-11-20 18:53:33 +08:00
const decodedInput = self . ctrl . decodeInput ( inputList [ joinIndex - 1 ] ) ;
2022-11-10 18:18:00 +08:00
2022-11-23 15:04:32 +08:00
const prevDecodedInput = ( null == delayedInputFrameForPrevRenderFrame ? null : self . ctrl . decodeInput ( delayedInputFrameForPrevRenderFrame . inputList [ joinIndex - 1 ] ) ) ;
const prevBtnALevel = ( null == prevDecodedInput ? 0 : prevDecodedInput . btnALevel ) ;
if ( 1 == decodedInput . btnALevel && 0 == prevBtnALevel ) {
2022-11-23 22:11:28 +08:00
// console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
if ( self . bulletTriggerEnabled ) {
2022-11-24 19:45:48 +08:00
const punchSkillId = 1 ;
const punch = window . pb . protos . MeleeBullet . create ( self . meleeSkillConfig [ punchSkillId ] ) ;
thatPlayerInNextFrame . framesToRecover = punch . recoveryFrames ;
2022-11-23 22:11:28 +08:00
punch . battleLocalId = self . bulletBattleLocalIdCounter ++ ;
punch . offenderJoinIndex = joinIndex ;
punch . offenderPlayerId = playerId ;
punch . originatedRenderFrameId = currRenderFrame . id ;
2022-11-29 21:32:18 +08:00
nextRenderFrameMeleeBullets . push ( punch ) ;
2022-11-24 19:45:48 +08:00
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
2022-11-23 22:11:28 +08:00
thatPlayerInNextFrame . characterState = window . ATK _CHARACTER _STATE . Atk1 [ 0 ] ;
}
2022-11-23 15:04:32 +08:00
} else if ( 0 == decodedInput . btnALevel && 1 == prevBtnALevel ) {
2022-11-23 22:11:28 +08:00
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
2022-11-23 15:04:32 +08:00
} else {
2022-11-24 17:48:07 +08:00
// No bullet trigger, process movement inputs
2022-11-23 15:04:32 +08:00
if ( 0 != decodedInput . dx || 0 != decodedInput . dy ) {
// Update directions and thus would eventually update moving animation accordingly
2022-11-23 22:11:28 +08:00
thatPlayerInNextFrame . dirX = decodedInput . dx ;
thatPlayerInNextFrame . dirY = decodedInput . dy ;
thatPlayerInNextFrame . characterState = window . ATK _CHARACTER _STATE . Walking [ 0 ] ;
2022-11-23 15:04:32 +08:00
} else {
2022-11-23 22:11:28 +08:00
thatPlayerInNextFrame . characterState = window . ATK _CHARACTER _STATE . Idle1 [ 0 ] ;
2022-11-23 15:04:32 +08:00
}
2022-11-25 11:20:05 +08:00
const [ movementX , movementY ] = self . virtualGridToWorldPos ( decodedInput . dx + currPlayerDownsync . speed * decodedInput . dx , decodedInput . dy + currPlayerDownsync . speed * decodedInput . dy ) ;
playerCollider . x += movementX ;
playerCollider . y += movementY ;
2022-11-21 17:27:32 +08:00
}
2022-09-29 12:21:04 +08:00
}
2022-09-24 12:01:50 +08:00
2022-11-23 15:04:32 +08:00
collisionSys . update ( ) ; // by now all "bulletCollider"s are removed
const result2 = collisionSys . createResult ( ) ; // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
2022-10-03 00:22:05 +08:00
2022-11-11 00:01:53 +08:00
for ( let j in self . playerRichInfoArr ) {
const joinIndex = parseInt ( j ) + 1 ;
const playerId = self . playerRichInfoArr [ j ] . id ;
2022-09-24 12:01:50 +08:00
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
const potentials = playerCollider . potentials ( ) ;
2022-11-08 21:38:23 +08:00
for ( const potential of potentials ) {
2022-09-24 12:01:50 +08:00
// Test if the player collides with the wall
2022-11-23 15:04:32 +08:00
if ( ! playerCollider . collides ( potential , result2 ) ) continue ;
2022-09-24 12:01:50 +08:00
// Push the player out of the wall
2022-11-23 15:04:32 +08:00
effPushbacks [ joinIndex - 1 ] [ 0 ] += result2 . overlap * result2 . overlap _x ;
effPushbacks [ joinIndex - 1 ] [ 1 ] += result2 . overlap * result2 . overlap _y ;
2022-09-24 12:01:50 +08:00
}
2022-11-11 13:27:48 +08:00
}
for ( let j in self . playerRichInfoArr ) {
const joinIndex = parseInt ( j ) + 1 ;
const playerId = self . playerRichInfoArr [ j ] . id ;
const collisionPlayerIndex = self . collisionPlayerIndexPrefix + joinIndex ;
const playerCollider = collisionSysMap . get ( collisionPlayerIndex ) ;
2022-11-23 22:11:28 +08:00
const thatPlayerInNextFrame = nextRenderFramePlayers [ playerId ] ;
2022-11-25 11:20:05 +08:00
[ thatPlayerInNextFrame . virtualGridX , thatPlayerInNextFrame . virtualGridY ] = self . polygonColliderAnchorToVirtualGridPos ( playerCollider . x - effPushbacks [ joinIndex - 1 ] [ 0 ] , playerCollider . y - effPushbacks [ joinIndex - 1 ] [ 1 ] , self . playerRichInfoArr [ j ] . colliderRadius , self . playerRichInfoArr [ j ] . colliderRadius ) ;
2022-11-11 00:01:53 +08:00
}
2022-11-23 15:04:32 +08:00
2022-11-11 00:01:53 +08:00
}
2022-11-29 21:32:18 +08:00
return window . pb . protos . RoomDownsyncFrame . create ( {
id : currRenderFrame . id + 1 ,
players : nextRenderFramePlayers ,
meleeBullets : nextRenderFrameMeleeBullets ,
} ) ;
2022-11-11 00:01:53 +08:00
} ,
2022-11-11 13:27:48 +08:00
rollbackAndChase ( renderFrameIdSt , renderFrameIdEd , collisionSys , collisionSysMap , isChasing ) {
2022-11-11 00:01:53 +08:00
/*
2022-11-11 13:27:48 +08:00
This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted.
2022-11-11 00:01:53 +08:00
*/
const self = this ;
2022-11-29 21:32:18 +08:00
let i = renderFrameIdSt ,
prevLatestRdf = null ,
latestRdf = null ;
2022-11-11 00:01:53 +08:00
2022-11-29 21:32:18 +08:00
do {
latestRdf = self . recentRenderCache . getByFrameId ( i ) ; // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"!
if ( null == latestRdf ) {
2022-11-11 23:43:51 +08:00
console . warn ( ` Couldn't find renderFrame for i= ${ i } to rollback, self.renderFrameId= ${ self . renderFrameId } , lastAllConfirmedRenderFrameId= ${ self . lastAllConfirmedRenderFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } , might've been interruptted by onRoomDownsyncFrame ` ) ;
2022-11-25 11:20:05 +08:00
return [ prevLatestRdf , latestRdf ] ;
2022-11-11 00:01:53 +08:00
}
const j = self . _convertToInputFrameId ( i , self . inputDelayFrames ) ;
const delayedInputFrame = self . getCachedInputFrameDownsyncWithPrediction ( j ) ;
if ( null == delayedInputFrame ) {
2022-11-29 21:32:18 +08:00
// Shouldn't happen!
throw ` Failed to get cached delayedInputFrame for i= ${ i } , j= ${ j } , renderFrameId= ${ self . renderFrameId } , lastAllConfirmedRenderFrameId= ${ self . lastAllConfirmedRenderFrameId } , lastUpsyncInputFrameId= ${ self . lastUpsyncInputFrameId } , lastAllConfirmedInputFrameId= ${ self . lastAllConfirmedInputFrameId } , chaserRenderFrameId= ${ self . chaserRenderFrameId } ; recentRenderCache= ${ self . _stringifyRecentRenderCache ( false ) } , recentInputCache= ${ self . _stringifyRecentInputCache ( false ) } ` ;
2022-09-26 11:16:18 +08:00
}
2022-11-25 11:20:05 +08:00
prevLatestRdf = latestRdf ;
2022-11-29 21:32:18 +08:00
latestRdf = self . applyInputFrameDownsyncDynamicsOnSingleRenderFrame ( delayedInputFrame , prevLatestRdf , collisionSys , collisionSysMap ) ;
2022-11-11 00:01:53 +08:00
if (
self . _allConfirmed ( delayedInputFrame . confirmedList )
&&
latestRdf . id > self . lastAllConfirmedRenderFrameId
) {
// We got a more up-to-date "all-confirmed-render-frame".
self . lastAllConfirmedRenderFrameId = latestRdf . id ;
if ( latestRdf . id > self . chaserRenderFrameId ) {
2022-11-11 13:27:48 +08:00
// it must be true that "chaserRenderFrameId >= lastAllConfirmedRenderFrameId", regardeless of the "isChasing" param
2022-11-11 00:01:53 +08:00
self . chaserRenderFrameId = latestRdf . id ;
}
}
2022-11-11 13:27:48 +08:00
if ( true == isChasing ) {
// Move the cursor "self.chaserRenderFrameId", keep in mind that "self.chaserRenderFrameId" is not monotonic!
self . chaserRenderFrameId = latestRdf . id ;
}
2022-12-01 11:35:56 +08:00
self . recentRenderCache . setByFrameId ( latestRdf , latestRdf . id ) ;
2022-11-29 21:32:18 +08:00
++ i ;
} while ( i < renderFrameIdEd ) ;
2022-10-03 00:22:05 +08:00
2022-11-25 11:20:05 +08:00
return [ prevLatestRdf , latestRdf ] ;
2022-10-03 00:22:05 +08:00
} ,
2022-11-21 17:27:32 +08:00
_initPlayerRichInfoDict ( players ) {
2022-09-20 23:50:01 +08:00
const self = this ;
for ( let k in players ) {
const playerId = parseInt ( k ) ;
2022-09-22 12:45:17 +08:00
if ( self . playerRichInfoDict . has ( playerId ) ) continue ; // Skip already put keys
2022-09-20 23:50:01 +08:00
const immediatePlayerInfo = players [ playerId ] ;
2022-10-03 00:22:05 +08:00
self . playerRichInfoDict . set ( playerId , immediatePlayerInfo ) ;
2022-11-09 23:46:11 +08:00
2022-11-25 11:20:05 +08:00
const [ theNode , theScriptIns ] = self . spawnPlayerNode ( immediatePlayerInfo . joinIndex , immediatePlayerInfo . virtualGridX , immediatePlayerInfo . virtualGridY , immediatePlayerInfo ) ;
2022-09-20 23:50:01 +08:00
2022-09-22 12:45:17 +08:00
Object . assign ( self . playerRichInfoDict . get ( playerId ) , {
2022-11-25 11:20:05 +08:00
node : theNode ,
scriptIns : theScriptIns ,
2022-09-20 23:50:01 +08:00
} ) ;
if ( self . selfPlayerInfo . id == playerId ) {
2022-10-03 00:22:05 +08:00
self . selfPlayerInfo = Object . assign ( self . selfPlayerInfo , immediatePlayerInfo ) ;
2022-11-25 11:20:05 +08:00
theScriptIns . showArrowTipNode ( ) ;
2022-09-20 23:50:01 +08:00
}
}
2022-09-26 11:16:18 +08:00
self . playerRichInfoArr = new Array ( self . playerRichInfoDict . size ) ;
self . playerRichInfoDict . forEach ( ( playerRichInfo , playerId ) => {
2022-10-03 00:22:05 +08:00
self . playerRichInfoArr [ playerRichInfo . joinIndex - 1 ] = playerRichInfo ;
} ) ;
2022-09-20 23:50:01 +08:00
} ,
_stringifyRecentInputCache ( usefullOutput ) {
2022-09-26 10:36:46 +08:00
const self = this ;
2022-09-20 23:50:01 +08:00
if ( true == usefullOutput ) {
2022-09-22 17:09:49 +08:00
let s = [ ] ;
2022-09-26 10:36:46 +08:00
for ( let i = self . recentInputCache . stFrameId ; i < self . recentInputCache . edFrameId ; ++ i ) {
s . push ( JSON . stringify ( self . recentInputCache . getByFrameId ( i ) ) ) ;
}
2022-09-22 17:09:49 +08:00
return s . join ( '\n' ) ;
2022-09-20 23:50:01 +08:00
}
2022-11-11 23:43:51 +08:00
return ` [stInputFrameId= ${ self . recentInputCache . stFrameId } , edInputFrameId= ${ self . recentInputCache . edFrameId } ) ` ;
2022-09-20 23:50:01 +08:00
} ,
2022-09-21 17:22:34 +08:00
2022-09-26 10:36:46 +08:00
_stringifyRecentRenderCache ( usefullOutput ) {
2022-09-21 17:22:34 +08:00
const self = this ;
2022-09-26 10:36:46 +08:00
if ( true == usefullOutput ) {
let s = [ ] ;
for ( let i = self . recentRenderCache . stFrameId ; i < self . recentRenderCache . edFrameId ; ++ i ) {
s . push ( JSON . stringify ( self . recentRenderCache . getByFrameId ( i ) ) ) ;
2022-09-21 17:22:34 +08:00
}
2022-09-26 10:36:46 +08:00
return s . join ( '\n' ) ;
}
2022-11-11 23:43:51 +08:00
return ` [stRenderFrameId= ${ self . recentRenderCache . stFrameId } , edRenderFrameId= ${ self . recentRenderCache . edFrameId } ) ` ;
2022-09-21 17:22:34 +08:00
} ,
2022-09-26 10:36:46 +08:00
2022-11-09 18:13:53 +08:00
worldToVirtualGridPos ( x , y ) {
2022-11-10 18:18:00 +08:00
// [WARNING] Introduces loss of precision!
2022-11-09 18:13:53 +08:00
const self = this ;
// In JavaScript floating numbers suffer from seemingly non-deterministic arithmetics, and even if certain libs solved this issue by approaches such as fixed-point-number, they might not be used in other libs -- e.g. the "collision libs" we're interested in -- thus couldn't kill all pains.
2022-11-10 18:18:00 +08:00
let virtualGridX = Math . round ( x * self . worldToVirtualGridRatio ) ;
let virtualGridY = Math . round ( y * self . worldToVirtualGridRatio ) ;
2022-11-09 18:13:53 +08:00
return [ virtualGridX , virtualGridY ] ;
} ,
virtualGridToWorldPos ( vx , vy ) {
2022-11-10 18:18:00 +08:00
// No loss of precision
2022-11-09 18:13:53 +08:00
const self = this ;
let wx = parseFloat ( vx ) * self . virtualGridToWorldRatio ;
let wy = parseFloat ( vy ) * self . virtualGridToWorldRatio ;
return [ wx , wy ] ;
} ,
2022-11-25 08:21:03 +08:00
worldToPolygonColliderAnchorPos ( wx , wy , halfBoundingW , halfBoundingH ) {
return [ wx - halfBoundingW , wy - halfBoundingH ] ;
2022-11-09 18:13:53 +08:00
} ,
2022-11-25 08:21:03 +08:00
polygonColliderAnchorToWorldPos ( cx , cy , halfBoundingW , halfBoundingH ) {
return [ cx + halfBoundingW , cy + halfBoundingH ] ;
2022-11-09 18:13:53 +08:00
} ,
2022-11-25 08:21:03 +08:00
polygonColliderAnchorToVirtualGridPos ( cx , cy , halfBoundingW , halfBoundingH ) {
2022-11-09 18:13:53 +08:00
const self = this ;
2022-11-25 11:20:05 +08:00
const [ wx , wy ] = self . polygonColliderAnchorToWorldPos ( cx , cy , halfBoundingW , halfBoundingH ) ;
return self . worldToVirtualGridPos ( wx , wy )
2022-11-09 18:13:53 +08:00
} ,
2022-11-25 08:21:03 +08:00
virtualGridToPolygonColliderAnchorPos ( vx , vy , halfBoundingW , halfBoundingH ) {
2022-11-09 18:13:53 +08:00
const self = this ;
2022-11-25 11:20:05 +08:00
const [ wx , wy ] = self . virtualGridToWorldPos ( vx , vy ) ;
return self . worldToPolygonColliderAnchorPos ( wx , wy , halfBoundingW , halfBoundingH )
2022-11-09 18:13:53 +08:00
} ,
2022-09-20 23:50:01 +08:00
} ) ;