ICT290 / src / scene / AIController / Vehicle.cpp
Vehicle.cpp
Raw
#include "Vehicle.h"

#include "Attacks/AttackType.h"
#include "EntityBrain/SensoryMemory.h"
#include "SteeringBehaviours.h"
//#include "EntityBrain/TargetingSystem.h"
#include "EntityBrain/AttackSystem.h"
#include "EntityBrain/DecisionRegulator.h"
#include "Goals/ArcanistGoalTypes.h"
#include "Messaging/ArcanistMessaging.h"
#include "Messaging/MessageDispatcher.h"

Vehicle::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)
    : MovingEntity(position,
                   scale,
                   boundRadius,
                   heading,
                   velocity,
                   mass,
                   maxSpeed,
                   maxForce,
                   maxTurnRate),
      m_GameWorld(world) {
    m_GameWorld = world;
    m_HeadingSmoother = new Smoother(SMOOTHINGSAMPLESIZE);  // test sample
    m_Steering = new SteeringBehaviours(this);
    m_AttackSys = new AttackSystem(this,
                                   BOT_REACTIONTIME,
                                   BOT_AIMACCURACY,
                                   BOT_AIMPERSISTENCE);
    m_TargSys = new TargetingSystem(this);
    m_SensoryMem = new SensoryMemory(this, BOT_MEMORYSPAN);
    m_ViewDistance = BOT_VIEWRANGE;
    m_FieldOfView = BOT_FOV;
    m_Brain = new GoalThink(this);

    // create the regulators
    m_WeaponSelectionRegulator = new DecisionRegulator(
        BOT_WEAPONSELECTIONFREQUENCY);
    m_GoalArbitrationRegulator = new DecisionRegulator(
        BOT_GOALAPPRAISALUPDATEFREQ);
    m_TargetSelectionRegulator = new DecisionRegulator(BOT_TARGETINGUPDATEFREQ);
    m_TriggerTestRegulator = new DecisionRegulator(BOT_TRIGGERUPDATEFREQ);
    m_VisionUpdateRegulator = new DecisionRegulator(BOT_VISIONUPDATEFREQ);

    m_Status = alive;
    m_MaxHealth = maxHealth;
    m_Health = maxHealth;
}

Vehicle::~Vehicle() {
    // dtor
    delete m_Steering;
    delete m_HeadingSmoother;
    delete m_WeaponSelectionRegulator;
    delete m_GoalArbitrationRegulator;
    delete m_TargetSelectionRegulator;
    delete m_TriggerTestRegulator;
    delete m_VisionUpdateRegulator;

    delete m_AttackSys;
    delete m_TargSys;
    delete m_SensoryMem;

    m_Steering = nullptr;
    m_HeadingSmoother = nullptr;
    m_WeaponSelectionRegulator = nullptr;
    m_GoalArbitrationRegulator = nullptr;
    m_TargetSelectionRegulator = nullptr;
    m_TriggerTestRegulator = nullptr;
    m_VisionUpdateRegulator = nullptr;

    m_AttackSys = nullptr;
    m_TargSys = nullptr;
    m_SensoryMem = nullptr;
}

//-------------------------------- Update -------------------------------------
//
void Vehicle::Update(float elapsedTime) {
    // process the current active goal
    m_Brain->Process();

    // calculate a steering force to move the bot to achieve its goal
    updateMovement(elapsedTime);

    // if the bot is under AI control
    if (!m_Possessed) {
        // choose a target from the bots memory
        if (m_TargetSelectionRegulator->isReady()) {
            m_TargSys->update();
        }

        // appraise and arbitrate between all possible high level goals
        if (m_GoalArbitrationRegulator->isReady()) {
            m_Brain->Arbitrate();
        }

        // update the sensory memory with any visual stimulus
        if (m_VisionUpdateRegulator->isReady()) {
            m_SensoryMem->updateVision();
        }
        // use the current attack on an enemy if possible
        m_AttackSys->takeAimAndShoot();
    }
}

void Vehicle::updateMovement(float elapsedTime) {
    // update the time elapsed
    m_ElapsedTime = elapsedTime;

    glm::vec2 steeringForce(0.0);

    // calculate the combined force from each steering behavior in the
    // vehicle's list
    steeringForce = m_Steering->Calculate();

    // if no steering force is produced decelerate the player by applying a
    // braking force
    if (glm::length(steeringForce) < 0.0001f) {
        const glm::vec2 BrakingRate(0.8f);

        m_VelocityVec = m_VelocityVec * BrakingRate;
    }

    // Acceleration = Force/Mass
    glm::vec2 acceleration = steeringForce * glm::vec2(1 / getMass());

    // update velocity
    m_VelocityVec += acceleration * glm::vec2(elapsedTime);

    // get speed as velocityVec length
    float speed = glm::length(m_VelocityVec);
    // make sure vehicle does not exceed maximum velocity
    if (speed > m_MaxSpeed) {
        float speedLimiterRatio = m_MaxSpeed / speed;
        m_VelocityVec *= glm::vec2(speedLimiterRatio);
    }

    // update the position
    setPosition(getPosition() + (m_VelocityVec * glm::vec2(elapsedTime)));

    // update the heading if the vehicle has a non zero velocity
    if (glm::length(m_VelocityVec) > 0.00000001) {
        setHeadingVec(glm::normalize(m_VelocityVec));
    }
    if (isSmoothingOn()) {
        m_SmoothedHeading = m_HeadingSmoother->Update(getHeadingVec());
    }
    setHeadingVec(m_SmoothedHeading);
}

SteeringBehaviours* Vehicle::getSteering() {
    return m_Steering;
}

GameWorld* Vehicle::getGameWorld() {
    return m_GameWorld;
}

glm::vec2 Vehicle::getSmoothedHeading() const {
    return m_SmoothedHeading;
}

bool Vehicle::isSmoothingOn() const {
    return m_SmoothingOn;
}

void Vehicle::setSmoothingOn() {
    m_SmoothingOn = true;
}

void Vehicle::setSmoothingOff() {
    m_SmoothingOn = false;
}

void Vehicle::toggleSmoothing() {
    m_SmoothingOn = !m_SmoothingOn;
}

float Vehicle::getElapsedTime() const {
    return m_ElapsedTime;
}

//--------------------------- HandleMessage -----------------------------------
//-----------------------------------------------------------------------------
bool Vehicle::HandleMessage(const Telegram& msg) {
    // handle any messages not handles by the goals
    switch (msg.Msg) {
        case Msg_YouGotHit:

            // just return if already dead or spawning
            if (isDead() || isSpawning())
                return true;

            // the extra info field of the telegram carries the amount of damage
            reduceHealth(DereferenceToType<int>(msg.ExtraInfo));

            // if this bot is now dead let the shooter know
            if (isDead()) {
                Dispatcher->DispatchMsg(SEND_MSG_IMMEDIATELY,
                                        getID(),
                                        msg.Sender,
                                        Msg_YouKilledMe,
                                        nullptr);
            }

            return true;

        case Msg_YouKilledMe:

            // the bot this bot has just killed should be removed as the target
            m_TargSys->clearTarget();

            return true;

        case Msg_GunshotSound:

            // add the source of this sound to the bot's percepts
            getSensoryMem()->updateWithSoundSource(
                static_cast<Vehicle*>(msg.ExtraInfo));

            return true;

        case Msg_UserHasRemovedBot: {
            auto removedBot = static_cast<Vehicle*>(msg.ExtraInfo);

            getSensoryMem()->removeBotFromMemory(removedBot);

            // if the removed bot is the target, make sure the target
            // is cleared
            if (removedBot == getTargetSys()->getTarget()) {
                getTargetSys()->clearTarget();
            }

            return true;
        }

        default:
            return false;
    }
}

bool Vehicle::rotateFacingTowardPosition(glm::vec2 target) {
    glm::vec2 toTarget = glm::normalize(target - m_Position);

    float dot = glm::dot(m_Facing, toTarget);

    // clamp to rectify any rounding errors
    dot = std::clamp(dot, -1.0f, 1.0f);

    // determine the angle between the heading vector and the target
    float angle = acos(dot);

    // return true if the bot's facing is within WeaponAimTolerance degs of
    // facing the target
    const float WeaponAimTolerance = 0.01f;  // 2 degs approx

    if (angle < WeaponAimTolerance) {
        m_Facing = toTarget;
        return true;
    }

    m_Facing = toTarget;
    return false;
}

//--------------------------------- ReduceHealth ----------------------------
void Vehicle::reduceHealth(unsigned int val) {
    m_Health -= val;

    if (m_Health <= 0) {
        setDead();
    }

    m_Hit = true;
}

void Vehicle::changeWeapon(unsigned int type) {
    m_AttackSys->changeAttack(type);
}

void Vehicle::fireWeapon(glm::vec2 pos) {
    m_AttackSys->shootAt(pos);
}

float Vehicle::calculateTimeToReachPosition(glm::vec2 pos) const {
    return glm::length(m_Position - pos) / (m_MaxSpeed * m_ElapsedTime);
}

bool Vehicle::isAtPosition(glm::vec2 pos) const {
    const static float tolerance = 0.2f;

    return glm::length(m_Position - pos) < tolerance;
}

bool Vehicle::hasLOSto(glm::vec2 pos) const {
    return m_GameWorld->isLOSOkay(m_Position, pos);
}

bool Vehicle::canWalkTo(glm::vec2 pos) const {
    return !m_GameWorld->isPathObstructed(m_Position, pos, m_BoundingRadius);
}

bool Vehicle::canWalkBetween(glm::vec2 from, glm::vec2 to) const {
    return !m_GameWorld->isPathObstructed(from, to, m_BoundingRadius);
}

bool Vehicle::canStepLeft(glm::vec2& PositionOfStep) const {
    static const float StepDistance = m_BoundingRadius * 2;

    PositionOfStep = getSideVec() * StepDistance
                     - getSideVec() * m_BoundingRadius;
    PositionOfStep -= m_Position;
    return canWalkTo(PositionOfStep);
}

bool Vehicle::canStepRight(glm::vec2& PositionOfStep) const {
    static const float StepDistance = m_BoundingRadius * 2;

    PositionOfStep = getSideVec() * glm::vec2(StepDistance)
                     + getSideVec() * glm::vec2(m_BoundingRadius);
    PositionOfStep += m_Position;
    return canWalkTo(PositionOfStep);
}

bool Vehicle::canStepForward(glm::vec2& PositionOfStep) const {
    static const float StepDistance = m_BoundingRadius * 2;

    PositionOfStep = m_Position + m_HeadingVec * StepDistance
                     + m_HeadingVec * glm::vec2(m_BoundingRadius);

    return canWalkTo(PositionOfStep);
}

bool Vehicle::canStepBackward(glm::vec2& PositionOfStep) const {
    static const float StepDistance = m_BoundingRadius * 2;

    PositionOfStep = m_Position - m_HeadingVec * glm::vec2(StepDistance)
                     - m_HeadingVec * glm::vec2(m_BoundingRadius);

    return canWalkTo(PositionOfStep);
}

void Vehicle::takePossession() {
    m_Possessed = true;
}

void Vehicle::exorcise() {
    m_Possessed = false;
}

const TargetingSystem* Vehicle::getTargetSys() const {
    return m_TargSys;
}
TargetingSystem* Vehicle::getTargetSys() {
    return m_TargSys;
}
Vehicle* Vehicle::getTargetBot() const {
    return m_TargSys->getTarget();
}
AttackSystem* Vehicle::getWeaponSys() const {
    return m_AttackSys;
}
SensoryMemory* Vehicle::getSensoryMem() const {
    return m_SensoryMem;
}