Update terbaru
Menambahkan bypas nama gwa Nambahkan ban/unban/ban list menambahkan world clasik ringan
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user