X3AP: Laserpointer Defense System {WIP} - need performance advice

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

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

Post Reply
User avatar
Quinch
Posts: 362
Joined: Thu, 10. Jun 04, 01:09
xr

X3AP: Laserpointer Defense System {WIP} - need performance advice

Post by Quinch » Mon, 2. Sep 13, 17:17

Short version, I'm putting the finishing touches on a script that allows the player to let lasertowers pick their targets. Problem is - when I run it, I get major slowdowns, which probably has to do with six thousand lasertowers constantly checking their surroundings for hostiles using the 'find ship' command. Can anyone offer an alternative? The code can be found at https://dl.dropboxusercontent.com/u/335 ... ointer.rar * , but I'll paste the relevant part below.

Also, currently 'attack largest ship' is the only priority implemented - other menu items, such as automatic renaming and selective scoping, should be functional.

Code: Select all

* assign constants, i.e. values that are not expected to change
* page 8060 should be loaded by 'setup.Quinch.Laserpointer'
$rename.pattern.default = read text: page=8060 id=1411
$current.sector = [THIS]-> get sector

* class threat table, going from highest to lowest
$class.table = array alloc: size=0
append [M2] to array $class.table
append [M1] to array $class.table
append [M7] to array $class.table
append [M6] to array $class.table
append [M8] to array $class.table
append [M3] to array $class.table
append [M4] to array $class.table
append [M5] to array $class.table
append [TM] to array $class.table
append [TL] to array $class.table
append [TP] to array $class.table
append [TS] to array $class.table
append [Missile] to array $class.table
* \\\class threat table\\\

* first, pull up all the local variables and set defaults if needed
$rename = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Rename'
if $rename == null
[THIS] -> set local variable : name='Quinch.Laserpointer.Menu.Rename' value = 'Rename.Unchanged'
$rename = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Rename'
end

$rename.pattern = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Rename.Pattern'
if $rename.pattern == null
[THIS] -> set local variable : name='Quinch.Laserpointer.Menu.Rename.Pattern' value = $rename.pattern.default
$rename.pattern = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Rename.Pattern'
end

$priority = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Priority'
if $priority == null
[THIS] -> set local variable : name='Quinch.Laserpointer.Menu.Priority' value = 'Priority.Class.Hi'
$priority = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Priority'
end

$racial = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Racial'
if $racial == null
[THIS] -> set local variable : name='Quinch.Laserpointer.Menu.Racial' value = 'Racial.Attack'
$priority = [THIS] -> get local variable : name='Quinch.Laserpointer.Menu.Racial'
end
*****************
* first thing to do is to rename the tower, if needed. This is done only once per task start, as it would be too resource-consuming to constantly update
* $s: Current sector
* $n: Nearest neighboring sector
* $g: Nearest gate
* $p: Target priority

if $rename == 'Rename.Change'
$new.name = $rename.pattern
* pull up wildcard information 
$sector = [THIS] -> get sector
$gate = find gate: flags = [Find.Nearest], refobj = [THIS], max dist= 999999, refpos = null
$nearest.sector = $gate -> get gate destination: return sector=[TRUE]
* because string substitution doesn't work if the variable is a reference to an object, we need to force them into strings
$sector = $sector + ''
$gate = $gate + ''
$nearest.sector = $nearest.sector + ''

* set priority shorthand
if $priority == 'Priority.Class.Hi'
$priority.string = 'C+'
else if $priority == 'Priority.Class.Lo'
$priority.string = 'C-'
else if $priority == 'Priority.Shields.Hi'
$priority.string = 'S+'
else if $priority == 'Priority.Shields.Lo'
$priority.string = 'S-'
else if $priority == 'Priority.Weapons.Hi'
$priority.string = 'W+'
else if $priority == 'Priority.Weapons.Lo'
$priority.string = 'W-'
else if $priority == 'Priority.Proximity.Hi'
$priority.string = 'C+'
else if $priority == 'Priority.Proximity.Lo'
$priority.string = 'C-'
else if $priority == 'Priority.Shields.Hi'
$priority.string = 'S+'
else if $priority == 'Priority.Shields.Lo'
$priority.string = 'S-'
end

$new.name= substitute in string $new.name: pattern '$s' with $sector
$new.name = substitute in string $new.name: pattern '$n' with $nearest.sector
$new.name = substitute in string $new.name: pattern '$g' with $gate
$new.name = substitute in string $new.name: pattern '$p' with $priority.string
[THIS] -> set name to $new.name
end

while [TRUE]
* Get the engagement distance, i.e. the maximum range at which the lasertower will start appraising and engaging hostiles. 
* This needs to be constantly refreshed, as pre-active towers have no equipped weapons
$equipped.laser = [THIS]-> get laser type in bay 0
$engagement.range = get range of laser $equipped.laser

