virtual ActionResult<CTFBot> Update(CTFBot *actor, float dt) override { TRACE("[this: %08x] [actor: #%d]", (uintptr_t)this, ENTINDEX(actor)); if (this->m_hHint == nullptr) { return ActionResult<CTFBot>::Done("No hint entity"); } INextBot *nextbot = rtti_cast<INextBot *>(actor); if (nextbot->IsRangeGreaterThan(this->m_hHint->GetAbsOrigin(), 25.0f)) { TRACE_MSG("range_to_hint > 25: pathing\n"); if (this->m_ctRecomputePath.IsElapsed()) { TRACE_MSG("recomputing path\n"); this->m_ctRecomputePath.Start(RandomFloat(1.0f, 2.0f)); CTFBotPathCost cost_func(actor, FASTEST_ROUTE); this->m_PathFollower.Compute(nextbot, this->m_hHint->GetAbsOrigin(), cost_func, 0.0f, true); } this->m_PathFollower.Update(nextbot); if (!this->m_PathFollower.IsValid()) { return ActionResult<CTFBot>::Done("Path failed"); } return ActionResult<CTFBot>::Continue(); } TRACE_MSG("at hint: creating dispenser entity\n"); CBaseEntity *ent = CreateEntityByName("obj_dispenser"); if (ent == nullptr) { return ActionResult<CTFBot>::Done("Couldn't create entity"); } auto dispenser = rtti_cast<CObjectDispenser *>(ent); dispenser->SetName(this->m_hHint->GetEntityName()); dispenser->m_nDefaultUpgradeLevel = 2; dispenser->SetAbsOrigin(this->m_hHint->GetAbsOrigin()); dispenser->SetAbsAngles(this->m_hHint->GetAbsAngles()); dispenser->Spawn(); dispenser->StartPlacement(actor); suppress_speak = true; dispenser->StartBuilding(actor); suppress_speak = false; this->m_hHint->SetOwnerEntity(dispenser); actor->SpeakConceptIfAllowed(MP_CONCEPT_BUILDING_OBJECT, "objtype:dispenser"); return ActionResult<CTFBot>::Done("Built a dispenser"); }
//----------------------------------------------------------------------------- // Purpose: Start placing or building the currently selected object //----------------------------------------------------------------------------- void CTFWeaponBuilder::PrimaryAttack( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return; if ( !CanAttack() ) return; // Necessary so that we get the latest building position for the test, otherwise // we are one frame behind. UpdatePlacementState(); // What state should we move to? switch( m_iBuildState ) { case BS_IDLE: { // Idle state starts selection SetCurrentState( BS_SELECTING ); } break; case BS_SELECTING: { // Do nothing, client handles selection return; } break; case BS_PLACING: { if ( m_hObjectBeingBuilt ) { int iFlags = m_hObjectBeingBuilt->GetObjectFlags(); // Tricky, because this can re-calc the object position and change whether its a valid // pos or not. Best not to do this only in debug, but we can be pretty sure that this // will give the same result as was calculated in UpdatePlacementState() above. Assert( IsValidPlacement() ); // If we're placing an attachment, like a sapper, play a placement animation on the owner if ( m_hObjectBeingBuilt->MustBeBuiltOnAttachmentPoint() ) { pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_GRENADE ); } StartBuilding(); // Should we switch away? if ( iFlags & OF_ALLOW_REPEAT_PLACEMENT ) { // Start placing another SetCurrentState( BS_PLACING ); StartPlacement(); } else { SwitchOwnersWeaponToLast(); } } } break; case BS_PLACING_INVALID: { if ( m_flNextDenySound < gpGlobals->curtime ) { CSingleUserRecipientFilter filter( pOwner ); EmitSound( filter, entindex(), "Player.DenyWeaponSelection" ); m_flNextDenySound = gpGlobals->curtime + 0.5; } } break; } m_flNextPrimaryAttack = gpGlobals->curtime + 0.2f; }
//----------------------------------------------------------------------------- // Purpose: Start placing or building the currently selected object //----------------------------------------------------------------------------- void CWeaponBuilder::PrimaryAttack( void ) { CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() ); if ( !pOwner ) return; // What state should we move to? switch( m_iBuildState ) { case BS_IDLE: { // Idle state starts selection SetCurrentState( BS_SELECTING ); } break; case BS_SELECTING: { // Do nothing, client handles selection return; } break; case BS_PLACING: { if ( m_hObjectBeingBuilt ) { // Give the object a chance to veto the "start building" command. Objects like barbed wire // may want to change their properties instead of actually building yet. if ( m_hObjectBeingBuilt->PreStartBuilding() ) { int iFlags = m_hObjectBeingBuilt->GetObjectFlags(); // Can't build if the game hasn't started if ( !tf_fastbuild.GetInt() && CurrentActIsAWaitingAct() ) { ClientPrint( pOwner, HUD_PRINTCENTER, "Can't build until the game's started.\n" ); return; } StartBuilding(); // Should we switch away? if ( iFlags & OF_ALLOW_REPEAT_PLACEMENT ) { // Start placing another SetCurrentState( BS_PLACING ); StartPlacement(); } else { pOwner->SwitchToNextBestWeapon( NULL ); } } } } break; case BS_PLACING_INVALID: { WeaponSound( SINGLE_NPC ); // If there is any associated error text when placing the object, display it if( m_hObjectBeingBuilt != NULL ) { if (m_hObjectBeingBuilt->MustBeBuiltInResourceZone()) { ClientPrint( pOwner, HUD_PRINTCENTER, "Only placeable in an empty resource zone.\n" ); } else if (m_hObjectBeingBuilt->MustBeBuiltInConstructionYard()) { ClientPrint( pOwner, HUD_PRINTCENTER, "Only placeable in a construction yard.\n" ); } } } break; } m_flNextPrimaryAttack = gpGlobals->curtime + 0.2f; }
virtual ActionResult<CTFBot> Update(CTFBot *actor, float dt) override { TRACE("[this: %08x] [actor: #%d]", (uintptr_t)this, ENTINDEX(actor)); if (this->m_hHint == nullptr) { return ActionResult<CTFBot>::Done("No hint entity"); } INextBot *nextbot = rtti_cast<INextBot *>(actor); float range_to_hint = nextbot->GetRangeTo(this->m_hHint->GetAbsOrigin()); if (range_to_hint < 200.0f) { TRACE_MSG("range_to_hint < 200: crouching/aiming\n"); actor->PressCrouchButton(); actor->GetBodyInterface()->AimHeadTowards(this->m_hHint->GetAbsOrigin(), IBody::LookAtPriorityType::OVERRIDE_ALL, 0.1f, nullptr, "Placing dispenser"); if (!this->m_bNearHint) { this->m_bNearHint = true; //TFGameRules()->VoiceCommand(actor, 1, 4); //actor->SpeakConceptIfAllowed(MP_CONCEPT_PLAYER_DISPENSERHERE); } } else { this->m_bNearHint = false; } if (range_to_hint > 25.0f) { TRACE_MSG("range_to_hint > 25: pathing\n"); if (this->m_ctRecomputePath.IsElapsed()) { TRACE_MSG("recomputing path\n"); this->m_ctRecomputePath.Start(RandomFloat(1.0f, 2.0f)); CTFBotPathCost cost_func(actor, SAFEST_ROUTE); this->m_PathFollower.Compute(nextbot, this->m_hHint->GetAbsOrigin(), cost_func, 0.0f, true); } this->m_PathFollower.Update(nextbot); if (!this->m_PathFollower.IsValid()) { return ActionResult<CTFBot>::Done("Path failed"); } return ActionResult<CTFBot>::Continue(); } TRACE_MSG("at hint: creating dispenser entity\n"); CBaseEntity *ent = CreateEntityByName("obj_dispenser"); if (ent == nullptr) { return ActionResult<CTFBot>::Done("Couldn't create entity"); } // TODO: increment hint dword 0x370 (not important for mvm) auto dispenser = rtti_cast<CObjectDispenser *>(ent); dispenser->SetName(this->m_hHint->GetEntityName()); dispenser->m_nDefaultUpgradeLevel = 2; dispenser->SetAbsOrigin(this->m_hHint->GetAbsOrigin()); dispenser->SetAbsAngles(this->m_hHint->GetAbsAngles()); dispenser->Spawn(); dispenser->StartPlacement(actor); suppress_speak = true; dispenser->StartBuilding(actor); suppress_speak = false; this->m_hHint->SetOwnerEntity(dispenser); actor->SpeakConceptIfAllowed(MP_CONCEPT_BUILDING_OBJECT, "objtype:dispenser"); return ActionResult<CTFBot>::Done("Built a dispenser"); }
//----------------------------------------------------------------------------- // Purpose: Start placing or building the currently selected object //----------------------------------------------------------------------------- void CTFWeaponBuilder::PrimaryAttack( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return; if ( !CanAttack() ) return; // Necessary so that we get the latest building position for the test, otherwise // we are one frame behind. UpdatePlacementState(); // What state should we move to? switch( m_iBuildState ) { case BS_IDLE: { // Idle state starts selection SetCurrentState( BS_SELECTING ); } break; case BS_SELECTING: { // Do nothing, client handles selection return; } break; case BS_PLACING: { if ( m_hObjectBeingBuilt ) { int iFlags = m_hObjectBeingBuilt->GetObjectFlags(); // Tricky, because this can re-calc the object position and change whether its a valid // pos or not. Best not to do this only in debug, but we can be pretty sure that this // will give the same result as was calculated in UpdatePlacementState() above. Assert( IsValidPlacement() ); // If we're placing an attachment, like a sapper, play a placement animation on the owner if ( m_hObjectBeingBuilt->MustBeBuiltOnAttachmentPoint() ) { pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_GRENADE ); } // Need to save this for later since StartBuilding will clear m_hObjectBeingBuilt. CBaseObject *pParentObject = m_hObjectBeingBuilt->GetParentObject(); StartBuilding(); // Attaching a sapper to a teleporter automatically saps another end. if ( GetType() == OBJ_ATTACHMENT_SAPPER ) { CObjectTeleporter *pTeleporter = dynamic_cast<CObjectTeleporter *>( pParentObject ); if ( pTeleporter ) { CObjectTeleporter *pMatch = pTeleporter->GetMatchingTeleporter(); // If the other end is not already sapped then place a sapper on it. if ( pMatch && !pMatch->IsPlacing() && !pMatch->HasSapper() ) { SetCurrentState( BS_PLACING ); StartPlacement(); if ( m_hObjectBeingBuilt.Get() ) { m_hObjectBeingBuilt->UpdateAttachmentPlacement( pMatch ); StartBuilding(); } } } } // Should we switch away? if ( iFlags & OF_ALLOW_REPEAT_PLACEMENT ) { // Start placing another SetCurrentState( BS_PLACING ); StartPlacement(); } else { SwitchOwnersWeaponToLast(); } } } break; case BS_PLACING_INVALID: { if ( m_flNextDenySound < gpGlobals->curtime ) { CSingleUserRecipientFilter filter( pOwner ); EmitSound( filter, entindex(), "Player.DenyWeaponSelection" ); m_flNextDenySound = gpGlobals->curtime + 0.5; } } break; } m_flNextPrimaryAttack = gpGlobals->curtime + 0.2f; }