Compare commits

..

No commits in common. "5c5040cd42f1067f72446f228b43bbd63cacbc09" and "4bfb5af362f03bc3a5c3d2bf57cf3f3128c92da6" have entirely different histories.

16 changed files with 480 additions and 594 deletions

View File

@ -1,54 +0,0 @@
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)
}

View File

@ -1,12 +0,0 @@
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"
)

1
core/constants.go Normal file
View File

@ -0,0 +1 @@
package utils

1
core/types.go Normal file
View File

@ -0,0 +1 @@
package utils

2
go.mod
View File

@ -3,7 +3,7 @@ module goonscape
go 1.23.0 go 1.23.0
require ( require (
gitea.boner.be/bdnugget/goonserver v0.0.0-20241011195320-f16e8647dc6b gitea.boner.be/bdnugget/goonserver v0.0.0-20241011122434-4bd5303cfd46
github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe
google.golang.org/protobuf v1.35.1 google.golang.org/protobuf v1.35.1
) )

4
go.sum
View File

@ -1,5 +1,5 @@
gitea.boner.be/bdnugget/goonserver v0.0.0-20241011195320-f16e8647dc6b h1:hdhCZH0YGqCsnSl6ru+8I7rxvCyOj5pCtf92urwyruA= gitea.boner.be/bdnugget/goonserver v0.0.0-20241011122434-4bd5303cfd46 h1:T2D4QcmvBqzGoHO0VJGNUd1k2lLmUcyg6Rc/vN4/Im8=
gitea.boner.be/bdnugget/goonserver v0.0.0-20241011195320-f16e8647dc6b/go.mod h1:inR1bKrr/vcTba+G1KzmmY6vssMq9oGNOk836VwPa4c= gitea.boner.be/bdnugget/goonserver v0.0.0-20241011122434-4bd5303cfd46/go.mod h1:inR1bKrr/vcTba+G1KzmmY6vssMq9oGNOk836VwPa4c=
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe h1:mInjrbJkUglTM7tBmXG+epnPCE744aj15J7vjJwM4gs= github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe h1:mInjrbJkUglTM7tBmXG+epnPCE744aj15J7vjJwM4gs=

View File

@ -1,88 +0,0 @@
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
}

533
main.go
View File

@ -1,24 +1,296 @@
package main package main
import ( import (
"fmt"
"log" "log"
"math"
"net"
"time"
pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib" 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 ( var (
cameraDistance = float32(20.0) cameraDistance = float32(20.0)
cameraYaw = float32(145.0) cameraYaw = float32(145.0)
cameraPitch = float32(45.0) // Adjusted for a more overhead view cameraPitch = float32(45.0) // Adjusted for a more overhead view
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
}
// 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(mapGrid [][]Tile) {
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, mapGrid [][]Tile) {
// 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.DrawCube(playerPos, 16, 16, 16, rl.Green) // Draw player cube
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(mapGrid [][]Tile, 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, mapGrid [][]Tile) {
// 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, mapGrid [][]Tile, camera *rl.Camera) {
clickedTile, clicked := GetTileAtMouse(mapGrid, camera)
if clicked {
path := FindPath(mapGrid, 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() { func main() {
rl.InitWindow(1024, 768, "GoonScape") rl.InitWindow(1024, 768, "GoonScape")
defer rl.CloseWindow() defer rl.CloseWindow()
rl.InitAudioDevice() rl.InitAudioDevice()
defer rl.CloseAudioDevice() defer rl.CloseAudioDevice()
mapGrid := InitMap()
player := Player{ player := Player{
PosActual: rl.NewVector3(5*TileSize, 0, 5*TileSize), PosActual: rl.NewVector3(5*TileSize, 0, 5*TileSize),
PosTile: mapGrid[5][5], PosTile: mapGrid[5][5],
@ -39,32 +311,48 @@ func main() {
log.Fatalf("Failed to connect to server: %v", err) log.Fatalf("Failed to connect to server: %v", err)
} }
log.Printf("Player ID: %d", playerID) log.Printf("Player ID: %d", playerID)
player.ID = playerID
defer conn.Close() defer conn.Close()
otherPlayers := make(map[int32]*Player) go HandleServerCommunication(conn, playerID, &player)
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)
models, err := LoadModels() coomerModel := rl.LoadModel("resources/models/coomer.obj")
if err != nil { defer rl.UnloadModel(coomerModel)
log.Fatalf("Failed to load models: %v", err) 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},
} }
defer UnloadModels(models)
modelIndex := int(playerID) % len(models) modelIndex := int(playerID) % len(models)
player.Model = models[modelIndex].Model player.Model = models[modelIndex].Model
player.Texture = models[modelIndex].Texture player.Texture = models[modelIndex].Texture
music, err := LoadMusic("resources/audio/GoonScape2.mp3") rl.SetTargetFPS(60)
if err != nil {
log.Fatalf("Failed to load music: %v", err) // Music
} music := rl.LoadMusicStream("resources/audio/GoonScape2.mp3")
defer UnloadMusic(music)
rl.PlayMusicStream(music) rl.PlayMusicStream(music)
rl.SetMusicVolume(music, 0.5) rl.SetMusicVolume(music, 0.5)
defer rl.UnloadMusicStream(music)
rl.SetTargetFPS(60)
for !rl.WindowShouldClose() { for !rl.WindowShouldClose() {
@ -74,11 +362,11 @@ func main() {
deltaTime := rl.GetFrameTime() deltaTime := rl.GetFrameTime()
// Handle input // Handle input
HandleInput(&player, &camera) HandleInput(&player, mapGrid, &camera)
// Update player // Update player
if len(player.TargetPath) > 0 { if len(player.TargetPath) > 0 {
player.MoveTowards(player.TargetPath[0], deltaTime) player.MoveTowards(player.TargetPath[0], deltaTime, mapGrid)
} }
// Update camera // Update camera
@ -88,63 +376,190 @@ func main() {
rl.BeginDrawing() rl.BeginDrawing()
rl.ClearBackground(rl.RayWhite) rl.ClearBackground(rl.RayWhite)
rl.BeginMode3D(camera) rl.BeginMode3D(camera)
DrawMap() DrawMap(mapGrid)
DrawPlayer(player, player.Model) DrawPlayer(player, &player.Model, mapGrid)
for id, other := range otherPlayers { rl.DrawFPS(10, 10)
if len(other.TargetPath) > 0 {
other.MoveTowards(other.TargetPath[0], deltaTime)
}
DrawPlayer(*other, models[int(id)%len(models)].Model)
}
rl.EndMode3D() rl.EndMode3D()
rl.DrawFPS(10, 10)
rl.EndDrawing() 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
}
func LoadModels() ([]struct { log.Println("Connected to server. Waiting for player ID...")
Model rl.Model // Buffer for incoming server message
Texture rl.Texture2D buf := make([]byte, 1024)
}, error) { n, err := conn.Read(buf)
goonerModel := rl.LoadModel("resources/models/goonion.obj") if err != nil {
goonerTexture := rl.LoadTexture("resources/models/goonion.png") log.Printf("Error reading player ID from server: %v", err)
rl.SetMaterialTexture(goonerModel.Materials, rl.MapDiffuse, goonerTexture) return nil, 0, err
}
coomerModel := rl.LoadModel("resources/models/coomer.obj") log.Printf("Received data: %x", buf[:n])
coomerTexture := rl.LoadTexture("resources/models/coomer.png")
rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture)
shrekeModel := rl.LoadModel("resources/models/shreke.obj") // Unmarshal server message to extract the player ID
shrekeTexture := rl.LoadTexture("resources/models/shreke.png") var response pb.ServerMessage
rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture) if err := proto.Unmarshal(buf[:n], &response); err != nil {
log.Printf("Failed to unmarshal server response: %v", err)
return nil, 0, err
}
return []struct { playerID := response.GetPlayerId()
Model rl.Model log.Printf("Successfully connected with player ID: %d", playerID)
Texture rl.Texture2D return conn, playerID, nil
}{
{Model: goonerModel, Texture: goonerTexture},
{Model: coomerModel, Texture: coomerTexture},
{Model: shrekeModel, Texture: shrekeTexture},
}, nil
} }
func UnloadModels(models []struct { func HandleServerCommunication(conn net.Conn, playerID int32, player *Player) {
Model rl.Model for {
Texture rl.Texture2D // Check if there are actions in the player's queue
}) { if len(player.ActionQueue) > 0 {
for _, model := range models { // Process the first action in the queue
rl.UnloadModel(model.Model) actionData := player.ActionQueue[0]
rl.UnloadTexture(model.Texture) 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 based on the server's tick rate
time.Sleep(TickRate)
} }
} }
func LoadMusic(filename string) (rl.Music, error) { // pathfinding
music := rl.LoadMusicStream(filename) type Node struct {
return music, nil Tile Tile
Parent *Node
G, H, F float32
} }
func UnloadMusic(music rl.Music) { func FindPath(mapGrid [][]Tile, start, end Tile) []Tile {
rl.UnloadMusicStream(music) 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(mapGrid, 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(mapGrid [][]Tile, 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
} }

48
map.go
View File

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

View File

@ -1,121 +0,0 @@
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,
}
}
}
}
}
}

View File

@ -1,114 +0,0 @@
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
}

View File

@ -1,63 +0,0 @@
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
}
}
}

View File

@ -1 +0,0 @@
package main

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

View File

@ -1,31 +0,0 @@
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
}

1
utils/pathfinding.go Normal file
View File

@ -0,0 +1 @@
package utils