hextun Posted June 28, 2025 Author Posted June 28, 2025 9 minutes ago, MannySauce said: A question regarding variables - is it possible to add a prefix when a function uses it? Let's say I do: actor_race $system.partner "" set $local.Partner1Race $$ msg_console $"DEBUG: Partner race is {local.Partner1Race}" call $local.Partner1Race Can I somehow make the "call" section get "prefixRace" instead of "Race"? call prefix$local.Partner1Race ? call $"prefix{local.Partner1Race}" ? I have not tested it, but I think your last suggestion should work: call $"prefix{local.Partner1Race}" ; also, remember, you don't *need* to specify 'local' as that's the default, so this is fine, too: call $"prefix{Partner1Race}" ; in case that is more readable As I see that working, $"prefix{local.Partner1Race}" should get interpolated to be "prefixBreton" (for example). Then that should make the final result equivalent to "call prefixBreton" which should look for "prefixBreton.ini" or "prefixBreton.json" etc. If it's not too much hassle I would suggest trying it; you could add a msg_notify in your "prefixBreton.ini" or whatever the first time to make sure you're getting called, in case otherwise the script's effects would not be immediately obvious.
MannySauce Posted June 28, 2025 Posted June 28, 2025 17 minutes ago, hextun said: I have not tested it, but I think your last suggestion should work: call $"prefix{local.Partner1Race}" ; also, remember, you don't *need* to specify 'local' as that's the default, so this is fine, too: call $"prefix{Partner1Race}" ; in case that is more readable As I see that working, $"prefix{local.Partner1Race}" should get interpolated to be "prefixBreton" (for example). Then that should make the final result equivalent to "call prefixBreton" which should look for "prefixBreton.ini" or "prefixBreton.json" etc. If it's not too much hassle I would suggest trying it; you could add a msg_notify in your "prefixBreton.ini" or whatever the first time to make sure you're getting called, in case otherwise the script's effects would not be immediately obvious. Did indeed work. 👍
hextun Posted June 29, 2025 Author Posted June 29, 2025 I just came to say: [2025-06-29 15:24:23.942] [log] [debug] [sl_triggers.cpp:145] Core.Send_SLTR_OnPlayerActivateContainer containerRef([ObjectReference < (000B6CE1)>]) corpse(TRUE) empty(False) [2025-06-29 15:24:24.027] [log] [debug] [sl_triggers.cpp:145] Core.HandlePlayerContainerActivation [2025-06-29 15:24:24.061] [log] [debug] [sl_triggers.cpp:145] Core.OnSLTRContainerActivate fcontainerRef([ObjectReference < (000B6CE1)>]) / containerRef([ObjectReference < (000B6CE1)>]) / isConCorpse(TRUE) / isConEmpty(False) / fplocKeywd([Keyword <LocTypeDungeon (000130DB)>]) / kwpLocation([Keyword <LocTypeDungeon (000130DB)>]) [2025-06-29 15:24:38.264] [log] [debug] [sl_triggers.cpp:145] Player changed cells [2025-06-29 15:24:38.502] [log] [debug] [sl_triggers.cpp:145] Core.HandleOnPlayerCellChange: isNewGameLaunch(False) / isNewSession(False) [2025-06-29 15:24:38.519] [log] [debug] [sl_triggers.cpp:145] Core.OnSLTRPlayerCellChange: isNewGameLaunch:False / isNewSession: False Which, when translated, roughly means "I've got container activation and cell change triggers hooked up; I'm doing internal testing now, but that means new release soon". This one will not be direct save compatible; so at minimum, you would need to try to save/remove SLTR/load/save/install new SLTR/load. I introduced a number of new objects to the .esp, and also modified properties of several important objects, not to mention a number of changes for fragments added in CreationKit. Anyhoo, more to come soon.
hextun Posted June 29, 2025 Author Posted June 29, 2025 v122 is up and available for your SLTScript-ilicious needs. New: - New Core event: "Player Cell Change" - New Core event: "Player Loading Screen" (meh... it was there) - New Core event: "Player Opened Container" (past tense) - Improved parsing speed (reduced time between a trigger being requested and beginning of script execution; much more noticeable on larger scripts (like the regression test script which has over 250 active lines of script i.e. excluding blank lines)) - Improved variable resolution speed Note: Container activation logic has been graciously provided by @Bane Master (so big thanks there), though I have modified it and assume all responsibilities for my version. Of import: your SLTScript attached to a container open event is not likely to run until AFTER the container window closes. Why is this when the original code comes from Devious Enchanted Chests and their logic runs beforehand? Good question and it's because SLTScript is run as a result of casting a spell on the target actor, the Player in this case. But because casting a spell isn't a matter of "CastSpell() ; now the spell is there" instead it is "CastSpell() ; the VM has been notified of your request". So unlike DEC, where the developer knows precisely what code needs to be run, including the possibility of acting before the menu opens, with SLTScript I don't have a way to know before your script runs that you might have wanted to do something that might have prevented said container from being opened. Or from the contents being manipulated, etc. I'm aware of this limitation and while I'm not sure there will ever be a good way here to get that logic, I'm also aware that that would be preferable. Additionally, you will find a new file in your install, containers.json. It contains lists for being able to assign/modify what constitutes a "basic container" as well as being able to add additional containers (i.e. for mods that add their own containers). Examples are contained therein. I'm going to take a pass at the wiki next, to shore up the documentation. I'm also going to be looking at making a NexusMods release. 1
hextun Posted June 30, 2025 Author Posted June 30, 2025 I need to give better access to contextual variables related to the events that spawned your scripts. Things like the initial scriptname, the original script launch gametime/realtime, each script launch gametime/realtime, things like a reference to triggering objects, like the container that you opened. More to come here.
Fraying9981 Posted July 1, 2025 Posted July 1, 2025 With this version can you make a specific actor for example the one in the victim position, say something? As in, say something in game (not as notification) like: "I was an adventurer once then i took an arrow in the knee"
hextun Posted July 1, 2025 Author Posted July 1, 2025 (edited) 8 hours ago, Fraying9981 said: With this version can you make a specific actor for example the one in the victim position, say something? As in, say something in game (not as notification) like: "I was an adventurer once then i took an arrow in the knee" Okay... I'm a dork. The direct answer to your question is "yes, but". Here's the 'actor_say' documentation from the SLTR wiki: actor_say Description Causes the actor to 'say' the topic indicated by FormId; not usable on the Player Parameters actor: target Actor topic: TOPIC FormID Example actor_say $actor "Skyrim.esm:1234" So to start with, yes, you have the ability to have an actor "say" the content of a TOPIC FormID. Note: the comment about not being usable on the Player comes from the original documentation provided by Fotogen, which I did not alter. Note further that I actually removed that constraint in my version, but never updated the comment. I do not see anything in the CreationKit documentation that restricts this from being run by the Actor, but as far as I understand, "saying" or playing back a TOPIC is usually done by NPCs to follow a dialog branch, not the Player, so I'm uncertain of the impact here. Remember, playing back a Dialog in Skyrim is more than just printing text and making sounds; it can also be running scripts. Also, note this from the CreationKit documentation for ObjectReference.Say() (where Actor.Say gets it from): Quote If used on an actor and that actor attempts to initiate normal dialogue (for example a random greeting) while saying something through Say(), the game will crash to desktop. Now, in your case, you're talking about using it explicitly with the Player, who shouldn't be attempting to initiate greetings; unless (and it just occurred to me to consider) literally that means you cannot e.g. click to speak to someone during the "Say" because that counts as "initiating a greeting". I'm just saying there is enough jank involved here that I'm not 100% cool with suggesting it. Also, please read my notes below about possible side effects. So that is the difference between a SOND (Sound) record and a DIAL or INFO record. First off, the specific records you are talking about are: QUST: DialogueGuardsGeneral 0002BE3C (look on the Misc tab) / DIAL: 0002BDDD (has no editorID) / INFO: 00020954 (has no editorID) A SOND record in an .esm or .esp has a known FormID and is accessible via that FormID. If you go into CreationKit, the Audio section is where you find these. These are almost always going to be ambient noises, things like heartbeats, level ups, impact sounds from weapons, and so on. For example, the shipped script "Heart beat(A).ini": snd_play "skyrim.esm:318128" $system.self set $1 $$ snd_setvolume $1 1.0 util_waitforend $system.self snd_stop $1 references "skyrim.esm:318128", which is the same as "skyrim.esm|0x0004DAB0", and 0x0004DAB0 in Skyrim.esm refers to the SOND record UIHeartbeat or somesuch (that was from memory, sorry). Being a SOND record with a FormID we can call the CreationKit Sound.Play(Actor _actorToTargetSoundOn) and play it directly The DIAL (Dialogue) records contain INFO (Information) records that each contain the text and, when available, indications of whether they have the appropriate LIP file (for lip sync animation) and sound and so on. The sound file itself is contained in, in your specific case "<Skyrim folder>/Data/Skyrim - Voices_en0.bsa" (for my US EN install), and, that file itself being an archive, the very specific file inside is "/sound/voice/skyrim.esm/maleguard/dialogueguardsgeneral__00020954_1.fuz". This poses a few problems. First, I have yet to see any simple API that says "make this Actor play back this lip sync and sound". The closest I have seen are ways to "make this Actor play this Dialog topic" BUT dialogs can and often do have scripts/fragments attached to them, and playing them back can have side effects. Even if the base game doesn't, there's no way to know if a mod hasn't added effects. And all we want is audio and maybe lip sync. Second, the filenames are munged and not consistently. Staying fully within the boundaries of Papyrus script, I'm not sure how to go from "I have a Dialogue/Info topic 00020954" to "play back the BSA file at .../maleguard/dialogueguardsgeneral__00020954_1.fuz". As you can see, however, the ID *is* in the name. All of which to say, I might possibly... MAYBE... see a way to do what needs through use of the SKSE plugin. But it's one of those things where my first thought is "surely if it was this easy, someone would have already released a mod to do it", so I have doubts. Still, I'll give it a shot. The approach goes roughly: // sl-triggers is a CommonLibSSE-NG 3.7.0 based mod // while this code worked with Intellisense, I have no idea if it will work in reality RE::BSResource::ID bsaid; bsaid.GenerateFromPath("sound/voice/skyrim.esm/maleguard/dialogueguardsgeneral__00020954_1.fuz"); RE::BSSoundHandle soundHandle; // not sure what the flags will need to be, or the priority RE::BSAudioManager::GetSingleton()->BuildSoundDataFromFile(soundHandle, bsaid, 0, 0); soundHandle.SetPosition(cmdTarget->GetPosition()); soundHandle.Play(); Maybe it will work. Maybe it won't. If it does, then, at the very worst, it would require someone to dig up the path in the BSA, only a bit more onerous than having to find the dialog topic in the first place. If I can easily cache the file entries, searching by formID becomes trivial (i.e. *__<formid>_1.fuz or the like). And of course, there are other things. For example, Actor::PlayASound(BSSoundHandle, FormID,...) exists, which sounds great, but what is the FormID for? I'll have to investigate. Maybe that would be the better final option. Or maybe that's the same as the Say() option. Another example, I'm not sure if a BSSoundHandle automatically plays once and stops or loops? Does it need to be cleaned up after? I don't want to leak resources. Anyway, thanks for the research topic. I'll let you know if I turn up anything. Edited July 1, 2025 by hextun updated because boneheaded 1
Fraying9981 Posted July 1, 2025 Posted July 1, 2025 24 minutes ago, hextun said: So that is the difference between a SOND (Sound) record and a DIAL or INFO record. First off, the specific records you are talking about are: QUST: DialogueGuardsGeneral 0002BE3C (look on the Misc tab) / DIAL: 0002BDDD (has no editorID) / INFO: 00020954 (has no editorID) A SOND record in an .esm or .esp has a known FormID and is accessible via that FormID. If you go into CreationKit, the Audio section is where you find these. These are almost always going to be ambient noises, things like heartbeats, level ups, impact sounds from weapons, and so on. For example, the shipped script "Heart beat(A).ini": snd_play "skyrim.esm:318128" $system.self set $1 $$ snd_setvolume $1 1.0 util_waitforend $system.self snd_stop $1 references "skyrim.esm:318128", which is the same as "skyrim.esm|0x0004DAB0", and 0x0004DAB0 in Skyrim.esm refers to the SOND record UIHeartbeat or somesuch (that was from memory, sorry). Being a SOND record with a FormID we can call the CreationKit Sound.Play(Actor _actorToTargetSoundOn) and play it directly The DIAL (Dialogue) records contain INFO (Information) records that each contain the text and, when available, indications of whether they have the appropriate LIP file (for lip sync animation) and sound and so on. The sound file itself is contained in, in your specific case "<Skyrim folder>/Data/Skyrim - Voices_en0.bsa" (for my US EN install), and, that file itself being an archive, the very specific file inside is "/sound/voice/skyrim.esm/maleguard/dialogueguardsgeneral__00020954_1.fuz". This poses a few problems. First, I have yet to see any simple API that says "make this Actor play back this lip sync and sound". The closest I have seen are ways to "make this Actor play this Dialog topic" BUT dialogs can and often do have scripts/fragments attached to them, and playing them back can have side effects. Even if the base game doesn't, there's no way to know if a mod hasn't added effects. And all we want is audio and maybe lip sync. Second, the filenames are munged and not consistently. Staying fully within the boundaries of Papyrus script, I'm not sure how to go from "I have a Dialogue/Info topic 00020954" to "play back the BSA file at .../maleguard/dialogueguardsgeneral__00020954_1.fuz". As you can see, however, the ID *is* in the name. All of which to say, I might possibly... MAYBE... see a way to do what needs through use of the SKSE plugin. But it's one of those things where my first thought is "surely if it was this easy, someone would have already released a mod to do it", so I have doubts. Still, I'll give it a shot. The approach goes roughly: // sl-triggers is a CommonLibSSE-NG 3.7.0 based mod // while this code worked with Intellisense, I have no idea if it will work in reality RE::BSResource::ID bsaid; bsaid.GenerateFromPath("sound/voice/skyrim.esm/maleguard/dialogueguardsgeneral__00020954_1.fuz"); RE::BSSoundHandle soundHandle; // not sure what the flags will need to be, or the priority RE::BSAudioManager::GetSingleton()->BuildSoundDataFromFile(soundHandle, bsaid, 0, 0); soundHandle.SetPosition(cmdTarget->GetPosition()); soundHandle.Play(); Maybe it will work. Maybe it won't. If it does, then, at the very worst, it would require someone to dig up the path in the BSA, only a bit more onerous than having to find the dialog topic in the first place. If I can easily cache the file entries, searching by formID becomes trivial (i.e. *__<formid>_1.fuz or the like). And of course, there are other things. For example, Actor::PlayASound(BSSoundHandle, FormID,...) exists, which sounds great, but what is the FormID for? I'll have to investigate. Maybe that would be the better final option. Another example, I'm not sure if a BSSoundHandle automatically plays once and stops or loops? Does it need to be cleaned up after? I don't want to leak resources. Anyway, thanks for the research topic. I'll let you know if I turn up anything. Thanks for the detailed explanation. I'm actually looking for sth simpler: actor says text, but no audio or lip sync is needed. A bit like when mods dont have voice files. A dialogue line if you prefer.
hextun Posted July 1, 2025 Author Posted July 1, 2025 (edited) 4 hours ago, Fraying9981 said: Thanks for the detailed explanation. I'm actually looking for sth simpler: actor says text, but no audio or lip sync is needed. A bit like when mods dont have voice files. A dialogue line if you prefer. So I've done some digging and preliminary tests seem to suggest that I can get the line of text related to a TopicInfo and return it. You'd still need the FormID for said TopicInfo, but that would get it. [2025-07-01 13:07:01.320] [log] [debug] [sl_triggers.cpp:165] hextun_test: TopicInfo from (Skyrim.esm:0x00020954) was ([TopicInfo <topic info 00020954 on quest DialogueGuardsGeneral (0002BE3C)>]) and returned response text (I used to be an adventurer like you. Then I took an arrow in the knee...) [2025-07-01 13:07:01.372] [log] [debug] [sl_triggers.cpp:165] hextun_test: TopicInfo from (Skyrim.esm:0x000AC12C) was ([TopicInfo <topic info 000AC12C on quest DialogueGuardsGeneral (0002BE3C)>]) and returned response text (Hail, Companion.) [2025-07-01 13:07:01.422] [log] [debug] [sl_triggers.cpp:165] hextun_test: TopicInfo from (Skyrim.esm:0x000DA744) was ([TopicInfo <topic info 000DA744 on quest DialogueIvarstead (00021236)>]) and returned response text (Pardon me, milady. Would you care to hear me play my lute?) To be clear however, there are some, for me at least, unresolved ambiguities about how I'm going about getting the result, such that my confidence level on the accuracy is less than 100%. For starters, the function I'm calling, while it does start with the TopicInfo, which is good (since as far as I can tell, INFO records only ever have the one line of text), the object that function returns has, among other things, an entire LIST of strings called "responses". The catch? In every test, the list only ever had one member. Given that said object is built from a combination of the quest, the topic, and the topicinfo, it leads me to believe that in some circumstances it might return multiple strings. Or none? Plus, frankly, it also takes an Actor*, but I'm setting that to nullptr, and surprisingly it seems to be working. It also is ignoring all of the conditions associated, which is good because otherwise ... no. Anyhow, I'll include the functionality in the next release, exposed via the usual bound SLTScript function (probably something like topicinfo_gettext), but consider it an alpha within the beta. Edited July 1, 2025 by hextun 1
Mushano Posted July 2, 2025 Posted July 2, 2025 Really enjoying this so far. Had to convert all of my .json files to work with the new format, but at least that headache is over with! I was wondering if I can ask if you could add a few more functions to the list. I've searched the wiki but I don't think you've included a way to access the functions of an actorbase. Basically for my use purposes, I wanted to: 1. Detect if an actor is unique (aka IsUnique()). 2. Get or Set if Protected. 3. Set if Essential (IsEssential() is in there. Just not SetEssential()). 4. Get or Set if Invulnerable. Thanks for keeping this updated!
hextun Posted July 2, 2025 Author Posted July 2, 2025 58 minutes ago, Mushano said: Really enjoying this so far. Had to convert all of my .json files to work with the new format, but at least that headache is over with! I was wondering if I can ask if you could add a few more functions to the list. I've searched the wiki but I don't think you've included a way to access the functions of an actorbase. Basically for my use purposes, I wanted to: 1. Detect if an actor is unique (aka IsUnique()). 2. Get or Set if Protected. 3. Set if Essential (IsEssential() is in there. Just not SetEssential()). 4. Get or Set if Invulnerable. Thanks for keeping this updated! Let's see... - expose the ActorBase bits generally (similar to the form, objectreference, and actor bits I already did) -- fair enough, though I have other things ahead of it - IsUnique() - available on ActorBase and so will be exposed on actorbase_*; I assume that based on the relationship, the check will apply to the associated Actor - IsInvulnerable() - available in the CK docs, so it will be there when I add actorbase_*; it is also on CLIB RE::Actor, so I could also expose it for actor_* in SLTScript - IsProtected() - same ; same - IsEssential() - same ; same Similarly, SetEssential()/SetUnique()/SetProtected() are all available on (Papyrus) ActorBase. But CLIB does not expose them if they are directly present on RE::Actor. I suspect that setting Essential/Unique/Protected on the ActorBase for *any ol' Actor* is not the way to go. That said, in CLIB, you can get a reference to a struct suspiciously called ACTOR_RUNTIME_DATA with a suspiciously named boolFlags enumeration that has suspiciously named members like kEssential and kProtected, and it may be that simply modifying that flag is sufficient to make things happen. But that all sounds very suspicious so I have my doubts. Worth tinkering... I'd like to enlist help from anyone willing; I can expose endpoints with some simple null checks and such and get that put out (without much if any testing on my part) for others to have a go at. Trying things like modifying an ActorBase to see if that would work for an Actor. I mean, I *can* set it up, but it wasn't on my radar. Whereas throwing down some code that adheres to some APIs, that passes a few rudimentary tests? Piece of kelp.
MannySauce Posted July 2, 2025 Posted July 2, 2025 Made 2 scripts to test for partner1-4 validity since I had a vague memory of an old "call" script messing up compared to the parent script, but I've bumped into an issue with "actor_isvalid" always returning 0. Here's the scripts: "TestPlayerPartnerBase.ini" msg_console "Running TestPlayerPartnerBase.ini on Player" actor_isvalid $system.partner set $global.partner1base $$ msg_console $"partner1 returned {global.partner1base} (Base Script)" actor_isvalid $system.partner2 set $global.partner2base $$ msg_console $"partner2 returned {global.partner2base} (Base Script)" actor_isvalid $system.partner3 set $global.partner3base $$ msg_console $"partner3 returned {global.partner3base} (Base Script)" actor_isvalid $system.partner4 set $global.partner4base $$ msg_console $"partner4 returned {global.partner4base} (Base Script)" call TestPlayerPartnerCall.ini "TestPlayerPartnerCall.ini" msg_console "Running TestPlayerPartnerCall.ini from TestPlayerPartnerBase.ini on Player with call" actor_isvalid $system.partner set $global.partner1call $$ msg_console $"partner1 returned {global.partner1call} (Call Script)" actor_isvalid $system.partner2 set $global.partner2call $$ msg_console $"partner2 returned {global.partner2call} (Call Script)" actor_isvalid $system.partner3 set $global.partner3call $$ msg_console $"partner3 returned {global.partner3call} (Call Script)" actor_isvalid $system.partner4 set $global.partner4call $$ msg_console $"partner4 returned {global.partner4call} (Call Script)" And the trigger, just to be sure. { "float" : { "chance" : 100.0 }, "int" : { "__slt_mod_version__" : 122, "event" : 1, "player" : 1 }, "string" : { "do_1" : "TestPlayerPartnerBase.ini" } }
hextun Posted July 3, 2025 Author Posted July 3, 2025 (edited) 19 hours ago, MannySauce said: Made 2 scripts to test for partner1-4 validity since I had a vague memory of an old "call" script messing up compared to the parent script, but I've bumped into an issue with "actor_isvalid" always returning 0. Here's the scripts: "TestPlayerPartnerBase.ini" msg_console "Running TestPlayerPartnerBase.ini on Player" actor_isvalid $system.partner set $global.partner1base $$ msg_console $"partner1 returned {global.partner1base} (Base Script)" actor_isvalid $system.partner2 set $global.partner2base $$ msg_console $"partner2 returned {global.partner2base} (Base Script)" actor_isvalid $system.partner3 set $global.partner3base $$ msg_console $"partner3 returned {global.partner3base} (Base Script)" actor_isvalid $system.partner4 set $global.partner4base $$ msg_console $"partner4 returned {global.partner4base} (Base Script)" call TestPlayerPartnerCall.ini "TestPlayerPartnerCall.ini" msg_console "Running TestPlayerPartnerCall.ini from TestPlayerPartnerBase.ini on Player with call" actor_isvalid $system.partner set $global.partner1call $$ msg_console $"partner1 returned {global.partner1call} (Call Script)" actor_isvalid $system.partner2 set $global.partner2call $$ msg_console $"partner2 returned {global.partner2call} (Call Script)" actor_isvalid $system.partner3 set $global.partner3call $$ msg_console $"partner3 returned {global.partner3call} (Call Script)" actor_isvalid $system.partner4 set $global.partner4call $$ msg_console $"partner4 returned {global.partner4call} (Call Script)" And the trigger, just to be sure. { "float" : { "chance" : 100.0 }, "int" : { "__slt_mod_version__" : 122, "event" : 1, "player" : 1 }, "string" : { "do_1" : "TestPlayerPartnerBase.ini" } } I'll have a look. Thank you for the report. Also... as part of the cell change handling, following some discussion on how to ensure loading screen transitions were handled, I created my own implementation of the solution suggested here. Again, I give the credit for the idea and accept any blame in my implementation. As part of that second aspect of the implementation, the idea is to use an AI package and a unique, non-interactive, unseen actor, to track when the player enters loading screens. Unfortunately, as I found while testing SES2, currently my invisible friend can potentially get picked up and be used for interactions, as I found out when I was suddenly assaulted in the middle of nowhere. I have got an update to the .esp to make them a ghost, etc. But for now, if you have been seeing any odd interactions with unseen actors, and it happened only after you updated the last SLT .. *raises hand* yup that was *points it at a random spot in the room* definitely him. Edited July 3, 2025 by hextun added link to SES2 https://www.loverslab.com/topic/228597-ses2-preview-release/
hextun Posted July 3, 2025 Author Posted July 3, 2025 Also, this is a really easy topic to discuss but hard to get info on. At question: Continued use of .ini or start using a custom extension e.g. .slt or .sltscript? Reason: A custom extension at least offers an opportunity for improved syntax highlighting in some editors. It also would fully eliminate any conflicts between existing syntax highlighting behavior (like with .ini) in most editors. Alternative: I'm not sure what, as I haven't looked much, but perhaps there is another "common" file format that a lot of editors easily support. I settled on .ini at the time mostly because I was a) moving away from the structured .JSON format and b) just needed any simple flat file text format that wouldn't actually try to get itself processed by a real script engine. .ini came closest. And at that point, too, I was still treating it as trying to do ongoing support for Fotogen's original sl_triggers, and I already felt like it was going out far as it was to add support for any new file type, much introducing an entire new custom script extension. Except now I keep finding it aggravating when the syntax highlighting isn't matching my expectations. I know I could put together something rudimentary for Notepad++ and imagine other editors wouldn't be *too* difficult to set something up for, but I'll be honest, this isn't something I typically tangle with. Custom file extension syntax highlighting? It's a speed bump for me, but arbitrarily changing it to something that suddenly rules out a bunch of folks methods of editing these scripts also sounds like kind of a .. uncool move on my part. My question about the community (and, okay, yeah.. just.. you know... go with it for a moment) is a) roughly what percentage of you view the scripts, b) what percentage view the scripts such that syntax highlighting is helpful/useful, c) same about editing/making your own scripts, d) what editors you use. THIS. IS. NOT. A. TEXT. EDITOR. BATTLE. And, maybe I'm showing my age, or maybe it's never gone out of style, but here, all editors are perfect. I just would like to know what's getting used to get an idea about syntax highlighting support. And at the end of the day, if .ini is perfect for most, good enough for the rest, and just bad for me, that's okay, too. I use .vscode to view but mostly notepad++ to edit. I'm good. And whatever I come up with, I'll share. And also, also... if anyone has a better suggestion on how to handle the syntax highlighting, let me know? Like if there's some convenient options for things like (e.g. stretch goal here) adding in the current library of functions. Here or privately, whatever. And I'm cool with suggestions for improvements to the wiki. And pull requests, though I do confess I've been pretty chaotic until recently. Submit a PR if you're up for it though.
hextun Posted July 3, 2025 Author Posted July 3, 2025 Oh, also, also, also, I see no reason why the vast majority of the current CreationKit and SKSE Papyrus APIs couldn't be bound through SLTScript function libraries. There is the potential for either using existing CK script classes as scopes with associated functions analogous to each (similar to the existing actor_dogetter/do_consumer, but possibly more explicit e.g. actor.getname). I'm working on an iteration that's probably going to go into this pass which should turn the "return result" concept into something actually.. typed. At which point, you (as in you script creators, script runners) would be able to create IMMENSELY SLOWER versions of actual Papyrus script, but without needing to worry about compilation, and with a few additional features to boot. Something roadmappish.
hextun Posted July 3, 2025 Author Posted July 3, 2025 On 7/2/2025 at 11:31 AM, MannySauce said: Made 2 scripts to test for partner1-4 validity since I had a vague memory of an old "call" script messing up compared to the parent script, but I've bumped into an issue with "actor_isvalid" always returning 0. Here's the scripts: "TestPlayerPartnerBase.ini" msg_console "Running TestPlayerPartnerBase.ini on Player" actor_isvalid $system.partner set $global.partner1base $$ msg_console $"partner1 returned {global.partner1base} (Base Script)" actor_isvalid $system.partner2 set $global.partner2base $$ msg_console $"partner2 returned {global.partner2base} (Base Script)" actor_isvalid $system.partner3 set $global.partner3base $$ msg_console $"partner3 returned {global.partner3base} (Base Script)" actor_isvalid $system.partner4 set $global.partner4base $$ msg_console $"partner4 returned {global.partner4base} (Base Script)" call TestPlayerPartnerCall.ini "TestPlayerPartnerCall.ini" msg_console "Running TestPlayerPartnerCall.ini from TestPlayerPartnerBase.ini on Player with call" actor_isvalid $system.partner set $global.partner1call $$ msg_console $"partner1 returned {global.partner1call} (Call Script)" actor_isvalid $system.partner2 set $global.partner2call $$ msg_console $"partner2 returned {global.partner2call} (Call Script)" actor_isvalid $system.partner3 set $global.partner3call $$ msg_console $"partner3 returned {global.partner3call} (Call Script)" actor_isvalid $system.partner4 set $global.partner4call $$ msg_console $"partner4 returned {global.partner4call} (Call Script)" And the trigger, just to be sure. { "float" : { "chance" : 100.0 }, "int" : { "__slt_mod_version__" : 122, "event" : 1, "player" : 1 }, "string" : { "do_1" : "TestPlayerPartnerBase.ini" } } Fix pending; error in resolution of the SexLab partner/partner1/partner2/partner3/partner4 variables.
hextun Posted July 5, 2025 Author Posted July 5, 2025 Heads up, I'll be renaming scripts from .ini to .sltscript and providing syntax highlighting files in extern/ (on the github repo) for Notepad++ and VSCode. .ini files and .sltscript files will be treated/parsed identically, and support remains. But this will allow for better use in an editor. This means that if you just update in place, you will end up with duplicates of all of the built-in scripts. 1
hextun Posted July 6, 2025 Author Posted July 6, 2025 I've pushed the branch for the new update: https://github.com/lynnpye/sl_triggers/tree/v123 And I've updated the wiki: https://github.com/lynnpye/sl_triggers/wiki/Scripts. Of note is the list of new system and request scoped variables. I've some testing to do tomorrow before I release, but it's coming.
Jokekiller Posted July 6, 2025 Posted July 6, 2025 (edited) On 5/4/2025 at 6:20 PM, hextun said: It seems like you would want: - SexLab trigger - Event: Begin - Chance: 100% (I assume) - Player: Partner Player - Do 1: your script And your script would presumably look something like this: NPCNameFeedsPlayer.ini ---------------------- actor_name $self if $$ != "NPCName" notme ; the next few lines are from Alcohol.ini ; this is a random list of vanilla Skyrim alcohols ; if you know what you want to use, you could replace this line and "set $1 $$" with "set $1 "whatevermod.esp:integer form id" rnd_list "skyrim.esm:216158" "skyrim.esm:853342" "skyrim.esm:181082" "skyrim.esm:329930" "skyrim.esm:201531" "skyrim.esm:201532" "skyrim.esm:1079946" "skyrim.esm:901490" "skyrim.esm:758231" ; this puts the random selection into $1 set $1 $$ ; this differs a little from Alcohol.ini in that it targets the player directly instead of self item_adduse $player $1 1 0 [notme] Hi! I've tried this script on version 118 with all the above triggers in mcm the only thing I've changed is the id of item consumed (left only one item), and the name of the NPC "Elivan". It works somewhat, PC do recieve consumable but unfortunately it happens with any partner, not Elivan in particular. Can you help me please to figure out, what am I doing wrong? The script looks like this: actor_name $self if $$ != "Elivan" notme set $1 "skyrim.esm:216158" item_adduse $player $1 1 0 [notme] Edited July 6, 2025 by Jokekiller
hextun Posted July 6, 2025 Author Posted July 6, 2025 12 hours ago, Jokekiller said: Hi! I've tried this script on version 118 with all the above triggers in mcm the only thing I've changed is the id of item consumed (left only one item), and the name of the NPC "Elivan". It works somewhat, PC do recieve consumable but unfortunately it happens with any partner, not Elivan in particular. Can you help me please to figure out, what am I doing wrong? The script looks like this: actor_name $self if $$ != "Elivan" notme set $1 "skyrim.esm:216158" item_adduse $player $1 1 0 [notme] Something I am addressing in the next release (later today) is the fact that since I had moved the SKSE plugin into a separate git repo for a time, the code had gotten out of sync in terms of release tagging/branching, etc. As a result, I cannot verify right now whether the v118 released plugin had this issue. That said, I did find in the current plugin a bug where two things were happening: a) in some cases I was not performing a case insensitive compare on behalf of the Papyrus VM and b) in some cases a returned value still had surrounding double quotes after tokenization. That last one is an internals thing but I've worked on addressing it (it involved incomplete use of the Resolve* functions in the function library code). I've re-merged the SKSE plugin code back into the same repo as the Papyrus code, so going forward (again; I used to have it this way) there will be a clear tie between all aspects of each mod release. Meaning this sort of historical check up will be much easier going forward.
hextun Posted July 6, 2025 Author Posted July 6, 2025 v123 is posted, and github is updated with the latest tag and release. Remember: All of the scripts that come with the mod by default have been renamed from .ini to .sltscript, so if are reinstalling, remember to remove all of the old .ini files. Here are the changelog notes: 123 Savegame compatibility: CLEAN SAVE REQUIRED (New game or (not supported) Uninstalling SLTR, Cleaning your save (e.g. Load, click through the warning, Save; use a cleaner), Reinstall SLTR and reapply your customizations, Load) Note: Reminder that logging output is all sent to <My Documents>\My Games\Skyrim Special Edition\SKSE\sl-triggers.log (or whichever folder you have your SKSE logs directed to) bugfix: fixed SexLab resolution of partner related variables bugfix: fixed sl_triggersCmd.CompleteOperationOnActor(): now correctly marks runOpPending = false before responding to SLTReset bugfix: .esp change; marked the cell change detection actor as IsGhost and SimpleActor bugfix: 'cat' now properly concatenates all parameters bugfix: SLTScripts comment to end of line ';' was being enforced too aggressively i.e. middle of string literals bugfix: some string compares were not case insensitive bugfix: some function library implementations were not calling CmdPrimary.Resolve*() functions before using parameters; this resulted in some values (string literals) remaining wrapped in double quotes bugfix: on initial install, keys were all detected as pressed; the delusion has been cured enhancement: .sltscript file extension (equivalent to .ini) enhancement: Notepad++ syntax highlighting (see extern/lang-support/notepad++) enhancement: VSCode syntax highlighting (see extern/lang-support/vscode) enhancement: added topicinfo_getresponsetext function which returns the dialog text associated with the provided TopicInfo (by FormID or editorID) example: topicinfo_getresponsetext "Skyrim.esm:0x00020954" ; $$ would contain "I used to be an adventurer like you. Then I took an arrow in the knee..." enhancement: added rnd_float function which returns a random float between parameter1 and parameter2, inclusive example: rnd_float 23.0 482.0 set $result $$ enhancement: New system variables: $system.random.100 - result of rnd_float 0 100.0 enhancement: New contextual system variables: $request.* - these will vary depending on the trigger - e.g. Player Opened Container core.activatedContainer - the form/formID of the container that triggered the event core.activatedContainer.is_corpse - 1 if you are looting a corpse; 0 otherwise core.activatedContainer.is_empty - 1 if the container is empty; 0 otherwise core.activatedContainer.count - current count of items in the container; this will update if you modify the contents $system.currentScriptName - the name of the currently executing script $system.initialScriptName - the first script called in the call chain (if the script never "calls" another script, it will always be the same as the current script name) See more on the wiki. enhancement: many debug flags added and exposed via the MCM minor enhancement: slight improvement in accuracy of script related logging output rearchitecture: typing is being supported more explicitly behind the scenes (not necessarily immediately obvious) change: scripts converted to new extension, .sltscript Multiple bugs fixed, several enhancements, and while the syntax highlighting files are present in the git repo, I will also make them separately downloadable. Currently only available for Notepad++ and VSCode. 1
hextun Posted July 6, 2025 Author Posted July 6, 2025 Here's the Notepad++ UDL file for syntax highlighting of SLTScript: notepad++-support-sltscript.zip And the Visual Studio Code (VSCode) syntax highlighting extension for SLTScript: vscode-sltscript-support.zip More (not much more, but more) information can be found at: https://github.com/lynnpye/sl_triggers/wiki/SLTScript-Editor-Support 1
NismoMan Posted July 6, 2025 Posted July 6, 2025 1 hour ago, hextun said: v123 is posted, and github is updated with the latest tag and release. Remember: All of the scripts that come with the mod by default have been renamed from .ini to .sltscript, so if are reinstalling, remember to remove all of the old .ini files. Here are the changelog notes: 123 Savegame compatibility: CLEAN SAVE REQUIRED (New game or (not supported) Uninstalling SLTR, Cleaning your save (e.g. Load, click through the warning, Save; use a cleaner), Reinstall SLTR and reapply your customizations, Load) Note: Reminder that logging output is all sent to <My Documents>\My Games\Skyrim Special Edition\SKSE\sl-triggers.log (or whichever folder you have your SKSE logs directed to) bugfix: fixed SexLab resolution of partner related variables bugfix: fixed sl_triggersCmd.CompleteOperationOnActor(): now correctly marks runOpPending = false before responding to SLTReset bugfix: .esp change; marked the cell change detection actor as IsGhost and SimpleActor bugfix: 'cat' now properly concatenates all parameters bugfix: SLTScripts comment to end of line ';' was being enforced too aggressively i.e. middle of string literals bugfix: some string compares were not case insensitive bugfix: some function library implementations were not calling CmdPrimary.Resolve*() functions before using parameters; this resulted in some values (string literals) remaining wrapped in double quotes bugfix: on initial install, keys were all detected as pressed; the delusion has been cured enhancement: .sltscript file extension (equivalent to .ini) enhancement: Notepad++ syntax highlighting (see extern/lang-support/notepad++) enhancement: VSCode syntax highlighting (see extern/lang-support/vscode) enhancement: added topicinfo_getresponsetext function which returns the dialog text associated with the provided TopicInfo (by FormID or editorID) example: topicinfo_getresponsetext "Skyrim.esm:0x00020954" ; $$ would contain "I used to be an adventurer like you. Then I took an arrow in the knee..." enhancement: added rnd_float function which returns a random float between parameter1 and parameter2, inclusive example: rnd_float 23.0 482.0 set $result $$ enhancement: New system variables: $system.random.100 - result of rnd_float 0 100.0 enhancement: New contextual system variables: $request.* - these will vary depending on the trigger - e.g. Player Opened Container core.activatedContainer - the form/formID of the container that triggered the event core.activatedContainer.is_corpse - 1 if you are looting a corpse; 0 otherwise core.activatedContainer.is_empty - 1 if the container is empty; 0 otherwise core.activatedContainer.count - current count of items in the container; this will update if you modify the contents $system.currentScriptName - the name of the currently executing script $system.initialScriptName - the first script called in the call chain (if the script never "calls" another script, it will always be the same as the current script name) See more on the wiki. enhancement: many debug flags added and exposed via the MCM minor enhancement: slight improvement in accuracy of script related logging output rearchitecture: typing is being supported more explicitly behind the scenes (not necessarily immediately obvious) change: scripts converted to new extension, .sltscript Multiple bugs fixed, several enhancements, and while the syntax highlighting files are present in the git repo, I will also make them separately downloadable. Currently only available for Notepad++ and VSCode. Duuude 😭 On the upside, development seems to be getting a serious upgrade with every update. Looking forward to test this out. (Decided to create a new MO2 Profile just to test shit out while keeping my main one "stable", lol) But again, DUUUDE!! 😭😭😭
hextun Posted July 6, 2025 Author Posted July 6, 2025 Btw, here is the complete list of special scoped variables (at the current time of writing). This is copied from the wiki, which is of course a more authoritative source. But these are new values you can use in your scripts starting v123. Special Scopes System system scoped variables are typically read-only and offer various system-level pieces of information. Variable Returns $system.self Actor - the Actor the script is targeting/running on; for some triggers this will always be the Player $system.player Actor - the Player $system.actor Actor - the Actor returned from certain functions e.g. util_getrndactor $system.random.100 float - random number between 0.0 and 100.0 inclusive $system.none Form - none; i.e. a null Form $system.is_player.inside bool - is the Player currently in an interior location $system.is_player.outside bool - is the Player currently in an exterior location $system.is_player.in_city bool - is the Player currently in a City, Town, Habitation, or Dwelling $system.is_player.in_dungeon bool - is the Player currently in a DraugrCrypt, DragonPriestLair, FalmerHive, VampireLair, Dwarven Ruin, Dungeon, Mine, or Cave $system.is_player.in_safe bool - is the Player currently in a PlayerHome, Jail, or Inn $system.is_player.in_wilderness bool - is the Player currently in a Hold, BanditCamp, MilitaryFort, or a Location with no keyword $system.stats.running_scripts int - current count of running scripts; will always be 1 or greater because you will be calling it from a script $system.realtime float - the current real time (i.e. seconds since launch of SkyrimSE.exe) from Game.GetCurrentRealtime() $system.gametime float - the current game time (i.e. in-game days since your save was created) from Game.GetCurrentGametime() $system.initialGameTime float - the game time when the script was started $system.initialScriptName string - the initial script that was requested; might differ from current script in case call was used $system.currentScriptName string - the current script that is running; might differ from current script in case call was used $system.sessionid int - the current SLTR sessionid (changes with each load of a save or creation of a new game) $system.is_available.core bool - (Added by SLTR Core) is the SLTR Core extension available and enabled (very rare you would want this false) $system.is_available.sexlab bool - (Added by SLTR SexLab) is the SLTR SexLab extension available and enabled (would be false if you installed on a system without SexLab) $system.partner Actor - (Added by SLTR SexLab) the first member of the target Actor's current SexLab scene that is not the target Actor (same as $sexlab.partner1) $system.partner1 Actor - (Added by SLTR SexLab) the first member of the target Actor's current SexLab scene that is not the target Actor (same as $sexlab.partner) $system.partner2 Actor - (Added by SLTR SexLab) the second member of the target Actor's current SexLab scene that is not the target Actor $system.partner3 Actor - (Added by SLTR SexLab) the third member of the target Actor's current SexLab scene that is not the target Actor $system.partner4 Actor - (Added by SLTR SexLab) the fourth member of the target Actor's current SexLab scene that is not the target Actor Request request scoped variables are also read-only and typically intended to convey information relevant to the context of the trigger, or the environment at the time the script was requested. Unlike system scoped variables that are intrinsic to the system, request scoped variables are only going to have relevant information in certain circumstances. Variable Returns $request.core.activatedContainer Form - (Added by SLTR Core) container that was activated as part of a container activation trigger $request.core.activatedContainer.is_corpse bool - (Added by SLTR Core) true if the activated container was a corpse $request.core.activatedContainer.is_empty bool - (Added by SLTR Core) true if the activated container was empty $request.core.activatedContainer.is_common bool - (Added by SLTR Core) true if the activated container is one of the 'Common' types $request.core.activatedContainer.count int - (Added by SLTR Core) returns the current count of inventory items in the activated container (yes, current; remove something and this value's result will change) $request.core.was_player.inside bool - (Added by SLTR Core) was the Player "Inside" at the time the trigger was handled $request.core.was_player.outside bool - (Added by SLTR Core) was the Player "Outside" at the time the trigger was handled $request.core.was_player.in_safe_area bool - (Added by SLTR Core) was the Player "In a Safe Area" at the time the trigger was handled $request.core.was_player.in_city bool - (Added by SLTR Core) was the Player "In a City" at the time the trigger was handled $request.core.was_player.in_wilderness bool - (Added by SLTR Core) was the Player "In the Wilderness" at the time the trigger was handled $request.core.was_player.in_dungeon bool - (Added by SLTR Core) was the Player "In a Dungeon" at the time the trigger was handled Core core scoped variables are provided by the Core extension Variable Returns $core.toh_elapsed float - actual elapsed time in hours since the previous top of the hour (may be larger than 1.0 if e.g. sleeping or traveling)
Fraying9981 Posted July 7, 2025 Posted July 7, 2025 (edited) is there a way to scan npcs around? for example: 1. trigger: load cell or every in game hour (not SL related) 2. scan NPCs 3. create a buff/debuff fpr example on arousal and (different question): is there a way to trigger SL trigger redux based on equipping objects? 1. trigger: every hour or so (not on equip) = object X is equipped 2. SL trigger global variable $g1 ++ or -- that's it Edited July 7, 2025 by Fraying9981
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now