Jump to content

Recommended Posts

Posted

@Fraying9981

 

As you point out, the functionality *can* be implemented in SLTScript, but is painful and slow and lacks some ease of use. So what if the notion of objtrackers was introduced via some new functions. A set of bindings available in SLTScript that are implemented in Papyrus by SLTR for performance and handle a lot of the bookkeeping for you. Global in scope, tied to FormID to avoid needing to load objects for reference, along with some syntactic sugary goodness to add ease of use.

 

objtracker functions:
- globally accessible object based tracking
- objects tracked by formID
- can add/delete values that will be tracked
- a suite of functions to manipulate them
- mostly syntactic sugar
- SLTR handles the bookkeeping
- global (spans scripts, persists across saves, just like globals)
- can have multiple objtrackers (based on objtracker key)

 

; objtracker management
objtracker_create <objtracker_key> ; create an objtracker identified by the key
objtracker_delete <objtracker_key> ; delete an objtracker identified by the key
objtracker_exists <objtracker_key> ; return true if it exists, false otherwise

 

; objtracker object management (i.e. add/remove/list objects being tracked)

objtracker_object_add <objtracker_key> <object OR formID> ; add the object to be tracked by the identified objtracker; ignores duplicates
objtracker_object_delete <objtracker_key> <object OR formID> ; remove an object from the objtracker identified by the objtracker_key
objtracker_object_count <objtracker_key> ; return the count of tracked objects for this objtracker
objtracker_object_is_tracked <objtracker_key> <object OR formID> ; return true if the object specified is being tracked by this objtracker, false otherwise
; the next two may be of dubious usefulness, I dunno
objtracker_object_list <objtracker_key> ; return a Form[] list of the tracked objects for this objtracker
objtracker_object_get <objtracker_key> <index> ; return the Form at the index-th position for this objtracker

 

; objtracker tracked value management (i.e. which values are we tracking)

objtracker_value_create <objtracker_key> <value_name> <value_type> ; start tracking the value identified by value_name, of the indicated type (i.e. int, float, etc) for all tracked objects for the specified objtracker
objtracker_value_delete <objtracker_key> <value_name> ; stop tracking the value identified by value_name, for the specified objtracker (frees up memory, in case you dun goofed)

 

; setting/adjusting specific values
objtracker_value_set <objtracker_key> <object OR formID> <value_name> <new value> ; sets the value for the tracked value for the tracked object for the identified objtracker
objtracker_value_adjust <objtracker_key> <object OR formID> <value_name> <adjust value> ; adjusts the value likewise, usable only for reasonable types like int/float

 

; setting/adjusting aggregate values (i.e. for all tracked objects)

objtracker_valueall_set <objtracker_key> <value_name> <new value> ; for all tracked objects for this objtracker, for the indicated value, set them to the new value
objtracker_valueall_adjust <objtracker_key> <value_name> <adjust value> ; for all tracked objects for this objtracker, for the indicated value, perform the indicated adjustment (usable only for things like int and float)

 

; fetching tracked objects matching basic criteria (i.e. all objects with tracked value matching/not matching/greater than/less than specified value)

objtracker_object_valueget_matching <objtracker_key> <value_name> <value to compare> ; returns all tracked objects where the tracked value matches the value to compare
objtracker_object_valueget_not_matching <objtracker_key> <value_name> <value to compare> ; returns all tracked objects where the tracked value does not match the value to compare
objtracker_object_valueget_above <objtracker_key> <value_name> <value to compare> ; same, but if value is greater than value to compare
objtracker_object_valueget_below <objtracker_key> <value_name> <value to compare> ; same, but if value is less than value to compare


Use case like your DD time worn tracking.

 

; OnSessionStart.sltscript

;================

