ICT290 / src / scene / AIController / Vehicle.h
Vehicle.h
Raw
#pragma once

#include <glm/vec3.hpp>
#include "BasicEntity.h"
#include "EntityBrain/SensoryMemory.h"
#include "EntityBrain/TargetingSystem.h"
#include "GameWorld.h"
#include "Goals/GoalThink.h"
#include "MovingEntity.h"
#include "SteeringBehaviours.h"

class Vehicle;
class SteeringBehaviours;
class GameWorld;
class Smoother;

class DecisionRegulator;
class TargetingSystem;
class AttackSystem;
class SensoryMemory;
class GoalThink;

/**
 * @class Vehicle
 * @brief  This a vehicle that has behaviours and its own will
 * Code is based on the textbook:
 * "Programming game AI by Example" by Matt Buckland
 *
 * @author Michael John
 * @version 0.1
 * @date 24/10/2021
 *
 * @todo
 *
 * @bug
 */

// set the frequency of updates for the regulators (in seconds)
const float BOT_WEAPONSELECTIONFREQUENCY = 2.0f;
const float BOT_GOALAPPRAISALUPDATEFREQ = 4.0f;
const float BOT_TARGETINGUPDATEFREQ = 2.0f;
const float BOT_TRIGGERUPDATEFREQ = 8.0f;
const float BOT_VISIONUPDATEFREQ = 4.0f;
const float BOT_REACTIONTIME = 0.2f;
const float BOT_AIMACCURACY = 0.0f;

const float BOT_AIMPERSISTENCE = 1.0f;
const Uint32 BOT_MEMORYSPAN = 50000;  // in milliseconds
const float BOT_FOV = 360.0f;
const float BOT_VIEWRANGE = 5.0f;
const int SMOOTHINGSAMPLESIZE = 20;  // sets the vector size for smoothing, <10
                                     // is jankier
/**
 * A moving entity that makes decisions
 */
class Vehicle : public MovingEntity {
   public:
    Vehicle(GameWorld* world,
            glm::vec2 position,
            glm::vec2 scale,
            float boundRadius,
            glm::vec2 heading,
            glm::vec2 velocity,
            float mass,
            float maxSpeed,
            float maxForce,
            float maxTurnRate,
            int maxHealth);

    virtual ~Vehicle();

    /**
     * Message Handler for this vehicle - used for adjusting health
     * and attacking other entities
     * @param msg receiving message
     * @return true if successfully read
     */
    bool HandleMessage(const Telegram& msg) override;

    // updates the vehicle's position and orientation
    /**
     * Updates this entities decisions
     * @param elapsedTime time passed since last update - in seconds
     */
    void Update(float elapsedTime);

    /**
     *  Gets this entities Steering Behaviours
     * @return SteeringBehaviours object
     */
    SteeringBehaviours* getSteering();

    /**
     * Entity holds a pointer to the external game world to get other entitiy
     * positions
     * @return pointer to the GameWorld
     */
    GameWorld* getGameWorld();

    /**
     * Gets the smoothed heading. Smoother movements applied rather than the
     * entities heading at that moment
     * @return entities heading smoothed over n samples
     */
    glm::vec2 getSmoothedHeading() const;

    /**
     * Gets whether smoothing is on
     * @return Smoothign is on/off
     */
    bool isSmoothingOn() const;

    /**
     * Sets heading smoothing to on
     */
    void setSmoothingOn();

    /**
     * Sets heading smoothing to Off
     */
    void setSmoothingOff();

    /**
     * Toggle smoothing on/off
     */
    void toggleSmoothing();

    /**
     * Get elapsed time since last update in seconds
     * @return
     */
    float getElapsedTime() const;

    /**
     * Rotate towards target as fast as max rotation will allow
     * @param target Position to turn to
     * @return true if facing target
     */
    bool rotateFacingTowardPosition(glm::vec2 target);

    /**
     * Gets Entities current health
     * @return health of entity
     */
    int getHealth() const { return m_Health; }

    /**
     *  Get the maximum health allowed for this Entity
     * @return Max health allowed for this entity
     */
    int getMaxHealth() const { return m_MaxHealth; }

    /**
     * Reduce health of this entity
     * @param val amount to reduce health by
     */
    void reduceHealth(unsigned int val);

    /**
     * Increase health of this entity
     * @param val Amount to increase health by
     */
    void increaseHealth(unsigned int val);

    /**
     * Restores health back to maximum
     */
    void restoreHealthToMaximum();

    /**
     * Returns score of Entity
     * @return current score
     */
    int Score() const { return m_Score; }

    /**
     * Use to increment this Entities score by 1
     */
    void incrementScore() { ++m_Score; }

    /**
     * Gets a normalised vector this Entity is facing
     * @return normalised vector in direction facing
     */
    glm::vec2 getFacingVec() const { return m_Facing; }

    /**
     * Gets the FOV setting for this Entity
     * @return FOV in degrees
     */
    float getFieldOfView() const { return m_FieldOfView; }

    /**
     * Gets view distance setting of this Entity
     * @return float distance
     */
    float getViewDistance() const { return m_ViewDistance; }

    /**
     * Gets if this Entity is player controlled
     * @return True if player controlled
     */
    bool isPossessed() const { return m_Possessed; }

    /**
     * Gets if this Entity has no health
     * @return true if no health
     */
    bool isDead() const { return m_Status == dead; }

    /**
     * Gets if this Entity has health remaining
     * @return true if health not equal to 0
     */
    bool isAlive() const { return m_Status == alive; }

    /**
     * Gets if this Entity just spawned in - can be used to prevent spawn kills
     * @return
     */
    bool isSpawning() const { return m_Status == spawning; }

    /**
     * Gets if this Entity is in the process of dying
     * @return true if dying
     */
    bool isDying() const { return m_Status == dying; }

    /**
     * Sets the entities status to spawning
     */
    void SetSpawning() { m_Status = spawning; }

    /**
     * Sets the entities status to dead
     */
    void setDead() { m_Status = dead; }

    /**
     *  Sets the entities status to alive
     */
    void setAlive() { m_Status = alive; }

    /**
     *  Sets the entities status to dying
     */
    void setDying() { m_Status = dying; }

    /**
     * Calcs the time to reach a position - used for decision making
     * @param pos Desired position
     * @return time in seconds to reach the argument position
     */
    float calculateTimeToReachPosition(glm::vec2 pos) const;

    /**
     * Use to determine if the entity is within a small constant value of the
     * target position
     * @param pos checks if at this position
     * @return true if at this position
     */
    bool isAtPosition(glm::vec2 pos) const;

    /**
     * Call this for player entity to shoot at a position
     * @param pos position player shot at
     */
    void fireWeapon(glm::vec2 pos);

    /**
     * Change weapon type for attacks
     * @param type value of weapon chosen
     */
    void changeWeapon(unsigned int type);

    /**
     * Used for player to take posession of this entity
     */
    void takePossession();

    /**
     * Removes player possession of this entity
     */
    void exorcise();  // used for taking control of any bot

    /**
     * Spawns this entity in at the position argument
     * @param pos position to spawn entity in
     */
    void spawn(glm::vec2 pos);

    /**
     * Calculates if this entity has line of sight to a position
     * @param pos position to check line of sight to
     * @return true if this entity has LOS to position
     */
    bool hasLOSto(glm::vec2 pos) const;

    /**
     * Checks if this entity can walk to a position
     * Used for check if walls or obstacles prevent movement
     * @param pos position to check movement to
     * @return true if entity can walk to the argument position
     */
    bool canWalkTo(glm::vec2 pos) const;

    /**
     * Checks if a entity can from a position to another
     * @param from start position
     * @param to end position
     * @return true if no walls preventing movement
     */
    bool canWalkBetween(glm::vec2 from, glm::vec2 to) const;

    /**
     * Checks if entity can step left
     * Use for side strafing and dodging
     * @param PositionOfStep position to move left from
     * @return true if can step left
     */
    bool canStepLeft(glm::vec2& PositionOfStep) const;

    /**
     * Checks if entity can step right
     * Use for side strafing and dodging
     * @param PositionOfStep position to move right from
     * @return true if can step right
     */
    bool canStepRight(glm::vec2& PositionOfStep) const;

    /**
     * Checks if entity can step forward
     * Use for side strafing and dodging
     * @param PositionOfStep position to move forward from
     * @return true if can step forward
     */
    bool canStepForward(glm::vec2& PositionOfStep) const;

    /**
     * Checks if entity can step backward
     * Use for side strafing and dodging
     * @param PositionOfStep position to move backward from
     * @return true if can step backward
     */
    bool canStepBackward(glm::vec2& PositionOfStep) const;

    /**
     * Gets pointer to this entities goal decision making
     * @return Goal decision object
     */
    GoalThink* getBrain() { return m_Brain; }

