mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-27 12:08:21 +00:00
543 lines
27 KiB
JavaScript
543 lines
27 KiB
JavaScript
const i18n = require('LanguageData');
|
|
i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field
|
|
|
|
const OnlineMap = require('./Map');
|
|
/*
|
|
[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 -> ..."
|
|
|
|
Moreover, this "snapIntoPlatformOverlap" should be small enough such that the jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame).
|
|
*/
|
|
const snapIntoPlatformOverlap = 0.1;
|
|
|
|
cc.Class({
|
|
extends: OnlineMap,
|
|
|
|
onDestroy() {
|
|
console.warn("+++++++ Map onDestroy()");
|
|
},
|
|
|
|
onLoad() {
|
|
const self = this;
|
|
window.mapIns = self;
|
|
|
|
cc.director.getCollisionManager().enabled = false;
|
|
|
|
const mapNode = self.node;
|
|
const canvasNode = mapNode.parent;
|
|
|
|
self.mainCameraNode = self.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. */
|
|
|
|
self.inputDelayFrames = 8;
|
|
self.inputScaleFrames = 2;
|
|
self.inputFrameUpsyncDelayTolerance = 2;
|
|
|
|
self.renderCacheSize = 1024;
|
|
self.serverFps = 60;
|
|
self.rollbackEstimatedDt = 0.016667;
|
|
self.rollbackEstimatedDtMillis = 16.667;
|
|
self.rollbackEstimatedDtNanos = 16666666;
|
|
|
|
self.worldToVirtualGridRatio = 1000;
|
|
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
|
|
self.meleeSkillConfig = {
|
|
1: {
|
|
// for offender
|
|
startupFrames: 10,
|
|
activeFrames: 3,
|
|
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,
|
|
moveforward: {
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
hitboxOffset: 12.0, // should be about the radius of the PlayerCollider
|
|
hitboxSize: {
|
|
x: 23.0,
|
|
y: 32.0,
|
|
},
|
|
|
|
// for defender
|
|
hitStunFrames: 18,
|
|
blockStunFrames: 9,
|
|
pushback: 8.0,
|
|
releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge
|
|
damage: 5
|
|
}
|
|
};
|
|
|
|
const tiledMapIns = self.node.getComponent(cc.TiledMap);
|
|
|
|
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon");
|
|
cc.loader.loadRes(fullPathOfTmxFile, cc.TiledMapAsset, (err, tmxAsset) => {
|
|
if (null != err) {
|
|
console.error(err);
|
|
return;
|
|
}
|
|
|
|
tiledMapIns.tmxAsset = null;
|
|
mapNode.removeAllChildren();
|
|
self._resetCurrentMatch();
|
|
|
|
tiledMapIns.tmxAsset = tmxAsset;
|
|
const newMapSize = tiledMapIns.getMapSize();
|
|
const newTileSize = tiledMapIns.getTileSize();
|
|
self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height);
|
|
self.node.setPosition(cc.v2(0, 0));
|
|
|
|
let barrierIdCounter = 0;
|
|
const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node);
|
|
for (let boundaryObj of boundaryObjs.barriers) {
|
|
const x0 = boundaryObj.anchor.x,
|
|
y0 = boundaryObj.anchor.y;
|
|
|
|
const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => {
|
|
return [p.x, p.y];
|
|
}));
|
|
|
|
if (self.showCriticalCoordinateLabels) {
|
|
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;
|
|
barrierVertLabel.lineHeight = barrierVertLabel.fontSize + 1;
|
|
barrierVertLabel.string = `(${wx.toFixed(1)}, ${wy.toFixed(1)})`;
|
|
safelyAddChild(self.node, barrierVertLabelNode);
|
|
setLocalZOrder(barrierVertLabelNode, 5);
|
|
|
|
barrierVertLabelNode.active = true;
|
|
}
|
|
|
|
}
|
|
// console.log("Created barrier: ", newBarrier);
|
|
++barrierIdCounter;
|
|
const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter);
|
|
self.collisionSysMap.set(collisionBarrierIndex, newBarrier);
|
|
}
|
|
|
|
const startRdf = window.pb.protos.RoomDownsyncFrame.create({
|
|
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
|
|
players: {
|
|
10: window.pb.protos.PlayerDownsync.create({
|
|
id: 10,
|
|
joinIndex: 1,
|
|
virtualGridX: -50 * self.worldToVirtualGridRatio,
|
|
virtualGridY: -400 * self.worldToVirtualGridRatio,
|
|
speed: 1 * self.worldToVirtualGridRatio,
|
|
colliderRadius: 12,
|
|
characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0],
|
|
framesToRecover: 0,
|
|
dirX: 0,
|
|
dirY: 0,
|
|
velX: 0,
|
|
velY: 0,
|
|
inAir: true,
|
|
}),
|
|
11: window.pb.protos.PlayerDownsync.create({
|
|
id: 11,
|
|
joinIndex: 2,
|
|
virtualGridX: 100 * self.worldToVirtualGridRatio,
|
|
virtualGridY: -350 * self.worldToVirtualGridRatio,
|
|
speed: 1 * self.worldToVirtualGridRatio,
|
|
colliderRadius: 12,
|
|
characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0],
|
|
framesToRecover: 0,
|
|
dirX: 0,
|
|
dirY: 0,
|
|
velX: 0,
|
|
velY: 0,
|
|
inAir: true,
|
|
}),
|
|
}
|
|
});
|
|
self.selfPlayerInfo = {
|
|
id: 11
|
|
};
|
|
self._initPlayerRichInfoDict(startRdf.players);
|
|
self.onRoomDownsyncFrame(startRdf);
|
|
|
|
self.battleState = ALL_BATTLE_STATES.IN_BATTLE;
|
|
});
|
|
|
|
},
|
|
|
|
spawnPlayerNode(joinIndex, vx, vy, playerDownsyncInfo) {
|
|
const self = this;
|
|
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
|
|
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
|
|
if (1 == joinIndex) {
|
|
playerScriptIns.setSpecies("SoldierWaterGhost");
|
|
} else if (2 == joinIndex) {
|
|
playerScriptIns.setSpecies("SoldierFireGhost");
|
|
}
|
|
|
|
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
|
|
newPlayerNode.setPosition(wx, wy);
|
|
playerScriptIns.mapNode = self.node;
|
|
const colliderWidth = playerDownsyncInfo.colliderRadius * 2,
|
|
colliderHeight = playerDownsyncInfo.colliderRadius * 2.5;
|
|
const [x0, y0] = self.virtualGridToPolygonColliderAnchorPos(vx, vy, colliderWidth, colliderHeight),
|
|
pts = [[0, 0], [colliderWidth, 0], [colliderWidth, colliderHeight], [0, colliderHeight]];
|
|
|
|
const newPlayerCollider = self.collisionSys.createPolygon(x0, y0, pts);
|
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
|
newPlayerCollider.data = playerDownsyncInfo;
|
|
self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider);
|
|
|
|
console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.colliderRadius}`);
|
|
|
|
safelyAddChild(self.node, newPlayerNode);
|
|
setLocalZOrder(newPlayerNode, 5);
|
|
|
|
newPlayerNode.active = true;
|
|
playerScriptIns.updateCharacterAnim(playerDownsyncInfo, null, true);
|
|
|
|
return [newPlayerNode, playerScriptIns];
|
|
},
|
|
|
|
update(dt) {
|
|
const self = this;
|
|
if (ALL_BATTLE_STATES.IN_BATTLE == self.battleState) {
|
|
const elapsedMillisSinceLastFrameIdTriggered = performance.now() - self.lastRenderFrameIdTriggeredAt;
|
|
if (elapsedMillisSinceLastFrameIdTriggered < (self.rollbackEstimatedDtMillis)) {
|
|
// console.debug("Avoiding too fast frame@renderFrameId=", self.renderFrameId, ": elapsedMillisSinceLastFrameIdTriggered=", elapsedMillisSinceLastFrameIdTriggered);
|
|
return;
|
|
}
|
|
try {
|
|
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)) {
|
|
const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId);
|
|
prevSelfInput = prevAndCurrInputs[0];
|
|
currSelfInput = prevAndCurrInputs[1];
|
|
}
|
|
|
|
const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false);
|
|
self.applyRoomDownsyncFrameDynamics(rdf, prevRdf);
|
|
let t3 = performance.now();
|
|
} catch (err) {
|
|
console.error("Error during Map.update", err);
|
|
} finally {
|
|
++self.renderFrameId; // [WARNING] It's important to increment the renderFrameId AFTER all the operations above!!!
|
|
}
|
|
}
|
|
},
|
|
|
|
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,
|
|
dirX: currPlayerDownsync.dirX,
|
|
dirY: currPlayerDownsync.dirY,
|
|
velX: currPlayerDownsync.velX,
|
|
velY: currPlayerDownsync.velY,
|
|
characterState: currPlayerDownsync.characterState,
|
|
inAir: true, // will be updated if collided with a barrier with "0 > pushbackY"
|
|
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 nextRenderFrameMeleeBullets = [];
|
|
|
|
// Guaranteed determinism regardless of traversal order
|
|
const jumpTriggered = new Array(self.playerRichInfoArr.length);
|
|
const movements = new Array(self.playerRichInfoArr.length);
|
|
const bulletPushbacks = new Array(self.playerRichInfoArr.length);
|
|
const effPushbacks = new Array(self.playerRichInfoArr.length);
|
|
|
|
// Reset playerCollider position from the "virtual grid position"
|
|
for (let j in self.playerRichInfoArr) {
|
|
const joinIndex = parseInt(j) + 1;
|
|
jumpTriggered[joinIndex - 1] = false;
|
|
movements[joinIndex - 1] = [0.0, 0.0];
|
|
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);
|
|
const currPlayerDownsync = currRenderFrame.players[playerId];
|
|
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
|
|
|
const newVx = currPlayerDownsync.virtualGridX;
|
|
const newVy = currPlayerDownsync.virtualGridY;
|
|
[playerCollider.x, playerCollider.y] = self.virtualGridToPolygonColliderAnchorPos(newVx, newVy, self.playerRichInfoArr[joinIndex - 1].colliderRadius, self.playerRichInfoArr[joinIndex - 1].colliderRadius);
|
|
|
|
// Process gravity before anyother interaction, by now "currPlayerDownsync.velX & velY" are properly snapped to be parallel to the edge of its standing platform if necessary
|
|
[movements[joinIndex - 1][0], movements[joinIndex - 1][1]] = self.virtualGridToWorldPos(currPlayerDownsync.velX, currPlayerDownsync.velY);
|
|
playerCollider.x += movements[joinIndex - 1][0];
|
|
playerCollider.y += movements[joinIndex - 1][1];
|
|
if (currPlayerDownsync.inAir) {
|
|
thatPlayerInNextFrame.velX += self.gravityX;
|
|
thatPlayerInNextFrame.velY += self.gravityY;
|
|
}
|
|
}
|
|
|
|
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
|
const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
|
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];
|
|
|
|
let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis"
|
|
if (0 > offender.dirX) {
|
|
xfac = -1;
|
|
}
|
|
const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY);
|
|
const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset;
|
|
const bulletWy = offenderWy;
|
|
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]];
|
|
const newBulletCollider = collisionSys.createPolygon(bulletCx, bulletCy, pts);
|
|
newBulletCollider.data = meleeBullet;
|
|
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
|
|
bulletColliders.set(collisionBulletIndex, newBulletCollider);
|
|
// 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)}`);
|
|
}
|
|
}
|
|
|
|
collisionSys.update();
|
|
const result1 = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle?
|
|
|
|
bulletColliders.forEach((bulletCollider, collisionBulletIndex) => {
|
|
const potentials = bulletCollider.potentials();
|
|
const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId];
|
|
let shouldRemove = false;
|
|
for (const potential of potentials) {
|
|
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
|
|
if (!bulletCollider.collides(potential, result1)) continue;
|
|
if (null != potential.data && null !== potential.data.joinIndex) {
|
|
const joinIndex = potential.data.joinIndex;
|
|
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
|
|
bulletPushbacks[joinIndex - 1][1] += 0;
|
|
const thatAckedPlayerInCurFrame = currRenderFrame.players[potential.data.id];
|
|
const thatAckedPlayerInNextFrame = nextRenderFramePlayers[potential.data.id];
|
|
thatAckedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
|
|
if (thatAckedPlayerInCurFrame.inAir) {
|
|
thatAckedPlayerInNextFrame.characterState = window.toInAirConjugate(thatAckedPlayerInNextFrame.characterState);
|
|
}
|
|
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
|
|
}
|
|
shouldRemove = true;
|
|
}
|
|
if (shouldRemove) {
|
|
removedBulletsAtCurrFrame.add(collisionBulletIndex);
|
|
}
|
|
});
|
|
|
|
// [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback
|
|
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;
|
|
nextRenderFrameMeleeBullets.push(meleeBullet);
|
|
}
|
|
|
|
// Process player inputs
|
|
if (null != delayedInputFrame) {
|
|
const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames));
|
|
const inputList = delayedInputFrame.inputList;
|
|
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 currPlayerDownsync = currRenderFrame.players[playerId];
|
|
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
|
if (0 < thatPlayerInNextFrame.framesToRecover) {
|
|
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
|
playerCollider.x += bulletPushbacks[joinIndex - 1][0];
|
|
playerCollider.y += bulletPushbacks[joinIndex - 1][1];
|
|
if (0 != bulletPushbacks[joinIndex - 1][0] || 0 != bulletPushbacks[joinIndex - 1][1]) {
|
|
console.log(`playerId=${playerId}, joinIndex=${joinIndex} is pushbacked back by ${bulletPushbacks[joinIndex - 1]} by bullet impacts, now its framesToRecover is ${thatPlayerInNextFrame.framesToRecover}`);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
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);
|
|
const prevBtnBLevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnBLevel);
|
|
/*
|
|
[WARNING] Player input alone WOULD NOT take "characterState" into any "ATK_CHARACTER_STATE_IN_AIR_SET", only after the calculation of "effPushbacks" do we know exactly whether or not a player is "inAir", the finalize the transition of "thatPlayerInNextFrame.characterState".
|
|
*/
|
|
if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) {
|
|
const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState);
|
|
const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState);
|
|
if (
|
|
!characStateAlreadyInAir
|
|
&&
|
|
characStateIsInterruptWaivable
|
|
) {
|
|
thatPlayerInNextFrame.velY = currPlayerDownsync.speed * 4.5;
|
|
jumpTriggered[joinIndex - 1] = true;
|
|
console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnB at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}, nextVelY=${thatPlayerInNextFrame.velY}, characStateAlreadyInAir=${characStateAlreadyInAir}, characStateIsInterruptWaivable=${characStateIsInterruptWaivable}`);
|
|
}
|
|
}
|
|
|
|
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 (self.bulletTriggerEnabled) {
|
|
const punchSkillId = 1;
|
|
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
|
|
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
|
|
punch.battleLocalId = self.bulletBattleLocalIdCounter++;
|
|
punch.offenderJoinIndex = joinIndex;
|
|
punch.offenderPlayerId = playerId;
|
|
punch.originatedRenderFrameId = currRenderFrame.id;
|
|
nextRenderFrameMeleeBullets.push(punch);
|
|
// 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}`);
|
|
|
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
|
|
thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 (including inAir)
|
|
}
|
|
} 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}`);
|
|
} else {
|
|
// No bullet trigger, process joystick movement inputs (except for jumping).
|
|
if (0 != decodedInput.dx || 0 != decodedInput.dy) {
|
|
// Update directions and thus would eventually update moving animation accordingly
|
|
thatPlayerInNextFrame.dirX = decodedInput.dx;
|
|
thatPlayerInNextFrame.dirY = decodedInput.dy;
|
|
thatPlayerInNextFrame.velX = decodedInput.dx * currPlayerDownsync.speed;
|
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0];
|
|
} else {
|
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
|
thatPlayerInNextFrame.velX = 0;
|
|
}
|
|
}
|
|
if (currPlayerDownsync.inAir) {
|
|
thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState);
|
|
}
|
|
}
|
|
}
|
|
|
|
collisionSys.update(); // by now all "bulletCollider"s are removed
|
|
const result2 = 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();
|
|
const currPlayerDownsync = currRenderFrame.players[playerId];
|
|
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
|
let fallStopping = false;
|
|
let [snappedIntoPlatformEx, snappedIntoPlatformEy] = [null, null];
|
|
for (const potential of potentials) {
|
|
// Test if the player collides with the wall
|
|
if (!playerCollider.collides(potential, result2)) continue;
|
|
// Push the player out of the wall
|
|
let [pushbackX, pushbackY] = [result2.overlap * result2.overlap_x, result2.overlap * result2.overlap_y];
|
|
if (null == potential.data) {
|
|
// "null == potential.data" implies a barrier
|
|
const remainsNotInAir = (!currPlayerDownsync.inAir);
|
|
const localFallStopping = (currPlayerDownsync.inAir && 0 > pushbackY); // prevents false fall-stopping on the lateral sides
|
|
if (remainsNotInAir || localFallStopping) {
|
|
fallStopping |= localFallStopping;
|
|
[pushbackX, pushbackY] = [(result2.overlap - snapIntoPlatformOverlap) * result2.overlap_x, (result2.overlap - snapIntoPlatformOverlap) * result2.overlap_y]
|
|
// [overlay_x, overlap_y] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
|
snappedIntoPlatformEx = -result2.overlap_y;
|
|
snappedIntoPlatformEy = result2.overlap_x;
|
|
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY) {
|
|
[snappedIntoPlatformEx, snappedIntoPlatformEy] = [-snappedIntoPlatformEx, -snappedIntoPlatformEy];
|
|
}
|
|
}
|
|
}
|
|
// What if we're on the edge of 2 barriers? Would adding up make an unexpected bounce?
|
|
effPushbacks[joinIndex - 1][0] += pushbackX;
|
|
effPushbacks[joinIndex - 1][1] += pushbackY;
|
|
}
|
|
if (false == jumpTriggered[joinIndex - 1] && null != snappedIntoPlatformEx && null != snappedIntoPlatformEy) {
|
|
thatPlayerInNextFrame.inAir = false;
|
|
if (fallStopping) {
|
|
thatPlayerInNextFrame.velY = 0;
|
|
thatPlayerInNextFrame.velX = 0;
|
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
|
thatPlayerInNextFrame.framesToRecover = 0;
|
|
}
|
|
const dotProd = thatPlayerInNextFrame.velX * snappedIntoPlatformEx + thatPlayerInNextFrame.velY * snappedIntoPlatformEy;
|
|
[thatPlayerInNextFrame.velX, thatPlayerInNextFrame.velY] = [dotProd * snappedIntoPlatformEx, dotProd * snappedIntoPlatformEy];
|
|
}
|
|
}
|
|
|
|
// Get players out of stuck barriers if there's any
|
|
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 thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
|
[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);
|
|
}
|
|
|
|
return window.pb.protos.RoomDownsyncFrame.create({
|
|
id: currRenderFrame.id + 1,
|
|
players: nextRenderFramePlayers,
|
|
meleeBullets: nextRenderFrameMeleeBullets,
|
|
});
|
|
},
|
|
});
|