diff --git a/configs/tf/mvm_upgrades.cfg b/configs/tf/mvm_upgrades.cfg index 685ee6b..f203653 100644 --- a/configs/tf/mvm_upgrades.cfg +++ b/configs/tf/mvm_upgrades.cfg @@ -20,6 +20,7 @@ TFMvMUpgrades * "priority" "1" * "slot" "0" -- weapon slot used by this upgrade, -1 for player upgrades, 9 for canteen power up bottle * "weapons" "5,7,333" -- comma-delimited list of item definition index this upgrade is restricted to + * "maxlevel" "0" -- limit that max upgrade level, 0 (default) to use the upgrade max cap * } * } * diff --git a/extension/AMBuilder b/extension/AMBuilder index 7399b9a..8322fea 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -74,6 +74,8 @@ sourceFiles = [ 'bot/tf2/tasks/medic/tf2bot_medic_main_task.cpp', 'bot/tf2/tasks/medic/tf2bot_medic_retreat_task.cpp', 'bot/tf2/tasks/scenario/tf2bot_map_ctf.cpp', + 'bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.cpp', + 'bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.cpp', 'mods/tf2/teamfortress2mod.cpp', 'mods/tf2/tf2lib.cpp', 'mods/tf2/tf2mod_gameevents.cpp', diff --git a/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.cpp b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.cpp new file mode 100644 index 0000000..2fd0d0d --- /dev/null +++ b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include "tf2bot_mvm_upgrade.h" +#include "tf2bot_mvm_idle.h" + +TaskResult CTF2BotMvMIdleTask::OnTaskStart(CTF2Bot* bot, AITask* pastTask) +{ + return Continue(); +} + +TaskResult CTF2BotMvMIdleTask::OnTaskUpdate(CTF2Bot* bot) +{ + if (entprops->GameRules_GetRoundState() == RoundState_RoundRunning) + { + // Wave has started! + // TO-DO: Switch to combat task + return Continue(); + } + + // Wave has not started yet + if (entprops->GameRules_GetRoundState() == RoundState_BetweenRounds) + { + // Should the bot buy upgrades? + if (bot->GetUpgradeManager().ShouldGoToAnUpgradeStation()) + { + // Buy upgrades + return PauseFor(new CTF2BotMvMUpgradeTask, "Going to use an upgrade station!"); + } + } + + // TO-DO: Move to defensive positions near the robot spawn + // TO-DO: Engineer + // TO-DO: Add ready up logic + + return Continue(); +} + +TaskEventResponseResult CTF2BotMvMIdleTask::OnMoveToFailure(CTF2Bot* bot, CPath* path, IEventListener::MovementFailureType reason) +{ + return TryContinue(); +} + +TaskEventResponseResult CTF2BotMvMIdleTask::OnMoveToSuccess(CTF2Bot* bot, CPath* path) +{ + return TryContinue(); +} diff --git a/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.h b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.h new file mode 100644 index 0000000..97546b5 --- /dev/null +++ b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_idle.h @@ -0,0 +1,25 @@ +#ifndef NAVBOT_TF2BOT_TASK_MVM_IDLE_H_ +#define NAVBOT_TF2BOT_TASK_MVM_IDLE_H_ +#pragma once + +#include + +class CTF2Bot; + +class CTF2BotMvMIdleTask : public AITask +{ +public: + TaskResult OnTaskStart(CTF2Bot* bot, AITask* pastTask) override; + TaskResult OnTaskUpdate(CTF2Bot* bot) override; + + TaskEventResponseResult OnMoveToFailure(CTF2Bot* bot, CPath* path, IEventListener::MovementFailureType reason) override; + TaskEventResponseResult OnMoveToSuccess(CTF2Bot* bot, CPath* path) override; + + const char* GetName() const override { return "MvMIdle"; } +private: + CMeshNavigator m_nav; + Vector m_goal; + CountdownTimer m_repathtimer; +}; + +#endif // !NAVBOT_TF2BOT_TASK_MVM_IDLE_H_ diff --git a/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.cpp b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.cpp new file mode 100644 index 0000000..a4dfc06 --- /dev/null +++ b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.cpp @@ -0,0 +1,157 @@ +#include + +#include +#include +#include +#include +#include "tf2bot_mvm_upgrade.h" + +#undef min // undef valve mathlib stuff that causes issues with C++ STD lib +#undef max +#undef clamp + +TaskResult CTF2BotMvMUpgradeTask::OnTaskStart(CTF2Bot* bot, AITask* pastTask) +{ + if (!SelectNearestUpgradeStation(bot)) + { + bot->GetUpgradeManager().OnDoneUpgrading(); // Mark as done + return Done("No upgrade stations available!"); + } + + SetGoalPosition(); + + CTF2BotPathCost cost(bot); + if (!m_nav.ComputePathToPosition(bot, m_goal, cost)) + { + return Done("Failed to find a path to the upgrade station"); + } + + m_repathtimer.Start(2.0f); + return Continue(); +} + +TaskResult CTF2BotMvMUpgradeTask::OnTaskUpdate(CTF2Bot* bot) +{ + auto& manager = bot->GetUpgradeManager(); + + if (manager.IsDoneForCurrentWave()) + { + return Done("Done upgrading!"); + } + + if (!bot->IsInUpgradeZone()) + { + if (m_repathtimer.IsElapsed()) + { + m_repathtimer.Start(2.0f); + + CTF2BotPathCost cost(bot); + if (!m_nav.ComputePathToPosition(bot, m_goal, cost)) + { + return Done("Failed to find a path to the upgrade station"); + } + } + + // Path to the upgrade station + m_nav.Update(bot); + } + else + { + manager.Update(); // bot is inside an upgrade zone, buy upgrades + } + + return Continue(); +} + +TaskEventResponseResult CTF2BotMvMUpgradeTask::OnMoveToFailure(CTF2Bot* bot, CPath* path, IEventListener::MovementFailureType reason) +{ + m_repathtimer.Start(2.0f); + + CTF2BotPathCost cost(bot); + if (!m_nav.ComputePathToPosition(bot, m_goal, cost)) + { + return TryDone(PRIORITY_HIGH, "Failed to find a path to the upgrade station"); + } + + return TryContinue(PRIORITY_LOW); +} + +TaskEventResponseResult CTF2BotMvMUpgradeTask::OnMoveToSuccess(CTF2Bot* bot, CPath* path) +{ + if (!bot->IsInUpgradeZone()) + { + return TryDone(PRIORITY_HIGH, "Reached goal but outside upgrade zone."); + } + + return TryContinue(PRIORITY_LOW); +} + +bool CTF2BotMvMUpgradeTask::SelectNearestUpgradeStation(CTF2Bot* me) +{ + std::vector upgradestations; + upgradestations.reserve(8); + + UtilHelpers::ForEachEntityOfClassname("func_upgradestation", [&upgradestations](int index, edict_t* edict, CBaseEntity* entity) { + if (edict == nullptr) + { + return true; // return early, but keep looping. Upgrade stations should never have a NULL edict + } + + tfentities::HTFBaseEntity basentity(edict); + + if (basentity.IsDisabled()) + { + return true; // return early, keep loop + } + + // upgrade station is not disabled, add to the list + upgradestations.push_back(edict); + + return true; // continue looping + }); + + if (upgradestations.empty()) + { + return false; + } + + Vector origin = me->GetAbsOrigin(); + float best = std::numeric_limits::max(); + edict_t* target = nullptr; + + for (auto upgradeentity : upgradestations) + { + Vector dest = UtilHelpers::getWorldSpaceCenter(upgradeentity); + float distance = (dest - origin).Length(); + + if (distance < best) + { + target = upgradeentity; + best = distance; + } + + } + + if (target == nullptr) + { + return false; + } + + UtilHelpers::SetHandleEntity(m_upgradestation, target); + return true; +} + +void CTF2BotMvMUpgradeTask::SetGoalPosition() +{ + Vector center = UtilHelpers::getWorldSpaceCenter(UtilHelpers::GetEdictFromCBaseHandle(m_upgradestation)); + auto area = TheNavMesh->GetNearestNavArea(center, 1024.0f, false, false); + + if (area == nullptr) + { + m_goal = center; + return; + } + + area->GetClosestPointOnArea(center, &m_goal); + m_goal.z = area->GetCenter().z; +} diff --git a/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.h b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.h new file mode 100644 index 0000000..65a1667 --- /dev/null +++ b/extension/bot/tf2/tasks/scenario/mvm/tf2bot_mvm_upgrade.h @@ -0,0 +1,30 @@ +#ifndef NAVBOT_TF2BOT_TASK_MVM_UPGRADE_H_ +#define NAVBOT_TF2BOT_TASK_MVM_UPGRADE_H_ +#pragma once + +#include +#include + +class CTF2Bot; + +class CTF2BotMvMUpgradeTask : public AITask +{ +public: + TaskResult OnTaskStart(CTF2Bot* bot, AITask* pastTask) override; + TaskResult OnTaskUpdate(CTF2Bot* bot) override; + + TaskEventResponseResult OnMoveToFailure(CTF2Bot* bot, CPath* path, IEventListener::MovementFailureType reason) override; + TaskEventResponseResult OnMoveToSuccess(CTF2Bot* bot, CPath* path) override; + + const char* GetName() const override { return "MvMUpgrade"; } +private: + CMeshNavigator m_nav; + Vector m_goal; + CountdownTimer m_repathtimer; + CBaseHandle m_upgradestation; + + bool SelectNearestUpgradeStation(CTF2Bot* me); + void SetGoalPosition(); +}; + +#endif // !NAVBOT_TF2BOT_TASK_MVM_UPGRADE_H_ diff --git a/extension/bot/tf2/tasks/tf2bot_roam.cpp b/extension/bot/tf2/tasks/tf2bot_roam.cpp index 2ddff6c..951ef81 100644 --- a/extension/bot/tf2/tasks/tf2bot_roam.cpp +++ b/extension/bot/tf2/tasks/tf2bot_roam.cpp @@ -125,4 +125,4 @@ void CTF2BotRoamTask::FindRandomGoalPosition(CTF2Bot* me) m_goal = randomArea->GetCenter(); me->DebugPrintToConsole(BOTDEBUG_TASKS, 0, 130, 0, "%s Random Nav Area Goal #%i <%3.2f, %3.2f, %3.2f>", me->GetDebugIdentifier(), randomArea->GetID(), m_goal.x, m_goal.y, m_goal.z); -} +} \ No newline at end of file diff --git a/extension/bot/tf2/tasks/tf2bot_scenario.cpp b/extension/bot/tf2/tasks/tf2bot_scenario.cpp index 19097c4..69c21a2 100644 --- a/extension/bot/tf2/tasks/tf2bot_scenario.cpp +++ b/extension/bot/tf2/tasks/tf2bot_scenario.cpp @@ -4,6 +4,7 @@ #include "bot/tf2/tf2bot.h" #include #include +#include "scenario/mvm/tf2bot_mvm_idle.h" #include "tf2bot_scenario.h" TaskResult CTF2BotScenarioTask::OnTaskUpdate(CTF2Bot* bot) @@ -24,6 +25,10 @@ TaskResult CTF2BotScenarioTask::OnTaskUpdate(CTF2Bot* bot) auto newtask = new CTF2BotCTFMonitorTask; return SwitchTo(newtask, "Starting CTF Behavior"); } + case TeamFortress2::GameModeType::GM_MVM: + { + return SwitchTo(new CTF2BotMvMIdleTask, "Starting MvM Behavior"); + } default: break; } diff --git a/extension/bot/tf2/tf2bot.cpp b/extension/bot/tf2/tf2bot.cpp index 49d71e1..b680102 100644 --- a/extension/bot/tf2/tf2bot.cpp +++ b/extension/bot/tf2/tf2bot.cpp @@ -488,6 +488,16 @@ bool CTF2Bot::IsInUpgradeZone() const return val; } +void CTF2Bot::ToggleTournamentReadyStatus(bool isready) const +{ + char command[64]; + ke::SafeSprintf(command, sizeof(command), "tournament_player_readystate %i", isready ? 1 : 0); + + // Use 'FakeClientCommand'. + // Alternative method is manually setting the array on gamerules + serverpluginhelpers->ClientCommand(GetEdict(), command); +} + const CTF2Bot::KnownSpy* CTF2Bot::GetKnownSpy(edict_t* spy) const { int index = gamehelpers->IndexOfEdict(spy); diff --git a/extension/bot/tf2/tf2bot.h b/extension/bot/tf2/tf2bot.h index 98e783a..962a5f5 100644 --- a/extension/bot/tf2/tf2bot.h +++ b/extension/bot/tf2/tf2bot.h @@ -105,6 +105,11 @@ class CTF2Bot : public CBaseBot void FindMyBuildings(); int GetCurrency() const; bool IsInUpgradeZone() const; + /** + * @brief Toggles the ready status in Tournament modes. Also used in Mann vs Machine. + * @param isready Is the bot ready? + */ + void ToggleTournamentReadyStatus(bool isready = true) const; /** * @brief Gets a known spy information about a spy or NULL if none diff --git a/extension/bot/tf2/tf2bot_upgrades.cpp b/extension/bot/tf2/tf2bot_upgrades.cpp index 04fe3e2..f0468e2 100644 --- a/extension/bot/tf2/tf2bot_upgrades.cpp +++ b/extension/bot/tf2/tf2bot_upgrades.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "tf2bot.h" #include "tf2bot_upgrades.h" @@ -55,7 +56,7 @@ void CTF2BotUpgradeManager::ExecuteBuy() { if (!m_me->IsInUpgradeZone()) { - m_me->DebugPrintToConsole(BOTDEBUG_ERRORS, 255, 0, 0, "Error: CTF2BotUpgradeManager::Update called outside an upgrade zone!\n"); + m_me->DebugPrintToConsole(BOTDEBUG_ERRORS, 255, 0, 0, "%s Error: CTF2BotUpgradeManager::Update called outside an upgrade zone!\n", m_me->GetDebugIdentifier()); return; } @@ -71,12 +72,21 @@ void CTF2BotUpgradeManager::ExecuteBuy() int currency = m_me->GetCurrency(); auto data = GetOrCreateUpgradeData(upgradeinfo->upgrade); + // Upgrade reached desired level limit, remove it from the to-buy list + if (upgradeinfo->HasLevelLimit() && data->times_bought >= upgradeinfo->GetLevelLimit()) + { + RemoveSingleUpgradeInfo(upgradeinfo); + return; + } + + // Upgrade reached the max in game level, remove it from the list if (data->IsMaxed()) { RemoveFinishedUpgrades(); // clean up return; } + // Can't afford it if (!upgradeinfo->upgrade->CanAfford(m_me->GetCurrency())) { return; @@ -84,7 +94,7 @@ void CTF2BotUpgradeManager::ExecuteBuy() if (m_me->IsDebugging(BOTDEBUG_TASKS)) { - m_me->DebugPrintToConsole(BOTDEBUG_TASKS, 255, 87, 51, "%s bought upgrade ID %i <%s, %i>", m_me->GetDebugIdentifier(), upgradeinfo->GetUpgradeIndex(), + m_me->DebugPrintToConsole(BOTDEBUG_TASKS, 255, 87, 51, "%s bought upgrade ID %i <%s, %i>\n", m_me->GetDebugIdentifier(), upgradeinfo->GetUpgradeIndex(), upgradeinfo->attribute.c_str(), upgradeinfo->quality); } @@ -116,35 +126,33 @@ bool CTF2BotUpgradeManager::CanAffordAnyUpgrade() const return false; } -#if SOURCE_ENGINE == SE_TF2 - void CTF2BotUpgradeManager::BeginBuyingUpgrades() { KeyValues* kvcmd = new KeyValues("MvM_UpgradesBegin"); - engine->ClientCommandKeyValues(m_me->GetEdict(), kvcmd); - // kvcmd->deleteThis(); - kvcmd = nullptr; + UtilHelpers::FakeClientCommandKeyValues(m_me->GetEdict(), kvcmd); + // kvcmd->deleteThis(); // KeyValues sent as commands are deleted by the engine + // kvcmd = nullptr; } void CTF2BotUpgradeManager::BuySingleUpgrade(const int upgradeID, const int itemslot, const int quantity) { - KeyValues* kvcmd = new KeyValues("MvM_UpgradesBegin"); + KeyValues* kvcmd = new KeyValues("MVM_Upgrade"); KeyValues* kUpg = kvcmd->FindKey("Upgrade", true); kUpg->SetInt("itemslot", itemslot); kUpg->SetInt("Upgrade", upgradeID); kUpg->SetInt("count", quantity); - engine->ClientCommandKeyValues(m_me->GetEdict(), kvcmd); + UtilHelpers::FakeClientCommandKeyValues(m_me->GetEdict(), kvcmd); // kvcmd->deleteThis(); - kvcmd = nullptr; + // kvcmd = nullptr; m_numupgrades += quantity; } void CTF2BotUpgradeManager::RefundUpgrades() { KeyValues* kvcmd = new KeyValues("MVM_Respec"); - engine->ClientCommandKeyValues(m_me->GetEdict(), kvcmd); + UtilHelpers::FakeClientCommandKeyValues(m_me->GetEdict(), kvcmd); // kvcmd->deleteThis(); - kvcmd = nullptr; + // kvcmd = nullptr; m_numupgrades = 0; } @@ -152,38 +160,12 @@ void CTF2BotUpgradeManager::EndBuyingUpgrades() { KeyValues* kvcmd = new KeyValues("MvM_UpgradesDone"); kvcmd->SetInt("num_upgrades", m_numupgrades); - engine->ClientCommandKeyValues(m_me->GetEdict(), kvcmd); + UtilHelpers::FakeClientCommandKeyValues(m_me->GetEdict(), kvcmd); // kvcmd->deleteThis(); - kvcmd = nullptr; + // kvcmd = nullptr; m_numupgrades = 0; } -#else - -// The orangebox engine branch doesn't have ClientCommandKeyValues -// This code will only run under the TF2 branch so we just do this to avoid compile errors. - -void CTF2BotUpgradeManager::BeginBuyingUpgrades() -{ -} - -void CTF2BotUpgradeManager::BuySingleUpgrade(const int upgradeID, const int itemslot, const int quantity) -{ -} - -void CTF2BotUpgradeManager::RefundUpgrades() -{ -} - -void CTF2BotUpgradeManager::EndBuyingUpgrades() -{ -} - -#endif // SOURCE_ENGINE == SE_TF2 - - - - void CTF2BotUpgradeManager::FetchUpgrades() { CTeamFortress2Mod::GetTF2Mod()->GetMvMUpgradeManager().CollectUpgradesToBuy(m_tobuylist, m_nextpriority, m_me->GetMyClassType()); @@ -235,6 +217,20 @@ void CTF2BotUpgradeManager::RemoveFinishedUpgrades() m_tobuylist.erase(start, m_tobuylist.end()); // remove upgrades } +void CTF2BotUpgradeManager::RemoveSingleUpgradeInfo(const TF2BotUpgradeInfo_t* info) +{ + auto start = std::remove_if(m_tobuylist.begin(), m_tobuylist.end(), [&info](const TF2BotUpgradeInfo_t* object) { + if (object == info) + { + return true; + } + + return false; + }); + + m_tobuylist.erase(start, m_tobuylist.end()); // remove upgrades +} + TF2BotUpgrade_t* CTF2BotUpgradeManager::GetOrCreateUpgradeData(const MvMUpgrade_t* upgrade) { for (auto& data : m_boughtlist) diff --git a/extension/bot/tf2/tf2bot_upgrades.h b/extension/bot/tf2/tf2bot_upgrades.h index 03ebde5..88c5809 100644 --- a/extension/bot/tf2/tf2bot_upgrades.h +++ b/extension/bot/tf2/tf2bot_upgrades.h @@ -96,6 +96,7 @@ class CTF2BotUpgradeManager void FetchUpgrades(); void FilterUpgrades(); void RemoveFinishedUpgrades(); + void RemoveSingleUpgradeInfo(const TF2BotUpgradeInfo_t* info); void AdvancePriority() { ++m_nextpriority; } void ExecuteBuy(); void ExecuteRefund(); diff --git a/extension/concommands_debug.cpp b/extension/concommands_debug.cpp index d4e891d..0c4bd54 100644 --- a/extension/concommands_debug.cpp +++ b/extension/concommands_debug.cpp @@ -353,4 +353,29 @@ CON_COMMAND_F(sm_nav_debug_area_collector, "Debugs nav area collector.", FCVAR_C Msg("Collected %i areas \n", collectedAreas.size()); } +#if SOURCE_ENGINE == SE_TF2 + +CON_COMMAND_F(sm_navbot_tf_debug_test_buy_upgrade, "Testing sending KeyValue commands", FCVAR_CHEAT) +{ + edict_t* client = gamehelpers->EdictOfIndex(1); // gets the listen server host + KeyValues* kvcmd1 = new KeyValues("MvM_UpgradesBegin"); + UtilHelpers::FakeClientCommandKeyValues(client, kvcmd1); + + KeyValues* kvcmd2 = new KeyValues("MVM_Upgrade"); + KeyValues* kUpg = kvcmd2->FindKey("Upgrade", true); + kUpg->SetInt("itemslot", -1); + kUpg->SetInt("Upgrade", 59); // move speed bonus + kUpg->SetInt("count", 1); + gameclients->ClientCommandKeyValues(client, kvcmd2); + + KeyValues* kvcmd3 = new KeyValues("MvM_UpgradesDone"); + kvcmd3->SetInt("num_upgrades", 1); + gameclients->ClientCommandKeyValues(client, kvcmd3); + + Msg("Upgrade command sent!\n"); +} + +#endif // SOURCE_ENGINE == SE_TF2 + + #endif // EXT_DEBUG \ No newline at end of file diff --git a/extension/mods/tf2/mvm_upgrade.h b/extension/mods/tf2/mvm_upgrade.h index dfce446..d90153d 100644 --- a/extension/mods/tf2/mvm_upgrade.h +++ b/extension/mods/tf2/mvm_upgrade.h @@ -98,6 +98,7 @@ struct TF2BotUpgradeInfo_t quality = 0; // default priority = 1; itemslot = 0; + maxlevel = 0; allowedweapons.reserve(4); upgrade = nullptr; } @@ -122,10 +123,14 @@ struct TF2BotUpgradeInfo_t return upgrade->index; } + const bool HasLevelLimit() const { return maxlevel > 0; } + const int GetLevelLimit() const { return maxlevel; } + std::string attribute; int quality; int priority; int itemslot; + int maxlevel; std::unordered_set allowedweapons; const MvMUpgrade_t* upgrade; }; diff --git a/extension/mods/tf2/mvm_upgrade_manager.cpp b/extension/mods/tf2/mvm_upgrade_manager.cpp index 7bf4851..130d2d0 100644 --- a/extension/mods/tf2/mvm_upgrade_manager.cpp +++ b/extension/mods/tf2/mvm_upgrade_manager.cpp @@ -64,6 +64,10 @@ SourceMod::SMCResult CMvMUpgradeManager::ReadSMC_KeyValue(const SourceMod::SMCSt { m_parserinfo.itemslot = atoi(value); } + else if (strncmp(key, "maxlevel", 8) == 0) + { + m_parserinfo.maxlevel = atoi(value); + } else if (strncmp(key, "weapons", 7) == 0) { std::string szValue(value); diff --git a/extension/mods/tf2/nav/tfnavarea.cpp b/extension/mods/tf2/nav/tfnavarea.cpp index 4b354ad..ed1ecc1 100644 --- a/extension/mods/tf2/nav/tfnavarea.cpp +++ b/extension/mods/tf2/nav/tfnavarea.cpp @@ -19,6 +19,7 @@ void CTFNavArea::Save(std::fstream& filestream, uint32_t version) filestream.write(reinterpret_cast(&m_tfattributes), sizeof(m_tfattributes)); filestream.write(reinterpret_cast(&m_tfpathattributes), sizeof(m_tfpathattributes)); + filestream.write(reinterpret_cast(&m_mvmattributes), sizeof(m_mvmattributes)); } NavErrorType CTFNavArea::Load(std::fstream& filestream, uint32_t version, uint32_t subVersion) @@ -34,6 +35,7 @@ NavErrorType CTFNavArea::Load(std::fstream& filestream, uint32_t version, uint32 { filestream.read(reinterpret_cast(&m_tfattributes), sizeof(m_tfattributes)); filestream.read(reinterpret_cast(&m_tfpathattributes), sizeof(m_tfpathattributes)); + filestream.read(reinterpret_cast(&m_mvmattributes), sizeof(m_mvmattributes)); if (!filestream.good()) { diff --git a/extension/mods/tf2/nav/tfnavarea.h b/extension/mods/tf2/nav/tfnavarea.h index 965de8f..cd80c09 100644 --- a/extension/mods/tf2/nav/tfnavarea.h +++ b/extension/mods/tf2/nav/tfnavarea.h @@ -12,13 +12,14 @@ class CTFNavArea : public CNavArea { m_tfpathattributes = 0; m_tfattributes = 0; + m_mvmattributes = 0; m_spawnroomteam = 0; } - virtual void Save(std::fstream& filestream, uint32_t version) override; - virtual NavErrorType Load(std::fstream& filestream, uint32_t version, uint32_t subVersion) override; - virtual void UpdateBlocked(bool force = false, int teamID = NAV_TEAM_ANY) override; - virtual bool IsBlocked(int teamID, bool ignoreNavBlockers = false) const override; + void Save(std::fstream& filestream, uint32_t version) override; + NavErrorType Load(std::fstream& filestream, uint32_t version, uint32_t subVersion) override; + void UpdateBlocked(bool force = false, int teamID = NAV_TEAM_ANY) override; + bool IsBlocked(int teamID, bool ignoreNavBlockers = false) const override; // Pathing flags enum TFNavPathAttributes @@ -74,6 +75,7 @@ class CTFNavArea : public CNavArea private: int m_tfpathattributes; int m_tfattributes; + int m_mvmattributes; // Attributes exclusive for the Mann vs Machine game modes int m_spawnroomteam; }; diff --git a/extension/util/EntityUtils.cpp b/extension/util/EntityUtils.cpp index 3813b44..34bca48 100644 --- a/extension/util/EntityUtils.cpp +++ b/extension/util/EntityUtils.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "EntityUtils.h" @@ -330,10 +331,25 @@ bool IsEntityWalkable(edict_t *entity, unsigned int flags) { } // if we hit a breakable object, assume its walkable because we will shoot it when we touch it - return (((FClassnameIs( entity, "func_breakable" ) || FClassnameIs( entity, "func_breakable_surf" )) - && *BaseEntity(entity).getHealth() > 0) && (flags & WALK_THRU_BREAKABLES)) - || FClassnameIs( entity, "func_playerinfected_clip" ) - || (sm_nav_solid_props.GetBool() && FClassnameIs( entity, "prop_*" )); + if (FClassnameIs(entity, "func_breakable") || FClassnameIs(entity, "func_breakable_surf")) + { + if ((flags & WALK_THRU_BREAKABLES) && UtilHelpers::GetEntityHealth(gamehelpers->IndexOfEdict(entity)) > 0) + { + return true; + } + } + + if (FClassnameIs(entity, "func_playerinfected_clip")) + { + return true; + } + + if (sm_nav_solid_props.GetBool() && FClassnameIs(entity, "prop_*")) + { + return true; + } + + return false; } edict_t* UTIL_GetListenServerEnt() { diff --git a/extension/util/helpers.cpp b/extension/util/helpers.cpp index 5fb3715..628c477 100644 --- a/extension/util/helpers.cpp +++ b/extension/util/helpers.cpp @@ -793,3 +793,12 @@ const char* UtilHelpers::GetPlayerDebugIdentifier(edict_t* player) return message; } +void UtilHelpers::FakeClientCommandKeyValues(edict_t* client, KeyValues* kv) +{ +#if SOURCE_ENGINE >= SE_EYE + gameclients->ClientCommandKeyValues(client, kv); +#else + throw std::runtime_error("This engine branch does not support FakeClientCommandKeyValues!"); +#endif +} + diff --git a/extension/util/helpers.h b/extension/util/helpers.h index db21cd7..c06f898 100644 --- a/extension/util/helpers.h +++ b/extension/util/helpers.h @@ -12,6 +12,7 @@ class Vector; class CStudioHdr; class SendTable; class CBaseHandle; +class KeyValues; namespace UtilHelpers { @@ -218,6 +219,38 @@ namespace UtilHelpers functor(i, edict, player); } } + + /** + * @brief Runs a function on each entity of the given classname found. + * @tparam T A class with bool operator() overload with 3 parameter (int index, edict_t* edict, CBaseEntity* entity), Edict may be null if the entity is not networked. Return false to exit early. + * @param classname Entity classname to search + * @param functor Function to run + */ + template + inline void ForEachEntityOfClassname(const char* classname ,T functor) + { + int entity = INVALID_EHANDLE_INDEX; + while ((entity = UtilHelpers::FindEntityByClassname(entity, classname)) != INVALID_EHANDLE_INDEX) + { + edict_t* edict = nullptr; + CBaseEntity* be = nullptr; + + if (!IndexToAThings(entity, &be, &edict)) + continue; + + if (functor(entity, edict, be) == false) + { + return; + } + } + } + + /** + * @brief Calls ClientCommandKeyValues for the given client. Throws an exception if the engine branch doesn't support keyvalue commands. + * @param client Client that will be sending the command. + * @param kv Keyvalue command. + */ + void FakeClientCommandKeyValues(edict_t* client, KeyValues* kv); } template