Jump to content

[Help] Track how many times (within a quest) the player has hit enemies with a *specific* spell


AntheraeaMothcore

Recommended Posts

At the moment, I'm working on a kind of radiant quest for mages, much like the radiant Companions quests, largely to try to pad out the overly-short-by-comparison College main questline. The set of scripts I'm currently working does:

 

- Goes through the player's spell list and grabs the ones that are Destruction-based;

- Picks a random one of those and saves it to an alias (in theory; it's not really tested)

 

and then I thought that I might run an Event OnSpellCast, compare the spell name against the alias, and if it's true....well, I wanted it to increment a counter if it hits a hostile enemy but I'm not quite sure how to do that.

 

The closest vanilla analogue to this is JZargo's Experiment, which increments a count when you use his scroll against an undead enemy. The code for that however is an event that takes place when the magic effect starts and is attached to the scroll specifically, whereas I was intending for this to work with the random spell chosen (the JZargo effect also doesn't distinguish between hostile and friendly creatures, hence the explosion being able to happen when Serana is caught in the AoE).

 

I'm not quite sure how to do this. Maybe attach an invisible effect to the player? But that doesn't solve the "hit any hostile enemy with this spell" aspect.

 

for fun, scripts:

SRMGDRDes_PropertyDefinitions:

Scriptname SRMGRDes_PropertyDefinitions extends Quest
String Property SRMGRDesName auto

String Property SRMGRDesName_Property
	Function Set(String SpellName)
		SRMGRDesName = SpellName
	EndFunction
	String Function Get()
		return SRMGRDesName
	EndFunction
EndProperty

SRMGRDes_Init (intended to be called when the quest starts):

Scriptname SRMGRDes_Init extends Quest
Actor Property PlayerRef Auto
SRMGRDes_PropertyDefinitions Property PropDef Auto

Event OnInit()
	;Get list of player spells (flames and healing are always included)
	int playerSpellsCount = PlayerRef.GetSpellCount()
	String[] playerDesSpells = Utility.CreateStringArray(playerSpellsCount, 0)
	int iteration = 0
	While iteration < playerSpellsCount
		Spell CurrentSpell = PlayerRef.GetNthSpell(iteration)
		If(CurrentSpell.getNthEffectMagicEffect(0).getAssociatedSkill() == "Destruction")
			int index = playerDesSpells.length - 1
			playerDesSpells[index] = CurrentSpell.GetName()
		EndIf
		iteration = iteration + 1
	EndWhile

	;pick one of these randomly
	int randindex = Utility.RandomInt(0, playerDesSpells.length - 1)
	PropDef.SRMGRDesName = playerDesSpells[randindex]
EndEvent

SRMGRDes_Tracker (meant to keep track of when you cast spells and increment; as you can see the if body is blank)

Scriptname SRMGRDes_Tracker extends Quest
Quest Property SRMGRDes auto
SRMGRDes_PropertyDefinitions Property PropDef Auto
Actor Property PlayerRef Auto

GlobalVariable Property PlayerDesSkill Auto ;for a dialogue check
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
GlobalVariable Property SRMGRDesMax Auto ;max count of casts

Event OnSpellCast(Form castspell)
	string name = castspell.GetName()
	if(name == PropDef.SRMGRDesName)
	Else
		return
	EndIf

EndEvent

Function SCount()

		ModObjectiveGlobal(1, SRMGRDesCount, 10)
		if SRMGRDesCount.value == SRMGRDesMax.value
				SRMGRDes.setStage(20)
		endif

EndFunction

fair warning, I started this like two days ago, so I'm not even sure if my scripts are set up correctly at all :ninja:

 

 

Link to comment
Share on other sites

Having to detect when the player hits an enemy with a spell dynamically is a bit tricky, but is possible. How I would do it is to use the OnHit event for NPC's. You would need to add a Spell Ability to NPC's with a script attached that has the OnHit Event. I see you're using skse already, so you could use Papyrus Extender to get all loaded actors in your area on combat start and add the ability if they are hostile. Also I would save the random destruction spell in your quest script, rather than just the name.

 

Quest Script:

 

 

