Difference between revisions of "Dynamically Attaching Scripts"

From the CreationKit Wiki
Jump to navigation Jump to search
imported>Fg109
imported>Chesko
(Modified code slightly to check if the quest is completely stopped, with failover after 5 seconds. Should result in less waiting and more reliability.)
 
(11 intermediate revisions by 5 users not shown)
Line 3: Line 3:
There are many ways to do this, and this tutorial will only cover two of them.  The first method is to attach magic effect scripts to actors, and the second is to attach reference alias scripts to objects.
There are many ways to do this, and this tutorial will only cover two of them.  The first method is to attach magic effect scripts to actors, and the second is to attach reference alias scripts to objects.


==Attaching Scripts to Actors==


== Attaching Scripts to Actors ==
We're going to attach scripts to Actors by making use of Skyrim's [[Magic Effect#Effect Archetypes|Magic Effect archetypes]]. We're going to give the player an ability (Cloak), and that Cloak is going to give--via a scripted, aimed concentration spell--nearby Actors their own abilities containing scripted magic effects. For the purposes of this tutorial, we will be placing scripts on Actors which will track the damage done to them by the player, and display the damage as a notification.


We're going to attach scripts to actors by making use of magic effects.  We're going to give the player an ability, and that ability is going to be used to give nearby actors their own abilities containing scripted magic effects. For the purposes of this tutorial, we will be placing scripts on actors that will track the damage done to them by the player, and display the damage as a message in the upper left hand corner of the screen.
===Abstract===
[[File:DASOverview.png|thumb|250px|alt=Overview|Overview]]
This method consists of:


=== Create a Reference Alias ===
# A [[Quest|'''Quest''']], <code>'''mymodQuest'''</code>, which fills an [[Quest_Alias_Tab|'''Alias''']] on the player.
# A Cloak Ability, <code>'''CloakAbility'''</code>, and...
#* The Effect, <code>'''CloakEffect'''</code>, which cloaks an area around the player.
# A Concentrated, Aimed Spell, <code>'''ApplyingSpell'''</code> and...
#* The Effect, <code>'''ApplyingEffect'''</code>, which will determine whether or not to add a script to the Actor based on various conditions.
#** The Script, <code>'''ApplyingScript'''</code>, which is responsible for adding the 'MonitorAbility' spell to the Actor, and if necessary carrying out any additional conditional logic.
# An Ability, <code>'''MonitorAbility'''</code>, which is added to the Actor by the above script and...
#* The Effect, <code>'''MonitorEffect'''</code>, which contains...
#** The Script <code>'''MonitorScript'''</code>, which runs on the Actor and will monitor the damage done by the player.


First we need to create a [[Quest_Alias_Tab#Reference_Aliases|reference alias]].  This step is not actually necessary, but it is easier to just load up the mod with the player already having the ability, rather than using the console to add the ability to the player.  (Another way this could be done would be to create a quest script using [[AddSpell - Actor|AddSpell]].)
Additionally we will cover the various [[Condition_Functions|'''Condition Functions''']] which can help ensure the script does not get applied to Actors unnecessarily, and also show how condition functions can [[#Reducing_Cloaking_Frequency|reduce the frequency]] of unnecessary cloaking checksLastly, we will cover how to deal with issues like the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']].


In the [[Object_Window|'''Object Window''']], navigate to [[Quest|'''Quest''']] under the '''Character''' category.  Create a new quest by right-clicking on the list and choosing 'New'Make sure that '''Start Game Enabled''' in the [[Quest_Data_Tab|'''Quest Data''']] tab is checked.  Click on the [[Quest_Alias_Tab|'''Quest Alias''']] tab, then right-click in the empty list and choose to create a new reference alias.  Make sure that the '''Fill Type''' is '''Specific Reference''' and choose the reference 'PlayerRef' in the '(any)' cell.
For the sake of brevity and readibility, the prefix "mymod" has been removed from the Spell and Magic Effect names when compared to the tutorial imagesIt is recommended that you prefix all your forms with a unique identifier.


Click OK to create the reference alias, and OK again to close the quest window.  Hereafter, we will refer to the quest as '''DASQuest''' and the reference alias as '''PlayerAlias'''.


=== Create the First Ability ===
=== Set Up a Reference Alias ===
[[File:DASPlayerAlias-1.png|thumb|250px|alt=Reference Alias|Reference Alias]]
In the [[Object_Window|'''Object Window''']], navigate to [[Quest|'''Quest''']] under the Character category.  Create a new quest by right-clicking on the list and choosing 'New'.  For this tutorial we will be naming the quest <code>'''mymodQuest'''</code>. Make sure that '''Start Game Enabled''' in the [[Quest_Data_Tab|'''Quest Data''']] tab is checked.  Click on the [[Quest_Alias_Tab|'''Quest Alias''']] tab, then right-click in the empty list and choose 'New Reference Alias'.  We will name this alias <code>'''PlayerAlias'''</code>.  In the [[Reference_Alias#Reference_Aliases|Reference Alias]] window, under Fill Type, select the '''Specific Reference''' option and click the 'Select Forced Reference' button.  Under Cell choose <code>(any)</code>, and Ref should default to <code>PlayerRef (Player)</code>.  If not, select it from the dropdown menu.


Next, we create the ability that we're going to put on the player.  Navigate to [[Spell|'''Spells''']] which is under '''Magic''' in the '''Object Window'''.  Right-click in the list and choose to create a new spell.  Change the '''Type''' to 'Ability', making sure that '''Casting''' is 'Constant Effect' and that '''Delivery''' is 'Self'.  For now, we can leave the list of effects blank.


After closing the spell window, navigate to [[Magic_Effect|'''Magic Effects''']], under '''Magic''' in the '''Object Window'''.  Right-click in the list and choose to create a new magic effect.  Change the [[Magic_Effect#Effect_Archetypes|'''Effect Archetype''']] to 'Cloak'.  The '''Casting Type''' should be 'Constant Effect' and '''Delivery''' 'Self'.  Check the flag for '''Hide in UI'''.  Click OK to accept the effect as is for now.
=== Create the Cloak Ability ===


Go back to the ability we just created, and in the effects list, add in the magic effect we just created. There doesn't have to be any conditions or duration, but the '''Magnitude''' needs to be changedFor now, give magnitude the value of 192.0.
First we will set up the Cloak effect which we will put onto the Cloak Ability. In the '''Object Window''', navigate to [[Magic_Effect|'''Magic Effects''']], under the Magic categoryCreate a new Magic Effect by right-clicking on the list and choosing 'New'. Change the [[Magic_Effect#Effect_Archetypes|Effect Archetype]] to <code>Cloak</code>, set Casting Type to <code>Constant Effect</code>, and Delivery to <code>Self</code>.


{{InDepth|The magnitude of a Cloak archetype effect is the range of the cloak in feet. So a magnitude of 192.0 is 192 feet, or 4096 [[Units|units]], which is the length of a cell (4096 x 4096).}}
<div style="background-color: #ffebeb; border-style: dotted; border-color: #ffebeb; width: 625px; padding: 0 10px 0 10px;">
; <span style="FONT-VARIANT:SMALL-CAPS;"><span style="TEXT-TRANSFORM:LOWERCASE;">Important Flags</span></span>
:'''Hide in UI''' - If you want this ability hidden from the player.
:'''No Hit Event''' - We ''must'' check this flag as one of the steps to avoid the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']].
</div>
We'll give this the ID <code>'''CloakEffect'''</code> and a descriptive Name, and then click OK to accept the changes.


Hereafter, we will refer to this ability as '''CloakAbility''' and the magic effect as '''CloakEffect'''.
Next, we create the Cloak ability that will be placed on the player.  In the '''Object Window''', navigate to [[Spell|'''Spells''']] under the Magic category.  Create a new Spell by right-clicking on the list and choosing 'New'.  We'll give this the ID <code>'''CloakAbility'''</code> and a descriptive Name.  Change the Effect Archetype to <code>Ability</code>, set Casting Type to <code>Constant Effect</code> and Delivery to <code>Self</code>.  In the Effects list, we will add <code>'''CloakEffect'''</code>.  Set Magnitude to <code>192.0</code> and leave the Conditions list blank for now, but take note as we will fill this in later as part of working around the [[#HEADING NAME|''''Brawl Bug'''']]. Click OK to accept the changes.


=== Create the Second Ability ===
<div style="display:block; float:right; width: 50%;">
{{InDepth|The magnitude of a Cloak archetype effect is the range of the cloak in feet.  So a magnitude of <code>192.0</code> is 192 feet, or 4096 [[Units|units]], which is the length of a cell (4096 x 4096).}}
</div>
<gallery>
File:DASCloakEffect-1.png|Cloak Effect
File:DASCloakAbility-1.png|Cloak Ability
</gallery>


Now we create the ability that we're going to put on our actors.  Create a new ability similarly to how we created the first ability.  The difference here is that we're going to use another magic effect.
=== Create the Applying Spell and Effect ===


When creating the magic effect, change its '''Effect Archetype''' to 'Script'Again, make sure that the '''Casting Type''' is 'Constant Effect' and that '''Delivery''' is 'Self'.  We should check the '''No Death Dispel''' flag if we want the magic effect to stay active even when the actor is dead.
The Applying spell will be cast on an Actor when he/she comes into contact with the CloakThis spell is an intermediary between the Cloak and Actor, and in its simplest form only needs to run [[AddSpell_-_Actor|AddSpell()]].


Click OK to accept the effect as is for nowOnce the magic effect window has closed, double-click the magic effect to bring it back up againThis step is needed because when we first create a magic effect, we cannot attach a script to it. Well, now we canThis script is going to be the script that we want to attach to our actor:
First we will set up the effect which we will put onto the Applying Spell. In the '''Object Window''', navigate to [[Magic_Effect|'''Magic Effects''']], under the Magic categoryCreate a new Magic Effect by right-clicking on the list and choosing 'New'.  For this effect, change the '''Effect Archetype''' to <code>Script</code>, the '''Casting Type''' to <code>Concentration</code> and '''Delivery''' to <code>Aimed</code>.
<div style="background-color: #ffebeb; border-style: dotted; border-color: #ffebeb; width: 625px; padding: 0 10px 0 10px;">
; <span style="FONT-VARIANT:SMALL-CAPS;"><span style="TEXT-TRANSFORM:LOWERCASE;">Important Flags</span></span>
:'''No Death Dispel''' - Needed if we want this effect to affect dead actors as wellIn general, it is a good idea to check this and handle the cleanup of a magic effect inside an [[OnDying_-_Actor|OnDying()]] Event. <sup>[[#References|1]]</sup>
:'''No Hit Event''' - We ''must'' check this flag as one of the steps to avoid the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']].
</div>
We'll give this the ID <code>'''ApplyingEffect'''</code> and a descriptive Name, and then click OK to accept the changesOpen the Effect again to add a new a script, which we will title <code>'''ApplyingScript'''</code>:


<source lang="papyrus">Scriptname DamageMonitorScript extends ActiveMagicEffect   
<source lang="papyrus">Scriptname ApplyingScript extends ActiveMagicEffect 
 
Spell Property MonitorAbility Auto
 
Event OnEffectStart(Actor akTarget, Actor akCaster)
akTarget.AddSpell(MonitorAbility)
EndEvent</source>
Save the script, and click OK to accept all changes.  We will be coming back to this effect to fill its Conditions and script properties later.
 
In the '''Object Window''', navigate to [[Spell|'''Spells''']] under the Magic category.  Create a new Spell by right-clicking on the list and choosing 'New'.  Keep the Type as <code>Spell</code>, but change the Casting Type to <code>Concentration</code> and set Delivery to <code>Aimed</code>.  We'll give this the ID <code>'''ApplyingSpell'''</code> and a descriptive Name.  In the Effects list, we will add <code>'''ApplyingEffect'''</code>.  Leave magnitude at 0 and leave the Conditions list blank.  Click OK to accept all changes.
 
'''IMPORTANT:''' To ensure the <code>'''ApplyingSpell'''</code> is correctly applied to targets, you may need to change the duration of the <code>'''ApplyingSpell'''</code> to 1 (instead of 0, as it appears in the images).
 
==== Attaching the Spell to our Cloak Effect ====
 
With <code>'''ApplyingSpell'''</code> saved, we now must attach it to our Cloak Effect.  Open <code>'''CloakEffect'''</code> and in the Assoc. Item 1 dropdown, find and select <code>'''ApplyingSpell'''</code> from the list.  Click OK to accept changes on <code>'''CloakEffect'''</code>
<gallery>
File:DASApplyingEffect-1.png|Applying Effect
File:DASApplyingSpell-1.png|Applying Spell
File:DASCloakEffect-2.png|Attaching the Spell
</gallery>
 
=== Create the Ability and Effect for the Actor ===
 
This is the spell that will be applied to the Actors.  Again, for the purposes of this tutorial we have chosen to attach a script to Actors which monitors the damage done to them by the player.
 
Create a new magic effect like before, and set its Effect Archetype to <code>Script</code>, its Casting Type to <code>Constant Effect</code> and Delivery to <code>Self</code>. 
<div style="background-color: #ffebeb; border-style: dotted; border-color: #ffebeb; width: 625px; padding: 0 10px 0 10px;">
; <span style="FONT-VARIANT:SMALL-CAPS;"><span style="TEXT-TRANSFORM:LOWERCASE;">Important Flags</span></span>
:'''No Death Dispel''' - Needed if we want this effect to affect dead actors as well.  In general, it is a good idea to check this and handle the cleanup of a magic effect inside an [[OnDying_-_Actor|OnDying()]] Event. <sup>[[#References|1]]</sup>
:'''No Hit Event''' - We ''must'' check this flag as one of the steps to avoid the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']].
</div>
We'll be giving it the ID <code>'''MonitorEffect'''</code> and a descriptive Name, then click OK to accept the changes.  Open the Effect again to add a new script, which we will title <code>'''MonitorScript'''</code>:
 
<source lang="papyrus">Scriptname MonitorScript extends ActiveMagicEffect   


Actor MySelf
Actor MySelf
Line 60: Line 122:
Float Damage = Health - MySelf.GetActorValue("Health")
Float Damage = Health - MySelf.GetActorValue("Health")
Health = MySelf.GetActorValue("Health")
Health = MySelf.GetActorValue("Health")
if (akAggressor == Game.GetPlayer())
if (akAggressor == Game.GetPlayer()) && (Damage > 0)
Debug.Notification("You did " + Damage as Int + " points of damage.")
Debug.Notification("You did " + Damage as Int + " points of damage.")
endif
endif
EndEvent</source>
EndEvent</source>
Save the script, and click OK to accept all changes.  We will come back to this effect to fill its Conditions later.


Click OK to close the magic effect windowNow go back to the ability we have just made (the second one) and add the magic effect we just made (again, the second one) as its magic effectWe will leave the conditions blank with the duration and magnitude at 0. Click OK to close the spell window.
Create a new spell like before.  We'll give this the ID <code>'''MonitorAbility'''</code> and a descriptive NameChange the Effect Archetype to <code>Ability</code>, set Casting Type to <code>Constant Effect</code> and Delivery to <code>Self</code>.  In the Effects list, we will add <code>'''MonitorEffect'''</code>Leave Magnitude at 0 and leave the Conditions list blank. Click OK to accept the changes.
<gallery>
File:DASMonitorEffect-1.png|Monitor Effect
File:DASMonitorAbility-1.png|Monitor Ability
</gallery>


Hereafter, we refer to this ability as '''MonitorAbility''' and the magic effect as '''MonitorEffect'''.


=== Create the Spell ===
=== Putting It All Together ===


We need to create another new spell.  This is the spell that will be cast on an actor when he/she comes into contact with the cloak from the player's ability.
==== Filling Properties ====


Navigate to '''Spells''' under '''Magic''' in the '''Object Window''' again.  Right-click the list and choose to create a new spellThis time, keep the '''Type''' as 'Spell', but change the '''Casting''' to 'Concentration' and '''Delivery''' to 'Aimed'.  Click OK to accept the spell as it is for now.
First we need to fill the <code>'''MonitorAbility'''</code> property on the <code>'''ApplyingEffect'''</code> and its scriptOpen <code>'''ApplyingEffect'''</code>, then open its [[Papyrus_Introduction#Setting_Property_Data_in_the_Editor|'''Properties window''']] and auto-fill the <code>MonitorAbility</code> property, or select MonitorAbility manually from the drop-down menu.


Now create another new magic effect.  For this effect, change the '''Effect Archetype''' to 'Script', the '''Casting Type''' to 'Concentration' and '''Delivery''' to 'Aimed'.  If we want this effect to affect dead actors as well, then we should check the flag for '''No Death Dispel'''.
==== Filling Conditions ====


Close the window, and then open it again to attach a script to the magic effect:
Now we want to conditionalize some of the effects so that they only get applied when they should.  With <code>'''ApplyingEffect'''</code> still open, right-click in the Target Conditions list and select 'New'.  Our first condition will make it such that it does not attempt to apply <code>'''MonitorEffect'''</code> to an Actor if he/she already has the effect:


<source lang="papyrus">Scriptname AddAbilityScript extends ActiveMagicEffect 
[[HasMagicEffect]] MagicEffect: 'MonitorEffect' == 0


Spell Property MonitorAbility Auto
This needs to be run on '''Subject'''.  Optionally, instead of [[HasMagicEffect]] we may use the more general [[HasMagicEffectKeyword]] with our own custom [[Keyword]], especially if we want to apply more than one type of Magic Effect to an Actor based on certain conditions.  This is covered below in [[#Multiple_Conditional_Magic_Effects|'''Multiple Conditional Magic Effects''']].
 
We may also not want the spell to apply our script to dead Actors, so we should add another condition to the '''Subject''':
 
[[GetDead]] NONE == 0
 
You can also reorder your condition functions by clicking <code>'''<<'''</code> or <code>'''>>'''</code> with the condition selected.  It is more important that the Actor does not have <code>'''MonitorEffect'''</code> so we can place this above [[GetDead]].  The game ''should'' stop condition testing the when the first condition tests false, but it is unknown if it does so, so consider this a minor optimization.
 
We shall also apply some conditions to the CloakEffect, but we will cover this in the section covering the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']]
<div style="display:block; float:right; width: 50%;">
{{InDepth|Note that by adding a [[Condition_Functions|'''Condition Function''']] to the Magic Effect itself the condition test is only run once -- when the Magic Effect is first applied.  To test continuously for a condition, use the Target Conditions list for the Magic Effect on the Spell instead.}}
</div>
<gallery>
File:DASApplyingEffect-2.png|Condition 1
File:DASApplyingEffect-3.png|Condition 2
</gallery>
 
==== Adding the Cloak to the <code>PlayerAlias</code> ====
 
The only thing left to do is add our <code>'''CloakAbility'''</code> to our <code>'''PlayerAlias'''</code>.  There are two options:  We can either add the spell to the Alias Spells list; or if we are interested in [[#Reducing_Cloaking_Frequency|'''Reducing Cloaking Frequency''']] and circumventing the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']], we can follow the steps outlined below.
 
Now start the game with the mod loaded and attack someone.  The damage dealt by the player should be seen as messages on the upper left corner of the screen.
 
 
=== Optimizations & Bug Fixes ===
 
In this section we will deal with some of the downsides of this method, and ways of working around them. The most significant of which is the ''''Brawl Bug''''.
 
==== Reducing Cloaking Frequency ====
 
If your script is like the example shown here, whereby most of the logic is occuring within an [[OnUpdate_(Papyrus)|'''OnUpdate()''']] event on each Actor, we can significantly reduce the time spent cloaking surrounding NPCs.  In our effect, we are using a cloaking radius of <code>192.0</code>, which equates to a cell's width on all sides of the player.  It takes the player character about 8-10 seconds to sprint from one cell border to another, so we will attempt to run the cloak at least every half cell width, or every 4-5 seconds.  So, by the time we run a half cell away, our cloak effect will have already applied the spell to Actors a full cell ahead of the player character.
 
We can accomplish this by adding the <code>'''CloakAbility'''</code> to the player, waiting a short amount of time, and then removing it. To do this, we will need to add a script to our <code>'''PlayerAlias'''</code>.  Open your quest, and click on the [[Quest_Alias_Tab|'''Quest Alias''']] tab. Double-click on our <code>'''PlayerAlias'''</code> to open up the Reference Alias window.  Add a new script to our alias. We will call it <code>'''PlayerScript'''</code>, and fill it with the following code:
 
<source lang="papyrus">Scriptname PlayerScript extends ReferenceAlias 
 
Spell Property CloakAbility Auto
Actor Property PlayerRef Auto
 
Event OnInit()
RegisterForSingleUpdate(1)
EndEvent
 
Event OnUpdate()
PlayerRef.AddSpell(CloakAbility, false)
; How long you would like to keep the cloak active
Utility.Wait(1)
PlayerRef.RemoveSpell(CloakAbility)
; How long until the cloak activates again
RegisterForSingleUpdate(4)
EndEvent
</source>
Now we will save the script and auto-fill the properties. This method of adding and removing the cloak is also used in circumventing the [[#The_.27Brawl_Bug.27|''''Brawl Bug'''']].
 
==== Death Dispel ====
 
Not using the '''No Death Dispel''' flag on effects with scripts can lead to errors.<sup>[[#References|[1]]]</sup>  To avoid these, you should handle the cleanup of your magic effect in the magic effect itself. Example:
 
<source lang="papyrus">Scriptname MonitorScript extends ActiveMagicEffect 


Event OnEffectStart(Actor akTarget, Actor akCaster)
Event OnEffectStart(Actor akTarget, Actor akCaster)
akTarget.AddSpell(MonitorAbility)
; Any Initialization/Setup code here
EndEvent</source>
; Go to our Alive state
GoToState("alive")
EndEvent
 
State alive
Event OnBeginState()
RegisterForSingleUpdate(0.25)
EndEvent
 
Event OnUpdate()
; Regular code here
RegisterForSingleUpdate(0.25)
EndEvent
 
Event OnDying(Actor akKiller)
; Any additional cleanup code here
UnregisterForUpdate()
GoToState("dead")
EndEvent
 
EndState
 
State dead
; Nothing needed here
EndState
 
</source>
 
 
==== The 'Brawl Bug' ====
 
The 'Brawl Bug' affects several quests which rely on determining if the player has cast magic on the Actor.  This means that our <code>'''CloakEffect'''</code> can inadvertently progress various quests to stages which we may not desire.  The most prominent example of these undesired effects is during Brawls, where using anything but fists will cause the fight to become "real".
 
We have already taken one step toward circumventing this issue, and that is by checking <code>No Hit Event</code> on our magic effects.  Without this, none of our other changes will matter, and it will also cause the player to receive bounty in addition to causing the fight to turn sour.
 
First we need to add some [[Condition Functions]] to our <code>'''CloakAbility'''</code> and <code>'''CloakEffect'''</code>. Open our <code>'''CloakAbility'''</code> and double click on its magic effect.  Adding conditions to the magic effect here will cause the effect to dispel the moment these conditions become false. We should add the following conditions:
 
[[GetQuestRunning]] Quest: 'DGIntimidateQuest' == 0
[[GetStage]] Quest: 'C00' != 20
 
To elaborate, <code>DGIntimidateQuest</code> is the quest that begins running when a Brawl starts, and stops running when it ends.  Quest <code>C00</code> is the main Companions quest line, and specifically we are disallowing it from running during the stage where you must fight Vilkas.  Note, this is technically not one of the game-breaking examples of the bug, since Vilkas only needs to be punched three times to end the stage and progress the Companions quest.  What this condition does is prevent him from commenting on the player's "magic use".
 
Unfortunately, there is a minute chance that at the start of a Brawl our <code>'''CloakEffect'''</code> will still be active, and not dispel quickly enough.  To deal with this, we can also pause our cloak during some of the sub-quests leading up to the Brawl.  A non-exhaustive list:
 
[[GetQuestRunning]] Quest: 'Favor017' == 0
[[GetStage]] Quest: 'FreeformRiften19' != 30
 
And if all else fails...
 
===== Include an Off Switch =====
 
In case a user is having issues with the cloaking effects from our mod, we will provide an off switch to allow the user to temporarily cirumvent the issue without uninstalling our mod.  We will simply create a new [[GlobalVariable_Script|'''GlobalVariable''']] and wrap our Add/RemoveSpell logic on <code>'''PlayerScript'''</code> with it.  To create a new global, in the '''Object Window''', navigate to [[Global|'''Global''']] under the Miscellaneous category. In the object list, right-click and select 'New' to create a new global variable.  We will call this <code>'''mymod_CloakEffectOn'''</code> (replace "mymod" with your own prefix) and set the value to <code>1</code>.  We will leave the Variable Type as <code>Short</code>.
 
Now we will wrap our code in <code>'''PlayerScript'''</code> with a condition to run only when this global's value is set to <code>1</code>:
 
<source lang="papyrus">Scriptname PlayerScript extends ReferenceAlias 


Remember to set the value of the 'MonitorAbility' to the '''MonitorAbility''' that we created earlier.  Click OK to close the magic effect window.
Spell Property CloakAbility Auto
Actor Property PlayerRef Auto
GlobalVariable Property mymod_CloakEffectOn Auto


Go back to the spell we just created, and add this new magic effect to its effects listThe magnitude and duration can be left at 0, but this time we need to add a [[:Category:Conditions|condition]]. The condition is:
Event OnInit()
RegisterForSingleUpdate(1)
EndEvent
 
Event OnUpdate()
If mymod_CloakEffectOn.GetValue()
PlayerRef.AddSpell(CloakAbility, false)
; How long you would like to keep the cloak active
Utility.Wait(1)
PlayerRef.RemoveSpell(CloakAbility)
EndIf
; How long until the cloak activates again
RegisterForSingleUpdate(4)
EndEvent
</source>
Don't forget to auto-fill the new '''GlobalVariable''' property on our <code>'''PlayerScript'''</code>Users now have the opportunity of pausing the cloak on their own with the console command  <code>set mymod_CloakEffectOn to 0</code>.  


[[HasMagicEffect]] MagicEffect: 'MonitorEffect' == 0


Make sure that it's set to run on '''Subject'''.  If we don't want the spell to run on dead actors, we should add another condition run on '''Subject''':
=== Multiple Conditional Magic Effects ===


[[GetDead]] NONE == 0
Placeholder. (This section is a WIP)


Hereafter we will refer to this spell as '''ApplyingSpell''' and the magic effect as '''ApplyingEffect'''.
==References==


=== Putting It All Together ===
# See [[Papyrus_Runtime_Errors#.22Unable_to_call_X_-_no_native_object_bound_to_the_script_object.2C_or_object_is_of_incorrect_type.22|Common Errors]].
# Brawl Bug [http://skyrim.nexusmods.com/mods/24020 Modder's Resource]


Go back to '''CloakEffect''' and edit the '''Assoc. Item 1''' to point to '''ApplyingSpell'''.  Click OK to close the magic effect window and save the changes.


Open up '''DASQuest''' and edit '''PlayerAlias'''.  Click and drag '''CloakAbility''' into the spell list of '''PlayerAlias'''.  Click OK twice to close the alias and quest windows, and save the mod.


Now start the game with the mod loaded and attack someone.  The damage dealt by the player should be seen as messages on the upper left corner of the screen.
----




Line 124: Line 320:
This quest is going to be holding the reference aliases that we will be assigning to objects at runtime.  Navigate to '''Quest''' under '''Character''' in the '''Object Window'''.  Right-click on the list and select 'New' to create a new quest.
This quest is going to be holding the reference aliases that we will be assigning to objects at runtime.  Navigate to '''Quest''' under '''Character''' in the '''Object Window'''.  Right-click on the list and select 'New' to create a new quest.


Go to the '''Quest Alias''' tab, then right-click on the empty list and choose to create a new reference alias.  Check the flag for '''Optional'''.  For '''Fill Type''', choose '''Find Matching Reference''' and check the flags for '''In Loaded Area''' and '''Closest'''.  Under '''Match Conditions''', right-click the list and select 'New' to create a new condition:
Go to the '''Quest Alias''' tab, then right-click on the empty list and choose to create a '''New Reference Alias'''.  Check the flag for '''Optional'''.  For '''Fill Type''', choose '''Find Matching Reference''' and check the flags for '''In Loaded Area''' and '''Closest'''.  Under '''Match Conditions''', right-click the list and select 'New' to create a new condition:


  [[IsInList]] 'TreesList' == 1.00
  [[IsInList]] 'TreesList' == 1.00
Line 136: Line 332:
MiscObject Property Firewood01 Auto
MiscObject Property Firewood01 Auto


Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, \
  bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
if (akAggressor == Game.GetPlayer())
if (akAggressor == Game.GetPlayer())
if (akSource.HasKeyword(WeapTypeBattleAxe) || akSource.HasKeyword(WeapTypeWarAxe))
if (akSource.HasKeyword(WeapTypeBattleAxe) || akSource.HasKeyword(WeapTypeWarAxe))
Line 168: Line 365:
Event OnUpdate()
Event OnUpdate()
DASQuest.Stop()
DASQuest.Stop()
        ;We must wait until the quest has stopped before restarting the quest. Using Stop() and immediately Start() may fail.
        ;Continue after 5 seconds to prevent infinite loop.
        int i = 0
        while !DASQuest.IsStopped() && i < 50
            Utility.Wait(0.1)
            i += 1
        endWhile
DASQuest.Start()
DASQuest.Start()
RegisterForSingleUpdate(5)
RegisterForSingleUpdate(5)
Line 176: Line 382:
=== Conclusion ===
=== Conclusion ===


The two quests are all that's necessary for this mod.  Save the mod and then load tge game with it to test it out.  A piece of firewood should be received every time the player hits a tree with an axe.
The two quests are all that's necessary for this mod.  Save the mod and then load the game with it to test it out.  A piece of firewood should be received every time the player hits a tree with an axe.


The method for attaching scripts to objects using reference aliases is much easier than what we did with magic effects and actors.  But there are disadvantages to using it:
The method for attaching scripts to objects using reference aliases is much easier than what we did with magic effects and actors.  But there are disadvantages to using it:
Line 186: Line 392:


That would have been much more complicated because then we would have to store the number of times we hit the tree somewhere.  Obviously, we cannot store this variable in the reference alias scripts because the object filling the alias is not always going to be the same tree.  We might swing our axe twice but then the alias gets refilled before the third strike, which means it could take us 5 strikes to get a piece of firewood.
That would have been much more complicated because then we would have to store the number of times we hit the tree somewhere.  Obviously, we cannot store this variable in the reference alias scripts because the object filling the alias is not always going to be the same tree.  We might swing our axe twice but then the alias gets refilled before the third strike, which means it could take us 5 strikes to get a piece of firewood.
== Known Issues ==
*When using the second method with constantly refilling aliases, be careful with your alias scripts.  If the alias script happens to be running a while loop when its quest is stopped, then it leads to suspended stacks, in turn leading to suspended scripts.




== External Links ==
== External Links ==
[http://forums.bethsoft.com/topic/1364191-porting-kuertees-sittable-rocks-mod-from-oblivion/ Porting Kuertee's Sittable Rocks Mod from Oblivion]


[http://forums.bethsoft.com/topic/1349649-dynamically-attaching-scripts-to-actors-near-the-player/ Dynamically Attaching Scripts to Actors Near the Player]
[http://forums.bethsoft.com/topic/1349649-dynamically-attaching-scripts-to-actors-near-the-player/ Dynamically Attaching Scripts to Actors Near the Player]


[http://forums.bethsoft.com/topic/1364191-porting-kuertees-sittable-rocks-mod-from-oblivion/ Porting Kuertee's Sittable Rocks Mod from Oblivion]
[http://forums.bethsoft.com/topic/1362501-suspended-stacks-suspends-your-scripts-sometimes-indefinitely/ Suspended Stacks Suspends Your Scripts, Sometimes Indefinitely]


[[Category:Tutorials]]
[[Category:Tutorials]]
[[Category:Papyrus Tutorials]]
[[Category:Papyrus Tutorials]]
[[Category:Community Tutorials]]
[[Category:Community Tutorials]]

Latest revision as of 12:04, 20 February 2015

There are times that we may want to dynamically attach scripts to objects and actors. This could be because we don't want to alter the vanilla records, or because we think it's going to be too much work to edit every single vanilla record, or because we want our mod to work on objects added by other mods.

There are many ways to do this, and this tutorial will only cover two of them. The first method is to attach magic effect scripts to actors, and the second is to attach reference alias scripts to objects.

Attaching Scripts to Actors[edit | edit source]

We're going to attach scripts to Actors by making use of Skyrim's Magic Effect archetypes. We're going to give the player an ability (Cloak), and that Cloak is going to give--via a scripted, aimed concentration spell--nearby Actors their own abilities containing scripted magic effects. For the purposes of this tutorial, we will be placing scripts on Actors which will track the damage done to them by the player, and display the damage as a notification.

Abstract[edit | edit source]

Overview
Overview

This method consists of:

  1. A Quest, mymodQuest, which fills an Alias on the player.
  2. A Cloak Ability, CloakAbility, and...
    • The Effect, CloakEffect, which cloaks an area around the player.
  3. A Concentrated, Aimed Spell, ApplyingSpell and...
    • The Effect, ApplyingEffect, which will determine whether or not to add a script to the Actor based on various conditions.
      • The Script, ApplyingScript, which is responsible for adding the 'MonitorAbility' spell to the Actor, and if necessary carrying out any additional conditional logic.
  4. An Ability, MonitorAbility, which is added to the Actor by the above script and...
    • The Effect, MonitorEffect, which contains...
      • The Script MonitorScript, which runs on the Actor and will monitor the damage done by the player.

Additionally we will cover the various Condition Functions which can help ensure the script does not get applied to Actors unnecessarily, and also show how condition functions can reduce the frequency of unnecessary cloaking checks. Lastly, we will cover how to deal with issues like the 'Brawl Bug'.

For the sake of brevity and readibility, the prefix "mymod" has been removed from the Spell and Magic Effect names when compared to the tutorial images. It is recommended that you prefix all your forms with a unique identifier.


Set Up a Reference Alias[edit | edit source]

Reference Alias
Reference Alias

In the Object Window, navigate to Quest under the Character category. Create a new quest by right-clicking on the list and choosing 'New'. For this tutorial we will be naming the quest mymodQuest. Make sure that Start Game Enabled in the Quest Data tab is checked. Click on the Quest Alias tab, then right-click in the empty list and choose 'New Reference Alias'. We will name this alias PlayerAlias. In the Reference Alias window, under Fill Type, select the Specific Reference option and click the 'Select Forced Reference' button. Under Cell choose (any), and Ref should default to PlayerRef (Player). If not, select it from the dropdown menu.


Create the Cloak Ability[edit | edit source]

First we will set up the Cloak effect which we will put onto the Cloak Ability. In the Object Window, navigate to Magic Effects, under the Magic category. Create a new Magic Effect by right-clicking on the list and choosing 'New'. Change the Effect Archetype to Cloak, set Casting Type to Constant Effect, and Delivery to Self.

Important Flags
Hide in UI - If you want this ability hidden from the player.
No Hit Event - We must check this flag as one of the steps to avoid the 'Brawl Bug'.

We'll give this the ID CloakEffect and a descriptive Name, and then click OK to accept the changes.

Next, we create the Cloak ability that will be placed on the player. In the Object Window, navigate to Spells under the Magic category. Create a new Spell by right-clicking on the list and choosing 'New'. We'll give this the ID CloakAbility and a descriptive Name. Change the Effect Archetype to Ability, set Casting Type to Constant Effect and Delivery to Self. In the Effects list, we will add CloakEffect. Set Magnitude to 192.0 and leave the Conditions list blank for now, but take note as we will fill this in later as part of working around the 'Brawl Bug'. Click OK to accept the changes.

InDepth.jpg The magnitude of a Cloak archetype effect is the range of the cloak in feet. So a magnitude of 192.0 is 192 feet, or 4096 units, which is the length of a cell (4096 x 4096).

Create the Applying Spell and Effect[edit | edit source]

The Applying spell will be cast on an Actor when he/she comes into contact with the Cloak. This spell is an intermediary between the Cloak and Actor, and in its simplest form only needs to run AddSpell().

First we will set up the effect which we will put onto the Applying Spell. In the Object Window, navigate to Magic Effects, under the Magic category. Create a new Magic Effect by right-clicking on the list and choosing 'New'. For this effect, change the Effect Archetype to Script, the Casting Type to Concentration and Delivery to Aimed.

Important Flags
No Death Dispel - Needed if we want this effect to affect dead actors as well. In general, it is a good idea to check this and handle the cleanup of a magic effect inside an OnDying() Event. 1
No Hit Event - We must check this flag as one of the steps to avoid the 'Brawl Bug'.

We'll give this the ID ApplyingEffect and a descriptive Name, and then click OK to accept the changes. Open the Effect again to add a new a script, which we will title ApplyingScript:

Scriptname ApplyingScript extends ActiveMagicEffect  

Spell Property MonitorAbility Auto

Event OnEffectStart(Actor akTarget, Actor akCaster)
	akTarget.AddSpell(MonitorAbility)
EndEvent

Save the script, and click OK to accept all changes. We will be coming back to this effect to fill its Conditions and script properties later.

In the Object Window, navigate to Spells under the Magic category. Create a new Spell by right-clicking on the list and choosing 'New'. Keep the Type as Spell, but change the Casting Type to Concentration and set Delivery to Aimed. We'll give this the ID ApplyingSpell and a descriptive Name. In the Effects list, we will add ApplyingEffect. Leave magnitude at 0 and leave the Conditions list blank. Click OK to accept all changes.

IMPORTANT: To ensure the ApplyingSpell is correctly applied to targets, you may need to change the duration of the ApplyingSpell to 1 (instead of 0, as it appears in the images).

Attaching the Spell to our Cloak Effect[edit | edit source]

With ApplyingSpell saved, we now must attach it to our Cloak Effect. Open CloakEffect and in the Assoc. Item 1 dropdown, find and select ApplyingSpell from the list. Click OK to accept changes on CloakEffect

Create the Ability and Effect for the Actor[edit | edit source]

This is the spell that will be applied to the Actors. Again, for the purposes of this tutorial we have chosen to attach a script to Actors which monitors the damage done to them by the player.

Create a new magic effect like before, and set its Effect Archetype to Script, its Casting Type to Constant Effect and Delivery to Self.

Important Flags
No Death Dispel - Needed if we want this effect to affect dead actors as well. In general, it is a good idea to check this and handle the cleanup of a magic effect inside an OnDying() Event. 1
No Hit Event - We must check this flag as one of the steps to avoid the 'Brawl Bug'.

We'll be giving it the ID MonitorEffect and a descriptive Name, then click OK to accept the changes. Open the Effect again to add a new script, which we will title MonitorScript:

Scriptname MonitorScript extends ActiveMagicEffect  

Actor MySelf
Float Health

Event OnEffectStart(Actor akTarget, Actor akCaster)
	MySelf = akTarget
	Health = MySelf.GetActorValue("Health")
	RegisterForSingleUpdate(0.25)
EndEvent

Event OnUpdate()
	Health = MySelf.GetActorValue("Health")
	RegisterForSingleUpdate(0.25)
EndEvent

Event OnEffectFinish(Actor akTarget, Actor akCaster)
	UnregisterForUpdate()
EndEvent

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, \
  bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
	Float Damage = Health - MySelf.GetActorValue("Health")
	Health = MySelf.GetActorValue("Health")
	if (akAggressor == Game.GetPlayer()) && (Damage > 0)
		Debug.Notification("You did " + Damage as Int + " points of damage.")
	endif
EndEvent

Save the script, and click OK to accept all changes. We will come back to this effect to fill its Conditions later.

Create a new spell like before. We'll give this the ID MonitorAbility and a descriptive Name. Change the Effect Archetype to Ability, set Casting Type to Constant Effect and Delivery to Self. In the Effects list, we will add MonitorEffect. Leave Magnitude at 0 and leave the Conditions list blank. Click OK to accept the changes.


Putting It All Together[edit | edit source]

Filling Properties[edit | edit source]

First we need to fill the MonitorAbility property on the ApplyingEffect and its script. Open ApplyingEffect, then open its Properties window and auto-fill the MonitorAbility property, or select MonitorAbility manually from the drop-down menu.

Filling Conditions[edit | edit source]

Now we want to conditionalize some of the effects so that they only get applied when they should. With ApplyingEffect still open, right-click in the Target Conditions list and select 'New'. Our first condition will make it such that it does not attempt to apply MonitorEffect to an Actor if he/she already has the effect:

HasMagicEffect MagicEffect: 'MonitorEffect' == 0

This needs to be run on Subject. Optionally, instead of HasMagicEffect we may use the more general HasMagicEffectKeyword with our own custom Keyword, especially if we want to apply more than one type of Magic Effect to an Actor based on certain conditions. This is covered below in Multiple Conditional Magic Effects.

We may also not want the spell to apply our script to dead Actors, so we should add another condition to the Subject:

GetDead NONE == 0

You can also reorder your condition functions by clicking << or >> with the condition selected. It is more important that the Actor does not have MonitorEffect so we can place this above GetDead. The game should stop condition testing the when the first condition tests false, but it is unknown if it does so, so consider this a minor optimization.

We shall also apply some conditions to the CloakEffect, but we will cover this in the section covering the 'Brawl Bug'

InDepth.jpg Note that by adding a Condition Function to the Magic Effect itself the condition test is only run once -- when the Magic Effect is first applied. To test continuously for a condition, use the Target Conditions list for the Magic Effect on the Spell instead.

Adding the Cloak to the PlayerAlias[edit | edit source]

The only thing left to do is add our CloakAbility to our PlayerAlias. There are two options: We can either add the spell to the Alias Spells list; or if we are interested in Reducing Cloaking Frequency and circumventing the 'Brawl Bug', we can follow the steps outlined below.

Now start the game with the mod loaded and attack someone. The damage dealt by the player should be seen as messages on the upper left corner of the screen.


Optimizations & Bug Fixes[edit | edit source]

In this section we will deal with some of the downsides of this method, and ways of working around them. The most significant of which is the 'Brawl Bug'.

Reducing Cloaking Frequency[edit | edit source]

If your script is like the example shown here, whereby most of the logic is occuring within an OnUpdate() event on each Actor, we can significantly reduce the time spent cloaking surrounding NPCs. In our effect, we are using a cloaking radius of 192.0, which equates to a cell's width on all sides of the player. It takes the player character about 8-10 seconds to sprint from one cell border to another, so we will attempt to run the cloak at least every half cell width, or every 4-5 seconds. So, by the time we run a half cell away, our cloak effect will have already applied the spell to Actors a full cell ahead of the player character.

We can accomplish this by adding the CloakAbility to the player, waiting a short amount of time, and then removing it. To do this, we will need to add a script to our PlayerAlias. Open your quest, and click on the Quest Alias tab. Double-click on our PlayerAlias to open up the Reference Alias window. Add a new script to our alias. We will call it PlayerScript, and fill it with the following code:

Scriptname PlayerScript extends ReferenceAlias  

Spell Property CloakAbility Auto
Actor Property PlayerRef Auto

Event OnInit()
	RegisterForSingleUpdate(1)
EndEvent

Event OnUpdate()
	PlayerRef.AddSpell(CloakAbility, false)
	; How long you would like to keep the cloak active
	Utility.Wait(1)
	PlayerRef.RemoveSpell(CloakAbility)
	; How long until the cloak activates again
	RegisterForSingleUpdate(4)
EndEvent

Now we will save the script and auto-fill the properties. This method of adding and removing the cloak is also used in circumventing the 'Brawl Bug'.

Death Dispel[edit | edit source]

Not using the No Death Dispel flag on effects with scripts can lead to errors.[1] To avoid these, you should handle the cleanup of your magic effect in the magic effect itself. Example:

Scriptname MonitorScript extends ActiveMagicEffect  

Event OnEffectStart(Actor akTarget, Actor akCaster)
	; Any Initialization/Setup code here
	; Go to our Alive state
	GoToState("alive")
EndEvent

State alive
	
	Event OnBeginState()
		RegisterForSingleUpdate(0.25)
	EndEvent

	Event OnUpdate()
		; Regular code here
		RegisterForSingleUpdate(0.25)
	EndEvent

	Event OnDying(Actor akKiller)
		; Any additional cleanup code here
		UnregisterForUpdate()
		GoToState("dead")
	EndEvent

EndState

State dead
; Nothing needed here
EndState


The 'Brawl Bug'[edit | edit source]

The 'Brawl Bug' affects several quests which rely on determining if the player has cast magic on the Actor. This means that our CloakEffect can inadvertently progress various quests to stages which we may not desire. The most prominent example of these undesired effects is during Brawls, where using anything but fists will cause the fight to become "real".

We have already taken one step toward circumventing this issue, and that is by checking No Hit Event on our magic effects. Without this, none of our other changes will matter, and it will also cause the player to receive bounty in addition to causing the fight to turn sour.

First we need to add some Condition Functions to our CloakAbility and CloakEffect. Open our CloakAbility and double click on its magic effect. Adding conditions to the magic effect here will cause the effect to dispel the moment these conditions become false. We should add the following conditions:

GetQuestRunning Quest: 'DGIntimidateQuest' == 0
GetStage Quest: 'C00' != 20

To elaborate, DGIntimidateQuest is the quest that begins running when a Brawl starts, and stops running when it ends. Quest C00 is the main Companions quest line, and specifically we are disallowing it from running during the stage where you must fight Vilkas. Note, this is technically not one of the game-breaking examples of the bug, since Vilkas only needs to be punched three times to end the stage and progress the Companions quest. What this condition does is prevent him from commenting on the player's "magic use".

Unfortunately, there is a minute chance that at the start of a Brawl our CloakEffect will still be active, and not dispel quickly enough. To deal with this, we can also pause our cloak during some of the sub-quests leading up to the Brawl. A non-exhaustive list:

GetQuestRunning Quest: 'Favor017' == 0
GetStage Quest: 'FreeformRiften19' != 30

And if all else fails...

Include an Off Switch[edit | edit source]

In case a user is having issues with the cloaking effects from our mod, we will provide an off switch to allow the user to temporarily cirumvent the issue without uninstalling our mod. We will simply create a new GlobalVariable and wrap our Add/RemoveSpell logic on PlayerScript with it. To create a new global, in the Object Window, navigate to Global under the Miscellaneous category. In the object list, right-click and select 'New' to create a new global variable. We will call this mymod_CloakEffectOn (replace "mymod" with your own prefix) and set the value to 1. We will leave the Variable Type as Short.

Now we will wrap our code in PlayerScript with a condition to run only when this global's value is set to 1:

Scriptname PlayerScript extends ReferenceAlias  

Spell Property CloakAbility Auto
Actor Property PlayerRef Auto
GlobalVariable Property mymod_CloakEffectOn Auto

Event OnInit()
	RegisterForSingleUpdate(1)
EndEvent

Event OnUpdate()
	If mymod_CloakEffectOn.GetValue()
		PlayerRef.AddSpell(CloakAbility, false)
		; How long you would like to keep the cloak active
		Utility.Wait(1)
		PlayerRef.RemoveSpell(CloakAbility)
	EndIf
	; How long until the cloak activates again
	RegisterForSingleUpdate(4)
EndEvent

Don't forget to auto-fill the new GlobalVariable property on our PlayerScript. Users now have the opportunity of pausing the cloak on their own with the console command set mymod_CloakEffectOn to 0.


Multiple Conditional Magic Effects[edit | edit source]

Placeholder. (This section is a WIP)

References[edit | edit source]

  1. See Common Errors.
  2. Brawl Bug Modder's Resource




Attaching Scripts to Objects[edit | edit source]

Unlike with actors, objects cannot run magic effect scripts. To give an object a script at runtime, we need to assign the object a reference alias with a reference alias script. For this tutorial, we will create a mod that gives the player firewood whenever he/she hits a tree with an axe.

Create a FormList[edit | edit source]

First, we're going to create a FormList of trees that we want to give out firewood. We will be using this later to fill the reference aliases we create.

Navigate to FormList under Miscellaneous in the Object Window. Right-click on the list and select 'New' to create a new formlist. Now navigate to Landscape under Tree which is under World Objects in the Object Window. Highlight everything in the list, then click and drag them into the new formlist.

Click OK to create the formlist. Hereafter we will refer to this formlist as TreesList.

Create the First Quest[edit | edit source]

This quest is going to be holding the reference aliases that we will be assigning to objects at runtime. Navigate to Quest under Character in the Object Window. Right-click on the list and select 'New' to create a new quest.

Go to the Quest Alias tab, then right-click on the empty list and choose to create a New Reference Alias. Check the flag for Optional. For Fill Type, choose Find Matching Reference and check the flags for In Loaded Area and Closest. Under Match Conditions, right-click the list and select 'New' to create a new condition:

IsInList 'TreesList' == 1.00

Click OK to close the alias window. Then double-click on the alias in order to open it again. We need to do this step because when we first create an alias, we can't attach a script to it. Attach this script to the reference alias:

Scriptname TreeAliasScript extends ReferenceAlias

Keyword Property WeapTypeBattleAxe Auto
Keyword Property WeapTypeWarAxe Auto
MiscObject Property Firewood01 Auto

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, \
  bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
	if (akAggressor == Game.GetPlayer())
		if (akSource.HasKeyword(WeapTypeBattleAxe) || akSource.HasKeyword(WeapTypeWarAxe))
			akAggressor.AddItem(FireWood01)
		endif
	endif
EndEvent

Set the properties for the script. Since the properties are named exactly the same as the editor IDs of what we want them to represent, just click the Auto-Fill All button to set all the values automatically.

Click OK to close the alias window. Now right-click on the reference alias we just created and select 'Duplicate' to create a duplicate of the alias. You can do this as many times as you like, although five is probably enough for most circumstances.

Click OK to close the quest window. Hereafter we will refer to this quest as DASQuest.

Create the Second Quest[edit | edit source]

This quest is going to start and stop the first quest. We do this so that the game will constantly refill the aliases in the first quest with the closest trees.

Navigate to Quest under Character in the Object Window. Right-click on the list and select 'New' to create a new quest. Click OK to close the quest window, then double-click the quest to open the window again. This step is necessary because if we try to attach a script to a quest right when we create it, the CK crashes (at least in the author's experience).

Attach this script to the quest:

Scriptname UpdateQuestScript extends Quest  

Quest Property DASQuest Auto

Event OnInit()
	RegisterForSingleUpdate(5)
EndEvent

Event OnUpdate()
	DASQuest.Stop()

        ;We must wait until the quest has stopped before restarting the quest. Using Stop() and immediately Start() may fail. 
        ;Continue after 5 seconds to prevent infinite loop.
        int i = 0
        while !DASQuest.IsStopped() && i < 50
            Utility.Wait(0.1)
            i += 1
        endWhile

	DASQuest.Start()
	RegisterForSingleUpdate(5)
EndEvent

Remember to set the value of the 'DASQuest' property to the DASQuest we created earlier. Check to make sure that Start Game Enabled is checked in the Quest Data tab, then click OK to close the quest window.

Conclusion[edit | edit source]

The two quests are all that's necessary for this mod. Save the mod and then load the game with it to test it out. A piece of firewood should be received every time the player hits a tree with an axe.

The method for attaching scripts to objects using reference aliases is much easier than what we did with magic effects and actors. But there are disadvantages to using it:

  1. The number of actors/objects that we can attach the script to is limited to the number of reference aliases we create.
  2. The script does not stay on the actor/object because we need to constantly refill the aliases.

So while this method worked for what we did here, it might not be good for doing something else. For example, what if instead we wanted the trees to only give out firewood after being hit three times instead of once?

That would have been much more complicated because then we would have to store the number of times we hit the tree somewhere. Obviously, we cannot store this variable in the reference alias scripts because the object filling the alias is not always going to be the same tree. We might swing our axe twice but then the alias gets refilled before the third strike, which means it could take us 5 strikes to get a piece of firewood.

Known Issues[edit | edit source]

  • When using the second method with constantly refilling aliases, be careful with your alias scripts. If the alias script happens to be running a while loop when its quest is stopped, then it leads to suspended stacks, in turn leading to suspended scripts.


External Links[edit | edit source]

Porting Kuertee's Sittable Rocks Mod from Oblivion

Dynamically Attaching Scripts to Actors Near the Player

Suspended Stacks Suspends Your Scripts, Sometimes Indefinitely