RegionIO/internal/server/spawner.go

112 lines
2.7 KiB
Go

package server
import (
"context"
"math"
"math/rand"
"time"
"regionio/internal/registry"
"regionio/internal/world"
)
// StartSpawning begins the entity tick and spawn loops.
func (s *Server) StartSpawning(ctx context.Context) {
go s.entityTickLoop(ctx)
go s.mobSpawnLoop(ctx)
}
func (s *Server) entityTickLoop(ctx context.Context) {
ticker := time.NewTicker(50 * time.Millisecond) // 20 TPS
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
all := s.entities.All()
for i := range all {
snapshot := all[i]
yBelow := int(math.Floor(snapshot.Y - 0.1))
blockBelow := s.chunks.GetBlock(int(math.Floor(snapshot.X)), yBelow, int(math.Floor(snapshot.Z)))
nearest, hasPlayer := s.NearestPlayer(snapshot.X, snapshot.Y, snapshot.Z)
s.entities.Update(all[i].ID, func(e *world.Entity) {
// Apply gravity
if blockBelow == world.StateAir || blockBelow == world.StateWater { // Air or Water
e.VelocityY -= 80 // gravity acceleration
if e.VelocityY < -3000 {
e.VelocityY = -3000 // terminal velocity
}
} else {
e.VelocityY = 0
e.Y = float64(yBelow + 1)
// Basic random wandering or player tracking when on ground
if hasPlayer && e.TypeName == "minecraft:zombie" {
// Zombies move towards the player
dx := nearest[0] - e.X
dz := nearest[2] - e.Z
dist := math.Sqrt(dx*dx + dz*dz)
if dist > 1.0 && dist < 32.0 {
e.X += (dx / dist) * 0.15
e.Z += (dz / dist) * 0.15
// Simple yaw calculation
e.Yaw = float32(math.Atan2(-dx, dz) * (180 / math.Pi))
}
} else {
// Random wander
e.X += (rand.Float64() - 0.5) * 0.2
e.Z += (rand.Float64() - 0.5) * 0.2
e.Yaw += float32((rand.Float64() - 0.5) * 10.0)
}
}
if e.VelocityY != 0 {
e.Y += float64(e.VelocityY) / 8000.0
}
})
}
}
}
}
func (s *Server) mobSpawnLoop(ctx context.Context) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
pigType := registry.EntityTypeIndex("minecraft:pig")
zombieType := registry.EntityTypeIndex("minecraft:zombie")
if pigType < 0 || zombieType < 0 {
return
}
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if s.PlayerCount() == 0 || s.entities.Count() >= 50 {
continue // limit to 50 entities
}
// Spawn near the spawn point (8.5, 200, 8.5)
x := (rand.Float64() - 0.5) * 30.0
z := (rand.Float64() - 0.5) * 30.0
t := pigType
name := "minecraft:pig"
if rand.Float32() < 0.5 {
t = zombieType
name = "minecraft:zombie"
}
s.entities.Add(&world.Entity{
TypeID: t,
TypeName: name,
X: x + 8.5,
Y: 200.0,
Z: z + 8.5,
})
}
}
}