Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug Fix]: Fixes for Never Wanted "Flickering" in Zancudo/Bolingbroke #3152

Merged
merged 10 commits into from
May 27, 2024

Conversation

rkwapisz
Copy link
Contributor

Added calls to SET_MAX_WANTED_LEVEL to prevent "flickering" of wanted level in areas like Bolingbroke and Zancudo that, in some situations, attempt to persist a wanted level on the player until they leave.

We're also saving the players max wanted level before toggling off the feature to provide a simple return to normal once the feature is disabled. However, this obviously only remembers what the wanted level was before Never Wanted was enabled, and doesn't know what the max wanted level SHOULD be. However this would be an issue in most other implementations.

I like the idea of hooking the natives directly and blocking calls to SET_PLAYER_WANTED_LEVEL (and related natives) as @gir489returns suggested but that's a bit out of my ability at the moment. However I haven't seen any unexpected conflicts with this implementation.

Fixes #3150. Tested it in Zancudo and Bolingbroke and achieved the desired behavior - no more wanted level flickering.

@ShinyWasabi
Copy link
Contributor

Could you test whether it's possible to regain wanted stars after unloading the menu with never wanted toggled on? I tested this exact code yesterday, but the game just set the maximum wanted level to 0 when I unloaded the menu, even though the on_disable function is supposed to revert it.

@rkwapisz
Copy link
Contributor Author

rkwapisz commented May 18, 2024

Could you test whether it's possible to regain wanted stars after unloading the menu with never wanted toggled on? I tested this exact code yesterday, but the game just set the maximum wanted level to 0 when I unloaded the menu, even though the on_disable function is supposed to revert it.

I did test toggling the feature on and off (unloading the menu should call on_disable the same way) without issue. When I disabled Never Wanted in Bolingbroke I almost immediately regained the expected wanted level.

Most likely you weren't properly calling the command with
components::command_checkbox<"neverwanted">();

So your on_enable and on_disable overrides weren't actually executing.
Well, on_enable would execute only once, but on_disable wouldn't.

@gir489returns
Copy link
Contributor

@rkwapisz rkwapisz changed the title Fixes for Never Wanted [Bug Fix]: Fixes for Never Wanted "Flickering" in Zancudo/Bolingbroke May 18, 2024
@rkwapisz
Copy link
Contributor Author

https://github.com/YimMenu/YimMenu/blob/master/src/native_hooks/native_hooks.cpp#L91-L104

Thanks. See comment here for some of my results trying this approach:
#3150 (comment)

I can continue investigating since I'm interested in what's going on, but for now I think the SET_MAX_WANTED_LEVEL workaround is acceptable. Ideally I'd prefer hooking whatever sets our wanted level without touching SET_MAX_WANTED_LEVEL.

@ShinyWasabi
Copy link
Contributor

