127 lines
3.2 KiB
Go
127 lines
3.2 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()
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
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 += (rng.Float64() - 0.5) * 0.2
|
|
e.Z += (rng.Float64() - 0.5) * 0.2
|
|
e.Yaw += float32((rng.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()
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
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() >= 20 {
|
|
continue
|
|
}
|
|
s.spawnMobNearPlayer(rng, pigType, zombieType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) spawnMobNearPlayer(rng *rand.Rand, pigType, zombieType int) bool {
|
|
players := s.PlayerSnapshots()
|
|
if len(players) == 0 {
|
|
return false
|
|
}
|
|
player := players[rng.Intn(len(players))]
|
|
angle := rng.Float64() * 2 * math.Pi
|
|
distance := 16.0 + rng.Float64()*16.0
|
|
x := int(math.Floor(player.X + math.Cos(angle)*distance))
|
|
z := int(math.Floor(player.Z + math.Sin(angle)*distance))
|
|
y, ok := s.chunks.SafeSpawnY(x, z)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
typeID := pigType
|
|
typeName := "minecraft:pig"
|
|
if rng.Float32() < 0.5 {
|
|
typeID = zombieType
|
|
typeName = "minecraft:zombie"
|
|
}
|
|
s.entities.Add(&world.Entity{
|
|
TypeID: typeID,
|
|
TypeName: typeName,
|
|
X: float64(x) + 0.5,
|
|
Y: float64(y),
|
|
Z: float64(z) + 0.5,
|
|
})
|
|
return true
|
|
}
|