Allow StateBagChangeHandlers to be a partial name

It would be useful if you could do ‘entity:’ instead of having to specify the entity:35565 this is mainly useful for certain state bags we have that have to do separate logic on localEntity and entity

Do you have a more specific example here?

bagName: The internal bag ID for the state bag which changed. This is usually player:Source, entity:NetID or localEntity:Handle.

bagName is used to filter what triggers the handler, so I guess the idea being just a match for the bag “type”. So you can setup a single statebag to handle all local entities (but not networked entities/players), without needing to use some specific key like setLocalEntityProperties.

I was rather asking, again, for a specific example, not an extrapolation from what is already said. This sounds like it’d unnecessarily complicate host code so there might be a cleaner way but without the actual use case being stated that’s a bit difficult.

I’ll start off with how I have it written currently

const getNumberFromBagName = (type: string, bagName: string) => parseInt(bagName.replace(type, ''));

AddStateBagChangeHandler("blockEvents", "", async (bagName: string, key: string, value: boolean) => {
    let entity: number;
	// Check if this is a networked entity
    if (bagName.startsWith("entity:")) {
        const entityNet = getNumberFromBagName("entity:", bagName);
        const timeout = GetGameTimer() + 2500
		// We're on recommended which doesn't have the fix for this
        while (!NetworkDoesEntityExistWithNetworkId(entityNet)) {
            if (timeout < GetGameTimer()) return console.log("entity doesn't exist")
            await Delay(0);
        }
        entity = NetworkGetEntityFromNetworkId(entityNet)
        await waitForCollision(entity);
	// The only only other time this should happen is for a localEntity
    } else {
        entity = getNumberFromBagName('localEntity:', bagName); 
        await waitForCollision(entity);
    }
    SetEntityInvincible(entity, value)
    FreezeEntityPosition(entity, value)
    TaskSetBlockingOfNonTemporaryEvents(entity, value)
})

How it would be written with allowing the bagName to be a partial name

const handleBlockEvent = (entity: number, value: boolean) => {
    SetEntityInvincible(entity, value)
    FreezeEntityPosition(entity, value)
    TaskSetBlockingOfNonTemporaryEvents(entity, value)
}

const getNumberFromBagName = (type: string, bagName: string) => parseInt(bagName.replace(type, ''));

AddStateBagChangeHandler("blockEvents", "entity:", async (bagName: string, key: string, value: boolean) => {
	const netId = getNumberFromBagName("entity:", bagName);
	const timeout = GetGameTimer() + 2500
	// We're on recommended which doesn't have the fix for this
	while (!NetworkDoesEntityExistWithNetworkId(netId)) {
		if (timeout < GetGameTimer()) return console.log("entity doesn't exist")
		await Delay(0);
	}
	const entity = NetworkGetEntityFromNetworkId(netId)
	await waitForCollision(entity);

	handleBlockEvent(entity, value);
})


AddStateBagChangeHandler("blockEvents", "localEntity:", async (bagName: string, key: string, value: boolean) => {
	const entity = getNumberFromBagName("localEntity:", bagName);
	await waitForCollision(entity);

	handleBlockEvent(entity, value);
})

… right, so the use case is actually somewhat mixed here:

  1. as a workaround for an unrelated bug
  2. as a replacement for a ‘state bag name to entity handle’ helper (and, I guess, conversely, the reverse?)

Both, as I suspected, are better solved by… fixing the bug, testing the bug fix, and having a helper to get an entity from a name, than by adding code to handle partial string matches especially as there aren’t really any other obvious use cases for partial matches other than discriminating by type, and in this case there’s a code path for both anyway.

(in fact, a helper could still have unified code for the bug workaround, as one would just have a unified loop waiting for the helper to return a non-zero value)

Here’s actually an example of how it could look with the hypothetical built-in helper:

assuming bug is fixed

AddStateBagChangeHandler("blockEvents", null, async (bagName: string, key: string, value: boolean) => {
    const entity = HypotheticalGetEntityFromStateBagName(bagName);
    await waitForCollision(entity);
    SetEntityInvincible(entity, value);
    FreezeEntityPosition(entity, value);
    TaskSetBlockingOfNonTemporaryEvents(entity, value);
});

assuming bug is not fixed

AddStateBagChangeHandler("blockEvents", null, async (bagName: string, key: string, value: boolean) => {
    let entity = HypotheticalGetEntityFromStateBagName(bagName);

    // this loop could of course be made into a shared function if having many handlers
    const timeout = GetGameTimer() + 2500;
    while (entity === 0) {
        if (timeout < GetGameTimer()) return console.log("entity doesn't exist")
        await Delay(0);
        entity = HypotheticalGetEntityFromStateBagName(bagName);
    }

    await waitForCollision(entity);
    SetEntityInvincible(entity, value);
    FreezeEntityPosition(entity, value);
    TaskSetBlockingOfNonTemporaryEvents(entity, value);
});

The bug can be ignored, its already fixed in newer versions iirc

How would this work if it was a player, would it just return the players ped? Nevermind I see it would return 0 if it doesn’t exist

Good point- that’s also a state type separate from the entity itself. I’d say this should just return 0 as it’s not an entity (or error/warn?) as otherwise expectations could be weird (round-tripping Entity(...).state.x would be wrong).

(similarly, a future ‘global player awareness’ mode indeed would sync selected state assigned to the player, but not the entity, yeah)

I think your right, it would probably be best to just return 0 and let the player choose how to handle there being no entity

aside: yeah I do agree that players should choose how scripts work /s

Should I PR something for this, or is it something you want a Collective member to work on?

Perhaps, but this would likely make most sense in native platform code and I’m not sure if there’s a place that has the required functions for this.

I have a basic version of it, but I don’t think the natives are in the proper files, I’ll open a PR

Added in feat: add GetEntityFromStateBagName helper by AvarianKnight · Pull Request #1736 · citizenfx/fivem · GitHub