AntheraeaMothcore Posted March 30, 2021 Share Posted March 30, 2021 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 More sharing options...
dylbill Posted March 30, 2021 Share Posted March 30, 2021 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 More sharing options...
AntheraeaMothcore Posted March 30, 2021 Author Share Posted March 30, 2021 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 More sharing options...
dylbill Posted March 30, 2021 Share Posted March 30, 2021 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 More sharing options...
AntheraeaMothcore Posted March 30, 2021 Author Share Posted March 30, 2021 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: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 More sharing options...
dylbill Posted March 30, 2021 Share Posted March 30, 2021 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. Link to comment Share on other sites More sharing options...
AntheraeaMothcore Posted March 31, 2021 Author Share Posted March 31, 2021 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 More sharing options...
AntheraeaMothcore Posted March 31, 2021 Author Share Posted March 31, 2021 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 More sharing options...
dylbill Posted March 31, 2021 Share Posted March 31, 2021 Maybe instead try using Find Matching Reference for fill type and put the condition GetIsId SRMGRDes_Dummy == 1. Link to comment Share on other sites More sharing options...
AntheraeaMothcore Posted March 31, 2021 Author Share Posted March 31, 2021 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 callstack: [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 callstack: [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 More sharing options...
Recommended Posts