feat: Implement player input replay, bullet grace period, and graze logic separation
This commit is contained in:
parent
8586180dbc
commit
7718a68991
2 changed files with 62 additions and 20 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Reference in a new issue