using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using CitizenFX.Core; using static CitizenFX.Core.Native.API; namespace RvWorldCleanup.Client { public class WorldCleanupClient : BaseScript { private static readonly string[] BlockedScenarioTypes = { "WORLD_HUMAN_AA_COFFEE", "WORLD_HUMAN_AA_SMOKE", "WORLD_HUMAN_BINOCULARS", "WORLD_HUMAN_CLIPBOARD", "WORLD_HUMAN_DRINKING", "WORLD_HUMAN_GUARD_STAND", "WORLD_HUMAN_HANG_OUT_STREET", "WORLD_HUMAN_SMOKING", "WORLD_HUMAN_STAND_IMPATIENT", "PROP_HUMAN_SEAT_BENCH", "PROP_HUMAN_SEAT_CHAIR", "WORLD_VEHICLE_ATTRACTOR", "WORLD_VEHICLE_BICYCLE_BMX", "WORLD_VEHICLE_EMPTY", "WORLD_VEHICLE_POLICE_CAR", "WORLD_VEHICLE_POLICE_NEXT_TO_CAR" }; private CleanupSettings _settings; private int _nextConfigRefreshAt; private int _nextCleanupAt; public WorldCleanupClient() { _settings = CleanupSettings.Load(); ApplyPersistentToggles(); EventHandlers["onClientResourceStart"] += new Action(OnClientResourceStart); EventHandlers["onClientResourceStop"] += new Action(OnClientResourceStop); Tick += OnTick; } private void OnClientResourceStart(string resourceName) { if (resourceName != GetCurrentResourceName()) { return; } _settings = CleanupSettings.Load(); ApplyPersistentToggles(); Debug.WriteLine(string.Format( CultureInfo.InvariantCulture, "[{0}] Ambient cleanup aktiv. Radius={1:0.##}, Intervall={2}ms", GetCurrentResourceName(), _settings.ClearRadius, _settings.ClearIntervalMs)); } private void OnClientResourceStop(string resourceName) { if (resourceName != GetCurrentResourceName()) { return; } RestorePersistentToggles(); } private async Task OnTick() { var now = GetGameTimer(); if (now >= _nextConfigRefreshAt) { _settings = CleanupSettings.Load(); ApplyPersistentToggles(); _nextConfigRefreshAt = now + _settings.ConfigRefreshMs; } if (!_settings.Enabled) { await Delay(1000); return; } ApplyFrameSuppression(); if (_settings.DisableScenarioActions) { ApplyScenarioSuppression(); } if (now >= _nextCleanupAt) { CleanupNearbyWorld(); _nextCleanupAt = now + _settings.ClearIntervalMs; } await Delay(0); } private void ApplyPersistentToggles() { SetRandomBoats(!_settings.DisableRandomBoats); SetGarbageTrucks(!_settings.DisableGarbageTrucks); var allowRandomCops = !_settings.DisableRandomCops; SetCreateRandomCops(allowRandomCops); SetCreateRandomCopsOnScenarios(allowRandomCops); SetCreateRandomCopsNotOnScenarios(allowRandomCops); var allowDispatch = !_settings.DisableDispatchServices; for (var service = 1; service <= 15; service++) { EnableDispatchService(service, allowDispatch); } SetMaxWantedLevel(_settings.DisableDispatchServices ? 0 : 5); } private void RestorePersistentToggles() { SetRandomBoats(true); SetGarbageTrucks(true); SetCreateRandomCops(true); SetCreateRandomCopsOnScenarios(true); SetCreateRandomCopsNotOnScenarios(true); for (var service = 1; service <= 15; service++) { EnableDispatchService(service, true); } SetMaxWantedLevel(5); } private void ApplyFrameSuppression() { SetPedPopulationBudget(_settings.DisableAmbientPeds ? 0 : 3); SetVehiclePopulationBudget(_settings.DisableAmbientVehicles ? 0 : 3); SetPedDensityMultiplierThisFrame(_settings.DisableAmbientPeds ? 0.0f : 1.0f); SetScenarioPedDensityMultiplierThisFrame( _settings.DisableScenarioPeds ? 0.0f : 1.0f, _settings.DisableScenarioPeds ? 0.0f : 1.0f); SetVehicleDensityMultiplierThisFrame(_settings.DisableAmbientVehicles ? 0.0f : 1.0f); SetRandomVehicleDensityMultiplierThisFrame(_settings.DisableAmbientVehicles ? 0.0f : 1.0f); SetParkedVehicleDensityMultiplierThisFrame(_settings.DisableParkedVehicles ? 0.0f : 1.0f); } private void ApplyScenarioSuppression() { SuppressShockingEventsNextFrame(); foreach (var scenarioType in BlockedScenarioTypes) { SetScenarioTypeEnabled(scenarioType, false); } } private void CleanupNearbyWorld() { var playerPed = PlayerPedId(); if (playerPed == 0 || !DoesEntityExist(playerPed)) { return; } var playerCoords = GetEntityCoords(playerPed, true); var radius = _settings.ClearRadius; if (_settings.DisableAmbientVehicles || _settings.DisableParkedVehicles) { RemoveVehiclesFromGeneratorsInArea( playerCoords.X - radius, playerCoords.Y - radius, playerCoords.Z - 10.0f, playerCoords.X + radius, playerCoords.Y + radius, playerCoords.Z + 10.0f); } if (_settings.DeleteExistingPeds) { CleanupPeds(playerCoords, radius, playerPed); } if (_settings.DeleteExistingVehicles) { CleanupVehicles(playerCoords, radius, playerPed); } } private void CleanupPeds(Vector3 origin, float radius, int playerPed) { var iterator = FindFirstPed(ref playerPed); try { if (iterator == -1) { return; } var ped = playerPed; do { if (!CanDeletePed(ped, playerPed, origin, radius)) { continue; } SetEntityAsMissionEntity(ped, true, true); DeletePed(ref ped); } while (FindNextPed(iterator, ref ped)); } finally { EndFindPed(iterator); } } private void CleanupVehicles(Vector3 origin, float radius, int playerPed) { var vehicle = 0; var iterator = FindFirstVehicle(ref vehicle); try { if (iterator == -1) { return; } do { if (!CanDeleteVehicle(vehicle, playerPed, origin, radius)) { continue; } SetEntityAsMissionEntity(vehicle, true, true); DeleteVehicle(ref vehicle); } while (FindNextVehicle(iterator, ref vehicle)); } finally { EndFindVehicle(iterator); } } private bool CanDeletePed(int ped, int playerPed, Vector3 origin, float radius) { if (ped == 0 || ped == playerPed || !DoesEntityExist(ped)) { return false; } if (IsPedAPlayer(ped)) { return false; } if (_settings.PreserveMissionEntities && IsEntityAMissionEntity(ped)) { return false; } if (_settings.PreserveNetworkedEntities && NetworkGetEntityIsNetworked(ped)) { return false; } if (IsPedInAnyVehicle(ped, false)) { return false; } return IsInsideRadius(origin, GetEntityCoords(ped, true), radius); } private bool CanDeleteVehicle(int vehicle, int playerPed, Vector3 origin, float radius) { if (vehicle == 0 || !DoesEntityExist(vehicle)) { return false; } var currentPlayerVehicle = GetVehiclePedIsIn(playerPed, false); if (vehicle == currentPlayerVehicle) { return false; } if (_settings.PreserveMissionEntities && IsEntityAMissionEntity(vehicle)) { return false; } if (_settings.PreserveNetworkedEntities && NetworkGetEntityIsNetworked(vehicle)) { return false; } if (_settings.PreserveOccupiedVehicles && !IsVehicleSeatFree(vehicle, -1)) { return false; } return IsInsideRadius(origin, GetEntityCoords(vehicle, true), radius); } private static bool IsInsideRadius(Vector3 origin, Vector3 target, float radius) { var distanceX = origin.X - target.X; var distanceY = origin.Y - target.Y; var distanceZ = origin.Z - target.Z; return (distanceX * distanceX) + (distanceY * distanceY) + (distanceZ * distanceZ) <= radius * radius; } private sealed class CleanupSettings { public bool Enabled { get; private set; } public bool DisableAmbientPeds { get; private set; } public bool DisableScenarioPeds { get; private set; } public bool DisableAmbientVehicles { get; private set; } public bool DisableParkedVehicles { get; private set; } public bool DisableDispatchServices { get; private set; } public bool DisableRandomCops { get; private set; } public bool DisableGarbageTrucks { get; private set; } public bool DisableRandomBoats { get; private set; } public bool DisableScenarioActions { get; private set; } public bool DeleteExistingPeds { get; private set; } public bool DeleteExistingVehicles { get; private set; } public bool PreserveMissionEntities { get; private set; } public bool PreserveNetworkedEntities { get; private set; } public bool PreserveOccupiedVehicles { get; private set; } public float ClearRadius { get; private set; } public int ClearIntervalMs { get; private set; } public int ConfigRefreshMs { get; private set; } public static CleanupSettings Load() { return new CleanupSettings { Enabled = ReadBool("rv_cleanup_enable", true), DisableAmbientPeds = ReadBool("rv_cleanup_disable_ambient_peds", true), DisableScenarioPeds = ReadBool("rv_cleanup_disable_scenario_peds", true), DisableAmbientVehicles = ReadBool("rv_cleanup_disable_ambient_vehicles", true), DisableParkedVehicles = ReadBool("rv_cleanup_disable_parked_vehicles", true), DisableDispatchServices = ReadBool("rv_cleanup_disable_dispatch_services", true), DisableRandomCops = ReadBool("rv_cleanup_disable_random_cops", true), DisableGarbageTrucks = ReadBool("rv_cleanup_disable_garbage_trucks", true), DisableRandomBoats = ReadBool("rv_cleanup_disable_random_boats", true), DisableScenarioActions = ReadBool("rv_cleanup_disable_scenario_actions", true), DeleteExistingPeds = ReadBool("rv_cleanup_delete_existing_peds", true), DeleteExistingVehicles = ReadBool("rv_cleanup_delete_existing_vehicles", true), PreserveMissionEntities = ReadBool("rv_cleanup_preserve_mission_entities", true), PreserveNetworkedEntities = ReadBool("rv_cleanup_preserve_networked_entities", true), PreserveOccupiedVehicles = ReadBool("rv_cleanup_preserve_occupied_vehicles", true), ClearRadius = ReadFloat("rv_cleanup_clear_radius", 175.0f), ClearIntervalMs = ReadInt("rv_cleanup_clear_interval_ms", 10000), ConfigRefreshMs = ReadInt("rv_cleanup_config_refresh_ms", 5000) }; } private static bool ReadBool(string convarName, bool defaultValue) { var defaultText = defaultValue ? "true" : "false"; var value = GetConvar(convarName, defaultText); if (string.IsNullOrWhiteSpace(value)) { return defaultValue; } value = value.Trim().ToLowerInvariant(); return value == "1" || value == "true" || value == "yes" || value == "on"; } private static int ReadInt(string convarName, int defaultValue) { var value = GetConvarInt(convarName, defaultValue); return value > 0 ? value : defaultValue; } private static float ReadFloat(string convarName, float defaultValue) { var rawValue = GetConvar(convarName, defaultValue.ToString(CultureInfo.InvariantCulture)); float parsedValue; if (float.TryParse(rawValue, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedValue) && parsedValue > 0.0f) { return parsedValue; } return defaultValue; } } } }