; create the tracker if it doesn't exist
; this could go into an OnSessionStart Core trigger which would run at each startup
objtracker_exists "dd_time_worn_tracker"
if $$ != true
    objtracker_create "dd_time_worn_tracker"
    ; note that I was originally, for this example, going to call the tracker "dd_time_worn" but didn't want to confuse this step
    ; there would, however, be nothing wrong with having a value name be the same as the objtracker name, just saying
    objtracker_value_create "dd_time_worn_tracker" "dd_time_worn" float
endif
; now an objtracker named "dd_time_worn_tracker" exists and all tracked objects it contains will have a float value associated called "dd_time_worn"

 

; OnActorAdd.sltscript

;================
; now in your accumulator script where you add tracked actors
; just verify that the actor matches the required criteria
; no worries about duplicating objects in the list
set $trackable_actor $system.self
; check if $trackable_actor *should* be tracked
; note that you *CAN* use objtracker_object_is_tracked here *if you want*
; for example to prevent yourself doing all the *other* checks for trackability 
; but you *do not have to* because duplicates will be ignored
; in this example, this could take place when an actor has a piece of DD gear applied
if $should_be_tracked
    ; again, note that it won't matter
    objtracker_object_add "dd_time_worn_tracker" $trackable_actor
endif

 

; OnHourly.sltscript

;================

; each hour, just tell the objtracker to adjust the time worn
objtracker_valueall_adjust "dd_time_worn_tracker" "dd_time_worn" 1.0

 

; OnActorRemovedDD.sltscript

;================

; presumably if the actor no longer has any DD gear on them, you would remove them from the tracker
; in this case, the script would check to see if any remaining gear is on the actor in question
; and if not, removes them
if $no_remaining_dd_gear
    objtracker_object_delete "dd_time_worn_tracker" $system.self
endif

 

; ListCheck.sltscript

;================

; Fetch any actor (formID most likely is what I will store but idk) where dd_time_worn is over 24 hours
set $bondage_lovers resultfrom objtracker_object_valueget_above "dd_time_worn_tracker" "dd_time_worn" 24.0
set $list_count resultfrom listcount $bondage_lovers
msg_notify $"You are currently tracking ${list_count} bondage lovers."

 

 

Thoughts?

Posted
2 hours ago, hextun said:

Thoughts?

 

yes that would be even more awesome. because like you said, better performance-wise + no need to write all the code i shared.

 

2 hours ago, hextun said:

objtracker_value_create <objtracker_key> <value_name> <value_type> ; start tracking the value identified by value_name, of the indicated type (i.e. int, float, etc) for all tracked objects for the specified objtracker

 

There is one thing you need to decide imo that I didn't see fully in your reply (also applies to my initial proposition): typing.

You need to decide which type(s) you cover. Strings, ints. timesamps? nested arrays?

in my example: its an array of ints. The date one I'm not too sure how i hacked it lol (thank you claude).

this is an important design choice to make now because it will affect for example what you said about comparing values.

 

I would recommend a MINIMAL number of types handled. maybe just strings and ints. 

 

And another consideration - for this one as well, you are the judge whether relevant or not:

  • a key principle of this system we are talking about is top of the hour/regular updates
  • therefore it needs to be optimized to handle a batch update of 1 to n characters

this comes into play at 2 levels:

  1. batch logic. the base form is the for loop = for 1 to n ... update record (every time, all records in the array checked). i suppose that's good enough and probably the only way
  2. time logic
    1. = how do you track the last update of each variable?
    2. and should you update all values at once every time? should you check when they were last updated? pointing this out because values are somewhat independent in the array. and they are both updated by top of the hour, and individually during and after SL scenes
    3. my version is a bit primitive: i have booleans for whether a DD is worn RIGHT NOW, and a time field for each DD slot to know when it was last equipped
Posted

I'm going to post this in the club, too, and probably add it to the main page as well, because it's pretty important for performance tweaking.

 

SLTriggers Redux is *extremely* Papyrus intense, so anything you can do to improve scripting performance will have a *dramatic* impact on SLTScript performance.

 

