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