Jump to content

Populating MCM AddMenuOptionST with player spells and lesser powers


Recommended Posts

  • Replies 54
  • Created
  • Last Reply

Top Posters In This Topic

I was finally able to get v8 working, but it takes 21 seconds.

 

Thinking about version 9 and doing something to capture those odd spells in OnInit and then just insert them right after we clear the stringlist in OnPlayerLoadGame. It would be another stringlist, but I would only be capturing it once....

Link to comment
Share on other sites

I finally go through the papyrus source code and I was really astonished about this construct

Function Update_HAC_Spells(Spell akSpell)
    string s = akSpell.GetName()                                ; get the spell name as string
    StorageUtil.SetFormValue(None, s, akSpell as Form)          ; Make sure that the spell name string is mapped to the correct formid
    StorageUtil.StringListAdd(None, "HAC_Spells", s, False)     ; Add a new spell name to stringlist with allowdupes == FALSE
EndFunction

(1) Next code line makes no sense, should be removed!

    StorageUtil.SetFormValue(None, s, akSpell as Form)          ; Make sure that the spell name string is mapped to the correct formid

(2) We know its not possible to have a duplicated spell by creation process, so we take the default value

    StorageUtil.StringListAdd(None, "HAC_Spells", s)     ; Add a new spell name to stringlist with allowdupes == TRUE

version 10 with threading

 

;Import PO3_Events_Alias
;Import PO3_SKSEFunctions
; https://github.com/powerof3/PapyrusExtenderSSE/tree/master/src/Papyrus

;Import StorageUtil
; https://github.com/Vicyntae/SCLSE/blob/master/Data/Source/Scripts/StorageUtil.psc

    ; StorageUtil name: "HAC_Spells"  ; list of strings, names of spells the player has currently
    ; StorageUtil name: "map"         ; list of strings as keyname to formID as value
                                      ; used in MCM script with StorageUtil.GetFormValue(None, (StorageUtil.StringListGet(None, "HAC_Spells", INDEX+1)))

  Bool bRefresh     ; [default=False]
  Int  iFlag        ; [default=0]

Function UpdateFlag()  ; helper
;-------
    iFlag = iFlag - 1
EndFunction


; -- Events -- 3 + "Refresh"

Event OnInit()
; called first time mod will be seen on game (newgame or savegame)

    InitList()        ; "first come, first serve"
EndEvent


Event OnPlayerLoadGame()
; called every time a mod was loaded by savegame (player alias script only!)

    InitList()        ; "first come, first serve"
EndEvent


Event OnSpellLearned(Spell akSpell)
; added by Papyrus Extender SSE

    While ( bRefresh )
        Utility.Wait(0.1)    ; do the stringlist update for one thread only, any other (sneaking in) has to wait here
    EndWhile

    bRefresh = TRUE     ; *T*

    StorageUtil.StringListRemoveAt(None, "HAC_Spells", 0)       ; remove the escape menu, before adding a new spell to stringlist
    Update_HAC_Spells(akSpell, False)                           ; add this spell to stringlist
    InsertMenu_HAC_Spells()                                     ; insert the escape menu

    bRefresh = False    ; ***
EndEvent


;======================================
State Refresh
;============
    Function InitList()
        ; empty here, OnInit() or OnPlayerLoadGame() event can be the first
    EndFunction


    Event OnUpdate()
    If ( bRefresh )
        RETURN    ; - STOP - Just in case!
    EndIf
;   ------------------------
        bRefresh = TRUE     ; *T*

        PO3_Events_Alias.UnRegisterForSpellLearned(self)        ; unregister for the special event
        Debug.Notification("HAC: Starting Spell Maintenance")

        StorageUtil.StringlistClear(None, "HAC_Spells")         ; *** clear ***
; ------
        iFlag = 3                ; prepare for count of threads, that running now

        gotoState("Threading")        ; ### STATE ###
        RegisterForSingleUpdate(0.0)
        RegisterForSingleUpdateGameTime(0.0)

        CreateSpellList()        ; thread *** 1 ***

        WHILE (iFlag)            ; iFlag != 0
            Utility.Wait(0.1)    ; wait until all threads have finished the loop
        ENDWHILE
; ------
        InsertMenu_HAC_Spells()                                 ; Insert the escape menu
        gotoState("")                 ; ### STATE ###

        Debug.Notification("HAC: Spell Maintenance Completed")
        PO3_Events_Alias.RegisterForSpellLearned(self)          ; register for special event

        bRefresh = False    ; ***
    EndEvent
;=======
endState


;======================================
State Threading
;==============
    Function InitList()
        ; empty here
    EndFunction

    Event OnUpdate()
        Utility.Wait(0.1)
        CreateSpellList2()       ; thread *** 2 ***
    Endevent

    Event OnUpdateGameTime()
        Utility.Wait(0.2)
        CreateSpellListByRace()  ; thread *** 3 ***
    EndEvent
;=======
endState



; -- Functions -- (1) + 6

Function InitList()
;-------- 1
    gotoState("Refresh")            ; ### STATE ###
    RegisterForSingleUpdate(1.0)
