Script Compiler Override

From GECK
Jump to: navigation, search

Attention: Script Compiler Override has been deprecated in favour of Inline Expressions which were introduced in xNVSE 6.1. Inline expressions are more robust and behave in a more predictable way than compiler override, while offering all the same benefits.


Overview

If using Let, if eval, While, Print and any other NVSE-Aware Function, it is permitted to pass array elements, string variables and (perhaps user defined) nested function calls directly as parameters. However, the vanilla script compiler will prohibit direct passage to vanilla and older NVSE functions, complaining that the paramater type is invalid (it isn't sure what type the array element or function return will be, since it could potentially be any). Typically, this forces you to use an intermediary variable. Alternatively, the script compiler override (CO) allows you to ignore these 'misgivings', and assuming that you know the types are safe, compile scripts that will work correctly.

To use the CO, you prefix a script block type with an underscore (eg: Begin _GameMode). See the following examples for further details, and also the warning at the end of this article.

Example #1

Let's say we have a number that we want to get the Floor of, the nearest whole number lower than the float. The vanilla compiler will know what we want to do if we specify it as a float variable that we pass to the floor function as a parameter:

let iSomeInt := floor fSomeFloat

It will not understand what we want though if we tell it to get the float from an array element or from a UDF call:

let iSomeInt := floor somearray[someIndex]
let iSomeInt := floor call someUDF

Will not compile. The vanilla compiler can only accept float variables as parameters to the floor function, and has no idea what's waiting in that array or that UDF at compile time anyway. Could be anything, and nuh-uh, it ain't standing for it. To get around that, you have to switch over the script block to use the NVSE compiler instead, which you do by prepending the block name with an underscore:

Begin _Gamemode ; * or _Menumode, _ScriptEffectStart, _OnActivate, _Function, etc 
    let someInt := floor somearray[somekey]
    let someInt := floor (call someFunction)
End

and then you can go about your business, getting that floor, you badass. You’re basically telling the compiler to take it on faith that it’s gonna be fine, you know that there’s a float waiting under that key in that array, or that a float is bound to come back from that UDF. You’re taking over some of the responsibility from the compiler, in exchange for more freedom. In turn, you’ll need to keep your head, not fuck up, test stuff in-game instead of relying on error reports in the geck, and use parentheses a little more to make sure the compiler doesn’t neatly compile something that can’t possibly work in-game.


Example #2

Let’s say we want to check a bunch of different SPECIAL actor values on an actor and give them a bump if they’re under 5, then usually that’d mean:

ref rActor

Begin OnActivate

    if rActor.GetAV Charisma < 5
       rActor.SetAV Charisma 5
    endif
    if rActor.GetAV Intelligence < 5
       rActor.SetAV Intelligence 5
    endif
    
...yawn... I'm not gonna bother with the other ones. Huge waste of script space, bound to give you RSI

End

Now, you should know that the parameters Charisma, Intelligence, etc are each in fact a string, a combination of characters. To shorten the chore of checking and setting 7 actor values, we can stick them in an array, retrieve them in a Foreach loop and stick them in a string var, and just check them like that:

ref rActor
array_var entry
array_var somearray
string_var somestringvar

Begin _OnActivate

    let somearray := Ar_List "Agility", "Charisma", "Endurance", "Intelligence", "Luck", "Perception", "Strength"

    foreach entry <- somearray
        let somestringvar := entry[value]
        if eval 5 > rActor.GetAV somestringvar
            rActor.SetAV somestringvar 5
        endif
    loop
End

but, well, that's just a roundabout way of showing the override will allow a string_var rather than the actual string for the actor value. Strictly speaking we can shorten that even more:

ref rActor
array_var somearray
array_var entry

Begin _OnActivate

    let somearray := ar_list "Agility", "Charisma", "Endurance", "Intelligence", "Luck", "Perception", "Strength"
    
    foreach entry <- somearray
        if eval 5 > rActor.GetAV entry[value]
            rActor.SetAV entry[value] 5
        endif
    loop
End

Example #3

Just the example given in the OBSE docs, which if you understood the previous ones, you should be able to get now:

string_var axis
float pos
array_var somearray

Begin GameMode

    let axis := somearray[index]  ; the axis paramater for the setpos function is held in an array as a string that can be "x", "y" or "z"

    let pos := (call someFunction) + someRef.GetPos z  ; the setpos function will only accept a float for the position parameter so that needs to be calculated first

    if eval axis == "z"
        setpos z pos
    elseif eval axis == "y"
        setpos y pos
    elseif eval axis == "x"
        setpos x pos
    endif
End

becomes the epically short:

Begin _GameMode
    setPos someArray[index], (call SomeFunction)  + someRef.getPos z
End


An added advantage of using the script compiler override is that instead of

set fHealth to rActor.GetAV Health  ; hey, hey or: let fHealth := rActor.GetAV somestringvar/somearrayelement, remember?

You can refer to the actor value by its code number:

let fHealth := rActor.GetAV 16

Why is this an advantage? Well, you can do stuff with numbers that you can’t with strings like "Health". Like sticking them in an int:

let fHealth := rActor.GetAV iSomeInt

So that you could pretty much loop through a bunch of actor values with a While and math and assign loop too, for instance:

int iSomeInt

Begin _ScriptEffectStart

    let iSomeInt := 24
    while (iSomeInt += 1) < 32 ; loops through 25-31, the AV codes for your body parts’ condition
        if eval 100 > GetAV iSomeInt
            SetAV iSomeInt 100
        endif
    loop
End

in case you want to make some "Doc's Super Duper Doctor's Bag" or "Instant Heal Button" something ;)

Example #4

Maybe you got tired of trying to remember all those damn magic numbers for type codes, equipment locations, etc? In your main quest:

let Locations := Ar_Map "Upper Body"::2, "Weapon"::5, "Hat"::10 ...
let TypeCodes := Ar_Map "Armor"::24, "Weapon"::40, "Alchemy"::47 ...

In some script:

array_var loc
Begin _GameMode
    let loc := MyMainQuest.Locations ; * aside: this is a reference not wasteful copy ;)

    let rItem := PlayerREF.GetEquippedObject loc["Upper Body"]
End

A Warning About '1-Lining'

It is easy to get carried away with the compiler override, especially if you have experience of real programming languages, since it is now possible to do things that used to take 10 or more lines of scripting in a single line. For example, suppose you wanted to pick a random weapon from a form list and give it to the player in a random condition, without the compiler override:

int iCount
int iChoice
float fCondition
ref rItem

Begin GameMode ; or whatever
   let iCount := ListGetCount SomeFormList
   let iChoice := Rand 0, iCount
   let rItem := ListGetNthForm SomeFormList, iChoice
   let fCondition := Rand 0, 1
   PlayerREF.AddItemHealthPercent rItem, 1, fCondition
End

With the CO, you can do this as a 1-liner with no variable declaration:

Begin _GameMode
    PlayerREF.AddItemHealthPercent (ListGetNthForm SomeFormList, (Rand 0, (ListGetCount SomeFormList))), 1, (Rand 0, 1)
End

'1-liners' full of nested function calls like this are logically correct and may work fine in game. Sometimes they won't, possibly instead giving a CTD. It may be prudent (and probably more readable) to replace excessive literal '1-liners' with a UDF call.

Unforeseen Consequences

With the Compiler Override mode on, certain expressions will be evaluated differently than one might expect. For example:

if Sin 14 == 1

evaluates "14 == 1" first, leaving:

if Sin 0

Great care must be taken to ensure that one's script will behave as expected, and some habits may have to be unlearned. Wrapping statements in parentheses, like doing "(Sin 14) == 1", can help alleviate these issues, as it will force the script to run in the indented order.

See Also

External Links