Skip to content

Instantly share code, notes, and snippets.

@umaruru
Last active September 9, 2024 07:18
Show Gist options
  • Save umaruru/1cdbfc302dda20d8c5601f0ce8f0e03c to your computer and use it in GitHub Desktop.
Save umaruru/1cdbfc302dda20d8c5601f0ce8f0e03c to your computer and use it in GitHub Desktop.
RDR2 Prompt research

Prompt Research

Note: The code examples below should work on RedM. On other frameworks, you may need to adjust some function names.

Creating prompts

To create a prompt, start with _UI_PROMPT_REGISTER_BEGIN (0x04F97DE45A519419), set up some settings, then finish with _UI_PROMPT_REGISTER_END (0xF7AA2696A22AD8B9). While there's a native to create prompts (_UI_PROMPT_CREATE (0x29FA7910726C3889)), there aren't many exemples around.

A prompt basically needs a control and a text. You can find a list of controls here Controls (femga/rdr3_discoveries). The text has to be created with VAR_STRING (0xFA925AC00EB830B9).

When a prompt is not needed, they can removed them with _UI_PROMPT_DELETE (0x00EDE88D4D13CF59).

-- creates a prompt with text "My prompt" with R button as control.
local prompt = PromptRegisterBegin()
PromptSetControlAction(prompt, GetHashKey("INPUT_CONTEXT_X")) -- R key
PromptSetText(prompt, CreateVarString(10, "LITERAL_STRING", "My prompt"))
PromptRegisterEnd(prompt)

-- deleting a prompt
PromptDelete(prompt)

Some things can only be set when the prompt is being registered, while others can be changed later. The control can't be changed, but the text can.

There are various types of prompts and various ways to use them. You can check the related natives in vespura and alloc8or. They are prefixed with UiPrompt / _UI_PROMPT. These natives use the handle returned by _UI_PROMPT_REGISTER_BEGIN. There's the uiprompt resource by kibook to create and manage prompts. You can study their code to see how prompts can be configured and how to check when they're pressed, etc.

When a prompt is created this way, they stay on the screen indefinitely. You can manage them by hiding/disabling when needed, but that is a lot of work and not very flexible. There are other ways to work with prompts that are easier to manage.

Context Prompt

Instead of checking if the player is close to a position to enable a prompt, you can set the prompt as a context prompt. It enables and disables itself automatically when the player enters/leaves an area.

A context prompt has a position and an area. The area is defined by a radius or a volume. Radius basically creates a sphere around the position. Volume is more advanced, the area is defined by different shapes (box, cylinder, sphere, etc). They are created with CREATE_VOLUME_* natives. It seems volumes be combined with *_VOLUME_AGGREGATE_* natives to create more complex areas, but I didn't test it.

You can use _UI_PROMPT_IS_ACTIVE (0x546E342E01DE71CF) to check if the prompt is active and do stuff with it.

local position = vector3(1223.04, -1301.13, 76.95) -- rhodes station
local radius = 2.5
-- _UI_PROMPT_CONTEXT_SET_POINT
Citizen.InvokeNative(0xAE84C5EE2C384FB3, prompt, position.x, position.y, position.z)
-- _UI_PROMPT_CONTEXT_SET_RADIUS
Citizen.InvokeNative(0x0C718001B77CA468, prompt, radius)

-- You can also use a volume instead of a radius to define the prompt area
-- _UI_PROMPT_CONTEXT_SET_VOLUME
-- Citizen.InvokeNative(0x4D107406667423BE, prompt, volume)

-- inside a loop:
if PromptIsActive(prompt) then
	-- check if prompt is pressed, check progress, etc.
end

If there are multiple context prompts close to each other (areas are overlapping), there can be some situations:

  1. If they have different controls (for example, R and G), both prompts are activated.
  2. If they have the same control:
    1. If both are set with radius, the one closest to the character facing direction is activated.
    2. If one is volume and the other radius, the volume one gets prioritized.
    3. If both are volumes, the one that was created first gets activated.

Prompt groups

Prompt groups are very useful to group related prompts. Prompt groups allow you to set a text below the prompts as well.

A group is basically a number, and it's very common to use a random integer for that. To add prompts to a group, use the native _UI_PROMPT_SET_GROUP (0x2F11D3A254169EA4).

local group = GetRandomIntInRange(1, 0xFFFFFF)
PromptSetGroup(prompt1, group, 0)
PromptSetGroup(prompt2, group, 0)

Prompt groups can have multiple tabs, the ones you press Q to change between pages. The third argument of _UI_PROMPT_SET_GROUP determines the tab the prompt will be put in.

