From 5c5040cd42f1067f72446f228b43bbd63cacbc09 Mon Sep 17 00:00:00 2001 From: bdnugget Date: Thu, 31 Oct 2024 01:06:39 +0100 Subject: [PATCH] Fix OOB and refactor --- camera.go | 54 +++++ constants.go | 12 + core/constants.go | 1 - core/types.go | 1 - input.go | 88 +++++++ main.go | 552 ++++--------------------------------------- map.go | 48 ++++ network.go | 121 ++++++++++ pathfinding.go | 114 +++++++++ player.go | 63 +++++ render.go | 1 + types.go | 31 +++ utils/pathfinding.go | 1 - 13 files changed, 579 insertions(+), 508 deletions(-) create mode 100644 camera.go create mode 100644 constants.go delete mode 100644 core/constants.go delete mode 100644 core/types.go create mode 100644 input.go create mode 100644 map.go create mode 100644 network.go create mode 100644 pathfinding.go create mode 100644 player.go create mode 100644 render.go create mode 100644 types.go delete mode 100644 utils/pathfinding.go diff --git a/camera.go b/camera.go new file mode 100644 index 0000000..b5e356f --- /dev/null +++ b/camera.go @@ -0,0 +1,54 @@ +package main + +import ( + "math" + + rl "github.com/gen2brain/raylib-go/raylib" +) + +func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) { + // Update camera based on mouse wheel + wheelMove := rl.GetMouseWheelMove() + if wheelMove != 0 { + cameraDistance += -wheelMove * 5 + if cameraDistance < 10 { + cameraDistance = 10 + } + if cameraDistance > 250 { + cameraDistance = 250 + } + } + + // Orbit camera around the player using arrow keys + if rl.IsKeyDown(rl.KeyRight) { + cameraYaw += 100 * deltaTime + } + if rl.IsKeyDown(rl.KeyLeft) { + cameraYaw -= 100 * deltaTime + } + if rl.IsKeyDown(rl.KeyUp) { + cameraPitch -= 50 * deltaTime + if cameraPitch < 20 { + cameraPitch = 20 + } + } + if rl.IsKeyDown(rl.KeyDown) { + cameraPitch += 50 * deltaTime + if cameraPitch > 85 { + cameraPitch = 85 + } + } + + // Calculate the new camera position using spherical coordinates + cameraYawRad := float64(cameraYaw) * rl.Deg2rad + cameraPitchRad := float64(cameraPitch) * rl.Deg2rad + cameraPos := rl.Vector3{ + X: player.X + cameraDistance*float32(math.Cos(cameraYawRad))*float32(math.Cos(cameraPitchRad)), + Y: player.Y + cameraDistance*float32(math.Sin(cameraPitchRad)), + Z: player.Z + cameraDistance*float32(math.Sin(cameraYawRad))*float32(math.Cos(cameraPitchRad)), + } + + // Update the camera's position and target + camera.Position = cameraPos + camera.Target = rl.NewVector3(player.X, player.Y, player.Z) +} diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..a31f2c9 --- /dev/null +++ b/constants.go @@ -0,0 +1,12 @@ +package main + +import "time" + +const ( + MapWidth = 50 + MapHeight = 50 + TileSize = 32 + TileHeight = 2.0 + TickRate = 2600 * time.Millisecond // Server tick rate (600ms) + serverAddr = "localhost:6969" +) diff --git a/core/constants.go b/core/constants.go deleted file mode 100644 index d4b585b..0000000 --- a/core/constants.go +++ /dev/null @@ -1 +0,0 @@ -package utils diff --git a/core/types.go b/core/types.go deleted file mode 100644 index d4b585b..0000000 --- a/core/types.go +++ /dev/null @@ -1 +0,0 @@ -package utils diff --git a/input.go b/input.go new file mode 100644 index 0000000..599c4fa --- /dev/null +++ b/input.go @@ -0,0 +1,88 @@ +package main + +import ( + "fmt" + + rl "github.com/gen2brain/raylib-go/raylib" +) + +func GetTileAtMouse(camera *rl.Camera3D) (Tile, bool) { + if !rl.IsMouseButtonPressed(rl.MouseLeftButton) { + return Tile{}, false + } + mouse := rl.GetMousePosition() + ray := rl.GetMouseRay(mouse, *camera) + + for x := 0; x < MapWidth; x++ { + for y := 0; y < MapHeight; y++ { + tile := mapGrid[x][y] + + // Define the bounding box for each tile based on its position and height + tilePos := rl.NewVector3(float32(x*TileSize), tile.Height*TileHeight, float32(y*TileSize)) + boxMin := rl.Vector3Subtract(tilePos, rl.NewVector3(TileSize/2, TileHeight/2, TileSize/2)) + boxMax := rl.Vector3Add(tilePos, rl.NewVector3(TileSize/2, TileHeight/2, TileSize/2)) + + // Check if the ray intersects the bounding box + if RayIntersectsBox(ray, boxMin, boxMax) { + fmt.Println("Clicked:", tile.X, tile.Y) + return tile, true + } + } + } + return Tile{}, false +} + +func HandleInput(player *Player, camera *rl.Camera) { + clickedTile, clicked := GetTileAtMouse(camera) + if clicked { + path := FindPath(mapGrid[player.PosTile.X][player.PosTile.Y], clickedTile) + if path != nil { + // Exclude the first tile (current position) + if len(path) > 1 { + player.TargetPath = path[1:] + player.ActionQueue = append(player.ActionQueue, Action{Type: MoveAction, X: clickedTile.X, Y: clickedTile.Y}) + } + } + } +} + +// Helper function to test ray-box intersection (slab method) +func RayIntersectsBox(ray rl.Ray, boxMin, boxMax rl.Vector3) bool { + tmin := (boxMin.X - ray.Position.X) / ray.Direction.X + tmax := (boxMax.X - ray.Position.X) / ray.Direction.X + + if tmin > tmax { + tmin, tmax = tmax, tmin + } + + tymin := (boxMin.Z - ray.Position.Z) / ray.Direction.Z + tymax := (boxMax.Z - ray.Position.Z) / ray.Direction.Z + + if tymin > tymax { + tymin, tymax = tymax, tymin + } + + if (tmin > tymax) || (tymin > tmax) { + return false + } + + if tymin > tmin { + tmin = tymin + } + if tymax < tmax { + tmax = tymax + } + + tzmin := (boxMin.Y - ray.Position.Y) / ray.Direction.Y + tzmax := (boxMax.Y - ray.Position.Y) / ray.Direction.Y + + if tzmin > tzmax { + tzmin, tzmax = tzmax, tzmin + } + + if (tmin > tzmax) || (tzmin > tmax) { + return false + } + + return true +} diff --git a/main.go b/main.go index f62dea1..82a9c6d 100644 --- a/main.go +++ b/main.go @@ -1,24 +1,9 @@ package main import ( - "fmt" "log" - "math" - "net" - "time" - pb "gitea.boner.be/bdnugget/goonserver/actions" rl "github.com/gen2brain/raylib-go/raylib" - "google.golang.org/protobuf/proto" -) - -const ( - MapWidth = 50 - MapHeight = 50 - TileSize = 32 - TileHeight = 2.0 - TickRate = 2600 * time.Millisecond // Server tick rate (600ms) - serverAddr = "localhost:6969" ) var ( @@ -28,263 +13,6 @@ var ( mapGrid = InitMap() ) -type Tile struct { - X, Y int - Height float32 - Walkable bool -} - -type ActionType int - -const ( - MoveAction ActionType = iota -) - -type Action struct { - Type ActionType - X, Y int // Target position for movement -} - -type Player struct { - PosActual rl.Vector3 - PosTile Tile - TargetPath []Tile - Speed float32 - ActionQueue []Action // Queue for player actions - Model rl.Model - Texture rl.Texture2D - ID int32 -} - -// Initialize the map with some height data -func InitMap() [][]Tile { - mapGrid := make([][]Tile, MapWidth) - for x := 0; x < MapWidth; x++ { - mapGrid[x] = make([]Tile, MapHeight) - for y := 0; y < MapHeight; y++ { - mapGrid[x][y] = Tile{ - X: x, - Y: y, - Height: 1.0 + float32(x%5), // Example height - Walkable: true, // Set to false for obstacles - } - } - } - return mapGrid -} - -func DrawMap() { - for x := 0; x < MapWidth; x++ { - for y := 0; y < MapHeight; y++ { - tile := mapGrid[x][y] - // Interpolate height between adjacent tiles for a smoother landscape - height := tile.Height - if x > 0 { - height += mapGrid[x-1][y].Height - } - if y > 0 { - height += mapGrid[x][y-1].Height - } - if x > 0 && y > 0 { - height += mapGrid[x-1][y-1].Height - } - height /= 4.0 - // Draw each tile as a 3D cube based on its height - tilePos := rl.Vector3{ - X: float32(x * TileSize), // X-axis for horizontal position - Y: height * TileHeight, // Y-axis for height (Z in 3D is Y here) - Z: float32(y * TileSize), // Z-axis for depth (Y in 3D is Z here) - } - color := rl.Color{R: uint8(height * 25), G: 100, B: 100, A: 64} - rl.DrawCube(tilePos, TileSize, TileHeight, TileSize, color) // Draw a cube representing the tile - } - } -} - -func DrawPlayer(player Player, model rl.Model) { - // Draw the player based on its actual position (PosActual) and current tile height - playerPos := rl.Vector3{ - X: player.PosActual.X, - Y: mapGrid[player.PosTile.X][player.PosTile.Y].Height*TileHeight + 16.0, - Z: player.PosActual.Z, - } - - rl.DrawModel(model, playerPos, 16, rl.White) - - // Draw highlight around target tile - if len(player.TargetPath) > 0 { - targetTile := player.TargetPath[len(player.TargetPath)-1] // last tile in the slice - targetPos := rl.Vector3{ - X: float32(targetTile.X * TileSize), - Y: mapGrid[targetTile.X][targetTile.Y].Height * TileHeight, - Z: float32(targetTile.Y * TileSize), - } - rl.DrawCubeWires(targetPos, TileSize, TileHeight, TileSize, rl.Green) - - nextTile := player.TargetPath[0] // first tile in the slice - nextPos := rl.Vector3{ - X: float32(nextTile.X * TileSize), - Y: mapGrid[nextTile.X][nextTile.Y].Height * TileHeight, - Z: float32(nextTile.Y * TileSize), - } - rl.DrawCubeWires(nextPos, TileSize, TileHeight, TileSize, rl.Yellow) - } -} - -// Helper function to test ray-box intersection (slab method) -func RayIntersectsBox(ray rl.Ray, boxMin, boxMax rl.Vector3) bool { - tmin := (boxMin.X - ray.Position.X) / ray.Direction.X - tmax := (boxMax.X - ray.Position.X) / ray.Direction.X - - if tmin > tmax { - tmin, tmax = tmax, tmin - } - - tymin := (boxMin.Z - ray.Position.Z) / ray.Direction.Z - tymax := (boxMax.Z - ray.Position.Z) / ray.Direction.Z - - if tymin > tymax { - tymin, tymax = tymax, tymin - } - - if (tmin > tymax) || (tymin > tmax) { - return false - } - - if tymin > tmin { - tmin = tymin - } - if tymax < tmax { - tmax = tymax - } - - tzmin := (boxMin.Y - ray.Position.Y) / ray.Direction.Y - tzmax := (boxMax.Y - ray.Position.Y) / ray.Direction.Y - - if tzmin > tzmax { - tzmin, tzmax = tzmax, tzmin - } - - if (tmin > tzmax) || (tzmin > tmax) { - return false - } - - return true -} - -func GetTileAtMouse(camera *rl.Camera3D) (Tile, bool) { - if !rl.IsMouseButtonPressed(rl.MouseLeftButton) { - return Tile{}, false - } - mouse := rl.GetMousePosition() - ray := rl.GetMouseRay(mouse, *camera) - - for x := 0; x < MapWidth; x++ { - for y := 0; y < MapHeight; y++ { - tile := mapGrid[x][y] - - // Define the bounding box for each tile based on its position and height - tilePos := rl.NewVector3(float32(x*TileSize), tile.Height*TileHeight, float32(y*TileSize)) - boxMin := rl.Vector3Subtract(tilePos, rl.NewVector3(TileSize/2, TileHeight/2, TileSize/2)) - boxMax := rl.Vector3Add(tilePos, rl.NewVector3(TileSize/2, TileHeight/2, TileSize/2)) - - // Check if the ray intersects the bounding box - if RayIntersectsBox(ray, boxMin, boxMax) { - fmt.Println("Clicked:", tile.X, tile.Y) - return tile, true - } - } - } - return Tile{}, false -} - -func (player *Player) MoveTowards(target Tile, deltaTime float32) { - // Calculate the direction vector to the target tile - targetPos := rl.Vector3{ - X: float32(target.X * TileSize), - Y: mapGrid[target.X][target.Y].Height * TileHeight, - Z: float32(target.Y * TileSize), - } - - // Calculate direction and normalize it for smooth movement - direction := rl.Vector3Subtract(targetPos, player.PosActual) - distance := rl.Vector3Length(direction) - if distance > 0 { - direction = rl.Vector3Scale(direction, player.Speed*deltaTime/distance) - } - - // Move the player towards the target tile - if distance > 1.0 { - player.PosActual = rl.Vector3Add(player.PosActual, direction) - } else { - // Snap to the target tile when close enough - player.PosActual = targetPos - player.PosTile = target // Update player's tile - player.TargetPath = player.TargetPath[1:] // Move to next tile in path if any - } -} - -func HandleInput(player *Player, camera *rl.Camera) { - clickedTile, clicked := GetTileAtMouse(camera) - if clicked { - path := FindPath(mapGrid[player.PosTile.X][player.PosTile.Y], clickedTile) - if path != nil { - // Exclude the first tile (current position) - if len(path) > 1 { - player.TargetPath = path[1:] - player.ActionQueue = append(player.ActionQueue, Action{Type: MoveAction, X: clickedTile.X, Y: clickedTile.Y}) - } - } - } -} - -func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) { - // Update camera based on mouse wheel - wheelMove := rl.GetMouseWheelMove() - if wheelMove != 0 { - cameraDistance += -wheelMove * 5 - if cameraDistance < 10 { - cameraDistance = 10 - } - if cameraDistance > 250 { - cameraDistance = 250 - } - } - - // Orbit camera around the player using arrow keys - if rl.IsKeyDown(rl.KeyRight) { - cameraYaw += 100 * deltaTime - } - if rl.IsKeyDown(rl.KeyLeft) { - cameraYaw -= 100 * deltaTime - } - if rl.IsKeyDown(rl.KeyUp) { - cameraPitch -= 50 * deltaTime - if cameraPitch < 20 { - cameraPitch = 20 - } - } - if rl.IsKeyDown(rl.KeyDown) { - cameraPitch += 50 * deltaTime - if cameraPitch > 85 { - cameraPitch = 85 - } - } - - // Calculate the new camera position using spherical coordinates - cameraYawRad := float64(cameraYaw) * rl.Deg2rad - cameraPitchRad := float64(cameraPitch) * rl.Deg2rad - cameraPos := rl.Vector3{ - X: player.X + cameraDistance*float32(math.Cos(cameraYawRad))*float32(math.Cos(cameraPitchRad)), - Y: player.Y + cameraDistance*float32(math.Sin(cameraPitchRad)), - Z: player.Z + cameraDistance*float32(math.Sin(cameraYawRad))*float32(math.Cos(cameraPitchRad)), - } - - // Update the camera's position and target - camera.Position = cameraPos - camera.Target = rl.NewVector3(player.X, player.Y, player.Z) -} - func main() { rl.InitWindow(1024, 768, "GoonScape") defer rl.CloseWindow() @@ -318,44 +46,25 @@ func main() { go HandleServerCommunication(conn, playerID, &player, otherPlayers) - goonerModel := rl.LoadModel("resources/models/goonion.obj") - defer rl.UnloadModel(goonerModel) - playerTexture := rl.LoadTexture("resources/models/goonion.png") - defer rl.UnloadTexture(playerTexture) - rl.SetMaterialTexture(goonerModel.Materials, rl.MapDiffuse, playerTexture) - - coomerModel := rl.LoadModel("resources/models/coomer.obj") - defer rl.UnloadModel(coomerModel) - coomerTexture := rl.LoadTexture("resources/models/coomer.png") - defer rl.UnloadTexture(coomerTexture) - rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture) - - shrekeModel := rl.LoadModel("resources/models/shreke.obj") - defer rl.UnloadModel(shrekeModel) - shrekeTexture := rl.LoadTexture("resources/models/shreke.png") - defer rl.UnloadTexture(shrekeTexture) - rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture) - - models := []struct { - Model rl.Model - Texture rl.Texture2D - }{ - {Model: goonerModel, Texture: playerTexture}, - {Model: coomerModel, Texture: coomerTexture}, - {Model: shrekeModel, Texture: shrekeTexture}, + models, err := LoadModels() + if err != nil { + log.Fatalf("Failed to load models: %v", err) } + defer UnloadModels(models) modelIndex := int(playerID) % len(models) player.Model = models[modelIndex].Model player.Texture = models[modelIndex].Texture - rl.SetTargetFPS(60) - - // Music - music := rl.LoadMusicStream("resources/audio/GoonScape2.mp3") + music, err := LoadMusic("resources/audio/GoonScape2.mp3") + if err != nil { + log.Fatalf("Failed to load music: %v", err) + } + defer UnloadMusic(music) rl.PlayMusicStream(music) rl.SetMusicVolume(music, 0.5) - defer rl.UnloadMusicStream(music) + + rl.SetTargetFPS(60) for !rl.WindowShouldClose() { @@ -383,6 +92,9 @@ func main() { DrawPlayer(player, player.Model) for id, other := range otherPlayers { + if len(other.TargetPath) > 0 { + other.MoveTowards(other.TargetPath[0], deltaTime) + } DrawPlayer(*other, models[int(id)%len(models)].Model) } @@ -391,218 +103,48 @@ func main() { rl.EndDrawing() } } -func ConnectToServer() (net.Conn, int32, error) { - // Attempt to connect to the server - conn, err := net.Dial("tcp", serverAddr) - if err != nil { - log.Printf("Failed to dial server: %v", err) - return nil, 0, err - } - log.Println("Connected to server. Waiting for player ID...") - // Buffer for incoming server message - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - log.Printf("Error reading player ID from server: %v", err) - return nil, 0, err - } +func LoadModels() ([]struct { + Model rl.Model + Texture rl.Texture2D +}, error) { + goonerModel := rl.LoadModel("resources/models/goonion.obj") + goonerTexture := rl.LoadTexture("resources/models/goonion.png") + rl.SetMaterialTexture(goonerModel.Materials, rl.MapDiffuse, goonerTexture) - log.Printf("Received data: %x", buf[:n]) + coomerModel := rl.LoadModel("resources/models/coomer.obj") + coomerTexture := rl.LoadTexture("resources/models/coomer.png") + rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture) - // Unmarshal server message to extract the player ID - var response pb.ServerMessage - if err := proto.Unmarshal(buf[:n], &response); err != nil { - log.Printf("Failed to unmarshal server response: %v", err) - return nil, 0, err - } + shrekeModel := rl.LoadModel("resources/models/shreke.obj") + shrekeTexture := rl.LoadTexture("resources/models/shreke.png") + rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture) - playerID := response.GetPlayerId() - log.Printf("Successfully connected with player ID: %d", playerID) - return conn, playerID, nil + return []struct { + Model rl.Model + Texture rl.Texture2D + }{ + {Model: goonerModel, Texture: goonerTexture}, + {Model: coomerModel, Texture: coomerTexture}, + {Model: shrekeModel, Texture: shrekeTexture}, + }, nil } -func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, otherPlayers map[int32]*Player) { - // Goroutine to handle sending player's actions to the server - go func() { - for { - if len(player.ActionQueue) > 0 { - // Process the first action in the queue - actionData := player.ActionQueue[0] - action := &pb.Action{ - PlayerId: playerID, - Type: pb.Action_MOVE, - X: int32(actionData.X), - Y: int32(actionData.Y), - } - - // Serialize the action - data, err := proto.Marshal(action) - if err != nil { - log.Printf("Failed to marshal action: %v", err) - continue - } - - // Send action to server - _, err = conn.Write(data) - if err != nil { - log.Printf("Failed to send action to server: %v", err) - return - } - - // Remove the action from the queue once it's sent - player.ActionQueue = player.ActionQueue[1:] - } - - // Add a delay to match the server's tick rate - time.Sleep(TickRate) - } - }() - - // Main loop to handle receiving updates from the server - for { - buf := make([]byte, 4096) - n, err := conn.Read(buf) - if err != nil { - log.Printf("Failed to read from server: %v", err) - return - } - - var serverMessage pb.ServerMessage - if err := proto.Unmarshal(buf[:n], &serverMessage); err != nil { - log.Printf("Failed to unmarshal server message: %v", err) - continue - } - - // Update other players' states - for _, state := range serverMessage.Players { - if state.PlayerId != playerID { - if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { - otherPlayer.PosTile = Tile{X: int(state.X), Y: int(state.Y)} - } else { - otherPlayers[state.PlayerId] = &Player{ - PosTile: Tile{X: int(state.X), Y: int(state.Y)}, - PosActual: rl.Vector3{ - X: float32(state.X * TileSize), - Y: float32(state.Y * TileHeight), - Z: float32(state.Y * TileSize), - }, - ID: state.PlayerId, - } - } - } - } +func UnloadModels(models []struct { + Model rl.Model + Texture rl.Texture2D +}) { + for _, model := range models { + rl.UnloadModel(model.Model) + rl.UnloadTexture(model.Texture) } } -// pathfinding -type Node struct { - Tile Tile - Parent *Node - G, H, F float32 +func LoadMusic(filename string) (rl.Music, error) { + music := rl.LoadMusicStream(filename) + return music, nil } -func FindPath(start, end Tile) []Tile { - openList := []*Node{} - closedList := make(map[[2]int]bool) - - startNode := &Node{Tile: start, G: 0, H: heuristic(start, end)} - startNode.F = startNode.G + startNode.H - openList = append(openList, startNode) - - for len(openList) > 0 { - // Find node with lowest F - current := openList[0] - currentIndex := 0 - for i, node := range openList { - if node.F < current.F { - current = node - currentIndex = i - } - } - - // Move current to closed list - openList = append(openList[:currentIndex], openList[currentIndex+1:]...) - closedList[[2]int{current.Tile.X, current.Tile.Y}] = true - - // Check if reached the end - if current.Tile.X == end.X && current.Tile.Y == end.Y { - path := []Tile{} - node := current - for node != nil { - path = append([]Tile{node.Tile}, path...) - node = node.Parent - } - fmt.Printf("Path found: %v\n", path) - return path - } - - // Generate neighbors - neighbors := GetNeighbors(current.Tile) - for _, neighbor := range neighbors { - if !neighbor.Walkable || closedList[[2]int{neighbor.X, neighbor.Y}] { - continue - } - - tentativeG := current.G + distance(current.Tile, neighbor) - inOpen := false - var existingNode *Node - for _, node := range openList { - if node.Tile.X == neighbor.X && node.Tile.Y == neighbor.Y { - existingNode = node - inOpen = true - break - } - } - - if !inOpen || tentativeG < existingNode.G { - newNode := &Node{ - Tile: neighbor, - Parent: current, - G: tentativeG, - H: heuristic(neighbor, end), - } - newNode.F = newNode.G + newNode.H - if !inOpen { - openList = append(openList, newNode) - } - } - } - } - - // No path found - fmt.Println("No path found") - return nil -} - -func heuristic(a, b Tile) float32 { - return float32(abs(a.X-b.X) + abs(a.Y-b.Y)) -} - -func distance(a, b Tile) float32 { - _ = a - _ = b - return 1.0 //uniform cost for now -} - -func GetNeighbors(tile Tile) []Tile { - directions := [][2]int{ - {1, 0}, {-1, 0}, {0, 1}, {0, -1}, - {1, 1}, {-1, -1}, {1, -1}, {-1, 1}, - } - neighbors := []Tile{} - for _, dir := range directions { - nx, ny := tile.X+dir[0], tile.Y+dir[1] - if nx >= 0 && nx < MapWidth && ny >= 0 && ny < MapHeight { - neighbors = append(neighbors, mapGrid[nx][ny]) - } - } - return neighbors -} - -func abs(x int) int { - if x < 0 { - return -x - } - return x +func UnloadMusic(music rl.Music) { + rl.UnloadMusicStream(music) } diff --git a/map.go b/map.go new file mode 100644 index 0000000..d200697 --- /dev/null +++ b/map.go @@ -0,0 +1,48 @@ +package main + +import rl "github.com/gen2brain/raylib-go/raylib" + +// Initialize the map with some height data +func InitMap() [][]Tile { + mapGrid := make([][]Tile, MapWidth) + for x := 0; x < MapWidth; x++ { + mapGrid[x] = make([]Tile, MapHeight) + for y := 0; y < MapHeight; y++ { + mapGrid[x][y] = Tile{ + X: x, + Y: y, + Height: 1.0 + float32(x%5), // Example height + Walkable: true, // Set to false for obstacles + } + } + } + return mapGrid +} + +func DrawMap() { + for x := 0; x < MapWidth; x++ { + for y := 0; y < MapHeight; y++ { + tile := mapGrid[x][y] + // Interpolate height between adjacent tiles for a smoother landscape + height := tile.Height + if x > 0 { + height += mapGrid[x-1][y].Height + } + if y > 0 { + height += mapGrid[x][y-1].Height + } + if x > 0 && y > 0 { + height += mapGrid[x-1][y-1].Height + } + height /= 4.0 + // Draw each tile as a 3D cube based on its height + tilePos := rl.Vector3{ + X: float32(x * TileSize), // X-axis for horizontal position + Y: height * TileHeight, // Y-axis for height (Z in 3D is Y here) + Z: float32(y * TileSize), // Z-axis for depth (Y in 3D is Z here) + } + color := rl.Color{R: uint8(height * 25), G: 100, B: 100, A: 64} + rl.DrawCube(tilePos, TileSize, TileHeight, TileSize, color) // Draw a cube representing the tile + } + } +} diff --git a/network.go b/network.go new file mode 100644 index 0000000..75959e8 --- /dev/null +++ b/network.go @@ -0,0 +1,121 @@ +package main + +import ( + "log" + "net" + "time" + + pb "gitea.boner.be/bdnugget/goonserver/actions" + rl "github.com/gen2brain/raylib-go/raylib" + "google.golang.org/protobuf/proto" +) + +func ConnectToServer() (net.Conn, int32, error) { + // Attempt to connect to the server + conn, err := net.Dial("tcp", serverAddr) + if err != nil { + log.Printf("Failed to dial server: %v", err) + return nil, 0, err + } + + log.Println("Connected to server. Waiting for player ID...") + // Buffer for incoming server message + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + log.Printf("Error reading player ID from server: %v", err) + return nil, 0, err + } + + log.Printf("Received data: %x", buf[:n]) + + // Unmarshal server message to extract the player ID + var response pb.ServerMessage + if err := proto.Unmarshal(buf[:n], &response); err != nil { + log.Printf("Failed to unmarshal server response: %v", err) + return nil, 0, err + } + + playerID := response.GetPlayerId() + log.Printf("Successfully connected with player ID: %d", playerID) + return conn, playerID, nil +} + +func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, otherPlayers map[int32]*Player) { + // Goroutine to handle sending player's actions to the server + go func() { + for { + if len(player.ActionQueue) > 0 { + // Process the first action in the queue + actionData := player.ActionQueue[0] + action := &pb.Action{ + PlayerId: playerID, + Type: pb.Action_MOVE, + X: int32(actionData.X), + Y: int32(actionData.Y), + } + + // Serialize the action + data, err := proto.Marshal(action) + if err != nil { + log.Printf("Failed to marshal action: %v", err) + continue + } + + // Send action to server + _, err = conn.Write(data) + if err != nil { + log.Printf("Failed to send action to server: %v", err) + return + } + + // Remove the action from the queue once it's sent + player.ActionQueue = player.ActionQueue[1:] + } + + // Add a delay to match the server's tick rate + time.Sleep(TickRate) + } + }() + + // Main loop to handle receiving updates from the server + for { + buf := make([]byte, 4096) + n, err := conn.Read(buf) + if err != nil { + log.Printf("Failed to read from server: %v", err) + return + } + + var serverMessage pb.ServerMessage + if err := proto.Unmarshal(buf[:n], &serverMessage); err != nil { + log.Printf("Failed to unmarshal server message: %v", err) + continue + } + + // Update other players' states + for _, state := range serverMessage.Players { + if state.PlayerId != playerID { + if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { + otherPlayer.PosTile = Tile{X: int(state.X), Y: int(state.Y)} + otherPlayer.PosActual = rl.Vector3{ + X: float32(state.X * TileSize), + Y: float32(state.Y * TileHeight), + Z: float32(state.Y * TileSize), + } + otherPlayer.MoveTowards(Tile{X: int(state.X), Y: int(state.Y)}, 0) + } else { + otherPlayers[state.PlayerId] = &Player{ + PosTile: Tile{X: int(state.X), Y: int(state.Y)}, + PosActual: rl.Vector3{ + X: float32(state.X * TileSize), + Y: float32(state.Y * TileHeight), + Z: float32(state.Y * TileSize), + }, + ID: state.PlayerId, + } + } + } + } + } +} diff --git a/pathfinding.go b/pathfinding.go new file mode 100644 index 0000000..3ebc201 --- /dev/null +++ b/pathfinding.go @@ -0,0 +1,114 @@ +package main + +import "fmt" + +type Node struct { + Tile Tile + Parent *Node + G, H, F float32 +} + +func FindPath(start, end Tile) []Tile { + openList := []*Node{} + closedList := make(map[[2]int]bool) + + startNode := &Node{Tile: start, G: 0, H: heuristic(start, end)} + startNode.F = startNode.G + startNode.H + openList = append(openList, startNode) + + for len(openList) > 0 { + // Find node with lowest F + current := openList[0] + currentIndex := 0 + for i, node := range openList { + if node.F < current.F { + current = node + currentIndex = i + } + } + + // Move current to closed list + openList = append(openList[:currentIndex], openList[currentIndex+1:]...) + closedList[[2]int{current.Tile.X, current.Tile.Y}] = true + + // Check if reached the end + if current.Tile.X == end.X && current.Tile.Y == end.Y { + path := []Tile{} + node := current + for node != nil { + path = append([]Tile{node.Tile}, path...) + node = node.Parent + } + fmt.Printf("Path found: %v\n", path) + return path + } + + // Generate neighbors + neighbors := GetNeighbors(current.Tile) + for _, neighbor := range neighbors { + if !neighbor.Walkable || closedList[[2]int{neighbor.X, neighbor.Y}] { + continue + } + + tentativeG := current.G + distance(current.Tile, neighbor) + inOpen := false + var existingNode *Node + for _, node := range openList { + if node.Tile.X == neighbor.X && node.Tile.Y == neighbor.Y { + existingNode = node + inOpen = true + break + } + } + + if !inOpen || tentativeG < existingNode.G { + newNode := &Node{ + Tile: neighbor, + Parent: current, + G: tentativeG, + H: heuristic(neighbor, end), + } + newNode.F = newNode.G + newNode.H + if !inOpen { + openList = append(openList, newNode) + } + } + } + } + + // No path found + fmt.Println("No path found") + return nil +} + +func heuristic(a, b Tile) float32 { + return float32(abs(a.X-b.X) + abs(a.Y-b.Y)) +} + +func distance(a, b Tile) float32 { + _ = a + _ = b + return 1.0 //uniform cost for now +} + +func GetNeighbors(tile Tile) []Tile { + directions := [][2]int{ + {1, 0}, {-1, 0}, {0, 1}, {0, -1}, + {1, 1}, {-1, -1}, {1, -1}, {-1, 1}, + } + neighbors := []Tile{} + for _, dir := range directions { + nx, ny := tile.X+dir[0], tile.Y+dir[1] + if nx >= 0 && nx < MapWidth && ny >= 0 && ny < MapHeight { + neighbors = append(neighbors, mapGrid[nx][ny]) + } + } + return neighbors +} + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} diff --git a/player.go b/player.go new file mode 100644 index 0000000..aaf3e9f --- /dev/null +++ b/player.go @@ -0,0 +1,63 @@ +package main + +import ( + rl "github.com/gen2brain/raylib-go/raylib" +) + +func DrawPlayer(player Player, model rl.Model) { + // Draw the player based on its actual position (PosActual) and current tile height + playerPos := rl.Vector3{ + X: player.PosActual.X, + Y: mapGrid[player.PosTile.X][player.PosTile.Y].Height*TileHeight + 16.0, + Z: player.PosActual.Z, + } + + rl.DrawModel(model, playerPos, 16, rl.White) + + // Draw highlight around target tile + if len(player.TargetPath) > 0 { + targetTile := player.TargetPath[len(player.TargetPath)-1] // last tile in the slice + targetPos := rl.Vector3{ + X: float32(targetTile.X * TileSize), + Y: mapGrid[targetTile.X][targetTile.Y].Height * TileHeight, + Z: float32(targetTile.Y * TileSize), + } + rl.DrawCubeWires(targetPos, TileSize, TileHeight, TileSize, rl.Green) + + nextTile := player.TargetPath[0] // first tile in the slice + nextPos := rl.Vector3{ + X: float32(nextTile.X * TileSize), + Y: mapGrid[nextTile.X][nextTile.Y].Height * TileHeight, + Z: float32(nextTile.Y * TileSize), + } + rl.DrawCubeWires(nextPos, TileSize, TileHeight, TileSize, rl.Yellow) + } +} + +func (player *Player) MoveTowards(target Tile, deltaTime float32) { + // Calculate the direction vector to the target tile + targetPos := rl.Vector3{ + X: float32(target.X * TileSize), + Y: mapGrid[target.X][target.Y].Height * TileHeight, + Z: float32(target.Y * TileSize), + } + + // Calculate direction and normalize it for smooth movement + direction := rl.Vector3Subtract(targetPos, player.PosActual) + distance := rl.Vector3Length(direction) + if distance > 0 { + direction = rl.Vector3Scale(direction, player.Speed*deltaTime/distance) + } + + // Move the player towards the target tile + if distance > 1.0 { + player.PosActual = rl.Vector3Add(player.PosActual, direction) + } else { + // Snap to the target tile when close enough + player.PosActual = targetPos + player.PosTile = target // Update player's tile + if len(player.TargetPath) > 1 { + player.TargetPath = player.TargetPath[1:] // Move to next tile in path if any + } + } +} diff --git a/render.go b/render.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/render.go @@ -0,0 +1 @@ +package main diff --git a/types.go b/types.go new file mode 100644 index 0000000..29eac1c --- /dev/null +++ b/types.go @@ -0,0 +1,31 @@ +package main + +import rl "github.com/gen2brain/raylib-go/raylib" + +type Tile struct { + X, Y int + Height float32 + Walkable bool +} + +type ActionType int + +const ( + MoveAction ActionType = iota +) + +type Action struct { + Type ActionType + X, Y int // Target position for movement +} + +type Player struct { + PosActual rl.Vector3 + PosTile Tile + TargetPath []Tile + Speed float32 + ActionQueue []Action // Queue for player actions + Model rl.Model + Texture rl.Texture2D + ID int32 +} diff --git a/utils/pathfinding.go b/utils/pathfinding.go deleted file mode 100644 index d4b585b..0000000 --- a/utils/pathfinding.go +++ /dev/null @@ -1 +0,0 @@ -package utils