GOingTunneling/player.go

1550 lines
47 KiB
Go
Raw Normal View History

2024-08-29 00:05:28 +02:00
package main
import (
2024-08-30 19:07:56 +02:00
"bufio"
"bytes"
"encoding/binary"
2024-08-29 00:05:28 +02:00
"github.com/veandco/go-sdl2/sdl"
2024-08-30 19:07:56 +02:00
tunnelerProto "goingtunneling/proto"
"google.golang.org/protobuf/proto"
2024-08-29 00:05:28 +02:00
"image/color"
2024-08-30 19:07:56 +02:00
"io"
"log"
2024-08-29 00:05:28 +02:00
"math/rand"
2024-08-30 19:07:56 +02:00
"net"
"os"
"sync"
2024-08-29 00:05:28 +02:00
)
2024-08-31 15:35:19 +02:00
var playerUpdateMutex sync.Mutex
2024-08-30 19:07:56 +02:00
var lastPlayerID = uint32(0)
2024-08-31 15:35:19 +02:00
var playersMutex sync.RWMutex
2024-08-30 19:07:56 +02:00
2024-08-29 00:05:28 +02:00
type Player struct {
2024-08-30 19:07:56 +02:00
local bool
2024-08-30 21:20:41 +02:00
connection *net.TCPConn
2024-08-30 19:07:56 +02:00
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
2024-08-31 15:35:19 +02:00
reloadWait uint32
2024-08-30 19:07:56 +02:00
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
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
window *sdl.Window
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
logicalSurface *sdl.Surface
playSurface *sdl.Surface
HUDSurface *sdl.Surface
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
playSurfaceRect *sdl.Rect
HUDSurfaceRect *sdl.Rect
2024-08-29 00:05:28 +02:00
2024-08-29 18:48:27 +02:00
playSurfaceTargetRect *sdl.Rect
HUDSurfaceTargetRect *sdl.Rect
2024-08-29 21:12:18 +02:00
totalHandleEventsTime uint64
totalRenderTime uint64
totalFrameTime uint64
totalScalingTime uint64
frameCount uint64
2024-08-29 18:48:27 +02:00
}
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
type TileUpdate struct {
PosX uint32
PosY uint32
Kind uint8
}
2024-08-29 18:48:27 +02:00
func (player *Player) track(camera *sdl.Rect) {
camera.X = player.gameObject.baseRect.X - 37
camera.Y = player.gameObject.baseRect.Y - 38
}
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
func (player *Player) render(camera *sdl.Rect, surface *sdl.Surface, players map[uint32]*Player) {
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
if player.shields > 0 &&
player.shields <= serverConfig.MaxShields &&
player.gameObject.baseRect.X >= 0 &&
player.gameObject.baseRect.Y >= 0 {
2024-08-29 18:48:27 +02:00
player.gameObject.render(camera, surface)
2024-08-30 19:07:56 +02:00
if !player.gameObject.inView && !config.Server {
delete(players, player.playerID)
}
2024-08-29 00:05:28 +02:00
}
}
2024-08-30 19:07:56 +02:00
func (player *Player) tick(bullets map[uint32]*Bullet) {
2024-08-29 00:05:28 +02:00
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--
}
2024-08-29 18:48:27 +02:00
if player.reloadCooldown > 0 {
player.reloadCooldown--
2024-08-31 15:35:19 +02:00
} else if player.ammunition < serverConfig.MaxAmmunition && player.shootCooldown == 0 && player.reloadWait == 0 && player.energy > serverConfig.ReloadCost {
2024-08-29 18:48:27 +02:00
player.ammunition++
2024-08-30 19:07:56 +02:00
player.reloadCooldown = serverConfig.ReloadCooldown
player.energy -= serverConfig.ReloadCost
2024-08-29 18:48:27 +02:00
}
if player.shields <= 0 {
2024-08-30 19:07:56 +02:00
player.shields = serverConfig.MaxShields + 1
2024-08-29 18:48:27 +02:00
player.explode(bullets)
}
2024-08-31 15:35:19 +02:00
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
2024-08-29 00:05:28 +02:00
}
2024-08-30 19:07:56 +02:00
func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players map[uint32]*Player) (moved bool) {
2024-08-31 15:35:19 +02:00
player.gameObject.adjustBaseRect()
2024-08-29 00:05:28 +02:00
if player.movementCooldown > 0 {
return false
}
2024-08-31 15:35:19 +02:00
2024-08-30 19:07:56 +02:00
ranOutOfEnergy := (isShooting && player.energy <= serverConfig.DiggingCost+serverConfig.ShootDiggingCostBonus) || player.energy <= serverConfig.DiggingCost
2024-08-29 00:05:28 +02:00
if ranOutOfEnergy {
isShooting = false
}
2024-08-31 15:35:19 +02:00
// 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
}
2024-08-29 00:05:28 +02:00
2024-08-31 15:35:19 +02:00
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),
}
2024-08-29 00:05:28 +02:00
2024-08-31 15:35:19 +02:00
// Check for player collision
playersMutex.RLock()
for _, opponent := range players {
if opponent != player && opponent.gameObject.baseRect.HasIntersection(player.gameObject.collisionRect) {
playersMutex.RUnlock()
return false
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
}
playersMutex.RUnlock()
2024-08-29 00:05:28 +02:00
2024-08-31 15:35:19 +02:00
// Initialize flags for movement and digging status
stopped := false
2024-08-29 00:05:28 +02:00
2024-08-31 15:35:19 +02:00
// 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
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
} else if digResult == 2 { // Block could not be dug (e.g., solid block)
stopped = true
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
if stopped || !(digResult == 0 || (digResult == 1 && isShooting)) {
stopped = true
break
2024-08-29 00:05:28 +02:00
}
}
2024-08-31 15:35:19 +02:00
if stopped {
break
2024-08-29 00:05:28 +02:00
}
}
2024-08-31 15:35:19 +02:00
// 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
2024-08-29 18:48:27 +02:00
}
2024-08-31 15:35:19 +02:00
if dy > 1 {
dyT = 1
} else if dy < -1 {
dyT = -1
} else {
dyT = dy
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
player.gameObject.baseRect.X += dxT
player.gameObject.baseRect.Y += dyT
moved = true
if config.Client {
player.sendPositionToServer()
2024-08-29 00:05:28 +02:00
}
}
2024-08-31 15:35:19 +02:00
// Send the updated position to the server
// Apply movement cooldown
2024-08-29 00:05:28 +02:00
if moved {
if ranOutOfEnergy {
2024-08-30 19:07:56 +02:00
player.movementCooldown = serverConfig.MovementCooldownNoEnergy
2024-08-29 00:05:28 +02:00
} else {
2024-08-30 19:07:56 +02:00
player.movementCooldown = serverConfig.MovementCooldown
2024-08-29 00:05:28 +02:00
}
}
2024-08-31 15:35:19 +02:00
2024-08-29 00:05:28 +02:00
return
}
2024-08-31 15:35:19 +02:00
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
}
}
2024-08-29 18:48:27 +02:00
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)
}
type Point struct {
X int32
Y int32
}
2024-08-30 19:07:56 +02:00
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)) {
2024-08-29 00:05:28 +02:00
return
}
if player.shootCooldown == 0 {
var shootX, shootY int32
offsets := []Point{
{X: 2, Y: 0}, // 0: Up
{X: 6, Y: 2}, // 1: Right
{X: 2, Y: 6}, // 2: Down
{X: 0, Y: 2}, // 3: Left
{X: 6, Y: 0}, // 4: Up-Right
{X: 0, Y: 0}, // 5: Up-Left
{X: 5, Y: 5}, // 6: Down-Right
{X: 0, Y: 6}, // 7: Down-Left
2024-08-29 00:05:28 +02:00
}
// Access the offset based on the player's orientation
shootX = player.gameObject.baseRect.X + offsets[player.gameObject.orientation].X
shootY = player.gameObject.baseRect.Y + offsets[player.gameObject.orientation].Y
2024-08-30 19:07:56 +02:00
player.shootCooldown = serverConfig.ShootCooldown
2024-08-29 00:05:28 +02:00
// Set cooldown and decrease energy
if super {
2024-08-30 19:07:56 +02:00
player.energy -= serverConfig.SuperShotCost
2024-08-29 18:48:27 +02:00
player.ammunition = 0
2024-08-29 00:05:28 +02:00
} else {
2024-08-30 19:07:56 +02:00
player.energy -= serverConfig.NormalShotCost
2024-08-29 18:48:27 +02:00
player.ammunition--
}
2024-08-31 15:35:19 +02:00
player.reloadWait = serverConfig.ReloadWait
2024-08-30 19:07:56 +02:00
if config.Client {
player.sendShootToServer(super)
} else {
// Set bullet color
bulletColor := player.playerColors.body
2024-08-31 15:35:19 +02:00
bulletMutex.Lock()
2024-08-30 19:07:56 +02:00
// Create and add the bullet
bullets[bulletLastID] = &Bullet{
posX: shootX,
posY: shootY,
direction: player.gameObject.orientation,
color: bulletColor,
super: super,
id: bulletLastID,
2024-08-31 15:35:19 +02:00
ownerID: player.playerID,
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
bulletMutex.Unlock()
2024-08-30 19:07:56 +02:00
bulletLastID++
}
2024-08-29 18:48:27 +02:00
}
}
2024-08-30 19:07:56 +02:00
func (player *Player) explode(bullets map[uint32]*Bullet) {
2024-08-29 18:48:27 +02:00
// Set bullet color
bulletColor := player.playerColors.body
2024-08-30 19:07:56 +02:00
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++ {
2024-08-29 18:48:27 +02:00
// Create and add the bullet
2024-08-31 15:35:19 +02:00
bulletMutex.Lock()
2024-08-30 19:07:56 +02:00
bullets[bulletLastID] = &Bullet{
2024-08-29 18:48:27 +02:00
posX: x,
posY: y,
direction: uint8(rand.Intn(8)),
color: bulletColor,
super: true,
2024-08-30 19:07:56 +02:00
id: bulletLastID,
2024-08-31 15:35:19 +02:00
ownerID: player.playerID,
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
bulletMutex.Unlock()
2024-08-30 19:07:56 +02:00
bulletLastID++
2024-08-29 18:48:27 +02:00
2024-08-29 00:05:28 +02:00
}
}
}
2024-08-31 15:35:19 +02:00
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) {
2024-08-29 18:48:27 +02:00
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
2024-08-30 19:07:56 +02:00
if (config.DoAllKeymapsPlayers && i <= uint8(len(keyMaps))) || (config.DoKeymapPlayer && i == 0) || (uint8(joyStickCount) <= i) {
keyMap = keyMaps[(addedKeyboardPlayers+int(config.KeyBindOffset))%(len(keyMaps)-1)]
2024-08-29 18:48:27 +02:00
addedKeyboardPlayers++
} else {
joyStickIndex := i - uint8(addedKeyboardPlayers)
joyMap = joyMaps[joyStickIndex]
joyStick = sdl.JoystickOpen(int(joyStickIndex))
}
createPlayer(
2024-08-30 19:07:56 +02:00
true,
2024-08-31 15:35:19 +02:00
playerColors[getUnusedColor(playerColors, players)],
2024-08-30 19:07:56 +02:00
nil,
2024-08-29 18:48:27 +02:00
keyMap,
joyMap,
joyStick,
gameMap,
players,
2024-08-31 15:35:19 +02:00
bases,
2024-08-29 18:48:27 +02:00
)
}
}
2024-08-30 19:07:56 +02:00
func closeThings(players map[uint32]*Player) {
2024-08-31 15:35:19 +02:00
playersMutex.Lock()
2024-08-30 19:07:56 +02:00
for _, player := range players {
2024-08-29 18:48:27 +02:00
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()
}
}
2024-08-31 15:35:19 +02:00
playersMutex.Unlock()
2024-08-29 18:48:27 +02:00
}
2024-08-31 15:35:19 +02:00
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 {
2024-08-29 00:05:28 +02:00
coordsAreValid := false
2024-08-31 15:35:19 +02:00
var posX, posY uint32
2024-08-29 18:48:27 +02:00
maxTries := 1000
2024-08-31 15:35:19 +02:00
baseSize := uint32(36) // Since the base is 36x36
minDistance := uint32(200) // Minimum distance between bases
maxDistance := uint32(500) // Maximum distance between bases
2024-08-29 18:48:27 +02:00
for !coordsAreValid && maxTries >= 0 {
maxTries--
2024-08-31 15:35:19 +02:00
posX = uint32(16 + rand.Intn(int(gameMap.width-baseSize-16)))
posY = uint32(16 + rand.Intn(int(gameMap.height-baseSize-16)))
2024-08-29 00:05:28 +02:00
coordsAreValid = true
2024-08-31 15:35:19 +02:00
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 {
2024-08-29 00:05:28 +02:00
coordsAreValid = false
break
}
}
2024-08-31 15:35:19 +02:00
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 {
2024-08-29 18:48:27 +02:00
coordsAreValid = false
break
}
2024-08-29 00:05:28 +02:00
}
2024-08-31 15:35:19 +02:00
2024-08-29 18:48:27 +02:00
if maxTries < 0 {
panic("Could not place all players, increase map size")
}
gameObject := &GameObject{}
2024-08-31 15:35:19 +02:00
gameObject.baseRect = &sdl.Rect{
X: int32(posX),
Y: int32(posY),
2024-08-29 18:48:27 +02:00
W: 7,
H: 7,
}
2024-08-30 19:07:56 +02:00
gameObject.addColor(thisPlayerColors.tracks)
gameObject.addColor(thisPlayerColors.body)
gameObject.addColor(thisPlayerColors.cannon)
2024-08-29 18:48:27 +02:00
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
2024-08-31 15:35:19 +02:00
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)
2024-08-29 18:48:27 +02:00
gameObject.orientation = 2 // Down
2024-08-31 15:35:19 +02:00
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)
2024-08-29 18:48:27 +02:00
gameObject.orientation = 3 // Left
2024-08-31 15:35:19 +02:00
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)
2024-08-29 18:48:27 +02:00
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
2024-08-31 15:35:19 +02:00
gameObject.adjustBaseRect()
2024-08-29 00:05:28 +02:00
2024-08-30 19:07:56 +02:00
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)
}
2024-08-31 15:35:19 +02:00
playersMutex.Lock()
2024-08-30 19:07:56 +02:00
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,
}
2024-08-31 15:35:19 +02:00
playersMutex.Unlock()
2024-08-30 19:07:56 +02:00
lastPlayerID++
return lastPlayerID - 1
}
2024-08-30 21:20:41 +02:00
func sendMessageRaw(data []byte, conn *net.TCPConn) (fail bool) {
2024-08-30 19:07:56 +02:00
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)
}
2024-08-31 15:35:19 +02:00
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
2024-08-30 19:07:56 +02:00
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()
2024-08-31 15:35:19 +02:00
player.updateTile(uint32(x), uint32(y))
2024-08-30 19:07:56 +02:00
player.knownGameMap.tiles[x][y] = gameMap.tiles[x][y]
lck.Unlock()
}
}
}(x)
}
// Wait for all goroutines to finish
wg.Wait()
}
2024-08-31 15:35:19 +02:00
func (player *Player) updateRemotePlayerPlayers(players map[uint32]*Player, wgX *sync.WaitGroup) {
defer wgX.Done()
2024-08-30 19:07:56 +02:00
player.knownPlayers = make(map[uint32]*Player)
2024-08-31 15:35:19 +02:00
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
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
}
}
}
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet, wgX *sync.WaitGroup) {
defer wgX.Done()
2024-08-30 19:07:56 +02:00
player.knownBullets = make(map[uint32]*Bullet)
2024-08-31 15:35:19 +02:00
bulletMutex.RLock()
2024-08-30 19:07:56 +02:00
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
}
}
2024-08-31 15:35:19 +02:00
bulletMutex.RUnlock()
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint32]*BulletParticle, wgX *sync.WaitGroup) {
defer wgX.Done()
2024-08-30 19:07:56 +02:00
player.knownBulletParticles = make(map[uint32]*BulletParticle)
2024-08-31 15:35:19 +02:00
bulletParticleMutex.RLock()
2024-08-30 19:07:56 +02:00
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
}
}
2024-08-31 15:35:19 +02:00
bulletParticleMutex.RUnlock()
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base, wgX *sync.WaitGroup) {
defer wgX.Done()
2024-08-30 19:07:56 +02:00
player.knownBases = make(map[uint32]*Base)
2024-08-31 15:35:19 +02:00
baseMutex.RLock()
2024-08-30 19:07:56 +02:00
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
}
}
}
2024-08-31 15:35:19 +02:00
baseMutex.RUnlock()
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
func (player *Player) sendInfoToPlayer(wgX *sync.WaitGroup) bool {
defer wgX.Done()
2024-08-30 19:07:56 +02:00
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
}
2024-08-31 15:35:19 +02:00
func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player, wgX *sync.WaitGroup) bool {
defer wgX.Done()
2024-08-30 19:07:56 +02:00
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 {
2024-08-31 15:35:19 +02:00
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
r, g, b, a := players[knownBase.ownerID].playerColors.body.RGBA()
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
2024-08-30 19:07:56 +02:00
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,
},
2024-08-31 15:35:19 +02:00
Id: knownBullet.id,
OwnerID: knownBullet.ownerID,
2024-08-30 19:07:56 +02:00
})
}
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
}
2024-08-31 15:35:19 +02:00
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
}
2024-08-30 19:07:56 +02:00
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
}
2024-08-30 21:20:41 +02:00
func sendVersionToServer(connection *net.TCPConn) {
2024-08-30 19:07:56 +02:00
if connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerStartRequest{
PlayerStartRequest: &tunnelerProto.PlayerStartRequest{
2024-08-31 15:35:19 +02:00
Version: GameVersion,
2024-08-30 19:07:56 +02:00
},
},
}
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)
}
}
2024-08-30 21:20:41 +02:00
func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[uint32]*Bullet, bases map[uint32]*Base) {
2024-08-30 19:07:56 +02:00
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
2024-08-31 15:35:19 +02:00
if clientVersion != GameVersion {
log.Fatalf("Wrong client version, a client connected with %s to %s", clientVersion, GameVersion)
2024-08-30 19:07:56 +02:00
return
}
2024-08-31 15:35:19 +02:00
newPlayerID := createPlayer(false, playerColors[getUnusedColor(playerColors, players)], conn, KeyMap{}, JoyMap{}, nil, gameMap, players, bases)
2024-08-30 19:07:56 +02:00
defer delete(players, newPlayerID)
2024-08-31 15:35:19 +02:00
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
player := players[newPlayerID]
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
baseMutex.Lock()
2024-08-30 19:07:56 +02:00
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))
2024-08-31 15:35:19 +02:00
baseMutex.Unlock()
2024-08-30 19:07:56 +02:00
defer bases[newPlayerID].delete(bases)
2024-08-30 21:20:41 +02:00
netPlayerMapper[conn] = player
2024-08-30 19:07:56 +02:00
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)
2024-08-31 15:35:19 +02:00
var wgX sync.WaitGroup
wgX.Add(1)
fail = player.sendInfoToPlayer(&wgX)
2024-08-30 19:07:56 +02:00
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:
2024-08-30 21:20:41 +02:00
player := netPlayerMapper[conn]
2024-08-30 19:07:56 +02:00
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
2024-08-31 15:35:19 +02:00
// 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
}
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
// 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()
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
2024-08-30 19:07:56 +02:00
case *tunnelerProto.ServerBound_PlayerAction:
2024-08-30 21:20:41 +02:00
player := netPlayerMapper[conn]
2024-08-30 19:07:56 +02:00
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
2024-08-31 15:35:19 +02:00
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
}
}
2024-08-30 19:07:56 +02:00
case *tunnelerProto.PlayerAction_Shoot:
isSuper := action.Shoot.Super
player.shoot(isSuper, bullets)
}
}
}
}
2024-08-30 21:20:41 +02:00
func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases map[uint32]*Base, bullets map[uint32]*Bullet, bulletParticles map[uint32]*BulletParticle) {
2024-08-30 19:07:56 +02:00
var player *Player
sendVersionToServer(conn)
2024-08-30 21:20:41 +02:00
r := bufio.NewReader(conn)
2024-08-30 19:07:56 +02:00
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
2024-08-31 15:35:19 +02:00
if serverVersion != GameVersion {
log.Fatalf("Wrong server version, connected with %s to %s", GameVersion, serverVersion)
2024-08-30 19:07:56 +02:00
return
} else {
2024-08-31 15:35:19 +02:00
log.Printf("Connected to %s, running version %s with version %s\n", config.Address, serverVersion, GameVersion)
2024-08-30 19:07:56 +02:00
}
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,
2024-08-31 15:35:19 +02:00
ReloadWait: serverInfo.ReloadWait,
2024-08-30 19:07:56 +02:00
}
gameMap.createGameMap(false)
lastPlayerID = serverInfo.PlayerID
createPlayer(true, playerColors[serverInfo.PlayerColorID], conn, keyMaps[config.KeyBindOffset], JoyMap{}, nil, gameMap, players, bases)
2024-08-31 15:35:19 +02:00
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
player = players[serverInfo.PlayerID]
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
2024-08-30 19:07:56 +02:00
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)
2024-08-31 15:35:19 +02:00
player.gameObject.adjustBaseRect()
2024-08-30 19:07:56 +02:00
}
case *tunnelerProto.ClientBound_WorldUpdate:
if clientInitialized {
worldUpdate := msg.GetWorldUpdate()
worldLock.Lock()
for _, playerLoop := range worldUpdate.Players {
playerID := playerLoop.PlayerID
2024-08-31 15:35:19 +02:00
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
existingPlayer := players[playerID]
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
2024-08-30 19:07:56 +02:00
if existingPlayer == nil {
lastPlayerID = playerID
2024-08-31 15:35:19 +02:00
createPlayer(false, playerColors[playerID%uint32(len(playerColors))], nil, KeyMap{}, JoyMap{}, nil, gameMap, players, bases)
playersMutex.RLock()
2024-08-30 19:07:56 +02:00
newPlayer := players[playerID]
2024-08-31 15:35:19 +02:00
playersMutex.RUnlock()
2024-08-30 19:07:56 +02:00
newPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
2024-08-31 15:35:19 +02:00
newPlayer.gameObject.adjustBaseRect()
2024-08-30 19:07:56 +02:00
newPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX
newPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY
} else {
existingPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
2024-08-31 15:35:19 +02:00
existingPlayer.gameObject.adjustBaseRect()
2024-08-30 19:07:56 +02:00
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
2024-08-31 15:35:19 +02:00
baseMutex.RLock()
2024-08-30 19:07:56 +02:00
existingBase := bases[baseID]
2024-08-31 15:35:19 +02:00
baseMutex.RUnlock()
2024-08-30 19:07:56 +02:00
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 {
2024-08-31 15:35:19 +02:00
baseMutex.Lock()
2024-08-30 19:07:56 +02:00
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)),
)
2024-08-31 15:35:19 +02:00
baseMutex.Unlock()
2024-08-30 19:07:56 +02:00
} 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 {
2024-08-31 15:35:19 +02:00
bulletMutex.Lock()
2024-08-30 19:07:56 +02:00
bullets[bulletID] = &Bullet{
posX: bullet.Position.PosX,
posY: bullet.Position.PosY,
direction: uint8(bullet.Direction),
color: inColor,
super: bullet.Super,
id: bulletID,
2024-08-31 15:35:19 +02:00
ownerID: bullet.OwnerID,
2024-08-30 19:07:56 +02:00
}
2024-08-31 15:35:19 +02:00
bulletMutex.Unlock()
2024-08-30 19:07:56 +02:00
} 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),
}
2024-08-31 15:35:19 +02:00
bulletParticleMutex.RLock()
2024-08-30 19:07:56 +02:00
existingBulletParticle := bulletParticles[bulletParticleID]
2024-08-31 15:35:19 +02:00
bulletParticleMutex.RUnlock()
2024-08-30 19:07:56 +02:00
if existingBulletParticle == nil {
2024-08-31 15:35:19 +02:00
bulletParticleMutex.Lock()
2024-08-30 19:07:56 +02:00
bulletParticles[bulletParticleID] = &BulletParticle{
posX: bulletParticle.Position.PosX,
posY: bulletParticle.Position.PosY,
expirationTimer: bulletParticle.ExpirationTimer,
color: inColor,
id: bulletParticleID,
}
2024-08-31 15:35:19 +02:00
bulletParticleMutex.Unlock()
2024-08-30 19:07:56 +02:00
} 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()
}
}
}
2024-08-29 00:05:28 +02:00
}