From 0ba96ca4783faf8f557444433e71ee6379fa1a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Ryb=C3=A1rsky?= Date: Sat, 31 Aug 2024 15:35:19 +0200 Subject: [PATCH] Fix a few network issues --- base.go | 62 ++-- bullet.go | 6 + gameobject.go | 53 ++- main.go | 258 ++++++++++----- player.go | 874 ++++++++++++++++++++++++------------------------- profiler.go | 15 +- tuneller.proto | 2 + 7 files changed, 694 insertions(+), 576 deletions(-) diff --git a/base.go b/base.go index 283e30f..1e6b966 100644 --- a/base.go +++ b/base.go @@ -3,8 +3,12 @@ package main import ( "github.com/veandco/go-sdl2/sdl" "image/color" + "strconv" + "sync" ) +var baseMutex sync.RWMutex + type Base struct { ownerID uint32 openingWidth int32 @@ -12,8 +16,9 @@ type Base struct { } func (base *Base) tick(players map[uint32]*Player) { + playersMutex.RLock() for playerID, player := range players { - if player.gameObject.baseRect.HasIntersection(&base.gameObject.baseRect) { + if player.gameObject.baseRect.HasIntersection(base.gameObject.baseRect) { if player.rechargeCooldown == 0 && player.energy < serverConfig.MaxEnergy { if serverConfig.MaxEnergy-player.energy < 4 { player.energy++ @@ -32,6 +37,7 @@ func (base *Base) tick(players map[uint32]*Player) { } } } + playersMutex.RUnlock() } func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface, bases map[uint32]*Base) { @@ -42,14 +48,6 @@ func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface, bases map[uint3 } func (base *Base) build(gameMap *GameMap) { - borderWidth := base.gameObject.baseRect.W - base.openingWidth - borderWidthA := borderWidth / 2 - if borderWidth < 0 { - panic("Bad border width") - } - if base.gameObject.baseRect.H < 9 { - panic("Bad border height") - } if gameMap.width-uint32(base.gameObject.baseRect.X)-uint32(base.gameObject.baseRect.W) <= 0 { panic("Bad base x location") } @@ -57,30 +55,32 @@ func (base *Base) build(gameMap *GameMap) { panic("Bad base y location") } if base.gameObject.baseRect.X < 0 || base.gameObject.baseRect.Y < 0 { - panic("Bad base negative location") + panic("Bad base negative location " + strconv.Itoa(int(base.gameObject.baseRect.X)) + " - " + strconv.Itoa(int(base.gameObject.baseRect.Y))) } - for x := base.gameObject.baseRect.X; x < base.gameObject.baseRect.X+base.gameObject.baseRect.W+1; x++ { - for y := base.gameObject.baseRect.Y; y < base.gameObject.baseRect.Y+base.gameObject.baseRect.H+1; y++ { + base.gameObject.adjustBaseRect() + for x := base.gameObject.baseRect.X; x < base.gameObject.baseRect.X+base.gameObject.baseRect.W; x++ { + for y := base.gameObject.baseRect.Y; y < base.gameObject.baseRect.Y+base.gameObject.baseRect.H; y++ { gameMap.tiles[x][y] = 0 } } - for y := base.gameObject.baseRect.Y; y < base.gameObject.baseRect.Y+base.gameObject.baseRect.H; y++ { - gameMap.tiles[base.gameObject.baseRect.X][y] = 4 - gameMap.tiles[base.gameObject.baseRect.X+base.gameObject.baseRect.W][y] = 4 - } - for x := base.gameObject.baseRect.X; x < base.gameObject.baseRect.X+borderWidthA; x++ { - gameMap.tiles[x][base.gameObject.baseRect.Y] = 4 - gameMap.tiles[x][base.gameObject.baseRect.Y+base.gameObject.baseRect.H] = 4 - } - for x := base.gameObject.baseRect.X + borderWidthA + base.openingWidth; x < base.gameObject.baseRect.X+base.gameObject.baseRect.W+1; x++ { - gameMap.tiles[x][base.gameObject.baseRect.Y] = 4 - gameMap.tiles[x][base.gameObject.baseRect.Y+base.gameObject.baseRect.H] = 4 + for _, rectT := range base.gameObject.getCurrentRects() { + rect := sdl.Rect{ + X: rectT.rect.X + base.gameObject.baseRect.X, + Y: rectT.rect.Y + base.gameObject.baseRect.Y, + W: rectT.rect.W, + H: rectT.rect.H, + } + for x := rect.X; x < rect.X+rect.W; x++ { + for y := rect.Y; y < rect.Y+rect.H; y++ { + gameMap.tiles[x][y] = 4 + } + } } } func createBase(gameMap *GameMap, baseColor color.Color, posX, posY, ownerID, openingWidth uint32) *Base { gameObject := &GameObject{} - gameObject.baseRect = sdl.Rect{ + gameObject.baseRect = &sdl.Rect{ X: int32(posX), Y: int32(posY), W: 35, @@ -97,12 +97,12 @@ func createBase(gameMap *GameMap, baseColor color.Color, posX, posY, ownerID, op panic("Bad border width") } gameObject.addColor(baseColor) - gameObject.addColoredRect(0, 0, 1, gameObject.baseRect.H, 0) - gameObject.addColoredRect(gameObject.baseRect.W, 0, 1, gameObject.baseRect.H, 0) + gameObject.addColoredRect(0, 0, 1, gameObject.baseRect.H-1, 0) + gameObject.addColoredRect(gameObject.baseRect.W, 0, 1, gameObject.baseRect.H-1, 0) gameObject.addColoredRect(0, 0, borderWidthA, 1, 0) gameObject.addColoredRect(borderWidthA+int32(openingWidth), 0, borderWidthB, 1, 0) - gameObject.addColoredRect(0, gameObject.baseRect.H, borderWidthA, 1, 0) - gameObject.addColoredRect(borderWidthA+int32(openingWidth), gameObject.baseRect.H, borderWidthB, 1, 0) + gameObject.addColoredRect(0, gameObject.baseRect.H-1, borderWidthA, 1, 0) + gameObject.addColoredRect(borderWidthA+int32(openingWidth), gameObject.baseRect.H-1, borderWidthB, 1, 0) base := &Base{ gameObject: gameObject, ownerID: ownerID, @@ -118,12 +118,16 @@ func (base *Base) delete(bases map[uint32]*Base) { gameMap.tiles[x][y] = 0 } } + baseMutex.Lock() delete(bases, base.ownerID) + baseMutex.Unlock() } func createBases(players map[uint32]*Player, gameMap *GameMap) map[uint32]*Base { bases := map[uint32]*Base{} + playersMutex.RLock() for ownerID, player := range players { + baseMutex.Lock() bases[ownerID] = createBase(gameMap, player.playerColors.body, uint32(player.gameObject.baseRect.X-14), @@ -131,6 +135,8 @@ func createBases(players map[uint32]*Player, gameMap *GameMap) map[uint32]*Base ownerID, uint32(float64(player.gameObject.baseRect.W)*1.5), ) + baseMutex.Unlock() } + playersMutex.RUnlock() return bases } diff --git a/bullet.go b/bullet.go index 08f09e3..2fbb8e0 100644 --- a/bullet.go +++ b/bullet.go @@ -4,11 +4,15 @@ import ( "github.com/veandco/go-sdl2/sdl" "image/color" "math/rand" + "sync" ) var bulletLastID = uint32(0) var bulletParticleLastID = uint32(0) +var bulletMutex sync.RWMutex +var bulletParticleMutex sync.RWMutex + type Bullet struct { posX, posY int32 direction uint8 @@ -159,6 +163,7 @@ func (bullet *Bullet) tick(gameMap *GameMap, H: 1, } hitPlayer := false + playersMutex.RLock() for _, player := range players { if player.playerID == bullet.ownerID { continue @@ -174,6 +179,7 @@ func (bullet *Bullet) tick(gameMap *GameMap, } } } + playersMutex.RUnlock() if collisionResult != 0 || hitPlayer { bullet.explode(gameMap, bulletParticleMap) delete(bulletMap, bullet.id) diff --git a/gameobject.go b/gameobject.go index e5c4e0f..d917fd0 100644 --- a/gameobject.go +++ b/gameobject.go @@ -6,13 +6,14 @@ import ( ) type GameObject struct { - baseRect sdl.Rect - prevBaseRect sdl.Rect - borderRect sdl.Rect - orientation uint8 - visualRects [][]*ColoredRect - colors []color.Color - inView bool + baseRect *sdl.Rect + prevBaseRect *sdl.Rect + borderRect *sdl.Rect + collisionRect *sdl.Rect + orientation uint8 + visualRects [][]*ColoredRect + colors []color.Color + inView bool } type ColoredRect struct { @@ -38,6 +39,13 @@ func (gameObject *GameObject) adjustRectWorld(offset *sdl.Rect) *sdl.Rect { } } +func (gameObject *GameObject) adjustColoredRectWorld(offset *ColoredRect) *ColoredRect { + return &ColoredRect{ + color: offset.color, + rect: gameObject.adjustRectWorld(offset.rect), + } +} + func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.Rect) *sdl.Rect { return &sdl.Rect{ X: gameObject.baseRect.X + offset.X - camera.X, @@ -47,20 +55,31 @@ func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.R } } +func (gameObject *GameObject) adjustBaseRect() { + first := true + oldX, oldY := gameObject.baseRect.X, gameObject.baseRect.Y + for _, rect := range gameObject.getCurrentRects() { + if first { + gameObject.baseRect = gameObject.adjustRectWorld(rect.rect) + first = false + } else { + + } + newRect := gameObject.baseRect.Union(gameObject.adjustRectWorld(rect.rect)) + gameObject.baseRect = &newRect + gameObject.baseRect.X, gameObject.baseRect.Y = oldX, oldY + } +} + func (gameObject *GameObject) render(camera *sdl.Rect, surface *sdl.Surface) { - if camera.HasIntersection(&gameObject.baseRect) { + if camera.HasIntersection(gameObject.baseRect) { gameObject.inView = true if config.Debug { - gameObject.borderRect = sdl.Rect{ - X: gameObject.baseRect.X - 1, - Y: gameObject.baseRect.Y - 1, - W: gameObject.baseRect.W + 2, - H: gameObject.baseRect.H + 2, - } - borderRectFinal := adjustRectToCamera(&gameObject.borderRect, camera) - baseRectFinal := adjustRectToCamera(&gameObject.baseRect, camera) - surface.FillRect(borderRectFinal, sdl.MapRGBA(surface.Format, 20, 192, 128, 64)) + baseRectFinal := adjustRectToCamera(gameObject.baseRect, camera) surface.FillRect(baseRectFinal, sdl.MapRGBA(surface.Format, 255, 20, 10, 64)) + if !gameObject.collisionRect.Empty() { + surface.FillRect(adjustRectToCamera(gameObject.collisionRect, camera), sdl.MapRGBA(surface.Format, 40, 192, 255, 64)) + } } if config.RenderGameObjects { for _, coloredRect := range gameObject.visualRects[gameObject.orientation] { diff --git a/main.go b/main.go index b1b2002..224925c 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,13 @@ import ( "log" "net" "os" + "runtime/pprof" "sync" "time" ) +const GameVersion = "TunnEElineningnegbfbf Through the wiAldWest" + type ServerConfig struct { MapWidth uint32 `json:"map_width"` MapHeight uint32 `json:"map_height"` @@ -37,44 +40,47 @@ type ServerConfig struct { MovementCooldownNoEnergy uint32 `json:"movement_cooldown_no_energy"` DiggingCooldownNoEnergy uint32 `json:"digging_cooldown_no_energy"` ReloadCooldown uint32 `json:"reload_cooldown"` + ReloadWait uint32 `json:"reload_wait"` } type Config struct { - Debug bool `json:"debug"` - MapWindow bool `json:"map_window"` - RenderGameObjects bool `json:"render_game_objects"` - ProfilerOn bool `json:"profiler_on"` - ProfilerInterval uint64 `json:"profiler_interval"` - Server bool `json:"server"` - CameraW int32 `json:"camera_w"` - CameraH int32 `json:"camera_h"` - Version string `json:"version"` - Client bool `json:"client"` - Address string `json:"address"` - JoyStickDeadZone int16 `json:"joystick_dead_zone"` - DoAllKeymapsPlayers bool `json:"do_all_keymaps_players"` - DoJoyStickPlayers bool `json:"do_joystick_players"` - DoKeymapPlayer bool `json:"do_keymap_player"` - ServerConfig ServerConfig `json:"server_config"` + Debug bool `json:"debug"` + MapWindow bool `json:"map_window"` + MapUpdateInterval uint16 `json:"map_update_interval"` + RenderGameObjects bool `json:"render_game_objects"` + ProfilerOn bool `json:"profiler_on"` + ProfilerInterval uint64 `json:"profiler_interval"` + Server bool `json:"server"` + CameraW int32 `json:"camera_w"` + CameraH int32 `json:"camera_h"` + Client bool `json:"client"` + Address string `json:"address"` + JoyStickDeadZone int16 `json:"joystick_dead_zone"` + DoAllKeymapsPlayers bool `json:"do_all_keymaps_players"` + DoJoyStickPlayers bool `json:"do_joystick_players"` + DoKeymapPlayer bool `json:"do_keymap_player"` + RecentlyDugBlocksClearInterval uint16 `json:"recently_dug_blocks_clear_interval"` + ServerConfig ServerConfig `json:"server_config"` } func loadOrCreateConfig(filename string) (*Config, error) { config := &Config{ - Debug: false, - MapWindow: false, - RenderGameObjects: true, - ProfilerOn: false, - ProfilerInterval: 100, - Server: false, - CameraW: 76, - CameraH: 76, - Version: "69420", - Client: true, - Address: "192.168.1.8:5074", - JoyStickDeadZone: 8000, - DoAllKeymapsPlayers: false, - DoJoyStickPlayers: true, - DoKeymapPlayer: true, + Debug: false, + MapWindow: false, + MapUpdateInterval: 999, + RenderGameObjects: true, + ProfilerOn: false, + ProfilerInterval: 100, + Server: false, + CameraW: 76, + CameraH: 76, + Client: true, + Address: "192.168.1.8:5074", + JoyStickDeadZone: 8000, + DoAllKeymapsPlayers: false, + DoJoyStickPlayers: true, + DoKeymapPlayer: true, + RecentlyDugBlocksClearInterval: 20, ServerConfig: ServerConfig{ MapWidth: 1000, MapHeight: 1000, @@ -100,6 +106,7 @@ func loadOrCreateConfig(filename string) (*Config, error) { MovementCooldownNoEnergy: 4, DiggingCooldownNoEnergy: 8, ReloadCooldown: 16, + ReloadWait: 16, }, } @@ -149,6 +156,8 @@ var clientInitialized = false var worldLock sync.Mutex +var totalRenderTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalScalingTime, totalRemotePlayerUpdate, tickCount, totalFrameTime, frameCount uint64 + func main() { initializeSDL() defer sdl.Quit() @@ -158,6 +167,17 @@ func main() { } serverConfig = configX.ServerConfig config = configX + if config.ProfilerOn { + f, err := os.Create("cpuprofile") + if err != nil { + log.Fatal("could not create CPU profile: ", err) + } + defer f.Close() // error handling omitted for example + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatal("could not start CPU profile: ", err) + } + defer pprof.StopCPUProfile() + } mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)} players := make(map[uint32]*Player) initPlayerColors() @@ -171,7 +191,7 @@ func main() { if !config.Client { gameMap.createGameMap(true) - createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players) + createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players, bases) bases = createBases(players, gameMap) } else { // Create a connection to the server @@ -195,9 +215,11 @@ func main() { defer closeThings(players) + playersMutex.Lock() for playerIndex, player := range players { initPlayer(uint8(playerIndex), player) } + playersMutex.Unlock() if config.Server { // Create a connection to the server @@ -216,7 +238,12 @@ func main() { for { conn, err := listen.AcceptTCP() if err != nil { - log.Fatal(err) + if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" { + log.Println("Listener closed, stopping server.") + return + } + log.Println("Error accepting connection:", err) + continue } go handleRequest(conn, players, bullets, bases) } @@ -227,7 +254,6 @@ func main() { mapWindow, mapSurface = setupMapWindowAndSurface() } running := true - var totalRenderTime, totalScalingTime, totalFrameTime, frameCount uint64 // Delta time management var prevTime = sdl.GetTicks64() @@ -239,17 +265,21 @@ func main() { prevTime = currentTime worldLock.Lock() - // Catch up in case of a large delta time - for deltaTime > maxDeltaTime { - deltaTime -= maxDeltaTime + totalGameLogicCatchUp += profileSection(func() { + // Catch up in case of a large delta time + for deltaTime > maxDeltaTime { + deltaTime -= maxDeltaTime - // Run multiple logic ticks if deltaTime is too large + // Run multiple logic ticks if deltaTime is too large + runGameLogic(players, gameMap, bases, bullets, bulletParticles) + } + }) + totalNormalGameLogic += profileSection(func() { + // Run logic for the remaining delta time runGameLogic(players, gameMap, bases, bullets, bulletParticles) - } - - // Run logic for the remaining delta time - runGameLogic(players, gameMap, bases, bullets, bulletParticles) + }) + playersMutex.RLock() for playerIndex, player := range players { if player.local { running = doPlayerFrame(uint8(playerIndex), player, players, gameMap, bases, bullets, bulletParticles) @@ -258,25 +288,34 @@ func main() { } } } + playersMutex.RUnlock() // Render logic - if config.MapWindow { + if config.MapWindow && frameCount%uint64(config.MapUpdateInterval) == 0 { // Profile rendering (aggregate all renderings) totalRenderTime += profileSection(func() { running = running && handleEvents(mapWindow, mapSurface) gameMap.render(mapRendererRect, mapSurface) + bulletMutex.RLock() for _, bullet := range bullets { (*bullet).render(mapRendererRect, mapSurface, bullets) } + bulletMutex.RUnlock() + baseMutex.RLock() for _, base := range bases { (*base).render(mapRendererRect, mapSurface, bases) } + baseMutex.RUnlock() + playersMutex.RLock() for _, playerLoop := range players { (*playerLoop).render(mapRendererRect, mapSurface, players) } + playersMutex.RUnlock() + bulletParticleMutex.RLock() for _, bulletParticle := range bulletParticles { bulletParticle.render(mapRendererRect, mapSurface, bulletParticles) } + bulletParticleMutex.RUnlock() }) totalScalingTime += profileSection(func() { adjustWindow(mapWindow, mapSurface) @@ -289,8 +328,8 @@ func main() { // Log profiling information every 1000 frames if frameCount%config.ProfilerInterval == 0 && config.ProfilerOn { - logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, frameCount) - resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &frameCount) + logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate, frameCount, tickCount) + resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &totalGameLogicCatchUp, &totalNormalGameLogic, &totalTicking, &totalRemotePlayerUpdate, &frameCount, &tickCount) } worldLock.Unlock() enforceFrameRate(currentTime, 60) @@ -303,51 +342,94 @@ func runGameLogic(players map[uint32]*Player, gameMap *GameMap, bases map[uint32 if config.Server { //update remote player maps + totalRemotePlayerUpdate += profileSection(func() { + var wgX sync.WaitGroup + var wgX2 sync.WaitGroup + playerUpdateMutex.Lock() + playersMutex.RLock() + for _, player := range players { + if player.local { + continue + } + wgX.Add(6) + go player.updateRemotePlayerMap(&wgX) + go player.updateRemotePlayerBases(bases, &wgX) + go player.updateRemotePlayerBullets(bullets, &wgX) + go player.updateRemotePlayerBulletParticles(bulletParticles, &wgX) + go player.updateRemotePlayerPlayers(players, &wgX) + playerX := player + go func() { + fail := playerX.sendInfoToPlayer(&wgX) + if fail { + if playerX.connection != nil { + (*playerX.connection).Close() + } + baseMutex.Lock() + if bases[playerX.playerID] != nil { + bases[playerX.playerID].delete(bases) + } + baseMutex.Unlock() + playersMutex.TryLock() + delete(players, playerX.playerID) + playersMutex.Unlock() + } + }() + } + playersMutex.RUnlock() + wgX.Wait() + + playersMutex.Lock() + for _, player := range players { + wgX2.Add(1) + playerX := player + go func() { + fail := playerX.sendUpdatesToPlayer(players, &wgX2) + if fail { + if playerX.connection != nil { + (*playerX.connection).Close() + } + baseMutex.Lock() + if bases[playerX.playerID] != nil { + bases[playerX.playerID].delete(bases) + } + baseMutex.Unlock() + delete(players, playerX.playerID) + } + }() + } + playersMutex.Unlock() + wgX2.Wait() + playerUpdateMutex.Unlock() + }) + } + totalTicking += profileSection(func() { + playerUpdateMutex.Lock() + bulletMutex.RLock() + for _, bullet := range bullets { + (*bullet).tick(gameMap, bulletParticles, bullets, players) + } + bulletMutex.RUnlock() + playersMutex.RLock() for _, player := range players { if player.local { continue } - go player.updateRemotePlayerMap() - go player.updateRemotePlayerBases(bases) - go player.updateRemotePlayerBullets(bullets) - go player.updateRemotePlayerBulletParticles(bulletParticles) - go player.updateRemotePlayerPlayers(players) - fail := player.sendInfoToPlayer() - if fail { - if player.connection != nil { - (*player.connection).Close() - } - delete(players, player.playerID) - continue - } - fail = player.sendUpdatesToPlayer(players) - if fail { - if player.connection != nil { - (*player.connection).Close() - } - delete(players, player.playerID) - continue - } + (*player).tick(bullets) } - } - - playerUpdateMutex.Lock() - for _, bullet := range bullets { - (*bullet).tick(gameMap, bulletParticles, bullets, players) - } - for _, player := range players { - if player.local { - continue + playersMutex.RUnlock() + baseMutex.RLock() + for _, base := range bases { + (*base).tick(players) } - (*player).tick(bullets) - } - for _, base := range bases { - (*base).tick(players) - } - for _, bulletParticle := range bulletParticles { - bulletParticle.tick(bulletParticles) - } - playerUpdateMutex.Unlock() + baseMutex.RUnlock() + bulletParticleMutex.RLock() + for _, bulletParticle := range bulletParticles { + bulletParticle.tick(bulletParticles) + } + bulletParticleMutex.RUnlock() + playerUpdateMutex.Unlock() + }) + tickCount++ } func initPlayer(playerIndex uint8, player *Player) { @@ -387,18 +469,26 @@ func doPlayerFrame(playerIndex uint8, player *Player, players map[uint32]*Player player.track(player.camera) gameMap.render(player.camera, player.playSurface) + bulletMutex.RLock() for _, bullet := range bullets { (*bullet).render(player.camera, player.playSurface, bullets) } + bulletMutex.RUnlock() + baseMutex.RLock() for _, base := range bases { (*base).render(player.camera, player.playSurface, bases) } + baseMutex.RUnlock() + playersMutex.RLock() for _, playerLoop := range players { (*playerLoop).render(player.camera, player.playSurface, players) } + playersMutex.RUnlock() + bulletParticleMutex.RLock() for _, bulletParticle := range bulletParticles { bulletParticle.render(player.camera, player.playSurface, bulletParticles) } + bulletParticleMutex.RUnlock() player.tick(bullets) player.gameObject.prevBaseRect = player.gameObject.baseRect diff --git a/player.go b/player.go index 1d24beb..dba61c1 100644 --- a/player.go +++ b/player.go @@ -16,7 +16,9 @@ import ( "sync" ) +var playerUpdateMutex sync.Mutex var lastPlayerID = uint32(0) +var playersMutex sync.RWMutex type Player struct { local bool @@ -36,6 +38,7 @@ type Player struct { rechargeCooldown uint32 movementCooldown uint32 reloadCooldown uint32 + reloadWait uint32 gameObject *GameObject playerID uint32 @@ -82,9 +85,6 @@ func (player *Player) track(camera *sdl.Rect) { func (player *Player) render(camera *sdl.Rect, surface *sdl.Surface, players map[uint32]*Player) { - // Common part: Rendering player - player.gameObject.baseRect = sdl.Rect{X: player.gameObject.baseRect.X, Y: player.gameObject.baseRect.Y, W: 7, H: 7} - if player.shields > 0 && player.shields <= serverConfig.MaxShields && player.gameObject.baseRect.X >= 0 && @@ -97,22 +97,6 @@ func (player *Player) render(camera *sdl.Rect, surface *sdl.Surface, players map } -func (player *Player) digBlock(posX, posY int32, gameMap *GameMap, isShooting bool) uint8 { - collisionAtDigSite := gameMap.checkCollision(posX, posY) - if collisionAtDigSite == 0 { - return 0 - } else if collisionAtDigSite == 1 && (player.digCooldown == 0 || isShooting) { - player.digCooldown = serverConfig.DiggingCooldown - if config.Client { - player.sendDigToServer(uint32(posX), uint32(posY), isShooting) - return 2 - } - gameMap.tiles[posX][posY] = 0 - return 1 - } - return 2 -} - func (player *Player) tick(bullets map[uint32]*Bullet) { if player.digCooldown > 0 { player.digCooldown-- @@ -131,7 +115,7 @@ func (player *Player) tick(bullets map[uint32]*Bullet) { } if player.reloadCooldown > 0 { player.reloadCooldown-- - } else if player.ammunition < serverConfig.MaxAmmunition && player.shootCooldown == 0 && player.energy > serverConfig.ReloadCost { + } else if player.ammunition < serverConfig.MaxAmmunition && player.shootCooldown == 0 && player.reloadWait == 0 && player.energy > serverConfig.ReloadCost { player.ammunition++ player.reloadCooldown = serverConfig.ReloadCooldown player.energy -= serverConfig.ReloadCost @@ -140,298 +124,126 @@ func (player *Player) tick(bullets map[uint32]*Bullet) { player.shields = serverConfig.MaxShields + 1 player.explode(bullets) } + if player.reloadWait > 0 { + player.reloadWait-- + } +} + +func (player *Player) digBlock(posX, posY int32, gameMap *GameMap, isShooting bool) uint8 { + collisionAtDigSite := gameMap.checkCollision(posX, posY) + if collisionAtDigSite == 0 { + return 0 + } else if collisionAtDigSite == 1 && (player.digCooldown == 0 || isShooting) { + if player.energy > serverConfig.DiggingCost { + if isShooting && player.energy > serverConfig.ShootDiggingCostBonus { + player.energy -= serverConfig.ShootDiggingCostBonus + } + player.digCooldown = serverConfig.DiggingCooldown + } else { + player.digCooldown = serverConfig.DiggingCooldownNoEnergy + } + if isShooting && player.ammunition < serverConfig.MaxAmmunition && rand.Intn(2) == 0 { + player.ammunition++ + } + + if config.Client { + player.sendDigToServer(uint32(posX), uint32(posY), isShooting) + } + gameMap.tiles[posX][posY] = 0 + return 1 + } + return 2 } func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players map[uint32]*Player) (moved bool) { + player.gameObject.adjustBaseRect() if player.movementCooldown > 0 { return false } + ranOutOfEnergy := (isShooting && player.energy <= serverConfig.DiggingCost+serverConfig.ShootDiggingCostBonus) || player.energy <= serverConfig.DiggingCost if ranOutOfEnergy { isShooting = false } - shouldPenalizeDigging := false + + // Define movement deltas based on orientation + movementDeltas := []struct { + dx, dy int32 + }{ + {0, -1}, {1, 0}, {0, 1}, {-1, 0}, // Up, Right, Down, Left + {1, -1}, {-1, -1}, {1, 1}, {-1, 1}, // Up-Right, Up-Left, Down-Right, Down-Left + } + + dx, dy := movementDeltas[player.gameObject.orientation].dx, movementDeltas[player.gameObject.orientation].dy + // Set collision rectangle + player.gameObject.collisionRect = &sdl.Rect{ + X: player.gameObject.baseRect.X + oNeg(dx), + Y: player.gameObject.baseRect.Y + oNeg(dy), + W: player.gameObject.baseRect.W + abs(dx), + H: player.gameObject.baseRect.H + abs(dy), + } + + // Check for player collision + playersMutex.RLock() + for _, opponent := range players { + if opponent != player && opponent.gameObject.baseRect.HasIntersection(player.gameObject.collisionRect) { + playersMutex.RUnlock() + return false + } + } + playersMutex.RUnlock() + + // Initialize flags for movement and digging status stopped := false - switch player.gameObject.orientation { - case 0: // Up - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X, - Y: player.gameObject.baseRect.Y - 1, - W: 5, - H: 7, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) && opponent.shields <= serverConfig.MaxShields { - return false - } - } - for x := player.gameObject.baseRect.X; x < player.gameObject.baseRect.X+5 && !stopped; x++ { - for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+7; y++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break + // Check collision and dig at the target position + for x := player.gameObject.baseRect.X + oNeg(dx); x < player.gameObject.baseRect.X+dx+player.gameObject.baseRect.W; x++ { + for y := player.gameObject.baseRect.Y + oNeg(dy); y < player.gameObject.baseRect.Y+dy+player.gameObject.baseRect.H; y++ { + digResult := player.digBlock(x, y, gameMap, isShooting) + if digResult == 1 { // Successfully dug a block + if !isShooting { + stopped = true // Stop movement if not shooting } + } else if digResult == 2 { // Block could not be dug (e.g., solid block) + stopped = true + } + if stopped || !(digResult == 0 || (digResult == 1 && isShooting)) { + stopped = true + break } } - if !stopped { - moved = true - player.gameObject.baseRect.Y-- - } - - case 1: // Right - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X + 1, - Y: player.gameObject.baseRect.Y, - W: 7, - H: 5, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+6 && !stopped; y++ { - for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8; x++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.X++ - } - - case 2: // Down - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X, - Y: player.gameObject.baseRect.Y + 1, - W: 5, - H: 7, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for x := player.gameObject.baseRect.X; x < player.gameObject.baseRect.X+5 && !stopped; x++ { - for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.Y++ - } - - case 3: // Left - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X - 1, - Y: player.gameObject.baseRect.Y, - W: 7, - H: 5, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+6 && !stopped; y++ { - for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6; x++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.X-- - } - - case 4: // Up-Right - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X + 1, - Y: player.gameObject.baseRect.Y - 1, - W: 7, - H: 7, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8 && !stopped; x++ { - for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+6; y++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.X++ - player.gameObject.baseRect.Y-- - } - - case 5: // Up-Left - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X - 1, - Y: player.gameObject.baseRect.Y - 1, - W: 7, - H: 7, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6 && !stopped; x++ { - for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+6; y++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.X-- - player.gameObject.baseRect.Y-- - } - - case 6: // Down-Right - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X + 1, - Y: player.gameObject.baseRect.Y + 1, - W: 7, - H: 7, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8 && !stopped; x++ { - for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.X++ - player.gameObject.baseRect.Y++ - } - - case 7: // Down-Left - collisionRect := sdl.Rect{ - X: player.gameObject.baseRect.X - 1, - Y: player.gameObject.baseRect.Y + 1, - W: 7, - H: 7, - } - for _, opponent := range players { - if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { - return false - } - } - for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6 && !stopped; x++ { - for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ { - digResult := player.digBlock(x, y, gameMap, isShooting) - if digResult == 1 { - shouldPenalizeDigging = true - if !isShooting { - stopped = true - break - } - } else if digResult != 0 { - stopped = true - break - } - } - } - if !stopped { - moved = true - player.gameObject.baseRect.X-- - player.gameObject.baseRect.Y++ + if stopped { + break } } - if config.Client { - player.sendPositionToServer() + // Move player if there was no obstruction + if !stopped { + var dxT, dyT int32 + if dx > 1 { + dxT = 1 + } else if dx < -1 { + dxT = -1 + } else { + dxT = dx + } + if dy > 1 { + dyT = 1 + } else if dy < -1 { + dyT = -1 + } else { + dyT = dy + } + player.gameObject.baseRect.X += dxT + player.gameObject.baseRect.Y += dyT + moved = true + if config.Client { + player.sendPositionToServer() + } } - // Penalties and cooldown handling - if shouldPenalizeDigging { - if isShooting && player.ammunition < serverConfig.MaxAmmunition && rand.Intn(2) == 0 { - player.ammunition++ - } - if ranOutOfEnergy { - player.digCooldown = serverConfig.DiggingCooldownNoEnergy - } - player.energy -= serverConfig.DiggingCost - if isShooting { - player.energy -= serverConfig.ShootDiggingCostBonus - } - } + // Send the updated position to the server + // Apply movement cooldown if moved { if ranOutOfEnergy { player.movementCooldown = serverConfig.MovementCooldownNoEnergy @@ -439,9 +251,26 @@ func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players map[uin player.movementCooldown = serverConfig.MovementCooldown } } + return } +func abs(dx int32) int32 { + if dx < 0 { + return -dx + } else { + return dx + } +} + +func oNeg(dx int32) int32 { + if dx < 0 { + return dx + } else { + return 0 + } +} + func (player *Player) getRGBAColor(colorIndex uint8, format *sdl.PixelFormat) uint32 { var selectedColor color.Color switch colorIndex { @@ -512,12 +341,14 @@ func (player *Player) shoot(super bool, bullets map[uint32]*Bullet) { player.energy -= serverConfig.NormalShotCost player.ammunition-- } + player.reloadWait = serverConfig.ReloadWait if config.Client { player.sendShootToServer(super) } else { // Set bullet color bulletColor := player.playerColors.body + bulletMutex.Lock() // Create and add the bullet bullets[bulletLastID] = &Bullet{ posX: shootX, @@ -526,7 +357,9 @@ func (player *Player) shoot(super bool, bullets map[uint32]*Bullet) { color: bulletColor, super: super, id: bulletLastID, + ownerID: player.playerID, } + bulletMutex.Unlock() bulletLastID++ } } @@ -539,6 +372,7 @@ func (player *Player) explode(bullets map[uint32]*Bullet) { for x := player.gameObject.baseRect.X - int32(serverConfig.BlastRadius); x < int32(serverConfig.BlastRadius)*2+1; x++ { for y := player.gameObject.baseRect.Y - int32(serverConfig.BlastRadius); y < int32(serverConfig.BlastRadius)*2+1; y++ { // Create and add the bullet + bulletMutex.Lock() bullets[bulletLastID] = &Bullet{ posX: x, posY: y, @@ -546,14 +380,58 @@ func (player *Player) explode(bullets map[uint32]*Bullet) { color: bulletColor, super: true, id: bulletLastID, + ownerID: player.playerID, } + bulletMutex.Unlock() bulletLastID++ } } } -func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players map[uint32]*Player) { +func getUnusedColor(colors []PlayerColors, players map[uint32]*Player) uint32 { + for i, c := range colors { + foundPlayerWithColor := false + var colorCompare []uint8 + clrR, clrG, clrB, clrA := c.tracks.RGBA() + colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA)) + clrR, clrG, clrB, clrA = c.body.RGBA() + colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA)) + clrR, clrG, clrB, clrA = c.cannon.RGBA() + colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA)) + playersMutex.RLock() + for _, player := range players { + var playerColorsCompare []uint8 + plrR, plrG, plrB, plrA := player.playerColors.tracks.RGBA() + playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA)) + plrR, plrG, plrB, plrA = player.playerColors.body.RGBA() + playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA)) + plrR, plrG, plrB, plrA = player.playerColors.cannon.RGBA() + playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA)) + foundPlayerWithColor = false + if len(playerColorsCompare) == len(colorCompare) { + for i, c := range colorCompare { + if playerColorsCompare[i] == c { + foundPlayerWithColor = true + } else { + foundPlayerWithColor = false + break + } + } + if foundPlayerWithColor { + break + } + } + } + playersMutex.RUnlock() + if !foundPlayerWithColor { + return uint32(i) + } + } + return uint32(len(colors) - 1) +} + +func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players map[uint32]*Player, bases map[uint32]*Base) { joyStickCount := sdl.NumJoysticks() if amount > uint8(len(keyMaps)+len(joyMaps)) || amount > uint8(len(keyMaps)+joyStickCount) { panic("Too many players, not enough inputs") @@ -577,18 +455,20 @@ func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, } createPlayer( true, - playerColors[len(players)%len(playerColors)], + playerColors[getUnusedColor(playerColors, players)], nil, keyMap, joyMap, joyStick, gameMap, players, + bases, ) } } func closeThings(players map[uint32]*Player) { + playersMutex.Lock() for _, player := range players { if player.joyStick != nil { player.joyStick.Close() @@ -606,40 +486,57 @@ func closeThings(players map[uint32]*Player) { player.HUDSurface.Free() } } + playersMutex.Unlock() } -func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players map[uint32]*Player) uint32 { +func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players map[uint32]*Player, bases map[uint32]*Base) uint32 { coordsAreValid := false - var posX, posY int32 + var posX, posY uint32 maxTries := 1000 + baseSize := uint32(36) // Since the base is 36x36 + minDistance := uint32(200) // Minimum distance between bases + maxDistance := uint32(500) // Maximum distance between bases + for !coordsAreValid && maxTries >= 0 { maxTries-- - posX = int32(16 + rand.Intn(int(gameMap.width-43))) - posY = int32(16 + rand.Intn(int(gameMap.height-43))) + posX = uint32(16 + rand.Intn(int(gameMap.width-baseSize-16))) + posY = uint32(16 + rand.Intn(int(gameMap.height-baseSize-16))) coordsAreValid = true - for _, player := range players { - distance := (player.gameObject.baseRect.X-posX)*(player.gameObject.baseRect.X-posX) + - (player.gameObject.baseRect.Y-posY)*(player.gameObject.baseRect.Y-posY) - if distance < 300*300 { // Check if distance is less than 300 units + baseMutex.RLock() + for _, base := range bases { + basePosX := uint32(base.gameObject.baseRect.X) + basePosY := uint32(base.gameObject.baseRect.Y) + + // Calculate the distance between the edges of the bases + distanceX := max(0, max(basePosX-posX-baseSize, posX-basePosX-baseSize)) + distanceY := max(0, max(basePosY-posY-baseSize, posY-basePosY-baseSize)) + + distanceSquared := distanceX*distanceX + distanceY*distanceY + + if distanceSquared < minDistance*minDistance && distanceSquared > maxDistance*maxDistance { coordsAreValid = false break } } - if posX < 16 || posX > int32(gameMap.width-36-7) || posY < 16 || posY > int32(gameMap.height-36-7) { + baseMutex.RUnlock() + + // Edge clamping to ensure the base is within the map boundaries + if posX < 16 || posX > gameMap.width-baseSize-16 || posY < 16 || posY > gameMap.height-baseSize-16 { coordsAreValid = false break } } + if maxTries < 0 { panic("Could not place all players, increase map size") } gameObject := &GameObject{} - gameObject.baseRect = sdl.Rect{ - X: posX, - Y: posY, + gameObject.baseRect = &sdl.Rect{ + X: int32(posX), + Y: int32(posY), W: 7, H: 7, } @@ -655,22 +552,22 @@ func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, gameObject.addColoredRect(2, 0, 1, 4, 2) gameObject.orientation = 1 // Right - gameObject.addColoredRect(1, 1, 6, 1, 0) - gameObject.addColoredRect(1, 5, 6, 1, 0) - gameObject.addColoredRect(2, 2, 4, 3, 1) - gameObject.addColoredRect(4, 3, 4, 1, 2) + gameObject.addColoredRect(0, 0, 6, 1, 0) + gameObject.addColoredRect(0, 4, 6, 1, 0) + gameObject.addColoredRect(1, 1, 4, 3, 1) + gameObject.addColoredRect(3, 2, 4, 1, 2) gameObject.orientation = 2 // Down - gameObject.addColoredRect(0, 1, 1, 6, 0) - gameObject.addColoredRect(4, 1, 1, 6, 0) - gameObject.addColoredRect(1, 2, 3, 4, 1) - gameObject.addColoredRect(2, 4, 1, 4, 2) + gameObject.addColoredRect(0, 0, 1, 6, 0) + gameObject.addColoredRect(4, 0, 1, 6, 0) + gameObject.addColoredRect(1, 1, 3, 4, 1) + gameObject.addColoredRect(2, 3, 1, 4, 2) gameObject.orientation = 3 // Left - gameObject.addColoredRect(1, 1, 6, 1, 0) - gameObject.addColoredRect(1, 5, 6, 1, 0) - gameObject.addColoredRect(2, 2, 4, 3, 1) - gameObject.addColoredRect(0, 3, 4, 1, 2) + gameObject.addColoredRect(1, 0, 6, 1, 0) + gameObject.addColoredRect(1, 4, 6, 1, 0) + gameObject.addColoredRect(2, 1, 4, 3, 1) + gameObject.addColoredRect(0, 2, 4, 1, 2) gameObject.orientation = 4 // Up-Right gameObject.addColoredRect(3, 0, 1, 1, 0) @@ -764,6 +661,7 @@ func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, gameObject.addColoredRect(3, 3, 1, 1, 2) gameObject.orientation = 0 + gameObject.adjustBaseRect() if !local && (keyMap.exit != keyMap.shoot || joyMap.exitButton != joyMap.shootButton) { panic("Input assigned to remote player") @@ -774,6 +672,7 @@ func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, knownGameMap.tiles[i] = make([]uint8, serverConfig.MapHeight) } + playersMutex.Lock() players[lastPlayerID] = &Player{ playerColors: thisPlayerColors, keyMap: keyMap, @@ -791,6 +690,7 @@ func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, knownBullets: make(map[uint32]*Bullet), knownGameMap: &knownGameMap, } + playersMutex.Unlock() lastPlayerID++ return lastPlayerID - 1 } @@ -834,9 +734,17 @@ func (player *Player) sendMessage(data []byte) bool { return sendMessageRaw(data, player.connection) } -func (player *Player) updateRemotePlayerMap() { - playerUpdateMutex.Lock() - defer playerUpdateMutex.Unlock() +func (player *Player) updateTile(x, y uint32) { + player.gameMapUpdates = append(player.gameMapUpdates, TileUpdate{ + PosX: x, + PosY: y, + Kind: gameMap.tiles[x][y], + }) +} + +func (player *Player) updateRemotePlayerMap(wgX *sync.WaitGroup) { + defer wgX.Done() + player.gameMapUpdates = nil fromX := player.gameObject.baseRect.X - (config.CameraW / 2) - 1 fromY := player.gameObject.baseRect.Y - (config.CameraH / 2) - 1 toX := player.gameObject.baseRect.X + (config.CameraW / 2) + 2 @@ -870,11 +778,7 @@ func (player *Player) updateRemotePlayerMap() { for y := fromY; y < toY; y++ { if player.knownGameMap.tiles[x][y] != gameMap.tiles[x][y] { lck.Lock() - player.gameMapUpdates = append(player.gameMapUpdates, TileUpdate{ - PosX: uint32(x), - PosY: uint32(y), - Kind: gameMap.tiles[x][y], - }) + player.updateTile(uint32(x), uint32(y)) player.knownGameMap.tiles[x][y] = gameMap.tiles[x][y] lck.Unlock() } @@ -886,10 +790,10 @@ func (player *Player) updateRemotePlayerMap() { wg.Wait() } -func (player *Player) updateRemotePlayerPlayers(players map[uint32]*Player) { - playerUpdateMutex.Lock() - defer playerUpdateMutex.Unlock() +func (player *Player) updateRemotePlayerPlayers(players map[uint32]*Player, wgX *sync.WaitGroup) { + defer wgX.Done() player.knownPlayers = make(map[uint32]*Player) + playersMutex.RLock() for _, playerLoop := range players { player.camera = &sdl.Rect{ X: player.gameObject.baseRect.X - (config.CameraW / 2), @@ -905,12 +809,13 @@ func (player *Player) updateRemotePlayerPlayers(players map[uint32]*Player) { } } } + playersMutex.RUnlock() } -func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet) { - playerUpdateMutex.Lock() - defer playerUpdateMutex.Unlock() +func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet, wgX *sync.WaitGroup) { + defer wgX.Done() player.knownBullets = make(map[uint32]*Bullet) + bulletMutex.RLock() for _, bullet := range bullets { bulletRect := &sdl.Rect{ X: bullet.posX, @@ -928,12 +833,13 @@ func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet) { player.knownBullets[bullet.id] = bullet } } + bulletMutex.RUnlock() } -func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint32]*BulletParticle) { - playerUpdateMutex.Lock() - defer playerUpdateMutex.Unlock() +func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint32]*BulletParticle, wgX *sync.WaitGroup) { + defer wgX.Done() player.knownBulletParticles = make(map[uint32]*BulletParticle) + bulletParticleMutex.RLock() for _, bulletParticle := range bulletParticles { bulletRect := &sdl.Rect{ X: bulletParticle.posX, @@ -951,12 +857,13 @@ func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint player.knownBulletParticles[bulletParticle.id] = bulletParticle } } + bulletParticleMutex.RUnlock() } -func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base) { - playerUpdateMutex.Lock() - defer playerUpdateMutex.Unlock() +func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base, wgX *sync.WaitGroup) { + defer wgX.Done() player.knownBases = make(map[uint32]*Base) + baseMutex.RLock() for _, base := range bases { player.camera = &sdl.Rect{ X: player.gameObject.baseRect.X - (config.CameraW / 2), @@ -971,67 +878,11 @@ func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base) { } } } + baseMutex.RUnlock() } -func (player *Player) sendVersionBackToPlayer() bool { - if player.connection != nil && config.Server { - response := tunnelerProto.ClientBound{ - ClientBoundMessage: &tunnelerProto.ClientBound_PlayerStartResponse{ - PlayerStartResponse: &tunnelerProto.PlayerStartResponse{ - Version: config.Version, - }, - }, - } - out, err := proto.Marshal(&response) - if err != nil { - panic("Error serializing version") - } - return player.sendMessage(out) - } - return false -} - -func (player *Player) sendServerInfoToPlayer(players map[uint32]*Player) bool { - if player.connection != nil && config.Server { - response := tunnelerProto.ClientBound{ - ClientBoundMessage: &tunnelerProto.ClientBound_ServerInfo{ - ServerInfo: &tunnelerProto.ServerInfo{ - MaxEnergy: serverConfig.MaxEnergy, - MaxAmmunition: serverConfig.MaxAmmunition, - MaxShields: serverConfig.MaxShields, - MapWidth: serverConfig.MapWidth, - MapHeight: serverConfig.MapHeight, - NormalShotCost: serverConfig.NormalShotCost, - SuperShotCost: serverConfig.SuperShotCost, - ReloadCost: serverConfig.ReloadCost, - MovementCost: serverConfig.MovementCost, - DiggingCost: serverConfig.DiggingCost, - ShootDiggingBonus: serverConfig.ShootDiggingCostBonus, - ShootCooldown: serverConfig.ShootCooldown, - RechargeCooldown: serverConfig.RechargeCooldownOwn, - RechargeOpponentCooldown: serverConfig.RechargeCooldownOpponent, - RepairCooldown: serverConfig.RepairCooldown, - DiggingCooldown: serverConfig.DiggingCooldown, - MovementCooldown: serverConfig.MovementCooldown, - MovementCooldownNoEnergy: serverConfig.MovementCooldownNoEnergy, - DiggingCooldownNoEnergy: serverConfig.DiggingCooldownNoEnergy, - ReloadCooldown: serverConfig.ReloadCooldown, - BlastRadius: serverConfig.BlastRadius, - PlayerID: player.playerID, - PlayerColorID: uint32((len(players) - 1) % len(playerColors)), - }, - }, - } - out, err := proto.Marshal(&response) - if err != nil { - panic("Error serializing server info") - } - return player.sendMessage(out) - } - return false -} - -func (player *Player) sendInfoToPlayer() bool { +func (player *Player) sendInfoToPlayer(wgX *sync.WaitGroup) bool { + defer wgX.Done() if player.connection != nil && config.Server { response := tunnelerProto.ClientBound{ ClientBoundMessage: &tunnelerProto.ClientBound_PlayerUpdate{ @@ -1051,33 +902,8 @@ func (player *Player) sendInfoToPlayer() bool { return false } -func (player *Player) sendLocationToPlayer() bool { - if player.connection != nil && config.Server { - response := tunnelerProto.ClientBound{ - ClientBoundMessage: &tunnelerProto.ClientBound_PlayerLocationUpdate{ - PlayerLocationUpdate: &tunnelerProto.PlayerLocation{ - Position: &tunnelerProto.Position{ - PosX: player.gameObject.baseRect.X, - PosY: player.gameObject.baseRect.Y, - }, - Orientation: uint32(player.gameObject.orientation), - }, - }, - } - out, err := proto.Marshal(&response) - if err != nil { - panic("Error serializing location") - } - return player.sendMessage(out) - } - return false -} - -var playerUpdateMutex sync.Mutex - -func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player) bool { - playerUpdateMutex.Lock() - defer playerUpdateMutex.Unlock() +func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player, wgX *sync.WaitGroup) bool { + defer wgX.Done() if player.connection != nil && config.Server { var playersToSend []*tunnelerProto.Player @@ -1103,7 +929,9 @@ func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player) bool { } for _, knownBase := range player.knownBases { + playersMutex.RLock() r, g, b, a := players[knownBase.ownerID].playerColors.body.RGBA() + playersMutex.RUnlock() basesToSend = append(basesToSend, &tunnelerProto.BaseLocation{ Position: &tunnelerProto.Position{ PosX: knownBase.gameObject.baseRect.X, @@ -1136,7 +964,8 @@ func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player) bool { Blue: bulletBlue, Alpha: bulletAlpha, }, - Id: knownBullet.id, + Id: knownBullet.id, + OwnerID: knownBullet.ownerID, }) } @@ -1188,6 +1017,87 @@ func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player) bool { return false } +func (player *Player) sendServerInfoToPlayer(players map[uint32]*Player) bool { + if player.connection != nil && config.Server { + response := tunnelerProto.ClientBound{ + ClientBoundMessage: &tunnelerProto.ClientBound_ServerInfo{ + ServerInfo: &tunnelerProto.ServerInfo{ + MaxEnergy: serverConfig.MaxEnergy, + MaxAmmunition: serverConfig.MaxAmmunition, + MaxShields: serverConfig.MaxShields, + MapWidth: serverConfig.MapWidth, + MapHeight: serverConfig.MapHeight, + NormalShotCost: serverConfig.NormalShotCost, + SuperShotCost: serverConfig.SuperShotCost, + ReloadCost: serverConfig.ReloadCost, + MovementCost: serverConfig.MovementCost, + DiggingCost: serverConfig.DiggingCost, + ShootDiggingBonus: serverConfig.ShootDiggingCostBonus, + ShootCooldown: serverConfig.ShootCooldown, + RechargeCooldown: serverConfig.RechargeCooldownOwn, + RechargeOpponentCooldown: serverConfig.RechargeCooldownOpponent, + RepairCooldown: serverConfig.RepairCooldown, + DiggingCooldown: serverConfig.DiggingCooldown, + MovementCooldown: serverConfig.MovementCooldown, + MovementCooldownNoEnergy: serverConfig.MovementCooldownNoEnergy, + DiggingCooldownNoEnergy: serverConfig.DiggingCooldownNoEnergy, + ReloadCooldown: serverConfig.ReloadCooldown, + BlastRadius: serverConfig.BlastRadius, + ReloadWait: serverConfig.ReloadWait, + PlayerID: player.playerID, + PlayerColorID: uint32((len(players) - 1) % len(playerColors)), + }, + }, + } + out, err := proto.Marshal(&response) + if err != nil { + panic("Error serializing server info") + } + return player.sendMessage(out) + } + return false +} + +func (player *Player) sendVersionBackToPlayer() bool { + if player.connection != nil && config.Server { + response := tunnelerProto.ClientBound{ + ClientBoundMessage: &tunnelerProto.ClientBound_PlayerStartResponse{ + PlayerStartResponse: &tunnelerProto.PlayerStartResponse{ + Version: GameVersion, + }, + }, + } + out, err := proto.Marshal(&response) + if err != nil { + panic("Error serializing version") + } + return player.sendMessage(out) + } + return false +} + +func (player *Player) sendLocationToPlayer() bool { + if player.connection != nil && config.Server { + response := tunnelerProto.ClientBound{ + ClientBoundMessage: &tunnelerProto.ClientBound_PlayerLocationUpdate{ + PlayerLocationUpdate: &tunnelerProto.PlayerLocation{ + Position: &tunnelerProto.Position{ + PosX: player.gameObject.baseRect.X, + PosY: player.gameObject.baseRect.Y, + }, + Orientation: uint32(player.gameObject.orientation), + }, + }, + } + out, err := proto.Marshal(&response) + if err != nil { + panic("Error serializing location") + } + return player.sendMessage(out) + } + return false +} + func (player *Player) sendPositionToServer() bool { if player.connection != nil && config.Client { response := tunnelerProto.ServerBound{ @@ -1215,7 +1125,7 @@ func sendVersionToServer(connection *net.TCPConn) { response := tunnelerProto.ServerBound{ ServerBoundMessage: &tunnelerProto.ServerBound_PlayerStartRequest{ PlayerStartRequest: &tunnelerProto.PlayerStartRequest{ - Version: config.Version, + Version: GameVersion, }, }, } @@ -1306,14 +1216,17 @@ func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[ui switch msg.GetServerBoundMessage().(type) { case *tunnelerProto.ServerBound_PlayerStartRequest: clientVersion := msg.GetPlayerStartRequest().Version - if clientVersion != config.Version { - log.Fatalf("Wrong client version, a client connected with %s to %s", clientVersion, config.Version) + if clientVersion != GameVersion { + log.Fatalf("Wrong client version, a client connected with %s to %s", clientVersion, GameVersion) return } - newPlayerID := createPlayer(false, playerColors[len(players)%len(playerColors)], conn, KeyMap{}, JoyMap{}, nil, gameMap, players) + newPlayerID := createPlayer(false, playerColors[getUnusedColor(playerColors, players)], conn, KeyMap{}, JoyMap{}, nil, gameMap, players, bases) defer delete(players, newPlayerID) + playersMutex.RLock() player := players[newPlayerID] + playersMutex.RUnlock() + baseMutex.Lock() bases[newPlayerID] = createBase(gameMap, player.playerColors.body, uint32(player.gameObject.baseRect.X-14), @@ -1321,6 +1234,7 @@ func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[ui player.playerID, uint32(float64(player.gameObject.baseRect.W)*1.5)) + baseMutex.Unlock() defer bases[newPlayerID].delete(bases) netPlayerMapper[conn] = player @@ -1334,7 +1248,9 @@ func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[ui return } log.Printf("Sent server info to %d", newPlayerID) - fail = player.sendInfoToPlayer() + var wgX sync.WaitGroup + wgX.Add(1) + fail = player.sendInfoToPlayer(&wgX) if fail { return } @@ -1355,16 +1271,56 @@ func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[ui newY := newPosition.Position.PosY oldX := player.gameObject.baseRect.X oldY := player.gameObject.baseRect.Y - if ((newX > oldX && newX-oldX <= 2) || (newX < oldX && oldX-newX <= 2) || newX == oldX) && - (newY > oldY && newY-oldY <= 2) || (newY < oldY && oldY-newY <= 2 || newY == oldY) { - player.gameObject.baseRect = sdl.Rect{ - X: newX, - Y: newY, - W: player.gameObject.baseRect.W, - H: player.gameObject.baseRect.H, - } - } + // Update player orientation player.gameObject.orientation = uint8(newPosition.Orientation) + player.gameObject.adjustBaseRect() + + // Check if the new position is adjacent to the old position + isAdjacentX := (newX > oldX && newX-oldX <= 1) || (newX < oldX && oldX-newX <= 1) || newX == oldX + isAdjacentY := (newY > oldY && newY-oldY <= 1) || (newY < oldY && oldY-newY <= 1) || newY == oldY + + // Ensure the player is not moving while on cooldown and is moving to an adjacent position + if isAdjacentX && isAdjacentY && player.movementCooldown == 0 { + // Terrain collision check: Loop over all tiles in the new bounding box area + canMove := true + for x := newX; x < newX+player.gameObject.baseRect.W; x++ { + for y := newY; y < newY+player.gameObject.baseRect.H; y++ { + if gameMap.tiles[x][y] != 0 { // Assumes 0 is passable, any other value is impassable + player.knownGameMap.tiles[x][y] = 0 + canMove = false + } + } + if !canMove { + break + } + } + + // If all tiles in the area are passable, update the player's position + if canMove { + player.gameObject.baseRect = &sdl.Rect{ + X: newX, + Y: newY, + W: player.gameObject.baseRect.W, + H: player.gameObject.baseRect.H, + } + // Deduct energy if the player has moved + if newX != oldX || newY != oldY { + if player.energy > serverConfig.MovementCost { + player.energy -= serverConfig.MovementCost + player.movementCooldown = serverConfig.MovementCooldown + } else { + player.movementCooldown = serverConfig.MovementCooldownNoEnergy + } + } + } else { + // If any part of the new area is impassable, revert to the old position + player.sendLocationToPlayer() + } + } else { + // If the movement is invalid (not adjacent or on cooldown), revert to the old position + player.sendLocationToPlayer() + } + case *tunnelerProto.ServerBound_PlayerAction: player := netPlayerMapper[conn] if player == nil { @@ -1378,7 +1334,14 @@ func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[ui case *tunnelerProto.PlayerAction_DigBlock: position := action.DigBlock.Position isShooting := action.DigBlock.IsShooting //TODO:FIX && player.ammunition < serverConfig.MaxAmmunition - player.digBlock(position.PosX, position.PosY, gameMap, isShooting) + posX := position.PosX + posY := position.PosY + res := player.digBlock(posX, posY, gameMap, isShooting) + if res == 1 { + if player.energy > serverConfig.DiggingCost { + player.energy -= serverConfig.DiggingCost + } + } case *tunnelerProto.PlayerAction_Shoot: isSuper := action.Shoot.Super @@ -1419,11 +1382,11 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases switch msg.GetClientBoundMessage().(type) { case *tunnelerProto.ClientBound_PlayerStartResponse: serverVersion := msg.GetPlayerStartResponse().Version - if serverVersion != config.Version { - log.Fatalf("Wrong server version, connected with %s to %s", config.Version, serverVersion) + if serverVersion != GameVersion { + log.Fatalf("Wrong server version, connected with %s to %s", GameVersion, serverVersion) return } else { - log.Printf("Connected to %s, running version %s with version %s\n", config.Address, serverVersion, config.Version) + log.Printf("Connected to %s, running version %s with version %s\n", config.Address, serverVersion, GameVersion) } case *tunnelerProto.ClientBound_ServerInfo: serverInfo := msg.GetServerInfo() @@ -1449,11 +1412,14 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases MovementCooldownNoEnergy: serverInfo.MovementCooldownNoEnergy, DiggingCooldownNoEnergy: serverInfo.DiggingCooldownNoEnergy, ReloadCooldown: serverInfo.ReloadCooldown, + ReloadWait: serverInfo.ReloadWait, } gameMap.createGameMap(false) lastPlayerID = serverInfo.PlayerID - createPlayer(true, playerColors[serverInfo.PlayerColorID], conn, keyMaps[0], JoyMap{}, nil, gameMap, players) + createPlayer(true, playerColors[serverInfo.PlayerColorID], conn, keyMaps[0], JoyMap{}, nil, gameMap, players, bases) + playersMutex.RLock() player = players[serverInfo.PlayerID] + playersMutex.RUnlock() log.Printf("Got server info, now initializing\n") clientInitialized = true case *tunnelerProto.ClientBound_PlayerUpdate: @@ -1470,6 +1436,7 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases player.gameObject.baseRect.X = playerLocationUpdate.Position.PosX player.gameObject.baseRect.Y = playerLocationUpdate.Position.PosY player.gameObject.orientation = uint8(playerLocationUpdate.Orientation) + player.gameObject.adjustBaseRect() } case *tunnelerProto.ClientBound_WorldUpdate: @@ -1478,23 +1445,31 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases worldLock.Lock() for _, playerLoop := range worldUpdate.Players { playerID := playerLoop.PlayerID + playersMutex.RLock() existingPlayer := players[playerID] + playersMutex.RUnlock() if existingPlayer == nil { lastPlayerID = playerID - createPlayer(false, playerColors[playerID%uint32(len(playerColors))], nil, KeyMap{}, JoyMap{}, nil, gameMap, players) + createPlayer(false, playerColors[playerID%uint32(len(playerColors))], nil, KeyMap{}, JoyMap{}, nil, gameMap, players, bases) + playersMutex.RLock() newPlayer := players[playerID] + playersMutex.RUnlock() newPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation) + newPlayer.gameObject.adjustBaseRect() newPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX newPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY } else { existingPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation) + existingPlayer.gameObject.adjustBaseRect() existingPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX existingPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY } } for _, base := range worldUpdate.Base { baseID := base.Owner.PlayerID + baseMutex.RLock() existingBase := bases[baseID] + baseMutex.RUnlock() inColor := color.RGBA{ R: uint8(base.Color.Red), G: uint8(base.Color.Green), @@ -1502,6 +1477,7 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases A: uint8(base.Color.Alpha), } if existingBase == nil { + baseMutex.Lock() bases[baseID] = createBase(gameMap, inColor, uint32(base.Position.PosX), @@ -1509,6 +1485,7 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases base.Owner.PlayerID, uint32(int32(float64(player.gameObject.baseRect.W)*1.5)), ) + baseMutex.Unlock() } else { existingBase.gameObject.baseRect.X = base.Position.PosX existingBase.gameObject.baseRect.Y = base.Position.PosY @@ -1525,6 +1502,7 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases } existingBullet := bullets[bulletID] if existingBullet == nil { + bulletMutex.Lock() bullets[bulletID] = &Bullet{ posX: bullet.Position.PosX, posY: bullet.Position.PosY, @@ -1532,7 +1510,9 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases color: inColor, super: bullet.Super, id: bulletID, + ownerID: bullet.OwnerID, } + bulletMutex.Unlock() } else { existingBullet.direction = uint8(bullet.Direction) existingBullet.color = inColor @@ -1549,8 +1529,11 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases B: uint8(bulletParticle.Color.Blue), A: uint8(bulletParticle.Color.Alpha), } + bulletParticleMutex.RLock() existingBulletParticle := bulletParticles[bulletParticleID] + bulletParticleMutex.RUnlock() if existingBulletParticle == nil { + bulletParticleMutex.Lock() bulletParticles[bulletParticleID] = &BulletParticle{ posX: bulletParticle.Position.PosX, posY: bulletParticle.Position.PosY, @@ -1558,6 +1541,7 @@ func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases color: inColor, id: bulletParticleID, } + bulletParticleMutex.Unlock() } else { existingBulletParticle.color = inColor existingBulletParticle.posX = bulletParticle.Position.PosX diff --git a/profiler.go b/profiler.go index ea3f35f..4fcd2e0 100644 --- a/profiler.go +++ b/profiler.go @@ -18,14 +18,20 @@ func logProfilingInfo(handleEventsTime, renderTime, scaleTime, frameTime, frameC fmt.Printf("Average render time: %f ms\n", float64(renderTime)/floatFrame) fmt.Printf("Average scaling time: %f ms\n", float64(scaleTime)/floatFrame) fmt.Printf("Average frame time: %f ms\n", float64(frameTime)/floatFrame) + fmt.Print("\n") } -func logMapProfilingInfo(renderTime, scaleTime, frameTime, frameCount uint64) { +func logMapProfilingInfo(renderTime, scaleTime, frameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate, tickCount, frameCount uint64) { floatFrame := float64(frameCount) + floatTick := float64(tickCount) fmt.Printf("Average map render time: %f ms\n", float64(renderTime)/floatFrame) fmt.Printf("Average map scaling time: %f ms\n", float64(scaleTime)/floatFrame) fmt.Printf("Average full render time: %f ms\n", float64(frameTime)/floatFrame) + fmt.Printf("Average total gamelogic catching up time: %f ms\n", float64(totalGameLogicCatchUp)/floatFrame) + fmt.Printf("Average normal gamelogic time: %f ms\n", float64(totalNormalGameLogic)/floatFrame) + fmt.Printf("Average ticking time: %f ms\n", float64(totalTicking)/floatTick) + fmt.Printf("Average remote player update time: %f ms\n", float64(totalRemotePlayerUpdate)/floatTick) fmt.Print("\n") } @@ -37,11 +43,16 @@ func resetProfilingCounters(handleEventsTime, renderTime, scaleTime, frameTime * *frameCount = 0 } -func resetMapProfilingCounters(renderTime, scaleTime, frameTime *uint64, frameCount *uint64) { +func resetMapProfilingCounters(renderTime, scaleTime, frameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate *uint64, frameCount, tickCount *uint64) { *renderTime = 0 *scaleTime = 0 *frameTime = 0 *frameCount = 0 + *totalGameLogicCatchUp = 0 + *totalNormalGameLogic = 0 + *totalTicking = 0 + *totalRemotePlayerUpdate = 0 + *tickCount = 0 } func enforceFrameRate(frameStart uint64, targetFPS int) { diff --git a/tuneller.proto b/tuneller.proto index 0a909af..b578b80 100644 --- a/tuneller.proto +++ b/tuneller.proto @@ -23,6 +23,7 @@ message Bullet { Color color = 3; uint32 id = 4; bool super = 5; + uint32 ownerID = 6; } message Color { @@ -63,6 +64,7 @@ message ServerInfo { uint32 blastRadius = 21; uint32 playerID = 22; uint32 playerColorID = 23; + uint32 reloadWait = 24; } message PlayerUpdate {