A temp broken commit except for OfflineMap - refactoring character/skill/bullet config hierarchy.

This commit is contained in:
genxium 2023-01-01 15:43:25 +08:00
parent c7fc377a2b
commit 8b80117d3d
8 changed files with 310 additions and 6480 deletions

File diff suppressed because one or more lines are too long

View File

@ -97,9 +97,9 @@ message MeleeBullet {
// for defender // for defender
int32 hitStunFrames = 9; int32 hitStunFrames = 9;
int32 blockStunFrames = 10; int32 blockStunFrames = 10;
double pushback = 11; double pushbackX = 11;
double pushbackY = 12;
int32 releaseTriggerType = 12; // 1: rising-edge, 2: falling-edge
int32 damage = 13; int32 damage = 13;
int32 offenderJoinIndex = 14; int32 offenderJoinIndex = 14;
@ -108,44 +108,28 @@ message MeleeBullet {
double hitboxSizeX = 16; double hitboxSizeX = 16;
double hitboxSizeY = 17; double hitboxSizeY = 17;
double selfMoveforwardX = 18; double selfLockVelX = 18;
double selfMoveforwardY = 19; double selfLockVelY = 19;
int32 releaseTriggerType = 999; // 1: rising-edge, 2: falling-edge
} }
message BattleColliderInfo { message BattleColliderInfo {
string stageName = 1; string stageName = 1;
int32 stageDiscreteW = 2;
int32 stageDiscreteH = 3;
int32 stageTileW = 4;
int32 stageTileH = 5;
int32 intervalToPing = 6; int32 intervalToPing = 2;
int32 willKickIfInactiveFor = 7; int32 willKickIfInactiveFor = 3;
int32 boundRoomId = 8; int32 boundRoomId = 4;
int32 battleDurationFrames = 9; int64 battleDurationNanos = 5;
int64 battleDurationNanos = 10; int32 inputFrameUpsyncDelayTolerance = 6;
int32 serverFps = 11; int32 maxChasingRenderFramesPerUpdate = 7;
int32 inputDelayFrames = 12; // in the count of render frames double rollbackEstimatedDtMillis = 8;
uint32 inputScaleFrames = 13; // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames) int64 rollbackEstimatedDtNanos = 9;
int32 nstDelayFrames = 14; // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
int32 inputFrameUpsyncDelayTolerance = 15;
int32 maxChasingRenderFramesPerUpdate = 16;
int32 playerBattleState = 17;
double rollbackEstimatedDtMillis = 18;
int64 rollbackEstimatedDtNanos = 19;
double worldToVirtualGridRatio = 20; int32 renderCacheSize = 10;
double virtualGridToWorldRatio = 21; int32 spaceOffsetX = 11;
int32 spaceOffsetY = 12;
int32 spAtkLookupFrames = 22; int32 collisionMinStep = 13;
int32 renderCacheSize = 23;
double snapIntoPlatformOverlap = 24;
double snapIntoPlatformThreshold = 25;
int32 jumpingInitVelY = 26;
int32 gravityX = 27;
int32 gravityY = 28;
int32 collisionMinStep = 29;
bool frameDataLoggingEnabled = 999; bool frameDataLoggingEnabled = 999;
} }

View File

