GOingTunneling/player.go

1581 lines
46 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-30 19:07:56 +02:00
var lastPlayerID = uint32(0)
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
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-29 18:48:27 +02:00
// Common part: Rendering player
player.gameObject.baseRect = sdl.Rect{X: player.gameObject.baseRect.X, Y: player.gameObject.baseRect.Y, W: 7, H: 7}
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
}
}
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) {
2024-08-30 19:07:56 +02:00
player.digCooldown = serverConfig.DiggingCooldown
if config.Client {
player.sendDigToServer(uint32(posX), uint32(posY), isShooting)
return 2
}
2024-08-29 00:05:28 +02:00
gameMap.tiles[posX][posY] = 0
return 1
}
return 2
}
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-30 19:07:56 +02:00
} else if player.ammunition < serverConfig.MaxAmmunition && player.shootCooldown == 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-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-29 00:05:28 +02:00
if player.movementCooldown > 0 {
return false
}
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
}
shouldPenalizeDigging := false
stopped := false
2024-08-29 18:48:27 +02:00
switch player.gameObject.orientation {
2024-08-29 00:05:28 +02:00
case 0: // Up
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X,
Y: player.gameObject.baseRect.Y - 1,
2024-08-29 00:05:28 +02:00
W: 5,
H: 7,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) && opponent.shields <= serverConfig.MaxShields {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for x := player.gameObject.baseRect.X; x < player.gameObject.baseRect.X+5 && !stopped; x++ {
for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+7; y++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.Y--
2024-08-29 00:05:28 +02:00
}
case 1: // Right
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X + 1,
Y: player.gameObject.baseRect.Y,
2024-08-29 00:05:28 +02:00
W: 7,
H: 5,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+6 && !stopped; y++ {
for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8; x++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.X++
2024-08-29 00:05:28 +02:00
}
case 2: // Down
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X,
Y: player.gameObject.baseRect.Y + 1,
2024-08-29 00:05:28 +02:00
W: 5,
H: 7,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for x := player.gameObject.baseRect.X; x < player.gameObject.baseRect.X+5 && !stopped; x++ {
for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.Y++
2024-08-29 00:05:28 +02:00
}
case 3: // Left
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X - 1,
Y: player.gameObject.baseRect.Y,
2024-08-29 00:05:28 +02:00
W: 7,
H: 5,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+6 && !stopped; y++ {
for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6; x++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.X--
2024-08-29 00:05:28 +02:00
}
case 4: // Up-Right
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X + 1,
Y: player.gameObject.baseRect.Y - 1,
2024-08-29 00:05:28 +02:00
W: 7,
H: 7,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8 && !stopped; x++ {
for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+6; y++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.X++
player.gameObject.baseRect.Y--
2024-08-29 00:05:28 +02:00
}
case 5: // Up-Left
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X - 1,
Y: player.gameObject.baseRect.Y - 1,
2024-08-29 00:05:28 +02:00
W: 7,
H: 7,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6 && !stopped; x++ {
for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+6; y++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.X--
player.gameObject.baseRect.Y--
2024-08-29 00:05:28 +02:00
}
case 6: // Down-Right
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X + 1,
Y: player.gameObject.baseRect.Y + 1,
2024-08-29 00:05:28 +02:00
W: 7,
H: 7,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8 && !stopped; x++ {
for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.X++
player.gameObject.baseRect.Y++
2024-08-29 00:05:28 +02:00
}
case 7: // Down-Left
collisionRect := sdl.Rect{
2024-08-29 18:48:27 +02:00
X: player.gameObject.baseRect.X - 1,
Y: player.gameObject.baseRect.Y + 1,
2024-08-29 00:05:28 +02:00
W: 7,
H: 7,
}
2024-08-30 19:07:56 +02:00
for _, opponent := range players {
2024-08-29 18:48:27 +02:00
if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) {
2024-08-29 00:05:28 +02:00
return false
}
}
2024-08-29 18:48:27 +02:00
for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6 && !stopped; x++ {
for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ {
2024-08-29 00:05:28 +02:00
digResult := player.digBlock(x, y, gameMap, isShooting)
if digResult == 1 {
shouldPenalizeDigging = true
if !isShooting {
stopped = true
break
}
} else if digResult != 0 {
stopped = true
break
}
}
}
if !stopped {
moved = true
2024-08-29 18:48:27 +02:00
player.gameObject.baseRect.X--
player.gameObject.baseRect.Y++
2024-08-29 00:05:28 +02:00
}
}
2024-08-30 19:07:56 +02:00
if config.Client {
player.sendPositionToServer()
}
2024-08-29 00:05:28 +02:00
// Penalties and cooldown handling
if shouldPenalizeDigging {
2024-08-30 19:07:56 +02:00
if isShooting && player.ammunition < serverConfig.MaxAmmunition && rand.Intn(2) == 0 {
2024-08-29 18:48:27 +02:00
player.ammunition++
}
2024-08-29 00:05:28 +02:00
if ranOutOfEnergy {
2024-08-30 19:07:56 +02:00
player.digCooldown = serverConfig.DiggingCooldownNoEnergy
2024-08-29 00:05:28 +02:00
}
2024-08-30 19:07:56 +02:00
player.energy -= serverConfig.DiggingCost
2024-08-29 00:05:28 +02:00
if isShooting {
2024-08-30 19:07:56 +02:00
player.energy -= serverConfig.ShootDiggingCostBonus
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
}
}
return
}
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)
}
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
2024-08-29 18:48:27 +02:00
switch player.gameObject.orientation {
2024-08-29 00:05:28 +02:00
case 0: // Up
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y - 1
shootX = player.gameObject.baseRect.X + 2
2024-08-29 00:05:28 +02:00
break
case 1: // Right
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y + 3
shootX = player.gameObject.baseRect.X + 8
2024-08-29 00:05:28 +02:00
break
case 2: // Down
2024-08-29 18:48:27 +02:00
shootX = player.gameObject.baseRect.X + 2
shootY = player.gameObject.baseRect.Y + 8
2024-08-29 00:05:28 +02:00
break
case 3: // Left
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y + 3
shootX = player.gameObject.baseRect.X - 2
2024-08-29 00:05:28 +02:00
break
case 4: // Up-Right
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y
shootX = player.gameObject.baseRect.X + 5
2024-08-29 00:05:28 +02:00
break
case 5: // Up-Left
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y
shootX = player.gameObject.baseRect.X - 1
2024-08-29 00:05:28 +02:00
break
case 6: // Down-Right
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y + 5
shootX = player.gameObject.baseRect.X + 5
2024-08-29 00:05:28 +02:00
break
case 7: // Down-Left
2024-08-29 18:48:27 +02:00
shootY = player.gameObject.baseRect.Y + 5
shootX = player.gameObject.baseRect.X - 1
2024-08-29 00:05:28 +02:00
break
}
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-30 19:07:56 +02:00
if config.Client {
player.sendShootToServer(super)
} else {
// Set bullet color
bulletColor := player.playerColors.body
// Create and add the bullet
bullets[bulletLastID] = &Bullet{
posX: shootX,
posY: shootY,
direction: player.gameObject.orientation,
color: bulletColor,
super: super,
id: bulletLastID,
}
bulletLastID++
}
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-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,
}
bulletLastID++
2024-08-29 18:48:27 +02:00
2024-08-29 00:05:28 +02:00
}
}
}
2024-08-30 19:07:56 +02:00
func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players map[uint32]*Player) {
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) {
2024-08-29 18:48:27 +02:00
keyMap = keyMaps[addedKeyboardPlayers]
addedKeyboardPlayers++
} else {
joyStickIndex := i - uint8(addedKeyboardPlayers)
joyMap = joyMaps[joyStickIndex]
joyStick = sdl.JoystickOpen(int(joyStickIndex))
}
createPlayer(
2024-08-30 19:07:56 +02:00
true,
playerColors[len(players)%len(playerColors)],
nil,
2024-08-29 18:48:27 +02:00
keyMap,
joyMap,
joyStick,
gameMap,
players,
)
}
}
2024-08-30 19:07:56 +02:00
func closeThings(players map[uint32]*Player) {
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-30 21:20:41 +02:00
func createPlayer(local bool, thisPlayerColors PlayerColors, conn *net.TCPConn, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players map[uint32]*Player) uint32 {
2024-08-29 00:05:28 +02:00
coordsAreValid := false
var posX, posY int32
2024-08-29 18:48:27 +02:00
maxTries := 1000
for !coordsAreValid && maxTries >= 0 {
maxTries--
posX = int32(16 + rand.Intn(int(gameMap.width-43)))
posY = int32(16 + rand.Intn(int(gameMap.height-43)))
2024-08-29 00:05:28 +02:00
coordsAreValid = true
2024-08-30 19:07:56 +02:00
for _, player := range players {
2024-08-29 18:48:27 +02:00
distance := (player.gameObject.baseRect.X-posX)*(player.gameObject.baseRect.X-posX) +
(player.gameObject.baseRect.Y-posY)*(player.gameObject.baseRect.Y-posY)
2024-08-29 00:05:28 +02:00
if distance < 300*300 { // Check if distance is less than 300 units
coordsAreValid = false
break
}
}
2024-08-30 19:07:56 +02:00
if posX < 16 || posX > int32(gameMap.width-36-7) || posY < 16 || posY > int32(gameMap.height-36-7) {
2024-08-29 18:48:27 +02:00
coordsAreValid = false
break
}
2024-08-29 00:05:28 +02:00
}
2024-08-29 18:48:27 +02:00
if maxTries < 0 {
panic("Could not place all players, increase map size")
}
gameObject := &GameObject{}
gameObject.baseRect = sdl.Rect{
X: posX,
Y: posY,
W: 7,
H: 7,
}
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
gameObject.addColoredRect(1, 1, 6, 1, 0)
gameObject.addColoredRect(1, 5, 6, 1, 0)
gameObject.addColoredRect(2, 2, 4, 3, 1)
gameObject.addColoredRect(4, 3, 4, 1, 2)
gameObject.orientation = 2 // Down
gameObject.addColoredRect(0, 1, 1, 6, 0)
gameObject.addColoredRect(4, 1, 1, 6, 0)
gameObject.addColoredRect(1, 2, 3, 4, 1)
gameObject.addColoredRect(2, 4, 1, 4, 2)
gameObject.orientation = 3 // Left
gameObject.addColoredRect(1, 1, 6, 1, 0)
gameObject.addColoredRect(1, 5, 6, 1, 0)
gameObject.addColoredRect(2, 2, 4, 3, 1)
gameObject.addColoredRect(0, 3, 4, 1, 2)
gameObject.orientation = 4 // Up-Right
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(2, 1, 1, 1, 0)
gameObject.addColoredRect(1, 2, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(5, 4, 1, 1, 0)
gameObject.addColoredRect(4, 5, 1, 1, 0)
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(2, 2, 2, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(2, 4, 3, 1, 1)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(5, 1, 1, 1, 2)
gameObject.addColoredRect(4, 2, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
// Up-Left orientation (Y-axis reflection)
gameObject.orientation = 5 // Up-Left
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(4, 1, 1, 1, 0)
gameObject.addColoredRect(5, 2, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(1, 4, 1, 1, 0)
gameObject.addColoredRect(2, 5, 1, 1, 0)
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(3, 2, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(2, 4, 3, 1, 1)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(1, 1, 1, 1, 2)
gameObject.addColoredRect(2, 2, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
// Down-Right orientation (X-axis reflection)
gameObject.orientation = 6 // Down-Right
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(2, 5, 1, 1, 0)
gameObject.addColoredRect(1, 4, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(5, 2, 1, 1, 0)
gameObject.addColoredRect(4, 1, 1, 1, 0)
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(2, 4, 2, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(2, 2, 3, 1, 1)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(5, 5, 1, 1, 2)
gameObject.addColoredRect(4, 4, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
// Down-Left orientation (XY reflection)
gameObject.orientation = 7 // Down-Left
gameObject.addColoredRect(3, 6, 1, 1, 0)
gameObject.addColoredRect(4, 5, 1, 1, 0)
gameObject.addColoredRect(5, 4, 1, 1, 0)
gameObject.addColoredRect(6, 3, 1, 1, 0)
gameObject.addColoredRect(0, 3, 1, 1, 0)
gameObject.addColoredRect(1, 2, 1, 1, 0)
gameObject.addColoredRect(2, 1, 1, 1, 0)
gameObject.addColoredRect(3, 0, 1, 1, 0)
gameObject.addColoredRect(3, 1, 1, 1, 1)
gameObject.addColoredRect(2, 2, 3, 1, 1)
gameObject.addColoredRect(1, 3, 2, 1, 1)
gameObject.addColoredRect(4, 3, 2, 1, 1)
gameObject.addColoredRect(3, 4, 2, 1, 1)
gameObject.addColoredRect(3, 5, 1, 1, 1)
gameObject.addColoredRect(1, 5, 1, 1, 2)
gameObject.addColoredRect(2, 4, 1, 1, 2)
gameObject.addColoredRect(3, 3, 1, 1, 2)
gameObject.orientation = 0
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)
}
players[lastPlayerID] = &Player{
playerColors: thisPlayerColors,
keyMap: keyMap,
joyMap: joyMap,
joyStick: joyStick,
shields: serverConfig.MaxShields,
energy: serverConfig.MaxEnergy,
gameObject: gameObject,
local: local,
connection: conn,
playerID: lastPlayerID,
knownBulletParticles: make(map[uint32]*BulletParticle),
knownBases: make(map[uint32]*Base),
knownPlayers: make(map[uint32]*Player),
knownBullets: make(map[uint32]*Bullet),
knownGameMap: &knownGameMap,
}
lastPlayerID++
return lastPlayerID - 1
}
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)
}
func (player *Player) updateRemotePlayerMap() {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
fromX := player.gameObject.baseRect.X - (config.CameraW / 2) - 1
fromY := player.gameObject.baseRect.Y - (config.CameraH / 2) - 1
toX := player.gameObject.baseRect.X + (config.CameraW / 2) + 2
toY := player.gameObject.baseRect.Y + (config.CameraH / 2) + 1
if uint32(toX) > serverConfig.MapWidth {
toX = int32(serverConfig.MapWidth)
}
if uint32(toY) > serverConfig.MapHeight {
toY = int32(serverConfig.MapHeight)
}
if fromX < 0 {
fromX = 0
}
if fromY < 0 {
fromY = 0
}
// Create a WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup
var lck sync.Mutex
// Process columns instead of individual pixels
for x := fromX; x < toX; x++ {
// Increment the WaitGroup counter
wg.Add(1)
// Launch a goroutine for each column
go func(x int32) {
defer wg.Done() // Decrement the counter when the goroutine completes
for y := fromY; y < toY; y++ {
if player.knownGameMap.tiles[x][y] != gameMap.tiles[x][y] {
lck.Lock()
player.gameMapUpdates = append(player.gameMapUpdates, TileUpdate{
PosX: uint32(x),
PosY: uint32(y),
Kind: gameMap.tiles[x][y],
})
player.knownGameMap.tiles[x][y] = gameMap.tiles[x][y]
lck.Unlock()
}
}
}(x)
}
// Wait for all goroutines to finish
wg.Wait()
}
func (player *Player) updateRemotePlayerPlayers(players map[uint32]*Player) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownPlayers = make(map[uint32]*Player)
for _, playerLoop := range players {
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
for _, playerLoopRect := range playerLoop.gameObject.getCurrentRects() {
if player.camera.HasIntersection(playerLoop.gameObject.adjustRectWorld(playerLoopRect.rect)) && playerLoop.shields <= serverConfig.MaxShields {
player.knownPlayers[playerLoop.playerID] = playerLoop
break
}
}
}
}
func (player *Player) updateRemotePlayerBullets(bullets map[uint32]*Bullet) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownBullets = make(map[uint32]*Bullet)
for _, bullet := range bullets {
bulletRect := &sdl.Rect{
X: bullet.posX,
Y: bullet.posY,
W: 1,
H: 1,
}
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
if player.camera.HasIntersection(bulletRect) {
player.knownBullets[bullet.id] = bullet
}
}
}
func (player *Player) updateRemotePlayerBulletParticles(bulletParticles map[uint32]*BulletParticle) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownBulletParticles = make(map[uint32]*BulletParticle)
for _, bulletParticle := range bulletParticles {
bulletRect := &sdl.Rect{
X: bulletParticle.posX,
Y: bulletParticle.posY,
W: 1,
H: 1,
}
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
if player.camera.HasIntersection(bulletRect) {
player.knownBulletParticles[bulletParticle.id] = bulletParticle
}
}
}
func (player *Player) updateRemotePlayerBases(bases map[uint32]*Base) {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
player.knownBases = make(map[uint32]*Base)
for _, base := range bases {
player.camera = &sdl.Rect{
X: player.gameObject.baseRect.X - (config.CameraW / 2),
Y: player.gameObject.baseRect.Y - (config.CameraH / 2),
W: config.CameraW,
H: config.CameraH,
}
for _, baseRect := range base.gameObject.getCurrentRects() {
if player.camera.HasIntersection(base.gameObject.adjustRectWorld(baseRect.rect)) {
player.knownBases[base.ownerID] = base
break
}
}
}
}
func (player *Player) sendVersionBackToPlayer() bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_PlayerStartResponse{
PlayerStartResponse: &tunnelerProto.PlayerStartResponse{
Version: config.Version,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing version")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendServerInfoToPlayer(players map[uint32]*Player) bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_ServerInfo{
ServerInfo: &tunnelerProto.ServerInfo{
MaxEnergy: serverConfig.MaxEnergy,
MaxAmmunition: serverConfig.MaxAmmunition,
MaxShields: serverConfig.MaxShields,
MapWidth: serverConfig.MapWidth,
MapHeight: serverConfig.MapHeight,
NormalShotCost: serverConfig.NormalShotCost,
SuperShotCost: serverConfig.SuperShotCost,
ReloadCost: serverConfig.ReloadCost,
MovementCost: serverConfig.MovementCost,
DiggingCost: serverConfig.DiggingCost,
ShootDiggingBonus: serverConfig.ShootDiggingCostBonus,
ShootCooldown: serverConfig.ShootCooldown,
RechargeCooldown: serverConfig.RechargeCooldownOwn,
RechargeOpponentCooldown: serverConfig.RechargeCooldownOpponent,
RepairCooldown: serverConfig.RepairCooldown,
DiggingCooldown: serverConfig.DiggingCooldown,
MovementCooldown: serverConfig.MovementCooldown,
MovementCooldownNoEnergy: serverConfig.MovementCooldownNoEnergy,
DiggingCooldownNoEnergy: serverConfig.DiggingCooldownNoEnergy,
ReloadCooldown: serverConfig.ReloadCooldown,
BlastRadius: serverConfig.BlastRadius,
PlayerID: player.playerID,
PlayerColorID: uint32((len(players) - 1) % len(playerColors)),
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing server info")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendInfoToPlayer() bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_PlayerUpdate{
PlayerUpdate: &tunnelerProto.PlayerUpdate{
Energy: player.energy,
Ammo: player.ammunition,
Shields: player.shields,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing info")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendLocationToPlayer() bool {
if player.connection != nil && config.Server {
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_PlayerLocationUpdate{
PlayerLocationUpdate: &tunnelerProto.PlayerLocation{
Position: &tunnelerProto.Position{
PosX: player.gameObject.baseRect.X,
PosY: player.gameObject.baseRect.Y,
},
Orientation: uint32(player.gameObject.orientation),
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing location")
}
return player.sendMessage(out)
}
return false
}
var playerUpdateMutex sync.Mutex
func (player *Player) sendUpdatesToPlayer(players map[uint32]*Player) bool {
playerUpdateMutex.Lock()
defer playerUpdateMutex.Unlock()
if player.connection != nil && config.Server {
var playersToSend []*tunnelerProto.Player
var basesToSend []*tunnelerProto.BaseLocation
var bulletsToSend []*tunnelerProto.Bullet
var bulletParticlesToSend []*tunnelerProto.BulletParticle
var tileUpdatesToSend []*tunnelerProto.TileUpdate
for playerID, knownPlayer := range player.knownPlayers {
if playerID == player.playerID {
continue
}
playersToSend = append(playersToSend, &tunnelerProto.Player{
PlayerID: playerID,
Location: &tunnelerProto.PlayerLocation{
Position: &tunnelerProto.Position{
PosX: knownPlayer.gameObject.baseRect.X,
PosY: knownPlayer.gameObject.baseRect.Y,
},
Orientation: uint32(knownPlayer.gameObject.orientation),
},
})
}
for _, knownBase := range player.knownBases {
r, g, b, a := players[knownBase.ownerID].playerColors.body.RGBA()
basesToSend = append(basesToSend, &tunnelerProto.BaseLocation{
Position: &tunnelerProto.Position{
PosX: knownBase.gameObject.baseRect.X,
PosY: knownBase.gameObject.baseRect.Y,
},
Owner: &tunnelerProto.Player{
PlayerID: knownBase.ownerID,
Location: nil,
},
Color: &tunnelerProto.Color{
Red: r,
Green: g,
Blue: b,
Alpha: a,
},
})
}
for _, knownBullet := range player.knownBullets {
bulletRed, bulletGreen, bulletBlue, bulletAlpha := knownBullet.color.RGBA()
bulletsToSend = append(bulletsToSend, &tunnelerProto.Bullet{
Position: &tunnelerProto.Position{
PosX: knownBullet.posX,
PosY: knownBullet.posY,
},
Direction: uint32(knownBullet.direction),
Color: &tunnelerProto.Color{
Red: bulletRed,
Green: bulletGreen,
Blue: bulletBlue,
Alpha: bulletAlpha,
},
Id: knownBullet.id,
})
}
for _, knownBulletParticle := range player.knownBulletParticles {
bulletParticleRed, bulletParticleGreen, bulletParticleBlue, bulletParticleAlpha := knownBulletParticle.color.RGBA()
bulletParticlesToSend = append(bulletParticlesToSend, &tunnelerProto.BulletParticle{
Position: &tunnelerProto.Position{
PosX: knownBulletParticle.posX,
PosY: knownBulletParticle.posY,
},
ExpirationTimer: knownBulletParticle.expirationTimer,
Color: &tunnelerProto.Color{
Red: bulletParticleRed,
Green: bulletParticleGreen,
Blue: bulletParticleBlue,
Alpha: bulletParticleAlpha,
},
Id: knownBulletParticle.id,
})
}
for _, tileUpdate := range player.gameMapUpdates {
tileUpdatesToSend = append(tileUpdatesToSend, &tunnelerProto.TileUpdate{
Position: &tunnelerProto.Position{
PosX: int32(tileUpdate.PosX),
PosY: int32(tileUpdate.PosY),
},
Kind: uint32(tileUpdate.Kind),
})
}
response := tunnelerProto.ClientBound{
ClientBoundMessage: &tunnelerProto.ClientBound_WorldUpdate{
WorldUpdate: &tunnelerProto.WorldUpdate{
Players: playersToSend,
Base: basesToSend,
Bullets: bulletsToSend,
BulletParticles: bulletParticlesToSend,
TileUpdate: tileUpdatesToSend,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing updates")
}
return player.sendMessage(out)
}
return false
}
func (player *Player) sendPositionToServer() bool {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerPosition{
PlayerPosition: &tunnelerProto.PlayerLocation{
Position: &tunnelerProto.Position{
PosX: player.gameObject.baseRect.X,
PosY: player.gameObject.baseRect.Y,
},
Orientation: uint32(player.gameObject.orientation),
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing location")
}
return player.sendMessage(out)
}
return false
}
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{
Version: config.Version,
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing version")
}
sendMessageRaw(out, connection)
}
}
func (player *Player) sendDigToServer(posX, posY uint32, isShooting bool) {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction_DigBlock{
DigBlock: &tunnelerProto.DigBlock{
Position: &tunnelerProto.Position{
PosX: int32(posX),
PosY: int32(posY),
},
IsShooting: isShooting,
},
},
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing dig")
}
player.sendMessage(out)
}
}
func (player *Player) sendShootToServer(isSuper bool) {
if player.connection != nil && config.Client {
response := tunnelerProto.ServerBound{
ServerBoundMessage: &tunnelerProto.ServerBound_PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction{
PlayerAction: &tunnelerProto.PlayerAction_Shoot{
Shoot: &tunnelerProto.Shoot{
Super: isSuper,
},
},
},
},
}
out, err := proto.Marshal(&response)
if err != nil {
panic("Error serializing shot")
}
player.sendMessage(out)
}
}
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
if clientVersion != config.Version {
log.Fatalf("Wrong client version, a client connected with %s to %s", clientVersion, config.Version)
return
}
2024-08-30 21:20:41 +02:00
newPlayerID := createPlayer(false, playerColors[len(players)%len(playerColors)], conn, KeyMap{}, JoyMap{}, nil, gameMap, players)
2024-08-30 19:07:56 +02:00
defer delete(players, newPlayerID)
player := players[newPlayerID]
bases[newPlayerID] = createBase(gameMap,
player.playerColors.body,
uint32(player.gameObject.baseRect.X-14),
uint32(player.gameObject.baseRect.Y-14),
player.playerID,
uint32(float64(player.gameObject.baseRect.W)*1.5))
defer bases[newPlayerID].delete(bases)
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)
fail = player.sendInfoToPlayer()
if fail {
return
}
log.Printf("Sent info to %d", newPlayerID)
fail = player.sendLocationToPlayer()
if fail {
return
}
log.Printf("Sent location to %d", newPlayerID)
case *tunnelerProto.ServerBound_PlayerPosition:
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
if ((newX > oldX && newX-oldX <= 2) || (newX < oldX && oldX-newX <= 2) || newX == oldX) &&
(newY > oldY && newY-oldY <= 2) || (newY < oldY && oldY-newY <= 2 || newY == oldY) {
player.gameObject.baseRect = sdl.Rect{
X: newX,
Y: newY,
W: player.gameObject.baseRect.W,
H: player.gameObject.baseRect.H,
}
}
player.gameObject.orientation = uint8(newPosition.Orientation)
case *tunnelerProto.ServerBound_PlayerAction:
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
player.digBlock(position.PosX, position.PosY, gameMap, isShooting)
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
if serverVersion != config.Version {
log.Fatalf("Wrong server version, connected with %s to %s", config.Version, serverVersion)
return
} else {
log.Printf("Connected to %s, running version %s with version %s\n", config.Address, serverVersion, config.Version)
}
case *tunnelerProto.ClientBound_ServerInfo:
serverInfo := msg.GetServerInfo()
serverConfig = ServerConfig{
MapWidth: serverInfo.MapWidth,
MapHeight: serverInfo.MapHeight,
BlastRadius: serverInfo.BlastRadius,
MaxEnergy: serverInfo.MaxEnergy,
MaxAmmunition: serverInfo.MaxAmmunition,
MaxShields: serverInfo.MaxShields,
NormalShotCost: serverInfo.NormalShotCost,
SuperShotCost: serverInfo.SuperShotCost,
ReloadCost: serverInfo.ReloadCost,
MovementCost: serverInfo.MovementCost,
DiggingCost: serverInfo.DiggingCost,
ShootDiggingCostBonus: serverInfo.ShootDiggingBonus,
ShootCooldown: serverInfo.ShootCooldown,
RechargeCooldownOwn: serverInfo.RechargeCooldown,
DiggingCooldown: serverInfo.DiggingCooldown,
RechargeCooldownOpponent: serverInfo.RechargeOpponentCooldown,
RepairCooldown: serverInfo.RepairCooldown,
MovementCooldown: serverInfo.MovementCooldown,
MovementCooldownNoEnergy: serverInfo.MovementCooldownNoEnergy,
DiggingCooldownNoEnergy: serverInfo.DiggingCooldownNoEnergy,
ReloadCooldown: serverInfo.ReloadCooldown,
}
gameMap.createGameMap(false)
lastPlayerID = serverInfo.PlayerID
createPlayer(true, playerColors[serverInfo.PlayerColorID], conn, keyMaps[0], JoyMap{}, nil, gameMap, players)
player = players[serverInfo.PlayerID]
log.Printf("Got server info, now initializing\n")
clientInitialized = true
case *tunnelerProto.ClientBound_PlayerUpdate:
if clientInitialized {
playerUpdate := msg.GetPlayerUpdate()
player.energy = playerUpdate.Energy
player.ammunition = playerUpdate.Ammo
player.shields = playerUpdate.Shields
}
case *tunnelerProto.ClientBound_PlayerLocationUpdate:
if clientInitialized {
playerLocationUpdate := msg.GetPlayerLocationUpdate()
player.gameObject.baseRect.X = playerLocationUpdate.Position.PosX
player.gameObject.baseRect.Y = playerLocationUpdate.Position.PosY
player.gameObject.orientation = uint8(playerLocationUpdate.Orientation)
}
case *tunnelerProto.ClientBound_WorldUpdate:
if clientInitialized {
worldUpdate := msg.GetWorldUpdate()
worldLock.Lock()
for _, playerLoop := range worldUpdate.Players {
playerID := playerLoop.PlayerID
existingPlayer := players[playerID]
if existingPlayer == nil {
lastPlayerID = playerID
createPlayer(false, playerColors[playerID%uint32(len(playerColors))], nil, KeyMap{}, JoyMap{}, nil, gameMap, players)
newPlayer := players[playerID]
newPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
newPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX
newPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY
} else {
existingPlayer.gameObject.orientation = uint8(playerLoop.Location.Orientation)
existingPlayer.gameObject.baseRect.X = playerLoop.Location.Position.PosX
existingPlayer.gameObject.baseRect.Y = playerLoop.Location.Position.PosY
}
}
for _, base := range worldUpdate.Base {
baseID := base.Owner.PlayerID
existingBase := bases[baseID]
inColor := color.RGBA{
R: uint8(base.Color.Red),
G: uint8(base.Color.Green),
B: uint8(base.Color.Blue),
A: uint8(base.Color.Alpha),
}
if existingBase == nil {
bases[baseID] = createBase(gameMap,
inColor,
uint32(base.Position.PosX),
uint32(base.Position.PosY),
base.Owner.PlayerID,
uint32(int32(float64(player.gameObject.baseRect.W)*1.5)),
)
} else {
existingBase.gameObject.baseRect.X = base.Position.PosX
existingBase.gameObject.baseRect.Y = base.Position.PosY
existingBase.gameObject.colors[0] = inColor
}
}
for _, bullet := range worldUpdate.Bullets {
bulletID := bullet.Id
inColor := color.RGBA{
R: uint8(bullet.Color.Red),
G: uint8(bullet.Color.Green),
B: uint8(bullet.Color.Blue),
A: uint8(bullet.Color.Alpha),
}
existingBullet := bullets[bulletID]
if existingBullet == nil {
bullets[bulletID] = &Bullet{
posX: bullet.Position.PosX,
posY: bullet.Position.PosY,
direction: uint8(bullet.Direction),
color: inColor,
super: bullet.Super,
id: bulletID,
}
} else {
existingBullet.direction = uint8(bullet.Direction)
existingBullet.color = inColor
existingBullet.super = bullet.Super
existingBullet.posX = bullet.Position.PosX
existingBullet.posY = bullet.Position.PosY
}
}
for _, bulletParticle := range worldUpdate.BulletParticles {
bulletParticleID := bulletParticle.Id
inColor := color.RGBA{
R: uint8(bulletParticle.Color.Red),
G: uint8(bulletParticle.Color.Green),
B: uint8(bulletParticle.Color.Blue),
A: uint8(bulletParticle.Color.Alpha),
}
existingBulletParticle := bulletParticles[bulletParticleID]
if existingBulletParticle == nil {
bulletParticles[bulletParticleID] = &BulletParticle{
posX: bulletParticle.Position.PosX,
posY: bulletParticle.Position.PosY,
expirationTimer: bulletParticle.ExpirationTimer,
color: inColor,
id: bulletParticleID,
}
} else {
existingBulletParticle.color = inColor
existingBulletParticle.posX = bulletParticle.Position.PosX
existingBulletParticle.posY = bulletParticle.Position.PosY
existingBulletParticle.expirationTimer = bulletParticle.ExpirationTimer
}
}
for _, tileUpdate := range worldUpdate.TileUpdate {
posX := uint32(tileUpdate.Position.PosX)
posY := uint32(tileUpdate.Position.PosY)
kind := uint8(tileUpdate.Kind)
if posX > 0 && posX < gameMap.width && posY > 0 && posY < gameMap.height {
gameMap.tiles[posX][posY] = kind
}
}
worldLock.Unlock()
}
}
}
2024-08-29 00:05:28 +02:00
}