/* * 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: * Fabian 'x3n' Landau * Co-authors: * Dominik Solenicki * */ //bug or feature? Press 4 control keys from {Q,W,E,A,S,D,C} at the same time or 3 keys from {Q,E,A,D}, spaceship goes in free fly mode #include "controllers/CommonController.h" #include "core/XMLPort.h" #include "weaponsystem/WeaponMode.h" #include "weaponsystem/WeaponPack.h" #include "weaponsystem/Weapon.h" #include "weaponsystem/WeaponSlot.h" #include "weaponsystem/WeaponSlot.h" #include "worldentities/pawns/SpaceShip.h" #include "Scene.h" #include "worldentities/pawns/TeamBaseMatchBase.h" #include "gametypes/TeamDeathmatch.h" #include "gametypes/Dynamicmatch.h" #include "gametypes/Mission.h" #include "gametypes/Gametype.h" #include "controllers/WaypointPatrolController.h" #include "controllers/NewHumanController.h" #include "controllers/DroneController.h" namespace orxonox { RegisterClass( CommonController ); const float SPEED = 0.9f/0.02f; const float ROTATEFACTOR = 1.0f/0.02f; CommonController::CommonController( Context* context ): Controller( context ) { this->action_ = Action::FLY; this->stopLookingAtTarget(); RegisterObject( CommonController ); } CommonController::~CommonController() { //orxout(internal_error) << "I died, my Rank is " << rank_ << endl; } void CommonController::XMLPort( Element& xmlelement, XMLPort::Mode mode ) { SUPER( CommonController, XMLPort, xmlelement, mode ); XMLPortParam( CommonController, "formationMode", setFormationModeXML, getFormationModeXML, xmlelement, mode ); XMLPortParam( CommonController, "action", setActionXML, getActionXML, xmlelement, mode ); XMLPortParam ( CommonController, "protect", setProtectXML, getProtectXML, xmlelement, mode ); //XMLPortParam ( CommonController, "enemy", setEnemyXML, getEnemyXML, xmlelement, mode ); } void CommonController::setProtectXML( std::string val ) { for (ObjectList::iterator itP = ObjectList::begin(); itP; ++itP) { if ((*itP)->getName() == val) { this->setProtect (static_cast(*itP)); } } } std::string CommonController::getProtectXML () { if (!this->getProtect()) return "noProtectWasSet"; return this->getProtect()->getName(); } void CommonController::setProtect (ControllableEntity* protect) { this->protect_ = protect; } ControllableEntity* CommonController::getProtect () { return this->protect_; } void CommonController::setActionXML( std::string val) { const std::string valUpper = getUppercase( val ); Action::Value value; if ( valUpper == "FIGHT" ) value = Action::FIGHT; else if ( valUpper == "FLY" ) value = Action::FLY; else if ( valUpper == "PROTECT" ) value = Action::PROTECT; else ThrowException( ParseError, std::string( "Attempting to set an unknown Action: '" )+ val + "'." ); this->setAction( value ); } std::string CommonController::getActionXML() { switch ( this->action_ ) { case Action::FIGHT: { return "FIGHT"; break; } case Action::FLY: { return "FLY"; break; } case Action::PROTECT: { return "PROTECT"; break; } default: return "FIGHT"; break; } } void CommonController::setFormationModeXML( std::string val ) { const std::string valUpper = getUppercase( val ); FormationMode::Value value; if ( valUpper == "WALL" ) value = FormationMode::WALL; else if ( valUpper == "FINGER4" ) value = FormationMode::FINGER4; else if ( valUpper == "DIAMOND" ) value = FormationMode::DIAMOND; else ThrowException( ParseError, std::string( "Attempting to set an unknown FormationMode: '" )+ val + "'." ); this->setFormationMode( value ); } std::string CommonController::getFormationModeXML() { switch ( this->formationMode_ ) { case FormationMode::WALL: { return "WALL"; break; } case FormationMode::FINGER4: { return "FINGER4"; break; } case FormationMode::DIAMOND: { return "DIAMOND"; break; } default: return "DIAMOND"; break; } } Action::Value CommonController::getAction () { return this->action_; } void CommonController::setAction (Action::Value action) { this->action_ = action; } void CommonController::setAction (Action::Value action, ControllableEntity* target) { this->action_ = action; if (action == Action::FIGHT) { if (target) this->setTarget (target); } else if (action == Action::PROTECT) { } } void CommonController::setAction (Action::Value action, const Vector3& target) { this->action_ = action; if (action == Action::FLY) { this->setTargetPosition (target); } else if (action == Action::PROTECT) { } } void CommonController::setAction (Action::Value action, const Vector3& target, const Quaternion& orient ) { this->action_ = action; if (action == Action::FLY) { this->setTargetPosition (target); this->setTargetOrientation (orient); } else if (action == Action::PROTECT) { } } void CommonController::setClosestTarget() { if (!this->getControllableEntity()) return; Pawn* closestTarget = 0; float minDistance = std::numeric_limits::infinity(); Gametype* gt = this->getGametype(); for (ObjectList::iterator itP = ObjectList::begin(); itP; ++itP) { if ( CommonController::sameTeam (this->getControllableEntity(), static_cast(*itP), gt) ) continue; float distance = CommonController::distance (*itP, this->getControllableEntity()); if (distance < minDistance) { closestTarget = *itP; minDistance = distance; } } if (closestTarget) { (this)->setTarget(static_cast(closestTarget)); } } void CommonController::maneuver() { maneuverCounter_++; if (maneuverCounter_ > 5) maneuverCounter_ = 0; if ( this->target_ && this->getControllableEntity()) { Vector3 thisPosition = this->getControllableEntity()->getWorldPosition(); //Quaternion thisOrientation = this->getControllableEntity()->getOrientation(); this->setPositionOfTarget( getPredictedPosition( thisPosition, hardcoded_projectile_speed, this->target_->getWorldPosition() , this->target_->getVelocity() ) ); this->setOrientationOfTarget( this->target_->getOrientation() ); Vector3 diffVector = this->positionOfTarget_ - thisPosition; float diffLength = diffVector.length(); Vector3 diffUnit = diffVector/diffLength; //bool bThisIsLookingAtTarget = this->isLooking ( getControllableEntity(), this->target_, math::pi/4 ); bool bTargetIsLookingAtThis = this->isLooking ( this->target_, getControllableEntity(), math::pi/10.0f ); //too far? well, come closer then if ( diffLength > 3000 ) { if (diffLength < 6000) { } else { } this->setTargetPosition( this->positionOfTarget_ ); } //too close? How do u expect to dodge anything? Just attack! else if ( diffLength < 500 ) { //at this point, just look and shoot if ( diffLength < 250 ) { this->stopMoving(); this->startLookingAtTarget(); } else { this->setTargetPosition( this->positionOfTarget_ ); } } //Good distance? Check if target looks at us. It doesn't? Go hunt! else if ( !bTargetIsLookingAtThis ) { this->setTargetPosition( this->positionOfTarget_ ); /* if (maneuverCounter_ == 0) { this->setTargetPosition( this->positionOfTarget_ ); return; } else { dodge( thisPosition, diffUnit ); }*/ } //That's unfortunate, he is looking and probably shooting... try to dodge what we can... else { if (maneuverCounter_ == 0) { this->setTargetPosition( this->positionOfTarget_ ); return; } dodge( thisPosition, diffUnit ); } } //orxout ( internal_error ) << "ManeuverType = " << this->maneuverType_ << endl; } ControllableEntity* CommonController::getTarget() { return this->target_; } void CommonController::dodge(Vector3& thisPosition, Vector3& diffUnit) { float factorX = 0, factorY = 0, factorZ = 0; float rand = randomInRange (0, 1); if (rand <= 0.5) { factorX = 1; } else { factorX = -1; } rand = randomInRange (0, 1); if (rand <= 0.5) { factorY = 1; } else { factorY = -1; } rand = randomInRange (0, 1); if (rand <= 0.5) { factorZ = 1; } else { factorZ = -1; } Vector3 target = ( diffUnit )* 8000.0f; Vector3* randVector = new Vector3( factorX * randomInRange( 10000, 40000 ), factorY * randomInRange( 10000, 40000 ), factorZ * randomInRange( 10000, 40000 ) ); Vector3 projection = randVector->dotProduct( diffUnit )* diffUnit; *randVector -= projection; target += *randVector; this->setTargetPosition( thisPosition + target ); } void CommonController::stopMoving() { this->bHasTargetPosition_ = false; } void CommonController::startLookingAtTarget() { this->bLookAtTarget_ = true; } void CommonController::stopLookingAtTarget() { this->bLookAtTarget_ = false; } void CommonController::lookAtTarget(float dt) { ControllableEntity* entity = this->getControllableEntity(); if ( !entity ) return; Vector2 coord = get2DViewCoordinates ( entity->getPosition() , entity->getOrientation() * WorldEntity::FRONT, entity->getOrientation() * WorldEntity::UP, positionOfTarget_ ); //rotates should be in range [-1,+1], clamp cuts off all that is not float rotateX = -clamp( coord.x * 10, -1.0f, 1.0f ); float rotateY = clamp( coord.y * 10, -1.0f, 1.0f ); //Yaw and Pitch are enough to start facing the target this->getControllableEntity() ->rotateYaw( ROTATEFACTOR * rotateX * dt ); this->getControllableEntity() ->rotatePitch( ROTATEFACTOR * rotateY * dt ); } bool CommonController::setWingman ( CommonController* wingman ) { return false; } bool CommonController::hasWingman() { return true; } void CommonController::setTarget( ControllableEntity* target ) { this->target_ = target; //orxout ( internal_error ) << " TARGET SET " << endl; if ( this->target_ ) { this->setPositionOfTarget( target_->getWorldPosition() ); } } bool CommonController::hasTarget() { if ( this->target_ ) return true; return false; } void CommonController::setPositionOfTarget( const Vector3& target ) { this->positionOfTarget_ = target; this->bHasPositionOfTarget_ = true; } void CommonController::setOrientationOfTarget( const Quaternion& orient ) { this->orientationOfTarget_=orient; this->bHasOrientationOfTarget_=true; } void CommonController::setTargetPosition( const Vector3& target ) { this->targetPosition_ = target; this->bHasTargetPosition_ = true; } void CommonController::setTargetOrientation( const Quaternion& orient ) { this->targetOrientation_=orient; this->bHasTargetOrientation_=true; } void CommonController::setTargetOrientation( ControllableEntity* target ) { if ( target ) setTargetOrientation( target->getOrientation() ); } //copy the Roll orientation of given Quaternion. void CommonController::copyOrientation( const Quaternion& orient, float dt ) { //roll angle difference in radian float diff=orient.getRoll( false ).valueRadians() -( this->getControllableEntity() ->getOrientation() .getRoll( false ).valueRadians() ); while( diff>math::twoPi )diff-=math::twoPi; while( diff<-math::twoPi )diff+=math::twoPi; this->getControllableEntity() ->rotateRoll( diff*ROTATEFACTOR * dt ); } void CommonController::copyTargetOrientation( float dt ) { if ( bHasTargetOrientation_ ) { copyOrientation( targetOrientation_, dt ); } } void CommonController::moveToTargetPosition( float dt ) { this->moveToPosition( this->targetPosition_, dt ); } void CommonController::moveToPosition( const Vector3& target, float dt ) { //100 is ( so far )the smallest tolerance ( empirically found )that can be reached, //with smaller distance spaceships can't reach position and go circles around it instead int tolerance = 65; ControllableEntity* entity = this->getControllableEntity(); Vector2 coord = get2DViewCoordinates ( entity->getPosition() , entity->getOrientation() * WorldEntity::FRONT, entity->getOrientation() * WorldEntity::UP, target ); float distance = ( target - this->getControllableEntity() ->getPosition() ).length(); //rotates should be in range [-1,+1], clamp cuts off all that is not float rotateX = -clamp( coord.x * 10, -1.0f, 1.0f ); float rotateY = clamp( coord.y * 10, -1.0f, 1.0f ); if ( distance > tolerance ) { //Yaw and Pitch are enough to start facing the target this->getControllableEntity() ->rotateYaw( ROTATEFACTOR * rotateX * dt ); this->getControllableEntity() ->rotatePitch( ROTATEFACTOR * rotateY * dt ); //300 works, maybe less is better if ( distance < 400 ) { //Change roll when close. When Spaceship faces target, roll doesn't affect it's trajectory //It's important that roll is not changed in the process of changing yaw and pitch //Wingmen won't face same direction as Leaders, but when Leaders start moving //Yaw and Pitch will adapt. if ( bHasTargetOrientation_ ) { copyTargetOrientation( dt ); } } this->getControllableEntity() ->moveFrontBack( SPEED * dt ); } else { bHasTargetPosition_ = false; bHasTargetOrientation_ = false; } } float CommonController::randomInRange( float a, float b ) { float random = rnd( 1.0f ); float diff = b - a; float r = random * diff; return a + r; } //to be called in action //PRE: relativeTargetPosition is desired position relative to the spaceship, //angleRoll is the angle in degrees of Roll that should be applied by the end of the movement //POST: target orientation and position are set, so that it can be used by MoveAndRoll() void CommonController::moveToPoint( const Vector3& relativeTargetPosition, float angleRoll ) { ControllableEntity* entity = this->getControllableEntity(); if ( !entity ) return; Quaternion orient = entity->getWorldOrientation(); Quaternion rotation = Quaternion( Degree( angleRoll ), Vector3::UNIT_Z ); Vector3 target = orient * relativeTargetPosition + entity->getWorldPosition(); setTargetPosition( target ); orient = orient * rotation; this->setTargetOrientation( orient ); } //to be called in tick //PRE: MoveToPoint was called //POST: spaceship changes its yaw and pitch to point towards targetPosition_, //moves towards targetPosition_ by amount depending on dt and its speed, //rolls by amount depending on the difference between angleRoll_ and angleRolled_, dt, and //angular speed //if position reached with a certain tolerance, and angleRolled_ = angleRoll_, returns false, //otherwise returns true //dt is normally around 0.02f, which makes it 1/0.02 = 50 frames/sec bool CommonController::moveAndRoll( float dt ) { float factor = 1; if ( !this->getControllableEntity() ) return false; if ( this->rank_ == Rank::DIVISIONLEADER ) factor = 0.8; if ( this->rank_ == Rank::SECTIONLEADER ) factor = 0.9; int tolerance = 60; ControllableEntity* entity = this->getControllableEntity(); if ( !entity ) return true; Vector2 coord = get2DViewCoordinates ( entity->getPosition() , entity->getOrientation() * WorldEntity::FRONT, entity->getOrientation() * WorldEntity::UP, targetPosition_ ); float distance = ( targetPosition_ - this->getControllableEntity() ->getPosition() ).length(); //rotates should be in range [-1,+1], clamp cuts off all that is not float rotateX = clamp( coord.x * 10, -1.0f, 1.0f ); float rotateY = clamp( coord.y * 10, -1.0f, 1.0f ); if ( distance > tolerance ) { //Yaw and Pitch are enough to start facing the target this->getControllableEntity() ->rotateYaw( -2.0f * ROTATEFACTOR * rotateX * dt ); this->getControllableEntity() ->rotatePitch( 2.0f * ROTATEFACTOR * rotateY * dt ); //Roll if ( bHasTargetOrientation_ ) { copyTargetOrientation( dt ); } //Move this->getControllableEntity() ->moveFrontBack( 1.2f * SPEED * factor * dt ); //if still moving, return false return false; } else { //if finished, return true; return true; } } float CommonController::squaredDistanceToTarget() const { if ( !this->getControllableEntity() ) return 0; if ( !this->target_ || !this->getControllableEntity() ) return ( this->getControllableEntity() ->getPosition() .squaredDistance( this->targetPosition_ ) ); else return ( this->getControllableEntity() ->getPosition() .squaredDistance( this->positionOfTarget_ ) ); } bool CommonController::isLookingAtTarget( float angle )const { if ( !this->getControllableEntity() || !this->target_ ) return false; return ( getAngle( this->getControllableEntity() ->getPosition() , this->getControllableEntity() ->getOrientation() * WorldEntity::FRONT, this->positionOfTarget_ ) < angle ); } bool CommonController::isLooking( ControllableEntity* entityThatLooks, ControllableEntity* entityBeingLookedAt, float angle ) { if ( !entityThatLooks || !entityBeingLookedAt ) return false; return ( getAngle( entityThatLooks ->getPosition() , entityThatLooks->getOrientation() * WorldEntity::FRONT, entityBeingLookedAt->getWorldPosition() ) < angle ); } bool CommonController::canFire() { //no target? why fire? if ( !this->target_ ) return false; Vector3 newPositionOfTarget = getPredictedPosition( this->getControllableEntity() ->getWorldPosition() , hardcoded_projectile_speed, this->target_->getWorldPosition() , this->target_->getVelocity() ); if ( newPositionOfTarget != Vector3::ZERO ) { this->setPositionOfTarget( newPositionOfTarget ); } float squaredDistance = squaredDistanceToTarget(); if ( squaredDistance < 9000000.0f && this->isLookingAtTarget( math::pi / 20.0f)) { return true; } else { return false; } } float CommonController::distance (ControllableEntity* entity1, ControllableEntity* entity2) { if (!entity1 || !entity2) return std::numeric_limits::infinity(); return ( entity1->getPosition() - entity2->getPosition() ).length(); } bool CommonController::sameTeam (ControllableEntity* entity1, ControllableEntity* entity2, Gametype* gametype) { /*if (!entity1 || !entity2) return false; return entity1->getTeam() == entity2->getTeam();*/ if (entity1 == entity2) return true; int team1 = entity1->getTeam(); int team2 = entity2->getTeam(); Controller* controller = 0; if (entity1->getController()) controller = entity1->getController(); else controller = entity1->getXMLController(); if (controller) { CommonController* ac = orxonox_cast(controller); if (ac) team1 = ac->getTeam(); } if (entity2->getController()) controller = entity2->getController(); else controller = entity2->getXMLController(); if (controller) { CommonController* ac = orxonox_cast(controller); if (ac) team2 = ac->getTeam(); } TeamGametype* tdm = orxonox_cast(gametype); if (tdm) { if (entity1->getPlayer()) team1 = tdm->getTeam(entity1->getPlayer()); if (entity2->getPlayer()) team2 = tdm->getTeam(entity2->getPlayer()); } TeamBaseMatchBase* base = 0; base = orxonox_cast(entity1); if (base) { switch (base->getState()) { case BaseState::ControlTeam1: team1 = 0; break; case BaseState::ControlTeam2: team1 = 1; break; case BaseState::Uncontrolled: default: team1 = -1; } } base = orxonox_cast(entity2); if (base) { switch (base->getState()) { case BaseState::ControlTeam1: team2 = 0; break; case BaseState::ControlTeam2: team2 = 1; break; case BaseState::Uncontrolled: default: team2 = -1; } } DroneController* droneController = 0; droneController = orxonox_cast(entity1->getController()); if (droneController && static_cast(droneController->getOwner()) == entity2) return true; droneController = orxonox_cast(entity2->getController()); if (droneController && static_cast(droneController->getOwner()) == entity1) return true; DroneController* droneController1 = orxonox_cast(entity1->getController()); DroneController* droneController2 = orxonox_cast(entity2->getController()); if (droneController1 && droneController2 && droneController1->getOwner() == droneController2->getOwner()) return true; Dynamicmatch* dynamic = orxonox_cast(gametype); if (dynamic) { if (dynamic->notEnoughPigs||dynamic->notEnoughKillers||dynamic->notEnoughChasers) {return false;} if (entity1->getPlayer()) team1 = dynamic->getParty(entity1->getPlayer()); if (entity2->getPlayer()) team2 = dynamic->getParty(entity2->getPlayer()); if (team1 ==-1 ||team2 ==-1 ) {return false;} else if (team1 == dynamic->chaser && team2 != dynamic->chaser) {return false;} else if (team1 == dynamic->piggy && team2 == dynamic->chaser) {return false;} else if (team1 == dynamic->killer && team2 == dynamic->chaser) {return false;} else return true; } return (team1 == team2 && team1 != -1); } void CommonController::doFire() { if ( !this->target_ || !this->getControllableEntity() ) { return; } Pawn* pawn = orxonox_cast( this->getControllableEntity() ); if ( pawn ) //pawn->setAimPosition( this->getControllableEntity() ->getWorldPosition() + 4000*( this->getControllableEntity() ->getOrientation() * WorldEntity::FRONT )); pawn->setAimPosition( this->positionOfTarget_ ); this->getControllableEntity() ->fire( 0 ); } }