GOingTunneling/player.go

1565 lines
47 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 playerUpdateMutex sync.Mutex
var lastPlayerID = uint32(0)
var playersMutex sync.RWMutex
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
reloadWait 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) {
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) 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.reloadWait == 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)
}
if player.reloadWait > 0 {
player.reloadWait--
}
}
func (player *Player) digBlock(posX, posY int32, gameMap *GameMap, isShooting bool) uint8 {
collisionAtDigSite := gameMap.checkCollision(posX, posY)
if collisionAtDigSite == 0 {
return 0
} else if collisionAtDigSite == 1 && (player.digCooldown == 0 || isShooting) {
if player.energy > serverConfig.DiggingCost {
if isShooting && player.energy > serverConfig.ShootDiggingCostBonus {
player.energy -= serverConfig.ShootDiggingCostBonus
}
player.digCooldown = serverConfig.DiggingCooldown
} else {
player.digCooldown = serverConfig.DiggingCooldownNoEnergy
}
if isShooting && player.ammunition < serverConfig.MaxAmmunition && rand.Intn(2) == 0 {
player.ammunition++
}
if config.Client {
player.sendDigToServer(uint32(posX), uint32(posY), isShooting)
}
gameMap.tiles[posX][posY] = 0
return 1
}
return 2
}
func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players map[uint32]*Player) (moved bool) {
player.gameObject.adjustBaseRect()
if player.movementCooldown > 0 {
return false
}
ranOutOfEnergy := (isShooting && player.energy <= serverConfig.DiggingCost+serverConfig.ShootDiggingCostBonus) || player.energy <= serverConfig.DiggingCost
if ranOutOfEnergy {
isShooting = false
}
// Define movement deltas based on orientation
movementDeltas := []struct {
dx, dy int32
}{
{0, -1}, {1, 0}, {0, 1}, {-1, 0}, // Up, Right, Down, Left
{1, -1}, {-1, -1}, {1, 1}, {-1, 1}, // Up-Right, Up-Left, Down-Right, Down-Left
}
dx, dy := movementDeltas[player.gameObject.orientation].dx, movementDeltas[player.gameObject.orientation].dy
// Set collision rectangle
player.gameObject.collisionRect = &sdl.Rect{
X: player.gameObject.baseRect.X + oNeg(dx),
Y: player.gameObject.baseRect.Y + oNeg(dy),
W: player.gameObject.baseRect.W + abs(dx),
H: player.gameObject.baseRect.H + abs(dy),
}
// Check for player collision
playersMutex.RLock()
for _, opponent := range players {
if opponent != player && opponent.gameObject.baseRect.HasIntersection(player.gameObject.collisionRect) {
playersMutex.RUnlock()
return false
}
}
playersMutex.RUnlock()
// Initialize flags for movement and digging status
stopped := false
// Check collision and dig at the target position
for x := player.gameObject.baseRect.X + oNeg(dx); x < player.gameObject.baseRect.X+dx+player.gameObject.baseRect.W; x++ {
for y := player.gameObject.baseRect.Y + oNeg(dy); y < player.gameObject.baseRect.Y+dy+player.gameObject.baseRect.H; y++ {
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 { // Successfully dug a block
if !isShooting {
stopped = true // Stop movement if not shooting
}
} else if digResult == 2 { // Block could not be dug (e.g., solid block)
stopped = true
}
if stopped || !(digResult == 0 || (digResult == 1 && isShooting)) {
stopped = true
break
}
}
if stopped {
break
}
}
// Move player if there was no obstruction
if !stopped {
var dxT, dyT int32
if dx > 1 {
dxT = 1
} else if dx < -1 {
dxT = -1
} else {
dxT = dx
}
if dy > 1 {
dyT = 1
} else if dy < -1 {
dyT = -1
} else {
dyT = dy
}
player.gameObject.baseRect.X += dxT
player.gameObject.baseRect.Y += dyT
moved = true
if config.Client {
player.sendPositionToServer()
}
}
// Send the updated position to the server
// Apply movement cooldown
if moved {
if ranOutOfEnergy {
player.movementCooldown = serverConfig.MovementCooldownNoEnergy
} else {
player.movementCooldown = serverConfig.MovementCooldown
}
}
return
}
func abs(dx int32) int32 {
if dx < 0 {
return -dx
} else {
return dx
}
}
func oNeg(dx int32) int32 {
if dx < 0 {
return dx
} else {
return 0
}
}
func (player *Player) getRGBAColor(colorIndex uint8, format *sdl.PixelFormat) uint32 {
var selectedColor color.Color
switch colorIndex {
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--
}
player.reloadWait = serverConfig.ReloadWait
if config.Client {
player.sendShootToServer(super)
} else {
// Set bullet color
bulletColor := player.playerColors.body
bulletMutex.Lock()
// Create and add the bullet
bullets[bulletLastID] = &Bullet{
posX: shootX,
posY: shootY,
direction: player.gameObject.orientation,
color: bulletColor,
super: super,
id: bulletLastID,
ownerID: player.playerID,
}
bulletMutex.Unlock()
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
bulletMutex.Lock()
bullets[bulletLastID] = &Bullet{
posX: x,
posY: y,
direction: uint8(rand.Intn(8)),
color: bulletColor,
super: true,
id: bulletLastID,
ownerID: player.playerID,
}
bulletMutex.Unlock()
bulletLastID++
}
}
}
func getUnusedColor(colors []PlayerColors, players map[uint32]*Player) uint32 {
for i, c := range colors {
foundPlayerWithColor := false
var colorCompare []uint8
clrR, clrG, clrB, clrA := c.tracks.RGBA()
colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA))
clrR, clrG, clrB, clrA = c.body.RGBA()
colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA))
clrR, clrG, clrB, clrA = c.cannon.RGBA()
colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA))
playersMutex.RLock()
for _, player := range players {
var playerColorsCompare []uint8
plrR, plrG, plrB, plrA := player.playerColors.tracks.RGBA()
playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA))
plrR, plrG, plrB, plrA = player.playerColors.body.RGBA()
playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA))
plrR, plrG, plrB, plrA = player.playerColors.cannon.RGBA()
playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA))
foundPlayerWithColor = false
if len(playerColorsCompare) == len(colorCompare) {
for i, c := range colorCompare {
if playerColorsCompare[i] == c {
foundPlayerWithColor = true
} else {
foundPlayerWithColor = false
break
}
}
if foundPlayerWithColor {
break
}
}
}
playersMutex.RUnlock()
if !foundPlayerWithColor {
return uint32(i)
}
}
return uint32(len(colors) - 1)
}
func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players map[uint32]*Player, bases map[uint32]*Base) {
joyStickCount := sdl.NumJoysticks()
if amount > uint8(len(keyMaps)+len(joyMaps)) || amount > uint8(len(keyMaps)+joyStickCount) {
panic("Too many players, not enough inputs")
}
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[getUnusedColor(playerColors, players)],
nil,
keyMap,
joyMap,
joyStick,
gameMap,
players,
bases,
)
}
}
func closeThings(players map[uint32]*Player) {
playersMutex.Lock()
for _, player := range players {
if player.joyStick != nil {
player.joyStick.Close()
}
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()
}
}
playersMutex.Unlock()
}
func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players map[uint32]*Player, bases map[uint32]*Base) uint32 {
coordsAreValid := false
var posX, posY uint32
maxTries := 1000
baseSize := uint32(36) // Since the base is 36x36
minDistance := uint32(200) // Minimum distance between bases
maxDistance := uint32(500) // Maximum distance between bases
for !coordsAreValid && maxTries >= 0 {
maxTries--
posX = uint32(16 + rand.Intn(int(gameMap.width-baseSize-16)))
posY = uint32(16 + rand.Intn(int(gameMap.height-baseSize-16)))
coordsAreValid = true
baseMutex.RLock()
for _, base := range bases {
basePosX := uint32(base.gameObject.baseRect.X)
basePosY := uint32(base.gameObject.baseRect.Y)
// Calculate the distance between the edges of the bases
distanceX := max(0, max(basePosX-posX-baseSize, posX-basePosX-baseSize))
distanceY := max(0, max(basePosY-posY-baseSize, posY-basePosY-baseSize))
distanceSquared := distanceX*distanceX + distanceY*distanceY
if distanceSquared < minDistance*minDistance && distanceSquared > maxDistance*maxDistance {
coordsAreValid = false
break
}
}
baseMutex.RUnlock()
// Edge clamping to ensure the base is within the map boundaries
if posX < 16 || posX > gameMap.width-baseSize-16 || posY < 16 || posY > gameMap.height-baseSize-16 {
coordsAreValid = false
break
}
}
if maxTries < 0 {
panic("Could not place all players, increase map size")
}
gameObject := &GameObject{}
gameObject.baseRect = &sdl.Rect{
X: int32(posX),
Y: int32(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(0, 0, 6, 1, 0)
gameObject.addColoredRect(0, 4, 6, 1, 0)
gameObject.addColoredRect(1, 1, 4, 3, 1)
gameObject.addColoredRect(3, 2, 4, 1, 2)
gameObject.orientation = 2 // Down
gameObject.addColoredRect(0, 0, 1, 6, 0)
gameObject.addColoredRect(4, 0, 1, 6, 0)
gameObject.addColoredRect(1, 1, 3, 4, 1)
gameObject.addColoredRect(2, 3, 1, 4, 2)
gameObject.orientation = 3 // Left
gameObject.addColoredRect(1, 0, 6, 1, 0)
gameObject.addColoredRect(1, 4, 6, 1, 0)
gameObject.addColoredRect(2, 1, 4, 3, 1)
gameObject.addColoredRect(0, 2, 4, 1, 2)
gameObject.orientation = 4 // Up-Right
gameObject.addColoredRect(3, 0, 1, 1, 0)
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
gameObject.adjustBaseRect()
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)
}
playersMutex.Lock()
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,
}
playersMutex.Unlock()
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) updateTile(x, y uint32) {
player.gameMapUpdates = append(player.gameMapUpdates, TileUpdate{
PosX: x,
PosY: y,
Kind: gameMap.tiles[x][y],
})
}
func (player *Player) updateRemotePlayerMap(wgX *sync.WaitGroup) {
defer wgX.Done()
player.gameMapUpdates = nil
fromX := player.gameObject.baseRect.X - (config.CameraW / 2) - 1
fromY := player.gameObject.baseRect.Y - (config.CameraH / 2) - 1
toX := player.gameObject.baseRect.X + (config.CameraW / 2) + 2
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.updateTile(uint32(x), uint32(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, wgX *sync.WaitGroup) {
defer wgX.Done()
player.knownPlayers = make(map[uint32]*Player)
playersMutex.RLock()
for _, playerLoop := range players {
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
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
}
}
}
playersMutex.RUnlock()
}
func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet, wgX *sync.WaitGroup) {
defer wgX.Done()
player.knownBullets = make(map[uint32]*Bullet)
bulletMutex.RLock()
for _, bullet := range bullets {
bulletRect := &sdl.Rect{
X: bullet.posX,
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
}
}
bulletMutex.RUnlock()
}
func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint32]*BulletParticle, wgX *sync.WaitGroup) {
defer wgX.Done()
player.knownBulletParticles = make(map[uint32]*BulletParticle)
bulletParticleMutex.RLock()
for _, bulletParticle := range bulletParticles {
bulletRect := &sdl.Rect{
X: bulletParticle.posX,
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
}
}
bulletParticleMutex.RUnlock()
}
func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base, wgX *sync.WaitGroup) {
defer wgX.Done()
player.knownBases = make(map[uint32]*Base)
baseMutex.RLock()
for _, base := range bases {
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
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
}
}
}
baseMutex.RUnlock()
}
func (player *Player) sendInfoToPlayer(wgX *sync.WaitGroup) bool {
defer wgX.Done()
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) sendUpdatesToPlayer(players map[uint32]*Player, wgX *sync.WaitGroup) bool {
defer wgX.Done()
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 {
playersMutex.RLock()
r, g, b, a := players[knownBase.ownerID].playerColors.body.RGBA()
playersMutex.RUnlock()
basesToSend = append(basesToSend, &tunnelerProto.BaseLocation{
Position: &tunnelerProto.Position{
PosX: knownBase.gameObject.baseRect.X,
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,
OwnerID: knownBullet.ownerID,
})
}
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) sendServerInfoToPlayer(players map[uint32]*Player) bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_ServerInfo{
ServerInfo: &tunnelerProto.ServerInfo{
MaxEnergy: serverConfig.MaxEnergy,
MaxAmmunition: serverConfig.MaxAmmunition,
MaxShields: serverConfig.MaxShields,
MapWidth: serverConfig.MapWidth,
MapHeight: serverConfig.MapHeight,
NormalShotCost: serverConfig.NormalShotCost,
SuperShotCost: serverConfig.SuperShotCost,
ReloadCost: serverConfig.ReloadCost,
MovementCost: serverConfig.MovementCost,
DiggingCost: serverConfig.DiggingCost,
ShootDiggingBonus: serverConfig.ShootDiggingCostBonus,
ShootCooldown: serverConfig.ShootCooldown,
RechargeCooldown: serverConfig.RechargeCooldownOwn,
RechargeOpponentCooldown: serverConfig.RechargeCooldownOpponent,
RepairCooldown: serverConfig.RepairCooldown,
DiggingCooldown: serverConfig.DiggingCooldown,
MovementCooldown: serverConfig.MovementCooldown,
MovementCooldownNoEnergy: serverConfig.MovementCooldownNoEnergy,
DiggingCooldownNoEnergy: serverConfig.DiggingCooldownNoEnergy,
ReloadCooldown: serverConfig.ReloadCooldown,
BlastRadius: serverConfig.BlastRadius,
ReloadWait: serverConfig.ReloadWait,
PlayerID: player.playerID,
PlayerColorID: uint32((len(players) - 1) % len(playerColors)),
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing server info")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendVersionBackToPlayer() bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_PlayerStartResponse{
PlayerStartResponse: &tunnelerProto.PlayerStartResponse{
Version: GameVersion,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing version")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendLocationToPlayer() bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_PlayerLocationUpdate{
PlayerLocationUpdate: &tunnelerProto.PlayerLocation{
Position: &tunnelerProto.Position{
PosX: player.gameObject.baseRect.X,
PosY: player.gameObject.baseRect.Y,
},
Orientation: uint32(player.gameObject.orientation),
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing location")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendPositionToServer() bool {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
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: GameVersion,
},
},
}
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 != GameVersion {
log.Fatalf("Wrong client version, a client connected with %s to %s", clientVersion, GameVersion)
return
}
newPlayerID := createPlayer(false, playerColors[getUnusedColor(playerColors, players)], conn, KeyMap{}, JoyMap{}, nil, gameMap, players, bases)
defer delete(players, newPlayerID)
playersMutex.RLock()
player := players[newPlayerID]
playersMutex.RUnlock()
baseMutex.Lock()
bases[newPlayerID] = createBase(gameMap,
player.playerColors.body,
uint32(player.gameObject.baseRect.X-14),
uint32(player.gameObject.baseRect.Y-14),
player.playerID,
uint32(float64(player.gameObject.baseRect.W)*1.5))
baseMutex.Unlock()
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)
var wgX sync.WaitGroup
wgX.Add(1)
fail = player.sendInfoToPlayer(&wgX)
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
// Update player orientation
player.gameObject.orientation = uint8(newPosition.Orientation)
player.gameObject.adjustBaseRect()
// Check if the new position is adjacent to the old position
isAdjacentX := (newX > oldX && newX-oldX <= 1) || (newX < oldX && oldX-newX <= 1) || newX == oldX
isAdjacentY := (newY > oldY && newY-oldY <= 1) || (newY < oldY && oldY-newY <= 1) || newY == oldY
// Ensure the player is not moving while on cooldown and is moving to an adjacent position
if isAdjacentX && isAdjacentY && player.movementCooldown == 0 {
// Terrain collision check: Loop over all tiles in the new bounding box area
canMove := true
for x := newX; x < newX+player.gameObject.baseRect.W; x++ {
for y := newY; y < newY+player.gameObject.baseRect.H; y++ {
if gameMap.tiles[x][y] != 0 { // Assumes 0 is passable, any other value is impassable
player.knownGameMap.tiles[x][y] = 0
canMove = false
}
}
if !canMove {
break
}
}
// If all tiles in the area are passable, update the player's position
if canMove {
player.gameObject.baseRect = &sdl.Rect{
X: newX,
Y: newY,
W: player.gameObject.baseRect.W,
H: player.gameObject.baseRect.H,
}
// Deduct energy if the player has moved
if newX != oldX || newY != oldY {
if player.energy > serverConfig.MovementCost {
player.energy -= serverConfig.MovementCost
player.movementCooldown = serverConfig.MovementCooldown
} else {
player.movementCooldown = serverConfig.MovementCooldownNoEnergy
}
}
} else {
// If any part of the new area is impassable, revert to the old position
player.sendLocationToPlayer()
}
} else {
// If the movement is invalid (not adjacent or on cooldown), revert to the old position
player.sendLocationToPlayer()
}
case *tunnelerProto.ServerBound_PlayerAction:
player := netPlayerMapper[conn]
if player == nil {
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
posX := position.PosX
posY := position.PosY
res := player.digBlock(posX, posY, gameMap, isShooting)
if res == 1 {
if player.energy > serverConfig.DiggingCost {
player.energy -= serverConfig.DiggingCost
}
}
case *tunnelerProto.PlayerAction_Shoot:
isSuper := action.Shoot.Super
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 != GameVersion {
log.Fatalf("Wrong server version, connected with %s to %s", GameVersion, serverVersion)
return
} else {
log.Printf("Connected to %s, running version %s with version %s\n", config.Address, serverVersion, GameVersion)
}
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,
ReloadWait: serverInfo.ReloadWait,
}
gameMap.createGameMap(false)
lastPlayerID = serverInfo.PlayerID
createPlayer(true, playerColors[serverInfo.PlayerColorID], conn, keyMaps[0], JoyMap{}, nil, gameMap, players, bases)
playersMutex.RLock()
player = players[serverInfo.PlayerID]
playersMutex.RUnlock()
log.Printf("Got server info, now initializing\n")
clientInitialized = true
case *tunnelerProto.ClientBound_PlayerUpdate:
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)
player.gameObject.adjustBaseRect()
}
case *tunnelerProto.ClientBound_WorldUpdate:
if clientInitialized {
worldUpdate := msg.GetWorldUpdate()
worldLock.Lock()
for _, playerLoop := range worldUpdate.Players {
playerID := playerLoop.PlayerID
playersMutex.RLock()
existingPlayer := players[playerID]
playersMutex.RUnlock()
if existingPlayer == nil {
lastPlayerID = playerID
createPlayer(false, playerColors[playerID%uint32(len(playerColors))], nil, KeyMap{}, JoyMap{}, nil, gameMap, players, bases)
playersMutex.RLock()
newPlayer := players[playerID]
playersMutex.RUnlock()
newPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
newPlayer.gameObject.adjustBaseRect()
newPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX
newPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY
} else {
existingPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
existingPlayer.gameObject.adjustBaseRect()
existingPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX
existingPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY
}
}
for _, base := range worldUpdate.Base {
baseID := base.Owner.PlayerID
baseMutex.RLock()
existingBase := bases[baseID]
baseMutex.RUnlock()
inColor := color.RGBA{
R: uint8(base.Color.Red),
G: uint8(base.Color.Green),
B: uint8(base.Color.Blue),
A: uint8(base.Color.Alpha),
}
if existingBase == nil {
baseMutex.Lock()
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)),
)
baseMutex.Unlock()
} 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 {
bulletMutex.Lock()
bullets[bulletID] = &Bullet{
posX: bullet.Position.PosX,
posY: bullet.Position.PosY,
direction: uint8(bullet.Direction),
color: inColor,
super: bullet.Super,
id: bulletID,
ownerID: bullet.OwnerID,
}
bulletMutex.Unlock()
} 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),
}
bulletParticleMutex.RLock()
existingBulletParticle := bulletParticles[bulletParticleID]
bulletParticleMutex.RUnlock()
if existingBulletParticle == nil {
bulletParticleMutex.Lock()
bulletParticles[bulletParticleID] = &BulletParticle{
posX: bulletParticle.Position.PosX,
posY: bulletParticle.Position.PosY,
expirationTimer: bulletParticle.ExpirationTimer,
color: inColor,
id: bulletParticleID,
}
bulletParticleMutex.Unlock()
} 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()
}
}
}
}