hextun Posted June 3, 2025 Posted June 3, 2025 2 hours ago, Fraying9981 said: Hey @hextun Would you have some feedback about this one? 🙏 Sure! First, don't be afraid to just try something. Remember you can output values to debug and see what things look like during your script runs. So first of all, nearly every token gets "resolved" before execution. So, for example, if you did this: set $1 "json_getvalue" set $3 "float" set $5 "1" ; you could then write $1 $2 $3 $g4 $5 ; and it would expand to json_getvalue $2 float $g4 1 So to your first question, the function should return "apache helicopter". Just as with any of the local variables, if you try to use it before setting it it will return an empty string. If used numerically, at best it would be treated like a zero (0). At worst, functions like `inc` might not recognize it because it isn't strictly numeric (I can't remember if I patched that in this revision or in redux). One way to do initialization would be to check for emptiness: ; do some initialization up front if $g4 = 3 alreadyinitialized set $g4 3 [alreadyinitialized] ; proceed ;;;;;;;;;;; ; or if you just know it should be set but don't know what it might be if $g4 &!= "" alreadyinitialized ; this should make sure it isn't still an empty string set $g4 3 [alreadyinitialized] ; proceed As for your last question, I don't see a way to determine that using non-redux (i.e. without switching to my version). This version, even at v108, does not expose the "IsInInterior" check directly (I exposed a large set of Papyrus APIs in redux). The interior check performed during SexLab trigger checks is done by checking the cell of the target for the "IsInInterior" value. But since that isn't available directly in the .ini/.json itself, there's no way to check the value (that I'm aware of) directly within your script.
Fraying9981 Posted June 5, 2025 Posted June 5, 2025 On 6/3/2025 at 4:01 AM, hextun said: I had extended sl_triggers a bit before moving over to SL Triggers Redux. So anything past Fotogen's original sl_triggers v12 isn't (I don't think; @Fotogen correct me if I'm wrong) likely to be supported. This would include the features that added the ".ini" format for scripts. As for "Aaaaa" and other zonky named scripts, that's my fault for leaving oddly named test scripts in places where they get scooped up by my packing script. It was the sound I was making at one point while testing... you know... "AAAAAaaaaaaaa"... usually precipitated by other frustrated noises. Then it became "Bbbbb" because I got over my emotional issues. And so on. I added scripts like Z01 through Z10 because when I wanted to test something that I thought was a syntax issue, I could modify each script in turn before it was loaded and could have up to 10 additional tests without needing to reload anything. So please ignore those and don't fault Fotogen for any of that. Thanks a lot @hextun! This is very helpful and also giving me extra work, but it's exciting. I hope I can show you what I'm trying to make right now. I see there is the possibility to add bounty to the player as a trigger, is there a way to clear the bounty?
Fraying9981 Posted June 6, 2025 Posted June 6, 2025 also, i'm looking to add items to inventory from various mods. I know each item info can be found with ssedit, but since you are an experienced modder, would you know of the smartest way to have a clean spreadsheet of all mod items so i can easily copy paste them into a json file to be used by SLT? in the "xx.esm:1234" format
Fraying9981 Posted June 7, 2025 Posted June 7, 2025 hey @hextun Could you share the right way to fetch values from a json file? cant get it to work. I've created a specific trigger to see which syntax work with 30+ methods, but they all return -1 (my fallback value for error)... im doing this as specified in the manual "../sl_triggers/commandstore/[filenamewithoutjsonextension]" here is the file im fetching from { "pc_current_rank": "9", "affinity_1": "0", "affinity_2": "0", "affinity_3": "0", "affinity_4": "0", "affinity_5": "0", "affinity_6": "0", "affinity_7": "0", "affinity_8": "0", "affinity_9": "0", "affinity_10": "0", "affinity_11": "0", "affinity_12": "0", "submissiveness": "0" } here is the full details of the things i have tried...cant get my value from the json file. Spoiler { "cmd" : [ ["msg_notify", "Starting rank fetch tests..."], ["msg_notify", "=== Current Directory Tests ==="], ["set", "$1", "player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 1: ", "$$"], ["set", "$1", "player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 2: ", "$$"], ["set", "$1", "./player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 3: ", "$$"], ["set", "$1", "./player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 4: ", "$$"], ["msg_notify", "=== SKSE Path Tests ==="], ["set", "$1", "SKSE/Plugins/sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 5: ", "$$"], ["set", "$1", "Data/SKSE/Plugins/sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 6: ", "$$"], ["set", "$1", "../commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 7: ", "$$"], ["msg_notify", "=== String Tests (with .json) ==="], ["set", "$1", "/commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 8: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 9: ", "$$"], ["set", "$1", "..//commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 10: ", "$$"], ["msg_notify", "=== Float Tests (with .json) ==="], ["set", "$1", "/commandstore/player_stats.json"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 11: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 12: ", "$$"], ["set", "$1", "..//commandstore/player_stats.json"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 13: ", "$$"], ["msg_notify", "=== Integer Tests (with .json) ==="], ["set", "$1", "/commandstore/player_stats.json"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 14: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 15: ", "$$"], ["set", "$1", "..//commandstore/player_stats.json"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 16: ", "$$"], ["msg_notify", "=== Extra Path Tests (with .json) ==="], ["set", "$1", "commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 17: ", "$$"], ["set", "$1", "./commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 18: ", "$$"], ["set", "$1", "../../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 19: ", "$$"], ["msg_notify", "=== String Tests (without .json) ==="], ["set", "$1", "/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 20: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 21: ", "$$"], ["set", "$1", "..//commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 22: ", "$$"], ["msg_notify", "=== Float Tests (without .json) ==="], ["set", "$1", "/commandstore/player_stats"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 23: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 24: ", "$$"], ["set", "$1", "..//commandstore/player_stats"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 25: ", "$$"], ["msg_notify", "=== Integer Tests (without .json) ==="], ["set", "$1", "/commandstore/player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 26: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 27: ", "$$"], ["set", "$1", "..//commandstore/player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 28: ", "$$"], ["msg_notify", "=== Extra Path Tests (without .json) ==="], ["set", "$1", "commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 29: ", "$$"], ["set", "$1", "./commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 30: ", "$$"], ["set", "$1", "../../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 31: ", "$$"], ["msg_notify", "Rank fetch tests complete."] ] } apologies for the triple post and thanks in advance for your tips!
hextun Posted June 8, 2025 Posted June 8, 2025 17 hours ago, Fraying9981 said: hey @hextun Could you share the right way to fetch values from a json file? cant get it to work. I've created a specific trigger to see which syntax work with 30+ methods, but they all return -1 (my fallback value for error)... im doing this as specified in the manual "../sl_triggers/commandstore/[filenamewithoutjsonextension]" here is the file im fetching from { "pc_current_rank": "9", "affinity_1": "0", "affinity_2": "0", "affinity_3": "0", "affinity_4": "0", "affinity_5": "0", "affinity_6": "0", "affinity_7": "0", "affinity_8": "0", "affinity_9": "0", "affinity_10": "0", "affinity_11": "0", "affinity_12": "0", "submissiveness": "0" } here is the full details of the things i have tried...cant get my value from the json file. Reveal hidden contents { "cmd" : [ ["msg_notify", "Starting rank fetch tests..."], ["msg_notify", "=== Current Directory Tests ==="], ["set", "$1", "player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 1: ", "$$"], ["set", "$1", "player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 2: ", "$$"], ["set", "$1", "./player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 3: ", "$$"], ["set", "$1", "./player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 4: ", "$$"], ["msg_notify", "=== SKSE Path Tests ==="], ["set", "$1", "SKSE/Plugins/sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 5: ", "$$"], ["set", "$1", "Data/SKSE/Plugins/sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 6: ", "$$"], ["set", "$1", "../commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 7: ", "$$"], ["msg_notify", "=== String Tests (with .json) ==="], ["set", "$1", "/commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 8: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 9: ", "$$"], ["set", "$1", "..//commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 10: ", "$$"], ["msg_notify", "=== Float Tests (with .json) ==="], ["set", "$1", "/commandstore/player_stats.json"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 11: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 12: ", "$$"], ["set", "$1", "..//commandstore/player_stats.json"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 13: ", "$$"], ["msg_notify", "=== Integer Tests (with .json) ==="], ["set", "$1", "/commandstore/player_stats.json"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 14: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 15: ", "$$"], ["set", "$1", "..//commandstore/player_stats.json"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 16: ", "$$"], ["msg_notify", "=== Extra Path Tests (with .json) ==="], ["set", "$1", "commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 17: ", "$$"], ["set", "$1", "./commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 18: ", "$$"], ["set", "$1", "../../sl_triggers/commandstore/player_stats.json"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 19: ", "$$"], ["msg_notify", "=== String Tests (without .json) ==="], ["set", "$1", "/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 20: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 21: ", "$$"], ["set", "$1", "..//commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 22: ", "$$"], ["msg_notify", "=== Float Tests (without .json) ==="], ["set", "$1", "/commandstore/player_stats"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 23: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 24: ", "$$"], ["set", "$1", "..//commandstore/player_stats"], ["json_getvalue", "$1", "float", "pc_current_rank", "-1.0"], ["msg_notify", "test 25: ", "$$"], ["msg_notify", "=== Integer Tests (without .json) ==="], ["set", "$1", "/commandstore/player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 26: ", "$$"], ["set", "$1", "../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 27: ", "$$"], ["set", "$1", "..//commandstore/player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 28: ", "$$"], ["msg_notify", "=== Extra Path Tests (without .json) ==="], ["set", "$1", "commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 29: ", "$$"], ["set", "$1", "./commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 30: ", "$$"], ["set", "$1", "../../sl_triggers/commandstore/player_stats"], ["json_getvalue", "$1", "string", "pc_current_rank", "-1"], ["msg_notify", "test 31: ", "$$"], ["msg_notify", "Rank fetch tests complete."] ] } apologies for the triple post and thanks in advance for your tips! I'll post responses to all 3 here. A Way to Clear a Bounty (presumably through SLT Script): ... I don't know. I don't see any current direct functions, HOWEVER... you "can" use the console command along with information from this UESP.net link about the "paycrimegold" console command. So for example, this script would remove your bounty in Riften (that's the faction ID converted to decimal): console $self paycrimegold 0 0 "skyrim.esm:164203" NOTE: THIS PAYS YOUR BOUNTY. GOLD IS REMOVED. YOU BECOME WHOLE WITH THE REALM AGAIN. Other than trying to remove/re-add your gold, or temporarily moving it to a container, I'm not sure how to prevent that from happening. Hunting for the Snuffleupagus: He wasn't visible to everyone at first, you know? And again... I don't have a good answer. I know that xEdit has some very nifty scripting features, so that you can write a Pascal (PASCAL!) script (PASCAL SCRIPT!) that will then operate on selected items. Ostensibly you could write one that, with xEdit open, might, for example, hunt through all ITEM entries under a specified mod file and then dump the contents, formatted, to a file. But that's not something I've mucked with. And oh my god... I FEEL YOUR PAIN. Fotogen added an initial 10 MGEF/SPEL records and I thought I could easily bump the pool size up. And you can... it's easy... just... really... tedious... unless... you... master.. the... xEdit... script... almost as tedious as typing that out was. So, unfortunately I don't have much help to offer; HOWEVER if you visit a nice modding discord site (I've liked the interaction at MrowrPurr's Discord site, helpful and knowledgeable) they may have a script lying around you could modify for purpose. Dealing With JSON Vorhees: The key here is this bit from "sl_triggers_script_description.txt": json_getvalue: get value from json file (uses PapyrusUtil, JsonUtil lib) PapyrusUtil's JsonUtil script has functions like: int function SetIntValue(string FileName, string KeyName, int value) global native float function SetFloatValue(string FileName, string KeyName, float value) global native string function SetStringValue(string FileName, string KeyName, string value) global native form function SetFormValue(string FileName, string KeyName, form value) global native int function GetIntValue(string FileName, string KeyName, int missing = 0) global native float function GetFloatValue(string FileName, string KeyName, float missing = 0.0) global native string function GetStringValue(string FileName, string KeyName, string missing = "") global native form function GetFormValue(string FileName, string KeyName, form missing = none) global native There are similar functions for the list versions (i.e. IntListGetValue, StringListGetValue). And, for example, dipping into a little Papyrus script for demonstration, if you do this with JsonUtil: JsonUtil.SetIntValue("somefilename.json", "thekey", 42) JsonUtil.IntListAdd("somefilename.json", "listkey", 57) The resulting .json file would look something like (variances in formatting of course): { "int": { "thekey": 42, "listkey": [ 57 ] } } So, if you want to pre-generate a .json for JsonUtil consumption, you can set up one of four sections: "int", "float", "string", "form" and then set the values as expected. One thing I noticed: your .json appears to have int values, but they are all stringified and you are explicitly fetching them as string. Are they actually intended to be strings? If not, you would be better served going ahead and specifying "int" instead of "string" on the data type specifier of your function calls. So... first, here are two versions of your .json, one as "int" and one as "string" (just in case though barring something I'm not aware of, I don't recommend it) that are structured properly for JsonUtil's consumption: { "int" : { "pc_current_rank": 9, "affinity_1": 0, "affinity_2": 0, "affinity_3": 0, "affinity_4": 0, "affinity_5": 0, "affinity_6": 0, "affinity_7": 0, "affinity_8": 0, "affinity_9": 0, "affinity_10": 0, "affinity_11": 0, "affinity_12": 0, "submissiveness": 0 } } { "string" : { "pc_current_rank": "9", "affinity_1": "0", "affinity_2": "0", "affinity_3": "0", "affinity_4": "0", "affinity_5": "0", "affinity_6": "0", "affinity_7": "0", "affinity_8": "0", "affinity_9": "0", "affinity_10": "0", "affinity_11": "0", "affinity_12": "0", "submissiveness": "0" } } And here is the first few lines of your "tried" script converted to use "int" (just by way of example; you might not need this specificity but the next person to come along might 😞 { "cmd" : [ ["msg_notify", "Starting rank fetch tests..."], ["msg_notify", "=== Current Directory Tests ==="], ["set", "$1", "player_stats"], ["json_getvalue", "$1", "int", "pc_current_rank", "-1"], ["msg_notify", "test 1: ", "$$"],
Fraying9981 Posted June 11, 2025 Posted June 11, 2025 (edited) Thank you @hextun! It makes a lot of sense now. Since below Papyrus I understand it's c++ running, it seems quite logical that variables need to be properly typed everywhere. Still exploring the intricacies of the system. It seems that setting local variables is converting them to uppercase, is this correct/the general case? ["set", "$7", "other"], ["msg_notify", "DEBUG - Value of $7 immediately after set: ", "$7"], this give me "OTHER" in-game, not "other". Since I'm using string concatenation to create a masking system to fetch values from json files - because I want to avoid an "if" forest - it would be super helpful if you could confirm my understanding is correct on this. I would then convert all the strings in json to uppercase alphanumeric, then I suppose the masking would work. to give you a simple example, my idea is to use a string e.g. "blowjob" and a number 0-9 to fetch comments (blowjob0, blowjob1) depending on the situation. I already have most of the code set up, but as you can see I keep asking you questions about the overall system architecture Thanks in advance for your tips! Edit: it seems when I do this in the same file ["set", "$1", "deviation_"], I get "deviation_" no caps. so could it be that other is a reserved keyword? why is it transformed to all caps? Edited June 11, 2025 by Fraying9981
hextun Posted June 11, 2025 Posted June 11, 2025 1 hour ago, Fraying9981 said: Thank you @hextun! It makes a lot of sense now. Since below Papyrus I understand it's c++ running, it seems quite logical that variables need to be properly typed everywhere. Still exploring the intricacies of the system. It seems that setting local variables is converting them to uppercase, is this correct/the general case? ["set", "$7", "other"], ["msg_notify", "DEBUG - Value of $7 immediately after set: ", "$7"], this give me "OTHER" in-game, not "other". Since I'm using string concatenation to create a masking system to fetch values from json files - because I want to avoid an "if" forest - it would be super helpful if you could confirm my understanding is correct on this. I would then convert all the strings in json to uppercase alphanumeric, then I suppose the masking would work. to give you a simple example, my idea is to use a string e.g. "blowjob" and a number 0-9 to fetch comments (blowjob0, blowjob1) depending on the situation. I already have most of the code set up, but as you can see I keep asking you questions about the overall system architecture Thanks in advance for your tips! Edit: it seems when I do this in the same file ["set", "$1", "deviation_"], I get "deviation_" no caps. so could it be that other is a reserved keyword? why is it transformed to all caps? Ahh... you've run into one of my FAVORITE quirks of the Papyrus engine. First a link to the Creation Kit StringUtil page and a quote from the linked page: Quote Collection of generic string utility global functions. (These global functions require SKSE) A comment found at the top of this script, still present as of SKSE64 version 2.2.6, reads: Note about the internal Skyrim implementation of the string classes used for scripting: The strings are case-insensitive. Each BSFixedString is managed in a cache and reused everywhere it is needed. This means that strings like "O" and "o" are technically equivalent. Which string is used depends greatly on which version is found first. We are investigating how to manage this, but for the time being be aware that the distinction between uppercase and lowercase may not exist. It also means that functions below returning an integer for the character may not correspond exactly. Also GetNthChar("Hello Skyrim!", 4) will return a string with either "O" or "o" depending on which might be registered first. All my tests so far have it return the uppercase, even though in the string it is lowercase. We may solve this problem by switching back to returning an integer rather than a string for GetNthChar, but this will still have problems. So the problem isn't that "Other" is a special word. It's that the first time (or at least the last time it was cached) the Papyrus VM ran across the string "Other" (or "other", or "OTHER", or "oThEr") it decided that "OTHER" (or "Other" or "other"..) is the version to keep and it put it into storage. From then on, from the Papyrus VM's perspective, if you try to retrieve a string in a way that the VM reconstitutes it for you, it is going to give you a copy with the case it understands and remembers. Here's the rub: when SLTScript is pulling and pushing variables and joining them and what not, there are points where a string might get rebuilt by the Papyrus VM, specifically in situations where, had it been pure Papyrus script, you might not encounter it. For example, variable fetching for SLTScript is a multi-step process and eventually goes to StorageUtil for the value. In regular Papyrus script, you're just assigning a variable directly. The upshot is that in Papyrus script you can usually trust that if you put "Other" somewhere, you'll get back "Other" later. The more layers you add on top of that, especially if you're doing concatenations and such, and the higher the likelihood is you will get whatever version of the string the VM wanted you to have. The fact is, though, string comparison is case insensitive anyway. So until you actually display it (or, as I ran into, send it to another mod, where strings suddenly become case sensitive, and now your string indexed values are not available anymore) you usually won't even notice, in pure Papyrus or in SLTScript.
Fraying9981 Posted June 11, 2025 Posted June 11, 2025 30 minutes ago, hextun said: Ahh... you've run into one of my FAVORITE quirks of the Papyrus engine. First a link to the Creation Kit StringUtil page and a quote from the linked page: So the problem isn't that "Other" is a special word. It's that the first time (or at least the last time it was cached) the Papyrus VM ran across the string "Other" (or "other", or "OTHER", or "oThEr") it decided that "OTHER" (or "Other" or "other"..) is the version to keep and it put it into storage. From then on, from the Papyrus VM's perspective, if you try to retrieve a string in a way that the VM reconstitutes it for you, it is going to give you a copy with the case it understands and remembers. Here's the rub: when SLTScript is pulling and pushing variables and joining them and what not, there are points where a string might get rebuilt by the Papyrus VM, specifically in situations where, had it been pure Papyrus script, you might not encounter it. For example, variable fetching for SLTScript is a multi-step process and eventually goes to StorageUtil for the value. In regular Papyrus script, you're just assigning a variable directly. The upshot is that in Papyrus script you can usually trust that if you put "Other" somewhere, you'll get back "Other" later. The more layers you add on top of that, especially if you're doing concatenations and such, and the higher the likelihood is you will get whatever version of the string the VM wanted you to have. The fact is, though, string comparison is case insensitive anyway. So until you actually display it (or, as I ran into, send it to another mod, where strings suddenly become case sensitive, and now your string indexed values are not available anymore) you usually won't even notice, in pure Papyrus or in SLTScript. thanks! lmao so you are saying papyrus decides whether to upper case or lower case variable names. that's crazy lol but I agree it's fun in some way. Does this apply cross script? i.e. if a global = "OTHER" per the VM, then it will be "OTHER" too per another script in the same game instance? I suppose yes, since the papyrus VM is already initialized. No big deal anyway, I guess I can ignore it then and debug the rest of my scripts.
hextun Posted June 12, 2025 Posted June 12, 2025 4 hours ago, Fraying9981 said: thanks! lmao so you are saying papyrus decides whether to upper case or lower case variable names. that's crazy lol but I agree it's fun in some way. Does this apply cross script? i.e. if a global = "OTHER" per the VM, then it will be "OTHER" too per another script in the same game instance? I suppose yes, since the papyrus VM is already initialized. No big deal anyway, I guess I can ignore it then and debug the rest of my scripts. To be clear, it's not variable names, at least not to the Papyrus VM; it's string values. If you get a value back and the case has changed, that's the VM playing goofy games. That said, if you are doing actual equality checks (i.e. if $2 = "hello") even if $2 comes back "HELLO" it will match. BUT, if you do something like (json_getvalue $filename $keyfield) where $keyfield returns "HELLO" but you had set it to "hello", unfortunately the key lookup is likely to fail, because JSON keys are case sensitive. It was maddening when I was writing some of the json attribute reading code for the function libraries.
Fraying9981 Posted June 12, 2025 Posted June 12, 2025 7 hours ago, hextun said: To be clear, it's not variable names, at least not to the Papyrus VM; it's string values. If you get a value back and the case has changed, that's the VM playing goofy games. That said, if you are doing actual equality checks (i.e. if $2 = "hello") even if $2 comes back "HELLO" it will match. BUT, if you do something like (json_getvalue $filename $keyfield) where $keyfield returns "HELLO" but you had set it to "hello", unfortunately the key lookup is likely to fail, because JSON keys are case sensitive. It was maddening when I was writing some of the json attribute reading code for the function libraries. I see! So then do you think it's a solution to put all caps everywhere? since the VM has this tendency to capitalize things (by my experience).
guliguliradish Posted June 27, 2025 Posted June 27, 2025 I come from a non English speaking country, and the process of exploring and learning it has been extremely difficult for me. So I think it's better for me to seek advice here: I hope to be able to wear specific clothing (assuming it is "A.nif") when playing a specific sexlab animation (with independent tags), and automatically remove it after the animation ends. What should I do? Please help me😢
hextun Posted June 28, 2025 Posted June 28, 2025 18 hours ago, guliguliradish said: I come from a non English speaking country, and the process of exploring and learning it has been extremely difficult for me. So I think it's better for me to seek advice here: I hope to be able to wear specific clothing (assuming it is "A.nif") when playing a specific sexlab animation (with independent tags), and automatically remove it after the animation ends. What should I do? Please help me😢 Hi there! If you know your animation's name, and you have the FormID of the specific pieces of clothing you want worn (the .nif file isn't what you need; you need the FormID of the item in game), this should be doable. The following should, I think, serve as a decent start: { "cmd" : [ [":", "you can safely delete all ':' labels except for 'done' as they were added for comments"], ["set", "$1", "whatever the animation name is"], [":", "Get the animation currently running and if it doesn't match, goto done"], ["sl_animname"], ["if", "$$", "!=", "$1", "done"], [":", "So if the formId for an item is 0xYY000802 for a mod named 'foo.esp'"], [":", "You would use set $2 '2050:foo.esp'"], ["set", "$2", "formid-clothing-1:modfile.esp"], ["set", "$3", "formid-clothing-2:modfile.esp"], [":", "I used item_adduse even though item_equip will add the item too, but the Creation Kit"], [":", "documentation for Actor.EquipItem says doing so may cause equip to fail, so add first"], [":", "item_adduse does both, adding the item then using/equipping it"], ["item_adduse", "$self", "$2", "0", "0"], ["item_adduse", "$self", "$3", "0", "0"], [":", "this will wait for the current running SexLab animation to end, or will end immediately if not playing"], ["util_waitforend"], [":", "Removing should unequip as well as remove from the inventory"], ["item_remove", "$self", "$2", "1", "1"], ["item_remove", "$self", "$3", "1", "1"], [":", "you can safely delete all ':' labels except for 'done' as they were added for comments"], [":", "added as a target to skip to if the animation doesn't match"], [":", "done"] ] }
hextun Posted June 28, 2025 Posted June 28, 2025 On 6/12/2025 at 2:44 AM, Fraying9981 said: I see! So then do you think it's a solution to put all caps everywhere? since the VM has this tendency to capitalize things (by my experience). Well, it really shouldn't matter until/unless you want to a) display something to yourself and b) you dislike unexpected capitalization. Or c) you try to use it as a json key and the case differs causing the lookup to fail. Which meant I instead had to fetch the json keys and iterate them in Papyrus to let the key matching become case insensitive, then fetch the value. Mostly I ignore it. Except when I am putting together strings to display in e.g. the MCM, or doing the key matching outside of Papyrus, I don't pay any attention to string case.
guliguliradish Posted June 28, 2025 Posted June 28, 2025 6 hours ago, hextun said: Hi there! If you know your animation's name, and you have the FormID of the specific pieces of clothing you want worn (the .nif file isn't what you need; you need the FormID of the item in game), this should be doable. The following should, I think, serve as a decent start: { "cmd" : [ [":", "you can safely delete all ':' labels except for 'done' as they were added for comments"], ["set", "$1", "whatever the animation name is"], [":", "Get the animation currently running and if it doesn't match, goto done"], ["sl_animname"], ["if", "$$", "!=", "$1", "done"], [":", "So if the formId for an item is 0xYY000802 for a mod named 'foo.esp'"], [":", "You would use set $2 '2050:foo.esp'"], ["set", "$2", "formid-clothing-1:modfile.esp"], ["set", "$3", "formid-clothing-2:modfile.esp"], [":", "I used item_adduse even though item_equip will add the item too, but the Creation Kit"], [":", "documentation for Actor.EquipItem says doing so may cause equip to fail, so add first"], [":", "item_adduse does both, adding the item then using/equipping it"], ["item_adduse", "$self", "$2", "0", "0"], ["item_adduse", "$self", "$3", "0", "0"], [":", "this will wait for the current running SexLab animation to end, or will end immediately if not playing"], ["util_waitforend"], [":", "Removing should unequip as well as remove from the inventory"], ["item_remove", "$self", "$2", "1", "1"], ["item_remove", "$self", "$3", "1", "1"], [":", "you can safely delete all ':' labels except for 'done' as they were added for comments"], [":", "added as a target to skip to if the animation doesn't match"], [":", "done"] ] } God bless you, kind sir. But in reality, I need to correspond one piece of clothing to eight animations (they have common labels, but not vagina, butt, and oral), and I don't know if this code is correct.
Fraying9981 Posted June 28, 2025 Posted June 28, 2025 10 hours ago, hextun said: Well, it really shouldn't matter until/unless you want to a) display something to yourself and b) you dislike unexpected capitalization. Or c) you try to use it as a json key and the case differs causing the lookup to fail. Which meant I instead had to fetch the json keys and iterate them in Papyrus to let the key matching become case insensitive, then fetch the value. Mostly I ignore it. Except when I am putting together strings to display in e.g. the MCM, or doing the key matching outside of Papyrus, I don't pay any attention to string case. Yeah it doesnt really work. I may try again on a nrw save with the redux version. Thanks
hextun Posted June 28, 2025 Posted June 28, 2025 (edited) 13 hours ago, guliguliradish said: God bless you, kind sir. But in reality, I need to correspond one piece of clothing to eight animations (they have common labels, but not vagina, butt, and oral), and I don't know if this code is correct. Ah, then maybe this would work. This version: - puts the current animation name into $1 - checks it against each of the animation names (I assume you have the names, note this just ignores the custom labels/tags/whatever) - each animation name corresponds to a different "anim" label where the specific item of clothing is put into $2 as a formId - then the original logic resumes, adding the item and equipping it, then waiting for end of the SL animation before removing it { "cmd" : [ [":", "you can safely delete all ':' labels except for 'done' as they were added for comments"], [":", "Get the animation currently running and put it into $1"], ["sl_animname"], ["set", "$1", "$$"], ["if", "$1", "=", "animation name #1", "anim1"], ["if", "$1", "=", "animation name #2", "anim2"], ["if", "$1", "=", "animation name #3", "anim3"], ["if", "$1", "=", "animation name #4", "anim4"], ["if", "$1", "=", "animation name #5", "anim5"], ["if", "$1", "=", "animation name #6", "anim6"], ["if", "$1", "=", "animation name #7", "anim7"], ["if", "$1", "=", "animation name #8", "anim8"], ["goto", "done"], [":", "So if the formId for an item is 0xYY000802 for a mod named 'foo.esp'"], [":", "You would use set $2 '2050:foo.esp'"], [":", "anim1"], ["set", "$2", "modfile.esp:formid-clothing-1"] ["goto", "dowork"], [":", "anim2"], ["set", "$2", "modfile.esp:formid-clothing-2"] ["goto", "dowork"], [":", "anim3"], ["set", "$2", "modfile.esp:formid-clothing-3"] ["goto", "dowork"], [":", "anim4"], ["set", "$2", "modfile.esp:formid-clothing-4"] ["goto", "dowork"], [":", "anim5"], ["set", "$2", "modfile.esp:formid-clothing-5"] ["goto", "dowork"], [":", "anim6"], ["set", "$2", "modfile.esp:formid-clothing-6"] ["goto", "dowork"], [":", "anim7"], ["set", "$2", "modfile.esp:formid-clothing-7"] ["goto", "dowork"], [":", "anim8"], ["set", "$2", "modfile.esp:formid-clothing-8"] ["goto", "dowork"], [":", "dowork"] [":", "I used item_adduse even though item_equip will add the item too, but the Creation Kit"], [":", "documentation for Actor.EquipItem says doing so may cause equip to fail, so add first"], [":", "item_adduse does both, adding the item then using/equipping it"], ["item_adduse", "$self", "$2", "0", "0"], [":", "this will wait for the current running SexLab animation to end, or will end immediately if not playing"], ["util_waitforend"], [":", "Removing should unequip as well as remove from the inventory"], ["item_remove", "$self", "$2", "1", "1"], [":", "you can safely delete all ':' labels except for 'done' as they were added for comments"], [":", "added as a target to skip to if the animation doesn't match"], [":", "done"] ] } Edited June 28, 2025 by hextun fixed example script
guliguliradish Posted June 28, 2025 Posted June 28, 2025 Once again, I offer my respect and gratitude! I still seem to have a few questions: 1 hour ago, hextun said: ["sl_animname"], Does this “animname” refer to the name of the animation package?or,I don't need to modify it,maybe? 1 hour ago, hextun said: "formid-clothing-1:modfile.esp" The term 'clothing' here refers to the word itself, right? (What I mean is, suppose this garment is called a 'shirt' - I named it in edit, but I still type 'clothing' here instead of 'shirt', right?)
hextun Posted June 28, 2025 Posted June 28, 2025 4 hours ago, guliguliradish said: Once again, I offer my respect and gratitude! I still seem to have a few questions: Does this “animname” refer to the name of the animation package?or,I don't need to modify it,maybe? The term 'clothing' here refers to the word itself, right? (What I mean is, suppose this garment is called a 'shirt' - I named it in edit, but I still type 'clothing' here instead of 'shirt', right?) No worries, glad to help! There should be a file inside of the mod named "sl_triggers_script_description.txt". In it you will find descriptions of the various functions available for use with SLTScript. One of those functions is "sl_animname". From the .txt file: sl_animname: put current animation name into $$ - (no parameters) Example: ["sl_animname"], ["mfg_notify", "Playing: ", "$$"], This is a Fotogen original function that's been available since v12 or earlier. So what that line is doing is requesting the currently running SexLab animation's name from SexLab and putting the result into the "$$" variable. As for these, first off, I accidentally reversed the format. Formats for formIDs used in SLTScript should adhere to "modfilename:formid". "modfile.esp:formid-clothing-1" "formid-clothing-1" is referring to the literal FormID (as an integer) from the mod. So, say your item of clothing is named "Nice Shirt", and you gave it an editorId of "mymodNiceShirt", and when you created it it was given FormID "XX000804" (just picking one at random from a typical starting range of 800). And let's assume you put it into your modfile called "myPersonalMod.esp". 804 in base 16 is 2052 in base 10. So the FormID, formatted for use with SLTScript, would be: "myPersonalMod.esp:2052" Note that it has nothing to do with the name or editorId. Just the numeric FormID. I'll use the base game "Iron Gauntlets" for an example. Here is a screenshot from SSEEdit. I have expanded the Armor node under "Skyrim.esm" and you can see the entry for "Iron Gauntlets". Note that the name (far right) is "Iron Gauntlets", human readable. The editorId is "ArmorIronGauntlets", not intended for human readability. The formID is "00012E46". We can take the base 16 value of 12E46 and convert it to base 10 to get 77382. So the equivalent SLTScript formID would be: "Skyrim.esm:77382" So the upshot of what the script I gave you was to do was: - get the currently running animation's name from SexLab - check it against each of the eight animation names you are targeting (note: it is ignoring tags or any other values; it is ONLY looking at the animation name) - each animation name has it's own goto label - when the correct if check matches the animation name, it then goes to that label and starts executing - if the currently running animation name does not match any of the eight animation names you are trying to react to, it skips everything and goes straight to "done" - all that does is set another variable, $2, to the formID of the animation's associated piece of clothing - each then does "goto dowork" which now goes to the dowork label and starts executing - starting at the "dowork" label, the script adds and uses the clothing identified by the formID set in $2 - it then uses util_waitforend to wait for the end of the animation - then it removes the item identified by the formID set in $2 This is intended to do what I interpreted this to mean: - "I need to correspond one piece of clothing to eight animations" which I took to mean: - "I have eight different specific animations, and each has a specific clothing item (ARMO record with FormID). When a SexLab animation starts with one of those eight animations, I want the player to be equipped with the item until the animation ends. If it is not one of the eight, I want the script to do nothing" Note that I am not making use of the tags because if you have the animation name we can very easily match against it with existing functions.
guliguliradish Posted June 29, 2025 Posted June 29, 2025 4 hours ago, hextun said: No worries, glad to help! There should be a file inside of the mod named "sl_triggers_script_description.txt". In it you will find descriptions of the various functions available for use with SLTScript. One of those functions is "sl_animname". From the .txt file: sl_animname: put current animation name into $$ - (no parameters) Example: ["sl_animname"], ["mfg_notify", "Playing: ", "$$"], This is a Fotogen original function that's been available since v12 or earlier. So what that line is doing is requesting the currently running SexLab animation's name from SexLab and putting the result into the "$$" variable. As for these, first off, I accidentally reversed the format. Formats for formIDs used in SLTScript should adhere to "modfilename:formid". "modfile.esp:formid-clothing-1" "formid-clothing-1" is referring to the literal FormID (as an integer) from the mod. So, say your item of clothing is named "Nice Shirt", and you gave it an editorId of "mymodNiceShirt", and when you created it it was given FormID "XX000804" (just picking one at random from a typical starting range of 800). And let's assume you put it into your modfile called "myPersonalMod.esp". 804 in base 16 is 2052 in base 10. So the FormID, formatted for use with SLTScript, would be: "myPersonalMod.esp:2052" Note that it has nothing to do with the name or editorId. Just the numeric FormID. I'll use the base game "Iron Gauntlets" for an example. Here is a screenshot from SSEEdit. I have expanded the Armor node under "Skyrim.esm" and you can see the entry for "Iron Gauntlets". Note that the name (far right) is "Iron Gauntlets", human readable. The editorId is "ArmorIronGauntlets", not intended for human readability. The formID is "00012E46". We can take the base 16 value of 12E46 and convert it to base 10 to get 77382. So the equivalent SLTScript formID would be: "Skyrim.esm:77382" So the upshot of what the script I gave you was to do was: - get the currently running animation's name from SexLab - check it against each of the eight animation names you are targeting (note: it is ignoring tags or any other values; it is ONLY looking at the animation name) - each animation name has it's own goto label - when the correct if check matches the animation name, it then goes to that label and starts executing - if the currently running animation name does not match any of the eight animation names you are trying to react to, it skips everything and goes straight to "done" - all that does is set another variable, $2, to the formID of the animation's associated piece of clothing - each then does "goto dowork" which now goes to the dowork label and starts executing - starting at the "dowork" label, the script adds and uses the clothing identified by the formID set in $2 - it then uses util_waitforend to wait for the end of the animation - then it removes the item identified by the formID set in $2 This is intended to do what I interpreted this to mean: - "I need to correspond one piece of clothing to eight animations" which I took to mean: - "I have eight different specific animations, and each has a specific clothing item (ARMO record with FormID). When a SexLab animation starts with one of those eight animations, I want the player to be equipped with the item until the animation ends. If it is not one of the eight, I want the script to do nothing" Note that I am not making use of the tags because if you have the animation name we can very easily match against it with existing functions. Based on your guidance, I have revised it again, this time with the actual "animation name" and the actual "item name", and I have converted the formid to decimal and rearranged the word order. It's here: KZanimLegrope.txt Additionally, I took screenshots of both the animation name and the item name Is the code I wrote correct?
hextun Posted July 13, 2025 Posted July 13, 2025 On 6/28/2025 at 7:45 PM, guliguliradish said: Based on your guidance, I have revised it again, this time with the actual "animation name" and the actual "item name", and I have converted the formid to decimal and rearranged the word order. It's here: KZanimLegrope.txt 1.36 kB · 1 download Additionally, I took screenshots of both the animation name and the item name Is the code I wrote correct? Hi there. First, I haven't tested this, but I modified your script and I *think* this should work. I made the following assumptions: - The animation name in your screenshot is "Kziitd_Missionary" (which guided the renaming I did on lines 7-14) - The set/goto pairs were in the same order as the desired anims (which I also deduced because you had the 'clothing-#' text on each one) I made the following changes: - I changed the animation names to match (what I assume) are the actual animation names in use in the mod (again, see my first assumption) - I added [":", "anim#"] labels before each set/goto pair (this is how the if/anim# construct knows where to go when the if matches) - I added [":", "done"] and [":", "dowork"] labels (same as the [":", "anim#"] labels) Also, keep in mind that while I do like to help those who ask, and I had been providing a little support on this mod, I have been developing my own version, so I'm not as attentive on this board. { "cmd" : [ ["sl_animname"], ["set", "$1", "$$"], ["if", "$1", "=", "Kziitd_Missionary", "anim1"], ["if", "$1", "=", "Kziitd_Doggy", "anim2"], ["if", "$1", "=", "Kziitd_Cowgirl", "anim3"], ["if", "$1", "=", "Kziitd_Laying", "anim4"], ["if", "$1", "=", "Kziitd_Laying2", "anim5"], ["if", "$1", "=", "Kziitd_Stand", "anim6"], ["if", "$1", "=", "Kziitd_Missionary2", "anim7"], ["if", "$1", "=", "Kziitd_Laying3", "anim8"], ["goto", "done"], [":", "anim1"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim2"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim3"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim4"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim5"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim6"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim7"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim8"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "dowork"], ["item_adduse", "$self", "$2", "0", "0"], ["util_waitforend"], ["item_remove", "$self", "$2", "1", "1"], [":", "done"] ] }
guliguliradish Posted July 14, 2025 Posted July 14, 2025 11 hours ago, hextun said: Hi there. First, I haven't tested this, but I modified your script and I *think* this should work. 你好。首先,我没有测试这个,但我修改了你的脚本,我认为这应该可以工作。 I made the following assumptions: 我做了以下假设: - The animation name in your screenshot is "Kziitd_Missionary" (which guided the renaming I did on lines 7-14) - 您屏幕截图中的动画名称是"Kziitd_Missionary"(这指导了我对第 7-14 行进行的重命名) - The set/goto pairs were in the same order as the desired anims (which I also deduced because you had the 'clothing-#' text on each one) - set/goto 对与所需的动画顺序相同(我也推断出来了,因为您在每个标签上都有'clothing-#'文本) I made the following changes: 我做了以下修改: - I changed the animation names to match (what I assume) are the actual animation names in use in the mod (again, see my first assumption) - 我修改了动画名称,以匹配(我认为)模组中实际使用的动画名称(再次,请参考我的第一个假设) - I added [":", "anim#"] labels before each set/goto pair (this is how the if/anim# construct knows where to go when the if matches) - 我在每个 set/goto 对之前添加了[":", "anim#"]标签(这就是 if/anim#结构在 if 匹配时知道去哪里的时候的方式) - I added [":", "done"] and [":", "dowork"] labels (same as the [":", "anim#"] labels) - 我添加了[":", "done"]和[":", "dowork"]标签(与[":", "anim#"]标签相同) Also, keep in mind that while I do like to help those who ask, and I had been providing a little support on this mod, I have been developing my own version, so I'm not as attentive on this board. 此外,请记住,虽然我喜欢帮助那些提问的人,我也一直在为这个模组提供一些支持,但我一直在开发自己的版本,所以我对这个论坛的关注度没有那么高。 { "cmd" : [ ["sl_animname"], ["set", "$1", "$$"], ["if", "$1", "=", "Kziitd_Missionary", "anim1"], ["if", "$1", "=", "Kziitd_Doggy", "anim2"], ["if", "$1", "=", "Kziitd_Cowgirl", "anim3"], ["if", "$1", "=", "Kziitd_Laying", "anim4"], ["if", "$1", "=", "Kziitd_Laying2", "anim5"], ["if", "$1", "=", "Kziitd_Stand", "anim6"], ["if", "$1", "=", "Kziitd_Missionary2", "anim7"], ["if", "$1", "=", "Kziitd_Laying3", "anim8"], ["goto", "done"], [":", "anim1"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim2"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim3"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim4"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim5"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim6"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim7"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "anim8"], ["set", "$2", "KXT-GuliExtensions.esp:99999999"] ["goto", "dowork"], [":", "dowork"], ["item_adduse", "$self", "$2", "0", "0"], ["util_waitforend"], ["item_remove", "$self", "$2", "1", "1"], [":", "done"] ] } I'm so happy to see your reply again. Yes, just yesterday, I noticed that you recently posted STR, right? This is an upgraded version of ST, right? So does this script also apply to STR? In addition, the "99999999" in "set", "$2", "KXT-GuliExtensions. esp: 99999999" is not a true hexadecimal value. I need to change them to their corresponding hexadecimal values, right?
hextun Posted July 14, 2025 Posted July 14, 2025 5 hours ago, guliguliradish said: I'm so happy to see your reply again. Yes, just yesterday, I noticed that you recently posted STR, right? This is an upgraded version of ST, right? So does this script also apply to STR? In addition, the "99999999" in "set", "$2", "KXT-GuliExtensions. esp: 99999999" is not a true hexadecimal value. I need to change them to their corresponding hexadecimal values, right? I am not calling it an upgrade; I was inspired by Fotogen's work and got permission to branch off with Fotogen's SLT as a basis. That said, as part of that branching off, quite a bit has changed. I have tried to continue supporting JSON but have not tested it rigorously in some time, so it is possible some features are not supported and some scripts may no longer work. I should test that status and make sure to update my page. Regardless, conversion to the new format is pretty easy and in this case I'd be happy to do the conversion; wouldn't take more than a minute of mostly editing out JSON bits. And I knew I was forgetting something. 😅 Yes, I changed those numbers. You were using "absolute" FormID values. I'll try to explain it, but first, have a look at this SPID article on FormIDs and You. In particular, expand all of the examples and take a look at the difference between ESL vs ESM/ESP. I'll try to explain here as well. To begin with, everything gets a FormID. A full FormID is 8 hex digits: e.g. 0x12345678. But, each mod only gets a range of those FormID values to work with. For ESM/ESP mods, that range is the last 6 hex digits. e.g. 0x12345678 For ESL flagged mods (they still have an .esp extension but are flagged as "light"), that range is only the last 3 hex digits. e.g. 0x12345678. ESL flagged mods are intended to take up less space in your load order by being limited to fewer FormIDs being available. Important note: All ESL flagged mods, during gameplay, will have the first two hex digits be 'FE'. This was the demarcated part of the FormID range Bethesda set aside for ESL use. What then do the other digits do? What are they for? The hex digits that aren't part of the mod's FormID range are used for the mod index as defined by the load order. For example, Skyrim.esm, being the master for the entire game, is always found at mod index 0x00. That's great if you are working with FormIDs for the base game, but if you want portable configurations in text files, such that you can refer to a mod's FormID, you have to take into account that you may not know what the modindex is in advance. So we have to use a "relative", not an "absolute", FormID. By "relative", it means "relative to the mod's modindex". Suppose your ESL flagged mod is named "coolmod.esp". It has a range of available FormIDs that exclude the first 5 hex digits (again, because it is an ESL with fewer FormIDs in its range). So hypothetically it might use a FormID anywhere between 0xXXXXX000 through 0xXXXXXFFF (and actually I think they are a little more constrained than that but that's unimportant for this discussion). Note that in those two numbers I have the first 5 hex digits masked out with 'X'; that's because they aren't important so long as I make sure to combine the last 3 digits with the mod's name. Anyway, suppose it has a WEAP (Weapon) record, a sword artifact. You open xedit, and it shows the FormID as 0xFE000801. Maybe you add another ESL and then rearrange it so it comes before "coolmod.esp". Now in xedit the WEAP record you looked at previously would likely have FormID 0xFE001801. The change from 0xFE000 (when it was the first ESL in your load order) to 0xFE001 (when you added another ESL and put it before "coolmod.esp") is explained purely by your change in load order; but the last 3 digits remain the same. And with an ESP/ESM, it's the same but only excluding the first two digits. And of course, an ESP or ESM will never be assigned mod index 0xFE because that's how Skyrim isolates ESL flagged mods from non-ESL flagged mods for FormID ranges. FormIDs are 8 hex digits e.g. 0x12345678 use the first 5 or 3 (depending on ESL vs ESP/ESM) hex digits as "modindex" use the last 3 or 5 (depending on ESL vs ESP/ESM) hex digits as the mod's available FormID range it is sufficient to locate any record in a mod with: the mod filename e.g. "coolmod.esp", "Skyrim.esm" the record's FormID with the modindex range masked with '0' (or removed outright as it is just leading zeroes) ESP/ESM 0x12345678 - the modindex range is indicated 0x12345678 - the FormID range is indicated Roughly 16M (million) available FormIDs (i.e. things you can put into your mod, quests, weapons, spells, dialog, NPCs) It is sufficient to locate any record in this mod with: "coolmod.esp" record with FormID 0xXX00D39 (ESP/ESM uses the last 5 hex digits, but this example has 2 leading zeroes in its range that can be ignored) "coolmod.esp:0xD39" - using hex numbers "coolmod.esp:3385" - using decimal numbers (hex 0xD39 converts to decimal 3,385) A better example, the Vigilant mod, "vigilant.esm", a master. Let's use the QUST (Quest) record for their MCM (editorID 'zzzVigMCM') with FormID 0xXX4881EA Notice it uses all 5 hex digits; this is what being an ESP/ESM buys you, a much larger FormID range "vigilant.esm:0x4881EA" - using hex numbers "vigilant.esm:4751850" - using decimal numbers (hex 0x4881EA converts to decimal 4,751,850 ESL flagged ESP (still has .esp extension but is ESL flagged) 0x12345678 - the modindex range is indicated 0x12345678 - the FormID range is indicated 4,095 available FormIDs (i.e. things you can put into your mod, quests, weapons, spells, dialog, NPCs) "coolmod.esp" record with FormID 0xXXXXD39 (ESP only gets the last 3 hex digits, and now we are using all 3, no leading zeroes) "coolmod.esp:0xD39" - using hex numbers "coolmod.esp:3385" - using decimal numbers (hex 0xD39 converts to 3,385) As you can see, in some cases, the notation would not change if the mod is flagged ESL or not, i.e. when the FormID is low enough in the range (i.e. below 0xFFF or 4095) What it looks like you did was take the full FormID in xedit (e.g. 0x0800088C) and convert it to decimal (e.g. 134,219,916). In that items' case (editorID "kftlegrope"): FormID: 0x0800088C If it's ESL flagged, that's 0x0800088C If it's ESP/ESM flagged, that's 0x0800088C Both result in the same thing i.e. 0x88C == 0x00088C (just like 10 == 00010) So in my case, trying to deduce which you might have... I can't, but it also means I can say that for at least this item it won't matter 0x88C hex converts to 2,188 decimal Judging from your JSON, your modfile name is "KXT-GuliExtensions.esp" So you want to use "KXT-GuliExtensions.esp:2188" I wasn't sure what the remaining FormIDs were so didn't want to make assumptions, not to mention that in the sample you provided you reused the same FormID in each of the 8 set/goto pairs. If that was your intent, if there is only the one item and not 8, your script can be greatly simplified.
guliguliradish Posted July 14, 2025 Posted July 14, 2025 1 hour ago, hextun said: I am not calling it an upgrade; I was inspired by Fotogen's work and got permission to branch off with Fotogen's SLT as a basis. That said, as part of that branching off, quite a bit has changed. I have tried to continue supporting JSON but have not tested it rigorously in some time, so it is possible some features are not supported and some scripts may no longer work. I should test that status and make sure to update my page. Regardless, conversion to the new format is pretty easy and in this case I'd be happy to do the conversion; wouldn't take more than a minute of mostly editing out JSON bits. And I knew I was forgetting something. 😅 Yes, I changed those numbers. You were using "absolute" FormID values. I'll try to explain it, but first, have a look at this SPID article on FormIDs and You. In particular, expand all of the examples and take a look at the difference between ESL vs ESM/ESP. I'll try to explain here as well. To begin with, everything gets a FormID. A full FormID is 8 hex digits: e.g. 0x12345678. But, each mod only gets a range of those FormID values to work with. For ESM/ESP mods, that range is the last 6 hex digits. e.g. 0x12345678 For ESL flagged mods (they still have an .esp extension but are flagged as "light"), that range is only the last 3 hex digits. e.g. 0x12345678. ESL flagged mods are intended to take up less space in your load order by being limited to fewer FormIDs being available. Important note: All ESL flagged mods, during gameplay, will have the first two hex digits be 'FE'. This was the demarcated part of the FormID range Bethesda set aside for ESL use. What then do the other digits do? What are they for? The hex digits that aren't part of the mod's FormID range are used for the mod index as defined by the load order. For example, Skyrim.esm, being the master for the entire game, is always found at mod index 0x00. That's great if you are working with FormIDs for the base game, but if you want portable configurations in text files, such that you can refer to a mod's FormID, you have to take into account that you may not know what the modindex is in advance. So we have to use a "relative", not an "absolute", FormID. By "relative", it means "relative to the mod's modindex". Suppose your ESL flagged mod is named "coolmod.esp". It has a range of available FormIDs that exclude the first 5 hex digits (again, because it is an ESL with fewer FormIDs in its range). So hypothetically it might use a FormID anywhere between 0xXXXXX000 through 0xXXXXXFFF (and actually I think they are a little more constrained than that but that's unimportant for this discussion). Note that in those two numbers I have the first 5 hex digits masked out with 'X'; that's because they aren't important so long as I make sure to combine the last 3 digits with the mod's name. Anyway, suppose it has a WEAP (Weapon) record, a sword artifact. You open xedit, and it shows the FormID as 0xFE000801. Maybe you add another ESL and then rearrange it so it comes before "coolmod.esp". Now in xedit the WEAP record you looked at previously would likely have FormID 0xFE001801. The change from 0xFE000 (when it was the first ESL in your load order) to 0xFE001 (when you added another ESL and put it before "coolmod.esp") is explained purely by your change in load order; but the last 3 digits remain the same. And with an ESP/ESM, it's the same but only excluding the first two digits. And of course, an ESP or ESM will never be assigned mod index 0xFE because that's how Skyrim isolates ESL flagged mods from non-ESL flagged mods for FormID ranges. FormIDs are 8 hex digits e.g. 0x12345678 use the first 5 or 3 (depending on ESL vs ESP/ESM) hex digits as "modindex" use the last 3 or 5 (depending on ESL vs ESP/ESM) hex digits as the mod's available FormID range it is sufficient to locate any record in a mod with: the mod filename e.g. "coolmod.esp", "Skyrim.esm" the record's FormID with the modindex range masked with '0' (or removed outright as it is just leading zeroes) ESP/ESM 0x12345678 - the modindex range is indicated 0x12345678 - the FormID range is indicated Roughly 16M (million) available FormIDs (i.e. things you can put into your mod, quests, weapons, spells, dialog, NPCs) It is sufficient to locate any record in this mod with: "coolmod.esp" record with FormID 0xXX00D39 (ESP/ESM uses the last 5 hex digits, but this example has 2 leading zeroes in its range that can be ignored) "coolmod.esp:0xD39" - using hex numbers "coolmod.esp:3385" - using decimal numbers (hex 0xD39 converts to decimal 3,385) A better example, the Vigilant mod, "vigilant.esm", a master. Let's use the QUST (Quest) record for their MCM (editorID 'zzzVigMCM') with FormID 0xXX4881EA Notice it uses all 5 hex digits; this is what being an ESP/ESM buys you, a much larger FormID range "vigilant.esm:0x4881EA" - using hex numbers "vigilant.esm:4751850" - using decimal numbers (hex 0x4881EA converts to decimal 4,751,850 ESL flagged ESP (still has .esp extension but is ESL flagged) 0x12345678 - the modindex range is indicated 0x12345678 - the FormID range is indicated 4,095 available FormIDs (i.e. things you can put into your mod, quests, weapons, spells, dialog, NPCs) "coolmod.esp" record with FormID 0xXXXXD39 (ESP only gets the last 3 hex digits, and now we are using all 3, no leading zeroes) "coolmod.esp:0xD39" - using hex numbers "coolmod.esp:3385" - using decimal numbers (hex 0xD39 converts to 3,385) As you can see, in some cases, the notation would not change if the mod is flagged ESL or not, i.e. when the FormID is low enough in the range (i.e. below 0xFFF or 4095) What it looks like you did was take the full FormID in xedit (e.g. 0x0800088C) and convert it to decimal (e.g. 134,219,916). In that items' case (editorID "kftlegrope"): FormID: 0x0800088C If it's ESL flagged, that's 0x0800088C If it's ESP/ESM flagged, that's 0x0800088C Both result in the same thing i.e. 0x88C == 0x00088C (just like 10 == 00010) So in my case, trying to deduce which you might have... I can't, but it also means I can say that for at least this item it won't matter 0x88C hex converts to 2,188 decimal Judging from your JSON, your modfile name is "KXT-GuliExtensions.esp" So you want to use "KXT-GuliExtensions.esp:2188" I wasn't sure what the remaining FormIDs were so didn't want to make assumptions, not to mention that in the sample you provided you reused the same FormID in each of the 8 set/goto pairs. If that was your intent, if there is only the one item and not 8, your script can be greatly simplified. You are really super attentive. Infinite gratitude! Sorry, please forgive me. I just realized that I have installed STR... So, does the script we have been discussing need any modifications?
lancelot6king Posted December 27, 2025 Posted December 27, 2025 Hi @hextun, I dont know whether you still answer the questions but I will be so grateful if you can help me. I was trying to make a command the add and equip some thing after SL scene and wait for some seconds then unequip it. My code is as such below:\ { "cmd" : [ ["set", "$1", "garbagedump.esp:852"], ["item_addex", "$player", "$1", "1", "1"], ["item_equipex", "$player", "$1", "0", "0"], ["msg_notify", "spermadd"], ["util_wait", "15"], ["msg_notify", "spermremove"], ["item_unequipex", "$player", "$1", "0"], ["item_remove", "$player", "$1", "1", "1"] ] } And is set it in MCM with 100% triggered, but it seems nothing happens, even the msg_notify didnt show up. I dont quite get what went wrong, could you plz teach me about my error? Thanks a lot,
hextun Posted December 28, 2025 Posted December 28, 2025 7 hours ago, lancelot6king said: Hi @hextun, I dont know whether you still answer the questions but I will be so grateful if you can help me. I was trying to make a command the add and equip some thing after SL scene and wait for some seconds then unequip it. My code is as such below:\ { "cmd" : [ ["set", "$1", "garbagedump.esp:852"], ["item_addex", "$player", "$1", "1", "1"], ["item_equipex", "$player", "$1", "0", "0"], ["msg_notify", "spermadd"], ["util_wait", "15"], ["msg_notify", "spermremove"], ["item_unequipex", "$player", "$1", "0"], ["item_remove", "$player", "$1", "1", "1"] ] } And is set it in MCM with 100% triggered, but it seems nothing happens, even the msg_notify didnt show up. I dont quite get what went wrong, could you plz teach me about my error? Thanks a lot, I don't see anything immediately wrong here. Remove everything except the msg_notify, maybe add a little more text there so it stands out easily when it appears. Basically, the script should just be a msg_notify. If that does not work, it suggests the problem isn't a faulty script but the trigger not running when it should.
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