The game uses the REPORT_CRIME native to set your wanted level in Fort Zancudo, rather than SET_PLAYER_WANTED_LEVEL (it's not even used). Here’s a relevant snippet from the am_armybase script:

if (!func_159()) // Is player allowed in Fort Zancudo
{
	PLAYER::REPORT_CRIME(PLAYER::PLAYER_ID(), 36/*CRIME_TERRORIST_ACTIVITY*/, (PLAYER::GET_WANTED_LEVEL_THRESHOLD(4) - PLAYER::GET_WANTED_LEVEL_THRESHOLD(PLAYER::GET_PLAYER_WANTED_LEVEL(PLAYER::PLAYER_ID()))));
}
else
{
	PLAYER::REPORT_CRIME(PLAYER::PLAYER_ID(), 36/*CRIME_TERRORIST_ACTIVITY*/, (PLAYER::GET_WANTED_LEVEL_THRESHOLD(Global_262145.f_22100 /* Tunable: GANGWANTEDLEVELZANCUDO */) - PLAYER::GET_WANTED_LEVEL_THRESHOLD(PLAYER::GET_PLAYER_WANTED_LEVEL(PLAYER::PLAYER_ID()))));
}

We can hook it as follows:

void REPORT_CRIME(rage::scrNativeCallContext* src)
{
	Player player         = src->get_arg<Player>(0);
	int crime_type        = src->get_arg<int>(1);
	int wanted_threshold  = src->get_arg<int>(2);
	
	if (player == self::id && g.self.never_wanted)
		return;

	PLAYER::REPORT_CRIME(player, crime_type, wanted_threshold);
}

Alternatively, there is a tunable that disables AI in Fort Zancudo, making NPCs ignore the player:

int func_3() // Should player get wanted
{
	//...
	if (Global_262145.f_22094 /* Tunable: DISABLEENEMYAIFORTZANCUDO */)
	{
		return 0;
	}
	//...
}

But I think this would be a cheap workaround?

Also, I don't think we need to save previous maximum wanted level. In most cases, scripts set the maximum wanted level to either 0 or 5. SP scripts, which YimMenu doesn’t generally support, are the primary exceptions. MP scripts usually set the wanted level to 0 or 6, but the native automatically reverts it back to 5 anyway:

__int64 __fastcall sub_143291F8C(__int64 a1)
{
  __int64 (__fastcall *v1)(__int64); // rbp
  __int64 v2; // rcx
  __int64 (__fastcall *v4)(__int64); // [rsp+0h] [rbp-8h] BYREF

  v2 = **(unsigned int **)(a1 + 16);
  if ( (int)v2 >= 5 )
    v2 = 5i64;
  v4 = v1;
  _InterlockedExchange64((volatile __int64 *)&v4, (__int64)sub_14064A204);
  return v4(v2);
}

Therefore, I suggest setting it directly to 5 in the on_disable function.

@rkwapisz
Copy link
Contributor Author

Awesome analysis! I need to start diving into decompiled scripts as well. I should have been able to identify the use of REPORT_CRIME. I assume you're doing this on your fork already but I'll try it this way tomorrow and make updates to this PR.

So we'll hook REPORT_CRIME to handle Bolingbroke and Zancudo. Everything else should be handled by the on_tick behavior of setting wanted level to zero. By chance, do you know if REPORT_CRIME also extends to the player gaining a wanted level from shooting peds, police, etc.? SET_PLAYER_WANTED_LEVEL doesn't do this either. The on_tick behavior makes this a non-issue, but if we have an opportunity to change this from a looped command to just a normal command, it would be more optimal to use a one-time native hook instead.

Therefore, I suggest setting it directly to 5 in the on_disable function.

That's fine as well. Though I did have SP in mind as the main reason for the check even though we don't officially support it. Again, checking through scripts would have identified any other corner cases so I trust that your review is complete and we don't need this behavior.

MP scripts usually set the wanted level to 0 or 6, but the native automatically reverts it back to 5 anyway

IIRC there were plans at some point in GTA's development to support 6 stars but that got axed. Sounds like instead of updating the scripts they just put in this workaround.

@DayibBaba
Copy link
Contributor

@rkwapisz give ur discord id.

@ShinyWasabi
Copy link
Contributor

By chance, do you know if REPORT_CRIME also extends to the player gaining a wanted level from shooting peds, police, etc.?

It can be used for them, but R* only uses it in am_armybase, am_prison, am_hold_up, and am_taxi.

The native calls sub_140642480, which calls sub_1406146E4 to evaluate a crime. If we hook and make it return false, then none of the crimes should be reported. I will test it now.

BTW, here's a list of all the crime types from common.rpf/data/ai/crimeinformation.meta:
crimeinformation.txt

Yimura
Yimura previously approved these changes May 19, 2024
Copy link

github-actions bot commented May 19, 2024

Download the artifacts for this pull request:

@gir489returns
Copy link
Contributor

gir489returns commented May 19, 2024

By chance, do you know if REPORT_CRIME also extends to the player gaining a wanted level from shooting peds, police, etc.?

The natives are only used by the scripts. Rockstar calls the script runners CTheScripts. The native interfaces are just extensions of what the engine can do. The native usually just does something like:

CPed* this_ped = get_ped_from_index(param0);
this_ped->m_wanted_level = param1;

That code section might be wrapped inside a function that the natives just call with the determination of CPed from CTheScripts. When you shoot a ped, the engine might internally just call that funcition with the aggressor of who shot the ped.

@ShinyWasabi
Copy link
Contributor

I hooked the original function that REPORT_CRIME calls (sub_140642480).

void hooks::report_crime(int64_t a1, int crime_type, rage::fvector3* coords, bool a4, bool a5, uint32_t points, int64_t a7, int a8)
{
	static uint32_t total_crime_points{};

	if (!g.self.never_wanted)
		g_hooking->get_original<report_crime_now>()(a1, crime_type, coords, a4, a5, points, a7, a8);

	if (g.self.never_wanted)
		points = 0;

	total_crime_points    += points;
	std::string crime_str = (crime_type_names.count(crime_type) > 0) ? crime_type_names.at(crime_type) : "CRIME_UNKNOWN";

	LOG(INFO) << crime_str << " (" << crime_type << ")" << ", +" << points << " points " << "(total " << total_crime_points << ")";
}
[15:20:07.9423167][INFO/report_crime.cpp:76] CRIME_KILL_PED (40), +20 points (total 20)
[15:20:08.5347582][INFO/report_crime.cpp:76] CRIME_HIT_PED (11), +5 points (total 25)
[15:20:27.2053884][INFO/report_crime.cpp:76] CRIME_KILL_PED (40), +20 points (total 45)
[15:20:27.2056535][INFO/report_crime.cpp:76] CRIME_HIT_PED (11), +5 points (total 50)
[15:20:48.1081397][INFO/report_crime.cpp:76] CRIME_KILL_PED (40), +20 points (total 70)
[15:20:49.3492826][INFO/report_crime.cpp:76] CRIME_STAB_PED (23), +35 points (total 105)
[15:21:01.5956757][INFO/report_crime.cpp:76] CRIME_KILL_PED (40), +20 points (total 125)
[15:21:02.7540763][INFO/report_crime.cpp:76] CRIME_STAB_PED (23), +35 points (total 160)
[15:21:28.6678844][INFO/report_crime.cpp:76] CRIME_STEALTH_KILL_PED (45), +20 points (total 180)
[15:21:38.9491923][INFO/report_crime.cpp:76] CRIME_CAUSE_EXPLOSION (22), +25 points (total 205)
[15:21:38.9674127][INFO/report_crime.cpp:76] CRIME_DESTROY_VEHICLE (25), +70 points (total 275)
[15:21:50.6381471][INFO/report_crime.cpp:76] CRIME_CAUSE_EXPLOSION (22), +25 points (total 300)
[15:21:50.6382354][INFO/report_crime.cpp:76] CRIME_COP_SET_ON_FIRE (19), +80 points (total 380)
[15:22:02.5418241][INFO/report_crime.cpp:76] CRIME_CAUSE_EXPLOSION (22), +25 points (total 405)
[15:22:02.5418894][INFO/report_crime.cpp:76] CRIME_COP_SET_ON_FIRE (19), +80 points (total 485)
[15:22:03.2132015][INFO/report_crime.cpp:76] CRIME_SHOOT_COP (14), +80 points (total 565)
[15:22:09.7234452][INFO/report_crime.cpp:76] CRIME_CAUSE_EXPLOSION (22), +25 points (total 590)
[15:22:09.7358737][INFO/report_crime.cpp:76] CRIME_DESTROY_VEHICLE (25), +70 points (total 660)
[15:22:43.0192274][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 730)
[15:22:43.0524617][INFO/report_crime.cpp:76] CRIME_DESTROY_PLANE (21), +400 points (total 1130)
[15:22:49.7049332][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 1200)
[15:22:49.7371107][INFO/report_crime.cpp:76] CRIME_DESTROY_PLANE (21), +400 points (total 1600)
[15:22:53.3690438][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 1670)
[15:22:53.3865586][INFO/report_crime.cpp:76] CRIME_DESTROY_HELI (17), +400 points (total 2070)
[15:22:58.8022870][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 2140)
[15:22:58.8337316][INFO/report_crime.cpp:76] CRIME_DESTROY_PLANE (21), +400 points (total 2540)
[15:23:00.9968344][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 2610)
[15:23:01.0593848][INFO/report_crime.cpp:76] CRIME_DESTROY_HELI (17), +400 points (total 3010)
[15:23:02.3036358][INFO/report_crime.cpp:76] CRIME_CAUSE_EXPLOSION (22), +25 points (total 3035)
[15:23:02.3036620][INFO/report_crime.cpp:76] CRIME_CAR_SET_ON_FIRE (20), +20 points (total 3055)
[15:23:04.2792043][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 3125)
[15:23:04.3104269][INFO/report_crime.cpp:76] CRIME_DESTROY_PLANE (21), +400 points (total 3525)
[15:23:09.1493863][INFO/report_crime.cpp:76] CRIME_FIREARM_DISCHARGE (28), +10 points (total 3535)
[15:23:14.0466312][INFO/report_crime.cpp:76] CRIME_SHOOT_VEHICLE (35), +15 points (total 3550)
[15:23:16.0712625][INFO/report_crime.cpp:76] CRIME_SHOOT_VEHICLE (35), +15 points (total 3565)
[15:23:19.1383617][INFO/report_crime.cpp:76] CRIME_SHOOT_COP (14), +80 points (total 3645)
[15:23:24.2181384][INFO/report_crime.cpp:76] CRIME_FIREARM_DISCHARGE (28), +10 points (total 3655)
[15:23:24.8905092][INFO/report_crime.cpp:76] CRIME_SHOOT_VEHICLE (35), +15 points (total 3670)
[15:23:31.4899836][INFO/report_crime.cpp:76] CRIME_DESTROY_HELI (17), +400 points (total 4070)
[15:23:46.7696978][INFO/report_crime.cpp:76] CRIME_SHOOT_VEHICLE (35), +15 points (total 4085)
[15:24:04.4071571][INFO/report_crime.cpp:76] CRIME_CAR_SET_ON_FIRE (20), +20 points (total 4105)
[15:24:04.4071779][INFO/report_crime.cpp:76] CRIME_CAUSE_EXPLOSION (22), +25 points (total 4130)
[15:24:07.5907449][INFO/report_crime.cpp:76] CRIME_SHOOT_COP (14), +80 points (total 4210)
[15:24:14.6955394][INFO/report_crime.cpp:76] CRIME_FIREARM_DISCHARGE (28), +10 points (total 4220)
[15:24:14.6955635][INFO/report_crime.cpp:76] CRIME_SHOOT_PED (13), +35 points (total 4255)
[15:24:20.1847428][INFO/report_crime.cpp:76] CRIME_SHOOT_PED_SUPPRESSED (46), +35 points (total 4290)
[15:24:20.4413653][INFO/report_crime.cpp:76] CRIME_DESTROY_HELI (17), +400 points (total 4690)
[15:24:20.5039832][INFO/report_crime.cpp:76] CRIME_VEHICLE_EXPLOSION (39), +70 points (total 4760)
[15:24:21.9096525][INFO/report_crime.cpp:76] CRIME_SHOOT_PED (13), +35 points (total 4795)

This function is responsible for reporting all types of crimes in the game. Each crime type requires a different amount of crime points to reach a specific wanted level. For example, CRIME_KILL_PED requires 50 crime points for 1 wanted star, 130 for 2 wanted stars, and so on. By setting the crime points to 0 whenever this function is called, we can ensure that the wanted level will never be updated. However, this won't work against calls like SET_PLAYER_WANTED_LEVEL etc., since they directly modify the wanted level.

So I think the current implementation with SET_MAX_WANTED_LEVEL is more than good enough. There is no need to go into further detail.

@gir489returns
Copy link
Contributor

However, this won't work against calls like SET_PLAYER_WANTED_LEVEL etc., since they directly modify the wanted level.

We want this behaviour. Scripts use it to set the player's wanted level for certian missions. As a test bed, I suggest you use the Casino Heist mission on Aggressive. When you get to the part where everyone leaves the casino, a very janky cutscene starts that sometimes gets stuck. If you interfere with the wanted level at all, you'll get the player stuck in a race condition, where the script is waiting for the player to become wanted, yet you'll be rejecting it, thus Philosopher A is waiting on Philsopher B, who's waiting on Philospher A who's waiting on... You get it.

@Yimura
Copy link
Member

Yimura commented May 19, 2024

@ShinyWasabi @gir489returns, so should I leave this open or merge it and if any changes need to be made merge that in later? Currently it's already in a functioning state and can be merged safely. As well as improve/fix some of the things that are wrong with the Wanted Level modifier.

@rkwapisz
Copy link
Contributor Author

If everyone is happy at the moment, we can solve the immediate issue reported in the bug. Potential conflicts with heists will remain a known issue.

@ShinyWasabi
Copy link
Contributor

@ShinyWasabi @gir489returns, so should I leave this open or merge it and if any changes need to be made merge that in later? Currently it's already in a functioning state and can be merged safely. As well as improve/fix some of the things that are wrong with the Wanted Level modifier.

Yeah I'm fine with it. We can make any other improvements later.

@gir489returns
Copy link
Contributor

I'd rather we implement the REPORT_CRIME detour now, and fix this now.

@rkwapisz
Copy link
Contributor Author

rkwapisz commented May 20, 2024

Verified intended behavior in Bolingbroke with the REPORT_CRIME hook. Couldn't verify Zancudo since my GTA:O characters all have access (I was actually testing Zancudo earlier in SP, but then I noticed SP doesn't use REPORT_CRIME apparently?).

