Difference between revisions of "Simulating new functions"

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search
imported>DragoonWraith
imported>JustTim
(Changed external URL because of website domain change.)
 
(12 intermediate revisions by 3 users not shown)
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:
{{Deprecated Article|This article is obsolete since [[OBSE]] has support for [[User Functions]].}}


I have a quest called testfunc that is initially disabled and contains the following script:
=== Introduction ===


<pre>scn testfuncscript
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).


float param1
But Kkuhlmann from Bethesda had a great idea to make a workaround: Quest Stages!
float param2
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!
float result


begin GameMode
I've spent MANY hours to advance this idea to a fully reusable function framework that can easily be used in your scripts.
  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":
=== The easy way: Math Library ESM ===
The easiest way to use stage functions is by using [http://www.hazardx.com/details.php?file=68 this ESM Math Library]. It already contains all math functions listed in the [[stage_function_repository|Stage Function Repository]]. To use it copy the ESM file to your oblivion data folder and select it as an additional master file when loading your mod with the Construction Set. By doing this all the necessary setup steps are already done for you. All you need to do to use a function is to call it. You can also add new stages to the predefined function quest very easily in your mod without all the setup hassle.
Be aware that other users of your mod will need the Library too to run it.


<pre>scn testscript
Download: [http://www.hazardx.com/details.php?file=68 ESM Math Library v1.0].


float r
=== Do it yourself: Setup ===


begin OnActivate
* Open any Plugin you wish or create a new one with the Construction Set
  Message "before call", 1
* Create a new Quest called "f" (yes, just the letter "f", nothing more)
  set testfunc.param1 to 10
* Activate the Checkbox "Allow repeated stages". (This is VERY important!!)
  set testfunc.param2 to 20
* Create a new Quest Script and copy the whole FunctionQuestScript from this article into this script. Don't forget to attach it to your f-Quest!
  StartQuest testfunc
<pre>ScriptName FunctionQuestScript
  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:
; Internals
short doOnce
float fQuestDelayTime


<pre>before call
; Constants
after call: 0
float pi
start func
float rad
end func: 30</pre>


This is what it should be:


<pre>before call
; Function In- and Output
start func
float fin1
end func: 30
float fin2
after call: 30</pre>
float fin3
float fout
float fout2
float fout3


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).
;S10 FUNCTION Arctan
float t3
float t5
float t7


--[[User:Maian|Maian]] 03:59, 18 April 2006 (EDT)
;S20 FUNCTION getAngle
float tan
float ang




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.
; Set constants first
Begin Gamemode
  if doOnce == 0
    set pi to 3.1415927
    set rad to 180.0/pi


Keep your variables in your quest script. In stage 10 results, you'd have:
    set fQuestDelayTime to 30
 
    set doOnce to 1
<pre>
  endif
  Message "start func", 1
End</pre>
  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
* Now create stage 10 for this quest with the following code:
<pre>;FUNCTION float Arctan(float tan)
;Approximation by Taylor Series - script by DragoonWraith


begin OnActivate
set f.t3 to (f.fin1 * f.fin1 * f.fin1)
  Message "before call", 1
set f.t5 to (f.t3 * f.fin1 * f.fin1)
  set testfunc.param1 to 10
set f.t7 to (f.t5 * f.fin1 * f.fin1)
  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).
set f.fout to (f.fin1 - (f.t3/3) + (f.t5/5) - (f.t7/7))</pre>


--[[User:Kkuhlmann|Kkuhlmann]] 14:02, 18 April 2006 (EDT)
* And stage 20:
<pre>;FUNCTION float getAngle(float x, float y)


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.
set f.tan to (f.fin1/f.fin2)


So for instance
if f.tan >1 || f.tan < -1
 
   set f.tan to (f.fin2/ -f.fin1)
<pre>scn ActivateFunction
   if f.fin1 >= 0
ref incoming
    set f.ang to 90
 
  else
begin OnActivate
    set f.ang to -90
   set incoming to GetActionRef
   if (incoming == CallingScriptRef.Me)
    set CallingScriptRef.Value to CallingScriptRef.Value + 3
    CallingScriptRef.Activate CallingScriptRef.ActivateFunctionRef 1
    return
   endif
   endif
