WARNING: Using GetObjectByTag With "ITEM" Objects

Hi All,

I encountered an issue while building my second module, which raised a concern about the GetObjectByTag function that I now bring to your attention. First I will give a brief overview and then a quick “flowchart” to see if it might affect you … Info believed to be correct at time of writing.

  • A) ITEMS ONLY: In my experience, the problem encountered only arises when using GetObjectByTag with ITEM objects. This appears to be mainly due to items either coming into a module (with imported PCs) or waiting to enter (on creature encounters) or having been destroyed (by builders removing unwanted items from entering PCs). I have not had an issue using the function with any other object type.

  • B) RESULTS VARY: Subject to steps taken, the issue the function has with items will range from negligible for some modules to potentially game-breaking for others. How much the problem affects you depends upon how you deal with items in general.

THREE POTENTIAL ISSUES

  1. REMOVING ITEMS FROM ENTERING PCS: This is the most likely means of you introducing the problem. If you are a builder who removes items from a PC upon them entering your module, or NEED to remove some items to avoid item duplication, then removing items will lead you to a problem …
  • THE PROBLEM: Assuming you strip all items from the PC at this stage, then any item you have removed (and destroyed), using GetObjectByTag will still return them as a valid object.

  • THE SOLUTION: Just before you destroy the items on the PC, change the item’s TAG to something you will not refer to. (Thanks to KevL for this quick solution.) This helps avoid the FALSE return of an item that is no longer actually present in the game, as it no longer responds to the original tag.

  • NB: There is another solution, which we will actually describe in a moment that requires saving and reloading the game. This has the affect of seeming to clear down the “FALSE” items.

  1. ITEMS LEFT WITH IMPORTED PCS: This is the second most likely reason you will encounter the problem. If you are a builder who leaves all or any items on a PC who enters your module …
  • THE PROBLEM: Assuming you remove only some (or no) items from the PC at this stage, then not only will you have the issue above for said items (unless you did the change tag trick), but you will also be left with the next issue, which is that any of those imported items left on the PC, when later used (and destroyed), will also still remain valid with GetObjectByTag. E.g. The PC imports with a Barkskin potion, which the builder allows to remain on the PC. The PC then uses the potion. If the player has not yet saved and reloaded their game since the initial start, then using GetObjectByTag will still return the Barkskin potion as a valid object even though it has been used. So if the builder is trying to check if the PC has a Barkskin it will return TRUE, even if it has been used! NB: This problem goes away if the player makes a SAVE and RELOAD prior to the function being used.

  • THE SOLUTION: This version of the problem is slightly more serious, as a builder may well make reference to items the PC has on them at some time using the GetObjectBytag function. The only solution I have found is to either recommend in your module notes for the player to SAVE and RELOAD after all PCs have entered the module (or whenever they import a PC into the module), or, as I have done, force the player to SAVE and RELOAD via scripting. This event only triggers when a player imports a PC that carries items with them. A gentle reminder GUI fires within a heartbeat requesting the player to save and reload their game. The save/load menu then persists until the action has been done by the player.

  1. ITEMS ON ENCOUNTER CREATURES: This is the least likely problem you will face, but is worth considering if you change item properties on a module wide scale. I have only two scripts in my campaign where I had to edit the code to accommodate this next problem …
  • THE PROBLEM: Items held in a creatures inventory (blueprint), which are then associated with an encounter trigger will return as valid objects, but do not return any valid location, nor any valid owner. E.g. A bugbear blueprint has a plot item with “PLOT_ITEM_A” in their inventory. This creature is then associated with an encounter trigger placed in the module. Even if this trigger is never triggered, doing a GetObjectByTag for “PLOT_ITEM_A” will return valid. With the caveat that neither a valid location nor valid creature ownership for said item will be returned. However, if you were just testing to see if this plot item was in the game using this function, you may be misled into believing the item is present, even though it is actually just hanging around on a creature that may be spawned from an encounter subject to the encounter settings.

  • THE SOLUTION: Double check your scripts where GetObjectByTag is used to ensure it is not looking for an item that may potentially be sitting on a pending encounter creature. You can do so by testing for both a valid location and valid ownership of said item.

FUNCTION CHECK FLOWCHART

This is just a quick flowchart test to see if you need to take any action … It makes the assumption that you do have some usage of GetObjectByTag somewhere in your scripting.

Start at Q1:

Q1: Do you allow imported PCs? YES>Goto Q2. NO>Goto Q3.
Q2: Do you strip all, some, or no items from them? ALL>Goto A1. SOME>Goto A2. NONE>Goto A2.
Q3: Do you use encounter triggers? YES>Goto Q4. NO>Goto Q5.
Q4: Do any spawned creatures carry any plot items? YES>Goto A3. NO>Goto Q5.
Q5: Did you say you allowed imported PCs? YES>Goto A5. NO>A4.

A1: You will need to change the TAG of all destroyed items at time of removal. (Goto Q3.)
A2: You will need to inform (or force) your players to save and reload after importing any PCs. (Goto Q3.)
A3: You need to decide if GetObjectByTag is supposed to reference the item potentially here too. (END)
A4: You do not need to take any action at all. (END)
A5: Deal with imported PCs as mentioned. Unlikely any further action with other scripting required. (END)

As far as I know, everything reported here is true, but welcome feedback that may conform or deny these results.

My thanks goes to @kevL_s for his continued patience and help in tracking down these issues.

3 Likes

When are you calling GetObjectByTag()? DestroyObject() runs after the script ends. So if you destroy all the items a PC has in the OnClientEnter script, any GetObjectByTag() of said items will still be valid until the script ends.

