csrd_bot_taunt_randomizer.sp 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /**
  2. * [CSRD] Bot Taunt Randomizer
  3. *
  4. * Patches taunt command handling so bots may use taunt items.
  5. */
  6. #pragma semicolon 1
  7. #include <sourcemod>
  8. #include <tf2_stocks>
  9. #pragma newdecls required
  10. #include <dhooks>
  11. #include <sdktools>
  12. #include <stocksoup/tf/econ>
  13. #include <tf_econ_data>
  14. #define PLUGIN_VERSION "1.1.0"
  15. public Plugin myinfo = {
  16. name = "[CSRD] Bot Taunt Randomizer",
  17. author = "nosoop",
  18. description = "Allows bots to use taunt items.",
  19. version = PLUGIN_VERSION,
  20. url = "https://git.csrd.science/"
  21. }
  22. Handle g_SDKFindPartnerTauntInitiator;
  23. public void OnPluginStart() {
  24. Handle hGameConf = LoadGameConfigFile("csrd.bot_taunt_randomizer");
  25. if (!hGameConf) {
  26. SetFailState("Failed to load gamedata (csrd.bot_taunt_randomizer).");
  27. }
  28. StartPrepSDKCall(SDKCall_Player);
  29. PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature,
  30. "CTFPlayer::FindPartnerTauntInitiator()");
  31. PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer);
  32. g_SDKFindPartnerTauntInitiator = EndPrepSDKCall();
  33. Handle dt_PlayTauntSceneFromItem = DHookCreateFromConf(hGameConf,
  34. "CTFPlayer::PlayTauntSceneFromItem()");
  35. DHookEnableDetour(dt_PlayTauntSceneFromItem, false, OnPlayTauntSceneFromItem);
  36. Handle dt_HandleTauntCommand = DHookCreateFromConf(hGameConf,
  37. "CTFPlayer::HandleTauntCommand()");
  38. DHookEnableDetour(dt_HandleTauntCommand, false, OnHandleTauntCommand);
  39. delete hGameConf;
  40. }
  41. /**
  42. * Makes bots use a taunt command with a non-zero slot if there is no valid taunt partner
  43. * nearby. Without this, bots will only use their default taunt (or participate in any nearby
  44. * partner taunts).
  45. */
  46. public MRESReturn OnHandleTauntCommand(int client, Handle hParams) {
  47. int nTauntSlot = DHookGetParam(hParams, 1);
  48. if (nTauntSlot || !CanRandomTaunt(client)) {
  49. return MRES_Ignored;
  50. }
  51. if (TF2_FindPartnerTauntInitiator(client) > 0) {
  52. return MRES_Ignored;
  53. }
  54. // 90% chance of using a taunt item (10% chance default taunt)
  55. if (GetURandomFloat() > 0.90) {
  56. return MRES_Ignored;
  57. }
  58. // no initiator, consider using a taunt item
  59. DHookSetParam(hParams, 1, 8);
  60. return MRES_ChangedHandled;
  61. }
  62. /**
  63. * Allow bots to use a random taunt item.
  64. *
  65. * `CTFPlayer::PlayTauntSceneFromItem()` gets a null pointer passed in if the player attempts
  66. * to taunt with an empty taunt slot.
  67. */
  68. public MRESReturn OnPlayTauntSceneFromItem(int client, Handle hReturn, Handle hParams) {
  69. if (!CanRandomTaunt(client)) {
  70. return MRES_Ignored;
  71. }
  72. Address pEconItemView = DHookGetParam(hParams, 1);
  73. if (pEconItemView) {
  74. return MRES_Ignored;
  75. }
  76. int taunt = TF2_GetRandomTauntForClass(TF2_GetPlayerClass(client));
  77. if (taunt == DEFINDEX_UNDEFINED) {
  78. return MRES_Ignored;
  79. }
  80. int wearable = TF2_SpawnWearable(taunt);
  81. if (!IsValidEntity(wearable)) {
  82. return MRES_Ignored;
  83. }
  84. // fix taunt slot index so it correctly uses new taunt properties
  85. SetEntProp(client, Prop_Send, "m_nActiveTauntSlot", -1);
  86. SetEntProp(client, Prop_Send, "m_iTauntItemDefIndex", taunt);
  87. DHookSetParam(hParams, 1, TF2_GetEconItemView(wearable));
  88. RemoveEntity(wearable);
  89. return MRES_ChangedHandled;
  90. }
  91. /**
  92. * Returns a pointer to an item entity's `CEconItemView`.
  93. */
  94. Address TF2_GetEconItemView(int item) {
  95. if (!IsValidEntity(item) || !HasEntProp(item, Prop_Send, "m_Item")) {
  96. // we should probably throw an error here
  97. return Address_Null;
  98. }
  99. return GetEntityAddress(item) + view_as<Address>(GetEntSendPropOffs(item, "m_Item", true));
  100. }
  101. /**
  102. * Returns a partner taunt initiator entity if one is nearby, or -1 if not.
  103. */
  104. int TF2_FindPartnerTauntInitiator(int client) {
  105. return SDKCall(g_SDKFindPartnerTauntInitiator, client);
  106. }
  107. /**
  108. * Returns a random class-specific or all-class taunt index.
  109. */
  110. int TF2_GetRandomTauntForClass(TFClassType playerClass) {
  111. int returnValue = DEFINDEX_UNDEFINED;
  112. ArrayList tauntList = TF2Econ_GetItemList(FilterClassTaunts, playerClass);
  113. if (tauntList.Length) {
  114. returnValue = tauntList.Get(GetRandomInt(0, tauntList.Length - 1));
  115. }
  116. delete tauntList;
  117. return returnValue;
  118. }
  119. public bool FilterClassTaunts(int defindex, TFClassType playerClass) {
  120. return TF2Econ_GetItemSlot(defindex, playerClass) == 11;
  121. }
  122. /**
  123. * Helper function for self-testing.
  124. */
  125. bool CanRandomTaunt(int client) {
  126. return IsFakeClient(client);
  127. }