* STANDBY MODE: Normally, the lasertower will only ping nearby ships every 5-6 seconds, since they spend most of their time idling
$find.flags = [Find.Enemy] | [Find.Multiple] | [Find.Nearest]
$find.ships = [Ship] | [Missile]
$enemies = find ship: sector=$current.sector class or type= $find.ships race=null flags=$find.flags refobj=[THIS] maxdist=$engagement.range maxnum=9999 refpos=null
$enemies.count = size of array $enemies

* ALERT MODE: If there are enemies in range, start responding immediately. Loop time goes down to 1-1.5 seconds and continues up to ten seconds after there are no more hostiles in range
if $enemies.count > 0
* enemies loop
$enemies.index = 0
while $enemies.index < $enemies.count
$enemy = $enemies[$enemies.index]
******************************
* first, check the racial variable in case the enemy belongs to a friendly race
if $racial == 'Racial.NoAttack'
* second, check the enemy race and player relations
$race = $enemy -> get owner race
$relation = [THIS] -> get relation to race $race
* if the race relation is not set to foe, ignore it and fall through to the next enemy
if $relation != [FOE]
gosub next.enemy:
end
* otherwise, proceed with target acquisition
end
******************************
* FORK: PRIORITY BY CLASS - DESCENDING
if $priority == 'Priority.Class.Hi'
* PRIORITY BY CLASS
$priority.rank = 999
* get the current enemy class
$class = $enemy -> get object class
* compare the enemy class against the current priority target
$current.rank = get index of $class in array $class.table offset=0
* if the current target has higher priority rank than previous one, assign it as priority
 if $current.rank < $priority.rank
$priority.target = $enemy
end
end

* TO DO

next.enemy:
inc $enemies.index
end
* ///enemies check
= [THIS]-> call script '!fight.attack.object' : victim=$priority.target follow=[FALSE]
end
= wait randomly from 5000 to 6000 ms
end
return null
* testing and feedback is appreciated - I'm hoping to add additional fine-tuning features if there's enough interest, but I want to make a working prototype first
Last edited by Quinch on Mon, 2. Sep 13, 18:46, edited 1 time in total.
I wasn't banished to the moon yesterday.

shabitz
Posts: 59
Joined: Sun, 21. Jul 13, 19:54
x4

Post by shabitz » Mon, 2. Sep 13, 17:46

I'm not a coder at all but would there be the possibility of making some sort of sensor node which it does the scanning and the laser towers attack it's target? Maybe have the lasertowers home base to the sensor node. I don't even know if this is possible I'm pulling stuff outta my a$$.

Sirrobert
Posts: 1213
Joined: Wed, 21. Aug 13, 13:55
x3ap

Post by Sirrobert » Mon, 2. Sep 13, 17:52

I don't know anything about scripting, but from what I understand your lasertowers are constandly checking for enemys.

Maybe you could have the scripts be inactive until enemys show up near them.
That way you'd significantly reduce the amount of lasertowers where the script would be active

User avatar
Quinch
Posts: 362
Joined: Thu, 10. Jun 04, 01:09
xr

Post by Quinch » Mon, 2. Sep 13, 18:00

shabitz wrote:I'm not a coder at all but would there be the possibility of making some sort of sensor node which it does the scanning and the laser towers attack it's target? Maybe have the lasertowers home base to the sensor node. I don't even know if this is possible I'm pulling stuff outta my a$$.
Well, the way I would use it {heck, the reason I came up with it} would be for tight clumps of towers guarding gates to hostile sectors, but if I'm going to release it into the wild, it would need to more versatile than that.
Sirrobert wrote:I don't know anything about scripting, but from what I understand your lasertowers are constandly checking for enemys.
That's my understanding too - for example, all the towers I have so far don't cause much of a drain if left to their default behavior, which means that there is something that's, for lack of a better word, cost-efficient that they're tapping into. My problem is, I haven't had any luck tracking down just what exactly controls lasertowers' default behavior so I can reverse-engineer something useful out of it.
I wasn't banished to the moon yesterday.

shabitz
Posts: 59
Joined: Sun, 21. Jul 13, 19:54
x4

Post by shabitz » Mon, 2. Sep 13, 18:22

Sounds like a really cool idea however you're going to approach it. I just wish there was someway of getting laser towers off the map, or maybe have them reclassified as an object type instead of ship to push them down to the bottom of the list, under asteroids. I gotta ask how are you able to manage running so many laser towers in a system and still be able to quickly find ships? I find that the most I can deal with is about 20 to 30 before the list becomes too long. I guess just flip over to the ship tab, but I almost never do. There's enough space there don't know why they didn't make a 4th tab or make them customization. Sorry if I'm getting off track a bit, just so many towers clutters up the view.

