WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Discuss game development here, from a distinct game project to an accessible third-party mutator, down to the interaction and design of individual units if you like.

Moderator: Moderators

Post Reply
[Fx]Doo
Posts: 66
Joined: 30 Aug 2013, 16:39

WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by [Fx]Doo »

Hello!

I am looking for an effective way to implement this behaviour for most units with weapons in Balanced Annihilation.
https://www.youtube.com/watch?v=TcOUcNOuNaM

The current trick i used to achieve this video was:
WeaponDefs: Create a dummy weapon from the original weapon with these differences:
FireTolerance = 1, => Increase aimweapon call rate
AvoidFriendly/ground/feature = false, ==> Get the aim ready even if cannot shoot yet

UnitDefs: Assign dummy weapon to a new slot, and slave it to the original weapon so they always have the same targets

UnitScript: Create an AimWeapon3 script for the dummy weapon, that is the same as the original's, except that it always returns (0) (or false) and doesn't send any aim signal (that would kill the AimWeapon1 thread).

The result is a unit that constantly adjust its aim from the dummy weapon because its firetolerance never allows it have a fine aim. Since the script returns false, there is no chance that the dummy shoots even if (with a lot of luck), the firetolerance condition is obtained.

Since the moved pieces are the original weapons', but the run of the dummy weapon script doesn't block the orginal's, it doesn't prevent it from reaching its goal and fire. The result is a constant readjusting of the pieces orientation rather than an occasional that happened only when the last aim was off and couldn't allow a new shot.

