Archived
1
0
Fork 0

Add grace-controlled grazing

This commit is contained in:
Sebastian Benjamin 2025-07-22 19:13:40 -07:00
parent 651adcc71a
commit 122590d10b
3 changed files with 37 additions and 10 deletions

View file

@ -18,6 +18,6 @@ const (
BULLET_OFFSCREEN_BUFFER_WIDTH float64 = 16.0
PLAYER_HIT_COL_RADIUS_MULTIPLIER float64 = 0.01
PLAYER_GRAZE_COL_RADIUS_MULTIPLIER float64 = 0.04
PLAYER_DEATH_TIMER_MAX int64 = 180
PLAYER_DEATH_TIMER_MAX int64 = 240
GRAZE_ADDITION_MULTIPLIER int = 1000
)

View file

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"encoding/json"
"github.com/heroiclabs/nakama-common/runtime"
)

View file

@ -15,7 +15,7 @@ type PlayerStageState struct {
updatePlayerPos bool
health int
graze int
score int
grazePerTick map[int64]int
deathTick int64
dead bool
cancelDeath bool
@ -25,6 +25,15 @@ type PlayerStageState struct {
}
func (s *PlayerStageState) ProcessPlayerInputs(tick int64) {
// Lock-in the earned graze
cutoffTick := tick - GRACE_WINDOW_TICKS
for t, v := range s.grazePerTick {
if t < cutoffTick {
s.graze += v
delete(s.grazePerTick, t)
}
}
// Clean up inputs outside the grace window
s.playerInputs = slices.DeleteFunc(s.playerInputs, func(input *ClientUpdate) bool {
return tick-input.Tick > GRACE_WINDOW_TICKS
@ -37,7 +46,7 @@ func (s *PlayerStageState) ProcessPlayerInputs(tick int64) {
// Replay each tick within the grace window
s.survivedGraceWindow = true
for t := tick - GRACE_WINDOW_TICKS; t < tick; t++ {
for t := tick - GRACE_WINDOW_TICKS; t <= tick; t++ {
// Find the input for the current tick
var currentInput *ClientUpdate
for _, input := range s.playerInputs {
@ -58,7 +67,15 @@ func (s *PlayerStageState) ProcessPlayerInputs(tick int64) {
// If the player dies in the grace window, don't cancel their death
if s.CheckPlayerDeadOnTick(t) {
s.survivedGraceWindow = false
for grazeT := range s.grazePerTick {
if grazeT > t {
delete(s.grazePerTick, grazeT)
}
}
break
} else {
s.grazePerTick[t] = s.CalculateGrazeDelta(t)
}
}
}
@ -69,6 +86,7 @@ func NewPlayerStage() *PlayerStageState {
grazeCol: ffi.NewCircle(STAGE_WIDTH*0.5, STAGE_HEIGHT-STAGE_HEIGHT*0.1, STAGE_WIDTH*PLAYER_GRAZE_COL_RADIUS_MULTIPLIER),
bullets: []*ffi.Bullet{},
updatePlayerPos: true,
grazePerTick: make(map[int64]int),
health: 3,
deathTick: PLAYER_ALIVE,
}
@ -162,12 +180,14 @@ func (s *PlayerStageState) CheckPlayerDeadOnTick(tick int64) bool {
return false
}
func (s *PlayerStageState) UpdateGrazeMultiplier(tick int64) {
if slices.ContainsFunc(s.bullets, func(b *ffi.Bullet) bool {
return b.CollidesWith(s.grazeCol, tick)
}) {
s.graze += GRAZE_ADDITION_MULTIPLIER
func (s *PlayerStageState) CalculateGrazeDelta(tick int64) int {
count := 0
for _, b := range s.bullets {
if b.CollidesWith(s.grazeCol, tick) {
count++
}
}
return count * GRAZE_ADDITION_MULTIPLIER
}
func (s *PlayerStageState) AddBullet(b *ffi.Bullet) {
@ -223,7 +243,13 @@ func (s *PlayerStageState) GetBoardStateDiff(tick int64) []map[string]any {
func (s *PlayerStageState) MakeServerTick(tick int64) *ServerTickUpdate {
hitPosX, hitPosY := s.hitCol.GetPos()
grazePosX, grazePosY := s.hitCol.GetPos()
grazePosX, grazePosY := s.grazeCol.GetPos()
prospectiveGraze := 0
for _, v := range s.grazePerTick {
prospectiveGraze += v
}
var tickData = ServerTickUpdate{
Tick: tick,
PlayerHitPos: map[string]any{
@ -238,7 +264,7 @@ func (s *PlayerStageState) MakeServerTick(tick int64) *ServerTickUpdate {
},
StageStateDiff: s.GetBoardStateDiff(tick),
ForcePlayerPos: s.updatePlayerPos,
Graze: s.graze,
Graze: s.graze + prospectiveGraze,
Dead: s.dead,
CancelDeath: s.cancelDeath,
DeathTick: s.deathTick,