// ====================================================================== // // AiCreatureController.cpp // // copyright 2003, sony online entertainment // // ====================================================================== #include "serverGame/FirstServerGame.h" #include "serverGame/AiCreatureController.h" #include "serverGame/AiMovementBase.h" #include "serverGame/AiMovementSwarm.h" #include "serverGame/AiMovementFace.h" #include "serverGame/AiMovementFlee.h" #include "serverGame/AiMovementFollow.h" #include "serverGame/AiMovementIdle.h" #include "serverGame/AiMovementLoiter.h" #include "serverGame/AiMovementMove.h" #include "serverGame/AiMovementPathFollow.h" #include "serverGame/AiMovementPatrol.h" #include "serverGame/AiMovementWander.h" #include "serverGame/AiMovementWanderInterior.h" #include "serverGame/AiCreatureCombatProfile.h" #include "serverGame/AiCreatureData.h" #include "serverGame/AiLogManager.h" #include "serverGame/CellObject.h" #include "serverGame/ConfigServerGame.h" #include "serverGame/ContainerInterface.h" #include "serverGame/CreatureObject.h" #include "serverGame/GameServer.h" #include "serverGame/NameManager.h" #include "serverGame/ObjectTracker.h" #include "serverGame/ServerMessageForwarding.h" #include "serverGame/ServerWorld.h" #include "serverGame/WeaponObject.h" #include "serverNetworkMessages/AiMovementMessage.h" #include "serverNetworkMessages/AiCreatureStateMessage.h" #include "serverPathfinding/CityPathGraphManager.h" #include "serverScript/GameScriptObject.h" #include "serverScript/ScriptParameters.h" #include "serverScript/ScriptFunctionTable.h" #include "sharedCollision/CollisionProperty.h" #include "sharedCollision/CollisionUtils.h" #include "sharedCollision/CollisionWorld.h" #include "sharedCollision/Floor.h" #include "sharedCollision/FloorLocator.h" #include "sharedCollision/Footprint.h" #include "sharedDebug/PerformanceTimer.h" #include "sharedDebug/Profiler.h" #include "sharedFile/FileNameUtils.h" #include "sharedFoundation/ConstCharCrcString.h" #include "sharedFoundation/FormattedString.h" #include "sharedFoundation/Os.h" #include "sharedGame/AiDebugString.h" #include "sharedGame/SharedCreatureObjectTemplate.h" #include "sharedUtility/Location.h" #include "sharedLog/Log.h" #include "sharedNetworkMessages/AIDebuggingMessages.h" #include "sharedNetworkMessages/GenericValueTypeMessage.h" #include "sharedNetworkMessages/MessageQueueGenericValueType.h" #include "sharedObject/AlterResult.h" #include "sharedObject/Appearance.h" #include "sharedObject/AppearanceTemplate.h" #include "sharedObject/CellProperty.h" #include "sharedObject/ObjectTemplateList.h" #include "sharedObject/NetworkIdManager.h" #include "sharedUtility/Location.h" #include "UnicodeUtils.h" #include using namespace Scripting; // ====================================================================== // // AiCreatureControllerNamespace // // ====================================================================== namespace AiCreatureControllerNamespace { bool s_installed = false; typedef std::set ObserverList; PersistentCrcString * s_defaultCreatureName = NULL; void remove(); Location getLocation(ServerObject const & serverObject); } using namespace AiCreatureControllerNamespace; //----------------------------------------------------------------------- void AiCreatureControllerNamespace::remove() { DEBUG_FATAL(!s_installed, ("Not installed.")); delete s_defaultCreatureName; s_defaultCreatureName = NULL; s_installed = false; } //----------------------------------------------------------------------- Location AiCreatureControllerNamespace::getLocation(ServerObject const & serverObject) { CellProperty const * const cellProperty = serverObject.getParentCell(); Object const * const cellObject = (cellProperty != NULL) ? &cellProperty->getOwner() : NULL; Vector const & positionRelativeToCellOrWorld = (cellObject != NULL) ? serverObject.getPosition_c() : serverObject.getPosition_w(); NetworkId const & networkIdForCellOrWorld = (cellObject != NULL) ? cellObject->getNetworkId() : NetworkId::cms_invalid; return Location(positionRelativeToCellOrWorld, networkIdForCellOrWorld, Location::getCrcBySceneName(serverObject.getSceneId())); } // ====================================================================== // // AiCreatureController // // ====================================================================== //----------------------------------------------------------------------- AICreatureController::AICreatureController(CreatureObject * newOwner) : CreatureController(newOwner) , m_movement() , m_pendingMovement() , m_suspendedMovement() , m_lastStartPosition(newOwner->getPosition_p()) , m_lastEndPosition(newOwner->getPosition_p()) , m_sleepTimer(0.0f) , m_inPathfindingRegion(false) , m_stuckCounter(0) , m_authority(false) , m_speed(0.0f) , m_aiCreatureData(&AiCreatureData::getDefaultCreatureData()) , m_primaryWeaponActions() , m_secondaryWeaponActions() , m_running(false) , m_homeLocation() , m_primaryWeapon() , m_secondaryWeapon() , m_frozen(false) , m_combatStartLocation() , m_retreating(false) , m_retreatingStartTime(0) , m_logging(false) , m_creatureName() , m_hibernationDelay(0) , m_hibernationTimer(0) #ifdef _DEBUG , m_aiDebugStringCrc(0) #endif // _DEBUG , m_invalidTarget(false) { m_creatureName.setSourceObject(this); } //----------------------------------------------------------------------- AICreatureController::~AICreatureController() { m_movement = AiMovementBaseNullPtr; m_pendingMovement = AiMovementBaseNullPtr; m_suspendedMovement = AiMovementBaseNullPtr; Object * const owner = getOwner(); if (owner != NULL && owner->isAuthoritative() && !getHibernate() && m_hibernationDelay.get() > 0 && m_hibernationTimer.get() > 0) ObjectTracker::removeDelayedHibernatingAI(); if ( (owner != NULL) && AiLogManager::isLogging(owner->getNetworkId())) { AiLogManager::setLogging(owner->getNetworkId(), false); } } //----------------------------------------------------------------------- void AICreatureController::install() { DEBUG_FATAL(s_installed, ("Already installed.")); LOGC(ConfigServerGame::isAiLoggingEnabled(), "debug_ai", ("")); LOGC(ConfigServerGame::isAiLoggingEnabled(), "debug_ai", ("-------- AI DEBUG - NEW SESSION --------")); LOGC(ConfigServerGame::isAiLoggingEnabled(), "debug_ai", ("")); AiCreatureCombatProfile::install(); AiCreatureData::install(); s_defaultCreatureName = new PersistentCrcString("NonCombatAI", false); ExitChain::add(AiCreatureControllerNamespace::remove, "AiCreatureControllerNamespace::remove"); s_installed = true; } //----------------------------------------------------------------------- void AICreatureController::CreatureNameChangedCallback::modified(AICreatureController & target, std::string const & /*oldValue*/, std::string const & newValue, bool /*local*/) const { target.onCreatureNameChanged(newValue); } //----------------------------------------------------------------------- void AICreatureController::handleMessage (int message, float value, const MessageQueue::Data* const data, uint32 flags) { CreatureObject * const owner = static_cast(getOwner()); if (owner == NULL) { DEBUG_FATAL(true, ("Owner is NULL in AiCreatureController::handleMessage\n")); return; } switch (message) { case CM_aiSetMovement: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetMovement) owner(%s)", getDebugInformation().c_str())); AiMovementMessage const * const msg = safe_cast(data); if (msg != NULL) { if (owner->isAuthoritative()) { WARNING(true, ("AICreatureController::handleMessage() owner(%s) Received CM_aiSetMovement(%d) for an authoritative object. Only proxied objects should receive this controller message.", getDebugInformation().c_str(), static_cast(msg->getMovementType()))); } else { if (msg->getMovement() != AiMovementBaseNullPtr) { changeMovement(msg->getMovement()); } else { changeMovement(AiMovementBaseNullPtr); } } } } break; case CM_aiSetMovementRun: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetMovementRun) owner(%s)", getDebugInformation().c_str())); setMovementRun(); } break; case CM_aiSetMovementWalk: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetMovementWalk) owner(%s)", getDebugInformation().c_str())); setMovementWalk(); } break; case CM_aiSetCreatureName: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetCreatureName) owner(%s)", getDebugInformation().c_str())); MessageQueueGenericValueType const * const msg = safe_cast const *>(data); if (msg != NULL) { setCreatureName(msg->getValue()); } } break; case CM_aiSetHomeLocation: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetHomeLocation) owner(%s)", getDebugInformation().c_str())); MessageQueueGenericValueType const * const msg = safe_cast const *>(data); if (msg != NULL) { setHomeLocation(msg->getValue()); } } break; case CM_aiSetFrozen: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetFrozen) owner(%s)", getDebugInformation().c_str())); MessageQueueGenericValueType const * const msg = safe_cast const *>(data); if (msg != NULL) { setFrozen(msg->getValue()); } } break; case CM_aiSetRetreating: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetRetreating) owner(%s)", getDebugInformation().c_str())); MessageQueueGenericValueType const * const msg = safe_cast const *>(data); if (msg != NULL) { setRetreating(msg->getValue()); } } break; case CM_aiSetLogging: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiSetLogging) owner(%s)", getDebugInformation().c_str())); MessageQueueGenericValueType const * const msg = safe_cast const *>(data); if (msg != NULL) { setLogging(msg->getValue()); } } break; case CM_aiMarkCombatStartLocation: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiMarkCombatStartLocation) owner(%s)", getDebugInformation().c_str())); markCombatStartLocation(); } break; case CM_aiEquipPrimaryWeapon: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiEquipPrimaryWeapon) owner(%s)", getDebugInformation().c_str())); equipPrimaryWeapon(); } break; case CM_aiEquipSecondaryWeapon: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiEquipSecondaryWeapon) owner(%s)", getDebugInformation().c_str())); equipSecondaryWeapon(); } break; case CM_aiUnEquipWeapons: { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::handleMessage(CM_aiUnEquipWeapons) owner(%s)", getDebugInformation().c_str())); unEquipWeapons(); } break; case CM_setHibernationDelay: { MessageQueueGenericValueType const * const msg = safe_cast const *>(data); if (msg != NULL) setHibernationDelay(msg->getValue()); } break; default: CreatureController::handleMessage(message, value, data, flags); break; } } //----------------------------------------------------------------------- void AICreatureController::setSpeed(float const speed) { m_speed = speed; } //----------------------------------------------------------------------- float AICreatureController::getSpeed() const { return m_speed; } // ---------------------------------------------------------------------- bool AICreatureController::isRunning ( void ) const { float runSpeed = getCreature()->getRunSpeed(); float walkSpeed = getCreature()->getWalkSpeed(); // This is a hack to make swimming always go at a constant speed. if (m_running.get()) { return (runSpeed != 0); } else { return (walkSpeed == 0); } } // ---------------------------------------------------------------------- float AICreatureController::getAcceleration ( void ) const { float base; if (isRunning()) { base = getCreature()->getAcceleration(SharedCreatureObjectTemplate::MT_run); } else { base = getCreature()->getAcceleration(SharedCreatureObjectTemplate::MT_walk); } float scale = getCreature()->getAccelScale(); float modifier = getCreature()->getAccelPercent(); return base * scale * modifier; } // ---------------------------------------------------------------------- void AICreatureController::moveTowards( CellProperty const * cell, Vector const & position, float time ) { turnToward(cell, position); float const stepDistance = getSpeed() * time; IGNORE_RETURN(moveAlongGround( cell, position, stepDistance )); } // ---------------------------------------------------------------------- float AICreatureController::getDistanceToLocationSquared(CellProperty const * cell, Vector const & point) const { Vector localPoint = CollisionUtils::transformToCell( cell, point, getCreatureCell() ); Vector creaturePos = getCreaturePosition_p(); return creaturePos.magnitudeBetweenSquared(localPoint); } // ---------------------------------------------------------------------- bool AICreatureController::reachedPoint ( CellProperty const * cell, Vector const & point, float radius ) const { float const distanceSquared = getDistanceToLocationSquared(cell, point); // We are giving a little tolerance here for floating point error return (distanceSquared <= (std::max(0.0f, sqr(radius)) + 0.01f)); } // ---------------------------------------------------------------------- void AICreatureController::updateMovementType ( void ) { CreatureObject * const creature = getCreature(); Vector newHeading = creature->getPosition_w() - m_lastStartPosition; float speed = getSpeed(); if (newHeading.normalize() && (speed > 0)) { setCurrentVelocity(newHeading * speed); if (creature->isAuthoritative()) { if (isRunning()) creature->setMovementRun(); else creature->setMovementWalk(); } } else { setCurrentVelocity(Vector::zero); if (creature->isAuthoritative()) creature->setMovementStationary(); } } // ---------------------------------------------------------------------- float AICreatureController::realAlter(float time) { PROFILER_AUTO_BLOCK_DEFINE("AiCreatureController::realAlter"); //DEBUG_REPORT_LOG(true, ("%u\n", Os::getProcessId())); float alterResult = CreatureController::realAlter(time); CreatureObject * const creatureOwner = getCreature(); #ifdef _DEBUG AiDebugString * aiDebugString = NULL; if (!creatureOwner->getObservers().empty()) { aiDebugString = new AiDebugString; } #endif // _DEBUG // tick down our hibernation timer if (m_hibernationTimer.get() > 0 && creatureOwner->isAuthoritative()) { if (shouldHibernate()) { // DEBUG_REPORT_LOG(true, ("[aitest] Hibernation timer countdown, timer = %f, time = %f\n", m_hibernationTimer.get(), time)); if (m_hibernationTimer.get() <= m_hibernationDelay.get()) m_hibernationTimer = std::max(m_hibernationTimer.get() - time, 0.0f); else { // we want to skip the first frame, due to coming out of hibernation m_hibernationTimer = m_hibernationDelay.get(); } if (m_hibernationTimer.get() == 0) { if (!getHibernate()) ObjectTracker::removeDelayedHibernatingAI(); updateHibernate(); } } else { // we are counting down, but we don't need to hibernate anyway; stop the count m_hibernationTimer = -1.0f; ObjectTracker::removeDelayedHibernatingAI(); } } // creatures not in the world and hibernating creatures should go no further if (!creatureOwner->isInWorld() || getHibernate()) { #ifdef _DEBUG if (aiDebugString != NULL) { sendDebugAiToClients(*aiDebugString); delete aiDebugString; } #endif // _DEBUG // DEBUG_REPORT_LOG(true, ("[aitest] Hibernating %s from alter\n", creatureOwner->getNetworkId().getValueString().c_str())); return alterResult; } // check floating { CollisionProperty const * const collision = creatureOwner->getCollisionProperty(); Footprint const * const foot = (collision != NULL) ? collision->getFootprint() : NULL; bool floating = (foot != NULL) ? foot->isFloating() : false; if (floating) { return alterResult; } } // check sleeping if (m_sleepTimer > 0.0f) { m_sleepTimer -= time; // DEBUG_REPORT_LOG(true, ("[aitest] Sleeping %s for %f from alter\n", creatureOwner->getNetworkId().getValueString().c_str(), m_sleepTimer)); AlterResult::incorporateLongAlterResult(alterResult, m_sleepTimer); return alterResult; } // change behaviors if (getPendingMovementType() != AMT_invalid) { PROFILER_AUTO_BLOCK_DEFINE("AiCreatureController::realAlter: getPendingMovementType() != AMT_invalid"); // Update our inPathfindingRegion flag whenever the behavior changes CityPathGraph const * graph = CityPathGraphManager::getCityGraphFor(creatureOwner); m_inPathfindingRegion = (graph != NULL); applyMovementChange(); } // alter behaviors Vector newStartPosition = getCreature()->getPosition_w(); if ( !creatureOwner->isDead() && creatureOwner->isAuthoritative()) { PROFILER_AUTO_BLOCK_DEFINE("AiCreatureController::realAlter: !creatureOwner->isDead() && creatureOwner->isAuthoritative()"); // This is to help catch and prevent bugs with retreating until all the kinks are worked out { if (isRetreating()) { PROFILER_AUTO_BLOCK_DEFINE("AiCreatureController::realAlter: isRetreating()"); int const retreatingTime = static_cast(Os::getRealSystemTime() - m_retreatingStartTime.get()); int const maxRetreatTime = 80; if (retreatingTime > maxRetreatTime) { AiLocation location(m_combatStartLocation.get().getCell(), m_combatStartLocation.get().getCoordinates(), 0.0f); warpTo(location.getCell(), location.getPosition_p()); setRetreating(false); WARNING(true, ("AICreatureController::realAlter() owner(%s) inCombat(%s) Retreating has lasted(%d) > %d seconds. Auto-releasing retreat.", getDebugInformation().c_str(), getCreature()->isInCombat() ? "yes" : "no", retreatingTime, maxRetreatTime)); } } } // Retreating - See if the hate list timer needs to be reset { if (!creatureOwner->isHateListEmpty()) { PROFILER_AUTO_BLOCK_DEFINE("AiCreatureController::realAlter: !creatureOwner->isHateListEmpty()"); bool resetHateTimer = false; CachedNetworkId const & hateTarget = creatureOwner->getHateTarget(); CreatureObject * const hateTargetCreatureObject = CreatureObject::asCreatureObject(hateTarget.getObject()); CreatureController const * const hateTargetCreatureController = (hateTargetCreatureObject != NULL) ? CreatureController::asCreatureController(hateTargetCreatureObject->getController()) : NULL; if ( (hateTargetCreatureObject != NULL) && (hateTargetCreatureController != NULL)) { float const hateTargetMovementSpeedSquared = hateTargetCreatureController->getCurrentVelocity().magnitudeSquared(); float const hateTargetWalkSpeedSquared = sqr(hateTargetCreatureObject->getWalkSpeed()); if (hateTargetMovementSpeedSquared <= hateTargetWalkSpeedSquared) { resetHateTimer = true; } else { Object * const combatStartLocationCell = NetworkIdManager::getObjectById(m_combatStartLocation.get().getCell()); Vector const & combatStartPosition_c = m_combatStartLocation.get().getCoordinates(); Vector const & combatStartPosition_w = (combatStartLocationCell != NULL) ? combatStartLocationCell->rotateTranslate_o2w(combatStartPosition_c) : combatStartPosition_c; float const distanceToCombatStartLocationSquared = creatureOwner->getPosition_w().magnitudeBetweenSquared(combatStartPosition_w); float const aggroRadius = getAggroRadius(); if (distanceToCombatStartLocationSquared <= sqr(aggroRadius * 1.2f)) { resetHateTimer = true; } } if (resetHateTimer) { creatureOwner->resetHateTimer(); hateTargetCreatureObject->resetHateTimer(); } } else if (hateTarget.getObject() != NULL) { // AI don't need to lose interest in stationary AI creatureOwner->resetHateTimer(); } // Fix up a bad hate list / no available target state. if(!hateTarget.isValid()) { if(m_invalidTarget) { creatureOwner->clearHateList(); m_invalidTarget = false; } else m_invalidTarget = true; // give the system 1 frame to try and aquire a new valid target. } else m_invalidTarget = false; } } if (m_movement != AiMovementBaseNullPtr) { PROFILER_AUTO_BLOCK_DEFINE("AiCreatureController::realAlter: m_movement != AiMovementBaseNullPtr"); PerformanceTimer timer; timer.start(); // it's somehow possible that m_movement will get deleted during its // alter, so increment its ref count to prevent that { AiMovementBasePtr temp(m_movement); if (!m_frozen.get()) { m_movement->alter(time); } #ifdef _DEBUG if (aiDebugString != NULL) { m_movement->addDebug(*aiDebugString); } #endif // _DEBUG temp = AiMovementBaseNullPtr; } timer.stop(); float msec = timer.getElapsedTime() * 1000.0f; // AI movements should hopefully never take 30 milliseconds if ( (m_movement != AiMovementBaseNullPtr) #ifdef _DEBUG && (msec >= 60.0f)) #else && (msec >= 30.0f)) #endif // _DEBUG { std::string warningString; m_movement->getDebugInfo(warningString); WARNING(true, (FormattedString<4096>().sprintf("AICreatureController::realAlter() owner(%s) movement(%s) alter took %.0fms. Dumping movement info - %s\n", getDebugInformation().c_str(), AiMovementBase::getMovementString(m_movement->getType()), msec, warningString.c_str()))); } } } m_lastStartPosition = newStartPosition; m_lastEndPosition = creatureOwner->getPosition_w(); updateMovementType(); #ifdef _DEBUG if (aiDebugString != NULL) { sendDebugAiToClients(*aiDebugString); delete aiDebugString; } #endif // _DEBUG // done return alterResult; } //---------------------------------------------------------------------- void AICreatureController::changeMovement(AiMovementBasePtr newMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::changeMovement() movement(%s)", (newMovement != AiMovementBaseNullPtr) ? AiMovementBase::getMovementString(newMovement->getType()) : "NULL")); if (getOwner()->isAuthoritative()) { if (m_pendingMovement != newMovement) { m_pendingMovement = newMovement; m_sleepTimer = 0.0f; getOwner()->scheduleForAlter(); } } else { m_movement = newMovement; m_pendingMovement = AiMovementBaseNullPtr; } } //----------------------------------------------------------------------- /** * Stops the current movement type of the creature, allowing it to be resumed at a * later time. * * NOTE: Only one movement can be suspended at a time. If this function * is called while a previous movement is suspended, it will fail. * * @return true if the movement has been suspended, false if not. */ bool AICreatureController::suspendMovement() { bool result = true; if (m_suspendedMovement == AiMovementBaseNullPtr) { m_suspendedMovement = m_movement; stop(); } else if (m_movement != AiMovementBaseNullPtr) { result = false; WARNING(true, ("AICreatureController::suspendMovement called for creature %s when there is already a suspended movement type %d", getOwner()->getNetworkId().getValueString().c_str(), static_cast(m_movement->getType()))); } return result; } //----------------------------------------------------------------------- /** * Resumes a previously suspended movement. * * @return true if the movement was resumed, false if there was no previous movement. */ bool AICreatureController::resumeMovement() { bool result = false; if (m_suspendedMovement != AiMovementBaseNullPtr) { result = true; m_pendingMovement = m_suspendedMovement; m_suspendedMovement = AiMovementBaseNullPtr; } return result; } //----------------------------------------------------------------------- bool AICreatureController::hasSuspendedMovement() { return m_suspendedMovement != AiMovementBaseNullPtr; } //----------------------------------------------------------------------- void AICreatureController::applyMovementChange() { if (m_movement != m_pendingMovement) { m_movement = m_pendingMovement; if (getOwner()->isAuthoritative()) { if (m_movement != AiMovementBaseNullPtr) { sendMessageToProxyServer(CM_aiSetMovement, new AiMovementMessage(getOwner()->getNetworkId(), m_movement)); (safe_cast(getOwner()))->setDefaultAlterTime(m_movement->getDefaultAlterTime()); } else { sendMessageToProxyServer(CM_aiSetMovement, new AiMovementMessage(getOwner()->getNetworkId())); (safe_cast(getOwner()))->setDefaultAlterTime(AlterResult::cms_keepNoAlter); } // note: we need to do this now or updateHibernate will fail m_pendingMovement = AiMovementBaseNullPtr; updateHibernate(); } } m_pendingMovement = AiMovementBaseNullPtr; } //----------------------------------------------------------------------- bool AICreatureController::hasPendingMovement() const { return m_pendingMovement != AiMovementBaseNullPtr; } //----------------------------------------------------------------------- void AICreatureController::getBehaviorDebugInfo (std::string & outString) const { if (m_movement != AiMovementBaseNullPtr) { m_movement->getDebugInfo(outString); } else { outString += "No Behavior\n"; } } // ====================================================================== // Wander // ====================================================================== //----------------------------------------------------------------------- bool AICreatureController::wander( float minDistance, float maxDistance, float minAngle, float maxAngle, float minDelay, float maxDelay) { if (isRetreating()) { WARNING(true, ("AICreatureController::wander() owner(%s) minDistance(%.2f) maxDistance(%.2f) minAngle(%.2f) maxAngle(%.2f) minDelay(%.2f) maxDelay(%.2f) Trying to wander while retreating, failing request.", getDebugInformation().c_str(), minDistance, maxDistance, minAngle, maxAngle, minDelay, maxDelay)); return false; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::wander() owner(%s) minDistance(%.2f) maxDistance(%.2f) minAngle(%.2f) maxAngle(%.2f) minDelay(%.2f) maxDelay(%.2f)", getDebugInformation().c_str(), minDistance, maxDistance, minAngle, maxAngle, minDelay, maxDelay)); if((minDistance == 0) && (maxDistance == 0)) { return false; } if (getCreature()->getParentCell() != CellProperty::getWorldCellProperty()) { AiMovementBasePtr movement(new AiMovementWanderInterior(this)); changeMovement(movement); } else { AiMovementBasePtr movement(new AiMovementWander(this, minDistance, maxDistance, minAngle, maxAngle, minDelay, maxDelay)); changeMovement(movement); } return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } // ====================================================================== // Loiter // ====================================================================== //----------------------------------------------------------------------- void AICreatureController::loiter(CellProperty const * homeCell, Vector const & home_p, float const minDistance, float const maxDistance, float const minDelay, float const maxDelay) { if (isRetreating()) { WARNING(true, ("AICreatureController::loiter() owner(%s) cell(%s) home(%.0f, %.0f, %.0f) distance[%.2f...%.2f] delay[%.0f...%.0f] Trying to loiter while retreating, failing request.", getDebugInformation().c_str(), ((homeCell != NULL) ? homeCell->getCellName() : "NULL"), home_p.x, home_p.y, home_p.z, minDistance, maxDistance, minDelay, maxDelay)); return; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::loiter() owner(%s) cell(%s) home(%.0f, %.0f, %.0f) distance[%.2f...%.2f] delay[%.0f...%.0f]", getDebugInformation().c_str(), ((homeCell != NULL) ? homeCell->getCellName() : "NULL"), home_p.x, home_p.y, home_p.z, minDistance, maxDistance, minDelay, maxDelay)); AiMovementBasePtr movement(new AiMovementLoiter(this, homeCell, home_p, minDistance, maxDistance, minDelay, maxDelay)); changeMovement(movement); } //----------------------------------------------------------------------- void AICreatureController::loiter(NetworkId const & homeId, float const minDistance, float const maxDistance, float const minDelay, float const maxDelay) { if (isRetreating()) { WARNING(true, ("AICreatureController::loiter() owner(%s) home(%s) distance[%.2f...%.2f] delay[%.0f...%.0f] Trying to loiter while retreating, failing request.", getDebugInformation().c_str(), homeId.getValueString().c_str(), minDistance, maxDistance, minDelay, maxDelay)); return; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::loiter() owner(%s) home(%s) distance[%.2f...%.2f] delay[%.0f...%.0f]", getDebugInformation().c_str(), homeId.getValueString().c_str(), minDistance, maxDistance, minDelay, maxDelay)); ServerObject const * const home = ServerWorld::findObjectByNetworkId(homeId); AiMovementBasePtr movement(new AiMovementLoiter(this,home,minDistance,maxDistance,minDelay,maxDelay)); changeMovement(movement); } //----------------------------------------------------------------------- void AICreatureController::moveTo(CellProperty const * cell, Vector const & target_p, float const radius) { if (isRetreating()) { WARNING(true, ("AICreatureController::moveTo() owner(%s) cell(%s) position(%.0f, %.0f, %.0f) radius(%.2f) Trying to moveTo while retreating, failing request.", getDebugInformation().c_str(), ((cell != NULL) ? cell->getCellName() : "NULL"), target_p.x, target_p.y, target_p.z, radius)); return; } bool duplicateMovement = false; if (getMovementType() == AMT_move) { AiMovementMove * const aiMovementMove = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementMove() : NULL; if (aiMovementMove != NULL) { AiLocation const & target = aiMovementMove->getTarget(); if ( (target.getCell() == cell) && (target.getPosition_p() == target_p) && (target.getRadius() == radius)) { duplicateMovement = true; } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::moveTo() owner(%s) cell(%s) position(%.0f, %.0f, %.0f) radius(%.2f)", getDebugInformation().c_str(), ((cell != NULL) ? cell->getCellName() : "NULL"), target_p.x, target_p.y, target_p.z, radius)); AiMovementBasePtr movement(new AiMovementMove(this, cell, target_p, radius)); changeMovement(movement); } } //----------------------------------------------------------------------- void AICreatureController::moveTo(Unicode::String const & targetName) { if (isRetreating()) { WARNING(true, ("AICreatureController::moveTo() owner(%s) target(%s) Trying to moveTo while retreating, failing request.", getDebugInformation().c_str(), Unicode::wideToNarrow(targetName).c_str())); return; } bool duplicateMovement = false; if (getMovementType() == AMT_move) { AiMovementMove * const aiMovementMove = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementMove() : NULL; if (aiMovementMove != NULL) { if (aiMovementMove->getTargetName() == targetName) { duplicateMovement = true; } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::moveTo() owner(%s) targetName(%s)", getDebugInformation().c_str(), Unicode::wideToNarrow(targetName).c_str())); AiMovementBasePtr behavior(new AiMovementMove(this, targetName)); changeMovement(behavior); } } //----------------------------------------------------------------------- void AICreatureController::patrol( std::vector const & locations, bool random, bool flip, bool repeat, int startPoint) { if (isRetreating()) { WARNING(true, ("AICreatureController::patrol() owner(%s) Trying to patrol while retreating, failing request.", getDebugInformation().c_str())); return; } bool duplicateMovement = false; if (getMovementType() == AMT_patrol) { AiMovementPatrol * const aiMovementPatrol = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementPatrol() : NULL; if (aiMovementPatrol != NULL) { // if () // { // duplicateMovement = true; // } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::patrol() owner(%s)", getDebugInformation().c_str())); AiMovementBasePtr movement(new AiMovementPatrol(this, locations, random, flip, repeat, startPoint)); changeMovement(movement); } } //----------------------------------------------------------------------- void AICreatureController::patrol( std::vector const & locations, bool random, bool flip, bool repeat, int startPoint) { if (isRetreating()) { WARNING(true, ("AICreatureController::patrol() owner(%s) Trying to patrol while retreating, failing request.", getDebugInformation().c_str())); return; } bool duplicateMovement = false; if (getMovementType() == AMT_patrol) { AiMovementPatrol * const aiMovementPatrol = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementPatrol() : NULL; if (aiMovementPatrol != NULL) { // if () // { // duplicateMovement = true; // } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::patrol() owner(%s)", getDebugInformation().c_str())); AiMovementBasePtr movement(new AiMovementPatrol(this, locations, random, flip, repeat, startPoint)); changeMovement(movement); } } //----------------------------------------------------------------------- void AICreatureController::stop() { if (isRetreating()) { WARNING(true, ("AICreatureController::stop() owner(%s) Trying to stop while retreating, failing request.", getDebugInformation().c_str())); return; } if (getMovementType() != AMT_idle) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::stop() owner(%s)", getDebugInformation().c_str())); AiMovementBasePtr behavior(new AiMovementIdle(this)); changeMovement(behavior); } } //----------------------------------------------------------------------- bool AICreatureController::faceTo(CellProperty const * targetCell, Vector const & target_p) { if (isRetreating()) { WARNING(true, ("AICreatureController::faceTo() owner(%s) cell(%s) position(%.0f, %.0f, %.0f) Trying to faceTo while retreating, failing faceTo request.", getDebugInformation().c_str(), ((targetCell != NULL) ? targetCell->getCellName() : "NULL"), target_p.x, target_p.y, target_p.z)); return false; } bool duplicateMovement = false; if (getMovementType() == AMT_face) { AiMovementFace * const aiMovementFace = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementFace() : NULL; if (aiMovementFace != NULL) { AiLocation const & target = aiMovementFace->getTarget(); if ( (target.getCell() == targetCell) && (target.getPosition_p() == target_p)) { duplicateMovement = true; } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::faceTo() owner(%s) cell(%s) position(%.0f, %.0f, %.0f)", getDebugInformation().c_str(), ((targetCell != NULL) ? targetCell->getCellName() : "NULL"), target_p.x, target_p.y, target_p.z)); AiMovementBasePtr behavior(new AiMovementFace(this, targetCell, target_p)); changeMovement(behavior); } return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } //----------------------------------------------------------------------- bool AICreatureController::faceTo( NetworkId const & targetId ) { if (isRetreating()) { WARNING(true, ("AICreatureController::faceTo() owner(%s) Trying to faceTo the target(%s) while retreating, failing request.", getDebugInformation().c_str(), targetId.getValueString().c_str())); return false; } ServerObject const * target = ServerWorld::findObjectByNetworkId(targetId); if (!target) { WARNING(true, ("AICreatureController::faceTo() owner(%s) Unable to resolve target(%s) to a ServerObject", getDebugInformation().c_str(), targetId.getValueString().c_str())); return false; } bool duplicateMovement = false; if (getMovementType() == AMT_face) { AiMovementFace * const aiMovementFace = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementFace() : NULL; if (aiMovementFace != NULL) { AiLocation const & target = aiMovementFace->getTarget(); if (target.getObjectId() == targetId) { duplicateMovement = true; } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::faceTo() owner(%s) target(%s)", getDebugInformation().c_str(), targetId.getValueString().c_str())); AiMovementBasePtr behavior(new AiMovementFace(this, target)); changeMovement(behavior); } return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } //---------------------------------------------------------------------- bool AICreatureController::follow( NetworkId const & targetId, float minDistance, float maxDistance ) { if (isRetreating()) { WARNING(true, ("AICreatureController::follow() owner(%s) distance[%.2f...%.2f] Trying to follow the target(%s) while retreating, failing request.", getDebugInformation().c_str(), minDistance, maxDistance, targetId.getValueString().c_str())); return false; } ServerObject const * target = ServerWorld::findObjectByNetworkId(targetId); if (!target) { WARNING(true, ("AICreatureController::follow() owner(%s) distance[%.2f...%.2f] Unable to resolve target(%s) to a ServerObject", getDebugInformation().c_str(), minDistance, maxDistance, targetId.getValueString().c_str())); return false; } bool duplicateMovement = false; if (getMovementType() == AMT_follow) { AiMovementFollow * const aiMovementFollow = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementFollow() : NULL; if (aiMovementFollow != NULL) { AiLocation const & target = aiMovementFollow->getTarget(); if ( (targetId == target.getObjectId()) && (WithinEpsilonInclusive(minDistance, aiMovementFollow->getMinDistance(), 0.001f)) && (WithinEpsilonInclusive(maxDistance, aiMovementFollow->getMaxDistance(), 0.001f))) { duplicateMovement = true; } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::follow() owner(%s) target(%s) distance[%.2f...%.2f]", getDebugInformation().c_str(), targetId.getValueString().c_str(), minDistance, maxDistance)); AiMovementBasePtr behavior(new AiMovementFollow(this, target, minDistance, maxDistance)); changeMovement(behavior); } return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } //---------------------------------------------------------------------- bool AICreatureController::follow( NetworkId const & targetId, Vector const & offset ) { if (isRetreating()) { WARNING(true, ("AICreatureController::follow() owner(%s) offset(%.0f, %.0f, %.0f) Trying to follow the target(%s) while retreating, failing request.", getDebugInformation().c_str(), offset.x, offset.y, offset.z, targetId.getValueString().c_str())); return false; } ServerObject const * target = ServerWorld::findObjectByNetworkId(targetId); if (!target) { WARNING(true, ("AICreatureController::follow() owner(%s) offset(%.0f, %.0f, %.0f) Unable to resolve target(%s) to a ServerObject", getDebugInformation().c_str(), offset.x, offset.y, offset.z, targetId.getValueString().c_str())); return false; } bool duplicateMovement = false; if (getMovementType() == AMT_follow) { AiMovementFollow * const aiMovementFollow = (m_movement != AiMovementBaseNullPtr) ? m_movement->asAiMovementFollow() : NULL; if (aiMovementFollow != NULL) { AiLocation const & offsetTarget = aiMovementFollow->getOffsetTarget(); if ( (targetId == offsetTarget.getObjectId()) && (offset == offsetTarget.getOffset())) { duplicateMovement = true; } } } if (!duplicateMovement) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::follow() owner(%s) target(%s) offset(%.0f, %.0f, %.0f)", getDebugInformation().c_str(), targetId.getValueString().c_str(), offset.x, offset.y, offset.z)); bool relativeOffset = true; float minDistance = 0.0f; float maxDistance = 0.0f; AiMovementBasePtr behavior(new AiMovementFollow(this, target, offset, relativeOffset, minDistance, maxDistance)); changeMovement(behavior); } return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } // ---------------------------------------------------------------------- bool AICreatureController::swarm( NetworkId const & targetId ) { if (isRetreating()) { WARNING(true, ("AICreatureController::swarm() owner(%s) Trying to flee the target(%s) while retreating, failing request.", getDebugInformation().c_str(), targetId.getValueString().c_str())); return false; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::swarm() owner(%s) target(%s)", getDebugInformation().c_str(), targetId.getValueString().c_str())); if(!targetId) return false; ServerObject const * target = ServerWorld::findObjectByNetworkId(targetId); if(!target) return false; // ---------- // Check if we're already swarming the specified object. if (getMovementType() == AMT_swarm) { AiMovementSwarm *swarmBehavior = safe_cast(m_movement.get()); if (swarmBehavior && (swarmBehavior->getTarget().getObjectId() == targetId)) { // We're already swarming this object, keep swarming it with the same behavior. return true; } } AiMovementBasePtr behavior(new AiMovementSwarm(this,target)); changeMovement(behavior); return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } // ---------------------------------------------------------------------- bool AICreatureController::swarm( NetworkId const & targetId, float offset ) { if (isRetreating()) { WARNING(true, ("AICreatureController::swarm() owner(%s) offset(%.2f) Trying to flee the target(%s) while retreating, failing request.", getDebugInformation().c_str(), offset, targetId.getValueString().c_str())); return false; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::swarm() owner(%s) target(%s) offset(%.2f)", getDebugInformation().c_str(), targetId.getValueString().c_str(), offset)); if(!targetId) return false; ServerObject const * target = ServerWorld::findObjectByNetworkId(targetId); if(!target) return false; AiMovementBasePtr behavior(new AiMovementSwarm(this, target, offset)); changeMovement(behavior); return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } // ---------------------------------------------------------------------- bool AICreatureController::flee( NetworkId const & targetId, float minDistance, float maxDistance ) { if (isRetreating()) { WARNING(true, ("AICreatureController::flee() owner(%s) distance[%.2f...%.2f] Trying to flee the target(%s) while retreating, failing request.", getDebugInformation().c_str(), minDistance, maxDistance, targetId.getValueString().c_str(), targetId.getValueString().c_str())); return false; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::flee() owner(%s) target(%s) distance[%.2f...%.2f]", getDebugInformation().c_str(), targetId.getValueString().c_str(), minDistance, maxDistance)); if(getCreatureCell() != CellProperty::getWorldCellProperty()) { AiMovementBasePtr behavior(new AiMovementIdle(this)); changeMovement(behavior); return false; } if(!targetId) return false; ServerObject const * target = ServerWorld::findObjectByNetworkId(targetId); if(!target) return false; // ---------- AiMovementBasePtr behavior(new AiMovementFlee(this, target, minDistance, maxDistance)); changeMovement(behavior); return true; //lint !e429 // custodial pointer 'behavior' has not been freed or returned // okay: ownership transfers via changeMovement(). } // ---------------------------------------------------------------------- CreatureObject const * AICreatureController::getCreature ( void ) const { return safe_cast< CreatureObject const * >( getOwner() ); } CreatureObject * AICreatureController::getCreature ( void ) { return safe_cast< CreatureObject * >( getOwner() ); } CellProperty const * AICreatureController::getCreatureCell ( void ) const { return getOwner()->getParentCell(); } Vector AICreatureController::getCreaturePosition_p ( void ) const { return getCreature()->getPosition_p(); } Vector AICreatureController::getCreaturePosition_w ( void ) const { return getCreature()->getPosition_w(); } float AICreatureController::getCreatureRadius ( void ) const { return getCreature()->getRadius(); } // ---------------------------------------------------------------------- void AICreatureController::moveCreature( CellProperty const * newCell, Vector const & inNewPosition, bool testMove ) { if(testMove) { CanMoveResult result = CollisionWorld::canMove(getCreature(),inNewPosition,0.05f,false,true,false); if((result == CMR_MoveOK) || (result == CMR_HitObstacle)) { m_stuckCounter = 0; } else { m_stuckCounter++; return; } } else { m_stuckCounter = 0; } Vector newPosition = inNewPosition; CellProperty * currentCell = getOwner()->getParentCell(); CellObject * newCellObject = NULL; if( (newCell != NULL) && (newCell != CellProperty::getWorldCellProperty()) ) { newCellObject = const_cast(safe_cast(&newCell->getOwner())); } Vector oldPosition = getCreaturePosition_p(); // ---------- if(currentCell == newCell) { float dummy; Vector offset(0.0f,1.0f,0.0f); CellProperty const * destCell = getCreatureCell()->getDestinationCell(oldPosition+offset,newPosition+offset,dummy); if((destCell != NULL) && (destCell != newCell)) { newPosition = CollisionUtils::transformToCell(newCell,newPosition,destCell); newCell = destCell; newCellObject = NULL; if(newCell != CellProperty::getWorldCellProperty()) { newCellObject = const_cast(safe_cast(&newCell->getOwner())); } } } if (currentCell == newCell) { Transform objectToCell = getOwner()->getTransform_o2p(); objectToCell.setPosition_p(newPosition); setGoal(objectToCell, newCellObject); } else { Transform objectToWorld = getOwner()->getTransform_o2w(); Transform cellToWorld = newCellObject ? newCellObject->getTransform_o2w() : Transform::identity; Transform worldToCell; worldToCell.invert(cellToWorld); Transform objectToCell; objectToCell.multiply(worldToCell, objectToWorld); objectToCell.setPosition_p(newPosition); setGoal(objectToCell, newCellObject); } } //---------------------------------------------------------------------- bool stepTowards(Vector const & startPosition, Vector const & goalPosition, float stepDistance, Vector & newPosition) { bool reachedGoal = true; Vector directionToGoal = Vector(goalPosition.x, 0.0f, goalPosition.z) - Vector(startPosition.x, 0.0f, startPosition.z); float const distanceToGoalSquared = directionToGoal.magnitudeSquared(); if (distanceToGoalSquared <= sqr(stepDistance)) { // We have overshot our goal, so just snap to the goal position newPosition = goalPosition; } else { // We are not to our goal yet so step towards it reachedGoal = false; // Create a unit vector towards the goal directionToGoal.normalize(); // Move along the unit vector the requested distance directionToGoal *= stepDistance; newPosition = startPosition + directionToGoal; } return reachedGoal; } //---------------------------------------------------------------------- //---------------------------------------------------------------------- bool AICreatureController::moveAlongGround( CellProperty const * targetCell, Vector const & target, float stepDistance) { if (stepDistance <= 0) return true; CellProperty const * creatureCell = getOwner()->getParentCell(); Vector localTarget = CollisionUtils::transformToCell( targetCell, target, creatureCell ); Vector creaturePos = getOwner()->getPosition_p(); Vector stepGoal; bool reachedGoal = stepTowards(creaturePos,localTarget,stepDistance,stepGoal); // For warping to work properly even when we are not in a pathfinding region // we still need to warp if we are in a behavior like follow or flee. // Not warping in these cases was causing ai to get stuck in Tansarii and would // also cause problems anywhere there is not a CityPathGraphManager AiMovementType curMoveType = AMT_invalid; if (m_movement != AiMovementBaseNullPtr) { curMoveType = m_movement->getType(); } bool testCollisions = m_inPathfindingRegion || curMoveType == AMT_follow || curMoveType == AMT_flee; moveCreature(creatureCell,stepGoal,testCollisions); return reachedGoal; } //---------------------------------------------------------------------- void AICreatureController::turnToward( CellProperty const * targetCell, Vector const & target ) { CellProperty const * creatureCell = getOwner()->getParentCell(); Vector localTarget = CollisionUtils::transformToCell( targetCell, target, creatureCell ); Vector result = getOwner()->rotateTranslate_p2o(localTarget); float angle = result.theta(); getOwner()->yaw_o(angle); } //---------------------------------------------------------------------- float AICreatureController::getGroundDistance( Vector const & A, Vector const & B ) { float dx = A.x - B.x; float dz = A.z - B.z; return sqrt(dx*dx+dz*dz); } //-------------------------------------------------------------------- void AICreatureController::setMovementRun() { if (getOwner()->isAuthoritative()) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::setMovementRun() owner(%s)", getDebugInformation().c_str())); m_running = true; } else { sendMessageToAuthServer(CM_aiSetMovementRun); } } //-------------------------------------------------------------------- void AICreatureController::setMovementWalk() { if (getOwner()->isAuthoritative()) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::setMovementWalk() owner(%s)", getDebugInformation().c_str())); m_running = false; } else { sendMessageToAuthServer(CM_aiSetMovementWalk); } } // ---------------------------------------------------------------------- void AICreatureController::warpTo( CellProperty const * newCell, Vector const & newPosition ) { DEBUG_WARNING((!newCell || newCell == CellProperty::getWorldCellProperty()) && newPosition == Vector::zero, ("AICreatureController::warpTo tried to send a creature to the origin.")); moveCreature(newCell,newPosition,false); CollisionWorld::objectWarped(getOwner()); m_lastStartPosition = newPosition; m_lastEndPosition = newPosition; } // ---------------------------------------------------------------------- void AICreatureController::sleep ( float sleepTime ) { m_sleepTimer = sleepTime; } // ---------------------------------------------------------------------- bool AICreatureController::shouldHibernate ( void ) const { if (m_movement != AiMovementBaseNullPtr && !m_movement->getHibernateOk()) return false; if (m_pendingMovement != AiMovementBaseNullPtr) return false; if (isRetreating()) { return false; } return CreatureController::shouldHibernate(); } // ---------------------------------------------------------------------- void AICreatureController::setHibernate(bool hibernate) { bool oldHibernate = getHibernate(); float oldHibernationTimer = m_hibernationTimer.get(); if (!oldHibernate && hibernate && m_hibernationDelay.get() > 0 && oldHibernationTimer != 0) { // check if we want to start our hibernation timer if (oldHibernationTimer < 0) { m_hibernationTimer = m_hibernationDelay.get(); } } else { CreatureController::setHibernate(hibernate); bool newHibernate = getHibernate(); // if we're coming out of hibernation, reset our alter time if (!newHibernate && oldHibernate) { // flag the hibernate timer as ready to use if (m_hibernationDelay.get() > 0) m_hibernationTimer = -1.0f; if (m_movement != AiMovementBaseNullPtr) (safe_cast(getOwner()))->setDefaultAlterTime(m_movement->getDefaultAlterTime()); else (safe_cast(getOwner()))->setDefaultAlterTime(AlterResult::cms_keepNoAlter); } } if (hibernate && !oldHibernate && !getHibernate() && oldHibernationTimer <= 0 && m_hibernationTimer.get() > 0) ObjectTracker::addDelayedHibernatingAI(); } // ---------------------------------------------------------------------- void AICreatureController::setHibernationDelay(float delay) { if (getOwner()->isAuthoritative()) { if (delay < 0) delay = 0; m_hibernationDelay = delay; float oldTimer = m_hibernationTimer.get(); if (delay > 0) { if (!getHibernate() && oldTimer == 0) { // flag us as ready to delay the next time we want to hibernate m_hibernationTimer = -1.0f; } else { // we need to set the hibernation timer > than the delay to flag skipping the first alter, which will be a large number delay += 1.0f; m_hibernationTimer = delay; } } else { if (!getHibernate() && oldTimer > 0) ObjectTracker::removeDelayedHibernatingAI(); m_hibernationTimer = 0; } // see if we need to change our hibernate state if (oldTimer > 0 && delay == 0) updateHibernate(); else if (delay > 0 && getHibernate()) { setHibernate(false); updateHibernate(); // updateHibernate() can modify m_hibernationTimer, so we have to set it again m_hibernationTimer = delay; } } else { sendMessageToAuthServer(CM_setHibernationDelay, new MessageQueueGenericValueType(delay)); } } // ---------------------------------------------------------------------- int AICreatureController::getStuckCounter ( void ) const { return m_stuckCounter; } // ---------------------------------------------------------------------- void AICreatureController::setAuthority ( bool newAuthority ) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::setAuthority() authority(%s) inCombat(%s) owner(%s) ", newAuthority ? "yes" : "no", getCreature()->isInCombat() ? "yes" : "no", getDebugInformation().c_str())); // if we are being made authoritative, refresh our ai behavior if (newAuthority && !m_authority && m_movement != AiMovementBaseNullPtr) { m_movement->refresh(); } m_authority = newAuthority; } // ---------------------------------------------------------------------- void AICreatureController::forwardServerObjectSpecificBaselines() const { AiCreatureStateMessage aiCreatureStateMessage; aiCreatureStateMessage.m_networkId = getOwner()->getNetworkId(); aiCreatureStateMessage.m_movement = AiMovementMessage(getOwner()->getNetworkId(), m_movement); GenericValueTypeMessage const genericMessage("AiCreatureStateMessage", aiCreatureStateMessage); ServerMessageForwarding::send(genericMessage); } // ---------------------------------------------------------------------- typedef std::map< int, std::string > TriggerNameLookup; char const * const getStringForTrigger( int trigger ) { static TriggerNameLookup lookup; static bool initialized = false; if(!initialized) { #define add(A) lookup[static_cast(A)] = #A add( TRIG_WANDER_MOVING ); add( TRIG_WANDER_WAYPOINT ); add( TRIG_WANDER_WAITING ); add( TRIG_WANDER_PATH_NOT_FOUND ); add( TRIG_LOITER_MOVING ); add( TRIG_LOITER_WAYPOINT ); add( TRIG_LOITER_WAITING ); add( TRIG_FOLLOW_TARGET_LOST ); add( TRIG_FOLLOW_WAITING ); add( TRIG_FOLLOW_MOVING ); add( TRIG_FOLLOW_PATH_NOT_FOUND ); add( TRIG_FLEE_TARGET_LOST ); add( TRIG_FLEE_WAYPOINT ); add( TRIG_FLEE_PATH_NOT_FOUND ); add( TRIG_MOVE_PATH_COMPLETE ); add( TRIG_MOVE_MOVING ); add( TRIG_MOVE_PATH_NOT_FOUND ); add( TRIG_MADE_AUTHORITATIVE ); #undef add initialized = true; } return lookup[trigger].c_str(); } void AICreatureController::triggerScriptsSimple( int intId ) { TrigId id = static_cast(intId); //LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::triggerScriptsSimple : %s\n", getStringForTrigger(intId))); ScriptParams params; IGNORE_RETURN(getServerOwner()->getScriptObject()->trigAllScripts(id, params)); } // ---------------------------------------------------------------------- void AICreatureController::triggerScriptsWithTarget( int intId, NetworkId const & targetId ) { //LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::triggerScriptsWithTarget : %s\n", getStringForTrigger(intId))); TrigId id = static_cast(intId); ScriptParams params; params.addParam(targetId); IGNORE_RETURN(getServerOwner()->getScriptObject()->trigAllScripts(id, params)); } // ---------------------------------------------------------------------- void AICreatureController::triggerScriptsWithParam ( int intId, float & io_param ) { //LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::triggerScriptsWithParam: %s, %f\n", getStringForTrigger(intId),io_param)); TrigId id = static_cast(intId); ScriptParams params; params.addParam(io_param); IGNORE_RETURN(getServerOwner()->getScriptObject()->trigAllScripts(id, params)); io_param = params.getFloatParam(0); } // ---------------------------------------------------------------------- AICreatureController * AICreatureController::getAiCreatureController(NetworkId const & networkId) { Object * const object = NetworkIdManager::getObjectById(networkId); return (object != NULL) ? AICreatureController::asAiCreatureController(object->getController()) : NULL; } // ---------------------------------------------------------------------- AICreatureController * AICreatureController::asAiCreatureController(Controller * controller) { CreatureController * const creatureController = (controller != NULL) ? controller->asCreatureController() : NULL; AICreatureController * const aiCreatureController = (creatureController != NULL) ? creatureController->asAiCreatureController() : NULL; return aiCreatureController; } // ---------------------------------------------------------------------- AICreatureController const * AICreatureController::asAiCreatureController(Controller const * controller) { CreatureController const * const creatureController = (controller != NULL) ? controller->asCreatureController() : NULL; AICreatureController const * const aiCreatureController = (creatureController != NULL) ? creatureController->asAiCreatureController() : NULL; return aiCreatureController; } //---------------------------------------------------------------------- AICreatureController * AICreatureController::asAiCreatureController() { return this; } //---------------------------------------------------------------------- AICreatureController const * AICreatureController::asAiCreatureController() const { return this; } //---------------------------------------------------------------------- float AICreatureController::getAggroRadius() const { float result = ConfigServerGame::getAiBaseAggroRadius(); if (m_aiCreatureData->m_aggressive > 0.0f) { result = m_aiCreatureData->m_aggressive; } return clamp(0.0f, result, ConfigServerGame::getAiMaxAggroRadius()); } // ---------------------------------------------------------------------- float AICreatureController::getRespectRadius(NetworkId const & target) const { float const aggroRadius = getAggroRadius(); float result = aggroRadius; CreatureObject const * respectCreatureObject = CreatureObject::getCreatureObject(target); if (respectCreatureObject != NULL) { // If the target has a master, then use the master's level for the respect calculation NetworkId const & masterId = respectCreatureObject->getMasterId(); if (masterId != NetworkId::cms_invalid) { respectCreatureObject = CreatureObject::getCreatureObject(masterId); } // Respect is only towards players if ( (respectCreatureObject != NULL) && respectCreatureObject->isPlayerControlled()) { CreatureObject const * const creatureOwner = getCreature(); int const ownerLevel = creatureOwner->getLevel(); int const targetLevel = respectCreatureObject->getLevel(); float const levelDifference = static_cast(targetLevel) - static_cast(ownerLevel); float const respectPercent = clamp(0.04f, (1.0f - (levelDifference / 20.0f)), 1.20f); result = respectPercent * aggroRadius; } else { WARNING(true, ("AICreatureController::getRespectRadius() owner(%s) master(%s) The owner has a master who is not a player. Why does this happen?", getDebugInformation().c_str(), (respectCreatureObject != NULL) ? respectCreatureObject->getDebugInformation().c_str() : masterId.getValueString().c_str())); } } return result; } // ---------------------------------------------------------------------- float AICreatureController::getAssistRadius() const { float assistRadius = ConfigServerGame::getAiAssistRadius(); if (m_aiCreatureData->m_assist > 0.0f) { assistRadius = m_aiCreatureData->m_assist; } return clamp(0.0f, assistRadius, ConfigServerGame::getAiMaxAggroRadius()); } #ifdef _DEBUG // ---------------------------------------------------------------------- void AICreatureController::sendDebugAiToClients(AiDebugString & aiDebugString) { ServerObject * const serverOwner = getServerOwner(); const ObserverList & observerList = serverOwner->getObservers(); if (serverOwner->isAuthoritative() && !observerList.empty()) { CreatureObject * const creatureOwner = serverOwner->asCreatureObject(); FormattedString<512> fs; // Display the name and level { aiDebugString.addText(fs.sprintf("%d %s\n", creatureOwner->getLevel(), (getCreatureName().isEmpty() ? "NULL" : getCreatureName().getString())), PackedRgb::solidWhite); } // Display the look at target //{ // aiDebugString.addText(fs.sprintf("lookAtTarget(%s)\n", creatureOwner->getLookAtTarget().getValueString().c_str()), PackedRgb::solidWhite); //} // Display the trigger volumes //{ // aiDebugString.addText("Trigger Volumes\n", PackedRgb::solidWhite); // ServerObject::TriggerVolumeMap::const_iterator iterTriggerVolumes = creatureOwner->getTriggerVolumeMap().begin(); // int index = 1; // // for (; iterTriggerVolumes != creatureOwner->getTriggerVolumeMap().end(); ++iterTriggerVolumes) // { // aiDebugString.addText(fs.sprintf("%i %s\n", index, iterTriggerVolumes->first.c_str()), PackedRgb::solidWhite); // ++index; // } //} if (creatureOwner->isInvulnerable()) { aiDebugString.addText("INVULNERABLE\n", PackedRgb::solidMagenta); } if (isFrozen()) { aiDebugString.addText("FROZEN\n", PackedRgb::solidCyan); } if (creatureOwner->isDead()) { aiDebugString.addText("DEAD\n", PackedRgb::solidRed); } else { if (isRetreating()) { int const retreatingTime = static_cast(Os::getRealSystemTime() - m_retreatingStartTime.get()); aiDebugString.addText(fs.sprintf("RETREATING %d\n", retreatingTime), PackedRgb::solidMagenta); } // Show the floorId { Floor const * const floor = CollisionWorld::getFloorStandingOn(*creatureOwner); aiDebugString.addText(fs.sprintf("floor (%s)\n", (floor == NULL) ? "none" : floor->getId().getValueString().c_str()), PackedRgb::solidWhite); } // Display the AI movement speed { float const movementPercent = (creatureOwner->getMovementPercent() * 100.0f); aiDebugString.addText(fs.sprintf("mv (%.0f%%:%.1f)%s%s%s\n", movementPercent, getCurrentVelocity().magnitude(), (creatureOwner->isStopped() ? " stopped" : ""), (creatureOwner->isWalking() ? " walking" : ""), (creatureOwner->isRunning() ? " running" : "")), PackedRgb::solidWhite); aiDebugString.addText(fs.sprintf("walk %.1f run %.1f\n", creatureOwner->getWalkSpeed(), creatureOwner->getRunSpeed()), PackedRgb::solidWhite); } Vector const & ownerPosition_w = serverOwner->getPosition_w(); if (creatureOwner->isInCombat()) { // Primary Weapon { ServerObject const * const primaryServerObject = ServerObject::getServerObject(getPrimaryWeapon()); WeaponObject const * const primaryWeaponObject = (primaryServerObject != NULL) ? primaryServerObject->asWeaponObject() : NULL; if (primaryWeaponObject != NULL) { aiDebugString.addText(fs.sprintf("%s pri(%s) [%.0f...%.0f] sp(%s)\n", (usingPrimaryWeapon() ? "->" : ""), FileNameUtils::get(primaryWeaponObject->getObjectTemplateName(), FileNameUtils::fileName).c_str(), primaryWeaponObject->getMinRange(), primaryWeaponObject->getMaxRange(), m_aiCreatureData->m_primarySpecials.isEmpty() ? "none" : m_aiCreatureData->m_primarySpecials.getString()), PackedRgb::solidWhite); } else { aiDebugString.addText(fs.sprintf("pri(NULL:ERROR)\n"), PackedRgb::solidWhite); } //if (usingPrimaryWeapon()) //{ //float const ownerRadius = creatureOwner->getRadius(); //float const minRange = primaryWeaponObject->getMinRange() + ownerRadius; //float const maxRange = primaryWeaponObject->getMaxRange() + ownerRadius; // //if (minRange > ownerRadius) //{ // aiDebugString.addCircleAtObjectOffset(creatureOwner->getNetworkId(), Vector::zero, minRange, PackedRgb::solidMagenta); //} // //if (maxRange > ownerRadius) //{ // aiDebugString.addCircleAtObjectOffset(creatureOwner->getNetworkId(), Vector::zero, maxRange, PackedRgb::solidRed); //} //} } // Secondary Weapon { ServerObject const * const secondaryServerObject = ServerObject::getServerObject(getSecondaryWeapon()); WeaponObject const * const secondaryWeaponObject = (secondaryServerObject != NULL) ? secondaryServerObject->asWeaponObject() : NULL; if (secondaryWeaponObject != NULL) { aiDebugString.addText(fs.sprintf("%s sec (%s) [%.0f...%.0f] sp(%s)\n", (usingSecondaryWeapon() ? "->" : ""), FileNameUtils::get(secondaryWeaponObject->getObjectTemplateName(), FileNameUtils::fileName).c_str(), secondaryWeaponObject->getMinRange(), secondaryWeaponObject->getMaxRange(), m_aiCreatureData->m_secondarySpecials.isEmpty() ? "none" : m_aiCreatureData->m_secondarySpecials.getString()), PackedRgb::solidWhite); } else { aiDebugString.addText(fs.sprintf("sec (NONE)\n"), PackedRgb::solidWhite); } //if (usingSecondaryWeapon()) //{ //float const ownerRadius = creatureOwner->getRadius(); //float const minRange = secondaryWeaponObject->getMinRange() + ownerRadius; //float const maxRange = secondaryWeaponObject->getMaxRange() + ownerRadius; // //if (minRange > ownerRadius) //{ // aiDebugString.addCircleAtObjectOffset(creatureOwner->getNetworkId(), Vector::zero, minRange, PackedRgb::solidMagenta); //} // //if (maxRange > ownerRadius) //{ // aiDebugString.addCircleAtObjectOffset(creatureOwner->getNetworkId(), Vector::zero, maxRange, PackedRgb::solidRed); //} //} } // Display the leash length { aiDebugString.addLineToPosition(CellProperty::getPosition_w(m_combatStartLocation.get()), PackedRgb::solidGreen); //aiDebugString.addCircle(m_combatStartLocation.get().getCoordinates(), getLeashRadius(), PackedRgb::solidGreen); //float const leashLength = m_combatStartLocation.get().getCoordinates().magnitudeBetween(ownerPosition_w); //float const leashRemaining = (floorf(getLeashRadius() - leashLength) / 2.0f) * 2.0f; //aiDebugString.addText(fs.sprintf("leashRemaining(%.0fm)\n", leashRemaining), PackedRgb::solidCyan); } { aiDebugString.addText(fs.sprintf("combat duration (%d)\n", creatureOwner->getCombatDuration()), PackedRgb::solidWhite); } // Display the AI hate list { //aiDebugString.addText(fs.sprintf("in combat: %s\n", (creatureOwner->isInCombat() ? "YES" : "NO")), PackedRgb::solidWhite); HateList::SortedList hateList; creatureOwner->getSortedHateList(hateList); if (hateList.empty()) { aiDebugString.addText("* NO HATE TARGETS *\n", PackedRgb::solidYellow); } else { if (creatureOwner->isHateListAutoExpireTargetEnabled()) { aiDebugString.addText(fs.sprintf("* HATE LIST * %d\n", creatureOwner->getHateListAutoExpireTargetDuration() - creatureOwner->getTimeSinceLastHateListUpdate()), PackedRgb::solidRed); } else { aiDebugString.addText("* HATE LIST * no expire\n", PackedRgb::solidRed); } HateList::SortedList::const_iterator iterHateList = hateList.begin(); for (; iterHateList != hateList.end(); ++iterHateList) { CachedNetworkId const & hateTarget = iterHateList->first; TangibleObject const * const hateTargetTangibleObject = TangibleObject::asTangibleObject(hateTarget.getObject()); float const hate = iterHateList->second; std::string hateTargetName; if (hateTargetTangibleObject != NULL) { hateTargetName = Unicode::wideToNarrow(hateTargetTangibleObject->getEncodedObjectName()).c_str(); } else { hateTargetName = "NULL"; } aiDebugString.addText(fs.sprintf("%s:%s(%.1f)\n", hateTargetName.c_str(), hateTarget.getValueString().c_str(), hate), (iterHateList == hateList.begin()) ? PackedRgb::solidGreen : PackedRgb::solidRed); } } } } else { if (isAggressive()) { aiDebugString.addText(fs.sprintf("Aggro %.0fm\n", getAggroRadius()), PackedRgb::solidRed); aiDebugString.addCircleAtObjectOffset(creatureOwner->getNetworkId(), Vector::zero, getAggroRadius(), PackedRgb::solidRed); } if (isAssist()) { aiDebugString.addText(fs.sprintf("Assist %.0fm\n", getAssistRadius()), PackedRgb::solidOrange); aiDebugString.addCircleAtObjectOffset(creatureOwner->getNetworkId(), Vector::zero, getAssistRadius(), PackedRgb::solidOrange); } //if (isStalker()) //{ // aiDebugString.addText("S", PackedRgb::solidYellow); //} if (isKiller()) { aiDebugString.addText("DeathBlow", PackedRgb::solidYellow); } } // Line to server position aiDebugString.addLineToPosition(ownerPosition_w, PackedRgb::solidYellow); } std::string finalString; uint32 const crc = aiDebugString.toString(finalString); if (m_aiDebugStringCrc != crc) { m_aiDebugStringCrc = crc; ObserverList::const_iterator iterObserverList = observerList.begin(); for (; iterObserverList != observerList.end(); ++iterObserverList) { Client * const client = NON_NULL(*iterObserverList); ServerObject * const characterObject = client->getCharacterObject(); // Only send to the client if the objvar is set int temp = 0; if ( (characterObject != NULL) && characterObject->getObjVars().getItem("ai_debug_string", temp)) { if (temp > 0) { bool const reliable = true; GenericValueTypeMessage > message("AiDebugString", std::make_pair(getOwner()->getNetworkId(), finalString)); client->send(message, reliable); } } } } } } #endif // _DEBUG // ---------------------------------------------------------------------- void AICreatureController::setHomeLocation(Location const & location) { if (getOwner()->isAuthoritative()) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::setHomeLocation() owner(%s) sceneId(%u:%s) cell(%s) coordinates(%.2f, %.2f, %.2f)", getDebugInformation().c_str(), location.getSceneIdCrc(), (location.getSceneId() != NULL) ? location.getSceneId() : "NULL", location.getCell().getValueString().c_str(), location.getCoordinates().x, location.getCoordinates().y, location.getCoordinates().z)); m_homeLocation = location; } else { sendMessageToAuthServer(CM_aiSetHomeLocation, new MessageQueueGenericValueType(location)); } } // ---------------------------------------------------------------------- Location const & AICreatureController::getHomeLocation() const { return m_homeLocation.get(); } // ---------------------------------------------------------------------- void AICreatureController::markCombatStartLocation() { CreatureObject const * const creatureOwner = getCreature(); if (creatureOwner->isAuthoritative()) { m_combatStartLocation = getLocation(*creatureOwner); LOGC(AiLogManager::isLogging(creatureOwner->getNetworkId()), "debug_ai", ("AICreatureController::markCombatStartLocation() owner(%s) sceneId(%u:%s) cell(%s) coordinates(%.2f, %.2f, %.2f)", getDebugInformation().c_str(), m_combatStartLocation.get().getSceneIdCrc(), (m_combatStartLocation.get().getSceneId() != NULL) ? m_combatStartLocation.get().getSceneId() : "NULL", m_combatStartLocation.get().getCell().getValueString().c_str(), m_combatStartLocation.get().getCoordinates().x, m_combatStartLocation.get().getCoordinates().y, m_combatStartLocation.get().getCoordinates().z)); m_primaryWeaponActions.reset(); m_secondaryWeaponActions.reset(); } else { sendMessageToAuthServer(CM_aiMarkCombatStartLocation); } } // ---------------------------------------------------------------------- Location const & AICreatureController::getCombatStartLocation() const { return m_combatStartLocation.get(); } // ---------------------------------------------------------------------- float AICreatureController::getLeashRadius() { return ConfigServerGame::getAiLeashRadius(); } // ---------------------------------------------------------------------- void AICreatureController::setCreatureName(std::string const & creatureName) { if (getCreature()->isAuthoritative()) { m_creatureName = creatureName; } else { sendMessageToAuthServer(CM_aiSetCreatureName, new MessageQueueGenericValueType(creatureName)); } } // ---------------------------------------------------------------------- void AICreatureController::onCreatureNameChanged(std::string const & creatureName) { CreatureObject * const creatureOwner = getCreature(); // creatureName should be the creature name as defined in creatures_datatable.xml m_aiCreatureData = &AiCreatureData::getCreatureData(TemporaryCrcString(creatureName.c_str(), false)); if (creatureOwner && creatureOwner->isAuthoritative()) { setPrimaryWeapon(m_aiCreatureData->m_primaryWeapon); setSecondaryWeapon(m_aiCreatureData->m_secondaryWeapon); creatureOwner->setDifficulty(m_aiCreatureData->m_difficulty); } AiCreatureCombatProfile const * const primaryWeaponCombatProfile = AiCreatureCombatProfile::getCombatProfile(m_aiCreatureData->m_primarySpecials); if (primaryWeaponCombatProfile != NULL) { m_primaryWeaponActions.setCombatProfile(*creatureOwner, *primaryWeaponCombatProfile); } AiCreatureCombatProfile const * const secondaryWeaponCombatProfile = AiCreatureCombatProfile::getCombatProfile(m_aiCreatureData->m_secondarySpecials); if (secondaryWeaponCombatProfile != NULL) { m_secondaryWeaponActions.setCombatProfile(*creatureOwner, *secondaryWeaponCombatProfile); } LOGC(AiLogManager::isLogging(creatureOwner->getNetworkId()), "debug_ai", ("AICreatureController::setCreatureName() owner(%s) creatureName(%s) level(%d) primaryWeapon(%s:%s) secondaryWeapon(%s:%s)", getDebugInformation().c_str(), creatureName.c_str(), creatureOwner->getLevel(), m_primaryWeapon.get().getValueString().c_str(), m_aiCreatureData->m_primaryWeapon.getString(), m_secondaryWeapon.get().getValueString().c_str(), m_aiCreatureData->m_secondaryWeapon.getString())); } // ---------------------------------------------------------------------- void AICreatureController::setPrimaryWeapon(CrcString const & objectTemplate) { bool const required = true; NetworkId const newPrimaryWeapon = createWeapon("setPrimaryWeapon", objectTemplate, required); if (newPrimaryWeapon == NetworkId::cms_invalid) { WARNING(true, ("AICreatureController::setPrimaryWeapon() owner(%s) Unable to create the requested primary weapon(%s)", getDebugInformation().c_str(), objectTemplate.getString())); } else { bool const primaryWeaponEquipped = usingPrimaryWeapon(); destroyPrimaryWeapon(); m_primaryWeapon = newPrimaryWeapon; if (primaryWeaponEquipped) { equipPrimaryWeapon(); } } } // ---------------------------------------------------------------------- void AICreatureController::destroyPrimaryWeapon() { WeaponObject * const primaryWeapon = WeaponObject::getWeaponObject(getPrimaryWeapon()); if (primaryWeapon != NULL) { unEquipWeapons(); primaryWeapon->permanentlyDestroy(DeleteReasons::God); } } // ---------------------------------------------------------------------- void AICreatureController::setSecondaryWeapon(CrcString const & objectTemplate) { bool const required = false; NetworkId const newSecondaryWeapon = createWeapon("setSecondaryWeapon", objectTemplate, required); if (newSecondaryWeapon != NetworkId::cms_invalid) { bool const secondaryWeaponEquipped = usingSecondaryWeapon(); destroySecondaryWeapon(); m_secondaryWeapon = newSecondaryWeapon; if (secondaryWeaponEquipped) { equipSecondaryWeapon(); } } } // ---------------------------------------------------------------------- void AICreatureController::destroySecondaryWeapon() { WeaponObject * const secondaryWeapon = WeaponObject::getWeaponObject(getSecondaryWeapon()); if (secondaryWeapon != NULL) { unEquipWeapons(); secondaryWeapon->permanentlyDestroy(DeleteReasons::God); } } // ---------------------------------------------------------------------- PersistentCrcString const & AICreatureController::getCreatureName() const { if (m_aiCreatureData->m_name == NULL) { return *s_defaultCreatureName; } return *m_aiCreatureData->m_name; } // ---------------------------------------------------------------------- NetworkId const & AICreatureController::getPrimaryWeapon() const { return m_primaryWeapon.get(); } // ---------------------------------------------------------------------- NetworkId const & AICreatureController::getSecondaryWeapon() const { return m_secondaryWeapon.get(); } //----------------------------------------------------------------------- NetworkId AICreatureController::createWeapon(char const * const functionName, CrcString const & weaponName, bool const required) { NetworkId result; CreatureObject * const creatureOwner = getCreature(); ServerObject * const inventory = creatureOwner->getInventory(); if (inventory == NULL) { WARNING(true, ("AICreatureController::%s() Unable to find inventory for (%s) to place a weapon(%s)", functionName, getDebugInformation().c_str(), weaponName.getString())); } else { if (weaponName == TemporaryCrcString("none", false)) { if (required) { WARNING(required, ("debug_ai: AICreatureController::%s() A required weapon is specified as ""none"" for (%s), defaulting to ""unarmed""", functionName, getDebugInformation().c_str())); result = getUnarmedWeapon(); } else { result = NetworkId::cms_invalid; } } else { result = getUnarmedWeapon(); if (weaponName != TemporaryCrcString("unarmed", false)) { std::string const weaponTemplateName(AiCreatureData::getWeaponTemplateName(weaponName)); if (weaponTemplateName != "unarmed") { ConstCharCrcString const weaponCrcName(ObjectTemplateList::lookUp(weaponTemplateName.c_str())); if (weaponCrcName.getCrc() == 0) { WARNING(true, ("AICreatureController::%s() Unable to resolve a weapon(%s) crc for (%s)", functionName, weaponTemplateName.c_str(), getDebugInformation().c_str())); } else { bool const persisted = false; ServerObject * const newObject = ServerWorld::createNewObject(weaponCrcName.getCrc(), *inventory, persisted); if (newObject != NULL) { result = newObject->getNetworkId(); } else { WARNING(true, ("AICreatureController::%s() Unable to create a WeaponObject(%s) for (%s)", functionName, weaponTemplateName.c_str(), getDebugInformation().c_str())); } } } } } } return result; } //----------------------------------------------------------------------- NetworkId AICreatureController::getUnarmedWeapon() { NetworkId result; CreatureObject * const creatureOwner = getCreature(); WeaponObject * const defaultWeapon = creatureOwner->getDefaultWeapon(); if (defaultWeapon != NULL) { result = defaultWeapon->getNetworkId(); } else { WARNING(true, ("AICreatureController::getUnarmedWeapon() Unable to find a default weapon for (%s)", getDebugInformation().c_str())); } return result; } //----------------------------------------------------------------------- void AICreatureController::equipPrimaryWeapon() { CreatureObject * const creatureOwner = getCreature(); if (creatureOwner->isAuthoritative()) { if (!isCombatAi()) { WARNING(true, ("AICreatureController::equipPrimaryWeapon() owner(%s) A non-combat AI is trying to equip a weapon", getDebugInformation().c_str())); return; } if (!usingPrimaryWeapon()) { ServerObject * const primaryWeaponServerObject = ServerObject::getServerObject(getPrimaryWeapon()); WeaponObject * const primaryWeaponObject = (primaryWeaponServerObject != NULL) ? primaryWeaponServerObject->asWeaponObject() : NULL; if (primaryWeaponObject != NULL) { unEquipWeapons(); CreatureObject * const creatureOwner = getCreature(); if (primaryWeaponObject == creatureOwner->getDefaultWeapon()) { creatureOwner->setCurrentWeapon(*primaryWeaponObject); } else { Container::ContainerErrorCode errorCode; if (ContainerInterface::transferItemToGeneralContainer(*creatureOwner, *primaryWeaponServerObject, NULL, errorCode)) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::equipPrimaryWeapon() owner(%s) primaryWeapon(%s)\n", getDebugInformation().c_str(), primaryWeaponServerObject->getDebugInformation().c_str())); creatureOwner->setCurrentWeapon(*primaryWeaponObject); GameScriptObject * const gameScriptObject = GameScriptObject::asGameScriptObject(creatureOwner); if (gameScriptObject != NULL) { ScriptParams scriptParams; scriptParams.addParam(primaryWeaponObject->getNetworkId()); IGNORE_RETURN(gameScriptObject->trigAllScripts(Scripting::TRIG_AI_PRIMARY_WEAPON_EQUIPPED, scriptParams)); } } else { WARNING(true, ("AICreatureController::equipPrimaryWeapon() owner(%s) Transfer of primaryWeapon(%s) to the visible equipped slot failed(%d).", getDebugInformation().c_str(), primaryWeaponServerObject->getDebugInformation().c_str(), errorCode)); } } } else { WARNING(true, ("AICreatureController::equipPrimaryWeapon() owner(%s) Unable to resolve primaryWeapon(%s:%s) to a WeaponObject.", getDebugInformation().c_str(), m_primaryWeapon.get().getValueString().c_str(), m_aiCreatureData->m_primaryWeapon.getString())); } } } else { sendMessageToAuthServer(CM_aiEquipPrimaryWeapon); } } //----------------------------------------------------------------------- void AICreatureController::equipSecondaryWeapon() { CreatureObject * const creatureOwner = getCreature(); if (creatureOwner->isAuthoritative()) { if (!isCombatAi()) { WARNING(true, ("AICreatureController::equipSecondaryWeapon() owner(%s) A non-combat AI is trying to equip a weapon", getDebugInformation().c_str())); return; } if (!usingSecondaryWeapon()) { ServerObject * const secondaryWeaponServerObject = ServerObject::getServerObject(getSecondaryWeapon()); WeaponObject * const secondaryWeaponObject = (secondaryWeaponServerObject != NULL) ? secondaryWeaponServerObject->asWeaponObject() : NULL; if (secondaryWeaponObject != NULL) { unEquipWeapons(); CreatureObject * const creatureOwner = getCreature(); if (secondaryWeaponObject == creatureOwner->getDefaultWeapon()) { creatureOwner->setCurrentWeapon(*secondaryWeaponObject); } else { Container::ContainerErrorCode errorCode; if (ContainerInterface::transferItemToGeneralContainer(*creatureOwner, *secondaryWeaponServerObject, NULL, errorCode)) { LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::equipSecondaryWeapon() owner(%s) secondaryWeapon(%s) errorCode(%d)\n", getDebugInformation().c_str(), secondaryWeaponServerObject->getDebugInformation().c_str())); creatureOwner->setCurrentWeapon(*secondaryWeaponObject); GameScriptObject * const gameScriptObject = GameScriptObject::asGameScriptObject(creatureOwner); if (gameScriptObject != NULL) { ScriptParams scriptParams; scriptParams.addParam(secondaryWeaponObject->getNetworkId()); IGNORE_RETURN(gameScriptObject->trigAllScripts(Scripting::TRIG_AI_SECONDARY_WEAPON_EQUIPPED, scriptParams)); } } else { WARNING(true, ("AICreatureController::equipSecondaryWeapon() owner(%s) Transfer of secondaryWeapon(%s) to the visible equipped slot failed(%d).", getDebugInformation().c_str(), secondaryWeaponServerObject->getDebugInformation().c_str(), errorCode)); } } } else { WARNING(true, ("AICreatureController::equipSecondaryWeapon() owner(%s) Unable to resolve secondaryWeapon(%s:%s) to a WeaponObject.", getDebugInformation().c_str(), m_secondaryWeapon.get().getValueString().c_str(), m_aiCreatureData->m_secondaryWeapon.getString())); } } } else { sendMessageToAuthServer(CM_aiEquipSecondaryWeapon); } } //----------------------------------------------------------------------- void AICreatureController::unEquipWeapons() { CreatureObject * const creatureOwner = getCreature(); if (creatureOwner->isAuthoritative()) { if (!isCombatAi()) { WARNING(true, ("AICreatureController::unEquipWeapons() owner(%s) A non-combat AI is trying to unequip a weapon", getDebugInformation().c_str())); return; } LOGC(AiLogManager::isLogging(getOwner()->getNetworkId()), "debug_ai", ("AICreatureController::unEquipWeapons() owner(%s)\n", getDebugInformation().c_str())); CreatureObject * const creatureOwner = getCreature(); WeaponObject * const currentWeaponObject = creatureOwner->getCurrentWeapon(); WeaponObject * const defaultWeapon = creatureOwner->getDefaultWeapon(); if (currentWeaponObject != defaultWeapon) { // Put the current weapon back in the inventory { ServerObject * const inventory = creatureOwner->getInventory(); if (inventory != NULL) { Container::ContainerErrorCode error; if (!ContainerInterface::transferItemToVolumeContainer(*inventory, *currentWeaponObject, creatureOwner, error)) { WARNING(true, ("AICreatureController::unEquipWeapons() owner(%s) Transfer of the currentWeapon(%s) to the inventory failed(%d).", getDebugInformation().c_str(), currentWeaponObject->getDebugInformation().c_str(), error)); } } else { WARNING(true, ("AICreatureController::unEquipWeapons() owner(%s) Unable to find an inventory.", getDebugInformation().c_str())); } } // Equip the default weapon { if (defaultWeapon != NULL) { creatureOwner->setCurrentWeapon(*defaultWeapon); } else { WARNING(true, ("AICreatureController::unEquipWeapons() owner(%s) NULL default weapon", getDebugInformation().c_str())); } } } } else { sendMessageToAuthServer(CM_aiUnEquipWeapons); } } //----------------------------------------------------------------------- bool AICreatureController::hasPrimaryWeapon() const { bool const result = (getPrimaryWeapon() != NetworkId::cms_invalid); return result; } //----------------------------------------------------------------------- bool AICreatureController::hasSecondaryWeapon() const { bool const result = (getSecondaryWeapon() != NetworkId::cms_invalid); return result; } //----------------------------------------------------------------------- bool AICreatureController::usingPrimaryWeapon() { bool result = false; if (hasPrimaryWeapon()) { WeaponObject const * const currentWeaponObject = getCreature()->getCurrentWeapon(); if (currentWeaponObject != NULL) { result = (getPrimaryWeapon() == currentWeaponObject->getNetworkId()); } } return result; } //----------------------------------------------------------------------- bool AICreatureController::usingSecondaryWeapon() { bool result = false; if (hasSecondaryWeapon() && !usingPrimaryWeapon()) { WeaponObject const * const currentWeaponObject = getCreature()->getCurrentWeapon(); if (currentWeaponObject != NULL) { result = (getSecondaryWeapon() == currentWeaponObject->getNetworkId()); } } return result; } //----------------------------------------------------------------------- void AICreatureController::setFrozen(bool const frozen) { if (getOwner()->isAuthoritative()) { m_frozen = frozen; } else { sendMessageToAuthServer(CM_aiSetFrozen, new MessageQueueGenericValueType(frozen)); } } //----------------------------------------------------------------------- bool AICreatureController::isFrozen() const { return m_frozen.get(); } //----------------------------------------------------------------------- float AICreatureController::getMovementSpeedPercent() const { return m_aiCreatureData->m_movementSpeedPercent; } //----------------------------------------------------------------------- void AICreatureController::addServerNpAutoDeltaVariables(Archive::AutoDeltaByteStream & stream) { stream.addVariable(m_running); stream.addVariable(m_homeLocation); stream.addVariable(m_primaryWeapon); stream.addVariable(m_secondaryWeapon); stream.addVariable(m_frozen); stream.addVariable(m_combatStartLocation); stream.addVariable(m_retreating); stream.addVariable(m_retreatingStartTime); stream.addVariable(m_logging); stream.addVariable(m_creatureName); stream.addVariable(m_hibernationDelay); stream.addVariable(m_hibernationTimer); m_primaryWeaponActions.addServerNpAutoDeltaVariables(stream); m_secondaryWeaponActions.addServerNpAutoDeltaVariables(stream); } //----------------------------------------------------------------------- bool AICreatureController::isAggressive() const { CreatureObject const * const creatureOwner = getCreature(); return (!creatureOwner->isInvulnerable() && (m_aiCreatureData->m_aggressive > 0.0f)); } //----------------------------------------------------------------------- bool AICreatureController::isAssist() const { CreatureObject const * const creatureOwner = getCreature(); return (!creatureOwner->isInvulnerable() && (m_aiCreatureData->m_assist > 0.0f)); } //----------------------------------------------------------------------- bool AICreatureController::isStalker() const { CreatureObject const * const creatureOwner = getCreature(); return (!creatureOwner->isInvulnerable() && m_aiCreatureData->m_stalker); } //----------------------------------------------------------------------- bool AICreatureController::isKiller() const { CreatureObject const * const creatureOwner = getCreature(); return (!creatureOwner->isInvulnerable() && (m_aiCreatureData->m_deathBlow == AiCreatureData::DB_instant)); } //----------------------------------------------------------------------- void AICreatureController::setRetreating(bool const retreating) { CreatureObject * const creatureOwner = getCreature(); if (creatureOwner->isAuthoritative()) { bool const retreatStart = !m_retreating.get() && retreating; bool const retreatComplete = m_retreating.get() && !retreating; if (retreatStart) { // Start the AI towards its anchor position NetworkId const & cellId = m_combatStartLocation.get().getCell(); Vector const & position_c = m_combatStartLocation.get().getCoordinates(); if (cellId != NetworkId::cms_invalid) { CellObject const * const cellObject = CellObject::getCellObject(cellId); if (cellObject != NULL) { CellProperty const * const cellProperty = cellObject->getCellProperty(); moveTo(cellProperty, position_c); } else { WARNING(true, ("AICreatureController::setRetreating() ai(%s) Unable to resolve the cellId(%s) to a CellObject", creatureOwner->getDebugInformation().c_str(), cellId.getValueString().c_str())); } } else { moveTo(CellProperty::getWorldCellProperty(), position_c); } } m_retreating = retreating; if (retreatStart) { m_retreatingStartTime = Os::getRealSystemTime(); creatureOwner->clearHateList(); triggerScriptsSimple(TRIG_AI_RETREAT_START); } else if (retreatComplete) { triggerScriptsSimple(TRIG_AI_RETREAT_COMPLETE); } } else { sendMessageToAuthServer(CM_aiSetRetreating, new MessageQueueGenericValueType(retreating)); } } //----------------------------------------------------------------------- void AICreatureController::setLogging(bool const logging) { CreatureObject * const creatureOwner = getCreature(); if (creatureOwner->isAuthoritative()) { m_logging = logging; AiLogManager::setLogging(creatureOwner->getNetworkId(), logging); } else { sendMessageToAuthServer(CM_aiSetLogging, new MessageQueueGenericValueType(logging)); } } //----------------------------------------------------------------------- bool AICreatureController::isLogging() const { return m_logging.get(); } //----------------------------------------------------------------------- bool AICreatureController::isRetreating() const { return m_retreating.get(); } //----------------------------------------------------------------------- bool AICreatureController::isCombatAi() const { return (m_aiCreatureData != &AiCreatureData::getDefaultCreatureData()); } // ---------------------------------------------------------------------- std::string AICreatureController::getDebugInformation() const { return FormattedString<1024>().sprintf("creatureName=%s %s", getCreatureName().getString(), getOwner()->getDebugInformation().c_str()); } //----------------------------------------------------------------------- void AICreatureController::sendMessageToAuthServer(GameControllerMessage const gameControllerMessage) { int const flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER; appendMessage(gameControllerMessage, 0, flags); } //----------------------------------------------------------------------- void AICreatureController::sendMessageToAuthServer(GameControllerMessage const gameControllerMessage, MessageQueue::Data * messageQueue) { int const flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER; appendMessage(gameControllerMessage, 0, messageQueue, flags); } //----------------------------------------------------------------------- void AICreatureController::sendMessageToProxyServer(GameControllerMessage const gameControllerMessage, MessageQueue::Data * messageQueue) { int const flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_PROXY_SERVER; appendMessage(gameControllerMessage, 0, messageQueue, flags); } //----------------------------------------------------------------------- AiMovementType AICreatureController::getMovementType() const { AiMovementType result = AMT_invalid; if (m_movement != AiMovementBaseNullPtr) { result = m_movement->getType(); } return result; } //----------------------------------------------------------------------- AiMovementType AICreatureController::getPendingMovementType() const { AiMovementType result = AMT_invalid; if (m_pendingMovement != AiMovementBaseNullPtr) { result = m_pendingMovement->getType(); } return result; } //----------------------------------------------------------------------- PersistentCrcString const & AICreatureController::getCombatAction() { if (usingPrimaryWeapon()) { return m_primaryWeaponActions.getCombatAction(); } else if (usingSecondaryWeapon()) { return m_secondaryWeaponActions.getCombatAction(); } return PersistentCrcString::empty; } //----------------------------------------------------------------------- time_t AICreatureController::getKnockDownRecoveryTime() const { AiCreatureCombatProfile const * const combatProfile = AiCreatureCombatProfile::getCombatProfile(m_aiCreatureData->m_primarySpecials); return (combatProfile != NULL) ? combatProfile->m_knockDownRecoveryTime : 0; } //----------------------------------------------------------------------- std::string const AICreatureController::getCombatActionsString() { std::string result; FormattedString<4096> fs; result += fs.sprintf("knockDownRecoveryTime: %u\n", static_cast(getKnockDownRecoveryTime())); // Primary Weapon { WeaponObject const * const primaryWeaponObject = WeaponObject::getWeaponObject(getPrimaryWeapon()); if (primaryWeaponObject != NULL) { result += fs.sprintf("PRIMARY WEAPON >>> %s range[%.0f...%.0f] %s\n", FileNameUtils::get(primaryWeaponObject->getObjectTemplateName(), FileNameUtils::fileName).c_str(), primaryWeaponObject->getMinRange(), primaryWeaponObject->getMaxRange(), usingPrimaryWeapon() ? "(active)" : ""); } else { result += fs.sprintf("PRIMARY WEAPON >>> ERROR NO WEAPON\n"); } AiCreatureCombatProfile const * const primaryWeaponCombatProfile = AiCreatureCombatProfile::getCombatProfile(m_aiCreatureData->m_primarySpecials); if (primaryWeaponCombatProfile != NULL) { result += primaryWeaponCombatProfile->toString(); } else { result += fs.sprintf(" no special attacks\n"); } } // Secondary Weapon { WeaponObject const * const secondaryWeaponObject = WeaponObject::getWeaponObject(getSecondaryWeapon()); if (secondaryWeaponObject != NULL) { result += fs.sprintf("SECONDARY WEAPON >>> %s range[%.0f...%.0f] %s\n", FileNameUtils::get(secondaryWeaponObject->getObjectTemplateName(), FileNameUtils::fileName).c_str(), secondaryWeaponObject->getMinRange(), secondaryWeaponObject->getMaxRange(), usingSecondaryWeapon() ? "(active)" : ""); } else { result += fs.sprintf("SECONDARY WEAPON >>> NONE\n"); } AiCreatureCombatProfile const * const secondaryWeaponCombatProfile = AiCreatureCombatProfile::getCombatProfile(m_aiCreatureData->m_secondarySpecials); if (secondaryWeaponCombatProfile != NULL) { result += secondaryWeaponCombatProfile->toString(); } else { result += fs.sprintf(" no special attacks\n"); } } return result; } // ======================================================================