/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Gani Aliguzhinov * Co-authors: * ... * */ #include "ActionpointController.h" #include "core/XMLPort.h" #include #include "worldentities/Actionpoint.h" namespace orxonox { RegisterClass(ActionpointController); ActionpointController::ActionpointController(Context* context) : FightingController(context) { this->ticks_ = 0; this->bPatrolling_ = false; this->bInLoop_ = false; this->bLoop_ = false; this->bEndLoop_ = false; loopActionpoints_.clear(); parsedActionpoints_.clear(); actionpoints_.clear(); this->bTakenOver_ = false; this->action_ = Action::NONE; this->squaredaccuracy_ = 2500; this->bStartedDodging_ = false; this->bDefaultPatrol_ = true; this->bDefaultFightAll_ = true; RegisterObject(ActionpointController); } void ActionpointController::XMLPort( Element& xmlelement, XMLPort::Mode mode ) { SUPER( ActionpointController, XMLPort, xmlelement, mode ); XMLPortObject(ActionpointController, WorldEntity, "actionpoints", addActionpoint, getActionpoint, xmlelement, mode); XMLPortParam(ActionpointController, "defaultFightAll", setDefaultFightAll, getDefaultFightAll, xmlelement, mode).defaultValues(true); XMLPortParam(ActionpointController, "defaultPatrol", setDefaultPatrol, getDefaultPatrol, xmlelement, mode).defaultValues(true); } ActionpointController::~ActionpointController() { loopActionpoints_.clear(); parsedActionpoints_.clear(); actionpoints_.clear(); } void ActionpointController::tick(float dt) { if (!this->getControllableEntity() || !this->isActive()) return; //count ticks, ticks_ is unsigned, so overflow is not a problem ++this->ticks_; if (this->ticks_ == 1) { //those vectors are in reversed order after being set by XML. std::reverse(parsedActionpoints_.begin(), parsedActionpoints_.end()); std::reverse(actionpoints_.begin(), actionpoints_.end()); } //fly if (this->bHasTargetPosition_) { this->moveToTargetPosition(dt); }//or just rotate else if (this->bLookAtTarget_) { this->lookAtTarget(dt); } //don't fire rocket each tick if (timeout_ <= 0) { this->bFiredRocket_ = false; } else if (this->bFiredRocket_) { --this->timeout_; } //sometimes dodge, sometimes attack if (this->ticks_ % 80 <= 10) { this->bDodge_ = false; } else { this->bDodge_ = true; } //fire if you can if (this->bShooting_) { this->doFire(); } SUPER(ActionpointController, tick, dt); } /** @brief action() manages the state machine. */ void ActionpointController::action() { if (!this->getControllableEntity() || !this->isActive()) return; //deltaHp is used to know if this got attacked this->deltaHp = orxonox_cast (this->getControllableEntity())->getHealth() - this->previousHp; this->previousHp = orxonox_cast (this->getControllableEntity())->getHealth(); //look out for enemies if (this->bDefaultPatrol_ || (this->action_ != Action::FLY && this->action_ != Action::NONE)) { this->startAttackingEnemiesThatAreClose(); } //No action -> pop one from stack if (this->action_ == Action::NONE || this->bTakenOver_) { //if default behaviour is fighting all, push it onto the stack if (this->parsedActionpoints_.empty() && this->loopActionpoints_.empty() && this->bDefaultFightAll_) { Point p = { Action::FIGHTALL, "", Vector3::ZERO, false }; this->parsedActionpoints_.push_back (p); } this->executeActionpoint(); this->bTakenOver_ = false; } //Action fightall -> fight till nobody alive if (this->action_ == Action::FIGHTALL) { if (!this->hasTarget()) { ControllableEntity* newTarget = this->closestTarget(); if (newTarget) { this->setAction (Action::FIGHTALL, newTarget); } else { this->nextActionpoint(); this->executeActionpoint(); } } } //Action fight -> fight as long as enemies in range else if (this->action_ == Action::FIGHT) { if (!this->hasTarget() ) { //----find a target---- ControllableEntity* newTarget = this->closestTarget(); if (newTarget && CommonController::distance (this->getControllableEntity(), newTarget) < this->attackRange_) { this->setAction (Action::FIGHT, newTarget); } else { this->nextActionpoint(); this->executeActionpoint(); } } else if (this->hasTarget()) { //----fly in formation if far enough---- Vector3 diffVector = this->positionOfTarget_ - this->getControllableEntity()->getWorldPosition(); if (diffVector.length() > this->attackRange_) { ControllableEntity* newTarget = this->closestTarget(); if (newTarget && CommonController::distance (this->getControllableEntity(), newTarget) < this->attackRange_) { this->setAction (Action::FIGHT, newTarget); } else { this->nextActionpoint(); this->executeActionpoint(); } } } } else if (this->action_ == Action::FLY) { if (this->squaredDistanceToTarget() <= this->squaredaccuracy_) { this->nextActionpoint(); this->executeActionpoint(); } } else if (this->action_ == Action::PROTECT) { if (!this->getProtect()) { this->nextActionpoint(); this->executeActionpoint(); } this->stayNearProtect(); } else if (this->action_ == Action::ATTACK) { if (!this->hasTarget()) { this->nextActionpoint(); this->executeActionpoint(); } } } /** @brief if action is protect, this follows protect_ and fights enemies that are close */ void ActionpointController::setProtect (ControllableEntity* protect) { this->protect_ = protect; } ControllableEntity* ActionpointController::getProtect () { return this->protect_; } //XML method void ActionpointController::addActionpoint(WorldEntity* actionpoint) { std::string actionName; Vector3 position; std::string targetName; bool inLoop = false; Point p; if (actionpoint->getIdentifier()->getName() == "Actionpoint") { Actionpoint* ap = orxonox_cast (actionpoint); actionName = ap->getActionXML(); targetName = ap->getName(); position = ap->getWorldPosition(); if (this->bEndLoop_) { this->bInLoop_ = false; } if (!this->bInLoop_ && ap->getLoopStart()) { this->bInLoop_ = true; } if (this->bInLoop_ && ap->getLoopEnd()) { this->bEndLoop_ = true; } inLoop = this->bInLoop_; Action value; if ( actionName == "FIGHT" ) { value = Action::FIGHT; } else if ( actionName == "FLY" ) { value = Action::FLY; } else if ( actionName == "PROTECT" ) { value = Action::PROTECT; } else if ( actionName == "NONE" ) { value = Action::NONE; } else if ( actionName == "FIGHTALL" ) { value = Action::FIGHTALL; } else if ( actionName == "ATTACK" ) { value = Action::ATTACK; } else ThrowException( ParseError, std::string( "Attempting to set an unknown Action: '" )+ actionName + "'." ); p.action = value; p.name = targetName; p.position = position; p.inLoop = inLoop; } else { inLoop = true; p.action = Action::FLY; p.name = ""; p.position = actionpoint->getWorldPosition(); p.inLoop = inLoop; } parsedActionpoints_.push_back(p); this->actionpoints_.push_back(actionpoint); } //XML method WorldEntity* ActionpointController::getActionpoint(unsigned int index) const { if (index < this->actionpoints_.size()) return this->actionpoints_[index]; else return nullptr; } //XML method Action ActionpointController::getAction () { return this->action_; } //XML method std::string ActionpointController::getActionName() { switch ( this->action_ ) { case Action::FIGHT: { return "FIGHT"; } case Action::FLY: { return "FLY"; } case Action::PROTECT: { return "PROTECT"; } case Action::NONE: { return "NONE"; } case Action::FIGHTALL: { return "FIGHTALL"; } case Action::ATTACK: { return "ATTACK"; } default: return "NONE"; break; } } //XML method void ActionpointController::setAction (Action action) { this->action_ = action; } //set action and target/protect void ActionpointController::setAction (Action action, ControllableEntity* target) { if (!this->getControllableEntity()) return; this->action_ = action; if (action == Action::FIGHT || action == Action::FIGHTALL || action == Action::ATTACK) { if (target) this->setTarget (target); } else if (action == Action::PROTECT) { if (target) this->setProtect (target); } } //set action and target position void ActionpointController::setAction (Action action, const Vector3& target) { if (!this->getControllableEntity()) return; this->action_ = action; if (action == Action::FLY) { this->setTargetPosition (target); } } //set action and target position and orientation void ActionpointController::setAction (Action action, const Vector3& target, const Quaternion& orient ) { if (!this->getControllableEntity()) return; this->action_ = action; if (action == Action::FLY) { this->setTargetPosition (target); this->setTargetOrientation (orient); } } //------------------------------------------------------------------------------ //------------------------------Actionpoint methods----------------------------- //------------------------------------------------------------------------------ //POST: this starts doing what was asked by the last element of parsedActionpoints_, //if last element was failed to be parsed, next element will be executed. void ActionpointController::executeActionpoint() { if (!this->getControllableEntity()) return; Point p; if (this->bLoop_ && !loopActionpoints_.empty()) { p = loopActionpoints_.back(); } else if (this->bLoop_) { this->bLoop_ = false; return; } else if (!this->bLoop_ && !parsedActionpoints_.empty()) { p = parsedActionpoints_.back(); } else { this->setTarget(nullptr); this->setTargetPosition(this->getControllableEntity()->getWorldPosition()); this->action_ = Action::NONE; return; } if (!this->bLoop_ && this->parsedActionpoints_.back().inLoop) { //MOVES all points that are in loop to a loop vector this->fillLoop(); this->bLoop_ = true; executeActionpoint(); return; } this->setAction (p.action); switch (this->action_) { case Action::FIGHT: { std::string targetName = p.name; if (targetName == "") break; for (Pawn* pawn : ObjectList()) { if (CommonController::getName(pawn) == targetName) { this->setTarget (static_cast(pawn)); } } break; } case Action::FLY: { this->setTargetPosition( p.position ); if (this->squaredDistanceToTarget() <= this->squaredaccuracy_) { this->nextActionpoint(); this->executeActionpoint(); } break; } case Action::PROTECT: { std::string protectName = p.name; if (protectName == "reservedKeyword:human") { for (Pawn* pawn : ObjectList()) { if (orxonox_cast(pawn) && (pawn->getController()) && (pawn->getController()->getIdentifier()->getName() == "NewHumanController")) { this->setProtect (static_cast(pawn)); } } } else { for (Pawn* pawn : ObjectList()) { if (CommonController::getName(pawn) == protectName) { this->setProtect (static_cast(pawn)); } } } if (!this->getProtect()) { this->nextActionpoint(); this->executeActionpoint(); } break; } case Action::NONE: { break; } case Action::FIGHTALL: { break; } case Action::ATTACK: { std::string targetName = p.name; for (Pawn* pawn : ObjectList()) { if (CommonController::getName(pawn) == targetName) { this->setTarget (static_cast(pawn)); } } if (!this->hasTarget()) { this->nextActionpoint(); this->executeActionpoint(); } break; } default: break; } } //calculate where in world coordinates this ship has to be, so that it keeps distance to protect_, and fly there void ActionpointController::stayNearProtect() { if (!this->getControllableEntity()) return; Vector3 targetRelativePosition(0, 300, 300); if (!this->getProtect()) { this->nextActionpoint(); return; } Vector3 targetAbsolutePosition = ((this->getProtect()->getWorldPosition()) + (this->getProtect()->getWorldOrientation()* (targetRelativePosition))); this->setTargetPosition(targetAbsolutePosition); if (!this->getProtect()) { this->nextActionpoint(); return; } this->setTargetOrientation(this->getProtect()->getWorldOrientation()); } //remove current point from the stack void ActionpointController::nextActionpoint() { if (this->bLoop_) { if (this->bPatrolling_) { this->loopActionpoints_.pop_back(); this->bPatrolling_ = false; } else if (!this->loopActionpoints_.empty()) { this->moveBackToTop(); } } else { if (!this->parsedActionpoints_.empty()) { this->parsedActionpoints_.pop_back(); } } this->setAction(Action::NONE); this->bHasTargetPosition_ = false; } //if looping, instead of erasing point, move it to the top (back is what gets executed, so it's kinda reversed stack) void ActionpointController::moveBackToTop() { if (!this->getControllableEntity()) return; Point temp = loopActionpoints_.back(); loopActionpoints_.pop_back(); std::reverse (loopActionpoints_.begin(), loopActionpoints_.end()); loopActionpoints_.push_back(temp); std::reverse (loopActionpoints_.begin(), loopActionpoints_.end()); } //POST: moves all consecutive points that are in loop to the loop stack void ActionpointController::fillLoop() { loopActionpoints_.clear(); fillLoopReversed(); std::reverse (loopActionpoints_.begin(), loopActionpoints_.end()); } void ActionpointController::fillLoopReversed() { if (parsedActionpoints_.back().inLoop) { loopActionpoints_.push_back(parsedActionpoints_.back()); parsedActionpoints_.pop_back(); } if (parsedActionpoints_.back().inLoop) { fillLoopReversed(); } } //copy other ship's stacks so that if it dies, this can finish that ship's actions void ActionpointController::takeActionpoints (const std::vector& vector, const std::vector& loop, bool b) { if (!this->getControllableEntity()) return; this->parsedActionpoints_ = vector; this->loopActionpoints_ = loop; this->bLoop_ = b; this->bTakenOver_ = true; } //attack closest target void ActionpointController::setClosestTarget() { this->setTarget (static_cast( closestTarget() ) ); } //find closest target Pawn* ActionpointController::closestTarget() { if (!this->getControllableEntity()) return nullptr; Pawn* closestTarget = nullptr; float minDistance = std::numeric_limits::infinity(); Gametype* gt = this->getGametype(); for (Pawn* pawn : ObjectList()) { if ( CommonController::sameTeam (this->getControllableEntity(), static_cast(pawn), gt) ) continue; float distance = CommonController::distance (pawn, this->getControllableEntity()); if (distance < minDistance) { closestTarget = pawn; minDistance = distance; } } if (closestTarget) { return closestTarget; } return nullptr; } //push action FIGHT to the stack and set target to the closest enemy void ActionpointController::startAttackingEnemiesThatAreClose() { if (!this->getControllableEntity()) return; if (!this->target_ || (this->target_ && CommonController::distance (this->getControllableEntity(), this->target_) > this->attackRange_)) { Pawn* newTarget = this->closestTarget(); if ( newTarget && CommonController::distance (this->getControllableEntity(), static_cast(newTarget)) <= this->attackRange_ ) { this->setTarget(newTarget); if (this->bLoop_ && !this->bPatrolling_) { Point p = { Action::FIGHT, "", Vector3::ZERO, true }; this->loopActionpoints_.push_back(p); } else if (!this->bPatrolling_) { Point p = { Action::FIGHT, "", Vector3::ZERO, false }; this->parsedActionpoints_.push_back(p); } this->bPatrolling_ = true; this->executeActionpoint(); } } } }