Ya know how there's a complex cleaner mod that crunches factories, it would be kinda cool to have a complex of crunched towers into one laser defense complex. ... Deathstar !

User avatar
Quinch
Posts: 362
Joined: Thu, 10. Jun 04, 01:09
xr

Post by Quinch » Mon, 2. Sep 13, 18:44

shabitz wrote:I find that the most I can deal with is about 20 to 30 before the list becomes too long. I guess just flip over to the ship tab, but I almost never do. There's enough space there don't know why they didn't make a 4th tab or make them customization. Sorry if I'm getting off track a bit, just so many towers clutters up the view.
I feel your pain - unfortunately, all I can do is mash the page down key.
Ya know how there's a complex cleaner mod that crunches factories, it would be kinda cool to have a complex of crunched towers into one laser defense complex. ... Deathstar !
I've toyed with the idea myself - a polyhedron model with a laserbeam turret on each side. There would probably need to be some number tweaking in terms of shields and/or damage output as well. Unfortunately, I'm still getting started with X3 scripting in general, so making new ship models, ware types, factories and so on is out of my league for now.
I wasn't banished to the moon yesterday.

SirDeathwalker
Posts: 385
Joined: Fri, 6. Aug 10, 03:15
x4

Post by SirDeathwalker » Sat, 21. Sep 13, 05:30

