Jump to content

Devious Devices NG


Recommended Posts

Posted (edited)
39 minutes ago, HereBeTentacles said:

 

It still works that way, that's why it would need to be done through some sort of magic effect. Anything you do to the player speedmult directly is likely to get reset at some point. If you do it through a magic effect or some other indirect method it should be fine. Plus if you do it that way you can condition the effect to only apply when the player is wearing something with the petssuit keyword. Less scripting that way depending on what you are doing.

 

I have a similar sort of thing in my mod where I overencumber the player because I don't want them running/sprinting, but that leaves them too slow for my liking so I add an ability that ups their speedmult a bit to make up for that. The same idea could work for your mod.

Well, to back up a bit, I already handled the pet suit's speed debuff for DD 5.2 by temporarily changing the value for the hobble dress speed debuff (which is used for the pet suit enchantment's speed debuff).   That value came the MCM and it's stored as a script property, easy to access.  That works great, because DD's own logic maintains the desired speed so that it's not too slow.  Fire & forget, DD handles it.

 

However, DD NG removed that logic from zadx_HobbleSkirtEffectScript.  The speed value seems to come from afMaxSpeedMult in DeviousDevices.ini, so my assumption (always risky, those) is that the periodic updates moved into the dll.

 

That's an unfortunate loss of accessibility, because the ini setting is not exposed to a script.

 

Now, I'm confused.  I might have misunderstood, but I thought you had suggested adding a speedmult modifier to counter the pet suit's debuff, but you also told me that DD NG still periodically adjusts the speed of the pet suit's wearer.  That's what DD 5.2 does; every 5 seconds, it compares the current speedmult actor value to a target speed and adjusts the debuff to make the current speed comply.  So, if it still works the same way with periodic updates, my adding an increase to speed through a magic effect will just get countered by DD NG a few seconds later.  

 

I definitely don't want to add a periodic update of my own to fight with DD NG's.  That is why I had asked how to access DD NG's speed debug value from a script, so I can temporarily adjust it for my event and then restore the original value afterwards.

 

So, what am I missing?

Edited by Hex Bolt
Posted (edited)
50 minutes ago, Hex Bolt said:

Now, I'm confused.  I might have misunderstood, but I thought you had suggested adding a speedmult modifier to counter the pet suit's debuff

 

Little misunderstanding, this is not what I am suggesting or what would happen, think of it more like ad

 

DD is adjusting the players base speedmult via script and then the speedmult magic effect I am suggesting is just getting added to it at a later step. Like how right now if I set my charcters health from 130 to 100 while wearing a +30 health ring, it will still say it was 130 after the change because the ring +30 is separate from my characters health.

 

It would the same way for you except instead of a ring its a magic effect and instead of health its speedmult.

 

Just to make sure this works properly I just tried it in my game. I made it so if I press middle mouse my character gets a 400 speedmult from a an ability I set up. I put on a pet suit, press middle mouse, and off I go. You could do the same except instead of needing to be pressing middle mouse the ability just checks if you are wearing a petsuit.

 

The perk does need to periodically check if you are wearing a petsuit or not, but theres no scripting there, its all done through the engine which is a non-issue for a simple keyword check. The only scripting you would need for that is a script to add the ability to the player.

 

This is just what I do though, could be other ways. Only issue I have seen with this method is the engine only seems to poll conditions twice a second so it can only update the speedmult that fast. Not too big of an issue though.

Edited by HereBeTentacles
Posted (edited)
1 hour ago, HereBeTentacles said:

DD is adjusting the players base speedmult via script and then the speedmult magic effect I am suggesting is just getting added overtop of it. Like how right now if I set my charcters health from 130 to 100 while wearing a +30 health ring, it will still say it was 130 after the change because the ring +30 is separate from my characters health.

Okay.  Since the logic for that moved out of zadx_HobbleSkirtEffectScript in DD NG and I couldn't find it elsewhere, I couldn't be sure what DD NG is doing with that.  DD 5.2 constantly corrected speed to match its own target (add a speed buff and DD would cancel it with its own debuff seconds later).

 

From you've told me, it seems that DD NG doesn't keep correcting for speed buffs, so I can modify the wearer's speed once after the pet suit is equipped and have the wearer move faster, then remove the speed adjustment at the end of the event when the suit comes off.  Thank for the explanation and for your test.

Edited by Hex Bolt
Posted
15 minutes ago, Hex Bolt said:

Okay.  Since the logic for that moved out of zadx_HobbleSkirtEffectScript in DD NG and I couldn't find it elsewhere, I couldn't be sure what DD NG is doing with that.  DD 5.2 constantly corrected speed to match its own target (add a speed buff and DD would cancel it with its own debuff seconds later).

 

From you've told me, it seems that DD NG doesn't keep correcting for speed buffs, so I can modify the wearer's speed once after the pet suit is equipped and have the wearer move faster, then remove the speed adjustment at the end of the event when the suit comes off.  That's easy.  Thank for the explanation and for your test.

 

From what I tried 300 seemed to be a little under normal walk/run/sprint speed. The petsuit animations weren't really made for higher speeds so I think if you go past 450ish the sprint animation is going to look a bit off.

Posted (edited)
1 hour ago, HereBeTentacles said:

From what I tried 300 seemed to be a little under normal walk/run/sprint speed. The petsuit animations weren't really made for higher speeds so I think if you go past 450ish the sprint animation is going to look a bit off.

Hold on, now I'm confused again.  NG's handling of this is really not transparent.

 

Why do you need to apply a speedmult of 300 to 400 to reach average speed?  That's extreme, right?  Speedmults are additive.  If DD NG adjusts the player's speed to 15 (afMaxSpeedMult = 0.15 in the ini file), adding 85 is all that's needed to get to 100, normal speed.

 

Now, if DD NG is instead multiplying the multiplier, things get messy.  My mod doesn't know what multiplier DD NG is using because that moved from being a script property to an SKSE INI setting.  As far as I can tell, DD NG does not expose the afMaxSpeedMult value, so mods can't see it.  All I can see is the player's current speedmult actor value, which got multiplied by a hidden factor to its current value.  Players are free to change DD NG's afMaxSpeedMult, but my mod can't see that, so it won't know how much to increase the player's speedmult if DD NG is going to multiply that addition.  I could use 300 and hope for the best, but I've had some players tell me that they've increased afMaxSpeedMult a lot because they dislike being so slow.  Players who have done that will move crazy fast if I buff their speedmult by 300 or 400.

 

I do appreciate all the time you've taken to respond.  Maybe I'm dense, but how this works just isn't clear to me.

 

I'd like to understand how I can work with the system to achieve a specific desired speed.

Edited by Hex Bolt
Posted
12 minutes ago, Hex Bolt said:

Hold on, now I'm confused again.  Compared to 5.2, NG's handling of this is really not transparent.

 

Why do you need to apply a speedmult of 300 to 400 to reach average speed?  That's extreme, right?  Speedmults are additive.  If DD NG adjusts the player's speed to 15 (afMaxSpeedMult = 0.15 in the ini file), adding 85 is all that's needed to get to 100, normal speed.

 

Now, if DD NG is instead multiplying the multiplier, things get messy.  My mod doesn't know what multiplier DD NG is using because that moved from being a script property to an SKSE INI setting.  As far as I can tell, DD NG does not expose the afMaxSpeedMult value, so mods can't see it.  All I can see is the player's current speedmult actor value, which got multiplied by a hidden factor to its current value.  Players are free to change DD NG's afMaxSpeedMult, but my mod can't see that, so my mod won't know how much to increase the player's speedmult if DD NG is going to multiply that addition.  I could use 300 and hope for the best, but I've had some players tell me that they've increased afMaxSpeedMult a lot because they dislike being so slow.  Players who have done that will move crazy fast if I buff their speedmult by 300 or 400.

 

I would just leave it up to a button in an MCM or something like that so they can toggle on/off in case they already adjusted the speed elsewhere.

 

I don't think it would be impossible to expose the modifiers DDNG is using to a script for more fine control though, pretty sure the SKSE should be able to do that.

Posted
Just now, HereBeTentacles said:

I would just leave it up to a button in an MCM or something like that so they can toggle on/off in case they already adjusted the speed elsewhere.

I had considered that, but it's kind of clunky for another mod to have to add an adjustment toggle when it might have simply calculated the precise adjustment needed and applied that without player intervention.  It seems as if there should be a straightforward way to do that.  Prior to the zadx_HobbleSkirtEffectScript change, it wasn't hard.  I'm just trying to figure out how to achieve the same result with the change.  Again, thank you for the responses.

Posted
4 hours ago, Hex Bolt said:

Players are free to change DD NG's afMaxSpeedMult, but my mod can't see that, so it won't know how much to increase the player's speedmult if DD NG is going to multiply that addition.  I could use 300 and hope for the best, but I've had some players tell me that they've increased afMaxSpeedMult a lot because they dislike being so slow.  Players who have done that will move crazy fast if I buff their speedmult by 300 or 400.

Yes, that would be me...

The very odd thing is: Sometimes your approach works. Mistress puts Lola in a petsuit and the speed is much slower than my usual (which I changed in DD.ini) and when it's changed to let me move faster, it's back to normal-ish (can't tell exactly because there's no speedometer on my petsuit)

 

But as we are talking hobbleskirts: There's still a bug which has been present since... some years. The last step is always a stagger. With the last NG version it's smooth when the dress is freshly put on, but loading a save with the hobbleskirt worn, the stagger is back.

Posted (edited)

 

On 9/10/2026 at 10:41 PM, DonQuiWho said:

FYI - in case it's not been mentioned before

 

Using this version with these settings: 

 

   Devious Devices NG v0.4.3 + FeuerTin + Small Steps + Hop is Sprint + FH + No Dyn Armour 

 

Straitjacket ; HobbleDress: Red Ebonite doesn't hide hands whhich seem to be posed as in an armbinder

 

Pics In situ and + Console below

 

Hope that's of use

 

DQW

 

  Reveal hidden contents

 

SkyrimSE2026-09-1100-41-28-50.jpg.e019af572bc1f3d1ebc9cca05c936071.jpg

 

SkyrimSE2026-09-1101-19-23-28.jpg.15db257bad24ec3c789508c0e6ecea54.jpg

 

SkyrimSE2026-09-1101-19-38-22.jpg.005029fd4d14fc1cd28846f040545f73.jpg

 

 

 

at this ive found that dd straitjackets dont mark the hand slot as used so slaves will often either show the hands or additionally equip gloves. pahe includes fixes for that, but agreed it would be better handled from dd

On 9/10/2026 at 10:09 AM, DonQuiWho said:

@TrollAutokill 's  Diary of Mine (DOM), his NPC slavery mod developed from Paradise Halls Extended (PAHE) makes for excellent gameplay with respect to the more emotional aspects of NPC training

 

However, as it has developed further from the original PAHE, its handling of NPC restraint/s has changed so that, to a large extent, it no longer allows the player to easily directly control worn restraints, either ZAZ or DD/DDNG based.  Consequently it has lost some of its 'playability' for those of us who, lacking relational finesse, would really want to use it more as a 'Trading Entity' of the sort of 'Catch, Train and Sell' variety.  Sort of like when it first appeared on NEXUS a dozen years or so ago.  (The joys of being🧓)

 

The post linked to at the end sets out the sort thing that can happen in DOM presently - eg restraints vanishing as NPCs poses etc change - and which right now seem to be beyond the player's ability to properly control.  It's NOT a reflection on poor mod development - far from that! - but rather the author's greater interest in the emotive aspects, and as TAK has said, he doesn't use DDs, and I guess that his use of ZAZ items is pretty much picking up on the PAHE legacy.  

 

He seems happy about being willing to try to help on this - see the post I referred to - but basically it seems that any primary mechanism to do that might require some identification of ZAZ & DD items by keywords so that they can be identified as some sort of 'no strip' inventory items, and as you can see from the post attached, also able would need to ensure that for DD items BOTH the 'worn' Equipment and 'unworn' Inventory items are left in situ - at present once can disappear 😊

 

I have had a good search through some of the varied items my player has commanded NPCs to wear  - from manacles, armbinders and the full set of chains - but I can't see any readily common way of identifying them directly from the DD individual device keywords.  The only thing that I could see that was common overall was that the keywords' ultimately link back to the base DD mods., but I don't see how even looking at load orders could actually do that too easily when 2 lead characters are variable from set-up to set-up

 

BUT, a lot of mods out there can detect worn DDs so that they don't strip them, or at least quest DDs, from players or NPCs inadvertently eg Simple Slavery etc   

 

So is there some magic that can be done in this regard?  I am the ultimate non techie.  What would allow TAK to add something to devices applied to DOM slaves that he could then use as a means to check items worn by DOM NPC which would prevent his other functions - eg pose animation changes etc - from inadvertently removing them?

 

I have had a look through the thread for 'keyword' uses, in case there really was some simple catch all, but there wasn't anything that I could identify - or understand - as being particularly useful.  But what would I know ...😜

 

The real gurus on this eg @krzp are here, so any suggestions for something simple that would help in this would be gratefully received!

 

DQW  

 

  

On 9/11/2026 at 7:13 PM, DonQuiWho said:

 

Thanks very much for the prompt, and succinct, reply!

 

I'll pass that on, and see if that helps with this conundrum!

 

DQW

for that hes welcome to look back how pahe handles it. pahe already checks for the devious keywords and works accordingly

Edited by CliftonJD
Posted
10 minutes ago, CliftonJD said:

 

at this ive found that dd straitjackets dont mark the hand slot as used so slaves will often either show the hands or additionally equip gloves. pahe includes fixes for that, but agreed it would be better handled from dd

for that hes welcome to look back how pahe handles it. pahe already checks for the devious keywords and works accordingly

 

Thanks very much

 

I'm sure @TrollAutokill will be watching this thread and have noted those helpful remarks

 

Much appreciated 

 

DQW

Posted

I remember using a DD mod in the past where, if the player was wearing a gag, trying to use a Shout would not actually perform the attack. Instead, the character would make muffled moaning sounds.

 

Does anyone know which DD mod or feature does this?

 

Posted
On 8/21/2026 at 9:07 PM, bondjamesbond said:

Does anyone else have an issue with device manipulation not working? When removing the device, the message will say the device has been removed, but then the device will re-equip.  I've had this issue for the last few versions of DD NG now, but cannot find anything overwriting the mod.  Devices will remove if an animation plays out and the attempt was sucessful, but that can take numerous times, even on the easiest difficulty. 

I have same problem. I identified the problem with the rope restraints and the plug. By any chance, have you solved this problem? The only DD-related mod I use is Devious Curse.

Posted
2 hours ago, WWWGONA said:

I have same problem. I identified the problem with the rope restraints and the plug. By any chance, have you solved this problem? The only DD-related mod I use is Devious Curse.

Analysis of the source code and Papyrus logs using ChatGPT suggests that the re-equipping bug likely stems from the fact that, in the unconditional removal path, `OnUnequipped()` triggers before the `RemovalToken` is set—a token DD NG uses to distinguish between a user's intentional removal and an unequip event triggered internally by the mod.

