Skip to content

CopyObject() used on traps results in a trigger object that cannot be detected by players

When I use CopyObject() on a trap-type trigger the resulting trap is normal (it can be triggered, etc) except that it cannot be detected. It can be force detected with SetTrapDetectedBy() but the Search skill seems to be utterly useless. Is this a bug? Is there any way to get around this? Can this be fixed?

Comments

  • ProlericProleric Member Posts: 1,316
    You could put the trap in the palette, then use CreateObject instead.

    As for CopyObject, which object is running the script? Did you set the owner and copy variables parameters? It sounds like a bug, but I just wonder... for example, if the module is implicitly setting the trap, or something like that...
  • n00bdragonn00bdragon Member Posts: 11
    It's the area that's running the script. What I have going on is an OnEnter script that copies all the parameters of traps in the area, deactivates them, then copies them and sets the copy up with the parameters of the original. That way the traps appear to "respawn" whenever players reenter the area. It works flawlessly except for the fact that the cloned traps are undetectable (they even properly show themselves as detectable when interrogated with scripting, but the Search mode fails to reveal them, as if the DC were infinity). As noted, the trap IS there. DMs can see it. Players in DebugMode can see it. Players can be forced to see it with SetTrapDetectedBy(), but they cannot naturally discover it via Search.

    I've been going round and round with this problem for days and peeling apart every bit of code I can to make absolutely sure it's a flaw with the game and not my code and all I can conclude is that it's something with the CopyObject() function that is not working either as intended or as I would expect. All I can think of is that the copied traps aren't getting populated into some list that Search is looking at or there's something improper about the way they are created that makes them not interact with that skill.
  • NeverwinterWightsNeverwinterWights Member Posts: 339
    Anything at all to do with SetTrapActive(), SetTrapDetectable(), or check with GetTrapActive(), GetTrapDectable()? Not sure what all you got going on with the scripts. Just figured I'd throw these out there.
  • n00bdragonn00bdragon Member Posts: 11
    I'm not sure if all of this is really helpful but here is the exact logic being used:

    Each area uses the same OnEnter script to dynamically spawn in NPCs and such when the first player enters and an OnExit script despawns the NPCs (but leaves the placeables and other things without heartbeats) when the last player leaves. Among the stuff that this script is doing is the following subroutine for traps, executed as it iterates through every object in the area (oObject):
        //For floor trigger traps, save them passively so that they can be reset
        //when players reenter the area
        if(GetIsTrapped(oObject) && nObjectType == OBJECT_TYPE_TRIGGER && !GetLocalInt(oObject, OBJECT_TRAP_IS_CLONE))
        {
            if(!GetLocalInt(oObject, OBJECT_TRAP_STORED))
            {
                StoreTrapFromObject(oObject);
                SetTrapDetectable(oObject, FALSE);
                SetTrapDisarmable(oObject, FALSE);
                SetTrapRecoverable(oObject, FALSE);
                SetTrapActive(oObject, FALSE);
            }
    
            if(!GetIsClonedTrapValid(oObject))
            {
                CloneTrapFromTrigger(oObject);
            }
        }
    

    Here you can see I execute the StoreTrapFromObject() function (which I'll detail below), saving all the properties of the trap onto it in variable form, then I neuter the trap. I don't want to delete it because when it is copied the copy will share its shape. Then I can reactivate the cloned version of the trap.

    Next, the script checks if the trap currently has a clone out there (so that we don't create another clone every time a player first enters). If there's no clone, then we spawn a new clone just like the original trap, otherwise we leave it be.

    Here are the called functions:
    void StoreTrapFromObject(object oTrapped)
    {
        int nTrapActive = GetTrapActive(oTrapped);
        int nTrapType = GetTrapBaseType(oTrapped);
        int nDetectable = GetTrapDetectable(oTrapped);
        int nDisarmable = GetTrapDisarmable(oTrapped);
        int nOneShot = GetTrapOneShot(oTrapped);
        int nRecoverable = GetTrapRecoverable(oTrapped);
        int nDetectDC = GetTrapDetectDC(oTrapped);
        int nDisarmDC = GetTrapDisarmDC(oTrapped);
    
        string sOnDisarmScript = GetLocalString(oTrapped, OBJECT_TRAP_DISARM_SCRIPT);
        string sOnTriggerScript = GetLocalString(oTrapped, OBJECT_TRAP_TRIGGER_SCRIPT);
    
        SetLocalInt(oTrapped, OBJECT_TRAP_ACTIVE, nTrapActive);
        SetLocalInt(oTrapped, OBJECT_TRAP_TYPE, nTrapType);
        SetLocalInt(oTrapped, OBJECT_TRAP_DETECTABLE, nDetectable);
        SetLocalInt(oTrapped, OBJECT_TRAP_DISARMABLE, nDisarmable);
        SetLocalInt(oTrapped, OBJECT_TRAP_ONE_SHOT, nOneShot);
        SetLocalInt(oTrapped, OBJECT_TRAP_RECOVERABLE, nRecoverable);
        SetLocalInt(oTrapped, OBJECT_TRAP_DETECT_DC, nDetectDC);
        SetLocalInt(oTrapped, OBJECT_TRAP_DISARM_DC, nDisarmDC);
    
        SetLocalInt(oTrapped, OBJECT_TRAP_STORED, TRUE);
    }
    

    Store every collectable property of a trap in variable form for later recovery and mark it so that you only do this once. This "parent" trap will only be used as a reference to clone "child" traps from, copying the parent's shape and then restoring all the relevant properties from.
    void CloneTrapFromTrigger(object oTrapped)
    {
        if(!GetLocalInt(oTrapped, OBJECT_TRAP_STORED))
            return;
    
        int nTrapActive = GetLocalInt(oTrapped, OBJECT_TRAP_ACTIVE);
        int nTrapType = GetLocalInt(oTrapped, OBJECT_TRAP_TYPE);
        int nDetectable = GetLocalInt(oTrapped, OBJECT_TRAP_DETECTABLE);
        int nDisarmable = GetLocalInt(oTrapped, OBJECT_TRAP_DISARMABLE);
        int nOneShot = GetLocalInt(oTrapped, OBJECT_TRAP_ONE_SHOT);
        int nRecoverable = GetLocalInt(oTrapped, OBJECT_TRAP_RECOVERABLE);
        int nDetectDC = GetLocalInt(oTrapped, OBJECT_TRAP_DETECT_DC);
        int nDisarmDC = GetLocalInt(oTrapped, OBJECT_TRAP_DISARM_DC);
    
        string sOnDisarmScript = GetLocalString(oTrapped,OBJECT_TRAP_DISARM_SCRIPT);
        string sOnTriggerScript = GetLocalString(oTrapped, OBJECT_TRAP_TRIGGER_SCRIPT);
    
        object oClone = GetLocalObject(oTrapped, OBJECT_TRAP_CLONE);
        vector vZeroVector = Vector(0.0, 0.0, 0.0);
        if(!GetIsClonedTrapValid(oTrapped))
        {
            if(GetIsObjectValid(oClone))
                DestroyObject(oClone);
            oClone = CopyObject(oTrapped, GetLocation(oTrapped)); //Create a new trap trigger
            SetLocalObject(oTrapped, OBJECT_TRAP_CLONE, oClone);
            SetLocalObject(oClone, OBJECT_MY_PARENT, oTrapped);
        }
    
        SetTrapOneShot(oClone, nOneShot);
        SetTrapRecoverable(oClone, nRecoverable);
        SetTrapDetectDC(oClone, nDetectDC);
        SetTrapDisarmDC(oClone, nDisarmDC);
        SetTrapDetectable(oClone, nDetectable);
        SetTrapDisarmable(oClone, nDisarmable);
        SetTrapActive(oClone, nTrapActive);
        SetLocalInt(oClone, OBJECT_TRAP_IS_CLONE, TRUE);
    }
    

    This function retrieves all the properties from the "parent" trap, creates a clone, makes the parent and child aware of each other and marks the child as a clone, then assigns all the saved properties to the child. It's important to note that SetTrapDetectable() works just fine here. The child trap is correctly flagged as detectable and this is provable by interrogating the child trap with a script. It's just that the Search skill fails to find it, ever. This is why I believe it's a bug.
    int GetIsClonedTrapValid(object oSource)
    {
        object oClone = GetLocalObject(oSource, OBJECT_TRAP_CLONE);
        if(GetIsObjectValid(oClone))
        {
            if(GetPositionFromLocation(GetLocation(oClone)) == Vector(0.0, 0.0, 0.0))
            {
                return FALSE;
            }else{
                return TRUE;
            }
        }
        return FALSE;
    }
    

    Lastly is just a function to identify a valid deactivated trap from the soft-delete removal that happens to traps which have been sprung (they can still be found by reference, but their positions are set to a null vector and they lose all properties, it's weird).
  • NeverwinterWightsNeverwinterWights Member Posts: 339
    edited March 2022
    Yeah it looks like you have all your ducks in a row. It's like the trap is there but no actual dimensional trigger is drawn where there should be one. One difference in my finding is that I can't see it in debug mode (I didn't check with a DM). If you use a spell like Find Traps it still creates the visual effect at the new traps location too. Interesting.
Sign In or Register to comment.