From 543c229be24ba879be677d60c579a9b519334858 Mon Sep 17 00:00:00 2001
From: "R.K" <rkkwapisz@gmail.com>
Date: Wed, 8 May 2024 01:24:00 -0700
Subject: [PATCH] Refactor of Player Wanted Level (#3070)

---
 src/backend/backend.cpp                   |  2 +-
 src/backend/commands/self/clearwanted.cpp | 22 ------
 src/backend/looped/looped.hpp             |  2 +-
 src/backend/looped/self/police.cpp        | 33 --------
 src/backend/looped/self/wanted_level.cpp  | 93 +++++++++++++++++++++++
 src/byte_patch_manager.cpp                |  7 --
 src/core/settings.hpp                     |  5 +-
 src/util/police.hpp                       | 10 ---
 src/views/self/view_self.cpp              | 43 ++++++-----
 9 files changed, 124 insertions(+), 93 deletions(-)
 delete mode 100644 src/backend/commands/self/clearwanted.cpp
 delete mode 100644 src/backend/looped/self/police.cpp
 create mode 100644 src/backend/looped/self/wanted_level.cpp
 delete mode 100644 src/util/police.hpp

diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp
index 09e108b4..70686347 100644
--- a/src/backend/backend.cpp
+++ b/src/backend/backend.cpp
@@ -54,7 +54,7 @@ namespace big
 
 		while (g_running)
 		{
-			looped::self_police();
+			looped::self_wanted();
 			looped::self_hud();
 			looped::self_dance_mode();
 			looped::self_persist_outfit();
diff --git a/src/backend/commands/self/clearwanted.cpp b/src/backend/commands/self/clearwanted.cpp
deleted file mode 100644
index 8a281d1b..00000000
--- a/src/backend/commands/self/clearwanted.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "backend/command.hpp"
-#include "natives.hpp"
-
-namespace big
-{
-	class clear_wanted : command
-	{
-		using command::command;
-
-		virtual void execute(const command_arguments&, const std::shared_ptr<command_context> ctx) override
-		{
-			if(g_local_player && g_local_player !=nullptr && !g.self.force_wanted_level)
-			{
-				g_local_player->m_player_info->m_wanted_level = 0;
-				g.self.wanted_level = 0;
-				g_local_player->m_player_info->m_is_wanted = false;
-			}
-		}
-	};
-
-	clear_wanted g_clear_wanted("clearwantedlvl", "CLEAR_WANTED_LEVEL", "CLEAR_WANTED_LEVEL_DESC_SELF", 0);
-}
\ No newline at end of file
diff --git a/src/backend/looped/looped.hpp b/src/backend/looped/looped.hpp
index 0ef170c5..7543d267 100644
--- a/src/backend/looped/looped.hpp
+++ b/src/backend/looped/looped.hpp
@@ -25,7 +25,7 @@ namespace big
 		static void player_spectate();
 		static void player_remote_control_vehicle();
 
-		static void self_police();
+		static void self_wanted();
 		static void self_hud();
 		static void self_dance_mode();
 		static void self_persist_outfit();
diff --git a/src/backend/looped/self/police.cpp b/src/backend/looped/self/police.cpp
deleted file mode 100644
index cb43874d..00000000
--- a/src/backend/looped/self/police.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "backend/looped/looped.hpp"
-#include "pointers.hpp"
-#include "util/police.hpp"
-
-namespace big
-{
-	void looped::self_police()
-	{
-		if (g_local_player == nullptr || g_local_player->m_player_info == nullptr) [[unlikely]]
-			return;
-
-		static bool bLast = false;
-
-		bool b = g.self.never_wanted;
-
-		if (b)
-		{
-			g_local_player->m_player_info->m_wanted_level = 0;
-			police::m_max_wanted_level->apply();
-			police::m_max_wanted_level_2->apply();
-			bLast = b;
-		}
-		else if (b != bLast)
-		{
-			police::m_max_wanted_level->restore();
-			police::m_max_wanted_level_2->restore();
-			bLast = b;
-		}
-
-		if (g.self.force_wanted_level && !b)
-			g_local_player->m_player_info->m_wanted_level = g.self.wanted_level;
-	}
-}
\ No newline at end of file
diff --git a/src/backend/looped/self/wanted_level.cpp b/src/backend/looped/self/wanted_level.cpp
new file mode 100644
index 00000000..2ea5f472
--- /dev/null
+++ b/src/backend/looped/self/wanted_level.cpp
@@ -0,0 +1,93 @@
+#include "backend/command.hpp"
+#include "backend/looped_command.hpp"
+#include "backend/looped/looped.hpp"
+#include "pointers.hpp"
+
+namespace big
+{
+	bool user_updated_wanted_level = false;
+
+	class clear_wanted : command
+	{
+		using command::command;
+
+		virtual void execute(const command_arguments&, const std::shared_ptr<command_context> ctx) override
+		{
+			// If we're never wanted... don't bother since there is no wanted level to clear anyway (this button won't even be shown if never_wanted is enabled)
+			if (g.self.never_wanted)
+				return;
+
+			// Clear current wanted level
+			g_local_player->m_player_info->m_wanted_level = 0;
+			g_local_player->m_player_info->m_is_wanted = false;
+
+			// Keep the lock if it's on, but reset the wanted level
+			g.self.wanted_level = 0;
+		}
+	};
+
+	clear_wanted g_clear_wanted("clearwanted", "CLEAR_WANTED_LEVEL", "CLEAR_WANTED_LEVEL_DESC_SELF", 0);
+
+	class never_wanted : looped_command
+	{
+		using looped_command::looped_command;
+
+		virtual void on_tick() override
+		{
+			// Clear current wanted level
+			g_local_player->m_player_info->m_wanted_level = 0;
+			g_local_player->m_player_info->m_is_wanted    = false;
+
+			// Since we're hiding the force wanted checkbox and wanted slider, we don't need to do anything else
+		}
+		virtual void on_disable() override
+		{
+			// If we turn off never wanted, be sure to disable force wanted to restore to a "default" setting
+			g.self.force_wanted_level = false;
+		}
+	};
+
+	never_wanted g_never_wanted("neverwanted", "NEVER_WANTED", "NEVER_WANTED", g.self.never_wanted);
+
+	void looped::self_wanted()
+	{
+		// Don't do anything if we're supposed to be never wanted (this slider won't be shown if never_wanted is enabled)
+		if (g.self.never_wanted)
+			return;
+
+		if (g_local_player && g_local_player->m_player_info)
+		{
+			// We only want the wanted level to update when the player changed it via the ImGui slider
+			if (user_updated_wanted_level)
+			{
+				g_local_player->m_player_info->m_wanted_level = g.self.wanted_level;
+
+				if (g.self.wanted_level == 0)
+					g_local_player->m_player_info->m_is_wanted = false;
+				else
+					g_local_player->m_player_info->m_is_wanted = true;
+
+				// I don't know if ImGui resets this every tick, let's assume it doesn't
+				user_updated_wanted_level = false;
+				return;
+			}
+
+			// Otherwise, if we're locking the wanted level, then just keep it consistently at what the player set
+			if (g.self.force_wanted_level)
+			{
+				g_local_player->m_player_info->m_wanted_level = g.self.wanted_level;
+
+				if (g.self.wanted_level == 0)
+					g_local_player->m_player_info->m_is_wanted = false;
+				else
+					g_local_player->m_player_info->m_is_wanted = true;
+			}
+
+			if (!user_updated_wanted_level && !g.self.force_wanted_level)
+			{
+				// If the player hasn't updated the wanted level and we're not locking, then YimMenu should update its own slider value to reflect the actual in-game wanted level since it may have changed
+				g.self.wanted_level = g_local_player->m_player_info->m_wanted_level;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/byte_patch_manager.cpp b/src/byte_patch_manager.cpp
index 6e48fab3..691a2ec4 100644
--- a/src/byte_patch_manager.cpp
+++ b/src/byte_patch_manager.cpp
@@ -5,7 +5,6 @@
 #include "memory/byte_patch.hpp"
 #include "pointers.hpp"
 #include "util/explosion_anti_cheat_bypass.hpp"
-#include "util/police.hpp"
 #include "util/vehicle.hpp"
 #include "util/world_model.hpp"
 
@@ -16,12 +15,6 @@ namespace big
 {
 	static void init()
 	{
-		// Restore max wanted level after menu unload
-		police::m_max_wanted_level =
-		    memory::byte_patch::make(g_pointers->m_gta.m_max_wanted_level.add(5).rip().as<uint32_t*>(), 0).get();
-		police::m_max_wanted_level_2 =
-		    memory::byte_patch::make(g_pointers->m_gta.m_max_wanted_level.add(14).rip().as<uint32_t*>(), 0).get();
-
 		// Patch World Model Spawn Bypass
 		std::array<uint8_t, 24> world_spawn_patch;
 		std::fill(world_spawn_patch.begin(), world_spawn_patch.end(), 0x90);
diff --git a/src/core/settings.hpp b/src/core/settings.hpp
index 3ee7113a..456cef41 100644
--- a/src/core/settings.hpp
+++ b/src/core/settings.hpp
@@ -311,12 +311,12 @@ namespace big
 			} ipls{};
 
 			bool clean_player                 = false;
+			bool never_wanted                 = false;
 			bool force_wanted_level           = false;
 			bool passive                      = false;
 			bool free_cam                     = false;
 			bool invisibility                 = false;
 			bool local_visibility             = true;
-			bool never_wanted                 = false;
 			bool no_ragdoll                   = false;
 			bool noclip                       = false;
 			float noclip_aim_speed_multiplier = 0.25f;
@@ -387,7 +387,8 @@ namespace big
 				NLOHMANN_DEFINE_TYPE_INTRUSIVE(super_hero_fly, gradual, explosions, auto_land, charge, ptfx, fly_speed, initial_launch)
 			} super_hero_fly{};
 
-			NLOHMANN_DEFINE_TYPE_INTRUSIVE(self, ipls, ptfx_effects, clean_player, force_wanted_level, passive, free_cam, invisibility, local_visibility, never_wanted, no_ragdoll, noclip, noclip_aim_speed_multiplier, noclip_speed_multiplier, off_radar, super_run, no_collision, unlimited_oxygen, no_water_collision, wanted_level, god_mode, part_water, proof_bullet, proof_fire, proof_collision, proof_melee, proof_explosion, proof_steam, proof_water, proof_mask, mobile_radio, fast_respawn, auto_tp, super_jump, beast_jump, healthregen, healthregenrate, hud, superman, custom_weapon_stop, prompt_ambient_animations, persist_outfit, persist_outfits_mis, interaction_menu_freedom, super_hero_fly)
+			NLOHMANN_DEFINE_TYPE_INTRUSIVE(self, ipls, ptfx_effects, clean_player, never_wanted, force_wanted_level, passive, free_cam, invisibility, local_visibility, no_ragdoll, noclip, noclip_aim_speed_multiplier, noclip_speed_multiplier, off_radar, super_run, no_collision, unlimited_oxygen, no_water_collision, wanted_level, god_mode, part_water, proof_bullet, proof_fire, proof_collision, proof_melee, proof_explosion, proof_steam, proof_water, proof_mask, mobile_radio, fast_respawn, auto_tp, super_jump, beast_jump, healthregen, healthregenrate, hud, superman, custom_weapon_stop, prompt_ambient_animations, persist_outfit, persist_outfits_mis, interaction_menu_freedom, super_hero_fly)
+
 		} self{};
 
 		struct session
diff --git a/src/util/police.hpp b/src/util/police.hpp
deleted file mode 100644
index e2c393d7..00000000
--- a/src/util/police.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-namespace big
-{
-    struct police
-    {
-		inline static memory::byte_patch* m_max_wanted_level;
-		inline static memory::byte_patch* m_max_wanted_level_2;
-    };
-}
\ No newline at end of file
diff --git a/src/views/self/view_self.cpp b/src/views/self/view_self.cpp
index ed68eb14..e92eeb61 100644
--- a/src/views/self/view_self.cpp
+++ b/src/views/self/view_self.cpp
@@ -10,6 +10,8 @@
 
 namespace big
 {
+	extern bool user_updated_wanted_level;
+
 	void view::self()
 	{
 		components::command_button<"suicide">();
@@ -204,25 +206,32 @@ namespace big
 			}
 		});
 
-		ImGui::Checkbox("NEVER_WANTED"_T.data(), &g.self.never_wanted);
-		components::options_modal("POLICE"_T.data(), [] {
-			ImGui::Checkbox("NEVER_WANTED"_T.data(), &g.self.never_wanted);
-			components::command_button<"clearwantedlvl">();
-			if (!g.self.never_wanted)
-			{
-				ImGui::Checkbox("FORCE_WANTED_LVL"_T.data(), &g.self.force_wanted_level);
-				if (ImGui::IsItemHovered())
-					ImGui::SetTooltip("FORCE_WANTED_LVL_INFO"_T.data());
-				ImGui::Text("WANTED_LVL"_T.data());
-				if (ImGui::SliderInt("###wanted_level", &g.self.wanted_level, 0, 5) && !g.self.force_wanted_level && g_local_player != nullptr)
-				{
-					g_local_player->m_player_info->m_wanted_level = g.self.wanted_level;
-				}
-			}
-		});
-
 		ImGui::EndGroup();
 
+		ImGui::SeparatorText("WANTED_LEVEL"_T.data());
+
+		ImGui::Checkbox("NEVER_WANTED"_T.data(), &g.self.never_wanted);
+		if (ImGui::IsItemHovered())
+			ImGui::SetTooltip("NEVER_WANTED_DESC"_T.data());
+
+		// Only show all the other stuff like clear wanted, force wanted, and the slider if we don't have never_wanted enabled, since never_wanted overrides all of that
+		if (!g.self.never_wanted)
+		{
+			ImGui::SameLine();
+			components::command_button<"clearwanted">();
+
+			// Most ImGui widgets return true when they've been changed, so this is useful to prevent us from overwriting the wanted level's natural decay/progression if we're not keeping it locked
+			ImGui::SetNextItemWidth(200);
+			user_updated_wanted_level = ImGui::SliderInt("###wanted_level", &g.self.wanted_level, 0, 5);
+
+			if (ImGui::IsItemHovered())
+				ImGui::SetTooltip("WANTED_LEVEL_SLIDER_DESC"_T.data());
+			ImGui::SameLine();
+			ImGui::Checkbox("FORCE_WANTED_LEVEL"_T.data(), &g.self.force_wanted_level);
+			if (ImGui::IsItemHovered())
+				ImGui::SetTooltip("FORCE_WANTED_LEVEL_DESC"_T.data());
+		}
+
 		ImGui::SeparatorText("PROOFS"_T.data());
 
 		if (ImGui::Button("CHECK_ALL"_T.data()))