Is GetIsDM Working Correctly?

Hey folks,

I’ve got a simple script to clean up enemies in areas when no players are present.
It works great aside from running when a DM is in the area.

Does anyone know why this fires when a DM is present?
It doesn’t execute when a player or possessed creature is in the area.

The 'else if’s are from tweaking things. I tried using || (or) operators and they didn’t work either.

According to discussions in the archives, functions in the form GetFirst / NextObjectInXXXX don’t return DMs.

The Lexicon mentions this known bug, but (wrongly??) asserts that DM-possessed creatures are excluded:

https://nwnlexicon.com/index.php?title=GetFirstObjectInArea

The workaround is to cycle through GetFirst / NextPC(), discovering if any are DMs using GetIsDM(), and, if so, whether they are in the current area.

Incidentally, GetIsPC() is supposed to detect DMs and DM-Possessed creatures, according to the Lexicon - but obviously not if GetFirst / LastObjectInArea doesn’t find them in the first place.

1 Like

Welcome. As a debug method, it always helps to relay objects scanned in that loop to your PC via SendMessageToPC. This way you can see who is included and who is omitted (and why).


@Proleric, confirmed. Bonus info (also missing from the Lexicon?):

  1. Get*ObjectInArea do not yield DMs but yield DM-possessed creatures
  2. GetNearestObject & GetNearestCreature - same as 1
  3. Get*PC yield DMs (well, PCs) but do not yield DM-possessed creatures
  4. Get*FactionMember(GetFirstPC(), TRUE) - same as 3
  5. Get*FactionMember(GetFirstPC(), FALSE) - same as 4

@CaidenTheStoic, this means that the optimal course of action would be:

  1. check if any PCs are in current area
  2. during 1, check if any DM is in an invalid area - that means it is possessing a creature (that might be here)
  3. if not 1 but 2, check all creatures in the area if any of them is DM-possessed

For 3 I’d go with this to iterate only creatures in the script:

int iObject = 0;
object oOrigin = GetFirstObjectInArea(oArea);
object oObject = GetNearestObject(OBJECT_TYPE_CREATURE, oOrigin, ++iObject);
2 Likes

I’ve run into the same issues before, so to simplify my overall life, though create a slightly more complicated setup initially, I set a variable on all of my players, including DMs, when they login (OnClientEnter).

    if (GetIsDM(oPC))
    {    
        AddListObject(OBJECT_SELF, oPC, DM_ROSTER, TRUE);
        SetLocalInt(oPC, IS_DM, TRUE);
    }
    else if (GetIsPC(oPC))
    {
        AddListObject(OBJECT_SELF, oPC, PLAYER_ROSTER, TRUE);
        SetLocalInt(oPC, IS_PC, TRUE);
    }

The AddListObject command just adds them to a pseudo-array that I can cycle instead of using the whole GetFirst/GetNextPC thing. The important part is the IS_PC and IS_DM portion. Additionally, OnAreaEnter for each area, I use this:

AddListObject(OBJECT_SELF, oPC, AREA_ROSTER, TRUE);

This creates a roster of all players, including DMs, in the area. So if I want to determine if the area is empty, instead of looping every object in the area, I simply call:

if (!CountObjectList(oArea, AREA_ROSTER))
{
    //Do stuff here.
}

If I want to cycle the players in the area and determine if one’s a DM, I can do this:

int i, nCount = CountObjectList(oArea, AREA_ROSTER);
for (i = 0; i < nCount; i++)
{
    oPC = GetListObject(oArea, i, AREA_ROSTER);
    if (_GetIsDM(oPC))
        // Do something here                
}

And, of course, that couples with my custom _GetIsDM and _GetIsPC functions:

int _GetIsDM(object oPC)
{
    return _GetLocalInt(oPC, IS_DM) || (GetIsDM(oPC) || GetIsDMPossessed(oPC));
}

