Dynamically Attaching Scripts: Difference between revisions
Jump to navigation
Jump to search
Rewrite, Included images, extra info
imported>Fg109 |
imported>Jonwd7 (Rewrite, Included images, extra info) |
||
| 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== | |||
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. | |||
===Abstract=== | |||
[[File:DASOverview.png|thumb|250px|alt=Overview|Overview]] | |||
This method consists of: | |||
# 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. | |||
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 checks. Lastly, we will cover how to deal with issues like the [[#The_.27Brawl_Bug.27|''''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 === | ||
[[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. | |||
=== Create the Cloak Ability === | |||
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 category. Create 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>. | |||
<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. | |||
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. | |||
= | <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> | |||
=== Create the Applying Spell and Effect === | |||
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_-_Actor|AddSpell()]]. | |||
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 category. Create 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 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 give this the ID <code>'''ApplyingEffect'''</code> 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 <code>'''ApplyingScript'''</code>: | |||
<source lang="papyrus">Scriptname | <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. | |||
==== 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 64: | Line 125: | ||
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. | |||
Create a new spell like before. We'll give this the ID <code>'''MonitorAbility'''</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>'''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> | |||
=== Putting It All Together === | |||
==== Filling Properties ==== | |||
First we need to fill the <code>'''MonitorAbility'''</code> property on the <code>'''ApplyingEffect'''</code> and its script. Open <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. | |||
==== Filling Conditions ==== | |||
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: | |||
[[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|'''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''''. | |||
Spell Property | ==== 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) | ||
; 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 | |||
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 | |||
</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>. | |||
=== Multiple Conditional Magic Effects === | |||
Placeholder. (This section is a WIP) | |||
==References== | |||
# 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 modder's resource | |||