Laser guided RPG physics

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
Posts: 161
Joined: Fri Mar 14, 2014 12:40 am

Laser guided RPG physics

Post by Kagur »

So here was a neat example by a member named scooter to make a laser guided rocket:


I was wondering how would it be possible to add three reality based physics applied here.

1, The gravity starts to take effect within 3 seconds of launch
2, the initial thrust is very strong but like gravitational force, the rocket loses this thrust incrementally within the first 2 seconds
3, How to make the Rocket not explode on touching anything, and instead falling to the ground (dud)

I can see the ACS script inside and I understand it to some degree, but I don't know where I can make these changes. I already tried adding setactorproperty with APROP_gravity but that did nothing.

Thanks in advance to anyone who can guide me on this.
Posts: 161
Joined: Fri Mar 14, 2014 12:40 am

Re: Laser guided RPG physics

Post by Kagur »

So while I'm a bit letdown to see no response yet, I did some research on my own end to find the solution to this.

This is the script that makes this thing work.

Code: Select all

Guided Rocket Launcher
By Scotty

Set SC_DEBUG_MODE 1 in the console to use debug mode! It displays useless data in a hudmessage
and puts pretty blue trails behind the rockets. Ooooooh!

The constants below can be used to make quick changes to the behavior.

LATENCY: The length in time, in tics, with which the rockets recalculate their trajectories.

NUMBER_ROCKETS:	The max number of rockets that you can have following the laser.
					Any over will just fly straight where you were aiming.

ROCKET_SPEED: The speed with which the rocket will travel. (Duh)