@ -315,11 +315,7 @@ cc.Class({
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 = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1); self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1);
const spaceW = self.stageDiscreteW * self.stageTileW; self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs((self.spaceOffsetX << 1), (self.spaceOffsetY << 1), self.collisionMinStep, self.collisionMinStep);
const spaceH = self.stageDiscreteH * self.stageTileH;
self.spaceOffsetX = (spaceW >> 1);
self.spaceOffsetY = (spaceH >> 1);
self.gopkgsCollisionSys = gopkgs.NewCollisionSpaceJs(spaceW, spaceH, self.collisionMinStep, self.collisionMinStep);
self.gopkgsCollisionSysMap = {}; // [WARNING] Don't use "JavaScript Map" which could cause loss of type information when passing through Golang transpiled functions! self.gopkgsCollisionSysMap = {}; // [WARNING] Don't use "JavaScript Map" which could cause loss of type information when passing through Golang transpiled functions!
self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used self.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used
@ -596,7 +592,7 @@ cc.Class({
const jsMeleeBulletsArr = []; const jsMeleeBulletsArr = [];
for (let k in pbRdf.meleeBullets) { for (let k in pbRdf.meleeBullets) {
const pbBullet = pbRdf.meleeBullets[k]; const pbBullet = pbRdf.meleeBullets[k];
const jsBullet = gopkgs.NewMeleeBulletJs(pbBullet.battleLocalId, pbBullet.startupFrames, pbBullet.activeFrames, pbBullet.recoveryFrames, pbBullet.recoveryFramesOnBlock, pbBullet.recoveryFramesOnHit, pbBullet.hitStunFrames, pbBullet.blockStunFrames, pbBullet.releaseTriggerType, pbBullet.damage, pbBullet.offenderJoinIndex, pbBullet.offenderPlayerId, pbBullet.pushback, pbBullet.hitboxOffset, pbBullet.selfMoveforwardX, pbBullet.selfMoveforwardY, pbBullet.hitboxSizeX, pbBullet.hitboxSizeY); const jsBullet = gopkgs.NewMeleeBulletJs(pbBullet.originatedRenderFrameId, pbBullet.offenderJoinIndex, pbBullet.startupFrames, pbBullet.cancellableStFrame, pbBullet.cancellableEdFrame, pbBullet.activeFrames, pbBullet.hitStunFrames, pbBullet.blockStunFrames, pbBullet.pushbackX, pbBullet.pushbackY, pbBullet.damage, pbBullet.selfLockVelX, pbBullet.selfLockVelY, pbBullet.hitboxOffsetX, pbBullet.hitboxOffsetY, pbBullet.hitboxSizeX, pbBullet.hitboxSizeY, pbBullet.blowUp);
jsMeleeBulletsArr.push(jsBullet); jsMeleeBulletsArr.push(jsBullet);
} }
@ -638,8 +634,10 @@ cc.Class({
} }
// The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet) // The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet)
if (null == pbRdf.speciesIdList) {
console.error(`pbRdf.speciesIdList is required for starting or resyncing battle!`);
}
self.chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(pbRdf.speciesIdList); self.chConfigsOrderedByJoinIndex = gopkgs.GetCharacterConfigsOrderedByJoinIndex(pbRdf.speciesIdList);
self.playerOpPatternToSkillId = pbRdf.playerOpPatternToSkillId;
self._initPlayerRichInfoDict(rdf.PlayersArr); self._initPlayerRichInfoDict(rdf.PlayersArr);
// Show the top status indicators for IN_BATTLE // Show the top status indicators for IN_BATTLE
@ -831,13 +829,10 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
const self = this; const self = this;
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab) const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter"); const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
if (1 == joinIndex) { const chConfig = self.chConfigsOrderedByJoinIndex[joinIndex - 1];
playerScriptIns.setSpecies("SoldierWaterGhost"); playerScriptIns.setSpecies(chConfig.SpeciesName);
} else if (2 == joinIndex) {
playerScriptIns.setSpecies("UltramanTiga");
}
const [wx, wy] = self.virtualGridToWorldPos(vx, vy); const [wx, wy] = gopkgs.VirtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy); newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node; playerScriptIns.mapNode = self.node;
const colliderRadius = playerDownsyncInfo.ColliderRadius; const colliderRadius = playerDownsyncInfo.ColliderRadius;
@ -846,17 +841,7 @@ batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inpu
const colliderWidth = halfColliderWidth + halfColliderWidth, const colliderWidth = halfColliderWidth + halfColliderWidth,
colliderHeight = halfColliderHeight + halfColliderHeight; // avoid multiplying colliderHeight = halfColliderHeight + halfColliderHeight; // avoid multiplying
const [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY); const newPlayerCollider = gopkgs.GenerateRectColliderJs(wx, wy, colliderWidth, colliderHeight, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(cx, cy);
const gopkgsBoundaryPts = [
gopkgs.NewVec2DJs(0, 0),
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, 0),
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap),
gopkgs.NewVec2DJs(0, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap)
];
const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts);
const newPlayerCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
//const newPlayerCollider = gopkgs.GenerateRectColliderJs(wx, wy, colliderWidth, colliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
self.gopkgsCollisionSys.Add(newPlayerCollider); self.gopkgsCollisionSys.Add(newPlayerCollider);
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
self.gopkgsCollisionSysMap[collisionPlayerIndex] = newPlayerCollider; self.gopkgsCollisionSysMap[collisionPlayerIndex] = newPlayerCollider;
@ -1067,7 +1052,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
const currPlayerDownsync = playersArr[k]; const currPlayerDownsync = playersArr[k];
const chConfig = self.chConfigsOrderedByJoinIndex[k]; const chConfig = self.chConfigsOrderedByJoinIndex[k];
const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]); const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]);
const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY); const [wx, wy] = gopkgs.VirtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY);
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);
@ -1107,7 +1092,7 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
}; };
self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone); self.rdfIdToActuallyUsedInput.set(currRdf.Id, inputFrameDownsyncClone);
} }
const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.playerOpPatternToSkillId, self.chConfigsOrderedByJoinIndex); const nextRdf = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, currRdf, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex);
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!
@ -1236,12 +1221,6 @@ actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`);
return `{${(playerCollider.x + leftPadding + halfBoundingW).toFixed(2)}, ${(playerCollider.y + bottomPadding + halfBoundingH).toFixed(2)}}`; return `{${(playerCollider.x + leftPadding + halfBoundingW).toFixed(2)}, ${(playerCollider.y + bottomPadding + halfBoundingH).toFixed(2)}}`;
}, },
virtualGridToWorldPos(vx, vy) {
// No loss of precision
const self = this;
return [vx * self.virtualGridToWorldRatio, vy * self.virtualGridToWorldRatio];
},
showDebugBoundaries(rdf) { showDebugBoundaries(rdf) {
const self = this; const self = this;
const leftPadding = self.snapIntoPlatformOverlap, const leftPadding = self.snapIntoPlatformOverlap,

View File

@ -40,24 +40,6 @@ cc.Class({
self.rollbackEstimatedDtNanos = 16666666; self.rollbackEstimatedDtNanos = 16666666;
self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis; self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis;
self.worldToVirtualGridRatio = 100;
self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio;
const opJoinIndexPrefix1 = (1 << 8);
const playerOpPatternToSkillId = {};
playerOpPatternToSkillId[opJoinIndexPrefix1 + 0] = 1;
playerOpPatternToSkillId[opJoinIndexPrefix1 + 1] = 2;
/*
[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, "snapIntoPlatformOverlap" should be small enough such that the walking "velX" or jumping initial "velY" can escape from it by 1 renderFrame (when jumping is triggered, the character is waived from snappig for 1 renderFrame).
*/
self.snapIntoPlatformOverlap = 0.1;
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
self.jumpingInitVelY = 7 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame)
[self.gravityX, self.gravityY] = [0, -0.5 * self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2)
const tiledMapIns = self.node.getComponent(cc.TiledMap); const tiledMapIns = self.node.getComponent(cc.TiledMap);
const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon"); const fullPathOfTmxFile = cc.js.formatStr("map/%s/map", "dungeon");
@ -86,10 +68,8 @@ cc.Class({
self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height); self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height);
self.node.setPosition(cc.v2(0, 0)); self.node.setPosition(cc.v2(0, 0));
self.stageDiscreteW = newMapSize.width; self.spaceOffsetX = ((newMapSize.width * newTileSize.width) >> 1);
self.stageDiscreteH = newMapSize.height; self.spaceOffsetY = ((newMapSize.height * newTileSize.height) >> 1);
self.stageTileW = newTileSize.width;
self.stageTileH = newTileSize.height;
self._resetCurrentMatch(); self._resetCurrentMatch();
let barrierIdCounter = 0; let barrierIdCounter = 0;
@ -142,15 +122,18 @@ cc.Class({
self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider; self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider;
} }
const p1Vpos = gopkgs.WorldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y);
const speedV = gopkgs.WorldToVirtualGridPos(1.0, 0);
const startRdf = window.pb.protos.RoomDownsyncFrame.create({ const startRdf = window.pb.protos.RoomDownsyncFrame.create({
id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START,
playersArr: [ playersArr: [
window.pb.protos.PlayerDownsync.create({ window.pb.protos.PlayerDownsync.create({
id: 10, id: 10,
joinIndex: 1, joinIndex: 1,
virtualGridX: boundaryObjs.playerStartingPositions[0].x * self.worldToVirtualGridRatio, virtualGridX: p1Vpos[0],
virtualGridY: boundaryObjs.playerStartingPositions[0].y * self.worldToVirtualGridRatio, virtualGridY: p1Vpos[1],
speed: 1 * self.worldToVirtualGridRatio, speed: speedV[0],
colliderRadius: 12, colliderRadius: 12,
characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0], characterState: window.ATK_CHARACTER_STATE.InAirIdle1NoJump[0],
framesToRecover: 0, framesToRecover: 0,
@ -162,7 +145,6 @@ cc.Class({
}), }),
], ],
speciesIdList: [0], speciesIdList: [0],
playerOpPatternToSkillId: playerOpPatternToSkillId,
}); });
self.selfPlayerInfo = { self.selfPlayerInfo = {
@ -208,48 +190,4 @@ cc.Class({
} }
}, },
spawnPlayerNode(joinIndex, vx, vy, playerDownsyncInfo) {
const self = this;
const newPlayerNode = cc.instantiate(self.controlledCharacterPrefab)
const playerScriptIns = newPlayerNode.getComponent("ControlledCharacter");
if (1 == joinIndex) {
playerScriptIns.setSpecies("MonkGirl");
} else if (2 == joinIndex) {
playerScriptIns.setSpecies("UltramanTiga");
}
const [wx, wy] = self.virtualGridToWorldPos(vx, vy);
newPlayerNode.setPosition(wx, wy);
playerScriptIns.mapNode = self.node;
const colliderRadius = playerDownsyncInfo.ColliderRadius;
const halfColliderWidth = colliderRadius,
halfColliderHeight = colliderRadius + colliderRadius; // avoid multiplying
const colliderWidth = halfColliderWidth + halfColliderWidth,
colliderHeight = halfColliderHeight + halfColliderHeight; // avoid multiplying
const [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY);
const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(cx, cy);
const gopkgsBoundaryPts = [
gopkgs.NewVec2DJs(0, 0),
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, 0),
gopkgs.NewVec2DJs(self.snapIntoPlatformOverlap + colliderWidth + self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap),
gopkgs.NewVec2DJs(0, self.snapIntoPlatformOverlap + colliderHeight + self.snapIntoPlatformOverlap)
];
const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts);
const newPlayerCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
//const newPlayerCollider = gopkgs.GenerateRectColliderJs(wx, wy, colliderWidth, colliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY, playerDownsyncInfo, "Player");
self.gopkgsCollisionSys.Add(newPlayerCollider);
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
self.gopkgsCollisionSysMap[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];
},
}); });

View File

@ -20,9 +20,9 @@ const (
GRAVITY_X = int32(0) GRAVITY_X = int32(0)
GRAVITY_Y = -int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO) // makes all "playerCollider.Y" a multiple of 0.5 in all cases GRAVITY_Y = -int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
INPUT_DELAY_FRAMES = int32(8) INPUT_DELAY_FRAMES = int32(8) // in the count of render frames
NST_DELAY_FRAMES = int32(16) INPUT_SCALE_FRAMES = uint32(2) // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames)
INPUT_SCALE_FRAMES = uint32(2) NST_DELAY_FRAMES = int32(16) // network-single-trip delay in the count of render frames, proposed to be (InputDelayFrames >> 1) because we expect a round-trip delay to be exactly "InputDelayFrames"
SNAP_INTO_PLATFORM_OVERLAP = float64(0.1) SNAP_INTO_PLATFORM_OVERLAP = float64(0.1)
SNAP_INTO_PLATFORM_THRESHOLD = float64(0.5) SNAP_INTO_PLATFORM_THRESHOLD = float64(0.5)
@ -249,18 +249,18 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, re
return false return false
} }
func WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio float64) (int32, int32) { func WorldToVirtualGridPos(wx, wy float64) (int32, int32) {
// [WARNING] Introduces loss of precision! // [WARNING] Introduces loss of precision!
// 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. // 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.
var virtualGridX int32 = int32(math.Floor(wx * worldToVirtualGridRatio)) var virtualGridX int32 = int32(math.Floor(wx * WORLD_TO_VIRTUAL_GRID_RATIO))
var virtualGridY int32 = int32(math.Floor(wy * worldToVirtualGridRatio)) var virtualGridY int32 = int32(math.Floor(wy * WORLD_TO_VIRTUAL_GRID_RATIO))
return virtualGridX, virtualGridY return virtualGridX, virtualGridY
} }
func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float64, float64) { func VirtualGridToWorldPos(vx, vy int32) (float64, float64) {
// No loss of precision // No loss of precision
var wx float64 = float64(vx) * virtualGridToWorldRatio var wx float64 = float64(vx) * VIRTUAL_GRID_TO_WORLD_RATIO
var wy float64 = float64(vy) * virtualGridToWorldRatio var wy float64 = float64(vy) * VIRTUAL_GRID_TO_WORLD_RATIO
return wx, wy return wx, wy
} }
@ -272,13 +272,13 @@ func PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPaddin
return cx + halfBoundingW + leftPadding - collisionSpaceOffsetX, cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY return cx + halfBoundingW + leftPadding - collisionSpaceOffsetX, cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY
} }
func PolygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) { func PolygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (int32, int32) {
wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) return WorldToVirtualGridPos(wx, wy)
} }
func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) wx, wy := VirtualGridToWorldPos(vx, vy)
return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY)
} }
@ -357,11 +357,11 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
patternId := PATTERN_ID_NO_OP patternId := PATTERN_ID_NO_OP
if decodedInput.BtnALevel > prevBtnALevel { if decodedInput.BtnALevel > prevBtnALevel {
if currPlayerDownsync.InAir { if currPlayerDownsync.InAir {
patternId = 1 patternId = 1
} else { } else {
patternId = 0 patternId = 0
} }
effDx, effDy = 0, 0 // Most patterns/skills should not allow simultaneous movement effDx, effDy = 0, 0 // Most patterns/skills should not allow simultaneous movement
} }
@ -369,7 +369,7 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
} }
// [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(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, playerOpPatternToSkillId map[int]int, chConfigsOrderedByJoinIndex []*CharacterConfig) *RoomDownsyncFrame { func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *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)
@ -408,6 +408,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// 1. Process player inputs // 1. Process player inputs
for i, currPlayerDownsync := range currRenderFrame.PlayersArr { for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
jumpedOrNotList[i] = false jumpedOrNotList[i] = false
chConfig := chConfigsOrderedByJoinIndex[i]
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
patternId, jumpedOrNot, effDx, effDy := deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame, currRenderFrame, inputsBuffer, INPUT_DELAY_FRAMES, INPUT_SCALE_FRAMES) patternId, jumpedOrNot, effDx, effDy := deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame, currRenderFrame, inputsBuffer, INPUT_DELAY_FRAMES, INPUT_SCALE_FRAMES)
if PATTERN_ID_UNABLE_TO_OP == patternId { if PATTERN_ID_UNABLE_TO_OP == patternId {
@ -415,19 +416,22 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
} }
if jumpedOrNot { if jumpedOrNot {
thatPlayerInNextFrame.VelY = int32(chConfigsOrderedByJoinIndex[i].JumpingInitVelY) thatPlayerInNextFrame.VelY = int32(chConfig.JumpingInitVelY)
jumpedOrNotList[i] = true jumpedOrNotList[i] = true
} }
joinIndex := currPlayerDownsync.JoinIndex joinIndex := currPlayerDownsync.JoinIndex
if PATTERN_ID_NO_OP != patternId { if PATTERN_ID_NO_OP != patternId {
if skillId, existent := playerOpPatternToSkillId[(int(joinIndex)<<uint(8))+patternId]; existent { if skillId, existent := chConfig.PatternIdToSkillId[patternId]; existent {
skillConfig := skillIdToBullet[skillId].(*MeleeBullet) // Hardcoded type "MeleeBullet" for now skillConfig := skills[skillId]
var newMeleeBullet MeleeBullet = *skillConfig // Hardcoded to use only the first hit for now
newMeleeBullet.OffenderJoinIndex = joinIndex switch v := skillConfig.Hits[0].(type) {
newMeleeBullet.OffenderPlayerId = currPlayerDownsync.Id case *MeleeBullet:
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id var newBullet MeleeBullet = *v // Copied primitive fields into an onstack variable
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newMeleeBullet) newBullet.OriginatedRenderFrameId = currRenderFrame.Id
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames newBullet.OffenderJoinIndex = joinIndex
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newBullet)
thatPlayerInNextFrame.FramesToRecover = skillConfig.RecoveryFrames
}
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1 thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
if false == currPlayerDownsync.InAir { if false == currPlayerDownsync.InAir {
thatPlayerInNextFrame.VelX = 0 thatPlayerInNextFrame.VelX = 0
@ -453,13 +457,14 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex] playerCollider := collisionSysMap[collisionPlayerIndex]
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
chConfig := chConfigsOrderedByJoinIndex[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 jumpedOrNotList[i] { if jumpedOrNotList[i] {
newVy += int32(chConfigsOrderedByJoinIndex[i].JumpingInitVelY) // Immediately gets out of any snapping newVy += chConfig.JumpingInitVelY // Immediately gets out of any snapping
} }
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY, VIRTUAL_GRID_TO_WORLD_RATIO) playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY)
// Update in the collision system // Update in the collision system
playerCollider.Update() playerCollider.Update()
@ -475,13 +480,13 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
if (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames+meleeBullet.ActiveFrames > currRenderFrame.Id) { if (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames+meleeBullet.ActiveFrames > currRenderFrame.Id) {
offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1] offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1]
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis" xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX { if 0 > offender.DirX {
xfac = float64(-1.0) xfac = -xfac
} }
offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, VIRTUAL_GRID_TO_WORLD_RATIO) bulletWx, bulletWy := VirtualGridToWorldPos(offender.VirtualGridX+xfac*meleeBullet.HitboxOffsetX, offender.VirtualGridY)
bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy hitboxSizeWx, hitboxSizeWy := VirtualGridToWorldPos(meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY)
newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet") newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, hitboxSizeWx, hitboxSizeWy, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet")
collisionSys.Add(newBulletCollider) collisionSys.Add(newBulletCollider)
bulletColliders = append(bulletColliders, newBulletCollider) bulletColliders = append(bulletColliders, newBulletCollider)
} else { } else {
@ -497,7 +502,9 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, &(effPushbacks[joinIndex-1])) hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, playerCollider, playerShape, SNAP_INTO_PLATFORM_OVERLAP, &(effPushbacks[joinIndex-1]))
thatPlayerInNextFrame := nextRenderFramePlayers[i] thatPlayerInNextFrame := nextRenderFramePlayers[i]
chConfig := chConfigsOrderedByJoinIndex[i]
landedOnGravityPushback := false landedOnGravityPushback := false
if collision := playerCollider.Check(0, 0); nil != collision { if collision := playerCollider.Check(0, 0); nil != collision {
for _, obj := range collision.Objects { for _, obj := range collision.Objects {
isBarrier, isAnotherPlayer, isBullet := false, false, false isBarrier, isAnotherPlayer, isBullet := false, false, false
@ -547,10 +554,97 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
// fallStopping // fallStopping
thatPlayerInNextFrame.VelX = 0 thatPlayerInNextFrame.VelX = 0
thatPlayerInNextFrame.VelY = 0 thatPlayerInNextFrame.VelY = 0
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 if ATK_CHARACTER_STATE_BLOWN_UP1 == thatPlayerInNextFrame.CharacterState {
thatPlayerInNextFrame.FramesToRecover = 0 thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_LAY_DOWN1
thatPlayerInNextFrame.FramesToRecover = chConfig.LayDownFramesToRecover
} else {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame.FramesToRecover = 0
}
} else {
// not fallStopping, could be in LayDown or GetUp
if ATK_CHARACTER_STATE_LAY_DOWN1 == thatPlayerInNextFrame.CharacterState {
if 0 == thatPlayerInNextFrame.FramesToRecover {
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_GET_UP1
thatPlayerInNextFrame.FramesToRecover = chConfig.GetUpFramesToRecover
}
} else if ATK_CHARACTER_STATE_GET_UP1 == thatPlayerInNextFrame.CharacterState {
if thatPlayerInNextFrame.FramesInChState == chConfig.GetUpFrames {
// [WARNING] Before reaching here, the player had 3 invinsible frames to either attack or jump, if it ever took any action then this condition wouldn't have been met, thus we hereby only transit it back to IDLE as it took no action
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
}
}
} }
} }
}
// 5. Check bullet-anything collisions
for _, bulletCollider := range bulletColliders {
collision := bulletCollider.Check(0, 0)
bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
switch v := bulletCollider.Data.(type) {
case *MeleeBullet:
if nil == collision {
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, v)
continue
}
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
offender := currRenderFrame.PlayersArr[v.OffenderJoinIndex-1]
for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case *PlayerDownsync:
if v.OffenderJoinIndex == t.JoinIndex {
continue
}
overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape)
if !overlapped {
continue
}
joinIndex := t.JoinIndex
xfac := int32(1) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = -xfac
}
pushbackX, pushbackY := VirtualGridToWorldPos(-xfac*v.PushbackX, v.PushbackY)
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
if 0 > projectedMagnitude {
//fmt.Printf("defenderPlayerId=%d, joinIndex=%d reducing bullet pushback={%.3f, %.3f} by {%.3f, %.3f} where hardPushbackNorm={%.3f, %.3f}, projectedMagnitude=%.3f at renderFrame.id=%d", t.Id, joinIndex, pushbackX, pushbackY, projectedMagnitude*hardPushbackNorm.X, projectedMagnitude*hardPushbackNorm.Y, hardPushbackNorm.X, hardPushbackNorm.Y, projectedMagnitude, currRenderFrame.Id)
pushbackX -= projectedMagnitude * hardPushbackNorm.X
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
}
}
effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY
atkedPlayerInNextFrame := nextRenderFramePlayers[t.JoinIndex-1]
if v.BlowUp {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_BLOWN_UP1
} else {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if v.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = v.HitStunFrames
}
default:
}
}
}
}
// 6. Get players out of stuck barriers if there's any
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex]
// Update "virtual grid position"
thatPlayerInNextFrame := nextRenderFramePlayers[i]
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY)
// Update "CharacterState"
if thatPlayerInNextFrame.InAir { if thatPlayerInNextFrame.InAir {
oldNextCharacterState := thatPlayerInNextFrame.CharacterState oldNextCharacterState := thatPlayerInNextFrame.CharacterState
switch oldNextCharacterState { switch oldNextCharacterState {
@ -566,70 +660,6 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *RingBuffer
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
} }
} }
}
// 5. Check bullet-anything collisions
for _, bulletCollider := range bulletColliders {
meleeBullet := bulletCollider.Data.(*MeleeBullet)
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
collision := bulletCollider.Check(0, 0)
bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame
if nil == collision {
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet)
continue
}
offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1]
for _, obj := range collision.Objects {
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
switch t := obj.Data.(type) {
case *PlayerDownsync:
if meleeBullet.OffenderPlayerId == t.Id {
continue
}
overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape)
if !overlapped {
continue
}
joinIndex := t.JoinIndex
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
if 0 > offender.DirX {
xfac = float64(-1.0)
}
pushbackX, pushbackY := -xfac*meleeBullet.Pushback, float64(0)
for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] {
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
if 0 > projectedMagnitude {
//fmt.Printf("defenderPlayerId=%d, joinIndex=%d reducing bullet pushback={%.3f, %.3f} by {%.3f, %.3f} where hardPushbackNorm={%.3f, %.3f}, projectedMagnitude=%.3f at renderFrame.id=%d", t.Id, joinIndex, pushbackX, pushbackY, projectedMagnitude*hardPushbackNorm.X, projectedMagnitude*hardPushbackNorm.Y, hardPushbackNorm.X, hardPushbackNorm.Y, projectedMagnitude, currRenderFrame.Id)
pushbackX -= projectedMagnitude * hardPushbackNorm.X
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
}
}
effPushbacks[joinIndex-1].X += pushbackX
effPushbacks[joinIndex-1].Y += pushbackY
atkedPlayerInCurFrame, atkedPlayerInNextFrame := currRenderFrame.PlayersArr[t.JoinIndex-1], nextRenderFramePlayers[t.JoinIndex-1]
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
if atkedPlayerInCurFrame.InAir {
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
}
oldFramesToRecover := nextRenderFramePlayers[t.JoinIndex-1].FramesToRecover
if meleeBullet.HitStunFrames > oldFramesToRecover {
atkedPlayerInNextFrame.FramesToRecover = meleeBullet.HitStunFrames
}
default:
}
}
}
// 6. Get players out of stuck barriers if there's any
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
joinIndex := currPlayerDownsync.JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap[collisionPlayerIndex]
// Update "virtual grid position"
thatPlayerInNextFrame := nextRenderFramePlayers[i]
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY, WORLD_TO_VIRTUAL_GRID_RATIO)
if thatPlayerInNextFrame.CharacterState != currPlayerDownsync.CharacterState { if thatPlayerInNextFrame.CharacterState != currPlayerDownsync.CharacterState {
thatPlayerInNextFrame.FramesInChState = 0 thatPlayerInNextFrame.FramesInChState = 0
} }

View File

@ -1,5 +1,23 @@
package battle package battle
type CharacterConfig struct {
SpeciesId int
SpeciesName string
InAirIdleFrameIdxTurningPoint int
InAirIdleFrameIdxTurnedCycle int
LayDownFrames int32
LayDownFramesToRecover int32
GetUpFrames int32
GetUpFramesToRecover int32
JumpingInitVelY int32
PatternIdToSkillId map[int]int
}
var Characters = map[int]*CharacterConfig{ var Characters = map[int]*CharacterConfig{
0: &CharacterConfig{ 0: &CharacterConfig{
SpeciesId: 0, SpeciesId: 0,
@ -8,13 +26,13 @@ var Characters = map[int]*CharacterConfig{
InAirIdleFrameIdxTurningPoint: 11, InAirIdleFrameIdxTurningPoint: 11,
InAirIdleFrameIdxTurnedCycle: 1, InAirIdleFrameIdxTurnedCycle: 1,
LayDownFrames: 16, LayDownFrames: int32(16),
LayDownFramesToRecover: 16, LayDownFramesToRecover: int32(16),
GetUpFrames: 33, GetUpFrames: int32(33),
GetUpFramesToRecover: 30, // 3 invinsible frames for just-blown-up character to make a comeback GetUpFramesToRecover: int32(30), // 3 invinsible frames for just-blown-up character to make a comeback
JumpingInitVelY: int(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO), JumpingInitVelY: int32(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO),
PatternIdToSkillId: map[int]int{ PatternIdToSkillId: map[int]int{
0: 1, // Atk1 0: 1, // Atk1
@ -23,52 +41,51 @@ var Characters = map[int]*CharacterConfig{
}, },
} }
var skillIdToBullet = map[int]interface{}{ var skills = map[int]*Skill{
1: &MeleeBullet{ 1: &Skill{
Bullet: Bullet{ RecoveryFrames: int32(34),
// for offender RecoveryFramesOnBlock: int32(34),
StartupFrames: int32(5), RecoveryFramesOnHit: int32(34),
ActiveFrames: int32(10), ReleaseTriggerType: int32(1),
RecoveryFrames: int32(34), Hits: []interface{}{
RecoveryFramesOnBlock: int32(34), &MeleeBullet{
RecoveryFramesOnHit: int32(34), Bullet: Bullet{
HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider StartupFrames: int32(5),
ActiveFrames: int32(10),
// for defender HitStunFrames: int32(18),
HitStunFrames: int32(18), BlockStunFrames: int32(9),
BlockStunFrames: int32(9), Damage: int32(5),
Pushback: float64(8.0), PushbackX: int32(float64(8) * WORLD_TO_VIRTUAL_GRID_RATIO),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge PushbackY: int32(0),
Damage: int32(5), HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxOffsetY: int32(0),
SelfMoveforwardX: 0, HitboxSizeX: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfMoveforwardY: 0, HitboxSizeY: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
HitboxSizeX: 24.0, },
HitboxSizeY: 32.0, },
}, },
}, },
2: &MeleeBullet{ 2: &Skill{
Bullet: Bullet{ RecoveryFrames: int32(34),
// for offender RecoveryFramesOnBlock: int32(34),
StartupFrames: int32(3), RecoveryFramesOnHit: int32(34),
ActiveFrames: int32(20), ReleaseTriggerType: int32(1),
RecoveryFrames: int32(34), Hits: []interface{}{
RecoveryFramesOnBlock: int32(34), &MeleeBullet{
RecoveryFramesOnHit: int32(34), Bullet: Bullet{
HitboxOffset: float64(16.0), // should be about the radius of the PlayerCollider StartupFrames: int32(3),
ActiveFrames: int32(20),
// for defender HitStunFrames: int32(18),
HitStunFrames: int32(18), BlockStunFrames: int32(9),
BlockStunFrames: int32(9), Damage: int32(5),
Pushback: float64(6.0), PushbackX: int32(float64(6) * WORLD_TO_VIRTUAL_GRID_RATIO),
BlowUpVelY: int32(float64(3) * WORLD_TO_VIRTUAL_GRID_RATIO), PushbackY: int32(0),
ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge HitboxOffsetX: int32(float64(12) * WORLD_TO_VIRTUAL_GRID_RATIO),
Damage: int32(5), HitboxOffsetY: int32(0),
HitboxSizeX: int32(float64(32) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfMoveforwardX: 0, HitboxSizeY: int32(float64(24) * WORLD_TO_VIRTUAL_GRID_RATIO),
SelfMoveforwardY: 0, },
HitboxSizeX: 32.0, },
HitboxSizeY: 24.0,
}, },
}, },
} }

View File

@ -53,30 +53,29 @@ type Barrier struct {
type Bullet struct { type Bullet struct {
// for offender // for offender
BattleLocalId int32 OriginatedRenderFrameId int32 // Copied from the first bullet for all subsequent bullets
StartupFrames int32 OffenderJoinIndex int32 // Copied to favor collision handling of the dispatched bullet
StartupFrames int32 // from "OriginatedRenderFrameId"
CancellableStFrame int32 // from "OriginatedRenderFrameId"
CancellableEdFrame int32 // from "OriginatedRenderFrameId"
ActiveFrames int32 ActiveFrames int32
RecoveryFrames int32
RecoveryFramesOnBlock int32
RecoveryFramesOnHit int32
HitboxOffset float64
OriginatedRenderFrameId int32
// for defender // for defender
HitStunFrames int32 HitStunFrames int32
BlockStunFrames int32 BlockStunFrames int32
Pushback float64 PushbackX int32
ReleaseTriggerType int32 PushbackY int32
Damage int32 Damage int32
OffenderJoinIndex int32
OffenderPlayerId int32
SelfMoveforwardX float64 SelfLockVelX int32
SelfMoveforwardY float64 SelfLockVelY int32
HitboxSizeX float64
HitboxSizeY float64
BlowUpVelY int32 HitboxOffsetX int32
HitboxOffsetY int32
HitboxSizeX int32
HitboxSizeY int32
BlowUp bool
} }
type MeleeBullet struct { type MeleeBullet struct {
@ -95,7 +94,12 @@ type FireballBullet struct {
} }
type Skill struct { type Skill struct {
Hits []Bullet // Hits within a "Skill" are automatically triggered BattleLocalId int32
RecoveryFrames int32
RecoveryFramesOnBlock int32
RecoveryFramesOnHit int32
ReleaseTriggerType int32 // 1: rising-edge, 2: falling-edge
Hits []interface{} // Hits within a "Skill" are automatically triggered
} }
type RoomDownsyncFrame struct { type RoomDownsyncFrame struct {
@ -114,21 +118,3 @@ type InputFrameDownsync struct {
InputList []uint64 InputList []uint64
ConfirmedList uint64 ConfirmedList uint64
} }
type CharacterConfig struct {
SpeciesId int
SpeciesName string
InAirIdleFrameIdxTurningPoint int
InAirIdleFrameIdxTurnedCycle int
LayDownFrames int
LayDownFramesToRecover int
GetUpFrames int
GetUpFramesToRecover int
JumpingInitVelY int
PatternIdToSkillId map[int]int
}

View File

@ -64,29 +64,32 @@ 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 { func NewMeleeBulletJs(originatedRenderFrameId, offenderJoinIndex, startupFrames, cancellableStFrame, cancellableEdFrame, activeFrames, hitStunFrames, blockStunFrames, pushbackX, pushbackY, damage, selfLockVelX, selfLockVelY, hitboxOffsetX, hitboxOffsetY, hitboxSizeX, hitboxSizeY int32, blowUp bool) *js.Object {
return js.MakeWrapper(&MeleeBullet{ return js.MakeWrapper(&MeleeBullet{
Bullet: Bullet{ Bullet: Bullet{
BattleLocalId: battleLocalId, OriginatedRenderFrameId: originatedRenderFrameId,
StartupFrames: startupFrames, OffenderJoinIndex: offenderJoinIndex,
ActiveFrames: activeFrames,
RecoveryFrames: recoveryFrames,
RecoveryFramesOnBlock: recoveryFramesOnBlock,
RecoveryFramesOnHit: recoveryFramesOnHit,
HitboxOffset: hitboxOffset,
HitStunFrames: hitStunFrames,
BlockStunFrames: blockStunFrames,
Pushback: pushback,
ReleaseTriggerType: releaseTriggerType,
Damage: damage,
SelfMoveforwardX: selfMoveforwardX, StartupFrames: startupFrames,
SelfMoveforwardY: selfMoveforwardY, CancellableStFrame: cancellableStFrame,
HitboxSizeX: hitboxSizeX, CancellableEdFrame: cancellableEdFrame,
HitboxSizeY: hitboxSizeY, ActiveFrames: activeFrames,
OffenderJoinIndex: offenderJoinIndex, HitStunFrames: hitStunFrames,
OffenderPlayerId: offenderPlayerId, BlockStunFrames: blockStunFrames,
PushbackX: pushbackX,
PushbackY: pushbackY,
Damage: damage,
SelfLockVelX: selfLockVelX,
SelfLockVelY: selfLockVelY,
HitboxOffsetX: hitboxOffsetX,
HitboxOffsetY: hitboxOffsetY,
HitboxSizeX: hitboxSizeX,
HitboxSizeY: hitboxSizeY,
BlowUp: blowUp,
}, },
}) })
} }
@ -110,18 +113,19 @@ func GetCollisionSpaceObjsJs(space *resolv.Space) []*js.Object {
return ret return ret
} }
func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object { func GenerateRectColliderJs(wx, wy, w, h, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object {
/* /*
[WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows. [WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows.
``` ```
var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8); var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8);
var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player"); var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, spaceOffsetX, spaceOffsetY, "Player");
space.Add(a); space.Add(a);
``` ```
The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good. The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good.
However, the full wrapper access here is used for updating "collider.X/collider.Y" at JavaScript runtime. However, the full wrapper access here is used for updating "collider.X/collider.Y" at JavaScript runtime.
*/ */
topPadding, bottomPadding, leftPadding, rightPadding := SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP, SNAP_INTO_PLATFORM_OVERLAP
return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag)) return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag))
} }
@ -138,28 +142,30 @@ func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
return ret return ret
} }
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, playerOpPatternToSkillId map[int]int, chConfigsOrderedByJoinIndex []*CharacterConfig) *js.Object { func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *RingBuffer, currRenderFrame *RoomDownsyncFrame, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig) *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(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, playerOpPatternToSkillId, chConfigsOrderedByJoinIndex)) return js.MakeFullWrapper(ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrame, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex))
} }
func main() { func main() {
js.Global.Set("gopkgs", map[string]interface{}{ js.Global.Set("gopkgs", map[string]interface{}{
"NewVec2DJs": NewVec2DJs, "NewVec2DJs": NewVec2DJs,
"NewPolygon2DJs": NewPolygon2DJs, "NewPolygon2DJs": NewPolygon2DJs,
"NewBarrierJs": NewBarrierJs, "NewBarrierJs": NewBarrierJs,
"NewPlayerDownsyncJs": NewPlayerDownsyncJs, "NewPlayerDownsyncJs": NewPlayerDownsyncJs,
"NewMeleeBulletJs": NewMeleeBulletJs, "NewMeleeBulletJs": NewMeleeBulletJs,
"NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs, "NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs,
"NewCollisionSpaceJs": NewCollisionSpaceJs, "NewCollisionSpaceJs": NewCollisionSpaceJs,
"NewInputFrameDownsync": NewInputFrameDownsync, "NewInputFrameDownsync": NewInputFrameDownsync,
"NewRingBufferJs": NewRingBufferJs, "NewRingBufferJs": NewRingBufferJs,
"GenerateRectColliderJs": GenerateRectColliderJs, "GenerateRectColliderJs": GenerateRectColliderJs,
"GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs, "GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs,
"GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs, "GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs,
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
"WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types "WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types
"PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos, "PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos,
"WorldToVirtualGridPos": WorldToVirtualGridPos,
"VirtualGridToWorldPos": VirtualGridToWorldPos,
"GetCharacterConfigsOrderedByJoinIndex": GetCharacterConfigsOrderedByJoinIndex,
"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs,
}) })
} }