package gameserver import ( "fmt" "log" "strings" "ananta-ps/pkg/protocol" ) type POILandmark struct { Name string X float32 Y float32 Z float32 Facing float32 } var KnownLandmarks = map[string]POILandmark{ "center": {Name: "City Center", X: 2260.0, Y: 15.0, Z: 1380.0, Facing: 0.0}, "police": {Name: "Lixi Police Station", X: 2285.0, Y: 10.0, Z: 1405.0, Facing: 90.0}, "smokey": {Name: "Smokey Drift Garage", X: 2260.5, Y: 0.0, Z: 1374.3, Facing: 90.0}, "beach": {Name: "Bayside Beach", X: 2800.0, Y: 0.0, Z: 1296.0, Facing: 270.0}, "subway": {Name: "Metro Station", X: 2150.0, Y: -10.0, Z: 1520.0, Facing: 180.0}, "rooftop": {Name: "Skyline Rooftop", X: 2310.0, Y: 85.0, Z: 1350.0, Facing: 45.0}, } type AdminBridge struct { server *Server } func NewAdminBridge(srv *Server) *AdminBridge { return &AdminBridge{server: srv} } // ExecuteCommand executes a GM command string on active sessions. func (ab *AdminBridge) ExecuteCommand(cmdLine string) (string, error) { cmdLine = strings.TrimSpace(cmdLine) if cmdLine == "" { return "", fmt.Errorf("empty command") } parts := strings.Fields(cmdLine) cmd := strings.ToLower(parts[0]) sessions := ab.server.GetActiveSessions() if len(sessions) == 0 { return "", fmt.Errorf("no active players in the game world") } targetSess := sessions[0] // Apply to primary active player switch cmd { case "teleport", "tp": if len(parts) < 2 { return "", fmt.Errorf("usage: teleport OR teleport ") } if poi, ok := KnownLandmarks[strings.ToLower(parts[1])]; ok { _ = targetSess.Teleport(poi.X, poi.Y, poi.Z, poi.Facing) return fmt.Sprintf("Teleported to %s (%.1f, %.1f, %.1f)", poi.Name, poi.X, poi.Y, poi.Z), nil } if len(parts) >= 4 { var x, y, z float32 _, _ = fmt.Sscanf(parts[1], "%f", &x) _, _ = fmt.Sscanf(parts[2], "%f", &y) _, _ = fmt.Sscanf(parts[3], "%f", &z) _ = targetSess.Teleport(x, y, z, 0) return fmt.Sprintf("Teleported to coordinates (%.1f, %.1f, %.1f)", x, y, z), nil } return "", fmt.Errorf("unknown POI landmark '%s'", parts[1]) case "weather": if len(parts) < 2 { return "", fmt.Errorf("usage: weather (e.g. 1=Sunny, 2=Rain, 3=Fog)") } var wId uint32 _, _ = fmt.Sscanf(parts[1], "%d", &wId) ab.server.SetWorldWeather(wId) return fmt.Sprintf("World weather set to ID %d", wId), nil case "time": if len(parts) < 2 { return "", fmt.Errorf("usage: time (e.g. 12.0 for noon, 0.0 for midnight)") } var hour float32 _, _ = fmt.Sscanf(parts[1], "%f", &hour) ab.server.SetWorldTimeOfDay(hour) return fmt.Sprintf("World time set to %.1f:00", hour), nil case "wanted": if len(parts) < 2 { return "", fmt.Errorf("usage: wanted (0-5)") } var stars uint32 _, _ = fmt.Sscanf(parts[1], "%d", &stars) targetSess.wantedLevel = stars w := protocol.NewUxWriter() w.U64(targetSess.pid).U32(stars) _ = targetSess.NotifyData(protocol.MethodGetMethodIDDefault("SyncWantedLevel", 68582860), w.Bytes()) return fmt.Sprintf("Player wanted level set to %d stars", stars), nil case "spawn_vehicle", "veh": var templateId uint32 = 1001 if len(parts) >= 2 { _, _ = fmt.Sscanf(parts[1], "%d", &templateId) } vehUnitId := uint64(0x6000000000000000) | uint64(len(ab.server.sessions)+1) targetSess.activeVehicleId = vehUnitId w := protocol.NewUxWriter() w.U64(vehUnitId).U32(templateId).F32(targetSess.posX + 3.0).F32(targetSess.posY).F32(targetSess.posZ + 3.0).F32(targetSess.facing) _ = targetSess.NotifyData(protocol.MethodGetMethodIDDefault("SyncSpawnVehicle", 68582851), w.Bytes()) return fmt.Sprintf("Spawned vehicle template %d (unit %d)", templateId, vehUnitId), nil case "spawn_enemy": _ = ab.server.handleGmWorldRefreshEnemyGroup(targetSess, &protocol.RpcPacket{Body: nil}) return "Spawned combat enemy squad near player", nil case "switch", "char": if len(parts) < 2 { return "", fmt.Errorf("usage: switch ") } var newId uint32 _, _ = fmt.Sscanf(parts[1], "%d", &newId) // Perform clean character swap newUnitId := targetSess.unitId + 1 _ = targetSess.NotifyData(protocol.MethodSyncLogicAgentLeave, protocol.EncodeLogicAgentLeave(targetSess.unitId)) _ = targetSess.NotifyData(protocol.MethodSyncLogicAgentEnter, protocol.EncodeLogicAgentEnter(newUnitId)) _ = targetSess.NotifyData(protocol.MethodSyncManagedLogicAgent, protocol.EncodeManagedLogicAgent(newUnitId, targetSess.pid, 0)) newProj := protocol.EncodeRaidBattleUnitSpirit(newUnitId, newId, targetSess.posX, targetSess.posY, targetSess.posZ, targetSess.facing, targetSess.pid) _ = targetSess.NotifyData(protocol.MethodSyncRaidBattleUnitSpirit, newProj) _ = targetSess.NotifyData(protocol.MethodSyncPlayerCurrentSpirit, protocol.EncodePlayerCurrentSpirit(targetSess.pid, newId, newUnitId, true)) targetSess.templateId = newId targetSess.unitId = newUnitId return fmt.Sprintf("Switched player character to template %d", newId), nil case "fog_unlock": wFog := protocol.NewUxWriter() wFog.U32(uint32(targetSess.activeScene)).Bool(true) _ = targetSess.NotifyData(protocol.MethodSyncSceneFogMapAllUnlock, wFog.Bytes()) return "All map fog unlocked", nil case "dive_unlock": wDive := protocol.NewUxWriter() wDive.UxString("EnableDive").U8(0xFF).Int7(1).UxString("true") _ = targetSess.NotifyData(protocol.MethodSyncRunGmUtils, wDive.Bytes()) return "Diving mechanics enabled", nil default: log.Printf("[ADMIN] Unrecognized GM command: %s", cmd) return "", fmt.Errorf("unknown command '%s'", cmd) } }