Skip to content

PluginID - #577

Open
IIIaKa wants to merge 1 commit into
OxideMod:developfrom
IIIaKa:PluginID
Open

PluginID#577
IIIaKa wants to merge 1 commit into
OxideMod:developfrom
IIIaKa:PluginID

Conversation

@IIIaKa

@IIIaKa IIIaKa commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

I propose adding a ulong ModificationID property to BaseEntity(the name can be changed) as a replacement for the skinID.

There are many plugins that modify or completely replace the behavior of different entities(NPCs, turrets, containers, vehicles, etc).
Many plugins used skinID to identify entities created or modified by plugins. Since skinID was not widely used by vanilla entities at the time, it also provided a simple way for other plugins to determine whether an entity was vanilla or controlled/modified by plugin.

if (entity.skinID != 0uL)
{
    //Modified entity
}

However, this approach is becoming increasingly unreliable. With more and more vanilla entities supporting skins, skinID can no longer be safely used as a plugin-specific identifier.

That's why I propose adding a ulong ModificationID property to BaseEntity:

  • ModificationID == 0uL - vanilla entity
  • ModificationID != 0uL - entity created or modified by a plugin

Why is this useful?
Consider a plugin that modifies NPC targeting:

object OnNpcTarget(global::HumanNPC npc, BasePlayer target)
{
    return YOUR_CONDITIONS(target) ? true : null;//true(not null) => cancelation
}

In these conditions, the plugin takes control of all NPCs, including those created by other plugins, such as NPCs used for raids, events, custom events, etc, which can potentially lead to conflicts between plugins.

Without a common identifier, the plugin would need to explicitly check against every other plugin that creates custom NPCs:

object OnNpcTarget(global::HumanNPC npc, BasePlayer target)
{
    if (!IS_PLUGIN1_ENTITY(npc) &&
        !IS_PLUGIN2_ENTITY(npc) &&
        !IS_PLUGIN3_ENTITY(npc) &&
        ETC)
    {
        return YOUR_CONDITIONS(target) ? true : null;
    }

    return null;
}

This has several problems:

  • Such checks can become expensive when performed in frequently called hooks, such as NPC/Trap targeting.
  • The plugin needs to know about every other plugin that creates custom entities.
  • Every new plugin may require additional compatibility code.
  • There is no standardized way for plugins to communicate ownership of an entity.
  • Etc.

With ModificationID, the same logic becomes much simpler:

object OnNpcTarget(global::HumanNPC npc, BasePlayer target)
{
    if (npc.ModificationID == 0uL)
    {
        return YOUR_CONDITIONS(target) ? true : null;
    }

    return null;
}

This provides a cheap and generic way to ignore all modified entities.

It can also be used to process only entities belonging to a specific plugin:

object OnNpcTarget(global::HumanNPC npc, BasePlayer target)
{
    if (npc.ModificationID == YOUR_MODIFICATION_ID)
    {
        return YOUR_CONDITIONS(target) ? true : null;
    }

    return null;
}

The main advantage is that this is a simple value comparison entity.ModificationID == 0uL.
There is no need to query other plugins, perform dictionary lookups, or maintain compatibility lists for every plugin that creates custom entities.

The memory overhead is also minimal, a ulong requires only 8 bytes per entity. Even with 100,000 entities, this would require only ~0.76MB of memory, which is a much better trade-off than adding extra CPU overhead.
This can be especially useful in frequently executed hooks where even small amounts of unnecessary work can accumulate.

I also understand that this could be considered a somewhat "hacky" solution, but currently there does not appear to be a generic, lightweight mechanism that allows plugins to identify ownership or modification of an entity without maintaining plugin-specific compatibility logic.
If we reject every solution simply because it could be considered "hacky", we could keep searching for a perfect solution until the game itself is no longer around. This is similar to the issue with conflicts between return types in hooks, where the solution may also require accepting a practical compromise rather than waiting for a perfect, generic solution.

I understand that there is a possibility of plugin IDs overlapping, but I don't think this is as much of a problem as having to add numerous checks such as IS_PLUGIN2_ENTITY.
In the end, plugin authors can coordinate and choose different values if necessary. After all, the ulong range is 0 - 18,446,744,073,709,551,615, so there is an enormous number of possible values.

I am open to suggestions regarding the property name, semantics, or a better implementation approach.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant