Help optimizing locational damage script at runtime

Post » Wed Jun 20, 2012 11:23 am

So, I am prototyping a script that registers headshots from projectiles (Ice Spike is the guinea pig for this atm)

ObjectReference marker = akTarget.PlaceAtMe(Game.GetForm(0x6CD3D),1)

are you sure you do not want to use moveto (from a hidden cell) instead of placeatme ?

Head is not correct, if you do that you will not get what you want. It is just an example of how it is used, "head" is not a node name for a human male in skyrim, it is NPC Head [Head], pull up Body Part data to look for node names. It only works for nodes that are defined for the body type. Again you will have to look in the body party data and such to find all the nodes listed.

ObjectReference marker = akTarget.PlaceAtMe(Game.GetForm(0x6CD3D),1)


is correct. I wanted to place at the target a marker object, that marker object's form ID is 0x6CD3D. The Game.GetForm() returns that form, which is input into the place at me as the Form arg, I place 1 copy of it. If I used to I would be moving the target, which I do not see why I would want to do that. I want a ObjectReference I can get/set position coords with, so I create one. There is no direct way (I know of) that allows you to get the position of nodes on a model. You have to move an object to that node, and then get that object's position.
User avatar
Sherry Speakman
 
Posts: 3487
Joined: Fri Oct 20, 2006 1:00 pm

Post » Wed Jun 20, 2012 5:59 pm

oh? Has PlaceAtMe changed that much from Oblivion?

In Oblivion dong that for every projectile you shot would bloat the savegame!

A finite number of PlaceAtMe calls was acceptable, but for every projectile? Is there a new way to clean up the PlaceAtMe objects that I do not know about yet? (that would be great!) The Old Oblivion solution was to use ONE object and move it to your needed pos each time it was required rather than make a new one each time.

ObjectReference marker = akTarget.PlaceAtMe(Game.GetForm(0x6CD3D),1)


is correct. I wanted to place at the target a marker object, that marker object's form ID is 0x6CD3D. The Game.GetForm() returns that form, which is input into the place at me as the Form arg, I place 1 copy of it. If I used to I would be moving the target, which I do not see why I would want to do that. I want a ObjectReference I can get/set position coords with, so I create one. There is no direct way (I know of) that allows you to get the position of nodes on a model. You have to move an object to that node, and then get that object's position.
User avatar
Cody Banks
 
Posts: 3393
Joined: Thu Nov 22, 2007 9:30 am

Post » Wed Jun 20, 2012 10:30 am

oh? Has PlaceAtMe changed that much from Oblivion?

In Oblivion dong that for every projectile you shot would bloat the savegame!

A finite number of PlaceAtMe calls was acceptable, but for every projectile? Is there a new way to clean up the PlaceAtMe objects that I do not know about yet? (that would be great!) The Old Oblivion solution was to use ONE object and move it to your needed pos each time it was required rather than make a new one each time.

Nope, I'm pretty sure PlaceAtMe is still going to be causing saved game bloating. Didn't test yet, but didn't hear anything anywhere about it NOT bloating saved games, so I guess it still does. Which would also pretty much make sense.. you're constantly adding new objects to the world.

EDIT:

Actually, I was just looking at http://www.creationkit.com/PlaceAtMe_-_ObjectReference , and it does have a parameter bool abForcePersist which defaults at false. So maybe the references created are not automatically persistent and will get cleaned automatically.. I'm not sure though, should definitely investigate that before uploading a mod which includes the placeAtMe command.
User avatar
Emma Pennington
 
Posts: 3346
Joined: Tue Oct 17, 2006 8:41 am

Post » Wed Jun 20, 2012 8:25 pm

Getting rid of the globalvariable properties could speed up the process. I think registering the property would take more time than simply defining a variable inside the script and setting that. That said you could do the following:

The MagicEffect script and Impact Script, both save the positions to local variables.
The script on the player provides functions to pass the positions to it.
The MagicEffect script already has (per akCaster) a reference to the player that after casting it to be of type myPlugin_GlobalDataScript enables you to call that function from the magic effect (this is aimed at saving you the work of polling for the data to be there).
To have a reference to the player available in the Impact script you obvisously simply use game().getplayer() and cast it again to type myPlugin_GlobalDataScript to call the setter function.

At the end of each of the functions for passing the locations they set a respective bool to true (again no global needed just declare it outside of any functions) and then check if the other function's been called already by checking it's repsective bool. If this is true they call the function doing the distance calculation (mind you that this could result in race conditions so measures are needed to prevent the distance function from being called twice).
User avatar
Ladymorphine
 
Posts: 3441
Joined: Wed Nov 08, 2006 2:22 pm

Post » Wed Jun 20, 2012 2:00 pm

Getting rid of the globalvariable properties could speed up the process. I think registering the property would take more time than simply defining a variable inside the script and setting that. That said you could do the following:

The MagicEffect script and Impact Script, both save the positions to local variables.
The script on the player provides functions to pass the positions to it.
The MagicEffect script already has (per akCaster) a reference to the player that after casting it to be of type myPlugin_GlobalDataScript enables you to call that function from the magic effect (this is aimed at saving you the work of polling for the data to be there).
To have a reference to the player available in the Impact script you obvisously simply use game().getplayer() and cast it again to type myPlugin_GlobalDataScript to call the setter function.

At the end of each of the functions for passing the locations they set a respective bool to true (again no global needed just declare it outside of any functions) and then check if the other function's been called already by checking it's repsective bool. If this is true they call the function doing the distance calculation (mind you that this could result in race conditions so measures are needed to prevent the distance function from being called twice).

Thanks, Jason :smile:


I figured using globals was slower than using simple declared local vars, I will work that solution in. As for

However, I am starting to think not matter how fast I can set these variables, there can always be the case where an object will simply be moving too fast, so I am going to look for a time independent design that can adapt and use inaccurate data and create an estimate which can be further used as a 'double check'.

As for now, I am going to get started on the actual head shot process, which should be a lot more fun!



oh? Has PlaceAtMe changed that much from Oblivion?

In Oblivion dong that for every projectile you shot would bloat the savegame!

A finite number of PlaceAtMe calls was acceptable, but for every projectile? Is there a new way to clean up the PlaceAtMe objects that I do not know about yet? (that would be great!) The Old Oblivion solution was to use ONE object and move it to your needed pos each time it was required rather than make a new one each time.

Yes, it does bloat the save but this is a prototype, the main goal now is to get working design. Later on I can just implement scripts that clean up and delete these markers, for now I am just focused on getting and setting impact and target positions as close to the actual impact as possible.
User avatar
Lil Miss
 
Posts: 3373
Joined: Thu Nov 23, 2006 12:57 pm

Post » Wed Jun 20, 2012 2:34 pm

oceans,

Great work. It looks like this is very well thought through.

I second the recommendation to get rid of Globals if you can. I just made a post about them http://www.gamesas.com/topic/1361933-modding-best-practices-and-changes-persisting-in-saved-games/page__view__findpost__p__20547357, though in your case this particular problem might not matter. Try to use script properties instead to pass values between scripts. I believe it is faster, and you have more control over who is accessing what.
User avatar
Emma
 
Posts: 3287
Joined: Mon Aug 28, 2006 12:51 am

Post » Wed Jun 20, 2012 7:13 pm

oceans,

Great work. It looks like this is very well thought through.

I second the recommendation to get rid of Globals if you can. I just made a post about them http://www.gamesas.com/topic/1361933-modding-best-practices-and-changes-persisting-in-saved-games/page__view__findpost__p__20547357, though in your case this particular problem might not matter. Try to use script properties instead to pass values between scripts. I believe it is faster, and you have more control over who is accessing what.

That was actually my initial idea, but I am not very familiar with linking scripts together, therefore I could not find a way to have one script have an access to another script without incurring overhead in searching for the object that the script is attached to. In my case the object that gets spawned on impact has to have a reference to the script that gets ran when the projectile hits the target. For the Magic Effect script to get the Impact Script that is attached to the object gets spawned via explosion it would have to do a SearchForClosestReferenceOfType or something similar to get the object that spawned and access the script properties, this is all done at run time and I feel is not good at all, is there a static way of doing this so that the references can be set up ahead of time?
User avatar
michael flanigan
 
Posts: 3449
Joined: Thu Jun 14, 2007 2:33 pm

Post » Wed Jun 20, 2012 8:23 pm

Yeah, using FindClosestReferenceOfTypeFromRef might feel like a potentially slow operation, but we don't know anything about how the object graph is structured -- which data structures and algorithms it uses.
It might actually be a fast operation, especially if the object in question is close to the source.

You don't have to assume anything if you can test it.
User avatar
Charlie Sarson
 
Posts: 3445
Joined: Thu May 17, 2007 12:38 pm

Post » Wed Jun 20, 2012 3:28 pm

In my case the object that gets spawned on impact has to have a reference to the script that gets ran when the projectile hits the target.

You mean you need to get a referenece to the "myPlugin_IciclePenetration" script inside the "ImpactLocationGetter" script?
Why? If I may ask. Don't quite get why you'd need a connection between the two scripts that are there to get the positions. Just have them both report back to the script that does the calculations after they get the positions if that is attached to the player as you say getting the reference to it is quite easy. Anyways: Any searching for refs and passing of data should be done after you got the positions.

Actually I think I got his wrong: You meant you need to have the "ImpactLocationGetter" script have a reference to the script that spawns the ImpactLocationGetter-Item right? Also makes little sense to me. But in order to get that you'd need to pass it manually through calling a function on ImpactLocationGetter that takes the ref as a parameter. Obviously in that case you'd only have acces to the ref after the OnInit Event call is through, as that is what get's called first by the placeatme call.

For the Magic Effect script to get the Impact Script that is attached to the object gets spawned via explosion it would have to do a SearchForClosestReferenceOfType or something similar to get the object that spawned and access the script properties, this is all done at run time and I feel is not good at all, is there a static way of doing this so that the references can be set up ahead of time?
Sadly no. All those references are references in the sense of OOP languages. Meaning they are pointers to specific instances of the base object. So for any object being created at runtime the reference to it simply is neither available in the CK nor prior to the object actually being created. References to anything being created through placeatme calls are returned by the placeatme function. To make them available to other scripts than the one calling the placeatme you need to provide functions in those other scripts to pass the reference to them. And these other scripts must be easily refernceable, meaning they need to be attached to the player, a quest or an item/actor you manually placed via the CK beforehand so the reference to it is known and can be given as a property to the script.

For anything not created via placeatme (magiceffects, projectile scripts) it's even more complicated. For easily getting references to those they'd need to report the reference to themself using a script attached to them and calling a function on the script that needs the reference to pass it. Of course again the script that needs to have the reference must be easy to refernce itself, thus attached to either the player, a quest or a manually (in the CK) placed item, so you can get the reference via the GetPLayer() call or by having it as property of the script.

Edit:

Anyhow on the general idea of using the targetposition and an offset to get the headposition: Sounds like a very good approach, I haven't had a close look on the animations but I'd assume that the (is it Y or Z??) "height"-coordinate should suffice. Meaning: after you got the target's position as fast as possible after impact (that variable is the only time critical one, as the impact position won't change) simply spawn your marker move it to the head, get it's "height"-coordinate and replace the targetposition's "height"-coordinate with it, then do your distance calculations between target and impact position. That should be a pretty accurate result.
User avatar
Felix Walde
 
Posts: 3333
Joined: Sat Jun 02, 2007 4:50 pm

Post » Wed Jun 20, 2012 1:11 pm

If you want speed scrap the globals and get rid of "Game.GetForm" and directly link to an object instead. The object you move to the head seems to be irrelevant, it's just a positional placeholder to retrieve the position of the head.

You only want to determine a headshot, so you want to receive both the place of impact, and the head position asap...

Impact -> Save Pos -> Marker to head -> Save Pos -> Compare (Irrelevant in terms of computation)

Whats slowing your script down is the functions your calling between the storing of values, storing the values is fast just a simple push and pop, whats slowing you down is the creation of new objects, moving already existing objects is much faster. It would be better that you create the marker thats meant to move to the head beforehand and move it, preferably an object without geometry.
User avatar
Jennifer Munroe
 
Posts: 3411
Joined: Sun Aug 26, 2007 12:57 am

Post » Wed Jun 20, 2012 7:52 pm

It would be better that you create the marker thats meant to move to the head beforehand and move it, preferably an object without geometry.

Actually this is a good idea aswell. The object can be just anywhere at the start. Simply attach it as a property to the magiceffect script and then move it to the head.
User avatar
REVLUTIN
 
Posts: 3498
Joined: Tue Dec 26, 2006 8:44 pm

Post » Wed Jun 20, 2012 4:56 pm

Thanks for the reply you two :smile:

You mean you need to get a referenece to the "myPlugin_IciclePenetration" script inside the "ImpactLocationGetter" script?
Why? If I may ask. Don't quite get why you'd need a connection between the two scripts that are there to get the positions. Just have them both report back to the script that does the calculations after they get the positions if that is attached to the player as you say getting the reference to it is quite easy. Anyways: Any searching for refs and passing of data should be done after you got the positions.

No you got it right. I was just trying to get the idea across of directly linking the scripts without the player attached script being the middle man in all of this. Them reporting to the player script is what I do now essentially, just indirectly (which I will change).

Edit:

Anyhow on the general idea of using the targetposition and an offset to get the headposition: Sounds like a very good approach, I haven't had a close look on the animations but I'd assume that the (is it Y or Z??) "height"-coordinate should suffice. Meaning: after you got the target's position as fast as possible after impact (that variable is the only time critical one, as the impact position won't change) simply spawn your marker move it to the head, get it's "height"-coordinate and replace the targetposition's "height"-coordinate with it, then do your distance calculations between target and impact position. That should be a pretty accurate result.

I also thought of this as well,however, that solution is actually only a good heuristic when the target is in a pose where is head is close to his 'height' i.e standing straight up (good posture!). Quite a few enemies (2 handed enemies) hunch over and their head is not only displaced heightwise, but also moved forward due to being a bit hunched over (This means a chest-hit for a standing straight enemy should be a head shot for this hunched over guy assuming the they are facing you). If you use the displacement of the head it won't matter what pose he was in, as long as he was in the close to same pose at the previous position.


If you want speed scrap the globals and get rid of "Game.GetForm" and directly link to an object instead. The object you move to the head seems to be irrelevant, it's just a positional placeholder to retrieve the position of the head.

You only want to determine a headshot, so you want to receive both the place of impact, and the head position asap...

Impact -> Save Pos -> Marker to head -> Save Pos -> Compare (Irrelevant in terms of computation)

Whats slowing your script down is the functions your calling between the storing of values, storing the values is fast just a simple push and pop, whats slowing you down is the creation of new objects, moving already existing objects is much faster. It would be better that you create the marker thats meant to move to the head beforehand and move it, preferably an object without geometry.


Good insight :smile:

I am thinking of how this can be done. To be created before the script is run, but the script has to have access to it as close creation. It will most likely be pointed to by the player script, so the player script will have to provide the reference to the Icicle Penetration script, the best place I can see this happening is in the OnInit method, it will grab the player script reference then get the object reference. Any faster ways?
User avatar
Tania Bunic
 
Posts: 3392
Joined: Sun Jun 18, 2006 9:26 am

Post » Wed Jun 20, 2012 4:18 am

Well, I am finally getting to the final stages of my mod, just testing it now.

I think the final solution I am taking will be 2 parts. Both are separate solutions that work independently of each other but have the same goal in mind. The first solution is the easiest, simplest and most lightweight solution. However it is the slowest solution and will be subject to target movement inaccuracy in reporting the head positions.

Solution 1
Projectiles have a script that when an projectile impacts - spawn an object at that impact point that reports the location of the impact to a script running on the player called registerImpact(int x, int y, int z). After registering the impact position it jumps to the head node of the target by calling a getclosestactor function which definitely impacts performance, but there is no way for the spawned object to know of its intended target beforehand, and programming logic to have some other aware script give it the target will just take as much time. The spawned object moves to this target's head and reports its head position with a similar function - registerHead(int x, int y, int z, Actor target). Once these functions are called, they set a flag (impactR and headR) to true. Both function check to see if the other is true, then calls calcHeadDistance() which will determine if the impact was a headshot.

It's light, simple, runs in one script on an object that gets spawned on impact. Very compact and not resource intensive at all - but slow. Targets would need to standing still or moving on a flat plane in order for headshots to be accurate.

Solution 2
Projectiles do the same thing, they simply report their location, but are not tasked with getting an Actor and getting his head location and reporting that, too. A quest that fills Aliases with Actors near the player is running, and each alias has a OnHit event that will (when struck by the player and a projectile) report their head location *used with a non-persistent marker spawned at OnInit() time). Much faster than the other solution. Solution 1 took ~240+ time from registerImpact() to calcHeadDistance(). This solution goes from 70-180 time units (100 units average). This however requires a lot more complexity with 2 quests running and aliases. However, not all NPCs will be filled by the manager into aliases. Meaning headshots won't work period making it useless in these cases.


Final Solution?
Use both. It is essentially a race for whoever gets to registerHead() first which is not a bad thing. The projectile will report its impact position on impact like in both cases, and then the Actor will be hit and report his head position, meanwhile the Projectile will be in the process of getting that Actor's head position and reporting it, usually in will lose the race an the OnHit event will report it first. However, in the cases where the manager does not put the target into an Alias (making the OnHit never fire) it will still work since we have the Projectile getting and registering the head. The fact they are racing will not have a negative effect, since the sooner calcHeadDistance() gets called the better, if it gets called twice -- no big deal, one will use accurate data, one will use less accurate data, so if it was a head shot, it will still get registered. The only case would be if it was NOT a head shot, but the inaccurate laggy data said it was (you fire at a wall, miss, and the target dies). This is a shortcoming I am ok with. If the actor is standing next to a wall and your arrow hits the wall with the same z position the actors head is at, it will be a headshot -- oh well -- that's more fun than hitting them in the head and them not dying IMO.

Also, MOST people will probably use the slomo effect with archery, making both solutions become very accurate. Slowing the game down makes both methods report very good data. I honestly want to just use solution 1 and tell everyone to just do headshots in slow motion :tongue:
User avatar
Rowena
 
Posts: 3471
Joined: Sun Nov 05, 2006 11:40 am


Return to V - Skyrim