/** * [CSRD] Bot Taunt Randomizer * * Patches taunt command handling so bots may use taunt items. */ #pragma semicolon 1 #include #include #pragma newdecls required #include #include #include #include #define PLUGIN_VERSION "1.1.0" public Plugin myinfo = { name = "[CSRD] Bot Taunt Randomizer", author = "nosoop", description = "Allows bots to use taunt items.", version = PLUGIN_VERSION, url = "https://git.csrd.science/" } Handle g_SDKFindPartnerTauntInitiator; public void OnPluginStart() { Handle hGameConf = LoadGameConfigFile("csrd.bot_taunt_randomizer"); if (!hGameConf) { SetFailState("Failed to load gamedata (csrd.bot_taunt_randomizer)."); } StartPrepSDKCall(SDKCall_Player); PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CTFPlayer::FindPartnerTauntInitiator()"); PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer); g_SDKFindPartnerTauntInitiator = EndPrepSDKCall(); Handle dt_PlayTauntSceneFromItem = DHookCreateFromConf(hGameConf, "CTFPlayer::PlayTauntSceneFromItem()"); DHookEnableDetour(dt_PlayTauntSceneFromItem, false, OnPlayTauntSceneFromItem); Handle dt_HandleTauntCommand = DHookCreateFromConf(hGameConf, "CTFPlayer::HandleTauntCommand()"); DHookEnableDetour(dt_HandleTauntCommand, false, OnHandleTauntCommand); delete hGameConf; } /** * Makes bots use a taunt command with a non-zero slot if there is no valid taunt partner * nearby. Without this, bots will only use their default taunt (or participate in any nearby * partner taunts). */ public MRESReturn OnHandleTauntCommand(int client, Handle hParams) { int nTauntSlot = DHookGetParam(hParams, 1); if (nTauntSlot || !CanRandomTaunt(client)) { return MRES_Ignored; } if (TF2_FindPartnerTauntInitiator(client) > 0) { return MRES_Ignored; } // 90% chance of using a taunt item (10% chance default taunt) if (GetURandomFloat() > 0.90) { return MRES_Ignored; } // no initiator, consider using a taunt item DHookSetParam(hParams, 1, 8); return MRES_ChangedHandled; } /** * Allow bots to use a random taunt item. * * `CTFPlayer::PlayTauntSceneFromItem()` gets a null pointer passed in if the player attempts * to taunt with an empty taunt slot. */ public MRESReturn OnPlayTauntSceneFromItem(int client, Handle hReturn, Handle hParams) { if (!CanRandomTaunt(client)) { return MRES_Ignored; } Address pEconItemView = DHookGetParam(hParams, 1); if (pEconItemView) { return MRES_Ignored; } int taunt = TF2_GetRandomTauntForClass(TF2_GetPlayerClass(client)); if (taunt == DEFINDEX_UNDEFINED) { return MRES_Ignored; } int wearable = TF2_SpawnWearable(taunt); if (!IsValidEntity(wearable)) { return MRES_Ignored; } // fix taunt slot index so it correctly uses new taunt properties SetEntProp(client, Prop_Send, "m_nActiveTauntSlot", -1); SetEntProp(client, Prop_Send, "m_iTauntItemDefIndex", taunt); DHookSetParam(hParams, 1, TF2_GetEconItemView(wearable)); RemoveEntity(wearable); return MRES_ChangedHandled; } /** * Returns a pointer to an item entity's `CEconItemView`. */ Address TF2_GetEconItemView(int item) { if (!IsValidEntity(item) || !HasEntProp(item, Prop_Send, "m_Item")) { // we should probably throw an error here return Address_Null; } return GetEntityAddress(item) + view_as
(GetEntSendPropOffs(item, "m_Item", true)); } /** * Returns a partner taunt initiator entity if one is nearby, or -1 if not. */ int TF2_FindPartnerTauntInitiator(int client) { return SDKCall(g_SDKFindPartnerTauntInitiator, client); } /** * Returns a random class-specific or all-class taunt index. */ int TF2_GetRandomTauntForClass(TFClassType playerClass) { int returnValue = DEFINDEX_UNDEFINED; ArrayList tauntList = TF2Econ_GetItemList(FilterClassTaunts, playerClass); if (tauntList.Length) { returnValue = tauntList.Get(GetRandomInt(0, tauntList.Length - 1)); } delete tauntList; return returnValue; } public bool FilterClassTaunts(int defindex, TFClassType playerClass) { return TF2Econ_GetItemSlot(defindex, playerClass) == 11; } /** * Helper function for self-testing. */ bool CanRandomTaunt(int client) { return IsFakeClient(client); }