To that end, I strongly recommend the following to maximize the performance of your SLTScripts.

 

- Install Papyrus Tweaks NG

- In SKSE/Plugins/PapyrusTweaks.ini (a reference copy of which is available in the Files tab on Nexusmods) make the following changes:

- iMaxOpsPerFrame = 2000

- bSpeedUpNativeCalls = true

- bIgnoreMemoryLimit = true

 

For reference, I use several modlists for testing, including Skyrim Modding Essentials (SME), available on Wabbajack. This is a somewhat bare list meant as a springboard for creating your own modlist. While it includes Papyrus Tweaks NG, it only includes the unmodified reference copy of PapyrusTweaks.ini.

 

I install SLTriggers Redux with the "Test Scripts" enabled. To run them, open your console in game and:

- prid player

- slt run zz_sltr_regression_test

 

This will begin a set of SLTScripts which will send output to your sl-triggers.log file. I keep Notepad++ running, use "View | Always On Top", and "View | Monitoring (tail -f)" on sl-triggers.log so that I can see the output in real time and watch for failures, as well as knowing when the scripts have completed.

 

Using the baseline reference copy of PapyrusTweaks.ini on SME, the full suite of regression test scripts takes roughly 140 seconds to run.

 

Using just those 3 changes to PapyrusTweaks.ini, the execution time drops to just over 14 seconds.

 

Not a 10% reduction in execution time but a whopping 90% reduction.

 

I totally agree that SLTScripts are slow compared to actual compiled Papyrus, but you can get a lot of performance by making these changes.

 

 

Posted

v0.985 is out. Out and about. Give it a shout. See what it's about. It doesn't have gout. Oh, please, don't pout. Out.

 

Yeeeeahh... so anyway, that was weird, right? Well, on to what this drop is about.

 

The wait loop is removed. It turned out to be more straightforward than I thought. I saw a roughly 10% improvement in performance in my very basic testing. Also the timings I mentioned in the post above were using this version.

 

Yeah, that's pretty much it. I felt it was important enough to go ahead and release.

 

We are now quiesced.

Posted
1 hour ago, hextun said:

v0.985 is out. Out and about. Give it a shout. See what it's about. It doesn't have gout. Oh, please, don't pout. Out.

 

Yeeeeahh... so anyway, that was weird, right? Well, on to what this drop is about.

 

The wait loop is removed. It turned out to be more straightforward than I thought. I saw a roughly 10% improvement in performance in my very basic testing. Also the timings I mentioned in the post above were using this version.

 

Yeah, that's pretty much it. I felt it was important enough to go ahead and release.

 

We are now quiesced.

 

I feel like I'm underselling things a bit. So what is the "wait loop"?

 

Say you have an SLTScript like so:

 

1: set $counter 1
2: [loopy]
3: msg_notify $"At counter {counter}"
4: inc $counter 1
5: util_wait 5
6: goto [loopy]

 

Note: I added line numbers.

 

