b3lisario Posted January 2, 2014 Posted January 2, 2014 I think I broke it again... Now there is an issue with the space character in the esp file name. It's not just that, I tested a simpler mod with one script and it worked. As always I test with new saves, vanilla game, etc, but now I think it doesn't matter. I attach two esp files that are the same, only difference is the esp name. Don't check both. "Test Storage.esp" - Run game - Equip an armor piece, it will store - Quicksave and load, 0 items in list "TestStorage.esp" same steps, works fine bug2.7z
b3lisario Posted January 3, 2014 Posted January 3, 2014 I tried it yesterday and forgot responding here. It works!
ecartsiger Posted January 3, 2014 Posted January 3, 2014 this is probably the most important addition to skyrim modding community of all times!
h38fh2mf Posted January 3, 2014 Posted January 3, 2014 Here is an example how to create data structures / classes, use pointers and query by any member in the data structure. I chose to write the example as a pregnancy structure because it needs multiple variables of different type and illustrates how to use pointers to save the instance of pregnancy on multiple actors. This is also an example of a map. Here is the script source: ;/ This is an example script how to create a class structure in PapyrusUtil and use pointers to instances of that class and query all available instances by any member. We will have a class called Pregnancy with the following variables: int Id Actor mother Actor father int startTime int duration Race childRace int childGender /; scriptname Pregnancy hidden int function GenerateId() global int highId = StorageUtil.GetIntValue(none, "pregnancy_high") highId += 1 StorageUtil.SetIntValue(none, "pregnancy_high", highId) return highId endfunction int function CreateNew(Actor mother, Actor father, int startTime, int duration, Race childRace, int childGender) global int newId = GenerateId() ; This is saved as a data structure using index so that the index of ID is same as all of the members. StorageUtil.IntListAdd(none, "pregnancy_id", newId) StorageUtil.FormListAdd(none, "pregnancy_mother", mother) StorageUtil.FormListAdd(none, "pregnancy_father", father) StorageUtil.IntListAdd(none, "pregnancy_start", startTime) StorageUtil.IntListAdd(none, "pregnancy_duration", duration) StorageUtil.FormListAdd(none, "pregnancy_childrace", childRace) StorageUtil.IntListAdd(none, "pregnancy_childgender", childGender) ; Add pointer of pregnancy to parents. So we can iterate pregnancy instances using ; list on actor without iterating through all of the pregnancies. StorageUtil.IntListAdd(mother, "pregnancy_parent", newId) StorageUtil.IntListAdd(father, "pregnancy_parent", newId) return newId endfunction function Delete(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif ; Remove pointer to this instance from parents. Actor mother = GetMother(id) if(mother) StorageUtil.IntListRemove(mother, "pregnancy_list", id) endif Actor father = GetFather(id) if(father) StorageUtil.IntListRemove(father, "pregnancy_list", id) endif ; Delete instance. StorageUtil.IntListRemoveAt(none, "pregnancy_id", index) StorageUtil.FormListRemoveAt(none, "pregnancy_mother", index) StorageUtil.FormListRemoveAt(none, "pregnancy_father", index) StorageUtil.IntListRemoveAt(none, "pregnancy_start", index) StorageUtil.IntListRemoveAt(none, "pregnancy_duration", index) StorageUtil.FormListRemoveAt(none, "pregnancy_childrace", index) StorageUtil.IntListRemoveAt(none, "pregnancy_childgender", index) endfunction Actor function GetMother(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return none endif return StorageUtil.FormListGet(none, "pregnancy_mother", index) as Actor endfunction function SetMother(int id, Actor newMother) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif Actor mother = GetMother(id) if(mother == newMother) return endif ; Remove pointer of pregnancy from old mother and add it to new mother. StorageUtil.IntListRemove(mother, "pregnancy_list", id) StorageUtil.IntListAdd(newMother, "pregnancy_list", id) ; Set new mother in instance of pregnancy. StorageUtil.FormListSet(none, "pregnancy_mother", index, newMother) endfunction Actor function GetFather(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return none endif return StorageUtil.FormListGet(none, "pregnancy_father", index) as Actor endfunction function SetFather(int id, Actor newFather) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif Actor father = GetFather(id) if(father == newFather) return endif ; Remove pointer of pregnancy from old father and add it to new father. StorageUtil.IntListRemove(father, "pregnancy_list", id) StorageUtil.IntListAdd(newFather, "pregnancy_list", id) ; Set new father in instance of pregnancy. StorageUtil.FormListSet(none, "pregnancy_father", index, newFather) endfunction int function GetStartTime(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return -1 endif return StorageUtil.IntListGet(none, "pregnancy_start", index) endfunction function SetStartTime(int id, int time) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif StorageUtil.IntListSet(none, "pregnancy_start", index, time) endfunction int function GetDuration(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return -1 endif return StorageUtil.IntListGet(none, "pregnancy_duration", index) endfunction function SetDuration(int id, int duration) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif StorageUtil.IntListSet(none, "pregnancy_duration", index, duration) endfunction Race function GetChildRace(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return none endif return StorageUtil.FormListGet(none, "pregnancy_childrace", index) as Race endfunction function SetChildRace(int id, Race newRace) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif StorageUtil.FormListSet(none, "pregnancy_childrace", index, newRace) endfunction int function GetChildGender(int id) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return -1 endif return StorageUtil.IntListGet(none, "pregnancy_childgender", index) endfunction function SetChildGender(int id, int gender) global int index = StorageUtil.IntListFind(none, "pregnancy_id", id) if(index == -1) return endif StorageUtil.IntListSet(none, "pregnancy_childgender", index, gender) endfunction int function GetParentPregnancy(Actor parent, int index) global if(index >= StorageUtil.IntListCount(parent, "pregnancy_list")) return -1 endif return StorageUtil.IntListGet(parent, "pregnancy_list", index) endfunction bool function IsPregnant(Actor mother) global ; Check if actor is pregnant by finding it in mother list. The value returned by find here ; is the index (not Id!) of the first pregnancy where this actor is mother. return StorageUtil.FormListFind(none, "pregnancy_mother", mother) != -1 endfunction
h38fh2mf Posted January 11, 2014 Posted January 11, 2014 Updated version with few fix for string list. Added option to replace animations on objects. Example to make character laugh every time they try to move: Scriptname animtest extends activemagiceffect Idle Property replaceIdle Auto ; set to "IdleLaugh" from CK Event OnEffectStart(Actor akTarget, Actor akCaster) if(ObjectUtil.CountReplaceAnimation(akTarget) != 0) ObjectUtil.ClearReplaceAnimation(akTarget) Debug.Notification("Cleared animation replacement.") else ObjectUtil.SetReplaceAnimation(akTarget, "moveStart", replaceIdle) Debug.Notification("Replaced something.") endif endEvent This persists through save games so you have to remove if you want to disable this replacement. This needs a lot of testing before you should release a mod I expect there to be problems. Also added import / export of selected data in JSON format. Let's say you have variables with prefix "myvar_" that you want to save to a separate file for later import or whatever. Example: ;bool function ExportFile(string fileName, string restrictKey = "", int restrictType = -1, Form restrictForm = none, bool restrictGlobal = false, bool keyContains = false, bool append = true) global native ;bool function ImportFile(string fileName, string restrictKey = "", int restrictType = -1, Form restrictForm = none, bool restrictGlobal = false, bool keyContains = false) global native ;StorageUtil.ExportFile("myfile") This would export all storageUtil save game variables. StorageUtil.ExportFile("myfile", restrictKey="myvar_", keyContains=true) ; save all variables that's named "*myvar_*" wherever they are ; ... later StorageUtil.ImportFile("myfile") ; import all variables from a file - this will add/replace previous variables in save game ; ... later StorageUtil.ImportFile("myfile", restrictKey="myvar_abc") ; import only myvar_abc variable from file ; ... later StorageUtil.ExportFile("myfile", restrictKey="myvar_abc") ; update file but only myvar_abc variable, others will remain same in the file ; ... later StorageUtil.ExportFile("myfile", restrictKey="myvar_abc", append=false) ; clear file and save ONLY myvar_abc variable ; ... later StorageUtil.ExportFile("myfile", restrictForm=akTarget) ; save all variables on akTarget to file ; ... later StorageUtil.ImportFile("myfile", restrictForm=akTarget) ; load only variables about this form from file The file is created in Data/SKSE/Plugins/StorageUtilData folder and is a .txt file unless you specify otherwise. Data saved is JSON format and can be edited in text editor if you like. If you want to add data for your MOD it is not a bad idea to add it as this file and when initializing script you load this data file. But to make sure there are no errors you should export the data from a script instead of manually writing the file. It should be fine to edit the file after exporting. I added an example.txt file with JSON data to show how the format works.
Uriel Posted January 11, 2014 Posted January 11, 2014 Thanks for your work on this. Can we replace movement animations with this now? We have that PCEA mod from Nexus, but it makes Skyrim really unstable and causes incompatibilities with quite some animation mods. Also, will we, mere users, experience another heart-dividing war between PapirusUtil and jContainers in near future?
h38fh2mf Posted January 11, 2014 Posted January 11, 2014 Hmm I think you can use both at the same time this and jContainers but competition is always good it makes for better mods I really don't know how the animation replacement will work because I don't know anything about animations. I only tested few simple vanilla animations and it was working so rest is up to modders to figure out. If there are problems or requests I will try to fix of course. If you know how you could install custom movement animations as separate idle animations and try to replace the default game ones with this new idle see if it's working.
Uriel Posted January 11, 2014 Posted January 11, 2014 Yep, competition is good, but we will eventually end up with one mod requiring PU, and another requiring jC. Double things - square the bugs! Anyway, i'm staying with Papyrus Empire ruled by h38gh2mf dynasty. Damn those jCloak rebels. xD
h38fh2mf Posted January 11, 2014 Posted January 11, 2014 Haha the empire appreciates your support I did just think that you can replace default character idle with a custom pose that you could select from MCM menu. Some people would probably like that mod.
dweezer Posted January 15, 2014 Posted January 15, 2014 So in another thread I'm trying to use StorageUtil to hold onto values as an actor's NiNodes are changed via spell effects and have it persist across cell changes and game loading. It's working on the player's part, but not so much for other NPCs. Do I need to unset the float variable(s) before setting the new value, or can I just overwrite without unsetting?
h38fh2mf Posted January 15, 2014 Posted January 15, 2014 Overwriting is fine. I don't really know what you mean by not working this is beyond vague, post your script.
oli3d Posted January 16, 2014 Posted January 16, 2014 hello h38fh2mf... Nice tool ... solves many problems i saw in my planing of a more complexe mod. Thank you for this tool... Moin Oli
Heromaster Posted February 9, 2014 Posted February 9, 2014 Would it be possible to extend the Condition Function List, so that we can work directly with Variables stored in Papyrus Util? Something like a new Condition Function GetPapyrusUtilVariable.
h38fh2mf Posted February 10, 2014 Posted February 10, 2014 I thought about how to do this but didn't come up with a solution that works for all situations.
Guest Posted February 16, 2014 Posted February 16, 2014 Hi! Sexlab have his own papirusutil files and some are newer, are this the same? what should we use? Thanks!
Heromaster Posted February 20, 2014 Posted February 20, 2014 Could you give an example of how to create a array of arrays? You mentioned that in the JContainer thread but I don't see how that would be possible. And I have a suggestion: After building some arrays with PapyrusUtil I miss some functions like Reverse() and InsertAt(). I know that it can be done with Papyrus alone, but I would like to stress the Papyrus Engine as less as possible. I would like them seen as native support from PapyrusUtil.
h38fh2mf Posted February 20, 2014 Posted February 20, 2014 Reverse and sort are good ideas for list, but I didn't think of them when making mod. I didn't add insert for some reason even though I did think of it, but I don't remember why. List of lists has example on http://www.loverslab.com/topic/23713-papyrusutil/?p=625688 It's not specifically list of lists but it has the idea of pointers. Using them you can make any structures. There is an example of using map structure to store structures of data and then get pointers to the structure. Later you store pointers to another list to have list of maps for example. Or if you want very simple then just create multiple lists like this: "list_1" "list_2" "list_3" then another: "list" where you store { 1, 2, 3 } and you can iterate all values of "list" for example to get total count of all elements: int count = 0 int i = 0 while(i < StorageUtil.IntListCount(none, "list")) int n = StorageUtil.IntListGet(none, "list", i) i += 1 string listkey = "list_" + n count += StorageUtil.IntListCount(none, listkey) endwhile I don't remember papyrus syntax well so there may be errors here. Basically you can make lists so with some work it should be possible to create any data structures. Probably best to write wrapper functions for easier management of this.
Ashal Posted February 20, 2014 Author Posted February 20, 2014 Experimenting with moving all animation data in SexLab to json files using the StorageUtil.Export() and StorageUtil.Import(). Currently having trouble getting export to work property with keyContains = true however. Given the following Papyrus: StorageUtil.ExportFile("SexLabMissionary.json", "SexLabMissionary.", keyContains = true, append = false) StorageUtil.ExportFile("SexLabMissionaryTags.json", "SexLabMissionary.Tags", append = false) I get these 2 files: SexLabMissionary.json { "stringlist" : [ null ] } SexLabMissionaryTags.json { "stringlist" : [ { "key" : [ 77, 1158035935, "SexLab.esp" ], "value" : [ { "key" : "SexLabMissionary.Tags", "value" : [ "Default", "sex", "Missionary", "Laying", "Vaginal", "MF" ] } ] } ] } Would it also be possible to enable it to create the file if it doesn't exist? Ideally I'd like to make it so SexLab automatically exports the animation data to individual files so they can be easily found and edited by users for customizing offsets and such easily. Also unrelated to this, but SKSE 1.7.0 beta is currently out and has this in changelog, "enabled previously-temporary Papyrus plugin API" I assume that means they added more official support Papyrus functions to be added with SKSE plugins, and no idea of it makes any difference from the current method you're using to add Papyrus functions, but just a heads up, just in case.
h38fh2mf Posted February 21, 2014 Posted February 21, 2014 Not sure I don't remember anymore how it works. But for me it did create file, try if starting skyrim in admin mode does create files, if it does then it is file access problem. It's checking keys like this: a=how it is saved b=what you entered bool CheckKey(std::string a, std::string b, bool contains){ if(!contains) return !_stricmp(a.c_str(), b.c_str()); if(b.size() > a.size()) return false; auto it = std::search(a.begin(), a.end(), b.begin(), b.end(), [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); } ); return it != a.end();}
Ashal Posted February 22, 2014 Author Posted February 22, 2014 Not sure I don't remember anymore how it works. But for me it did create file, try if starting skyrim in admin mode does create files, if it does then it is file access problem. It's checking keys like this: a=how it is saved b=what you entered bool CheckKey(std::string a, std::string b, bool contains) { if(!contains) return !_stricmp(a.c_str(), b.c_str()); if(b.size() > a.size()) return false; auto it = std::search(a.begin(), a.end(), b.begin(), b.end(), [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); } ); return it != a.end(); } stricmp() isn't a "string contains string" function as far as I know but more of a string == string operator. A strstr() != NULL would be more appropriate here I believe. http://msdn.microsoft.com/en-us/library/k59z8dwe.aspx
h38fh2mf Posted February 22, 2014 Posted February 22, 2014 Keep reading if !keyContains then compare directly, if keyContains == true then skip that part and instead do the rest of function. I don't know about the search algorithm I copied it from google, but it did work on my tests. I can use a different algorithm and compile new binaries for you to test if you like. I used a different function in IFPV to do the same thing.
Ashal Posted February 23, 2014 Author Posted February 23, 2014 Keep reading if !keyContains then compare directly, if keyContains == true then skip that part and instead do the rest of function. I don't know about the search algorithm I copied it from google, but it did work on my tests. I can use a different algorithm and compile new binaries for you to test if you like. I used a different function in IFPV to do the same thing. Ah you're right, that !contains pretty much nullifies my entire post. Though I still imagine a simply strstr() != NULL at the bottom to replace the algorithm would accomplish the task, unless you're accounting for something I'm not aware of. If you don't mind recompiling with a different algorithm It'd be helpful, but either way I can make do, this is really just a means for me to be lazy and not have to manually export/import individual keys. What I find odd though is that it's exporting the { "stringlist" : [ null ] } Which suggests it IS finding the key, as the only value it should find is infact a stringlist, though it certainly isn't empty as shown by the non keyContains version. If I give it a key I know is wrong with keyContains, it doens't give me the null stringlist.
h38fh2mf Posted February 23, 2014 Posted February 23, 2014 Try this, I think I had them mixed up a was switched with b. papyrusutilv20.zip
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