The function is being called well after the the script that destroys the objects. You can even leave an area and the object remain “valid” as far as the function is concerned. A simple heart beat check for the TAG item shows it as valid throughout, until a save and reload.

Um, this is certainly interesting, but beyond the theoretical aspect, does this really have any practical relevance?

When you look for items possessed by characters you should use GetFirstItemInInventory and GetNextItemInInventory or GetItemPossessedBy or GetItemInSlot, not GetObjectByTag.

1 Like

As I say in point B, maybe not as much for some people, but it may do for others.

It’s a case of being made aware of the potential issue as much as anything else. Personally, I’d rather play it safe and be sure items return as expected, even from the start. E.g. I had such an issue that highlighted the problem for me when sorting code for the lobby of module 2 of my campaign. I did work around the issue a different way to begin with, but then reconsidered it easier to push for a save and reload rather than risk a player leaving it for a while and me having to consider it again.

Bottom line, it is a “rare” problem, more likely to affect PCs being imported from module to module. It’s something I’d like to be made aware of in this case - and so thought others may like to know too. :sunglasses:

i agree. I’ve ‘known’ that GetObjectByTag() can fail unexpectedly for years – never had a clue why though. So i just don’t use it …

I now believe the fundamental reason is that it erroneously checks for tags of items on cached Encounter-creature objects/items.

(and yes it might be searching creature-objects etc. Not only items – it wasn’t ruled out)

 
Example debug (credit @Lance_Botelle)

#0 #2 #3 potions are not genuinely extant in the module

and the code
// 'test_tag'
/*
	Console script. Searches through the currently loaded Module for items found
	by GetObjectByTag() and prints debug-to-chat.

	sTag - pass in a tag at the console

	eg.
	rs test_tag("tag")
*/

// prints to chat
void debug(string tell)
{
	SendMessageToPC(GetFirstPC(FALSE), tell);
}

// Gets area and position as a string.
string GetLocationString(location loc)
{
	string info;

	object oArea = GetAreaFromLocation(loc);
	info += GetName(oArea) + " ( " + GetTag(oArea) + " )";

	vector pos = GetPositionFromLocation(loc);
	info += "\n" + FloatToString(pos.x, 18,2) + ","
				 + FloatToString(pos.y, 18,2) + ","
				 + FloatToString(pos.z, 18,2);

	return info;
}

// Main
void main(string sTag)
{
	debug("\ntest_tag : sTag= " + sTag);

	int i = 0;

	object o = GetObjectByTag(sTag, i);
	while (GetIsObjectValid(o))
	{
		debug(IntToString(i));

		debug(GetName(o) + " ( " + GetTag(o) + " )");

		location loc = GetLocation(o);
		debug(GetLocationString(loc));

		if (!GetIsLocationValid(loc))
			debug(". location is invalid/unwalkable");

		string info;

		o = GetItemPossessor(o);
		if (GetIsObjectValid(o))
		{
			info = GetName(o) + " ( " + GetTag(o) + " )";
			loc = GetLocation(o);
		}
		else
			info = "OBJECT_INVALID";

		debug(". possessor= " + info);

		if (GetIsObjectValid(o))
		{
			debug(GetLocationString(loc));

			if (!GetIsLocationValid(loc))
				debug(". . location is invalid/unwalkable");
		}

		o = GetObjectByTag(sTag, ++i);
	}
}

 
ps. also bewary of the nwn2 Limbo feature … SendCreatureToLimbo() etc. /untested

2 Likes

one last thing is concerning. I hope Lance doesn’t mind me quoting his message here …

I’m not sure if that relates to the Encounters bug … or if it’s independant of enc’s

1 Like

@kevL_s,

The “rogue” items on pending encounters remain such even after save and reloads.

This just has to be considered if we use the function to search for such tagged items in the module.

2 Likes

Hello here.

To complete what Lance says.

GetObjectByTag is a function I use really really a lot, it works perfectly, and always gives good result.

Sowhen everything appears to be right and it’s not working, it’s not at GetObjectByTag you need to look at, but at the object.

First problem is what Lance is saying: The object doesn’t exist anymore, if it doesn t exit anymore you can’ t access it, be sure it’s here.

Second common problem : Several object with the same tag exist, GetObjectByTag will give you the first one created unless if you give it the number of object created you wish. It can be tricky, and I don’t remember if the first is referenced at 0 or 1 be carefull to test it if you use it.

for exemple: GetObjectByTag(“mytag”,1);

So if you don’t get the object you wished perhaps it’s beceause you have several object with the same tag, and the one returned isn’ t the one with the right index. I think I got that way of doing thing works correctly only once, I didn’t master the index made by the game, things are eluding me in its creation by the game. So for those case I prefer to use the GetNearestObjectByTag function to be sure to get the one I get by using a nearby Object of the Object I want.

Third case I encountered where it did’nt work:
The vicious case. IF it doesn’t work it’s not the function, it’s the TAG ! Some object loose their tags after a SAVE and LOAD. I save the game, close the game, Load again where I was, and a magic NWN2 bugs commes, somme type of objects have lost their tags. All their tags have becomme blank. One of the object I nailed with this problem are lights. All lights type object loose their tag when you load from a save, but upon the cration of the area the first time before a save the tag is correctly in game.

In order to access them I use SetLocalObject and GetLocalObject. Save your object with the Set and get it after with the Get one. This way still work after a load from a save, it’s more work and require more attention to set up, I only use it for type of object with tag issue on Save/load operation I nailed.

2 Likes

I haven’t read the whole discussion, because I don’t understand everything in another language.
But…
Items tend to be almost always 99% in the inventory of a creature or a placeable, so I would NEVER use GetoObjectByTag (), but a loop of GetFirstItemInInventory () or even better GetItemPossessedBy ().