EndFunction


Function Update_HAC_Spells(Spell akSpell, Bool b=TRUE)
;-------- 2
; None         = save list globally
; "HAC_Spells" = "keyname" to know where to store
; akSpell.GetName() = string to save to "keyname" list
; b = allow dupes (default=TRUE)

; Add a new spell name to stringlist with allowdupes == TRUE
    StorageUtil.StringListAdd(None, "HAC_Spells", akSpell.GetName(), b)
EndFunction


Function InsertMenu_HAC_Spells()
;-------- 3
; Sort the stringlist before insert the menu entry!
    StorageUtil.StringListSort(None, "HAC_Spells")

; Insert to stringlist at index 0 an escape menu as way out
    StorageUtil.StringListInsert(None, "HAC_Spells", 0, "Back / Cancel / No Change")
EndFunction


Function CreateSpellList()  ; OnUpdate() "Refresh"
;-------- 4
    actor player = Game.GetPlayer()

; https://www.creationkit.com/index.php?title=GetSpellCount_-_Actor
; https://www.creationkit.com/index.php?title=GetNthSpell_-_Actor

; i = 0  -> n/a
; i = 1  -> n/a
; i = 2  -> 2  - 2 --> GetNthSpell(0)
; i = 3  -> 3  - 2 --> GetNthSpell(1)
; i = 50 -> 50 - 2 --> GetNthSpell(48)

    int i = player.GetSpellCount()
    While (i > 1)
        i = i - 2        ; -2

;;;        ; return the base magicka cost of the spell
;;;        int Function GetMagickaCost() native

        spell akSpell = player.GetNthSpell(i)      
        If akSpell.GetNthEffectMagicEffect(0).GetCastingType()        ; (0..2)
               ; 1 = Fire and Forget
               ; 2 = Concentration

            Update_HAC_Spells( akSpell )
        EndIf
    EndWhile

    UpdateFlag()
EndFunction


Function CreateSpellList2()  ; OnUpdate() "Threading"
;-------- 5
    actor player = Game.GetPlayer()

; https://www.creationkit.com/index.php?title=GetSpellCount_-_Actor
; https://www.creationkit.com/index.php?title=GetNthSpell_-_Actor

; i = 0  -> n/a
; i = 1  -> 2  - 2 --> GetNthSpell(0)
; i = 2  -> 3  - 2 --> GetNthSpell(1)
; i = 3  -> 4  - 2 --> GetNthSpell(2)
; i = 50 -> 51 - 2 --> GetNthSpell(49)

    int i = player.GetSpellCount()
    If (i > 0)
        i = i + 1        ; after validation of spellcount +1 here
    EndIF

    While (i > 1)
        i = i - 2        ; -2

        spell akSpell = player.GetNthSpell(i)
        If akSpell.GetNthEffectMagicEffect(0).GetCastingType()        ; (0..2)
               ; 1 = Fire and Forget
               ; 2 = Concentration

            Update_HAC_Spells( akSpell )
        EndIf
    EndWhile

    UpdateFlag()
EndFunction


  FormList PROPERTY RaceSpecificSpells auto
  ; *** you have to create a new formlist by CK and
  ; fill with all spells which are not affected by SKSE function "player.GetNthSpell(i)" **

Function CreateSpellListByRace()  ; OnUpdateGameTime() "Threading"
;-------- 6
    actor player = Game.GetPlayer()

    int i = RaceSpecificSpells.GetSize()
    While (i)
        i = i - 1        ; -1

        spell akSpell = RaceSpecificSpells.GetAt(i) as Spell
        If (akSpell) && player.HasSpell(akSpell)
            Update_HAC_Spells( akSpell )
        EndIf
    EndWhile

    UpdateFlag()
EndFunction

 

 

 

For race specific spells, you have to create a formlist which has the vanilla spells. If DLCs or other mods should have such spells you can look for mod loaded by GeFormFromFile() and add such spell to list on the fly by using this https://www.creationkit.com/index.php?title=AddForm_-_FormList

Sometimes it makes a different in runtime to use Utility.Wait() in functions and/or events.

 

(3) maybe its faster to use this SKSE native spell function instead to get the MagicEffect of spell and check for ME.castingtype()

; return the base magicka cost of the spell
   int Function GetMagickaCost() native
Edited by ReDragon2013
Link to comment
Share on other sites

ReDragon2013,

 

Your comment #1 - this is what is used to retrieve the FormID of the Spell for the MCM, it can not be removed, it is what makes this whole thing work, it maps the String to the FormID, this way we can sort the spell names and still get the FormID associated with that name.... We can't remove this unless we have another way to get the FormID of the spell from the string we are storing in the stringlist in the MCM.....

 

Your comment #2 - You are correct, we most likely don't need the FALSE anymore as the creation process ensures no duplicates and we are clearing it first. This will speed it up some I am sure, good catch - thnak you! I think I added the FALSE in there when I was working on just updating the stringlist.

 

We will need #1 in there, or some other way to get to the formID from the stringlist for this to be usable in the MCM....

Link to comment
Share on other sites