In game, it results in units being able to follow their target, and not lose their dps when manoeuvering (provided that the turret turn speed is higher than the unit's turnrate).

Thing is, this is a little expensive for "just" a behaviour that should have been default to the engine:
- Target anticipation: get the weapons ready even if the target still isn't reachable, so that when it is, the weapons will be able to fire almost instantly.
- The turretspeed should be the only factor in determining if a unit will lose dps upon turning or aiming moving targets. That is if the turretspeed is lower than the turnrate, then it will not follow properly and miss shots, or if the target moves too fast, the turret will not be able to follow its movements.



I tried some other ways to achieve a similar result without have to create/use dummy weapons. The closest was attained by combining allowNonBlockingAim = true and a lower fireTolerance. The low fire tolerance would force a higher aimweapon call rate, while each new call wouldn't block the last one.
I had guessed that then, the only factor that would allow or disallow the shot to actually happen would be if the turret orientation allows the shot (angle < firetolerance/2). But from trying ingame, shots would happen even if the current orientation of the turret is completly opposed to the shot trajectory (180 degrees > firetolerance/2).

After reading what firetolerance does, it seemed it compared the last weapon's called heading/pitch with the requested one, and if they are too different, block the fire and call the new aim. That probably means that, since the previous call was in the right direction, even if the pieces rotation havent happened, the old heading/pitch are in accordance with the requested ones.

In anycase, relying on firetolerance and allownonblockingaim still wont allow any anticipation to happen since if the lof isn't free or if distance > range, it won't even call any aimweapon script.

Before I spend xx hours getting this done the dirty way, if someone actually has a more convenient option, i'm all ears. :)



Also, about the range, can I get some explanations on how does a unit decides at which range it should place itself when fighting an enemy?
When I artificially increase a unit's MaxRange, it will place itself at a distance from which it cannot shoot (= maxRange), but if I artificially lower it, the weapon's max range will override it, and it will stop closing on its target as soon as one of its weapon can aim it (=maxWeaponRange).
I guess the meaning is that the "engage" range is max(maxRange, maxWeaponRange) where maxRange is the one showing in tooltip, that can be changed via gadgets, and maxWeaponRange is the one set in the unitDefs, by setting the weapons.

The issue here is that, if I want my unit to anticipate incoming targets, i have to set the dummy weapon to a higher range, but then the unit won't engage properly and stay at this maxWeaponRange distance even if I actually lower the maxRange via gadget. Also in other cases, it broke some units' attack order because they had secondary weapons with higher range that would make it stay to far away from target.
Google_Frog
Moderator
Posts: 2464
Joined: 12 Oct 2007, 09:24

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by Google_Frog »

Your method looks reasonable. Are you effectively just making your weapons call AimWeapon at a high rate? It feels like this should be a parameter in the weapondef if you are going to go to this much effort to hack around it.

An alternate solution would be to use Spring.GetUnitWeaponTarget and Spring.GetUnitPiecePosDir in a thread to update the horizontal aiming of your weapon every frame. The vertical aiming would be tricky to implement for arcing projectiles so for those I would just use the value provided by AimWeapon. This method is cleaner in the sense that you only need to touch the script files. I do not know which would be faster.
[Fx]Doo
Posts: 66
Joined: 30 Aug 2013, 16:39

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by [Fx]Doo »

these can be used to schedule a weapon's AimWeapon script callin
reaimTime changes the *maximum* interval between calls (defaults
to 15 frames), forceAim advances or delays the next call
By setting reaimTime to say 1 frame, that would force the next AimWeaponX script to happen after 1 frame?
Does it count: After the last fire, After the last AimWeaponX call, after the last AimWeaponX return ?
I guess that would mostly need allowNonBlockingAIm in the case the time is < 15 frames?
This indeed seems interesting, although it only bring the "cannon follows target countinuously" behaviour, but not the part where it anticipates and start aiming at units that are in range but have no free LoF.

I really hope to see some WeaponDef tags for this, aswell as a separate tag from avoidFriendly/ground/Feature.
One that would allow aiming to not avoid friendly/ground/feature, but not necessarily to shoot (firing would depend on avoidFriendly/Ground/Feature)...
Maybe call it aimAvoidGround, or whatever, but that would probably be appreciated in quite alot of situations.

Anyway, i'm glad to see this will be coming for the next version. Whatever the state of my attempt at it, I at least know that it is coming in the future :D
raaar
Metal Factions Developer
Posts: 1094
Joined: 20 Feb 2010, 12:17

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by raaar »

I'm looking for this too.

Apparently setting allowNonBlockingAim=1 and fireTolerance= (low value, < 1k) should achieve this in many cases, but it doesn't work: allowNonBlockingAim overrides firetolerance and the unit just fires regardless of where it's aiming.

In many cases you have other logic on the aim script and want to rely on the "return true" to control if firing is allowed (for example, to check/wait for a popup animation, or just raising an arm that was pointing downward).

If we use the standard blocking aim, there's another problem : aim calls wait for the "return true", the wait-for-turn calls have a minimum delay of 1 frame, and the typical signals/maks to restart the animation on every call. If you call them on every frame they never finish so the unit won't fire.

Even at aiming every 15 frames, these delays affect the actual damage per second. This is most noticeable on weapons that fire like 4+ times per second (without bursts), like some machineguns or continuous beams, and end up firing at a slightly slower pace due to waiting an extra frame two times per second (actual dps is lower than expected by 10-20%).

what I ended up doing on metal factions was keeping the blocking aim, but setting firetolerance=tolerance (about 3k in most of the relevant cases) in weapondefs_post and, for units with fast firing weapons, modified most of my aim scripts to do a tolerance check and skip the "wait-for-turn" if the current weapon orientation is within tolerance of the desired one.

Another situation that may require custom scripting is when you have two different weapons on the same turret (conflicting) and try to have one or both aim at a faster rate. For those i often set one weapon as slave to the other. It would be useful to have a configuration options to have high reload time weapons not try to aim for X fraction of their reload time to avoid disrupting the aiming of other weapons that use the same turret/torso.
Google_Frog
Moderator
Posts: 2464
Joined: 12 Oct 2007, 09:24

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by Google_Frog »

This is in the engine now and works.

https://youtu.be/HJ-zYJhVPNE
raaar
Metal Factions Developer
Posts: 1094
Joined: 20 Feb 2010, 12:17

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by raaar »

This is an important development!

how does this new "reaimtime" attribute work?
sprunk
Posts: 100
Joined: 29 Jun 2015, 07:36

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by sprunk »

It changes how often the weapon calls script AimWeapon (the maximum value). The default value is 15 frames (0.5s).
Kloot
Spring Developer
Posts: 1867
Joined: 08 Oct 2006, 16:58

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by Kloot »

[Fx]Doo wrote:Target anticipation: get the weapons ready even if the target still isn't reachable, so that when it is, the weapons will be able to fire almost instantly.
Spring.SetUnitWeaponState(unitID, weaponNum, "autoTargetRangeBoost", 123.456)

(only available in Spring 354-gb7b7c7a and above, update now to get a free discount)
[Fx]Doo
Posts: 66
Joined: 30 Aug 2013, 16:39

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by [Fx]Doo »

I tried this for a bit, but this kind of ruins target prioritizing. It often prioritizes a unit that it cannot shoot yet rather than the lighter one in its range.
That would prolly be fixed if i was using LUS for all units and could setup target priority with the relevant callin.

Maybe a targetpriority factor could allow to only target units outside of range when there are none inside of range?
Kloot
Spring Developer
Posts: 1867
Joined: 08 Oct 2006, 16:58

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by Kloot »

Default priorities have been tweaked in 429-gd987965 so out-of-range targets always end up on the bottom of the pile.

AllowWeaponTarget can be hazardous to performance, use it only if you want a totally different ordering than is generated by the engine's los/range/damage/health prioritization scheme.
[Fx]Doo
Posts: 66
Joined: 30 Aug 2013, 16:39

Re: WeaponDefs/UnitDefs/Scripts setup for a "continuous aiming"

Post by [Fx]Doo »

I finally have something that is quite effective for COB scripts and using a 1 frame delay between each aim (using SetUnitWeaponState())

A gadget calls "SetWeaponXTurretSpeed" when custom params exist, another one sets up the reaim time
Most of the things to know are explained in the weaponcontrol helper.
I hope it'll be useful for gamedevs who didn't use LUS yet. (Actually, even if you do, i'm willing to bet a similar implementation would still be better than having to rely on wait-for-turn).

armpw script (i just erased the anim parts since they're kinda irrelavant and long...):

Code: Select all

#define TA			// This is a TA script

#include "sfxtype.h"
#include "exptype.h"
#include "unitDefsTurretSpeeds.h"

piece  torso, ruparm, luparm, rfire, lfire, rloarm, lloarm, pelvis, rthigh, lthigh, lleg, rleg, rfoot, lfoot, rgun, lgun, ground, aimx1, aimy1;
static-var  bMoving, bAiming, Static_Var_3, gun_1, restore_delay, moveSpeed, currentSpeed, burst1 ;

#include "weapon1control.h"

// Signal definitions
#define SIG_AIM				4


walk()
{
	--walk anim
}

walklegs()
{
	--walklegs anim
}

MotionControl(moving, aiming, justmoved)
{
	justmoved = TRUE;
	while( TRUE )
	{
		moving = bMoving;
		aiming = bAiming;
		if( moving )
		{
			if( aiming )
			{
				Static_Var_3 = 1;
				call-script walklegs();
			}
			else
			{
				Static_Var_3 = 0;
				call-script walk();
			}
			justmoved = TRUE;
		}
		else
		{
			Static_Var_3 = 1;
			if( justmoved )
			{
		-- restore legs
				if( !aiming )
				{
			-- restore upper body
				}
				justmoved = FALSE;
			}
			sleep 100;
		}
	}
}

UnitSpeed()
{
	while(TRUE)
	{
		currentSpeed = 2 * (get CURRENT_SPEED)*100/moveSpeed;
		if (currentSpeed<40) currentSpeed=40;
		sleep 142;
	}
}

SmokeUnit(healthpercent, sleeptime, smoketype)
{
--smoke unit
}

Create()
{
	hide rfire;
	turn rfire to x-axis <10> now;
	hide lfire;
	turn lfire to x-axis <10> now;
	bMoving = FALSE;
	bAiming = FALSE;
	Static_Var_3 = 1;
	gun_1 = 0;
	moveSpeed = get MAX_SPEED;
	currentSpeed = 100;
	restore_delay = 3000;
	burst1 = 1;
	start-script InitialSetup1();
	start-script SmokeUnit();
}

SetMaxReloadTime(Func_Var_1)
{
	restore_delay = Func_Var_1 * 2;
}

StartMoving()
{
	bMoving = TRUE;
}

StopMoving()
{
	bMoving = FALSE;
}

SweetSpot(piecenum)
{
	piecenum = torso;
}

DrawWeapon1()
{
	turn torso to y-axis <0> speed <300.00>;
	turn ruparm to x-axis <0> speed <300.000000>;
	turn ruparm to x-axis <0> speed <300.000000>;
	turn lloarm to x-axis <0> speed <300.000000>;
	turn rloarm to x-axis <0> speed <300.000000>;
	wait-for-turn lloarm around x-axis;
	wait-for-turn rloarm around x-axis;
	wait-for-turn luparm around x-axis;
	wait-for-turn ruparm around x-axis;
	
	start-script Weapon1Drawn();
}

RestoreAfterDelay()
{
	set-signal-mask SIG_AIM;
	sleep restore_delay;
	
	start-script RestoreWeapon1();
	
	turn torso to y-axis <0> speed <300.00>;
	turn ruparm to x-axis <0> speed <300.000000>;
	turn ruparm to x-axis <0> speed <300.000000>;
	turn lloarm to x-axis <90> speed <300.000000>;
	turn rloarm to x-axis <90> speed <300.000000>;
	
	call-script Weapon1Restored();
	
	bAiming = FALSE;
}

AimFromPrimary(piecenum)
{
	piecenum = aimy1;
}

QueryPrimary(piecenum)
{
	piecenum = rfire + gun_1;
}

Shot1()
{
 --weapon fire anim
}

AimPrimary(heading, pitch)
{
	signal SIG_AIM;
	start-script RestoreAfterDelay();
	bAiming = TRUE;
	start-script Weapon1SetWtdAim(heading, pitch);
	if (wpnReady1 == 0)
	{
		start-script DrawWeapon1();
	}
	return (aim1);
}

Killed(severity, corpsetype)
{
	--killed anim/corpsetype return
}

The "weaponcontrol" helper:

Code: Select all

/*
HOW TO:
- Model MUST contain aimx1 and aimy1 pieces
- Script must have defined Weapon1TurretX and Weapon1TurretY static-vars (turret turn speeds)
- Script must have defined aimx1 and aimy1 pieces
- Change script as such:
Create()
{
	[...]
	start-script InitialSetup1();
	[...]
}

AimWeapon1(heading, pitch)
{
	[...]
	start-script Weapon1Drawn(); -- can call a function that draws weapons and then calls Weapon1Drawn() when done if there is an actual animation (ie pw)
	[...] -- Remove animations from aimWeapon scripts (use a DrawWeapon1() if an animation is needed, weapon1control will rotate the different aimpieces)
	start-script Weapon1SetWtdAim(heading, pitch);
	[...]
	return (aim1);
}

RestoreAfterDelay()
{
	[...]
	sleep sleeptime;start-script RestoreWeapon1();
	
	[...] -- other animations;
	call-script Weapon1Restored();
	[...] -- tell script wpn has been restored if needed for walkscripts
}

- Weapon1Control moves the aim pieces depending on turretSpeeds, sets pitch = 1 when pitch reached and head = 1 when head reached
- Weapon1Drawn allows unit to fire by setting wpnReady1 = 1
- if pitch = 1, head = 1 and wpnReady = 1 then the weapon can shoot: aim1 = 1 and aimweapon returns aim1
- Restore animations go in RestoreAfterDelay, RestoreWeapon1() restores aimy and aimx pieces orientation, Weapon1Restored() waits for these pieces to be restored
- Avoid wait-for-turn in AimWeapon and/or Weapon1Control as much as possible. If you have to (ie DrawWeapon1 of armpw, make sure you don't have multiple turns stacking with different goals
*/

static-var curHead1, wtdHead1, head1, curPitch1, wtdPitch1, pitch1, aim1, wpnReady1;

Weapon1Control()
{
	while (TRUE)
	{
		if (curHead1 > <180>)
		{
			curHead1 = <-360> + curHead1;
		}
		if (curPitch1 > <180>)
		{
			curPitch1 = <-360> + curPitch1;
		}
		if (curHead1 < <-180>)
		{
			curHead1 = <360> + curHead1;
		}
		if (curPitch1 < <-180>)
		{
			curPitch1 = <360> + curPitch1;
		}
		if (Static_Var_3 == 1)
		{
			if (((get ABS(curHead1 - wtdHead1)) > <360>) OR(((get ABS(curHead1 - wtdHead1)) > (Weapon1TurretY / 30)) AND ((get ABS(curHead1 - wtdHead1)) < <360> - (Weapon1TurretY / 30))))
			{
				head1 = 0;
				if(curHead1 < wtdHead1) {
					if(get ABS(curHead1 - wtdHead1)< <180>)
					   curHead1 = curHead1 + (Weapon1TurretY / 30);
					else curHead1 = curHead1 - (Weapon1TurretY / 30);
					}

				else {
					if(get ABS(curHead1 - wtdHead1)< <180>)
					   curHead1 = curHead1 - (Weapon1TurretY / 30);
					else curHead1 = curHead1 + (Weapon1TurretY / 30);
					}
			}
			else
			{
				head1 = 1;
				curHead1 = wtdHead1;
			}
			if (((get ABS(curPitch1 - wtdPitch1)) > <360>) OR(((get ABS(curPitch1 - wtdPitch1)) > (Weapon1TurretX / 30)) AND ((get ABS(curPitch1 - wtdPitch1)) < <360> - (Weapon1TurretX / 30))))
			{
				pitch1 = 0;
				if(curPitch1 < wtdPitch1) {
					if(get ABS(curPitch1 - wtdPitch1)< <180>)
					   curPitch1 = curPitch1 + (Weapon1TurretX / 30);
					else curPitch1 = curPitch1 - (Weapon1TurretX / 30);
					}

				else {
					if(get ABS(curPitch1 - wtdPitch1)< <180>)
					   curPitch1 = curPitch1 - (Weapon1TurretX / 30);
					else curPitch1 = curPitch1 + (Weapon1TurretX / 30);
					}
			}
			else
			{
				pitch1 = 1;
				curPitch1 = wtdPitch1;
			}
			if (pitch1 == 1 AND head1 == 1 AND wpnReady1 == 1 AND wtdHead1 != 0)
			{
				aim1 = 1;
			}
			else
			{
				aim1 = 0;
			}
			turn aimy1 to y-axis curHead1 now;
			turn aimx1 to x-axis curPitch1 now;
		}
		sleep 1;
	}
}

InitialSetup1()
{
	curHead1 = 0;
	curPitch1 = 0;
	wtdHead1 = 0;
	wtdPitch1 = 0;
	wpnReady1 = 0;
	start-script Weapon1Control();
}

Weapon1Drawn()
{
	wpnReady1 = 1;
}

RestoreWeapon1()
{
	wtdHead1 = 0;
	wtdPitch1 = 0;
}

Weapon1Restored()
{
	wpnReady1 = 0;
	while (curHead1 != 0)
	{
		sleep 25;
	}
	return (TRUE);
}

Weapon1SetWtdAim(pitch, heading)
{
	wtdHead1 = heading;
	wtdPitch1 = <0> - pitch;
}
The unitdefsturretspeeds helper

Code: Select all

/* unitDefsTurretSpeeds.h -- sets turret speed via unitDefs (through lua gadget Spring.CallCOBScript())
*/

static-var Weapon1TurretX, Weapon1TurretY;
static-var Weapon2TurretX, Weapon2TurretY;
static-var Weapon3TurretX, Weapon3TurretY;
static-var Weapon4TurretX, Weapon4TurretY;
static-var Weapon5TurretX, Weapon5TurretY;
static-var Weapon6TurretX, Weapon6TurretY;
static-var Weapon7TurretX, Weapon7TurretY;
static-var Weapon8TurretX, Weapon8TurretY;
static-var Weapon9TurretX, Weapon9TurretY;
static-var Weapon10TurretX, Weapon10TurretY;

SetWeapon1TurretSpeed(var1,var2)
{
	Weapon1TurretX = var1;
	Weapon1TurretY = var2;	
}
SetWeapon2TurretSpeed(var1,var2)
{
	Weapon2TurretX = var1;
	Weapon2TurretY = var2;	
}
SetWeapon3TurretSpeed(var1,var2)
{
	Weapon3TurretX = var1;
	Weapon3TurretY = var2;	
}
SetWeapon4TurretSpeed(var1,var2)
{
	Weapon4TurretX = var1;
	Weapon4TurretY = var2;	
}
SetWeapon5TurretSpeed(var1,var2)
{
	Weapon5TurretX = var1;
	Weapon5TurretY = var2;	
}
SetWeapon6TurretSpeed(var1,var2)
{
	Weapon6TurretX = var1;
	Weapon6TurretY = var2;	
}
SetWeapon7TurretSpeed(var1,var2)
{
	Weapon7TurretX = var1;
	Weapon7TurretY = var2;	
}
SetWeapon8TurretSpeed(var1,var2)
{
	Weapon8TurretX = var1;
	Weapon8TurretY = var2;	
}
SetWeapon9TurretSpeed(var1,var2)
{
	Weapon9TurretX = var1;
	Weapon9TurretY = var2;	
}
SetWeapon10TurretSpeed(var1,var2)
{
	Weapon10TurretX = var1;
	Weapon10TurretY = var2;	
}
Post Reply

Return to “Game Development”