NPC Attack complete rewrite: (formerly NPC strafing)

The place to discuss scripting and game modifications for X³: Terran Conflict and X³: Albion Prelude.

Moderators: Scripting / Modding Moderators, Moderators for English X Forum

Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

NPC Attack complete rewrite: (formerly NPC strafing)

Post by Shush »

Update:

I've added a new video showing current progress. http://youtu.be/ywbzyvG2HPM

As I was not able to make the MSCI's force position play nicely with any of the MSCI's internal movement commands, (i.e. attack run on target, follow object, defensive move, etc), I decided to write my own attack run command using the MSCI's set rotation and force position. In the video you will see me applying an attack script to all player owned ships in the immediate area, they will then begin manoeuvering and attacking the player ship with the following caveats:
  • - Jerky movement/rotation for slower ships.
    - Gimbal Lock as one the target vector axis collapses onto another, (this is easily solved).
    - No speed indicator for attacking ships.
    - Target indicator in the wrong position as it can't handle strafe vectors, (the internal target indicator code assumes the ship is always in motion along it's local z axis.
    - Rotation rates do not match turning rates of individual ships.
    - Attacking ship's fixed turrets, (front lasers), have no idea where to aim as the attacker and attacked apply more strafing.
Anyway it's a start, I am planning on adding a simple state machine that will build up classic dog fighting manoeuvers out of smaller modules so that Immelmann, Split S, Scissors, etc, should all be possible.

The actual manoeuvers performed in this video are extremely simple.
  • - Each attacking ship is rotating in yaw and pitch deltas trying to always face the player.
    - To simulate a modicum of what players are used to with dog-fighting sims, the attacking ship does a proportional roll to it's yaw delta.
    - There is a constant up and right strafe vector applied to each attacking ship that is set to a percentage of it's maximum speed, (this should obviously be controlled by a state machine based on the manoeuver the ship is trying to perform and maybe could be even limited by a "can't use unless charged up" functionality).
    - A constant forward vector based on the ship's maximum speed, in reality I would like these super charged high level ships to do their utmost to stay behind the player causing maximum grief.
If you watch it until the end you'll notice a couple of bonuses, 1: set rotation/force position are not just limited to ships. 2: LIFE's dynamic news generates an article about the testing of the new attack run code :)


Technical:

X3's MSCI uses Euler angles http://en.wikipedia.org/wiki/Euler_angles, (specifically Tait–Bryan x-y-z), which is great when all you want is an extremely simple user interface for the interaction of 3D co-ordinate systems and your average Joe. Unfortunately this is not so great when the MSCI programmer wants to do more than just single axis rotations on static objects.

There's a couple of mandatory functions you need when working with Euler angles. 1: Euler to Bi-tangent, Tangent and Normal. 2: 3D vector to Euler. Because of the one to many and many to one mappings of Euler angles to 3d vector and vice versa, we will find ourselves in situations where one basis axis collapses onto another, (Gimbal Lock), thus leaving us with 2 Axis instead of 3 for our calculations and mathematical mayhem. With the system that X3 has chosen, the discontinuities occur in the vertical basis axis and are simple to resolve by restricting your calculations with no go zones.

All maths in the MSCI is 32 bit signed integer, given that I chose a fairly standard power of 10 +/- xxxxxxx.yyy fixed point format that allows for around +/-2147483 unique integers and 3 full decimal places of fractional component.

1: Converting Euler angles to basis vectors involves some trigonometry; assuming normalised vectors, (remember our normalised vectors will have a magnitude of 1000 because of the fixed point format, not a magnitude of 1), our basis vectors become the following:
- alpha = yaw, beta = pitch, gamma = roll, positive angles represent anti-clockwise rotations where 0-65536 = 0-360 degrees.
- for X3 alpha is world horizontal rotation, (around the world vertical axis), beta is world vertical rotation, (around the world horizontal axis), gamma is local tangent rotation, (around the object's forward direction).
  • * Bi-tangent, i.e. forward
    fwd.x = -cos(pitch) * sin(yaw)
    fwd.y = sin(pitch)
    fwd.z = cos(yaw) * cos(pitch)

    * Tangent, i.e. transverse
    tan.x = sin(yaw) * sin(pitch) * sin(roll) + cos(yaw) * cos(roll)
    tan.y = cos(pitch) * sin(roll)
    tan.z = cos(roll) * sin(yaw) - cos(yaw) * sin(pitch) * sin(roll)

    * Normal, i.e. up
    nrm.x = cos(roll) * sin(yaw) * sin(pitch) - cos(yaw) * sin(roll)
    nrm.y = cos(pitch) * cos(roll)
    nrm.z = -cos(yaw) * cos(roll) * sin(pitch) - sin(yaw) * sin(roll)
2: The next crucial functionality needed is the ability to convert a 3D vector to a set of Euler angles, to achieve this I am relying on the tried and trusted atan2 function which is a great fully quadrant happy version of atan, (arctan). You may be wondering how this is even possible given that the MSCI only exposes fixed sin and fixed cos, we'll get to that :)[/list]
So, given a 3D vector, how do we obtain yaw and pitch? *Note* you can't obtain roll from a 3D vector as there are an infinite number of rotations that vector can represent around it's local axis. If we had access to and were using Quaternions, this would be a different story. For the system X3 has chosen:
  • yaw = atan2(z, x) - PI/2

    pitch = atan(y, sqrt(x*x + z*z))
Why aren't we using atan2 for the pitch calculation? It's a simple optimisation, because sqrt(x*x + z*z), (the denominator), is always guaranteed to be a positive result which if you look at the following link, http://en.wikipedia.org/wiki/Atan2#Definition, collapses atan2 to just atan.

So how did I calculate atan in the MSCI, really simply actually. On the PC I generated two lookup tables, (as MSCI source code that I simply copy'n'pasted into an MSCI file), one for atan with a domain of -1 to 1 and another with a domain of -1000 to 1000. These two separate lookup tables of 2000 entries each give me the ability to not have to forgo resolution if I had tried to use just one lookup table to represent all of atan. atan has a domain of -infinity to +infinity but because I can guarantee that my normalised vectors will always be within the domain of -1000 to 1000 I can restrict my atan lookup table to this restricted domain.

Even though I am using two lookup tables to allow much better resolution and fidelity, 2000 entries for the -1000 to 1000 domain atan lookup table is nowhere near enough, so within my MSCI atan subroutine I use some simple linear interpolation to produce a higher resolution result. To validate this function I should compare it to the the math.h atan2 function across a wide variety of inputs, noting, min, max and avg errors, but as I am only after a visual effect and there are so many other sources of errors in the MSCI, (fixed point, non deterministic script execution, etc), there really was no need.


Old:

I'm adding NPC strafing to the leveling NPC's with my LIFE mod and have come up against a couple of issues.

Firstly here is my progress so far: So as you can see it works fine when running the idle command and works in varying degrees when running the internal attack run on target command based on the scale value I use for the strafing.

For these series of tests the strafing is a constant amount that is always on and is based on the ship's local basis tangent vector, i.e. all strafing is done to the right.

Questions:
  • * So why isn't the strafing script I have written that uses force position playing nice with the internal position update commands such as "attack run on target"?
    * Does the internal update code know about force position, i.e. is the update code's copy of the current position updated by force position, or does force position merely update the final position before the draw matrices are built without notifying anyone of the change in position?
    * Is the non-deterministic sample rate of the script I am running causing some of the force positions to be ignored or written over before they are visually seen by the internal position updates?
Experiments:
  • * I've tried running the script on a different task slot id.
    * I've tried running individual scripts, 1 per ship with different task slot id's.
    * I've tried running a global script that isn't associated with any object and hence task slots/commands.
If anyone has thoughts, ideas or maybe even some X3 source code in front of them, (Cycrow, Gazz), throw me a bone.

Cheers.
Last edited by Shush on Sat, 28. Dec 13, 03:14, edited 4 times in total.
User avatar
RoverTX
Posts: 1466
Joined: Wed, 16. Nov 11, 18:37
x4

Post by RoverTX »

This is most likely a stupid suggestion, have you just tried adding in a @ wait 1 ms somewhere so that it will actually go away from the fighting script in a way that won't be noticeable?

Also can you post how you make the call to the strafe script?
Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

Post by Shush »

RoverTX wrote:This is most likely a stupid suggestion, have you just tried adding in a @ wait 1 ms somewhere so that it will actually go away from the fighting script in a way that won't be noticeable?
Correct me if I am wrong, but the X3 MSCI scheduler is a co-operative multi-tasking scheduler running on the X3 engine which all runs on a single thread, (not counting IO, audio, system and DirectX threads). This means that if a script doesn't give up some X3 processing time, then that script is the only thing that is going to be running which will make X3 unresponsive until that script terminates.

For this reason I already have a wait inside my strafing loop :)

RoverTX wrote:Also can you post how you make the call to the strafe script?
Here are some examples, they all do the same thing, i.e. the way my strafing code and the internal ship update routines interact produces the same NQR results.

Code: Select all

* global 
START null-> call script 'plugin.LIFE.ship.ai' : arg1=$argShip arg2=$argStrafe arg3=null arg4=null arg5=null

* player ship
[PLAYERSHIP]-> begin task $taskID with script 'plugin.LIFE.ship.ai' and priority null: arg1=$argShip arg2=$argStrafe arg3=null arg4=null arg5=null

* individual ships
$argShip-> begin task $taskID with script 'plugin.LIFE.ship.ai' and priority null: arg1=$argShip arg2=$argStrafe arg3=null arg4=null arg5=null
Here's the inner loop that for current testing just runs for 120 seconds. It doesn't make a difference what value I set the wait timer to, except of course if I make it too big then the strafing movement starts to become non fluid.

Code: Select all

$time = playing time
$endTime = playing time
$endTime = $endTime + 120

while $time < $endTime
    gosub StrafeShip:

    = wait 10 ms

    $time = playing time
end
Nicoman35
Posts: 681
Joined: Thu, 17. Nov 05, 13:12
x3tc

Re: NPC strafing:

Post by Nicoman35 »

Shush wrote:* Strafing with scale=5
http://www.youtube.com/watch?v=F-nk-CRwuNY
Uhhh, what an unpleasant opponent. This will make me run form enemy even more than I do now.... "OH NO :o there comes another one of these killer machines - RUUUN"
User avatar
DrBullwinkle
Posts: 5715
Joined: Sat, 17. Dec 11, 01:44
x3tc

Re: NPC strafing:

Post by DrBullwinkle »

Shush wrote:So why isn't the strafing script I have written that uses force position playing nice with the internal position update commands such as "attack run on target"?
You are trying to compete with commands written in KC code. It is not surprising that those commands have different rules about giving up control to an MSCI script.
  • (Perhaps Jack08 will chime in? He knows his way around that sort of thing.)




If you can get this to work reliably, even if it only works under some conditions, then it would be a brilliant addition to the game.

Suggestion: Give this only to enemies. Or, at least, make the effect stronger for enemies. The player is already overwhelmingly dominant in the vanilla game; enemies need more help.
Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

Re: NPC strafing:

Post by Shush »

Nicoman35 wrote:
Shush wrote:* Strafing with scale=5
http://www.youtube.com/watch?v=F-nk-CRwuNY
Uhhh, what an unpleasant opponent. This will make me run form enemy even more than I do now.... "OH NO :o there comes another one of these killer machines - RUUUN"
Hehe well that was the idea; at scale 5 at 50 frames per second that Xenon L is strafing at around 250ms which is completely ridiculous, but it is interesting to watch how the strafing code and the underlying move/attack commands interact.

DrBullwinkle wrote:You are trying to compete with commands written in KC code. It is not surprising that those commands have different rules about giving up control to an MSCI script.
I can see where you are coming from, but I don't quite see it that way. What I am doing is modifying a ship's position using a script command, (hence I am actually using the underlying KC code), n times per second. Unfortunately this n times per second is merely a hint when using the wait timer, (as we all know), how many times it actually runs per second is completely dependent on factors like, universe size, scripting load, cpu speed, etc etc. i.e. probably completely non-deterministic, but that is a problem to solve after I solve this issue, (it may not even be a problem at all, as I am not after accurate physical strafing movement, all I am after is a believable visual strafe whilst making the enemy harder to hit.

Back to the updating of position: I think what's really happening is that there is some kind issue with the actual modification of the position by force position and that modification not being reflected back to the internal update routines, (i.e. move/follow/attack etc). In my script I obviously do a get position, calculate my strafe vector, add it to the position, (all done in fixed point), then update the ship's position with this new position. Now if the position I just modified is then used as the input to the next set of update calculations by the internal routines everything should be hunky dory, all I have done is basically add an offset to the ship's position that the internal routines shouldn't know or care about.

If the internal routines are keeping some sort of history or state of previous positions, (which I can't really fathom a need for), then that could cause this kind of issue, as I would be modifying the position causing it to be out of sync with what the internal routines expected it to be.

Anyway you're right I am sure someone can answer this question pretty quickly, it may just not be possible, these internal movement routines may have to be treated as indivisible operations if they are to produce correct movement patterns. With that said though I am doing exactly the same thing in my explosion shockwaves within LIFE and I don't see any of this behaviour, but that's probably because it's almost impossible to focus on a ship that is being accelerated away from the point of origin of an explosion with all the mayhem and screen shake that is going on.

DrBullwinkle wrote:If you can get this to work reliably, even if it only works under some conditions, then it would be a brilliant addition to the game.

Suggestion: Give this only to enemies. Or, at least, make the effect stronger for enemies. The player is already overwhelmingly dominant in the vanilla game; enemies need more help.
:) Yeah although not giving it to the player owned ships may just make the game close to impossible, imagine meeting up with 5+ well armed foes all strafing out of the kill zone of 99% of weapons that are not fast enough to keep up with them, (just like a player can do).
User avatar
DrBullwinkle
Posts: 5715
Joined: Sat, 17. Dec 11, 01:44
x3tc

Re: NPC strafing:

Post by DrBullwinkle »

Shush wrote:I can see where you are coming from, but I don't quite see it that way.
You took my comment out of context. :) . *In* context (of why your script competes with "attack run"), it is more intuitive.

But we are both guessing. Jack08 will know how it *really* works. I would not be surprised if he has already tried to do something similar.



And please do carry on. This could be cool!
User avatar
Jack08
Posts: 2993
Joined: Sun, 25. Dec 05, 10:42
x3tc

Post by Jack08 »

Disclaimer:
I haven't a clue how the sector engine(C++) actually works, This post is pure speculation based on my past exprience and observations, i haven't a clue if its right or not.
--

In order for a ship to attack, it needs to be pointing its guns perfectly at the target, by changing the position of the ship, even by a few meters, your invalidating that vector - and making the engine think it no longer has a target lock and needs to re-acquire.

You can see this behavior in standard fight scripts on ships that move fast, they will rotate, shoot, rotate, shoot - each time they lose target lock they rotate.

The main reason i can see for this happening is that the coordinates in MSCI have very little resolution, x = 1 is a grid point, when in actuality that position of "1" is treated by the game as position 500, it uses this resolution of 1=500 to avoid using floating point while still maintaining accuracy of 500'ths of a unit (0.002) - because we cant pass a coordinate with a resolution lower then 1.0, subtle adjustments to the ships position that wont throw out the target lock may not be possible.

There may also be some sort of state change in the engine when you issue the force position command that invalidates some other state required for the attack to work correctly, but this is just wild speculation about the C++ engine that only egosoft would know the true answers to.
[ external image ]
"One sure mark of a fool is to dismiss anything that falls outside his experience as being impossible."
―Farengar Secret-Fire
Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

Post by Shush »

Jack08 wrote:In order for a ship to attack, it needs to be pointing its guns perfectly at the target, by changing the position of the ship, even by a few meters, your invalidating that vector - and making the engine think it no longer has a target lock and needs to re-acquire.
Yeah I have seen this, I was hoping whatever state, (attack, flee, reacquire target etc), that the ship was putting itself in, that it would use the position updated by force position as if it had calculated it itself. Whether it is or not I can't determine, the reactions of the enemy ship with the strafe script applied are strange to say the least.

Jack08 wrote:The main reason i can see for this happening is that the coordinates in MSCI have very little resolution, x = 1 is a grid point, when in actuality that position of "1" is treated by the game as position 500, it uses this resolution of 1=500 to avoid using floating point while still maintaining accuracy of 500'ths of a unit (0.002) - because we cant pass a coordinate with a resolution lower then 1.0, subtle adjustments to the ships position that wont throw out the target lock may not be possible.
This has been the main issue all along and causes so much grief, minimum 1 meter resolution, no floating point, no obtainable direction, tangent and normal vectors...it's as if the X2 engine that this is all based on was designed not just pre hardware GPUs, but pre CPU floating point units.

I do get that representing a universe, (more accurately sectors), with longs solves a good many floating point precision problems, but it would have been trivial to use longs as the engine's main distance representation whilst using floats for local/relative distances and making those floats available to the scripting engine. Lets face it, they are converting those longs to floats in their engine eventually as DirectX9 doesn't understand anything but floats, especially in it's draw calls, shaders, world/view/projection matrices etc.

Jack08 wrote:There may also be some sort of state change in the engine when you issue the force position command that invalidates some other state required for the attack to work correctly, but this is just wild speculation about the C++ engine that only egosoft would know the true answers to.
This was my initial thought as well, (though I was thinking more specifically just position state), you are right it could be any kind of attack information based state that is causing something to periodically reset/get out of sync between the internal attack run and the strafe script.

It really seems as if these internal commands need to be treated as indivisible operations with respect to script commands.

The other option I have considered is writing my own attack run code by using global signals to bypass the internal attack scripts that call the internal attack commands. The problem with this is: non trivial amount of work, the 1 meter position resolution, the non deterministic sample rate, no means to measure frame time in ms, etc....i.e. so many hurdles I don't think it's even worth investigating with a simple test /sigh

Anyway some great info Jack, cheers.
User avatar
Jack08
Posts: 2993
Joined: Sun, 25. Dec 05, 10:42
x3tc

Post by Jack08 »

Actually, you can mesure frame time - its just totally undocumented as no one does it.

$frameTime = wait 1 ms

This command will return the exact amount of time waited in ms.
[ external image ]
"One sure mark of a fool is to dismiss anything that falls outside his experience as being impossible."
―Farengar Secret-Fire
Cronos988
Posts: 691
Joined: Mon, 27. Aug 07, 12:34
x3tc

Post by Cronos988 »

This is probably pretty off-topic, but I would be really interested to see what happens if you let ships without main guns (i.e. capital ships) strafe around. How do the turret scripts respond to that?

Edit: Also, have you tried just using attack runs with speedlimit 0m/s and no wait after the fire lasers on target command?
User avatar
RoverTX
Posts: 1466
Joined: Wed, 16. Nov 11, 18:37
x4

Post by RoverTX »

In one of the videos he posted I believe that when an M3 strafed its turrets also stopped firing. Though I might not have been looking close enough.

Edit: Saw it in the 3rd video, the one where scale= 1. Now looking at it again I am unsure if it stopped because of the strafe command or because the strafing caused the player ship to no longer be in LoS to the turret >.>;
Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

Post by Shush »

Jack08 wrote:Actually, you can mesure frame time - its just totally undocumented as no one does it.

$frameTime = wait 1 ms

This command will return the exact amount of time waited in ms.
Nice, like you say I never noticed either. Does it return the time since the last frame or the time that the last call to wait was satisfied in?

Hmm thinking about it, if the value is set to something that is smaller than the previous elapsed frame time, then it probably does return the frame time. I'll have a play with this.

Cronos988 wrote:This is probably pretty off-topic, but I would be really interested to see what happens if you let ships without main guns (i.e. capital ships) strafe around. How do the turret scripts respond to that?

Edit: Also, have you tried just using attack runs with speedlimit 0m/s and no wait after the fire lasers on target command?
I've tried it with little ships, big ships and huge ships. I've also tried it with other internal commands like "defensive move" and "follow".

The position syncing issues don't seem to be specific to attacking, any internal position updating command that the ship is running gets out of sync when I have this strafing script applied :/

P.S. I did try it on a player owned ship running idle with a call to "attack run on target" as soon as the player ship got within 2km. The exact same position syncing issue reared it's ugly head, there doesn't seem to be any simple way around this for now.

It would be excellent if Cycrow could chime in here, as I am pretty sure he has complete access to this part of X3's source code.

