#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 };