package gameserver import ( "fmt" "log" "math" "strings" "time" "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 targetSess.stateMu.Lock() defer targetSess.stateMu.Unlock() 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 { if err := targetSess.teleport(poi.X, poi.Y, poi.Z, poi.Facing); err != nil { return "", err } 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 if _, err := fmt.Sscanf(parts[1], "%f", &x); err != nil { return "", fmt.Errorf("invalid x coordinate") } if _, err := fmt.Sscanf(parts[2], "%f", &y); err != nil { return "", fmt.Errorf("invalid y coordinate") } if _, err := fmt.Sscanf(parts[3], "%f", &z); err != nil { return "", fmt.Errorf("invalid z coordinate") } if math.IsNaN(float64(x)) || math.IsInf(float64(x), 0) || math.IsNaN(float64(y)) || math.IsInf(float64(y), 0) || math.IsNaN(float64(z)) || math.IsInf(float64(z), 0) { return "", fmt.Errorf("coordinates must be finite") } if err := targetSess.teleport(x, y, z, 0); err != nil { return "", err } 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 if _, err := fmt.Sscanf(parts[1], "%d", &wId); err != nil { return "", fmt.Errorf("invalid weather id") } if err := ab.server.SetWorldWeather(wId); err != nil { return "", err } 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 if _, err := fmt.Sscanf(parts[1], "%f", &hour); err != nil { return "", fmt.Errorf("invalid hour") } if err := ab.server.SetWorldTimeOfDay(hour); err != nil { return "", err } 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) if err := targetSess.NotifyData(protocol.MethodGetMethodIDDefault("SyncWantedLevel", 68582860), w.Bytes()); err != nil { return "", err } 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(time.Now().UnixNano()) 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) if err := targetSess.NotifyData(protocol.MethodGetMethodIDDefault("SyncSpawnVehicle", 68582851), w.Bytes()); err != nil { return "", err } 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 if _, err := fmt.Sscanf(parts[1], "%d", &newId); err != nil || newId == 0 { return "", fmt.Errorf("invalid spirit template") } // Perform clean character swap newUnitId := targetSess.unitId + 1 if err := targetSess.NotifyData(protocol.MethodSyncLogicAgentLeave, protocol.EncodeLogicAgentLeave(targetSess.unitId)); err != nil { return "", err } if err := targetSess.NotifyData(protocol.MethodSyncPlayerCurrentSpirit, protocol.EncodePlayerCurrentSpirit(targetSess.pid, newId, newUnitId, true)); err != nil { return "", err } if err := targetSess.NotifyData(protocol.MethodSyncLogicAgentEnter, protocol.EncodeLogicAgentEnter(newUnitId)); err != nil { return "", err } if err := targetSess.NotifyData(protocol.MethodSyncManagedLogicAgent, protocol.EncodeManagedLogicAgent(newUnitId, targetSess.pid, 0)); err != nil { return "", err } newProj := protocol.EncodeRaidBattleUnitSpirit(newUnitId, newId, targetSess.posX, targetSess.posY, targetSess.posZ, targetSess.facing, targetSess.pid) if err := targetSess.NotifyData(protocol.MethodSyncRaidBattleUnitSpirit, newProj); err != nil { return "", err } if err := targetSess.NotifyData(protocol.MethodSyncPlayerCurrentSpirit, protocol.EncodePlayerCurrentSpirit(targetSess.pid, newId, newUnitId, true)); err != nil { return "", err } 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) if err := targetSess.NotifyData(protocol.MethodSyncSceneFogMapAllUnlock, wFog.Bytes()); err != nil { return "", err } return "All map fog unlocked", nil case "dive_unlock": wDive := protocol.NewUxWriter() wDive.UxString("EnableDive").U8(0xFF).Int7(1).UxString("true") if err := targetSess.NotifyData(protocol.MethodSyncRunGmUtils, wDive.Bytes()); err != nil { return "", err } return "Diving mechanics enabled", nil default: log.Printf("[ADMIN] Unrecognized GM command: %s", cmd) return "", fmt.Errorf("unknown command '%s'", cmd) } }