RoverTX wrote:In one of the videos he posted I believe that when an M3 strafed its turrets also stopped firing. Though I might not have been looking close enough.

Edit: Saw it in the 3rd video, the one where scale= 1. Now looking at it again I am unsure if it stopped because of the strafe command or because the strafing caused the player ship to no longer be in LoS to the turret >.>;
I think the attacking state is a red herring, there are position syncing issues with all the internal movement commands as above :/

I'll post a video after Christmas of where I am at with my strafing script, it's now not calling any internal movement commands whatsoever; this means it is doing all the trig and vector manipulation for rotating towards target, attack runs, fleeing, strafing etc.

It surprisingly works pretty well in performance terms, I've run it on 100 simultaneous ships, (in sector), and the frame rate was fine.

Where it fails is in anything that depends on fine position control, (which we don't have access to from the MSCI):
  • * high rate turns look clunky.
    * high rate of change of strafe looks clunky; I implemented an attack run that had a corkscrew strafe applied. For small values of strafe delta's it actually looked pretty good, but as soon as those delta's got larger it quickly turned into a train wreck.
    * lack of control over speed, all speed is a multiple of 1 meter traveled per script evocation. e.g. if the script runs at 100 times per second, then minimum speed is 100 m/s, next available speed is 200 m/s, (although I am using fixed point with resolution of 0.001 so clunky/jerky in-between speeds can be hacked).
    * speed readout is always 0
    * non determinism of how often the script is actually run and without a reliable frame time measure this means variable frame rates will cause incorrect speed calculations, (*Note* Jack08 may have come up for solution for this).
    * Non trivial implementation, not only do I have to fully implement my own attack runs and defensive flees, I need to seamlessly integrate this script with the current ship command system as obviously I don't want to be implementing all of the movement logic, (only stuff that involves attacking/strafing).
So for now I'm doing this purely as a thought exercise to see how far I can take it, it could never be used to replace all in sector attack behavior, but I still feel there is a slim chance that it could be used for a select group of ships, (e.g. my leveling NPC's in LIFE), as a way of making combat much more interesting.
User avatar
Jack08
Posts: 2993
Joined: Sun, 25. Dec 05, 10:42
x3tc

Post by Jack08 »

Nice, like you say I never noticed either. Does it return the time since the last frame or the time that the last call to wait was satisfied in?

Hmm thinking about it, if the value is set to something that is smaller than the previous elapsed frame time, then it probably does return the frame time. I'll have a play with this.
It returns the time waited, with a 1ms wait, your almost guaranteed to receive the update every frame, unless your frame-rate drops below like 5 fps and the engine starts to backup with calls to make.

What your receiving here isnt the time of the rendering frame,, but rather the time of the script frame, spesific to this co-routine wait call - which in this case is more useful, getting the exact time of last frame when that time was recorded 3ms ago (for example) is less uesful :D
[ external image ]
"One sure mark of a fool is to dismiss anything that falls outside his experience as being impossible."
―Farengar Secret-Fire
Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

Post by Shush »

Jack08 wrote:It returns the time waited, with a 1ms wait, your almost guaranteed to receive the update every frame, unless your frame-rate drops below like 5 fps and the engine starts to backup with calls to make.
I must be missing something obvious, as wait always return 0 for me, no matter what value I pass to it.
Shush
Posts: 244
Joined: Sat, 6. Dec 03, 16:21
x4

Post by Shush »

Updated the original post with lots of info and a new video http://youtu.be/ywbzyvG2HPM
User avatar
Jack08
Posts: 2993
Joined: Sun, 25. Dec 05, 10:42
x3tc

Post by Jack08 »

Shush wrote:
Jack08 wrote:It returns the time waited, with a 1ms wait, your almost guaranteed to receive the update every frame, unless your frame-rate drops below like 5 fps and the engine starts to backup with calls to make.
I must be missing something obvious, as wait always return 0 for me, no matter what value I pass to it.
Very strange, i am seeing this too - I used to use this in TC im not sure why its not working anymore :(
[ external image ]
"One sure mark of a fool is to dismiss anything that falls outside his experience as being impossible."
―Farengar Secret-Fire

Return to “X³: Terran Conflict / Albion Prelude - Scripts and Modding”