Jump to content

[LE] How to tell Papyrus "If player has SpellB, use SpellB not default SpellA"?


Recommended Posts

How would one tweak Jaxons Lights Please to use mod-provided spells? I have a lighting spell enhancement mod that provides stronger versions of Magelight and Candlelight. I just don't understand Papyrus's poorly documented syntax well enough to pull this off, simple as it seems. No matter what I try, I get errors relating to variable/property types ("did you forget a cast?", "not a known user-defined type", "cannot cast a string to a form", "not a known user-defined type", and so on. I can't believe it's this hard to tell Papyrus "if the player has Magelight3, then use that spell ID, not Magelight". Argh. Here's the latest version I was working on, but this is about the 20th variant I've tried over the course of three hours.

Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giMageLightHotkey Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
Spell Property Candlelight5 Auto
Spell Property Candlelight4 Auto
Spell Property Candlelight3 Auto
Spell Property Candlelight2 Auto
Spell Property Magelight5 Auto
Spell Property Magelight4 Auto
Spell Property Magelight3 Auto
Spell Property Magelight2 Auto
Sound Property sndFail Auto
String CLSpellID
String MLSpellID
 
Event OnInit()
; Does the player have better lights from MoreMagicLight.esp?
    if (actPlayer.HasSpell(Candlelight5))
        CLSpellID = Candlelight5
    elseif (actPlayer.HasSpell(Candlelight4))
        CLSpellID = Candlelight4
    elseif (actPlayer.HasSpell(Candlelight3))
        CLSpellID = Candlelight3
    elseif (actPlayer.HasSpell(Candlelight2))
        CLSpellID = Candlelight2
    elseif (actPlayer.HasSpell(splCandlelight))
        CLSpellID = splCandlelight
    endif
    if (actPlayer.HasSpell(Magelight5))
        MLSpellID = Magelight5
    elseif (actPlayer.HasSpell(Magelight4))
        MLSpellID = Magelight4
    elseif (actPlayer.HasSpell(Magelight3))
        MLSpellID = Magelight3
    elseif (actPlayer.HasSpell(Magelight2))
        MLSpellID = Magelight2
    elseif (actPlayer.HasSpell(splMagelight))
        MLSpellID = splMagelight
    endif
 
    OnSettingsChanged()
EndEvent
 
Function OnSettingsChanged()
;update key mappings; called by MCM menu
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
EndFunction
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode
        ;see if there is an object grabbed
        If KeyCode == giCandleLightHotkey.GetValueInt()
            If actPlayer.HasSpell(CLSpellID)
                bCandleLightOn = !bCandleLightOn
                if bCandleLightOn
                    CastCandleLight()
                Else
                    actPlayer.DispelSpell(CLSpellID)
                EndIf
            Else
                Debug.Notification("Unable to cast Candlelight because you don't know that spell")
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
            If actPlayer.HasSpell(MLSpellID)
                If actPlayer.GetActorValue("Magicka") >= MLSpellID.GetEffectiveMagickaCost(actPlayer)
                    actPlayer.DamageActorValue("Magicka", MLSpellID.GetEffectiveMagickaCost(actPlayer))
                    MLSpellID.Cast(actPlayer)
                Else
                    sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
                EndIf
            Else
                Debug.Notification("Unable to cast Magelight because you don't know that spell")
            EndIf
        EndIf
    EndIf
EndEvent
 
Event OnUpdate()
;Event OnUpdateGameTime()
    if bCandleLightOn
        CastCandleLight()
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent
 
Function CastCandleLight()
    if (actPlayer.GetActorValue("Magicka") >= CLSpellID.GetEffectiveMagickaCost(actPlayer))
        actPlayer.DamageActorValue("Magicka", CLSpellID.GetEffectiveMagickaCost(actPlayer))
        CLSPellID.Cast(actPlayer)
        ;       RegisterForSingleUpdateGameTime((CLSpellID.GetNthEffectDuration(0) as float) * 0.00028 * gfTimeScale.GetValue() )  ;convert spell duration seconds to game hours
        RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        Debug.Notification("Insufficient magicka to renew Candlelight; retry in 5 seconds ...")
        RegisterForSingleUpdate(5.0)
    EndIf
EndFunction

 

 

 

Here's the original script:


 
Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giMageLightHotkey Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
 
Sound Property sndFail Auto
 
Event OnInit()
    OnSettingsChanged()
EndEvent
 
Function OnSettingsChanged()
;update key mappings; called  by MCM menu
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
EndFunction
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode
        ;see if there is an object grabbed
        If KeyCode == giCandleLightHotkey.GetValueInt()
            If actPlayer.HasSpell(splCandleLight)
                bCandleLightOn = !bCandleLightOn
                if bCandleLightOn
                    CastCandleLight()
                Else
                    actPlayer.DispelSpell(splCandleLight)
                EndIf
            Else
                Debug.Notification("Unable to cast Candlelight because you don't know that spell")
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
            If actPlayer.HasSpell(splMageLight)
                If actPlayer.GetActorValue("Magicka") >= splMageLight.GetEffectiveMagickaCost(actPlayer)
                    actPlayer.DamageActorValue("Magicka", splMageLight.GetEffectiveMagickaCost(actPlayer))
                    splMageLight.Cast(actPlayer)
                Else
                    sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
                EndIf
            Else
                Debug.Notification("Unable to cast Magelight because you don't know that spell")
            EndIf
        EndIf
    EndIf
EndEvent
 
Event OnUpdate()
;Event OnUpdateGameTime()
    if bCandleLightOn
        CastCandleLight()
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent
 
Function CastCandleLight()
    if (actPlayer.GetActorValue("Magicka") >= splCandleLight.GetEffectiveMagickaCost(actPlayer))
        actPlayer.DamageActorValue("Magicka", splCandleLight.GetEffectiveMagickaCost(actPlayer))
        splCandleLight.Cast(actPlayer)
;       RegisterForSingleUpdateGameTime((splCandleLight.GetNthEffectDuration(0) as float) * 0.00028 * gfTimeScale.GetValue() )  ;convert spell duration seconds to game hours
        RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        Debug.Notification("Insufficient magicka to renew Candlelight, retry in 5 seconds...")
        RegisterForSingleUpdate(5.0)
    EndIf
EndFunction

Aside: It's unclear to my why the original script (and this one, in the parts of it I've retained) is calling vanilla Magelight as splMageLight, since that's not its ID (and the mod doesn't add one by that name; it adds no spells or effects at all). I assume this is some kind of special syntax. Another mod I looked at, which adds a spell to the player's spell list the first time the game is loaded with that mod, and it calls its spell by its actual EditorID name in the "Spell Property FooBarBaz Auto" line; it doesn't use "Spell Property splFooBarBaz Auto".
Edited by Darklocq
Link to comment
Share on other sites

