Talk:Simulating new functions

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search

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 hope 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! I've advanced his idea to a working solution! See the article for more info. --JustTim 11:26, 7 May 2006 (EDT)

The math for getAngle is definitely not correct. You mixed up cos and sin (cos = x/h; sin = y/h). I'm not sure why you're multiplying cos or sin with 180/PI since that's not the angle. In fact, you shouldn't even be using cos or sin when trying to get the arctangent. --Maian 00:56, 8 May 2006 (EDT)

Oops, now i see why i've recieved some strange results. But the results aren't too far from what they should be. ^^ But i'd appreciate a corrected version very much. As said before my math skills are VERY limited and those functions are nothing but patchwork of stuff i found on the net, put together by trial and error instead of reasonable mathematics. :) --JustTim 11:34, 8 May 2006 (EDT)

BTW: If there is something you'd like to change or contribute to the article or if you know of a good way to write a simplier example don't hesitate to modify the article directly. I know the article lacks a professional writing style (presumably due to me not being a native english-speaker) and it would be cool if someone could help me to bring this into a professional, more wiki-like (and error-free) form. --JustTim 15:16, 8 May 2006 (EDT)

This is a script I made but ended up never using once I discovered the getHeadingAngle function (so it's rather untested and probably has bugs). It's basically a lookup table, one row for a degree (so the script is large), except since there are no arrays, I made the look up act as a binary search. It also doesn't use the quest result script thing - I'm using the method I described above.

scn ArcTanScript
; by Maian

float x
float y
float result
float ratio

begin GameMode
	if x == 0
		if y >= 0
			set result to 90
		else
			set result to 270
		endif
		return
	endif
	set ratio to y / x
	if ratio < 0
		set ratio to -ratio
	endif
	if ratio < 1.018
		if ratio < 0.414
			if ratio < 0.203
				if ratio < 0.096
					if ratio < 0.044
						if ratio < 0.009
							set result to 0
						elseif ratio < 0.026
							set result to 1
						else
							set result to 2
						endif
					else	; 0.044
						if ratio < 0.061
							set result to 3
						elseif ratio < 0.079
							set result to 4
						else
							set result to 5
						endif
					endif
				else	; 0.096
					if ratio < 0.149
						if ratio < 0.114
							set result to 6
						elseif ratio < 0.132
							set result to 7
						else
							set result to 8
						endif
					else	; 0.149
						if ratio < 0.167
							set result to 9
						elseif ratio < 0.185
							set result to 10
						else
							set result to 11
						endif
					endif
				endif
			else	; 0.203
				if ratio < 0.296
					if ratio < 0.24
						if ratio < 0.222
							set result to 12
						else
							set result to 13
						endif
					else	; 0.24
						if ratio < 0.259
							set result to 14
						elseif ratio < 0.277
							set result to 15
						else
							set result to 16
						endif
					endif
				else	; 0.296
					if ratio < 0.354
						if ratio < 0.315
							set result to 17
						elseif ratio < 0.335
							set result to 18
						else
							set result to 19
						endif
					else	; 0.354
						if ratio < 0.374
							set result to 20
						elseif ratio < 0.394
							set result to 21
						else
							set result to 22
						endif
					endif
				endif
			endif
		else	; 0.414
			if ratio < 0.662
				if ratio < 0.521
					if ratio < 0.456
						if ratio < 0.435
							set result to 23
						else
							set result to 24
						endif
					else	; 0.456
						if ratio < 0.477
							set result to 25
						elseif ratio < 0.499
							set result to 26
						else
							set result to 27
						endif
					endif
				else	; 0.521
					if ratio < 0.589
						if ratio < 0.543
							set result to 28
						elseif ratio < 0.566
							set result to 29
						else
							set result to 30
						endif
					else	; 0.589
						if ratio < 0.613
							set result to 31
						elseif ratio < 0.637
							set result to 32
						else
							set result to 33
						endif
					endif
				endif
			else	; 0.662
				if ratio < 	0.824
					if ratio < 0.74
						if ratio < 0.687
							set result to 34
						elseif ratio < 0.713
							set result to 35
						else
							set result to 36
						endif
					else	; 0.74
						if ratio < 0.767
							set result to 37
						elseif ratio < 0.795
							set result to 38
						else
							set result to 39
						endif
					endif
				else	; 0.824
					if ratio < 0.916
						if ratio < 0.854
							set result to 40
						elseif ratio < 0.885
							set result to 41
						else
							set result to 42
						endif
					else	; 0.916
						if ratio < 0.949
							set result to 43
						elseif ratio < 0.983
							set result to 44
						else
							set result to 45
						endif
					endif
				endif
			endif
		endif
	else	; 1.018
		if ratio < 2.414
			if ratio < 1.511
				if ratio < 1.213
					if ratio < 1.091
						if ratio < 1.054
							set result to 46
						else
							set result to 47
						endif
					else	; 1.091
						if ratio < 1.13
							set result to 48
						elseif ratio < 1.171
							set result to 49
						else
							set result to 50
						endif
					endif
				else	; 1.213
					if ratio < 1.351
						if ratio < 1.257
							set result to 51
						elseif ratio < 1.303
							set result to 52
						else
							set result to 53
						endif
					else	; 1.351
						if ratio < 1.402
							set result to 54
						elseif ratio < 1.455
							set result to 55
						else
							set result to 56
						endif
					endif
				endif
			else	; 1.511
				if ratio < 	1.842
					if ratio < 1.632
						if ratio < 1.57
							set result to 57
						else
							set result to 58
						endif
					else	; 1.632
						if ratio < 1.698
							set result to 59
						elseif ratio < 1.767
							set result to 60
						else
							set result to 61
						endif
					endif
				else	; 1.842
					if ratio < 2.097
						if ratio < 1.921
							set result to 62
						elseif ratio < 2.006
							set result to 63
						else
							set result to 64
						endif
					else	; 2.097
						if ratio < 2.194
							set result to 65
						elseif ratio < 2.3
							set result to 66
						else
							set result to 67
						endif
					endif
				endif
			endif
		else	; 2.414
			if ratio < 4.915
				if ratio < 3.172
					if ratio < 2.675
						if ratio < 2.539
							set result to 68
						elseif ratio < 2.006
							set result to 69
						endif
					else	; 2.675
						if ratio < 2.824
							set result to 70
						elseif ratio < 2.989
							set result to 71
						else
							set result to 72
						endif
					endif
				else	; 3.172
					if ratio < 3.867
						if ratio < 3.376
							set result to 73
						elseif ratio < 3.606
							set result to 74
						else
							set result to 75
						endif
					else	; 3.867
						if ratio < 4.165
							set result to 76
						elseif ratio < 4.511
							set result to 77
						else
							set result to 78
						endif
					endif
				endif
			else	; 4.915
				if ratio < 	10.385
					if ratio < 6.691
						if ratio < 5.396
							set result to 79
						elseif ratio < 5.976
							set result to 80
						else
							set result to 81
						endif
					else	; 6.691
						if ratio < 7.596
							set result to 82
						elseif ratio < 8.777
							set result to 83
						else
							set result to 84
						endif
					endif
				else	; 10.385
					if ratio < 22.904
						if ratio < 10.385
							set result to 85
						elseif ratio < 12.706
							set result to 86
						else
							set result to 87
						endif
					else	; 22.904
						if ratio < 38.188
							set result to 88
						elseif ratio < 114.589
							set result to 89
						else
							set result to 90
						endif
					endif
				endif
			endif
		endif
	endif
	if x < 0 && y < 0
		set result to result + 180
	elseif x < 0
		set result to -result + 180
	elseif y < 0
		set result to -result + 360
	endif
end

--Maian 16:50, 8 May 2006 (EDT)

Thanks for sharing this! I've already written a new getAngle Function using the Arctan function by DragoonWraith, flipping it around in 90 degree steps to avoid the inaccuracy in angles bigger than 45 degrees. You can find it in the article. It could (once again) be total mathematical nonsense, but in my tests it looks very accurate. You said you are using activators to simulate functions, are you sure the activation executes immediately? i had problems with this in my tests. --JustTim 18:29, 8 May 2006 (EDT)

Although I haven't tested it a lot, the OnActivate blocks should immediately execute. After all, Tegid and MrFlippy use OnActivate extensively in their pseudo-array scripts.

The math looks fine to me. You can increase the accuracy of the arctan function by adding t* terms. See http://en.wikipedia.org/wiki/Taylor_series#List_of_Taylor_series_of_some_common_functions for more info. The accuracy of the square root function you once had could be increased by adding more the same lines, though I think it's accurate enough. Of course, there's a balance between speed and accuracy, and I think it would be okay if the arctan func is accurate to 1 degree. BTW you should add that square root function back - even if it isn't used for getAngle, it's still be a very useful function. --Maian 19:40, 8 May 2006 (EDT)

Yes, i'm planning to add a function repository tomorrow containing the squareroot function and all math functions included in the trigonometry article. Thanks again for your help! --JustTim 20:07, 8 May 2006 (EDT)