From 91cdbab54a098c685dee803f2a99b8c2a5571f7e Mon Sep 17 00:00:00 2001 From: bdnugget Date: Mon, 13 Jan 2025 11:10:48 +0100 Subject: [PATCH] Reorganize code --- assets/assets.go | 41 +++++++ camera.go => game/camera.go | 19 +-- game/game.go | 168 ++++++++++++++++++++++++++ game/input.go | 31 +++++ pathfinding.go => game/pathfinding.go | 42 +++---- game/utils.go | 45 +++++++ game/world.go | 39 ++++++ goonserver | 2 +- input.go | 94 -------------- main.go | 137 +++------------------ map.go | 48 -------- network.go => network/network.go | 34 ++---- player.go | 101 ---------------- render.go | 1 - types/player.go | 76 ++++++++++++ types.go => types/types.go | 26 +++- 16 files changed, 480 insertions(+), 424 deletions(-) create mode 100644 assets/assets.go rename camera.go => game/camera.go (76%) create mode 100644 game/game.go create mode 100644 game/input.go rename pathfinding.go => game/pathfinding.go (75%) create mode 100644 game/utils.go create mode 100644 game/world.go delete mode 100644 input.go delete mode 100644 map.go rename network.go => network/network.go (70%) delete mode 100644 player.go delete mode 100644 render.go create mode 100644 types/player.go rename types.go => types/types.go (62%) diff --git a/assets/assets.go b/assets/assets.go new file mode 100644 index 0000000..60826d8 --- /dev/null +++ b/assets/assets.go @@ -0,0 +1,41 @@ +package assets + +import ( + "gitea.boner.be/bdnugget/goonscape/types" + rl "github.com/gen2brain/raylib-go/raylib" +) + +func LoadModels() ([]types.ModelAsset, error) { + goonerModel := rl.LoadModel("resources/models/goonion.obj") + goonerTexture := rl.LoadTexture("resources/models/goonion.png") + rl.SetMaterialTexture(goonerModel.Materials, rl.MapDiffuse, goonerTexture) + + coomerModel := rl.LoadModel("resources/models/coomer.obj") + coomerTexture := rl.LoadTexture("resources/models/coomer.png") + rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture) + + shrekeModel := rl.LoadModel("resources/models/shreke.obj") + shrekeTexture := rl.LoadTexture("resources/models/shreke.png") + rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture) + + return []types.ModelAsset{ + {Model: goonerModel, Texture: goonerTexture}, + {Model: coomerModel, Texture: coomerTexture}, + {Model: shrekeModel, Texture: shrekeTexture}, + }, nil +} + +func LoadMusic(filename string) (rl.Music, error) { + return rl.LoadMusicStream(filename), nil +} + +func UnloadModels(models []types.ModelAsset) { + for _, model := range models { + rl.UnloadModel(model.Model) + rl.UnloadTexture(model.Texture) + } +} + +func UnloadMusic(music rl.Music) { + rl.UnloadMusicStream(music) +} diff --git a/camera.go b/game/camera.go similarity index 76% rename from camera.go rename to game/camera.go index b5e356f..49f0ffe 100644 --- a/camera.go +++ b/game/camera.go @@ -1,4 +1,4 @@ -package main +package game import ( "math" @@ -6,8 +6,13 @@ import ( rl "github.com/gen2brain/raylib-go/raylib" ) +var ( + cameraDistance = float32(20.0) + cameraYaw = float32(145.0) + cameraPitch = float32(45.0) +) + 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 @@ -19,7 +24,6 @@ func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) { } } - // Orbit camera around the player using arrow keys if rl.IsKeyDown(rl.KeyRight) { cameraYaw += 100 * deltaTime } @@ -39,16 +43,13 @@ func UpdateCamera(camera *rl.Camera3D, player rl.Vector3, deltaTime float32) { } } - // Calculate the new camera position using spherical coordinates cameraYawRad := float64(cameraYaw) * rl.Deg2rad cameraPitchRad := float64(cameraPitch) * rl.Deg2rad - cameraPos := rl.Vector3{ + + camera.Position = 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) + camera.Target = player } diff --git a/game/game.go b/game/game.go new file mode 100644 index 0000000..eb10d3b --- /dev/null +++ b/game/game.go @@ -0,0 +1,168 @@ +package game + +import ( + "gitea.boner.be/bdnugget/goonscape/assets" + "gitea.boner.be/bdnugget/goonscape/types" + pb "gitea.boner.be/bdnugget/goonserver/actions" + rl "github.com/gen2brain/raylib-go/raylib" +) + +type Game struct { + Player *types.Player + OtherPlayers map[int32]*types.Player + Camera rl.Camera3D + Models []types.ModelAsset + Music rl.Music +} + +func New() *Game { + InitWorld() + game := &Game{ + Player: &types.Player{ + PosActual: rl.NewVector3(5*types.TileSize, 0, 5*types.TileSize), + PosTile: GetTile(5, 5), + Speed: 50.0, + TargetPath: []types.Tile{}, + }, + OtherPlayers: make(map[int32]*types.Player), + Camera: rl.Camera3D{ + Position: rl.NewVector3(0, 10, 10), + Target: rl.NewVector3(0, 0, 0), + Up: rl.NewVector3(0, 1, 0), + Fovy: 45.0, + Projection: rl.CameraPerspective, + }, + } + return game +} + +func (g *Game) LoadAssets() error { + var err error + g.Models, err = assets.LoadModels() + if err != nil { + return err + } + + g.Music, err = assets.LoadMusic("resources/audio/GoonScape2.mp3") + if err != nil { + return err + } + + return nil +} + +func (g *Game) Update(deltaTime float32) { + g.HandleInput() + + if len(g.Player.TargetPath) > 0 { + g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid()) + } + + for _, other := range g.OtherPlayers { + if len(other.TargetPath) > 0 { + other.MoveTowards(other.TargetPath[0], deltaTime, GetMapGrid()) + } + } + + UpdateCamera(&g.Camera, g.Player.PosActual, deltaTime) +} + +func (g *Game) DrawMap() { + for x := 0; x < types.MapWidth; x++ { + for y := 0; y < types.MapHeight; y++ { + height := GetTileHeight(x, y) + + // Interpolate height for smoother landscape + if x > 0 { + height += GetTileHeight(x-1, y) + } + if y > 0 { + height += GetTileHeight(x, y-1) + } + if x > 0 && y > 0 { + height += GetTileHeight(x-1, y-1) + } + height /= 4.0 + + tilePos := rl.Vector3{ + X: float32(x * types.TileSize), + Y: height * types.TileHeight, + Z: float32(y * types.TileSize), + } + color := rl.Color{R: uint8(height * 25), G: 100, B: 100, A: 64} + rl.DrawCube(tilePos, types.TileSize, types.TileHeight, types.TileSize, color) + } + } +} + +func (g *Game) DrawPlayer(player *types.Player, model rl.Model) { + player.Lock() + defer player.Unlock() + + grid := GetMapGrid() + playerPos := rl.Vector3{ + X: player.PosActual.X, + Y: grid[player.PosTile.X][player.PosTile.Y].Height*types.TileHeight + 16.0, + Z: player.PosActual.Z, + } + + rl.DrawModel(model, playerPos, 16, rl.White) + + if len(player.TargetPath) > 0 { + targetTile := player.TargetPath[len(player.TargetPath)-1] + targetPos := rl.Vector3{ + X: float32(targetTile.X * types.TileSize), + Y: grid[targetTile.X][targetTile.Y].Height * types.TileHeight, + Z: float32(targetTile.Y * types.TileSize), + } + rl.DrawCubeWires(targetPos, types.TileSize, types.TileHeight, types.TileSize, rl.Green) + + nextTile := player.TargetPath[0] + nextPos := rl.Vector3{ + X: float32(nextTile.X * types.TileSize), + Y: grid[nextTile.X][nextTile.Y].Height * types.TileHeight, + Z: float32(nextTile.Y * types.TileSize), + } + rl.DrawCubeWires(nextPos, types.TileSize, types.TileHeight, types.TileSize, rl.Yellow) + } +} + +func (g *Game) Render() { + rl.BeginDrawing() + rl.ClearBackground(rl.RayWhite) + rl.BeginMode3D(g.Camera) + + g.DrawMap() + g.DrawPlayer(g.Player, g.Player.Model) + + for id, other := range g.OtherPlayers { + g.DrawPlayer(other, g.Models[int(id)%len(g.Models)].Model) + } + + rl.EndMode3D() + rl.DrawFPS(10, 10) + rl.EndDrawing() +} + +func (g *Game) Cleanup() { + assets.UnloadModels(g.Models) + assets.UnloadMusic(g.Music) +} + +func (g *Game) HandleInput() { + clickedTile, clicked := g.GetTileAtMouse() + if clicked { + path := FindPath(GetTile(g.Player.PosTile.X, g.Player.PosTile.Y), clickedTile) + if path != nil && len(path) > 1 { + g.Player.Lock() + g.Player.TargetPath = path[1:] + g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{ + Type: pb.Action_MOVE, + X: int32(clickedTile.X), + Y: int32(clickedTile.Y), + PlayerId: g.Player.ID, + }) + g.Player.Unlock() + } + } +} diff --git a/game/input.go b/game/input.go new file mode 100644 index 0000000..9957871 --- /dev/null +++ b/game/input.go @@ -0,0 +1,31 @@ +package game + +import ( + "fmt" + + "gitea.boner.be/bdnugget/goonscape/types" + rl "github.com/gen2brain/raylib-go/raylib" +) + +func (g *Game) GetTileAtMouse() (types.Tile, bool) { + if !rl.IsMouseButtonPressed(rl.MouseLeftButton) { + return types.Tile{}, false + } + mouse := rl.GetMousePosition() + ray := rl.GetMouseRay(mouse, g.Camera) + + for x := 0; x < types.MapWidth; x++ { + for y := 0; y < types.MapHeight; y++ { + tile := GetTile(x, y) + tilePos := rl.NewVector3(float32(x*types.TileSize), tile.Height*types.TileHeight, float32(y*types.TileSize)) + boxMin := rl.Vector3Subtract(tilePos, rl.NewVector3(types.TileSize/2, types.TileHeight/2, types.TileSize/2)) + boxMax := rl.Vector3Add(tilePos, rl.NewVector3(types.TileSize/2, types.TileHeight/2, types.TileSize/2)) + + if RayIntersectsBox(ray, boxMin, boxMax) { + fmt.Printf("Clicked: %d, %d\n", tile.X, tile.Y) + return tile, true + } + } + } + return types.Tile{}, false +} diff --git a/pathfinding.go b/game/pathfinding.go similarity index 75% rename from pathfinding.go rename to game/pathfinding.go index 3ebc201..ce59e31 100644 --- a/pathfinding.go +++ b/game/pathfinding.go @@ -1,14 +1,18 @@ -package main +package game -import "fmt" +import ( + "fmt" + + "gitea.boner.be/bdnugget/goonscape/types" +) type Node struct { - Tile Tile + Tile types.Tile Parent *Node G, H, F float32 } -func FindPath(start, end Tile) []Tile { +func FindPath(start, end types.Tile) []types.Tile { openList := []*Node{} closedList := make(map[[2]int]bool) @@ -17,7 +21,6 @@ func FindPath(start, end Tile) []Tile { openList = append(openList, startNode) for len(openList) > 0 { - // Find node with lowest F current := openList[0] currentIndex := 0 for i, node := range openList { @@ -27,23 +30,20 @@ func FindPath(start, end Tile) []Tile { } } - // 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{} + path := []types.Tile{} node := current for node != nil { - path = append([]Tile{node.Tile}, path...) + path = append([]types.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}] { @@ -75,32 +75,30 @@ func FindPath(start, end Tile) []Tile { } } } - - // No path found - fmt.Println("No path found") return nil } -func heuristic(a, b Tile) float32 { +func heuristic(a, b types.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 distance(a, b types.Tile) float32 { + return 1.0 // uniform cost for now } -func GetNeighbors(tile Tile) []Tile { +func GetNeighbors(tile types.Tile) []types.Tile { directions := [][2]int{ {1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}, } - neighbors := []Tile{} + neighbors := []types.Tile{} + grid := GetMapGrid() 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]) + if nx >= 0 && nx < types.MapWidth && ny >= 0 && ny < types.MapHeight { + if grid[nx][ny].Walkable { + neighbors = append(neighbors, grid[nx][ny]) + } } } return neighbors diff --git a/game/utils.go b/game/utils.go new file mode 100644 index 0000000..a827e21 --- /dev/null +++ b/game/utils.go @@ -0,0 +1,45 @@ +package game + +import ( + rl "github.com/gen2brain/raylib-go/raylib" +) + +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/game/world.go b/game/world.go new file mode 100644 index 0000000..cd2687f --- /dev/null +++ b/game/world.go @@ -0,0 +1,39 @@ +package game + +import ( + "gitea.boner.be/bdnugget/goonscape/types" +) + +var ( + mapGrid [][]types.Tile +) + +func GetMapGrid() [][]types.Tile { + return mapGrid +} + +func InitWorld() { + mapGrid = make([][]types.Tile, types.MapWidth) + for x := 0; x < types.MapWidth; x++ { + mapGrid[x] = make([]types.Tile, types.MapHeight) + for y := 0; y < types.MapHeight; y++ { + mapGrid[x][y] = types.Tile{ + X: x, + Y: y, + Height: 1.0 + float32(x%5), + Walkable: true, + } + } + } +} + +func GetTile(x, y int) types.Tile { + if x >= 0 && x < types.MapWidth && y >= 0 && y < types.MapHeight { + return mapGrid[x][y] + } + return types.Tile{} +} + +func GetTileHeight(x, y int) float32 { + return mapGrid[x][y].Height +} diff --git a/goonserver b/goonserver index a459e8b..4b73492 160000 --- a/goonserver +++ b/goonserver @@ -1 +1 @@ -Subproject commit a459e8b4a5cfa02f0ee588596dba5ce26afac39f +Subproject commit 4b73492ffc0824faccddd81053c08d3d7607919a diff --git a/input.go b/input.go deleted file mode 100644 index b8ff046..0000000 --- a/input.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "fmt" - - pb "gitea.boner.be/bdnugget/goonserver/actions" - 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, &pb.Action{ - Type: pb.Action_MOVE, - X: int32(clickedTile.X), - Y: int32(clickedTile.Y), - PlayerId: player.ID, - }) - } - } - } -} - -// 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 c834b09..3520ce5 100644 --- a/main.go +++ b/main.go @@ -3,148 +3,45 @@ package main import ( "log" + "gitea.boner.be/bdnugget/goonscape/game" + "gitea.boner.be/bdnugget/goonscape/network" rl "github.com/gen2brain/raylib-go/raylib" ) -var ( - cameraDistance = float32(20.0) - cameraYaw = float32(145.0) - cameraPitch = float32(45.0) // Adjusted for a more overhead view - mapGrid = InitMap() -) - func main() { rl.InitWindow(1024, 768, "GoonScape") defer rl.CloseWindow() rl.InitAudioDevice() defer rl.CloseAudioDevice() - player := Player{ - PosActual: rl.NewVector3(5*TileSize, 0, 5*TileSize), - PosTile: mapGrid[5][5], - Speed: 50.0, - TargetPath: []Tile{}, + game := game.New() + if err := game.LoadAssets(); err != nil { + log.Fatalf("Failed to load assets: %v", err) } + defer game.Cleanup() - camera := rl.Camera3D{ - Position: rl.NewVector3(0, 10, 10), // Will be updated every frame - Target: player.PosActual, - Up: rl.NewVector3(0, 1, 0), // Y is up in 3D - Fovy: 45.0, - Projection: rl.CameraPerspective, - } - - conn, playerID, err := ConnectToServer() + conn, playerID, err := network.ConnectToServer() if err != nil { log.Fatalf("Failed to connect to server: %v", err) } - log.Printf("Player ID: %d", playerID) - player.ID = playerID defer conn.Close() - otherPlayers := make(map[int32]*Player) + game.Player.ID = playerID + modelIndex := int(playerID) % len(game.Models) + game.Player.Model = game.Models[modelIndex].Model + game.Player.Texture = game.Models[modelIndex].Texture - go HandleServerCommunication(conn, playerID, &player, otherPlayers) - - 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 - - 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) + go network.HandleServerCommunication(conn, playerID, game.Player, game.OtherPlayers) + rl.PlayMusicStream(game.Music) + rl.SetMusicVolume(game.Music, 0.5) rl.SetTargetFPS(60) for !rl.WindowShouldClose() { - - rl.UpdateMusicStream(music) - - // Time management + rl.UpdateMusicStream(game.Music) deltaTime := rl.GetFrameTime() - // Handle input - HandleInput(&player, &camera) - - // Update player - if len(player.TargetPath) > 0 { - player.MoveTowards(player.TargetPath[0], deltaTime) - } - - // Update camera - UpdateCamera(&camera, player.PosActual, deltaTime) - - // Rendering - rl.BeginDrawing() - rl.ClearBackground(rl.RayWhite) - rl.BeginMode3D(camera) - DrawMap() - 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) - } - - rl.EndMode3D() - rl.DrawFPS(10, 10) - rl.EndDrawing() + game.Update(deltaTime) + game.Render() } } - -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) - - coomerModel := rl.LoadModel("resources/models/coomer.obj") - coomerTexture := rl.LoadTexture("resources/models/coomer.png") - rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture) - - shrekeModel := rl.LoadModel("resources/models/shreke.obj") - shrekeTexture := rl.LoadTexture("resources/models/shreke.png") - rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture) - - return []struct { - Model rl.Model - Texture rl.Texture2D - }{ - {Model: goonerModel, Texture: goonerTexture}, - {Model: coomerModel, Texture: coomerTexture}, - {Model: shrekeModel, Texture: shrekeTexture}, - }, nil -} - -func UnloadModels(models []struct { - Model rl.Model - Texture rl.Texture2D -}) { - for _, model := range models { - rl.UnloadModel(model.Model) - rl.UnloadTexture(model.Texture) - } -} - -func LoadMusic(filename string) (rl.Music, error) { - music := rl.LoadMusicStream(filename) - return music, nil -} - -func UnloadMusic(music rl.Music) { - rl.UnloadMusicStream(music) -} diff --git a/map.go b/map.go deleted file mode 100644 index d200697..0000000 --- a/map.go +++ /dev/null @@ -1,48 +0,0 @@ -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/network.go similarity index 70% rename from network.go rename to network/network.go index 2ed166b..1dd29b8 100644 --- a/network.go +++ b/network/network.go @@ -1,24 +1,23 @@ -package main +package network import ( "log" "net" "time" + "gitea.boner.be/bdnugget/goonscape/types" pb "gitea.boner.be/bdnugget/goonserver/actions" "google.golang.org/protobuf/proto" ) func ConnectToServer() (net.Conn, int32, error) { - // Attempt to connect to the server - conn, err := net.Dial("tcp", serverAddr) + conn, err := net.Dial("tcp", types.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 { @@ -26,9 +25,6 @@ func ConnectToServer() (net.Conn, int32, error) { 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) @@ -40,33 +36,28 @@ func ConnectToServer() (net.Conn, int32, error) { return conn, playerID, nil } -func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, otherPlayers map[int32]*Player) { +func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player) { buf := make([]byte, 4096) - // Ticker for sending actions - actionTicker := time.NewTicker(ClientTickRate) + actionTicker := time.NewTicker(types.ClientTickRate) defer actionTicker.Stop() go func() { for range actionTicker.C { player.Lock() if len(player.ActionQueue) > 0 { - // Bundle all actions that occurred during this tick actions := make([]*pb.Action, len(player.ActionQueue)) - copy(actions, player.ActionQueue) // Copy to avoid holding lock while sending + copy(actions, player.ActionQueue) - // Create a batch message batch := &pb.ActionBatch{ PlayerId: playerID, Actions: actions, Tick: player.CurrentTick, } - // Clear the action queue after copying player.ActionQueue = player.ActionQueue[:0] player.Unlock() - // Serialize and send the batch data, err := proto.Marshal(batch) if err != nil { log.Printf("Failed to marshal action batch: %v", err) @@ -83,7 +74,6 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot } }() - // Main loop to handle receiving updates from the server for { n, err := conn.Read(buf) if err != nil { @@ -97,14 +87,11 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot continue } - // Update game state with received data player.Lock() player.CurrentTick = serverMessage.CurrentTick - // Check for desync tickDiff := serverMessage.CurrentTick - player.CurrentTick - if tickDiff > MaxTickDesync { - // Force resync if we're too far behind + if tickDiff > types.MaxTickDesync { for _, state := range serverMessage.Players { if state.PlayerId == playerID { player.ForceResync(state) @@ -114,16 +101,15 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot } player.Unlock() - // Update other players for _, state := range serverMessage.Players { if state.PlayerId == playerID { - continue // Skip self + continue } if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { - otherPlayer.UpdatePosition(state, ServerTickRate) + otherPlayer.UpdatePosition(state, types.ServerTickRate) } else { - otherPlayers[state.PlayerId] = NewPlayer(state) + otherPlayers[state.PlayerId] = types.NewPlayer(state) } } } diff --git a/player.go b/player.go deleted file mode 100644 index ee1f881..0000000 --- a/player.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "time" - - pb "gitea.boner.be/bdnugget/goonserver/actions" - 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 - } - } -} - -func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) { - targetTile := Tile{X: int(state.X), Y: int(state.Y)} - if p.PosTile != targetTile { - p.PosTile = targetTile - p.LastUpdateTime = time.Now() - p.InterpolationProgress = 0 - p.TargetPath = []Tile{targetTile} - } -} - -func (p *Player) ForceResync(state *pb.PlayerState) { - p.PosTile = Tile{X: int(state.X), Y: int(state.Y)} - p.PosActual = rl.Vector3{ - X: float32(state.X * TileSize), - Y: float32(state.Y * TileHeight), - Z: float32(state.Y * TileSize), - } - p.TargetPath = nil - p.ActionQueue = nil - p.InterpolationProgress = 1.0 -} - -func NewPlayer(state *pb.PlayerState) *Player { - return &Player{ - PosActual: rl.Vector3{ - X: float32(state.X * TileSize), - Y: float32(state.Y * TileHeight), - Z: float32(state.Y * TileSize), - }, - PosTile: Tile{X: int(state.X), Y: int(state.Y)}, - Speed: 50.0, - ID: state.PlayerId, - } -} diff --git a/render.go b/render.go deleted file mode 100644 index 06ab7d0..0000000 --- a/render.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/types/player.go b/types/player.go new file mode 100644 index 0000000..25abe7f --- /dev/null +++ b/types/player.go @@ -0,0 +1,76 @@ +package types + +import ( + "time" + + pb "gitea.boner.be/bdnugget/goonserver/actions" + rl "github.com/gen2brain/raylib-go/raylib" +) + +func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) { + p.Lock() + defer p.Unlock() + + targetPos := rl.Vector3{ + X: float32(target.X * TileSize), + Y: mapGrid[target.X][target.Y].Height * TileHeight, + Z: float32(target.Y * TileSize), + } + + direction := rl.Vector3Subtract(targetPos, p.PosActual) + distance := rl.Vector3Length(direction) + if distance > 0 { + direction = rl.Vector3Scale(direction, p.Speed*deltaTime/distance) + } + + if distance > 1.0 { + p.PosActual = rl.Vector3Add(p.PosActual, direction) + } else { + p.PosActual = targetPos + p.PosTile = target + if len(p.TargetPath) > 1 { + p.TargetPath = p.TargetPath[1:] + } + } +} + +func NewPlayer(state *pb.PlayerState) *Player { + return &Player{ + PosActual: rl.Vector3{ + X: float32(state.X * TileSize), + Y: float32(state.Y * TileHeight), + Z: float32(state.Y * TileSize), + }, + PosTile: Tile{X: int(state.X), Y: int(state.Y)}, + Speed: 50.0, + ID: state.PlayerId, + } +} + +func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) { + p.Lock() + defer p.Unlock() + + targetTile := Tile{X: int(state.X), Y: int(state.Y)} + if p.PosTile != targetTile { + p.PosTile = targetTile + p.LastUpdateTime = time.Now() + p.InterpolationProgress = 0 + p.TargetPath = []Tile{targetTile} + } +} + +func (p *Player) ForceResync(state *pb.PlayerState) { + p.Lock() + defer p.Unlock() + + p.PosTile = Tile{X: int(state.X), Y: int(state.Y)} + p.PosActual = rl.Vector3{ + X: float32(state.X * TileSize), + Y: float32(state.Y * TileHeight), + Z: float32(state.Y * TileSize), + } + p.TargetPath = nil + p.ActionQueue = nil + p.InterpolationProgress = 1.0 +} diff --git a/types.go b/types/types.go similarity index 62% rename from types.go rename to types/types.go index d039417..68744fa 100644 --- a/types.go +++ b/types/types.go @@ -1,11 +1,11 @@ -package main +package types import ( + "sync" + "time" + pb "gitea.boner.be/bdnugget/goonserver/actions" rl "github.com/gen2brain/raylib-go/raylib" - - "sync" - time "time" ) type Tile struct { @@ -28,3 +28,21 @@ type Player struct { LastUpdateTime time.Time InterpolationProgress float32 } + +type ModelAsset struct { + Model rl.Model + Texture rl.Texture2D +} + +const ( + MapWidth = 50 + MapHeight = 50 + TileSize = 32 + TileHeight = 2.0 + + // RuneScape-style tick rate (600ms) + ServerTickRate = 600 * time.Millisecond + ClientTickRate = 50 * time.Millisecond + MaxTickDesync = 5 + ServerAddr = "localhost:6969" +)