Scriptname SRMGRDes_PropertyDefinitions extends Quest

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player
Spell Property DestructionSpell Auto
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
Actor Property PlayerRef Auto

Event OnInit() 
    DestructionSpell = None ;clear old destruction spell
    SRMGRDesCount.SetValue(0)
    int playerSpellsCount = PlayerRef.GetSpellCount() - 1 
    Int Index = Utility.RandomInt(0, playerSpellsCount) ;get random number to start from
    Int Count = 0
    
    While Count < playerSpellsCount && DestructionSpell == None 
        Count += 1 ;add 1 to count
        Spell CurrentSpell = PlayerRef.GetNthSpell(Index)
        If(CurrentSpell.getNthEffectMagicEffect(0).getAssociatedSkill() == "Destruction")
            DestructionSpell = CurrentSpell
        Endif 
        
        Index += 1 
        
        If Index > playerSpellsCount ;if index is larger than spell count, go back to 0.
            Index = 0 
        Endif 
    EndWhile
    
    If DestructionSpell == None 
        ;player doesn't know any destruction spells 
    Else 
        PlayerRef.AddSpell(SRMGCombatDeletectAbility) 
    Endif 
EndEvent

Script that goes on the SRMGCombatDeletectAbility's magic effect which is added to the player:

Scriptname SRMGRDes_CombatDetectScript extends ActiveMagicEffect 
;put this script on a SRMGCombatDeletectAbility magic effect. Make the spell have the condition IsInCombat == 1

Spell Property HitAbilitySpell Auto ;Spell with the OnHit event.
Actor Property PlayerRef Auto

Event OnEffectStart(Actor akTarget, Actor akCaster)
    ;Effect starts when combat starts. 
    
    Actor[] LoadedActors = PO3_SKSEfunctions.GetActorsByProcessingLevel(0) ;get all loaded actors in the area from Papyrus Extender
    Int L = LoadedActors.Length 
    While L > 0 
        L -= 1 
        If LoadedActors[L].IsHostileToActor(PlayerRef) 
            LoadedActors[L].AddSpell(HitAbilitySpell) ;add spell with onhit event. 
        Endif 
    EndWhile 
EndEvent

Script that goes on the SRMGHitAbilitySpell's magic effect.

Scriptname SRMGRDes_OnHitScript extends ActiveMagicEffect 
;put this script on the SRMGHitAbilitySpell ability. Tracks when enemies of the player are hit by the destruction spell.

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player.
Spell Property SRMGHitAbilitySpell Auto ;This Spell
Quest Property SRMGRDes auto
SRMGRDes_PropertyDefinitions Property PropDef Auto 
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
GlobalVariable Property SRMGRDesMax Auto ;max count of casts

Actor Property PlayerRef Auto

Actor Target
Event OnEffectStart(Actor akTarget, Actor akCaster)
   Target = akTarget ;save the target actor to use in the onhit event.
EndEvent 

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, Bool abPowerAttack, Bool abSneakAttack, Bool abBashAttack, Bool abHitBlocked)
    If SRMGRDes.GetStage() == 10 
        If akAggressor == PlayerRef && akSource == PropDef.DestructionSpell
            SRMGRDesCount.Mod(1) ;add 1 to the SRMGRDesCount 
            If SRMGRDesCount.GetValue() >= SRMGRDesMax.GetValue()
                SRMGRDes.SetStage(20)
                PlayerRef.RemoveSpell(SRMGCombatDeletectAbility)
                Target.RemoveSpell(SRMGHitAbilitySpell)
            Endif
        Endif 
    Else 
        PlayerRef.RemoveSpell(SRMGCombatDeletectAbility)
        Target.RemoveSpell(SRMGHitAbilitySpell) ;remove this ability from the target, quest isn't in the right stage.
    Endif 
EndEvent

Link to comment
Share on other sites

 

Having to detect when the player hits an enemy with a spell dynamically is a bit tricky, but is possible. How I would do it is to use the OnHit event for NPC's. You would need to add a Spell Ability to NPC's with a script attached that has the OnHit Event. I see you're using skse already, so you could use Papyrus Extender to get all loaded actors in your area on combat start and add the ability if they are hostile. Also I would save the random destruction spell in your quest script, rather than just the name.

 