ROCKET_CUTOFF: The minimum distance away from the laser for the rocket to recalculate
				its angle and pitch. (If it gets too close to the laser, it will stop 
				updating its trajectory and just fly straight for wherever it was heading.
ROCKET_TURNVALUE: A fixed point number representing the max angle that rockets can adjust 
					per update. This creates a curve effect. Use 0 to ignore this and just
					use whateverangle and pitch it needs to head straight for the laser.


#include "zcommon.acs"

#define	LATENCY			1
#define	NUMBER_ROCKETS	100
#define ROCKET_SPEED	20
#define ROCKET_CUTOFF 	32

bool rocket[NUMBER_ROCKETS] = {0};

int laserX = 0;
int laserY = 0;
int laserZ = 0;

function int abs (int x) {
    if (x < 0)
        return -x;
	return x;

//Finds the first zero element in the rocket[] array.
//This would be much more useful if I could pass in a pointer to the array!
//elements -	INT, the number of elements in the array. 	
//Returns:	INT value for the first element with a 0; -1 if none were found.
function int checkForFirstZero (int elements) {
	int result = -1;
	int i;
	bool found = 0;
	//Cycle through each value in the rocket array
	for(i = 0; i < elements || !found; i++) {
		if(rocket[i] == 0) {
			result = i;
			found = 1;
	return result;

//Taken and modified from a function on the zdoom wiki
function int getTargetPitch (int tid1, int targetX, int targetY, int targetZ) {
    int x, y, z, xy, a;
    x = GetActorX(tid1) - targetX;
    y = GetActorY (tid1) - targetY;
    z = GetActorZ(tid1) - targetZ;
    a = VectorAngle(x, y);

    if ((a + 0.125) % 0.5 > 0.25)
        xy = FixedDiv(y, sin(a));
        xy = FixedDiv(x, cos(a));

    return -VectorAngle(xy, z);

//Returns the signed difference between two angles
//angleA -	FIXED, The first angle to compare
//angleB -	FIXED, The second angle to compare
function int diffAngle(int angleA, int angleB) {
	int result = angleA - angleB;
	if(result > 0.5) {
		result -= 1.0;
	} else if (result < -0.5) {
		result += 1.0;
	return result;

//This is a function that creates a curve effect on the projectile.
//Essentially what it does is takes a target angle, the projectile's current
//angle, finds the difference between the two, and adds or subtracts a small
//fixed numbner (changeValue) from the current angle and returns the result,
//so it eases closer to the result rather than just being the result.
//targetAngle -		FIXED, The target angle that the projectile needs to have
//currentAngle - 	FIXED, the current angle that the projectile has
//changeValue -		FIXED, a small value to change the current angle by.	
function int closeIn(int targetAngle, int currentAngle, int changeValue) { 
	int diff = diffAngle(currentAngle, targetAngle);
	int result; 
	if(abs(diff) < changeValue) {
		result = targetAngle;
	} else if(diff > 0) {
		result = currentAngle - changeValue;
	} else if (diff < 0) {
		result = currentAngle + changeValue;
	return result;

//This is the meat and potatoes of the mod!
//This takes the TID of a rocket, the X, Y, and Z positions of a target (laser),
//Does math to figure out where the rocket needs to go and changes the velocity
//projectileTID -	INT, The TID of the projectile
//targetX - 		INT, The target X coordinate
//targetY - 		INT, The target Y coordinate
//targetZ - 		INT, The target Z coordinate
//speed -			INT, the speed with which the projectile will move
//cutoff -			FIXED, the distance with which to make the projectile stop following the target.
//turnValue - 		FIXED, the amount per call the rocket is allowed to turn. Pass 0 to ignore this and just use the target angle.
//Returns: bool value that indicates if the projectile can follow its target.
function bool setVelocityToTarget(int projectileTID, int targetX, int targetY, int targetZ, int speed, int cutoff, int turnValue) {
	//Fixed point variables which define the individual X, Y, and Z distances 
	//that the rocket is from the target.
	int disX = ((targetX  << 16) - getactorx(projectileTID));
	int disY = ((targetY  << 16) - getactory(projectileTID));
	int disZ = ((targetZ  << 16) - getactorZ(projectileTID));
	//Int equivalents of the values above. These are used to determine the cutoff distance.
	int idisX = disX / 65535;
	int idisY = disY / 65535;
	int idisZ = disZ / 65535;
	//Actual distance the projectile is from the target.
	int distance = sqrt(idisx * idisx + idisy * idisy + idisz * idisz);
	int gravity = GetActorProperty(ProjectileTID, APROP_GRAVITY);

	//speed needs to be replaced with thrust
	//This is to check for a cutoff distance.
	//This is used because without it, if you were to shoot the rocket downward at a
	//specific range of angles, it doesn't want to touch the ground. Instead it
	//goes berserk in place, hovering just above the floor.
	if(distance > cutoff) {
		//Target and Current Angle
		int targetAngle = vectorAngle(disX, disY);
		int currentAngle = getActorAngle(projectileTID);
		//Target and Current Pitch
		int targetPitch = getTargetPitch(projectileTid, laserX << 16, laserY << 16, laserZ << 16);
		//Normalize the pitch to a positive value.
		if(targetPitch < -0.5) {
			targetPitch += 1.0;
		//Grab the current pitch
		int currentPitch = getActorPitch(projectileTID);
		//This is intended to fix a small anomaly that causes a rocket to fly out of the 
		//launcher at 0.0 pitch at first then readjust, without regard to the player pitch.
		if(currentPitch == 0) {
			currentPitch = targetPitch;
		//Next Angle and Pitch
		int nextAngle, nextPitch;
		//If the turnValue is not 0, we'll calculate the next angle by the difference
		//between the target and current angles, plus or minus the turnvalue.
		//This creates a curve effect!
		if(turnValue != 0) {
			nextAngle = closeIn(targetAngle, currentAngle, turnValue);
			nextPitch = closeIn(targetPitch, currentPitch, turnValue);
		//If the turnValue is 0, we'll just set the pitch and angle to what it needs to be.
		//This was the original incarnation of this weapon used and it works really well
		//if you're annoyed by the curve effect.
 		} else {
			nextAngle = targetAngle;
			nextPitch = targetPitch;
		//Next X, Y, and Z velocities
		//Trig here to make sure the combined X, Y, and Z velocities equal the speed that is passed in.
		int nextVelX = fixedmul(Cos(nextAngle), Cos(nextPitch))*speed;
		int nextVelY = fixedmul(Sin(nextAngle), Cos(nextPitch))*speed;
		int nextVelZ = sin(nextPitch)*speed;
		//Chage the velocity here using the calculated values above.
		setActorVelocity(projectileTid, nextVelX, nextVelY, nextVelZ, 0, 0);
		//Store the pitch and angle data on the actor's Pitch and Angle.
		//Angle affects the look, and the values on both will be used next round to 
		//determine the velocities to set.
		setActorPitch(projectileTID, nextPitch);
		setActorAngle(projectileTID, nextAngle);
		//Dumps debug data if the cvar SC_DEBUG_MODE is set to true.
		if(getCvar("SC_DEBUG_MODE") == 1) {
				s:"TargetX:   ", i: targetX,
				s:"\nTargetY:   ", i: targetY,
				s:"\nTargetZ:   ", i: targetZ,
				s:"\nRocketX:  ", f: getactorx(projectileTID),
				s:"\nRocketY:  ", f: getactory(projectileTID),
				s:"\nRocketZ:  ", f: getactorz(projectileTID),
				s:"\nDisX:     ", f: disx,
				s:"\nDisY:     ", f: disy,
				s:"\nDisZ:     ", f: disz,
				s:"\nDist:     ", f: sqrt((idisx * idisx) + (idisy * idisy)) << 16,			
				s:"\nT-Angle:  ", f: targetAngle,
				s:"\nC-Angle:  ", f: currentAngle,
				s:"\nT-Pitch:  ", f: targetPitch,
				s:"\nC-Pitch:  ", f: currentPitch,
				s:"\nX-Vel:    ", f: nextVelX,
				s:"\nY-Vel:    ", f: nextVelY,
				s:"\nZ-Vel:    ", f: nextVelZ;
				HUDMSG_LOG, 10, 0, 0.1, 0.5, 35
			SpawnSpotForced("Trail", projectileTID, 0, 0);
		return TRUE;
	return FALSE;

script "rocketFollow" (void) {
	//Grab the first slot available for a rocket to occupy
	int checkedArray = checkForFirstZero(NUMBER_ROCKETS);
	//If the result is good...
	if(checkedArray != -1) { 
		thing_changetid(0,20000 + checkedArray);
		//Store the rocket's TID - 20000 in the array. This will flag it as occupied.
		rocket[activatorTID() - 20000] = 1;
		Bool follow = true;
		//Then the rocket follows the laser until it's exploded or it gets too close to the laser.
		while(rocket[activatorTID() - 20000] == 1 && follow) {
			follow = setVelocityToTarget(activatorTID(), laserX, laserY, laserZ, ROCKET_SPEED, ROCKET_CUTOFF, ROCKET_TURNVALUE);

script "rocketCleanup" (void)
	if(activatorTID() != 0) {
		//Open a slot in the array
		rocket[activatorTID() - 20000] = 0;
		//Make sure the velocity is 0
		setActorVelocity(activatorTID(), 0, 0, 0, 0, 0);
		thing_changeTid(0, 0);

script "getLaserCoords" (int x, int y, int z) {
	laserX = x;
	laserY = y;
	laserZ = Z;
On trail and testing I realized the biggest confusion comes from putting in something to direct the speed with the influence of gravity and an initial thrust.

Essentially this code:

Code: Select all

	int nextVelX = fixedmul(Cos(nextAngle), Cos(nextPitch))*speed;
		int nextVelY = fixedmul(Sin(nextAngle), Cos(nextPitch))*speed;
		int nextVelZ = sin(nextPitch)*speed;
Would anyone have suitable math equation I could put to use on the speed variable to make it function as such?

Thank you
User avatar
Posts: 593
Joined: Thu Jul 05, 2007 6:13 pm

Re: Laser guided RPG physics

Post by KeksDose »

I suppose nowadays you'd be better off using zscript for those sorta endeavours. :p

But what I say now mostly goes in acs as it does in zscript.

The acs won't allow you to use gravity, since the SetActorVelocity calls reset the z-velocity. You also cannot simply set it to add, because the nextVel* vars would then be used for acceleration instead, and your rocket would speed up very fast. You also cannot just reset the x- and y-velocity, keeping z, and add the nextVel* results, because then, the rocket would speed up very fast still in the z-direction.

What you can do is count up the tics the script is running and, if the tics are past some value, add the actor's gravity value onto a separate vel_gravity. Then you subtract that value from nextVelZ.

For the speed falloff, count the tics the script is running and introduce a var speed_cur that is speed * f, where f is the speed modifier. Then we operate on f instead. Basically, you select a time t1 and t2 and before t1, f will be at its maximum, after t2, it'll be at its minimum and anywhere in between, it'll fall off linearly. Just look at this example which I didn't test:

Code: Select all

int tics = 0; // Count this up every iteration of the following loop
int t1 = 10; // Max speed for first 10 tics
int t2 = 70; // Lowest speed reached in two seconds
int tics_slowdown = t2 - t1;

// Normal speed factors:
int f_max = 1.0;
int f_min = 0.5;

// Inside of your loop:
int f = 0; // The speed factor

if(tics <= t1) {
   f = f_max;

else if(t2 <= tics) {
   f = f_min;

else {
   // Fall linearly from (t1, 1.0) to (t2, 0.0):
   f  = (t2 - tics) << 16;
   f /= tics_slowdown;

   // Move f into the desired range.
   // Then f falls linearly from (t1, f_max) to (t2, f_min).
   f  = FixedMul(f, f_max - f_min) + f_min;

// Then change the current speed by that factor:

cur_speed = FixedMul(f, speed);
// Finally multiply nextVel* by cur_speed instead of speed. 
User avatar
Posts: 9693
Joined: Sun Jan 04, 2004 5:37 pm
Preferred Pronouns: They/Them
Operating System Version (Optional): Debian Bullseye
Location: Gotham City SAR, Wyld-Lands of the Lotus People, Dominionist PetroConfederacy of Saudi Canadia

Re: Laser guided RPG physics

Post by Matt »

Yesterday I typed out a post explaining in general terms how the rockets in Hideous Destructor accomplish something similar (or used to, wrt the guided missile part), but I wasn't sure if it would be welcome since all of it was in ZScript and pretty much none of the ACS code here would be transferable.

Suffice it to say it involved:

A_ChangeVelocity (for forward thrust)

target.LineAttack (to find the spot where the player is pointing, instead of simply having the rocket adopt the player's angle)

vel.z-- (call every tic to emulate gravity; could use vel.z-=GetGravity() instead)

A_SpawnItemEx (for creating a new dud rocket actor; you could try doing it all from the same actor but I find it a bit unwieldy)

(on a bit of a tangent, I just realized that A_FireProjectile returns an actor in ZScript, so you can manipulate a projectile's properties from the weapon - something I kinda really wish I'd bothered to look at this time last year!)

Return to “Scripting”