123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- /**
- * [CSRD] Bot Taunt Randomizer
- *
- * Patches taunt command handling so bots may use taunt items.
- */
- #pragma semicolon 1
- #include <sourcemod>
- #include <tf2_stocks>
- #include <tf2idb>
- #pragma newdecls required
- #include <dhooks>
- #include <sdktools>
- #include <stocksoup/log_server>
- #include <stocksoup/tf/econ>
- #define PLUGIN_VERSION "1.0.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;
- ArrayList g_TauntEntries[TFClassType];
- 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;
-
- for (TFClassType i = TFClass_Unknown; i < TFClassType; i++) {
- g_TauntEntries[i] = new ArrayList();
- }
- }
- /**
- * Build lists of taunts sorted by class via TF2IDB.
- */
- public void OnAllPluginsLoaded() {
- ArrayList args = new ArrayList();
- DBResultSet results = view_as<DBResultSet>(TF2IDB_CustomQuery(
- "SELECT id, class FROM tf2idb_class WHERE slot = 'taunt'", args, 4));
-
- if (!results) {
- return;
- }
-
- while (results.FetchRow()) {
- char className[16];
-
- int id = results.FetchInt(0);
- results.FetchString(1, className, sizeof(className));
-
- g_TauntEntries[TF2_GetClass(className)].Push(id);
- }
- delete results;
- }
- /**
- * 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
- 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;
- }
-
- int item = DHookGetParam(hParams, 1);
- if (item) {
- 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 nTauntsAllClass = g_TauntEntries[0].Length;
- int nTauntsPlayerClass = g_TauntEntries[playerClass].Length;
-
- if (nTauntsAllClass + nTauntsPlayerClass == 0) {
- return DEFINDEX_UNDEFINED;
- }
-
- int index = GetRandomInt(0, nTauntsAllClass + nTauntsPlayerClass - 1);
-
- if (index < nTauntsAllClass) {
- return g_TauntEntries[0].Get(index);
- }
- return g_TauntEntries[playerClass].Get(index - nTauntsAllClass);
- }
- /**
- * Helper function for self-testing.
- */
- bool CanRandomTaunt(int client) {
- return IsFakeClient(client);
- }
|