I found that the ring of fire script works for laser towers (with cloaking, so they don't clutter up things works well (attacks anything hostile then re-cloaks until needed)

http://forum.egosoft.com/viewtopic.php?t=221915

hope this helps

(laser towers cloak until needed, de-cloak to repel hostiles, then re-cloak, when cloaked, do not appear on the list of ships on the sector map)

EDIT: Ring of fire with sufficient density will trump DCS... and it is pretty to watch the fireworks to boot.

Ningyo
Posts: 16
Joined: Wed, 7. Dec 11, 08:26

Post by Ningyo » Sat, 21. Sep 13, 06:49

Well a few ideas and haven't done much scripting yet, so not sure how workable they are.

Code: Select all

$equipped.laser = [THIS]-> get laser type in bay 0 
$engagement.range = get range of laser $equipped.laser 

* STANDBY MODE: Normally, the lasertower will only ping nearby ships every 5-6 seconds, since they spend most of their time idling 
$find.flags = [Find.Enemy] | [Find.Multiple] | [Find.Nearest] 
$find.ships = [Ship] | [Missile] 
$enemies = find ship: sector=$current.sector class or type= $find.ships race=null flags=$find.flags refobj=[THIS] maxdist=$engagement.range maxnum=9999 refpos=null 
$enemies.count = size of array $enemies
first you know the weapons max range, so unless you want to leave this as a more all purpose script you can just set $engagement.range to about the max range of a laser tower. Secondly set it to a number that it is easy to get a square root of since this is likely calculated every time it find the range of an enemy (5927, or 6084 are good {EDIT: this is probably wrong I was assuming meters, but the actual in game measurement is likely done in 2cm increments. So 2999824 might be a better number}).



Beyond this it might be faster to have your script only run at all when enemies are in the same sector as a Lasertower. I think some other scripts that run constantly check for enemies in sector so you could insert a line in one of those scripts to activate your script when enemies exist.

Code: Select all

= wait randomly from 5000 to 6000 ms
another option would be to modify this number based on distance of enemy from lasertower, (10000ms - target distance for instance)


Hmm was looking and I don't see any delay at all during your active loop. That could be your largest problem, or am I not seeing it?

Nicoman35
Posts: 681
Joined: Thu, 17. Nov 05, 13:12
x3tc

Post by Nicoman35 » Sat, 21. Sep 13, 10:11

The 'find ship' command is very time consuming and leaves a big cpu footprint.
Reason: When searching for objects at a certain distance from a source object, all specified objects inside a sector will be registered. Then, for every object the three coordinates x, y and z will be compared with the Pythagorean theorem to get the distance between the source object and the currently examined object.
Using the Pythagorean theorem, a radical has to be made to get the distance. When making a radial of a number, the cpu has to do a quite a work untill it gets an relative exact result.
Countermeasures:
1. use $enemies = find ship: sector=$current.sector class or type= $find.ships race=null flags=$find.flags refobj=[THIS] maxdist=null maxnum=9999 refpos=null
to find every object. So no distance check has to be made.
2. To get the distance, make a distance check not using any radical. Simply calculate without it. Example:
You want all objects not more than 10m away.
Our source object is at 0,0,0. One of the objects you search is at 5,6,4. Normaly this is to be made to get the distance
distance=radial(5*5 + 6*6 +4*4) = 8,7749643873921220604063883074163
so, the object is inside the search range -> positive. As said this way is very time consuming for the cpu.
How about this: Range = 10 * 10 = 100
The range of the object is d=(5*5 + 6*6 +4*4) = 77
77 < 100 -> object is inside the search range.
Get the point? Instead of doing any radials, you only need a multiplication.
I *think* this is lower cpu footprint than the easy way of measuring distances.

User avatar
Quinch
Posts: 362
Joined: Thu, 10. Jun 04, 01:09
xr

Post by Quinch » Sun, 22. Sep 13, 05:32

First off, thanks to everyone from your input. I kinda gave up on the idea until the thread picked back up again, so yay!

Anyway, the rundown/ideas. Apparently the lasertower default behavior is hardcoded, which allows them to short-circuit things that regular scripts have to go through.
Reason: When searching for objects at a certain distance from a source object, all specified objects inside a sector will be registered. Then, for every object the three coordinates x, y and z will be compared with the Pythagorean theorem to get the distance between the source object and the currently examined object.
Huh. It would make more sense for the find ship command to prioritize the less CPU-intensive filters first {i.e. get all enemy ships in the sector, then filter out the ones outside the scan range}, but I guess that's MSCI for you. It does give me an idea, though.
first you know the weapons max range, so unless you want to leave this as a more all purpose script you can just set $engagement.range to about the max range of a laser tower.


Problem with hardcoding it would be that if a mod changes the laser range {for example, XRM replaces the weapon with another one altogether}, it would end up using the wrong data.


So, with the new suggestions in mind... what's your take on this kind of flow?

1: When the command is committed through the menu, the script checks the universe and loads all sectors containing player-owned lasertowers into a global variable array.

2: Over the course of every five seconds {subject to tweaking}, it checks each sector for hostile ships. This would be gradual - for example, if there were ten sectors containing lasertowers, it would wait half a second between checking another sector.

3: If enemy ships are present, fire up the sector's lasertowers.

4: Lasertowers load all enemy ships into an array, regardless of distance - each ship is then checked for distance, and discarded if out of range.

5: The remaining ships are then prioritized and engaged.

6: When there are no more enemy ships in the sector, the towers go back into idle mode until the sector scanner wakes them up again.


What do you think? Any serious bottlenecks or fail states in this version?
I wasn't banished to the moon yesterday.

Nicoman35
Posts: 681
Joined: Thu, 17. Nov 05, 13:12
x3tc

Post by Nicoman35 » Sun, 22. Sep 13, 11:05

Quinch wrote:1: When the command is committed through the menu, the script checks the universe and loads all sectors containing player-owned lasertowers into a global variable array.

2: Over the course of every five seconds {subject to tweaking}, it checks each sector for hostile ships. This would be gradual - for example, if there were ten sectors containing lasertowers, it would wait half a second between checking another sector.

3: If enemy ships are present, fire up the sector's lasertowers.

4: Lasertowers load all enemy ships into an array, regardless of distance - each ship is then checked for distance, and discarded if out of range.

5: The remaining ships are then prioritized and engaged.

6: When there are no more enemy ships in the sector, the towers go back into idle mode until the sector scanner wakes them up again.
Sounds good for me :).

Sotanaht
Posts: 22
Joined: Mon, 30. Sep 13, 08:02
x3ap

Post by Sotanaht » Thu, 10. Oct 13, 17:43

Quinch wrote: Well, the way I would use it {heck, the reason I came up with it} would be for tight clumps of towers guarding gates to hostile sectors, but if I'm going to release it into the wild, it would need to more versatile than that.
If you come up with a way to get tight clumps of gate-defending LTs to hog less of my computer resources, even if that's the ONLY thing your mod does, I would implore you to go ahead and publish it.

Vayde
Posts: 850
Joined: Fri, 6. Feb 04, 21:02
x3tc

Post by Vayde » Thu, 10. Oct 13, 19:14

What happens to those ships that are out of range on the first array sort. Is the array repopulated/refreshed until no enemy ships are detected?

Vayde
Still life in the old dog yet...

User avatar
Quinch
Posts: 362
Joined: Thu, 10. Jun 04, 01:09
xr

Post by Quinch » Thu, 10. Oct 13, 19:38

A ship that's out of laser range is simply dropped from the array. Thus, if all ships in the sector are outside the range, there's no targets for the laser to pick from, and thus no script is activated.
I wasn't banished to the moon yesterday.

Post Reply

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