diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index 35c9df1..c7b3353 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -435,24 +435,6 @@ func (pR *Room) StartBattle() { return } - if 0 == pR.RenderFrameId { - for _, player := range pR.PlayersArr { - playerId := player.Id - thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState)) // Might be changed in "OnPlayerDisconnected/OnPlayerLost" from other threads - // [WARNING] DON'T try to send any message to an inactive player! - switch thatPlayerBattleState { - case PlayerBattleStateIns.DISCONNECTED: - case PlayerBattleStateIns.LOST: - case PlayerBattleStateIns.EXPELLED_DURING_GAME: - case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL: - continue - } - kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame) - pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true) - } - Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id)) - } - /* [WARNING] Golang "time.Sleep" is known to be taking longer than specified time to wake up at millisecond granularity, as discussed in https://github.com/golang/go/issues/44343 @@ -462,6 +444,24 @@ func (pR *Room) StartBattle() { nextRenderFrameId := int32((totalElapsedNanos + pR.dilutedRollbackEstimatedDtNanos - 1) / pR.dilutedRollbackEstimatedDtNanos) // fast ceiling toSleepNanos := int64(0) if nextRenderFrameId > pR.RenderFrameId { + if 0 == pR.RenderFrameId { + // It's important to send kickoff frame iff "0 == pR.RenderFrameId && nextRenderFrameId > pR.RenderFrameId", otherwise it might send duplicate kickoff frames + for _, player := range pR.PlayersArr { + playerId := player.Id + thatPlayerBattleState := atomic.LoadInt32(&(player.BattleState)) // Might be changed in "OnPlayerDisconnected/OnPlayerLost" from other threads + // [WARNING] DON'T try to send any message to an inactive player! + switch thatPlayerBattleState { + case PlayerBattleStateIns.DISCONNECTED: + case PlayerBattleStateIns.LOST: + case PlayerBattleStateIns.EXPELLED_DURING_GAME: + case PlayerBattleStateIns.EXPELLED_IN_DISMISSAL: + continue + } + kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame) + pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true) + } + Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id)) + } prevRenderFrameId := pR.RenderFrameId pR.RenderFrameId = nextRenderFrameId @@ -754,7 +754,7 @@ func (pR *Room) OnDismissed() { pR.RollbackEstimatedDtNanos = 16666666 // A little smaller than the actual per frame time, just for logging FAST FRAME dilutedServerFps := float64(55.0) pR.dilutedRollbackEstimatedDtNanos = int64(float64(pR.RollbackEstimatedDtNanos) * float64(pR.ServerFps) / dilutedServerFps) - pR.BattleDurationFrames = 30 * pR.ServerFps + pR.BattleDurationFrames = 60 * pR.ServerFps pR.BattleDurationNanos = int64(pR.BattleDurationFrames) * (pR.RollbackEstimatedDtNanos + 1) pR.InputFrameUpsyncDelayTolerance = 2 pR.MaxChasingRenderFramesPerUpdate = 8 @@ -1307,8 +1307,6 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF } nextRenderFrameMeleeBullets := make([]*MeleeBullet, 0, len(currRenderFrame.MeleeBullets)) // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation? - - // Guaranteed determinism regardless of traversal order effPushbacks := make([]Vec2D, pR.Capacity) hardPushbackNorms := make([][]Vec2D, pR.Capacity) @@ -1346,7 +1344,9 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF } if !characStateAlreadyInAir && characStateIsInterruptWaivable { thatPlayerInNextFrame.VelY = pR.JumpingInitVelY - Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v triggered a jump at renderFrame.id=%v, delayedInputFrame.id=%v, nextVelY=%v", playerId, joinIndex, currRenderFrame.Id, delayedInputFrame.InputFrameId, thatPlayerInNextFrame.VelY)) + if 1 == currPlayerDownsync.JoinIndex { + Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v jumped at {renderFrame.id: %d, virtualX: %d, virtualY: %d, nextVelX: %d, nextVelY: %d}, delayedInputFrame.id=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, delayedInputFrame.InputFrameId)) + } } } @@ -1375,8 +1375,8 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF if 0 != decodedInput.Dx || 0 != decodedInput.Dy { thatPlayerInNextFrame.DirX = decodedInput.Dx thatPlayerInNextFrame.DirY = decodedInput.Dy - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING } else { thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 thatPlayerInNextFrame.VelX = 0 @@ -1389,6 +1389,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF for _, player := range pR.PlayersArr { playerId := player.Id joinIndex := player.JoinIndex + effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0) collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex playerCollider := collisionSysMap[collisionPlayerIndex] currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] @@ -1448,67 +1449,77 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] fallStopping := false possiblyFallStoppedOnAnotherPlayer := false - if collision := playerCollider.Check(0, 0); collision != nil { - for _, obj := range collision.Objects { - isBarrier, isAnotherPlayer, isBullet := false, false, false - switch obj.Data.(type) { - case *Barrier: - isBarrier = true - case *Player: - isAnotherPlayer = true - case *MeleeBullet: - isBullet = true - } - if isBullet { - // ignore bullets for this step - continue - } - bShape := obj.Shape.(*resolv.ConvexPolygon) - if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape); overlapped { - normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0)) - landedOnGravityPushback := (pR.SnapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides - if landedOnGravityPushback { - // kindly note that one player might land on top of another player, and snapping is also required in such case - pushbackX, pushbackY = (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapY - thatPlayerInNextFrame.InAir = false - } - for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] { - projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y - if isBarrier || (0 > projectedMagnitude && isAnotherPlayer) { - pushbackX -= projectedMagnitude * hardPushbackNorm.X - pushbackY -= projectedMagnitude * hardPushbackNorm.Y - } - } - if currPlayerDownsync.InAir && landedOnGravityPushback { - fallStopping = true - if isAnotherPlayer { - possiblyFallStoppedOnAnotherPlayer = true - } - } - effPushbacks[joinIndex-1].X += pushbackX - effPushbacks[joinIndex-1].Y += pushbackY + collision := playerCollider.Check(0, 0) + if nil == collision { + continue + } + for _, obj := range collision.Objects { + isBarrier, isAnotherPlayer, isBullet := false, false, false + switch obj.Data.(type) { + case *Barrier: + isBarrier = true + case *Player: + isAnotherPlayer = true + case *MeleeBullet: + isBullet = true + } + if isBullet { + // ignore bullets for this step + continue + } + bShape := obj.Shape.(*resolv.ConvexPolygon) + overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape) + if !overlapped { + continue + } + normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0)) + landedOnGravityPushback := (pR.SnapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides + if landedOnGravityPushback { + // kindly note that one player might land on top of another player, and snapping is also required in such case + pushbackX, pushbackY = (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapY + thatPlayerInNextFrame.InAir = false + } + for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] { + projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y + if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) { + pushbackX -= projectedMagnitude * hardPushbackNorm.X + pushbackY -= projectedMagnitude * hardPushbackNorm.Y } } - if fallStopping { - thatPlayerInNextFrame.VelX = 0 - thatPlayerInNextFrame.VelY = 0 - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 - thatPlayerInNextFrame.FramesToRecover = 0 - if possiblyFallStoppedOnAnotherPlayer { - Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d possiblyFallStoppedOnAnotherPlayer with effPushback={%.2f, %.2f} at renderFrame.id=%d", playerId, joinIndex, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, currRenderFrame.Id)) + effPushbacks[joinIndex-1].X += pushbackX + effPushbacks[joinIndex-1].Y += pushbackY + if currPlayerDownsync.InAir && landedOnGravityPushback { + fallStopping = true + if isAnotherPlayer { + possiblyFallStoppedOnAnotherPlayer = true + } + if 1 == thatPlayerInNextFrame.JoinIndex { + Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#1 at {renderFrame.id: %d, virtualX: %d, virtualY: %d, velX: %d, velY: %d} with effPushback={%.3f, %.3f}, overlapMag=%.4f, possiblyFallStoppedOnAnotherPlayer=%v", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, currPlayerDownsync.VelX, currPlayerDownsync.VelY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap, possiblyFallStoppedOnAnotherPlayer)) } } - if currPlayerDownsync.InAir { - switch thatPlayerInNextFrame.CharacterState { - case ATK_CHARACTER_STATE_IDLE1: - case ATK_CHARACTER_STATE_WALKING: - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1 - case ATK_CHARACTER_STATE_ATK1: - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1 - case ATK_CHARACTER_STATE_ATKED1: - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 - default: - } + if 1 == joinIndex && currPlayerDownsync.InAir && isBarrier && !landedOnGravityPushback { + Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d inAir & pushed back by barrier & not landed at {renderFrame.id: %d, virtualX: %d, virtualY: %d, velX: %d, velY: %d} with effPushback={%.3f, %.3f}, playerColliderPos={%.3f, %.3f}, barrierPos={%.3f, %.3f}, overlapMag=%.4f, len(hardPushbackNorms)=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, currPlayerDownsync.VelX, currPlayerDownsync.VelY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, playerCollider.X-pR.collisionSpaceOffsetX, playerCollider.Y-pR.collisionSpaceOffsetY, bShape.X-pR.collisionSpaceOffsetX, bShape.Y-pR.collisionSpaceOffsetY, overlapResult.Overlap, len(hardPushbackNorms))) + } + if 1 == joinIndex && currPlayerDownsync.InAir && isAnotherPlayer { + Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d inAir & pushed back by another player at {renderFrame.id: %d, virtualX: %d, virtualY: %d, velX: %d, velY: %d} with effPushback={%.3f, %.3f}, landedOnGravityPushback=%v, fallStopping=%v, playerColliderPos={%.3f, %.3f}, anotherPlayerColliderPos={%.3f, %.3f}, overlapMag=%.4f, len(hardPushbackNorms)=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, currPlayerDownsync.VelX, currPlayerDownsync.VelY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, landedOnGravityPushback, fallStopping, playerCollider.X-pR.collisionSpaceOffsetX, playerCollider.Y-pR.collisionSpaceOffsetY, bShape.X-pR.collisionSpaceOffsetX, bShape.Y-pR.collisionSpaceOffsetY, overlapResult.Overlap, len(hardPushbackNorms))) + } + } + if fallStopping { + thatPlayerInNextFrame.VelX = 0 + thatPlayerInNextFrame.VelY = 0 + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 + thatPlayerInNextFrame.FramesToRecover = 0 + } + if currPlayerDownsync.InAir { + switch thatPlayerInNextFrame.CharacterState { + case ATK_CHARACTER_STATE_IDLE1: + case ATK_CHARACTER_STATE_WALKING: + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1 + case ATK_CHARACTER_STATE_ATK1: + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1 + case ATK_CHARACTER_STATE_ATKED1: + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 + default: } } } @@ -1537,7 +1548,7 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] { projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y if 0 > projectedMagnitude { - Logger.Info(fmt.Sprintf("defenderPlayerId=%d, joinIndex=%d reducing bullet pushback={%.2f, %.2f} by {%.2f, %.2f} where hardPushbackNorm={%.2f, %.2f}, projectedMagnitude=%.2f at renderFrame.id=%d", t.Id, joinIndex, pushbackX, pushbackY, projectedMagnitude*hardPushbackNorm.X, projectedMagnitude*hardPushbackNorm.Y, hardPushbackNorm.X, hardPushbackNorm.Y, projectedMagnitude, currRenderFrame.Id)) + Logger.Info(fmt.Sprintf("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 } @@ -1587,16 +1598,29 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF playerId := player.Id collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex playerCollider := collisionSysMap[collisionPlayerIndex] + playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) // Update "virtual grid position" - thatPlayerInNextFrame := nextRenderFramePlayers[playerId] + currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderAnchorToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio) + + if 1 == thatPlayerInNextFrame.JoinIndex { + if thatPlayerInNextFrame.InAir && (0 != thatPlayerInNextFrame.VelY) { + Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d inAir trajectory: {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelX: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) + } + if currPlayerDownsync.InAir && !thatPlayerInNextFrame.InAir { + Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2 at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelX: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) + } + if !currPlayerDownsync.InAir && thatPlayerInNextFrame.InAir { + Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off at {nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelX: %d}, with playerColliderPos={%.3f, %.3f}, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, playerShape.X-pR.collisionSpaceOffsetX, playerShape.Y-pR.collisionSpaceOffsetY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) + } + } } return &RoomDownsyncFrame{ Id: currRenderFrame.Id + 1, Players: nextRenderFramePlayers, - CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos), MeleeBullets: nextRenderFrameMeleeBullets, + CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos), } } @@ -1676,7 +1700,7 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend } if nil != inputsBufferSnapshot { - Logger.Warn(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, inputsBufferSnapshot.UnconfirmedMask)) + // Logger.Warn(fmt.Sprintf("roomId=%v, room.RenderFrameId=%v, room.CurDynamicsRenderFrameId=%v, room.LastAllConfirmedInputFrameId=%v, unconfirmedMask=%v", pR.Id, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, pR.LastAllConfirmedInputFrameId, inputsBufferSnapshot.UnconfirmedMask)) pR.downsyncToAllPlayers(inputsBufferSnapshot) } } @@ -1746,7 +1770,8 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender 2. reconnection */ - toSendInputFrameIdSt, toSendInputFrameIdEd := toSendInputFrameDownsyncsSnapshot[0].InputFrameId, toSendInputFrameDownsyncsSnapshot[len(toSendInputFrameDownsyncsSnapshot)-1].InputFrameId+1 + //toSendInputFrameIdSt, toSendInputFrameIdEd := toSendInputFrameDownsyncsSnapshot[0].InputFrameId, toSendInputFrameDownsyncsSnapshot[len(toSendInputFrameDownsyncsSnapshot)-1].InputFrameId+1 + _, toSendInputFrameIdEd := toSendInputFrameDownsyncsSnapshot[0].InputFrameId, toSendInputFrameDownsyncsSnapshot[len(toSendInputFrameDownsyncsSnapshot)-1].InputFrameId+1 if pR.BackendDynamicsEnabled && shouldResyncOverall { tmp := pR.RenderFrameBuffer.GetByFrameId(refRenderFrameId) if nil == tmp { @@ -1759,7 +1784,7 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender } refRenderFrame.BackendUnconfirmedMask = unconfirmedMask pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false) - Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false))) + // Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false))) } else { pR.sendSafely(nil, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_INPUT_BATCH, playerId, false) } @@ -1804,21 +1829,25 @@ func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*InputFrameDowns func (pR *Room) calcHardPushbacksNorms(playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, pEffPushback *Vec2D) []Vec2D { ret := make([]Vec2D, 0, 10) // no one would simultaneously have more than 5 hardPushbacks - if collision := playerCollider.Check(0, 0); collision != nil { - for _, obj := range collision.Objects { - switch obj.Data.(type) { - case *Barrier: - barrierShape := obj.Shape.(*resolv.ConvexPolygon) - if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped { - // ALWAY snap into hardPushbacks! - // [OverlapX, OverlapY] is the unit vector that points into the platform - pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY - ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY}) - pEffPushback.X += pushbackX - pEffPushback.Y += pushbackY - } + collision := playerCollider.Check(0, 0) + if nil == collision { + return ret + } + for _, obj := range collision.Objects { + switch obj.Data.(type) { + case *Barrier: + barrierShape := obj.Shape.(*resolv.ConvexPolygon) + overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape) + if !overlapped { + continue } - + // ALWAY snap into hardPushbacks! + // [OverlapX, OverlapY] is the unit vector that points into the platform + pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY + ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY}) + pEffPushback.X += pushbackX + pEffPushback.Y += pushbackY + default: } } return ret diff --git a/collider_visualizer/worldColliderDisplay.go b/collider_visualizer/worldColliderDisplay.go index 8a89c11..8a9e566 100644 --- a/collider_visualizer/worldColliderDisplay.go +++ b/collider_visualizer/worldColliderDisplay.go @@ -34,14 +34,15 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi spaceOffsetX := float64(spaceW) * 0.5 spaceOffsetY := float64(spaceH) * 0.5 - virtualGridToWorldRatio := 0.1 - playerDefaultSpeed := 20 + worldToVirtualGridRatio := float64(1000) + virtualGridToWorldRatio := float64(1)/worldToVirtualGridRatio + playerDefaultSpeed := 1 * worldToVirtualGridRatio minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 2) - playerColliderRadius := float64(24) + playerColliderRadius := float64(12) playerColliders := make([]*resolv.Object, len(playerPosList.Eles)) space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) for i, playerPos := range playerPosList.Eles { - playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" + playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, playerColliderRadius*2, playerColliderRadius*4, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos =(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon)))) playerColliders[i] = playerCollider space.Add(playerCollider) @@ -50,33 +51,34 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi barrierLocalId := 0 for _, barrierUnaligned := range barrierList.Eles { barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier") - Logger.Info(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon)))) + Logger.Debug(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon)))) space.Add(barrierCollider) barrierLocalId++ } world.Space = space - moveToCollide := false + moveToCollide := true if moveToCollide { - newVx, newVy := int32(-2959), int32(-2261) effPushback := Vec2D{X: float64(0), Y: float64(0)} toTestPlayerCollider := playerColliders[0] + newVx, newVy := int32(43900), int32(-451350) toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, playerColliderRadius, playerColliderRadius, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) - Logger.Info(fmt.Sprintf("Checking collision for virtual (%d, %d), now playerShape=%v", newVx, newVy, ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)))) + Logger.Info(fmt.Sprintf("Checking collision for playerShape=%v", ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)))) toTestPlayerCollider.Update() if collision := toTestPlayerCollider.Check(0, 0); collision != nil { playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon) for _, obj := range collision.Objects { - barrierShape := obj.Shape.(*resolv.ConvexPolygon) - if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped { - Logger.Warn(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY)) + bShape := obj.Shape.(*resolv.ConvexPolygon) + Logger.Warn(fmt.Sprintf("Checking potential: a=%v, b=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape))) + if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape); overlapped { + Logger.Warn(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape), pushbackX, pushbackY)) effPushback.X += pushbackX effPushback.Y += pushbackY } else { - Logger.Warn(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult)) + Logger.Warn(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape), overlapResult)) } } toTestPlayerCollider.X -= effPushback.X @@ -109,7 +111,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge Damage: int32(5), } - bulletLeftToRight := true + bulletLeftToRight := false if bulletLeftToRight { xfac := float64(1.0) offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y @@ -132,7 +134,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi } } - bulletRightToLeft := true + bulletRightToLeft := false if bulletRightToLeft { xfac := float64(-1.0) offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y @@ -177,7 +179,7 @@ func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) { } } - world.Game.DebugDraw(screen, world.Space) + //world.Game.DebugDraw(screen, world.Space) if world.Game.ShowHelpText { diff --git a/dnmshared/resolv_helper.go b/dnmshared/resolv_helper.go index 480360e..67d2258 100644 --- a/dnmshared/resolv_helper.go +++ b/dnmshared/resolv_helper.go @@ -12,7 +12,7 @@ import ( func ConvexPolygonStr(body *resolv.ConvexPolygon) string { var s []string = make([]string, len(body.Points)) for i, p := range body.Points { - s[i] = fmt.Sprintf("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y) + s[i] = fmt.Sprintf("[%.3f, %.3f]", p[0]+body.X, p[1]+body.Y) } return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n")) @@ -66,6 +66,7 @@ func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve playerShape.SetPosition(origX, origY) }() playerShape.SetPosition(origX+oldDx, origY+oldDy) + overlapResult := &SatResult{ Overlap: 0, OverlapX: 0, @@ -74,7 +75,7 @@ func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.Conve BContainedInA: true, Axis: vector.Vector{0, 0}, } - if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped { + if overlapped := isPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped { pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY return true, pushbackX, pushbackY, overlapResult } else { @@ -91,16 +92,17 @@ type SatResult struct { Axis vector.Vector } -func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool { +func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool { aCnt, bCnt := len(a.Points), len(b.Points) // Single point case if 1 == aCnt && 1 == bCnt { if nil != result { result.Overlap = 0 } - return a.Points[0].X() == b.Points[0].X() && a.Points[0].Y() == b.Points[0].Y() + return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1] } + //Logger.Info(fmt.Sprintf("Checking collision between a=%v, b=%v", ConvexPolygonStr(a), ConvexPolygonStr(b))) if 1 < aCnt { for _, axis := range a.SATAxes() { if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { @@ -116,6 +118,7 @@ func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool } } } + //Logger.Info(fmt.Sprintf("a=%v and b=%v are overlapped", ConvexPolygonStr(a), ConvexPolygonStr(b))) return true } @@ -137,9 +140,10 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re e = (-2.98, 1.49).Unit() */ + //Logger.Info(fmt.Sprintf("Checking separation between a=%v, b=%v along axis e={%.3f, %.3f}#1", ConvexPolygonStr(a), ConvexPolygonStr(b), e[0], e[1])) var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64 for _, p := range a.Points { - dot := (p.X()+a.X)*e.X() + (p.Y()+a.Y)*e.Y() + dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1] if aStart > dot { aStart = dot @@ -151,7 +155,7 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re } for _, p := range b.Points { - dot := (p.X()+b.X)*e.X() + (p.Y()+b.Y)*e.Y() + dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1] if bStart > dot { bStart = dot @@ -168,7 +172,6 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re } if nil != result { - result.Axis = e overlap := float64(0) if aStart < bStart { @@ -209,16 +212,19 @@ func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re absoluteOverlap = -overlap } - if 0 == currentOverlap || currentOverlap > absoluteOverlap { + if (0 == result.Axis[0] && 0 == result.Axis[1]) || currentOverlap > absoluteOverlap { var sign float64 = 1 if overlap < 0 { sign = -1 } result.Overlap = absoluteOverlap - result.OverlapX = e.X() * sign - result.OverlapY = e.Y() * sign + result.OverlapX = e[0] * sign + result.OverlapY = e[1] * sign } + + result.Axis = e + //Logger.Info(fmt.Sprintf("Checking separation between a=%v, b=%v along axis e={%.3f, %.3f}#2: aStart=%.3f, aEnd=%.3f, bStart=%.3f, bEnd=%.3f, overlap=%.3f, currentOverlap=%.3f, absoluteOverlap=%.3f, result=%v", ConvexPolygonStr(a), ConvexPolygonStr(b), e[0], e[1], aStart, aEnd, bStart, bEnd, overlap, currentOverlap, absoluteOverlap, result)) } // the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated diff --git a/frontend/assets/resources/map/dungeon/map.tmx b/frontend/assets/resources/map/dungeon/map.tmx index bbda482..94ed106 100644 --- a/frontend/assets/resources/map/dungeon/map.tmx +++ b/frontend/assets/resources/map/dungeon/map.tmx @@ -5,14 +5,14 @@ - eJzt3DFv00AYgGErFUuHigqBVGYGJP4ECxLqgDrRjak/BImdkZGJ/k8cNUbBSmLnavu75HuGR5WaDum9Z+fi5HzRNM0FAAAAAAAAAAAAAAAAAAAUuNyIfh4s52FL1/8hoegOkf1XG13/b63bPVZnSP//+++j//nZ7n870P+yglb6z9d/F/3P2/p/f72xa2z654ToVvov2z8D/fWP7hDZ/0WB6Gb666+//vrH9K+B/vrrr7/++ut/Wv1/jvy76w39z6v/n9bvPY+tP39+bH3RP7x/6Zh21xR/PJP++uuv//q53PTof/79b3r9Hf/66x/f/+WAKftv0z+2/1D3Xf1/tb4eqetv/VdP/7Ht7xrr/1Mxtv/Y9iv9T8pQ/12NP7Su9rTv9++7O5L+cf13tb/a0u8+R/83rXf6V9P/0Hl/u//U9K+3f8ka8Vj6L99/zBzoj+WUfYdM2X9VQYsa+081xoeUtNf/fPpH0z/3HNBff/31j24R1T96/KNlf/8XPf7R9M9N/9z0z03/3PTPTf/c9M9N/9z0z03/3PTPTf/c9M9N/9z0z03/cq96olvqf9r9rwfM8R01/evp7/ivu3+/t/6nzfpP/6XHe99re2T/VQUtau5/v/G5ab5/evr573f3ge30X6Z/172vmwfRDfWfr/+hvUFD79ciz+vH9I/uUGv/rvP7Z6h9D5n+86p9H2H2/nPv+ax9L2n2/ksd/53arhPpv2z/bg4cS//l+k+5ni+5t4/+9Rz/z50Hpfd3Wup1Qv9xSufB28L+j83T9SX96+hfqu3/sbR/Z855kL0/8R0AAAAAAAAAAAAAAACA5f0FyTyU0g== + eJzt3DFv00AYgOEoFUuHigqBVGYGJP4EI+qAOsHG1H/CzsjIRP8njhRLwUpi52L7O/t7hmdpOiT33tmXtJebzWZzAwAAAAAAAAAAAAAAAAAABW73op8H83k+0PZ/Tii6Q2T/7V7b/0fj8YTtCun/f/9T9F+fw/6PPf1vK2il/3T9j9F/3Xav/e3esbHpXhOiW+k/b/8M9Nc/ukNk/1cFopvpr7/++usf078G+uuvv/7666//svr/Gvh793v6r6v/38afE4/t/v780viqf3j/0jFtP1P8eSX99ddf/91zeejQf/39Hzr9rX/99Y/v/7rHmP0P6R/bv6/7sf6/G98u1Pa3/6un/9D2Txv7/6UY2n9o+63+i9LX/1jjT427E+27/bueLqR/XP9j7e8OdLtP0f9d44P+1fQ/d90/7D82/evtX7JHvJT+8/cfMge6Yzlm3z5j9t9W0KLG/mON8Tkl7fVfT/9o+ueeA/rrr7/+0S2i+kePf7Ts7/+ixz+a/rnpn5v+uemfm/656Z+b/rnpn5v+uemfm/656Z+b/rnpn5v+uelf7k1HdEv9l93/vscU/6Omfz39rf+6+3d7679s9n/6zz3ep+7tkf23FbSouf/3ve71vv159BrWf9r+5+75+i/XkP7nzgb1vV+LvK5f0j+6Q639284fr1D7GTL9p1X7OcLs/ac+81n7WdLs/eda/63aPifSf97+7Ry4lP7z9R9zP1/y3T7617P+r50Hpd/vNNd9Qv9hSufB+8L+L40v+lfTv1TT/3Np/9aU8yB7f+I7AAAAAAAAAAAAAAAAAPP7B3wkkY4= - + - + @@ -164,11 +164,6 @@ - - - - - @@ -229,35 +224,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index f2acb21..e5bc0e5 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 216.67520680312998, + 216.19964242526865, 0, 0, 0, diff --git a/frontend/assets/scenes/offline_map_1.fire b/frontend/assets/scenes/offline_map_1.fire index 354c327..840a0db 100644 --- a/frontend/assets/scenes/offline_map_1.fire +++ b/frontend/assets/scenes/offline_map_1.fire @@ -454,7 +454,7 @@ "array": [ 0, 0, - 216.67520680312998, + 215.64032554232523, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 29ee2f7..8c50693 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -331,7 +331,7 @@ cc.Class({ window.mapIns = self; window.forceBigEndianFloatingNumDecoding = self.forceBigEndianFloatingNumDecoding; - self.showCriticalCoordinateLabels = false; + self.showCriticalCoordinateLabels = true; console.warn("+++++++ Map onLoad()"); window.handleClientSessionError = function() { @@ -427,6 +427,16 @@ cc.Class({ mapNode.removeAllChildren(); self._resetCurrentMatch(); + if (self.showCriticalCoordinateLabels) { + const drawer = new cc.Node(); + drawer.setPosition(cc.v2(0, 0)) + safelyAddChild(self.node, drawer); + setLocalZOrder(drawer, 999); + const g = drawer.addComponent(cc.Graphics); + g.lineWidth = 2; + self.g = g; + } + tiledMapIns.tmxAsset = tmxAsset; const newMapSize = tiledMapIns.getMapSize(); const newTileSize = tiledMapIns.getTileSize(); @@ -443,12 +453,14 @@ cc.Class({ } 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 newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => { + const refBoundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node).barriers; + const boundaryObjs = parsedBattleColliderInfo.strToPolygon2DListMap; + for (let k = 0; k < boundaryObjs["Barrier"].eles.length; k++) { + let boundaryObj = boundaryObjs["Barrier"].eles[k]; + const refBoundaryObj = refBoundaryObjs[k]; + // boundaryObj = refBoundaryObj; + const [x0, y0] = [boundaryObj.anchor.x, boundaryObj.anchor.y]; + const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj.points, p => { return [p.x, p.y]; })); newBarrierCollider.data = { @@ -967,13 +979,13 @@ cc.Class({ applyRoomDownsyncFrameDynamics(rdf, prevRdf) { const self = this; for (let [playerId, playerRichInfo] of self.playerRichInfoDict.entries()) { - const immediatePlayerInfo = rdf.players[playerId]; + const currPlayerDownsync = rdf.players[playerId]; const prevRdfPlayer = (null == prevRdf ? null : prevRdf.players[playerId]); - const [wx, wy] = self.virtualGridToWorldPos(immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY); + const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.virtualGridX, currPlayerDownsync.virtualGridY); //const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y)); playerRichInfo.node.setPosition(wx, wy); - playerRichInfo.scriptIns.updateSpeed(immediatePlayerInfo.speed); - playerRichInfo.scriptIns.updateCharacterAnim(immediatePlayerInfo, prevRdfPlayer, false); + playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.speed); + playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false); } // Update countdown @@ -1125,7 +1137,9 @@ cc.Class({ characStateIsInterruptWaivable ) { thatPlayerInNextFrame.velY = self.jumpingInitVelY; - 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 == joinIndex) { + console.log(`playerId=${playerId}, joinIndex=${joinIndex} jumped at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); + } } } @@ -1258,15 +1272,26 @@ cc.Class({ pushback[1] -= projectedMagnitude * hardPushbackNorm[1]; } } + + effPushbacks[joinIndex - 1][0] += pushback[0]; + effPushbacks[joinIndex - 1][1] += pushback[1]; if (currPlayerDownsync.inAir && landedOnGravityPushback) { fallStopping = true; if (isAnotherPlayer) { possiblyFallStoppedOnAnotherPlayer = true; } + if (1 == thatPlayerInNextFrame.joinIndex) { + console.log(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} fallStopping#1 at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, velX: ${currPlayerDownsync.velX}, velY: ${currPlayerDownsync.velY}} with effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, possiblyFallStoppedOnAnotherPlayer=${possiblyFallStoppedOnAnotherPlayer}`); + } } - effPushbacks[joinIndex - 1][0] += pushback[0]; - effPushbacks[joinIndex - 1][1] += pushback[1]; + if (1 == joinIndex && currPlayerDownsync.inAir && isBarrier && !landedOnGravityPushback) { + console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir & pushed back by barrier & not landed at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, velX: ${currPlayerDownsync.velX}, velY: ${currPlayerDownsync.velY}} with effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, barrierPos={${potential.x.toFixed(3)}, ${potential.y.toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, len(hardPushbackNorms)=${hardPushbackNorms.length}`); + } + + if (1 == joinIndex && currPlayerDownsync.inAir && isAnotherPlayer) { + console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir and pushed back by another player at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, velX: ${currPlayerDownsync.velX}, velY: ${currPlayerDownsync.velY}} with effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, landedOnGravityPushback=${landedOnGravityPushback}, fallStopping=${fallStopping}, playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, anotherPlayerColliderPos={${potential.x.toFixed(3)}, ${potential.y.toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, len(hardPushbackNorms)=${hardPushbackNorms.length}`); + } } if (fallStopping) { @@ -1274,9 +1299,6 @@ cc.Class({ thatPlayerInNextFrame.velY = 0; thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; thatPlayerInNextFrame.framesToRecover = 0; - if (possiblyFallStoppedOnAnotherPlayer) { - console.log(`playerId=${playerId}, joinIndex=${joinIndex} possiblyFallStoppedOnAnotherPlayer with effPushback=${effPushbacks[joinIndex - 1]} at renderFrame.id=${currRenderFrame.id}`); - } } if (currPlayerDownsync.inAir) { thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState); @@ -1307,7 +1329,7 @@ cc.Class({ // Otherwise when smashing into a wall the atked player would be pushed into the wall first and only got back in the next renderFrame, not what I want here bulletPushback[0] -= (projectedMagnitude * hardPushbackNorm[0]); bulletPushback[1] -= (projectedMagnitude * hardPushbackNorm[1]); - // console.log(`playerId=${playerId}, joinIndex=${joinIndex} reducing bulletPushback=${JSON.stringify(bulletPushback)} by ${JSON.stringify([projectedMagnitude * hardPushbackNorm[0], projectedMagnitude * hardPushbackNorm[1]])} where hardPushbackNorm=${JSON.stringify(hardPushbackNorm)}, projectedMagnitude=${projectedMagnitude} at renderFrame.id=${currRenderFrame.id}`); + // console.log(`playerId=${playerId}, joinIndex=${joinIndex} reducing bulletPushback=${JSON.stringify(bulletPushback)} by ${JSON.stringify([projectedMagnitude * hardPushbackNorm[0], projectedMagnitude * hardPushbackNorm[1]])} where hardPushbackNorm=${JSON.stringify(hardPushbackNorm)}, projectedMagnitude=${projectedMagnitude} at renderFrame.id=${currRenderFrame.id}`); } } // console.log(`playerId=${playerId}, joinIndex=${joinIndex} is actually pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`); @@ -1348,8 +1370,20 @@ cc.Class({ const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); // Update "virtual grid position" - const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; + const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], 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); + + if (1 == thatPlayerInNextFrame.joinIndex) { + if (thatPlayerInNextFrame.inAir && 0 != thatPlayerInNextFrame.velY) { + console.log(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} inAir trajectory: {nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, with playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`); + } + if (currPlayerDownsync.inAir && !thatPlayerInNextFrame.inAir) { + console.warn(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} fallStopping#2 at {nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, with playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`); + } + if (!currPlayerDownsync.inAir && thatPlayerInNextFrame.inAir) { + console.warn(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} took off at {nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, with playerColliderPos={${playerCollider.x.toFixed(3)}, ${playerCollider.y.toFixed(3)}}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`); + } + } } return window.pb.protos.RoomDownsyncFrame.create({ @@ -1364,11 +1398,10 @@ cc.Class({ This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted. */ const self = this; - let i = renderFrameIdSt, - prevLatestRdf = null, + let prevLatestRdf = null, latestRdf = null; - do { + for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) { latestRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing", this function can be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"! if (null == latestRdf) { console.warn(`Couldn't find renderFrame for i=${i} to rollback, self.renderFrameId=${self.renderFrameId}, lastAllConfirmedRenderFrameId=${self.lastAllConfirmedRenderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`); @@ -1400,8 +1433,7 @@ cc.Class({ self.chaserRenderFrameId = latestRdf.id; } self.recentRenderCache.setByFrameId(latestRdf, latestRdf.id); - ++i; - } while (i < renderFrameIdEd); + } return [prevLatestRdf, latestRdf]; }, @@ -1496,6 +1528,7 @@ cc.Class({ }, calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformOverlap, effPushback) { + const self = this; let ret = []; for (const potential of potentials) { if (null == potential.data || !(true == potential.data.hardPushback)) continue; diff --git a/frontend/collision_test_nodejs.js b/frontend/collision_test_nodejs.js index 3db8003..4aac87d 100644 --- a/frontend/collision_test_nodejs.js +++ b/frontend/collision_test_nodejs.js @@ -11,24 +11,28 @@ function polygonStr(body) { return JSON.stringify(coords); } -const playerCollider = collisionSys.createPolygon(1269.665, 1353.335, [[0, 0], [64, 0], [64, 64], [0, 64]]); +const playerCollider1 = collisionSys.createPolygon(944.000, 676.000, [[0, 0], [24, 0], [24, 48], [0, 48]]); +playerCollider1.data = {isPlayer: true}; +const playerCollider2 = collisionSys.createPolygon(958.000, 724.000, [[0, 0], [24, 0], [24, 48], [0, 48]]); +playerCollider2.data = {isPlayer: true}; const barrierCollider1 = collisionSys.createPolygon(1277.7159000000001, 1570.5575, [[642.5696, 319.159], [0, 319.15680000000003], [5.7286, 0], [643.7451, 0.9014999999999986]]); const barrierCollider2 = collisionSys.createPolygon(1289.039, 1318.0805, [[628.626, 54.254500000000064], [0, 56.03250000000003], [0.42449999999999477, 1.1229999999999905], [625.9715000000001, 0]]); const barrierCollider3 = collisionSys.createPolygon(1207, 1310, [[69, 581], [0, 579], [8, 3], [79, 0]]); -playerCollider.x += -2.98; -playerCollider.y += -50.0; collisionSys.update(); const effPushback = [0.0, 0.0]; const result = collisionSys.createResult(); -const potentials = playerCollider.potentials(); - -for (const barrier of potentials) { - if (!playerCollider.collides(barrier, result)) continue; +// Check collision for player1 +const potentials = playerCollider1.potentials(); +for (const potential of potentials) { + if (null == potential.data || true != potential.data.isPlayer) continue; + console.log(`Collided player potential of a=${polygonStr(playerCollider1)}: b=${polygonStr(potential)}`); + if (!playerCollider1.collides(potential, result)) continue; + console.log(`Collided player of a=${polygonStr(playerCollider1)}: b=${polygonStr(potential)}`); const pushbackX = result.overlap * result.overlap_x; const pushbackY = result.overlap * result.overlap_y; console.log(`Overlapped: a=${polygonStr(result.a)}, b=${polygonStr(result.b)}, pushbackX=${pushbackX}, pushbackY=${pushbackY}`);