Update networking stack
This commit is contained in:
parent
b3c789303b
commit
589fd61902
@ -181,7 +181,9 @@ func (bullet *Bullet) tick(gameMap *GameMap,
|
|||||||
}
|
}
|
||||||
playersMutex.RUnlock()
|
playersMutex.RUnlock()
|
||||||
if collisionResult != 0 || hitPlayer {
|
if collisionResult != 0 || hitPlayer {
|
||||||
|
if !config.Client {
|
||||||
bullet.explode(gameMap, bulletParticleMap)
|
bullet.explode(gameMap, bulletParticleMap)
|
||||||
|
}
|
||||||
delete(bulletMap, bullet.id)
|
delete(bulletMap, bullet.id)
|
||||||
} else {
|
} else {
|
||||||
bullet.posX = nextX
|
bullet.posX = nextX
|
||||||
|
465
clientboundpacket.go
Normal file
465
clientboundpacket.go
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -76,16 +76,16 @@ func (gameObject *GameObject) render(camera *sdl.Rect, surface *sdl.Surface) {
|
|||||||
gameObject.inView = true
|
gameObject.inView = true
|
||||||
if config.Debug {
|
if config.Debug {
|
||||||
baseRectFinal := adjustRectToCamera(gameObject.baseRect, camera)
|
baseRectFinal := adjustRectToCamera(gameObject.baseRect, camera)
|
||||||
surface.FillRect(baseRectFinal, sdl.MapRGBA(surface.Format, 255, 20, 10, 64))
|
_ = surface.FillRect(baseRectFinal, sdl.MapRGBA(surface.Format, 255, 20, 10, 64))
|
||||||
if !gameObject.collisionRect.Empty() {
|
if !gameObject.collisionRect.Empty() {
|
||||||
surface.FillRect(adjustRectToCamera(gameObject.collisionRect, camera), sdl.MapRGBA(surface.Format, 40, 192, 255, 64))
|
_ = surface.FillRect(adjustRectToCamera(gameObject.collisionRect, camera), sdl.MapRGBA(surface.Format, 40, 192, 255, 64))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.RenderGameObjects {
|
if config.RenderGameObjects {
|
||||||
for _, coloredRect := range gameObject.visualRects[gameObject.orientation] {
|
for _, coloredRect := range gameObject.visualRects[gameObject.orientation] {
|
||||||
finalRect := gameObject.adjustRectToCamera(coloredRect.rect, camera)
|
finalRect := gameObject.adjustRectToCamera(coloredRect.rect, camera)
|
||||||
r, g, b, a := coloredRect.color.RGBA()
|
r, g, b, a := coloredRect.color.RGBA()
|
||||||
surface.FillRect(finalRect, sdl.MapRGBA(surface.Format, uint8(r), uint8(g), uint8(b), uint8(a)))
|
_ = surface.FillRect(finalRect, sdl.MapRGBA(surface.Format, uint8(r), uint8(g), uint8(b), uint8(a)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
1
go.mod
1
go.mod
@ -5,5 +5,4 @@ go 1.21
|
|||||||
require (
|
require (
|
||||||
github.com/aquilax/go-perlin v1.1.0
|
github.com/aquilax/go-perlin v1.1.0
|
||||||
github.com/veandco/go-sdl2 v0.4.40
|
github.com/veandco/go-sdl2 v0.4.40
|
||||||
google.golang.org/protobuf v1.34.2
|
|
||||||
)
|
)
|
||||||
|
10
graphics.go
10
graphics.go
@ -99,8 +99,8 @@ func handleWindowResize(window *sdl.Window, logicalSurface *sdl.Surface) {
|
|||||||
srcRect := &sdl.Rect{X: 0, Y: 0, W: logicalSurface.W, H: logicalSurface.H}
|
srcRect := &sdl.Rect{X: 0, Y: 0, W: logicalSurface.W, H: logicalSurface.H}
|
||||||
dstRect := &sdl.Rect{X: letterboxX, Y: letterboxY, W: newWidth, H: newHeight}
|
dstRect := &sdl.Rect{X: letterboxX, Y: letterboxY, W: newWidth, H: newHeight}
|
||||||
|
|
||||||
logicalSurface.BlitScaled(srcRect, windowSurface, dstRect)
|
_ = logicalSurface.BlitScaled(srcRect, windowSurface, dstRect)
|
||||||
window.UpdateSurface()
|
_ = window.UpdateSurface()
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustWindow(window *sdl.Window, logicalSurface *sdl.Surface) {
|
func adjustWindow(window *sdl.Window, logicalSurface *sdl.Surface) {
|
||||||
@ -133,15 +133,15 @@ func adjustWindow(window *sdl.Window, logicalSurface *sdl.Surface) {
|
|||||||
letterboxY := (windowHeight - newHeight) / 2
|
letterboxY := (windowHeight - newHeight) / 2
|
||||||
|
|
||||||
// Fill background
|
// Fill background
|
||||||
windowSurface.FillRect(nil, sdl.MapRGBA(logicalSurface.Format, 80, 20, 10, 255))
|
_ = windowSurface.FillRect(nil, sdl.MapRGBA(logicalSurface.Format, 80, 20, 10, 255))
|
||||||
|
|
||||||
// Set source and destination rectangles
|
// Set source and destination rectangles
|
||||||
srcRect := &sdl.Rect{X: 0, Y: 0, W: logicalSurface.W, H: logicalSurface.H}
|
srcRect := &sdl.Rect{X: 0, Y: 0, W: logicalSurface.W, H: logicalSurface.H}
|
||||||
dstRect := &sdl.Rect{X: letterboxX, Y: letterboxY, W: newWidth, H: newHeight}
|
dstRect := &sdl.Rect{X: letterboxX, Y: letterboxY, W: newWidth, H: newHeight}
|
||||||
|
|
||||||
// Perform the scaled blit
|
// Perform the scaled blit
|
||||||
logicalSurface.BlitScaled(srcRect, windowSurface, dstRect)
|
_ = logicalSurface.BlitScaled(srcRect, windowSurface, dstRect)
|
||||||
|
|
||||||
// Update the window surface
|
// Update the window surface
|
||||||
window.UpdateSurface()
|
_ = window.UpdateSurface()
|
||||||
}
|
}
|
||||||
|
8
hud.go
8
hud.go
@ -48,13 +48,13 @@ func initHud(surface *sdl.Surface) {
|
|||||||
sdl.MapRGBA(surface.Format, 40, 243, 243, 255),
|
sdl.MapRGBA(surface.Format, 40, 243, 243, 255),
|
||||||
}
|
}
|
||||||
HUDColor := sdl.MapRGBA(surface.Format, 101, 101, 101, 255)
|
HUDColor := sdl.MapRGBA(surface.Format, 101, 101, 101, 255)
|
||||||
surface.FillRect(nil, HUDColor)
|
_ = surface.FillRect(nil, HUDColor)
|
||||||
|
|
||||||
for letterIndex, letter := range rects {
|
for letterIndex, letter := range rects {
|
||||||
for _, letterRect := range letter {
|
for _, letterRect := range letter {
|
||||||
offsetRect := letterRect
|
offsetRect := letterRect
|
||||||
offsetRect.Y += letterOffsets[letterIndex]
|
offsetRect.Y += letterOffsets[letterIndex]
|
||||||
surface.FillRect(&offsetRect, letterColors[letterIndex])
|
_ = surface.FillRect(&offsetRect, letterColors[letterIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ func initHud(surface *sdl.Surface) {
|
|||||||
func renderHud(player *Player, surface *sdl.Surface) {
|
func renderHud(player *Player, surface *sdl.Surface) {
|
||||||
|
|
||||||
for _, letterOffset := range letterOffsets {
|
for _, letterOffset := range letterOffsets {
|
||||||
surface.FillRect(&sdl.Rect{X: 16, Y: letterOffset - 1, W: 90, H: 7}, 0)
|
_ = surface.FillRect(&sdl.Rect{X: 16, Y: letterOffset - 1, W: 90, H: 7}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
HUDValues := []int32{
|
HUDValues := []int32{
|
||||||
@ -72,7 +72,7 @@ func renderHud(player *Player, surface *sdl.Surface) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for HUDValueIndex, HUDValue := range HUDValues {
|
for HUDValueIndex, HUDValue := range HUDValues {
|
||||||
surface.FillRect(
|
_ = surface.FillRect(
|
||||||
&sdl.Rect{
|
&sdl.Rect{
|
||||||
X: 17,
|
X: 17,
|
||||||
Y: letterOffsets[HUDValueIndex],
|
Y: letterOffsets[HUDValueIndex],
|
||||||
|
74
main.go
74
main.go
@ -2,23 +2,22 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime/pprof"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const GameVersion = "TunnEElineningnegbfbf Through the wiAldWest"
|
const GameVersion = "0.0.1"
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
MapWidth uint32 `json:"map_width"`
|
MapWidth uint32 `json:"map_width"`
|
||||||
MapHeight uint32 `json:"map_height"`
|
MapHeight uint32 `json:"map_height"`
|
||||||
BlastRadius uint32 `json:"blast_radius"`
|
BlastRadius uint8 `json:"blast_radius"`
|
||||||
|
|
||||||
MaxEnergy uint32 `json:"max_energy"`
|
MaxEnergy uint32 `json:"max_energy"`
|
||||||
MaxAmmunition uint32 `json:"max_ammunition"`
|
MaxAmmunition uint32 `json:"max_ammunition"`
|
||||||
@ -120,14 +119,14 @@ func loadOrCreateConfig(filename string) (*Config, error) {
|
|||||||
return nil, fmt.Errorf("failed to marshal config: %v", err)
|
return nil, fmt.Errorf("failed to marshal config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, data, 0644)
|
err = os.WriteFile(filename, data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to write config file: %v", err)
|
return nil, fmt.Errorf("failed to write config file: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Config file created with default values.")
|
fmt.Println("Config file created with default values.")
|
||||||
} else {
|
} else {
|
||||||
// File exists, load the config
|
// File exists, load the config
|
||||||
data, err := ioutil.ReadFile(filename)
|
data, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read config file: %v", err)
|
return nil, fmt.Errorf("failed to read config file: %v", err)
|
||||||
}
|
}
|
||||||
@ -172,17 +171,6 @@ func main() {
|
|||||||
initializeSDL()
|
initializeSDL()
|
||||||
defer sdl.Quit()
|
defer sdl.Quit()
|
||||||
}
|
}
|
||||||
if config.ProfilerOn {
|
|
||||||
f, err := os.Create("cpuprofile")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("could not create CPU profile: ", err)
|
|
||||||
}
|
|
||||||
defer f.Close() // error handling omitted for example
|
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
|
||||||
log.Fatal("could not start CPU profile: ", err)
|
|
||||||
}
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
|
||||||
mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)}
|
mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)}
|
||||||
players := make(map[uint32]*Player)
|
players := make(map[uint32]*Player)
|
||||||
initPlayerColors()
|
initPlayerColors()
|
||||||
@ -210,7 +198,9 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to connect to server: %v", err)
|
log.Fatalf("Failed to connect to server: %v", err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func(conn *net.TCPConn) {
|
||||||
|
_ = conn.Close()
|
||||||
|
}(conn)
|
||||||
|
|
||||||
go handleConnectionClient(conn, players, bases, bullets, bulletParticles)
|
go handleConnectionClient(conn, players, bases, bullets, bulletParticles)
|
||||||
for !clientInitialized {
|
for !clientInitialized {
|
||||||
@ -238,12 +228,15 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// close listener
|
// close listener
|
||||||
defer listen.Close()
|
defer func(listen *net.TCPListener) {
|
||||||
|
_ = listen.Close()
|
||||||
|
}(listen)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := listen.AcceptTCP()
|
conn, err := listen.AcceptTCP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" {
|
var opErr *net.OpError
|
||||||
|
if errors.As(err, &opErr) && opErr.Err.Error() == "use of closed network connection" {
|
||||||
log.Println("Listener closed, stopping server.")
|
log.Println("Listener closed, stopping server.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -349,25 +342,24 @@ func runGameLogic(players map[uint32]*Player, gameMap *GameMap, bases map[uint32
|
|||||||
//update remote player maps
|
//update remote player maps
|
||||||
totalRemotePlayerUpdate += profileSection(func() {
|
totalRemotePlayerUpdate += profileSection(func() {
|
||||||
var wgX sync.WaitGroup
|
var wgX sync.WaitGroup
|
||||||
var wgX2 sync.WaitGroup
|
|
||||||
playerUpdateMutex.Lock()
|
playerUpdateMutex.Lock()
|
||||||
playersMutex.RLock()
|
playersMutex.RLock()
|
||||||
for _, player := range players {
|
for _, player := range players {
|
||||||
if player.local {
|
if player.local || !player.initialized {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
wgX.Add(6)
|
wgX.Add(6)
|
||||||
|
go player.updateRemotePlayerBases(bases, players, &wgX)
|
||||||
go player.updateRemotePlayerMap(&wgX)
|
go player.updateRemotePlayerMap(&wgX)
|
||||||
go player.updateRemotePlayerBases(bases, &wgX)
|
|
||||||
go player.updateRemotePlayerBullets(bullets, &wgX)
|
go player.updateRemotePlayerBullets(bullets, &wgX)
|
||||||
go player.updateRemotePlayerBulletParticles(bulletParticles, &wgX)
|
go player.updateRemotePlayerBulletParticles(bulletParticles, &wgX)
|
||||||
go player.updateRemotePlayerPlayers(players, &wgX)
|
go player.updateRemotePlayerPlayers(players, &wgX)
|
||||||
playerX := player
|
playerX := player
|
||||||
go func() {
|
go func() {
|
||||||
fail := playerX.sendInfoToPlayer(&wgX)
|
success := playerX.sendPlayerUpdate(&wgX)
|
||||||
if fail {
|
if !success {
|
||||||
if playerX.connection != nil {
|
if playerX.connection != nil {
|
||||||
(*playerX.connection).Close()
|
_ = (*playerX.connection).Close()
|
||||||
}
|
}
|
||||||
baseMutex.Lock()
|
baseMutex.Lock()
|
||||||
if bases[playerX.playerID] != nil {
|
if bases[playerX.playerID] != nil {
|
||||||
@ -382,28 +374,6 @@ func runGameLogic(players map[uint32]*Player, gameMap *GameMap, bases map[uint32
|
|||||||
}
|
}
|
||||||
playersMutex.RUnlock()
|
playersMutex.RUnlock()
|
||||||
wgX.Wait()
|
wgX.Wait()
|
||||||
|
|
||||||
playersMutex.Lock()
|
|
||||||
for _, player := range players {
|
|
||||||
wgX2.Add(1)
|
|
||||||
playerX := player
|
|
||||||
go func() {
|
|
||||||
fail := playerX.sendUpdatesToPlayer(players, &wgX2)
|
|
||||||
if fail {
|
|
||||||
if playerX.connection != nil {
|
|
||||||
(*playerX.connection).Close()
|
|
||||||
}
|
|
||||||
baseMutex.Lock()
|
|
||||||
if bases[playerX.playerID] != nil {
|
|
||||||
bases[playerX.playerID].delete(bases)
|
|
||||||
}
|
|
||||||
baseMutex.Unlock()
|
|
||||||
delete(players, playerX.playerID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
playersMutex.Unlock()
|
|
||||||
wgX2.Wait()
|
|
||||||
playerUpdateMutex.Unlock()
|
playerUpdateMutex.Unlock()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -443,11 +413,11 @@ func initPlayer(playerIndex uint8, player *Player) {
|
|||||||
}
|
}
|
||||||
player.window, player.logicalSurface = setupWindowAndSurface(playerIndex)
|
player.window, player.logicalSurface = setupWindowAndSurface(playerIndex)
|
||||||
logicalColor := sdl.MapRGBA(player.logicalSurface.Format, 101, 101, 0, 255)
|
logicalColor := sdl.MapRGBA(player.logicalSurface.Format, 101, 101, 0, 255)
|
||||||
player.logicalSurface.FillRect(nil, logicalColor)
|
_ = player.logicalSurface.FillRect(nil, logicalColor)
|
||||||
|
|
||||||
player.playSurface, player.playSurfaceRect, player.playSurfaceTargetRect = setupPlaySurface()
|
player.playSurface, player.playSurfaceRect, player.playSurfaceTargetRect = setupPlaySurface()
|
||||||
playColor := sdl.MapRGBA(player.playSurface.Format, 101, 0, 101, 255)
|
playColor := sdl.MapRGBA(player.playSurface.Format, 101, 0, 101, 255)
|
||||||
player.playSurface.FillRect(nil, playColor)
|
_ = player.playSurface.FillRect(nil, playColor)
|
||||||
|
|
||||||
player.HUDSurface, player.HUDSurfaceRect, player.HUDSurfaceTargetRect = setupHUDSurface()
|
player.HUDSurface, player.HUDSurfaceRect, player.HUDSurfaceTargetRect = setupHUDSurface()
|
||||||
initHud(player.HUDSurface)
|
initHud(player.HUDSurface)
|
||||||
@ -498,8 +468,8 @@ func doPlayerFrame(playerIndex uint8, player *Player, players map[uint32]*Player
|
|||||||
player.tick(bullets)
|
player.tick(bullets)
|
||||||
player.gameObject.prevBaseRect = player.gameObject.baseRect
|
player.gameObject.prevBaseRect = player.gameObject.baseRect
|
||||||
renderHud(player, player.HUDSurface)
|
renderHud(player, player.HUDSurface)
|
||||||
player.playSurface.BlitScaled(player.playSurfaceRect, player.logicalSurface, player.playSurfaceTargetRect)
|
_ = player.playSurface.BlitScaled(player.playSurfaceRect, player.logicalSurface, player.playSurfaceTargetRect)
|
||||||
player.HUDSurface.BlitScaled(player.HUDSurfaceRect, player.logicalSurface, player.HUDSurfaceTargetRect)
|
_ = player.HUDSurface.BlitScaled(player.HUDSurfaceRect, player.logicalSurface, player.HUDSurfaceTargetRect)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Profile window adjustments
|
// Profile window adjustments
|
||||||
|
119
netCode.go
Normal file
119
netCode.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"hash/crc32"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sendPacket sends a packet with the given packetID and data over the provided TCP connection.
|
||||||
|
func sendPacket(packetID uint8, data []byte, conn *net.TCPConn) error {
|
||||||
|
// Create a buffer with the packet ID and data
|
||||||
|
tempBuffer := append([]byte{packetID}, data...)
|
||||||
|
|
||||||
|
// Calculate the checksum of the packet ID + data
|
||||||
|
checksum := crc32.ChecksumIEEE(tempBuffer)
|
||||||
|
|
||||||
|
// Convert the checksum to a 4-byte slice
|
||||||
|
checksumBytes := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(checksumBytes, checksum)
|
||||||
|
|
||||||
|
// Prepend the checksum to the beginning of the buffer
|
||||||
|
tempBuffer = append(checksumBytes, tempBuffer...)
|
||||||
|
|
||||||
|
// Calculate the total length (checksum + packet ID + data)
|
||||||
|
packetLength := uint32(len(tempBuffer))
|
||||||
|
|
||||||
|
// Prepare the output buffer with the length field
|
||||||
|
outBuffer := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(outBuffer, packetLength)
|
||||||
|
|
||||||
|
// Append the tempBuffer (checksum + packet ID + data) to outBuffer
|
||||||
|
outBuffer = append(outBuffer, tempBuffer...)
|
||||||
|
|
||||||
|
// Send the packet over the connection
|
||||||
|
writeLen, err := conn.Write(outBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if writeLen != len(outBuffer) {
|
||||||
|
return errors.New("write length does not match output buffer length")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// receivePacket receives a packet from the provided TCP connection and verifies it.
|
||||||
|
func receivePacket(conn *net.TCPConn) (uint8, uint32, []byte, error) {
|
||||||
|
// Step 1: Read the packet length (4 bytes)
|
||||||
|
lengthBuffer := make([]byte, 4)
|
||||||
|
n, err := conn.Read(lengthBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, nil, err
|
||||||
|
}
|
||||||
|
if n != len(lengthBuffer) {
|
||||||
|
return 0, 0, nil, errors.New("read length does not match read length")
|
||||||
|
}
|
||||||
|
|
||||||
|
packetLength := binary.LittleEndian.Uint32(lengthBuffer)
|
||||||
|
|
||||||
|
// Step 2: Allocate a buffer to hold the rest of the packet (checksum + packetID + data)
|
||||||
|
packetBuffer := make([]byte, packetLength)
|
||||||
|
n, err = conn.Read(packetBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, nil, err
|
||||||
|
}
|
||||||
|
if n != int(packetLength) {
|
||||||
|
return 0, 0, nil, errors.New("read length does not match packet length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we've read enough bytes
|
||||||
|
if uint32(len(packetBuffer)) != packetLength {
|
||||||
|
return 0, 0, nil, errors.New("incomplete packet received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Extract the checksum (4 bytes)
|
||||||
|
receivedChecksum := binary.LittleEndian.Uint32(packetBuffer[:4])
|
||||||
|
|
||||||
|
// Step 4: Extract the packet ID (1 byte)
|
||||||
|
packetID := packetBuffer[4]
|
||||||
|
|
||||||
|
// Step 5: Extract the data
|
||||||
|
data := packetBuffer[5:]
|
||||||
|
|
||||||
|
// Step 6: Verify the checksum
|
||||||
|
calculatedChecksum := crc32.ChecksumIEEE(append([]byte{packetID}, data...))
|
||||||
|
|
||||||
|
if receivedChecksum != calculatedChecksum {
|
||||||
|
return 0, 0, nil, errors.New("checksum mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the packet ID and data
|
||||||
|
return packetID, uint32(len(data)), data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolToByte(b bool) byte {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteToBool(b byte) bool {
|
||||||
|
return b == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentVersion() []uint8 {
|
||||||
|
versionArrayString := strings.Split(GameVersion, ".")
|
||||||
|
if len(versionArrayString) < 3 {
|
||||||
|
return []uint8{}
|
||||||
|
}
|
||||||
|
var versionArray []uint8
|
||||||
|
for i := 0; i < len(versionArrayString); i++ {
|
||||||
|
intVersion, _ := strconv.Atoi(versionArrayString[i])
|
||||||
|
versionArray = append(versionArray, uint8(intVersion))
|
||||||
|
}
|
||||||
|
return versionArray
|
||||||
|
}
|
@ -379,9 +379,11 @@ func handleInput(keyboard []uint8, bullets map[uint32]*Bullet, player *Player, g
|
|||||||
// Handle movement after the loop
|
// Handle movement after the loop
|
||||||
if moveUp || moveDown || moveLeft || moveRight {
|
if moveUp || moveDown || moveLeft || moveRight {
|
||||||
if player.tryMove(gameMap, shoot, players) {
|
if player.tryMove(gameMap, shoot, players) {
|
||||||
|
if player.energy > serverConfig.MovementCost {
|
||||||
player.energy -= serverConfig.MovementCost
|
player.energy -= serverConfig.MovementCost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
316
playerutil.go
Normal file
316
playerutil.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUnusedColor(colors []PlayerColors, players map[uint32]*Player) uint32 {
|
||||||
|
for i, c := range colors {
|
||||||
|
foundPlayerWithColor := false
|
||||||
|
var colorCompare []uint8
|
||||||
|
clrR, clrG, clrB, clrA := c.tracks.RGBA()
|
||||||
|
colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA))
|
||||||
|
clrR, clrG, clrB, clrA = c.body.RGBA()
|
||||||
|
colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA))
|
||||||
|
clrR, clrG, clrB, clrA = c.cannon.RGBA()
|
||||||
|
colorCompare = append(colorCompare, uint8(clrR), uint8(clrG), uint8(clrB), uint8(clrA))
|
||||||
|
playersMutex.RLock()
|
||||||
|
for _, player := range players {
|
||||||
|
var playerColorsCompare []uint8
|
||||||
|
plrR, plrG, plrB, plrA := player.playerColors.tracks.RGBA()
|
||||||
|
playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA))
|
||||||
|
plrR, plrG, plrB, plrA = player.playerColors.body.RGBA()
|
||||||
|
playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA))
|
||||||
|
plrR, plrG, plrB, plrA = player.playerColors.cannon.RGBA()
|
||||||
|
playerColorsCompare = append(playerColorsCompare, uint8(plrR), uint8(plrG), uint8(plrB), uint8(plrA))
|
||||||
|
foundPlayerWithColor = false
|
||||||
|
if len(playerColorsCompare) == len(colorCompare) {
|
||||||
|
for i, c := range colorCompare {
|
||||||
|
if playerColorsCompare[i] == c {
|
||||||
|
foundPlayerWithColor = true
|
||||||
|
} else {
|
||||||
|
foundPlayerWithColor = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundPlayerWithColor {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playersMutex.RUnlock()
|
||||||
|
if !foundPlayerWithColor {
|
||||||
|
return uint32(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uint32(len(colors) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players map[uint32]*Player, bases map[uint32]*Base) {
|
||||||
|
joyStickCount := sdl.NumJoysticks()
|
||||||
|
if amount > uint8(len(keyMaps)+len(joyMaps)) || amount > uint8(len(keyMaps)+joyStickCount) {
|
||||||
|
panic("Too many players, not enough inputs")
|
||||||
|
}
|
||||||
|
if amount >= uint8(len(playerColors)) {
|
||||||
|
panic("Too many players, not enough colors")
|
||||||
|
}
|
||||||
|
addedKeyboardPlayers := 0
|
||||||
|
for i := uint8(0); i < amount; i++ {
|
||||||
|
var keyMap KeyMap
|
||||||
|
var joyMap JoyMap
|
||||||
|
var joyStick *sdl.Joystick
|
||||||
|
|
||||||
|
if (config.DoAllKeymapsPlayers && i <= uint8(len(keyMaps))) || (config.DoKeymapPlayer && i == 0) || (uint8(joyStickCount) <= i) {
|
||||||
|
keyMap = keyMaps[(addedKeyboardPlayers+int(config.KeyBindOffset))%(len(keyMaps)-1)]
|
||||||
|
addedKeyboardPlayers++
|
||||||
|
} else {
|
||||||
|
joyStickIndex := i - uint8(addedKeyboardPlayers)
|
||||||
|
joyMap = joyMaps[joyStickIndex]
|
||||||
|
joyStick = sdl.JoystickOpen(int(joyStickIndex))
|
||||||
|
}
|
||||||
|
unusedColor := getUnusedColor(playerColors, players)
|
||||||
|
createPlayer(
|
||||||
|
true,
|
||||||
|
playerColors[unusedColor],
|
||||||
|
unusedColor,
|
||||||
|
nil,
|
||||||
|
keyMap,
|
||||||
|
joyMap,
|
||||||
|
joyStick,
|
||||||
|
gameMap,
|
||||||
|
players,
|
||||||
|
bases,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeThings(players map[uint32]*Player) {
|
||||||
|
playersMutex.Lock()
|
||||||
|
for _, player := range players {
|
||||||
|
if player.joyStick != nil {
|
||||||
|
player.joyStick.Close()
|
||||||
|
}
|
||||||
|
if player.window != nil {
|
||||||
|
_ = player.window.Destroy()
|
||||||
|
}
|
||||||
|
if player.logicalSurface != nil {
|
||||||
|
player.logicalSurface.Free()
|
||||||
|
}
|
||||||
|
if player.playSurface != nil {
|
||||||
|
player.playSurface.Free()
|
||||||
|
}
|
||||||
|
if player.HUDSurface != nil {
|
||||||
|
player.HUDSurface.Free()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playersMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPlayer(local bool, thisPlayerColors PlayerColors, thisPlayerColorIndex uint32, conn *net.TCPConn, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players map[uint32]*Player, bases map[uint32]*Base) uint32 {
|
||||||
|
coordsAreValid := false
|
||||||
|
var posX, posY uint32
|
||||||
|
maxTries := 1000
|
||||||
|
baseSize := uint32(36) // Since the base is 36x36
|
||||||
|
minDistance := uint32(200) // Minimum distance between bases
|
||||||
|
maxDistance := uint32(500) // Maximum distance between bases
|
||||||
|
|
||||||
|
for !coordsAreValid && maxTries >= 0 {
|
||||||
|
maxTries--
|
||||||
|
posX = uint32(16 + rand.Intn(int(gameMap.width-baseSize-16)))
|
||||||
|
posY = uint32(16 + rand.Intn(int(gameMap.height-baseSize-16)))
|
||||||
|
|
||||||
|
coordsAreValid = true
|
||||||
|
baseMutex.RLock()
|
||||||
|
for _, base := range bases {
|
||||||
|
basePosX := uint32(base.gameObject.baseRect.X)
|
||||||
|
basePosY := uint32(base.gameObject.baseRect.Y)
|
||||||
|
|
||||||
|
// Calculate the distance between the edges of the bases
|
||||||
|
distanceX := max(0, max(basePosX-posX-baseSize, posX-basePosX-baseSize))
|
||||||
|
distanceY := max(0, max(basePosY-posY-baseSize, posY-basePosY-baseSize))
|
||||||
|
|
||||||
|
distanceSquared := distanceX*distanceX + distanceY*distanceY
|
||||||
|
|
||||||
|
if distanceSquared < minDistance*minDistance && distanceSquared > maxDistance*maxDistance {
|
||||||
|
coordsAreValid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
baseMutex.RUnlock()
|
||||||
|
|
||||||
|
// Edge clamping to ensure the base is within the map boundaries
|
||||||
|
if posX < 16 || posX > gameMap.width-baseSize-16 || posY < 16 || posY > gameMap.height-baseSize-16 {
|
||||||
|
coordsAreValid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxTries < 0 {
|
||||||
|
panic("Could not place all players, increase map size")
|
||||||
|
}
|
||||||
|
|
||||||
|
gameObject := &GameObject{}
|
||||||
|
|
||||||
|
gameObject.baseRect = &sdl.Rect{
|
||||||
|
X: int32(posX),
|
||||||
|
Y: int32(posY),
|
||||||
|
W: 7,
|
||||||
|
H: 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
gameObject.addColor(thisPlayerColors.tracks)
|
||||||
|
gameObject.addColor(thisPlayerColors.body)
|
||||||
|
gameObject.addColor(thisPlayerColors.cannon)
|
||||||
|
|
||||||
|
gameObject.orientation = 0 // Up
|
||||||
|
gameObject.addColoredRect(0, 1, 1, 6, 0)
|
||||||
|
gameObject.addColoredRect(4, 1, 1, 6, 0)
|
||||||
|
gameObject.addColoredRect(1, 2, 3, 4, 1)
|
||||||
|
gameObject.addColoredRect(2, 0, 1, 4, 2)
|
||||||
|
|
||||||
|
gameObject.orientation = 1 // Right
|
||||||
|
gameObject.addColoredRect(0, 0, 6, 1, 0)
|
||||||
|
gameObject.addColoredRect(0, 4, 6, 1, 0)
|
||||||
|
gameObject.addColoredRect(1, 1, 4, 3, 1)
|
||||||
|
gameObject.addColoredRect(3, 2, 4, 1, 2)
|
||||||
|
|
||||||
|
gameObject.orientation = 2 // Down
|
||||||
|
gameObject.addColoredRect(0, 0, 1, 6, 0)
|
||||||
|
gameObject.addColoredRect(4, 0, 1, 6, 0)
|
||||||
|
gameObject.addColoredRect(1, 1, 3, 4, 1)
|
||||||
|
gameObject.addColoredRect(2, 3, 1, 4, 2)
|
||||||
|
|
||||||
|
gameObject.orientation = 3 // Left
|
||||||
|
gameObject.addColoredRect(1, 0, 6, 1, 0)
|
||||||
|
gameObject.addColoredRect(1, 4, 6, 1, 0)
|
||||||
|
gameObject.addColoredRect(2, 1, 4, 3, 1)
|
||||||
|
gameObject.addColoredRect(0, 2, 4, 1, 2)
|
||||||
|
|
||||||
|
gameObject.orientation = 4 // Up-Right
|
||||||
|
gameObject.addColoredRect(3, 0, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(2, 1, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(1, 2, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(0, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(6, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(5, 4, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(4, 5, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(3, 6, 1, 1, 0)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(3, 1, 1, 1, 1)
|
||||||
|
gameObject.addColoredRect(2, 2, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(1, 3, 2, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(4, 3, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(2, 4, 3, 1, 1)
|
||||||
|
gameObject.addColoredRect(3, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(5, 1, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(4, 2, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(3, 3, 1, 1, 2)
|
||||||
|
|
||||||
|
// Up-Left orientation (Y-axis reflection)
|
||||||
|
gameObject.orientation = 5 // Up-Left
|
||||||
|
gameObject.addColoredRect(3, 0, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(4, 1, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(5, 2, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(6, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(0, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(1, 4, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(2, 5, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(3, 6, 1, 1, 0)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(3, 1, 1, 1, 1)
|
||||||
|
gameObject.addColoredRect(3, 2, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(4, 3, 2, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(1, 3, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(2, 4, 3, 1, 1)
|
||||||
|
gameObject.addColoredRect(3, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(1, 1, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(2, 2, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(3, 3, 1, 1, 2)
|
||||||
|
|
||||||
|
// Down-Right orientation (X-axis reflection)
|
||||||
|
gameObject.orientation = 6 // Down-Right
|
||||||
|
gameObject.addColoredRect(3, 6, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(2, 5, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(1, 4, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(0, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(6, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(5, 2, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(4, 1, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(3, 0, 1, 1, 0)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(3, 5, 1, 1, 1)
|
||||||
|
gameObject.addColoredRect(2, 4, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(1, 3, 2, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(4, 3, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(2, 2, 3, 1, 1)
|
||||||
|
gameObject.addColoredRect(3, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(5, 5, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(4, 4, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(3, 3, 1, 1, 2)
|
||||||
|
|
||||||
|
// Down-Left orientation (XY reflection)
|
||||||
|
gameObject.orientation = 7 // Down-Left
|
||||||
|
gameObject.addColoredRect(3, 6, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(4, 5, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(5, 4, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(6, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(0, 3, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(1, 2, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(2, 1, 1, 1, 0)
|
||||||
|
gameObject.addColoredRect(3, 0, 1, 1, 0)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(3, 1, 1, 1, 1)
|
||||||
|
gameObject.addColoredRect(2, 2, 3, 1, 1)
|
||||||
|
gameObject.addColoredRect(1, 3, 2, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(4, 3, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(3, 4, 2, 1, 1)
|
||||||
|
gameObject.addColoredRect(3, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
gameObject.addColoredRect(1, 5, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(2, 4, 1, 1, 2)
|
||||||
|
gameObject.addColoredRect(3, 3, 1, 1, 2)
|
||||||
|
|
||||||
|
gameObject.orientation = 0
|
||||||
|
gameObject.adjustBaseRect()
|
||||||
|
|
||||||
|
if !local && (keyMap.exit != keyMap.shoot || joyMap.exitButton != joyMap.shootButton) {
|
||||||
|
panic("Input assigned to remote player")
|
||||||
|
}
|
||||||
|
|
||||||
|
knownGameMap := GameMap{width: gameMap.width, height: gameMap.height, tiles: make([][]uint8, serverConfig.MapWidth)}
|
||||||
|
for i := uint32(0); i < serverConfig.MapWidth; i++ {
|
||||||
|
knownGameMap.tiles[i] = make([]uint8, serverConfig.MapHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
playersMutex.Lock()
|
||||||
|
players[lastPlayerID] = &Player{
|
||||||
|
playerColors: thisPlayerColors,
|
||||||
|
playerColorID: thisPlayerColorIndex,
|
||||||
|
keyMap: keyMap,
|
||||||
|
joyMap: joyMap,
|
||||||
|
joyStick: joyStick,
|
||||||
|
shields: serverConfig.MaxShields,
|
||||||
|
energy: serverConfig.MaxEnergy,
|
||||||
|
gameObject: gameObject,
|
||||||
|
local: local,
|
||||||
|
connection: conn,
|
||||||
|
playerID: lastPlayerID,
|
||||||
|
knownBulletParticles: make(map[uint32]*BulletParticle),
|
||||||
|
knownBases: make(map[uint32]*Base),
|
||||||
|
knownPlayers: make(map[uint32]*Player),
|
||||||
|
knownBullets: make(map[uint32]*Bullet),
|
||||||
|
knownGameMap: &knownGameMap,
|
||||||
|
}
|
||||||
|
playersMutex.Unlock()
|
||||||
|
lastPlayerID++
|
||||||
|
return lastPlayerID - 1
|
||||||
|
}
|
236
serverboundpacket.go
Normal file
236
serverboundpacket.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendPlayerStartRequest(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 receivePlayerStartRequest(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 sendPlayerLocation(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 receivePlayerLocation(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 sendDigBlock(posX, posY uint32, isShooting bool, conn *net.TCPConn) error {
|
||||||
|
data := make([]byte, 9)
|
||||||
|
binary.LittleEndian.PutUint32(data[0:4], posX)
|
||||||
|
binary.LittleEndian.PutUint32(data[4:8], posY)
|
||||||
|
data[8] = boolToByte(isShooting)
|
||||||
|
return sendPacket(2, data, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveDigBlock(data []byte) (posX, posY uint32, isShooting bool, err error) {
|
||||||
|
if len(data) < 9 {
|
||||||
|
return 0, 0, false, 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])
|
||||||
|
isShooting = byteToBool(data[8])
|
||||||
|
return posX, posY, isShooting, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendShoot(isSuper bool, conn *net.TCPConn) error {
|
||||||
|
data := make([]byte, 1)
|
||||||
|
data[0] = boolToByte(isSuper)
|
||||||
|
return sendPacket(3, data, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveShoot(data []byte) (isSuper bool, err error) {
|
||||||
|
if len(data) < 1 {
|
||||||
|
return false, fmt.Errorf("insufficient data length, got %d instead of 1", len(data))
|
||||||
|
}
|
||||||
|
isSuper = byteToBool(data[0])
|
||||||
|
return isSuper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRequest(conn *net.TCPConn, players map[uint32]*Player, bullets map[uint32]*Bullet, bases map[uint32]*Base) {
|
||||||
|
defer func(conn *net.TCPConn) {
|
||||||
|
_ = conn.Close()
|
||||||
|
}(conn)
|
||||||
|
for {
|
||||||
|
packetID, _, packetData, err := receivePacket(conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packetID {
|
||||||
|
//goland:noinspection GoDeferInLoop,GoDeferInLoop
|
||||||
|
case 0:
|
||||||
|
versionArray := getCurrentVersion()
|
||||||
|
major, minor, patch, err := receivePlayerStartRequest(packetData)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if versionArray[0] != major || versionArray[1] != minor || versionArray[2] != patch {
|
||||||
|
log.Printf("Incorrect version tried to connect with %d.%d.%d from %s to %d.%d.%d", versionArray[0], versionArray[1], versionArray[2], conn.RemoteAddr().String(), major, minor, patch)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Connected from %s with %d.%d.%d", conn.RemoteAddr().String(), major, minor, patch)
|
||||||
|
unusedColor := getUnusedColor(playerColors, players)
|
||||||
|
newPlayerID := createPlayer(false, playerColors[unusedColor], unusedColor, conn, KeyMap{}, JoyMap{}, nil, gameMap, players, bases)
|
||||||
|
defer delete(players, newPlayerID)
|
||||||
|
playersMutex.RLock()
|
||||||
|
player := players[newPlayerID]
|
||||||
|
playersMutex.RUnlock()
|
||||||
|
baseMutex.Lock()
|
||||||
|
bases[newPlayerID] = createBase(gameMap,
|
||||||
|
player.playerColors.body,
|
||||||
|
uint32(player.gameObject.baseRect.X-14),
|
||||||
|
uint32(player.gameObject.baseRect.Y-14),
|
||||||
|
player.playerID,
|
||||||
|
uint32(float64(player.gameObject.baseRect.W)*1.5))
|
||||||
|
|
||||||
|
baseMutex.Unlock()
|
||||||
|
defer bases[newPlayerID].delete(bases)
|
||||||
|
netPlayerMapper[conn] = player
|
||||||
|
|
||||||
|
success := player.sendVersionBackToPlayer()
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Sent version to %d", newPlayerID)
|
||||||
|
success = player.sendServerInfoToPlayer()
|
||||||
|
if !success {
|
||||||
|
log.Printf("Server info not sent to %d", newPlayerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Sent server info to %d", newPlayerID)
|
||||||
|
var wgX sync.WaitGroup
|
||||||
|
wgX.Add(1)
|
||||||
|
success = player.sendPlayerUpdate(&wgX)
|
||||||
|
if !success {
|
||||||
|
log.Printf("Server info not sent to %d", newPlayerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Sent info to %d", newPlayerID)
|
||||||
|
success = player.sendLocationToPlayer()
|
||||||
|
if !success {
|
||||||
|
log.Printf("Server location not sent to %d", newPlayerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Sent location to %d", newPlayerID)
|
||||||
|
player.initialized = true
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
player := netPlayerMapper[conn]
|
||||||
|
if player == nil {
|
||||||
|
log.Fatalf("Can't find player for connection")
|
||||||
|
}
|
||||||
|
newX, newY, orientation, err := receivePlayerLocation(packetData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error receiving player location: %s", err)
|
||||||
|
}
|
||||||
|
oldX := uint32(player.gameObject.baseRect.X)
|
||||||
|
oldY := uint32(player.gameObject.baseRect.Y)
|
||||||
|
// Update player orientation
|
||||||
|
player.gameObject.orientation = 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+uint32(player.gameObject.baseRect.W); x++ {
|
||||||
|
for y := newY; y < newY+uint32(player.gameObject.baseRect.H); y++ {
|
||||||
|
if gameMap.tiles[x][y] != 0 { // Assumes 0 is passable, any other value is impassable
|
||||||
|
player.knownGameMap.tiles[x][y] = 0
|
||||||
|
canMove = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !canMove {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all tiles in the area are passable, update the player's position
|
||||||
|
if canMove {
|
||||||
|
player.gameObject.baseRect = &sdl.Rect{
|
||||||
|
X: int32(newX),
|
||||||
|
Y: int32(newY),
|
||||||
|
W: player.gameObject.baseRect.W,
|
||||||
|
H: player.gameObject.baseRect.H,
|
||||||
|
}
|
||||||
|
// Deduct energy if the player has moved
|
||||||
|
if newX != oldX || newY != oldY {
|
||||||
|
if player.energy > serverConfig.MovementCost {
|
||||||
|
player.energy -= serverConfig.MovementCost
|
||||||
|
player.movementCooldown = serverConfig.MovementCooldown
|
||||||
|
} else {
|
||||||
|
player.movementCooldown = serverConfig.MovementCooldownNoEnergy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If any part of the new area is impassable, revert to the old position
|
||||||
|
player.sendLocationToPlayer()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the movement is invalid (not adjacent or on cooldown), revert to the old position
|
||||||
|
player.sendLocationToPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
player := netPlayerMapper[conn]
|
||||||
|
if player == nil {
|
||||||
|
log.Fatalf("Can't find player for connection")
|
||||||
|
}
|
||||||
|
posX, posY, isShooting, err := receiveDigBlock(packetData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error receiving dig block: %s", err)
|
||||||
|
}
|
||||||
|
res := player.digBlock(posX, posY, gameMap, isShooting)
|
||||||
|
if res == 1 {
|
||||||
|
if player.energy > serverConfig.DiggingCost {
|
||||||
|
player.energy -= serverConfig.DiggingCost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
player := netPlayerMapper[conn]
|
||||||
|
if player == nil {
|
||||||
|
log.Fatalf("Can't find player for connection")
|
||||||
|
}
|
||||||
|
isSuper, err := receiveShoot(packetData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error receiving dig block: %s", err)
|
||||||
|
}
|
||||||
|
player.shoot(isSuper, bullets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
tuneller.proto
135
tuneller.proto
@ -1,135 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
package goingtunneling;
|
|
||||||
option go_package = "./proto";
|
|
||||||
|
|
||||||
message Position {
|
|
||||||
int32 posX = 1;
|
|
||||||
int32 posY = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Player {
|
|
||||||
uint32 playerID = 1;
|
|
||||||
PlayerLocation location = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerLocation {
|
|
||||||
Position position = 1;
|
|
||||||
uint32 orientation = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Bullet {
|
|
||||||
Position position = 1;
|
|
||||||
uint32 direction = 2;
|
|
||||||
Color color = 3;
|
|
||||||
uint32 id = 4;
|
|
||||||
bool super = 5;
|
|
||||||
uint32 ownerID = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Color {
|
|
||||||
uint32 red = 1;
|
|
||||||
uint32 green = 2;
|
|
||||||
uint32 blue = 3;
|
|
||||||
uint32 alpha = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BulletParticle {
|
|
||||||
Position position = 1;
|
|
||||||
uint32 expirationTimer = 2;
|
|
||||||
Color color = 3;
|
|
||||||
uint32 id = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ServerInfo {
|
|
||||||
uint32 maxEnergy = 1;
|
|
||||||
uint32 maxAmmunition = 2;
|
|
||||||
uint32 maxShields = 3;
|
|
||||||
uint32 mapWidth = 4;
|
|
||||||
uint32 mapHeight = 5;
|
|
||||||
uint32 normalShotCost = 6;
|
|
||||||
uint32 superShotCost = 7;
|
|
||||||
uint32 reloadCost = 8;
|
|
||||||
uint32 movementCost = 9;
|
|
||||||
uint32 diggingCost = 10;
|
|
||||||
uint32 shootDiggingBonus = 11;
|
|
||||||
uint32 shootCooldown = 12;
|
|
||||||
uint32 rechargeCooldown = 13;
|
|
||||||
uint32 rechargeOpponentCooldown = 14;
|
|
||||||
uint32 repairCooldown = 15;
|
|
||||||
uint32 diggingCooldown = 16;
|
|
||||||
uint32 movementCooldown = 17;
|
|
||||||
uint32 movementCooldownNoEnergy = 18;
|
|
||||||
uint32 diggingCooldownNoEnergy = 19;
|
|
||||||
uint32 reloadCooldown = 20;
|
|
||||||
uint32 blastRadius = 21;
|
|
||||||
uint32 playerID = 22;
|
|
||||||
uint32 playerColorID = 23;
|
|
||||||
uint32 reloadWait = 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerUpdate {
|
|
||||||
uint32 energy = 1;
|
|
||||||
uint32 ammo = 2;
|
|
||||||
uint32 shields = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BaseLocation {
|
|
||||||
Position position = 1;
|
|
||||||
Player owner = 2;
|
|
||||||
Color color = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TileUpdate {
|
|
||||||
Position position = 1;
|
|
||||||
uint32 kind = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WorldUpdate {
|
|
||||||
repeated Player players = 1;
|
|
||||||
repeated BaseLocation base = 2;
|
|
||||||
repeated Bullet bullets = 3;
|
|
||||||
repeated BulletParticle bulletParticles = 4;
|
|
||||||
repeated TileUpdate tileUpdate = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ClientBound {
|
|
||||||
oneof clientBoundMessage {
|
|
||||||
ServerInfo serverInfo = 1;
|
|
||||||
PlayerUpdate playerUpdate = 2;
|
|
||||||
PlayerLocation playerLocationUpdate = 3;
|
|
||||||
WorldUpdate worldUpdate = 4;
|
|
||||||
PlayerStartResponse playerStartResponse = 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message DigBlock {
|
|
||||||
Position position = 1;
|
|
||||||
bool isShooting = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Shoot {
|
|
||||||
bool super = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerAction {
|
|
||||||
oneof playerAction {
|
|
||||||
DigBlock digBlock = 1;
|
|
||||||
Shoot shoot = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerStartRequest {
|
|
||||||
string version = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerStartResponse {
|
|
||||||
string version = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ServerBound {
|
|
||||||
oneof serverBoundMessage {
|
|
||||||
PlayerLocation playerPosition = 1;
|
|
||||||
PlayerAction playerAction = 2;
|
|
||||||
PlayerStartRequest playerStartRequest = 3;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user