jorio / OttoMatic

Pangea Software’s Otto Matic 🤖
https://pangeasoft.net/otto
Other
141 stars 13 forks source link

Make the weapon rapid-fire while held down? #9

Closed foote-darrell closed 1 year ago

foote-darrell commented 2 years ago

How can you modify the code to make the weapons rapid-fire when held down? (Semi-Otto Matic to fully Otto Matic.) The Ideas From the Deep version had this feature.

jorio commented 2 years ago

Huh, I had no idea IFD's version had this! Interesting. I'd only ever played the Mac version in the PowerPC days.

Okay, here's how I'd go about adding rapid-fire to Otto Matic:


The first thing we need to do is find the piece of code that fires a bullet when the user presses the shoot key.

How does the code refer to the shoot key? In input.h, there's an enum of all the possible inputs ("needs") that the game can respond to:

enum
{
    kNeed_Forward,
    kNeed_Backward,
    kNeed_TurnLeft,
    kNeed_TurnRight,
    kNeed_PrevWeapon,
    kNeed_NextWeapon,
    kNeed_Shoot,
    kNeed_PunchPickup,
    kNeed_Jump,
    kNeed_CameraMode,
    ...

Looks like it is kNeed_Shoot.

If we grep the code for kNeed_Shoot, we get about ten hits. I won't go through them all here — the one that's relevant to us is in Player_Weapons.c:

void CheckPOWControls(ObjNode *theNode)
{
        /* SEE IF SHOOT GUN */

    if (GetNewNeedState(kNeed_Shoot))  // see if user pressed the key
    {
        ShootWeapon(theNode);
    }

    ...

The player's update routine calls CheckPOWControls on every frame. At the very top of that function, there's a bit of code that determines whether or not to call ShootWeapon (which actually spawns the bullet and gets it moving).

GetNewNeedState checks that the user has just pressed the key during the current frame. For rapid-firing weapons, we don't want this. We want to see if the user is holding down the key — not if they just pressed it. There's another function that does just that: GetNeedState.

If we change GetNewNeedState to GetNeedState in the snippet above, we'll get rapid-firing weapons alright. But they will keep spamming bullets on every single frame as long as the key is held down! That's obviously overkill, but I advise you try to recompile the game with this change just to see what's going on:

void CheckPOWControls(ObjNode *theNode)
{
        /* SEE IF SHOOT GUN */

    if (GetNeedState(kNeed_Shoot))  // fire bullet on every frame - bad!
    {
        ShootWeapon(theNode);
    }

    ...

So, we need to add a cooldown timer to space out the shots a bit.

The general idea of a cooldown timer is as follows:

A rough implementation can look like this:

// new global variable to keep track of delay until next shot
float gRapidFireCooldown = 0;

void CheckPOWControls(ObjNode *theNode)
{
        /* UPDATE RAPID-FIRE COOLDOWN */

    gRapidFireCooldown -= gFramesPerSecondFrac;  // continuously decrease cooldown
    // (gFramesPerSecondFrac is the duration of the previous frame, in seconds)

        /* SEE IF SHOOT GUN */

    if (GetNeedState(kNeed_Shoot)   // see if user is holding down the key
        && gRapidFireCooldown <= 0) // and we've waited long enough since last shot
    {
        ShootWeapon(theNode);       // fire a bullet
        gRapidFireCooldown = 0.25f; // allow next shot in a quarter of a second
    }

    ...

That feels much better. Of course, that's not quite polished enough. We still need to add different rapid-firing rates for every weapon. But that was the gist of adding fully-automatic weapons to Otto Matic. Have fun!

foote-darrell commented 2 years ago

Wow, that does fire quickly. Fortunately due to the advent of the cheat, there is unlimited ammo.

Does the f in 0.25f mean frames? Or is the f for seconds?

jorio commented 2 years ago

This delay is expressed in seconds. So, Otto will fire a bullet every 0.25 seconds, which is one fourth of a second. You can tweak this value to adjust the firing rate.

The f is a bit of syntax from the C programming language. It instructs the compiler to interpret that 0.25 as a float literal. If we leave out the f, the compiler will interpret the value as a double literal — it'll work, but the compiler may complain that we're trying to shoehorn a double value into a float variable. Otto Matic tends to use floats rather than doubles, so I'm sticking to that tradition when adding code to the game.