GOingTunneling/main.go

491 lines
15 KiB
Go
Raw Normal View History

2024-08-29 00:05:28 +02:00
package main
import (
2024-08-30 19:07:56 +02:00
"encoding/json"
2024-09-01 20:13:53 +02:00
"errors"
2024-08-30 19:07:56 +02:00
"fmt"
2024-08-29 00:05:28 +02:00
"github.com/veandco/go-sdl2/sdl"
2024-08-30 19:07:56 +02:00
"log"
"net"
"os"
"sync"
"time"
2024-08-29 00:05:28 +02:00
)
2024-09-01 20:13:53 +02:00
const GameVersion = "0.0.1"
2024-08-31 15:35:19 +02:00
2024-08-30 19:07:56 +02:00
type ServerConfig struct {
2024-08-30 20:46:13 +02:00
MapWidth uint32 `json:"map_width"`
MapHeight uint32 `json:"map_height"`
2024-09-01 20:13:53 +02:00
BlastRadius uint8 `json:"blast_radius"`
2024-08-30 20:46:13 +02:00
MaxEnergy uint32 `json:"max_energy"`
MaxAmmunition uint32 `json:"max_ammunition"`
MaxShields uint32 `json:"max_shields"`
NormalShotCost uint32 `json:"normal_shot_cost"`
SuperShotCost uint32 `json:"super_shot_cost"`
ReloadCost uint32 `json:"reload_cost"`
MovementCost uint32 `json:"movement_cost"`
DiggingCost uint32 `json:"digging_cost"`
ShootDiggingCostBonus uint32 `json:"shoot_digging_cost_bonus"`
ShootCooldown uint32 `json:"shoot_cooldown"`
RechargeCooldownOwn uint32 `json:"recharge_cooldown_own"`
DiggingCooldown uint32 `json:"digging_cooldown"`
RechargeCooldownOpponent uint32 `json:"recharge_cooldown_opponent"`
RepairCooldown uint32 `json:"repair_cooldown"`
MovementCooldown uint32 `json:"movement_cooldown"`
MovementCooldownNoEnergy uint32 `json:"movement_cooldown_no_energy"`
DiggingCooldownNoEnergy uint32 `json:"digging_cooldown_no_energy"`
ReloadCooldown uint32 `json:"reload_cooldown"`
2024-08-31 15:35:19 +02:00
ReloadWait uint32 `json:"reload_wait"`
2024-08-30 19:07:56 +02:00
}
type Config struct {
2024-08-31 15:35:19 +02:00
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"`
KeyBindOffset uint16 `json:"key_bind_offset"`
2024-08-31 15:35:19 +02:00
ServerConfig ServerConfig `json:"server_config"`
2024-08-30 19:07:56 +02:00
}
func loadOrCreateConfig(filename string) (*Config, error) {
config := &Config{
2024-08-31 15:35:19 +02:00
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,
KeyBindOffset: 0,
2024-08-30 20:46:13 +02:00
ServerConfig: ServerConfig{
MapWidth: 1000,
MapHeight: 1000,
BlastRadius: 5,
MaxEnergy: 3520,
MaxAmmunition: 6,
MaxShields: 100,
NormalShotCost: 7,
SuperShotCost: 80,
ReloadCost: 4,
MovementCost: 1,
DiggingCost: 3,
ShootDiggingCostBonus: 1,
ShootCooldown: 8,
RechargeCooldownOwn: 0,
DiggingCooldown: 4,
RechargeCooldownOpponent: 6,
RepairCooldown: 4,
MovementCooldown: 2,
MovementCooldownNoEnergy: 4,
DiggingCooldownNoEnergy: 8,
ReloadCooldown: 16,
2024-08-31 15:35:19 +02:00
ReloadWait: 16,
2024-08-30 20:46:13 +02:00
},
2024-08-30 19:07:56 +02:00
}
// Check if the file exists
if _, err := os.Stat(filename); os.IsNotExist(err) {
// File does not exist, create it with the default config
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal config: %v", err)
}
2024-09-01 20:13:53 +02:00
err = os.WriteFile(filename, data, 0644)
2024-08-30 19:07:56 +02:00
if err != nil {
return nil, fmt.Errorf("failed to write config file: %v", err)
}
fmt.Println("Config file created with default values.")
} else {
// File exists, load the config
2024-09-01 20:13:53 +02:00
data, err := os.ReadFile(filename)
2024-08-30 19:07:56 +02:00
if err != nil {
return nil, fmt.Errorf("failed to read config file: %v", err)
}
err = json.Unmarshal(data, config)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %v", err)
}
fmt.Println("Config file loaded.")
}
return config, nil
}
var config *Config
var gameMap = &GameMap{}
2024-08-30 20:46:13 +02:00
var serverConfig ServerConfig
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
var mapWindow *sdl.Window
var mapSurface *sdl.Surface
2024-08-30 20:46:13 +02:00
var mapRendererRect *sdl.Rect
2024-08-30 19:07:56 +02:00
2024-08-30 21:20:41 +02:00
var netPlayerMapper = map[*net.TCPConn]*Player{}
2024-08-30 19:07:56 +02:00
var clientInitialized = false
var worldLock sync.Mutex
2024-08-29 00:05:28 +02:00
2024-08-31 15:35:19 +02:00
var totalRenderTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalScalingTime, totalRemotePlayerUpdate, tickCount, totalFrameTime, frameCount uint64
2024-08-29 18:48:27 +02:00
func main() {
2024-08-30 19:07:56 +02:00
configX, err := loadOrCreateConfig("config.json")
if err != nil {
log.Fatal(err)
}
2024-08-30 20:46:13 +02:00
serverConfig = configX.ServerConfig
2024-08-30 19:07:56 +02:00
config = configX
2024-08-31 22:48:36 +02:00
if !(config.Server && !config.DoKeymapPlayer && !config.DoJoyStickPlayers && !config.DoAllKeymapsPlayers && !config.MapWindow) {
initializeSDL()
defer sdl.Quit()
}
2024-08-30 20:46:13 +02:00
mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)}
2024-08-30 19:07:56 +02:00
players := make(map[uint32]*Player)
2024-08-29 18:48:27 +02:00
initPlayerColors()
2024-08-30 19:07:56 +02:00
bullets := make(map[uint32]*Bullet)
bulletParticles := make(map[uint32]*BulletParticle)
bases := make(map[uint32]*Base)
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
if config.Client && config.Server {
panic("You can' t run client and server in the same instance")
}
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
if !config.Client {
gameMap.createGameMap(true)
2024-08-31 15:35:19 +02:00
createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players, bases)
2024-08-30 19:07:56 +02:00
bases = createBases(players, gameMap)
} else {
// Create a connection to the server
2024-08-30 21:20:41 +02:00
addr, err := net.ResolveTCPAddr("tcp", config.Address)
if err != nil {
log.Fatal("Error resolving address " + config.Address + ": " + err.Error())
}
conn, err := net.DialTCP("tcp", nil, addr)
2024-08-30 19:07:56 +02:00
if err != nil {
log.Fatalf("Failed to connect to server: %v", err)
}
2024-09-01 20:13:53 +02:00
defer func(conn *net.TCPConn) {
_ = conn.Close()
}(conn)
2024-08-29 00:05:28 +02:00
2024-08-30 21:20:41 +02:00
go handleConnectionClient(conn, players, bases, bullets, bulletParticles)
2024-08-30 19:07:56 +02:00
for !clientInitialized {
time.Sleep(100 * time.Millisecond)
}
}
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
defer closeThings(players)
2024-08-31 15:35:19 +02:00
playersMutex.Lock()
2024-08-30 19:07:56 +02:00
for playerIndex, player := range players {
2024-08-29 18:48:27 +02:00
initPlayer(uint8(playerIndex), player)
}
2024-08-31 15:35:19 +02:00
playersMutex.Unlock()
2024-08-30 19:07:56 +02:00
if config.Server {
2024-08-30 21:20:41 +02:00
// Create a connection to the server
addr, err := net.ResolveTCPAddr("tcp", config.Address)
if err != nil {
log.Fatal("Error resolving address " + config.Address + ": " + err.Error())
}
listen, err := net.ListenTCP("tcp", addr)
2024-08-30 19:07:56 +02:00
if err != nil {
log.Fatal(err)
}
// close listener
2024-09-01 20:13:53 +02:00
defer func(listen *net.TCPListener) {
_ = listen.Close()
}(listen)
2024-08-30 19:07:56 +02:00
go func() {
for {
2024-08-30 21:20:41 +02:00
conn, err := listen.AcceptTCP()
2024-08-30 19:07:56 +02:00
if err != nil {
2024-09-01 20:13:53 +02:00
var opErr *net.OpError
if errors.As(err, &opErr) && opErr.Err.Error() == "use of closed network connection" {
2024-08-31 15:35:19 +02:00
log.Println("Listener closed, stopping server.")
return
}
log.Println("Error accepting connection:", err)
continue
2024-08-30 19:07:56 +02:00
}
go handleRequest(conn, players, bullets, bases)
}
}()
}
if config.MapWindow {
2024-08-29 18:48:27 +02:00
mapWindow, mapSurface = setupMapWindowAndSurface()
}
2024-08-29 00:05:28 +02:00
running := true
2024-08-29 21:12:18 +02:00
// Delta time management
var prevTime = sdl.GetTicks64()
const maxDeltaTime uint64 = 1000 / 60 // max 60 FPS, ~16ms per frame
2024-08-29 00:05:28 +02:00
for running {
2024-08-29 21:12:18 +02:00
currentTime := sdl.GetTicks64()
deltaTime := currentTime - prevTime
prevTime = currentTime
2024-08-30 19:07:56 +02:00
worldLock.Lock()
2024-08-29 21:12:18 +02:00
2024-08-31 15:35:19 +02:00
totalGameLogicCatchUp += profileSection(func() {
// Catch up in case of a large delta time
for deltaTime > maxDeltaTime {
deltaTime -= maxDeltaTime
2024-08-29 21:12:18 +02:00
2024-08-31 15:35:19 +02:00
// 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
2024-08-29 21:12:18 +02:00
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
2024-08-31 15:35:19 +02:00
})
2024-08-30 19:07:56 +02:00
2024-08-31 15:35:19 +02:00
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
for playerIndex, player := range players {
2024-08-29 18:48:27 +02:00
if player.local {
2024-08-30 19:07:56 +02:00
running = doPlayerFrame(uint8(playerIndex), player, players, gameMap, bases, bullets, bulletParticles)
2024-08-29 18:48:27 +02:00
if !running {
break
}
}
}
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
2024-08-29 21:12:18 +02:00
// Render logic
2024-08-31 15:35:19 +02:00
if config.MapWindow && frameCount%uint64(config.MapUpdateInterval) == 0 {
2024-08-29 21:12:18 +02:00
// Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() {
2024-08-30 20:46:13 +02:00
running = running && handleEvents(mapWindow, mapSurface)
2024-08-29 21:12:18 +02:00
gameMap.render(mapRendererRect, mapSurface)
2024-08-31 15:35:19 +02:00
bulletMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, bullet := range bullets {
(*bullet).render(mapRendererRect, mapSurface, bullets)
2024-08-29 21:12:18 +02:00
}
2024-08-31 15:35:19 +02:00
bulletMutex.RUnlock()
baseMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, base := range bases {
(*base).render(mapRendererRect, mapSurface, bases)
2024-08-29 21:12:18 +02:00
}
2024-08-31 15:35:19 +02:00
baseMutex.RUnlock()
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, playerLoop := range players {
(*playerLoop).render(mapRendererRect, mapSurface, players)
2024-08-29 21:12:18 +02:00
}
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
bulletParticleMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, bulletParticle := range bulletParticles {
bulletParticle.render(mapRendererRect, mapSurface, bulletParticles)
2024-08-29 21:12:18 +02:00
}
2024-08-31 15:35:19 +02:00
bulletParticleMutex.RUnlock()
2024-08-29 21:12:18 +02:00
})
totalScalingTime += profileSection(func() {
adjustWindow(mapWindow, mapSurface)
})
2024-08-29 18:48:27 +02:00
}
2024-08-29 21:12:18 +02:00
frameEnd := sdl.GetTicks64()
totalFrameTime += frameEnd - currentTime
frameCount++
// Log profiling information every 1000 frames
2024-08-30 19:07:56 +02:00
if frameCount%config.ProfilerInterval == 0 && config.ProfilerOn {
2024-08-31 15:35:19 +02:00
logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate, frameCount, tickCount)
resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &totalGameLogicCatchUp, &totalNormalGameLogic, &totalTicking, &totalRemotePlayerUpdate, &frameCount, &tickCount)
2024-08-29 18:48:27 +02:00
}
2024-08-30 19:07:56 +02:00
worldLock.Unlock()
2024-08-29 21:12:18 +02:00
enforceFrameRate(currentTime, 60)
}
}
// Separate function to handle game logic
2024-08-30 19:07:56 +02:00
func runGameLogic(players map[uint32]*Player, gameMap *GameMap, bases map[uint32]*Base, bullets map[uint32]*Bullet, bulletParticles map[uint32]*BulletParticle) {
2024-08-29 21:12:18 +02:00
// Tick world
2024-08-30 19:07:56 +02:00
if config.Server {
//update remote player maps
2024-08-31 15:35:19 +02:00
totalRemotePlayerUpdate += profileSection(func() {
var wgX sync.WaitGroup
playerUpdateMutex.Lock()
playersMutex.RLock()
for _, player := range players {
2024-09-01 20:13:53 +02:00
if player.local || !player.initialized {
2024-08-31 15:35:19 +02:00
continue
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
wgX.Add(6)
2024-09-01 20:13:53 +02:00
go player.updateRemotePlayerBases(bases, players, &wgX)
2024-08-31 15:35:19 +02:00
go player.updateRemotePlayerMap(&wgX)
go player.updateRemotePlayerBullets(bullets, &wgX)
go player.updateRemotePlayerBulletParticles(bulletParticles, &wgX)
go player.updateRemotePlayerPlayers(players, &wgX)
playerX := player
go func() {
2024-09-01 20:13:53 +02:00
success := playerX.sendPlayerUpdate(&wgX)
if !success {
2024-08-31 15:35:19 +02:00
if playerX.connection != nil {
2024-09-01 20:13:53 +02:00
_ = (*playerX.connection).Close()
2024-08-31 15:35:19 +02:00
}
baseMutex.Lock()
if bases[playerX.playerID] != nil {
bases[playerX.playerID].delete(bases)
}
baseMutex.Unlock()
playersMutex.TryLock()
delete(players, playerX.playerID)
playersMutex.Unlock()
}
}()
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
wgX.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 {
2024-08-30 19:07:56 +02:00
continue
}
2024-08-31 15:35:19 +02:00
(*player).tick(bullets)
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
baseMutex.RLock()
for _, base := range bases {
(*base).tick(players)
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
baseMutex.RUnlock()
bulletParticleMutex.RLock()
for _, bulletParticle := range bulletParticles {
bulletParticle.tick(bulletParticles)
}
bulletParticleMutex.RUnlock()
playerUpdateMutex.Unlock()
})
tickCount++
2024-08-29 18:48:27 +02:00
}
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
func initPlayer(playerIndex uint8, player *Player) {
2024-08-30 19:07:56 +02:00
if !player.local {
return
}
2024-08-29 18:48:27 +02:00
player.window, player.logicalSurface = setupWindowAndSurface(playerIndex)
logicalColor := sdl.MapRGBA(player.logicalSurface.Format, 101, 101, 0, 255)
2024-09-01 20:13:53 +02:00
_ = player.logicalSurface.FillRect(nil, logicalColor)
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
player.playSurface, player.playSurfaceRect, player.playSurfaceTargetRect = setupPlaySurface()
playColor := sdl.MapRGBA(player.playSurface.Format, 101, 0, 101, 255)
2024-09-01 20:13:53 +02:00
_ = player.playSurface.FillRect(nil, playColor)
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
player.HUDSurface, player.HUDSurfaceRect, player.HUDSurfaceTargetRect = setupHUDSurface()
initHud(player.HUDSurface)
2024-08-30 19:07:56 +02:00
player.camera = &sdl.Rect{X: 0, Y: 0, W: config.CameraW, H: config.CameraH}
2024-08-29 00:05:28 +02:00
}
2024-08-30 19:07:56 +02:00
func doPlayerFrame(playerIndex uint8, player *Player, players map[uint32]*Player, gameMap *GameMap, bases map[uint32]*Base, bullets map[uint32]*Bullet, bulletParticles map[uint32]*BulletParticle) bool {
2024-08-29 18:48:27 +02:00
running := true
var (
2024-08-30 19:07:56 +02:00
shouldContinue bool
2024-08-29 18:48:27 +02:00
)
frameStart := sdl.GetTicks64()
// Profile handleEvents
2024-08-29 21:12:18 +02:00
player.totalHandleEventsTime += profileSection(func() {
2024-08-29 18:48:27 +02:00
running = handleEvents(player.window, player.logicalSurface)
keyboard := sdl.GetKeyboardState()
2024-08-30 19:07:56 +02:00
shouldContinue = handleInput(keyboard, bullets, player, gameMap, players)
2024-08-29 18:48:27 +02:00
running = running && shouldContinue
})
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
// Profile rendering (aggregate all renderings)
2024-08-29 21:12:18 +02:00
player.totalRenderTime += profileSection(func() {
2024-08-29 18:48:27 +02:00
player.track(player.camera)
gameMap.render(player.camera, player.playSurface)
2024-08-29 00:05:28 +02:00
2024-08-31 15:35:19 +02:00
bulletMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, bullet := range bullets {
(*bullet).render(player.camera, player.playSurface, bullets)
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
bulletMutex.RUnlock()
baseMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, base := range bases {
(*base).render(player.camera, player.playSurface, bases)
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
baseMutex.RUnlock()
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, playerLoop := range players {
(*playerLoop).render(player.camera, player.playSurface, players)
2024-08-29 18:48:27 +02:00
}
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
bulletParticleMutex.RLock()
2024-08-30 19:07:56 +02:00
for _, bulletParticle := range bulletParticles {
bulletParticle.render(player.camera, player.playSurface, bulletParticles)
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
bulletParticleMutex.RUnlock()
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
player.tick(bullets)
player.gameObject.prevBaseRect = player.gameObject.baseRect
2024-08-29 18:48:27 +02:00
renderHud(player, player.HUDSurface)
2024-09-01 20:13:53 +02:00
_ = player.playSurface.BlitScaled(player.playSurfaceRect, player.logicalSurface, player.playSurfaceTargetRect)
_ = player.HUDSurface.BlitScaled(player.HUDSurfaceRect, player.logicalSurface, player.HUDSurfaceTargetRect)
2024-08-29 18:48:27 +02:00
})
// Profile window adjustments
2024-08-29 21:12:18 +02:00
player.totalScalingTime += profileSection(func() {
2024-08-29 18:48:27 +02:00
adjustWindow(player.window, player.logicalSurface)
})
frameEnd := sdl.GetTicks64()
2024-08-29 21:12:18 +02:00
player.totalFrameTime += frameEnd - frameStart
player.frameCount++
2024-08-29 18:48:27 +02:00
// Log profiling information every 1000 frames
2024-08-30 19:07:56 +02:00
if player.frameCount%config.ProfilerInterval == 0 && config.ProfilerOn && playerIndex == 0 {
2024-08-29 21:12:18 +02:00
logProfilingInfo(player.totalHandleEventsTime, player.totalRenderTime, player.totalScalingTime, player.totalFrameTime, player.frameCount)
resetProfilingCounters(&player.totalHandleEventsTime, &player.totalRenderTime, &player.totalScalingTime, &player.totalFrameTime, &player.frameCount)
2024-08-29 18:48:27 +02:00
}
return running
2024-08-29 00:05:28 +02:00
}