end</pre>
else
 
  if f.fin2 >= 0
<pre>scn CallingScript
    set f.ang to 0
ref Me
   else
ref ActivateFunctionRef
    set f.ang to 180
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
   endif
  ActivateFunctionRef.Activate Me 1
endif
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
;CALL float Arctan(float tan)
ref self
set f.fin1 to f.tan
setStage f 10


begin Onload
if f.fout > (f.pi/2)
  set self to GetSelf
   set f.fout to (f.pi/2)
end
elseif f.fout < (f.pi/ -2)
 
   set f.fout to (f.pi/ -2)
begin OnActivate
endif
  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 called "Function" with the following script:
<pre>ScriptName FunctionQuestScript
 
short sParam1
short sParam2
short sParam3
short sResult
short sResult2
 
float fParam1
float fParam2
float fParam3
float fResult
float fResult2
 
ref rParam1
ref rParam2
ref rParam3
ref rResult
ref rResult2</pre>
This is a generalized function framework that can be re-used for as many functions as you like.
 
And now make stage 10 for this quest with the following code:
<pre>; FUNCTION float Hypotenuse(float CathetusA, float CathetusB)
float sqroot
float n
 
set n to ((Function.fParam1 * Function.fParam1) + (Function.fParam2 * Function.fParam2))
if (n <= 0)
   set Function.fResult to 1
else
  set sqroot to n/2
  set sqroot to (sqroot + (n/sqroot))/2
  set sqroot to (sqroot + (n/sqroot))/2
  set sqroot to (sqroot + (n/sqroot))/2
  set sqroot to (sqroot + (n/sqroot))/2
   set sqroot to (sqroot + (n/sqroot))/2
  set Function.fResult to (n * sqroot)
endif</pre>


Okay, the function setup is complete. now to use it in a script just type:
set f.fout to ((f.fout*f.rad) + f.ang)</pre>
<pre>; float Hypotenuse(float CathetusA, float CathetusB)
* Okay, the function setup is complete.
set Function.fParam1 to SomeValue
set Function.fParam2 to AnotherValue
setStage Function 10
set YetAnotherValue to Function.fResult</pre>


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].
=== Usage ===


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!
Now you've already got 2 functions: One to calculate the arctan and another one that uses arctan 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)
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</pre>
Et voilà, here is your Z-Angle between Object 1 and 2!


(I haven't tested the functions here but they should work. Thanks to Galerion for the Square Root function.)
=== Conclusion ===
--[[User:JustTim|JustTim]] 18:13, 6 May 2006 (EDT)


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)!


Another cool function:
It 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.
<pre>; FUNCTION float,float SinCos(float Angle)
float AngleA
float X
float X2
float X3
float X4
float X5
float X6
float X7
float X8
float X9
short xcoeff
short ycoeff


set AngleA to Function.fParam1
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).
If AngleA >= 270
  Set xcoeff to 1
  Set ycoeff to -1
  Set AngleA to AngleA - 270
ElseIf AngleA >= 180
  Set xcoeff to -1
  Set ycoeff to -1
  Set AngleA to AngleA - 180
ElseIf AngleA >= 90
  Set xcoeff to -1
  Set ycoeff to 1
  Set AngleA to AngleA - 90
Else
  Set xcoeff to 1
  Set ycoeff to 1
EndIf


set X to AngleA / 57.2957792
Many thanks go tu Kkuhlmann for his great idea, to DragoonWraith for the Arctan function and to Maian for telling me that my old functions were completely wrong. :)
set X2 to X*X
The new version of the getAngle Function seems to be very accurate, but if anyone has an idea on how to improve these scripts for accuracy and performance then please contribute!
set X3 to X2*X
set X4 to X2*X2
set X5 to X4*X
set X6 to X3*X3
set X7 to X5*X2
set X8 to X4*X4
set X9 to X7*X2


