Archived
1
0
Fork 0
This repository has been archived on 2026-01-19. You can view files and clone it, but cannot push or open issues or pull requests.
Danmaku/server/game-modes/battle-royale/game-loop.go

122 lines
3.4 KiB
Go

package battleroyale
import (
"context"
"danmaku/ffi"
"database/sql"
"encoding/json"
"github.com/heroiclabs/nakama-common/runtime"
"math"
)
func CheckMatchTerminate(lobbyState *MatchState, logger *runtime.Logger) bool {
// If we have no presences in the match according to the match state, increment the empty ticks count
if len(lobbyState.presences) == 0 {
lobbyState.emptyTicks++
}
// If the match has been empty for more than 100 ticks, end the match by returning nil
if lobbyState.emptyTicks > 100 {
(*logger).Info("Match terminated due to empty lobby.")
return true
}
return false
}
func RespondToInput(lobbyState *MatchState, messages []runtime.MatchData, logger *runtime.Logger) {
for _, msg := range messages {
_, exists := lobbyState.presences[msg.GetSessionId()]
if !exists {
(*logger).Warn("Received input for non-existent player session ID: %v", msg.GetSessionId())
continue
}
// Parse player message
var update ClientUpdate
if err := json.Unmarshal(msg.GetData(), &update); err != nil {
(*logger).Warn("Failed to parse input: %v", err)
continue
}
lobbyState.presences[msg.GetSessionId()].stageState.BoundsCheckedMove(update.X, update.Y)
}
}
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) {
for _, v := range lobbyState.presences {
v.stageState.DeleteBulletsBeyondKillBoundary(tick)
var newBulletsToBroadcast = []map[string]any{}
if v.stageState.CheckDeathState(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)
if err != nil {
(*logger).Error("Error marshalling bullet data", err)
} else {
reliable := true
(*dispatcher).BroadcastMessage(STATE_UPDATE, data, nil, nil, reliable)
}
}
}
// Main game loop, executed at tickRate per second specified in MatchInit
func (m *Match) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state any, messages []runtime.MatchData) any {
lobbyState, ok := state.(*MatchState)
if !ok {
logger.Error("State is not a valid lobby state object for MatchLoop.")
return nil
}
if CheckMatchTerminate(lobbyState, &logger) {
return nil
}
RespondToInput(lobbyState, messages, &logger)
BroadcastToPresences(tick, lobbyState, &logger, &dispatcher)
return lobbyState
}