466 lines
18 KiB
Go
466 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"image/color"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
)
|
|
|
|
func sendPlayerStartResponse(major, minor, patch uint8, conn *net.TCPConn) error {
|
|
data := make([]byte, 3)
|
|
data[0] = major
|
|
data[1] = minor
|
|
data[2] = patch
|
|
return sendPacket(0, data, conn)
|
|
}
|
|
|
|
func receivePlayerStartResponse(data []byte) (major, minor, patch uint8, err error) {
|
|
if len(data) < 3 {
|
|
return 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 3", len(data))
|
|
}
|
|
major = data[0]
|
|
minor = data[1]
|
|
patch = data[2]
|
|
return major, minor, patch, nil
|
|
}
|
|
|
|
func sendServerInfo(
|
|
playerID, playerColorID, maxEnergy, maxAmmunition, maxShields, mapWidth, mapHeight, normalShotCost,
|
|
superShotCost, reloadCost, movementCost, diggingCost, shootDiggingBonus, shootCooldown, rechargeCooldown,
|
|
rechargeOpponentCooldown, repairCooldown, diggingCooldown, movementCooldown, movementCooldownNoEnergy,
|
|
diggingCooldownNoEnergy, reloadCooldown uint32, blastRadius uint8, reloadWait uint32, conn *net.TCPConn) error {
|
|
|
|
data := make([]byte, 93) // Adjusted size to accommodate playerID and playerColorID
|
|
binary.LittleEndian.PutUint32(data[0:4], playerID)
|
|
binary.LittleEndian.PutUint32(data[4:8], playerColorID)
|
|
binary.LittleEndian.PutUint32(data[8:12], maxEnergy)
|
|
binary.LittleEndian.PutUint32(data[12:16], maxAmmunition)
|
|
binary.LittleEndian.PutUint32(data[16:20], maxShields)
|
|
binary.LittleEndian.PutUint32(data[20:24], mapWidth)
|
|
binary.LittleEndian.PutUint32(data[24:28], mapHeight)
|
|
binary.LittleEndian.PutUint32(data[28:32], normalShotCost)
|
|
binary.LittleEndian.PutUint32(data[32:36], superShotCost)
|
|
binary.LittleEndian.PutUint32(data[36:40], reloadCost)
|
|
binary.LittleEndian.PutUint32(data[40:44], movementCost)
|
|
binary.LittleEndian.PutUint32(data[44:48], diggingCost)
|
|
binary.LittleEndian.PutUint32(data[48:52], shootDiggingBonus)
|
|
binary.LittleEndian.PutUint32(data[52:56], shootCooldown)
|
|
binary.LittleEndian.PutUint32(data[56:60], rechargeCooldown)
|
|
binary.LittleEndian.PutUint32(data[60:64], rechargeOpponentCooldown)
|
|
binary.LittleEndian.PutUint32(data[64:68], repairCooldown)
|
|
binary.LittleEndian.PutUint32(data[68:72], diggingCooldown)
|
|
binary.LittleEndian.PutUint32(data[72:76], movementCooldown)
|
|
binary.LittleEndian.PutUint32(data[76:80], movementCooldownNoEnergy)
|
|
binary.LittleEndian.PutUint32(data[80:84], diggingCooldownNoEnergy)
|
|
binary.LittleEndian.PutUint32(data[84:88], reloadCooldown)
|
|
data[88] = blastRadius
|
|
binary.LittleEndian.PutUint32(data[89:93], reloadWait)
|
|
return sendPacket(5, data, conn)
|
|
}
|
|
|
|
func receiveServerInfo(data []byte) (
|
|
playerID, playerColorID, maxEnergy, maxAmmunition, maxShields, mapWidth, mapHeight, normalShotCost, superShotCost,
|
|
reloadCost, movementCost, diggingCost, shootDiggingBonus, shootCooldown, rechargeCooldown, rechargeOpponentCooldown,
|
|
repairCooldown, diggingCooldown, movementCooldown, movementCooldownNoEnergy, diggingCooldownNoEnergy, reloadCooldown uint32,
|
|
blastRadius uint8, reloadWait uint32, err error) {
|
|
|
|
if len(data) < 93 {
|
|
return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 93", len(data))
|
|
}
|
|
|
|
playerID = binary.LittleEndian.Uint32(data[0:4])
|
|
playerColorID = binary.LittleEndian.Uint32(data[4:8])
|
|
maxEnergy = binary.LittleEndian.Uint32(data[8:12])
|
|
maxAmmunition = binary.LittleEndian.Uint32(data[12:16])
|
|
maxShields = binary.LittleEndian.Uint32(data[16:20])
|
|
mapWidth = binary.LittleEndian.Uint32(data[20:24])
|
|
mapHeight = binary.LittleEndian.Uint32(data[24:28])
|
|
normalShotCost = binary.LittleEndian.Uint32(data[28:32])
|
|
superShotCost = binary.LittleEndian.Uint32(data[32:36])
|
|
reloadCost = binary.LittleEndian.Uint32(data[36:40])
|
|
movementCost = binary.LittleEndian.Uint32(data[40:44])
|
|
diggingCost = binary.LittleEndian.Uint32(data[44:48])
|
|
shootDiggingBonus = binary.LittleEndian.Uint32(data[48:52])
|
|
shootCooldown = binary.LittleEndian.Uint32(data[52:56])
|
|
rechargeCooldown = binary.LittleEndian.Uint32(data[56:60])
|
|
rechargeOpponentCooldown = binary.LittleEndian.Uint32(data[60:64])
|
|
repairCooldown = binary.LittleEndian.Uint32(data[64:68])
|
|
diggingCooldown = binary.LittleEndian.Uint32(data[68:72])
|
|
movementCooldown = binary.LittleEndian.Uint32(data[72:76])
|
|
movementCooldownNoEnergy = binary.LittleEndian.Uint32(data[76:80])
|
|
diggingCooldownNoEnergy = binary.LittleEndian.Uint32(data[80:84])
|
|
reloadCooldown = binary.LittleEndian.Uint32(data[84:88])
|
|
blastRadius = data[88]
|
|
reloadWait = binary.LittleEndian.Uint32(data[89:93])
|
|
|
|
return playerID, playerColorID, maxEnergy, maxAmmunition, maxShields, mapWidth, mapHeight, normalShotCost, superShotCost,
|
|
reloadCost, movementCost, diggingCost, shootDiggingBonus, shootCooldown, rechargeCooldown, rechargeOpponentCooldown,
|
|
repairCooldown, diggingCooldown, movementCooldown, movementCooldownNoEnergy, diggingCooldownNoEnergy, reloadCooldown,
|
|
blastRadius, reloadWait, nil
|
|
}
|
|
|
|
func sendPositionUpdate(posX, posY uint32, orientation uint8, conn *net.TCPConn) error {
|
|
data := make([]byte, 9)
|
|
binary.LittleEndian.PutUint32(data[0:4], posX)
|
|
binary.LittleEndian.PutUint32(data[4:8], posY)
|
|
data[8] = orientation
|
|
return sendPacket(1, data, conn)
|
|
}
|
|
|
|
func receivePositionUpdate(data []byte) (posX, posY uint32, orientation uint8, err error) {
|
|
if len(data) < 9 {
|
|
return 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 9", len(data))
|
|
}
|
|
posX = binary.LittleEndian.Uint32(data[0:4])
|
|
posY = binary.LittleEndian.Uint32(data[4:8])
|
|
orientation = data[8]
|
|
return posX, posY, orientation, nil
|
|
}
|
|
|
|
func sendOtherPlayer(playerID, posX, posY uint32, orientation uint8, conn *net.TCPConn) error {
|
|
data := make([]byte, 13)
|
|
binary.LittleEndian.PutUint32(data[0:4], playerID)
|
|
binary.LittleEndian.PutUint32(data[4:8], posX)
|
|
binary.LittleEndian.PutUint32(data[8:12], posY)
|
|
data[12] = orientation
|
|
return sendPacket(2, data, conn)
|
|
}
|
|
|
|
func receiveOtherPlayer(data []byte) (playerID, posX, posY uint32, orientation uint8, err error) {
|
|
if len(data) < 13 {
|
|
return 0, 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 13", len(data))
|
|
}
|
|
playerID = binary.LittleEndian.Uint32(data[0:4])
|
|
posX = binary.LittleEndian.Uint32(data[4:8])
|
|
posY = binary.LittleEndian.Uint32(data[8:12])
|
|
orientation = data[12]
|
|
return playerID, posX, posY, orientation, nil
|
|
}
|
|
|
|
func sendBullet(posX, posY uint32, direction, red, green, blue, alpha uint8, bulletID uint32, isSuper bool, ownerID uint32, conn *net.TCPConn) error {
|
|
data := make([]byte, 22)
|
|
binary.LittleEndian.PutUint32(data[0:4], posX)
|
|
binary.LittleEndian.PutUint32(data[4:8], posY)
|
|
data[8] = direction
|
|
data[9] = red
|
|
data[10] = green
|
|
data[11] = blue
|
|
data[12] = alpha
|
|
binary.LittleEndian.PutUint32(data[13:17], bulletID)
|
|
data[17] = boolToByte(isSuper)
|
|
binary.LittleEndian.PutUint32(data[18:22], ownerID)
|
|
return sendPacket(3, data, conn)
|
|
}
|
|
|
|
func receiveBullet(data []byte) (posX, posY uint32, direction, red, green, blue, alpha uint8, bulletID uint32, isSuper bool, ownerID uint32, err error) {
|
|
if len(data) < 22 {
|
|
return 0, 0, 0, 0, 0, 0, 0, 0, false, 0, fmt.Errorf("insufficient data length, got %d instead of 21", len(data))
|
|
}
|
|
posX = binary.LittleEndian.Uint32(data[0:4])
|
|
posY = binary.LittleEndian.Uint32(data[4:8])
|
|
direction = data[8]
|
|
red = data[9]
|
|
green = data[10]
|
|
blue = data[11]
|
|
alpha = data[12]
|
|
bulletID = binary.LittleEndian.Uint32(data[13:17])
|
|
isSuper = byteToBool(data[17])
|
|
ownerID = binary.LittleEndian.Uint32(data[18:22])
|
|
return posX, posY, direction, red, green, blue, alpha, bulletID, isSuper, ownerID, nil
|
|
}
|
|
|
|
func sendBulletParticle(posX, posY, expirationTimer uint32, red, green, blue, alpha uint8, bulletParticleID uint32, conn *net.TCPConn) error {
|
|
data := make([]byte, 20)
|
|
binary.LittleEndian.PutUint32(data[0:4], posX)
|
|
binary.LittleEndian.PutUint32(data[4:8], posY)
|
|
binary.LittleEndian.PutUint32(data[8:12], expirationTimer)
|
|
data[12] = red
|
|
data[13] = green
|
|
data[14] = blue
|
|
data[15] = alpha
|
|
binary.LittleEndian.PutUint32(data[16:20], bulletParticleID)
|
|
return sendPacket(4, data, conn)
|
|
}
|
|
|
|
func receiveBulletParticle(data []byte) (posX, posY, expirationTimer uint32, red, green, blue, alpha uint8, bulletParticleID uint32, err error) {
|
|
if len(data) < 20 {
|
|
return 0, 0, 0, 0, 0, 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 20", len(data))
|
|
}
|
|
posX = binary.LittleEndian.Uint32(data[0:4])
|
|
posY = binary.LittleEndian.Uint32(data[4:8])
|
|
expirationTimer = binary.LittleEndian.Uint32(data[8:12])
|
|
red = data[12]
|
|
green = data[13]
|
|
blue = data[14]
|
|
alpha = data[15]
|
|
bulletParticleID = binary.LittleEndian.Uint32(data[16:20])
|
|
return posX, posY, expirationTimer, red, green, blue, alpha, bulletParticleID, nil
|
|
}
|
|
|
|
func sendPlayerUpdate(energy, ammo, shields uint32, conn *net.TCPConn) error {
|
|
data := make([]byte, 12)
|
|
binary.LittleEndian.PutUint32(data[0:4], energy)
|
|
binary.LittleEndian.PutUint32(data[4:8], ammo)
|
|
binary.LittleEndian.PutUint32(data[8:12], shields)
|
|
return sendPacket(6, data, conn)
|
|
}
|
|
|
|
func receivePlayerUpdate(data []byte) (energy, ammo, shields uint32, err error) {
|
|
if len(data) < 12 {
|
|
return 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 12", len(data))
|
|
}
|
|
energy = binary.LittleEndian.Uint32(data[0:4])
|
|
ammo = binary.LittleEndian.Uint32(data[4:8])
|
|
shields = binary.LittleEndian.Uint32(data[8:12])
|
|
return energy, ammo, shields, nil
|
|
}
|
|
|
|
func sendBaseLocation(posX, posY uint32, ownerID, playerColorID uint32, conn *net.TCPConn) error {
|
|
data := make([]byte, 16)
|
|
binary.LittleEndian.PutUint32(data[0:4], posX)
|
|
binary.LittleEndian.PutUint32(data[4:8], posY)
|
|
binary.LittleEndian.PutUint32(data[8:12], ownerID)
|
|
binary.LittleEndian.PutUint32(data[12:16], playerColorID)
|
|
return sendPacket(7, data, conn)
|
|
}
|
|
|
|
func receiveBaseLocation(data []byte) (posX, posY uint32, ownerID, playerColorID uint32, err error) {
|
|
if len(data) < 16 {
|
|
return 0, 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 16", len(data))
|
|
}
|
|
posX = binary.LittleEndian.Uint32(data[0:4])
|
|
posY = binary.LittleEndian.Uint32(data[4:8])
|
|
ownerID = binary.LittleEndian.Uint32(data[8:12])
|
|
playerColorID = binary.LittleEndian.Uint32(data[12:16])
|
|
return posX, posY, ownerID, playerColorID, nil
|
|
}
|
|
|
|
func sendTileUpdate(posX, posY uint32, kind uint8, conn *net.TCPConn) error {
|
|
data := make([]byte, 9)
|
|
binary.LittleEndian.PutUint32(data[0:4], posX)
|
|
binary.LittleEndian.PutUint32(data[4:8], posY)
|
|
data[8] = kind
|
|
return sendPacket(8, data, conn)
|
|
}
|
|
|
|
func receiveTileUpdate(data []byte) (posX, posY uint32, kind uint8, err error) {
|
|
if len(data) < 9 {
|
|
return 0, 0, 0, fmt.Errorf("insufficient data length, got %d instead of 9", len(data))
|
|
}
|
|
posX = binary.LittleEndian.Uint32(data[0:4])
|
|
posY = binary.LittleEndian.Uint32(data[4:8])
|
|
kind = data[8]
|
|
return posX, posY, kind, nil
|
|
}
|
|
|
|
func handleConnectionClient(conn *net.TCPConn, players map[uint32]*Player, bases map[uint32]*Base, bullets map[uint32]*Bullet, bulletParticles map[uint32]*BulletParticle) {
|
|
var player *Player
|
|
sendVersionToServer(conn)
|
|
for {
|
|
packetID, _, packetData, err := receivePacket(conn)
|
|
if err != nil {
|
|
log.Printf("Error receiving packet: %v", err)
|
|
os.Exit(0)
|
|
return
|
|
}
|
|
switch packetID {
|
|
case 0:
|
|
major, minor, patch, err := receivePlayerStartResponse(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving player start response: %v", err)
|
|
}
|
|
versionArray := getCurrentVersion()
|
|
if versionArray[0] != major || versionArray[1] != minor || versionArray[2] != patch {
|
|
log.Fatalf("Wrong version tried to connect with %d.%d.%d to %d.%d.%d", major, minor, patch, versionArray[0], versionArray[1], versionArray[2])
|
|
}
|
|
case 1:
|
|
if clientInitialized {
|
|
posX, posY, orientation, err := receivePositionUpdate(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving player location: %v", err)
|
|
}
|
|
player.gameObject.baseRect.X = int32(posX)
|
|
player.gameObject.baseRect.Y = int32(posY)
|
|
player.gameObject.orientation = orientation
|
|
player.gameObject.adjustBaseRect()
|
|
}
|
|
|
|
case 2:
|
|
playerID, posX, posY, orientation, err := receiveOtherPlayer(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving player response: %v", err)
|
|
}
|
|
playersMutex.RLock()
|
|
existingPlayer := players[playerID]
|
|
playersMutex.RUnlock()
|
|
if existingPlayer == nil {
|
|
lastPlayerID = playerID
|
|
playerColor := playerID % uint32(len(playerColors))
|
|
createPlayer(false, playerColors[playerColor], playerColor, nil, KeyMap{}, JoyMap{}, nil, gameMap, players, bases)
|
|
playersMutex.RLock()
|
|
newPlayer := players[playerID]
|
|
playersMutex.RUnlock()
|
|
newPlayer.gameObject.orientation = orientation
|
|
newPlayer.gameObject.adjustBaseRect()
|
|
newPlayer.gameObject.baseRect.X = int32(posX)
|
|
newPlayer.gameObject.baseRect.Y = int32(posY)
|
|
} else {
|
|
existingPlayer.gameObject.orientation = orientation
|
|
existingPlayer.gameObject.adjustBaseRect()
|
|
existingPlayer.gameObject.baseRect.X = int32(posX)
|
|
existingPlayer.gameObject.baseRect.Y = int32(posY)
|
|
}
|
|
|
|
case 3:
|
|
posX, posY, direction, red, green, blue, alpha, bulletID, isSuper, ownerID, err := receiveBullet(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving bullet response: %v", err)
|
|
}
|
|
inColor := color.RGBA{
|
|
R: red,
|
|
G: green,
|
|
B: blue,
|
|
A: alpha,
|
|
}
|
|
existingBullet := bullets[bulletID]
|
|
if existingBullet == nil {
|
|
bulletMutex.Lock()
|
|
bullets[bulletID] = &Bullet{
|
|
posX: int32(posX),
|
|
posY: int32(posY),
|
|
direction: direction,
|
|
color: inColor,
|
|
super: isSuper,
|
|
id: bulletID,
|
|
ownerID: ownerID,
|
|
}
|
|
bulletMutex.Unlock()
|
|
} else {
|
|
existingBullet.direction = direction
|
|
existingBullet.color = inColor
|
|
existingBullet.super = isSuper
|
|
existingBullet.posX = int32(posX)
|
|
existingBullet.posY = int32(posY)
|
|
}
|
|
|
|
case 4:
|
|
posX, posY, expirationTimer, red, green, blue, alpha, bulletParticleID, err := receiveBulletParticle(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving bullet particle: %v", err)
|
|
}
|
|
inColor := color.RGBA{
|
|
R: red,
|
|
G: green,
|
|
B: blue,
|
|
A: alpha,
|
|
}
|
|
bulletParticleMutex.Lock()
|
|
existingBulletParticle := bulletParticles[bulletParticleID]
|
|
if existingBulletParticle == nil {
|
|
bulletParticles[bulletParticleID] = &BulletParticle{
|
|
posX: int32(posX),
|
|
posY: int32(posY),
|
|
expirationTimer: expirationTimer,
|
|
color: inColor,
|
|
id: bulletParticleID,
|
|
}
|
|
} else {
|
|
existingBulletParticle.color = inColor
|
|
existingBulletParticle.posX = int32(posX)
|
|
existingBulletParticle.posY = int32(posY)
|
|
existingBulletParticle.expirationTimer = expirationTimer
|
|
}
|
|
bulletParticleMutex.Unlock()
|
|
|
|
case 5:
|
|
playerID, playerColorID, maxEnergy, maxAmmunition, maxShields, mapWidth, mapHeight, normalShotCost, superShotCost, reloadCost, movementCost,
|
|
diggingCost, shootDiggingBonus, shootCooldown, rechargeCooldown, rechargeOpponentCooldown, repairCooldown, diggingCooldown, movementCooldown,
|
|
movementCooldownNoEnergy, diggingCooldownNoEnergy, reloadCooldown, blastRadius, reloadWait, err := receiveServerInfo(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving server info: %v", err)
|
|
}
|
|
serverConfig = ServerConfig{
|
|
MapWidth: mapWidth,
|
|
MapHeight: mapHeight,
|
|
BlastRadius: blastRadius,
|
|
MaxEnergy: maxEnergy,
|
|
MaxAmmunition: maxAmmunition,
|
|
MaxShields: maxShields,
|
|
NormalShotCost: normalShotCost,
|
|
SuperShotCost: superShotCost,
|
|
ReloadCost: reloadCost,
|
|
MovementCost: movementCost,
|
|
DiggingCost: diggingCost,
|
|
ShootDiggingCostBonus: shootDiggingBonus,
|
|
ShootCooldown: shootCooldown,
|
|
RechargeCooldownOwn: rechargeCooldown,
|
|
DiggingCooldown: diggingCooldown,
|
|
RechargeCooldownOpponent: rechargeOpponentCooldown,
|
|
RepairCooldown: repairCooldown,
|
|
MovementCooldown: movementCooldown,
|
|
MovementCooldownNoEnergy: movementCooldownNoEnergy,
|
|
DiggingCooldownNoEnergy: diggingCooldownNoEnergy,
|
|
ReloadCooldown: reloadCooldown,
|
|
ReloadWait: reloadWait,
|
|
}
|
|
gameMap.createGameMap(false)
|
|
lastPlayerID = playerID
|
|
createPlayer(true, playerColors[playerColorID], playerColorID, conn, keyMaps[config.KeyBindOffset], JoyMap{}, nil, gameMap, players, bases)
|
|
playersMutex.RLock()
|
|
player = players[playerID]
|
|
playersMutex.RUnlock()
|
|
log.Printf("Got server info, now initializing\n")
|
|
clientInitialized = true
|
|
|
|
case 6:
|
|
if clientInitialized {
|
|
energy, ammo, shields, err := receivePlayerUpdate(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving player update: %v", err)
|
|
}
|
|
player.energy = energy
|
|
player.ammunition = ammo
|
|
player.shields = shields
|
|
}
|
|
|
|
case 7:
|
|
posX, posY, ownerID, playerColorID, err := receiveBaseLocation(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving base location: %v", err)
|
|
}
|
|
baseID := ownerID
|
|
baseMutex.RLock()
|
|
existingBase := bases[baseID]
|
|
baseMutex.RUnlock()
|
|
inColor := playerColors[playerColorID].body
|
|
if existingBase == nil {
|
|
baseMutex.Lock()
|
|
bases[baseID] = createBase(gameMap,
|
|
inColor,
|
|
posX,
|
|
posY,
|
|
ownerID,
|
|
uint32(float64(player.gameObject.baseRect.W)*1.5),
|
|
)
|
|
baseMutex.Unlock()
|
|
} else {
|
|
existingBase.gameObject.baseRect.X = int32(posX)
|
|
existingBase.gameObject.baseRect.Y = int32(posY)
|
|
existingBase.gameObject.colors[0] = inColor
|
|
}
|
|
|
|
case 8:
|
|
posX, posY, kind, err := receiveTileUpdate(packetData)
|
|
if err != nil {
|
|
log.Printf("Error receiving tile update: %v", err)
|
|
}
|
|
if posX > 0 && posX < gameMap.width && posY > 0 && posY < gameMap.height {
|
|
gameMap.tiles[posX][posY] = kind
|
|
}
|
|
}
|
|
}
|
|
}
|