Jump to content

Specific Scripting Help


LordSerathDarklands

Recommended Posts

Thank you, I must say it does indeed look pretty neat - all thanks to the magnificent OBSE, for without it, the solution would probably be a nightmare. :happy: Now the only thing that remains to be seen is whether it helps LordSerathDarklands.

 

Edit: Whoops. Posted without refreshing the page. Sorry. Just a minute...

 

Edit 2: Okay, so script blocks: in Oblivion, scripts are placed in these "blocks", and each block is only run under certain circumstances. You can have multiple script blocks in one script, and so you can have different script blocks run under different circumstances on one object. One object, in Oblivion, can only have one script attached to it (unlike Skyrim, if you are familiar with Papyrus, I have no idea...). Script blocks, in general are like this:

Begin <name>
    <contents>
End

And the game runs the contents of the block when the block should run. Here are some of the common script blocks: http://cs.elderscrolls.com/index.php?title=Category:Blocktypes

  • GameMode - runs constantly when not in a menu, if attached to an object like activator or an NPC, runs each frame, if attached to a quest, runs every time the quest updates (default 5 seconds)
  • MenuMode <menucode> - same as GameMode, but when a menu is open, and if <menucode> is defined, only runs when that specific menu is open, otherwise runs when any menu is open
  • OnActivate <ref> - runs once each time an object is activated, if <ref> is supplied, only runs when that <ref> has activated the target, otherwise runs each time something activates the target, if OnActivate is attached on an actor, it will prevent interaction with that actor, and only the OnActivate block is run
  • OnDeath - runs once after an actor the script is attached to dies
  • OnEquip/Unequip - runs once when an item the script is attached to is equipped/unequipped, such as weapons and armour, also misc items and all that
  • ScriptEffectStart - runs once when the script effect starts
  • ScriptEffectFinish - runs once when the script effect finishes
  • ScriptEffectUpdate - like GameMode, runs every frame, for as long as the effect is active