You're suffering from some misconceptions here, fundamental ones.

 

Firstly, what variables and properties are named has exactly zero required connection to what they represent. Consider the case of storing a number: You wouldn't name the variable that holds '5' as "Five", right?

 

So, splFooBar and FooBar can both have the Spell BarFoo or anything else of the type they're declared as in them. For Properties, which is usually how you tell Papyrus what game objects you need it to talk to, you can see what they are set to by hitting the "Edit Properties" menu. It is usual practice to name things in a descriptive way that can be understood by someone looking at the code, so often they closely match an editor ID, but it's by no means always true.

 

 

Second is that Papyrus is a fairly strongly typed language. Every variable and property has a specific kind of thing it can store: integers, floats, Forms, Actors, whatever. You declare this explicitly when you create any of them. Some, but not all, types can be cast between each other. Integers can be converted to and from floating point numbers, anything can be converted to a string (but the reverse is not true), script objects can be converted along their inheritance tree (always up; down if and only if they are also the subtype you cast to. For example, an Actor can be cast to an ObjectReference can be cast to a Form, but a Form can only be cast to an ObjectReference is it is an actual thing in the game world, with position data and so on. You can find a table here:https://www.creationkit.com/index.php?title=Category:Script_Objects)

 

 

So what's happening in your original script is that, for example, when you say, "actPlayer.HasSpell(CLSpellID)", you're asking if the player has the spell "<arbitrary sequence of letters and characters>". As far as the compiler is concerned this is a nonsense, like asking about the taste of five.

 

Fortunately the coding to solve your problem simplifies things. You don't need to convert to strings at all! (Or back!)

 

The logic flow in the Jaxon mod is to have a specific hotkey that then uses Papyrus scripting to simulate casting a spell through the game systems, because those game systems won't let it quite do what is desired. All you have to do is change what spell is being cast.

 

Consider something like this:

 

 

Scriptname JaxonzLightsPleaseEXT extends JaxonzLightsPlease
{Simple utility to improve Candlelight and Magelight casting}

FormList Property CandleLightList Auto
FormList Property MageLightList Auto

Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode

    if KeyCode == giCandleLightHotkey.GetValueInt()
        Spell testSpell = GetBestKnownSpell(CandleLightList)
        if testSpell
            splCandleLight = testSpell
        endif
    elseif KeyCode == giMageLightHotkey.GetValueInt()
        Spell testSpell = GetBestKnownSpell(MageLightList)
        if testSpell
            splMageLight = testSpell
        endif
    endif

       parent.OnKeyDown(Int KeyCode)
    EndIf
EndEvent

;assumes formlist is organized from least(0) to best(n)
Function GetBestKnownSpell(FormList akSpellList)
    int i = akSpellList.GetSize()
    Spell bestSpell = none
    
    while i > 0 && !bestSpell
        i-=1
        Spell testSpell = akSpellList.GetAt(i) as Spell
        if testSpell
            if actPlayer.HasSpell(testSpell)
                return testSpell
            endif
        endif
    endwhile
    return none
EndFunction

 

When a Papyrus script extends another it has access to all the code and functions written in the first, and the properties (though not the variables). So what's happening here is I have, in essence, inserted a preprocessor that pulls the best spell out of the appropriate formlist, and saves that (if the player has it) into the property that the rest of the Jaxon script accesses in its casting routines and logic.

 

Other methods for doing the same thing -- e.g. just directly editing the code of the original script -- exist, depending on implementation details.

Link to comment
Share on other sites

This doesn't compile, unfortunately.

 

I first got:

* (22, 24) Mismatched input 'int', expecting RPAREN

* (22, 35) Required (...)+ loop did not match anything at input ')'

 

After fixing "parent.OnKeyDown(Int KeyCode)" to read just "parent.OnKeyDown()", it got past that problem, but had other errors:

* (22, 11) Argument keycode is not specified and has no default value

* (36, 16) Cannot return a spell from getbestknownspell, the function does not return a value

 

I tried integrating the gist of this into the original script (I wasn't actually quite sure what to do with a separate one):

 

 

 

Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giMageLightHotkey Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
Sound Property sndFail Auto
FormList Property CandleLightList Auto
FormList Property MageLightList Auto
 
Event OnInit()
    OnSettingsChanged()
EndEvent
 
Function OnSettingsChanged()
;update key mappings; called  by MCM menu
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
EndFunction
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode
        ;see if there is an object grabbed
        If KeyCode == giCandleLightHotkey.GetValueInt()
            Spell testSpell = GetBestKnownSpell(CandleLightList)
            If testSpell
                splCandleLight = testSpell
            EndIf
            If actPlayer.HasSpell(splCandleLight)
                bCandleLightOn = !bCandleLightOn
                If bCandleLightOn
                    CastCandleLight()
                Else
                    actPlayer.DispelSpell(splCandleLight)
                EndIf
            Else
                Debug.Notification("Unable to cast Candlelight because you don't know that spell")
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
            Spell testSpell = GetBestKnownSpell(MageLightList)
            If testSpell
                splMageLight = testSpell
            EndIf
            If actPlayer.HasSpell(splMageLight)
                If actPlayer.GetActorValue("Magicka") >= splMageLight.GetEffectiveMagickaCost(actPlayer)
                    actPlayer.DamageActorValue("Magicka", splMageLight.GetEffectiveMagickaCost(actPlayer))
                    splMageLight.Cast(actPlayer)
                Else
                    sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
                EndIf
            Else
                Debug.Notification("Unable to cast Magelight because you don't know that spell")
            EndIf
        EndIf
    EndIf
EndEvent
 
Event OnUpdate()
;Event OnUpdateGameTime()
    if bCandleLightOn
        CastCandleLight()
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent
 
Function GetBestKnownSpell(FormList akSpellList)
;assumes formlist is organized from least(0) to best(n)
    Int i = akSpellList.GetSize()
    Spell bestSpell = none
    While i > 0 && !bestSpell
        i-=1
        Spell testSpell = akSpellList.GetAt(i) as Spell
        If testSpell
            If actPlayer.HasSpell(testSpell)
                Return testSpell
            EndIf
        EndIf
    EndWhile
    Return none
EndFunction
 
Function CastCandleLight()
    If (actPlayer.GetActorValue("Magicka") >= splCandleLight.GetEffectiveMagickaCost(actPlayer))
        actPlayer.DamageActorValue("Magicka", splCandleLight.GetEffectiveMagickaCost(actPlayer))
        splCandleLight.Cast(actPlayer)
;       RegisterForSingleUpdateGameTime((splCandleLight.GetNthEffectDuration(0) as float) * 0.00028 * gfTimeScale.GetValue() )  ;convert spell duration seconds to game hours
        RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        Debug.Notification("Insufficient magicka to renew Candlelight, retry in 5 seconds...")
        RegisterForSingleUpdate(5.0)
    EndIf
EndFunction

Link to comment
Share on other sites

(Cont'd from above (my post kept truncating at the /spoiler:)

 

But I still got the "Cannot return a spell from getbestknownspell, the function does not return a value" error despite it clearly returning a value.

I searched around, and it seems that functions in this language default to Int as the return type, but must be explicitly defined as another if this is not the intent. I could not find a list of return type, but guessed blindly (from Int) that it's the entire list of things like Bool, Spell, Actor, etc., so I tried "Spell Function GetBestKnownSpell(FormList akSpellList)" in your code, and that got rid of that error, though the one about "Argument keycode is not specified and has no default value" remained.
I tried the same fix in my merged version, and it compiled!
Now, I'm not sure if doing it as I'm doing it is a good idea, or if this "which spells you got?" test should be moved to OnSettingsChanged(), at the cost of not noticing your spell list has changed unless you twiddle something in the MCM. Or maybe there's a pre-existing function for detecting changes to the spell list. Or a way to run this check only every few minutes, or something. It just seems inefficient to ask what spells you have every single time.
Might also be better to never return a value of "None" from that function checking the form ID lists. I assume I can feed it the default names of the spell to check as a second function parameter (which ultimately comes from the Property definition in the ESP, not in the script) as a default to fall back on. And probably also best to get the actual name of the spell for the error messages instead of saying "Candlelight" or "Magelight" in a hard-coded way – to account for translations and for mods that rename vanilla spells (the one I'm patching does so, and calls "Candlelight" by "Firefly I").
I THINK this will do all of that:


Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giMageLightHotkey Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
Spell Property DefaultVanillaSpell Auto
Sound Property sndFail Auto
FormList Property CandleLightList Auto
FormList Property MageLightList Auto
 
Event OnInit()
    OnSettingsChanged()
EndEvent
 
Function OnSettingsChanged()
;update key mappings; called  by MCM menu
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
EndFunction
 
Spell Function GetBestKnownSpell(FormList akSpellList, Spell DefaultVanillaSpell)
;assumes formlist is organized from least(0) to best(n)
    Int i = akSpellList.GetSize()
    Spell bestSpell = none
    While i > 0 && !bestSpell
        i-=1
        Spell testSpell = akSpellList.GetAt(i) as Spell
        If testSpell
            If actPlayer.HasSpell(testSpell)
                Return testSpell
            EndIf
        EndIf
    EndWhile
    Return DefaultVanillaSpell
EndFunction
 
Function CastCandleLight()
    If (actPlayer.GetActorValue("Magicka") >= splCandleLight.GetEffectiveMagickaCost(actPlayer))
        actPlayer.DamageActorValue("Magicka", splCandleLight.GetEffectiveMagickaCost(actPlayer))
        splCandleLight.Cast(actPlayer)
;       RegisterForSingleUpdateGameTime((splCandleLight.GetNthEffectDuration(0) as float) * 0.00028 * gfTimeScale.GetValue() )  ;convert spell duration seconds to game hours
        RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        Debug.Notification("Insufficient magicka to renew "+splCandleLight.GetName()+", retry in 5 seconds...")
        RegisterForSingleUpdate(5.0)
    EndIf
EndFunction
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode
        ;see if there is an object grabbed
        If KeyCode == giCandleLightHotkey.GetValueInt()
            Spell testSpell = GetBestKnownSpell(CandleLightList, splCandleLight)
            If testSpell
                splCandleLight = testSpell
            EndIf
            If actPlayer.HasSpell(splCandleLight)
                bCandleLightOn = !bCandleLightOn
                If bCandleLightOn
                    CastCandleLight()
                Else
                    actPlayer.DispelSpell(splCandleLight)
                EndIf
            Else
                Debug.Notification("Unable to cast "+splCandleLight.GetName()+" because you don't know that spell")
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
            Spell testSpell = GetBestKnownSpell(MageLightList, splMageLight)
            If testSpell
                splMageLight = testSpell
            EndIf
            If actPlayer.HasSpell(splMageLight)
                If actPlayer.GetActorValue("Magicka") >= splMageLight.GetEffectiveMagickaCost(actPlayer)
                    actPlayer.DamageActorValue("Magicka", splMageLight.GetEffectiveMagickaCost(actPlayer))
                    splMageLight.Cast(actPlayer)
                Else
                    sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
                EndIf
            Else
                Debug.Notification("Unable to cast "+splMageLight.GetName()+" because you don't know that spell")
            EndIf
        EndIf
    EndIf
EndEvent
 
Event OnUpdate()
;Event OnUpdateGameTime()
    if bCandleLightOn
        CastCandleLight()
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent

It DID compile at least, but bears extensive testing.
I did not move the FormIDList checks. It occurs to me that people can use spell deletion mods or the Console to remove spells at any time, so this really does need to be sure the spell is available. However, it bugs my sense of code efficiency that this is asking twice whether the player has the spell (once to decide which to use and again to decide to use it). I don't know enough about the language to be sure how to streamline that. I did put the functions at the top, though; just how I roll from too long using languages that require them pre-declared before use.
Not sure the explicit declaration of DefaultVanillaSpell near the top is necessary if it's created as a function parameter name.
Link to comment
Share on other sites

 

This doesn't compile, unfortunately.

 

I first got:

* (22, 24) Mismatched input 'int', expecting RPAREN

* (22, 35) Required (...)+ loop did not match anything at input ')'

 

After fixing "parent.OnKeyDown(Int KeyCode)" to read just "parent.OnKeyDown()", it got past that problem, but had other errors:

* (22, 11) Argument keycode is not specified and has no default value

* (36, 16) Cannot return a spell from getbestknownspell, the function does not return a value

 

I tried integrating the gist of this into the original script (I wasn't actually quite sure what to do with a separate one):

 

 

Those are both sloppy mistakes on my part; sorry. 'parent.OnKeyDown(int KeyCode)' should be 'parent.OnKeyDown(KeyCode)' (the parameter needs to be passed through), and 'Function GetBestKnownSpell(FormList akSpellList)' should be 'Spell Function GetBestKnownSpell(FormList akSpellList)' (I forgot to declare it as having a return value, so when I try to return one, the compiler chokes).

 

Returning none as a 'this couldn't find any spell in the list on the player' is I think preferred. Any further checks can be done using 'is there anything in this variable', which is very very fast.

 

Given you're re-writing the script, you could do this:

Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
;Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giMageLightHotkey Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
Spell splLastCandleLight = none
FormList Property CandleLightList Auto
FormList Property MageLightList Auto
Sound Property sndFail Auto
 
Event OnInit()
    OnSettingsChanged()
EndEvent
 
Function OnSettingsChanged()
;update key mappings; called  by MCM menu
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
EndFunction
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode

    if KeyCode == giCandleLightHotkey.GetValueInt()

        If KeyCode == giCandleLightHotkey.GetValueInt()
        splCandleLight = GetBestKnownSpell(CandleLightList)
            If splCandleLight
                if !splLastCandleLight
                    if CastLightSpell(splCandleLight)
            splLastCandleLight = splCandleLight
                RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
             endif
                Else
                    actPlayer.DispelSpell(splLastCandleLight)
            splLastCandleLight = none
                EndIf
            Else
                Debug.Notification("Unable to cast " + CandleLightList.GetAt(0).GetName() + " because you don't know that spell")
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
        splMageLight = GetBestKnownSpell(MageLightList)
            If splMageLight
        CastLightSpell(akLightSpell)
            Else
                Debug.Notification("Unable to cast " + MageLightList.GetAt(0).GetName() + " because you don't know that spell")
            EndIf
        EndIf
    EndIf
EndEvent

;assumes formlist is organized from least(0) to best(n)
Spell Function GetBestKnownSpell(FormList akSpellList)
    int i = akSpellList.GetSize()
    Spell bestSpell = none
    
    while i > 0 && !bestSpell
        i-=1
        Spell testSpell = akSpellList.GetAt(i) as Spell
        if testSpell
            if actPlayer.HasSpell(testSpell)
                return testSpell
            endif
        endif
    endwhile
    return none
EndFunction
 
Event OnUpdate()
;Event OnUpdateGameTime()
    if bCandleLightOn
        if !CastLightSpell(splCandleLight)
        Debug.Notification("Insufficient magicka to renew " + splCandleLight.GetName() + ", retry in 5 seconds...")
        RegisterForSingleUpdate(5.0)
    endif
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent

bool Function CastLightSpell(Spell akLightSpell)
    If actPlayer.GetActorValue("Magicka") >= akLightSpell.GetEffectiveMagickaCost(actPlayer)
        actPlayer.DamageActorValue("Magicka", akLightSpell.GetEffectiveMagickaCost(actPlayer))
        akLightSpell.Cast(actPlayer)
        return true
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        return false
    EndIf
EndFunction
 
Function CastCandleLight()
    if (actPlayer.GetActorValue("Magicka") >= splCandleLight.GetEffectiveMagickaCost(actPlayer))
        actPlayer.DamageActorValue("Magicka", splCandleLight.GetEffectiveMagickaCost(actPlayer))
        splCandleLight.Cast(actPlayer)
       RegisterForSingleUpdateGameTime((splCandleLight.GetNthEffectDuration(0) as float) * 0.00028 * gfTimeScale.GetValue() )  ;convert spell duration seconds to game hours
        RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
    EndIf
EndFunction

I grouped up some of the common casting routines into a function named CastLightSpell() -- CastCandleLight is now redundant but left intact in case some external script calls it --, and patched a logic hole that arose when things got shifted to multiple spell variants (which I hadn't noticed beforehand). Magelight would have worked fine, but Candlelight is supposed to be sustained indefinitely, until the player turns it off. That means that the spells the player knows could potentially change during its lifetime, and if, say, the player learned a better candlelight, the dispel effect would've broken and left it permanently active. Now, when Candlelight is turned on, it saves the specific spell that was used and will dispel that, which should avoid the issue. This can also be used to track the casting state of Candlelight, rendering the boolean flag redundant, so I got rid of that.

 

The code logic is slightly different than original in that my code will not start candlelight unless you can successfully cast the first five second's worth, whereas the original would toggle it on regardless and then try to renew.

Edited by foamyesque
Link to comment
Share on other sites

It DID work, after setting up properties in the ESP to pass in the FormID Lists (had to be done as Objects, though Arrays was more intuitive). Also did not work unless the active effect from Jaxon's script-spell that launches CandleLight was not on already in the loaded test save (had to go back to one that did not have it on already).

 

However, it takes so long to go down the 5-spell checklist in the FormIDList that it's impractical (on my potato anyway), so I ended up using a globalvariable to store whether this check has been done, and then bypass the check if true (but force the check next time if you come up with no spell to use at all this time, or change the hotkeys). So, if you get new and better candlelight or magelight spells, you have to "register" them with the mod by either deleting your crappy ones or going into the MCM and changing something temporarily to trigger its "Function OnSettingsChanged()".

 

This is functional enough for use now. Thanks for the help again. It was just exactly enough to get me on the path to working out enough of this language to not walk into the wall over and over again, metaphorically speaking. Here's the source as it stands now:

 

 

 

Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giMageLightHotkey Auto
GlobalVariable Property giJaxonzCandleLightSpellSet Auto
GlobalVariable Property giJaxonzMageLightSpellSet Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
Spell Property DefaultVanillaSpell Auto
Sound Property sndFail Auto
FormList Property CandleLightList Auto
FormList Property MageLightList Auto
 
Event OnInit()
    OnSettingsChanged()
EndEvent
 
Function OnSettingsChanged()
;update key mappings; called  by MCM menu
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
    giJaxonzCandleLightSpellSet.SetValueInt(0)
    giJaxonzMageLightSpellSet.SetValueInt(0)
EndFunction
 
Spell Function GetBestKnownSpell(FormList akSpellList, Spell DefaultVanillaSpell)
;assumes formlist is organized from least(0) to best(n)
    Int i = akSpellList.GetSize()
    Spell bestSpell = none
    While i > 0 && !bestSpell
        i-=1
        Spell testSpell = akSpellList.GetAt(i) as Spell
        If testSpell
            If actPlayer.HasSpell(testSpell)
                Return testSpell
            EndIf
        EndIf
    EndWhile
    Return DefaultVanillaSpell
EndFunction
 
Function CastCandleLight()
    If (actPlayer.GetActorValue("Magicka") >= splCandleLight.GetEffectiveMagickaCost(actPlayer))
        actPlayer.DamageActorValue("Magicka", splCandleLight.GetEffectiveMagickaCost(actPlayer))
        splCandleLight.Cast(actPlayer)
;       RegisterForSingleUpdateGameTime((splCandleLight.GetNthEffectDuration(0) as float) * 0.00028 * gfTimeScale.GetValue() )  ;convert spell duration seconds to game hours
        RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        Debug.Notification("Insufficient magicka to renew "+splCandleLight.GetName()+", retry in 5 seconds...")
        RegisterForSingleUpdate(5.0)
    EndIf
EndFunction
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode
        ;see if there is an object grabbed
        If KeyCode == giCandleLightHotkey.GetValueInt()
            If giJaxonzCandleLightSpellSet.GetValueInt() == 0
                Spell testSpell = GetBestKnownSpell(CandleLightList, splCandleLight)
                If testSpell
                    splCandleLight = testSpell
                    giJaxonzCandleLightSpellSet.SetValueInt(1)
                EndIf
            EndIf
            If actPlayer.HasSpell(splCandleLight)
                bCandleLightOn = !bCandleLightOn
                If bCandleLightOn
                    CastCandleLight()
                Else
                    actPlayer.DispelSpell(splCandleLight)
                EndIf
            Else
                giJaxonzCandleLightSpellSet.SetValueInt(0)
                Debug.Notification("Unable to cast "+splCandleLight.GetName()+" because you don't know that spell")
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
            If giJaxonzMageLightSpellSet.GetValueInt() == 0
                Spell testSpell = GetBestKnownSpell(MageLightList, splMageLight)
                If testSpell
                    splMageLight = testSpell
                    giJaxonzMageLightSpellSet.SetValueInt(1)
                EndIf
            EndIf
            If actPlayer.HasSpell(splMageLight)
                If actPlayer.GetActorValue("Magicka") >= splMageLight.GetEffectiveMagickaCost(actPlayer)
                    actPlayer.DamageActorValue("Magicka", splMageLight.GetEffectiveMagickaCost(actPlayer))
                    splMageLight.Cast(actPlayer)
                Else
                    sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
                EndIf
            Else
                giJaxonzMageLightSpellSet.SetValueInt(0)
                Debug.Notification("Unable to cast "+splMageLight.GetName()+" because you don't know that spell")
            EndIf
        EndIf
    EndIf
EndEvent
 
Event OnUpdate()
;Event OnUpdateGameTime()
    if bCandleLightOn
        CastCandleLight()
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent

Link to comment
Share on other sites

Edit conflict: I didn't see your update before posting my own (I had this open in drafting mode for hours and hours while I was out of the house). I'll go over what you posted in your last version and see about integrating it, though the version I posted is functional so far (almost surprisingly, given I don't know what I'm doing in Papyrus). I'm not sure my way to do the globals I added is the most efficient. It's not clear how to do a global as a bool, but it seemed to work fine as Int.

 

Anyway, I like most of what you did. I thought about keeping the list checker returning the default vanilla spell name on failure, so that the "you don't have a spell at all" message the player gets would report that name as what's missing ("Unable to cast Candlelight because you don't know that spell"). Then again, it might be fine to just say something like "Unable to cast a Candlelight-type spell because you don't know one", and thus be able to use this: "Any further checks can be done using 'is there anything in this variable', which is very very fast." While I didn't know of the technique at the time, "CandleLightList.GetAt(0).GetName()" is smarter than what I was doing, but unfortunately any accessing by FormID List values is noticeably, annoyingly slow on a potato (e.g., going through a list of 5 spells took 12 to 20 seconds on my box, depending on other conditions, like whether it was raining). So, I'm going to skip that kind of looking in the error message and try just the "Unable to cast a Candlelight-type spell because you don't know one" genericizing approach.

 

I'm not sure the Candlelight would actually run forever if the spell went missing, since it's timed for 60 seconds or whatever it is. In my testing, I actually DID remove the spell while the effect was still running and I think it did eventually expire. Definitely, re-running the Candlelight caster, and having any alternative spell to use as found in the FormID list, did caused that to replace the current active (now missing) spell as the active effect.

 

 

My "parent.OnKeyDown()" (without passing KeyCode explicitly) somehow appeared to work, at least for Candlelight, but was probably going to re-fire Candlelight if I tested Magelight. The current script doesn't use the parent thing now, so moot point. :-)

 

I'm not sure what to do with the optional code about time scaling. One of your edits appears to have inadvertently turned one of those lines on, and another removes one from another section. It'll try to figure out what they do and re-integrate them for people who are using that feature of JLP (assuming it works at all – maybe Jaxonz commented it all out because it was faulty, for all I know). I don't want to use it myself, since i'm used to short-term magic effects working in realtime, and only long-term ones (like shrine blessings) working in game-time. I don't see any benefit to speeding up the rate at which this script fires! That would definitely happen to me sometimes, since I have a timescale mod that speeds up the clock in the wilderness, keeps it the same in the city, and reduces it indoors.

Edited by Darklocq
Link to comment
Share on other sites

Your version had compilation errors again. I worked around those, and integrated almost everything into a combined version. It has your merged function for firing off both spell types and your "what if the active Candlelight spell goes missing?" tests, and my global vars used to prevent the "run through the entire FormID list" stuff at every use, and so on. It compiles, and it seems to work at first, but it becomes "stuck". Once you press your Candlelight key, you get the spell working, but pressing the key again to turn it off does nothing, and it doesn't expire, but will just apparently run forever. Similarly, if you press the key for Magelight, it will fire one off, but then doing it again will not fire another one as expected. It doesn't seem to be failure of the event to close, since you can do Magelight then Candlelight or vice versa. Something's preventing it from running the same section of the event twice, though.

 

Worse, it seems to result in CTD within a few minutes of use, which suggests some kind of runaway memory usage problem.

 

Here's the code, with some temporary debug messages to show in-game where the script is at:

 

 

 

Scriptname JaxonzLightsPlease extends Quest  
{Simple utility to improve Candlelight and Magelight casting}
 
Bool bCandleLightOn = false
GlobalVariable Property giCandleLightHotkey Auto
GlobalVariable Property giMageLightHotkey Auto
GlobalVariable Property gfTimeScale Auto
GlobalVariable Property giJaxonzCandleLightSpellSet Auto
GlobalVariable Property giJaxonzMageLightSpellSet Auto
Actor Property actPlayer Auto
Spell Property splCandleLight Auto
Spell Property splMageLight Auto
Spell splLastCandleLight = none
FormList Property CandleLightList Auto
FormList Property MageLightList Auto
Sound Property sndFail Auto
 
Function OnSettingsChanged()
    ;Update key mappings; called by MCM menu. This also triggers a re-read of the spell FormID lists at next use (to "register" new spells).
    UnregisterForAllKeys()
    ;register keystroke callbacks
    RegisterForKey(giCandleLightHotkey.GetValueInt())   
    RegisterForKey(giMageLightHotkey.GetValueInt())
    ;set light spells as unknown to force the list to be re-parsed
    giJaxonzCandleLightSpellSet.SetValue(0 as Int)
    giJaxonzMageLightSpellSet.SetValue(0 as Int)
EndFunction
 
Spell Function GetBestKnownSpell(FormList akSpellList)
;assumes formlist is organized from least(0) to best(n)
    Int i = akSpellList.GetSize()
    Spell bestSpell = none
    While i > 0 && !bestSpell
        i-=1
        Spell testSpell = akSpellList.GetAt(i) as Spell
        If testSpell
            If actPlayer.HasSpell(testSpell)
                Return testSpell
            EndIf
        EndIf
    EndWhile
    Return none
EndFunction
 
Bool Function CastLightSpell(Spell akLightSpell)
    Debug.Notification("akLightSpell passed = " + akLightSpell.GetName())
    If actPlayer.GetActorValue("Magicka") >= akLightSpell.GetEffectiveMagickaCost(actPlayer)
        Debug.Notification("TEMP: There is sufficient Magicka.")
        actPlayer.DamageActorValue("Magicka", akLightSpell.GetEffectiveMagickaCost(actPlayer))
        akLightSpell.Cast(actPlayer)
        Debug.Notification("TEMP: Spell should have fired.")
        Return true
    Else
        sndFail.Play(actPlayer as ObjectReference)  ;insufficient magicka sound
        Debug.Notification("TEMP: There is NOT sufficient Magicka.")
        Return false
    EndIf
EndFunction
 
Event OnInit()
    OnSettingsChanged()
EndEvent
 
Event OnKeyDown(Int KeyCode)
    if !Utility.IsInMenuMode() ;only pay attention to hotkeys in game mode
        ;see if there is an object grabbed
        If KeyCode == giCandleLightHotkey.GetValueInt()
            If giJaxonzCandleLightSpellSet.GetValue() == 0
                Debug.Notification("TEMP: giJaxonzCandleLightSpellSet = " + giJaxonzCandleLightSpellSet.GetValue())
                splCandleLight = GetBestKnownSpell(CandleLightList)
                If splCandleLight
                    giJaxonzCandleLightSpellSet.SetValue (1 as Int)
                    Debug.Notification("TEMP: giJaxonzCandleLightSpellSet = " + giJaxonzCandleLightSpellSet.GetValue())
                    If !splLastCandleLight
                        If CastLightSpell(splCandleLight)
                            splLastCandleLight = splCandleLight
                            RegisterForSingleUpdate((splCandleLight.GetNthEffectDuration(0) - 1) as float)
                        Endif
                    Else
                        actPlayer.DispelSpell(splLastCandleLight)
                        splLastCandleLight = None
                    EndIf
                Else
                    giJaxonzCandleLightSpellSet.SetValue (0 as Int)
                    Debug.Notification("Unable to cast a Candlelight-type spell because you don't know one.")
                EndIf
            EndIf
        ElseIf KeyCode == giMageLightHotkey.GetValueInt()
            If giJaxonzMageLightSpellSet.GetValue() == 0
                Debug.Notification("TEMP: giJaxonzMageLightSpellSet = " + giJaxonzMageLightSpellSet.GetValue())
                splMageLight = GetBestKnownSpell(MageLightList)
                If splMageLight
                    giJaxonzMageLightSpellSet.SetValue (1 as Int)
                    Debug.Notification("TEMP: giJaxonzMageLightSpellSet = " + giJaxonzMageLightSpellSet.GetValue())
                    CastLightSpell(splMageLight)
                Else
                    giJaxonzMageLightSpellSet.SetValue (0 as Int)
                    Debug.Notification("TEMP: giJaxonzMageLightSpellSet = " + giJaxonzMageLightSpellSet.GetValue())
                    Debug.Notification("Unable to cast a Magelight-type spell because you don't know one.")
                EndIf
            EndIf
        EndIf
    EndIf
EndEvent
 
Event OnUpdate()
;Event OnUpdateGameTime()
    If bCandleLightOn
        If !CastLightSpell(splCandleLight)
        Debug.Notification("Insufficient magicka to renew " + splCandleLight.GetName() + ", retry in 5 seconds...")
        RegisterForSingleUpdate(5.0)
        EndIf
    Else
;       UnregisterForUpdateGameTime()
    EndIf
EndEvent

 

 

Edited by Darklocq
Link to comment
Share on other sites

  • Recently Browsing   0 members

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