Posted
On 9/15/2026 at 2:39 AM, sacattack93 said:

Crash whenever I try to start a new game or load a game with DDNG. Build works perfectly fine without DD/DDNG installed. Anybody know of a fix?

 

Crash log: https://pastebin.com/Grr66MFs

Load order:  https://pastebin.com/uzpivUrQ 

 

thx!!

 

Your DD/DDng installation is not correct: you should have 4 .esm in your load order (Assets, Integration, Expansion, Contraptions) while there is only the first.

Solution: reinstall both mods.

Posted
On 9/15/2026 at 10:19 PM, WWWGONA said:

I have same problem. I identified the problem with the rope restraints and the plug. By any chance, have you solved this problem? The only DD-related mod I use is Devious Curse.

Thank you for the reply.  I have not solved the problem on my end yet.  I have never had the mod Devious Curse in my load order, so you can hopefully rule that out.  I've tried removing a few mods with possible interactions related to DD, but haven't had luck there. I don't see any conflicts with DD NG in Vortex, as DD NG wins all. 

I tried the DD log that someone mentioned, but it is difficult to interpret those.  My log did mention device removal token like you described, but I didn't see anything in the log pointing to a mod that I had which could be causing any issue.   

 

I wonder if something is happening too quick without the struggle animation.  I didn't mention this in my original post, but the device manipulation function isn't the only way I've had the device re-equip issue. This issue also happens if I sit the player in a chair and then do the struggle/pick lock/break device, and have a successful outcome. Sitting in the chair before the attempt basically just bypasses the animation,. I did this in the past when I didn't want to sit through repeated 10-20 second animations.  

 

If I ever figure out the issue on my end, I will post what I did to fix it. Hopefully very few people have run into this.

 

Posted
On 9/12/2026 at 11:58 PM, HereBeTentacles said:

 

Most likely you are trying to use DDNG on the latest version of skryim, which you cannot do yet. You will either have to wait or roll your game back to one of the previous supported versions.

The funniest thing is that with version DD NG 0.4.1.7, the game actually launched after the latest Bethesda update—it just complained: "must recompiled for new address library."

Posted
18 hours ago, bondjamesbond said:

Thank you for the reply.  I have not solved the problem on my end yet.  I have never had the mod Devious Curse in my load order, so you can hopefully rule that out.  I've tried removing a few mods with possible interactions related to DD, but haven't had luck there. I don't see any conflicts with DD NG in Vortex, as DD NG wins all. 

I tried the DD log that someone mentioned, but it is difficult to interpret those.  My log did mention device removal token like you described, but I didn't see anything in the log pointing to a mod that I had which could be causing any issue.   

 

I wonder if something is happening too quick without the struggle animation.  I didn't mention this in my original post, but the device manipulation function isn't the only way I've had the device re-equip issue. This issue also happens if I sit the player in a chair and then do the struggle/pick lock/break device, and have a successful outcome. Sitting in the chair before the attempt basically just bypasses the animation,. I did this in the past when I didn't want to sit through repeated 10-20 second animations.  

 

If I ever figure out the issue on my end, I will post what I did to fix it. Hopefully very few people have run into this.

 

I have successfully reproduced the exact issue you are experiencing.

Based on my findings, the re-equipping bug occurs in the following scenarios:
 

1. Equipment that can be removed without conditions, such as plugs or rope cuffs.

2. Equipment configured via MCM settings to allow removal without conditions.

3. Equipment removed while the character is in a crouching position.
 

The common factor among these three scenarios is that the equipment removal animation does not play.
 

After analyzing all DDNG .psc script files and bug logs using GPT, the following report has been generated.

"The temporary OnEquipped() instance created during device-menu handling survives long enough to observe the completion of an actual removal, but has no mechanism to distinguish that completed removal from a normal device swap. It subsequently executes its normal equipment logic and re-equips the device.

In other words, the fundamental bug is a state/race-condition failure in the synchronization between the temporary re-equip operation and the actual removal operation, with the five-second zad_RemovalOperation wait acting as the critical synchronization boundary."

Posted
On 8/21/2026 at 9:07 PM, bondjamesbond said:

Does anyone else have an issue with device manipulation not working? When removing the device, the message will say the device has been removed, but then the device will re-equip.  I've had this issue for the last few versions of DD NG now, but cannot find anything overwriting the mod.  Devices will remove if an animation plays out and the attempt was sucessful, but that can take numerous times, even on the easiest difficulty. 

I make bug fix with ChatGPT. Test Please.

DDNG Re-Equip Bug Fix Patch

Logic Structure and Changes Compared with the Original zadEquipScript.psc

1. Scope and Purpose

This patch modifies zadEquipScript.psc to address the specific re-equip problem caused by an OnEquipped() event that remains alive while a device-removal operation is in progress.

The original script intentionally re-equips the inventory device from OnUnequipped() so that the player can access DeviceMenu(). That intentional EquipItem() call generates another OnEquipped() event.

