bool CAI_Pathfinder::IsLinkStillStale(int moveType, CAI_Link *nodeLink) { if ( m_bIgnoreStaleLinks ) return false; if ( !(nodeLink->m_LinkInfo & bits_LINK_STALE_SUGGESTED ) ) return false; if ( gpGlobals->curtime < nodeLink->m_timeStaleExpires ) return true; // NPC should only check one stale link per think if (gpGlobals->curtime == m_flLastStaleLinkCheckTime) { return true; } else { m_flLastStaleLinkCheckTime = gpGlobals->curtime; } // Test movement, if suceeds, clear the stale bit if (CheckStaleRoute(GetNetwork()->GetNode(nodeLink->m_iSrcID)->GetPosition(GetHullType()), GetNetwork()->GetNode(nodeLink->m_iDestID)->GetPosition(GetHullType()), moveType)) { nodeLink->m_LinkInfo &= ~bits_LINK_STALE_SUGGESTED; return false; } nodeLink->m_timeStaleExpires = gpGlobals->curtime + 1.0; return true; }
//--------------------------------------------------------- //--------------------------------------------------------- void CZombie::SetZombieModel( void ) { Hull_t lastHull = GetHullType(); if ( m_fIsTorso ) { SetModel( "models/zombie/classic_torso.mdl" ); SetHullType( HULL_TINY ); } else { SetModel( "models/zombie/classic.mdl" ); SetHullType( HULL_HUMAN ); } SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless ); SetHullSizeNormal( true ); SetDefaultEyeOffset(); SetActivity( ACT_IDLE ); // hull changed size, notify vphysics // UNDONE: Solve this generally, systematically so other // NPCs can change size if ( lastHull != GetHullType() ) { if ( VPhysicsGetObject() ) { SetupVPhysicsHull(); } } }
//----------------------------------------------------------------------------- // Computes the link type //----------------------------------------------------------------------------- Navigation_t CAI_Pathfinder::ComputeWaypointType( CAI_Node **ppNodes, int parentID, int destID ) { Navigation_t navType = NAV_NONE; CAI_Node *pNode = ppNodes[parentID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { // BRJ 10/1/02 // FIXME: pNPC->CapabilitiesGet() is actually the mechanism by which fliers // filter out the bitfields in the waypoint type (most importantly, bits_MOVE_CAP_GROUND) // that would cause the waypoint distance to be computed in a 2D, as opposed to 3D fashion // This is a super-scary weak link if you ask me. int linkMoveTypeBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( linkMoveTypeBits & CapabilitiesGet()); if ( !moveTypeBits && linkMoveTypeBits == bits_CAP_MOVE_JUMP ) { Assert( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_JUMP_OVERRIDE ); ppNodes[destID]->Lock(0.3); moveTypeBits = linkMoveTypeBits; } Navigation_t linkType = MoveBitsToNavType( moveTypeBits ); // This will only trigger if the links disagree about their nav type Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; break; } } // @TODO (toml 10-15-02): one would not expect to come out of the above logic // with NAV_NONE. However, if a graph is newly built, it can contain malformed // links that are referred to by the destination node, not the source node. // This has to be fixed if ( navType == NAV_NONE ) { pNode = ppNodes[destID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { int npcMoveBits = CapabilitiesGet(); int nodeMoveBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( npcMoveBits & nodeMoveBits ); Navigation_t linkType = MoveBitsToNavType( moveTypeBits ); Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; DevMsg( "Note: Strange link found between nodes in AI node graph\n" ); break; } } } AssertMsg( navType != NAV_NONE, "Pathfinder appears to have output a path with consecutive nodes thate are not actually connected\n" ); return navType; }
//----------------------------------------------------------------------------- // Purpose: // // NOTE: This function is still heavy with common code (found at the bottom). // we should consider moving some into the base class! (sjb) //----------------------------------------------------------------------------- void CNPC_Monster::SetZombieModel( void ) { Hull_t lastHull = GetHullType(); SetModel( cModel.ToCStr() ); if (m_fIsTorso) SetHullType(HULL_TINY); else SetHullType(HULL_HUMAN); SetHullSizeNormal( true ); SetDefaultEyeOffset(); SetActivity( ACT_IDLE ); m_nSkin = m_iSkin; if ( lastHull != GetHullType() ) { if ( VPhysicsGetObject() ) { SetupVPhysicsHull(); } } CollisionProp()->SetSurroundingBoundsType( USE_OBB_COLLISION_BOUNDS ); }
void CZombie::SetZombieModel( void ) { Hull_t lastHull = GetHullType(); if(m_iZombieType == ZOMBIE_BISOU) SetModel( "models/zombie/Bisounours.mdl" ); else SetModel( "models/zombie/classic.mdl" ); SetHullType( HULL_HUMAN ); //Commenté en attendant de trouver à quoi ça sert //SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless ); SetHullSizeNormal( true ); SetDefaultEyeOffset(); SetActivity( ACT_IDLE ); // hull changed size, notify vphysics // UNDONE: Solve this generally, systematically so other // NPCs can change size if ( lastHull != GetHullType() ) if ( VPhysicsGetObject() ) SetupVPhysicsHull(); }
int CAI_TacticalServices::FindBackAwayNode(const Vector &vecThreat ) { if ( !CAI_NetworkManager::NetworksLoaded() ) { DevWarning( 2, "Graph not ready for FindBackAwayNode!\n" ); return NO_NODE; } int iMyNode = GetPathfinder()->NearestNodeToNPC(); int iThreatNode = GetPathfinder()->NearestNodeToPoint( vecThreat ); if ( iMyNode == NO_NODE ) { DevWarning( 2, "FindBackAwayNode() - %s has no nearest node!\n", GetEntClassname()); return NO_NODE; } if ( iThreatNode == NO_NODE ) { // DevWarning( 2, "FindBackAwayNode() - Threat has no nearest node!\n" ); iThreatNode = iMyNode; // return false; } // A vector pointing to the threat. Vector vecToThreat; vecToThreat = vecThreat - GetLocalOrigin(); // Get my current distance from the threat float flCurDist = VectorNormalize( vecToThreat ); // Check my neighbors to find a node that's further away for (int link = 0; link < GetNetwork()->GetNode(iMyNode)->NumLinks(); link++) { CAI_Link *nodeLink = GetNetwork()->GetNode(iMyNode)->GetLinkByIndex(link); if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) ) continue; int destID = nodeLink->DestNodeID(iMyNode); float flTestDist = ( vecThreat - GetNetwork()->GetNode(destID)->GetPosition(GetHullType()) ).Length(); if ( flTestDist > flCurDist ) { // Make sure this node doesn't take me past the enemy's position. Vector vecToNode; vecToNode = GetNetwork()->GetNode(destID)->GetPosition(GetHullType()) - GetLocalOrigin(); VectorNormalize( vecToNode ); if( DotProduct( vecToNode, vecToThreat ) < 0.0 ) { return destID; } } } return NO_NODE; }
// Purpose: Monitor the antlion's jump to play the proper landing sequence bool CASW_Alien_Jumper::CheckLanding( void ) { trace_t tr; Vector testPos; //Amount of time to predict forward const float timeStep = 0.1f; //Roughly looks one second into the future testPos = GetAbsOrigin() + ( GetAbsVelocity() * timeStep ); testPos[2] -= ( 0.5 * sv_gravity.GetFloat() * GetGravity() * timeStep * timeStep); if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Line( GetAbsOrigin(), testPos, 255, 0, 0, 0, 0.5f ); NDebugOverlay::Cross3D( m_vecSavedJump, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, true, 0.5f ); } // Look below AI_TraceHull( GetAbsOrigin(), testPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); //See if we're about to contact, or have already contacted the ground if ( ( tr.fraction != 1.0f ) || ( GetFlags() & FL_ONGROUND ) ) { int sequence = SelectWeightedSequence( (Activity)ACT_ASW_ALIEN_LAND ); if ( GetSequence() != sequence ) { VacateStrategySlot(); SetIdealActivity( (Activity) ACT_ASW_ALIEN_LAND ); CreateDust( false ); EmitSound( "ASW_Drone.Land" ); // asw todo: make the alien attack here? //if ( GetEnemy() && GetEnemy()->IsPlayer() ) //{ //CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() ); //if ( pPlayer && pPlayer->IsInAVehicle() == false ) //MeleeAttack( ANTLION_MELEE1_RANGE, sk_antlion_swipe_damage.GetFloat(), QAngle( 4.0f, 0.0f, 0.0f ), Vector( -250.0f, 1.0f, 1.0f ) ); //} SetAbsVelocity( GetAbsVelocity() * 0.33f ); return false; } return IsActivityFinished(); } return false; }
//----------------------------------------------------------------------------- // Purpose: Monitor the alien's jump to play the proper landing sequence //----------------------------------------------------------------------------- bool CAI_ASW_JumpBehavior::CheckLanding( void ) { trace_t tr; Vector testPos; //Amount of time to predict forward const float timeStep = 0.1f; //Roughly looks one second into the future testPos = GetAbsOrigin() + ( GetOuter()->GetAbsVelocity() * timeStep ); testPos[2] -= ( 0.5 * sv_gravity.GetFloat() * GetGravity() * timeStep * timeStep); if ( asw_debug_jump_behavior.GetInt() == 2 ) { NDebugOverlay::Line( GetAbsOrigin(), testPos, 255, 0, 0, 0, 0.5f ); NDebugOverlay::Cross3D( m_vecSavedJump, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, true, 0.5f ); } // Look below AI_TraceHull( GetAbsOrigin(), testPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), GetOuter()->GetAITraceMask(), GetOuter(), COLLISION_GROUP_NONE, &tr ); //See if we're about to contact, or have already contacted the ground if ( ( tr.fraction != 1.0f ) || ( GetOuter()->GetFlags() & FL_ONGROUND ) ) { int sequence = GetOuter()->SelectWeightedSequence( (Activity)ACT_ALIEN_LAND ); if ( GetSequence() != sequence ) { CASW_Alien *pNPC = static_cast< CASW_Alien * >( GetOuter() ); pNPC->VacateStrategySlot(); pNPC->SetIdealActivity( (Activity) ACT_ALIEN_LAND ); //pNPC->Land(); if ( GetEnemy() && GetEnemy()->IsPlayer() ) { CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() ); if ( pPlayer && pPlayer->IsInAVehicle() == false ) { //pNPC->MeleeAttack( m_flDamageDistance, m_flJumpDamage, QAngle( 4.0f, 0.0f, 0.0f ), Vector( -250.0f, 1.0f, 1.0f ) ); } } GetOuter()->SetAbsVelocity( GetOuter()->GetAbsVelocity() * 0.33f ); return false; } return GetOuter()->IsActivityFinished(); } return false; }
double ShipDesign::ProductionCost(int empire_id, int location_id) const { if (CHEAP_AND_FAST_SHIP_PRODUCTION) { return 1.0; } else { double cost_accumulator = 0.0; if (const HullType* hull = GetHullType(m_hull)) cost_accumulator += hull->ProductionCost(empire_id, location_id); for (std::vector<std::string>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it) if (const PartType* part = GetPartType(*it)) cost_accumulator += part->ProductionCost(empire_id, location_id); return std::max(0.0, cost_accumulator); } }
int ShipDesign::ProductionTime(int empire_id, int location_id) const { if (CHEAP_AND_FAST_SHIP_PRODUCTION) { return 1; } else { int time_accumulator = 1; if (const HullType* hull = GetHullType(m_hull)) time_accumulator = std::max(time_accumulator, hull->ProductionTime(empire_id, location_id)); for (std::vector<std::string>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it) if (const PartType* part = GetPartType(*it)) time_accumulator = std::max(time_accumulator, part->ProductionTime(empire_id, location_id)); return std::max(1, time_accumulator); } }
//----------------------------------------------------------------------------- // Purpose: Given an array of parentID's and endID, contruct a linked // list of waypoints through those parents //----------------------------------------------------------------------------- AI_Waypoint_t* CAI_Pathfinder::MakeRouteFromParents( int *parentArray, int endID ) { AI_Waypoint_t *pOldWaypoint = NULL; AI_Waypoint_t *pNewWaypoint = NULL; int currentID = endID; CAI_Node **pAInode = GetNetwork()->AccessNodes(); while (currentID != NO_NODE) { // Try to link it to the previous waypoint int prevID = parentArray[currentID]; int destID; if (prevID != NO_NODE) { destID = prevID; } else { // If we have no previous node, then use the next node if ( !pOldWaypoint ) return NULL; destID = pOldWaypoint->iNodeID; } Navigation_t waypointType = ComputeWaypointType( pAInode, currentID, destID ); // BRJ 10/1/02 // FIXME: It appears potentially possible for us to compute waypoints // here which the NPC is not capable of traversing (because // pNPC->CapabilitiesGet() in ComputeWaypointType() above filters it out). // It's also possible if none of the lines have an appropriate DestNodeID. // Um, shouldn't such a waypoint not be allowed?!?!? Assert( waypointType != NAV_NONE ); pNewWaypoint = new AI_Waypoint_t( pAInode[currentID]->GetPosition(GetHullType()), pAInode[currentID]->GetYaw(), waypointType, bits_WP_TO_NODE, currentID ); // Link it up... pNewWaypoint->SetNext( pOldWaypoint ); pOldWaypoint = pNewWaypoint; currentID = prevID; } return pOldWaypoint; }
ShipDesign::ShipDesign(const std::string& name, const std::string& description, int designed_on_turn, const std::string& hull, const std::vector<std::string>& parts, const std::string& icon, const std::string& model, bool name_desc_in_stringtable, bool monster) : m_id(INVALID_OBJECT_ID), m_name(name), m_description(description), m_designed_on_turn(designed_on_turn), m_hull(hull), m_parts(parts), m_is_monster(monster), m_icon(icon), m_3D_model(model), m_name_desc_in_stringtable(name_desc_in_stringtable), m_is_armed(false), m_detection(0.0), m_colony_capacity(0.0), m_troop_capacity(0.0), m_stealth(0.0), m_fuel(0.0), m_shields(0.0), m_structure(0.0), m_battle_speed(0.0), m_starlane_speed(0.0), m_min_SR_range(FLT_MAX), m_max_SR_range(0.0), m_min_LR_range(FLT_MAX), m_max_LR_range(0.0), m_min_PD_range(FLT_MAX), m_max_PD_range(0.0), m_min_weapon_range(FLT_MAX), m_max_weapon_range(0.0), m_min_non_PD_weapon_range(FLT_MAX), m_max_non_PD_weapon_range(0.0) { // expand parts list to have empty values if fewer parts are given than hull has slots if (const HullType* hull_type = GetHullType(m_hull)) { if (m_parts.size() < hull_type->NumSlots()) m_parts.resize(hull_type->NumSlots(), ""); } if (!ValidDesign(m_hull, m_parts)) { Logger().errorStream() << "constructing an invalid ShipDesign!"; Logger().errorStream() << Dump(); } BuildStatCaches(); }
bool ShipDesign::ProductionCostTimeLocationInvariant() const { if (CHEAP_AND_FAST_SHIP_PRODUCTION) return true; // as seen in ShipDesign::ProductionCost, the location is passed as the // local candidate in the ScriptingContext // check hull and all parts if (const HullType* hull = GetHullType(m_hull)) if (!hull->ProductionCostTimeLocationInvariant()) return false; for (std::vector<std::string>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it) if (const PartType* part = GetPartType(*it)) if (!part->ProductionCostTimeLocationInvariant()) return false; // if hull and all parts are invariant, so is whole design return true; }
ShipDesign::ShipDesign(const std::string& name, const std::string& description, int designed_on_turn, int designed_by_empire, const std::string& hull, const std::vector<std::string>& parts, const std::string& icon, const std::string& model, bool name_desc_in_stringtable, bool monster) : m_id(INVALID_OBJECT_ID), m_name(name), m_description(description), m_designed_on_turn(designed_on_turn), m_designed_by_empire(designed_by_empire), m_hull(hull), m_parts(parts), m_is_monster(monster), m_icon(icon), m_3D_model(model), m_name_desc_in_stringtable(name_desc_in_stringtable), m_is_armed(false), m_can_bombard(false), m_detection(0.0), m_colony_capacity(0.0), m_troop_capacity(0.0), m_stealth(0.0), m_fuel(0.0), m_shields(0.0), m_structure(0.0), m_battle_speed(0.0), m_starlane_speed(0.0), m_research_generation(0.0), m_industry_generation(0.0), m_trade_generation(0.0), m_is_production_location(false) { // expand parts list to have empty values if fewer parts are given than hull has slots if (const HullType* hull_type = GetHullType(m_hull)) { if (m_parts.size() < hull_type->NumSlots()) m_parts.resize(hull_type->NumSlots(), ""); } if (!ValidDesign(m_hull, m_parts)) { ErrorLogger() << "constructing an invalid ShipDesign!"; ErrorLogger() << Dump(); } BuildStatCaches(); }
bool CAI_Pathfinder::IsLinkUsable(CAI_Link *pLink, int startID) { // -------------------------------------------------------------------------- // Skip if link turned off // -------------------------------------------------------------------------- if (pLink->m_LinkInfo & bits_LINK_OFF) { CDynamicLink *pDynamicLink = dynamic_cast<CDynamicLink *>(CEntity::Instance(pLink->m_pDynamicLink)); if ( !pDynamicLink || *(pDynamicLink->m_strAllowUse.ptr) == NULL_STRING ) return false; const char *pszAllowUse = STRING( *(pDynamicLink->m_strAllowUse) ); if ( *(pDynamicLink->m_bInvertAllow) ) { // Exlude only the specified entity name or classname if ( GetOuter()->NameMatches(pszAllowUse) || GetOuter()->ClassMatches( pszAllowUse ) ) return false; } else { // Exclude everything but the allowed entity name or classname if ( !GetOuter()->NameMatches( pszAllowUse) && !GetOuter()->ClassMatches( pszAllowUse ) ) return false; } } // -------------------------------------------------------------------------- // Get the destination nodeID // -------------------------------------------------------------------------- int endID = pLink->DestNodeID(startID); // -------------------------------------------------------------------------- // Make sure I have the ability to do the type of movement specified by the link // -------------------------------------------------------------------------- int linkMoveTypes = pLink->m_iAcceptedMoveTypes[GetHullType()]; int moveType = ( linkMoveTypes & CapabilitiesGet() ); CAI_Node *pStartNode,*pEndNode; pStartNode = GetNetwork()->GetNode(startID); pEndNode = GetNetwork()->GetNode(endID); if ( (linkMoveTypes & bits_CAP_MOVE_JUMP) && !moveType ) { CE_AI_Hint *pStartHint = dynamic_cast<CE_AI_Hint *>(pStartNode->GetHint()); CE_AI_Hint *pEndHint = dynamic_cast<CE_AI_Hint *>(pEndNode->GetHint()); if ( pStartHint && pEndHint ) { if ( pStartHint->HintType() == HINT_JUMP_OVERRIDE && pEndHint->HintType() == HINT_JUMP_OVERRIDE && ( ( ( pStartHint->GetSpawnFlags() | pEndHint->GetSpawnFlags() ) & SF_ALLOW_JUMP_UP ) || pStartHint->GetAbsOrigin().z > pEndHint->GetAbsOrigin().z ) ) { if ( !pStartNode->IsLocked() ) { if ( pStartHint->GetTargetNode() == -1 || pStartHint->GetTargetNode() == endID ) moveType = bits_CAP_MOVE_JUMP; } } } } if (!moveType) { return false; } // -------------------------------------------------------------------------- // Check if NPC has a reason not to use the desintion node // -------------------------------------------------------------------------- CEntity *cent = pEndNode->GetHint(); if (GetOuter()->IsUnusableNode(endID, ((cent) ? cent->BaseEntity() : NULL))) { return false; } // -------------------------------------------------------------------------- // If a jump make sure the jump is within NPC's legal parameters for jumping // -------------------------------------------------------------------------- if (moveType == bits_CAP_MOVE_JUMP) { if (!GetOuter()->IsJumpLegal(pStartNode->GetPosition(GetHullType()), pEndNode->GetPosition(GetHullType()), pEndNode->GetPosition(GetHullType()))) { return false; } } // -------------------------------------------------------------------------- // If an NPC suggested that this link is stale and I haven't checked it yet // I should make sure the link is still valid before proceeding // -------------------------------------------------------------------------- if (pLink->m_LinkInfo & bits_LINK_STALE_SUGGESTED) { if (IsLinkStillStale(moveType, pLink)) { return false; } } return true; }
//----------------------------------------------------------------------------- // Purpose: Allow pre-frame adjustments on the player //----------------------------------------------------------------------------- void CHL1_Player::PreThink(void) { CheckExplosionEffects(); if ( player_showpredictedposition.GetBool() ) { Vector predPos; UTIL_PredictedPosition( this, player_showpredictedposition_timestep.GetFloat(), &predPos ); NDebugOverlay::Box( predPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 0, 255, 0, 0, 0.01f ); NDebugOverlay::Line( GetAbsOrigin(), predPos, 0, 255, 0, 0, 0.01f ); } int buttonsChanged; buttonsChanged = m_afButtonPressed | m_afButtonReleased; g_pGameRules->PlayerThink( this ); if ( g_fGameOver || IsPlayerLockedInPlace() ) return; // intermission or finale ItemPreFrame( ); WaterMove(); if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; else m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; // checks if new client data (for HUD and view control) needs to be sent to the client UpdateClientData(); CheckTimeBasedDamage(); CheckSuitUpdate(); if (m_lifeState >= LIFE_DYING) { PlayerDeathThink(); return; } // So the correct flags get sent to client asap. // if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) AddFlag( FL_ONTRAIN ); else RemoveFlag( FL_ONTRAIN ); // Train speed control if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) { CBaseEntity *pTrain = GetGroundEntity(); float vel; if ( pTrain ) { if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) pTrain = NULL; } if ( !pTrain ) { if ( GetActiveWeapon() && (GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) { m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; if ( m_nButtons & IN_FORWARD ) { m_iTrain |= TRAIN_FAST; } else if ( m_nButtons & IN_BACK ) { m_iTrain |= TRAIN_BACK; } else { m_iTrain |= TRAIN_NEUTRAL; } return; } else { trace_t trainTrace; // Maybe this is on the other side of a level transition UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) pTrain = trainTrace.m_pEnt; if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(GetContainingEntity(pev)) ) { // Warning( "In train mode with no train!\n" ); m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; m_iTrain = TRAIN_NEW|TRAIN_OFF; return; } } } else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) { // Turn off the train if you jump, strafe, or the train controls go dead m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; m_iTrain = TRAIN_NEW|TRAIN_OFF; return; } SetAbsVelocity( vec3_origin ); vel = 0; if ( m_afButtonPressed & IN_FORWARD ) { vel = 1; pTrain->Use( this, this, USE_SET, (float)vel ); } else if ( m_afButtonPressed & IN_BACK ) { vel = -1; pTrain->Use( this, this, USE_SET, (float)vel ); } if (vel) { m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; } } else if (m_iTrain & TRAIN_ACTIVE) { m_iTrain = TRAIN_NEW; // turn off train } // THIS CODE DOESN'T SEEM TO DO ANYTHING!!! // WHY IS IT STILL HERE? (sjb) if (m_nButtons & IN_JUMP) { // If on a ladder, jump off the ladder // else Jump Jump(); } // If trying to duck, already ducked, or in the process of ducking if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) Duck(); // // If we're not on the ground, we're falling. Update our falling velocity. // if ( !( GetFlags() & FL_ONGROUND ) ) { m_Local.m_flFallVelocity = -GetAbsVelocity().z; } if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) { SetAbsVelocity( vec3_origin ); } // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? //Find targets for NPC to shoot if they decide to miss us FindMissTargets(); }
void ShipDesign::BuildStatCaches() { const HullType* hull = GetHullType(m_hull); if (!hull) { ErrorLogger() << "ShipDesign::BuildStatCaches couldn't get hull with name " << m_hull; return; } m_producible = hull->Producible(); m_detection = hull->Detection(); m_colony_capacity = hull->ColonyCapacity(); m_troop_capacity = hull->TroopCapacity(); m_stealth = hull->Stealth(); m_fuel = hull->Fuel(); m_shields = hull->Shields(); m_structure = hull->Structure(); m_speed = hull->Speed(); for (std::vector<std::string>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it) { if (it->empty()) continue; const PartType* part = GetPartType(*it); if (!part) { ErrorLogger() << "ShipDesign::BuildStatCaches couldn't get part with name " << *it; continue; } if (!part->Producible()) m_producible = false; switch (part->Class()) { case PC_SHORT_RANGE: case PC_MISSILES: case PC_FIGHTERS: case PC_POINT_DEFENSE: { m_is_armed = true; break; } case PC_COLONY: m_colony_capacity += part->Capacity(); break; case PC_TROOPS: m_troop_capacity += part->Capacity(); break; case PC_STEALTH: m_stealth += part->Capacity(); break; case PC_SPEED: m_speed += part->Capacity(); break; case PC_SHIELD: m_shields += part->Capacity(); break; case PC_FUEL: m_fuel += part->Capacity(); break; case PC_ARMOUR: m_structure += part->Capacity(); break; case PC_DETECTION: m_detection += part->Capacity(); break; case PC_BOMBARD: m_can_bombard = true; break; case PC_RESEARCH: m_research_generation += part->Capacity(); break; case PC_INDUSTRY: m_industry_generation += part->Capacity(); break; case PC_TRADE: m_trade_generation += part->Capacity(); break; case PC_PRODICTION_LOCATION: m_is_production_location = true; break; default: break; } } }
bool CAI_Pathfinder::Triangulate( Navigation_t navType, const Vector &vecStart, const Vector &vecEndIn, float flDistToBlocker, const CEntity *pTargetEnt, Vector *pApex ) { if ( GetOuter()->IsFlaggedEfficient() ) return false; Assert( pApex ); Vector vecForward, vecUp, vecPerpendicular; VectorSubtract( vecEndIn, vecStart, vecForward ); float flTotalDist = VectorNormalize( vecForward ); Vector vecEnd; // If we're walking, then don't try to triangulate over large distances if ( navType != NAV_FLY && flTotalDist > MAX_TRIAGULATION_DIST) { vecEnd = vecForward * MAX_TRIAGULATION_DIST; flTotalDist = MAX_TRIAGULATION_DIST; if ( !GetOuter()->GetMoveProbe()->MoveLimit(navType, vecEnd, vecEndIn, MASK_NPCSOLID, pTargetEnt) ) { return false; } } else vecEnd = vecEndIn; // Compute a direction vector perpendicular to the desired motion direction if ( 1.0f - fabs(vecForward.z) > 1e-3 ) { vecUp.Init( 0, 0, 1 ); CrossProduct( vecForward, vecUp, vecPerpendicular ); // Orthogonal to facing } else { vecUp.Init( 0, 1, 0 ); vecPerpendicular.Init( 1, 0, 0 ); } // Grab the size of the navigation bounding box float sizeX = 0.5f * NAI_Hull::Length(GetHullType()); float sizeZ = 0.5f * NAI_Hull::Height(GetHullType()); // start checking right about where the object is, picking two equidistant // starting points, one on the left, one on the right. As we progress // through the loop, we'll push these away from the obstacle, hoping to // find a way around on either side. m_vecSize.x is added to the ApexDist // in order to help select an apex point that insures that the NPC is // sufficiently past the obstacle before trying to turn back onto its original course. float flApexDist = flDistToBlocker + sizeX; if (flApexDist > flTotalDist) { flApexDist = flTotalDist; } // Compute triangulation apex points (NAV_FLY attempts vertical triangulation too) Vector vecDelta[2]; Vector vecApex[4]; float pApexDist[4]; Vector vecCenter; int nNumToTest = 2; VectorMultiply( vecPerpendicular, sizeX, vecDelta[0] ); VectorMA( vecStart, flApexDist, vecForward, vecCenter ); VectorSubtract( vecCenter, vecDelta[0], vecApex[0] ); VectorAdd( vecCenter, vecDelta[0], vecApex[1] ); vecDelta[0] *= 2.0f; pApexDist[0] = pApexDist[1] = flApexDist; if (navType == NAV_FLY) { VectorMultiply( vecUp, 3.0f * sizeZ, vecDelta[1] ); VectorSubtract( vecCenter, vecDelta[1], vecApex[2] ); VectorAdd( vecCenter, vecDelta[1], vecApex[3] ); pApexDist[2] = pApexDist[3] = flApexDist; nNumToTest = 4; } AIMoveTrace_t moveTrace; for (int i = 0; i < 2; ++i ) { // NOTE: Do reverse order so fliers try to move over the top first for (int j = nNumToTest; --j >= 0; ) { if (TestTriangulationRoute(navType, vecStart, vecApex[j], vecEnd, pTargetEnt, &moveTrace)) { *pApex = vecApex[j]; return true; } // Here, the starting half of the triangle was blocked. Lets // pull back the apex toward the start... if (IsMoveBlocked(moveTrace)) { Vector vecStartToObstruction; VectorSubtract( moveTrace.vEndPosition, vecStart, vecStartToObstruction ); float flDistToObstruction = DotProduct( vecStartToObstruction, vecForward ); float flNewApexDist = pApexDist[j]; if (pApexDist[j] > flDistToObstruction) flNewApexDist = flDistToObstruction; VectorMA( vecApex[j], flNewApexDist - pApexDist[j], vecForward, vecApex[j] ); pApexDist[j] = flNewApexDist; } // NOTE: This has to occur *after* the code above because // the above code uses vecApex for some distance computations if (j & 0x1) vecApex[j] += vecDelta[j >> 1]; else vecApex[j] -= vecDelta[j >> 1]; } }
int CAI_TacticalServices::FindCoverNode(const Vector &vNearPos, const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinDist, float flMaxDist ) { if ( !CAI_NetworkManager::NetworksLoaded() ) return NO_NODE; AI_PROFILE_SCOPE( CAI_TacticalServices_FindCoverNode ); MARK_TASK_EXPENSIVE(); DebugFindCover( NO_NODE, GetOuter()->EyePosition(), vThreatEyePos, 0, 255, 255 ); int iMyNode = GetPathfinder()->NearestNodeToPoint( vNearPos ); if ( iMyNode == NO_NODE ) { Vector pos = GetOuter()->GetAbsOrigin(); DevWarning( 2, "FindCover() - %s has no nearest node! (Check near %f %f %f)\n", GetEntClassname(), pos.x, pos.y, pos.z); return NO_NODE; } if ( !flMaxDist ) { // user didn't supply a MaxDist, so work up a crazy one. flMaxDist = 784; } if ( flMinDist > 0.5 * flMaxDist) { flMinDist = 0.5 * flMaxDist; } // ------------------------------------------------------------------------------------ // We're going to search for a cover node by expanding to our current node's neighbors // and then their neighbors, until cover is found, or all nodes are beyond MaxDist // ------------------------------------------------------------------------------------ AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * GetNetwork()->NumNodes() ); CNodeList list( pBuffer, GetNetwork()->NumNodes() ); CVarBitVec wasVisited(GetNetwork()->NumNodes()); // Nodes visited // mark start as visited list.Insert( AI_NearNode_t(iMyNode, 0) ); wasVisited.Set( iMyNode ); float flMinDistSqr = flMinDist*flMinDist; float flMaxDistSqr = flMaxDist*flMaxDist; static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time; // Search until the list is empty while( list.Count() ) { // Get the node that is closest in the number of steps and remove from the list int nodeIndex = list.ElementAtHead().nodeIndex; list.RemoveAtHead(); CAI_Node *pNode = GetNetwork()->GetNode(nodeIndex); Vector nodeOrigin = pNode->GetPosition(GetHullType()); float dist = (vNearPos - nodeOrigin).LengthSqr(); if (dist >= flMinDistSqr && dist < flMaxDistSqr) { Activity nCoverActivity = GetOuter()->GetCoverActivity( pNode->GetHint() ); Vector vEyePos = nodeOrigin + GetOuter()->EyeOffset(nCoverActivity); if ( GetOuter()->IsValidCover( nodeOrigin, pNode->GetHint() ) ) { // Check if this location will block the threat's line of sight to me if (GetOuter()->IsCoverPosition(vThreatEyePos, vEyePos)) { // -------------------------------------------------------- // Don't let anyone else use this node for a while // -------------------------------------------------------- pNode->Lock( 1.0 ); if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW ) ) { if ( GetOuter()->GetHintNode() ) { GetOuter()->GetHintNode()->Unlock(GetOuter()->GetHintDelay(GetOuter()->GetHintNode()->HintType())); GetOuter()->SetHintNode( NULL ); } GetOuter()->SetHintNode( pNode->GetHint() ); } // The next NPC who searches should use a slight different pattern nSearchRandomizer = nodeIndex; DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 0, 255, 0 ); return nodeIndex; } else { DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 255, 0, 0 ); } } else { DebugFindCover( pNode->GetId(), vEyePos, vThreatEyePos, 0, 0, 255 ); } } // Add its children to the search list // Go through each link // UNDONE: Pass in a cost function to measure each link? for ( int link = 0; link < GetNetwork()->GetNode(nodeIndex)->NumLinks(); link++ ) { int index = (link + nSearchRandomizer) % GetNetwork()->GetNode(nodeIndex)->NumLinks(); CAI_Link *nodeLink = GetNetwork()->GetNode(nodeIndex)->GetLinkByIndex(index); if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) ) continue; int newID = nodeLink->DestNodeID(nodeIndex); // If not already on the closed list, add to it and set its distance if (!wasVisited.IsBitSet(newID)) { // Don't accept climb nodes or nodes that aren't ready to use yet if ( GetNetwork()->GetNode(newID)->GetType() != NODE_CLIMB && !GetNetwork()->GetNode(newID)->IsLocked() ) { // UNDONE: Shouldn't we really accumulate the distance by path rather than // absolute distance. After all, we are performing essentially an A* here. nodeOrigin = GetNetwork()->GetNode(newID)->GetPosition(GetHullType()); dist = (vNearPos - nodeOrigin).LengthSqr(); // use distance to threat as a heuristic to keep AIs from running toward // the threat in order to take cover from it. float threatDist = (vThreatPos - nodeOrigin).LengthSqr(); // Now check this node is not too close towards the threat if ( dist < threatDist * 1.5 ) { list.Insert( AI_NearNode_t(newID, dist) ); } } // mark visited wasVisited.Set(newID); } } } // We failed. Not cover node was found // Clear hint node used to set ducking GetOuter()->ClearHintNode(); return NO_NODE; }
int CAI_TacticalServices::FindLosNode(const Vector &vThreatPos, const Vector &vThreatEyePos, float flMinThreatDist, float flMaxThreatDist, float flBlockTime, FlankType_t eFlankType, const Vector &vecFlankRefPos, float flFlankParam ) { if ( !CAI_NetworkManager::NetworksLoaded() ) return NO_NODE; AI_PROFILE_SCOPE( CAI_TacticalServices_FindLosNode ); MARK_TASK_EXPENSIVE(); int iMyNode = GetPathfinder()->NearestNodeToNPC(); if ( iMyNode == NO_NODE ) { Vector pos = GetOuter()->GetAbsOrigin(); DevWarning( 2, "FindCover() - %s has no nearest node! (Check near %f %f %f)\n", GetEntClassname(), pos.x, pos.y, pos.z); return NO_NODE; } // ------------------------------------------------------------------------------------ // We're going to search for a shoot node by expanding to our current node's neighbors // and then their neighbors, until a shooting position is found, or all nodes are beyond MaxDist // ------------------------------------------------------------------------------------ AI_NearNode_t *pBuffer = (AI_NearNode_t *)stackalloc( sizeof(AI_NearNode_t) * GetNetwork()->NumNodes() ); CNodeList list( pBuffer, GetNetwork()->NumNodes() ); CVarBitVec wasVisited(GetNetwork()->NumNodes()); // Nodes visited // mark start as visited wasVisited.Set( iMyNode ); list.Insert( AI_NearNode_t(iMyNode, 0) ); static int nSearchRandomizer = 0; // tries to ensure the links are searched in a different order each time; while ( list.Count() ) { int nodeIndex = list.ElementAtHead().nodeIndex; // remove this item from the list list.RemoveAtHead(); const Vector &nodeOrigin = GetNetwork()->GetNode(nodeIndex)->GetPosition(GetHullType()); // HACKHACK: Can't we rework this loop and get rid of this? // skip the starting node, or we probably wouldn't have called this function. if ( nodeIndex != iMyNode ) { bool skip = false; // See if the node satisfies the flanking criteria. switch ( eFlankType ) { case FLANKTYPE_NONE: break; case FLANKTYPE_RADIUS: { Vector vecDist = nodeOrigin - vecFlankRefPos; if ( vecDist.Length() < flFlankParam ) { skip = true; } break; } case FLANKTYPE_ARC: { Vector vecEnemyToRef = vecFlankRefPos - vThreatPos; VectorNormalize( vecEnemyToRef ); Vector vecEnemyToNode = nodeOrigin - vThreatPos; VectorNormalize( vecEnemyToNode ); float flDot = DotProduct( vecEnemyToRef, vecEnemyToNode ); if ( RAD2DEG( acos( flDot ) ) < flFlankParam ) { skip = true; } break; } } // Don't accept climb nodes, and assume my nearest node isn't valid because // we decided to make this check in the first place. Keep moving if ( !skip && !GetNetwork()->GetNode(nodeIndex)->IsLocked() && GetNetwork()->GetNode(nodeIndex)->GetType() != NODE_CLIMB ) { // Now check its distance and only accept if in range float flThreatDist = ( nodeOrigin - vThreatPos ).Length(); if ( flThreatDist < flMaxThreatDist && flThreatDist > flMinThreatDist ) { CAI_Node *pNode = GetNetwork()->GetNode(nodeIndex); if ( GetOuter()->IsValidShootPosition( nodeOrigin, pNode, pNode->GetHint() ) ) { if (GetOuter()->TestShootPosition(nodeOrigin,vThreatEyePos)) { // Note when this node was used, so we don't try // to use it again right away. GetNetwork()->GetNode(nodeIndex)->Lock( flBlockTime ); #if 0 if ( GetOuter()->GetHintNode() ) { GetOuter()->GetHintNode()->Unlock(GetOuter()->GetHintDelay(GetOuter()->GetHintNode()->HintType())); GetOuter()->SetHintNode( NULL ); } // This used to not be set, why? (kenb) // @Note (toml 05-19-04): I think because stomping the hint can lead to // unintended side effects. The hint node is primarily a high level // tool, and certain NPCs break if it gets slammed here. If we need // this, we should propagate it out and let the schedule selector // or task decide to set the hint node GetOuter()->SetHintNode( GetNetwork()->GetNode(nodeIndex)->GetHint() ); #endif if ( ShouldDebugLos( nodeIndex ) ) { NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:los!", nodeIndex), false, 1 ); } // The next NPC who searches should use a slight different pattern nSearchRandomizer = nodeIndex; return nodeIndex; } else { if ( ShouldDebugLos( nodeIndex ) ) { NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!shoot", nodeIndex), false, 1 ); } } } else { if ( ShouldDebugLos( nodeIndex ) ) { NDebugOverlay::Text( nodeOrigin, CFmtStr( "%d:!valid", nodeIndex), false, 1 ); } } } else { if ( ShouldDebugLos( nodeIndex ) ) { CFmtStr msg( "%d:%s", nodeIndex, ( flThreatDist < flMaxThreatDist ) ? "too close" : "too far" ); NDebugOverlay::Text( nodeOrigin, msg, false, 1 ); } } } } // Go through each link and add connected nodes to the list for (int link=0; link < GetNetwork()->GetNode(nodeIndex)->NumLinks();link++) { int index = (link + nSearchRandomizer) % GetNetwork()->GetNode(nodeIndex)->NumLinks(); CAI_Link *nodeLink = GetNetwork()->GetNode(nodeIndex)->GetLinkByIndex(index); if ( !m_pPathfinder->IsLinkUsable( nodeLink, iMyNode ) ) continue; int newID = nodeLink->DestNodeID(nodeIndex); // If not already visited, add to the list if (!wasVisited.IsBitSet(newID)) { float dist = (GetLocalOrigin() - GetNetwork()->GetNode(newID)->GetPosition(GetHullType())).LengthSqr(); list.Insert( AI_NearNode_t(newID, dist) ); wasVisited.Set( newID ); } } } // We failed. No range attack node node was found return NO_NODE; }
Vector CAI_TacticalServices::GetNodePos( int node ) { return GetNetwork()->GetNode((int)node)->GetPosition(GetHullType()); }
void ShipDesign::BuildStatCaches() { const HullType* hull = GetHullType(m_hull); if (!hull) { Logger().errorStream() << "ShipDesign::BuildStatCaches couldn't get hull with name " << m_hull; return; } m_producible = hull->Producible(); m_detection = hull->Detection(); m_colony_capacity = hull->ColonyCapacity(); m_troop_capacity = hull->TroopCapacity(); m_stealth = hull->Stealth(); m_fuel = hull->Fuel(); m_shields = hull->Shields(); m_structure = hull->Structure(); m_battle_speed = hull->BattleSpeed(); m_starlane_speed = hull->StarlaneSpeed(); for (std::vector<std::string>::const_iterator it = m_parts.begin(); it != m_parts.end(); ++it) { if (it->empty()) continue; const PartType* part = GetPartType(*it); if (!part) { Logger().errorStream() << "ShipDesign::BuildStatCaches couldn't get part with name " << *it; continue; } if (!part->Producible()) m_producible = false; switch (part->Class()) { case PC_SHORT_RANGE: { const DirectFireStats& stats = boost::get<DirectFireStats>(part->Stats()); m_SR_weapons.insert(std::make_pair(stats.m_range, part)); m_is_armed = true; m_min_SR_range = std::min(m_min_SR_range, stats.m_range); m_max_SR_range = std::max(m_max_SR_range, stats.m_range); m_min_weapon_range = std::min(m_min_weapon_range, stats.m_range); m_max_weapon_range = std::max(m_max_weapon_range, stats.m_range); m_min_non_PD_weapon_range = std::min(m_min_non_PD_weapon_range, stats.m_range); m_max_non_PD_weapon_range = std::max(m_max_non_PD_weapon_range, stats.m_range); break; } case PC_MISSILES: { const LRStats& stats = boost::get<LRStats>(part->Stats()); m_LR_weapons.insert(std::make_pair(stats.m_range, part)); m_is_armed = true; m_min_LR_range = std::min(m_min_LR_range, stats.m_range); m_max_LR_range = std::max(m_max_LR_range, stats.m_range); m_min_weapon_range = std::min(m_min_weapon_range, stats.m_range); m_max_weapon_range = std::max(m_max_weapon_range, stats.m_range); m_min_non_PD_weapon_range = std::min(m_min_non_PD_weapon_range, stats.m_range); m_max_non_PD_weapon_range = std::max(m_max_non_PD_weapon_range, stats.m_range); break; } case PC_FIGHTERS: m_F_weapons.push_back(part); m_is_armed = true; break; case PC_POINT_DEFENSE: { const DirectFireStats& stats = boost::get<DirectFireStats>(part->Stats()); m_PD_weapons.insert(std::make_pair(stats.m_range, part)); m_is_armed = true; m_min_PD_range = std::min(m_min_PD_range, stats.m_range); m_max_PD_range = std::max(m_max_PD_range, stats.m_range); m_min_weapon_range = std::min(m_min_weapon_range, stats.m_range); m_max_weapon_range = std::max(m_max_weapon_range, stats.m_range); break; } case PC_COLONY: m_colony_capacity += boost::get<float>(part->Stats()); break; case PC_TROOPS: m_troop_capacity += boost::get<float>(part->Stats()); break; case PC_STEALTH: m_stealth += boost::get<float>(part->Stats()); break; case PC_BATTLE_SPEED: m_battle_speed += boost::get<float>(part->Stats()); break; case PC_STARLANE_SPEED: m_starlane_speed += boost::get<float>(part->Stats()); break; case PC_SHIELD: m_shields += boost::get<float>(part->Stats()); break; case PC_FUEL: m_fuel += boost::get<float>(part->Stats()); break; case PC_ARMOUR: m_structure += boost::get<float>(part->Stats()); break; case PC_DETECTION: m_detection += boost::get<float>(part->Stats()); break; default: break; } } if (m_SR_weapons.empty()) m_min_SR_range = 0.0; if (m_LR_weapons.empty()) m_min_LR_range = 0.0; if (m_PD_weapons.empty()) m_min_PD_range = 0.0; if (!m_min_SR_range && !m_min_LR_range && !m_min_PD_range) m_min_weapon_range = 0.0; if (!m_min_LR_range && !m_min_PD_range) m_min_non_PD_weapon_range = 0.0; }
AI_Waypoint_t *CAI_Pathfinder::FindBestPath(int startID, int endID) { if ( !GetNetwork()->NumNodes() ) return NULL; int nNodes = GetNetwork()->NumNodes(); CAI_Node **pAInode = GetNetwork()->AccessNodes(); CVarBitVec openBS(nNodes); CVarBitVec closeBS(nNodes); // ------------- INITIALIZE ------------------------ float* nodeG = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeH = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeF = (float *)stackalloc( nNodes * sizeof(float) ); int* nodeP = (int *)stackalloc( nNodes * sizeof(int) ); // Node parent for (int node=0;node<nNodes;node++) { nodeG[node] = FLT_MAX; nodeP[node] = -1; } nodeG[startID] = 0; nodeH[startID] = 0.1*(pAInode[startID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); // Don't want to over estimate nodeF[startID] = nodeG[startID] + nodeH[startID]; openBS.Set(startID); closeBS.Set( startID ); // --------------- FIND BEST PATH ------------------ while (!openBS.IsAllClear()) { int smallestID = CAI_Network::FindBSSmallest(&openBS,nodeF,nNodes); openBS.Clear(smallestID); CAI_Node *pSmallestNode = pAInode[smallestID]; if (GetOuter()->IsUnusableNode(smallestID, pSmallestNode->m_pHint)) continue; if (smallestID == endID) { AI_Waypoint_t* route = MakeRouteFromParents(&nodeP[0], endID); return route; } // Check this if the node is immediately in the path after the startNode // that it isn't blocked for (int link=0; link < pSmallestNode->NumLinks();link++) { CAI_Link *nodeLink = pSmallestNode->GetLinkByIndex(link); if (!IsLinkUsable(nodeLink,smallestID)) continue; // FIXME: the cost function should take into account Node costs (danger, flanking, etc). int moveType = nodeLink->m_iAcceptedMoveTypes[GetHullType()] & CapabilitiesGet(); int testID = nodeLink->DestNodeID(smallestID); Vector r1 = pSmallestNode->GetPosition(GetHullType()); Vector r2 = pAInode[testID]->GetPosition(GetHullType()); float dist = GetOuter()->GetNavigator()->MovementCost( moveType, r1, r2 ); // MovementCost takes ref parameters!! if ( dist == FLT_MAX ) continue; float new_g = nodeG[smallestID] + dist; if ( !closeBS.IsBitSet(testID) || (new_g < nodeG[testID]) ) { nodeP[testID] = smallestID; nodeG[testID] = new_g; nodeH[testID] = (pAInode[testID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); nodeF[testID] = nodeG[testID] + nodeH[testID]; closeBS.Set( testID ); openBS.Set( testID ); } } } return NULL; }