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