GOingTunneling/player.go

1581 lines
46 KiB
Go

package main
import (
"bufio"
"bytes"
"encoding/binary"
"github.com/veandco/go-sdl2/sdl"
tunnelerProto "goingtunneling/proto"
"google.golang.org/protobuf/proto"
"image/color"
"io"
"log"
"math/rand"
"net"
"os"
"sync"
)
var lastPlayerID = uint32(0)
type Player struct {
local bool
connection *net.TCPConn
playerColors PlayerColors
keyMap KeyMap
joyMap JoyMap
joyStick *sdl.Joystick
camera *sdl.Rect
energy uint32
ammunition uint32
shields uint32
digCooldown uint32
shootCooldown uint32
repairCooldown uint32
rechargeCooldown uint32
movementCooldown uint32
reloadCooldown uint32
gameObject *GameObject
playerID uint32
knownGameMap *GameMap
gameMapUpdates []TileUpdate
knownBullets map[uint32]*Bullet
knownBulletParticles map[uint32]*BulletParticle
knownBases map[uint32]*Base
knownPlayers map[uint32]*Player
previousEnergy uint32
previousAmmunition uint32
previousShields uint32
window *sdl.Window
logicalSurface *sdl.Surface
playSurface *sdl.Surface
HUDSurface *sdl.Surface
playSurfaceRect *sdl.Rect
HUDSurfaceRect *sdl.Rect
playSurfaceTargetRect *sdl.Rect
HUDSurfaceTargetRect *sdl.Rect
totalHandleEventsTime uint64
totalRenderTime uint64
totalFrameTime uint64
totalScalingTime uint64
frameCount uint64
}
type TileUpdate struct {
PosX uint32
PosY uint32
Kind uint8
}
func (player *Player) track(camera *sdl.Rect) {
camera.X = player.gameObject.baseRect.X - 37
camera.Y = player.gameObject.baseRect.Y - 38
}
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 &&
player.gameObject.baseRect.Y >= 0 {
player.gameObject.render(camera, surface)
if !player.gameObject.inView && !config.Server {
delete(players, player.playerID)
}
}
}
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--
}
if player.shootCooldown > 0 {
player.shootCooldown--
}
if player.repairCooldown > 0 {
player.repairCooldown--
}
if player.rechargeCooldown > 0 {
player.rechargeCooldown--
}
if player.movementCooldown > 0 {
player.movementCooldown--
}
if player.reloadCooldown > 0 {
player.reloadCooldown--
} else if player.ammunition < serverConfig.MaxAmmunition && player.shootCooldown == 0 && player.energy > serverConfig.ReloadCost {
player.ammunition++
player.reloadCooldown = serverConfig.ReloadCooldown
player.energy -= serverConfig.ReloadCost
}
if player.shields <= 0 {
player.shields = serverConfig.MaxShields + 1
player.explode(bullets)
}
}
func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players map[uint32]*Player) (moved bool) {
if player.movementCooldown > 0 {
return false
}
ranOutOfEnergy := (isShooting && player.energy <= serverConfig.DiggingCost+serverConfig.ShootDiggingCostBonus) || player.energy <= serverConfig.DiggingCost
if ranOutOfEnergy {
isShooting = false
}
shouldPenalizeDigging := false
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
}
}
}
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 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
}
}
if moved {
if ranOutOfEnergy {
player.movementCooldown = serverConfig.MovementCooldownNoEnergy
} else {
player.movementCooldown = serverConfig.MovementCooldown
}
}
return
}
func (player *Player) getRGBAColor(colorIndex uint8, format *sdl.PixelFormat) uint32 {
var selectedColor color.Color
switch colorIndex {
case 0:
selectedColor = player.playerColors.tracks
case 1:
selectedColor = player.playerColors.body
case 2:
selectedColor = player.playerColors.cannon
}
var r, g, b, a uint8
if selectedColor != nil {
rt, gt, bt, at := selectedColor.RGBA()
r, g, b, a = uint8(rt), uint8(gt), uint8(bt), uint8(at)
}
return sdl.MapRGBA(format, r, g, b, a)
}
func (player *Player) shoot(super bool, bullets map[uint32]*Bullet) {
if (super && (player.energy <= serverConfig.SuperShotCost || player.ammunition < serverConfig.MaxAmmunition)) || (!super && (player.energy <= serverConfig.NormalShotCost || player.ammunition < 1)) {
return
}
if player.shootCooldown == 0 {
var shootX, shootY int32
switch player.gameObject.orientation {
case 0: // Up
shootY = player.gameObject.baseRect.Y - 1
shootX = player.gameObject.baseRect.X + 2
break
case 1: // Right
shootY = player.gameObject.baseRect.Y + 3
shootX = player.gameObject.baseRect.X + 8
break
case 2: // Down
shootX = player.gameObject.baseRect.X + 2
shootY = player.gameObject.baseRect.Y + 8
break
case 3: // Left
shootY = player.gameObject.baseRect.Y + 3
shootX = player.gameObject.baseRect.X - 2
break
case 4: // Up-Right
shootY = player.gameObject.baseRect.Y
shootX = player.gameObject.baseRect.X + 5
break
case 5: // Up-Left
shootY = player.gameObject.baseRect.Y
shootX = player.gameObject.baseRect.X - 1
break
case 6: // Down-Right
shootY = player.gameObject.baseRect.Y + 5
shootX = player.gameObject.baseRect.X + 5
break
case 7: // Down-Left
shootY = player.gameObject.baseRect.Y + 5
shootX = player.gameObject.baseRect.X - 1
break
}
player.shootCooldown = serverConfig.ShootCooldown
// Set cooldown and decrease energy
if super {
player.energy -= serverConfig.SuperShotCost
player.ammunition = 0
} else {
player.energy -= serverConfig.NormalShotCost
player.ammunition--
}
if config.Client {
player.sendShootToServer(super)
} else {
// Set bullet color
bulletColor := player.playerColors.body
// Create and add the bullet
bullets[bulletLastID] = &Bullet{
posX: shootX,
posY: shootY,
direction: player.gameObject.orientation,
color: bulletColor,
super: super,
id: bulletLastID,
}
bulletLastID++
}
}
}
func (player *Player) explode(bullets map[uint32]*Bullet) {
// Set bullet color
bulletColor := player.playerColors.body
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
bullets[bulletLastID] = &Bullet{
posX: x,
posY: y,
direction: uint8(rand.Intn(8)),
color: bulletColor,
super: true,
id: bulletLastID,
}
bulletLastID++
}
}
}
func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players map[uint32]*Player) {
joyStickCount := sdl.NumJoysticks()
if amount > uint8(len(keyMaps)+len(joyMaps)) || amount > uint8(len(keyMaps)+joyStickCount) {
panic("Too many players, not enough inputs")
}
if amount >= uint8(len(playerColors)) {
panic("Too many players, not enough colors")
}
addedKeyboardPlayers := 0
for i := uint8(0); i < amount; i++ {
var keyMap KeyMap
var joyMap JoyMap
var joyStick *sdl.Joystick
if (config.DoAllKeymapsPlayers && i <= uint8(len(keyMaps))) || (config.DoKeymapPlayer && i == 0) || (uint8(joyStickCount) <= i) {
keyMap = keyMaps[addedKeyboardPlayers]
addedKeyboardPlayers++
} else {
joyStickIndex := i - uint8(addedKeyboardPlayers)
joyMap = joyMaps[joyStickIndex]
joyStick = sdl.JoystickOpen(int(joyStickIndex))
}
createPlayer(
true,
playerColors[len(players)%len(playerColors)],
nil,
keyMap,
joyMap,
joyStick,
gameMap,
players,
)
}
}
func closeThings(players map[uint32]*Player) {
for _, player := range players {
if player.joyStick != nil {
player.joyStick.Close()
}
if player.window != nil {
player.window.Destroy()
}
if player.logicalSurface != nil {
player.logicalSurface.Free()
}
if player.playSurface != nil {
player.playSurface.Free()
}
if player.HUDSurface != nil {
player.HUDSurface.Free()
}
}
}
func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players map[uint32]*Player) uint32 {
coordsAreValid := false
var posX, posY int32
maxTries := 1000
for !coordsAreValid && maxTries >= 0 {
maxTries--
posX = int32(16 + rand.Intn(int(gameMap.width-43)))
posY = int32(16 + rand.Intn(int(gameMap.height-43)))
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
coordsAreValid = false
break
}
}
if posX < 16 || posX > int32(gameMap.width-36-7) || posY < 16 || posY > int32(gameMap.height-36-7) {
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,
W: 7,
H: 7,
}
gameObject.addColor(thisPlayerColors.tracks)
gameObject.addColor(thisPlayerColors.body)
gameObject.addColor(thisPlayerColors.cannon)
gameObject.orientation = 0 // Up
gameObject.addColoredRect(0, 1, 1, 6, 0)
gameObject.addColoredRect(4, 1, 1, 6, 0)
gameObject.addColoredRect(1, 2, 3, 4, 1)
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.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.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.orientation = 4 // Up-Right
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(2, 1, 1, 1, 0)
gameObject.addColoredRect(1, 2, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(5, 4, 1, 1, 0)
gameObject.addColoredRect(4, 5, 1, 1, 0)
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(2, 2, 2, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(2, 4, 3, 1, 1)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(5, 1, 1, 1, 2)
gameObject.addColoredRect(4, 2, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
// Up-Left orientation (Y-axis reflection)
gameObject.orientation = 5 // Up-Left
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(4, 1, 1, 1, 0)
gameObject.addColoredRect(5, 2, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(1, 4, 1, 1, 0)
gameObject.addColoredRect(2, 5, 1, 1, 0)
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(3, 2, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(2, 4, 3, 1, 1)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(1, 1, 1, 1, 2)
gameObject.addColoredRect(2, 2, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
// Down-Right orientation (X-axis reflection)
gameObject.orientation = 6 // Down-Right
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(2, 5, 1, 1, 0)
gameObject.addColoredRect(1, 4, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(5, 2, 1, 1, 0)
gameObject.addColoredRect(4, 1, 1, 1, 0)
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(2, 4, 2, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(2, 2, 3, 1, 1)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(5, 5, 1, 1, 2)
gameObject.addColoredRect(4, 4, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
// Down-Left orientation (XY reflection)
gameObject.orientation = 7 // Down-Left
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(4, 5, 1, 1, 0)
gameObject.addColoredRect(5, 4, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(1, 2, 1, 1, 0)
gameObject.addColoredRect(2, 1, 1, 1, 0)
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(2, 2, 3, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(3, 4, 2, 1, 1)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(1, 5, 1, 1, 2)
gameObject.addColoredRect(2, 4, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
gameObject.orientation = 0
if !local && (keyMap.exit != keyMap.shoot || joyMap.exitButton != joyMap.shootButton) {
panic("Input assigned to remote player")
}
knownGameMap := GameMap{width: gameMap.width, height: gameMap.height, tiles: make([][]uint8, serverConfig.MapWidth)}
for i := uint32(0); i < serverConfig.MapWidth; i++ {
knownGameMap.tiles[i] = make([]uint8, serverConfig.MapHeight)
}
players[lastPlayerID] = &Player{
playerColors: thisPlayerColors,
keyMap: keyMap,
joyMap: joyMap,
joyStick: joyStick,
shields: serverConfig.MaxShields,
energy: serverConfig.MaxEnergy,
gameObject: gameObject,
local: local,
connection: conn,
playerID: lastPlayerID,
knownBulletParticles: make(map[uint32]*BulletParticle),
knownBases: make(map[uint32]*Base),
knownPlayers: make(map[uint32]*Player),
knownBullets: make(map[uint32]*Bullet),
knownGameMap: &knownGameMap,
}
lastPlayerID++
return lastPlayerID - 1
}
func sendMessageRaw(data []byte, conn *net.TCPConn) (fail bool) {
if conn != nil {
// Send the length of the message first
length := uint32(len(data))
lengthBuf := new(bytes.Buffer)
if err := binary.Write(lengthBuf, binary.BigEndian, length); err != nil {
if config.Server {
log.Printf("Failed to write message length: %v", err)
fail = true
} else {
log.Fatalf("Failed to write message length: %v", err)
}
}
// Send the length followed by the message itself
if _, err := (*conn).Write(lengthBuf.Bytes()); err != nil {
if config.Server {
log.Printf("Failed to write message length to connection: %v", err)
fail = true
} else {
log.Fatalf("Failed to write message length to connection: %v", err)
}
}
if _, err := (*conn).Write(data); err != nil {
if config.Server {
log.Printf("Failed to write message to connection: %v", err)
fail = true
} else {
log.Fatalf("Failed to write message to connection: %v", err)
}
}
}
return
}
func (player *Player) sendMessage(data []byte) bool {
return sendMessageRaw(data, player.connection)
}
func (player *Player) updateRemotePlayerMap() {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
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
toY := player.gameObject.baseRect.Y + (config.CameraH / 2) + 1
if uint32(toX) > serverConfig.MapWidth {
toX = int32(serverConfig.MapWidth)
}
if uint32(toY) > serverConfig.MapHeight {
toY = int32(serverConfig.MapHeight)
}
if fromX < 0 {
fromX = 0
}
if fromY < 0 {
fromY = 0
}
// Create a WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup
var lck sync.Mutex
// Process columns instead of individual pixels
for x := fromX; x < toX; x++ {
// Increment the WaitGroup counter
wg.Add(1)
// Launch a goroutine for each column
go func(x int32) {
defer wg.Done() // Decrement the counter when the goroutine completes
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.knownGameMap.tiles[x][y] = gameMap.tiles[x][y]
lck.Unlock()
}
}
}(x)
}
// Wait for all goroutines to finish
wg.Wait()
}
func (player *Player) updateRemotePlayerPlayers(players map[uint32]*Player) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownPlayers = make(map[uint32]*Player)
for _, playerLoop := range players {
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
for _, playerLoopRect := range playerLoop.gameObject.getCurrentRects() {
if player.camera.HasIntersection(playerLoop.gameObject.adjustRectWorld(playerLoopRect.rect)) && playerLoop.shields <= serverConfig.MaxShields {
player.knownPlayers[playerLoop.playerID] = playerLoop
break
}
}
}
}
func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownBullets = make(map[uint32]*Bullet)
for _, bullet := range bullets {
bulletRect := &sdl.Rect{
X: bullet.posX,
Y: bullet.posY,
W: 1,
H: 1,
}
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
if player.camera.HasIntersection(bulletRect) {
player.knownBullets[bullet.id] = bullet
}
}
}
func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint32]*BulletParticle) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownBulletParticles = make(map[uint32]*BulletParticle)
for _, bulletParticle := range bulletParticles {
bulletRect := &sdl.Rect{
X: bulletParticle.posX,
Y: bulletParticle.posY,
W: 1,
H: 1,
}
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
if player.camera.HasIntersection(bulletRect) {
player.knownBulletParticles[bulletParticle.id] = bulletParticle
}
}
}
func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownBases = make(map[uint32]*Base)
for _, base := range bases {
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
for _, baseRect := range base.gameObject.getCurrentRects() {
if player.camera.HasIntersection(base.gameObject.adjustRectWorld(baseRect.rect)) {
player.knownBases[base.ownerID] = base
break
}
}
}
}
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 {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_PlayerUpdate{
PlayerUpdate: &tunnelerProto.PlayerUpdate{
Energy: player.energy,
Ammo: player.ammunition,
Shields: player.shields,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing info")
}
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
}
var playerUpdateMutex sync.Mutex
func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player) bool {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
if player.connection != nil && config.Server {
var playersToSend []*tunnelerProto.Player
var basesToSend []*tunnelerProto.BaseLocation
var bulletsToSend []*tunnelerProto.Bullet
var bulletParticlesToSend []*tunnelerProto.BulletParticle
var tileUpdatesToSend []*tunnelerProto.TileUpdate
for playerID, knownPlayer := range player.knownPlayers {
if playerID == player.playerID {
continue
}
playersToSend = append(playersToSend, &tunnelerProto.Player{
PlayerID: playerID,
Location: &tunnelerProto.PlayerLocation{
Position: &tunnelerProto.Position{
PosX: knownPlayer.gameObject.baseRect.X,
PosY: knownPlayer.gameObject.baseRect.Y,
},
Orientation: uint32(knownPlayer.gameObject.orientation),
},
})
}
for _, knownBase := range player.knownBases {
r, g, b, a := players[knownBase.ownerID].playerColors.body.RGBA()
basesToSend = append(basesToSend, &tunnelerProto.BaseLocation{
Position: &tunnelerProto.Position{
PosX: knownBase.gameObject.baseRect.X,
PosY: knownBase.gameObject.baseRect.Y,
},
Owner: &tunnelerProto.Player{
PlayerID: knownBase.ownerID,
Location: nil,
},
Color: &tunnelerProto.Color{
Red: r,
Green: g,
Blue: b,
Alpha: a,
},
})
}
for _, knownBullet := range player.knownBullets {
bulletRed, bulletGreen, bulletBlue, bulletAlpha := knownBullet.color.RGBA()
bulletsToSend = append(bulletsToSend, &tunnelerProto.Bullet{
Position: &tunnelerProto.Position{
PosX: knownBullet.posX,
PosY: knownBullet.posY,
},
Direction: uint32(knownBullet.direction),
Color: &tunnelerProto.Color{
Red: bulletRed,
Green: bulletGreen,
Blue: bulletBlue,
Alpha: bulletAlpha,
},
Id: knownBullet.id,
})
}
for _, knownBulletParticle := range player.knownBulletParticles {
bulletParticleRed, bulletParticleGreen, bulletParticleBlue, bulletParticleAlpha := knownBulletParticle.color.RGBA()
bulletParticlesToSend = append(bulletParticlesToSend, &tunnelerProto.BulletParticle{
Position: &tunnelerProto.Position{
PosX: knownBulletParticle.posX,
PosY: knownBulletParticle.posY,
},
ExpirationTimer: knownBulletParticle.expirationTimer,
Color: &tunnelerProto.Color{
Red: bulletParticleRed,
Green: bulletParticleGreen,
Blue: bulletParticleBlue,
Alpha: bulletParticleAlpha,
},
Id: knownBulletParticle.id,
})
}
for _, tileUpdate := range player.gameMapUpdates {
tileUpdatesToSend = append(tileUpdatesToSend, &tunnelerProto.TileUpdate{
Position: &tunnelerProto.Position{
PosX: int32(tileUpdate.PosX),
PosY: int32(tileUpdate.PosY),
},
Kind: uint32(tileUpdate.Kind),
})
}
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_WorldUpdate{
WorldUpdate: &tunnelerProto.WorldUpdate{
Players: playersToSend,
Base: basesToSend,
Bullets: bulletsToSend,
BulletParticles: bulletParticlesToSend,
TileUpdate: tileUpdatesToSend,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing updates")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendPositionToServer() bool {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerPosition{
PlayerPosition: &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 sendVersionToServer(connection *net.TCPConn) {
if connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerStartRequest{
PlayerStartRequest: &tunnelerProto.PlayerStartRequest{
Version: config.Version,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing version")
}
sendMessageRaw(out, connection)
}
}
func (player *Player) sendDigToServer(posX, posY uint32, isShooting bool) {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction_DigBlock{
DigBlock: &tunnelerProto.DigBlock{
Position: &tunnelerProto.Position{
PosX: int32(posX),
PosY: int32(posY),
},
IsShooting: isShooting,
},
},
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing dig")
}
player.sendMessage(out)
}
}
func (player *Player) sendShootToServer(isSuper bool) {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction_Shoot{
Shoot: &tunnelerProto.Shoot{
Super: isSuper,
},
},
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing shot")
}
player.sendMessage(out)
}
}
func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[uint32]*Bullet, bases map[uint32]*Base) {
defer conn.Close()
r := bufio.NewReader(conn)
for {
// Read the length of the incoming message (4 bytes)
lengthBuf := make([]byte, 4)
_, err := io.ReadFull(r, lengthBuf)
if err != nil {
log.Printf("Failed to read message length: %v", err)
return
}
// Convert length to an integer
length := binary.BigEndian.Uint32(lengthBuf)
// Read the actual message data
messageBuf := make([]byte, length)
_, err = io.ReadFull(r, messageBuf)
if err != nil {
log.Printf("Failed to read message data: %v", err)
return
}
// Unmarshal the protobuf message
var msg tunnelerProto.ServerBound
if err := proto.Unmarshal(messageBuf, &msg); err != nil {
log.Printf("Failed to unmarshal protobuf message: %v", err)
return
}
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)
return
}
newPlayerID := createPlayer(false, playerColors[len(players)%len(playerColors)], conn, KeyMap{}, JoyMap{}, nil, gameMap, players)
defer delete(players, newPlayerID)
player := players[newPlayerID]
bases[newPlayerID] = createBase(gameMap,
player.playerColors.body,
uint32(player.gameObject.baseRect.X-14),
uint32(player.gameObject.baseRect.Y-14),
player.playerID,
uint32(float64(player.gameObject.baseRect.W)*1.5))
defer bases[newPlayerID].delete(bases)
netPlayerMapper[conn] = player
fail := player.sendVersionBackToPlayer()
if fail {
return
}
log.Printf("Sent version to %d", newPlayerID)
fail = player.sendServerInfoToPlayer(players)
if fail {
return
}
log.Printf("Sent server info to %d", newPlayerID)
fail = player.sendInfoToPlayer()
if fail {
return
}
log.Printf("Sent info to %d", newPlayerID)
fail = player.sendLocationToPlayer()
if fail {
return
}
log.Printf("Sent location to %d", newPlayerID)
case *tunnelerProto.ServerBound_PlayerPosition:
player := netPlayerMapper[conn]
if player == nil {
return
}
newPosition := msg.GetPlayerPosition()
newX := newPosition.Position.PosX
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,
}
}
player.gameObject.orientation = uint8(newPosition.Orientation)
case *tunnelerProto.ServerBound_PlayerAction:
player := netPlayerMapper[conn]
if player == nil {
return
}
playerAction := msg.GetPlayerAction()
if playerAction == nil {
return
}
switch action := playerAction.PlayerAction.(type) {
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)
case *tunnelerProto.PlayerAction_Shoot:
isSuper := action.Shoot.Super
player.shoot(isSuper, bullets)
}
}
}
}
func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases map[uint32]*Base, bullets map[uint32]*Bullet, bulletParticles map[uint32]*BulletParticle) {
var player *Player
sendVersionToServer(conn)
r := bufio.NewReader(conn)
for {
// Read the length of the incoming message (4 bytes)
lengthBuf := make([]byte, 4)
_, err := io.ReadFull(r, lengthBuf)
if err != nil {
log.Printf("Failed to read message length: %v", err)
os.Exit(0)
}
// Convert length to an integer
length := binary.BigEndian.Uint32(lengthBuf)
// Read the actual message data
messageBuf := make([]byte, length)
_, err = io.ReadFull(r, messageBuf)
if err != nil {
log.Printf("Failed to read message data: %v", err)
os.Exit(0)
}
var msg tunnelerProto.ClientBound
if err := proto.Unmarshal(messageBuf, &msg); err != nil {
log.Printf("Failed to unmarshal protobuf message: %v", err)
}
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)
return
} else {
log.Printf("Connected to %s, running version %s with version %s\n", config.Address, serverVersion, config.Version)
}
case *tunnelerProto.ClientBound_ServerInfo:
serverInfo := msg.GetServerInfo()
serverConfig = ServerConfig{
MapWidth: serverInfo.MapWidth,
MapHeight: serverInfo.MapHeight,
BlastRadius: serverInfo.BlastRadius,
MaxEnergy: serverInfo.MaxEnergy,
MaxAmmunition: serverInfo.MaxAmmunition,
MaxShields: serverInfo.MaxShields,
NormalShotCost: serverInfo.NormalShotCost,
SuperShotCost: serverInfo.SuperShotCost,
ReloadCost: serverInfo.ReloadCost,
MovementCost: serverInfo.MovementCost,
DiggingCost: serverInfo.DiggingCost,
ShootDiggingCostBonus: serverInfo.ShootDiggingBonus,
ShootCooldown: serverInfo.ShootCooldown,
RechargeCooldownOwn: serverInfo.RechargeCooldown,
DiggingCooldown: serverInfo.DiggingCooldown,
RechargeCooldownOpponent: serverInfo.RechargeOpponentCooldown,
RepairCooldown: serverInfo.RepairCooldown,
MovementCooldown: serverInfo.MovementCooldown,
MovementCooldownNoEnergy: serverInfo.MovementCooldownNoEnergy,
DiggingCooldownNoEnergy: serverInfo.DiggingCooldownNoEnergy,
ReloadCooldown: serverInfo.ReloadCooldown,
}
gameMap.createGameMap(false)
lastPlayerID = serverInfo.PlayerID
createPlayer(true, playerColors[serverInfo.PlayerColorID], conn, keyMaps[0], JoyMap{}, nil, gameMap, players)
player = players[serverInfo.PlayerID]
log.Printf("Got server info, now initializing\n")
clientInitialized = true
case *tunnelerProto.ClientBound_PlayerUpdate:
if clientInitialized {
playerUpdate := msg.GetPlayerUpdate()
player.energy = playerUpdate.Energy
player.ammunition = playerUpdate.Ammo
player.shields = playerUpdate.Shields
}
case *tunnelerProto.ClientBound_PlayerLocationUpdate:
if clientInitialized {
playerLocationUpdate := msg.GetPlayerLocationUpdate()
player.gameObject.baseRect.X = playerLocationUpdate.Position.PosX
player.gameObject.baseRect.Y = playerLocationUpdate.Position.PosY
player.gameObject.orientation = uint8(playerLocationUpdate.Orientation)
}
case *tunnelerProto.ClientBound_WorldUpdate:
if clientInitialized {
worldUpdate := msg.GetWorldUpdate()
worldLock.Lock()
for _, playerLoop := range worldUpdate.Players {
playerID := playerLoop.PlayerID
existingPlayer := players[playerID]
if existingPlayer == nil {
lastPlayerID = playerID
createPlayer(false, playerColors[playerID%uint32(len(playerColors))], nil, KeyMap{}, JoyMap{}, nil, gameMap, players)
newPlayer := players[playerID]
newPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
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.baseRect.X = playerLoop.Location.Position.PosX
existingPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY
}
}
for _, base := range worldUpdate.Base {
baseID := base.Owner.PlayerID
existingBase := bases[baseID]
inColor := color.RGBA{
R: uint8(base.Color.Red),
G: uint8(base.Color.Green),
B: uint8(base.Color.Blue),
A: uint8(base.Color.Alpha),
}
if existingBase == nil {
bases[baseID] = createBase(gameMap,
inColor,
uint32(base.Position.PosX),
uint32(base.Position.PosY),
base.Owner.PlayerID,
uint32(int32(float64(player.gameObject.baseRect.W)*1.5)),
)
} else {
existingBase.gameObject.baseRect.X = base.Position.PosX
existingBase.gameObject.baseRect.Y = base.Position.PosY
existingBase.gameObject.colors[0] = inColor
}
}
for _, bullet := range worldUpdate.Bullets {
bulletID := bullet.Id
inColor := color.RGBA{
R: uint8(bullet.Color.Red),
G: uint8(bullet.Color.Green),
B: uint8(bullet.Color.Blue),
A: uint8(bullet.Color.Alpha),
}
existingBullet := bullets[bulletID]
if existingBullet == nil {
bullets[bulletID] = &Bullet{
posX: bullet.Position.PosX,
posY: bullet.Position.PosY,
direction: uint8(bullet.Direction),
color: inColor,
super: bullet.Super,
id: bulletID,
}
} else {
existingBullet.direction = uint8(bullet.Direction)
existingBullet.color = inColor
existingBullet.super = bullet.Super
existingBullet.posX = bullet.Position.PosX
existingBullet.posY = bullet.Position.PosY
}
}
for _, bulletParticle := range worldUpdate.BulletParticles {
bulletParticleID := bulletParticle.Id
inColor := color.RGBA{
R: uint8(bulletParticle.Color.Red),
G: uint8(bulletParticle.Color.Green),
B: uint8(bulletParticle.Color.Blue),
A: uint8(bulletParticle.Color.Alpha),
}
existingBulletParticle := bulletParticles[bulletParticleID]
if existingBulletParticle == nil {
bulletParticles[bulletParticleID] = &BulletParticle{
posX: bulletParticle.Position.PosX,
posY: bulletParticle.Position.PosY,
expirationTimer: bulletParticle.ExpirationTimer,
color: inColor,
id: bulletParticleID,
}
} else {
existingBulletParticle.color = inColor
existingBulletParticle.posX = bulletParticle.Position.PosX
existingBulletParticle.posY = bulletParticle.Position.PosY
existingBulletParticle.expirationTimer = bulletParticle.ExpirationTimer
}
}
for _, tileUpdate := range worldUpdate.TileUpdate {
posX := uint32(tileUpdate.Position.PosX)
posY := uint32(tileUpdate.Position.PosY)
kind := uint8(tileUpdate.Kind)
if posX > 0 && posX < gameMap.width && posY > 0 && posY < gameMap.height {
gameMap.tiles[posX][posY] = kind
}
}
worldLock.Unlock()
}
}
}
}