Other deviant criminal behavior is cleared as before, and since we're not doing anything with SET_WANTED_LEVEL or SET_MAX_WANTED_LEVEL, it shouldn't clash with most scripts.

Haven't had a chance to confirm intended behavior with the Casino Heist as I don't have one set up to test with at the moment.

@ShinyWasabi
Copy link
Contributor

I was actually testing Zancudo earlier in SP, but then I noticed SP doesn't use REPORT_CRIME apparently?

Zancudo and Bolingbroke in SP are handled by re_armybase and re_prison scripts. Here's the function that sets your wanted level in Zancudo:

void func_51()//Position - 0x3779
{
	if (!PED::IS_PED_INJURED(PLAYER::PLAYER_PED_ID()))
	{
		if ((ENTITY::IS_ENTITY_IN_ANGLED_AREA(PLAYER::PLAYER_PED_ID(), -1599.593f, 2818.15f, -17.645f, -1612.423f, 2806.997f, 17.645f, 51f, true, false, 0) || ENTITY::IS_ENTITY_IN_ANGLED_AREA(PLAYER::PLAYER_PED_ID(), -2301.089f, 3385.031f, -31.086f, -2305.302f, 3379.441f, 31.086f, 16f, true, false, 0)) || ENTITY::IS_ENTITY_IN_ANGLED_AREA(PLAYER::PLAYER_PED_ID(), -2287.138f, 3385.616f, 31.124f, -2292.554f, 3378.428f, 31.124f, 19.8f, true, false, 0))
		{
			if (PLAYER::GET_PLAYER_WANTED_LEVEL(PLAYER::PLAYER_ID()) < 5)
			{
				PLAYER::SET_PLAYER_WANTED_LEVEL(PLAYER::PLAYER_ID(), 4, false);
				PLAYER::SET_PLAYER_WANTED_LEVEL_NOW(PLAYER::PLAYER_ID(), false);
			}
		}
	}
}

