Add networking

This commit is contained in:
Bruno Rybársky 2024-08-30 19:07:56 +02:00
parent 842c41fdca
commit dc854d411f
13 changed files with 1529 additions and 296 deletions

3
.gitignore vendored Normal file

@ -0,0 +1,3 @@
proto/tuneller.pb.go
proto/*
config.json

116
base.go

@ -1,38 +1,44 @@
package main
import "github.com/veandco/go-sdl2/sdl"
import (
"github.com/veandco/go-sdl2/sdl"
"image/color"
)
type Base struct {
owner *Player
ownerID uint32
openingWidth int32
gameObject *GameObject
}
func (base *Base) tick(players *[]*Player) {
for _, player := range *players {
func (base *Base) tick(players map[uint32]*Player) {
for playerID, player := range players {
if player.gameObject.baseRect.HasIntersection(&base.gameObject.baseRect) {
if player.rechargeCooldown == 0 && player.energy < MaxEnergy {
if MaxEnergy-player.energy < 4 {
if player.rechargeCooldown == 0 && player.energy < serverConfig.MaxEnergy {
if serverConfig.MaxEnergy-player.energy < 4 {
player.energy++
} else {
player.energy += 4
}
if player == base.owner {
player.rechargeCooldown = RechargeCooldownOwn
if playerID == base.ownerID {
player.rechargeCooldown = serverConfig.RechargeCooldownOwn
} else {
player.rechargeCooldown = RechargeCooldownOpponent
player.rechargeCooldown = serverConfig.RechargeCooldownOpponent
}
}
if player == base.owner && player.repairCooldown == 0 && player.shields < MaxShields {
if playerID == base.ownerID && player.repairCooldown == 0 && player.shields < serverConfig.MaxShields {
player.shields++
player.repairCooldown = RepairCooldown
player.repairCooldown = serverConfig.RepairCooldown
}
}
}
}
func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface) {
func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface, bases map[uint32]*Base) {
base.gameObject.render(camera, surface)
if !base.gameObject.inView && !config.Server {
delete(bases, base.ownerID)
}
}
func (base *Base) build(gameMap *GameMap) {
@ -44,10 +50,10 @@ func (base *Base) build(gameMap *GameMap) {
if base.gameObject.baseRect.H < 9 {
panic("Bad border height")
}
if gameMap.width-base.gameObject.baseRect.X-base.gameObject.baseRect.W <= 0 {
if gameMap.width-uint32(base.gameObject.baseRect.X)-uint32(base.gameObject.baseRect.W) <= 0 {
panic("Bad base x location")
}
if gameMap.height-base.gameObject.baseRect.Y-base.gameObject.baseRect.H <= 0 {
if gameMap.height-uint32(base.gameObject.baseRect.Y)-uint32(base.gameObject.baseRect.H) <= 0 {
panic("Bad base y location")
}
if base.gameObject.baseRect.X < 0 || base.gameObject.baseRect.Y < 0 {
@ -72,41 +78,59 @@ func (base *Base) build(gameMap *GameMap) {
}
}
func createBases(players *[]*Player, gameMap *GameMap) *[]*Base {
bases := &[]*Base{}
func createBase(gameMap *GameMap, baseColor color.Color, posX, posY, ownerID, openingWidth uint32) *Base {
gameObject := &GameObject{}
gameObject.baseRect = sdl.Rect{
X: int32(posX),
Y: int32(posY),
W: 35,
H: 35,
}
if openingWidth%2 == 0 {
openingWidth++
}
for ownerID, player := range *players {
gameObject := &GameObject{}
gameObject.baseRect = sdl.Rect{
X: player.gameObject.baseRect.X - 14,
Y: player.gameObject.baseRect.Y - 14,
W: 35,
H: 35,
}
openingWidth := int32(float64(player.gameObject.baseRect.W) * 1.5)
if openingWidth%2 == 0 {
openingWidth++
}
borderWidth := gameObject.baseRect.W - int32(openingWidth)
borderWidthA := borderWidth / 2
borderWidthB := borderWidth - borderWidthA + 1
if borderWidth < 0 {
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, 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)
base := &Base{
gameObject: gameObject,
ownerID: ownerID,
openingWidth: int32(openingWidth),
}
base.build(gameMap)
return base
}
borderWidth := gameObject.baseRect.W - openingWidth
borderWidthA := borderWidth / 2
borderWidthB := borderWidth - borderWidthA + 1
if borderWidth < 0 {
panic("Bad border width")
func (base *Base) delete(bases map[uint32]*Base) {
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++ {
gameMap.tiles[x][y] = 0
}
gameObject.addColor((*player).playerColors.body)
gameObject.addColoredRect(0, 0, 1, gameObject.baseRect.H, 0)
gameObject.addColoredRect(gameObject.baseRect.W, 0, 1, gameObject.baseRect.H, 0)
gameObject.addColoredRect(0, 0, borderWidthA, 1, 0)
gameObject.addColoredRect(borderWidthA+openingWidth, 0, borderWidthB, 1, 0)
gameObject.addColoredRect(0, gameObject.baseRect.H, borderWidthA, 1, 0)
gameObject.addColoredRect(borderWidthA+openingWidth, gameObject.baseRect.H, borderWidthB, 1, 0)
*bases = append(*bases, &Base{
gameObject: gameObject,
owner: (*players)[ownerID],
openingWidth: openingWidth,
})
(*bases)[ownerID].build(gameMap)
}
delete(bases, base.ownerID)
}
func createBases(players map[uint32]*Player, gameMap *GameMap) map[uint32]*Base {
bases := map[uint32]*Base{}
for ownerID, player := range players {
bases[ownerID] = createBase(gameMap,
player.playerColors.body,
uint32(player.gameObject.baseRect.X-14),
uint32(player.gameObject.baseRect.Y-14),
ownerID,
uint32(float64(player.gameObject.baseRect.W)*1.5),
)
}
return bases
}

@ -6,47 +6,53 @@ import (
"math/rand"
)
var bulletLastID = uint32(0)
var bulletParticleLastID = uint32(0)
type Bullet struct {
posX, posY int32
direction uint8
color color.Color
super bool
id uint32
}
type BulletParticle struct {
posX, posY int32
expirationTimer uint8
expirationTimer uint32
color color.Color
id uint32
}
func (bulletParticle *BulletParticle) render(camera *sdl.Rect, surface *sdl.Surface) {
func (bulletParticle *BulletParticle) render(camera *sdl.Rect, surface *sdl.Surface, bulletParticles map[uint32]*BulletParticle) {
if camera.IntersectLine(&bulletParticle.posX, &bulletParticle.posY, &bulletParticle.posX, &bulletParticle.posY) {
relativeBulletX := bulletParticle.posX - camera.X
relativeBulletY := bulletParticle.posY - camera.Y
surface.Set(int(relativeBulletX), int(relativeBulletY), bulletParticle.color)
} else if !config.Server {
delete(bulletParticles, bulletParticle.id)
}
}
func (bulletParticle *BulletParticle) tick(particles *[]*BulletParticle) {
func (bulletParticle *BulletParticle) tick(particles map[uint32]*BulletParticle) {
if bulletParticle.expirationTimer <= 0 {
for i, particle := range *particles {
if particle == bulletParticle {
*particles = append((*particles)[:i], (*particles)[i+1:]...)
}
}
delete(particles, bulletParticle.id)
} else {
bulletParticle.expirationTimer--
}
bulletParticle.expirationTimer--
}
func (bullet *Bullet) render(camera *sdl.Rect, surface *sdl.Surface) {
func (bullet *Bullet) render(camera *sdl.Rect, surface *sdl.Surface, bullets map[uint32]*Bullet) {
if camera.IntersectLine(&bullet.posX, &bullet.posY, &bullet.posX, &bullet.posY) {
relativeBulletX := bullet.posX - camera.X
relativeBulletY := bullet.posY - camera.Y
surface.Set(int(relativeBulletX), int(relativeBulletY), bullet.color)
} else if !config.Server {
delete(bullets, bullet.id)
}
}
func (bullet *Bullet) explode(gameMap *GameMap, bulletParticleMap *[]*BulletParticle) {
func (bullet *Bullet) explode(gameMap *GameMap, bulletParticleMap map[uint32]*BulletParticle) {
// Define the possible directions with their x and y velocity components.
directions := [][2]int{
{0, -1}, // Up
@ -75,13 +81,15 @@ func (bullet *Bullet) explode(gameMap *GameMap, bulletParticleMap *[]*BulletPart
// Check for collision and bullet behavior (normal or super).
if collision == 1 || collision == 0 || (bullet.super && collision == 2) {
*bulletParticleMap = append(*bulletParticleMap, &BulletParticle{
bulletParticleMap[bulletParticleLastID] = &BulletParticle{
posX: xPos,
posY: yPos,
expirationTimer: uint8(5 + rand.Intn(10)), // Randomize expiration time between 15 and 25.
expirationTimer: uint32(5 + rand.Intn(10)), // Randomize expiration time between 15 and 25.
color: bullet.color,
})
if xPos > 0 && yPos > 0 && xPos < MapWidth && yPos < MapHeight {
id: bulletParticleLastID,
}
bulletParticleLastID++
if xPos > 0 && yPos > 0 && uint32(xPos) < serverConfig.MapWidth && uint32(yPos) < serverConfig.MapHeight {
gameMap.tiles[xPos][yPos] = 0
}
}
@ -93,12 +101,14 @@ func (bullet *Bullet) explode(gameMap *GameMap, bulletParticleMap *[]*BulletPart
xPos, yPos := bullet.posX+int32(xOffset), bullet.posY+int32(yOffset)
collision := gameMap.checkCollision(xPos, yPos)
if collision == 1 || collision == 0 || (bullet.super && collision == 2) {
*bulletParticleMap = append(*bulletParticleMap, &BulletParticle{
bulletParticleMap[bulletParticleLastID] = &BulletParticle{
posX: xPos,
posY: yPos,
expirationTimer: uint8(15 + rand.Intn(15)),
expirationTimer: uint32(15 + rand.Intn(15)),
color: bullet.color,
})
id: bulletParticleLastID,
}
bulletParticleLastID++
gameMap.tiles[xPos][yPos] = 0
}
}
@ -107,7 +117,7 @@ func (bullet *Bullet) explode(gameMap *GameMap, bulletParticleMap *[]*BulletPart
}
func (bullet *Bullet) tick(gameMap *GameMap,
bulletParticleMap *[]*BulletParticle, bulletMap *[]*Bullet, players *[]*Player) {
bulletParticleMap map[uint32]*BulletParticle, bulletMap map[uint32]*Bullet, players map[uint32]*Player) {
var nextX, nextY int32
nextX, nextY = bullet.posX, bullet.posY
switch bullet.direction {
@ -148,13 +158,13 @@ func (bullet *Bullet) tick(gameMap *GameMap,
H: 1,
}
hitPlayer := false
for _, player := range *players {
for _, player := range players {
if hitPlayer {
break
}
for _, coloredRect := range player.gameObject.getCurrentRects() {
if coloredRect.rect.HasIntersection(&bulletRect) {
hitPlayer = true
if player.gameObject.adjustRectWorld(coloredRect.rect).HasIntersection(&bulletRect) {
hitPlayer = player.shields <= serverConfig.MaxShields
player.shields -= 10
break
}
@ -162,11 +172,7 @@ func (bullet *Bullet) tick(gameMap *GameMap,
}
if collisionResult != 0 || hitPlayer {
bullet.explode(gameMap, bulletParticleMap)
for i, bulletInMap := range *bulletMap {
if bulletInMap == bullet {
*bulletMap = append((*bulletMap)[:i], (*bulletMap)[i+1:]...)
}
}
delete(bulletMap, bullet.id)
} else {
bullet.posX = nextX
bullet.posY = nextY

@ -6,11 +6,13 @@ import (
)
type GameObject struct {
baseRect sdl.Rect
borderRect sdl.Rect
orientation uint8
visualRects [][]*ColoredRect
colors []color.Color
baseRect sdl.Rect
prevBaseRect sdl.Rect
borderRect sdl.Rect
orientation uint8
visualRects [][]*ColoredRect
colors []color.Color
inView bool
}
type ColoredRect struct {
@ -27,6 +29,15 @@ func adjustRectToCamera(object *sdl.Rect, camera *sdl.Rect) *sdl.Rect {
}
}
func (gameObject *GameObject) adjustRectWorld(offset *sdl.Rect) *sdl.Rect {
return &sdl.Rect{
X: gameObject.baseRect.X + offset.X,
Y: gameObject.baseRect.Y + offset.Y,
W: offset.W,
H: offset.H,
}
}
func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.Rect) *sdl.Rect {
return &sdl.Rect{
X: gameObject.baseRect.X + offset.X - camera.X,
@ -38,7 +49,8 @@ func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.R
func (gameObject *GameObject) render(camera *sdl.Rect, surface *sdl.Surface) {
if camera.HasIntersection(&gameObject.baseRect) {
if Debug {
gameObject.inView = true
if config.Debug {
gameObject.borderRect = sdl.Rect{
X: gameObject.baseRect.X - 1,
Y: gameObject.baseRect.Y - 1,
@ -50,13 +62,15 @@ func (gameObject *GameObject) render(camera *sdl.Rect, surface *sdl.Surface) {
surface.FillRect(borderRectFinal, sdl.MapRGBA(surface.Format, 20, 192, 128, 64))
surface.FillRect(baseRectFinal, sdl.MapRGBA(surface.Format, 255, 20, 10, 64))
}
if RenderGameObjects {
if config.RenderGameObjects {
for _, coloredRect := range gameObject.visualRects[gameObject.orientation] {
finalRect := gameObject.adjustRectToCamera(coloredRect.rect, camera)
r, g, b, a := coloredRect.color.RGBA()
surface.FillRect(finalRect, sdl.MapRGBA(surface.Format, uint8(r), uint8(g), uint8(b), uint8(a)))
}
}
} else {
gameObject.inView = false
}
}

1
go.mod

@ -5,4 +5,5 @@ go 1.21
require (
github.com/aquilax/go-perlin v1.1.0
github.com/veandco/go-sdl2 v0.4.40
google.golang.org/protobuf v1.34.2
)

6
go.sum

@ -1,4 +1,10 @@
github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s=
github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/veandco/go-sdl2 v0.4.40 h1:fZv6wC3zz1Xt167P09gazawnpa0KY5LM7JAvKpX9d/U=
github.com/veandco/go-sdl2 v0.4.40/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=

@ -27,13 +27,12 @@ func setupWindowAndSurface(playerIndex uint8) (*sdl.Window, *sdl.Surface) {
}
func setupMapWindowAndSurface() (*sdl.Window, *sdl.Surface) {
const windowWidth, windowHeight = MapWidth, MapHeight
window, err := sdl.CreateWindow("Tunneler map", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, windowWidth, windowHeight, sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
window, err := sdl.CreateWindow("Tunneler map", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, int32(serverConfig.MapWidth), int32(serverConfig.MapWidth), sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
if err != nil {
panic(err)
}
logicalSurface, err := sdl.CreateRGBSurface(0, windowWidth, windowHeight, 32, 0, 0, 0, 0)
logicalSurface, err := sdl.CreateRGBSurface(0, int32(serverConfig.MapWidth), int32(serverConfig.MapWidth), 32, 0, 0, 0, 0)
if err != nil {
panic(err)
}
@ -42,12 +41,12 @@ func setupMapWindowAndSurface() (*sdl.Window, *sdl.Surface) {
}
func setupPlaySurface() (*sdl.Surface, *sdl.Rect, *sdl.Rect) {
playSurface, err := sdl.CreateRGBSurface(0, 76, 76, 32, 0, 0, 0, 0)
playSurface, err := sdl.CreateRGBSurface(0, config.CameraW, config.CameraH, 32, 0, 0, 0, 0)
if err != nil {
panic(err)
}
playSurfaceRect := &sdl.Rect{X: 0, Y: 0, W: 76, H: 76}
playSurfaceTargetRect := &sdl.Rect{X: 42, Y: 0, W: 76, H: 76}
playSurfaceRect := &sdl.Rect{X: 0, Y: 0, W: config.CameraW, H: config.CameraH}
playSurfaceTargetRect := &sdl.Rect{X: 42, Y: 0, W: config.CameraW, H: config.CameraH}
return playSurface, playSurfaceRect, playSurfaceTargetRect
}
@ -58,7 +57,7 @@ func setupHUDSurface() (*sdl.Surface, *sdl.Rect, *sdl.Rect) {
panic(err)
}
HUDSurfaceRect := &sdl.Rect{X: 0, Y: 0, W: 112, H: 25}
HUDSurfaceTargetRect := &sdl.Rect{X: 24, Y: 76, W: 112, H: 25}
HUDSurfaceTargetRect := &sdl.Rect{X: 24, Y: config.CameraH, W: 112, H: 25}
return HUDSurface, HUDSurfaceRect, HUDSurfaceTargetRect
}

8
hud.go

@ -11,7 +11,7 @@ var letterOffsets = []int32{
var letterColors []uint32
// Map function to convert value from range [0, 6] to range [0, 88]
func mapRange(x, minX, maxX, minY, maxY uint16) int32 {
func mapRange(x, minX, maxX, minY, maxY uint32) int32 {
// Perform conversion to float64 to ensure proper division
return int32((float64(x-minX)/float64(maxX-minX))*float64(maxY-minY) + float64(minY))
}
@ -66,9 +66,9 @@ func renderHud(player *Player, surface *sdl.Surface) {
}
HUDValues := []int32{
mapRange(player.energy, 0, MaxEnergy, 0, 88),
mapRange(player.ammunition, 0, MaxAmmunition, 0, 88),
mapRange(player.shields, 0, MaxShields, 0, 88),
mapRange(player.energy, 0, serverConfig.MaxEnergy, 0, 88),
mapRange(player.ammunition, 0, serverConfig.MaxAmmunition, 0, 88),
mapRange(player.shields, 0, serverConfig.MaxShields, 0, 88),
}
for HUDValueIndex, HUDValue := range HUDValues {

341
main.go

@ -1,74 +1,213 @@
package main
import (
"encoding/json"
"fmt"
"github.com/veandco/go-sdl2/sdl"
"io/ioutil"
"log"
"net"
"os"
"sync"
"time"
)
const (
MapWidth = 1000 // Width of the map
MapHeight = 1000 // Height of the map
BlastRadius = 5
type ServerConfig struct {
MapWidth uint32
MapHeight uint32
BlastRadius uint32
MaxEnergy = 3520
MaxAmmunition = 6
MaxShields = 100
MaxEnergy uint32
MaxAmmunition uint32
MaxShields uint32
NormalShotCost = 7
SuperShotCost = 80
ReloadCost = 4
MovementCost = 1
DiggingCost = 3
ShootDiggingCostBonus = 1
NormalShotCost uint32
SuperShotCost uint32
ReloadCost uint32
MovementCost uint32
DiggingCost uint32
ShootDiggingCostBonus uint32
ShootCooldown = 8
RechargeCooldownOwn = 0
DiggingCooldown = 4
RechargeCooldownOpponent = 6
RepairCooldown = 4
MovementCooldown = 2
MovementCooldownNoEnergy = 4
DiggingCooldownNoEnergy = 8
ReloadCooldown = 16
ShootCooldown uint32
RechargeCooldownOwn uint32
DiggingCooldown uint32
RechargeCooldownOpponent uint32
RepairCooldown uint32
MovementCooldown uint32
MovementCooldownNoEnergy uint32
DiggingCooldownNoEnergy uint32
ReloadCooldown uint32
}
Debug = false
MapWindow = false
RenderGameObjects = true
ProfilerOn = true
ProfilerInterval = 100
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"`
}
JoyStickDeadZone = 8000
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,
}
DoAllKeymapsPlayers = false
DoJoyStickPlayers = true
DoKeymapPlayer = true
)
// 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)
}
err = ioutil.WriteFile(filename, data, 0644)
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
data, err := ioutil.ReadFile(filename)
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{}
var 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,
}
var mapWindow *sdl.Window
var mapSurface *sdl.Surface
var mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: MapWidth, H: MapHeight}
var mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)}
var netPlayerMapper = map[*net.Conn]*Player{}
var clientInitialized = false
var worldLock sync.Mutex
func main() {
initializeSDL()
defer sdl.Quit()
gameMap := &GameMap{}
gameMap.createGameMap(MapWidth, MapHeight)
players := &[]*Player{}
configX, err := loadOrCreateConfig("config.json")
if err != nil {
log.Fatal(err)
}
config = configX
players := make(map[uint32]*Player)
initPlayerColors()
createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players)
bullets := make(map[uint32]*Bullet)
bulletParticles := make(map[uint32]*BulletParticle)
bases := make(map[uint32]*Base)
if config.Client && config.Server {
panic("You can' t run client and server in the same instance")
}
if !config.Client {
gameMap.createGameMap(true)
createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players)
bases = createBases(players, gameMap)
} else {
// Create a connection to the server
conn, err := net.Dial("tcp", config.Address)
if err != nil {
log.Fatalf("Failed to connect to server: %v", err)
}
defer conn.Close()
go handleConnectionClient(&conn, players, bases, bullets, bulletParticles)
for !clientInitialized {
time.Sleep(100 * time.Millisecond)
}
}
defer closeThings(players)
bases := createBases(players, gameMap)
bullets := &[]*Bullet{}
bulletParticles := &[]*BulletParticle{}
for playerIndex, player := range *players {
for playerIndex, player := range players {
initPlayer(uint8(playerIndex), player)
}
if MapWindow {
if config.Server {
listen, err := net.Listen("tcp", "0.0.0.0:5074")
if err != nil {
log.Fatal(err)
}
// close listener
defer listen.Close()
go func() {
for {
conn, err := listen.Accept()
if err != nil {
log.Fatal(err)
}
go handleRequest(conn, players, bullets, bases)
}
}()
}
if config.MapWindow {
mapWindow, mapSurface = setupMapWindowAndSurface()
}
running := true
@ -82,6 +221,7 @@ func main() {
currentTime := sdl.GetTicks64()
deltaTime := currentTime - prevTime
prevTime = currentTime
worldLock.Lock()
// Catch up in case of a large delta time
for deltaTime > maxDeltaTime {
@ -91,34 +231,34 @@ func main() {
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
}
for playerIndex, player := range *players {
// Run logic for the remaining delta time
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
for playerIndex, player := range players {
if player.local {
running := doPlayerFrame(uint8(playerIndex), player, players, gameMap, bases, bullets, bulletParticles)
running = doPlayerFrame(uint8(playerIndex), player, players, gameMap, bases, bullets, bulletParticles)
if !running {
break
}
}
}
// Run logic for the remaining delta time
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
// Render logic
if MapWindow {
if config.MapWindow {
// Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() {
gameMap.render(mapRendererRect, mapSurface)
for _, bullet := range *bullets {
(*bullet).render(mapRendererRect, mapSurface)
for _, bullet := range bullets {
(*bullet).render(mapRendererRect, mapSurface, bullets)
}
for _, base := range *bases {
(*base).render(mapRendererRect, mapSurface)
for _, base := range bases {
(*base).render(mapRendererRect, mapSurface, bases)
}
for _, playerLoop := range *players {
(*playerLoop).render(mapRendererRect, mapSurface)
for _, playerLoop := range players {
(*playerLoop).render(mapRendererRect, mapSurface, players)
}
for _, bulletParticle := range *bulletParticles {
bulletParticle.render(mapRendererRect, mapSurface)
for _, bulletParticle := range bulletParticles {
bulletParticle.render(mapRendererRect, mapSurface, bulletParticles)
}
})
totalScalingTime += profileSection(func() {
@ -131,30 +271,70 @@ func main() {
frameCount++
// Log profiling information every 1000 frames
if frameCount%ProfilerInterval == 0 && ProfilerOn {
if frameCount%config.ProfilerInterval == 0 && config.ProfilerOn {
logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, frameCount)
resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &frameCount)
}
worldLock.Unlock()
enforceFrameRate(currentTime, 60)
}
}
// Separate function to handle game logic
func runGameLogic(players *[]*Player, gameMap *GameMap, bases *[]*Base, bullets *[]*Bullet, bulletParticles *[]*BulletParticle) {
func runGameLogic(players map[uint32]*Player, gameMap *GameMap, bases map[uint32]*Base, bullets map[uint32]*Bullet, bulletParticles map[uint32]*BulletParticle) {
// Tick world
for _, bullet := range *bullets {
if config.Server {
//update remote player maps
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
}
}
}
for _, bullet := range bullets {
(*bullet).tick(gameMap, bulletParticles, bullets, players)
}
for _, base := range *bases {
for _, player := range players {
if player.local {
continue
}
(*player).tick(bullets)
}
for _, base := range bases {
(*base).tick(players)
}
for _, bulletParticle := range *bulletParticles {
for _, bulletParticle := range bulletParticles {
bulletParticle.tick(bulletParticles)
}
}
func initPlayer(playerIndex uint8, player *Player) {
if !player.local {
return
}
player.window, player.logicalSurface = setupWindowAndSurface(playerIndex)
logicalColor := sdl.MapRGBA(player.logicalSurface.Format, 101, 101, 0, 255)
player.logicalSurface.FillRect(nil, logicalColor)
@ -165,13 +345,13 @@ func initPlayer(playerIndex uint8, player *Player) {
player.HUDSurface, player.HUDSurfaceRect, player.HUDSurfaceTargetRect = setupHUDSurface()
initHud(player.HUDSurface)
player.camera = &sdl.Rect{X: 0, Y: 0, W: 76, H: 76}
player.camera = &sdl.Rect{X: 0, Y: 0, W: config.CameraW, H: config.CameraH}
}
func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMap *GameMap, bases *[]*Base, bullets *[]*Bullet, bulletParticles *[]*BulletParticle) bool {
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 {
running := true
var (
isShooting, shouldContinue bool
shouldContinue bool
)
frameStart := sdl.GetTicks64()
@ -179,7 +359,7 @@ func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMa
player.totalHandleEventsTime += profileSection(func() {
running = handleEvents(player.window, player.logicalSurface)
keyboard := sdl.GetKeyboardState()
shouldContinue, isShooting = handleInput(keyboard, bullets, player, gameMap, players)
shouldContinue = handleInput(keyboard, bullets, player, gameMap, players)
running = running && shouldContinue
})
@ -188,20 +368,21 @@ func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMa
player.track(player.camera)
gameMap.render(player.camera, player.playSurface)
for _, bullet := range *bullets {
(*bullet).render(player.camera, player.playSurface)
for _, bullet := range bullets {
(*bullet).render(player.camera, player.playSurface, bullets)
}
for _, base := range *bases {
(*base).render(player.camera, player.playSurface)
for _, base := range bases {
(*base).render(player.camera, player.playSurface, bases)
}
for _, playerLoop := range *players {
(*playerLoop).render(player.camera, player.playSurface)
for _, playerLoop := range players {
(*playerLoop).render(player.camera, player.playSurface, players)
}
for _, bulletParticle := range *bulletParticles {
bulletParticle.render(player.camera, player.playSurface)
for _, bulletParticle := range bulletParticles {
bulletParticle.render(player.camera, player.playSurface, bulletParticles)
}
player.tick(isShooting, bullets, player.playSurface.Format)
player.tick(bullets)
player.gameObject.prevBaseRect = player.gameObject.baseRect
renderHud(player, player.HUDSurface)
player.playSurface.BlitScaled(player.playSurfaceRect, player.logicalSurface, player.playSurfaceTargetRect)
player.HUDSurface.BlitScaled(player.HUDSurfaceRect, player.logicalSurface, player.HUDSurfaceTargetRect)
@ -217,7 +398,7 @@ func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMa
player.frameCount++
// Log profiling information every 1000 frames
if player.frameCount%ProfilerInterval == 0 && ProfilerOn && playerIndex == 0 {
if player.frameCount%config.ProfilerInterval == 0 && config.ProfilerOn && playerIndex == 0 {
logProfilingInfo(player.totalHandleEventsTime, player.totalRenderTime, player.totalScalingTime, player.totalFrameTime, player.frameCount)
resetProfilingCounters(&player.totalHandleEventsTime, &player.totalRenderTime, &player.totalScalingTime, &player.totalFrameTime, &player.frameCount)
}

59
map.go

@ -16,8 +16,8 @@ type GameMap struct {
//tile 3 - soil 2
//tile 4 - base
//tile 5 - world edge (should not be present in the map object)
width int32
height int32
width uint32
height uint32
}
func GetTileColorValue(value uint8) color.Color {
@ -40,7 +40,7 @@ func GetTileColorValue(value uint8) color.Color {
}
func (gameMap *GameMap) getTileColor(x int32, y int32) color.Color {
if x < 0 || x >= gameMap.width || y < 0 || y >= gameMap.height {
if x < 0 || x >= int32(gameMap.width) || y < 0 || y >= int32(gameMap.height) {
return GetTileColorValue(5)
} else {
tileValue := gameMap.tiles[x][y]
@ -48,10 +48,9 @@ func (gameMap *GameMap) getTileColor(x int32, y int32) color.Color {
}
}
func (gameMap *GameMap) createGameMap(width int32, height int32) {
func (gameMap *GameMap) createGameMap(withData bool) {
// Initialize the 2D slice for game tiles
gameMap.tiles = make([][]uint8, width)
gameMap.tiles = make([][]uint8, serverConfig.MapWidth)
// Create Perlin noise generators
perlinNoiseRock := perlin.NewPerlin(2, 4, 8, time.Now().Unix())
perlinNoiseSoil := perlin.NewPerlin(2, 8, 4, time.Now().Unix())
@ -61,39 +60,45 @@ func (gameMap *GameMap) createGameMap(width int32, height int32) {
// Loop over the rows
for i := range gameMap.tiles {
// Increment WaitGroup counter for each goroutine
wg.Add(1)
// Launch a goroutine for each row
go func(i int) {
defer wg.Done() // Signal that this goroutine is done
if withData {
// Launch a goroutine for each row
// Increment WaitGroup counter for each goroutine
wg.Add(1)
go func(i int) {
defer wg.Done() // Signal that this goroutine is done
// Initialize the row
gameMap.tiles[i] = make([]uint8, height)
// Initialize the row
gameMap.tiles[i] = make([]uint8, serverConfig.MapHeight)
// Process each tile in the row
for j := range gameMap.tiles[i] {
perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(width)*6, float64(j)/float64(height)*6)
if perlinRock > 0.2 {
gameMap.tiles[i][j] = 1
} else {
perlinSoil := perlinNoiseSoil.Noise2D(float64(i)/float64(width)*10, float64(j)/float64(height)*10)
if perlinSoil >= 0 {
gameMap.tiles[i][j] = 2
// Process each tile in the row
for j := range gameMap.tiles[i] {
perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(serverConfig.MapWidth)*6, float64(j)/float64(serverConfig.MapHeight)*6)
if perlinRock > 0.2 {
gameMap.tiles[i][j] = 1
} else {
gameMap.tiles[i][j] = 3
perlinSoil := perlinNoiseSoil.Noise2D(float64(i)/float64(serverConfig.MapWidth)*10, float64(j)/float64(serverConfig.MapHeight)*10)
if perlinSoil >= 0 {
gameMap.tiles[i][j] = 2
} else {
gameMap.tiles[i][j] = 3
}
}
}
}(i)
} else {
for x := uint32(0); x < serverConfig.MapWidth; x++ {
gameMap.tiles[x] = make([]uint8, serverConfig.MapHeight)
}
}(i)
}
}
// Wait for all goroutines to finish
wg.Wait()
// Set the dimensions of the game map
gameMap.width = width
gameMap.height = height
gameMap.width = serverConfig.MapWidth
gameMap.height = serverConfig.MapHeight
}
func (gameMap *GameMap) render(camera *sdl.Rect, surface *sdl.Surface) {
@ -131,7 +136,7 @@ func (gameMap *GameMap) checkCollision(posX, posY int32) uint8 {
//1 destructible collision
//2 rock collision
//3 indestructible collision
if posX >= gameMap.width || posX < 0 || posY >= gameMap.height || posY < 0 {
if posX >= int32(gameMap.width) || posX < 0 || posY >= int32(gameMap.height) || posY < 0 {
return 3
}
switch gameMap.tiles[posX][posY] {

1027
player.go

File diff suppressed because it is too large Load Diff

@ -74,15 +74,15 @@ type PlayerColors struct {
func getNeededPlayers() (neededPlayers uint8) {
neededPlayers = 0
if DoAllKeymapsPlayers {
if config.DoAllKeymapsPlayers {
neededPlayers += uint8(len(keyMaps))
} else if DoKeymapPlayer && len(keyMaps) > 0 {
} else if config.DoKeymapPlayer && len(keyMaps) > 0 {
neededPlayers++
}
if DoJoyStickPlayers {
if config.DoJoyStickPlayers {
neededPlayers += uint8(sdl.NumJoysticks())
}
if neededPlayers < 2 {
if neededPlayers < 2 && !config.Server {
neededPlayers = 2
}
return neededPlayers
@ -285,9 +285,9 @@ func initPlayerColors() {
}
}
func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *GameMap, players *[]*Player) (bool, bool) {
if !player.local || player.shields <= 0 || player.shields > MaxShields {
return true, false
func handleInput(keyboard []uint8, bullets map[uint32]*Bullet, player *Player, gameMap *GameMap, players map[uint32]*Player) bool {
if !player.local || player.shields <= 0 || player.shields > serverConfig.MaxShields {
return true
}
shoot := false
super := false
@ -306,7 +306,7 @@ func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *
}
if key == player.keyMap.exit {
return false, shoot
return false
} else if key == player.keyMap.shoot {
shoot = true
} else if key == player.keyMap.super {
@ -329,10 +329,10 @@ func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *
xAxis := player.joyStick.Axis(player.joyMap.xAxis)
yAxis := player.joyStick.Axis(player.joyMap.yAxis)
moveRight = xAxis > JoyStickDeadZone
moveLeft = xAxis < -JoyStickDeadZone
moveUp = yAxis > JoyStickDeadZone
moveDown = yAxis < -JoyStickDeadZone
moveRight = xAxis > config.JoyStickDeadZone
moveLeft = xAxis < -config.JoyStickDeadZone
moveUp = yAxis > config.JoyStickDeadZone
moveDown = yAxis < -config.JoyStickDeadZone
} else {
moveUp = player.joyStick.Button(player.joyMap.upButton) == sdl.PRESSED
@ -341,7 +341,7 @@ func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *
moveRight = player.joyStick.Button(player.joyMap.rightButton) == sdl.PRESSED
}
if player.joyStick.Button(player.joyMap.exitButton) == sdl.PRESSED {
return false, shoot
return false
}
if player.joyStick.Button(player.joyMap.shootButton) == sdl.PRESSED {
shoot = true
@ -379,9 +379,9 @@ func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *
// Handle movement after the loop
if moveUp || moveDown || moveLeft || moveRight {
if player.tryMove(gameMap, shoot, players) {
player.energy -= MovementCost
player.energy -= serverConfig.MovementCost
}
}
return true, shoot
return true
}

133
tuneller.proto Normal file

@ -0,0 +1,133 @@
syntax = "proto3";
package goingtunneling;
option go_package = "./proto";
message Position {
int32 posX = 1;
int32 posY = 2;
}
message Player {
uint32 playerID = 1;
PlayerLocation location = 2;
}
message PlayerLocation {
Position position = 1;
uint32 orientation = 2;
}
message Bullet {
Position position = 1;
uint32 direction = 2;
Color color = 3;
uint32 id = 4;
bool super = 5;
}
message Color {
uint32 red = 1;
uint32 green = 2;
uint32 blue = 3;
uint32 alpha = 4;
}
message BulletParticle {
Position position = 1;
uint32 expirationTimer = 2;
Color color = 3;
uint32 id = 4;
}
message ServerInfo {
uint32 maxEnergy = 1;
uint32 maxAmmunition = 2;
uint32 maxShields = 3;
uint32 mapWidth = 4;
uint32 mapHeight = 5;
uint32 normalShotCost = 6;
uint32 superShotCost = 7;
uint32 reloadCost = 8;
uint32 movementCost = 9;
uint32 diggingCost = 10;
uint32 shootDiggingBonus = 11;
uint32 shootCooldown = 12;
uint32 rechargeCooldown = 13;
uint32 rechargeOpponentCooldown = 14;
uint32 repairCooldown = 15;
uint32 diggingCooldown = 16;
uint32 movementCooldown = 17;
uint32 movementCooldownNoEnergy = 18;
uint32 diggingCooldownNoEnergy = 19;
uint32 reloadCooldown = 20;
uint32 blastRadius = 21;
uint32 playerID = 22;
uint32 playerColorID = 23;
}
message PlayerUpdate {
uint32 energy = 1;
uint32 ammo = 2;
uint32 shields = 3;
}
message BaseLocation {
Position position = 1;
Player owner = 2;
Color color = 3;
}
message TileUpdate {
Position position = 1;
uint32 kind = 2;
}
message WorldUpdate {
repeated Player players = 1;
repeated BaseLocation base = 2;
repeated Bullet bullets = 3;
repeated BulletParticle bulletParticles = 4;
repeated TileUpdate tileUpdate = 5;
}
message ClientBound {
oneof clientBoundMessage {
ServerInfo serverInfo = 1;
PlayerUpdate playerUpdate = 2;
PlayerLocation playerLocationUpdate = 3;
WorldUpdate worldUpdate = 4;
PlayerStartResponse playerStartResponse = 5;
}
}
message DigBlock {
Position position = 1;
bool isShooting = 2;
}
message Shoot {
bool super = 1;
}
message PlayerAction {
oneof playerAction {
DigBlock digBlock = 1;
Shoot shoot = 2;
}
}
message PlayerStartRequest {
string version = 1;
}
message PlayerStartResponse {
string version = 1;
}
message ServerBound {
oneof serverBoundMessage {
PlayerLocation playerPosition = 1;
PlayerAction playerAction = 2;
PlayerStartRequest playerStartRequest = 3;
}
}