Advertisement
Guest User

Untitled

a guest
Oct 13th, 2015
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.71 KB | None | 0 0
  1. /**
  2. * Timer Record Replay
  3. * by Peace-Maker
  4. *
  5. * Record best times on map and have bots play them back.
  6. *
  7. * Changelog:
  8. * 18.02.2013 1.0: Initial release
  9. * 18.02.2013 1.0.1: Fixed bots not instantly switching to new record, if the best time is beaten.
  10. *
  11. */
  12.  
  13. // todo:
  14. // add a max limit of record time, so afk players won't eat up memory with nonsense record.
  15. // keep recording for x seconds after player finishes round
  16. // stop recording, if a player exceeds current best time
  17. // make sure the bhop plugin's !hide feature doesn't hide the selected bot!
  18.  
  19. // required settings
  20. // bot_quota_mode normal
  21. // mp_limitteams 0
  22. // mp_autoteambalance 0
  23. //
  24. // You need a .nav file for the map.
  25. // If the server fails to create one, you'll have to create a listen server with that map in your client, spawn, watch the floor and:
  26. // 1. sv_cheats 1
  27. // 2. nav_edit 1
  28. // 3. nav_mark_walkable
  29. // 4. nav_generate
  30. // 5. nav_edit 0
  31. // 6. sv_cheats 0
  32. // Copy the generated .nav file to your dedicated server.
  33.  
  34. #pragma semicolon 1
  35. #include <sourcemod>
  36. #include <sdktools>
  37. #include <cstrike>
  38. #include <sdkhooks> // https://forums.alliedmods.net/showthread.php?t=106748
  39. //#include <collisionhook> // https://forums.alliedmods.net/showthread.php?t=197815
  40. #include <botmimic> // https://forums.alliedmods.net/showthread.php?t=164148
  41. #include <timer> // https://forums.alliedmods.net/showthread.php?t=189751
  42. #include <timer-mapzones>
  43. #include <timer-physics>
  44. #include <timer-stocks>
  45. #include <timer-worldrecord>
  46. #include <timer-config_loader.sp>
  47.  
  48. #define PLUGIN_VERSION "1.0.1"
  49.  
  50. #define TIMER_BOTMIMIC_CATEGORY "timer"
  51.  
  52. #define TOP_LENGTH MAX_RECORD_NAME_LENGTH + MAX_NAME_LENGTH + 2
  53. enum TopPlayer {
  54. String:TP_demoFile[MAX_RECORD_NAME_LENGTH],
  55. String:TP_playerName[MAX_NAME_LENGTH],
  56. Float:TP_time,
  57. bool:TP_fileNotFound
  58. }
  59.  
  60. //new Handle:g_hDatabase;
  61. // List of all Styles, where a record exists on this map
  62. new Handle:g_hStylesOnMap;
  63. // Corresponding TopPlayer enums to the Styles
  64. new Handle:g_hTopsPerStyle;
  65. // Remember, if we processed the OnTimerWorldRecordCacheLoaded forward already this map.
  66. // It get's called way too often.
  67. new bool:g_bWRFetchedThisMap;
  68.  
  69. // Remember which Style this player finished the map in
  70. new g_iPlayerRecordStyle[MAXPLAYERS+1] = {-1,...};
  71. // Remember what time he managed to get through the map
  72. new Float:g_fPlayerRecordTime[MAXPLAYERS+1];
  73.  
  74. // Don't stop recording right away, after the timer stopped, since the FinishRound callback may be called afterwards -.-
  75. new Handle:g_hDelayedStopRecording[MAXPLAYERS+1];
  76. // The name of the record this bot currently mimics
  77. new String:g_sBotMimicsRecord[MAXPLAYERS+1][MAX_RECORD_NAME_LENGTH];
  78. // The Style the record this bot currently mimics was done in
  79. new g_iBotMimicsStyle[MAXPLAYERS+1];
  80. // The next bot joining this server is going to mimic this record
  81. new String:g_sNextBotMimicsThis[MAX_RECORD_NAME_LENGTH];
  82. new g_iBotAddTime = -1;
  83.  
  84. // Bot names are cached in the CCSBotManager. Instead of changing it there too and require more gamedata, we just remember what the bot was called when he joined.
  85. new String:g_sRealBotName[MAXPLAYERS+1][MAX_NAME_LENGTH];
  86. // Sets, which Style the player wants to see
  87. new Handle:g_hShowBot[MAXPLAYERS+1];
  88. new g_iPlayerStyle[MAXPLAYERS+1] = {-1,...};
  89.  
  90.  
  91. // Handle collisions with brush entities.
  92. // We don't want them to collide with some stuff like doors.
  93. new bool:g_bShouldNotCollide[8097];
  94. // If you add a entity's classname here, make sure to also add it to the g_bNeedsTargetname array.
  95. new String:g_sIgnoreEntities[][64] = {"func_door", "func_wall_toggle", "func_brush", "func_button", "func_breakable"};
  96. // Some entities shouldn't be avoided if they don't have a targetname set.
  97. // This applies usually to bhop maps where the platforms on the floor are func_doors, and we want collision with them,
  98. // but we want bots to walk through real doors. Due to the nature of bhop maps, the doors open only on a certain event,
  99. // so they got targetnames, while the platforms don't do anything in interaction with other entities.
  100. new bool:g_bNeedsTargetname[] = {true, true, true, false, false};
  101.  
  102. public Plugin:myinfo =
  103. {
  104. name = "Timer Record Replay",
  105. author = "Jannik \"Peace-Maker\" Hartung",
  106. description = "Have bots replay the best times from the map. (Private)",
  107. version = PLUGIN_VERSION,
  108. url = "http://www.wcfan.de/"
  109. }
  110.  
  111. public OnPluginStart()
  112. {
  113. g_hStylesOnMap = CreateArray();
  114. g_hTopsPerStyle = CreateArray(TOP_LENGTH);
  115.  
  116. // Hook some events to hide chat spam for our mimicing bots.
  117. HookEvent("player_connect", Event_OnPlayerConnect, EventHookMode_Pre);
  118. HookEvent("player_team", Event_OnBlock, EventHookMode_Pre);
  119. HookEvent("player_disconnect", Event_OnBlock, EventHookMode_Pre);
  120. HookUserMessage(GetUserMessageId("SayText2"), UsrMsg_NameChange, true);
  121.  
  122. HookEvent("player_spawn", Event_OnPlayerSpawn);
  123. HookEvent("round_start", Event_OnRoundStart);
  124.  
  125. // avoid that annoying spam..
  126. new Handle:hBotQuota = FindConVar("bot_quota");
  127. SetConVarFlags(hBotQuota, GetConVarFlags(hBotQuota)&~FCVAR_NOTIFY);
  128.  
  129. RegAdminCmd("sm_resetreplay", Cmd_ResetReplay, ADMFLAG_CONFIG, "Reset a bot and let him start his playback from the beginning. Usage: sm_resetreplay <Style>");
  130. RegConsoleCmd("sm_replay", Cmd_Replay, "Select which replaying bot to show.");
  131.  
  132. // Don't need to set failstate here. timer-core will do for us.
  133. /*if(!SQL_CheckConfig("timer"))
  134. return;
  135.  
  136. SQL_TConnect(SQL_OnConnect, "timer");*/
  137. }
  138.  
  139. // Kick all bots we added!
  140. public OnPluginEnd()
  141. {
  142. for(new i=1;i<=MaxClients;i++)
  143. {
  144. if(g_sBotMimicsRecord[i][0] != '\0')
  145. ServerCommand("bot_kick \"%s\"", g_sRealBotName[i]);
  146. }
  147. }
  148.  
  149. public OnAllPluginsLoaded()
  150. {
  151. for(new i=1;i<=MaxClients;i++)
  152. {
  153. if(IsClientInGame(i) && IsFakeClient(i) && !IsClientSourceTV(i) && !IsClientReplay(i))
  154. {
  155. SDKHook(i, SDKHook_SetTransmit, Hook_SetTransmit);
  156. }
  157. }
  158. }
  159.  
  160. /**
  161. * Public Core forwards
  162. */
  163. public OnMapStart()
  164. {
  165. // Keep checking, that we're playing the correct records
  166. CreateTimer(5.0, Timer_PlayRecords, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
  167.  
  168. // Reload the best times from the timer every 5 minutes to keep up with changes, that we aren't notified about.
  169. CreateTimer(600.0, Timer_ReloadBestTimes, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
  170.  
  171. LoadPhysics();
  172. LoadTimerSettings();
  173.  
  174. // Query the best times for each Style for the current map
  175. FetchBestTimes();
  176.  
  177. // Search for entities bots shouldn't collide with and disable buttons for them
  178. AddFilterToTriggers();
  179. }
  180.  
  181. public OnMapEnd()
  182. {
  183. // Allow the processing of the OnTimerWorldRecordCacheLoaded forward once again
  184. g_bWRFetchedThisMap = false;
  185. g_sNextBotMimicsThis[0] = '\0';
  186. g_iBotAddTime = -1;
  187. }
  188.  
  189. public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
  190. {
  191. // Get the current bots name before we rename him.
  192. // bot_kick caches this name and doesn't recognize the renamed name of the bot.
  193. // We can't just kick bots with KickClient, since they're maintained by a botmanager in CSS which is readding them
  194. if(IsFakeClient(client))
  195. GetClientName(client, g_sRealBotName[client], sizeof(g_sRealBotName[]));
  196.  
  197. // We want the next joining bot to mimic some record?
  198. if(g_sNextBotMimicsThis[0] != '\0' && IsFakeClient(client) && !IsClientSourceTV(client) && !IsClientReplay(client))
  199. {
  200. strcopy(g_sBotMimicsRecord[client], sizeof(g_sBotMimicsRecord[]), g_sNextBotMimicsThis);
  201. g_sNextBotMimicsThis[0] = '\0';
  202. g_iBotAddTime = -1;
  203.  
  204. // Rename the bot
  205. new iTop[TOP_LENGTH];
  206. new iSize = GetArraySize(g_hTopsPerStyle);
  207. for(new i=0;i<iSize;i++)
  208. {
  209. GetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
  210. // He's not playing that record..
  211. if(!StrEqual(iTop[TP_demoFile], g_sBotMimicsRecord[client]))
  212. continue;
  213.  
  214. // Remember what Style he's supposed to mimic.
  215. g_iBotMimicsStyle[client] = GetArrayCell(g_hStylesOnMap, i);
  216.  
  217. RenameBot(client, i);
  218. }
  219. }
  220.  
  221. return true;
  222. }
  223.  
  224. public OnClientPutInServer(client)
  225. {
  226. // Hook fake clients.
  227. if(IsFakeClient(client) && !IsClientSourceTV(client) && !IsClientReplay(client))
  228. {
  229. SDKHook(client, SDKHook_SetTransmit, Hook_SetTransmit);
  230. DispatchKeyValue(client, "targetname", "mimic_bot");
  231. }
  232.  
  233. // The list of Styles this player wants to see
  234. g_hShowBot[client] = CreateArray();
  235.  
  236. // This player isn't supposed to mimic anything
  237. if(g_sBotMimicsRecord[client][0] == '\0')
  238. return;
  239.  
  240. // Let this bot mimic the record he was meant for
  241. new BMError:error = BotMimic_PlayRecordByName(client, g_sBotMimicsRecord[client]);
  242. if(error != BM_NoError)
  243. {
  244. decl String:sError[64];
  245. BotMimic_GetErrorString(error, sError, sizeof(sError));
  246. LogError("Error playing record %s: %s", g_sBotMimicsRecord[client], sError);
  247.  
  248. // Remember that this record failed to play and don't try again later.
  249. new iSize = GetArraySize(g_hTopsPerStyle);
  250. new iTop[TOP_LENGTH];
  251. for(new i=0;i<iSize;i++)
  252. {
  253. GetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
  254. if(StrEqual(iTop[TP_demoFile], g_sBotMimicsRecord[client]))
  255. {
  256. iTop[TP_fileNotFound] = true;
  257. SetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
  258. break;
  259. }
  260. }
  261.  
  262. // This bot isn't needed anymore.
  263. g_sBotMimicsRecord[client][0] = '\0';
  264. g_iBotMimicsStyle[client] = -1;
  265. ServerCommand("bot_kick \"%s\"", g_sRealBotName[client]);
  266. }
  267. }
  268.  
  269. public OnClientDisconnect(client)
  270. {
  271. ClearHandle(g_hDelayedStopRecording[client]);
  272. g_sBotMimicsRecord[client][0] = '\0';
  273. g_iPlayerRecordStyle[client] = -1;
  274. g_fPlayerRecordTime[client] = 0.0;
  275. g_iBotMimicsStyle[client] = -1;
  276. g_sRealBotName[client][0] = '\0';
  277. g_iPlayerStyle[client] = -1;
  278. ClearHandle(g_hShowBot[client]);
  279. }
  280.  
  281. // Since there is no defined order in which SM calls this callback in the loaded plugins, it might happen,
  282. // that this plugin gets here before the botmimic one, which will overwrite it again.
  283. // The order seems to be alphabetical, so try renaming this plugin to something after botmimic ;)
  284. public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
  285. {
  286. // Don't allow bots to use buttons or attack
  287. if(g_sBotMimicsRecord[client][0] != '\0')
  288. {
  289. buttons &= ~(IN_USE|IN_ATTACK|IN_ATTACK2);
  290. return Plugin_Changed;
  291. }
  292. return Plugin_Continue;
  293. }
  294.  
  295. /**
  296. * Command Handlers
  297. */
  298. // Have a bot start his playback from the beginning.
  299. public Action:Cmd_ResetReplay(client, args)
  300. {
  301. if(args < 1)
  302. {
  303. ReplyToCommand(client, "[BotMimic] Usage: sm_resetreplay <Style>");
  304. return Plugin_Handled;
  305. }
  306.  
  307. decl String:sStyle[64];
  308. GetCmdArgString(sStyle, sizeof(sStyle));
  309.  
  310. // Find the right Style
  311. new iSize = GetArraySize(g_hStylesOnMap);
  312. decl String:sBuffer[64];
  313. new iTop[TOP_LENGTH], iBotsReset = -1;
  314. for(new i=0;i<iSize;i++)
  315. {
  316. Timer_GetStyleName(GetArrayCell(g_hStylesOnMap, i), sBuffer, sizeof(sBuffer));
  317. // That's not the one he's looking for
  318. if(!StrEqual(sStyle, sBuffer, false))
  319. continue;
  320.  
  321. GetArrayArray(g_hTopsPerStyle, i, iTop, TOP_LENGTH);
  322. iBotsReset = 0;
  323. // Check which bot is currently playing the record for that Style
  324. for(new c=1;c<=MaxClients;c++)
  325. {
  326. if(StrEqual(g_sBotMimicsRecord[c], iTop[TP_demoFile]))
  327. {
  328. if(BotMimic_IsPlayerMimicing(c))
  329. BotMimic_ResetPlayback(c);
  330. iBotsReset++;
  331. }
  332. }
  333. }
  334.  
  335. if(iBotsReset == -1)
  336. {
  337. ReplyToCommand(client, "[BotMimic] There is no Style named \"%s\" or there is no record for that Style on the current map.", sBuffer);
  338. }
  339. else
  340. {
  341. ReplyToCommand(client, "[BotMimic] Reset %d bot%s playing Style \"%s\".", iBotsReset, (iBotsReset!=1?"s":""), sBuffer);
  342. }
  343. return Plugin_Handled;
  344. }
  345.  
  346. // Show a menu to select which bots to show.
  347. public Action:Cmd_Replay(client, args)
  348. {
  349. DisplayBotShowSelectionMenu(client);
  350. return Plugin_Handled;
  351. }
  352.  
  353. /**
  354. * SDKHooks Callbacks
  355. */
  356. public Action:Hook_SetTransmit(entity, client)
  357. {
  358. if(entity < 1 || entity > MaxClients)
  359. return Plugin_Continue;
  360.  
  361. // Dead players and spectators are always shown all bots.
  362. if(client < 1 || client > MaxClients || !IsClientInGame(client) || !IsPlayerAlive(client) || IsClientObserver(client) || GetClientTeam(client) < 2)
  363. return Plugin_Continue;
  364.  
  365. if(g_sBotMimicsRecord[entity][0] == '\0')
  366. return Plugin_Continue;
  367.  
  368. // Don't show bots to players, if they don't want to see them
  369. CheckForStyleChange(client);
  370. if(FindValueInArray(g_hShowBot[client], g_iBotMimicsStyle[entity]) == -1)
  371. return Plugin_Handled;
  372.  
  373. return Plugin_Continue;
  374. }
  375.  
  376. /**
  377. * CollideHook Callbacks
  378. */
  379. // Allow mimicing bots to pass through closed doors.
  380. /*
  381. public Action:CH_ShouldCollide( ent1, ent2, &bool:result )
  382. {
  383. new iPlayer, iEnt;
  384. if(ent1 > 0 && ent1 <= MaxClients)
  385. {
  386. iPlayer = ent1;
  387. iEnt = ent2;
  388. }
  389. else if(ent2 > 0 && ent2 <= MaxClients)
  390. {
  391. iPlayer = ent2;
  392. iEnt = ent1;
  393. }
  394. else
  395. return Plugin_Continue;
  396.  
  397. if(g_sBotMimicsRecord[iPlayer][0] != '\0' && g_bShouldNotCollide[iEnt])
  398. {
  399. result = false;
  400. return Plugin_Handled;
  401. }
  402. return Plugin_Continue;
  403. }
  404.  
  405. public Action:CH_PassFilter( ent1, ent2, &bool:result )
  406. {
  407. new iPlayer, iEnt;
  408. if(ent1 > 0 && ent1 <= MaxClients)
  409. {
  410. iPlayer = ent1;
  411. iEnt = ent2;
  412. }
  413. else if(ent2 > 0 && ent2 <= MaxClients)
  414. {
  415. iPlayer = ent2;
  416. iEnt = ent1;
  417. }
  418. else
  419. return Plugin_Continue;
  420.  
  421. if(g_sBotMimicsRecord[iPlayer][0] != '\0' && g_bShouldNotCollide[iEnt])
  422. {
  423. result = false;
  424. return Plugin_Handled;
  425. }
  426.  
  427. return Plugin_Continue;
  428. }
  429. */
  430.  
  431. /**
  432. * Timer Callbacks
  433. */
  434. public Action:Timer_PlayRecords(Handle:timer)
  435. {
  436. // Keep the correct number of bots playing the right thing on the server
  437. HandleBotPlaybackLogic();
  438. return Plugin_Continue;
  439. }
  440.  
  441. // This player didn't finish the round in best time, so discard his record.
  442. public Action:Timer_StopRecording(Handle:timer, any:userid)
  443. {
  444. new client = GetClientOfUserId(userid);
  445. if(!client)
  446. return Plugin_Handled;
  447.  
  448. g_hDelayedStopRecording[client] = INVALID_HANDLE;
  449. if(BotMimic_IsPlayerRecording(client))
  450. BotMimic_StopRecording(client, false);
  451. return Plugin_Handled;
  452. }
  453.  
  454. public Action:Timer_ReloadBestTimes(Handle:timer)
  455. {
  456. FetchBestTimes();
  457. return Plugin_Continue;
  458. }
  459.  
  460. /**
  461. * Event Handlers
  462. */
  463.  
  464. // Block the connect chat message for mimicing bots
  465. public Action:Event_OnPlayerConnect(Handle:event, const String:name[], bool:dontBroadcast)
  466. {
  467. decl String:sNetworkID[32];
  468. GetEventString(event, "networkid", sNetworkID, sizeof(sNetworkID));
  469.  
  470. if(!StrEqual(sNetworkID, "BOT") || g_sNextBotMimicsThis[0] == '\0')
  471. return Plugin_Continue;
  472.  
  473. dontBroadcast = true;
  474. SetEventBroadcast(event, true);
  475. return Plugin_Continue;
  476. }
  477.  
  478. // Block the teamchange and disconnect chat message for mimicing bots
  479. public Action:Event_OnBlock(Handle:event, const String:name[], bool:dontBroadcast)
  480. {
  481. new client = GetClientOfUserId(GetEventInt(event, "userid"));
  482. if(!client)
  483. return Plugin_Continue;
  484.  
  485. if(!IsFakeClient(client) || IsClientSourceTV(client) || IsClientReplay(client))
  486. return Plugin_Continue;
  487.  
  488. // This bot doesn't mimic anything and the next bot neither.
  489. if(g_sBotMimicsRecord[client][0] == '\0' && g_sNextBotMimicsThis[0] == '\0')
  490. return Plugin_Continue;
  491.  
  492. dontBroadcast = true;
  493. SetEventBroadcast(event, true);
  494. return Plugin_Continue;
  495. }
  496.  
  497. public Event_OnPlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
  498. {
  499. new client = GetClientOfUserId(GetEventInt(event, "userid"));
  500. if(!client)
  501. return;
  502.  
  503. if(IsFakeClient(client))
  504. {
  505. // This is important for the filter_activator_name, so bots won't trigger buttons on the ground.
  506. DispatchKeyValue(client, "targetname", "mimic_bot");
  507. }
  508.  
  509. CheckForStyleChange(client);
  510. }
  511.  
  512. public Event_OnRoundStart(Handle:event, const String:name[], bool:dontBroadcast)
  513. {
  514. AddFilterToTriggers();
  515. }
  516.  
  517. // By thetwistedpanda
  518. // https://forums.alliedmods.net/showthread.php?t=139760
  519. // Don't show bots changing their name in chat at all.
  520. public Action:UsrMsg_NameChange(UserMsg:msg_hd, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
  521. {
  522. if(GetEngineVersion() == Engine_CSS)
  523. {
  524. decl String:sBuffer[64], String:sName[MAX_NAME_LENGTH];
  525. BfReadString(bf, sBuffer, sizeof(sBuffer));
  526. BfReadString(bf, sBuffer, sizeof(sBuffer));
  527.  
  528. if(StrContains(sBuffer, "Name_Change") != -1)
  529. {
  530. BfReadString(bf, sBuffer, sizeof(sBuffer));
  531.  
  532. for(new i = 1; i <= MaxClients; i++)
  533. {
  534. if(IsClientConnected(i) && IsFakeClient(i))
  535. {
  536. GetClientName(i, sName, sizeof(sName));
  537.  
  538. if(StrEqual(sBuffer, sName))
  539. {
  540. return Plugin_Handled;
  541. }
  542. }
  543. }
  544. }
  545. }
  546. else if (GetEngineVersion() == Engine_CSGO)
  547. {
  548. decl String:sBuffer[64];
  549. decl String:sName[64];
  550. new iRepeat = PbGetRepeatedFieldCount(bf, "params");
  551. for(new i = 0; i < iRepeat; i++)
  552. {
  553. PbReadString(bf, "params", sBuffer, sizeof(sBuffer), i);
  554. if (StrContains(sBuffer, "Name_Change") != -1)
  555. {
  556. PbReadString(bf, "params", sBuffer, sizeof(sBuffer), i);
  557. for(new j = 1; j <= MaxClients; j++)
  558. {
  559. if(IsClientConnected(j) && IsFakeClient(j))
  560. {
  561. GetClientName(j, sName, sizeof(sName));
  562.  
  563. if(StrEqual(sBuffer, sName))
  564. {
  565. return Plugin_Handled;
  566. }
  567. }
  568. }
  569. }
  570. }
  571. }
  572.  
  573. return Plugin_Continue;
  574. }
  575.  
  576. /**
  577. * Bot Mimic Callbacks
  578. */
  579. public Action:BotMimic_OnStartRecording(client, String:name[], String:category[], String:subdir[], String:path[])
  580. {
  581. if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
  582. return Plugin_Continue;
  583.  
  584. // We don't want bots to record anything in our category.
  585. if(IsFakeClient(client))
  586. return Plugin_Handled;
  587.  
  588. return Plugin_Continue;
  589. }
  590.  
  591. public Action:BotMimic_OnStopRecording(client, String:name[], String:category[], String:subdir[], String:path[], &bool:save)
  592. {
  593. // This isn't a recording we started from here.
  594. // We don't care.
  595. if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
  596. return Plugin_Continue;
  597.  
  598. // This player actually did a new best time. Let him pass
  599. if(g_iPlayerRecordStyle[client] != -1)
  600. return Plugin_Continue;
  601.  
  602. // Don't save the recording, if it's not acutally a best time!
  603. save = false;
  604. return Plugin_Changed;
  605. }
  606.  
  607. // Player might have done a new record!
  608. public BotMimic_OnRecordSaved(client, String:name[], String:category[], String:subdir[], String:file[])
  609. {
  610. if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
  611. return;
  612.  
  613. if(g_iPlayerRecordStyle[client] == -1)
  614. return;
  615.  
  616. // That player managed to beat the 1st!
  617. new iTop[TOP_LENGTH];
  618. strcopy(iTop[TP_demoFile], MAX_RECORD_NAME_LENGTH, name);
  619. iTop[TP_time] = g_fPlayerRecordTime[client];
  620. GetClientName(client, iTop[TP_playerName], MAX_NAME_LENGTH);
  621.  
  622. // Replace old record
  623. new iIndex = FindValueInArray(g_hStylesOnMap, g_iPlayerRecordStyle[client]);
  624. if(iIndex != -1)
  625. {
  626. new iOldTop[TOP_LENGTH];
  627. GetArrayArray(g_hTopsPerStyle, iIndex, iOldTop, TOP_LENGTH);
  628. SetArrayArray(g_hTopsPerStyle, iIndex, iTop, TOP_LENGTH);
  629.  
  630. // Stop any bots from mimicing the old record and switch to this new one.
  631. for(new i=1;i<=MaxClients;i++)
  632. {
  633. if(StrEqual(g_sBotMimicsRecord[i], iOldTop[TP_demoFile]))
  634. {
  635. if(BotMimic_IsPlayerMimicing(i))
  636. BotMimic_StopPlayerMimic(i);
  637. strcopy(g_sBotMimicsRecord[i], sizeof(g_sBotMimicsRecord[]), name);
  638. g_iBotMimicsStyle[i] = g_iPlayerRecordStyle[client];
  639. BotMimic_PlayRecordFromFile(i, file);
  640. RenameBot(i, iIndex);
  641. }
  642. }
  643. }
  644. // Or add new one for this Style
  645. else
  646. {
  647. PushArrayCell(g_hStylesOnMap, g_iPlayerRecordStyle[client]);
  648. PushArrayArray(g_hTopsPerStyle, iTop, TOP_LENGTH);
  649. }
  650.  
  651. g_iPlayerRecordStyle[client] = -1;
  652. g_fPlayerRecordTime[client] = 0.0;
  653. }
  654.  
  655. // Called everytime the player starts his playback from the beginning
  656. public BotMimic_OnPlayerMimicLoops(client)
  657. {
  658. if(g_sBotMimicsRecord[client][0] != '\0')
  659. {
  660. // Make the bot invulnerable and render him slightly red
  661. SetEntProp(client, Prop_Data, "m_takedamage", 0);
  662. SetEntityRenderMode(client, RENDER_TRANSALPHA);
  663. SetEntityRenderColor(client, 192, 0, 0, 192);
  664.  
  665. //PrintToChatAll("%N starts mimicing %d (should be %d)", client, Timer_GetClientStyle(client), g_iBotMimicsStyle[client]);
  666. }
  667. }
  668.  
  669. public Action:BotMimic_OnPlayerStartsMimicing(client, String:name[], String:category[], String:path[])
  670. {
  671. // He's not mimicing something we recorded.
  672. if(!StrEqual(category, TIMER_BOTMIMIC_CATEGORY))
  673. return Plugin_Continue;
  674.  
  675. // We didn't tell him to mimic that one.
  676. if(g_sBotMimicsRecord[client][0] == '\0')
  677. return Plugin_Continue;
  678.  
  679. // Set the Style of this bot to the one this demo was recorded with.
  680. // This makes sure the conditions are the same for e.g. gravity and stamina settings.
  681. //Timer_SetClientStyle(client, g_iBotMimicsStyle[client]);
  682. Timer_SetStyle(client, g_iBotMimicsStyle[client]);
  683.  
  684. return Plugin_Continue;
  685. }
  686.  
  687. public BotMimic_OnPlayerStopsMimicing(client, String:name[], String:category[], String:path[])
  688. {
  689. // That bot doesn't mimic anything anymore :(
  690. g_sBotMimicsRecord[client][0] = '\0';
  691. g_iBotMimicsStyle[client] = -1;
  692. }
  693.  
  694. /**
  695. * Timer Plugin Callbacks
  696. */
  697. public OnTimerStarted(client)
  698. {
  699. StartRecording(client);
  700. }
  701.  
  702. StartRecording(client)
  703. {
  704. // Don't allow bots to use the timer at all.
  705. if(IsFakeClient(client))
  706. {
  707. Timer_Stop(client);
  708. return;
  709. }
  710.  
  711. // Stop the old record, if this player has been recording before.
  712. if(BotMimic_IsPlayerRecording(client))
  713. BotMimic_StopRecording(client, false);
  714.  
  715. ClearHandle(g_hDelayedStopRecording[client]);
  716.  
  717. decl String:sStyle[64];
  718. new style = Timer_GetStyle(client);
  719. new track = Timer_GetTrack(client);
  720. Timer_GetStyleName(style, sStyle, sizeof(sStyle));
  721.  
  722. // Name the record like STEAMID_Style and save it to our "timer" category in a subfolder named after the style.
  723. // e.g. data/botmimic/timer/map_name/Style/demo.rec
  724.  
  725. decl String:sRecordName[MAX_RECORD_NAME_LENGTH], String:sSteamID[40], String:sSubCat[32];
  726. GetClientAuthString(client, sSteamID, sizeof(sSteamID));
  727. ReplaceString(sSteamID, sizeof(sSteamID), ":", "_");
  728.  
  729. Format(sSubCat, sizeof(sSubCat), "%d_%d", style, track);
  730. Format(sRecordName, sizeof(sRecordName), "%d_%d_%s", style, track, sSteamID);
  731.  
  732. BotMimic_StartRecording(client, sRecordName, TIMER_BOTMIMIC_CATEGORY, sSubCat);
  733.  
  734. /*
  735. // e.g. data/botmimic/timer/map_name/Style/demo.rec
  736. decl String:sRecordName[MAX_RECORD_NAME_LENGTH], String:sSteamID[40];
  737. GetClientAuthString(client, sSteamID, sizeof(sSteamID));
  738. ReplaceString(sSteamID, sizeof(sSteamID), ":", "_");
  739. Format(sRecordName, sizeof(sRecordName), "%s_%s", sSteamID, sStyle);
  740. BotMimic_StartRecording(client, sRecordName, TIMER_BOTMIMIC_CATEGORY, sStyle);
  741. */
  742. }
  743.  
  744. public OnTimerStopped(client)
  745. {
  746. // Don't care if a bot's timer stopped.
  747. if(IsFakeClient(client))
  748. return;
  749.  
  750. // We've been recording this guy!
  751. if(BotMimic_IsPlayerRecording(client))
  752. {
  753. ClearHandle(g_hDelayedStopRecording[client]);
  754. // OnTimerStopped is called BEFORE OnTimerWorldRecord... >.<
  755. // We can't just stop recording here, since we might want to save the recording!
  756. g_hDelayedStopRecording[client] = CreateTimer(1.0, Timer_StopRecording, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
  757. }
  758. }
  759.  
  760. public OnTimerPaused(client)
  761. {
  762. // Don't care if a bot's timer stopped.
  763. if(IsFakeClient(client))
  764. return;
  765.  
  766. // We've been recording this guy!
  767. if(BotMimic_IsPlayerRecording(client))
  768. BotMimic_PauseRecording(client);
  769. }
  770.  
  771. public OnTimerResumed(client)
  772. {
  773. // Don't care if a bot's timer stopped.
  774. if(IsFakeClient(client))
  775. return;
  776.  
  777. // We've been recording this guy!
  778. if(BotMimic_IsRecordingPaused(client))
  779. BotMimic_ResumeRecording(client);
  780. }
  781.  
  782. public OnClientStartTouchLevel(client, level, lastlevel)
  783. {
  784. if(level < lastlevel)
  785. return;
  786.  
  787. decl String:buffer[32];
  788. Format(buffer, sizeof(buffer), "Level_%d", level);
  789. BotMimic_SaveBookmark(client, buffer);
  790. }
  791.  
  792. public OnClientStartTouchBonusLevel(client, level, lastlevel)
  793. {
  794. if(level < lastlevel)
  795. return;
  796.  
  797. decl String:buffer[32];
  798. Format(buffer, sizeof(buffer), "Level_%d", level);
  799. BotMimic_SaveBookmark(client, buffer);
  800. }
  801.  
  802. public OnTimerWorldRecord(client, track, mode, Float:time, Float:lasttime, currentrank, newrank)
  803. {
  804. // Bots should never finish a round, since we don't allow timers, but just in case..
  805. if(IsFakeClient(client))
  806. return;
  807.  
  808. if(BotMimic_IsPlayerRecording(client))
  809. {
  810. ClearHandle(g_hDelayedStopRecording[client]);
  811.  
  812. // Only save the record for the 1st on the list.
  813.  
  814. g_iPlayerRecordStyle[client] = mode;
  815. g_fPlayerRecordTime[client] = time;
  816. BotMimic_StopRecording(client, true);
  817. }
  818. }
  819.  
  820. public OnTimerDeleteOneRecord(client, Float:time, const String:map[], jumps, Style, flashbangs)
  821. {
  822. decl String:sCurrentMap[64];
  823. // Deleting stuff from other maps - don't care.
  824. GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
  825. if(!StrEqual(sCurrentMap, map))
  826. return;
  827.  
  828. // We don't have a record for this Style?
  829. new iIndex = FindValueInArray(g_hStylesOnMap, Style);
  830. if(iIndex == -1)
  831. return;
  832.  
  833. // This wasn't the best time our bot currently mimics.
  834. new iTop[TOP_LENGTH];
  835. GetArrayArray(g_hTopsPerStyle, iIndex, iTop, TOP_LENGTH);
  836. if(iTop[TP_time] != time)
  837. return;
  838.  
  839. RemoveFromArray(g_hStylesOnMap, iIndex);
  840. RemoveFromArray(g_hTopsPerStyle, iIndex);
  841.  
  842. // Stop bots from playing this one
  843. for(new i=1;i<=MaxClients;i++)
  844. {
  845. if(!StrEqual(g_sBotMimicsRecord[i], iTop[TP_demoFile]))
  846. continue;
  847.  
  848. g_sBotMimicsRecord[i][0] = '\0';
  849. g_iBotMimicsStyle[i] = -1;
  850.  
  851. if(BotMimic_IsPlayerMimicing(i))
  852. BotMimic_StopPlayerMimic(i);
  853. }
  854.  
  855. // Have a different file played.
  856. HandleBotPlaybackLogic();
  857. // See if there is a second place who's now 1st :)
  858. FetchBestTimes();
  859. }
  860.  
  861. public OnTimerWorldRecordCacheLoaded()
  862. {
  863. // Only process this callback once a map.
  864. if(g_bWRFetchedThisMap)
  865. return;
  866.  
  867. g_bWRFetchedThisMap = true;
  868. FetchBestTimes();
  869. }
  870.  
  871. public Timer_OnClientStyleChanged(client, oldStyle, newStyle)
  872. {
  873. PrintToChatAll("%N changed Style from %d to %d (should be %d)", client, oldStyle, newStyle, g_iBotMimicsStyle[client]);
  874. }
  875.  
  876. /**
  877. * Menu creation and handling
  878. */
  879. DisplayBotShowSelectionMenu(client)
  880. {
  881. new Handle:hMenu = CreateMenu(Menu_SelectShowBot);
  882. SetMenuExitButton(hMenu, true);
  883.  
  884. decl String:sStyle[64];
  885. Timer_GetStyleName(Timer_GetStyle(client), sStyle, sizeof(sStyle));
  886. SetMenuTitle(hMenu, "Timer Replay: Show replay\nYour Style: %s", sStyle);
  887.  
  888. // Add all mimicing bots to the menu
  889. decl String:sIndex[10], String:sBuffer[128], iStyle;
  890. new iSize = GetArraySize(g_hStylesOnMap);
  891. for(new d=0;d<iSize;d++)
  892. {
  893. iStyle = GetArrayCell(g_hStylesOnMap, d);
  894. Timer_GetStyleName(iStyle, sStyle, sizeof(sStyle));
  895.  
  896. // Check the box, if he wants to see that bot already.
  897. if(FindValueInArray(g_hShowBot[client], iStyle) != -1)
  898. Format(sBuffer, sizeof(sBuffer), "[x] %s", sStyle);
  899. else
  900. Format(sBuffer, sizeof(sBuffer), "[ ] %s", sStyle);
  901.  
  902. IntToString(iStyle, sIndex, sizeof(sIndex));
  903. AddMenuItem(hMenu, sIndex, sBuffer);
  904. }
  905.  
  906. DisplayMenu(hMenu, client, MENU_TIME_FOREVER);
  907. }
  908.  
  909. public Menu_SelectShowBot(Handle:menu, MenuAction:action, param1, param2)
  910. {
  911. switch(action)
  912. {
  913. case MenuAction_End:
  914. {
  915. CloseHandle(menu);
  916. }
  917. case MenuAction_Select:
  918. {
  919. decl String:sStyle[10];
  920. GetMenuItem(menu, param2, sStyle, sizeof(sStyle));
  921. new iStyle = StringToInt(sStyle);
  922.  
  923. // He wants to see that bot now?
  924. // Toggle
  925. new iIndex = FindValueInArray(g_hShowBot[param1], iStyle);
  926. if(iIndex == -1)
  927. {
  928. PushArrayCell(g_hShowBot[param1], iStyle);
  929. }
  930. else
  931. {
  932. RemoveFromArray(g_hShowBot[param1], iIndex);
  933. }
  934. DisplayBotShowSelectionMenu(param1);
  935. }
  936. }
  937. }
  938.  
  939. /**
  940. * Helpers
  941. */
  942.  
  943. FetchBestTimes()
  944. {
  945. ClearArray(g_hStylesOnMap);
  946. ClearArray(g_hTopsPerStyle);
  947.  
  948. new styleCacheId, Float:fTime, styleTotal, styleTop[TOP_LENGTH];
  949. decl String:sStyle[64], String:sAuth[MAX_NAME_LENGTH], String:sName[MAX_NAME_LENGTH], String:sBuffer[128];
  950.  
  951. new track = 0;
  952. for(new style=0;style<g_StyleCount;style++)
  953. {
  954. // This style is disabled.
  955. if(!g_Physics[style][StyleEnable])
  956. continue;
  957.  
  958. // Only play ranked best times.
  959. if(g_Physics[style][StyleCategory] != MCategory_Ranked)
  960. continue;
  961.  
  962. Timer_GetStyleRecordWRStats(style, TRACK_NORMAL, styleCacheId, fTime, styleTotal);
  963. // No record for this style yet..
  964. if(fTime <= 0.0)
  965. continue;
  966.  
  967. Timer_GetRecordHolderName(style, TRACK_NORMAL, 1, sName, sizeof(sName));
  968. Timer_GetRecordHolderAuth(style, TRACK_NORMAL, 1, sAuth, sizeof(sAuth));
  969.  
  970. // Build the demo name "STEAMID_Style"
  971. Timer_GetStyleName(style, sStyle, sizeof(sStyle));
  972. ReplaceString(sAuth, sizeof(sAuth), ":", "_");
  973. //Format(sBuffer, sizeof(sBuffer), "%s_%s", sAuth, sStyle);
  974. Format(sBuffer, sizeof(sBuffer), "%d_%d_%s", style, track, sAuth);
  975.  
  976. // Save the details
  977. strcopy(styleTop[TP_demoFile], MAX_RECORD_NAME_LENGTH, sBuffer);
  978. strcopy(styleTop[TP_playerName], MAX_NAME_LENGTH, sName);
  979. styleTop[TP_time] = fTime;
  980.  
  981. PushArrayCell(g_hStylesOnMap, style);
  982. PushArrayArray(g_hTopsPerStyle, styleTop, TOP_LENGTH);
  983. }
  984. /*
  985. decl String:sRecordName[MAX_RECORD_NAME_LENGTH], String:sSteamID[40], String:sSubCat[32];
  986. GetClientAuthString(client, sSteamID, sizeof(sSteamID));
  987. ReplaceString(sSteamID, sizeof(sSteamID), ":", "_");
  988.  
  989. Format(sSubCat, sizeof(sSubCat), "%d_%d", style, track);
  990. Format(sRecordName, sizeof(sRecordName), "%d_%d_%s", style, track, sSteamID);
  991. */
  992. /*new iCount = Timer_GetStyleCount();
  993. new iDetails[PhysicsStyle], iRecord[RecordCache], iTop[TOP_LENGTH];
  994. decl String:sStyle[64], String:sBuffer[128];
  995. // Go through all Styles, the Timer plugin has in its config.
  996. // We only save the ones where there actually is a best time
  997. for(new i=0;i<iCount;i++)
  998. {
  999. Timer_GetStyleDetailsByIndex(i, iDetails);
  1000. // Is there a record for that Style?
  1001. if(!Timer_GetWorldRecordForStyle(iDetails[Id], iRecord))
  1002. continue;
  1003.  
  1004. // Build the demo name "STEAMID_Style"
  1005. ReplaceString(iRecord[Auth], sizeof(iRecord[Auth]), ":", "_");
  1006. Timer_GetStyleName(iDetails[Id], sStyle, sizeof(sStyle));
  1007. Format(sBuffer, sizeof(sBuffer), "%s_%s", iRecord[Auth], sStyle);
  1008.  
  1009. // Save the details
  1010. strcopy(iTop[TP_demoFile], MAX_RECORD_NAME_LENGTH, sBuffer);
  1011. strcopy(iTop[TP_playerName], MAX_NAME_LENGTH, iRecord[Name]);
  1012. iTop[TP_time] = iRecord[Time];
  1013.  
  1014. PushArrayCell(g_hStylesOnMap, iDetails[Id]);
  1015. PushArrayArray(g_hTopsPerStyle, iTop, TOP_LENGTH);
  1016. }*/
  1017. }
  1018.  
  1019. // Players always see the bot for their own Style by default.
  1020. CheckForStyleChange(client)
  1021. {
  1022. new iStyle = Timer_GetStyle(client);
  1023. // This player didn't change his Style.
  1024. if(iStyle == g_iPlayerStyle[client])
  1025. return;
  1026.  
  1027. g_iPlayerStyle[client] = iStyle;
  1028.  
  1029. // Only add him, if the player doesn't want to seem him already.
  1030. if(FindValueInArray(g_hShowBot[client], iStyle) == -1)
  1031. PushArrayCell(g_hShowBot[client], iStyle);
  1032. }
  1033.  
  1034. RenameBot(client, index)
  1035. {
  1036. new iTop[TOP_LENGTH];
  1037. GetArrayArray(g_hTopsPerStyle, index, iTop, TOP_LENGTH);
  1038.  
  1039. // Set the clantag to the Style
  1040. decl String:sNewName[MAX_NAME_LENGTH], String:sTime[32], String:sStyle[32];
  1041. Timer_GetStyleName(GetArrayCell(g_hStylesOnMap, index), sStyle, sizeof(sStyle));
  1042. CS_SetClientClanTag(client, sStyle);
  1043.  
  1044. // Name the bot after the player who did the record including the time.
  1045. Timer_SecondsToTime(iTop[TP_time], sTime, sizeof(sTime), 2);
  1046. Format(sNewName, sizeof(sNewName), "%s - %s", iTop[TP_playerName], sTime);
  1047. SetClientInfo(client, "name", sNewName);
  1048. }
  1049.  
  1050. ClearBots()
  1051. {
  1052. // Don't kick anyone, if we might want him to mimic stuff!
  1053. if(g_sNextBotMimicsThis[0] != '\0')
  1054. return;
  1055.  
  1056. // Kick any bots, that don't mimic anything!
  1057. for(new i=1;i<=MaxClients;i++)
  1058. {
  1059. // Only consider bots
  1060. if(!IsClientInGame(i) || !IsFakeClient(i) || IsClientSourceTV(i) || IsClientReplay(i))
  1061. continue;
  1062.  
  1063. // This bot mimics something.
  1064. if(g_sBotMimicsRecord[i][0] != '\0')
  1065. continue;
  1066.  
  1067. // Don't kick bots, which mimic something else.
  1068. if(BotMimic_IsPlayerMimicing(i))
  1069. continue;
  1070.  
  1071. ServerCommand("bot_kick \"%s\"", g_sRealBotName[i]);
  1072. }
  1073. }
  1074.  
  1075. HandleBotPlaybackLogic()
  1076. {
  1077. // That bot takes his time..
  1078. if(g_iBotAddTime != -1 && (GetTime() - g_iBotAddTime) > 5)
  1079. {
  1080. // Try to add another one.
  1081. g_iBotAddTime = GetTime();
  1082. ServerCommand("bot_add");
  1083. return;
  1084. }
  1085.  
  1086. // Don't do anything while we wait for a bot to join.
  1087. if(g_sNextBotMimicsThis[0] != '\0')
  1088. return;
  1089.  
  1090. // Kick all bots, which don't mimic something currently, if we're currently having someone mimicing something.
  1091. new iSize = GetArraySize(g_hStylesOnMap);
  1092. if(IsRecordPlayed() || !iSize)
  1093. ClearBots();
  1094.  
  1095. // No records to play for this map
  1096. if(!iSize)
  1097. return;
  1098.  
  1099. // Check that we've got a bot mimicing the best time for each Style
  1100. new iTop[TOP_LENGTH];
  1101. new BMError:error, bool:bIsPlayed;
  1102. for(new d=0;d<iSize;d++)
  1103. {
  1104. GetArrayArray(g_hTopsPerStyle, d, iTop, TOP_LENGTH);
  1105.  
  1106. // We didn't find any bot who mimics that one yet.
  1107. bIsPlayed = false;
  1108.  
  1109. // That one already errored..
  1110. if(iTop[TP_fileNotFound])
  1111. continue;
  1112.  
  1113. // Is some bot currently mimicing that one?
  1114. for(new i=1;i<=MaxClients;i++)
  1115. {
  1116. // Only consider bots
  1117. if(!IsClientInGame(i) || !IsFakeClient(i) || IsClientSourceTV(i) || IsClientReplay(i))
  1118. continue;
  1119.  
  1120. // Yep, no need to do anything
  1121. if(StrEqual(g_sBotMimicsRecord[i], iTop[TP_demoFile]))
  1122. {
  1123. // This record is already played by a different bot!!
  1124. if(bIsPlayed)
  1125. {
  1126. g_iBotMimicsStyle[i] = -1;
  1127. g_sBotMimicsRecord[i][0] = '\0';
  1128. if(BotMimic_IsPlayerMimicing(i))
  1129. BotMimic_StopPlayerMimic(i);
  1130. continue;
  1131. }
  1132.  
  1133. // Remember that some bot is mimicing that record -> don't have two bots mimicing the same.
  1134. bIsPlayed = true;
  1135.  
  1136. // Is he actually still mimicing our record?
  1137. if(BotMimic_IsPlayerMimicing(i))
  1138. continue;
  1139.  
  1140. // That bot wasn't mimicing the record anymore?!
  1141. error = BotMimic_PlayRecordByName(i, g_sBotMimicsRecord[i]);
  1142. if(error != BM_NoError)
  1143. {
  1144. decl String:sError[64];
  1145. BotMimic_GetErrorString(error, sError, sizeof(sError));
  1146. LogError("Error playing record %s: %s", g_sBotMimicsRecord[i], sError);
  1147.  
  1148. g_iBotMimicsStyle[i] = -1;
  1149. g_sBotMimicsRecord[i][0] = '\0';
  1150. iTop[TP_fileNotFound] = true;
  1151. SetArrayArray(g_hTopsPerStyle, d, iTop, TOP_LENGTH);
  1152.  
  1153. // This one isn't mimicing :(
  1154. bIsPlayed = false;
  1155. }
  1156. }
  1157. }
  1158.  
  1159. // This one is covered.
  1160. if(bIsPlayed)
  1161. continue;
  1162.  
  1163. // No bot mimics this best time?
  1164. // Check if there is a spare bot to use on the server
  1165. for(new i=1;i<=MaxClients;i++)
  1166. {
  1167. // That guy is already playing something
  1168. if(g_sBotMimicsRecord[i][0] != '\0')
  1169. continue;
  1170.  
  1171. // Only consider bots
  1172. if(!IsClientInGame(i) || !IsFakeClient(i) || IsClientSourceTV(i) || IsClientReplay(i))
  1173. continue;
  1174.  
  1175. // He's already mimicing something else?
  1176. if(BotMimic_IsPlayerMimicing(i))
  1177. continue;
  1178.  
  1179. // Have him play the record!
  1180. strcopy(g_sBotMimicsRecord[i], sizeof(g_sBotMimicsRecord[]), iTop[TP_demoFile]);
  1181. g_iBotMimicsStyle[i] = GetArrayCell(g_hStylesOnMap, d);
  1182. error = BotMimic_PlayRecordByName(i, iTop[TP_demoFile]);
  1183. if(error == BM_NoError)
  1184. {
  1185. bIsPlayed = true;
  1186.  
  1187. RenameBot(i, d);
  1188. break;
  1189. }
  1190. else
  1191. {
  1192. decl String:sError[64];
  1193. BotMimic_GetErrorString(error, sError, sizeof(sError));
  1194. LogError("Error playing record %s: %s", iTop[TP_demoFile], sError);
  1195.  
  1196. g_iBotMimicsStyle[i] = -1;
  1197. g_sBotMimicsRecord[i][0] = '\0';
  1198. // Remember that this record failed so we don't try again.
  1199. iTop[TP_fileNotFound] = true;
  1200. SetArrayArray(g_hTopsPerStyle, d, iTop, TOP_LENGTH);
  1201. }
  1202. }
  1203.  
  1204. // Still no bot found?!
  1205. if(!bIsPlayed && !iTop[TP_fileNotFound])
  1206. {
  1207. // Add one.
  1208. strcopy(g_sNextBotMimicsThis, sizeof(g_sNextBotMimicsThis), iTop[TP_demoFile]);
  1209. g_iBotAddTime = GetTime();
  1210. ServerCommand("bot_add");
  1211. // We need to wait until the player joined..
  1212. return;
  1213. }
  1214. }
  1215. }
  1216.  
  1217. bool:IsRecordPlayed()
  1218. {
  1219. for(new i=1;i<=MaxClients;i++)
  1220. {
  1221. if(g_sBotMimicsRecord[i][0] != '\0')
  1222. return true;
  1223. }
  1224. return false;
  1225. }
  1226.  
  1227. stock ClearHandle(&Handle:hndl)
  1228. {
  1229. if(hndl != INVALID_HANDLE)
  1230. {
  1231. CloseHandle(hndl);
  1232. hndl = INVALID_HANDLE;
  1233. }
  1234. }
  1235.  
  1236. AddFilterToTriggers()
  1237. {
  1238. // Reset all previously ignored entities
  1239. // GetMaxEntities()*2 due to logical not networked entities.
  1240. for(new i=0;i<8097;i++)
  1241. g_bShouldNotCollide[i] = false;
  1242.  
  1243. decl String:sBuffer[64];
  1244. new iEnt;
  1245. // Let bots pass through all ignored entities
  1246. for(new i=0;i<sizeof(g_sIgnoreEntities);i++)
  1247. {
  1248. iEnt = -1;
  1249. while((iEnt = FindEntityByClassname(iEnt, g_sIgnoreEntities[i])) != -1)
  1250. {
  1251. GetEntPropString(iEnt, Prop_Data, "m_iName", sBuffer, sizeof(sBuffer));
  1252. // Check if this entity got a targetname set or ignore, if not needed.
  1253. if(!g_bNeedsTargetname[i] || sBuffer[0] != 0)
  1254. {
  1255. // mimicing bots shouldn't collide with this entity.
  1256. g_bShouldNotCollide[iEnt] = true;
  1257. }
  1258. }
  1259. }
  1260.  
  1261. // Add a filter to trigger_multiples
  1262. static iFilter = -1;
  1263. if(iFilter == -1 || !IsValidEntity(iFilter))
  1264. {
  1265. iFilter = CreateEntityByName("filter_activator_name");
  1266. if(iFilter == -1)
  1267. return;
  1268. DispatchKeyValue(iFilter, "filtername", "mimic_bot");
  1269. DispatchKeyValue(iFilter, "Negated", "1");
  1270. DispatchKeyValue(iFilter, "targetname", "mimic_filter");
  1271. // Activate the filter to fetch his filtername.
  1272. ActivateEntity(iFilter);
  1273. }
  1274.  
  1275. // Add the filter to all trigger_multiple
  1276. iEnt = -1;
  1277. while((iEnt = FindEntityByClassname(iEnt, "trigger_multiple")) != -1)
  1278. {
  1279. GetEntPropString(iEnt, Prop_Data, "m_iFilterName", sBuffer, sizeof(sBuffer));
  1280. if(sBuffer[0] != '\0')
  1281. {
  1282. LogError("trigger_multiple %d already has a filter set: %s", iEnt, sBuffer);
  1283. continue;
  1284. }
  1285. DispatchKeyValue(iEnt, "filtername", "mimic_filter");
  1286. ActivateEntity(iEnt);
  1287. }
  1288. }
  1289.  
  1290. stock Timer_GetStyleName(style, String:sName[], maxlen)
  1291. {
  1292. strcopy(sName, maxlen, g_Physics[style][StyleName]);
  1293. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement