Projectiles orbiting another projectile?

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!)
Post Reply
User avatar
Tera
Posts: 11
Joined: Sun Apr 29, 2018 5:22 pm
Graphics Processor: nVidia (Modern GZDoom)

Projectiles orbiting another projectile?

Post by Tera »

I'm wanting to make something somewhat similar to the HammerHound from the Boss Monster Resource Wad but instead of having projectiles orbit a monster, I have it orbit an invisible projectile that cannot damage anything, so it ends up looking like a tornado of four projectiles going towards the player.
Is there at least an example of another mod that does something like this?
This is what I have so far but it isn't quite working how I intended. Summoning the QuadSpawner will end up with the four projectiles still orbiting the player.

Code: Select all

Actor QuadSpawner
{
	Radius 1
	Speed 5
	Damage 0
	+NOPAIN
	+RIPPER
	+FLOORHUGGER
	Projectile
	States
	{
		Spawn:
			TNT1 A 0 A_SpawnProjectile("OrbitingProjectile", 14, 0, 0)
			TNT1 A 0 A_SpawnProjectile("OrbitingProjectile", 14, 0, 90)
			TNT1 A 0 A_SpawnProjectile("OrbitingProjectile", 14, 0, 180)
			TNT1 A 0 A_SpawnProjectile("OrbitingProjectile", 14, 0, 270)
			TNT1 A 1
			Goto Spawn+5
		Death:
			TNT1 A 0
			Stop
	}
}

Actor OrbitingProjectile
{
	Radius 16
	Speed 5
	Damage 1
	+RIPPER
	Projectile
	States
	{
		Spawn:
			BAL1 A 5
			BAL1 A 0 A_CustomMissile("OrbitingProjectile", 0, 0, 75)
			Stop
	}
}
User avatar
Player701
 
 
Posts: 1640
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support
Contact:

Re: Projectiles orbiting another projectile?

Post by Player701 »

Note: The following code is ZScript. I'm really sorry if you don't know how to work with it, but I cannot recommend using deprecated DECORATE anymore, especially for stuff like this, as trying to implement it in a way you are suggesting constitutes a hack (at least in my opinion), and I do not approve of them.

Here's some example code of a projectile that orbits around its target. Note that it isn't actually a projectile per se - more like a special effect, as it does not collide with anything (not even walls), courtesy of the +NOINTERACTION flag. It is not a requirement that it must work like that, of course - you are free to alter the code in any way you want and see what happens. The example actor's code is not restricted to orbiting players, only the method that spawns it does. You can make it orbit around anything you want by setting its target field accordingly immediately after you've spawned it.

The orbiting code has been adapted from SorcFX2, the Heresiarch's invulnerability effect from Hexen. In addition to orbiting in the XY plane, SorcFX2 also features some periodic vertical movement. This part is not included in the example code, you can look it up youself in gzdoom.pk3/zscript/hexen/heresiarch.txt.

Code: Select all

//===========================================================================
//
// OrbitingProjectile
//
// DO NOT SPAWN DIRECTLY. Use "give TestInv" instead. See below.
//
//===========================================================================
class OrbitingProjectile : Actor
{
    Default
    {
        // Make sure we aren't colliding with anything
        +NOINTERACTION;
    }
    
    States
    {
        Spawn:
            BAL1 AB 4 BRIGHT A_Orbit(32, 32, 20);
            Loop;
    }
    
    //===========================================================================
    //
    // OrbitingProjectile::A_Orbit
    //
    // Orbits around this actor's target. Destroys itself without a target.
    // Parameters:
    // - orbitDist: distance from the target actor's center;
    // - orbitHeight: distance from the target actor's feet;
    // - angleDelta: amount to increase the angle with each call.
    //
    // To make the projectile orbit faster:
    // 1) increase angleDelta (but at the cost of losing precision) OR
    // 2) call this function more often. In this example it is called
    //    every 4 ticks as defined in the Spawn state. Try calling it more often
    //    and see what happens.
    //
    //===========================================================================
    void A_Orbit(double orbitDist, double orbitHeight, double angleDelta)
    {
        // Disappear without a target.
        if (!target)
        {
            Destroy();
        }
        else
        {
            // Calculate the new position.
            let newPos = target.Vec3Angle(orbitDist, angle, target.Floorclip + orbitHeight);
            
            // Move to the newly calculated position.
            SetOrigin(newPos, true);
            
            // Increase the angle.
            angle += angleDelta;
        }
    }
}

//===========================================================================
//
// TestInv
//
// Type "give TestInv" in the console to spawn an orbiting projectile.
//
//===========================================================================
class TestInv : Inventory
{
    override bool TryPickup(out Actor toucher)
    {
        // Spawn an orbiting projectile at the toucher's position.
        let proj = Spawn('OrbitingProjectile', toucher.pos);
        if (proj)
        {
            // Assign the orbiting projectile's target
            // so that it won't disapepar.
            proj.target = toucher;
        }
        
        // Remove this item from play.
        GoAwayAndDie();
        return true;
    }
}
User avatar
Tera
Posts: 11
Joined: Sun Apr 29, 2018 5:22 pm
Graphics Processor: nVidia (Modern GZDoom)

Re: Projectiles orbiting another projectile?

Post by Tera »

Yeah, I really need to start focusing on ZScript more. I've tested the OrbitingProjectile summon out a few times and it seems to orbit as intended. I even made some modified Imps to fire some and it seems to create a shield of fireballs around them as intended. However, trying to get Imps to fire projectiles that have projectiles surrounding them doesn't seem to be working right. Not sure if it's an actor pointer related issue or not, but here's what I tried making as a replacement for QuadSpawner:

Code: Select all

class TestProj : Actor
{
    Default
    {
        Radius 1;
        Height 1;
        Speed 1;
        Damage 0;
        Projectile;
	+RIPPER;
	+FLOORHUGGER;
	+PAINLESS;
	+BLOODLESSIMPACT;
    }
    States
    {
    Spawn:
        BAL1 A 1 A_SpawnProjectile("OrbitingProjectile"); //I intended for the projectile to create one orbiting projectile around itself and then keep moving.
        Goto Spawn2;
    Spawn2:
		BAL1 AB 1;
		Loop;
	Death:
        BAL1 A 1;
        Stop;
    }
}
User avatar
Player701
 
 
Posts: 1640
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support
Contact:

Re: Projectiles orbiting another projectile?

Post by Player701 »

Well, there seem to be at least two problems with your code:
  1. You have a call to A_SpawnProjectile in the first state frame of your actor. This call will not be executed on the first tic, unless you add the "nodelay" keyword.
  2. A_SpawnProjectile seems to implement some special logic that checks whether the calling actor is also a projectile, and if it is, the spawned actor's target will point to the projectile's target and not to the projectile itself (this is probably done to track the original shooter so that they are properly credited with the damage they deal). This will make the spawned actor orbit the actor who shot the projectile, which is probably not what you want. To remedy this, the orbiting projectile should be summoned with an Actor.Spawn call. Its target should then be assigned manually, the same way TestInv in my example code does it.
Speaking of 1), I recommend moving all first-time initialization code to a BeginPlay or a PostBeginPlay override. Depending on what exactly you intend to achieve, sometimes you may need one, and sometimes the other, but in this case, the former should suffice. This helps avoid the "nodelay" problem and also decouples some of the actor's inner logic from its state frames. (Granted, it is not possible to decouple it entirely in the current model, since the actor's states control its behavior, and not the other way around - but considering that's how it has always been, it is probably going to stay that way for a very long time.)
User avatar
Tera
Posts: 11
Joined: Sun Apr 29, 2018 5:22 pm
Graphics Processor: nVidia (Modern GZDoom)

Re: Projectiles orbiting another projectile?

Post by Tera »

Okay, it taken me a while to figure it out but I finally have it done. Man this made me feel kinda dumb. :oops:
To save others who want to do something similar from embarrassment, I'll go ahead and post what I have for the projectile in the center of orbit.

Code: Select all

class TestProj : Actor
{
    Default
    {
        Radius 1;
		Height 1;
        Speed 1;
        Damage 0;
        Projectile;
		+RIPPER;
		+FLOORHUGGER;
		+PAINLESS;
		+BLOODLESSIMPACT;
    }
    States
    {
    Spawn:
		BAL1 AB 1; //I'd recommend replacing this with TNT1 A 1, unless you want something at the center.
		Loop;
	Death:
        Stop;
    }
	override void BeginPlay()
	{
		let proj = Spawn('OrbitingProjectile', pos);
        if (proj)
        {
            proj.target = self;
        }
	}
}
User avatar
Player701
 
 
Posts: 1640
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support
Contact:

Re: Projectiles orbiting another projectile?

Post by Player701 »

There isn't anything embarassing in learning, especially when it comes to things like this where it's mostly trial and error - all thanks to the lack of official documentation (though there is an unofficial documentation project currently being worked on - I've found it quite useful a few times).

What I usually do when I want to implement something is look at the code in gzdoom.pk3 to consider what classes, functions or fields I could use to make it work. If I'm in doubt about what a particular function does or what a class or field is responsible for, I search for its implementation in the source code. Unless the relative part of the source is so complex that it's difficult to find out what exactly is going on, this kind of research tends to suffice most of the time. Otherwise, I test it in-game and usually also ask a question here to confirm or deny the conclusions deduced from these tests. See, I'd like to be 100% sure that the code is doing precisely what I want it to do :P
Post Reply

Return to “Scripting”