diff --git a/server/game-modes/battle-royale/game-loop.go b/server/game-modes/battle-royale/game-loop.go index 3905798..4190d5d 100644 --- a/server/game-modes/battle-royale/game-loop.go +++ b/server/game-modes/battle-royale/game-loop.go @@ -38,23 +38,8 @@ func RespondToInput(lobbyState *MatchState, messages []runtime.MatchData, logger continue } - // Apply latest player input - lobbyState.presences[msg.GetSessionId()].stageState.BoundsCheckedMove(update.X, update.Y) - - // Check if the input is within the grace window - if update.Tick < tick && tick-update.Tick <= GRACE_WINDOW_TICKS { - // Check the player's collision state for all subsequent ticks - playerSurvives := true - for t := update.Tick; t < tick; t++ { - if lobbyState.presences[msg.GetSessionId()].stageState.CheckCollisionState(t) == PLAYER_DEAD { - playerSurvives = false - } - } - // Set a flag to cancel death if the player survives all ticks - if playerSurvives { - lobbyState.presences[msg.GetSessionId()].stageState.cancelDeath = true - } - } + // Store the input in the player's stage state + lobbyState.presences[msg.GetSessionId()].stageState.playerInputs = append(lobbyState.presences[msg.GetSessionId()].stageState.playerInputs, update) } } @@ -131,6 +116,11 @@ func (m *Match) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB } RespondToInput(lobbyState, messages, &logger, tick, &dispatcher) + for _, playerState := range lobbyState.presences { + playerState.stageState.ProcessPlayerInputs(tick) + playerState.stageState.CleanupBullets(tick) + } + BroadcastToPresences(tick, lobbyState, &logger, &dispatcher) return lobbyState diff --git a/server/game-modes/battle-royale/player-stage.go b/server/game-modes/battle-royale/player-stage.go index 2f41d82..115ef73 100644 --- a/server/game-modes/battle-royale/player-stage.go +++ b/server/game-modes/battle-royale/player-stage.go @@ -10,13 +10,52 @@ import ( type PlayerStageState struct { hitCol *ffi.Circle grazeCol *ffi.Circle - bullets []*ffi.Bullet + bullets []*Bullet updatePlayerPos bool health int graze int score int deathTimer int cancelDeath bool + playerInputs []ClientUpdate + lastInput *ClientUpdate +} + +func (s *PlayerStageState) ProcessPlayerInputs(tick int64) { + // Sort inputs by tick + slices.SortFunc(s.playerInputs, func(a, b ClientUpdate) bool { + return a.Tick < b.Tick + }) + + // Replay each tick within the grace window + for t := tick - GRACE_WINDOW_TICKS; t < tick; t++ { + // Find the input for the current tick + var currentInput *ClientUpdate + for _, input := range s.playerInputs { + if input.Tick == t { + currentInput = &input + break + } + } + + // Apply the correct movement or no movement + if currentInput != nil { + s.BoundsCheckedMove(currentInput.X, currentInput.Y) + s.lastInput = currentInput + } else if s.lastInput != nil { + s.BoundsCheckedMove(s.lastInput.X, s.lastInput.Y) + } + + if s.CheckCollisionState(t) == PLAYER_DEAD { + s.cancelDeath = false + break + } + } + + // Clean up inputs outside the grace window + s.playerInputs = slices.DeleteFunc(s.playerInputs, func(input ClientUpdate) bool { + return tick-input.Tick > GRACE_WINDOW_TICKS + }) } func NewPlayerStage() *PlayerStageState { @@ -55,7 +94,15 @@ func (s *PlayerStageState) BoundsCheckedMove(x float64, y float64) { func (s *PlayerStageState) DeleteBulletsBeyondKillBoundary(tick int64) { s.bullets = slices.DeleteFunc(s.bullets, func(b *ffi.Bullet) bool { - if b.BeyondKillBoundary(tick) { + if b.BeyondKillBoundary(tick) && b.deletionTick == 0 { + b.deletionTick = tick + } + }) +} + +func (s *PlayerStageState) CleanupBullets(tick int64) { + s.bullets = slices.DeleteFunc(s.bullets, func(b *Bullet) bool { + if b.deletionTick > 0 && tick-b.deletionTick > GRACE_WINDOW_TICKS { ffi.DestroyBullet(b) return true } @@ -87,7 +134,12 @@ func (s *PlayerStageState) CheckCollisionState(tick int64) int { return b.CollidesWith(s.hitCol, tick) }) { return PLAYER_DEAD - } else if slices.ContainsFunc(s.bullets, func(b *ffi.Bullet) bool { + } + return PLAYER_ALIVE +} + +func (s *PlayerStageState) UpdateGrazeMultiplier(tick int64) { + if slices.ContainsFunc(s.bullets, func(b *Bullet) bool { return b.CollidesWith(s.grazeCol, tick) }) { s.graze += GRAZE_ADDITION_MULTIPLIER