· 9 years ago · Nov 26, 2016, 06:10 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, ...};
32
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 13 0 14.00212 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 /* -- Map -- */
263 for(new Type = 0; Type < MAX_TYPES; Type++)
264 {
265 LoadName(gS_BotSteamID[Type], Type);
266
267 if(!StrEqual(gS_BotTime[Type], ""))
268 {
269 FormatEx(sTempFullName[Type], MAX_NAME_LENGTH, "%s • %s", gS_BotTime[Type], gS_BotLastName[Type]);
270 }
271
272 if(gI_ReplayBotClient[Type] > 0)
273 {
274 if(!IsPlayerAlive(gI_ReplayBotClient[Type]))
275 {
276 CS_RespawnPlayer(gI_ReplayBotClient[Type]);
277 }
278
279 SetClientInfo(gI_ReplayBotClient[Type], "name", sTempFullName[Type]);
280
281 SetEntProp(gI_ReplayBotClient[Type], Prop_Data, "m_takedamage", 0, 1);
282
283 //SetEntProp(gI_ReplayBotClient[Type], Prop_Send, "m_nSolidType", 0); //invisible4triggers
284 SetEntProp(gI_ReplayBotClient[Type], Prop_Data, "m_nSolidType", 0);
285 }
286 }
287}
288
289public OnClientPutInServer(client)
290{
291 gA_PlayerFrames[client] = CreateArray(8);
292}
293
294public OnMapStart()
295{
296
297 /* -- Map -- */
298 for(new Type = 0; Type < MAX_TYPES; Type++)
299 {
300 gI_ReplayTick[Type] = 0;
301 gI_ReplayBotClient[Type] = 0;
302 FormatEx(gS_BotTime[Type], MAX_NAME_LENGTH, "");
303 FormatEx(gS_BotLastName[Type], MAX_NAME_LENGTH, "Nyastle");
304 FormatEx(gS_BotSteamID[Type], MAX_NAME_LENGTH, "No SteamID");
305 }
306
307 GetCurrentMap(gS_Map, 128);
308 RemoveMapPath(gS_Map, gS_Map, 128);
309
310 new String:sTempMap[140];
311 Format(sTempMap, 140, "maps/%s.nav", gS_Map);
312
313 if(!FileExists(sTempMap))
314 {
315 File_Copy("maps/base.nav", sTempMap);
316
317 ForceChangeLevel(gS_Map, ".nav file generate");
318
319 return;
320 }
321
322 ServerCommand("bot_kick");
323
324 new bot_quota = 0;
325
326 for(new i = 0; i < MAX_TYPES; i++)
327 {
328 bot_quota++;
329 }
330
331 for(new n = 0; n < bot_quota; n++)
332 gI_ReplayTick[n] = 0;
333
334 ServerCommand("sm_cvar bot_quota %d", bot_quota);
335
336 new Handle:bot_join_after_player = FindConVar("bot_join_after_player");
337 SetConVarString(bot_join_after_player, "0");
338
339 new Handle:bot_chatter = FindConVar("bot_chatter");
340 SetConVarString(bot_chatter, "off");
341
342 CreateTimer(3.2, LoadTimerReplays);
343
344 CreateTimer(0.1, HUDTimer_CSS, _, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT);
345}
346
347public Action:HUDTimer_CSS(Handle:timer)
348{
349 for (new client = 1; client <= MaxClients; client++)
350 {
351 if (IsClientInGame(client) && !IsFakeClient(client))
352 Update_BotHUDS(client);
353 }
354
355 return Plugin_Continue;
356}
357
358Update_BotHUDS(client)
359{
360 new iClientToShow, iObserverMode;
361
362 // Show own buttons by default
363 iClientToShow = client;
364
365 // Get target he's spectating
366 if(!IsPlayerAlive(client) || IsClientObserver(client))
367 {
368 iObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode");
369
370 //if(iObserverMode == SPECMODE_FIRSTPERSON || iObserverMode == SPECMODE_3RDPERSON)
371 if(iObserverMode == 4 || iObserverMode == 5)
372 {
373 iClientToShow = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget");
374
375 // Check client index
376 if(iClientToShow <= 0 || iClientToShow > MaxClients)
377 return;
378 }
379 else
380 {
381 return; // don't proceed, if in freelook..
382 }
383 }
384
385 if(IsFakeClient(iClientToShow))
386 {
387 new Float:fVelocity[3];
388 GetEntPropVector(iClientToShow, Prop_Data, "m_vecVelocity", fVelocity);
389 new Float:currentspeed = SquareRoot(Pow(fVelocity[0],2.0)+Pow(fVelocity[1],2.0)); //player speed (units per secound)XY
390
391
392 new iSize[2];
393 new Float:fPTS[2];
394 new String:sTime[2][32];
395 new Float:fTempTime[2];
396
397 //FormatSeconds(Float:time, String:newtime[], newtimesize)
398
399 /* -- Map -- */
400 for(new i = 0; i < MAX_TYPES; i++)
401 {
402 iSize[i] = GetArraySize(gA_Frames[i]) - 3;
403 fPTS[i] = float(gI_ReplayTick[i]) / float(iSize[i]) * 100.0;
404
405 fTempTime[i] = float(gI_ReplayTick[i]) / 100.0;
406
407 FormatSeconds(fTempTime[i], sTime[i], 32);
408
409 if(fPTS[i] > 100.0) fPTS[i] = 100.0;
410 else if(fPTS[i] < 0.0) fPTS[i] = 0.0;
411
412 if(iClientToShow == gI_ReplayBotClient[i])
413 PrintHintText(client, "%s\n-\n( %s )\n[ %.2f\%%%% ]\n\nSpeed: %d", gS_BotLastName[i], sTime[i], fPTS[i], RoundToFloor(currentspeed));
414 }
415 }
416}
417
418public OnMapEnd()
419{
420 // Remove ghost to get a clean start next map
421 ServerCommand("bot_kick all");
422
423 /* -- Map -- */
424 for(new i = 0; i < MAX_TYPES; i++)
425 {
426 gI_ReplayTick[i] = 0;
427 gI_ReplayBotClient[i] = 0;
428 FormatEx(gS_BotTime[i], MAX_NAME_LENGTH, "");
429 FormatEx(gS_BotLastName[i], MAX_NAME_LENGTH, "Nyastle");
430 FormatEx(gS_BotSteamID[i], MAX_NAME_LENGTH, "No SteamID");
431 }
432}
433
434public Action:LoadTimerReplays(Handle:Timer)
435{
436 new String:sPath[PLATFORM_MAX_PATH];
437 BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "data/trikzreplay");
438
439 if(!DirExists(sPath))
440 {
441 CreateDirectory(sPath, 511);
442 }
443
444 /* -- Map -- */
445 for(new i = 0; i < MAX_TYPES; i++)
446 {
447 gA_Frames[i] = CreateArray(8);
448 LoadReplay(i);
449 }
450}
451
452public bool:LoadReplay(Type)
453{
454 new String:sPath[PLATFORM_MAX_PATH];
455 BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "data/trikzreplay/%s_%d.rec", gS_Map, Type);
456
457 if(FileExists(sPath))
458 {
459 new Handle:hFile = OpenFile(sPath, "r");
460
461 ReadFileLine(hFile, gS_BotTime[Type], 32);
462 ReadFileLine(hFile, gS_BotSteamID[Type], 32);
463
464 TrimString(gS_BotTime[Type]);
465 TrimString(gS_BotSteamID[Type]);
466
467 new String:sLine[320];
468 new String:sExplodedLine[8][64];
469
470 ReadFileLine(hFile, sLine, 320);
471
472 new iSize = 0;
473
474 while(!IsEndOfFile(hFile))
475 {
476 ReadFileLine(hFile, sLine, 320);
477 ExplodeString(sLine, "|", sExplodedLine, 8, 64);
478
479 iSize = GetArraySize(gA_Frames[Type]) + 1;
480 ResizeArray(gA_Frames[Type], iSize);
481
482 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[0]), 0);
483 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[1]), 1);
484 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[2]), 2);
485 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[3]), 3);
486 SetArrayCell(gA_Frames[Type], iSize - 1, StringToFloat(sExplodedLine[4]), 4);
487 SetArrayCell(gA_Frames[Type], iSize - 1, StringToInt(sExplodedLine[5]), 5);
488 SetArrayCell(gA_Frames[Type], iSize - 1, StringToInt(sExplodedLine[6]), 6);
489 SetArrayCell(gA_Frames[Type], iSize - 1, StringToInt(sExplodedLine[7]), 7);
490 }
491
492 CloseHandle(hFile);
493 }
494
495 return true;
496}
497
498public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
499{
500 if(IsFakeClient(client))
501 {
502 for(new i = 0; i < MAX_TYPES; i++)
503 {
504 if(gI_ReplayBotClient[i] == 0)
505 {
506 gI_ReplayBotClient[i] = client;
507
508 new String:sBotTag[32] = "N REPLAY";
509
510 CS_SetClientClanTag(gI_ReplayBotClient[i], sBotTag);
511 SetClientInfo(gI_ReplayBotClient[i], "name", "No Record");
512 break;
513 }
514 }
515 }
516
517 return true;
518}
519
520public OnClientDisconnect(client)
521{
522 /* -- Map -- */
523 for(new i = 0; i < MAX_TYPES; i++)
524 {
525 if(client == gI_ReplayBotClient[i])
526 {
527 gI_ReplayBotClient[i] = 0;
528 }
529 }
530}
531
532public SaveReplay(client, Type)
533{
534 new String:sPath[PLATFORM_MAX_PATH];
535 BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "data/trikzreplay/%s_%d.rec", gS_Map, Type);
536
537 if(DirExists(sPath))
538 {
539 DeleteFile(sPath);
540 }
541
542 new Handle:hFile = OpenFile(sPath, "w");
543
544 WriteFileLine(hFile, gS_BotTime[Type]);
545 WriteFileLine(hFile, gS_BotSteamID[Type]);
546
547 new iSize = GetArraySize(gA_Frames[Type]);
548
549 new String:sBuffer[512];
550
551 for(new i = 0; i < iSize; i++)
552 {
553 FormatEx(sBuffer, sizeof(sBuffer), "%f|%f|%f|%f|%f|%d|%d|%d", GetArrayCell(gA_Frames[Type], i, 0),
554 GetArrayCell(gA_Frames[Type], i, 1),
555 GetArrayCell(gA_Frames[Type], i, 2),
556 GetArrayCell(gA_Frames[Type], i, 3),
557 GetArrayCell(gA_Frames[Type], i, 4),
558 GetArrayCell(gA_Frames[Type], i, 5),
559 GetArrayCell(gA_Frames[Type], i, 6),
560 GetArrayCell(gA_Frames[Type], i, 7));
561
562 WriteFileLine(hFile, sBuffer);
563 }
564
565 CloseHandle(hFile);
566}
567
568
569public StartRecord(client)
570{
571 if(!IsFakeClient(client))
572 {
573 ClearArray(gA_PlayerFrames[client]);
574 }
575}
576
577public EndRecord(client, Type, Float:Time, bool:IsValidWR)
578{
579 if(IsValidWR)
580 {
581 EndTime[client] = GetEngineTime() + 3;
582
583 GetClientName(client, gS_BotLastName[Type], 32);
584 FormatSeconds(Time, gS_BotTime[Type], 32);
585 GetClientAuthString(client, gS_BotSteamID[Type], 32);
586
587 new String:sTempFullName[32];
588 FormatEx(sTempFullName, 32, "%s • %s", gS_BotTime[Type], gS_BotLastName[Type]);
589
590 SetClientInfo(gI_ReplayBotClient[Type], "name", sTempFullName);
591
592 gI_ReplayTick[Type] = 0;
593
594 gA_Frames[Type] = CloneArray(gA_PlayerFrames[client]);
595
596 ClearArray(gA_PlayerFrames[client]);
597 SaveReplay(client, Type);
598 }
599}
600
601public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon)
602{
603 if(IsPlayerAlive(client))
604 {
605 if(!IsFakeClient(client))
606 {
607 if(gB_Recording[client] && !gB_Paused[client])
608 {
609 // Weapons part
610 new CSWeaponID:CSWeapon;
611 new iNewWeapon = Client_GetActiveWeapon(client);
612 if (IsValidEntity(iNewWeapon) && IsValidEdict(iNewWeapon))
613 {
614 new String:sClassName[64];
615 GetEdictClassname(iNewWeapon, sClassName, 64);
616 ReplaceString(sClassName, 64, "weapon_", "");
617 new String:sWeaponAlias[64];
618 CS_GetTranslatedWeaponAlias(sClassName, sWeaponAlias, 64);
619 new CSWeaponID:weaponId = CS_AliasToWeaponID(sWeaponAlias);
620 CSWeapon = weaponId;
621 }
622
623 // Record player movement data
624 new iSize = GetArraySize(gA_PlayerFrames[client]);
625 ResizeArray(gA_PlayerFrames[client], iSize + 1);
626
627 new Float:vPos[3], Float:vAng[3];
628 GetEntPropVector(client, PropType:1, "m_vecOrigin", vPos, 0);
629 GetClientEyeAngles(client, vAng);
630
631 SetArrayCell(gA_PlayerFrames[client], iSize, vPos[0], 0);
632 SetArrayCell(gA_PlayerFrames[client], iSize, vPos[1], 1);
633 SetArrayCell(gA_PlayerFrames[client], iSize, vPos[2], 2);
634 SetArrayCell(gA_PlayerFrames[client], iSize, vAng[0], 3);
635 SetArrayCell(gA_PlayerFrames[client], iSize, vAng[1], 4);
636 SetArrayCell(gA_PlayerFrames[client], iSize, buttons, 5);
637 SetArrayCell(gA_PlayerFrames[client], iSize, impulse, 6);
638 SetArrayCell(gA_PlayerFrames[client], iSize, CSWeapon, 7);
639 }
640 }
641 else
642 {
643 /* -- Map -- */
644 for(new Type = 0; Type < MAX_TYPES; Type++)
645 {
646 if(client == gI_ReplayBotClient[Type] && gA_Frames[Type] != INVALID_HANDLE)
647 {
648 new iSize = GetArraySize(gA_Frames[Type]);
649 new Float:vPos[3], Float:vAng[3];
650
651 //g_fStartReplayTime[Type] = g_fPauseTime[Type] + GetConVarFloat(g_hGhostStartPauseTime);
652
653 if(gI_ReplayTick[Type] == 0)
654 {
655 if(iSize > 0)
656 {
657 vPos[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 0);
658 vPos[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 1);
659 vPos[2] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 2);
660 vAng[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 3);
661 vAng[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 4);
662 buttons = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5);
663 impulse = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 6);
664 Client_RemoveAllWeapons(gI_ReplayBotClient[Type], "", false);
665
666 TeleportEntity(gI_ReplayBotClient[Type], vPos, vAng, Float:{0.0, 0.0, 0.0});
667 }
668
669 if(g_GhostPaused[Type] == false)
670 {
671 g_GhostPaused[Type] = true;
672 g_fPauseTime[Type] = GetEngineTime();
673 }
674
675 if(GetEngineTime() > g_fPauseTime[Type] + GetConVarFloat(g_hGhostStartPauseTime))
676 {
677 g_GhostPaused[Type] = false;
678 gI_ReplayTick[Type]++;
679 }
680 }
681 else if(gI_ReplayTick[Type] == (iSize - 1))
682 {
683 if(iSize > 0)
684 {
685 vPos[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 0);
686 vPos[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 1);
687 vPos[2] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 2);
688 vAng[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 3);
689 vAng[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 4);
690 buttons = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5);
691 impulse = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 6);
692
693 TeleportEntity(gI_ReplayBotClient[Type], vPos, vAng, Float:{0.0, 0.0, 0.0});
694 }
695
696 if(g_GhostPaused[Type] == false)
697 {
698 g_GhostPaused[Type] = true;
699 g_fPauseTime[Type] = GetEngineTime();
700 }
701
702 if(GetEngineTime() > g_fPauseTime[Type] + GetConVarFloat(g_hGhostEndPauseTime))
703 {
704 g_GhostPaused[Type] = false;
705 gI_ReplayTick[Type] = (gI_ReplayTick[Type] + 1) % iSize;
706 }
707 }
708 else if(gI_ReplayTick[Type] < iSize)
709 {
710 new Float:vPos2[3];
711 GetEntPropVector(gI_ReplayBotClient[Type], PropType:1, "m_vecOrigin", vPos2, 0);
712
713 vPos[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 0);
714 vPos[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 1);
715 vPos[2] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 2);
716 vAng[0] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 3);
717 vAng[1] = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 4);
718 buttons = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 5);
719 impulse = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 6);
720
721 if(GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type] - 1, 5) & IN_ATTACK)
722 {
723 buttons &= ~IN_ATTACK;
724 buttons &= ~IN_ATTACK2;
725 }
726
727 if(GetVectorDistance(vPos, vPos2) > 50.0)
728 {
729 TeleportEntity(gI_ReplayBotClient[Type], vPos, vAng, NULL_VECTOR);
730 }
731 else
732 {
733 // Get the new velocity from the the 2 points
734 new Float:vVel[3];
735 MakeVectorFromPoints(vPos2, vPos, vVel);
736 ScaleVector(vVel, 100.0);
737
738 TeleportEntity(gI_ReplayBotClient[Type], NULL_VECTOR, vAng, vVel);
739 }
740
741 if(GetEntityFlags(gI_ReplayBotClient[Type]) & FL_ONGROUND)
742 SetEntityMoveType(gI_ReplayBotClient[Type], MOVETYPE_WALK);
743 else
744 SetEntityMoveType(gI_ReplayBotClient[Type], MOVETYPE_NOCLIP);
745
746 gI_ReplayTick[Type] = (gI_ReplayTick[Type] + 1) % iSize;
747
748 new CSWeaponID:newWeapon = GetArrayCell(gA_Frames[Type], gI_ReplayTick[Type], 7);
749 if (newWeapon)
750 {
751 new iCurrentWeapon = Client_GetActiveWeapon(client);
752 new CSWeaponID:CurrentweaponId;
753 if (IsValidEntity(iCurrentWeapon) && IsValidEdict(iCurrentWeapon))
754 {
755 new String:sClassName[64];
756 GetEdictClassname(iCurrentWeapon, sClassName, 64);
757 ReplaceString(sClassName, 64, "weapon_", "");
758 new String:sWeaponAlias[64];
759 CS_GetTranslatedWeaponAlias(sClassName, sWeaponAlias, 64);
760 CurrentweaponId = CS_AliasToWeaponID(sWeaponAlias);
761 }
762 if (CurrentweaponId != newWeapon)
763 {
764 Client_RemoveAllWeapons(client, "", false);
765
766 decl String:sAlias[64];
767 CS_WeaponIDToAlias(newWeapon, sAlias, 64);
768 Format(sAlias, 64, "weapon_%s", sAlias);
769 GivePlayerItem(client, sAlias, 0);
770 }
771 }
772 else
773 {
774 Client_RemoveAllWeapons(client, "", false);
775 }
776
777 if(g_GhostPaused[Type] == true)
778 {
779 if(GetEntityMoveType(gI_ReplayBotClient[Type]) != MOVETYPE_NONE)
780 {
781 SetEntityMoveType(gI_ReplayBotClient[Type], MOVETYPE_NONE);
782 }
783 }
784 }
785 //gI_ReplayTick[Type] = gI_HUDTick[Type];
786 }
787 }
788 /* -- End Map -- */
789 }
790 }
791
792 return Plugin_Changed;
793}
794
795stock SubString(const String:source[], start, len, String:destination[], maxlen)
796{
797 if(maxlen < 1)
798 {
799 ThrowError("Destination size must be 1 or greater, but was %d", maxlen);
800 }
801
802 if(len == 0)
803 {
804 destination[0] = '\0';
805
806 return true;
807 }
808
809 if(start < 0)
810 {
811 start = strlen(source) + start;
812
813 if(start < 0)
814 {
815 start = 0;
816 }
817 }
818
819 if(len < 0)
820 {
821 len = strlen(source) + len - start;
822
823 if(len < 0)
824 {
825 return false;
826 }
827 }
828
829 new realLength = len + 1 < maxlen? len + 1:maxlen;
830
831 strcopy(destination, realLength, source[start]);
832
833 return true;
834}
835
836stock RemoveMapPath(const String:map[], String:destination[], maxlen)
837{
838 if(strlen(map) < 1)
839 {
840 ThrowError("Bad map name: %s", map);
841 }
842
843 new pos = FindCharInString(map, '/', true);
844
845 if(pos == -1)
846 {
847 pos = FindCharInString(map, '\\', true);
848
849 if(pos == -1)
850 {
851 strcopy(destination, maxlen, map);
852 return false;
853 }
854 }
855
856 new len = strlen(map) - 1 - pos;
857
858 SubString(map, pos + 1, len, destination, maxlen);
859
860 return true;
861}
862
863FormatSeconds(Float:time, String:newtime[], newtimesize)
864{
865 new iTemp = RoundToFloor(time);
866
867 new iHours;
868 new iMinutes;
869
870 if(iTemp > 3600)
871 {
872 iHours = RoundToFloor(iTemp / 3600.0);
873 iTemp %= 3600;
874 }
875
876 new String:sHours[8];
877
878 if(iHours < 10)
879 {
880 FormatEx(sHours, 8, "0%d", iHours);
881 }
882 else
883 {
884 FormatEx(sHours, 8, "%d", iHours);
885 }
886
887 if(iTemp >= 60)
888 {
889 iMinutes = RoundToFloor(iTemp / 60.0);
890 iTemp %= 60;
891 }
892
893 new String:sMinutes[8];
894
895 if(iMinutes < 10)
896 {
897 FormatEx(sMinutes, 8, "0%d", iMinutes);
898 }
899 else
900 {
901 FormatEx(sMinutes, 8, "%d", iMinutes);
902 }
903
904 new Float:fSeconds = ((iTemp) + time - RoundToFloor(time));
905
906 new String:sSeconds[16];
907
908 if(fSeconds < 10)
909 {
910 FormatEx(sSeconds, 16, "0%.02f", fSeconds);
911 }
912 else
913 {
914 FormatEx(sSeconds, 16, "%.02f", fSeconds);
915 }
916
917 if(iHours > 0)
918 {
919 FormatEx(newtime, newtimesize, "%s:%s:%s", sHours, sMinutes, sSeconds);
920 }
921 else if(iMinutes > 0)
922 {
923 FormatEx(newtime, newtimesize, "%s:%s", sMinutes, sSeconds);
924 }
925 else
926 {
927 FormatEx(newtime, newtimesize, "00:%.02f", fSeconds);
928 }
929}
930
931LoadName(String:Auth[], Type)
932{
933 if (g_hSQL != INVALID_HANDLE)
934 {
935 decl String:query[128];
936 FormatEx(query, sizeof(query), "SELECT `lastname` FROM players WHERE `steamid` = '%s'", Auth);
937 SQL_TQuery(g_hSQL, LoadNameCallback, query, Type, DBPrio_Normal);
938 }
939}
940
941public LoadNameCallback(Handle:owner, Handle:hndl, const String:error[], any:Type)
942{
943 if (hndl == INVALID_HANDLE)
944 {
945 LogError(error);
946 }
947
948 while (SQL_FetchRow(hndl))
949 {
950 SQL_FetchString(hndl, 0, gS_BotLastName[Type], 32);
951 }
952}