mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-09 16:46:38 +00:00
Drafted attack trigger logic in OfflineMap.
This commit is contained in:
@@ -45,10 +45,14 @@ message PlayerDownsync {
|
||||
bool removed = 9;
|
||||
int32 score = 10;
|
||||
int32 lastMoveGmtMillis = 11;
|
||||
int32 framesToRecover = 12;
|
||||
int32 hp = 13;
|
||||
int32 maxHp = 14;
|
||||
int32 characterState = 15;
|
||||
|
||||
string name = 12;
|
||||
string displayName = 13;
|
||||
string avatar = 14;
|
||||
string name = 16;
|
||||
string displayName = 17;
|
||||
string avatar = 18;
|
||||
}
|
||||
|
||||
message InputFrameDecoded {
|
||||
@@ -72,12 +76,6 @@ message HeartbeatUpsync {
|
||||
int64 clientTimestamp = 1;
|
||||
}
|
||||
|
||||
message RoomDownsyncFrame {
|
||||
int32 id = 1;
|
||||
map<int32, PlayerDownsync> players = 2;
|
||||
int64 countdownNanos = 3;
|
||||
}
|
||||
|
||||
message WsReq {
|
||||
int32 msgId = 1;
|
||||
int32 playerId = 2;
|
||||
@@ -97,3 +95,36 @@ message WsResp {
|
||||
repeated InputFrameDownsync inputFrameDownsyncBatch = 5;
|
||||
BattleColliderInfo bciFrame = 6;
|
||||
}
|
||||
|
||||
message MeleeBullet {
|
||||
// Jargon reference https://www.thegamer.com/fighting-games-frame-data-explained/
|
||||
// ALL lengths are in world coordinate
|
||||
|
||||
// for offender
|
||||
int32 battleLocalId = 1;
|
||||
int32 startupFrames = 2;
|
||||
int32 activeFrames = 3;
|
||||
int32 recoveryFrames = 4;
|
||||
int32 recoveryFramesOnBlock = 5;
|
||||
int32 recoveryFramesOnHit = 6;
|
||||
sharedprotos.Vec2D moveforward = 7;
|
||||
sharedprotos.Vec2D hitboxOffset = 8;
|
||||
sharedprotos.Vec2D hitboxSize = 9;
|
||||
int32 offenderJoinIndex = 10;
|
||||
int32 originatedRenderFrameId = 11;
|
||||
|
||||
// for defender
|
||||
int32 hitStunFrames = 12;
|
||||
int32 blockStunFrames = 13;
|
||||
double pushback = 14;
|
||||
|
||||
int32 releaseTriggerType = 15; // 1: rising-edge, 2: falling-edge
|
||||
int32 damage = 16;
|
||||
}
|
||||
|
||||
message RoomDownsyncFrame {
|
||||
int32 id = 1;
|
||||
map<int32, PlayerDownsync> players = 2;
|
||||
int64 countdownNanos = 3;
|
||||
repeated MeleeBullet meleeBullets = 4; // I don't know how to mimic inheritance/composition in protobuf by far, thus using an array for each type of bullet as a compromise
|
||||
}
|
||||
|
@@ -440,7 +440,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
210.4441731196186,
|
||||
216.50635094610968,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@@ -453,7 +453,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
210.4441731196186,
|
||||
216.50635094610968,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@@ -22,6 +22,9 @@ cc.Class({
|
||||
|
||||
ctor() {
|
||||
this.speciesName = null;
|
||||
this.hp = 100;
|
||||
this.maxHp = 100;
|
||||
this.framesToRecover = 0;
|
||||
},
|
||||
|
||||
setSpecies(speciesName) {
|
||||
|
@@ -300,6 +300,7 @@ cc.Class({
|
||||
// Clearing previous info of all players. [ENDS]
|
||||
|
||||
self.renderFrameId = 0; // After battle started
|
||||
self.bulletBattleLocalIdCounter = 0;
|
||||
self.lastAllConfirmedRenderFrameId = -1;
|
||||
self.lastAllConfirmedInputFrameId = -1;
|
||||
self.lastUpsyncInputFrameId = -1;
|
||||
@@ -313,6 +314,7 @@ cc.Class({
|
||||
self.collisionSys = new collisions.Collisions();
|
||||
|
||||
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
|
||||
self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets
|
||||
self.collisionSysMap = new Map();
|
||||
|
||||
self.transitToState(ALL_MAP_STATES.VISUAL);
|
||||
@@ -408,19 +410,7 @@ cc.Class({
|
||||
/** Init required prefab ended. */
|
||||
|
||||
window.handleBattleColliderInfo = function(parsedBattleColliderInfo) {
|
||||
self.inputDelayFrames = parsedBattleColliderInfo.inputDelayFrames;
|
||||
self.inputScaleFrames = parsedBattleColliderInfo.inputScaleFrames;
|
||||
self.inputFrameUpsyncDelayTolerance = parsedBattleColliderInfo.inputFrameUpsyncDelayTolerance;
|
||||
|
||||
self.battleDurationNanos = parsedBattleColliderInfo.battleDurationNanos;
|
||||
self.rollbackEstimatedDt = parsedBattleColliderInfo.rollbackEstimatedDt;
|
||||
self.rollbackEstimatedDtMillis = parsedBattleColliderInfo.rollbackEstimatedDtMillis;
|
||||
self.rollbackEstimatedDtNanos = parsedBattleColliderInfo.rollbackEstimatedDtNanos;
|
||||
self.maxChasingRenderFramesPerUpdate = parsedBattleColliderInfo.maxChasingRenderFramesPerUpdate;
|
||||
self.spAtkLookupFrames = parsedBattleColliderInfo.spAtkLookupFrames;
|
||||
|
||||
self.worldToVirtualGridRatio = parsedBattleColliderInfo.worldToVirtualGridRatio;
|
||||
self.virtualGridToWorldRatio = parsedBattleColliderInfo.virtualGridToWorldRatio;
|
||||
Object.assign(self, parsedBattleColliderInfo);
|
||||
|
||||
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
||||
|
||||
@@ -758,7 +748,7 @@ cc.Class({
|
||||
playerScriptIns.joinIndex = joinIndex;
|
||||
|
||||
if (1 == joinIndex) {
|
||||
playerScriptIns.setSpecies("SoldierElf");
|
||||
playerScriptIns.setSpecies("SoldierWaterGhost");
|
||||
} else if (2 == joinIndex) {
|
||||
playerScriptIns.setSpecies("SoldierFireGhost");
|
||||
if (0 == playerRichInfo.dir.dx && 0 == playerRichInfo.dir.dy) {
|
||||
@@ -1045,7 +1035,6 @@ cc.Class({
|
||||
|
||||
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
|
||||
|
||||
// console.log(`Got non-zero inputs for playerId=${playerId}, decodedInput=${JSON.stringify(decodedInput)} @currRenderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.id}`);
|
||||
/*
|
||||
Reset "position" of players in "collisionSys" according to "virtual grid position". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
|
||||
*/
|
||||
|
@@ -3,6 +3,34 @@ i18n.init(window.language); // languageID should be equal to the one we input in
|
||||
|
||||
const OnlineMap = require('./Map');
|
||||
|
||||
const PunchAtkConfig = {
|
||||
// for offender
|
||||
startupFrames: 2,
|
||||
activeFrames: 2,
|
||||
recoveryFrames: 4, // usually but not always "startupFrames+activeFrames"
|
||||
recoveryFramesOnBlock: 4, // usually but not always the same as "recoveryFrames"
|
||||
recoveryFramesOnHit: 4, // usually but not always the same as "recoveryFrames"
|
||||
moveforward: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
hitboxOffset: {
|
||||
x: 24.0, // should be about the radius of the PlayerCollider
|
||||
y: 0,
|
||||
},
|
||||
hitboxSize: {
|
||||
x: 24.0,
|
||||
y: 24.0,
|
||||
},
|
||||
|
||||
// for defender
|
||||
hitStunFrames: 0,
|
||||
blockStunFrames: 0,
|
||||
pushback: 10.0,
|
||||
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
|
||||
damage: 5
|
||||
};
|
||||
|
||||
cc.Class({
|
||||
extends: OnlineMap,
|
||||
|
||||
@@ -162,8 +190,8 @@ cc.Class({
|
||||
11: {
|
||||
id: 11,
|
||||
joinIndex: 2,
|
||||
virtualGridX: 80*self.worldToVirtualGridRatio,
|
||||
virtualGridY: 40*self.worldToVirtualGridRatio,
|
||||
virtualGridX: 80 * self.worldToVirtualGridRatio,
|
||||
virtualGridY: 40 * self.worldToVirtualGridRatio,
|
||||
speed: 2 * self.worldToVirtualGridRatio,
|
||||
colliderRadius: 12,
|
||||
dir: {
|
||||
@@ -213,4 +241,141 @@ cc.Class({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Overriding this function to test experimental dynamics
|
||||
applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap) {
|
||||
const self = this;
|
||||
const nextRenderFramePlayers = {}
|
||||
for (let playerId in currRenderFrame.players) {
|
||||
const currPlayerDownsync = currRenderFrame.players[playerId];
|
||||
nextRenderFramePlayers[playerId] = {
|
||||
id: playerId,
|
||||
virtualGridX: currPlayerDownsync.virtualGridX,
|
||||
virtualGridY: currPlayerDownsync.virtualGridY,
|
||||
dir: {
|
||||
dx: currPlayerDownsync.dir.dx,
|
||||
dy: currPlayerDownsync.dir.dy,
|
||||
},
|
||||
speed: currPlayerDownsync.speed,
|
||||
battleState: currPlayerDownsync.battleState,
|
||||
score: currPlayerDownsync.score,
|
||||
removed: currPlayerDownsync.removed,
|
||||
joinIndex: currPlayerDownsync.joinIndex,
|
||||
framesToRecover: (0 < currPlayerDownsync.framesToRecover ? currPlayerDownsync.framesToRecover - 1 : 0),
|
||||
hp: currPlayerDownsync.hp,
|
||||
maxHp: currPlayerDownsync.maxHp,
|
||||
};
|
||||
}
|
||||
|
||||
const toRet = {
|
||||
id: currRenderFrame.id + 1,
|
||||
players: nextRenderFramePlayers,
|
||||
meleeBullets: []
|
||||
};
|
||||
|
||||
if (null != delayedInputFrame) {
|
||||
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
|
||||
const inputList = delayedInputFrame.inputList;
|
||||
const effPushbacks = new Array(self.playerRichInfoArr.length); // Guaranteed determinism regardless of traversal order
|
||||
for (let j in self.playerRichInfoArr) {
|
||||
const joinIndex = parseInt(j) + 1;
|
||||
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);
|
||||
const player = currRenderFrame.players[playerId];
|
||||
|
||||
const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]);
|
||||
|
||||
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) {
|
||||
console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||
if (0 == player.framesToRecover) {
|
||||
nextRenderFramePlayers[playerId].framesToRecover = PunchAtkConfig.recoveryFrames;
|
||||
const punch = window.pb.protos.MeleeBullet.create(PunchAtkConfig);
|
||||
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
|
||||
punch.offenderJoinIndex = joinIndex;
|
||||
punch.originatedRenderFrameId = currRenderFrame.id;
|
||||
toRet.meleeBullets.push(punch);
|
||||
console.log(`A rising-edge of meleeBullet=${JSON.stringify(punch)} is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||
}
|
||||
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
|
||||
console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||
}
|
||||
|
||||
/*
|
||||
Reset "position" of players in "collisionSys" according to "virtual grid position". The easy part is that we don't have path-dependent-integrals to worry about like that of thermal dynamics.
|
||||
*/
|
||||
const newVx = player.virtualGridX + (decodedInput.dx + player.speed * decodedInput.dx);
|
||||
const newVy = player.virtualGridY + (decodedInput.dy + player.speed * decodedInput.dy);
|
||||
const newCpos = self.virtualGridToPlayerColliderPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1]);
|
||||
playerCollider.x = newCpos[0];
|
||||
playerCollider.y = newCpos[1];
|
||||
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
|
||||
// Update directions and thus would eventually update moving animation accordingly
|
||||
nextRenderFramePlayers[playerId].dir.dx = decodedInput.dx;
|
||||
nextRenderFramePlayers[playerId].dir.dy = decodedInput.dy;
|
||||
}
|
||||
}
|
||||
|
||||
for (let k in currRenderFrame.meleeBullets) {
|
||||
const meleeBullet = currRenderFrame.meleeBullets[k];
|
||||
if (meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > currRenderFrame.id) {
|
||||
// Won't cause any collision
|
||||
const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId;
|
||||
const bulletCollider = collisionSysMap.get(collisionBulletIndex);
|
||||
if (null != bulletCollider) {
|
||||
console.log(`A rising-edge of meleeBullet=${JSON.stringify(meleeBullet)} is removed from collisionSys at renderFrame.id=${currRenderFrame.id} as active frames ended`);
|
||||
bulletCollider.remove();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames == currRenderFrame.id) {
|
||||
// Add meleeBullet to collisionSys (shall we just remove all bullets at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` considering that we might be doing the calculation during rollback?
|
||||
const collisionBulletIndex = self.collisionBulletIndexPrefix + melee.battleLocalId;
|
||||
const collisionOffenderIndex = self.collisionPlayerIndexPrefix + melee.offenderJoinIndex;
|
||||
const offenderCollider = collisionSysMap.get(collisionOffenderIndex);
|
||||
const x0 = offenderCollider.x + meleeBullet.hitboxOffset.x,
|
||||
y0 = offenderCollider.y + meleeBullet.hitboxOffset.y;
|
||||
const pts = [[0, 0], [meleeBullet.hitboxSize.x, 0], [meleeBullet.hitboxSize.x, meleeBullet.hitboxSize.y], [0, meleeBullet.hitboxSize.y]];
|
||||
const newBulletCollider = collisionSys.createPolygon(x0, y0, pts);
|
||||
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
|
||||
console.log(`A rising-edge of meleeBullet=${JSON.stringify(meleeBullet)} is added to collisionSys at renderFrame.id=${currRenderFrame.id} as start-up frames ended`);
|
||||
}
|
||||
toRet.meleeBullets.push(meleeBullet);
|
||||
}
|
||||
|
||||
collisionSys.update();
|
||||
const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
||||
|
||||
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);
|
||||
const potentials = playerCollider.potentials();
|
||||
for (const potential of potentials) {
|
||||
// Test if the player collides with the wall
|
||||
if (!playerCollider.collides(potential, result)) continue;
|
||||
// Push the player out of the wall
|
||||
effPushbacks[joinIndex - 1][0] += result.overlap * result.overlap_x;
|
||||
effPushbacks[joinIndex - 1][1] += result.overlap * result.overlap_y;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
const newVpos = self.playerColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j]);
|
||||
nextRenderFramePlayers[playerId].virtualGridX = newVpos[0];
|
||||
nextRenderFramePlayers[playerId].virtualGridY = newVpos[1];
|
||||
}
|
||||
}
|
||||
|
||||
return toRet;
|
||||
},
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user