RegisterPedheadshotTransparent and RegisterPedheadshot randomly not working
What happened?
Issue:
I am trying to use RegisterPedheadshotTransparent but this is not reliable and it is randomly working. It is like a 50/50 Chance that it gives me a Headshot or not.
Expected result
it should give me a Picture of my player head
Reproduction steps
try using it and displaying it
Importancy
Unknown
Area(s)
FiveM
Specific version(s)
FiveM Build 3095 // Artifacts 8633
Additional information
No response
This isn't a bug, first if you aren't please make sure you wait for IsPedheadshotReady (and possibly HasStreamedPedAssetsLoaded) and make sure you UnregisterPedheadshot when you're finished with them.
A hopefully understandable explanation on why this happens (thanks to gottfriedleibniz for most of this, this is largely paraphrasing him):
REGISTER_PEDHEADSHOT_TRANSPARENT is hard coded to use pedmugshot_01.ytd (128x128px) the ytds are encoded as DXT5/BC3 and has 128x128 which has alpha channels.
REGISTER_PEDHEADSHOT_HIRES is coded to use pedmugshot_0[2..8].ytd (which is also 128x128px) the ytds are encoded as DXT1/BC1.
REGISTER_PEDHEADSHOT is coded to use pedmugshot_0[9..34].ytd (which is 64x64px) these ytds are encoded as DXT1/BC1
To note: BC1 has 1 bit for alpha data BC3 has 1 byte for alpha data
The notable things here though is that the they both use 128x128px textures, so if you replace pedmugshot_0[2..8].ytd with a copy of pedmugshot_01.ytd it will be BC3 which means it will actually be able to store alpha data, and you'll be able to use 7 ped headshots at a time (or 8 if you still use RegisterPedheadshotTransparent)
TLDR:
RegisterPedheadshotTransparent only has 1 available texture slot to draw to (being pedmugshot_01.ytd), which is why this happens.
You can use RegisterPedheadshot_3 (aka REGISTER_PEDHEADSHOT_HIRES) with transparent textures streamed over the original slots and it will work the same as RegisterPedshotTransparent, you will only have 7 slots to work with at a time though.
Here's a zip with the stream-able files
Put these into a resources stream/ folder and you should be able to request 7 slots at a time for RegisterPedheadshot_3.
Some example code for how I've used this:
Client:
// note this isn't complete code, we have stuff to request this 7 at a time & spam retry 20 times until it eventually get it
// or just fails.
const getPedHeadshot = async (pedHandle: number): Promise<boolean> => {
let pedShot = RegisterPedheadshot_3(pedHandle);
const abortAt = GetGameTimer() + 100;
while (!IsPedheadshotReady(pedShot)) {
const gt = GetGameTimer();
if (gt > abortAt) {
// might get stuck if we don't stop the request
UnregisterPedheadshot(pedShot);
return false;
}
await Delay(0);
}
const txd: string = GetPedheadshotTxdString(pedShot);
await nuiHandler.sendNUIMessage({
app: "headshot",
method: "RegisterHeadshot",
data: txd
});
UnregisterPedheadshot(pedShot);
return true;
};
Ui:
useNuiEvent("headshot", "RegisterHeadshot", async (txd: string) => {
const imgFetch = await fetch(`https://nui-img/${txd}/${txd}`);
const imgBlob = await imgFetch.blob();
setImageBlob(imgBlob);
});
This isn't a bug, first if you aren't please make sure you wait for
IsPedheadshotReady(and possiblyHasStreamedPedAssetsLoaded) and make sure youUnregisterPedheadshotwhen you're finished with them
I have done the exact that, I have been taking the headshot and I have a wait to ensure the headshot is usable and ready, at the end I use Unregister, but still sometimes when I try to load my ID the photo is not there and it is stuck in an endless loop trying to wait for a usable headshot. I dont know if Understood that correct but if I put in the Stream files it might work better?
this is currently the code for the headshot
local function getPlayerHeadshot(playerSource)
local playerPed = GetPlayerPed(GetPlayerFromServerId(playerSource))
local handle, test = RegisterPedheadshotTransparent(playerPed)
local abortAt = GetGameTimer() + 100
-- print(1, handle, IsPedheadshotReady(handle), IsPedheadshotValid(handle))
while not IsPedheadshotReady(handle) do
local gt = GetGameTimer()
if gt > abortAt then
UnregisterPedheadshot(handle)
return false
end
Wait(0)
end
local txd = GetPedheadshotTxdString(handle)
UnregisterPedheadshot(handle)
return txd
end
function ClientShowCardEvent(data)
data.player.character.head = './images/person.png'
if data.player.character.head == './images/person.png' then
local headTxt = getPlayerHeadshot(data.playerSource)
if headTxt then
data.player.character.head = string.format("https://nui-img/%s/%s", headTxt, headTxt)
end
end
SetNuiFocus(true, true)
SendNUIMessage({
action = 'showCard',
data = data
})
SendNUIMessage({
action = 'setVisible',
data = true
})
end
Please refer to this part of my explanation:
RegisterPedheadshotTransparent only has 1 available texture slot to draw to (being pedmugshot_01.ytd), which is why this happens.
You can use RegisterPedheadshot_3 (aka REGISTER_PEDHEADSHOT_HIRES) with transparent textures streamed over the original slots and it will work the same as RegisterPedshotTransparent, you will only have 7 slots to work with at a time though.
If you try to request another ped headshot while one is still loading you'll get a weird lockup (I don't know how else to describe it) where the texture refuses to get released even though it was never properly loaded.
@AvarianKnight I've recently had to implement a new feature with ped headshots, i saw your message before and went ahead and used RegisterPedheadshot_3 along with streaming those textures so i can process multiple images.
The issue for me as of currently is the fact that these images come out with missing pixels.
Example:
My Implementation:
const handleHeadshot = async (): Promise<string | boolean> => {
const ped = PlayerPedId();
let pedShot = RegisterPedheadshot_3(ped);
const abortAt = GetGameTimer() + 100000;
while (!IsPedheadshotReady(pedShot)) {
const gt = GetGameTimer();
if (gt > abortAt) {
console.log('(handleHeadshot) Aborting request');
UnregisterPedheadshot(pedShot);
return false;
}
await sleep(0);
}
const txd: string = GetPedheadshotTxdString(pedShot);
console.log('(handleHeadshot) txd: ', txd);
return txd;
};
Front End:
const characterImage = useMemo(() => {
if (!character.texture) {
console.error('(CharacterCard) character.texture is null');
return '';
}
return `https://nui-img/${character.texture}/${character.texture}?v=${Date.now()}`;
}, [character.texture]);
<img
className={cn(
'w-full h-full transition-all group-focus:scale-110 group-hover:scale-110 object-contain',
isSelected && 'scale-110'
)}
src={characterImage}
alt={`Character Image`}
/>