I think Sphered mentioned earlier that everyone would be able to assist better if I clearly defined what I am trying to do with this, so I thought I would take a moment on lunch to refresh the goals for anyone trying to help:

 

1) I have a mod that requires the player to select a spell that they can cast in the MCM menu, I am using AddMenuOptionST in the MCM to do this, and therefore need a string array that I can pass to it.

2) The string array should include all spells that the player can cast, and I would like it to be sorted alphabetically as in the late game you may have 200-300 spells that you can cast, them being sorted allows you to find the spell you want quicker.

3) I need to be able to, once a spell name has been selected from the MCM Menu we populated earlier, get the FormID of the spell associated with the string I just selected so my mod can use it.

4) I have another mod that I am working on that would benefit from this as well, it would be NICE but not NECESSARY if I could share the same list with the other mod (I would have it all set to run the maintenance from one mod or the other, not both). StorageUtil allows for this.

 

If there is a better way than what I have been trying I am certainly open to it.

 

My current method is:

 

1) I need the stringlist for AddMenuOptionST so I HAVE to build it.

2) I'm going to sort the stringlist, so there is no reason to build a form list that matches the string list because the stringlist indexes are going to change when I sort it,

3) so INSTEAD I am mapping the String that I am adding to the stringlist to a formID using StorageUtil.SetFormValue(None, s, akSpell as Form)

4) then I can sort the stringlist and retrieve the formID with the string in the MCM once a selection has been made using StorageUtil.GetFormValue(None, s).

 

I hope this clarifies what I am trying to do. If there is a better way I am definitely open to it....

 

GetNthSpell doesn't get all the spells, so I widened what I was getting to include ALL spells and then check if the player has the spell, but this is very slow (if numbers help, after we filter for GetCastType the list becomes about 1,285 spells in the load order I am doing most of the testing on). So that's why I'm looking at somehow coming up with the spells that GetNthSpell will never find and then use it, because it will only ever be, at most, all the spells that have been added to the player.

 

I hope this helps!

Link to comment
Share on other sites

String Theory = 0x9DB72 + "ZZ" + 0x9FA64 + "ZZ" + 0xA0E7D + "ZZ" + 0xA59AD + "ZZ" + 0xA59AE + "ZZ" + 0xA0E7F + "ZZ" + 0xF5E0B + "ZZ" + 0x109AC2 + "ZZ" + 0x109AC3

 

GotoState(Theory)

 

The ZZs is a way to isolate the indiv hexes. Anyway...

 

Later....

 

Hey whats that long ass string I need those hexes now

 

String Cheese = GetState()

 

String[] Splut = StringUtil.Split(Cheese,"ZZ")

 

Form ShouldBeHealSpellArt = Game.GetFormEx(Splut[2] as Int)

 

You basically create an array using simply your state. You can declare the string instead up top etc. That single string can be read by your MCM

 

You can append this too. Fetch it... add more. Remove stuff. Whatever. It can go beyond 128 items too. Just need a new separator so we can tell where 128 is and do a new split

 

Just depends how dedicated you are to this. Sounds like you are, so... I suggest def getting acquainted with StringUtil

Link to comment
Share on other sites

Thank you Sphered, I'll take a longer look at StingUtil.psc, although I see you storing the FormIDs AS strings, I'm not sure how they are associated with THE string.

 

In your example above, I see you creating an array of formIDs with "ZZ" as a delimiter in one string (Theory), then , then returning the formIDs as strings in a string list, splitting them on the delimiter "ZZ", but I'm not sure why I would want to do that for my use case?

 

I'm trying to create a string array of player spell names, and then be able to grab the FormID of that spell later (knowing that I will sort the string array, so having two arrays, one string one form, doesn't work unless I can keep their indexes in sync as I sort the string list, which I have no idea how to do THAT other than something like StorageUtil or possibly JMap or JDB from JContainers which all allow you to map a form value to a string) and I'm trying to apply that to the example above and am quite confused....

 

And you are correct, I am VERY committed to this as I am learning A LOT from you all, even if it never ends up outside of my load order, every mod I ever make is going to benefit from what you folks are teaching me and I appreciate the time you all spend explaining things to me more than I can express...

 

Thank you!

Link to comment
Share on other sites

And I'm a SQL guy, so that is making this all that much more difficult for me, because in SQL I would just create a table that has a String column and a FormID column, fill each, sort the whole thing on the string and return just the sorted string list when I need it, then lookup the FormID using String when I need it...

 

Maybe I'm just stuck in 35 years of SQL thinking and can't get past the methods I've used for so long LOL, but it seems like this should be something easy and it shouldn't be something that I'm the first person to come up against...

 

I was, at one point, creating a form array manually in the script with all the vanilla spells using GetFormFromFile().

 

I could, as ReDragon2013 suggested, just make a form list for that and run through it to add the vanilla spells/lesser powers, but then what if the end user is using a mod that changes those?

 

My goal was to do it all via script and to account for everything that I could, but maybe I only deal with "Vanilla" race spells, and if they use a mod that changes that stuff they won't have access to those spells at all....

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...