2026-07-09 08:33:57 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"log/slog"
|
2026-07-12 04:04:55 +07:00
|
|
|
"math"
|
|
|
|
|
"math/rand/v2"
|
2026-07-09 08:33:57 +08:00
|
|
|
"os"
|
2026-07-12 04:04:55 +07:00
|
|
|
"runtime"
|
|
|
|
|
"runtime/debug"
|
|
|
|
|
"time"
|
2026-07-09 08:33:57 +08:00
|
|
|
|
|
|
|
|
"github.com/df-mc/dragonfly/server"
|
2026-07-12 04:04:55 +07:00
|
|
|
"github.com/df-mc/dragonfly/server/entity"
|
|
|
|
|
"github.com/df-mc/dragonfly/server/player"
|
2026-07-09 08:33:57 +08:00
|
|
|
"github.com/df-mc/dragonfly/server/player/chat"
|
|
|
|
|
"github.com/df-mc/dragonfly/server/world"
|
2026-07-12 04:04:55 +07:00
|
|
|
"github.com/go-gl/mathgl/mgl64"
|
2026-07-09 08:33:57 +08:00
|
|
|
"github.com/pelletier/go-toml"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
|
|
|
|
|
|
|
|
|
chat.Global.Subscribe(chat.StdoutSubscriber{})
|
|
|
|
|
|
|
|
|
|
conf, err := readConfig(slog.Default())
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
srv := conf.New()
|
|
|
|
|
srv.CloseOnProgramEnd()
|
|
|
|
|
|
|
|
|
|
srv.Listen()
|
|
|
|
|
|
2026-07-12 04:04:55 +07:00
|
|
|
// 1. Periodic mob spawner loop
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
time.Sleep(20 * time.Second)
|
|
|
|
|
srv.World().Exec(func(tx *world.Tx) {
|
|
|
|
|
mobsCount := 0
|
|
|
|
|
for e := range tx.Entities() {
|
|
|
|
|
if _, ok := e.H().Type().(entity.MobType); ok {
|
|
|
|
|
mobsCount++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only spawn if count is below 25 to optimize performance
|
|
|
|
|
if mobsCount < 25 {
|
|
|
|
|
for plEntity := range tx.Players() {
|
|
|
|
|
pl, ok := plEntity.(*player.Player)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// 50% chance to spawn a mob around this player
|
|
|
|
|
if rand.IntN(2) == 0 {
|
|
|
|
|
dx := float64(rand.IntN(30) - 15)
|
|
|
|
|
dz := float64(rand.IntN(30) - 15)
|
|
|
|
|
if math.Abs(dx) > 5 || math.Abs(dz) > 5 {
|
|
|
|
|
pos := pl.Position().Add(mgl64.Vec3{dx, 0, dz})
|
|
|
|
|
y := float64(tx.HighestBlock(int(pos.X()), int(pos.Z()))) + 1.0
|
|
|
|
|
if y > float64(tx.Range()[0]) {
|
|
|
|
|
spawnPos := mgl64.Vec3{pos.X(), y, pos.Z()}
|
|
|
|
|
types := []world.EntityType{
|
|
|
|
|
entity.PigType,
|
|
|
|
|
entity.CowType,
|
|
|
|
|
entity.SheepType,
|
|
|
|
|
entity.ZombieType,
|
|
|
|
|
entity.SkeletonType,
|
|
|
|
|
}
|
|
|
|
|
t := types[rand.IntN(len(types))]
|
|
|
|
|
|
|
|
|
|
opts := world.EntitySpawnOpts{Position: spawnPos}
|
|
|
|
|
h := opts.New(t, entity.PassiveBehaviourConfig{
|
|
|
|
|
Gravity: 0.08,
|
|
|
|
|
Drag: 0.02,
|
|
|
|
|
})
|
|
|
|
|
tx.AddEntity(h)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// 2. Periodic item clearing and auto-optimization loop
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
// Sleep 10 minutes, then notify
|
|
|
|
|
time.Sleep(10 * time.Minute)
|
|
|
|
|
srv.World().Exec(func(tx *world.Tx) {
|
|
|
|
|
for plEntity := range tx.Players() {
|
|
|
|
|
if pl, ok := plEntity.(*player.Player); ok {
|
|
|
|
|
pl.Message("§e[Pemberitahuan] Semua item di tanah akan dibersihkan dalam 5 menit!§r")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Sleep 5 minutes, then clear and optimize
|
|
|
|
|
time.Sleep(5 * time.Minute)
|
|
|
|
|
srv.World().Exec(func(tx *world.Tx) {
|
|
|
|
|
count := 0
|
|
|
|
|
for e := range tx.Entities() {
|
|
|
|
|
if e.H().Type().EncodeEntity() == "minecraft:item" {
|
|
|
|
|
_ = e.Close()
|
|
|
|
|
count++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for plEntity := range tx.Players() {
|
|
|
|
|
if pl, ok := plEntity.(*player.Player); ok {
|
|
|
|
|
pl.Message(fmt.Sprintf("§a[Pemberitahuan] Berhasil membersihkan %d item di tanah!§r", count))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Trigger automatic optimization/GC
|
|
|
|
|
runtime.GC()
|
|
|
|
|
debug.FreeOSMemory()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2026-07-09 08:33:57 +08:00
|
|
|
for p := range srv.Accept() {
|
2026-07-12 04:04:55 +07:00
|
|
|
p.SetGameMode(world.GameModeSurvival)
|
|
|
|
|
p.Message("§aSelamat datang! Bermainlah dalam Survival Mode.§r")
|
|
|
|
|
|
|
|
|
|
// Teleport player randomly near an existing player, or spawn
|
|
|
|
|
go func(pl *player.Player) {
|
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
pl.H().ExecWorld(func(tx *world.Tx, e world.Entity) {
|
|
|
|
|
p := e.(*player.Player)
|
|
|
|
|
var players []world.Entity
|
|
|
|
|
for other := range tx.Players() {
|
|
|
|
|
if other.H().UUID() != p.H().UUID() {
|
|
|
|
|
players = append(players, other)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var basePos mgl64.Vec3
|
|
|
|
|
if len(players) > 0 {
|
|
|
|
|
target := players[rand.IntN(len(players))]
|
|
|
|
|
basePos = target.Position()
|
|
|
|
|
} else {
|
|
|
|
|
basePos = tx.World().Spawn().Vec3()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Spawn randomly within 40 blocks of base position
|
|
|
|
|
dx := float64(rand.IntN(80) - 40)
|
|
|
|
|
dz := float64(rand.IntN(80) - 40)
|
|
|
|
|
spawnX := basePos.X() + dx
|
|
|
|
|
spawnZ := basePos.Z() + dz
|
|
|
|
|
spawnY := float64(tx.HighestBlock(int(spawnX), int(spawnZ))) + 1.0
|
|
|
|
|
|
|
|
|
|
spawnPos := mgl64.Vec3{spawnX, spawnY, spawnZ}
|
|
|
|
|
p.Teleport(spawnPos)
|
|
|
|
|
})
|
|
|
|
|
}(p)
|
2026-07-09 08:33:57 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readConfig(log *slog.Logger) (server.Config, error) {
|
|
|
|
|
c := server.DefaultConfig()
|
|
|
|
|
var zero server.Config
|
|
|
|
|
|
|
|
|
|
if _, err := os.Stat("config.toml"); os.IsNotExist(err) {
|
|
|
|
|
data, err := toml.Marshal(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return zero, fmt.Errorf("encode default config: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := os.WriteFile("config.toml", data, 0644); err != nil {
|
|
|
|
|
return zero, fmt.Errorf("create default config: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c.Config(log)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err := os.ReadFile("config.toml")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return zero, fmt.Errorf("read config: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := toml.Unmarshal(data, &c); err != nil {
|
|
|
|
|
return zero, fmt.Errorf("decode config: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c.Config(log)
|
|
|
|
|
}
|