The problem occurs when the actual removal finishes before that OnEquipped() event has finished waiting on zad_RemovalOperation. The original event does not terminate when the removal succeeds. Once the wait ends, it can continue through the normal equipment path and execute EquipItem() again, causing the already-removed device to be re-equipped.

The final patch detects this obsolete OnEquipped() invocation and aborts it before it reaches the actual equipment calls.


2. Original OnEquipped() Logic

The relevant player-side logic in the original script is:

OnEquipped()
    ↓
Check zad_RemovalOperation
    ↓
If active: wait up to 5 seconds
    ↓
Continue processing
    ↓
Equipment / conflict / filter checks
    ↓
OnEquippedPre()
    ↓
EquipItem(DeviceInventory)
    ↓
EquipItem(DeviceRendered)

The important limitation is that the original code only asks whether zad_RemovalOperation has ended.

It does not ask whether the device that this particular OnEquipped() invocation was waiting for has actually been removed.


3. The Original Re-Equip Mechanism

OnUnequipped() deliberately contains this behavior for the player:

akActor.EquipItem(deviceInventory, false, true)
DeviceMenu()

This is intended to let the player access the device interaction menu.

The resulting event sequence can therefore be:

OnUnequipped()
    ↓
zad_RemovalOperation = 1
    ↓
Temporary EquipItem(deviceInventory)
    ↓
New OnEquipped()
    ↓
Wait while removal operation is active

If the device is then genuinely removed, the normal removal branch processes the RemovalToken, calls UnsetStoredDevice(), and eventually clears zad_RemovalOperation.

However, the original OnEquipped() that was waiting does not disappear.

When the wait ends, it continues executing.

If the rendered device is no longer present, it can eventually reach:

if !akActor.IsEquipped(DeviceInventory)
    akActor.EquipItem(DeviceInventory, false, true)
EndIf

akActor.EquipItem(DeviceRendered, true, true)

Those are the actual re-equipping calls responsible for the bug.


4. New Logic Added by the Final Patch

The final patch keeps the original five-second wait, but records whether this particular OnEquipped() invocation entered that wait.

bool waitingForRemoval = false

if StorageUtil.GetIntValue(
    akActor,
    "zad_RemovalOperation" + zad_DeviousDevice,
    0
) == 1

    waitingForRemoval = true

EndIf

This is a local variable belonging to the current OnEquipped() execution.

No additional persistent StorageUtil wait/completion flags are introduced.


5. How the Patch Determines Whether the Device Was Actually Removed

After the wait ends, the patch examines DDNG's existing stored-device state:

Armor storedDevice =
    StorageUtil.GetFormValue(
        akActor,
        "zad_Equipped"
        + libs.LookupDeviceType(zad_DeviousDevice)
        + "_Inventory"
    ) as Armor

It then checks:

if storedDevice != deviceInventory \
    && !akActor.IsEquipped(deviceRendered) \
    && akActor.GetItemCount(deviceRendered) <= 0

    return

EndIf

The three checks serve different purposes.

Condition A — The stored inventory device is no longer this device

The normal successful-removal branch calls UnsetStoredDevice(). That removes the stored _Inventory form for the device type.

Therefore, if the current deviceInventory is no longer the stored device, the old equipment event is no longer associated with an active stored restraint of that type.

Condition B — The rendered device is not currently equipped

!akActor.IsEquipped(deviceRendered)

This prevents the stale-event test from firing merely because the stored form changed while the device is still physically present.

Condition C — The rendered device is not in the inventory

akActor.GetItemCount(deviceRendered) <= 0

This further distinguishes a genuinely removed device from a device that is temporarily in a different equipment state.

Only when all three conditions are satisfied does the patch conclude that this OnEquipped() invocation is obsolete.

It then returns immediately.


6. Final Control Flow

Normal temporary re-equip

OnUnequipped()
    ↓
RemovalOperation = 1
    ↓
Temporary EquipItem()
    ↓
OnEquipped()
    ↓
waitingForRemoval = true
    ↓
Wait up to 5 seconds
    ↓
No genuine removal occurred
    ↓
Stored device is still this device
    ↓
Continue original equipment processing

The original menu-access behavior is therefore preserved.


Fast removal without an animation

OnUnequipped()
    ↓
Temporary EquipItem()
    ↓
OnEquipped()
    ↓
RemovalOperation = 1
    ↓
Wait
    ↓
RemoveDevice()
    ↓
UnlockDevice()
    ↓
OnUnequipped()
    ↓
RemovalToken detected
    ↓
UnsetStoredDevice()
    ↓
RemovalOperation cleared
    ↓
Waiting OnEquipped resumes
    ↓
Stored device != current device
+ rendered device not equipped
+ rendered device absent from inventory
    ↓
return

The obsolete OnEquipped() therefore never reaches the final EquipItem() calls.


Removal while seated / animation path does not execute

Removal request
    ↓
Animation path entered
    ↓
Animation path terminates without executing the animation
    ↓
RemoveDevice()
    ↓
Actual removal
    ↓
Waiting OnEquipped resumes
    ↓
Device is confirmed to be gone
    ↓
return

This follows the same protection mechanism as other rapid-removal cases.


7. Exact Code-Level Changes

Change 1 — Added a local state variable

Original:

int counter = 10

Final patch additionally introduces:

bool waitingForRemoval = false

and sets it when zad_RemovalOperation is active.


Change 2 — Added an explicit default value

The original uses:

StorageUtil.GetIntValue(
    akActor,
    "zad_RemovalOperation" + zad_DeviousDevice
)

The final patch uses:

StorageUtil.GetIntValue(
    akActor,
    "zad_RemovalOperation" + zad_DeviousDevice,
    0
)

This explicitly treats a missing value as zero.


Change 3 — Added stale-event detection after the wait

The original has no post-wait test that distinguishes a genuinely valid equipment event from an equipment event whose corresponding device has already been removed.

The final version reads the existing stored inventory form and checks:

storedDevice != deviceInventory
&& !akActor.IsEquipped(deviceRendered)
&& akActor.GetItemCount(deviceRendered) <= 0

When the condition is satisfied, the event is logged and immediately terminated.


Change 4 — The actual OnUnequipped() removal mechanism is unchanged

The final patch does not alter the normal removal sequence:

akActor.RemoveItem(deviceRendered, 1, true)
UnsetStoredDevice(akActor)
OnRemoveDevice(akActor)
...
StorageUtil.UnsetIntValue(
    akActor,
    "zad_RemovalToken" + deviceInventory
)

Nor does it change the final clearing of:

zad_RemovalOperation + zad_DeviousDevice

in OnUnequipped().

The patch therefore does not replace the removal system itself. It only prevents an obsolete OnEquipped() invocation from continuing after that removal has already completed.


8. Improvement over the Earlier Test Patch

An earlier test version used two additional persistent StorageUtil flags:

zad_RemovalWait
zad_RemovalCompleted

The final patch removes those extra persistent states.

Instead, it uses information DDNG already maintains:

zad_Equipped..._Inventory
actual rendered-device equipped state
rendered-device inventory count

This reduces the amount of patch-specific persistent state and avoids having separate wait/completion markers that could be overwritten or left behind when multiple equip/removal events overlap.


9. Features Intentionally Left Untouched

The patch does not modify:

  • quest-item removal protection

  • OnUnequippedFilter()

  • zad_RemovalToken

  • zad_TightenToken

  • zad_UntightenToken

  • linked-device processing

  • equipment conflict checks

  • ShouldEquipSilently()

  • OnEquippedPre() / OnEquippedPost()

  • bound-effect handling

  • the existing remove-all-items compatibility re-equip logic

The patch is deliberately limited to discarding the obsolete OnEquipped() invocation created by the removal/re-equip race.


10. Core Difference

The original behavior is effectively:

"An OnEquipped() that was waiting for removal
continues executing after that removal finishes."

The final behavior is:

"An OnEquipped() that was waiting for removal
checks whether its device has actually disappeared.
If it has, that old event is discarded."

The patch therefore does not globally disable EquipItem(), does not disable UpdateControls(), and does not remove DD's intended menu-access re-equip behavior.

It specifically limits the lifetime of the stale OnEquipped() event that causes the re-equipping race.

Devious device NG 0.4.3 RE_Equip_Bug_fix.zip

Posted
3 hours ago, WWWGONA said:

I make bug fix with ChatGPT. Test Please.

DDNG Re-Equip Bug Fix Patch

Logic Structure and Changes Compared with the Original zadEquipScript.psc

1. Scope and Purpose

This patch modifies zadEquipScript.psc to address the specific re-equip problem caused by an OnEquipped() event that remains alive while a device-removal operation is in progress.

The original script intentionally re-equips the inventory device from OnUnequipped() so that the player can access DeviceMenu(). That intentional EquipItem() call generates another OnEquipped() event.

The problem occurs when the actual removal finishes before that OnEquipped() event has finished waiting on zad_RemovalOperation. The original event does not terminate when the removal succeeds. Once the wait ends, it can continue through the normal equipment path and execute EquipItem() again, causing the already-removed device to be re-equipped.

The final patch detects this obsolete OnEquipped() invocation and aborts it before it reaches the actual equipment calls.


2. Original OnEquipped() Logic

The relevant player-side logic in the original script is:

OnEquipped()
    ↓
Check zad_RemovalOperation
    ↓
If active: wait up to 5 seconds
    ↓
Continue processing
    ↓
Equipment / conflict / filter checks
    ↓
OnEquippedPre()
    ↓
EquipItem(DeviceInventory)
    ↓
EquipItem(DeviceRendered)

The important limitation is that the original code only asks whether zad_RemovalOperation has ended.

It does not ask whether the device that this particular OnEquipped() invocation was waiting for has actually been removed.


3. The Original Re-Equip Mechanism

OnUnequipped() deliberately contains this behavior for the player:

akActor.EquipItem(deviceInventory, false, true)
DeviceMenu()

This is intended to let the player access the device interaction menu.

The resulting event sequence can therefore be:

OnUnequipped()
    ↓
zad_RemovalOperation = 1
    ↓
Temporary EquipItem(deviceInventory)
    ↓
New OnEquipped()
    ↓
Wait while removal operation is active