Ugly little thing that loops forever (I don't suggest you run this because it will literally never quit until/unless you reload an older save or perform an SLTR reset), it increments a counter an updates a message with the counter value each 5 seconds.

 

Lines 1, 2, 4, and 6 all execute "fully inside the engine". That is, during the execution loop, which goes line by line executing the command, those all use intrinsics that are directly performed without leaving that main loop.

 

Lines 3 and 5, however, call functions, specifically "msg_notify" and "util_wait". These functions, because they are not part of the core language, are sent to the plugin .dll, which finds the implementing Papyrus function and sends the request.

 

At this point, in v0.984 and older, two things start happening:

- the execution loop starts sitting inside a relatively tight loop ** this is the "wait loop" **, waiting for 0.01 seconds (which increments by 0.01 each loop, so first waiting 0.01 seconds, then 0.02 seconds, etc. up to 1 second wait times) until the requested operation completes 

- the implementing Papyrus function does its work and then notifies the execution loop it is complete

 

Once the completion flag is set, the "wait loop" stops and the execution loop proceeds.

 

This is, to say the least, non-optimal, and something I have been wanting to eliminate for awhile. But aside from long running commands (like, say, "util_wait 600", which would keep that wait loop running on 1 second loops for 10 minutes), most functions return quickly enough that the amount of time spent in the wait loop itself is relatively small. Still, it's script time that just wasn't doing anything.

 

When I had first implemented this, I had actually meant to implement this via events or some other non-loop-flag method, but for whatever reason it just didn't click.

 

The clickening has occurred however.

 

Now what happens when a function, like on lines 3 and 5, is reached:

- the execution loop just stops processing altogether, but leaves important flags set to let it know basically where to pick back up

- once the implementing Papyrus function completes, it's no longer just setting a flag for the wait loop to stop; instead it restarts the execution loop where it left off

 

No more "wait loop". Roughly 10% speed improvement in my tests and less script load overall.

Posted
1 hour ago, hextun said:

 

I feel like I'm underselling things a bit. So what is the "wait loop"?

 

Say you have an SLTScript like so:

 

1: set $counter 1
2: [loopy]
3: msg_notify $"At counter {counter}"
4: inc $counter 1
5: util_wait 5
6: goto [loopy]

 

Note: I added line numbers.

 

Ugly little thing that loops forever (I don't suggest you run this because it will literally never quit until/unless you reload an older save or perform an SLTR reset), it increments a counter an updates a message with the counter value each 5 seconds.

 

Lines 1, 2, 4, and 6 all execute "fully inside the engine". That is, during the execution loop, which goes line by line executing the command, those all use intrinsics that are directly performed without leaving that main loop.

 

Lines 3 and 5, however, call functions, specifically "msg_notify" and "util_wait". These functions, because they are not part of the core language, are sent to the plugin .dll, which finds the implementing Papyrus function and sends the request.

 

At this point, in v0.984 and older, two things start happening:

- the execution loop starts sitting inside a relatively tight loop ** this is the "wait loop" **, waiting for 0.01 seconds (which increments by 0.01 each loop, so first waiting 0.01 seconds, then 0.02 seconds, etc. up to 1 second wait times) until the requested operation completes 

- the implementing Papyrus function does its work and then notifies the execution loop it is complete

 

Once the completion flag is set, the "wait loop" stops and the execution loop proceeds.

 

This is, to say the least, non-optimal, and something I have been wanting to eliminate for awhile. But aside from long running commands (like, say, "util_wait 600", which would keep that wait loop running on 1 second loops for 10 minutes), most functions return quickly enough that the amount of time spent in the wait loop itself is relatively small. Still, it's script time that just wasn't doing anything.

 

When I had first implemented this, I had actually meant to implement this via events or some other non-loop-flag method, but for whatever reason it just didn't click.

 

The clickening has occurred however.

 

Now what happens when a function, like on lines 3 and 5, is reached:

- the execution loop just stops processing altogether, but leaves important flags set to let it know basically where to pick back up

- once the implementing Papyrus function completes, it's no longer just setting a flag for the wait loop to stop; instead it restarts the execution loop where it left off

 

No more "wait loop". Roughly 10% speed improvement in my tests and less script load overall.

 

sorry i didnt get it, how did you improve wait loop? it's faster now but we can still use wait right?

Posted
3 hours ago, Fraying9981 said:

 

sorry i didnt get it, how did you improve wait loop? it's faster now but we can still use wait right?

 

And I was so proud of my explanation. :)

 

So, this has nothing to do with the "util_wait" (or various other "wait" functions). 

 

Instead, this is an internal, SLTriggers Redux script execution engine level fix.

 

Before, anytime your .sltscript called a function documented here (Function Libraries · sltriggersredux Wiki), while that function was executing, the SLTriggers Redux "engine" was still idling, by sitting inside the equivalent of a "while executing_function/Utility.Wait(0.01)" loop. When the function in question would complete, it would set "executing_function" to false, breaking the tight loop and allowing the "engine" to move to the next line of your script. I call this the "wait loop" (and maybe I should have called it the "function execution wait loop" but in my head it was much easier to just think "wait loop"). The specifics were a *little* more complicated than that, but that's the general idea.

 

Now, with this latest change, when you call a function documented here (Function Libraries · sltriggersredux Wiki), the "engine" literally just stops running altogether. No more tight "Utility.wait(0.01)" loop. Instead, at the same point after a function finishes running where it would have previously set "executing_function" to false, it kick starts the "engine" again, letting it continue where it had left off.

 

Papyrus Code (yes, actual Papyrus code) incoming:

 

The "engine" of SLTriggers Redux is implemented by a pair of Papyrus functions in the "sl_triggersCmd.psc" script: "RunScript()" and "RunCommandLine()". They are pretty hefty and go beyond the scope of dumping here, but basically:

- RunScript(): iterates the currently running .sltscript and calls RunCommandLine() for each line in the script

- RunCommandLine(): parses the line and either runs the command internally (for things like "set") or, for functions documented here(Function Libraries · sltriggersredux Wiki), calls "RunOperationOnActor()" to ask the .dll to call the implementing Papyrus function.

 

So, when your script hits a function documented here (Function Libraries · sltriggersredux Wiki), it at some point calls my Papyrus function "RunOperationOnActor" with a string[] parameter that includes the tokens from the command to run. So for an .sltscript line that said:

 

  perk_addpoints 10

 

RunOperationOnActor would get called with string[] parameter:

  0: "perk_addpoints"

  1: "10"

 

== RunOperationOnActor: Before ================================================

Spoiler
Function RunOperationOnActor(string[] opCmdLine)
    if !opCmdLine.Length
        return
    endif
    if IsResetRequested || !SLT.IsEnabled || SLT.IsResetting
        SFI("SLTReset requested(" + IsResetRequested + ") / SLT.IsEnabled(" + SLT.IsEnabled + ") / SLT.IsResetting(" + SLT.IsResetting + ")")
        CleanupAndRemove()
        Return
    endif

    runOpReturnedValue = false
    runOpPending = true
    bool success = sl_triggers_internal.RunOperationOnActor(CmdTargetActor, self, opCmdLine)
    if !success
        runOpPending = false
        return
    endif
    float afDelay = 0.0

    while runOpPending && isExecuting
        if afDelay < 1.0
            afDelay += 0.01
        endif
        
        if IsResetRequested || !SLT.IsEnabled || SLT.IsResetting
            SFI("SLTReset requested(" + IsResetRequested + ") / SLT.IsEnabled(" + SLT.IsEnabled + ") / SLT.IsResetting(" + SLT.IsResetting + ")")
            CleanupAndRemove()
            Return
        endif

        Utility.Wait(afDelay)
    endwhile
EndFunction

 

 

== RunOperationOnActor: Now ================================================

Spoiler
Function RunOperationOnActor(string[] opCmdLine, bool runOpForResultfrom = false)
    if !opCmdLine.Length
        return
    endif
    if IsResetRequested || !SLT.IsEnabled || SLT.IsResetting
        SFI("SLTReset requested(" + IsResetRequested + ") / SLT.IsEnabled(" + SLT.IsEnabled + ") / SLT.IsResetting(" + SLT.IsResetting + ")")
        CleanupAndRemove()
        Return
    endif

    runOpReturnedValue = false
    runOpPending = true
    resultfromRunOpPending = runOpForResultfrom
    bool success = sl_triggers_internal.RunOperationOnActor(CmdTargetActor, self, opCmdLine)
    if !success
        runOpPending = false
        return
    endif
EndFunction

 

 

Note first, that mostly all that has happened is the removal of the whole big block of code at the end with the "while/Utility.Wait(afDelay)/endwhile" loop. There are also some bookkeeping flags that were added, but mostly the loop is gone. That's the "wait loop". Literally any time you ran a function that is documented here(Function Libraries · sltriggersredux Wiki), this loop was *also* running, waiting for the function to finish.

 

Note also, the call to "sl_triggers_internal.RunOperationOnActor"? That is telling the .dll to go find the Papyrus function that implements, in this case "perk_addpoints", and send it the arguments in question. Here is the Papyrus code that implements "perk_addpoints" (this hasn't changed):

 

== perk_addpoints: Unchanged ================================================

Spoiler
function perk_addpoints(Actor CmdTargetActor, ActiveMagicEffect _CmdPrimary, string[] param) global
	sl_triggersCmd CmdPrimary = _CmdPrimary as sl_triggersCmd

	if ParamLengthEQ(CmdPrimary, param.Length, 2)
		Game.AddPerkPoints(CmdPrimary.ResolveInt(param[1]))
	endif

	CmdPrimary.CompleteOperationOnActor()
endFunction

 

 

This is pretty close to the barebones you can get for the implementation of any function library call (i.e things that would be documented here, Function Libraries · sltriggersredux Wiki, and that are not part of the core script language).

 

Note in particular the call to "CmdPrimary.CompleteOperationOnActor()". That is what tells the engine "hey, I'm back, start processing the rest of the script".

 

== CompleteOperationOnActor: Before ================================================

Spoiler
Function CompleteOperationOnActor()
    runOpPending = false

    if !runOpReturnedValue
        InvalidateMostRecentResult()
    endif

    if IsResetRequested || !SLT.IsEnabled || SLT.IsResetting
        SFI("SLTReset requested(" + IsResetRequested + ") / SLT.IsEnabled(" + SLT.IsEnabled + ") / SLT.IsResetting(" + SLT.IsResetting + ")")
        CleanupAndRemove()
        Return
    endif
EndFunction

 

 

== CompleteOperationOnActor: After ================================================

Spoiler
Function CompleteOperationOnActor()
    runOpPending = false

    if !runOpReturnedValue
        InvalidateMostRecentResult()
    endif

    if IsResetRequested || !SLT.IsEnabled || SLT.IsResetting
        SFI("SLTReset requested(" + IsResetRequested + ") / SLT.IsEnabled(" + SLT.IsEnabled + ") / SLT.IsResetting(" + SLT.IsResetting + ")")
        CleanupAndRemove()
        Return
    endif

    RunScript()
EndFunction

 

 

Note the additional call to "RunScript()" at the end? That's pretty much it.

 

And that's it. That's (basically) the change. And because that tight "Utility.Wait()" loop is now gone, "RunScript()" isn't sitting there, using up execution cycles to wait in place while the Papyrus invoked by "RunOperationOnActor()" wraps up.

 

Final note: You may look at this and think "hey, is this somehow introducing a sort of recursion issue within the Papyrus VM?" Nope; each use of "sl_triggers_internal.RunOperationOnActor()" to call the .dll results in, basically, a new callstack within the VM because it is dispatching a new request. Functionally similar to dispatching an event. Say you had an .sltscript that was:

 

  perk_addpoints 1

  perk_addpoints 1

 

When the "CompleteOperationOnActor()" call for the first perk_addpoints ran "RunScript()", and the .sltscript reached the next line to call "sl_triggers_internal.RunOperationOnActor()" for the next "perk_addpoints" call, it's "RunScript()" would return immediately, basically ending that callstack. The new call dispatched by the .dll would start a new callstack that itself would finish with its own call to "CompleteOperationOnActor()" and when the end of the script was reached, that call to "RunScript()" would see that and properly terminate the entire .sltscript and tell the ActiveMagicEffect to dispel itself.

 

 

Posted (edited)

Working on expanding the Devious Devices NG bindings to include:

 

        dd_get_device_name: return the name of the worn device specified by slot OR keyword
        dd_is_wearing_device: return true if wearing a device (even if blockgeneric or questitem) specified by slot OR keyword
        dd_is_generic_device: return true if wearing a generic specified by slot OR keyword
        dd_unlock: unlocks the device specified by slot OR keyword
        dd_is_lock_jammed: returns true if device specified by slot OR keyword is jammed
        dd_jam_lock: jams the lock of the device specified by slot OR keyword is jammed
        dd_unjam_lock: unjams the lock of the device specified by slot OR keyword is jammed
        dd_is_lock_manipulated: returns true if device specified by slot OR keyword is manipulated
        dd_set_lock_manipulated: sets device specified by slot OR keyword to be manipulated
        dd_set_lock_unmanipulated: sets device specified by slot OR keyword to be unmanipulated
        dd_play_horny_animation: plays the horny animation for the specified actor
        dd_orgasm: performs a Devious Devices orgasm event
        dd_masturbate: start a Devious Devices masturbation scene
        dd_edge_actor: start a Devious Devices edge actor event
        dd_chastity_belt_struggle: start a Devious Devices chastity belt struggle event
        dd_trip: start a Devious Devices trip event
        dd_catch_breath: start a Devious Devices catch breath event
        dd_has_breasts_exposed: Returns true if breasts are exposed, false otherwise
        dd_inflate_anal_plug: inflates an anal plug
        dd_inflate_vaginal_plug: inflates a vaginal plug
        dd_inflate_random_plug: inflates a random plug
        dd_deflate_anal_plug: deflates an anal plug
        dd_deflate_vaginal_plug: deflates a vaginal plug
        dd_lock: lock an item in actor's inventory onto an actor

 

Note that dd_unlockslot already exists, but dd_unlock will allow for either slot or keyword specification as described above.

 

I've implemented them all but have only tested the first four (dd_get_device_name, dd_is_wearing_device, dd_is_generic_device, and dd_unlock).

 

Also, new canned script, UnlockSelectedDevice.sltscript, which will use UILIB to popup a list of currently worn generic devices to give you the option to cancel, unlock all, or unlock the selected device.

 

(fourth edit?) Though now I think about it, I could also create a "Device Management" script that lets you choose one of the options above (unlock, jam, unjam, manipulate, unmanipulate) or other general actions (orgasm, masturbate, inflate, trip, struggle) with the associated device picker (if needed).

Edited by hextun
Posted

Okay, reporting progress:

 

I've managed to test all of the newly minted DD bindings to my satisfaction. Jams, Unjams, Manipulates, Unmanipulates, Locks, Unlocks. My poor DB probably wonders why those pesky collars keep acting up! And that's not to mention the inflatable plugs *brr*. And the trips, breath catching, chastity belt struggles (which, of course, you have to be wearing a belt for that to work), and other animations.

 

One fun fact: with my removal of the "wait loop" logic I messed up handling of unknown functions. Right now if you are on 985, and your script contains a typo (think "mgs_nytofi" instead of "msg_notify"), instead of just emitting a warning and the script moving on, the script just stops. The magic effect will just sit there not doing anything (so there's no script or engine performance impact) and will silently end after accruing 24 hours of real time, but will otherwise just be taking up one of the 30 available slots for that actor. This probably won't be a problem unless the same script keeps getting run and hanging. An SLT reset should fix it but of course it will occur again if you run the script again.

 

Anyhow, next release will have a fix for that.

 

Funny story: I discovered it while running my test scripts. I accidentally tried to run "dd_is_lock_UNmanipulated" (when there is only "dd_is_lock_manipulated") and had to take a moment to work out why my script just started dying there.

 

Other funny story: I spent more than a little time troubleshooting "dd_is_lock_jammed", which should be a ridiculously simple implementation, because in my test script I had not included the target actor parameter. It wasn't until I dug into sl-triggers.log that I realized what was going on. :P

 

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...