Quest Script:

 

 

Scriptname SRMGRDes_PropertyDefinitions extends Quest

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player
Spell Property DestructionSpell Auto
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
Actor Property PlayerRef Auto

Event OnInit() 
    DestructionSpell = None ;clear old destruction spell
    SRMGRDesCount.SetValue(0)
    int playerSpellsCount = PlayerRef.GetSpellCount() - 1 
    Int Index = Utility.RandomInt(0, playerSpellsCount) ;get random number to start from
    Int Count = 0
    
    While Count < playerSpellsCount && DestructionSpell == None 
        Count += 1 ;add 1 to count
        Spell CurrentSpell = PlayerRef.GetNthSpell(Index)
        If(CurrentSpell.getNthEffectMagicEffect(0).getAssociatedSkill() == "Destruction")
            DestructionSpell = CurrentSpell
        Endif 
        
        Index += 1 
        
        If Index > playerSpellsCount ;if index is larger than spell count, go back to 0.
            Index = 0 
        Endif 
    EndWhile
    
    If DestructionSpell == None 
        ;player doesn't know any destruction spells 
    Else 
        PlayerRef.AddSpell(SRMGCombatDeletectAbility) 
    Endif 
EndEvent

Script that goes on the SRMGCombatDeletectAbility's magic effect which is added to the player:

Scriptname SRMGRDes_CombatDetectScript extends ActiveMagicEffect 
;put this script on a SRMGCombatDeletectAbility magic effect. Make the spell have the condition IsInCombat == 1

Spell Property HitAbilitySpell Auto ;Spell with the OnHit event.
Actor Property PlayerRef Auto

Event OnEffectStart(Actor akTarget, Actor akCaster)
    ;Effect starts when combat starts. 
    
    Actor[] LoadedActors = PO3_SKSEfunctions.GetActorsByProcessingLevel(0) ;get all loaded actors in the area from Papyrus Extender
    Int L = LoadedActors.Length 
    While L > 0 
        L -= 1 
        If LoadedActors[L].IsHostileToActor(PlayerRef) 
            LoadedActors[L].AddSpell(HitAbilitySpell) ;add spell with onhit event. 
        Endif 
    EndWhile 
EndEvent

Script that goes on the SRMGHitAbilitySpell's magic effect.

Scriptname SRMGRDes_OnHitScript extends ActiveMagicEffect 
;put this script on the SRMGHitAbilitySpell ability. Tracks when enemies of the player are hit by the destruction spell.

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player.
Spell Property SRMGHitAbilitySpell Auto ;This Spell
Quest Property SRMGRDes auto
SRMGRDes_PropertyDefinitions Property PropDef Auto 
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
GlobalVariable Property SRMGRDesMax Auto ;max count of casts

Actor Property PlayerRef Auto

Actor Target
Event OnEffectStart(Actor akTarget, Actor akCaster)
   Target = akTarget ;save the target actor to use in the onhit event.
EndEvent 

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, Bool abPowerAttack, Bool abSneakAttack, Bool abBashAttack, Bool abHitBlocked)
    If SRMGRDes.GetStage() == 10 
        If akAggressor == PlayerRef && akSource == PropDef.DestructionSpell
            SRMGRDesCount.Mod(1) ;add 1 to the SRMGRDesCount 
            If SRMGRDesCount.GetValue() >= SRMGRDesMax.GetValue()
                SRMGRDes.SetStage(20)
                PlayerRef.RemoveSpell(SRMGCombatDeletectAbility)
                Target.RemoveSpell(SRMGHitAbilitySpell)
            Endif
        Endif 
    Else 
        PlayerRef.RemoveSpell(SRMGCombatDeletectAbility)
        Target.RemoveSpell(SRMGHitAbilitySpell) ;remove this ability from the target, quest isn't in the right stage.
    Endif 
EndEvent

 

 

Thank you very much :) sorry for taking a while to respond; I implemented the script changes and got to work on trying to test them...only problem is, I have no idea what spell was chosen to test the effect with, because the alias needed to show the name just isn't populating and I'm unsure how to do so (researching this is why it took me a bit to respond). You wouldn't happen to know of a way to fill an alias with a string? I know of the Uses Stored Text checkbox and such, but am unsure how to actually populate it in scripting, or what fill type it should be given. :confused:

Link to comment
Share on other sites

Hey, no problem. Alias's can only fill with objectReferences. So, to display the spell name in the quest objective, I would make a new dummy misc item and place a reference in a new empty cell in the creation kit. You can duplicate a vanilla misc item. For the reference alias use Specific Reference and fill it with that item. Then in the script rename the misc item using SetName when you initialize the quest.

 

 

 

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player
Spell Property DestructionSpell Auto
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
Actor Property PlayerRef Auto
MiscObject Property SRMGDes_DummyMisc Auto ;fill ReferenceAlias with an ObjectReference of this MiscObject.

Event OnInit() 
    DestructionSpell = None ;clear old destruction spell
    SRMGRDesCount.SetValue(0)
    int playerSpellsCount = PlayerRef.GetSpellCount() - 1 
    Int Index = Utility.RandomInt(0, playerSpellsCount) ;get random number to start from
    Int Count = 0
    
    While Count < playerSpellsCount && DestructionSpell == None 
        Count += 1 ;add 1 to count
        Spell CurrentSpell = PlayerRef.GetNthSpell(Index)
        If(CurrentSpell.getNthEffectMagicEffect(0).getAssociatedSkill() == "Destruction")
            DestructionSpell = CurrentSpell
            SRMGDes_DummyMisc.SetName(DestructionSpell.GetName()) ;display spell name in quest objective. 
        Endif 
        
        Index += 1 
        
        If Index > playerSpellsCount ;if index is larger than spell count, go back to 0.
            Index = 0 
        Endif 
    EndWhile
    
    If DestructionSpell == None 
        ;player doesn't know any destruction spells 
    Else 
        PlayerRef.AddSpell(SRMGCombatDeletectAbility) 
    Endif 
EndEvent

Then in your quest object you can use the tag <Alias=MyCoolAlias> and change MyCoolAlias to the name of your reference alias.
You may also need to set the name again with OnPlayerLoadGame() because I don't think SetName carries over after saving and loading a game again. To do that, make another reference alias, Fill type Specific Refence again, Cell Any and PlayerRef and use that event to set the name of the miscObject.
Link to comment
Share on other sites

 

Hey, no problem. Alias's can only fill with objectReferences. So, to display the spell name in the quest objective, I would make a new dummy misc item and place a reference in a new empty cell in the creation kit. You can duplicate a vanilla misc item. For the reference alias use Specific Reference and fill it with that item. Then in the script rename the misc item using SetName when you initialize the quest.

 

 

 

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player
Spell Property DestructionSpell Auto
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
Actor Property PlayerRef Auto
MiscObject Property SRMGDes_DummyMisc Auto ;fill ReferenceAlias with an ObjectReference of this MiscObject.

Event OnInit() 
    DestructionSpell = None ;clear old destruction spell
    SRMGRDesCount.SetValue(0)
    int playerSpellsCount = PlayerRef.GetSpellCount() - 1 
    Int Index = Utility.RandomInt(0, playerSpellsCount) ;get random number to start from
    Int Count = 0
    
    While Count < playerSpellsCount && DestructionSpell == None 
        Count += 1 ;add 1 to count
        Spell CurrentSpell = PlayerRef.GetNthSpell(Index)
        If(CurrentSpell.getNthEffectMagicEffect(0).getAssociatedSkill() == "Destruction")
            DestructionSpell = CurrentSpell
            SRMGDes_DummyMisc.SetName(DestructionSpell.GetName()) ;display spell name in quest objective. 
        Endif 
        
        Index += 1 
        
        If Index > playerSpellsCount ;if index is larger than spell count, go back to 0.
            Index = 0 
        Endif 
    EndWhile
    
    If DestructionSpell == None 
        ;player doesn't know any destruction spells 
    Else 
        PlayerRef.AddSpell(SRMGCombatDeletectAbility) 
    Endif 
EndEvent

Then in your quest object you can use the tag <Alias=MyCoolAlias> and change MyCoolAlias to the name of your reference alias.
You may also need to set the name again with OnPlayerLoadGame() because I don't think SetName carries over after saving and loading a game again. To do that, make another reference alias, Fill type Specific Refence again, Cell Any and PlayerRef and use that event to set the name of the miscObject.

 

excellent. :) you are correct, SetName is stated on the wiki as not being persistent across saves. I'm still roadblocked however, on getting it to fill at all:

image.png

with this configuration, the quest is not starting (because the alias is not optional and it is not being filled). This is true even on a clean save.

 

I had cloned a MiscItem, as suggested, cloned a test cell, removed everything from it, added in the miscitem as a reference, and added in the new code, which compiles (so all's good there). :thinking: Lots of the help topics online are about filling in location aliases...

 

Link to comment
Share on other sites

Hmmm, are you sure it's not being filled? That's always worked for me. Try putting some debug.notifications in the script to see if they are firing.

Yep, I know it's not being filled because the quest doesn't start, and when the flag "optional" is set, the text appears as [...] in the objective on screen. So, lemme go double check what I implemented; at least what I did wasn't totally wrong! :happy:

Link to comment
Share on other sites

Hmmm yeah, I'm still not sure what's up. Here is the code as implemented; I did try changing it from "Dummy" (the base object) to "DummyRef" (the reference used in the test cell), but it still resulted in [...] as usual :thinking:

 

 

 

Scriptname SRMGRDes_PropertyDefinitions extends Quest

Spell Property SRMGCombatDeletectAbility Auto ;Detects combat start on player
Spell Property DestructionSpell Auto
MiscObject Property SRMGRDes_Dummy Auto
GlobalVariable Property SRMGRDesCount Auto ;current count of casts
GlobalVariable Property SRMGRDesMax Auto ;max number of casts
Actor Property PlayerRef Auto

Event OnInit() 
    DestructionSpell = None ;clear old destruction spell
    SRMGRDesCount.SetValue(0)
    int playerSpellsCount = PlayerRef.GetSpellCount() - 1 
    Int Index = Utility.RandomInt(0, playerSpellsCount) ;get random number to start from
    Int Count = 0
    
    While Count < playerSpellsCount && DestructionSpell == None 
        Count += 1 ;add 1 to count
        Spell CurrentSpell = PlayerRef.GetNthSpell(Index)
        If(CurrentSpell.getNthEffectMagicEffect(0).getAssociatedSkill() == "Destruction")
            DestructionSpell = CurrentSpell
		SRMGRDes_Dummy.SetName(DestructionSpell.GetName())
        Endif 
        
        Index += 1 
        
        If Index > playerSpellsCount ;if index is larger than spell count, go back to 0.
            Index = 0 
        Endif 
    EndWhile
    
    If DestructionSpell == None 
        ;player doesn't know any destruction spells 
    Else 
        PlayerRef.AddSpell(SRMGCombatDeletectAbility) 
    Endif 
EndEvent

 

 

Link to comment
Share on other sites

There we go. It's not particularly happy:

[03/30/2021 - 09:19:53PM] Error: Unable to bind script SRMGRDes_OnHitScript to SRMGRDes (27000D62) because their base types do not match
[03/30/2021 - 09:19:53PM] Error: Unable to bind script SRMGRDes_CombatDetectScript to SRMGRDes (27000D62) because their base types do not match

[03/30/2021 - 09:20:18PM] Error: Cannot call SetValue() on a None object, aborting function call
stack:
[sRMGRDes (27000D62)].SRMGRDes_PropertyDefinitions.OnInit() - "SRMGRDes_PropertyDefinitions.psc" Line ?
[03/30/2021 - 09:20:18PM] Error: Cannot call GetSpellCount() on a None object, aborting function call
stack:
[sRMGRDes (27000D62)].SRMGRDes_PropertyDefinitions.OnInit() - "SRMGRDes_PropertyDefinitions.psc" Line ?
[03/30/2021 - 09:20:18PM] warning: Assigning None to a non-object variable named "::temp2"
stack:
[sRMGRDes (27000D62)].SRMGRDes_PropertyDefinitions.OnInit() - "SRMGRDes_PropertyDefinitions.psc" Line ?

Link to comment
Share on other sites

  • Recently Browsing   0 members

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