Groups have to be activated every frame to be visible. There are two types of groups, active and ambient groups.

Active Group

Active groups are exclusive, only one can be activated at a time. To have them activated, you have to call _UI_PROMPT_SET_ACTIVE_GROUP_THIS_FRAME (0xC65A45D4453C2627) every frame:

PromptSetActiveGroupThisFrame(
	group, -- integer
	name, -- char*
	tabAmount, -- integer
	tabDefaultIndex, -- integer
	p4, -- integer
	prompt, -- prompt
)
  • group the group number, usually a random integer created previously
  • name the text shown below the group. Has to be created with VAR_STRING / CreateVarString.
  • tabAmount the number of tabs in the prompt group.
  • tabDefaultIndex the default tab shown when the group is first activated
  • p4 unknown (0 works)
  • prompt custom prompt or something? setting it to 0 works fine

Ambient Group

Ambient groups are easily seen in the game when there are multiple entities together. Things like hats, dead animals, lootable bodies, plants, etc., where they group and the player can change between tabs pressing Q.

Active groups are exclusive, only one can be activated at a time. Different scripts using active groups can interfere with each other. On the other hand, ambient groups allow multiple groups to be active at the same time. You can create and activate ambient groups in different scripts and not have to worry about this problem.

They enable/disable themselves automatically based on the player position (like context prompts) and can be set to 'group' with other groups.

There are two ways to activate ambient groups: one is attaching it to an entity (ped, vehicle or object) and the other to a volume.

Ambient groups in entities

To activate an ambient group in an entity, you have to call _UI_PROMPT_SET_AMBIENT_GROUP_THIS_FRAME (0x315C81D760609108) every frame.

PromptSetAmbientGroupThisFrame(
	entity, -- entity
	radius, -- number
	p2, -- unknown (commonly 2 in R* scripts)
	tabAmount, -- integer
	group, -- integer
	name, -- char*
	flags -- integer
)
  • entity the target entity
  • radius the distance (in meters) the group will be active
  • p2 unknown. Commonly 2 in R* scrips
  • tabAmount determines how many tabs the group has
  • group the group to be activated
  • name the text shown below the prompts (create with VAR_STRING/CreateVarString)
  • flags affect grouping behavior (and maybe other stuff)
Flags:
0: (no flags) does not group
1: unknown
2: group with other ambient groups / show tabs
4: unknown. with 2 | 4 the tabs are visible, but you can't change with Q
8: unknown

Ambient groups in volumes

To activate, you have to call 0x8B55B324A9123F6B every frame.

Citizen.InvokeNative(0x8B55B324A9123F6B, -- the native doesn't have an official name
	group, -- integer
	volume, -- volume
	name, -- char*
	p3, -- integer
	tabAmount, -- integer
	flag, -- integer
)
  • group the group to be activated.
  • volume a volume created with a CREATE_VOLUME_* native.
  • name the text shown below prompts. Create with VAR_STRING / CreateVarString
  • p3 unknown. Usually 2 in R* scripts (probably does the same as p2 in 0x315C81D760609108)
  • tabAmount determines how many tabs the group has
  • flag see above.

Considerations about ambient groups

You probably don't want to create 1000 ambient groups and have all of them activated all the time. You can check the distance from the player or use something like _GET_ENTITIES_NEAR_POINT (0x59B57C4B06531E1E) to filter which groups should be activated.

To check if a group is active, use _UI_PROMPT_GET_GROUP_ACTIVE_PAGE (0xC1FCC36C3F7286C8). If it returns >= 0, the group is active. If it returns -1, it's inactive.

Interaction Lockon

Interaction lockon prompt (ILO) allows the player to focus on the entity to access its prompts. You can register ILOs to peds, vehicles and objects. Peds can be focused by default, but vehicles and objects need to be enabled manually:

-- vehicle
-- SET_VEHICLE_CAN_BE_TARGETTED
Citizen.InvokeNative(0x05254BA0B44ADC16, entity, true)

-- object
-- _SET_OBJECT_TARGETTABLE_FOCUS
Citizen.InvokeNative(0xA22712E8471AA08E, entity, true, true)

The last argument of _SET_OBJECT_TARGETTABLE_FOCUS determines if an object will show its prompts when you aim a gun at it.

You can register lockon prompts using REGISTER_INTERACTION_LOCKON_PROMPT (0x870708A6E147A9AD)