set AngleA to X - X3/6 + X5/120 - X7/5040 + X9/362880 - X9*X2/39916800
=== Function Repository ===
set Function.fResult to AngleA * ycoeff ;Sin


set AngleA to 1 - X2/2 + X4/24 - X6/720 + X8/40320 - X8*X2/3628800
If you are looking for a complete setup with many math functions included then take a look at the [[stage_function_repository|Stage Function Repository]]!
set Function.fResult2 to AngleA * xcoeff ;Cos</pre>
Haven't tested this too. I hope it works. Many thanks to mrflippy and Grundulum for this function! --[[User:JustTim|JustTim]] 19:35, 6 May 2006 (EDT)


[[Category:Useful Code]]
[[Category:Solutions]]
[[Category:Solutions]]

Latest revision as of 13:12, 17 April 2013


Introduction[edit | edit source]

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.

The easy way: Math Library ESM[edit | edit source]

The easiest way to use stage functions is by using this ESM Math Library. It already contains all math functions listed in the Stage Function Repository. To use it copy the ESM file to your oblivion data folder and select it as an additional master file when loading your mod with the Construction Set. By doing this all the necessary setup steps are already done for you. All you need to do to use a function is to call it. You can also add new stages to the predefined function quest very easily in your mod without all the setup hassle. Be aware that other users of your mod will need the Library too to run it.

Download: ESM Math Library v1.0.

Do it yourself: Setup[edit | edit source]

  • Open any Plugin you wish or create a new one with the Construction Set
  • Create a new Quest called "f" (yes, just the letter "f", nothing more)
  • Activate the Checkbox "Allow repeated stages". (This is VERY important!!)
  • Create a new Quest Script and copy the whole FunctionQuestScript from this article into this script. Don't forget to attach it to your f-Quest!
ScriptName FunctionQuestScript

; Internals
short doOnce
float fQuestDelayTime

; Constants
float pi
float rad


; Function In- and Output
float fin1
float fin2
float fin3
float fout
float fout2
float fout3


;S10 FUNCTION Arctan
float t3
float t5
float t7

;S20 FUNCTION getAngle
float tan
float ang


; Set constants first
Begin Gamemode
  if doOnce == 0
    set pi to 3.1415927
    set rad to 180.0/pi

    set fQuestDelayTime to 30
    set doOnce to 1
  endif
End
  • Now create stage 10 for this quest with the following code:
;FUNCTION float Arctan(float tan)
;Approximation by Taylor Series - script by DragoonWraith

set f.t3 to (f.fin1 * f.fin1 * f.fin1)
set f.t5 to (f.t3 * f.fin1 * f.fin1)
set f.t7 to (f.t5 * f.fin1 * f.fin1)

set f.fout to (f.fin1 - (f.t3/3) + (f.t5/5) - (f.t7/7))
  • And stage 20:
;FUNCTION float getAngle(float x, float y)

set f.tan to (f.fin1/f.fin2)

if f.tan >1 || f.tan < -1
  set f.tan to (f.fin2/ -f.fin1)
  if f.fin1 >= 0
    set f.ang to 90
  else
    set f.ang to -90
  endif
else
  if f.fin2 >= 0
    set f.ang to 0
  else
    set f.ang to 180
  endif
endif

;CALL float Arctan(float tan)
set f.fin1 to f.tan
setStage f 10

if f.fout > (f.pi/2)
  set f.fout to (f.pi/2)
elseif f.fout < (f.pi/ -2)
  set f.fout to (f.pi/ -2)
endif

set f.fout to ((f.fout*f.rad) + f.ang)
  • Okay, the function setup is complete.

Usage[edit | edit source]

Now you've already got 2 functions: One to calculate the arctan and another one that uses arctan 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!

Conclusion[edit | edit source]

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)!

It 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, to DragoonWraith for the Arctan function and to Maian for telling me that my old functions were completely wrong. :) The new version of the getAngle Function seems to be very accurate, but if anyone has an idea on how to improve these scripts for accuracy and performance then please contribute!

Function Repository[edit | edit source]

If you are looking for a complete setup with many math functions included then take a look at the Stage Function Repository!