int _GetIsPC(object oPC)
{
    return _GetLocalInt(oPC, IS_PC) || (GetIsPC(oPC) && !_GetIsDM(oPC));
}

Don’t worry about the _GetLocal*, those are also custom variable handling functions that are essentially equivalent to the organic GetLocal*. So, yeah, a much more complicated setup, but makes my life a bit easier later on when trying to figure out who is where and what they are. One of the nice bonuses of this setup is the ability to create an OnAreaEmpty event, which is essentially what you’re using (in this case, I start a timer that runs when the AREA_ROSTER is depeleted – this is in the global OnAreaExit):

if (!RemoveListObject(OBJECT_SELF, oPC, AREA_ROSTER) && ENABLE_ON_AREA_EMPTY_EVENT)
{
    int nTimerID = CreateTimer(OBJECT_SELF, AREA_EVENT_ON_EMPTY, ON_AREA_EMPTY_EVENT_DELAY, 1);
    StartTimer(nTimerID, FALSE);
}

When the timer expires, the cleanup (or other) functions run. If a new player enters the area before the time expires, the timer is destroyed. The IS_DM and IS_PC is a global function for me, so I can use it anywhere for just about anything, such as ensuring a specific item can only be used by a DM:

if (!_GetIsDM(oActivator))
....

Sorry for the long-winded post, just trying to explain all aspects of it. This is probably overkill for small projects, but is a life (and cycle) saver for me on my bigger projects. The nice thing about this setup, though, is it’s all generic and importable into a new module, so once it’s up and running once, you can use it for any project.

Of course, now that I think about all your “is this guy possessed stuff”, I probably need to consider a check for PC possession of their familiars in the _GetIsPC call. Bleh, more work!

3 Likes

These replies are wonderful.
Thank you folks so much.
I appreciate the insight.

I’ll tweak things and get it working.

Here is my updated code that works wonderfully.

If anyone is looking for a script to remove NPCs when an area is empty, feel free to use this!

I got stuck on this call for quite a while:
object oCreature = GetNearestCreature(4, 1, oOrigin, iCount);

Turns out it doesn’t accept -1, -1 as the first two values.
You have to set them.

2 Likes

I copied the wrong line when I was testing the few approaches. Yes, they need to be set. Sorry about that. But the same can be achieved with this:

oCreature = GetNearestObject(OBJECT_TYPE_CREATURE, oOrigin, ...)

It has no corner cases - can return oOrigin if it happens to be a creature.

1 Like

No worries
Your suggestion still helped very much.

Thanks for the followup!
You’re all wonderful for helping. :grinning:

1 Like

No problem. BTW, I can share my code too, if you wish to compare notes. I turned portion of the test script into a library function. It employs the bold line (2) from my first post. Just be careful - I reuse variables for different purposes.

CLICK TO SEE THE SCRIPT
int NWSH_ArePlayersInArea(object oArea=OBJECT_SELF)
{
    object oObject;
    object oOrigin;
    int iObject;

    oArea = GetArea(oArea);
    oObject = GetFirstPC();

    while(GetIsObjectValid(oObject))
    {
        oOrigin = GetArea(oObject);
        if(oOrigin == oArea)
        {
            return TRUE;
        }
        iObject |= !GetIsObjectValid(oOrigin);
        oObject = GetNextPC();
    }

    if(!iObject)
    {
        return FALSE;
    }

    oOrigin = GetFirstObjectInArea(oArea);
    oObject = oOrigin;

    do
    {
        if(GetIsDMPossessed(oObject))
        {
            return TRUE;
        }
        oObject = GetNearestObject(OBJECT_TYPE_CREATURE, oOrigin, iObject++);
    }
    while(GetIsObjectValid(oObject));

    return FALSE;
}

void main()
{
    SpeakString("ARE PLAYERS IN AREA (1/0)? " + IntToString(NWSH_ArePlayersInArea()));
}

You can also easily post script code here between [code] & [/code]. We have a unique NWScript syntax highlighting working on these forums :innocent:

2 Likes