Citizen.InvokeNative(0x870708A6E147A9AD, -- REGISTER_INTERACTION_LOCKON_PROMPT
	entity, -- entity
	text, -- string
	radius, -- number
	p3, -- number
	flag, -- integer
	p5, -- number
	p6, -- number
	prompt, -- prompt
	p8, -- boolean
	p9 -- integer
)
  • entity target entity
  • text text shown on the prompt when you approach the object
  • radius distance to show the focusing prompt
  • p3 focus distance? usually same as radius. If set higher than radius, you can still focus on the object up to that distance, even when the prompt is not visible.
  • flag ILO prompt flags
  • p5 unknown (usually 0.0 on R* scripts)
  • p6 unknown (usually 0.0 on R* scripts)
  • prompt could not make it work with a custom prompt. Usually 0 on R* scripts
  • p8 determines whether the ILO prompt appears when you approach the entity. You can still focus on the entity when this is set to false.
  • p9 unknown (usually -1 on R* scripts)
Flags:
1: make peds less likely to have their prompts shown (see below)
2: unknown
4: unknown
8: focus prompt does not show (can still focus)
16: unknown
32: unknown
64: camera doesn't lock to the entity, you can still look around
128: unknown
256: ilo prompts will show even when not looking at the target (in 3rd person)
512: hides the border on the prompt icon
1024: focus prompt does not show (can still focus)
2048: unknown
4096: unknown
8192: unknown
16384: unknown
32768: unknown
65536: unknown

By default, peds are more likely to have the prompt shown when multiple ILOs are in the same area. Flag 1 seem to turn that the other way around (vehicles and objects prompts show more).

To have prompts show when the entity is focused, you have to add them to the entity group. Use _UI_PROMPT_GET_GROUP_ID_FOR_TARGET_ENTITY (0xB796970BD125FCE8) to get the group and _UI_PROMPT_SET_GROUP (0x2F11D3A254169EA4) to add a prompt to the group.

local group = PromptGetGroupIdForTargetEntity(entity)
PromptSetGroup(prompt, group, 0)

-- remove the prompt from the group:
PromptRemoveGroup(prompt, group)

To remove the ilo prompt from the entity, use UNREGISTER_INTERACTION_LOCKON_PROMPT (0xE98D55C5983F2509)

-- UNREGISTER_INTERACTION_LOCKON_PROMPT
Citizen.InvokeNative(0xE98D55C5983F2509, entity)

Focus text

When you focus on an entity, it will not show the same text as the ILO prompt. For peds, it will show "Stranger", or something related to the ped (Post Clerk, Gunsmith, etc.). Vehicles and objects show "Interact".

You can change the ped text with _SET_PED_PROMPT_NAME (0x4A48B6E03BABB4AC).

SetPedPromptName(ped, "Funny name")

You can change the object text using 0xAEE6C800E124CFE1. The native is not named yet.

local text = "Hello, I'm object!"
Citizen.InvokeNative(0xAEE6C800E124CFE1, entity, text)

I could not find the function to change the text for vehicles.

Limits

There seems to be a limit of around 10 objects that can have ILOs registered. If you keep registering objects, they will still be able to be focused and interacted with, but the right click prompt won't show and distances can get messed up. To avoid that, you can register and unregister ILOs as needed (checking distance from the player, _GET_ENTITIES_NEAR_POINT (0x59B57C4B06531E1E), etc). This limit doesn't seem to apply to peds and vehicles, but it's a good idea to do the same for them.

If you disable ped reactions with SET_BLOCKING_OF_NON_TEMPORARY_EVENTS (0x9F8AA94D6D97DBF4), you have to set a ped config flag to force lockon prompt to show with SET_PED_CONFIG_FLAG (0x9CFBE10D). More flags: ePedScriptConfigFlags

SetPedConfigFlag(ped, 297, true) -- PCF_ForceInteractionLockonOnTargetPed
-- maybe this one as well
SetPedConfigFlag(ped, 301, false) -- PCF_DisableInteractionLockonOnTargetPed

Prompt ordering

When we create prompts, the order they are displayed is not the same as the order they are created. The order is defined by their controls. You can use the native _UI_PROMPT_SET_ORDERING_AS_INPUT_TYPE (0x2F385ECC5200938D) to set the control that is will be used for ordering. This has to be done when the prompt is being registered or immediately after, otherwise this won't apply.

The list below has some controls that can be used for ordering. The order is from bottom to top.

   hash      default key     control/action name
0x3B24C470       F         INPUT_CONTEXT_B
0x5181713D     SPACE       INPUT_CONTEXT_A
0xE3BF959B       R         INPUT_CONTEXT_X
0xD51B784F       E         INPUT_CONTEXT_Y
0xE6F612E4       1         INPUT_SELECT_QUICKSELECT_SIDEARMS_LEFT
0x1CE6D9EB       2         INPUT_SELECT_QUICKSELECT_DUALWIELD
0x4F49CC4C       3         INPUT_SELECT_QUICKSELECT_SIDEARMS_RIGHT
0x8F9F9E58       4         INPUT_SELECT_QUICKSELECT_UNARMED
0xAB62E997       5         INPUT_SELECT_QUICKSELECT_MELEE_NO_UNARMED
0xA1FDE2A6       6         INPUT_SELECT_QUICKSELECT_SECONDARY_LONGARM
0xB03A913B       7         INPUT_SELECT_QUICKSELECT_THROWN
0x42385422       8         INPUT_SELECT_QUICKSELECT_PRIMARY_LONGARM
-- after PromptRegisterBegin() and before PromptRegisterEnd()
-- prompt1
PromptSetOrderingAsInputType(prompt1, GetHashKey("INPUT_CONTEXT_A")) -- Space

-- prompt2
PromptSetOrderingAsInputType(prompt2, GetHashKey("INPUT_CONTEXT_B")) -- F

-- prompt1 will show above prompt2

Gotchas

  • Prompt functions in RedM don't have the 'Ui' prefix. When referring from Vespura, UiPromptCreate becomes PromptCreate and so on. Also, not all natives are implemented, and you have to call them directly with Citizen.InvokeNative.
  • Looks like the maximum number of prompts in the screen is 10. You can use group tabs if you need more prompts.
  • Controls can be finicky. Some don't even show the prompt. Some can be held, some can't. You have to test the controls to see if they work with your prompts.
  • You can not add the same ambient group to multiple entities, only one gets to have the group activated. On the other hand, you can assign multiple ambient groups to a single entity, but the order they show up is random.
  • It seems like a prompt can be added up to four groups. They stop showing after the fourth group. I think it's better to assign a prompt to only one group.
  • When you add prompts to an entity group and the entity dies, the prompts will show when you approach the dead body, like an ambient group. This is easily seen when you add ILO prompts to a ped and it dies. You can remove the prompts from the entity group to fix that.
  • You can have a registered ILO and an activated ambient group in the same entity.
  • You can use the same group as active group and ambient group (like if you want to force an ambient group to be activated).
  • You can register ILO for an entity and use the use the entity group in another place. Go to Harriet (naturalist lady) in RDO and you can see that in action: you can focus on her with ILO and activate the ambient group by approaching the table inside the tent.
  • Ambient groups have higher priority than context prompts when they are close together. They don't stack, that results in context prompts becoming inaccessible. Context prompts stack with ILOs as long as they don't use the same input.
  • Entities can be of three types: Ped, Vehicle or Object. When you call GET_ENTITY_TYPE (0x97F696ACA466B4E0), it returns 1 for peds, 2 for vehicles and 3 for objects: eEntityType. The entity type is also used in the natives _GET_ENTITIES_NEAR_POINT (0x59B57C4B06531E1E) and _GET_ENTITIES_IN_VOLUME (0x886171A12F400B89), so you're either searching for peds, vehicles or objects.
  • You can have a lot of context prompts, but it's not unlimited. I kept adding context prompts and it stopped around 2500 prompts.
  • I don't know if the limit above is only for context prompts or if it applies for all prompts. Remember to manage the prompts carefully. You can delete prompts that are likely to be inactive for a long time and create them when needed. Don't forget to delete unused prompts.

Prompt Transport Mode

added 15-12-2023

You can set a prompt to show on foot, on a vehicle (or mount), or both using the native _UI_PROMPT_SET_TRANSPORT_MODE (0x876E4A35C73A6655).

TM_ANY = 0,
TM_ON_FOOT = 1,
TM_IN_VEHICLE = 2
  • 0: the prompt will show regardless if the player is on a vehicle or not.
  • 1: the prompt will show only if the player is on foot (not on any mount or vehicle).
  • 2: the prompt will only if the player is on a mount or vehicle.

You can change the transport mode at any time, not only during prompt registering.

PromptSetTransportMode(prompt1, 0) -- on foot, vehicles and mounts
PromptSetTransportMode(prompt2, 1) -- only on foot
PromptSetTransportMode(prompt3, 2) -- only on mounts or vehicles

Groups don't appear if there are no visible prompts in it. If all prompts from a group are set to ON_FOOT and the player is mounted on a horse, the group text also doesn't show.

@umaruru
Copy link
Author

umaruru commented Dec 15, 2023

Added prompt transport mode. It controls if a prompt should be visible if the player is on foot or on a vehicle.

@draobrehtom
Copy link

Brilliant!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment