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 } } } }