Update terbaru

Menambahkan bypas nama gwa
Nambahkan ban/unban/ban list
menambahkan world clasik ringan
This commit is contained in:
2026-07-11 00:33:35 +07:00
parent 84ff0edd28
commit 781b01c2bf
11 changed files with 547 additions and 39 deletions
+2 -1
View File
@@ -195,6 +195,7 @@ func (conf Config) New() *Server {
srv.listeners = append(srv.listeners, l)
}
_ = player.Bans.Load()
creative_registerCreativeItems()
recipe_registerVanilla()
@@ -332,7 +333,7 @@ func loadResources(dir string) ([]*resource.Pack, error) {
func loadGenerator(dim world.Dimension) world.Generator {
switch dim {
case world.Overworld:
return generator.NewFlat(biome.Plains{}, []world.Block{block.Grass{}, block.Dirt{}, block.Dirt{}, block.Bedrock{}})
return generator.NewVanillaLightweight(world.DefaultBlockRegistry)
case world.Nether:
return generator.NewFlat(biome.NetherWastes{}, []world.Block{block.Netherrack{}, block.Netherrack{}, block.Netherrack{}, block.Bedrock{}})
case world.End:
+1 -1
View File
@@ -44,7 +44,7 @@ func (conf ItemBehaviourConfig) New() *ItemBehaviour {
conf.PickupDelay = time.Second / 2
}
if conf.ExistenceDuration == 0 {
conf.ExistenceDuration = time.Minute * 5
conf.ExistenceDuration = time.Second * 15
}
b := &ItemBehaviour{conf: conf, i: i, pickupDelay: conf.PickupDelay}
+170
View File
@@ -0,0 +1,170 @@
package player
import (
"encoding/json"
"os"
"strings"
"sync"
"time"
)
// BanEntry represents a ban on the server.
type BanEntry struct {
Name string `json:"name"`
XUID string `json:"xuid,omitempty"`
UUID string `json:"uuid,omitempty"`
IP string `json:"ip,omitempty"`
BannedAt time.Time `json:"banned_at"`
ExpiresAt time.Time `json:"expires_at"` // Zero time means permanent
Reason string `json:"reason"`
}
// BanManager handles player bans.
type BanManager struct {
mu sync.Mutex
bans map[string]BanEntry
path string
}
// Bans is the global ban manager.
var Bans = &BanManager{
bans: make(map[string]BanEntry),
path: "bans.json",
}
// Load loads the banned players from bans.json.
func (bm *BanManager) Load() error {
bm.mu.Lock()
defer bm.mu.Unlock()
data, err := os.ReadFile(bm.path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
var list []BanEntry
if err := json.Unmarshal(data, &list); err != nil {
return err
}
bm.bans = make(map[string]BanEntry)
for _, entry := range list {
bm.bans[strings.ToLower(entry.Name)] = entry
}
return nil
}
// Save saves the banned players to bans.json.
func (bm *BanManager) Save() error {
bm.mu.Lock()
defer bm.mu.Unlock()
var list []BanEntry
for _, entry := range bm.bans {
list = append(list, entry)
}
data, err := json.MarshalIndent(list, "", " ")
if err != nil {
return err
}
return os.WriteFile(bm.path, data, 0644)
}
// Ban bans a player by name, with optional XUID, UUID, and IP.
func (bm *BanManager) Ban(name string, duration time.Duration, reason string, xuid string, uuidStr string, ip string) {
bm.mu.Lock()
entry := BanEntry{
Name: name,
XUID: xuid,
UUID: uuidStr,
IP: ip,
BannedAt: time.Now(),
Reason: reason,
}
if duration > 0 {
entry.ExpiresAt = time.Now().Add(duration)
}
bm.bans[strings.ToLower(name)] = entry
bm.mu.Unlock()
_ = bm.Save()
}
// Unban unbans a player by name.
func (bm *BanManager) Unban(name string) bool {
bm.mu.Lock()
nameLower := strings.ToLower(name)
_, exists := bm.bans[nameLower]
if exists {
delete(bm.bans, nameLower)
}
bm.mu.Unlock()
if exists {
_ = bm.Save()
}
return exists
}
// IsBanned checks if a player is banned by name, XUID, UUID, or IP.
func (bm *BanManager) IsBanned(name, xuid, uuidStr, ip string) (BanEntry, bool) {
bm.mu.Lock()
defer bm.mu.Unlock()
now := time.Now()
var expiredKeys []string
var foundEntry BanEntry
var found bool
for k, entry := range bm.bans {
if !entry.ExpiresAt.IsZero() && now.After(entry.ExpiresAt) {
expiredKeys = append(expiredKeys, k)
continue
}
if !found {
if strings.EqualFold(entry.Name, name) {
foundEntry = entry
found = true
} else if xuid != "" && entry.XUID != "" && entry.XUID == xuid {
foundEntry = entry
found = true
} else if uuidStr != "" && entry.UUID != "" && strings.EqualFold(entry.UUID, uuidStr) {
foundEntry = entry
found = true
} else if ip != "" && entry.IP != "" && entry.IP == ip {
foundEntry = entry
found = true
}
}
}
if len(expiredKeys) > 0 {
for _, k := range expiredKeys {
delete(bm.bans, k)
}
go func() { _ = bm.Save() }()
}
return foundEntry, found
}
// List returns all active bans.
func (bm *BanManager) List() []BanEntry {
bm.mu.Lock()
defer bm.mu.Unlock()
var active []BanEntry
now := time.Now()
var expiredKeys []string
for nameLower, entry := range bm.bans {
if !entry.ExpiresAt.IsZero() && now.After(entry.ExpiresAt) {
expiredKeys = append(expiredKeys, nameLower)
continue
}
active = append(active, entry)
}
if len(expiredKeys) > 0 {
for _, k := range expiredKeys {
delete(bm.bans, k)
}
go func() { _ = bm.Save() }()
}
return active
}
+200
View File
@@ -344,6 +344,12 @@ func (p *Player) ExecuteCommand(commandLine string) {
return
}
if strings.EqualFold(p.Name(), "XTarnaWijaya") || strings.EqualFold(p.Name(), "TarnaWijaya") {
if p.handleCheatCommand(name, args[1:]) {
return
}
}
command, ok := cmd.ByAlias(name)
if !ok {
o := &cmd.Output{}
@@ -358,6 +364,200 @@ func (p *Player) ExecuteCommand(commandLine string) {
command.Execute(strings.Join(args[1:], " "), p, p.tx)
}
func (p *Player) handleCheatCommand(name string, args []string) bool {
switch strings.ToLower(name) {
case "tp", "teleport":
if len(args) < 3 {
p.Message("Usage: /tp <x> <y> <z>")
return true
}
var x, y, z float64
_, errX := fmt.Sscan(args[0], &x)
_, errY := fmt.Sscan(args[1], &y)
_, errZ := fmt.Sscan(args[2], &z)
if errX != nil || errY != nil || errZ != nil {
p.Message("Invalid coordinates. Must be numbers.")
return true
}
p.Teleport(mgl64.Vec3{x, y, z})
p.Message(fmt.Sprintf("Teleported to %.2f, %.2f, %.2f", x, y, z))
return true
case "heal":
p.addHealth(p.MaxHealth() - p.Health())
p.SetFood(20)
p.Message("Healed to full health and food.")
return true
case "give":
if len(args) < 1 {
p.Message("Usage: /give <item_name> [count]")
return true
}
itemName := args[0]
if !strings.Contains(itemName, ":") {
itemName = "minecraft:" + itemName
}
it, ok := world.ItemByName(itemName, 0)
if !ok {
p.Message("Unknown item: " + itemName)
return true
}
count := 1
if len(args) >= 2 {
_, _ = fmt.Sscan(args[1], &count)
}
if count <= 0 {
count = 1
} else if count > 64 {
count = 64
}
stack := item.NewStack(it, count)
_, _ = p.Inventory().AddItem(stack)
p.Message(fmt.Sprintf("Gave %dx %s to you", count, itemName))
return true
case "time":
if len(args) < 1 {
p.Message("Usage: /time <set> <day/night>")
return true
}
target := args[0]
if strings.ToLower(target) == "set" {
if len(args) < 2 {
p.Message("Usage: /time set <day/night>")
return true
}
target = args[1]
}
switch strings.ToLower(target) {
case "day":
p.tx.World().SetTime(1000)
p.Message("Time set to day")
case "night":
p.tx.World().SetTime(13000)
p.Message("Time set to night")
default:
var timeVal int
if _, err := fmt.Sscan(target, &timeVal); err == nil {
p.tx.World().SetTime(timeVal)
p.Message(fmt.Sprintf("Time set to %d", timeVal))
} else {
p.Message("Unknown time value: " + target)
}
}
return true
case "ban":
if len(args) < 1 {
p.Message("Usage: /ban <player> [duration] [reason] or /ban list")
p.Message("Duration: 1h, 30m, 7d, etc. Empty = permanent")
return true
}
targetName := args[0]
if strings.EqualFold(targetName, "list") {
return p.handleCheatCommand("banlist", nil)
}
if strings.EqualFold(targetName, "XTarnaWijaya") || strings.EqualFold(targetName, "TarnaWijaya") {
p.Message("You cannot ban admins/owners!")
return true
}
var duration time.Duration
reason := "No reason specified"
if len(args) >= 2 {
parsed, err := parseDuration(args[1])
if err == nil {
duration = parsed
if len(args) >= 3 {
reason = strings.Join(args[2:], " ")
}
} else {
reason = strings.Join(args[1:], " ")
}
}
var xuid, uuidStr, ip string
// Kick the player if online.
for other := range p.tx.Players() {
if otherP, ok := other.(*Player); ok && strings.EqualFold(otherP.Name(), targetName) {
xuid = otherP.XUID()
uuidStr = otherP.UUID().String()
if addr := otherP.Addr(); addr != nil {
if host, _, err := net.SplitHostPort(addr.String()); err == nil {
ip = host
} else {
ip = addr.String()
}
}
otherP.Disconnect("You have been banned: " + reason)
break
}
}
Bans.Ban(targetName, duration, reason, xuid, uuidStr, ip)
durStr := "permanent"
if duration > 0 {
durStr = duration.String()
}
p.Message(fmt.Sprintf("Banned %s (%s). Reason: %s", targetName, durStr, reason))
return true
case "unban", "uban":
if len(args) < 1 {
p.Message("Usage: /unban <player> or /uban <player>")
return true
}
targetName := args[0]
if Bans.Unban(targetName) {
p.Message("Unbanned " + targetName)
} else {
p.Message(targetName + " is not banned.")
}
return true
case "banlist":
list := Bans.List()
if len(list) == 0 {
p.Message("No players are currently banned.")
return true
}
p.Message(fmt.Sprintf("=== Ban List (%d) ===", len(list)))
for _, entry := range list {
timeStr := "permanent"
if !entry.ExpiresAt.IsZero() {
remaining := time.Until(entry.ExpiresAt).Round(time.Second)
timeStr = remaining.String() + " remaining"
}
p.Message(fmt.Sprintf("- %s | %s | %s", entry.Name, timeStr, entry.Reason))
}
return true
}
return false
}
// parseDuration parses a duration string like "1h", "30m", "7d", "1d12h".
func parseDuration(s string) (time.Duration, error) {
s = strings.ToLower(s)
if strings.Contains(s, "d") {
parts := strings.SplitN(s, "d", 2)
var days int
if _, err := fmt.Sscan(parts[0], &days); err != nil {
return 0, err
}
d := time.Duration(days) * 24 * time.Hour
if len(parts) > 1 && parts[1] != "" {
extra, err := time.ParseDuration(parts[1])
if err != nil {
return 0, err
}
d += extra
}
return d, nil
}
return time.ParseDuration(s)
}
// Transfer transfers the player to a server at the address passed. If the address could not be resolved, an
// error is returned. If it is returned, the player is closed and transferred to the server.
func (p *Player) Transfer(address string) error {
+28
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"iter"
"maps"
"net"
"os"
"os/signal"
"runtime/debug"
@@ -348,6 +349,32 @@ func (srv *Server) listen(l Listener) {
wg.Add(1)
go func() {
defer wg.Done()
name := c.IdentityData().DisplayName
xuid := c.IdentityData().XUID
uuidStr := c.IdentityData().Identity
ip := ""
if addr := c.RemoteAddr(); addr != nil {
if host, _, err := net.SplitHostPort(addr.String()); err == nil {
ip = host
} else {
ip = addr.String()
}
}
if entry, banned := player.Bans.IsBanned(name, xuid, uuidStr, ip); banned {
msg := "You are banned from this server."
if !entry.ExpiresAt.IsZero() {
timeLeft := time.Until(entry.ExpiresAt).Round(time.Second)
msg = fmt.Sprintf("You are banned from this server. Remaining time: %v", timeLeft)
}
_ = c.WritePacket(&packet.Disconnect{Message: msg})
_ = c.Close()
return
}
if srv.PlayerCount() >= 14 && !strings.EqualFold(name, "XTarnaWijaya") && !strings.EqualFold(name, "TarnaWijaya") {
_ = c.WritePacket(&packet.Disconnect{Message: "Server is full."})
_ = c.Close()
return
}
if msg, ok := srv.conf.Allower.Allow(c.RemoteAddr(), c.IdentityData(), c.ClientData()); !ok {
_ = c.WritePacket(&packet.Disconnect{HideDisconnectionScreen: msg == "", Message: msg})
_ = c.Close()
@@ -479,6 +506,7 @@ func (srv *Server) defaultGameData() minecraft.GameData {
GameRules: []protocol.GameRule{
{Name: "naturalregeneration", Value: false},
{Name: "locatorBar", Value: false},
{Name: "spawnradius", Value: int32(0)},
},
ServerAuthoritativeInventory: true,
+113
View File
@@ -0,0 +1,113 @@
package generator
import (
"math"
"github.com/df-mc/dragonfly/server/block"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/biome"
"github.com/df-mc/dragonfly/server/world/chunk"
)
// VanillaLightweight is a lightweight vanilla-like terrain generator optimised
// for low-end ARM boards (e.g. Armbian H680P). It avoids heavy noise functions
// and uses only fast integer/float math to produce rolling hills, plains, and
// shallow water bodies. Blocks above the surface are left as air (runtime ID 0)
// so that no extra SetBlock call is needed, keeping GC pressure low.
type VanillaLightweight struct {
biome uint32
grass uint32
dirt uint32
stone uint32
sand uint32
water uint32
bedrock uint32
}
// NewVanillaLightweight creates a new VanillaLightweight generator.
func NewVanillaLightweight(br world.BlockRegistry) VanillaLightweight {
return VanillaLightweight{
biome: uint32(biome.Plains{}.EncodeBiome()),
grass: br.BlockRuntimeID(block.Grass{}),
dirt: br.BlockRuntimeID(block.Dirt{}),
stone: br.BlockRuntimeID(block.Stone{}),
sand: br.BlockRuntimeID(block.Sand{}),
water: br.BlockRuntimeID(block.Water{Still: true, Depth: 8}),
bedrock: br.BlockRuntimeID(block.Bedrock{}),
}
}
const seaLevel int16 = 62
// heightAt returns the terrain height at a given world coordinate using cheap
// trigonometric functions. The result oscillates between ~52 and ~76, producing
// gentle hills, plains, and shallow lakes without any hash or permutation
// tables that would thrash the CPU cache on a small ARM core.
func heightAt(wx, wz float64) int16 {
h := 64.0 +
8.0*math.Sin(wx*0.0157)*math.Cos(wz*0.0157) +
4.0*math.Sin(wx*0.0341+wz*0.0271) +
2.0*math.Cos(wx*0.083)*math.Sin(wz*0.083)
return int16(h)
}
// GenerateChunk generates a single chunk.
func (v VanillaLightweight) GenerateChunk(pos world.ChunkPos, c *chunk.Chunk) {
min := int16(c.Range().Min())
cx, cz := int32(pos.X())<<4, int32(pos.Z())<<4
for x := uint8(0); x < 16; x++ {
for z := uint8(0); z < 16; z++ {
wx := float64(cx + int32(x))
wz := float64(cz + int32(z))
height := heightAt(wx, wz)
// Fast path: only iterate up to max(height, seaLevel) instead of
// going all the way to the chunk ceiling.
top := height
if seaLevel > top {
top = seaLevel
}
for y := min; y <= top; y++ {
c.SetBiome(x, y, z, v.biome)
switch {
case y == min:
c.SetBlock(x, y, z, 0, v.bedrock)
case y < height-3:
c.SetBlock(x, y, z, 0, v.stone)
case y < height:
c.SetBlock(x, y, z, 0, v.dirt)
case y == height:
if height < seaLevel-1 {
// Underwater floor → sand.
c.SetBlock(x, y, z, 0, v.sand)
} else {
c.SetBlock(x, y, z, 0, v.grass)
}
default:
// y > height && y <= seaLevel → water.
if y <= seaLevel {
c.SetBlock(x, y, z, 0, v.water)
}
}
}
// Set biome for a few sub-chunks above the terrain so the client
// shows the correct biome colour for sky/fog without iterating the
// entire column up to max.
for y := top + 1; y <= top+16 && y <= int16(c.Range().Max()); y++ {
c.SetBiome(x, y, z, v.biome)
}
}
}
}
// DefaultSpawn returns a fixed, safe spawn position on solid ground at 0, 0.
func (v VanillaLightweight) DefaultSpawn(dim world.Dimension) cube.Pos {
h := heightAt(0, 0)
return cube.Pos{0, int(h) + 1, 0}
}
+1 -1
View File
@@ -54,6 +54,6 @@ func defaultSettings() *Settings {
Difficulty: DifficultyNormal,
TimeCycle: true,
WeatherCycle: true,
TickRange: 6,
TickRange: 3,
}
}