#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(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(msg.ExtraInfo)); return true; case Msg_UserHasRemovedBot: { auto removedBot = static_cast(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; }