    /**
     * Gets a pointer to this entities Targeting system object
     * @return pointer Targeting system object
     */
    const TargetingSystem* getTargetSys() const;
    TargetingSystem* getTargetSys();

    /**
     * Gets this entities target enemy
     * @return pointer to this entities vehicle class enemy
     */
    Vehicle* getTargetBot() const;

    /**
     * Gets the entities weapon system that holds the list of attacks
     * @return Attack system pointer
     */
    AttackSystem* getWeaponSys() const;

    /**
     * Gets the entities memory that holds the positions of all enemies in FOV
     * @return sensory memory pointer
     */
    SensoryMemory* getSensoryMem() const;

    /**
     * Current goal of this entity for debugging
     */
    int m_Goal{0};

   private:
    void updateMovement(float elapsedTime);  /// updates the movement of this
                                             /// bot
    GameWorld* m_GameWorld{nullptr};  /// pointer to the overarching game world
    SteeringBehaviours* m_Steering{
        nullptr};              /// pointer to this entities Steering behaviours
    float m_ElapsedTime{0};    /// holds the last update elapsed time
    bool m_SmoothingOn{true};  /// smoothingOn/Off
    glm::vec2 m_SmoothedHeading{1};  /// smoothedHeading
    Smoother* m_HeadingSmoother{nullptr};
    enum Status { alive, dead, spawning, dying };
    Status m_Status{alive};  /// status of this entity

    GoalThink* m_Brain{nullptr};  /// selects goals based the bots inputs
    SensoryMemory* m_SensoryMem{
        nullptr};  /// Remembers enemies and their locations
    TargetingSystem* m_TargSys{nullptr};  /// Responsible for choosing targets
    AttackSystem* m_AttackSys{
        nullptr};  /// handles weapon selection based on range and ammo

    // A regulator object limits the update frequency of a specific AI component
    DecisionRegulator* m_WeaponSelectionRegulator{
        nullptr};  /// regulates attack type selections
    DecisionRegulator* m_GoalArbitrationRegulator{
        nullptr};  /// regulates goal decision making
    DecisionRegulator* m_TargetSelectionRegulator{
        nullptr};  /// regulates target selection
    DecisionRegulator* m_TriggerTestRegulator{nullptr};  /// regulates triggers
    DecisionRegulator* m_VisionUpdateRegulator{
        nullptr};  /// regulates add enemies to memory

    int m_Health{0};               /// bots health
    int m_MaxHealth{0};            /// bots max health
    int m_Score{0};                /// bots score
    glm::vec2 m_Facing{1};         /// bots direction facing for FOV calcs, not
                                   /// movement
    float m_FieldOfView{BOT_FOV};  /// bots field of view in degrees
    float m_ViewDistance{BOT_VIEWRANGE};  /// bots view range
    bool m_Hit{false};                    /// Used for adding effects on hit
    bool m_Possessed{false};              /// player control of this entity

    Vehicle(const Vehicle&);             /// prevent copying of vehicles
    Vehicle& operator=(const Vehicle&);  /// prevent copying of vehicles
};

/**
 * smooths a vector by averaging it over a list
 */
class Smoother {
   public:
    // to instantiate a Smoother, pass it the number of samples you want
    explicit Smoother(int SampleSize) {
        m_History.reserve(SampleSize);
        m_History.assign(SampleSize, glm::vec2(0.0f));
        m_NextUpdateSlot = 0;
    }

    /**
     * Updates the sampling list rotating through a circular list and
     * replacing the oldest vector with the newest parameter vector
     * @param MostRecentValue glm::vec2 adds this latest vector to the list
     * @return the average or smoothed vector from the list
     */
    inline glm::vec2 Update(const glm::vec2& MostRecentValue) {
        // overwrite the oldest value with the newest
        m_History[m_NextUpdateSlot++] = MostRecentValue;

        // make sure m_NextUpdateSlot wraps around.
        if (m_NextUpdateSlot == (int)m_History.size())
            m_NextUpdateSlot = 0;

        // now to calculate the average of the history list
        float sumX = 0;
        float sumY = 0;
        for (auto headings : m_History) {
            sumX += headings.x;
            sumY += headings.y;
        }
        float smoothedX = sumX / (float)m_History.size();
        float smoothedY = sumY / (float)m_History.size();

        return glm::vec2{smoothedX, smoothedY};
    }

   private:
    std::vector<glm::vec2> m_History;  /// the list of samples to smooth over
    int m_NextUpdateSlot;              /// next vector to be replaced
};