123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- /**
- * [CSRD] Bot Taunt Randomizer
- *
- * Patches taunt command handling so bots may use taunt items.
- */
- #pragma semicolon 1
- #include <sourcemod>
- #include <tf2_stocks>
- #pragma newdecls required
- #include <dhooks>
- #include <sdktools>
- #include <stocksoup/tf/econ>
- #include <tf_econ_data>
- #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<Address>(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);
- }
|