Source: projectile.js

/*******************************************************************************
 *
 *  @file projectile.js A file with the dynamics of projectiles
 *
 *  @author Omar Essilfie-Quaye <omareq08+githubio@gmail.com>
 *  @version 1.0
 *  @date 12-February-2025
 *  @link https://omareq.github.io/tanks/
 *  @link https://omareq.github.io/tanks/docs/
 *
 *******************************************************************************
 *
 *                   GNU General Public License V3.0
 *                   --------------------------------
 *
 *   Copyright (C) 2025 Omar Essilfie-Quaye
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 *****************************************************************************/
"use strict";

var TankGame = TankGame || {};

/**
 * Struct template to turn a series of keys into a structure.
 *
 * @type{Object}
 */
const Struct = (...keys) => ((...v) => keys.reduce((o, k, i) => {o[k] = v[i]; return o;} , {})); // eslint-disable-line

/**
 * Structure for projectile parameters.
 *
 * @type{Struct}
 */
TankGame.ProjectileParam = Struct(
    "damage",
    "projectileRadius",
    "explosionRadius",
    "duplicationFactor",
    "explodeAtApogee",
    "cost"
);

/**
 * Dictionary of projectile parameter structs.  Describes the parameters for all
 * the projectile types.
 *
 * @see TankGame.ProjectileParam
 *
 * @type{Dict<TankGame.ProjectileParam>}
 */
TankGame.ProjectileParamList = {};

/*******************************************************************************
 * MISSILES
 ******************************************************************************/

/**
 * Small missile.  Pure projectile that explodes on impact.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.SmallMissile = TankGame.ProjectileParam(25, 2, 15, 0, false, 25);

/**
 * Medium missile.  Pure projectile that explodes on impact.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.MediumMissile = TankGame.ProjectileParam(50, 4, 20, 0, false, 75);

/**
 * Large missile.  Pure projectile that explodes on impact.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.LargeMissile = TankGame.ProjectileParam(75, 5, 30, 0, false, 150);

/*******************************************************************************
 * GROUND BURST BOMBS
 ******************************************************************************/

/**
 * Small Ground Burst Bomb. Ballistic projectile that splits into 5 small
 * missiles on impact.  These missiles spread out radially.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.SmallGroundBurst = TankGame.ProjectileParam(25, 2, 15, 5, false, 100);

/**
 * Medium Ground Burst Bomb. Ballistic projectile that splits into 7 small
 * missiles on impact.  These missiles spread out radially.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.MediumGroundBurst = TankGame.ProjectileParam(25, 4, 20, 7, false, 150);

/**
 * Large Ground Burst Bomb. Ballistic projectile that splits into 9 small
 * missiles on impact.  These missiles spread out radially.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.LargeGroundBurst = TankGame.ProjectileParam(25, 5, 30, 9, false, 200);

/*******************************************************************************
 * AIR BURST BOMBS
 ******************************************************************************/

/**
 * Small Air Burst Bomb. Ballistic projectile that splits into 5 small
 * missiles at apogee.  These missiles spread out radially.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.SmallAirBurst = TankGame.ProjectileParam(25, 2, 15, 5, true, 175);


/**
 * Medium Air Burst Bomb. Ballistic projectile that splits into 7 small
 * missiles at apogee.  These missiles spread out radially.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.MediumAirBurst = TankGame.ProjectileParam(25, 4, 20, 7, true, 200);


/**
 * Large Air Burst Bomb. Ballistic projectile that splits into 9 small
 * missiles at apogee.  These missiles spread out radially.
 *
 * @type{TankGame.ProjectileParam}
 */
TankGame.ProjectileParamList.LargeAirBurst = TankGame.ProjectileParam(25, 5, 30, 9, true, 250);


/**
 * Class Projectile that represents the weapons projectiles.
 *
 * @see TankGame.ModeList.DebugProjectile
 */
TankGame.Projectile = class {
    /**
     * Projectile constructor.
     *
     * @param {p5.Vector} startPos - Starting position of the projectile
     * @param {number}    speed    - The start speed from 0-100
     * @param {number}    bearing  - Bearing to fire the projectile 0-180 in degrees.
     * @param {TankGame.ProjectileParam} projectileParam - Struct of projectile parameters.
     */
    constructor(startPos, speed, bearing, projectileParam) {
        this.minSpeed = 0.15;

        this.pos = startPos.copy();
        let startVel = createVector(1, 0);
        startVel.rotate(-radians(bearing));
        startVel.setMag(speed * this.minSpeed);
        this.vel = startVel.copy();
        this.projectileParam = projectileParam;
        this.radius = this.projectileParam.projectileRadius;
        this.mass = PI * this.radius**2;
        this.applyDrag = true;
        this.applyWind = true;
        this.gameEngine = undefined;
        this.isExploding = false;
        this.finishedExploding = false;
    };

    /**
     * Attach the projectile to the game engine and keep a handle for the
     * game engine.
     *
     * @param {TankGame.GameEngine} gameEngine - The game engine.
     *
     * @throws {Error} param gameEngine should be instance of TankGame.GameEngine
     */
    attachTo(gameEngine) {
        if(!(gameEngine instanceof TankGame.GameEngine)) {
            let err = "gameEngine should be an instance of ";
            err += "TankGame.GameEngine\n";
            throw(err);
        }
        this.gameEngine = gameEngine;
    }

    /**
     * Disable the drag calculations on the projectile
     */
    disableDrag() {
        this.applyDrag = false;
    };

    /**
     * Enable the drag calculations on the projectile
     */
    enableDrag() {
        this.applyDrag = true;
    };

    /**
     * Determines if the projectile is off screen or not.  Does not return true
     * if the projectile is above the screen as it might fall back into view.
     *
     * @returns {Boolean} if the projectile is on screen
     */
    isOffScreen() {
        const horizontal = this.pos.x > width || this.pos.x < 0;
        const vertical = this.pos.y > height;
        return horizontal || vertical;
    };

    /**
     * Set the exploding flag to true to start the animation and produces any
     * projectiles that are spawned from the explosion.  These new projectiles
     * are directly added to the game engine.
     *
     * @see TankGame.GameEngine
     */
    explode() {
        this.isExploding = true;
        const numProjectiles = this.projectileParam.duplicationFactor;
        if(numProjectiles > 0) {
            for(let i = 0; i < numProjectiles; i++) {
                const startPos = this.pos.copy().sub(createVector(0, 2));
                const speed = 20;
                const bearing = i * 140 / (this.projectileParam.duplicationFactor) + 30;
                const testProjectile = new TankGame.Projectile(
                    startPos,
                    speed,
                    bearing,
                    TankGame.ProjectileParamList["SmallMissile"]);
                this.gameEngine.addProjectile(testProjectile);
            }
        }
    }

    /**
     * updates the projectiles position.
     *
     * @param {number} dt - The time difference between the the current frame
     * and the previous frame.
     */
    update(dt) {
        if(this.isOffScreen()) {
            return;
        }

        if(this.isExploding) {
            if(this.radius > this.projectileParam.explosionRadius) {
                this.finishedExploding = true;
                return;
            }

//TODO: update the explosion animation by time and easing functions
            this.radius+=4;
            return;
        }

        this.vel = this.vel.copy().add(this.gameEngine.gravity.copy().div(dt));
        if(this.applyDrag) {
            this.vel = this.vel.copy().sub(this.vel.mult(0.009));
        }

        if(this.applyWind) {
            this.vel = this.vel.copy().add(this.gameEngine.getCurrentWind());
        }

        this.pos = this.pos.copy().add(this.vel.copy());

        if(this.projectileParam["explodeAtApogee"] && this.vel.y > 0) {
            this.explode();
        }
    };

    /**
     * Draws the projectile
     */
    draw() {
        push();
        fill(255, 0, 0);
        ellipse(this.pos.x, this.pos.y, 2 * this.radius);
        pop();
    };
};

Documentation generated by JSDoc 4.0.2 on Wed Oct 29 2025 23:51:01 GMT-0700 (Pacific Daylight Time)