There are more behind the link I gave, but those ones are the most common. To maybe try and explain the solution I gave a little... there are two scripts: one quest script, and one magic effect script. The idea would be this:

  1. the magic effect plays some effect on the target, in ScriptEffectStart block, that is run when the effect first starts
  2. in the ScriptEffectFinish block (that runs when the effect finishes), the magic effect checks if the list (array) on the quest script has been initilaised, and if not, initialises (cannot add stuff to an inexistent list, right?)
  3. then it makes a sort of... umm... package of two <key>:<value> pairs, with the reference the magic effect runs on stored with the "ref" key ("ref"::GetSelf) and the duration, in seconds, for which it should be kept disabled in the "time" key ("time"::5.0)
  4. the effect script adds that package of <key>:<value> pairs to an array (a sort of list in this case) located in a quest (the script of that quest)
  5. the effect script disables the object it is run on (which stops script processing, and that is why it is done last)
  6. the quest script, in its GameMode block (that runs when not in MenuMode, and in this case, runs every fQuestDelayTime seconds), checks the list, and for each package of ref and time, it decreases the "time" value stored by fQuestDelayTime, which, in this case, is the time passed (unless you set fQuestDelayTime to less than 1.0/FPS when the checks would not be made every fQuestDelayTime, but only every frame, but you probably won't, so that is not too relevant, just thought about mentioning it)
  7. if the (now decreased) "time" value is less than 0, the original time (+1 second, in this case, so actually 6 seconds, whoops) has passed, and that means the reference needs to be enabled again, and so the quest script enables the reference in the "ref" key
  8. then, the quest script removes that specific package of <key>:<value> pairs from the array, because it is no longer needed

Because the packages are stored in a list, the system can handle lots of references at once, with no limits (at least not ones you would reach during normal play). With comments, the scripts, the Quest Script first:

float fQuestDelayTime ; because this is attached to a quest, the GameMode block runs every fQuestDelayTime seconds
Array_var aRefs       ; this is the list that holds all packages of <key>:<value> pairs
int iTemp             ; temporary integer for traversing the list
ref rTemp             ; temporary reference because Enable apparently needs a real ref variable and not an array element

Begin _GameMode ; this block runs every fQuestDelayTime seconds, when not in menumode

    let iTemp := ar_Size aRefs  ; the size of the list, as an index, this is the last element + 1 (indexing starts at 0)
    let fQuestDelayTime := 1.0  ; to make sure it runs every one second

    While ( iTemp > 0 ) ; for as long as we have not reached the beginning of the list...
        let iTemp -= 1  ; take one step closer to the beginning (travelling backwards, that is)
        let aRefs[iTemp]["time"] -= fQuestDelayTime ; decrease the "time" the ref needs to remain disabled
        If Eval ( aRefs[iTemp]["time"] < 0 ) ; if the ref has already been disabled for the necessary time...
            let rTemp := aRefs[iTemp]["ref"] ; get the ref (for use in Enable, straight usage of the array element refused to compile)
            rTemp.Enable ; enable the ref
            rTemp.PlayMagicShaderVisuals effectEnchantAlteration 0.5 ; tiny addition, to play visuals!
            ar_Erase aRefs iTemp ; remove the now-unnecessary package from the list
        EndIf
    Loop ; return to While, and evaluate whether to run again (on the next element of list)

End

And the magic effect script:

Begin _ScriptEffectStart ; this block runs once, when the effect starts

    If Eval ( IsDoor ) || ( IsActivator ) ; only play effects if the target is either door or an activator
        PlayMagicShaderVisuals effectEnchantAlteration 0.5 ; plays nice flashing effect
        ; having duration of something like > 1 will allow for the effect to finish
    EndIf

End

Begin _ScriptEffectFinish ; runs once when the effect finishes

    ; these are the scenarios when we do NOT want to affect the target in any way (because it is irrelevant):
    ; - the target is neither a door nor an activator (some dungeon gates, the ones that go up and down, are activators)
    ; - the target has an enable parent, in which case we cannot disable it
    ; - the target is a load door (makes no sense to hide it, then, right, and make a hole in the wall)
    If Eval ( !( IsDoor ) && !( IsActivator ) ) || ( GetParentRef ) || ( IsLoadDoor )
        Message "The target resisted the spell!"
        Return
    EndIf

    If Eval ( YourQuestID.aRefs == ar_Null ) ; if the list has not been initialised, initialise it
        let YourQuestID.aRefs := ar_Construct "Array"
    EndIf

    ; ar_Map creates a package of two <key>:<value> pairs
    ; ar_Append adds an element to an array, and in this case, the new package is created and added to the array/list
    ar_Append YourQuestID.aRefs ( ar_Map "ref"::GetSelf "time"::5.0 )

    Disable ; this is at the bottom, because it ends script processing
    
End

Also, to make it easier to use OBSE commands in a more sensible way, I have used the compiler override. For example, if NOT using compiler override, the adding of a new "package" would look like this:

Array_var aTemp

...

Begin MagicEffectFinish

    ...

    let aTemp := ar_Construct "StringMap"
    let aTemp["ref"] := GetSelf
    let aTemp["time"] := 5.0
    ar_Append YourQuestID.aRefs aTemp

    ...

End

Whereas, with OBSE/CSE compiler override, it is possible to do exactly the same, with only one line of code and one less variable:

...

Begin _ScriptEffectFinish

    ...

   ar_Append YourQuestID.aRefs ( ar_Map "ref"::GetSelf "time"::5.0 )

   ...

End

Where the compiler override, in CSE at least, is applied by typing an underscore before the block name. When this is the normal version:

Begin ScriptEffectStart

This, in turn, is the version that gets compiler override applied to it:

Begin _ScriptEffectStart

However, compiler override will mean you need to make sure parentheses are correctly placed and plenty. Other than that, using compiler override is a godsend, as is the whole OBSE. :happy:

 

Hopefully that helps a little. Maybe it indeed is a bit... um... not too basic. Sorry. :blush:

 

You can check the Wiki, it should have something to get you started: http://cs.elderscrolls.com/index.php?title=Portal:Scripting

Edited by Contrathetix
Link to comment
Share on other sites

Hmm, I mysteriously wasn't aware of that compiler override feature, although I heard of it before, and wondered why you used those underscores before the block names all the time. Guess you never learn out even after 10+ years of doing this stuff, eh? :happy:

 

One slight improvement, if I may. Why don't you use "GetSecondsPassed" for the subtraction instead of subtracting the "fQuestDelayTime" from the timer, which is only a guideline rather than any margin that's exactly met anyways, so the chance for 1 second being subtracted after 1 second passed is almost "0" in reality, whereas GetSecondsPassed will always return the exact amount of seconds passed since the last run of the block, no matter if there were "fQuestDalyTime + 0.5" seconds between the two or "fQuestDelayTime - 0.8" or whatever? Or was it the function call overhead speaking against it?

Link to comment
Share on other sites

Indeed, I only learned about it a while (a few months I think) ago. There is always something hiding in plain sight, and I have a feeling I have not yet found everything, either. And this is just scripting, AI seems to be quite the mystery to me, still, as are meshes, animations and all that. Constant sense of something new lurking just around the corner is what (partially?) keeps this interesting for me, at least. Great to be of help.

 

I just thought it would be more handy, considering how GetSecondsPassed seems to return the amount only once in each "run", and so it needs to be stored in a variable if used multiple times in the same "run". But, in this case, when the quest script would only update approximately once every second, I thought it would not matter that much. No idea about the function call overhead. It really is a guideline? That is something new, I thought Oblivion scripts would run pretty close to the intended interval, since apparently framerate is the first to get sacrificed when script load jumps up. I know Papyrus in Skyrim likes to put all scripts in a queue, so that when told to update every 0.5 seconds, the script can update every 0.5, 1.0, 2.0, 20.0 or so seconds if there is a lot to process. But Oblivion scripts not being on time? New for me, veeeeery interesting. I need to think about that. Thank you. If that is the case, GetSecondsPassed with the additional temp variable might be better indeed. :sweat:

Link to comment
Share on other sites

  • Recently Browsing   0 members

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