211 lines
5.7 KiB
Go
211 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"flag"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"log"
|
|
"math"
|
|
"math/rand"
|
|
"os"
|
|
"server/net"
|
|
)
|
|
|
|
const WorldSize = 100
|
|
|
|
func generateHeightmap(size int) [][]float32 {
|
|
heightmap := make([][]float32, size)
|
|
for i := range heightmap {
|
|
heightmap[i] = make([]float32, size)
|
|
}
|
|
|
|
// Simple perlin-like noise
|
|
for y := range heightmap {
|
|
for x := range heightmap[y] {
|
|
nx := float64(x) / float64(size) * 4
|
|
ny := float64(y) / float64(size) * 4
|
|
heightmap[y][x] = float32(
|
|
math.Sin(nx*2+rand.Float64())*0.5+
|
|
math.Cos(ny*3+rand.Float64())*0.3+
|
|
rand.Float64()*0.2) * 10.0
|
|
}
|
|
}
|
|
|
|
// Smooth the heightmap
|
|
for range 3 {
|
|
newHeightmap := make([][]float32, size)
|
|
for y := range newHeightmap {
|
|
newHeightmap[y] = make([]float32, size)
|
|
for x := range newHeightmap[y] {
|
|
sum := heightmap[y][x]
|
|
count := float32(1)
|
|
for dy := -1; dy <= 1; dy++ {
|
|
for dx := -1; dx <= 1; dx++ {
|
|
nx, ny := x+dx, y+dy
|
|
if nx >= 0 && nx < size && ny >= 0 && ny < size {
|
|
sum += heightmap[ny][nx]
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
newHeightmap[y][x] = sum / count
|
|
}
|
|
}
|
|
heightmap = newHeightmap
|
|
}
|
|
|
|
return heightmap
|
|
}
|
|
|
|
func saveHeightmapPNG(heightmap [][]float32, filename string) error {
|
|
size := len(heightmap)
|
|
img := image.NewGray(image.Rect(0, 0, size, size))
|
|
|
|
// Find min/max for normalization
|
|
minH, maxH := heightmap[0][0], heightmap[0][0]
|
|
for y := range heightmap {
|
|
for x := range heightmap[y] {
|
|
if heightmap[y][x] < minH {
|
|
minH = heightmap[y][x]
|
|
}
|
|
if heightmap[y][x] > maxH {
|
|
maxH = heightmap[y][x]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply normalized values to image
|
|
for y := range heightmap {
|
|
for x := range heightmap[y] {
|
|
normalized := (heightmap[y][x] - minH) / (maxH - minH)
|
|
img.SetGray(x, y, color.Gray{Y: uint8(normalized * 255)})
|
|
}
|
|
}
|
|
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create file %s: %w", filename, err)
|
|
}
|
|
defer file.Close()
|
|
|
|
if err := png.Encode(file, img); err != nil {
|
|
return fmt.Errorf("failed to encode PNG: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func saveHeightmapBinary(heightmap [][]float32, filename string) error {
|
|
size := len(heightmap)
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create file %s: %w", filename, err)
|
|
}
|
|
defer file.Close()
|
|
|
|
if err := binary.Write(file, binary.LittleEndian, int32(size)); err != nil {
|
|
return fmt.Errorf("failed to write size: %w", err)
|
|
}
|
|
|
|
for y := range heightmap {
|
|
for x := range heightmap[y] {
|
|
if err := binary.Write(file, binary.LittleEndian, heightmap[y][x]); err != nil {
|
|
return fmt.Errorf("failed to write heightmap data: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// loadHeightmapBinary loads a heightmap from a binary file
|
|
func loadHeightmapBinary(filename string) ([][]float32, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file %s: %w", filename, err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var size int32
|
|
if err := binary.Read(file, binary.LittleEndian, &size); err != nil {
|
|
return nil, fmt.Errorf("failed to read size: %w", err)
|
|
}
|
|
|
|
heightmap := make([][]float32, size)
|
|
for y := range heightmap {
|
|
heightmap[y] = make([]float32, size)
|
|
for x := range heightmap[y] {
|
|
if err := binary.Read(file, binary.LittleEndian, &heightmap[y][x]); err != nil {
|
|
return nil, fmt.Errorf("failed to read heightmap data: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return heightmap, nil
|
|
}
|
|
|
|
func main() {
|
|
// Parse command-line flags
|
|
var (
|
|
port = flag.String("port", "9999", "UDP port to listen on")
|
|
worldSize = flag.Int("size", WorldSize, "World size for heightmap generation")
|
|
skipGen = flag.Bool("skip-gen", false, "Skip heightmap generation (fail if none exists)")
|
|
forceGen = flag.Bool("force-gen", false, "Force heightmap regeneration even if one exists")
|
|
assetsPath = flag.String("assets", "../assets", "Path to assets directory")
|
|
)
|
|
flag.Parse()
|
|
|
|
// Setup logging
|
|
log.SetPrefix("[Game] ")
|
|
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
|
|
|
|
var heightmap [][]float32
|
|
binPath := fmt.Sprintf("%s/heightmap.bin", *assetsPath)
|
|
|
|
// Check if heightmap exists and should be loaded
|
|
if _, err := os.Stat(binPath); err == nil && !*forceGen && !*skipGen {
|
|
// Heightmap exists and we're not forcing regeneration
|
|
log.Printf("Found existing heightmap at %s, loading...", binPath)
|
|
heightmap, err = loadHeightmapBinary(binPath)
|
|
if err != nil {
|
|
log.Printf("Failed to load existing heightmap: %v, generating new one...", err)
|
|
heightmap = nil
|
|
} else {
|
|
log.Printf("Successfully loaded existing heightmap")
|
|
}
|
|
} else if *forceGen {
|
|
log.Printf("Force regeneration requested, ignoring existing heightmap")
|
|
}
|
|
|
|
// Generate new heightmap if needed (not loaded, force generation, or doesn't exist)
|
|
if heightmap == nil && !*skipGen {
|
|
log.Printf("Generating new %dx%d heightmap...", *worldSize, *worldSize)
|
|
heightmap = generateHeightmap(*worldSize)
|
|
|
|
pngPath := fmt.Sprintf("%s/heightmap.png", *assetsPath)
|
|
if err := saveHeightmapPNG(heightmap, pngPath); err != nil {
|
|
log.Printf("Warning: Failed to save PNG heightmap: %v", err)
|
|
} else {
|
|
log.Printf("Saved heightmap PNG to %s", pngPath)
|
|
}
|
|
|
|
if err := saveHeightmapBinary(heightmap, binPath); err != nil {
|
|
log.Fatalf("Failed to save binary heightmap: %v", err)
|
|
}
|
|
log.Printf("Saved heightmap binary to %s", binPath)
|
|
} else if *skipGen && heightmap == nil {
|
|
// skip-gen was specified but no heightmap exists
|
|
log.Fatalf("No existing heightmap found and generation was skipped (--skip-gen flag)")
|
|
}
|
|
|
|
server, err := net.NewServer(*port, heightmap)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create server: %v", err)
|
|
}
|
|
|
|
log.Printf("Starting game server on port %s", *port)
|
|
if err := server.Run(); err != nil {
|
|
log.Fatalf("Server failed: %v", err)
|
|
}
|
|
}
|