See func_31, func_34, and func_88 in re_prison for Bolingbroke's wanted logic.

@rkwapisz
Copy link
Contributor Author

Implemented some final fixes asked for by @ShinyWasabi

Copy link
Contributor

@gir489returns gir489returns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this solution, as it leaves open the SET_WANTED_LEVEL to allow the wanted level to be on for at least one frame, thus the scripts SHOULD work. I will test this either today or tomorrow and report back if Casino Heist works.

@gir489returns
Copy link
Contributor

Copy link
Contributor

@gir489returns gir489returns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature needs to be consistent across freemode and heists. The user asked to be "never wanted" not to fail missions randomly. Shutting this feature off during missions isn't acceptable, as that isn't what the user wanted, and we can fix this unlike something like Vehicle Flymode in FPV.

@ShinyWasabi
Copy link
Contributor

This is the function that sets our wanted level in Casino Heist:

void func_12477()//Position - 0x3FE8D7
{
    if (!BitTest(uLocal_60051, 6))
    {
        if (BitTest(uLocal_60051, 5))
        {
            if (!func_49(&uLocal_60059))
            {
                if (PLAYER::GET_PLAYER_WANTED_LEVEL(bLocal_3225) != 5)
                {
                    PLAYER::SET_PLAYER_WANTED_LEVEL(bLocal_3225, 5, false);
                    PLAYER::SET_PLAYER_WANTED_LEVEL_NOW(bLocal_3225, false);
                }
                PLAYER::SUPPRESS_LOSING_WANTED_LEVEL_IF_HIDDEN_THIS_FRAME(bLocal_3225);
                func_324(&uLocal_60059, 0, 0);
            }
            else if (!func_240(&uLocal_60059, 30000, 0))
            {
                PLAYER::SUPPRESS_LOSING_WANTED_LEVEL_IF_HIDDEN_THIS_FRAME(bLocal_3225);
            }
            else
            {
                MISC::SET_BIT(&uLocal_60051, 6);
            }
        }
    }
}

We just check for the 6th bit of the local in the on_tick function, and if it is off, return. This should be consistent with every mission that uses fm_mission_controller.

src/native_hooks/all_scripts.hpp Outdated Show resolved Hide resolved
@ShinyWasabi
Copy link
Contributor

Some missions still had issues where your wanted level would continuously be set, and the REPORT_CRIME hook wasn't effective in these cases. Therefore, I removed REPORT_CRIME and added the SET_MAX_WANTED_LEVEL native, which resolves the issue comprehensively. I also tested it with the Casino Heist, and it just worked fine.

Copy link
Contributor

@gir489returns gir489returns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks fine. Author says he's tested it.

@Yimura Yimura merged commit 3e577ee into YimMenu:master May 27, 2024
1 check passed
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 27, 2024
@ShinyWasabi ShinyWasabi deleted the FightingTheLawAgain branch May 28, 2024 13:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Never Wanted doesn't work properly
5 participants