· 9 years ago · Nov 27, 2016, 03:04 PM
1#include <sourcemod>
2#include <cstrike>
3#include <sdktools>
4#include <sdkhooks>
5#include <smlib>
6
7#define MAX_TYPES 2
8
9new Handle:g_hGhostStartPauseTime,
10 Handle:g_hGhostEndPauseTime;
11
12/* -- Map -- */
13new gI_ReplayTick[MAX_TYPES],
14 gI_ReplayBotClient[MAX_TYPES];
15new Handle:gA_Frames[MAX_TYPES];
16
17new String:gS_BotTime[MAX_TYPES][32], //Run's time
18 String:gS_BotLastName[MAX_TYPES][32], //Last player's name
19 String:gS_BotSteamID[MAX_TYPES][32]; //Player's SteamID
20
21new bool:g_GhostPaused[MAX_TYPES];
22new Float:g_fPauseTime[MAX_TYPES];
23
24//Client
25new Handle:gA_PlayerFrames[MAXPLAYERS+1];
26new Float:EndTime[MAXPLAYERS+1];
27
28new String:gS_Map[128];
29
30new bool:gB_Recording[MAXPLAYERS+1] = {false, ...};
31new bool:gB_Paused[MAXPLAYERS+1] = {false, ...};
32new bool:ThrowedFlash[MAXPLAYERS + 1] = { true, ... };
33new bool:g_Debug = false;
34
35//SQL
36new Handle:g_hSQL = INVALID_HANDLE;
37
38public Plugin:myinfo =
39{
40 name = "Replay bots",
41 author = "korqee",
42 description = "Bot replay for trikz",
43 version = "1.0",
44 url = "+79124197803"
45}
46
47
48/*
49"kq_bot"
50{
51 "driver" "sqlite"
52 "host" "localhost"
53 "database" "kq_bot"
54 "user" "root"
55 "pass" ""
56}
57*/
58
59public OnPluginStart()
60{
61 if(!SQL_CheckConfig("kq_bot"))
62 {
63 SetFailState("[SQLite] 'kq_bot' wasn't found in databases.cfg");
64 return;
65 }
66
67 new String:error[128];
68 g_hSQL = SQLite_UseDatabase("kq_bot", error, 256);
69
70 SQL_TQuery(g_hSQL, SQL_DatabaseCreateCallback, "CREATE TABLE IF NOT EXISTS `players` (`steamid` varchar(32) NOT NULL, `lastname` varchar(32) NOT NULL, PRIMARY KEY (`steamid`))");
71
72 g_hGhostStartPauseTime = CreateConVar("timer_ghoststartpause", "2.0", "How long the ghost will pause before starting its run.");
73 g_hGhostEndPauseTime = CreateConVar("timer_ghostendpause", "2.0", "How long the ghost will pause after it finishes its run.");
74
75 CreateTimer(5.0, BotCheck, INVALID_HANDLE, TIMER_REPEAT);
76
77 RegConsoleCmd("sm_replay_debug", Cmd_DebugMode);
78 RegConsoleCmd("sm_record.start", Command_StartRecord);
79 RegConsoleCmd("sm_record.stop", Command_EndRecord);
80 RegConsoleCmd("sm_record.pause", Command_Pause);
81 RegConsoleCmd("sm_record.unpause", Command_UnPause);
82
83 for(new i = 1; i <= MaxClients; i++)
84 {
85 OnClientPutInServer(i);
86 }
87}
88
89public SQL_DatabaseCreateCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
90{
91 if (hndl == INVALID_HANDLE) LogError(error);
92}
93
94stock bool:IsValidPlayer(client)
95{
96 if (client < 1 || client > MaxClients || !IsClientConnected(client))
97 return false;
98
99 return true;
100}
101
102public OnPluginEnd()
103{
104 ServerCommand("sm_cvar bot_quota 0");
105}
106
107public OnClientPostAdminCheck(client)
108{
109 new String:sAuth[32];
110 GetClientAuthString(client, sAuth, 32);
111
112 new String:sName[32];
113 GetClientName(client, sName, 32);
114
115 decl String:query[512];
116 FormatEx(query, sizeof(query), "INSERT OR REPLACE INTO players (steamid, lastname) VALUES ('%s', '%s');", sAuth, sName);
117
118 SQL_TQuery(g_hSQL, SQL_ConnectCallback, query);
119
120 for(new Type = 0; Type < MAX_TYPES; Type++)
121 LoadName(gS_BotSteamID[Type], Type);
122}
123
124public SQL_ConnectCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
125{
126 if (hndl == INVALID_HANDLE) LogError(error);
127}
128
129// Input Example:
130// sm_record.start <userid>
131// sm_record.start 13
132// It means player (13) is starting recording
133//Also, if you want to test it from client type "sm_rcon " before
134public Action:Command_StartRecord(client, args)
135{
136 decl String:arg[32];
137 GetCmdArg(1, arg, sizeof(arg));
138
139 if (IsValidPlayer(client))
140 {
141
142 gB_Recording[client] = true;
143 gB_Paused[client] = false;
144
145 StartRecord(client);
146 PrintToChatAll("\x03[Replay] %N (%d) started recording", client, client);
147
148 }
149 else
150 {
151 PrintToChatAll("\x03[Replay] %N not valid", client);
152 }
153
154 return Plugin_Stop;
155}
156
157// Input Example:
158// sm_record.stop <userid> <Type> <Time> <validwr>
159// sm_record.stop 14 0 45.67 1
160// it means player (13) who was booster (0) stopped recording with time 14.00212 which had WR (1)
161public Action:Command_EndRecord(client, args)
162{
163 if(args == 4)
164 {
165 new String:sBuffer[4][32];
166 GetCmdArg(1, sBuffer[0], 32);
167 GetCmdArg(2, sBuffer[1], 32);
168 GetCmdArg(3, sBuffer[2], 32);
169 GetCmdArg(4, sBuffer[3], 32);
170
171 new iTarget = StringToInt(sBuffer[0]);
172 if (iTarget != 14)
173 {
174 return Plugin_Continue;
175 }
176
177 new Type = StringToInt(sBuffer[1]);
178 new Float:Time = StringToFloat(sBuffer[2]);
179 new bool:IsValidWR = StringToInt(sBuffer[3]);
180
181 PrintToChatAll("\x03[Replay] %N ended recording", client);
182
183 gB_Recording[client] = false;
184
185 //EndRecord(iTarget, Type, Float:Time, bool:IsValidWR)
186 EndRecord(client, Type, Float:Time, bool:IsValidWR)
187 }
188
189 return Plugin_Stop;
190}
191
192public Action:Command_Pause(client, args)
193{
194 new String:sBuffer[8];
195 GetCmdArg(1, sBuffer, 8);
196 new iTarget = StringToInt(sBuffer);
197
198 PrintToChatAll("\x03[Replay] %N paused recording", iTarget);
199
200 gB_Paused[iTarget] = true;
201
202 return Plugin_Stop;
203}
204
205public Action:Command_UnPause(client, args)
206{
207 new String:sBuffer[8];
208 GetCmdArg(1, sBuffer, 8);
209 new iTarget = StringToInt(sBuffer);
210
211 PrintToChatAll("\x03[Replay] %N unpaused recording", iTarget);
212
213 gB_Paused[iTarget] = false;
214
215 return Plugin_Stop;
216}
217
218public OnEntityCreated(entity, const String:classname[])
219{
220 if (StrEqual(classname, "func_button", true))
221 {
222 SDKHook(entity, SDKHook_Use, OnTrigger);
223 }
224 else
225 {
226 if (StrContains(classname, "trigger_", true) != -1)
227 {
228 SDKHook(entity, SDKHook_StartTouch, OnTrigger);
229 SDKHook(entity, SDKHook_EndTouch, OnTrigger);
230 SDKHook(entity, SDKHook_Touch, OnTrigger);
231 }
232 }
233}
234
235public Action:OnTrigger(entity, other)
236{
237 if(0 < other <= MaxClients)
238 {
239 if(IsClientConnected(other))
240 {
241 if(IsFakeClient(other))
242 {
243 return Plugin_Handled;
244 }
245 }
246 }
247
248 return Plugin_Continue;
249}
250
251public Action:Cmd_DebugMode(client, args)
252{
253 g_Debug = !g_Debug;
254 if(g_Debug) PrintToChat(client, "DEBUG-Replay Mode enabled!");
255 else PrintToChat(client, "DEBUG-Replay Mode disabled!");
256}
257
258public Action:BotCheck(Handle:Timer)
259{
260 new String:sTempFullName[MAX_TYPES][32];
261
262 gI_ReplayTick[0] = gI_ReplayTick[1];
263
264 /* -- Map -- */
265 for(new Type = 0; Type < MAX_TYPES; Type++)
266 {
267 LoadName(gS_BotSteamID[Type], Type);
268
269 if(!StrEqual(gS_BotTime[Type], ""))
270 {
271 FormatEx(sTempFullName[Type], MAX_NAME_LENGTH, "%s • %s", gS_BotTime[Type], gS_BotLastName[Type]);
272 }
273
274 if(gI_ReplayBotClient[Type] > 0)
275 {
276 if(!IsPlayerAlive(gI_ReplayBotClient[Type]))
277 {
278 CS_RespawnPlayer(gI_ReplayBotClient[Type]);
279 }
280
281 SetClientInfo(gI_ReplayBotClient[Type], "name", sTempFullName[Type]);
282
283 SetEntProp(gI_ReplayBotClient[Type], Prop_Data, "m_takedamage", 0, 1);
284
285 //SetEntProp(gI_ReplayBotClient[Type], Prop_Send, "m_nSolidType", 0); //invisible4triggers
286 SetEntProp(gI_ReplayBotClient[Type], Prop_Data, "m_nSolidType", 0);
287 }
288 }
289}
290
291public OnClientPutInServer(client)
292{
293 gA_PlayerFrames[client] = CreateArray(8);
294}
295
296public OnMapStart()
297{
298 /* -- Map -- */
299 for(new Type = 0; Type < MAX_TYPES; Type++)
300 {
301 gI_ReplayTick[Type] = 0;
302 gI_ReplayBotClient[Type] = 0;
303 FormatEx(gS_BotTime[Type], MAX_NAME_LENGTH, "");
304 FormatEx(gS_BotLastName[Type], MAX_NAME_LENGTH, "Loading...");
305 FormatEx(gS_BotSteamID[Type], MAX_NAME_LENGTH, "No SteamID");
306 }
307
308 GetCurrentMap(gS_Map, 128);
309 RemoveMapPath(gS_Map, gS_Map, 128);
310
311 new String:sTempMap[140];
312 Format(sTempMap, 140, "maps/%s.nav", gS_Map);
313
314 if(!FileExists(sTempMap))
315 {
316 File_Copy("maps/base.nav", sTempMap);
317
318 ForceChangeLevel(gS_Map, ".nav file generate");
319
320 return;
321 }
322
323 ServerCommand("bot_kick");
324
325 new bot_quota = 0;
326
327 for(new i = 0; i < MAX_TYPES; i++)
328 bot_quota++;
329
330 for(new n = 0; n < bot_quota; n++)
331 gI_ReplayTick[n] = 0;
332
333 ServerCommand("sm_cvar bot_quota %d", bot_quota);
334
335 new Handle:bot_join_after_player = FindConVar("bot_join_after_player");
336 SetConVarString(bot_join_after_player, "0");
337
338 new Handle:bot_chatter = FindConVar("bot_chatter");
339 SetConVarString(bot_chatter, "off");
340
341 CreateTimer(10.0, LoadTimerReplays);
342
343 CreateTimer(0.1, HUDTimer_CSS, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
344}
345
346RestartBots()
347{
348 for(new i = 0; i < MAX_TYPES; i++)
349 {
350 gI_ReplayTick[i] = 0;
351 }
352}
353
354public Action:HUDTimer_CSS(Handle:timer)
355{
356 for (new client = 1; client <= MaxClients; client++)
357 {
358 if (IsClientInGame(client) && !IsFakeClient(client))
359 Update_BotHUDS(client);
360 }
361
362 return Plugin_Continue;
363}
364
365Update_BotHUDS(client)
366{
367 new iClientToShow, iObserverMode;
368
369 // Show own buttons by default
370 iClientToShow = client;
371
372 // Get target he's spectating
373 if(!IsPlayerAlive(client) || IsClientObserver(client))
374 {
375 iObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode");
376
377 //if(iObserverMode == SPECMODE_FIRSTPERSON || iObserverMode == SPECMODE_3RDPERSON)
378 if(iObserverMode == 4 || iObserverMode == 5)
379 {
380 iClientToShow = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget");
381
382 // Check client index
383 if(iClientToShow <= 0 || iClientToShow > MaxClients)
384 return;
385 }
386 else
387 {
388 return; // don't proceed, if in freelook..
389 }
390 }
391
392 if(IsFakeClient(iClientToShow))
393 {
394 new Float:fVelocity[3];
395 GetEntPropVector(iClientToShow, Prop_Data, "m_vecVelocity", fVelocity);
396 new Float:currentspeed = SquareRoot(Pow(fVelocity[0],2.0)+Pow(fVelocity[1],2.0)); //player speed (units per secound)XY
397
398
399 new iSize[2];
400 new Float:fPTS[2];
401 new String:sTime[2][32];
402 new Float:fTempTime[2];
403
404 //FormatSeconds(Float:time, String:newtime[], newtimesize)
405
406 /* -- Map -- */
407 for(new i = 0; i < MAX_TYPES; i++)
408 {
409 if(gA_Frames[i] != INVALID_HANDLE)
410 iSize[i] = GetArraySize(gA_Frames[i]) - 3;
411
412 fPTS[i] = float(gI_ReplayTick[i]) / float(iSize[i]) * 100.0;
413
414 fTempTime[i] = float(gI_ReplayTick[i]) / 100.0;
415
416 FormatSeconds(fTempTime[i], sTime[i], 32);
417
418 if(fPTS[i] > 100.0) fPTS[i] = 100.0;
419 else if(fPTS[i] < 0.0) fPTS[i] = 0.0;
420
421 if(iClientToShow == gI_ReplayBotClient[i])
422 PrintHintText(client, "%s\n-\n( %s )\n[ %.2f\%%%% ]\n\nSpeed: %d", gS_BotLastName[i], sTime[i], fPTS[i], RoundToFloor(currentspeed));
423 }
424 }
425}
426
427public OnMapEnd()
428{
429 // Remove ghost to get a clean start next map
430 ServerCommand("bot_kick all");
431
432 /* -- Map -- */
433 for(new i = 0; i < MAX_TYPES; i++)
434 {
435 gI_ReplayTick[i] = 0;
436 gI_ReplayBotClient[i] = 0;
437 FormatEx(gS_BotTime[i], MAX_NAME_LENGTH, "");
438 FormatEx(gS_BotLastName[i], MAX_NAME_LENGTH, "Loading...");
439 FormatEx(gS_BotSteamID[i], MAX_NAME_LENGTH, "No SteamID");
440 }
441}
442
443public Action:LoadTimerReplays(Handle:Timer)
444{
445 new String:sPath[PLATFORM_MAX_PATH];
446 BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "data/trikzreplay");
447
448 if(!DirExists(sPath))
449 {
450 CreateDirectory(sPath, 511);
451 }
452
453 /* -- Map -- */
454 for(new i = 0; i < MAX_TYPES; i++)
455 {
456 gA_Frames[i] = CreateArray(8);
457 LoadReplay(i);
458 }
459}
460
461public bool:LoadReplay(Type)
462{
463 new String:sPath[PLATFORM_MAX_PATH];
464 BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "data/trikzreplay/%s_%d.rec", gS_Map, Type);
465
466 if(FileExists(sPath))
467 {
468 new Handle:hFile = OpenFile(sPath, "r");
469
470 ReadFileLine(hFile, gS_BotTime[Type], 32);
471 ReadFileLine(hFile, gS_BotSteamID[Type], 32);
472
473 TrimString(gS_BotTime[Type]);
474 TrimString(gS_BotSteamID[Type]);
475
476 new String:sLine[320];
477 new String:sExplodedLine[8][64];
478
479 ReadFileLine(hFile, sLine, 320);
480
481 new iSize = 0;
482
483 while(!IsEndOfFile(hFile))
484 {
485 ReadFileLine(hFile, sLine, 320);
486 ExplodeString(sLine, "|", sExplodedLine, 8, 64);
487
488 iSize = GetArraySize(gA_Frames[Type]) + 1;
489 ResizeArray(gA_Frames[Type], iSize);
490
491 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[0]), 0);
492 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[1]), 1);
493 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[2]), 2);
494 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[3]), 3);
495 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[4]), 4);
496 SetArrayCell(gA_Frames[Type], iSize - 1, StringToInt(sExplodedLine[5]), 5);
497 SetArrayCell(gA_Frames[Type], iSize - 1, StringToInt(sExplodedLine[6]), 6);
498 SetArrayCell(gA_Frames[Type], iSize - 1, StringToInt(sExplodedLine[7]), 7);
499 }
500
501 CloseHandle(hFile);
502 }
503
504 return true;
505}
506
507public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
508{
509 if(IsFakeClient(client))
510 {
511 for(new i = 0; i < MAX_TYPES; i++)
512 {
513 if(gI_ReplayBotClient[i] == 0)
514 {
515 gI_ReplayBotClient[i] = client;
516
517 new String:sBotTag[32] = "N REPLAY";
518
519 CS_SetClientClanTag(gI_ReplayBotClient[i], sBotTag);
520 SetClientInfo(gI_ReplayBotClient[i], "name", "No Record");
521 break;
522 }
523 }
524 }
525
526 return true;
527}
528
529public OnClientDisconnect(client)
530{
531 /* -- Map -- */
532 for(new i = 0; i < MAX_TYPES; i++)
533 {
534 if(client == gI_ReplayBotClient[i])
535 {
536 gI_ReplayBotClient[i] = 0;
537 }
538 }
539}
540
541public SaveReplay(client, Type)
542{
543 new String:sPath[PLATFORM_MAX_PATH];
544 BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "data/trikzreplay/%s_%d.rec", gS_Map, Type);
545
546 if(DirExists(sPath))
547 {
548 DeleteFile(sPath);
549 }
550
551 new Handle:hFile = OpenFile(sPath, "w");
552
553 WriteFileLine(hFile, gS_BotTime[Type]);
554 WriteFileLine(hFile, gS_BotSteamID[Type]);
555
556 new iSize = GetArraySize(gA_Frames[Type]);
557
558 new String:sBuffer[512];
559
560 for(new i = 0; i < iSize; i++)
561 {
562 FormatEx(sBuffer, sizeof(sBuffer), "%f|%f|%f|%f|%f|%d|%d|%d", GetArrayCell(gA_Frames[Type], i, 0),
563 GetArrayCell(gA_Frames[Type], i, 1),
564 GetArrayCell(gA_Frames[Type], i, 2),
565 GetArrayCell(gA_Frames[Type], i, 3),
566 GetArrayCell(gA_Frames[Type], i, 4),
567 GetArrayCell(gA_Frames[Type], i, 5),
568 GetArrayCell(gA_Frames[Type], i, 6),
569 GetArrayCell(gA_Frames[Type], i, 7));
570
571 WriteFileLine(hFile, sBuffer);
572 }
573
574 CloseHandle(hFile);
575}
576
577
578public StartRecord(client)
579{
580 if(!IsFakeClient(client))
581 {
582 ClearArray(gA_PlayerFrames[client]);
583 }
584}
585
586public EndRecord(client, Type, Float:Time, bool:IsValidWR)
587{
588 if(IsValidWR)
589 {
590 EndTime[client] = GetEngineTime() + 3;
591
592 GetClientName(client, gS_BotLastName[Type], 32);
593 FormatSeconds(Time, gS_BotTime[Type], 32);
594 GetClientAuthString(client, gS_BotSteamID[Type], 32);
595
596 new String:sTempFullName[32];
597 FormatEx(sTempFullName, 32, "%s • %s", gS_BotTime[Type], gS_BotLastName[Type]);
598
599 SetClientInfo(gI_ReplayBotClient[Type], "name", sTempFullName);
600
601 gI_ReplayTick[Type] = 0;
602
603 gA_Frames[Type] = CloneArray(gA_PlayerFrames[client]);
604
605 ClearArray(gA_PlayerFrames[client]);
606 SaveReplay(client, Type);
607 }
608}
609
610public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon)
611{
612 if(IsPlayerAlive(client))
613 {
614 if(!IsFakeClient(client))
615 {
616 if(gB_Recording[client] && !gB_Paused[client])
617 {
618 // Weapons part
619 new CSWeaponID:CSWeapon;
620 new iNewWeapon = Client_GetActiveWeapon(client);
621 if (IsValidEntity(iNewWeapon) && IsValidEdict(iNewWeapon))
622 {
623 new String:sClassName[64];
624 GetEdictClassname(iNewWeapon, sClassName, 64);
625 ReplaceString(sClassName, 64, "weapon_", "");
626 new String:sWeaponAlias[64];
627 CS_GetTranslatedWeaponAlias(sClassName, sWeaponAlias, 64);
628 new CSWeaponID:weaponId = CS_AliasToWeaponID(sWeaponAlias);
629 CSWeapon = weaponId;
630 }
631
632 // Record player movement data
633 new iSize = GetArraySize(gA_PlayerFrames[client]);
634 ResizeArray(gA_PlayerFrames[client], iSize + 1);
635
636 new Float:vPos[3], Float:vAng[3];
637 GetEntPropVector(client, PropType:1, "m_vecOrigin", vPos, 0);
638 GetClientEyeAngles(client, vAng);
639
640 SetArrayCell(gA_PlayerFrames[client], iSize, vPos[0], 0);
641 SetArrayCell(gA_PlayerFrames[client], iSize, vPos[1], 1);
642 SetArrayCell(gA_PlayerFrames[client], iSize, vPos[2], 2);
643 SetArrayCell(gA_PlayerFrames[client], iSize, vAng[0], 3);
644 SetArrayCell(gA_PlayerFrames[client], iSize, vAng[1], 4);
645 SetArrayCell(gA_PlayerFrames[client], iSize, buttons, 5);
646 SetArrayCell(gA_PlayerFrames[client], iSize, impulse, 6);
647 SetArrayCell(gA_PlayerFrames[client], iSize, CSWeapon, 7);
648 }
649 }
650 else
651 {
652 /* -- Map -- */
653 for(new Type = 0; Type < MAX_TYPES; Type++)
654 {
655 if(client == gI_ReplayBotClient[Type] && gA_Frames[Type] != INVALID_HANDLE)
656 {
657 new iSize = GetArraySize(gA_Frames[Type]);
658 new Float:vPos[3], Float:vAng[3];
659
660 //g_fStartReplayTime[Type] = g_fPauseTime[Type] + GetConVarFloat(g_hGhostStartPauseTime);
661
662 if(gI_ReplayTick[Type] == 0)
663 {
664 if(iSize > 0)
665 {
666 vPos[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 0);
667 vPos[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 1);
668 vPos[2] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 2);
669 vAng[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 3);
670 vAng[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 4);
671 buttons = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5);
672 impulse = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 6);
673 Client_RemoveAllWeapons(gI_ReplayBotClient[Type], "", false);
674
675 TeleportEntity(gI_ReplayBotClient[Type], vPos, vAng, Float:{0.0, 0.0, 0.0});
676 }
677
678 if(g_GhostPaused[Type] == false)
679 {
680 g_GhostPaused[Type] = true;
681 g_fPauseTime[Type] = GetEngineTime();
682 }
683
684 if(GetEngineTime() > g_fPauseTime[Type] + GetConVarFloat(g_hGhostStartPauseTime))
685 {
686 g_GhostPaused[Type] = false;
687 gI_ReplayTick[Type]++;
688 }
689 }
690 else if(gI_ReplayTick[Type] == (iSize - 1))
691 {
692 if(iSize > 0)
693 {
694 vPos[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 0);
695 vPos[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 1);
696 vPos[2] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 2);
697 vAng[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 3);
698 vAng[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 4);
699 buttons = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5);
700 impulse = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 6);
701
702 TeleportEntity(gI_ReplayBotClient[Type], vPos, vAng, Float:{0.0, 0.0, 0.0});
703 }
704
705 if(g_GhostPaused[Type] == false)
706 {
707 g_GhostPaused[Type] = true;
708 g_fPauseTime[Type] = GetEngineTime();
709 }
710
711 if(GetEngineTime() > g_fPauseTime[Type] + GetConVarFloat(g_hGhostEndPauseTime))
712 {
713 g_GhostPaused[Type] = false;
714 RestartBots();
715 }
716 }
717 else if(gI_ReplayTick[Type] < iSize)
718 {
719 new Float:vPos2[3];
720 GetEntPropVector(gI_ReplayBotClient[Type], PropType:1, "m_vecOrigin", vPos2, 0);
721
722 vPos[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 0);
723 vPos[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 1);
724 vPos[2] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 2);
725 vAng[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 3);
726 vAng[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 4);
727 buttons = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5);
728 impulse = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 6);
729
730
731 if(GetVectorDistance(vPos, vPos2) > 50.0)
732 {
733 TeleportEntity(gI_ReplayBotClient[Type], vPos, vAng, NULL_VECTOR);
734 }
735 else
736 {
737 // Get the new velocity from the the 2 points
738 new Float:vVel[3];
739 MakeVectorFromPoints(vPos2, vPos, vVel);
740 ScaleVector(vVel, 100.0);
741
742 TeleportEntity(gI_ReplayBotClient[Type], NULL_VECTOR, vAng, vVel);
743 }
744
745 if(GetEntityFlags(gI_ReplayBotClient[Type]) & FL_ONGROUND)
746 SetEntityMoveType(gI_ReplayBotClient[Type], MOVETYPE_WALK);
747 else
748 SetEntityMoveType(gI_ReplayBotClient[Type], MOVETYPE_NOCLIP);
749
750 gI_ReplayTick[Type] = (gI_ReplayTick[Type] + 1) % iSize;
751
752 new CSWeaponID:newWeapon = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 7);
753 if (newWeapon)
754 {
755 new iCurrentWeapon = Client_GetActiveWeapon(client);
756 new CSWeaponID:CurrentweaponId;
757 if (IsValidEntity(iCurrentWeapon) && IsValidEdict(iCurrentWeapon))
758 {
759 new String:sClassName[64];
760 GetEdictClassname(iCurrentWeapon, sClassName, 64);
761 ReplaceString(sClassName, 64, "weapon_", "");
762 new String:sWeaponAlias[64];
763 CS_GetTranslatedWeaponAlias(sClassName, sWeaponAlias, 64);
764 CurrentweaponId = CS_AliasToWeaponID(sWeaponAlias);
765 if((GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5) & IN_ATTACK)
766 && !(GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type] + 1, 5) & IN_ATTACK)
767 && StrEqual("flashbang", sClassName, false)
768 && ThrowedFlash[client])
769 {
770 ThrowFlash(client);
771 }
772 }
773 if (CurrentweaponId != newWeapon)
774 {
775 Client_RemoveAllWeapons(client, "", false);
776
777 decl String:sAlias[64];
778 CS_WeaponIDToAlias(newWeapon, sAlias, 64);
779 Format(sAlias, 64, "weapon_%s", sAlias);
780 GivePlayerItem(client, sAlias, 0);
781 }
782 }
783 else
784 {
785 Client_RemoveAllWeapons(client, "", false);
786 }
787
788 if(g_GhostPaused[Type] == true)
789 {
790 if(GetEntityMoveType(gI_ReplayBotClient[Type]) != MOVETYPE_NONE)
791 {
792 SetEntityMoveType(gI_ReplayBotClient[Type], MOVETYPE_NONE);
793 }
794 }
795 }
796 //gI_ReplayTick[Type] = gI_HUDTick[Type];
797 }
798 }
799 /* -- End Map -- */
800 }
801 }
802
803 return Plugin_Changed;
804}
805
806ThrowFlash(client)
807{
808 //PrintToChatAll("ThrowedFlash[client] = false");
809
810 ThrowedFlash[client] = false;
811
812 CreateTimer(0.2, ExpireCoolDown, client);
813}
814
815public Action:ExpireCoolDown(Handle:Timer, any:client)
816{
817 //PrintToChatAll("HERE!!!");
818 Client_RemoveAllWeapons(client, "", false);
819 GivePlayerItem(client, "weapon_flashbang", 0);
820
821 ThrowedFlash[client] = true;
822}
823
824stock SubString(const String:source[], start, len, String:destination[], maxlen)
825{
826 if(maxlen < 1)
827 {
828 ThrowError("Destination size must be 1 or greater, but was %d", maxlen);
829 }
830
831 if(len == 0)
832 {
833 destination[0] = '\0';
834
835 return true;
836 }
837
838 if(start < 0)
839 {
840 start = strlen(source) + start;
841
842 if(start < 0)
843 {
844 start = 0;
845 }
846 }
847
848 if(len < 0)
849 {
850 len = strlen(source) + len - start;
851
852 if(len < 0)
853 {
854 return false;
855 }
856 }
857
858 new realLength = len + 1 < maxlen? len + 1:maxlen;
859
860 strcopy(destination, realLength, source[start]);
861
862 return true;
863}
864
865stock RemoveMapPath(const String:map[], String:destination[], maxlen)
866{
867 if(strlen(map) < 1)
868 {
869 ThrowError("Bad map name: %s", map);
870 }
871
872 new pos = FindCharInString(map, '/', true);
873
874 if(pos == -1)
875 {
876 pos = FindCharInString(map, '\\', true);
877
878 if(pos == -1)
879 {
880 strcopy(destination, maxlen, map);
881 return false;
882 }
883 }
884
885 new len = strlen(map) - 1 - pos;
886
887 SubString(map, pos + 1, len, destination, maxlen);
888
889 return true;
890}
891
892FormatSeconds(Float:time, String:newtime[], newtimesize)
893{
894 new iTemp = RoundToFloor(time);
895
896 new iHours;
897 new iMinutes;
898
899 if(iTemp > 3600)
900 {
901 iHours = RoundToFloor(iTemp / 3600.0);
902 iTemp %= 3600;
903 }
904
905 new String:sHours[8];
906
907 if(iHours < 10)
908 {
909 FormatEx(sHours, 8, "0%d", iHours);
910 }
911 else
912 {
913 FormatEx(sHours, 8, "%d", iHours);
914 }
915
916 if(iTemp >= 60)
917 {
918 iMinutes = RoundToFloor(iTemp / 60.0);
919 iTemp %= 60;
920 }
921
922 new String:sMinutes[8];
923
924 if(iMinutes < 10)
925 {
926 FormatEx(sMinutes, 8, "0%d", iMinutes);
927 }
928 else
929 {
930 FormatEx(sMinutes, 8, "%d", iMinutes);
931 }
932
933 new Float:fSeconds = ((iTemp) + time - RoundToFloor(time));
934
935 new String:sSeconds[16];
936
937 if(fSeconds < 10)
938 {
939 FormatEx(sSeconds, 16, "0%.02f", fSeconds);
940 }
941 else
942 {
943 FormatEx(sSeconds, 16, "%.02f", fSeconds);
944 }
945
946 if(iHours > 0)
947 {
948 FormatEx(newtime, newtimesize, "%s:%s:%s", sHours, sMinutes, sSeconds);
949 }
950 else if(iMinutes > 0)
951 {
952 FormatEx(newtime, newtimesize, "%s:%s", sMinutes, sSeconds);
953 }
954 else
955 {
956 FormatEx(newtime, newtimesize, "00:%.02f", fSeconds);
957 }
958}
959
960LoadName(String:Auth[], Type)
961{
962 if (g_hSQL != INVALID_HANDLE)
963 {
964 decl String:query[128];
965 FormatEx(query, sizeof(query), "SELECT `lastname` FROM players WHERE `steamid` = '%s'", Auth);
966 SQL_TQuery(g_hSQL, LoadNameCallback, query, Type, DBPrio_Normal);
967 }
968}
969
970public LoadNameCallback(Handle:owner, Handle:hndl, const String:error[], any:Type)
971{
972 if (hndl == INVALID_HANDLE)
973 {
974 LogError(error);
975 }
976
977 while (SQL_FetchRow(hndl))
978 {
979 SQL_FetchString(hndl, 0, gS_BotLastName[Type], 32);
980 }
981}