First finished iteration of a grace system for late inputs
This commit is contained in:
parent
7718a68991
commit
651adcc71a
6 changed files with 180 additions and 155 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -13,3 +13,4 @@ target/
|
||||||
|
|
||||||
lib/
|
lib/
|
||||||
*.vscode/
|
*.vscode/
|
||||||
|
.aider*
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,13 @@ func (c *Circle) GetPos() (float64, float64) {
|
||||||
// Bullets
|
// Bullets
|
||||||
type Bullet struct {
|
type Bullet struct {
|
||||||
cptr *C.Bullet
|
cptr *C.Bullet
|
||||||
|
DeletionTick int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values for selecting bullet paths
|
// Values for selecting bullet paths
|
||||||
const (
|
const (
|
||||||
BULLET_LINEAR = 0
|
BULLET_LINEAR = 0
|
||||||
|
ACTIVE_BULLET = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLinearBullet(tick int64, spawnX float64, spawnY float64, radius float64, velX float64, velY float64) *Bullet {
|
func NewLinearBullet(tick int64, spawnX float64, spawnY float64, radius float64, velX float64, velY float64) *Bullet {
|
||||||
|
|
@ -57,6 +59,7 @@ func NewLinearBullet(tick int64, spawnX float64, spawnY float64, radius float64,
|
||||||
C.double(velX),
|
C.double(velX),
|
||||||
C.double(velY),
|
C.double(velY),
|
||||||
),
|
),
|
||||||
|
DeletionTick: ACTIVE_BULLET,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
package battleroyale
|
package battleroyale
|
||||||
|
|
||||||
const TICK_RATE = 60
|
|
||||||
|
|
||||||
const (
|
|
||||||
PLAYER_DEATH_RESET = 0
|
|
||||||
PLAYER_ALIVE = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MATCH_LOADING = iota
|
MATCH_LOADING = iota
|
||||||
MATCH_START
|
MATCH_START
|
||||||
|
|
@ -16,11 +9,15 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
TICK_RATE = 60
|
||||||
|
GRACE_WINDOW_TICKS = 15
|
||||||
|
ACTIVE_BULLET = -1
|
||||||
|
PLAYER_ALIVE = -1
|
||||||
STAGE_WIDTH float64 = 90.0
|
STAGE_WIDTH float64 = 90.0
|
||||||
STAGE_HEIGHT float64 = 160.0
|
STAGE_HEIGHT float64 = 160.0
|
||||||
BULLET_OFFSCREEN_BUFFER_WIDTH float64 = 16.0
|
BULLET_OFFSCREEN_BUFFER_WIDTH float64 = 16.0
|
||||||
PLAYER_HIT_COL_RADIUS_MULTIPLIER float64 = 0.01
|
PLAYER_HIT_COL_RADIUS_MULTIPLIER float64 = 0.01
|
||||||
PLAYER_GRAZE_COL_RADIUS_MULTIPLIER float64 = 0.04
|
PLAYER_GRAZE_COL_RADIUS_MULTIPLIER float64 = 0.04
|
||||||
PLAYER_DEATH_TIMER_MAX int = 180
|
PLAYER_DEATH_TIMER_MAX int64 = 180
|
||||||
GRAZE_ADDITION_MULTIPLIER int = 1000
|
GRAZE_ADDITION_MULTIPLIER int = 1000
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@ package battleroyale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"danmaku/ffi"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/heroiclabs/nakama-common/runtime"
|
"github.com/heroiclabs/nakama-common/runtime"
|
||||||
"math"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckMatchTerminate(lobbyState *MatchState, logger *runtime.Logger) bool {
|
func CheckMatchTerminate(lobbyState *MatchState, logger *runtime.Logger) bool {
|
||||||
|
|
@ -24,7 +22,7 @@ func CheckMatchTerminate(lobbyState *MatchState, logger *runtime.Logger) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func RespondToInput(lobbyState *MatchState, messages []runtime.MatchData, logger *runtime.Logger, tick int64, dispatcher *runtime.MatchDispatcher) {
|
func StorePlayerInputs(lobbyState *MatchState, messages []runtime.MatchData, logger *runtime.Logger, tick int64) {
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
_, exists := lobbyState.presences[msg.GetSessionId()]
|
_, exists := lobbyState.presences[msg.GetSessionId()]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
|
@ -39,59 +37,13 @@ func RespondToInput(lobbyState *MatchState, messages []runtime.MatchData, logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the input in the player's stage state
|
// Store the input in the player's stage state
|
||||||
lobbyState.presences[msg.GetSessionId()].stageState.playerInputs = append(lobbyState.presences[msg.GetSessionId()].stageState.playerInputs, update)
|
lobbyState.presences[msg.GetSessionId()].stageState.playerInputs = append(lobbyState.presences[msg.GetSessionId()].stageState.playerInputs, &update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFireBullets(tick int64) []*ffi.Bullet {
|
|
||||||
var bullets = []*ffi.Bullet{}
|
|
||||||
if tick%30 == 0 {
|
|
||||||
numBullets := 20
|
|
||||||
spreadAngle := 60.0
|
|
||||||
startAngle := 90 - (spreadAngle / 2)
|
|
||||||
bulletSpeed := STAGE_WIDTH / float64(TICK_RATE) / 3
|
|
||||||
bulletRadius := 0.01 * STAGE_WIDTH
|
|
||||||
|
|
||||||
// Define a single spawn point near the top of the screen
|
|
||||||
spawnX := STAGE_WIDTH / 2 // Centered horizontally
|
|
||||||
spawnY := STAGE_HEIGHT * 0.1 // 10% from the top
|
|
||||||
|
|
||||||
for i := range numBullets {
|
|
||||||
angle := startAngle + (spreadAngle/float64(numBullets-1))*float64(i)
|
|
||||||
angleRad := angle * (math.Pi / 180.0)
|
|
||||||
|
|
||||||
velx := bulletSpeed * math.Cos(angleRad)
|
|
||||||
vely := bulletSpeed * math.Sin(angleRad)
|
|
||||||
|
|
||||||
bullet := ffi.NewLinearBullet(
|
|
||||||
tick,
|
|
||||||
spawnX,
|
|
||||||
spawnY,
|
|
||||||
bulletRadius,
|
|
||||||
velx,
|
|
||||||
vely,
|
|
||||||
)
|
|
||||||
bullets = append(bullets, bullet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bullets
|
|
||||||
}
|
|
||||||
|
|
||||||
func BroadcastToPresences(tick int64, lobbyState *MatchState, logger *runtime.Logger, dispatcher *runtime.MatchDispatcher) {
|
func BroadcastToPresences(tick int64, lobbyState *MatchState, logger *runtime.Logger, dispatcher *runtime.MatchDispatcher) {
|
||||||
for _, v := range lobbyState.presences {
|
for _, v := range lobbyState.presences {
|
||||||
v.stageState.DeleteBulletsBeyondKillBoundary(tick)
|
var tickData = v.stageState.MakeServerTick(tick)
|
||||||
|
|
||||||
var newBulletsToBroadcast = []map[string]any{}
|
|
||||||
if v.stageState.CheckCollisionState(tick) == PLAYER_ALIVE {
|
|
||||||
newBullets := TestFireBullets(tick)
|
|
||||||
|
|
||||||
for _, bullet := range newBullets {
|
|
||||||
v.stageState.AddBullet(bullet)
|
|
||||||
newBulletsToBroadcast = append(newBulletsToBroadcast, bullet.Serialize(tick))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tickData = v.stageState.MakeServerTick(tick, newBulletsToBroadcast)
|
|
||||||
|
|
||||||
data, err := json.Marshal(tickData)
|
data, err := json.Marshal(tickData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -115,10 +67,13 @@ func (m *Match) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
RespondToInput(lobbyState, messages, &logger, tick, &dispatcher)
|
StorePlayerInputs(lobbyState, messages, &logger, tick)
|
||||||
|
|
||||||
for _, playerState := range lobbyState.presences {
|
for _, playerState := range lobbyState.presences {
|
||||||
|
playerState.stageState.MarkBulletsBeyondKillBoundary(tick)
|
||||||
playerState.stageState.ProcessPlayerInputs(tick)
|
playerState.stageState.ProcessPlayerInputs(tick)
|
||||||
playerState.stageState.CleanupBullets(tick)
|
playerState.stageState.HandleDeath(tick)
|
||||||
|
playerState.stageState.CleanupOldBullets(tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
BroadcastToPresences(tick, lobbyState, &logger, &dispatcher)
|
BroadcastToPresences(tick, lobbyState, &logger, &dispatcher)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package battleroyale
|
package battleroyale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"danmaku/ffi"
|
"danmaku/ffi"
|
||||||
"math"
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -10,52 +11,56 @@ import (
|
||||||
type PlayerStageState struct {
|
type PlayerStageState struct {
|
||||||
hitCol *ffi.Circle
|
hitCol *ffi.Circle
|
||||||
grazeCol *ffi.Circle
|
grazeCol *ffi.Circle
|
||||||
bullets []*Bullet
|
bullets []*ffi.Bullet
|
||||||
updatePlayerPos bool
|
updatePlayerPos bool
|
||||||
health int
|
health int
|
||||||
graze int
|
graze int
|
||||||
score int
|
score int
|
||||||
deathTimer int
|
deathTick int64
|
||||||
|
dead bool
|
||||||
cancelDeath bool
|
cancelDeath bool
|
||||||
playerInputs []ClientUpdate
|
survivedGraceWindow bool
|
||||||
|
playerInputs []*ClientUpdate
|
||||||
lastInput *ClientUpdate
|
lastInput *ClientUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) ProcessPlayerInputs(tick int64) {
|
func (s *PlayerStageState) ProcessPlayerInputs(tick int64) {
|
||||||
|
// Clean up inputs outside the grace window
|
||||||
|
s.playerInputs = slices.DeleteFunc(s.playerInputs, func(input *ClientUpdate) bool {
|
||||||
|
return tick-input.Tick > GRACE_WINDOW_TICKS
|
||||||
|
})
|
||||||
|
|
||||||
// Sort inputs by tick
|
// Sort inputs by tick
|
||||||
slices.SortFunc(s.playerInputs, func(a, b ClientUpdate) bool {
|
slices.SortFunc(s.playerInputs, func(a, b *ClientUpdate) int {
|
||||||
return a.Tick < b.Tick
|
return cmp.Compare(a.Tick, b.Tick)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Replay each tick within the grace window
|
// 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
|
// Find the input for the current tick
|
||||||
var currentInput *ClientUpdate
|
var currentInput *ClientUpdate
|
||||||
for _, input := range s.playerInputs {
|
for _, input := range s.playerInputs {
|
||||||
if input.Tick == t {
|
if input.Tick == t {
|
||||||
currentInput = &input
|
currentInput = input
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the correct movement or no movement
|
// Apply the correct movement or no movement
|
||||||
if currentInput != nil {
|
if currentInput != nil {
|
||||||
s.BoundsCheckedMove(currentInput.X, currentInput.Y)
|
s.clampedMove(currentInput.X, currentInput.Y)
|
||||||
s.lastInput = currentInput
|
s.lastInput = currentInput
|
||||||
} else if s.lastInput != nil {
|
} else if s.lastInput != nil {
|
||||||
s.BoundsCheckedMove(s.lastInput.X, s.lastInput.Y)
|
s.clampedMove(s.lastInput.X, s.lastInput.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.CheckCollisionState(t) == PLAYER_DEAD {
|
// If the player dies in the grace window, don't cancel their death
|
||||||
s.cancelDeath = false
|
if s.CheckPlayerDeadOnTick(t) {
|
||||||
|
s.survivedGraceWindow = false
|
||||||
break
|
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 {
|
func NewPlayerStage() *PlayerStageState {
|
||||||
|
|
@ -65,7 +70,7 @@ func NewPlayerStage() *PlayerStageState {
|
||||||
bullets: []*ffi.Bullet{},
|
bullets: []*ffi.Bullet{},
|
||||||
updatePlayerPos: true,
|
updatePlayerPos: true,
|
||||||
health: 3,
|
health: 3,
|
||||||
deathTimer: -1,
|
deathTick: PLAYER_ALIVE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,83 +82,146 @@ func (s *PlayerStageState) Delete() {
|
||||||
ffi.DestroyCircle(s.grazeCol)
|
ffi.DestroyCircle(s.grazeCol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) BoundsCheckedMove(x float64, y float64) {
|
func (s *PlayerStageState) clampedMove(x, y float64) bool {
|
||||||
clampedX := x < 0 || x > STAGE_WIDTH
|
clampedX := x < 0 || x > STAGE_WIDTH
|
||||||
clampedY := y < 0 || y > STAGE_HEIGHT
|
clampedY := y < 0 || y > STAGE_HEIGHT
|
||||||
|
|
||||||
x = math.Max(0, math.Min(x, STAGE_WIDTH))
|
newX := math.Max(0, math.Min(x, STAGE_WIDTH))
|
||||||
y = math.Max(0, math.Min(y, STAGE_HEIGHT))
|
newY := math.Max(0, math.Min(y, STAGE_HEIGHT))
|
||||||
|
|
||||||
s.hitCol.UpdatePos(x, y)
|
s.hitCol.UpdatePos(newX, newY)
|
||||||
s.grazeCol.UpdatePos(x, y)
|
s.grazeCol.UpdatePos(newX, newY)
|
||||||
|
|
||||||
if clampedX || clampedY {
|
return clampedX || clampedY
|
||||||
s.updatePlayerPos = true
|
}
|
||||||
|
|
||||||
|
func (s *PlayerStageState) MarkBulletsBeyondKillBoundary(tick int64) {
|
||||||
|
for _, b := range s.bullets {
|
||||||
|
if b.BeyondKillBoundary(tick) && b.DeletionTick == ACTIVE_BULLET {
|
||||||
|
b.DeletionTick = tick
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) DeleteBulletsBeyondKillBoundary(tick int64) {
|
func (s *PlayerStageState) CleanupOldBullets(tick int64) {
|
||||||
s.bullets = slices.DeleteFunc(s.bullets, func(b *ffi.Bullet) bool {
|
s.bullets = slices.DeleteFunc(s.bullets, func(b *ffi.Bullet) bool {
|
||||||
if b.BeyondKillBoundary(tick) && b.deletionTick == 0 {
|
if b.DeletionTick > ACTIVE_BULLET && tick-b.DeletionTick > GRACE_WINDOW_TICKS {
|
||||||
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)
|
ffi.DestroyBullet(b)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) UpdateDeathTimer(tick int64) {
|
func (s *PlayerStageState) Kill(tick int64) {
|
||||||
// If the player is dead, decrement the death timer
|
s.deathTick = tick
|
||||||
if s.deathTimer >= 0 {
|
s.dead = true
|
||||||
s.deathTimer -= 1
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if s.deathTimer == PLAYER_DEATH_RESET {
|
func (s *PlayerStageState) Revive() {
|
||||||
s.hitCol.UpdatePos(STAGE_WIDTH*0.5, STAGE_HEIGHT-STAGE_HEIGHT*0.1)
|
s.hitCol.UpdatePos(STAGE_WIDTH*0.5, STAGE_HEIGHT-STAGE_HEIGHT*0.1)
|
||||||
s.grazeCol.UpdatePos(STAGE_WIDTH*0.5, STAGE_HEIGHT-STAGE_HEIGHT*0.1)
|
s.grazeCol.UpdatePos(STAGE_WIDTH*0.5, STAGE_HEIGHT-STAGE_HEIGHT*0.1)
|
||||||
s.updatePlayerPos = true
|
s.updatePlayerPos = true
|
||||||
} else if s.deathTimer == PLAYER_ALIVE {
|
s.playerInputs = nil
|
||||||
// Use CheckCollisionState to determine if the player should be dead
|
s.dead = false
|
||||||
if s.CheckCollisionState(tick) == PLAYER_DEAD {
|
s.deathTick = PLAYER_ALIVE
|
||||||
s.deathTimer = PLAYER_DEATH_TIMER_MAX
|
}
|
||||||
|
|
||||||
|
func (s *PlayerStageState) CancelDeath() {
|
||||||
|
s.dead = false
|
||||||
|
s.deathTick = PLAYER_ALIVE
|
||||||
|
s.cancelDeath = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PlayerStageState) HandleDeath(tick int64) {
|
||||||
|
// If the player didn't survive the grace window and they're currently alive, kill them
|
||||||
|
if !(s.survivedGraceWindow) && (s.dead == false) {
|
||||||
|
s.Kill(tick)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the player is currently dead and they survived the grace window, cancel their death
|
||||||
|
if (s.dead == true) && s.survivedGraceWindow {
|
||||||
|
s.CancelDeath()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the player is currently dead and they have been dead for greater than PLAYER_DEATH_TIMER_MAX ticks, revive them
|
||||||
|
if s.dead == true && ((tick - s.deathTick) > PLAYER_DEATH_TIMER_MAX) {
|
||||||
|
s.Revive()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) CheckCollisionState(tick int64) int {
|
func (s *PlayerStageState) CheckPlayerDeadOnTick(tick int64) bool {
|
||||||
if slices.ContainsFunc(s.bullets, func(b *ffi.Bullet) bool {
|
if slices.ContainsFunc(s.bullets, func(b *ffi.Bullet) bool {
|
||||||
return b.CollidesWith(s.hitCol, tick)
|
return b.CollidesWith(s.hitCol, tick)
|
||||||
}) {
|
}) {
|
||||||
return PLAYER_DEAD
|
return true
|
||||||
}
|
}
|
||||||
return PLAYER_ALIVE
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) UpdateGrazeMultiplier(tick int64) {
|
func (s *PlayerStageState) UpdateGrazeMultiplier(tick int64) {
|
||||||
if slices.ContainsFunc(s.bullets, func(b *Bullet) bool {
|
if slices.ContainsFunc(s.bullets, func(b *ffi.Bullet) bool {
|
||||||
return b.CollidesWith(s.grazeCol, tick)
|
return b.CollidesWith(s.grazeCol, tick)
|
||||||
}) {
|
}) {
|
||||||
s.graze += GRAZE_ADDITION_MULTIPLIER
|
s.graze += GRAZE_ADDITION_MULTIPLIER
|
||||||
}
|
}
|
||||||
return PLAYER_ALIVE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) AddBullet(b *ffi.Bullet) {
|
func (s *PlayerStageState) AddBullet(b *ffi.Bullet) {
|
||||||
s.bullets = append(s.bullets, b)
|
s.bullets = append(s.bullets, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayerStageState) MakeServerTick(tick int64, serializedNewBullets []map[string]any) *ServerTickUpdate {
|
func MakeTestFireBullets(tick int64) []*ffi.Bullet {
|
||||||
s.UpdateDeathTimer(tick)
|
var bullets = []*ffi.Bullet{}
|
||||||
|
if tick%30 == 0 {
|
||||||
|
numBullets := 20
|
||||||
|
spreadAngle := 60.0
|
||||||
|
startAngle := 90 - (spreadAngle / 2)
|
||||||
|
bulletSpeed := STAGE_WIDTH / float64(TICK_RATE) / 3
|
||||||
|
bulletRadius := 0.01 * STAGE_WIDTH
|
||||||
|
|
||||||
|
// Define a single spawn point near the top of the screen
|
||||||
|
spawnX := STAGE_WIDTH / 2 // Centered horizontally
|
||||||
|
spawnY := STAGE_HEIGHT * 0.1 // 10% from the top
|
||||||
|
|
||||||
|
for i := range numBullets {
|
||||||
|
angle := startAngle + (spreadAngle/float64(numBullets-1))*float64(i)
|
||||||
|
angleRad := angle * (math.Pi / 180.0)
|
||||||
|
|
||||||
|
velx := bulletSpeed * math.Cos(angleRad)
|
||||||
|
vely := bulletSpeed * math.Sin(angleRad)
|
||||||
|
|
||||||
|
bullet := ffi.NewLinearBullet(
|
||||||
|
tick,
|
||||||
|
spawnX,
|
||||||
|
spawnY,
|
||||||
|
bulletRadius,
|
||||||
|
velx,
|
||||||
|
vely,
|
||||||
|
)
|
||||||
|
bullets = append(bullets, bullet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bullets
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PlayerStageState) GetBoardStateDiff(tick int64) []map[string]any {
|
||||||
|
var newBulletsToBroadcast = []map[string]any{}
|
||||||
|
if !s.dead {
|
||||||
|
newBullets := MakeTestFireBullets(tick)
|
||||||
|
|
||||||
|
for _, bullet := range newBullets {
|
||||||
|
s.AddBullet(bullet)
|
||||||
|
newBulletsToBroadcast = append(newBulletsToBroadcast, bullet.Serialize(tick))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newBulletsToBroadcast
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PlayerStageState) MakeServerTick(tick int64) *ServerTickUpdate {
|
||||||
hitPosX, hitPosY := s.hitCol.GetPos()
|
hitPosX, hitPosY := s.hitCol.GetPos()
|
||||||
grazePosX, grazePosY := s.hitCol.GetPos()
|
grazePosX, grazePosY := s.hitCol.GetPos()
|
||||||
var tickData = ServerTickUpdate{
|
var tickData = ServerTickUpdate{
|
||||||
|
|
@ -168,19 +236,16 @@ func (s *PlayerStageState) MakeServerTick(tick int64, serializedNewBullets []map
|
||||||
"y": grazePosY,
|
"y": grazePosY,
|
||||||
"radius": STAGE_WIDTH * PLAYER_GRAZE_COL_RADIUS_MULTIPLIER,
|
"radius": STAGE_WIDTH * PLAYER_GRAZE_COL_RADIUS_MULTIPLIER,
|
||||||
},
|
},
|
||||||
NewBullets: serializedNewBullets,
|
StageStateDiff: s.GetBoardStateDiff(tick),
|
||||||
ForcePlayerPos: s.updatePlayerPos,
|
ForcePlayerPos: s.updatePlayerPos,
|
||||||
DeathTimer: s.deathTimer,
|
|
||||||
Graze: s.graze,
|
Graze: s.graze,
|
||||||
|
Dead: s.dead,
|
||||||
|
CancelDeath: s.cancelDeath,
|
||||||
|
DeathTick: s.deathTick,
|
||||||
UTCTime: float64(time.Now().UnixMilli()) / 1000.0,
|
UTCTime: float64(time.Now().UnixMilli()) / 1000.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.cancelDeath {
|
|
||||||
tickData.CancelDeath = true
|
|
||||||
s.cancelDeath = false
|
s.cancelDeath = false
|
||||||
}
|
|
||||||
|
|
||||||
// When this is called, we want to transmit updatePlayerPos if it's true once and then reset
|
|
||||||
s.updatePlayerPos = false
|
s.updatePlayerPos = false
|
||||||
|
|
||||||
return &tickData
|
return &tickData
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ type PresenceState struct { // present time! hahahahahahahah!
|
||||||
type ClientUpdate struct {
|
type ClientUpdate struct {
|
||||||
X float64 `json:"x"`
|
X float64 `json:"x"`
|
||||||
Y float64 `json:"y"`
|
Y float64 `json:"y"`
|
||||||
|
Tick int64 `json:"tick"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct to serialize server->client updates
|
// Struct to serialize server->client updates
|
||||||
|
|
@ -32,8 +33,11 @@ type ServerTickUpdate struct {
|
||||||
Tick int64 `json:"tick"`
|
Tick int64 `json:"tick"`
|
||||||
PlayerHitPos map[string]any `json:"playerHitPos"`
|
PlayerHitPos map[string]any `json:"playerHitPos"`
|
||||||
PlayerGrazePos map[string]any `json:"playerGrazePos"`
|
PlayerGrazePos map[string]any `json:"playerGrazePos"`
|
||||||
NewBullets []map[string]any `json:"newBullets"`
|
StageStateDiff []map[string]any `json:"stageStateDiff"`
|
||||||
ForcePlayerPos bool `json:"forcePlayerPos"`
|
ForcePlayerPos bool `json:"forcePlayerPos"`
|
||||||
DeathTimer int `json:"deathTimer"`
|
Dead bool `json:"dead"`
|
||||||
|
CancelDeath bool `json:"cancelDeath"`
|
||||||
|
DeathTick int64 `json:"deathTick"`
|
||||||
Graze int `json:"graze"`
|
Graze int `json:"graze"`
|
||||||
|
UTCTime float64 `json:"utctime"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue