Jump to content

[LE] Can you help me fill this huge (obvious?) hole in my Papyrus knowledge?


Recommended Posts

I have a macro-level problem or big hole in my understanding of scripting. I don't want a specific problem solved, but I would appreciate someone telling me how or where I can learn the correct contextual use of various Papyrus elements.
Boiled down, the issue is that I don't understand in which contexts various language elements are valid and in which they are not. I have several working scripts, but it was hell getting some of them to work, because I just kept trying various implementations in various contexts until something worked. Not a smart way to code, obviously.
That may be too vague, so here's a quick example scenario to highlight my conceptual failure:
Say I want to create an enchanted item that makes a follower essential as long as he is wearing it, but vulnerable or protected (whatever his default quest-alias or base state is) when it is not equipped.
I go to the language reference and search for "essential." I find the SetEssential() function and see that it is a member of the Actor Base script. Great! But where and how can I use SetEssential? I try to put it in a script, but no go. I try to add it as a script fragment in a quest stage. No go. I keep trying various forms and look for the correct property to add to the script, but I can't find this right combination and/or context.
Note: I have several working scripts, including ones that use multiple properties, so I understand the need to connect built-in game functions so I can access and those functions and "extend" them with my code. Sometimes it is clear where and how to use some functions, but for functions such as SetEssential and many others, I'm lost and just start hammering away until I drive a nail or hit my thumb so hard I quit for a while. :smile:
In my example, I might try to put SetEssential...
In a Payrus fragment. Look for some sort of Actor property and code aKspeaker.setessential(1))
In a quest stage function. Look for the right property to add and use Alias_NPCAlias.SetEssential(1)
In a script attached to an object or quest :
Event OnTriggerEnter(ObjectReference akActionRef)
If akActionRef == NPCREF
NPCBase.SetEssential()
endif
blah blah blah
EndEvent
And I might find that one or none of them work. How do I avoid this crap shoot? What do I need to study?
Thank you so much for reading and thank you in advance for schooling me!
Edited by Eldurin
Link to comment
Share on other sites

Papyrus is an event-driven language. That means every scripting task can be viewed in the form "When ... happens do ..." and both parts need to be considered separately.

 

The "when ... happens" part gets represented by either one of those fragment boxes in the dialogue, quest, scene, or perk sections of the Creation Kit or in an Event in some script attached to some game object. The fragments run when certain dialogue lines are spoken, when quest or scene stages change, or when perk entry points are checked. In every other case you need to find a particular Event you can attach to an appropriate object in the game. Sometimes you have more than one option and it's not clear which one is better. Sometimes finding an event that will fire when you need to take action is very hard (which is why people fall back to the OnUpdate event).

 

So in your example, you have two reasonable choices on where to attach the script. Since the item is enchanted and enchantment effects start and stop when the item is equipped and unequipped you can use a scripted magic effect extending the ActiveMagicEffect and use the OnEffectStart and OnEffectFinish events to hold your code. The other option is to attach a script extending ObjectReference directly to the item itself and use the OnEquipped and OnUnequipped events. Using the ActiveMagicEffect script version would potentially allow the player to enchant new items. While using the ObjectReference script could allow smithing the appropriate items.

 

Important note: If your goal is to detect some specific action of the player or some other thing that's part of the original game it's a bad idea to add a script directly. Instead you would create a quest and an alias. The ReferenceAlias scripts inherit any events that are normally sent to the thing filling the alias which means they also get the Actor and ObjectReference events.

 

For the "do ..." part you have to choose which functions you need and those have to be called against the right object in the game. Once you've found the correct event (or events) you generally have an argument you might be able to use. For example the OnEquipped and OnUnequipped events have an akActor argument. If the event doesn't have the argument you need you'll have to add a property to the script and fill it. Generally speaking you don't want properties pointing to ObjectReference or Actor objects and that's why those are the most common type of arguments available through the events themselves.

Link to comment
Share on other sites

I think cdcooley has given you a good overview and perspective, additional I would like to make it a bit more practical.

Your first target to learn more about Skyrim scripting, should be:
http://www.creationkit.com/index.php?title=Category:Scripting

Keep in mind: A Skyrim actor may have the protected flag or the essential flag!

; SetProtected() native
http://www.creationkit.com/index.php?title=SetProtected_-_ActorBase
Sets or clears this actor's protected flag.

; SetEssential() native
http://www.creationkit.com/index.php?title=SetEssential_-_ActorBase
Sets or clears this actor's essential flag.

; IsEssential() native
http://www.creationkit.com/index.php?title=IsEssential_-_ActorBase


(0) base function to make an actor essential depending on the protect flag

 FUNCTION MakeEssential(Actor aRef, Bool bSET=TRUE)
;--------------------------------------------------
 IF ( !aRef )                                       ; IF (aRef == None)
   RETURN    ; - STOP - aRef is invalid!
 ENDIF

;;; actorBase AB = aRef.GetBaseObject() as ActorBase
    actorBase AB = aRef.GetActorBase()

 IF ( bSET )                                        ; IF     (bSET == TRUE)
   IF !AB.IsProtected()
     AB.SetEssential(TRUE)
   ENDIF
 ELSE                                               ; ELSEIF (bSET == False)
   IF AB.IsEssential()
     AB.SetEssential(False)
   ENDIF  
 ENDIF
 ENDFUNCTION

(1) ReferenceAlias

 

 ReferenceAlias PROPERTY myAlias auto
 
 FUNCTION TEST_1a()
   actor aRef = myAlias.GetActorReference()
   MakeEssential(aRef)
 ENDFUNCTION

 FUNCTION TEST_1b(ReferenceAlias RA)
   IF ( !RA )
     RETURN    ; - STOP - RA is <None>
   ENDIF
  MakeEssential( RA.GetActorReference() )
 ENDFUNCTION

 


(2) ObjectReference

 

ObjectReference PROPERTY myREF auto
 
 FUNCTION TEST_2a()
   actor aRef = myREF as Actor
   MakeEssential(aRef)
 ENDFUNCTION

 FUNCTION TEST_2b(ObjectReference oRef)
   IF ( oRef )
     MakeEssential(oRef as Actor)
   ENDIF
 ENDFUNCTION

 EVENT OnTriggerEnter(ObjectReference triggerRef)  
   IF (triggerRef == myREF)
;;;  MakeEssential(triggerRef as Actor)
     MakeEssential(myREF as Actor)               ; set essential flag
   ENDIF
 ENDEVENT

 EVENT OnTriggerLeave(ObjectReference triggerRef)  
   IF (triggerRef == myREF)
;;;  MakeEssential(triggerRef as Actor, False)
     MakeEssential(myREF as Actor, False)        ; remove essential flag
   ENDIF
 ENDEVENT

 


(3) Actor

 

 Actor PROPERTY myActor auto
 
 FUNCTION TEST_3a()
   MakeEssential(myActor)          ; actor is taken from script property
 ENDFUNCTION

 FUNCTION TEST_3b(Actor aRef)
   IF ( aRef )
     MakeEssential(aRef)           ; actor is taken from function variable
   ENDIF
 ENDFUNCTION

 

 

Edited by ReDragon2013
Link to comment
Share on other sites

You commented out GetBaseObject() as Actorbase when it is much faster than GetActorBase() (GetActorBase calls GetBaseObject and is then cast to actorbase..)

 

There's nothing wrong with convenience functions, but overuse of them can slow down a script.

Edited by TheDungeonDweller
Link to comment
Share on other sites

You commented out GetBaseObject() as Actorbase when it is much faster than GetActorBase() (GetActorBase calls GetBaseObject and is then cast to actorbase..)

 

There's nothing wrong with convenience functions, but overuse of them can slow down a script.

 

They'd need to cast to ActorBase anyway in order to use SetEssential, but SetEssential isn't the best tool here because it does operate on ActorBases (which is fine for unique characters, but not for, say, bandits) and trying to use script-driven checks for protected/essential status can cause problems; even if they're reported accurately to begin with, if that status were to be changed (a quest completed, becoming a follower, etc) those changes won't be properly reflected.

 

 

 

@Eldurin:

There's two things to understand about Papyrus:

 

1. It's event-driven, as cdcooley explained; your entry points into the game are the game's natively generated events. There's a list here: https://www.creationkit.com/index.php?title=Events

2. It's object-oriented. Every event and every function is associated with some particular script objects, which are arranged in a hierarchy. The majority are descendants of the Form object; the major exceptions are Aliases and their descendants and ActiveMagicEffects. There's a reference here: https://www.creationkit.com/index.php?title=Script_Objects

 

Understanding the script object tree, what objects receive what events, and what functions you can call where, is critical to navigating Papyrus. It's very important to know that any particular instance of anything in the world is an ObjectReference, not an ActorBase or Hazard or whatever; to get access to those you need to use GetBaseObject and a cast, and if you then use any Set operations on the base forms, you'll be changing all instances of it in the world.

 

 

Concerning your specific desire, my solution would be to:

 

1. Make a quest with a bunch of reference aliases to accommodate more than one actor wearing the item simultaneously. Have the quest run on startup, have the aliases be optional, and have them ticked to set whatever fills them to be essential.

2. Create a magic effect with a script that has paired OnEffectStart and OnEffectEnd events. OnEffectStart, loop through the quests' aliases until you find an empty one, then fill the alias with the target of the effect. Store that alias in a script variable initialized in the header. OnEffectEnd, clear the alias you stored in the variable.

3. Add the magic effect to an enchantment.

4. Add the enchantment to your item.

 

This will throw the burden of evaluating the actor's essential/protected status onto the part of the game engine that does so in the ordinary case and avoids messing with base objects when you only really want to be changing individual ObjectReferences in the world.

Link to comment
Share on other sites

  • Recently Browsing   0 members

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