mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 11:48:56 +00:00
Draft version for jumping with backend sync.
This commit is contained in:
parent
5a463239bb
commit
c171ebc211
@ -17,12 +17,16 @@ func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) map[int32]
|
|||||||
VirtualGridY: last.VirtualGridY,
|
VirtualGridY: last.VirtualGridY,
|
||||||
DirX: last.DirX,
|
DirX: last.DirX,
|
||||||
DirY: last.DirY,
|
DirY: last.DirY,
|
||||||
ColliderRadius: last.ColliderRadius,
|
VelX: last.VelX,
|
||||||
|
VelY: last.VelY,
|
||||||
Speed: last.Speed,
|
Speed: last.Speed,
|
||||||
BattleState: last.BattleState,
|
BattleState: last.BattleState,
|
||||||
|
CharacterState: last.CharacterState,
|
||||||
|
InAir: last.InAir,
|
||||||
|
JoinIndex: last.JoinIndex,
|
||||||
|
ColliderRadius: last.ColliderRadius,
|
||||||
Score: last.Score,
|
Score: last.Score,
|
||||||
Removed: last.Removed,
|
Removed: last.Removed,
|
||||||
JoinIndex: last.JoinIndex,
|
|
||||||
}
|
}
|
||||||
if withMetaInfo {
|
if withMetaInfo {
|
||||||
toRet[k].Name = last.Name
|
toRet[k].Name = last.Name
|
||||||
|
@ -201,6 +201,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, session *websocke
|
|||||||
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
||||||
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
pPlayerFromDbInit.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||||
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||||
|
pPlayerFromDbInit.InAir = true // Hardcoded
|
||||||
|
|
||||||
pR.Players[playerId] = pPlayerFromDbInit
|
pR.Players[playerId] = pPlayerFromDbInit
|
||||||
pR.PlayerDownsyncSessionDict[playerId] = session
|
pR.PlayerDownsyncSessionDict[playerId] = session
|
||||||
@ -232,6 +233,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
|||||||
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
|
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
|
||||||
pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
pEffectiveInRoomPlayerInstance.Speed = pR.PlayerDefaultSpeed // Hardcoded
|
||||||
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||||
|
pEffectiveInRoomPlayerInstance.InAir = true // Hardcoded
|
||||||
|
|
||||||
pR.PlayerDownsyncSessionDict[playerId] = session
|
pR.PlayerDownsyncSessionDict[playerId] = session
|
||||||
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
|
pR.PlayerSignalToCloseDict[playerId] = signalToCloseConnOfThisPlayer
|
||||||
@ -422,7 +424,7 @@ func (pR *Room) StartBattle() {
|
|||||||
stCalculation := utils.UnixtimeNano()
|
stCalculation := utils.UnixtimeNano()
|
||||||
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
|
elapsedNanosSinceLastFrameIdTriggered := stCalculation - pR.LastRenderFrameIdTriggeredAt
|
||||||
if elapsedNanosSinceLastFrameIdTriggered < pR.RollbackEstimatedDtNanos {
|
if elapsedNanosSinceLastFrameIdTriggered < pR.RollbackEstimatedDtNanos {
|
||||||
Logger.Warn(fmt.Sprintf("renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v", pR.RenderFrameId, pR.Id, elapsedNanosSinceLastFrameIdTriggered))
|
Logger.Debug(fmt.Sprintf("renderFrameId=%v@roomId=%v: Is backend running too fast? elapsedNanosSinceLastFrameIdTriggered=%v", pR.RenderFrameId, pR.Id, elapsedNanosSinceLastFrameIdTriggered))
|
||||||
}
|
}
|
||||||
|
|
||||||
if pR.RenderFrameId > pR.BattleDurationFrames {
|
if pR.RenderFrameId > pR.BattleDurationFrames {
|
||||||
@ -566,12 +568,12 @@ func (pR *Room) OnBattleCmdReceived(pReq *WsReq) {
|
|||||||
atomic.StoreInt32(&(player.AckingFrameId), ackingFrameId)
|
atomic.StoreInt32(&(player.AckingFrameId), ackingFrameId)
|
||||||
atomic.StoreInt32(&(player.AckingInputFrameId), ackingInputFrameId)
|
atomic.StoreInt32(&(player.AckingInputFrameId), ackingInputFrameId)
|
||||||
|
|
||||||
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock about to lock: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
|
//Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock about to lock: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
|
||||||
pR.InputsBufferLock.Lock()
|
pR.InputsBufferLock.Lock()
|
||||||
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock locked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
|
//Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock locked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
|
||||||
defer func() {
|
defer func() {
|
||||||
pR.InputsBufferLock.Unlock()
|
pR.InputsBufferLock.Unlock()
|
||||||
Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock unlocked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
|
//Logger.Debug(fmt.Sprintf("OnBattleCmdReceived-InputsBufferLock unlocked: roomId=%v, fromPlayerId=%v", pR.Id, playerId))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
inputsBufferSnapshot := pR.markConfirmationIfApplicable(inputFrameUpsyncBatch, playerId, player)
|
inputsBufferSnapshot := pR.markConfirmationIfApplicable(inputFrameUpsyncBatch, playerId, player)
|
||||||
@ -757,7 +759,7 @@ func (pR *Room) OnDismissed() {
|
|||||||
pR.InputFrameUpsyncDelayTolerance = 2
|
pR.InputFrameUpsyncDelayTolerance = 2
|
||||||
pR.MaxChasingRenderFramesPerUpdate = 8
|
pR.MaxChasingRenderFramesPerUpdate = 8
|
||||||
|
|
||||||
pR.BackendDynamicsEnabled = false // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work!
|
||||||
punchSkillId := int32(1)
|
punchSkillId := int32(1)
|
||||||
pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0)
|
pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0)
|
||||||
pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{
|
pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{
|
||||||
@ -1286,7 +1288,10 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
VirtualGridY: currPlayerDownsync.VirtualGridY,
|
||||||
DirX: currPlayerDownsync.DirX,
|
DirX: currPlayerDownsync.DirX,
|
||||||
DirY: currPlayerDownsync.DirY,
|
DirY: currPlayerDownsync.DirY,
|
||||||
|
VelX: currPlayerDownsync.VelX,
|
||||||
|
VelY: currPlayerDownsync.VelY,
|
||||||
CharacterState: currPlayerDownsync.CharacterState,
|
CharacterState: currPlayerDownsync.CharacterState,
|
||||||
|
InAir: true,
|
||||||
Speed: currPlayerDownsync.Speed,
|
Speed: currPlayerDownsync.Speed,
|
||||||
BattleState: currPlayerDownsync.BattleState,
|
BattleState: currPlayerDownsync.BattleState,
|
||||||
Score: currPlayerDownsync.Score,
|
Score: currPlayerDownsync.Score,
|
||||||
@ -1301,47 +1306,106 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toRet := &RoomDownsyncFrame{
|
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?
|
||||||
Id: currRenderFrame.Id + 1,
|
|
||||||
Players: nextRenderFramePlayers,
|
|
||||||
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
|
|
||||||
MeleeBullets: make([]*MeleeBullet, 0), // Is there any better way to reduce malloc/free impact, e.g. smart prediction for fixed memory allocation?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guaranteed determinism regardless of traversal order
|
// Guaranteed determinism regardless of traversal order
|
||||||
jumpTriggered := make([]bool, pR.Capacity)
|
|
||||||
movements := make([]Vec2D, pR.Capacity)
|
|
||||||
bulletPushbacks := make([]Vec2D, pR.Capacity)
|
|
||||||
effPushbacks := make([]Vec2D, pR.Capacity)
|
effPushbacks := make([]Vec2D, pR.Capacity)
|
||||||
|
hardPushbackNorms := make([][]Vec2D, pR.Capacity)
|
||||||
|
|
||||||
// Reset playerCollider position from the "virtual grid position"
|
// 1. Process player inputs
|
||||||
|
if nil != delayedInputFrame {
|
||||||
|
var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil
|
||||||
|
tmp := pR.InputsBuffer.GetByFrameId(pR.ConvertToInputFrameId(currRenderFrame.Id-1, pR.InputDelayFrames))
|
||||||
|
if nil != tmp {
|
||||||
|
delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync)
|
||||||
|
}
|
||||||
|
inputList := delayedInputFrame.InputList
|
||||||
|
for _, player := range pR.PlayersArr {
|
||||||
|
playerId := player.Id
|
||||||
|
joinIndex := player.JoinIndex
|
||||||
|
currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId]
|
||||||
|
if 0 < thatPlayerInNextFrame.FramesToRecover {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
decodedInput := pR.decodeInput(inputList[joinIndex-1])
|
||||||
|
prevBtnALevel, prevBtnBLevel := int32(0), int32(0)
|
||||||
|
if nil != delayedInputFrameForPrevRenderFrame {
|
||||||
|
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
|
||||||
|
prevBtnALevel = prevDecodedInput.BtnALevel
|
||||||
|
prevBtnBLevel = prevDecodedInput.BtnBLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
if decodedInput.BtnBLevel > prevBtnBLevel {
|
||||||
|
characStateAlreadyInAir := false
|
||||||
|
if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame.CharacterState {
|
||||||
|
characStateAlreadyInAir = true
|
||||||
|
}
|
||||||
|
characStateIsInterruptWaivable := false
|
||||||
|
if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState {
|
||||||
|
characStateIsInterruptWaivable = true
|
||||||
|
}
|
||||||
|
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
|
||||||
|
thatPlayerInNextFrame.VelY = 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 decodedInput.BtnALevel > prevBtnALevel {
|
||||||
|
punchSkillId := int32(1)
|
||||||
|
punchConfig := pR.MeleeSkillConfig[punchSkillId]
|
||||||
|
var newMeleeBullet MeleeBullet = *punchConfig
|
||||||
|
newMeleeBullet.BattleLocalId = pR.BulletBattleLocalIdCounter
|
||||||
|
pR.BulletBattleLocalIdCounter += 1
|
||||||
|
newMeleeBullet.OffenderJoinIndex = joinIndex
|
||||||
|
newMeleeBullet.OffenderPlayerId = playerId
|
||||||
|
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id
|
||||||
|
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newMeleeBullet)
|
||||||
|
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
|
||||||
|
if false == currPlayerDownsync.InAir {
|
||||||
|
thatPlayerInNextFrame.VelX = 0
|
||||||
|
}
|
||||||
|
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
||||||
|
|
||||||
|
} else if decodedInput.BtnALevel < prevBtnALevel {
|
||||||
|
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
||||||
|
} else {
|
||||||
|
// No bullet trigger, process movement inputs
|
||||||
|
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
||||||
|
thatPlayerInNextFrame.VelX = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Process player movement
|
||||||
for _, player := range pR.PlayersArr {
|
for _, player := range pR.PlayersArr {
|
||||||
playerId := player.Id
|
playerId := player.Id
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
jumpTriggered[joinIndex-1] = false
|
|
||||||
movements[joinIndex-1].X, movements[joinIndex-1].Y = float64(0), float64(0)
|
|
||||||
bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
|
||||||
effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0)
|
|
||||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
|
||||||
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
|
||||||
newVx, newVy := currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY
|
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||||
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
|
currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId]
|
||||||
|
// Reset playerCollider position from the "virtual grid position"
|
||||||
|
newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY
|
||||||
|
|
||||||
|
playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderAnchorPos(newVx, newVy, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio)
|
||||||
|
// Update in the collision system
|
||||||
|
playerCollider.Update()
|
||||||
|
|
||||||
movements[joinIndex-1].X, movements[joinIndex-1].Y = VirtualGridToWorldPos(currPlayerDownsync.VelX, currPlayerDownsync.VelY, pR.VirtualGridToWorldRatio)
|
|
||||||
playerCollider.X += movements[joinIndex-1].X
|
|
||||||
playerCollider.Y += movements[joinIndex-1].Y
|
|
||||||
if currPlayerDownsync.InAir {
|
if currPlayerDownsync.InAir {
|
||||||
thatPlayerInNextFrame.VelX += pR.GravityX
|
thatPlayerInNextFrame.VelX += pR.GravityX
|
||||||
thatPlayerInNextFrame.VelY += pR.GravityY
|
thatPlayerInNextFrame.VelY += pR.GravityY
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in the collision system
|
|
||||||
playerCollider.Update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check bullet-anything collisions first, because the pushbacks caused by bullets might later be reverted by player-barrier collision
|
// 3. Add bullet colliders into collision system
|
||||||
bulletColliders := make(map[int32]*resolv.Object, 0) // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
bulletColliders := make(map[int32]*resolv.Object, 0) // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible
|
||||||
removedBulletsAtCurrFrame := make(map[int32]int32, 0)
|
removedBulletsAtCurrFrame := make(map[int32]int32, 0)
|
||||||
for _, meleeBullet := range currRenderFrame.MeleeBullets {
|
for _, meleeBullet := range currRenderFrame.MeleeBullets {
|
||||||
@ -1368,141 +1432,70 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bulletCollider := range bulletColliders {
|
// 4. Invoke collision system stepping (no-op for backend collision lib)
|
||||||
shouldRemove := false
|
|
||||||
meleeBullet := bulletCollider.Data.(*MeleeBullet)
|
|
||||||
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
|
||||||
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
|
|
||||||
if collision := bulletCollider.Check(0, 0); collision != nil {
|
|
||||||
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId]
|
|
||||||
for _, obj := range collision.Objects {
|
|
||||||
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
|
|
||||||
switch t := obj.Data.(type) {
|
|
||||||
case *Player:
|
|
||||||
if meleeBullet.OffenderPlayerId != t.Id {
|
|
||||||
if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped {
|
|
||||||
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
|
|
||||||
if 0 > offender.DirX {
|
|
||||||
xfac = float64(-1.0)
|
|
||||||
}
|
|
||||||
bulletPushbacks[t.JoinIndex-1].X += xfac * meleeBullet.Pushback
|
|
||||||
thatAckedPlayerInCurFrame := currRenderFrame.Players[t.Id]
|
|
||||||
thatAckedPlayerInNextFrame := nextRenderFramePlayers[t.Id]
|
|
||||||
thatAckedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
|
|
||||||
if thatAckedPlayerInCurFrame.InAir {
|
|
||||||
thatAckedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
|
|
||||||
}
|
|
||||||
oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover
|
|
||||||
if meleeBullet.HitStunFrames > oldFramesToRecover {
|
|
||||||
nextRenderFramePlayers[t.Id].FramesToRecover = meleeBullet.HitStunFrames
|
|
||||||
}
|
|
||||||
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
Logger.Debug(fmt.Sprintf("Bullet %v collided with non-player %v: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v, objDataType=%t, objData=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape), pR.Id, currRenderFrame.Id, delayedInputFrame.InputFrameId, obj.Data, obj.Data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shouldRemove = true
|
|
||||||
}
|
|
||||||
if shouldRemove {
|
|
||||||
removedBulletsAtCurrFrame[collisionBulletIndex] = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, meleeBullet := range currRenderFrame.MeleeBullets {
|
// 5. Calc pushbacks for each player (after its movement) w/o bullets
|
||||||
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
|
||||||
if bulletCollider, existent := collisionSysMap[collisionBulletIndex]; existent {
|
|
||||||
bulletCollider.Space.Remove(bulletCollider)
|
|
||||||
delete(collisionSysMap, collisionBulletIndex)
|
|
||||||
}
|
|
||||||
if _, existent := removedBulletsAtCurrFrame[collisionBulletIndex]; existent {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
toRet.MeleeBullets = append(toRet.MeleeBullets, meleeBullet)
|
|
||||||
}
|
|
||||||
|
|
||||||
if nil != delayedInputFrame {
|
|
||||||
var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil
|
|
||||||
tmp := pR.InputsBuffer.GetByFrameId(pR.ConvertToInputFrameId(currRenderFrame.Id-1, pR.InputDelayFrames))
|
|
||||||
if nil != tmp {
|
|
||||||
delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync)
|
|
||||||
}
|
|
||||||
inputList := delayedInputFrame.InputList
|
|
||||||
// Process player inputs
|
|
||||||
for _, player := range pR.PlayersArr {
|
for _, player := range pR.PlayersArr {
|
||||||
playerId := player.Id
|
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
|
playerId := player.Id
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||||
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
if 0 < thatPlayerInNextFrame.FramesToRecover {
|
hardPushbackNorms[joinIndex-1] = pR.calcHardPushbacksNorms(playerCollider, playerShape, pR.SnapIntoPlatformOverlap, &(effPushbacks[joinIndex-1]))
|
||||||
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
if 0 < len(hardPushbackNorms[joinIndex-1]) {
|
||||||
// Also note that in this case we keep "CharacterState" of this player from last render frame
|
Logger.Debug(fmt.Sprintf("playerId=%d, joinIndex=%d got %d non-empty hardPushbacks at renderFrame.id=%d", playerId, joinIndex, len(hardPushbackNorms), currRenderFrame.Id))
|
||||||
playerCollider.X += bulletPushbacks[joinIndex-1].X
|
|
||||||
playerCollider.Y += bulletPushbacks[joinIndex-1].Y
|
|
||||||
// Update in the collision system
|
|
||||||
playerCollider.Update()
|
|
||||||
if 0 != bulletPushbacks[joinIndex-1].X || 0 != bulletPushbacks[joinIndex-1].Y {
|
|
||||||
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v is pushed back by (%.2f, %.2f) by bullet impacts, now its framesToRecover is %d at currRenderFrame.id=%v", pR.Id, playerId, bulletPushbacks[joinIndex-1].X, bulletPushbacks[joinIndex-1].Y, thatPlayerInNextFrame.FramesToRecover, currRenderFrame.Id))
|
|
||||||
}
|
}
|
||||||
|
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
|
continue
|
||||||
}
|
}
|
||||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
bShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||||
decodedInput := pR.decodeInput(inputList[joinIndex-1])
|
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape); overlapped {
|
||||||
prevBtnALevel, prevBtnBLevel := int32(0), int32(0)
|
normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0))
|
||||||
if nil != delayedInputFrameForPrevRenderFrame {
|
landedOnGravityPushback := (pR.SnapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides
|
||||||
prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1])
|
if landedOnGravityPushback {
|
||||||
prevBtnALevel = prevDecodedInput.BtnALevel
|
// kindly note that one player might land on top of another player, and snapping is also required in such case
|
||||||
prevBtnBLevel = prevDecodedInput.BtnBLevel
|
pushbackX, pushbackY = (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapY
|
||||||
|
thatPlayerInNextFrame.InAir = false
|
||||||
}
|
}
|
||||||
|
for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] {
|
||||||
if decodedInput.BtnBLevel > prevBtnBLevel {
|
projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y
|
||||||
characStateAlreadyInAir := false
|
if isBarrier || (0 > projectedMagnitude && isAnotherPlayer) {
|
||||||
if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame.CharacterState {
|
pushbackX -= projectedMagnitude * hardPushbackNorm.X
|
||||||
characStateAlreadyInAir = true
|
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
|
||||||
}
|
|
||||||
characStateIsInterruptWaivable := false
|
|
||||||
if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState {
|
|
||||||
characStateIsInterruptWaivable = true
|
|
||||||
}
|
|
||||||
if !characStateAlreadyInAir && characStateIsInterruptWaivable {
|
|
||||||
thatPlayerInNextFrame.VelY = pR.JumpingInitVelY
|
|
||||||
jumpTriggered[joinIndex-1] = true
|
|
||||||
Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v triggered a rising-edge of btnB at renderFrame.id=%v, delayedInputFrame.id=%v, nextVelY=%v, characStateAlreadyInAir=%v, characStateIsInterruptWaivable=%v", playerId, joinIndex, currRenderFrame.Id, delayedInputFrame.InputFrameId, thatPlayerInNextFrame.VelY, characStateAlreadyInAir, characStateIsInterruptWaivable))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if currPlayerDownsync.InAir && landedOnGravityPushback {
|
||||||
if decodedInput.BtnALevel > prevBtnALevel {
|
fallStopping = true
|
||||||
punchSkillId := int32(1)
|
if isAnotherPlayer {
|
||||||
punchConfig := pR.MeleeSkillConfig[punchSkillId]
|
possiblyFallStoppedOnAnotherPlayer = true
|
||||||
var newMeleeBullet MeleeBullet = *punchConfig
|
}
|
||||||
newMeleeBullet.BattleLocalId = pR.BulletBattleLocalIdCounter
|
}
|
||||||
pR.BulletBattleLocalIdCounter += 1
|
effPushbacks[joinIndex-1].X += pushbackX
|
||||||
newMeleeBullet.OffenderJoinIndex = joinIndex
|
effPushbacks[joinIndex-1].Y += pushbackY
|
||||||
newMeleeBullet.OffenderPlayerId = playerId
|
}
|
||||||
newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id
|
}
|
||||||
toRet.MeleeBullets = append(toRet.MeleeBullets, &newMeleeBullet)
|
if fallStopping {
|
||||||
thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames
|
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1
|
|
||||||
if false == currPlayerDownsync.InAir {
|
|
||||||
thatPlayerInNextFrame.VelX = 0
|
thatPlayerInNextFrame.VelX = 0
|
||||||
}
|
thatPlayerInNextFrame.VelY = 0
|
||||||
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
|
||||||
|
|
||||||
} else if decodedInput.BtnALevel < prevBtnALevel {
|
|
||||||
Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId))
|
|
||||||
} else {
|
|
||||||
// No bullet trigger, process movement inputs
|
|
||||||
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
|
|
||||||
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
|
|
||||||
} else {
|
|
||||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1
|
||||||
thatPlayerInNextFrame.VelX = 0
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if currPlayerDownsync.InAir {
|
if currPlayerDownsync.InAir {
|
||||||
@ -1518,62 +1511,93 @@ func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle pushbacks upon collision after all movements treated as simultaneous
|
// 6. Check bullet-anything collisions
|
||||||
for _, player := range pR.PlayersArr {
|
for _, bulletCollider := range bulletColliders {
|
||||||
joinIndex := player.JoinIndex
|
shouldRemove := false
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
meleeBullet := bulletCollider.Data.(*MeleeBullet)
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
||||||
currPlayerDownsync := currRenderFrame.Players[playerId]
|
bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon)
|
||||||
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
if collision := bulletCollider.Check(0, 0); collision != nil {
|
||||||
fallStopping := false
|
offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId]
|
||||||
snappedIntoPlatformEx, snappedIntoPlatformEy := float64(0), float64(0)
|
|
||||||
if collision := playerCollider.Check(0, 0); collision != nil {
|
|
||||||
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
|
||||||
for _, obj := range collision.Objects {
|
for _, obj := range collision.Objects {
|
||||||
barrierShape := obj.Shape.(*resolv.ConvexPolygon)
|
defenderShape := obj.Shape.(*resolv.ConvexPolygon)
|
||||||
if overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape); overlapped {
|
switch t := obj.Data.(type) {
|
||||||
if nil == obj.Data {
|
case *Player:
|
||||||
// "nil == obj.Data" implies a barrier
|
if meleeBullet.OffenderPlayerId != t.Id {
|
||||||
const flatEnough = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
|
if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped {
|
||||||
const remainsNotInAir = (!currPlayerDownsync.inAir && flatEnough);
|
joinIndex := t.JoinIndex
|
||||||
const localFallStopping = (currPlayerDownsync.inAir && flatEnough);
|
xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis"
|
||||||
if (remainsNotInAir || localFallStopping) {
|
if 0 > offender.DirX {
|
||||||
fallStopping |= localFallStopping;
|
xfac = float64(-1.0)
|
||||||
[pushbackX, pushbackY] = [(result2.overlap - self.snapIntoPlatformOverlap) * result2.overlap_x, (result2.overlap - self.snapIntoPlatformOverlap) * result2.overlap_y]
|
}
|
||||||
snappedIntoPlatformEx = -result2.overlap_y;
|
pushbackX, pushbackY := -xfac*meleeBullet.Pushback, float64(0)
|
||||||
snappedIntoPlatformEy = result2.overlap_x;
|
|
||||||
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY) {
|
for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] {
|
||||||
[snappedIntoPlatformEx, snappedIntoPlatformEy] = [-snappedIntoPlatformEx, -snappedIntoPlatformEy];
|
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))
|
||||||
|
pushbackX -= projectedMagnitude * hardPushbackNorm.X
|
||||||
|
pushbackY -= projectedMagnitude * hardPushbackNorm.Y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Logger.Debug(fmt.Sprintf("Overlapped: a=%v, b=%v, pushbackX=%v, pushbackY=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
|
|
||||||
effPushbacks[joinIndex-1].X += pushbackX
|
effPushbacks[joinIndex-1].X += pushbackX
|
||||||
effPushbacks[joinIndex-1].Y += pushbackY
|
effPushbacks[joinIndex-1].Y += pushbackY
|
||||||
} else {
|
atkedPlayerInCurFrame, atkedPlayerInNextFrame := currRenderFrame.Players[t.Id], nextRenderFramePlayers[t.Id]
|
||||||
Logger.Debug(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), overlapResult))
|
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1
|
||||||
|
if atkedPlayerInCurFrame.InAir {
|
||||||
|
atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
|
||||||
|
}
|
||||||
|
oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover
|
||||||
|
if meleeBullet.HitStunFrames > oldFramesToRecover {
|
||||||
|
atkedPlayerInNextFrame.FramesToRecover = meleeBullet.HitStunFrames
|
||||||
|
}
|
||||||
|
Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
Logger.Debug(fmt.Sprintf("Bullet %v collided with non-player %v: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v, objDataType=%t, objData=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape), pR.Id, currRenderFrame.Id, delayedInputFrame.InputFrameId, obj.Data, obj.Data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shouldRemove = true
|
||||||
|
}
|
||||||
|
if shouldRemove {
|
||||||
|
removedBulletsAtCurrFrame[collisionBulletIndex] = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback
|
||||||
|
for _, meleeBullet := range currRenderFrame.MeleeBullets {
|
||||||
|
collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId
|
||||||
|
if bulletCollider, existent := collisionSysMap[collisionBulletIndex]; existent {
|
||||||
|
bulletCollider.Space.Remove(bulletCollider)
|
||||||
|
delete(collisionSysMap, collisionBulletIndex)
|
||||||
|
}
|
||||||
|
if _, existent := removedBulletsAtCurrFrame[collisionBulletIndex]; existent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Get players out of stuck barriers if there's any
|
||||||
for _, player := range pR.PlayersArr {
|
for _, player := range pR.PlayersArr {
|
||||||
playerId := player.Id
|
|
||||||
joinIndex := player.JoinIndex
|
joinIndex := player.JoinIndex
|
||||||
|
playerId := player.Id
|
||||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||||
playerCollider := collisionSysMap[collisionPlayerIndex]
|
playerCollider := collisionSysMap[collisionPlayerIndex]
|
||||||
|
|
||||||
// Update "virtual grid position"
|
// Update "virtual grid position"
|
||||||
newVx, newVy := PolygonColliderAnchorToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, player.ColliderRadius, player.ColliderRadius, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio)
|
|
||||||
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
thatPlayerInNextFrame := nextRenderFramePlayers[playerId]
|
||||||
thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = newVx, newVy
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug(fmt.Sprintf("After applyInputFrameDownsyncDynamicsOnSingleRenderFrame: currRenderFrame.Id=%v, inputList=%v, currRenderFrame.Players=%v, nextRenderFramePlayers=%v", currRenderFrame.Id, inputList, currRenderFrame.Players, nextRenderFramePlayers))
|
return &RoomDownsyncFrame{
|
||||||
|
Id: currRenderFrame.Id + 1,
|
||||||
|
Players: nextRenderFramePlayers,
|
||||||
|
CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos),
|
||||||
|
MeleeBullets: nextRenderFrameMeleeBullets,
|
||||||
}
|
}
|
||||||
|
|
||||||
return toRet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pR *Room) decodeInput(encodedInput uint64) *InputFrameDecoded {
|
func (pR *Room) decodeInput(encodedInput uint64) *InputFrameDecoded {
|
||||||
@ -1613,6 +1637,7 @@ func (pR *Room) refreshColliders(spaceW, spaceH int32) {
|
|||||||
for _, barrier := range pR.Barriers {
|
for _, barrier := range pR.Barriers {
|
||||||
boundaryUnaligned := barrier.Boundary
|
boundaryUnaligned := barrier.Boundary
|
||||||
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier")
|
||||||
|
barrierCollider.Data = barrier
|
||||||
pR.Space.Add(barrierCollider)
|
pR.Space.Add(barrierCollider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1776,3 +1801,25 @@ func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*InputFrameDowns
|
|||||||
|
|
||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
@ -60,43 +60,6 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
|
|||||||
return collider
|
return collider
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalcPushbacksWithGravitySnapping(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon, currentInAir bool, snapIntoPlatformOverlap, snapIntoPlatformThreshold float64) (bool, float64, float64, *SatResult, float64, float64, bool) {
|
|
||||||
origX, origY := playerShape.Position()
|
|
||||||
snappedIntoPlatformEx, snappedIntoPlatformEy := float64(0), float64(0)
|
|
||||||
localFallStopping := false
|
|
||||||
defer func() {
|
|
||||||
playerShape.SetPosition(origX, origY)
|
|
||||||
}()
|
|
||||||
playerShape.SetPosition(origX+oldDx, origY+oldDy)
|
|
||||||
overlapResult := &SatResult{
|
|
||||||
Overlap: 0,
|
|
||||||
OverlapX: 0,
|
|
||||||
OverlapY: 0,
|
|
||||||
AContainedInB: true,
|
|
||||||
BContainedInA: true,
|
|
||||||
Axis: vector.Vector{0, 0},
|
|
||||||
}
|
|
||||||
if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped {
|
|
||||||
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
|
|
||||||
normAlignmentWithGravity := (overlapResult.OverlapX * 0 + overlapResult.OverlapX * (-1.0))
|
|
||||||
flatEnough := (snapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides
|
|
||||||
remainsNotInAir := (!currentInAir && flatEnough)
|
|
||||||
localFallStopping = (currentInAir && flatEnough)
|
|
||||||
if remainsNotInAir || localFallStopping {
|
|
||||||
// [OverlayX, OverlapY] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
|
||||||
snappedIntoPlatformEx, snappedIntoPlatformEy = -overlapResult.OverlapY, overlapResult.OverlapX
|
|
||||||
pushbackX, pushbackY = (overlapResult.Overlap - snapIntoPlatformOverlap) * overlapResult.OverlapX, (overlapResult.Overlap - snapIntoPlatformOverlap) * overlapResult.OverlapY
|
|
||||||
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY < 0) {
|
|
||||||
// snapped dir should have a positive projection from player facing dir
|
|
||||||
snappedIntoPlatformEx, snappedIntoPlatformEy = -snappedIntoPlatformEx, -snappedIntoPlatformEy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, pushbackX, pushbackY, overlapResult, snappedIntoPlatformEx, snappedIntoPlatformEy, localFallStopping
|
|
||||||
} else {
|
|
||||||
return false, 0, 0, overlapResult, 0, 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
||||||
origX, origY := playerShape.Position()
|
origX, origY := playerShape.Position()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="79">
|
<map version="1.2" tiledversion="1.2.3" orientation="orthogonal" renderorder="right-down" width="128" height="128" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="83">
|
||||||
<tileset firstgid="1" source="tiles0.tsx"/>
|
<tileset firstgid="1" source="tiles0.tsx"/>
|
||||||
<tileset firstgid="65" source="tiles1.tsx"/>
|
<tileset firstgid="65" source="tiles1.tsx"/>
|
||||||
<tileset firstgid="129" source="tiles2.tsx"/>
|
<tileset firstgid="129" source="tiles2.tsx"/>
|
||||||
@ -26,12 +26,6 @@
|
|||||||
</properties>
|
</properties>
|
||||||
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
||||||
</object>
|
</object>
|
||||||
<object id="9" x="650.667" y="1604.67">
|
|
||||||
<properties>
|
|
||||||
<property name="boundary_type" value="barrier"/>
|
|
||||||
</properties>
|
|
||||||
<polyline points="0,0 0,18.6667 1041.33,21.3333 1041.33,-1.33333"/>
|
|
||||||
</object>
|
|
||||||
<object id="10" x="634.485" y="505.455">
|
<object id="10" x="634.485" y="505.455">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
@ -274,27 +268,22 @@
|
|||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
</object>
|
</object>
|
||||||
<object id="75" x="1264.25" y="1599" width="47.75" height="17.25">
|
<object id="79" x="624.25" y="1616" width="1094.75" height="17.25">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
</object>
|
</object>
|
||||||
<object id="76" x="1264.63" y="1583.63" width="15.5" height="15.25">
|
<object id="80" x="1296.5" y="1600.25">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
|
<polyline points="0,0 -32.5,0 -32.25,-16.5 -16.5,-16.5"/>
|
||||||
</object>
|
</object>
|
||||||
<object id="77" x="1280.5" y="1583.75">
|
<object id="82" x="1328.5" y="1616">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="boundary_type" value="barrier"/>
|
<property name="boundary_type" value="barrier"/>
|
||||||
</properties>
|
</properties>
|
||||||
<polyline points="0,0 0,15.25 15.75,15.25"/>
|
<polyline points="0,0 -64.5,0 -64.0038,-15.75 -16.4734,-15.75"/>
|
||||||
</object>
|
|
||||||
<object id="78" x="1312" y="1599.25">
|
|
||||||
<properties>
|
|
||||||
<property name="boundary_type" value="barrier"/>
|
|
||||||
</properties>
|
|
||||||
<polyline points="0,0 0,17 15.75,17"/>
|
|
||||||
</object>
|
</object>
|
||||||
</objectgroup>
|
</objectgroup>
|
||||||
</map>
|
</map>
|
||||||
|
@ -440,7 +440,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
215.64032554232523,
|
216.67520680312998,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -454,7 +454,7 @@
|
|||||||
"array": [
|
"array": [
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
209.57814771583418,
|
216.67520680312998,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -128,7 +128,7 @@ cc.Class({
|
|||||||
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) {
|
_interruptPlayingAnimAndPlayNewAnimDragonBones(rdfPlayer, prevRdfPlayer, newCharacterState, newAnimName, underlyingAnimationCtrl, playingAnimName) {
|
||||||
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
if (window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) {
|
||||||
// No "framesToRecover"
|
// No "framesToRecover"
|
||||||
console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
|
// console.warn(`#DragonBones JoinIndex=${rdfPlayer.joinIndex}, ${playingAnimName} -> ${newAnimName}`);
|
||||||
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
|
underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1);
|
||||||
} else {
|
} else {
|
||||||
const animationData = underlyingAnimationCtrl._animations[newAnimName];
|
const animationData = underlyingAnimationCtrl._animations[newAnimName];
|
||||||
|
@ -1079,7 +1079,7 @@ cc.Class({
|
|||||||
velX: currPlayerDownsync.velX,
|
velX: currPlayerDownsync.velX,
|
||||||
velY: currPlayerDownsync.velY,
|
velY: currPlayerDownsync.velY,
|
||||||
characterState: currPlayerDownsync.characterState,
|
characterState: currPlayerDownsync.characterState,
|
||||||
inAir: true, // will be updated if collided with a barrier with "0 > pushbackY"
|
inAir: true,
|
||||||
speed: currPlayerDownsync.speed,
|
speed: currPlayerDownsync.speed,
|
||||||
battleState: currPlayerDownsync.battleState,
|
battleState: currPlayerDownsync.battleState,
|
||||||
score: currPlayerDownsync.score,
|
score: currPlayerDownsync.score,
|
||||||
@ -1106,7 +1106,6 @@ cc.Class({
|
|||||||
const joinIndex = parseInt(j) + 1;
|
const joinIndex = parseInt(j) + 1;
|
||||||
const playerRichInfo = self.playerRichInfoArr[j];
|
const playerRichInfo = self.playerRichInfoArr[j];
|
||||||
const playerId = playerRichInfo.id;
|
const playerId = playerRichInfo.id;
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
|
||||||
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
||||||
if (0 < thatPlayerInNextFrame.framesToRecover) {
|
if (0 < thatPlayerInNextFrame.framesToRecover) {
|
||||||
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
// No need to process inputs for this player, but there might be bullet pushbacks on this player
|
||||||
@ -1131,8 +1130,6 @@ cc.Class({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
|
if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) {
|
||||||
// console.log(`playerId=${playerId} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
|
||||||
if (self.bulletTriggerEnabled) {
|
|
||||||
const punchSkillId = 1;
|
const punchSkillId = 1;
|
||||||
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
|
const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]);
|
||||||
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
|
thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames;
|
||||||
@ -1141,14 +1138,12 @@ cc.Class({
|
|||||||
punch.offenderPlayerId = playerId;
|
punch.offenderPlayerId = playerId;
|
||||||
punch.originatedRenderFrameId = currRenderFrame.id;
|
punch.originatedRenderFrameId = currRenderFrame.id;
|
||||||
nextRenderFrameMeleeBullets.push(punch);
|
nextRenderFrameMeleeBullets.push(punch);
|
||||||
// console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}: ${self._stringifyRecentInputCache(true)}`);
|
// console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
console.log(`A rising-edge of meleeBullet is created at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
|
||||||
|
|
||||||
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0];
|
||||||
if (false == currPlayerDownsync.inAir) {
|
if (false == currPlayerDownsync.inAir) {
|
||||||
thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 on the ground
|
thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 on the ground
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
|
} else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) {
|
||||||
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
// console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`);
|
||||||
} else {
|
} else {
|
||||||
@ -1215,7 +1210,6 @@ cc.Class({
|
|||||||
newBulletCollider.data = meleeBullet;
|
newBulletCollider.data = meleeBullet;
|
||||||
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
|
collisionSysMap.set(collisionBulletIndex, newBulletCollider);
|
||||||
bulletColliders.set(collisionBulletIndex, newBulletCollider);
|
bulletColliders.set(collisionBulletIndex, newBulletCollider);
|
||||||
// console.log(`A meleeBullet is added to collisionSys at currRenderFrame.id=${currRenderFrame.id} as start-up frames ended and active frame is not yet ended: ${JSON.stringify(meleeBullet)}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1231,35 +1225,48 @@ cc.Class({
|
|||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
const potentials = playerCollider.potentials();
|
const potentials = playerCollider.potentials();
|
||||||
hardPushbackNorms[joinIndex - 1] = self.calcHardPushbacksNorms(playerCollider, potentials, result, self.snapIntoPlatformThreshold, self.snapIntoPlatformOverlap, joinIndex, effPushbacks);
|
hardPushbackNorms[joinIndex - 1] = self.calcHardPushbacksNorms(playerCollider, potentials, result, self.snapIntoPlatformOverlap, effPushbacks[joinIndex - 1]);
|
||||||
|
|
||||||
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
const [currPlayerDownsync, thatPlayerInNextFrame] = [currRenderFrame.players[playerId], nextRenderFramePlayers[playerId]];
|
||||||
let fallStopping = false;
|
let fallStopping = false;
|
||||||
|
let possiblyFallStoppedOnAnotherPlayer = false;
|
||||||
for (const potential of potentials) {
|
for (const potential of potentials) {
|
||||||
|
let [isBarrier, isAnotherPlayer, isBullet] = [true == potential.data.hardPushback, null != potential.data.joinIndex, null != potential.data.offenderJoinIndex];
|
||||||
// ignore bullets for this step
|
// ignore bullets for this step
|
||||||
if (null != potential.data && null != potential.data.offenderJoinIndex) continue;
|
if (isBullet) continue;
|
||||||
// Test if the player collides with the wall/another player
|
// Test if the player collides with the wall/another player
|
||||||
if (!playerCollider.collides(potential, result)) continue;
|
if (!playerCollider.collides(potential, result)) continue;
|
||||||
|
|
||||||
const normAlignmentWithGravity = (result.overlap_x * 0 + result.overlap_y * (-1.0));
|
const normAlignmentWithGravity = (result.overlap_x * 0 + result.overlap_y * (-1.0));
|
||||||
const landedOnGravityPushback = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
|
const landedOnGravityPushback = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides
|
||||||
// Push the player out of the wall/another player
|
let pushback = [result.overlap * result.overlap_x, result.overlap * result.overlap_y];
|
||||||
let [pushbackX, pushbackY] = [result.overlap * result.overlap_x, result.overlap * result.overlap_y];
|
|
||||||
if (landedOnGravityPushback) {
|
if (landedOnGravityPushback) {
|
||||||
// kindly note that one player might land on top of another player, and snapping is also required in such case
|
// kindly note that one player might land on top of another player, and snapping is also required in such case
|
||||||
[pushbackX, pushbackY] = [(result.overlap - self.snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - self.snapIntoPlatformOverlap) * result.overlap_y];
|
pushback = [(result.overlap - self.snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - self.snapIntoPlatformOverlap) * result.overlap_y];
|
||||||
|
thatPlayerInNextFrame.inAir = false;
|
||||||
}
|
}
|
||||||
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
|
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
|
||||||
// remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]")
|
// remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]")
|
||||||
const projectedMagnitude = pushbackX * hardPushbackNorm[0] + pushbackY * hardPushbackNorm[1];
|
const projectedMagnitude = pushback[0] * hardPushbackNorm[0] + pushback[1] * hardPushbackNorm[1];
|
||||||
pushbackX -= projectedMagnitude * hardPushbackNorm[0];
|
if (isBarrier
|
||||||
pushbackY -= projectedMagnitude * hardPushbackNorm[1];
|
||
|
||||||
|
(isAnotherPlayer && 0 > projectedMagnitude)
|
||||||
|
) {
|
||||||
|
// [WARNING] Pushing by another player is different from pushing by barrier!
|
||||||
|
// Otherwise the player couldn't be pushed by another player to opposite dir of a side wall
|
||||||
|
pushback[0] -= projectedMagnitude * hardPushbackNorm[0];
|
||||||
|
pushback[1] -= projectedMagnitude * hardPushbackNorm[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currPlayerDownsync.inAir && landedOnGravityPushback) {
|
||||||
|
fallStopping = true;
|
||||||
|
if (isAnotherPlayer) {
|
||||||
|
possiblyFallStoppedOnAnotherPlayer = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
thatPlayerInNextFrame.inAir &= !landedOnGravityPushback;
|
|
||||||
fallStopping |= (currPlayerDownsync.inAir && landedOnGravityPushback);
|
|
||||||
|
|
||||||
effPushbacks[joinIndex - 1][0] += pushbackX;
|
effPushbacks[joinIndex - 1][0] += pushback[0];
|
||||||
effPushbacks[joinIndex - 1][1] += pushbackY;
|
effPushbacks[joinIndex - 1][1] += pushback[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fallStopping) {
|
if (fallStopping) {
|
||||||
@ -1267,6 +1274,9 @@ cc.Class({
|
|||||||
thatPlayerInNextFrame.velY = 0;
|
thatPlayerInNextFrame.velY = 0;
|
||||||
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0];
|
||||||
thatPlayerInNextFrame.framesToRecover = 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) {
|
if (currPlayerDownsync.inAir) {
|
||||||
thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState);
|
thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState);
|
||||||
@ -1282,21 +1292,27 @@ cc.Class({
|
|||||||
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
|
if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue;
|
||||||
if (!bulletCollider.collides(potential, result)) continue;
|
if (!bulletCollider.collides(potential, result)) continue;
|
||||||
if (null != potential.data && null != potential.data.joinIndex) {
|
if (null != potential.data && null != potential.data.joinIndex) {
|
||||||
|
const playerId = potential.data.id;
|
||||||
const joinIndex = potential.data.joinIndex;
|
const joinIndex = potential.data.joinIndex;
|
||||||
let xfac = 1;
|
let xfac = 1;
|
||||||
if (0 > offender.dirX) {
|
if (0 > offender.dirX) {
|
||||||
xfac = -1;
|
xfac = -1;
|
||||||
}
|
}
|
||||||
let [pushbackX, pushbackY] = [-xfac * bulletCollider.data.pushback, 0]; // Only for straight punch, there's no y-pushback
|
// Only for straight punch, there's no y-pushback
|
||||||
|
let bulletPushback = [-xfac * bulletCollider.data.pushback, 0];
|
||||||
|
// console.log(`playerId=${playerId}, joinIndex=${joinIndex} is supposed to be pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`);
|
||||||
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
|
for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) {
|
||||||
// remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]")
|
const projectedMagnitude = bulletPushback[0] * hardPushbackNorm[0] + bulletPushback[1] * hardPushbackNorm[1];
|
||||||
const projectedMagnitude = pushbackX * hardPushbackNorm[0] + pushbackY * hardPushbackNorm[1];
|
if (0 > projectedMagnitude) {
|
||||||
pushbackX -= projectedMagnitude * hardPushbackNorm[0];
|
// 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
|
||||||
pushbackY -= projectedMagnitude * hardPushbackNorm[1];
|
bulletPushback[0] -= (projectedMagnitude * hardPushbackNorm[0]);
|
||||||
// TODO: What if a bullet knocks down the attacked player into ground?
|
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}`);
|
||||||
}
|
}
|
||||||
effPushbacks[joinIndex - 1][0] += pushbackX;
|
}
|
||||||
effPushbacks[joinIndex - 1][1] += pushbackY;
|
// console.log(`playerId=${playerId}, joinIndex=${joinIndex} is actually pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`);
|
||||||
|
effPushbacks[joinIndex - 1][0] += bulletPushback[0];
|
||||||
|
effPushbacks[joinIndex - 1][1] += bulletPushback[1];
|
||||||
const [atkedPlayerInCurFrame, atkedPlayerInNextFrame] = [currRenderFrame.players[potential.data.id], nextRenderFramePlayers[potential.data.id]];
|
const [atkedPlayerInCurFrame, atkedPlayerInNextFrame] = [currRenderFrame.players[potential.data.id], nextRenderFramePlayers[potential.data.id]];
|
||||||
atkedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
|
atkedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0];
|
||||||
if (atkedPlayerInCurFrame.inAir) {
|
if (atkedPlayerInCurFrame.inAir) {
|
||||||
@ -1331,6 +1347,7 @@ cc.Class({
|
|||||||
const playerId = self.playerRichInfoArr[j].id;
|
const playerId = self.playerRichInfoArr[j].id;
|
||||||
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex;
|
||||||
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
const playerCollider = collisionSysMap.get(collisionPlayerIndex);
|
||||||
|
// Update "virtual grid position"
|
||||||
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
const thatPlayerInNextFrame = nextRenderFramePlayers[playerId];
|
||||||
[thatPlayerInNextFrame.virtualGridX, thatPlayerInNextFrame.virtualGridY] = self.polygonColliderAnchorToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], self.playerRichInfoArr[j].colliderRadius, self.playerRichInfoArr[j].colliderRadius);
|
[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);
|
||||||
}
|
}
|
||||||
@ -1478,21 +1495,20 @@ cc.Class({
|
|||||||
return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH)
|
return self.worldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH)
|
||||||
},
|
},
|
||||||
|
|
||||||
calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformThreshold, snapIntoPlatformOverlap, joinIndex, effPushbacks) {
|
calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformOverlap, effPushback) {
|
||||||
let hardPushbackNorms = [];
|
let ret = [];
|
||||||
let fallStopping = false;
|
|
||||||
for (const potential of potentials) {
|
for (const potential of potentials) {
|
||||||
if (null == potential.data || !(true == potential.data.hardPushback)) continue;
|
if (null == potential.data || !(true == potential.data.hardPushback)) continue;
|
||||||
if (!collider.collides(potential, result)) continue;
|
if (!collider.collides(potential, result)) continue;
|
||||||
// allow snapping into all colliders with {hardPushback: true}
|
// ALWAY snap into hardPushbacks!
|
||||||
|
// [overlay_x, overlap_y] is the unit vector that points into the platform
|
||||||
const [pushbackX, pushbackY] = [(result.overlap - snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - snapIntoPlatformOverlap) * result.overlap_y];
|
const [pushbackX, pushbackY] = [(result.overlap - snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - snapIntoPlatformOverlap) * result.overlap_y];
|
||||||
|
|
||||||
// [overlay_x, overlap_y] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
ret.push([result.overlap_x, result.overlap_y]);
|
||||||
hardPushbackNorms.push([result.overlap_x, result.overlap_y]);
|
effPushback[0] += pushbackX;
|
||||||
effPushbacks[joinIndex - 1][0] += pushbackX;
|
effPushback[1] += pushbackY;
|
||||||
effPushbacks[joinIndex - 1][1] += pushbackY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return hardPushbackNorms;
|
return ret;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -75,7 +75,7 @@ cc.Class({
|
|||||||
|
|
||||||
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).
|
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.snapIntoPlatformOverlap = 0.01;
|
||||||
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
|
self.snapIntoPlatformThreshold = 0.5; // a platform must be "horizontal enough" for a character to "stand on"
|
||||||
self.jumpingInitVelY = 6 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame)
|
self.jumpingInitVelY = 6 * self.worldToVirtualGridRatio; // unit: (virtual grid length/renderFrame)
|
||||||
[self.gravityX, self.gravityY] = [0, -Math.ceil(4 * self.jumpingInitVelY / self.serverFps)]; // unit: (virtual grid length/renderFrame^2)
|
[self.gravityX, self.gravityY] = [0, -Math.ceil(4 * self.jumpingInitVelY / self.serverFps)]; // unit: (virtual grid length/renderFrame^2)
|
||||||
|
Loading…
Reference in New Issue
Block a user