Closed BhaalM closed 3 years ago
I've been talking with Shadooow on discord, here is what he remembers from the bug he posted in the old redmine bug tracker (All credits to him):
I'm experiencing the same issue with GetNearestObjectByTag() and wanted to share what I have found through testing in hopes it will contribute to finding a solution.
I have been able to repro the issue consistently with the following scenario:
int IXP_GetIsAssociate(object oCreature);
void DropChance(object oPC);
void SlipForward(object oPC);
void SlipBack(object oPC);
void BladeDamage(object oPC);
void main()
{
object oPC = GetEnteringObject();
object oArea = GetArea(oPC);
object oTrigger = GetNearestObjectByTag("I_SIC_BLADE_LOCATION", oPC, 1);
location lTrigger = GetLocation(oTrigger);
int nActivated = GetLocalInt(OBJECT_SELF, "ACTIVATED");
// Exit out of script if a DM.
if (GetIsDM(oPC))
return;
// Entering object must be a PC or an associate
if (!GetIsPC(oPC) & !IXP_GetIsAssociate(oPC))
return;
// Ghosts should not be counted.
if (GetLocalInt(oPC, "X2_L_IS_INCORPOREAL") == 1)
return;
// First check to see if the mechanism had been shut off.
if (GetLocalInt(oArea, "BLADEOFF"))
return;
if (!nActivated)
SetLocalInt(OBJECT_SELF, "ACTIVATED", 1);
if (!nActivated)
{
effect eBlades = EffectVisualEffect(VFX_FNF_SWINGING_BLADE);
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eBlades, lTrigger, 5.0f);
DelayCommand(5.0, SetLocalInt(OBJECT_SELF, "ACTIVATED", 2));
DelayCommand(15.0f, DeleteLocalInt(OBJECT_SELF, "ACTIVATED"));
}
if (nActivated != 2)
{
object oVictim = GetFirstInPersistentObject(OBJECT_SELF, OBJECT_TYPE_CREATURE);
while (GetIsObjectValid(oVictim) && !GetLocalInt(oVictim, "X2_L_IS_INCORPOREAL"))
{
if (GetLocalInt(oVictim, "HITBYBLADES") != 1)
{
SetLocalInt(oVictim, "HITBYBLADES", 1);
DelayCommand(0.5f, AssignCommand(oTrigger, BladeDamage(oVictim)));
}
oVictim = GetNextInPersistentObject(OBJECT_SELF, OBJECT_TYPE_CREATURE);
}
}
}
// Generates a location appropriate for an item that has dropped to the ground
// from an NPC's hand. Taken and modified from hc_inc_npccorpse (HCR 3.02 - Sunjammer et all)
// - oPC: the player character
// - nSlot: one of the INVENTORY_SLOT_*HAND constants
location GetLocationForDropped(object oPC, int nSlot)
{
vector vBody = GetPosition(oPC);
// get the offsets appropriate to the hand the item is falling from
float fFacing = (nSlot == INVENTORY_SLOT_LEFTHAND) ? 45.0f : -45.0f;
float fWeapon = (nSlot == INVENTORY_SLOT_LEFTHAND) ? -20.0f : 20.0f;
// add a random element to facings and direction, values are arbitray
fFacing += GetFacing(oPC) + IntToFloat(d20());
fWeapon += GetFacing(oPC) - IntToFloat(d20(2));
float fDistance = 0.5f + (IntToFloat(d10())/10);
// get co-ordinates of a point fDistance meters away at fFacing degrees
float fX = vBody.x + cos(fFacing) * fDistance;
float fY = vBody.y + sin(fFacing) * fDistance;
// return the location
return Location(GetArea(oPC), Vector(fX, fY, vBody.z), fWeapon);
}
// Determines if oCreature is an associate.
int IXP_GetIsAssociate(object oCreature)
{
if (GetAssociateType(oCreature) == ASSOCIATE_TYPE_NONE) return FALSE;
return TRUE;
}
// DropChance() runs a check to see if the character drops the objects in his hands.
void DropChance(object oPC)
{
int nChance = 50; // <-- Set percentage chance the character will drop an item here.
// First check to see if the character has something in his right hand.
if (GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC) != OBJECT_INVALID)
{
// Roll for righthand first.
int nRoll = d100(1);
if (nRoll <= nChance)
{
int nSlot = INVENTORY_SLOT_RIGHTHAND;
object oItem = GetItemInSlot(nSlot, oPC);
CopyObject(oItem, GetLocationForDropped(oPC, nSlot));
DestroyObject(oItem);
}
}
// Check the character's left hand.
if (GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oPC) != OBJECT_INVALID)
{
// Roll for lefthand.
int nRoll = d100(1);
if (nRoll <= nChance)
{
int nSlot = INVENTORY_SLOT_LEFTHAND;
object oItem = GetItemInSlot(nSlot, oPC);
CopyObject(oItem, GetLocationForDropped(oPC, nSlot));
DestroyObject(oItem);
}
}
}
// SlipForward causes all actions to be cleared, forces the character to fall forward,
// and checks to see if the character drops any items in his hands.
void SlipForward(object oPC)
{
AssignCommand(oPC, ClearAllActions());
AssignCommand(oPC, ActionPlayAnimation(ANIMATION_LOOPING_DEAD_FRONT, 1.0f, 4.2f));
DropChance(oPC);
// Disable the character's action cue so he cannot interrupt the animation.
DelayCommand(0.2, SetCommandable(FALSE, oPC));
// Enable the character's action cue after the animation finishes.
DelayCommand(6.4, SetCommandable(TRUE, oPC));
}
// SlipBack causes all actions to be cleared, forces the character to fall backward,
// and checks to see if the character drops any items in his hands.
void SlipBack(object oPC)
{
AssignCommand(oPC, ClearAllActions());
AssignCommand(oPC, ActionPlayAnimation(ANIMATION_LOOPING_DEAD_BACK, 1.0f, 4.2f));
DropChance(oPC);
// Disable the character's action cue so he cannot interrupt the animation.
DelayCommand(0.2, SetCommandable(FALSE, oPC));
// Enable the character's action cue after the animation finishes.
DelayCommand(6.4, SetCommandable(TRUE, oPC));
}
void BladeDamage(object oPC)
{
int nSaveDC = 20;
int nSave = ReflexSave(oPC, 20, SAVING_THROW_TYPE_NONE);
int nDamage = d6(7)+5;
if (nSave == 1)
nDamage = nDamage/2;
int nVoice;
switch (d3(1))
{
case 1: nVoice = VOICE_CHAT_PAIN1; break;
case 2: nVoice = VOICE_CHAT_PAIN2; break;
case 3: nVoice = VOICE_CHAT_PAIN3; break;
}
effect eDamage = EffectDamage(nDamage, DAMAGE_TYPE_SLASHING);
effect eVis = EffectVisualEffect(VFX_COM_CHUNK_RED_SMALL);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oPC);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oPC);
PlayVoiceChat(nVoice, oPC);
switch (d2(1))
{
case 1:
SlipForward(oPC); break;
case 2:
SlipBack(oPC); break;
}
DelayCommand(6.7f, DeleteLocalInt(oPC, "HITBYBLADES"));
}
If you were to enter the first trigger, then re-enter it without entering the second trigger, the script and damage are applied as expected. It is only when the damage command is passed to the waypoint that first trigger stops applying damage.
If you were to remove the waypoint and replace it with another invisible object with the same tag, the script will execute as expected consistently regardless of which trigger you enter.
Based on Shad000w repro module I made a very simple one.
1-Enter the module and activate the lever. Don't move!! 2-Wait
The Lever script is:
void GetMyDoor()
{
int n = GetLocalInt(OBJECT_SELF,"ATTEMPTS")+1;
SetLocalInt(OBJECT_SELF,"ATTEMPTS",n);
object oTarget = GetNearestObjectByTag("DVERE"); //Tag of the door
if(oTarget == OBJECT_INVALID)
{
SendMessageToPC(GetFirstPC(),"GetNearestObjectByTag didn't find door! number of loops: "+IntToString(n));
}
DelayCommand(0.1,GetMyDoor());
}
void main()
{
SendMessageToPC(GetFirstPC(), "Activated");
GetMyDoor();
}
Daaz created a tweak for NWNX. Seems to work. https://github.com/nwnxee/unified/pull/1381
For your information: the previous tweak also solve the GetFirst/GetNextObjectInShape bug with moving AoEs (the function returns OBJECT_INVALID even if there is clearly a creature in range)
Repro module for GetFirst/GetNextObjectInShape bug;
1-Click on the lever (don't move) 2-Wait
Maybe related with #66
To Reproduce
Specifics
If needed, describe the bug
GetNearestObjectByTag(string sTag, object oTarget=OBJECT_SELF, int nNth=1) "randomly" returns OBJECT_INVALID even if an object with the tag sTag exists near oTarget.
When I say randomly I mean per server reset: After resetting the server, if GetNearestObjectByTag fails it will always returns OBJECT_INVALID until reset. If it works well it will always returns the correct object.
I think this bug was reported by Shadooow a long time ago in the old Redmine bug tracker. There is also a fix from Shadooow. The following one is based on his fix, but I modified it a bit:
Every now and then my log is filled with messages like:
ERROR: GetNearestObjectByTag(ad_carav_noinus_store) returned OBJECT_INVALID, workaround returned 367b
(Obviously with different tags)