If the device is then genuinely removed, the normal removal branch processes the RemovalToken, calls UnsetStoredDevice(), and eventually clears zad_RemovalOperation.

However, the original OnEquipped() that was waiting does not disappear.

When the wait ends, it continues executing.

If the rendered device is no longer present, it can eventually reach:

if !akActor.IsEquipped(DeviceInventory)
    akActor.EquipItem(DeviceInventory, false, true)
EndIf

akActor.EquipItem(DeviceRendered, true, true)

Those are the actual re-equipping calls responsible for the bug.


4. New Logic Added by the Final Patch

The final patch keeps the original five-second wait, but records whether this particular OnEquipped() invocation entered that wait.

bool waitingForRemoval = false

if StorageUtil.GetIntValue(
    akActor,
    "zad_RemovalOperation" + zad_DeviousDevice,
    0
) == 1

    waitingForRemoval = true

EndIf

This is a local variable belonging to the current OnEquipped() execution.

No additional persistent StorageUtil wait/completion flags are introduced.


5. How the Patch Determines Whether the Device Was Actually Removed

After the wait ends, the patch examines DDNG's existing stored-device state:

Armor storedDevice =
    StorageUtil.GetFormValue(
        akActor,
        "zad_Equipped"
        + libs.LookupDeviceType(zad_DeviousDevice)
        + "_Inventory"
    ) as Armor

It then checks:

if storedDevice != deviceInventory \
    && !akActor.IsEquipped(deviceRendered) \
    && akActor.GetItemCount(deviceRendered) <= 0

    return

EndIf

The three checks serve different purposes.

Condition A — The stored inventory device is no longer this device

The normal successful-removal branch calls UnsetStoredDevice(). That removes the stored _Inventory form for the device type.

Therefore, if the current deviceInventory is no longer the stored device, the old equipment event is no longer associated with an active stored restraint of that type.

Condition B — The rendered device is not currently equipped

!akActor.IsEquipped(deviceRendered)

This prevents the stale-event test from firing merely because the stored form changed while the device is still physically present.

Condition C — The rendered device is not in the inventory

akActor.GetItemCount(deviceRendered) <= 0

This further distinguishes a genuinely removed device from a device that is temporarily in a different equipment state.

Only when all three conditions are satisfied does the patch conclude that this OnEquipped() invocation is obsolete.

It then returns immediately.


6. Final Control Flow

Normal temporary re-equip

OnUnequipped()
    ↓
RemovalOperation = 1
    ↓
Temporary EquipItem()
    ↓
OnEquipped()
    ↓
waitingForRemoval = true
    ↓
Wait up to 5 seconds
    ↓
No genuine removal occurred
    ↓
Stored device is still this device
    ↓
Continue original equipment processing

The original menu-access behavior is therefore preserved.


Fast removal without an animation

OnUnequipped()
    ↓
Temporary EquipItem()
    ↓
OnEquipped()
    ↓
RemovalOperation = 1
    ↓
Wait
    ↓
RemoveDevice()
    ↓
UnlockDevice()
    ↓
OnUnequipped()
    ↓
RemovalToken detected
    ↓
UnsetStoredDevice()
    ↓
RemovalOperation cleared
    ↓
Waiting OnEquipped resumes
    ↓
Stored device != current device
+ rendered device not equipped
+ rendered device absent from inventory
    ↓
return

The obsolete OnEquipped() therefore never reaches the final EquipItem() calls.


Removal while seated / animation path does not execute

Removal request
    ↓
Animation path entered
    ↓
Animation path terminates without executing the animation
    ↓
RemoveDevice()
    ↓
Actual removal
    ↓
Waiting OnEquipped resumes
    ↓
Device is confirmed to be gone
    ↓
return

This follows the same protection mechanism as other rapid-removal cases.


7. Exact Code-Level Changes

Change 1 — Added a local state variable

Original:

int counter = 10

Final patch additionally introduces:

bool waitingForRemoval = false

and sets it when zad_RemovalOperation is active.


Change 2 — Added an explicit default value

The original uses:

StorageUtil.GetIntValue(
    akActor,
    "zad_RemovalOperation" + zad_DeviousDevice
)

The final patch uses:

StorageUtil.GetIntValue(
    akActor,
    "zad_RemovalOperation" + zad_DeviousDevice,
    0
)

This explicitly treats a missing value as zero.


Change 3 — Added stale-event detection after the wait

The original has no post-wait test that distinguishes a genuinely valid equipment event from an equipment event whose corresponding device has already been removed.

The final version reads the existing stored inventory form and checks:

storedDevice != deviceInventory
&& !akActor.IsEquipped(deviceRendered)
&& akActor.GetItemCount(deviceRendered) <= 0

When the condition is satisfied, the event is logged and immediately terminated.


Change 4 — The actual OnUnequipped() removal mechanism is unchanged

The final patch does not alter the normal removal sequence:

akActor.RemoveItem(deviceRendered, 1, true)
UnsetStoredDevice(akActor)
OnRemoveDevice(akActor)
...
StorageUtil.UnsetIntValue(
    akActor,
    "zad_RemovalToken" + deviceInventory
)

Nor does it change the final clearing of:

