Preventing a magic projectile from being fired

Post » Sat Mar 09, 2013 8:37 am

As the title says. If the caster doesn't meet a certain condition the projectile shouldn't be fired at all. Too bad the OnEffectStart event expects an akTarget to be triggered, hence once the event is triggered the projectile is already gone. The OnInit event doesn't help either, I tried to detect the caster using GetCasterActor but the projectile is fired regardless of the condition. Any ideas on how to accomplish this?
User avatar
Esther Fernandez
 
Posts: 3415
Joined: Wed Sep 27, 2006 11:52 am

Post » Fri Mar 08, 2013 7:35 pm

. . .InterruptCast() ?

It can't be "Actor.InterruptCast()" it has to be the spell you wish to interrupt.
User avatar
Ysabelle
 
Posts: 3413
Joined: Sat Jul 08, 2006 5:58 pm

Post » Sat Mar 09, 2013 3:18 am

Sounds exactly what I need but how do I use it on a spell? It doesn't compile. "InterruptCast is not a function or does not exist". It compiles only if I use it on an actor, as per http://www.creationkit.com/InterruptCast_-_ObjectReference
User avatar
carla
 
Posts: 3345
Joined: Wed Aug 23, 2006 8:36 am

Post » Sat Mar 09, 2013 3:56 am

I was afraid of that. -.-

IT will compile because the actor is an object, but it WONT work because it's designed to stop spells and works in conjunction with the .cast() function. I know this because I wanted to do the same thing you wanted to do, last year and I spent many days being screwed over by this function.

But I have an idea to get around this, seeing as I'm learning papyrus anyhow. I'll get back to you when I'm done. (Or someone comes along with a solution)
User avatar
SaVino GοΜ
 
Posts: 3360
Joined: Mon Sep 17, 2007 8:00 pm

Post » Sat Mar 09, 2013 12:48 am

Thanks for your interest. Meanwhile I just finished testing the version for Self delivered spells, which works beautifully for those but it's obviously useless for Aimed delivered spells -.-

Spoiler

Scriptname SpellFail extends activemagiceffect  Message Property SpellFailedMSG AutoActor Property PlayerREF AutoString Property CasterSkill auto		; fill this string with Alteration, Illusion etc. depending on the spell schoolSpell Property CurrentSpell auto		; fill this with the spell this effect is attached toSpell Property CurrentSpell2 auto	; some effects are attached to more than one spell, the 2nd spell being specific to the left hand [now seriously Beth, WTF?]Event OnEffectStart(Actor akTarget, Actor akCaster)	bool righthand = false	bool lefthand = false	Spell LeftHandSpell = akCaster.GetEquippedSpell(0)	Spell RightHandSpell = akCaster.GetEquippedSpell(1)	if ( ( LeftHandSpell == CurrentSpell ) || ( RightHandSpell == CurrentSpell ) || ( LeftHandSpell == CurrentSpell2 ) || ( RightHandSpell == CurrentSpell2 ) )	; this is to prevent scrolls and staves effects to be dispelled		int RandomRate = Utility.RandomInt(1, 100)		float ActorSkill = akCaster.GetActorValue(CasterSkill)		int FailChanceOffset = 30	; no spell fail if the Actor skill is >= 70		float FailChance			FailChance = RandomRate / (ActorSkill + FailChanceOffset)		if ( FailChance > 1 )			if Self != NONE		; at this point the effect might be either weared off or non-existing, so we check for its existence to prevent Papyrus complaining on missing entities				dispel()			endif			if ( LeftHandSpell )	; the following is a convoluted and yet effective way to unequip and reequip a failed spell, so that holding the casting key during a spell fail is useless				lefthand  = true				akCaster.UnequipSpell(LeftHandSpell, 0)			endif			if ( RightHandSpell )				righthand  = true				akCaster.UnequipSpell(RightHandSpell, 1)			endif			if ( lefthand )				akCaster.EquipSpell(LeftHandSpell, 0)			endif			if ( righthand )				akCaster.EquipSpell(RightHandSpell, 1)			endif			If ( akCaster == PlayerREF )			    SpellFailedMSG.Show()	; your spell failed! LOL! NOOB!			EndIf		EndIf	EndifEndEvent
User avatar
Steve Fallon
 
Posts: 3503
Joined: Thu Aug 23, 2007 12:29 am

Post » Sat Mar 09, 2013 7:52 am

As the title says. If the caster doesn't meet a certain condition the projectile shouldn't be fired at all. Too bad the OnEffectStart event expects an akTarget to be triggered, hence once the event is triggered the projectile is already gone. The OnInit event doesn't help either, I tried to detect the caster using GetCasterActor but the projectile is fired regardless of the condition. Any ideas on how to accomplish this?
What about casting an invisible, harmless dummy spell, and if the conditions are met you spawn all the requisite visuals? Inside an http://www.creationkit.com/OnSpellCast_-_ObjectReference event this would probably be fast enough....
As for the magicka cost....well you've still attempted to cast the spell, you've just failed. A throwback to Morrowind :wink:

*EDIT: Late edit, I meant to say that when you are spawning the visuals you also apply the desired effect to the target.
See below post's, I think it's been sorted.
User avatar
Jason Rice
 
Posts: 3445
Joined: Thu Aug 16, 2007 3:42 pm

Post » Fri Mar 08, 2013 11:33 pm

Um.. then with what I'm supposed to actually hit the target? I'm not sure I'm following you.
User avatar
Brooks Hardison
 
Posts: 3410
Joined: Fri Sep 07, 2007 3:14 am

Post » Sat Mar 09, 2013 3:11 am

Whoa I forgot I DID figure out how to get it work on actors.

You can probably adjust this code a bit to work on projectiles and other actors. This code was originally for my Refresh Magicka spell that I scrapped.

What I was going to do in my previous post though was create a custom function version of interruptCast().

Spoiler

float Property max autoEvent OnEffectStart(Actor akTarget, Actor akCaster)	; Let's say you want to restore your health through a script.	If (Game.GetPlayer() == akTarget)		If (akTarget.GetActorValuePercentage("health") >= max)			akTarget.InterruptCast()			Debug.Notification("You cannot cast this spell at this time")			; Because your health is at 100% (represented by 1.0) this casting will be interrupted until the condition is met.		ElseIf (akTarget.GetActorValuePercentage("health") <= max)			akTarget.RestoreAV("health", 300)		EndIf	EndIfEndEvent
User avatar
Paula Ramos
 
Posts: 3384
Joined: Sun Jul 16, 2006 5:43 am

Post » Sat Mar 09, 2013 4:49 am

Is it just one projectile? I wonder how quickly a http://www.creationkit.com/FindRandomReferenceOfTypeFromRef_-_Game function would work with a projectile? Maybe you could find and delete it before it gets far and play a "fizzle-out" noise.

Edit: Derp, that's not going to work because OnEffectStart doesn't fire until the projectile has hit the target. You'd have to have another script monitoring the player for casts to do this. Where's my coffee?

Edit after some coffee: Maybe you could intercept it using a method similar to how I spawn the rocks before the spell is cast http://www.youtube.com/watch?v=AbBH2jYJYAU. Make a second, ability-type spell that monitors the player for an attempt to cast the spell you want to interrupt, and make that ability spell the Equip Ability for the spell you want to be able to interrupt.
User avatar
maddison
 
Posts: 3498
Joined: Sat Mar 10, 2007 9:22 pm

Post » Fri Mar 08, 2013 7:54 pm

Whoa I forgot I DID figure out how to get it work on actors.

You can probably adjust this code a bit to work on projectiles and other actors. This code was originally for my Refresh Magicka spell that I scrapped.

What I was going to do in my previous post though was create a custom function version of interruptCast().

Spoiler

float Property max autoEvent OnEffectStart(Actor akTarget, Actor akCaster)	; Let's say you want to restore your health through a script.	If (Game.GetPlayer() == akTarget)		If (akTarget.GetActorValuePercentage("health") >= max)			akTarget.InterruptCast()			Debug.Notification("You cannot cast this spell at this time")			; Because your health is at 100% (represented by 1.0) this casting will be interrupted until the condition is met.		ElseIf (akTarget.GetActorValuePercentage("health") <= max)			akTarget.RestoreAV("health", 300)		EndIf	EndIfEndEvent

Um, nope, the above works as intended because on Self delivered effects the OnEffectStart event is triggered immediately, but doing the same with Aimed effects won't work because the event isn't triggered until the projectile has hit the target [and once the target has been hit, the damage has already been applied to the target].
User avatar
Connor Wing
 
Posts: 3465
Joined: Wed Jun 20, 2007 1:22 am

Post » Fri Mar 08, 2013 5:58 pm

Is it just one projectile? I wonder how quickly a http://www.creationkit.com/FindRandomReferenceOfTypeFromRef_-_Game function would work with a projectile? Maybe you could find and delete it before it gets far and play a "fizzle-out" noise.

Edit: Derp, that's not going to work because OnEffectStart doesn't fire until the projectile has hit the target. You'd have to have another script monitoring the player for casts to do this. Where's my coffee?

Edit after some coffee: Maybe you could intercept it using a method similar to how I spawn the rocks before the spell is cast http://www.youtube.com/watch?v=AbBH2jYJYAU. Make a second, ability-type spell that monitors the player for an attempt to cast the spell you want to interrupt, and make that ability spell the Equip Ability for the spell you want to be able to interrupt.

Hmm I like the idea, I'm going to give it a try. Problem is, I have to make this thing work for every NPC in the world because I don't want to affect the player only with restrictions like these. That's imperative. Hence I'd have to attach the monitoring ability to the race as a whole and I'm afraid it might bring the system down to its knees. I'll see how it goes.
User avatar
Baylea Isaacs
 
Posts: 3436
Joined: Mon Dec 25, 2006 11:58 am

Post » Sat Mar 09, 2013 1:50 am

Hmm I like the idea, I'm going to give it a try. Problem is, I have to make this thing work for every NPC in the world because I don't want to affect the player only with restrictions like these. That's imperative. Hence I'd have to attach the monitoring ability to the race as a whole and I'm afraid it might bring the system down to its knees. I'll see how it goes.

Rather than attach it to races, maybe try using Skyproc. Just a thought.
User avatar
Eliza Potter
 
Posts: 3481
Joined: Mon Mar 05, 2007 3:20 am

Post » Sat Mar 09, 2013 5:16 am

I'm not familiar with Skyproc as a modder, I'm using it for ASIS but I don't even know where to start to create a patcher. At any rate, what for? As far as I can tell attaching a script to either all races or all NPC has exactly the same effect, you always come up with an ability refreshing every 1 second for all NPC.
User avatar
Noely Ulloa
 
Posts: 3596
Joined: Tue Jul 04, 2006 1:33 am

Post » Sat Mar 09, 2013 2:36 am

Yes, but you wouldn't have compatibility issues. Leastways I don't think so.
User avatar
Invasion's
 
Posts: 3546
Joined: Fri Aug 18, 2006 6:09 pm

Post » Fri Mar 08, 2013 11:27 pm

It is possible to achieve this by attaching a magical effect to the caster that polls isCasting() at intervals shorter than the charging time of a projectile spell. So, when polling, if the actor is casting - and - they have a given spell equipped - we interrupt them with interruptCast() (cast Actor as Obj. Ref).

As you have pointed out, polling everyone in the game every second (or even more frequently) is not an ideal solution. Depending on the application of your to-be-interrupted spell, this may be worked around with a cloak spell that has limited range. My thinking is that the "monitor" effect would be applied only to NPCs within range. If the spell is intended to be used in combat or in times when the PC is close (even if the projectile is not intended for the PC), the polling could be limited by both the distance from the player and the caster's combat state.

I'm not sure how well this would scale beyond 10 NPCs, but given the papyrus limitations I think this is (at least that I can come up with) the only way to do it. I have previously tried to prevent NPCs from casting certain spells under certain conditions by giving them a Perk that would increase their spell cost by huge amounts with actor values destructionMod and so on, but that did not work. It seems only the PC is affected by those actor values. This would be a very neat way to solve the problem if it worked as expected.

At any rate, the suggestion I'm giving is flawed on at least two levels (isEquipped x != isCasting x, polling continuously) and may not be applicable to your problem.

Please do report back if you find a good way to do this.
User avatar
Tiffany Carter
 
Posts: 3454
Joined: Wed Jul 19, 2006 4:05 am

Post » Fri Mar 08, 2013 8:08 pm

It is possible to achieve this by attaching a magical effect to the caster that polls isCasting() at intervals shorter than the charging time of a projectile spell. So, when polling, if the actor is casting - and - they have a given spell equipped - we interrupt them with interruptCast() (cast Actor as Obj. Ref).

You shouldn't need to poll at all. Actors that need to be watched would need a scripted ability. The ability needs one scripted magic effect with one condition (on the ability screen, not on the magic effect) - isCasting == 1. That way your scripted effect only happens when isCasting is true.

As to applying the ability, Verteiron's idea seems pretty sound.


...and make that ability spell the Equip Ability for the spell you want to be able to interrupt.
User avatar
Jennie Skeletons
 
Posts: 3452
Joined: Wed Jun 21, 2006 8:21 am

Post » Sat Mar 09, 2013 9:57 am

I'm afraid isCasting couldn't detect an Aiming delivered spell fast enough but I might give it a try. I'll keep you updated.
User avatar
Jamie Lee
 
Posts: 3415
Joined: Sun Jun 17, 2007 9:15 am

Post » Fri Mar 08, 2013 7:21 pm

You could register and watch for an animation event like BeginCastRight and BeginCastLeft; that should take care of all humanoid casters, anyway.
User avatar
ezra
 
Posts: 3510
Joined: Sun Aug 12, 2007 6:40 pm

Post » Sat Mar 09, 2013 4:13 am

I've never used isCasting but I know isDualCasting gives a fairly quick response. Theres a small lag (maybe .5 sec) between when casting starts and when the effect kicks in which if more than quick enough for what I'm using it for.
User avatar
laila hassan
 
Posts: 3476
Joined: Mon Oct 09, 2006 2:53 pm

Post » Fri Mar 08, 2013 11:10 pm

Here we go. Thanks for the help guys, your advices have been invaluable. It's only fair that I share the result.

Spoiler

Scriptname CastingManagement extends activemagiceffect  Message Property SpellFailedMSG Auto					; fill this with something along the lines of "The spell failed."Sound Property MAGFail AutoActor Property PlayerREF AutoKeyword Property MagicSchoolAlteration Auto					; since there's no way to detect beforehand a spell school,Keyword Property MagicSchoolConjuration Auto					; we have to attach these keywords to the spells thatKeyword Property MagicSchoolDestruction Auto					; should be affected by the interrupting abilityKeyword Property MagicSchoolIllusion AutoKeyword Property MagicSchoolRestoration AutoEvent OnEffectStart(Actor akTarget, Actor akCaster)	bool LeftSpellFailed = false					; we use these to check whether we should	bool RightSpellFailed = false					; inform the player that the spell failed or not	float LeftSpellSkill						; we need separate skill checks for either hands	float RightSpellSkill	Spell LeftHandSpell = akCaster.GetEquippedSpell(0)		; first off, get the equipped spells..	Spell RightHandSpell = akCaster.GetEquippedSpell(1)	LeftSpellSkill = GetSpellSkill (LeftHandSpell, akCaster)	; ..then check for the casting actor skill	RightSpellSkill = GetSpellSkill (RightHandSpell, akCaster)	int LeftRandomRate = Utility.RandomInt(1, 100)			; we use two separate random generate numbers,	int RightRandomRate = Utility.RandomInt(1, 100)			; so that the same spell could fail in one hand and have success in the other	int FailChanceOffset = 25					; no spell fail if the Actor skill is >= 75	float LeftFailChance = LeftRandomRate / (LeftSpellSkill + FailChanceOffset)	; the higher the skill, the lower the chance to fail	float RightFailChance = RightRandomRate / (RightSpellSkill + FailChanceOffset)	if ( LeftHandSpell )		if ( LeftFailChance > 1 )			akCaster.UnequipSpell(LeftHandSpell, 0)		; remove the spell and immediately reattach it to the hand,			akCaster.EquipSpell(LeftHandSpell, 0)		; this has the effect of actually preventing the spell from being casted			LeftSpellFailed = true				; without disrupting the gameplay		endif	endif	if ( RightHandSpell )		if ( RightFailChance > 1 )			akCaster.UnequipSpell(RightHandSpell, 1)	; same as above			akCaster.EquipSpell(RightHandSpell, 1)			RightSpellFailed = true		endif	endif	If ( ( akCaster == PlayerREF ) && ( ( LeftSpellFailed ) || ( RightSpellFailed ) ) )		SpellFailedMSG.Show()					; your spell failed! LOL! NOOB!		   	 MAGFail.play(akCaster)	EndIfEndEventFloat Function GetSpellSkill(Spell ActorSpell, Actor CastingActor)	float ActorSkill	if ( ActorSpell.GetNthEffectMagicEffect(0).HasKeyword( MagicSchoolAlteration ) )		ActorSkill = CastingActor.GetActorValue("Alteration")			elseif ( ActorSpell.GetNthEffectMagicEffect(0).HasKeyword( MagicSchoolConjuration ) )		ActorSkill = CastingActor.GetActorValue("Conjuration")	elseif ( ActorSpell.GetNthEffectMagicEffect(0).HasKeyword( MagicSchoolDestruction ) )		ActorSkill = CastingActor.GetActorValue("Destruction")	elseif ( ActorSpell.GetNthEffectMagicEffect(0).HasKeyword( MagicSchoolIllusion ) )		ActorSkill = CastingActor.GetActorValue("Illusion")	elseif ( ActorSpell.GetNthEffectMagicEffect(0).HasKeyword( MagicSchoolRestoration ) )		ActorSkill = CastingActor.GetActorValue("Restoration")	endif			return ActorSkillEndFunction

oh, forgot to mention: the ability should have the IsCasting OR IsDualCasting conditions.

forgot to mention #2: SKSE needed
User avatar
Jessica Lloyd
 
Posts: 3481
Joined: Fri Aug 25, 2006 2:11 pm

Post » Sat Mar 09, 2013 6:07 am

That's great that you pulled this off.


I is slightly jealous.
User avatar
Loane
 
Posts: 3411
Joined: Wed Apr 04, 2007 6:35 am

Post » Fri Mar 08, 2013 10:10 pm

:)

The downside is that you have to manually add the keywords yourself to any spell you wish to be affected by the interruption. The MagicSchool keywords are actually already there in the CK but no spell is using them. Laziness on Beth part, perhaps. I'm going to see what the USKP guys think about this, those keywords might be of great help to modders to detect beforehand a spell school.
User avatar
GabiiE Liiziiouz
 
Posts: 3360
Joined: Mon Jan 22, 2007 3:20 am

Post » Fri Mar 08, 2013 11:47 pm

Wow, clever using the Unequip/Equip to cancel the spell. I had no idea that would work quickly enough to cancel the spell. Nice work!
User avatar
BRIANNA
 
Posts: 3438
Joined: Thu Jan 11, 2007 7:51 pm

Post » Sat Mar 09, 2013 3:05 am

Now that I think about it, I might just use GetAssociatedSkill to detect the spell school and get rid of the keywords. I'm going to see how it goes and get back to you as soon as I've tested it enough to tell whether it's working as intended or not.
User avatar
Julie Ann
 
Posts: 3383
Joined: Thu Aug 23, 2007 5:17 am

Post » Sat Mar 09, 2013 5:14 am

Oh wow, it works :D forget the first version, no keywords needed anymore and it's missing an important check that throws warnings at the logs.

SKSE needed. Good thing the effect we need to check is always the one at index 0 for all spells.

Spoiler

Scriptname CastingManagement extends activemagiceffect  Message Property SpellFailedMSG Auto                ; fill this with something along the lines of "The spell failed."Actor Property PlayerREF AutoSound Property MAGFail AutoEvent OnEffectStart(Actor akTarget, Actor akCaster)    int FailChanceOffset = 25                            ; no spell fail if the Actor skill is >= 75    bool LeftSpellFailed = false                            ; we use these to check whether we should    bool RightSpellFailed = false                            ; inform the player that the spell failed or not    float LeftSpellSkill = 1                                ; we need separate skill checks for either hands    float RightSpellSkill = 1    float LeftFailChance    float RightFailChance    int LeftRandomRate    int RightRandomRate    Spell LeftHandSpell = akCaster.GetEquippedSpell(0)    ; first off, get the equipped spells..    Spell RightHandSpell = akCaster.GetEquippedSpell(1)    if ( LeftHandSpell )        LeftSpellSkill = GetSpellSkill (LeftHandSpell, akCaster)    ; ..then check for the casting actor skill        LeftRandomRate = Utility.RandomInt(1, 100)        ; we use two separate random generate numbers, so that the same spell could fail in one hand and have success in the other        LeftFailChance = LeftRandomRate / (LeftSpellSkill + FailChanceOffset)        ; the higher the skill, the lower the chance to fail        if ( LeftFailChance > 1 )            akCaster.UnequipSpell(LeftHandSpell, 0)        ; remove the spell and immediately reattach it to the hand,            akCaster.EquipSpell(LeftHandSpell, 0)        ; this has the effect of actually preventing the spell from being casted            LeftSpellFailed = true                        ; without disrupting the gameplay        endif    endif    if ( RightHandSpell )        RightSpellSkill = GetSpellSkill (RightHandSpell, akCaster)        RightRandomRate = Utility.RandomInt(1, 100)        RightFailChance = RightRandomRate / (RightSpellSkill + FailChanceOffset)        if ( RightFailChance > 1 )            akCaster.UnequipSpell(RightHandSpell, 1)    ; same as above            akCaster.EquipSpell(RightHandSpell, 1)            RightSpellFailed = true        endif    endif    If ( ( akCaster == PlayerREF ) && ( ( LeftSpellFailed ) || ( RightSpellFailed ) ) )        SpellFailedMSG.Show()                    ; your spell failed! LOL! NOOB!        MAGFail.play(akCaster)    EndIfEndEventFloat Function GetSpellSkill(Spell ActorSpell, Actor CastingActor)    float ActorSkill    if ( ActorSpell.GetNthEffectMagicEffect(0).GetAssociatedSkill() == "Alteration" )        ActorSkill = CastingActor.GetActorValue("Alteration")            elseif ( ActorSpell.GetNthEffectMagicEffect(0).GetAssociatedSkill() == "Conjuration")        ActorSkill = CastingActor.GetActorValue("Conjuration")    elseif ( ActorSpell.GetNthEffectMagicEffect(0).GetAssociatedSkill() == "Destruction")        ActorSkill = CastingActor.GetActorValue("Destruction")    elseif ( ActorSpell.GetNthEffectMagicEffect(0).GetAssociatedSkill() == "Illusion" )        ActorSkill = CastingActor.GetActorValue("Illusion")    elseif ( ActorSpell.GetNthEffectMagicEffect(0).GetAssociatedSkill() == "Restoration" )        ActorSkill = CastingActor.GetActorValue("Restoration")    endif            return ActorSkillEndFunction

edit: condensed for efficiency
User avatar
Alexx Peace
 
Posts: 3432
Joined: Thu Jul 20, 2006 5:55 pm

Next

Return to V - Skyrim