imported>JustTim |
imported>JustTim |
Line 1: |
Line 1: |
| I've tried creating a custom "function" that can be called by other scripts. The basic approach I took was to make the function a quest script, and have the calling script call the function via |StartQuest function_quest|. Ex:
| | Writing complex scripts for oblivion can be very painful and frustrating. Not being able to write reusable functions that execute directly on call instead of the next frame is one of the major problems (at least for me). |
|
| |
|
| I have a quest called testfunc that is initially disabled and contains the following script:
| | But Kkuhlmann from Bethesda had a great idea to make a workaround: Quest Stages! |
| | When you call setStage in a script, the related stage result script will be executed immediately BEFORE the current script continues! This is therefore a great way to write immediately executing function calls! |
|
| |
|
| <pre>scn testfuncscript
| | I've spent MANY hours to advance this idea to a fully reusable function framework that can easily be used in your scripts. Here is what i came up with: |
|
| |
|
| float param1
| |
| float param2
| |
| float result
| |
|
| |
|
| begin GameMode
| | Create a Quest just called "f" (for "function"), ACTIVATE "ALLOW REPEATED STAGES" and attach the following quest script: |
| Message "start func", 1
| |
| set result to param1 + param2
| |
| Message "end func: %.0f", result, 1
| |
| StopQuest testfunc
| |
| end</pre>
| |
| | |
| In another script, I call this "function":
| |
| | |
| <pre>scn testscript
| |
| | |
| float r
| |
| | |
| begin OnActivate
| |
| Message "before call", 1
| |
| set testfunc.param1 to 10
| |
| set testfunc.param2 to 20
| |
| StartQuest testfunc
| |
| set r to testfunc.result
| |
| Message "after call: %.0f", r, 1
| |
| end
| |
| </pre>
| |
| | |
| However, the above code doesn't work. The order of the output illustrates why:
| |
| | |
| <pre>before call
| |
| after call: 0
| |
| start func
| |
| end func: 30</pre>
| |
| | |
| This is what it should be:
| |
| | |
| <pre>before call
| |
| start func
| |
| end func: 30
| |
| after call: 30</pre>
| |
| | |
| The problem is that the calling script doesn't wait for the function script to execute, and instead the function script is delayed until after the calling script finishes executing.
| |
| | |
| So is there any way that can bypass this problem without resorting to nasty "wait for the other script to finish" hacks? (I could make the calling script have a GameMode block that would repeatly check when the function finishes executing, but that would be a serious PITA.) I suspect this same problem would've arisen if I had tried creating a function script by attaching it to a persistent object instead of a quest (and calling it via the Activate function).
| |
| | |
| --[[User:Maian|Maian]] 03:59, 18 April 2006 (EDT)
| |
| | |
| | |
| I'm just kind of brainstorming here, but you might try making a quest, marked "Allow repeated stages" so you can call the stages repeatedly, and make (say) stage 10 be your "function". Quest stage results are executed immediately, while the current script is processing, so you wouldn't have to wait for the results.
| |
| | |
| Keep your variables in your quest script. In stage 10 results, you'd have:
| |
| | |
| <pre>
| |
| Message "start func", 1
| |
| set testfunc.result to testfunc.param1 + testfunc.param2
| |
| Message "end func: %.0f", testfunc.result, 1
| |
| </pre>
| |
| | |
| To call this function, you'd do the following:
| |
| | |
| | |
| <pre>scn testscript
| |
| | |
| float r
| |
| | |
| begin OnActivate
| |
| Message "before call", 1
| |
| set testfunc.param1 to 10
| |
| set testfunc.param2 to 20
| |
| setstage testfunc 10 ; call the "function"
| |
| set r to testfunc.result
| |
| Message "after call: %.0f", r, 1
| |
| end
| |
| </pre>
| |
| | |
| I think this should work (I haven't tried it).
| |
| | |
| --[[User:Kkuhlmann|Kkuhlmann]] 14:02, 18 April 2006 (EDT)
| |
| | |
| I've been doing this for awhile now with OnActivate functions placed on activators. The reference you use to activate it acts as a parameter to tell it how to act. Then when that function is done it activates the calling object to let it know that it is finished and it is time to process the results.
| |
| | |
| So for instance
| |
| | |
| <pre>scn ActivateFunction
| |
| ref incoming
| |
| | |
| begin OnActivate
| |
| set incoming to GetActionRef
| |
| if (incoming == CallingScriptRef.Me)
| |
| set CallingScriptRef.Value to CallingScriptRef.Value + 3
| |
| CallingScriptRef.Activate CallingScriptRef.ActivateFunctionRef 1
| |
| return
| |
| endif
| |
| end</pre>
| |
| | |
| <pre>scn CallingScript
| |
| ref Me
| |
| ref ActivateFunctionRef
| |
| long Value
| |
| ref incoming
| |
| | |
| begin OnLoad
| |
| set Me to GetSelf
| |
| set Value to 0
| |
| Message "I am storing a value of %.0f", Value, 1
| |
| end
| |
| | |
| begin OnActivate
| |
| set incoming to GetActionRef
| |
| if (incoming == ActivateFunctionRef)
| |
| Message "Now I am storing a value of %.0f", Value, 1
| |
| return
| |
| endif
| |
| ActivateFunctionRef.Activate Me 1
| |
| end
| |
| </pre>
| |
| | |
| This removes the need for a game mode block and allows function passing (or setting in a persistent reference). I've used this quite successfully for some cool stuff I'm finishing the testing on.
| |
| --[[User:Tegid|Tegid]] 14:44, 18 April 2006 (EDT)
| |
| | |
| After some experimentation, I've verified that making the function script an object script and attaching it to a dummy object (in activator called testfunc in my case), it works. That is, it satisfies the following conditions:
| |
| #It can be called (via |testfunc.Activate player, 1|).
| |
| #The function is executed immediately rather than after the script that called it.
| |
| #It can be called even in places where the dummy object isn't at, e.g. I placed the dummy object inside an interior cell and called it in an exterior cell. This was what I was most worried about with this approach - that the function would only work if the dummy object is in the same area. I haven't tested this extensively, so it may be the case that it works only because the dummy object is still in memory, but I suspect that's not the case.
| |
| | |
| Tegid, in your example, the called function apparently needs a reference to the script calling it, which defeats the purpose of making the function generic (after all, the main reason for creating a new function is to share code between 2+ functions). Maybe if your script just checked if incoming.Me was set and just used incoming rather than CallingScriptRef, it could work generically.
| |
| | |
| Kkuhlmann, I haven't tried your method yet, but I'm post an update once I do so.
| |
| | |
| Thanks for all the help! --[[User:Maian|Maian]] 21:49, 18 April 2006 (EDT)
| |
| | |
| Kkuhlmann's solution also worked.
| |
| | |
| For reference, here's my dummy object + activate script solution:
| |
| | |
| On a dummy object:
| |
| | |
| <pre>scn testfuncobjscript
| |
| | |
| float param1
| |
| float param2
| |
| float result
| |
| | |
| begin OnActivate
| |
| if IsActionRef Player != 1
| |
| Message "start func", 1
| |
| set result to param1 + param2
| |
| Message "end func: %.0f", result, 1
| |
| else
| |
| Activate
| |
| endif
| |
| end</pre>
| |
| | |
| Calling script:
| |
| | |
| <pre>scn testobjscript
| |
| | |
| float r
| |
| ref self
| |
| | |
| begin Onload
| |
| set self to GetSelf
| |
| end
| |
| | |
| begin OnActivate
| |
| Message "before call", 1
| |
| set testfuncref.param1 to 10
| |
| set testfuncref.param2 to 20
| |
| testfuncref.Activate self, 1
| |
| set r to testfuncref.result
| |
| Message "after call: %.0f", r, 1
| |
| end</pre>
| |
| | |
| --[[User:Maian|Maian]] 01:47, 19 April 2006 (EDT)
| |
| | |
| The CallingScriptRef is just your single point of entry into the set of scripted functions. It is a persistent reference Activator like your testfuncref. Also, it has not been my experience that Activate calls interrupt the flow of script parsing. I was under the impression that the Activate call really just sets a bit so that when that script is reached, the OnActivate block is executed.
| |
| --[[User:Tegid|Tegid]] 11:07, 19 April 2006 (EDT)
| |
| | |
| | |
| I love the solution kkuhlmann has written! This is by far the best idea for immediately executing function calls!
| |
| Now imagine this:
| |
| | |
| Create a Quest just called "f" (for "function") with the following script:
| |
| <pre>ScriptName FunctionQuestScript | | <pre>ScriptName FunctionQuestScript |
|
| |
|
Line 227: |
Line 47: |
| ; Set constants first | | ; Set constants first |
| Begin Gamemode | | Begin Gamemode |
| if doOnce == 0
| | if doOnce == 0 |
| set rad to 57.2957792
| | set rad to 57.2957792 |
| set pi to 3.1415927
| | set pi to 3.1415927 |
| set doOnce to 1
| | set doOnce to 1 |
| endif
| | endif |
| ;StopQuest f
| |
| End</pre> | | End</pre> |
| This is a generalized function framework that can be re-used for as many functions as you like. | | This is a generalized function framework that can be re-used for as many functions as you like. |
Line 288: |
Line 107: |
| set f.fout to f.ang</pre> | | set f.fout to f.ang</pre> |
|
| |
|
| Okay, the function setup is complete. Now to get the Angle between two objects in a script just type: | | Okay, the function setup is complete. Now you've already got 3 functions: One to calculate the squareroot, one to calculate the Hypotenuse in a triangle and one to calculate the angle of a vector. To get the angle between two objects in a script just type: |
| <pre>;CALL float getAngle(float x, float y) | | <pre>;CALL float getAngle(float x, float y) |
| set f.fin1 to ( Object2.getPos x - Object1.getPos x ) | | set f.fin1 to ( Object2.getPos x - Object1.getPos x ) |
| set f.fin2 to ( Object2.getPos y - Object1.getPos y ) | | set f.fin2 to ( Object2.getPos y - Object1.getPos y ) |
| setStage f 20 | | setStage f 20 |
| set ResultingAngle to f.fout</pre> | | set ResultingAngleZ to f.fout</pre> |
| | | Et voilà, here is your Z-Angle between Object 1 and 2! You can reuse the function wherever you like, how often you wish to and you can even call functions from inside another stage of "f" (aka. another function)! |
| the Function executes immediately, it is easy to use and you can put one function for each stage into this quest framework! This is especially handy when you've to work with complex mathematical functions like those [http://www.elderscrolls.com/forums/index.php?showtopic=374963 HERE].
| |
| | |
| And you can easily share this functions with others since you'll only have to share the code you've put into this single stage!
| |
| | |
| (This is tested and works. Thanks to Galerion for the Square Root function.) | |
| --[[User:JustTim|JustTim]] 18:13, 6 May 2006 (EDT)
| |
| | |
| Damn, I've once again found a great limitation in scripting: It is obviously impossible to define new variables in a quest stage. It compiles, but they'll always have a value of 0. To get the above sampes to work you'll have to define all vars in the function quest script instead of the stage. i'm testing it further. --[[User:JustTim|JustTim]] 21:55, 6 May 2006 (EDT)
| |
|
| |
|
| Very cool: You are able to call a function (stage) from inside another function, even if both are part of the same quest! You'll just have to take care that they use diffrent veriables or the same but not at the same time. This is very handy due to the very limited size of one stage. It is also helpful to call the quest just "f" instead of "function" since you have to write the quest name for every variable you access in the script. --[[User:JustTim|JustTim]] 22:59, 6 May 2006 (EDT)
| | I is also very easy to share the functions with others since you only need to share the code of the stage result script for your function. |
|
| |
|
| I've updated the example with a more complex one. It is tested and working!! But it isn't very accurate due to the relatively unaccurate squareroot. If anyone knows how to improve this, please call me. --[[User:JustTim|JustTim]] 10:06, 7 May 2006 (EDT)
| | The only downsides are that you have to define all vars in the quest script (it compiles when you define them in the stage result script, but they'll always be 0) and the limited maximum length of each quest result script (which isn't a big problem since you are able to split a single function into multiple stages). |
|
| |
|
| | Many thanks go tu Kkuhlmann for his great idea and to Galerion for the Square Root function! |
| | The above function isn't very accurate, which may be due to my bad mathematics skills. If anyone has an idea on how to improve these scripts for accuracy and performance then please contribute! |
|
| |
|
| [[Category:Useful Code]] | | [[Category:Useful Code]] |
| [[Category:Solutions]] | | [[Category:Solutions]] |
Writing complex scripts for oblivion can be very painful and frustrating. Not being able to write reusable functions that execute directly on call instead of the next frame is one of the major problems (at least for me).
But Kkuhlmann from Bethesda had a great idea to make a workaround: Quest Stages!
When you call setStage in a script, the related stage result script will be executed immediately BEFORE the current script continues! This is therefore a great way to write immediately executing function calls!
I've spent MANY hours to advance this idea to a fully reusable function framework that can easily be used in your scripts. Here is what i came up with:
Create a Quest just called "f" (for "function"), ACTIVATE "ALLOW REPEATED STAGES" and attach the following quest script:
ScriptName FunctionQuestScript
; Internals
short doOnce
; Constants
float rad
float pi
; Function In- and Output
float fin1
float fin2
float fin3
float fout
float fout2
ref rin1
ref rin2
ref rin3
ref rout
ref rout2
;S5 FUNCTION sqrt
float sqr
;S10 FUNCTION Hypotenuse
;float sqr
float n
;S20 FUNCTION getAngle
;float sqr
float x
float y
float sin
float cos
float ang
; Set constants first
Begin Gamemode
if doOnce == 0
set rad to 57.2957792
set pi to 3.1415927
set doOnce to 1
endif
End
This is a generalized function framework that can be re-used for as many functions as you like.
And now make stage 5 for this quest with the following code:
;FUNCTION float sqrt(float input)
if (f.fin1 <= 0)
set f.fout to 0
else
set f.sqr to f.fin1/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.sqr to (f.sqr+(f.fin1/f.sqr))/2
set f.fout to f.sqr
endif
And stage 10:
;FUNCTION float Hypotenuse(float CathetusA, float CathetusB)
set f.n to ((f.fin1 * f.fin1) + (f.fin2 * f.fin2))
;CALL float sqrt(float input)
set f.fin1 to f.n
setStage f 5
;fout is already the result
And stage 20:
;FUNCTION float getAngle(float x, float y)
set f.x to f.fin1
set f.y to f.fin2
;CALL Hypotenuse using same fin
setStage f 10
set f.sqr to f.fout
set f.sin to (f.x/f.sqr)
set f.cos to (f.y/f.sqr)
if f.cos >= 0 && f.sin < 0 ;Q4
set f.ang to (f.cos*f.rad)
set f.ang to f.ang + 270
elseif f.cos < 0 && f.sin < 0; Q3
set f.ang to ((0-f.sin)*f.rad)
set f.ang to f.ang + 180
elseif f.cos < 0 && f.sin >= 0 ;Q2
set f.ang to ((0-f.cos)*f.rad)
set f.ang to f.ang + 90
else ;if f.cos >= 0 && f.sin >= 0 ;Q1
set f.ang to (f.sin*f.rad)
endif
set f.fout to f.ang
Okay, the function setup is complete. Now you've already got 3 functions: One to calculate the squareroot, one to calculate the Hypotenuse in a triangle and one to calculate the angle of a vector. To get the angle between two objects in a script just type:
;CALL float getAngle(float x, float y)
set f.fin1 to ( Object2.getPos x - Object1.getPos x )
set f.fin2 to ( Object2.getPos y - Object1.getPos y )
setStage f 20
set ResultingAngleZ to f.fout
Et voilà, here is your Z-Angle between Object 1 and 2! You can reuse the function wherever you like, how often you wish to and you can even call functions from inside another stage of "f" (aka. another function)!
I is also very easy to share the functions with others since you only need to share the code of the stage result script for your function.
The only downsides are that you have to define all vars in the quest script (it compiles when you define them in the stage result script, but they'll always be 0) and the limited maximum length of each quest result script (which isn't a big problem since you are able to split a single function into multiple stages).
Many thanks go tu Kkuhlmann for his great idea and to Galerion for the Square Root function!
The above function isn't very accurate, which may be due to my bad mathematics skills. If anyone has an idea on how to improve these scripts for accuracy and performance then please contribute!