zad_RemovalOperation + zad_DeviousDevice

in OnUnequipped().

The patch therefore does not replace the removal system itself. It only prevents an obsolete OnEquipped() invocation from continuing after that removal has already completed.


8. Improvement over the Earlier Test Patch

An earlier test version used two additional persistent StorageUtil flags:

zad_RemovalWait
zad_RemovalCompleted

The final patch removes those extra persistent states.

Instead, it uses information DDNG already maintains:

zad_Equipped..._Inventory
actual rendered-device equipped state
rendered-device inventory count

This reduces the amount of patch-specific persistent state and avoids having separate wait/completion markers that could be overwritten or left behind when multiple equip/removal events overlap.


9. Features Intentionally Left Untouched

The patch does not modify:

  • quest-item removal protection

  • OnUnequippedFilter()

  • zad_RemovalToken

  • zad_TightenToken

  • zad_UntightenToken

  • linked-device processing

  • equipment conflict checks

  • ShouldEquipSilently()

  • OnEquippedPre() / OnEquippedPost()

  • bound-effect handling

  • the existing remove-all-items compatibility re-equip logic

The patch is deliberately limited to discarding the obsolete OnEquipped() invocation created by the removal/re-equip race.


10. Core Difference

The original behavior is effectively:

"An OnEquipped() that was waiting for removal
continues executing after that removal finishes."

The final behavior is:

"An OnEquipped() that was waiting for removal
checks whether its device has actually disappeared.
If it has, that old event is discarded."

The patch therefore does not globally disable EquipItem(), does not disable UpdateControls(), and does not remove DD's intended menu-access re-equip behavior.

It specifically limits the lifetime of the stale OnEquipped() event that causes the re-equipping race.

Devious device NG 0.4.3 RE_Equip_Bug_fix.zip 35.64 kB · 1 download

I just tested out your patch and tested it on two devices. They both removed without re-equipping.  Thank you!

Posted
On 9/12/2026 at 2:13 AM, DonQuiWho said:

 

Thanks very much for the prompt, and succinct, reply!

 

I'll pass that on, and see if that helps with this conundrum!

 

DQW

In addition to that:

  • DD has two Armor entries for each restraint: one that is visible in the inventory and has a script attached to make it lockable, and one that is invisible from the inventory and is rendered on the character while equipped. zad_inventoryDevice should be on every script-carrying restraint Armor, and zad_Lockable on every corresponding lockable rendered Armor. The easiest way to check whether an item is a DD item is to check for those keywords. (Some devices, like plugs, are not lockable but do have scripts, and so will have zad_InventoryDevice on the inventory Armor but no zad_Lockable on the rendered Armor. However since they are not lockable, they are considered free to remove.)
  • DD NG enforces restrained animations via C++ code (Ponzi's PAR or Partial Animation Replacer, shipped together with DD NG), bypassing the native animation system. DD 5.2 does not, and there are many ways to break its animations.
  • zad_HeavyBondage is used for most devices that restrain the arms. These I think all have animations enforced via PAR. There are other devices like hobble dresses or blindfolds that have custom animations that are not enforced via PAR but instead via replacers. In DD NG these are done via OAR, but that means that they live inside OAR's priority system, so if other mods use absurdly high priorities they can overwrite DD NG's animations. Manually played animations will also overwrite OAR's idles.
  • DD NG's zadLibs script has functions to get the inventory device of a given rendered device and visa versa.
  • DD items do not play well with RemoveAllItems calls, unfortunately. Use of said function can lead to broken device states. I'm not sure about UnequipAll.
Posted
3 hours ago, Frayed said:

In addition to that:

  • DD has two Armor entries for each restraint: one that is visible in the inventory and has a script attached to make it lockable, and one that is invisible from the inventory and is rendered on the character while equipped. zad_inventoryDevice should be on every script-carrying restraint Armor, and zad_Lockable on every corresponding lockable rendered Armor. The easiest way to check whether an item is a DD item is to check for those keywords. (Some devices, like plugs, are not lockable but do have scripts, and so will have zad_InventoryDevice on the inventory Armor but no zad_Lockable on the rendered Armor. However since they are not lockable, they are considered free to remove.)
  • DD NG enforces restrained animations via C++ code (Ponzi's PAR or Partial Animation Replacer, shipped together with DD NG), bypassing the native animation system. DD 5.2 does not, and there are many ways to break its animations.
  • zad_HeavyBondage is used for most devices that restrain the arms. These I think all have animations enforced via PAR. There are other devices like hobble dresses or blindfolds that have custom animations that are not enforced via PAR but instead via replacers. In DD NG these are done via OAR, but that means that they live inside OAR's priority system, so if other mods use absurdly high priorities they can overwrite DD NG's animations. Manually played animations will also overwrite OAR's idles.
  • DD NG's zadLibs script has functions to get the inventory device of a given rendered device and visa versa.
  • DD items do not play well with RemoveAllItems calls, unfortunately. Use of said function can lead to broken device states. I'm not sure about UnequipAll.

 

Thanks very much

 

Took the liberty of linking to that on TAK's version of AYGAS for his info

 

DQW

 

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...