Simulating new functions

Revision as of 19:35, 6 May 2006 by imported>JustTim

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:

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

scn testfuncscript

float param1
float param2
float result

begin GameMode
   Message "start func", 1
   set result to param1 + param2
   Message "end func: %.0f", result, 1
   StopQuest testfunc
end

In another script, I call this "function":

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

However, the above code doesn't work. The order of the output illustrates why:

before call
after call: 0
start func
end func: 30

This is what it should be:

before call
start func
end func: 30
after call: 30

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

--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:

   Message "start func", 1
   set testfunc.result to testfunc.param1 + testfunc.param2
   Message "end func: %.0f", testfunc.result, 1

To call this function, you'd do the following:


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

I think this should work (I haven't tried it).

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

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

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. --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:

  1. It can be called (via |testfunc.Activate player, 1|).
  2. The function is executed immediately rather than after the script that called it.
  3. 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! --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:

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

Calling script:

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

--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. --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:

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

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:

; 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

Okay, the function setup is complete. now to use it in a script just type:

; float Hypotenuse(float CathetusA, float CathetusB)
set Function.fParam1 to SomeValue
set Function.fParam2 to AnotherValue
setStage Function 10
set YetAnotherValue to Function.fResult

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

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


Another cool function:

; 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
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
set X2 to X*X
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
set Function.fResult to AngleA * ycoeff ;Sin

set AngleA to 1 - X2/2 + X4/24 - X6/720 + X8/40320 - X8*X2/3628800
set Function.fResult2 to AngleA * xcoeff ;Cos

Haven't tested this too. I hope it works. Many thanks to mrflippy and Grundulum for this function! --JustTim 19:35, 6 May 2006 (EDT)