Temp broken commit during refactoration of jsexport inputbuffer handling.

This commit is contained in:
genxium 2022-12-28 18:06:05 +08:00
parent c017aaa7ed
commit f4868197b6
12 changed files with 6142 additions and 171 deletions

File diff suppressed because one or more lines are too long

View File

@ -139,14 +139,12 @@ message BattleColliderInfo {
int32 spAtkLookupFrames = 22; int32 spAtkLookupFrames = 22;
int32 renderCacheSize = 23; int32 renderCacheSize = 23;
map<int32, MeleeBullet> meleeSkillConfig = 24; // skillId -> skill double snapIntoPlatformOverlap = 24;
double snapIntoPlatformThreshold = 25;
double snapIntoPlatformOverlap = 25; int32 jumpingInitVelY = 26;
double snapIntoPlatformThreshold = 26; int32 gravityX = 27;
int32 jumpingInitVelY = 27; int32 gravityY = 28;
int32 gravityX = 28; int32 collisionMinStep = 29;
int32 gravityY = 29;
int32 collisionMinStep = 30;
bool frameDataLoggingEnabled = 999; bool frameDataLoggingEnabled = 999;
} }

View File

@ -440,7 +440,7 @@
"array": [ "array": [
0, 0,
0, 0,
216.79265527990535, 210.2224973754617,
0, 0,
0, 0,
0, 0,

View File

@ -461,7 +461,7 @@
"array": [ "array": [
0, 0,
0, 0,
216.79265527990535, 213.12914590737552,
0, 0,
0, 0,
0, 0,

View File

@ -67,7 +67,6 @@ cc.Class({
this.speciesName = null; this.speciesName = null;
this.hp = 100; this.hp = 100;
this.maxHp = 100; this.maxHp = 100;
this.framesToRecover = 0;
this.inAir = true; this.inAir = true;
}, },
@ -86,18 +85,18 @@ cc.Class({
}, },
updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) { updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) {
// As this function might be called after many frames of a rollback, it's possible that the playing animation was predicted, different from "prevRdfPlayer.characterState" but same as "newCharacterState". More granular checks are needed to determine whether we should interrupt the playing animation. // As this function might be called after many frames of a rollback, it's possible that the playing animation was predicted, different from "prevRdfPlayer.CharacterState" but same as "newCharacterState". More granular checks are needed to determine whether we should interrupt the playing animation.
// Update directions // Update directions
if (this.animComp && this.animComp.node) { if (this.animComp && this.animComp.node) {
if (0 > rdfPlayer.dirX) { if (0 > rdfPlayer.DirX) {
this.animComp.node.scaleX = (-1.0); this.animComp.node.scaleX = (-1.0);
} else if (0 < rdfPlayer.dirX) { } else if (0 < rdfPlayer.DirX) {
this.animComp.node.scaleX = (1.0); this.animComp.node.scaleX = (1.0);
} }
} }
let newCharacterState = rdfPlayer.characterState; let newCharacterState = rdfPlayer.CharacterState;
let newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1]; let newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1];
let playingAnimName = null; let playingAnimName = null;
let underlyingAnimationCtrl = null; let underlyingAnimationCtrl = null;
@ -110,7 +109,7 @@ cc.Class({
playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name); playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name);
} }
// It turns out that "prevRdfPlayer.characterState" is not useful in this function :) // It turns out that "prevRdfPlayer.CharacterState" is not useful in this function :)
if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) { if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
// No need to interrupt // No need to interrupt
// console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`); // console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`);
@ -131,7 +130,7 @@ cc.Class({
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1); underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
} else { } else {
const animationData = underlyingAnimationCtrl._animations[newAnimName]; const animationData = underlyingAnimationCtrl._animations[newAnimName];
let fromAnimFrame = (animationData.frameCount - rdfPlayer.framesToRecover); let fromAnimFrame = (animationData.frameCount - rdfPlayer.FramesToRecover);
if (fromAnimFrame < 0) { if (fromAnimFrame < 0) {
// For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames // For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames
fromAnimFrame = 0; fromAnimFrame = 0;
@ -149,7 +148,7 @@ cc.Class({
} }
// The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor. // The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor.
const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE
let fromTime = (targetClip.duration - rdfPlayer.framesToRecover / targetClip.sample); // TODO: Anyway to avoid using division here? let fromTime = (targetClip.duration - rdfPlayer.FramesToRecover / targetClip.sample); // TODO: Anyway to avoid using division here?
if (fromTime < 0) { if (fromTime < 0) {
// For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames // For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames
fromTime = 0; fromTime = 0;

View File

@ -25,7 +25,7 @@ cc.Class({
if (!self.mapScriptIns) return; if (!self.mapScriptIns) return;
if (!self.mapScriptIns.selfPlayerInfo) return; if (!self.mapScriptIns.selfPlayerInfo) return;
if (!self.mapScriptIns.playerRichInfoDict) return; if (!self.mapScriptIns.playerRichInfoDict) return;
const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.id); const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.Id);
if (!selfPlayerRichInfo) return; if (!selfPlayerRichInfo) return;
const selfPlayerNode = selfPlayerRichInfo.node; const selfPlayerNode = selfPlayerRichInfo.node;
if (!selfPlayerNode) return; if (!selfPlayerNode) return;

View File

@ -140,28 +140,24 @@ cc.Class({
let previousSelfInput = null, let previousSelfInput = null,
currSelfInput = null; currSelfInput = null;
const joinIndex = self.selfPlayerInfo.joinIndex || self.selfPlayerInfo.JoinIndex; const joinIndex = self.selfPlayerInfo.JoinIndex;
const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId); const existingInputFrame = self.recentInputCache.GetByFrameId(inputFrameId);
const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1); const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1);
previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]); previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.InputList[joinIndex - 1]);
if (null != existingInputFrame) { if (null != existingInputFrame) {
// 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 // 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
console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`); console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`);
return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]]; return [previousSelfInput, existingInputFrame.InputList[joinIndex - 1]];
} }
const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice()); const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.InputList.slice());
currSelfInput = self.ctrl.getEncodedInput(); // When "null == existingInputFrame", it'd be safe to say that the realtime "self.ctrl.getEncodedInput()" is for the requested "inputFrameId" 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; prefabbedInputList[(joinIndex - 1)] = currSelfInput;
while (self.recentInputCache.edFrameId <= inputFrameId) { while (self.recentInputCache.EdFrameId <= inputFrameId) {
// Fill the gap // Fill the gap
const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({ const prefabbedInputFrameDownsync = gopkgs.NewInputFrameDownsync(self.recentInputCache.EdFrameId, prefabbedInputList.slice(), (1 << (joinIndex - 1)));
inputFrameId: self.recentInputCache.edFrameId, // console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.InputFrameId}`);
inputList: prefabbedInputList, self.recentInputCache.Put(prefabbedInputFrameDownsync);
confirmedList: (1 << (joinIndex - 1))
});
// console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.inputFrameId}`);
self.recentInputCache.put(prefabbedInputFrameDownsync);
} }
return [previousSelfInput, currSelfInput]; return [previousSelfInput, currSelfInput];
@ -184,18 +180,18 @@ cc.Class({
const self = this; const self = this;
let inputFrameUpsyncBatch = []; let inputFrameUpsyncBatch = [];
let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1; let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1;
if (batchInputFrameIdSt < self.recentInputCache.stFrameId) { if (batchInputFrameIdSt < self.recentInputCache.StFrameId) {
// Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly. // Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly.
batchInputFrameIdSt = self.recentInputCache.stFrameId; batchInputFrameIdSt = self.recentInputCache.StFrameId;
} }
for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) { for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) {
const inputFrameDownsync = self.recentInputCache.getByFrameId(i); const inputFrameDownsync = self.recentInputCache.GetByFrameId(i);
if (null == inputFrameDownsync) { if (null == inputFrameDownsync) {
console.error(`sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId=${latestLocalInputFrameId}, recentInputCache=${self._stringifyRecentInputCache(false)}`); console.error(`sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId=${latestLocalInputFrameId}, recentInputCache=${self._stringifyRecentInputCache(false)}`);
} else { } else {
const inputFrameUpsync = { const inputFrameUpsync = {
inputFrameId: i, inputFrameId: i,
encoded: inputFrameDownsync.inputList[self.selfPlayerInfo.joinIndex - 1], encoded: inputFrameDownsync.InputList[self.selfPlayerInfo.JoinIndex - 1],
}; };
inputFrameUpsyncBatch.push(inputFrameUpsync); inputFrameUpsyncBatch.push(inputFrameUpsync);
} }
@ -204,15 +200,15 @@ cc.Class({
// console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`); // console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`);
const reqData = window.pb.protos.WsReq.encode({ const reqData = window.pb.protos.WsReq.encode({
msgId: Date.now(), msgId: Date.now(),
playerId: self.selfPlayerInfo.id, playerId: self.selfPlayerInfo.Id,
act: window.UPSYNC_MSG_ACT_PLAYER_CMD, act: window.UPSYNC_MSG_ACT_PLAYER_CMD,
joinIndex: self.selfPlayerInfo.joinIndex, joinIndex: self.selfPlayerInfo.JoinIndex,
ackingInputFrameId: self.lastAllConfirmedInputFrameId, ackingInputFrameId: self.lastAllConfirmedInputFrameId,
inputFrameUpsyncBatch: inputFrameUpsyncBatch, inputFrameUpsyncBatch: inputFrameUpsyncBatch,
}).finish(); }).finish();
window.sendSafely(reqData); window.sendSafely(reqData);
self.lastUpsyncInputFrameId = latestLocalInputFrameId; self.lastUpsyncInputFrameId = latestLocalInputFrameId;
if (self.lastUpsyncInputFrameId >= self.recentInputCache.edFrameId) { if (self.lastUpsyncInputFrameId >= self.recentInputCache.EdFrameId) {
throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`; throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
}, },
@ -305,7 +301,7 @@ cc.Class({
self.recentRenderCache = new RingBuffer(self.renderCacheSize); self.recentRenderCache = new RingBuffer(self.renderCacheSize);
self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others". self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others".
self.recentInputCache = new RingBuffer((self.renderCacheSize >> 1) + 1); self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
const spaceW = self.stageDiscreteW * self.stageTileW; const spaceW = self.stageDiscreteW * self.stageTileW;
const spaceH = self.stageDiscreteH * self.stageTileH; const spaceH = self.stageDiscreteH * self.stageTileH;
@ -606,7 +602,7 @@ cc.Class({
const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id); const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id);
let shouldForceDumping2 = (rdf.Id >= self.renderFrameId + self.renderFrameIdLagTolerance); let shouldForceDumping2 = (rdf.Id >= self.renderFrameId + self.renderFrameIdLagTolerance);
let shouldForceResync = pbRdf.shouldForceResync; let shouldForceResync = pbRdf.shouldForceResync;
const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.joinIndex - 1)))); const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.JoinIndex - 1))));
if (notSelfUnconfirmed) { if (notSelfUnconfirmed) {
shouldForceDumping2 = false; shouldForceDumping2 = false;
shouldForceResync = false; shouldForceResync = false;
@ -721,7 +717,7 @@ cc.Class({
return true; return true;
}, },
onInputFrameDownsyncBatch(batch) { onInputFrameDownsyncBatch(batch /* []*pb.InputFrameDownsync */ ) {
// TODO: find some kind of synchronization mechanism against "getOrPrefabInputFrameUpsync"! // TODO: find some kind of synchronization mechanism against "getOrPrefabInputFrameUpsync"!
if (null == batch) { if (null == batch) {
return; return;
@ -743,7 +739,7 @@ cc.Class({
} }
// [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase". // [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase".
self.lastAllConfirmedInputFrameId = inputFrameDownsyncId; self.lastAllConfirmedInputFrameId = inputFrameDownsyncId;
const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId); const localInputFrame = self.recentInputCache.GetByFrameId(inputFrameDownsyncId);
if (null != localInputFrame if (null != localInputFrame
&& &&
null == firstPredictedYetIncorrectInputFrameId null == firstPredictedYetIncorrectInputFrameId
@ -753,8 +749,9 @@ cc.Class({
firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId; firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId;
} }
inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1; inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1;
inputFrameDownsyncLocal = gopkgs.NewInputFrameDownsync(inputFrameDownsync.inputFrameId, inputFrameDownsync.inputList, inputFrameDownsync.confirmedList); // "battle.InputFrameDownsync" in "jsexport"
//console.log(`Confirmed inputFrameId=${inputFrameDownsync.inputFrameId}`); //console.log(`Confirmed inputFrameId=${inputFrameDownsync.inputFrameId}`);
const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId); const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.SetByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId);
if (window.RING_BUFF_FAILED_TO_SET == ret) { if (window.RING_BUFF_FAILED_TO_SET == ret) {
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)}`; 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)}`;
} }
@ -829,7 +826,7 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
const [wx, wy] = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy); newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node; playerScriptIns.mapNode = self.node;
const colliderRadius = playerDownsyncInfo.colliderRadius || playerDownsyncInfo.ColliderRadius; const colliderRadius = playerDownsyncInfo.ColliderRadius;
const halfColliderWidth = colliderRadius, const halfColliderWidth = colliderRadius,
halfColliderHeight = colliderRadius + colliderRadius; // avoid multiplying halfColliderHeight = colliderRadius + colliderRadius; // avoid multiplying
const colliderWidth = halfColliderWidth + halfColliderWidth, const colliderWidth = halfColliderWidth + halfColliderWidth,
@ -856,10 +853,6 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
setLocalZOrder(newPlayerNode, 5); setLocalZOrder(newPlayerNode, 5);
newPlayerNode.active = true; newPlayerNode.active = true;
playerDownsyncInfo.characterState = playerDownsyncInfo.CharacterState;
playerDownsyncInfo.dirX = playerDownsyncInfo.DirX;
playerDownsyncInfo.dirY = playerDownsyncInfo.DirY;
playerDownsyncInfo.framesToRecover = playerDownsyncInfo.FrameToRecover;
playerScriptIns.updateCharacterAnim(playerDownsyncInfo, null, true); playerScriptIns.updateCharacterAnim(playerDownsyncInfo, null, true);
return [newPlayerNode, playerScriptIns]; return [newPlayerNode, playerScriptIns];
@ -1063,10 +1056,6 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const playerRichInfo = self.playerRichInfoArr[k]; const playerRichInfo = self.playerRichInfoArr[k];
playerRichInfo.node.setPosition(wx, wy); playerRichInfo.node.setPosition(wx, wy);
playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed); playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed);
currPlayerDownsync.characterState = currPlayerDownsync.CharacterState;
currPlayerDownsync.dirX = currPlayerDownsync.DirX;
currPlayerDownsync.dirY = currPlayerDownsync.DirY;
currPlayerDownsync.framesToRecover = currPlayerDownsync.FrameToRecover;
playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false); playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false);
} }
@ -1079,13 +1068,13 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
getCachedInputFrameDownsyncWithPrediction(inputFrameId) { getCachedInputFrameDownsyncWithPrediction(inputFrameId) {
const self = this; const self = this;
const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); const inputFrameDownsync = self.recentInputCache.GetByFrameId(inputFrameId); // "battle.InputFrameDownsync" in "jsexport"
if (null != inputFrameDownsync && inputFrameId > self.lastAllConfirmedInputFrameId) { if (null != inputFrameDownsync && inputFrameId > self.lastAllConfirmedInputFrameId) {
const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId); const lastAllConfirmedInputFrame = self.recentInputCache.GetByFrameId(self.lastAllConfirmedInputFrameId);
if (null != lastAllConfirmedInputFrame) { if (null != lastAllConfirmedInputFrame) {
for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) { for (let i = 0; i < inputFrameDownsync.InputList.length; ++i) {
if (i == (self.selfPlayerInfo.joinIndex - 1)) continue; if (i == (self.selfPlayerInfo.JoinIndex - 1)) continue;
inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input! inputFrameDownsync.InputList[i] = (lastAllConfirmedInputFrame.InputList[i] & 15); // Don't predict attack input!
} }
} }
} }
@ -1103,24 +1092,23 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
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`; 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`;
} }
const j = self._convertToInputFrameId(i, self.inputDelayFrames); const j = self._convertToInputFrameId(i, self.inputDelayFrames);
const delayedInputFrame = self.recentInputCache.getByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing const delayedInputFrame = self.recentInputCache.GetByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing
if (null == delayedInputFrame) { if (null == delayedInputFrame) {
// Shouldn't happen! // Shouldn't happen!
throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`; throw `Failed to get cached delayedInputFrame for i=${i}, j=${j}, renderFrameId=${self.renderFrameId}, lastUpsyncInputFrameId=${self.lastUpsyncInputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, chaserRenderFrameId=${self.chaserRenderFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`;
} }
const jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames); const jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames);
const delayedInputFrameForPrevRenderFrame = self.recentInputCache.getByFrameId(jPrev);
if (self.frameDataLoggingEnabled) { if (self.frameDataLoggingEnabled) {
const actuallyUsedInputClone = delayedInputFrame.inputList.slice(); const actuallyUsedInputClone = delayedInputFrame.InputList.slice();
const inputFrameDownsyncClone = { const inputFrameDownsyncClone = {
inputFrameId: delayedInputFrame.inputFrameId, inputFrameId: delayedInputFrame.InputFrameId,
inputList: actuallyUsedInputClone, inputList: actuallyUsedInputClone,
confirmedList: delayedInputFrame.confirmedList, confirmedList: delayedInputFrame.ConfirmedList,
}; };
self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone); self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone);
} }
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(delayedInputFrame.inputList, (null == delayedInputFrameForPrevRenderFrame ? null : delayedInputFrameForPrevRenderFrame.inputList), currRdf, collisionSys, collisionSysMap, self.gravityX, self.gravityY, self.jumpingInitVelY, self.inputDelayFrames, self.inputScaleFrames, self.spaceOffsetX, self.spaceOffsetY, self.snapIntoPlatformOverlap, self.snapIntoPlatformThreshold, self.worldToVirtualGridRatio, self.virtualGridToWorldRatio); const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.gravityX, self.gravityY, self.jumpingInitVelY, self.inputDelayFrames, self.inputScaleFrames, self.spaceOffsetX, self.spaceOffsetY, self.snapIntoPlatformOverlap, self.snapIntoPlatformThreshold, self.worldToVirtualGridRatio, self.virtualGridToWorldRatio, self.playerOpPatternToSkillId);
if (true == isChasing) { if (true == isChasing) {
// [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic! // [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic!
@ -1140,7 +1128,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const self = this; const self = this;
for (let k in playersArr) { for (let k in playersArr) {
const immediatePlayerInfo = playersArr[k]; const immediatePlayerInfo = playersArr[k];
const playerId = immediatePlayerInfo.id || immediatePlayerInfo.Id; const playerId = immediatePlayerInfo.Id;
if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys
self.playerRichInfoDict.set(playerId, immediatePlayerInfo); self.playerRichInfoDict.set(playerId, immediatePlayerInfo);
const joinIndex = immediatePlayerInfo.joinIndex || immediatePlayerInfo.JoinIndex; const joinIndex = immediatePlayerInfo.joinIndex || immediatePlayerInfo.JoinIndex;
@ -1153,9 +1141,9 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
scriptIns: nodeAndScriptIns[1], scriptIns: nodeAndScriptIns[1],
}); });
const selfPlayerId = self.selfPlayerInfo.id || self.selfPlayerInfo.Id; const selfPlayerId = self.selfPlayerInfo.Id;
if (selfPlayerId == playerId) { if (selfPlayerId == playerId) {
self.selfPlayerInfo.joinIndex = immediatePlayerInfo.joinIndex || immediatePlayerInfo.JoinIndex; self.selfPlayerInfo.JoinIndex = immediatePlayerInfo.JoinIndex;
nodeAndScriptIns[1].showArrowTipNode(); nodeAndScriptIns[1].showArrowTipNode();
} }
} }
@ -1169,13 +1157,13 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const self = this; const self = this;
if (true == usefullOutput) { if (true == usefullOutput) {
let s = []; let s = [];
for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) { for (let i = self.recentInputCache.StFrameId; i < self.recentInputCache.EdFrameId; ++i) {
s.push(JSON.stringify(self.recentInputCache.getByFrameId(i))); s.push(JSON.stringify(self.recentInputCache.GetByFrameId(i)));
} }
return s.join('\n'); return s.join('\n');
} }
return `[stInputFrameId=${self.recentInputCache.stFrameId}, edInputFrameId=${self.recentInputCache.edFrameId})`; return `[stInputFrameId=${self.recentInputCache.StFrameId}, edInputFrameId=${self.recentInputCache.EdFrameId})`;
}, },
_stringifyGopkgRoomDownsyncFrame(rdf) { _stringifyGopkgRoomDownsyncFrame(rdf) {

View File

@ -42,27 +42,11 @@ cc.Class({
self.worldToVirtualGridRatio = 1000; self.worldToVirtualGridRatio = 1000;
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio; self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
self.meleeSkillConfig = { const opJoinIndexPrefix1 = (1 << 8);
1: { const opJoinIndexPrefix2 = (2 << 8);
// for offender self.playerOpPatternToSkillId = {};
startupFrames: 10, self.playerOpPatternToSkillId[opJoinIndexPrefix1 + 0] = 1;
activeFrames: 20, self.playerOpPatternToSkillId[opJoinIndexPrefix2 + 0] = 1;
recoveryFrames: 34, // usually but not always "startupFrames+activeFrames", I hereby set it to be 1 frame more than the actual animation to avoid critical transition, i.e. when the animation is 1 frame from ending but "rdfPlayer.framesToRecover" is already counted 0 and the player triggers an other same attack, making an effective bullet trigger but no animation is played due to same animName is still playing
recoveryFramesOnBlock: 34,
recoveryFramesOnHit: 34,
hitboxOffset: 12.0, // should be about the radius of the PlayerCollider
// for defender
hitStunFrames: 18,
blockStunFrames: 9,
pushback: 8.0,
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
damage: 5,
hitboxSizeX: 24.0,
hitboxSizeY: 32.0,
selfMoveforwardX: 0,
selfMoveforwardY: 0,
}
};
/* /*
[WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..." [WARNING] As when a character is standing on a barrier, if not carefully curated there MIGHT BE a bouncing sequence of "[(inAir -> dropIntoBarrier ->), (notInAir -> pushedOutOfBarrier ->)], [(inAir -> ..."
@ -195,11 +179,8 @@ cc.Class({
}); });
self.selfPlayerInfo = { self.selfPlayerInfo = {
Id: 11, Id: 10,
JoinIndex: 2, JoinIndex: 1,
// For compatibility
id: 11,
joinIndex: 2,
}; };
self.onRoomDownsyncFrame(startRdf); self.onRoomDownsyncFrame(startRdf);

View File

@ -10,6 +10,9 @@ const (
COLLISION_PLAYER_INDEX_PREFIX = (1 << 17) COLLISION_PLAYER_INDEX_PREFIX = (1 << 17)
COLLISION_BARRIER_INDEX_PREFIX = (1 << 16) COLLISION_BARRIER_INDEX_PREFIX = (1 << 16)
COLLISION_BULLET_INDEX_PREFIX = (1 << 15) COLLISION_BULLET_INDEX_PREFIX = (1 << 15)
PATTERN_ID_UNABLE_TO_OP = -2
PATTERN_ID_NO_OP = -1
) )
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged. // These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
@ -25,6 +28,32 @@ var DIRECTION_DECODER = [][]int32{
{-1, +1}, {-1, +1},
} }
var skillIdToBullet = map[int]interface{}{
1: &MeleeBullet{
Bullet: Bullet{
// for offender
StartupFrames: int32(10),
ActiveFrames: int32(10),
RecoveryFrames: int32(34),
RecoveryFramesOnBlock: int32(34),
RecoveryFramesOnHit: int32(34),
HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider
// for defender
HitStunFrames: int32(18),
BlockStunFrames: int32(9),
Pushback: float64(8.0),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge
Damage: int32(5),
SelfMoveforwardX: 0,
SelfMoveforwardY: 0,
HitboxSizeX: 24.0,
HitboxSizeY: 32.0,
},
},
}
const ( const (
ATK_CHARACTER_STATE_IDLE1 = int32(0) ATK_CHARACTER_STATE_IDLE1 = int32(0)
ATK_CHARACTER_STATE_WALKING = int32(1) ATK_CHARACTER_STATE_WALKING = int32(1)
@ -292,8 +321,60 @@ func calcHardPushbacksNorms(joinIndex int32, playerCollider *resolv.Object, play
return &ret return &ret
} }
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, inputsBuffer *RingBuffer, inputDelayFrames int32, inputScaleFrames uint32) (int, bool, int32, int32) {
// returns (patternId, jumpedOrNot, effectiveDx, effectiveDy)
delayedInputFrameId := ConvertToInputFrameId(currRenderFrame.Id, inputDelayFrames, inputScaleFrames)
delayedInputFrameIdForPrevRdf := ConvertToInputFrameId(currRenderFrame.Id-1, inputDelayFrames, inputScaleFrames)
if 0 >= delayedInputFrameId {
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
}
delayedInputList := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync).InputList
var delayedInputListForPrevRdf []uint64 = nil
if 0 < delayedInputFrameIdForPrevRdf {
delayedInputListForPrevRdf = inputsBuffer.GetByFrameId(delayedInputFrameIdForPrevRdf).(*InputFrameDownsync).InputList
}
jumpedOrNot := false
joinIndex := currPlayerDownsync.JoinIndex
if 0 < currPlayerDownsync.FramesToRecover {
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
}
decodedInput := decodeInput(delayedInputList[joinIndex-1])
effDx, effDy := decodedInput.Dx, decodedInput.Dy
prevBtnALevel, prevBtnBLevel := int32(0), int32(0)
if nil != delayedInputListForPrevRdf {
prevDecodedInput := decodeInput(delayedInputListForPrevRdf[joinIndex-1])
prevBtnALevel = prevDecodedInput.BtnALevel
prevBtnBLevel = prevDecodedInput.BtnBLevel
}
if decodedInput.BtnBLevel > prevBtnBLevel {
characStateAlreadyInAir := false
if ATK_CHARACTER_STATE_INAIR_IDLE1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == currPlayerDownsync.CharacterState {
characStateAlreadyInAir = true
}
characStateIsInterruptWaivable := false
if ATK_CHARACTER_STATE_IDLE1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_WALKING == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == currPlayerDownsync.CharacterState {
characStateIsInterruptWaivable = true
}
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
jumpedOrNot = true
}
}
patternId := PATTERN_ID_NO_OP
if decodedInput.BtnALevel > prevBtnALevel {
patternId = 0
effDx, effDy = 0, 0 // Most patterns/skills should not allow simultaneous movement
}
return patternId, jumpedOrNot, effDx, effDy
}
// [WARNING] The params of this method is carefully tuned such that only "battle.RoomDownsyncFrame" is a necessary custom struct. // [WARNING] The params of this method is carefully tuned such that only "battle.RoomDownsyncFrame" is a necessary custom struct.
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputList, delayedInputListForPrevRenderFrame []uint64, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames int32, inputScaleFrames uint32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64) *RoomDownsyncFrame { func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames int32, inputScaleFrames uint32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64, playerOpPatternToSkillId map[int]int) *RoomDownsyncFrame {
// [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked! // [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked!
roomCapacity := len(currRenderFrame.PlayersArr) roomCapacity := len(currRenderFrame.PlayersArr)
nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity) nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity)
@ -323,50 +404,49 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputList, delaye
} }
} }
nextRenderFrameMeleeBullets := make([]*MeleeBullet, 0, len(currRenderFrame.MeleeBullets)) // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
effPushbacks := make([]Vec2D, roomCapacity) effPushbacks := make([]Vec2D, roomCapacity)
hardPushbackNorms := make([]*[]Vec2D, roomCapacity) hardPushbackNorms := make([]*[]Vec2D, roomCapacity)
// 1. Process player inputs // 1. Process player inputs
if nil != delayedInputList {
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
if 0 < thatPlayerInNextFrame.FramesToRecover { patternId, jumpedOrNot, effDx, effDy := deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame, currRenderFrame, inputsBuffer, inputDelayFrames, inputScaleFrames)
if PATTERN_ID_UNABLE_TO_OP == patternId {
continue continue
} }
decodedInput := decodeInput(delayedInputList[joinIndex-1])
prevBtnBLevel := int32(0)
if nil != delayedInputListForPrevRenderFrame {
prevDecodedInput := decodeInput(delayedInputListForPrevRenderFrame[joinIndex-1])
prevBtnBLevel = prevDecodedInput.BtnBLevel
}
if decodedInput.BtnBLevel > prevBtnBLevel { if jumpedOrNot {
characStateAlreadyInAir := false
if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame.CharacterState {
characStateAlreadyInAir = true
}
characStateIsInterruptWaivable := false
if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState {
characStateIsInterruptWaivable = true
}
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
thatPlayerInNextFrame.VelY = jumpingInitVelY thatPlayerInNextFrame.VelY = jumpingInitVelY
thatPlayerInNextFrame.VirtualGridY += jumpingInitVelY // Immediately gets out of any snapping
} }
joinIndex := currPlayerDownsync.JoinIndex
if PATTERN_ID_NO_OP != patternId {
if skillId, existent := playerOpPatternToSkillId[(int(joinIndex)<<uint(8))+patternId]; existent {
skillConfig := skillIdToBullet[skillId].(*MeleeBullet) // Hardcoded type "MeleeBullet" for now
var newMeleeBullet MeleeBullet = *skillConfig
newMeleeBullet.OffenderJoinIndex = joinIndex
newMeleeBullet.OffenderPlayerId = currPlayerDownsync.Id
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newMeleeBullet)
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
if false == currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX = 0
}
}
continue
} }
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs if 0 != effDx || 0 != effDy {
if 0 != decodedInput.Dx || 0 != decodedInput.Dy { thatPlayerInNextFrame.DirX, thatPlayerInNextFrame.DirY = effDx, effDy
thatPlayerInNextFrame.DirX = decodedInput.Dx thatPlayerInNextFrame.VelX = effDx * currPlayerDownsync.Speed
thatPlayerInNextFrame.DirY = decodedInput.Dy
thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
} else { } else {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.VelX = 0 thatPlayerInNextFrame.VelX = 0
} }
} }
}
// 2. Process player movement // 2. Process player movement
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
@ -377,9 +457,6 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputList, delaye
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
// Reset playerCollider position from the "virtual grid position" // Reset playerCollider position from the "virtual grid position"
newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY
if thatPlayerInNextFrame.VelY == jumpingInitVelY {
newVy += thatPlayerInNextFrame.VelY
}
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY, virtualGridToWorldRatio) playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY, virtualGridToWorldRatio)
// Update in the collision system // Update in the collision system

View File

@ -1,4 +1,4 @@
package dnmshared package battle
const ( const (
RING_BUFF_CONSECUTIVE_SET = int32(0) RING_BUFF_CONSECUTIVE_SET = int32(0)

View File

@ -50,7 +50,7 @@ type Barrier struct {
Boundary *Polygon2D Boundary *Polygon2D
} }
type MeleeBullet struct { type Bullet struct {
// for offender // for offender
BattleLocalId int32 BattleLocalId int32
StartupFrames int32 StartupFrames int32
@ -75,11 +75,33 @@ type MeleeBullet struct {
HitboxSizeY float64 HitboxSizeY float64
} }
type MeleeBullet struct {
Bullet
}
type FireballBullet struct {
VirtualGridX int32
VirtualGridY int32
DirX int32
DirY int32
VelX int32
VelY int32
Speed int32
Bullet
}
type RoomDownsyncFrame struct { type RoomDownsyncFrame struct {
Id int32 Id int32
PlayersArr []*PlayerDownsync PlayersArr []*PlayerDownsync
CountdownNanos int64 CountdownNanos int64
MeleeBullets []*MeleeBullet MeleeBullets []*MeleeBullet
FireballBullets []*FireballBullet
BackendUnconfirmedMask uint64 BackendUnconfirmedMask uint64
ShouldForceResync bool ShouldForceResync bool
} }
type InputFrameDownsync struct {
InputFrameId int32
InputList []uint64
ConfirmedList uint64
}

View File

@ -6,6 +6,18 @@ import (
"resolv" "resolv"
) )
func NewInputFrameDownsync(inputFrameId int32, inputList []uint64, confirmedList uint64) *js.Object {
return js.MakeFullWrapper(&InputFrameDownsync{
InputFrameId: inputFrameId,
InputList: inputList,
ConfirmedList: confirmedList,
})
}
func NewRingBufferJs(n int32) *js.Object {
return js.MakeFullWrapper(NewRingBuffer(n))
}
func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object { func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object {
return js.MakeWrapper(resolv.NewSpace(spaceW, spaceH, minStepW, minStepH)) return js.MakeWrapper(resolv.NewSpace(spaceW, spaceH, minStepW, minStepH))
} }
@ -50,31 +62,6 @@ func NewPlayerDownsyncJs(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY,
}) })
} }
func NewMeleeBulletJs(battleLocalId, startupFrames, activeFrames, recoveryFrames, recoveryFramesOnBlock, recoveryFramesOnHit, hitStunFrames, blockStunFrames, releaseTriggerType, damage, offenderJoinIndex, offenderPlayerId int32, pushback, hitboxOffset, selfMoveforwardX, selfMoveforwardY, hitboxSizeX, hitboxSizeY float64) *js.Object {
return js.MakeWrapper(&MeleeBullet{
BattleLocalId: battleLocalId,
StartupFrames: startupFrames,
ActiveFrames: activeFrames,
RecoveryFrames: recoveryFrames,
RecoveryFramesOnBlock: recoveryFramesOnBlock,
RecoveryFramesOnHit: recoveryFramesOnHit,
HitboxOffset: hitboxOffset,
HitStunFrames: hitStunFrames,
BlockStunFrames: blockStunFrames,
Pushback: pushback,
ReleaseTriggerType: releaseTriggerType,
Damage: damage,
SelfMoveforwardX: selfMoveforwardX,
SelfMoveforwardY: selfMoveforwardY,
HitboxSizeX: hitboxSizeX,
HitboxSizeY: hitboxSizeY,
OffenderJoinIndex: offenderJoinIndex,
OffenderPlayerId: offenderPlayerId,
})
}
func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, meleeBullets []*MeleeBullet) *js.Object { func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, meleeBullets []*MeleeBullet) *js.Object {
// [WARNING] Avoid using "pb.RoomDownsyncFrame" here, in practive "MakeFullWrapper" doesn't expose the public fields for a "protobuf struct" as expected and requires helper functions like "GetCollisionSpaceObjsJs". // [WARNING] Avoid using "pb.RoomDownsyncFrame" here, in practive "MakeFullWrapper" doesn't expose the public fields for a "protobuf struct" as expected and requires helper functions like "GetCollisionSpaceObjsJs".
return js.MakeFullWrapper(&RoomDownsyncFrame{ return js.MakeFullWrapper(&RoomDownsyncFrame{
@ -114,9 +101,9 @@ func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spac
return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag)) return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag))
} }
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(delayedInputList, delayedInputListForPrevRenderFrame []uint64, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames int32, inputScaleFrames uint32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64) *js.Object { func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, gravityX, gravityY, jumpingInitVelY, inputDelayFrames int32, inputScaleFrames uint32, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio float64, playerOpPatternToSkillId map[int]int) *js.Object {
// We need access to all fields of RoomDownsyncFrame for displaying in frontend // We need access to all fields of RoomDownsyncFrame for displaying in frontend
return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputList, delayedInputListForPrevRenderFrame, currRenderFrame, collisionSys, collisionSysMap, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio)) return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, gravityX, gravityY, jumpingInitVelY, inputDelayFrames, inputScaleFrames, collisionSpaceOffsetX, collisionSpaceOffsetY, snapIntoPlatformOverlap, snapIntoPlatformThreshold, worldToVirtualGridRatio, virtualGridToWorldRatio, playerOpPatternToSkillId))
} }
func main() { func main() {
@ -127,6 +114,8 @@ func main() {
"NewPlayerDownsyncJs": NewPlayerDownsyncJs, "NewPlayerDownsyncJs": NewPlayerDownsyncJs,
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs, "NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
"NewCollisionSpaceJs": NewCollisionSpaceJs, "NewCollisionSpaceJs": NewCollisionSpaceJs,
"NewInputFrameDownsync": NewInputFrameDownsync,
"NewRingBufferJs": NewRingBufferJs,
"GenerateRectColliderJs": GenerateRectColliderJs, "GenerateRectColliderJs": GenerateRectColliderJs,
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs, "GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs, "GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,