Fix OOB and refactor
This commit is contained in:
parent
2b9ece3c10
commit
5c5040cd42
54
camera.go
Normal file
54
camera.go
Normal file
@ -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)
|
||||
}
|
12
constants.go
Normal file
12
constants.go
Normal file
@ -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"
|
||||
)
|
@ -1 +0,0 @@
|
||||
package utils
|
@ -1 +0,0 @@
|
||||
package utils
|
88
input.go
Normal file
88
input.go
Normal file
@ -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
|
||||
}
|
552
main.go
552
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)
|
||||
}
|
||||
|
48
map.go
Normal file
48
map.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
121
network.go
Normal file
121
network.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
114
pathfinding.go
Normal file
114
pathfinding.go
Normal file
@ -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
|
||||
}
|
63
player.go
Normal file
63
player.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
31
types.go
Normal file
31
types.go
Normal file
@ -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
|
||||
}
|
@ -1 +0,0 @@
|
||||
package utils
|
Loading…
x
Reference in New Issue
Block a user