Jump to content

Recommended Posts

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

Link to comment

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

 

 

Link to comment

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.

Link to comment

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?

Link to comment

Hmm I think you can use both at the same time this and jContainers but competition is always good it makes for better mods :P

 

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.

Link to comment

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

Link to comment

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?

Link to comment
  • 4 weeks later...

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.

Link to comment

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.

Link to comment

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.

Link to comment

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();
}

Link to comment

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

Link to comment

Keep reading :P 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.

Link to comment

Keep reading :P 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.

Link to comment

Create an account or sign in to comment

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

Create an account

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

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

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

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. For more information, see our Privacy Policy & Terms of Use