· 6 years ago · Jan 27, 2020, 07:12 PM
1#pragma newdecls required
2#pragma semicolon 1
3
4#include <sourcemod>
5#include <cstrike>
6//#include <slidy-timer>
7#include <influx/core>
8#include <influx/zones>
9#include <influx/recording>
10
11#define MAX_TEAMS 12
12#define MAX_TEAM_MEMBERS 10
13#define MAX_STYLES 12
14
15
16float g_flAirAccelerate;
17
18
19// CONVARS
20ConVar g_ConVar_AirAccelerate;
21ConVar g_ConVar_EnableBunnyhopping;
22ConVar g_ConVar_AutoBhop;
23
24ConVar g_ConVar_Auto_AirAccelerate;
25
26ConVar g_cvMaxPasses;
27ConVar g_cvMaxUndos;
28
29int MODE_TAGTEAM = 12;
30
31// invite system
32int g_iInviteStyle[MAXPLAYERS + 1];
33bool g_bCreatingTeam[MAXPLAYERS + 1];
34ArrayList g_aInvitedPlayers[MAXPLAYERS + 1];
35bool g_bInvitedPlayer[MAXPLAYERS + 1][MAXPLAYERS + 1];
36int g_nDeclinedPlayers[MAXPLAYERS + 1];
37ArrayList g_aAcceptedPlayers[MAXPLAYERS + 1];
38
39// teams system
40bool g_bAllowReset[MAXPLAYERS + 1];
41bool g_bAllowStyleChange[MAXPLAYERS + 1];
42
43int g_nUndoCount[MAX_TEAMS];
44bool g_bDidUndo[MAX_TEAMS];
45any g_LastCheckpoint[MAX_TEAMS][eCheckpoint];
46
47char g_cTeamName[MAX_TEAMS][MAX_NAME_LENGTH];
48int g_nPassCount[MAX_TEAMS];
49int g_nRelayCount[MAX_TEAMS];
50int g_iCurrentPlayer[MAX_TEAMS];
51bool g_bTeamTaken[MAX_TEAMS];
52int g_nTeamPlayerCount[MAX_TEAMS];
53
54int g_iTeamIndex[MAXPLAYERS + 1] = { -1, ... };
55int g_iNextTeamMember[MAXPLAYERS + 1];
56char g_cPlayerTeamName[MAXPLAYERS + 1][MAX_NAME_LENGTH];
57
58// records system
59ArrayList g_aCurrentSegmentStartTicks[MAX_TEAMS];
60ArrayList g_aCurrentSegmentPlayers[MAX_TEAMS];
61
62StringMap g_smSegmentPlayerNames[MAX_RUNS][MAX_STYLES];
63
64ArrayList g_aMapTopRecordIds[MAX_RUNS][MAX_STYLES];
65ArrayList g_aMapTopTimes[MAX_RUNS][MAX_STYLES];
66ArrayList g_aMapTopNames[MAX_RUNS][MAX_STYLES];
67
68int g_iRecordId[MAXPLAYERS + 1][MAX_RUNS][MAX_STYLES];
69float g_fPersonalBest[MAXPLAYERS + 1][MAX_RUNS][MAX_STYLES];
70
71// segments loading
72bool g_bFinishedInsertingSegments[MAX_RUNS][MAX_STYLES];
73bool g_bLoadSegmentsAfterInserting[MAX_RUNS][MAX_STYLES];
74
75Database g_hDatabase;
76char g_cMapName[PLATFORM_MAX_PATH];
77
78public Plugin myinfo =
79{
80 name = "Slidy's Timer - Tagteam relay",
81 author = "SlidyBat ~ celly",
82 description = "",
83 version = "1.3.3.7",
84 url = ""
85}
86
87public APLRes AskPluginLoad2( Handle myself, bool late, char[] error, int err_max )
88{
89 CreateNative( "Timer_IsClientInTagTeam", Native_IsClientInTagTeam );
90 CreateNative( "Timer_GetClientTeamIndex", Native_GetClientTeamIndex );
91 CreateNative( "Timer_GetTeamName", Native_GetTeamName );
92
93 RegPluginLibrary( "timer-tagteam" );
94
95 return APLRes_Success;
96}
97
98public void OnPluginStart()
99{
100 if ( (g_ConVar_AirAccelerate = FindConVar( "sv_airaccelerate" )) == null )
101 {
102 SetFailState( INF_CON_PRE..."Couldn't find handle for sv_airaccelerate!" );
103 }
104
105 if ( (g_ConVar_EnableBunnyhopping = FindConVar( "sv_enablebunnyhopping" )) == null )
106 {
107 SetFailState( INF_CON_PRE..."Couldn't find handle for sv_enablebunnyhopping!" );
108 }
109
110
111 if ( (g_ConVar_AutoBhop = FindConVar( "sv_autobunnyhopping" )) == null )
112 {
113 SetFailState( INF_CON_PRE..."Couldn't find handle for sv_autobunnyhopping!" );
114 }
115
116 g_ConVar_AutoBhop.Flags &= ~(FCVAR_REPLICATED | FCVAR_NOTIFY);
117
118
119
120 g_ConVar_Auto_AirAccelerate = CreateConVar( "influx_auto_airaccelerate", "1000", "", FCVAR_NOTIFY );
121 g_ConVar_Auto_AirAccelerate.AddChangeHook( E_CvarChange_Auto_AA );
122
123 g_flAirAccelerate = g_ConVar_Auto_AirAccelerate.FloatValue;
124
125 for( int i = 0; i < MAX_RUNS; i++ )
126 {
127 for( int j = 0; j < MAX_STYLES; j++ )
128 {
129 g_aMapTopRecordIds[i][j] = new ArrayList();
130 g_aMapTopTimes[i][j] = new ArrayList();
131 g_aMapTopNames[i][j] = new ArrayList( ByteCountToCells( MAX_NAME_LENGTH ) );
132 }
133 }
134
135 g_cvMaxPasses = CreateConVar( "sm_timer_tagteam_maxpasses", "-1", "Maximum number of passes a team can make or -1 for unlimited passes", _, true, -1.0, false );
136 g_cvMaxUndos = CreateConVar( "sm_timer_tagteam_maxundos", "3", "Maximum number of undos a team can make or -1 for unlimited undos", _, true, -1.0, false );
137 AutoExecConfig( true, "tagteam", "SlidyTimer" );
138
139 RegConsoleCmd( "sm_teamname", Command_TeamName );
140 RegConsoleCmd( "sm_exitteam", Command_ExitTeam );
141 RegConsoleCmd( "sm_pass", Command_Pass );
142 RegConsoleCmd( "sm_undo", Command_Undo );
143
144 char error[256];
145 g_hDatabase = SQL_Connect( "Slidy-Timer", true, error, sizeof(error) );
146
147 SQL_CreateTables();
148
149 GetCurrentMap( g_cMapName, sizeof(g_cMapName) );
150}
151public void OnAllPluginsLoaded()
152{
153 AddMode();
154}
155
156public void OnPluginEnd()
157{
158 Influx_RemoveMode( MODE_TAGTEAM );
159
160
161 g_ConVar_AutoBhop.Flags |= (FCVAR_REPLICATED | FCVAR_NOTIFY);
162}
163
164public void Influx_OnRequestModes()
165{
166 AddMode();
167}
168
169stock void AddMode()
170{
171 if ( !Influx_AddMode( MODE_TAGTEAM, "Tagteam", "team", "team", 260.0 ) )
172 {
173 SetFailState( INF_CON_PRE..."Couldn't add mode! (%i)", MODE_TAGTEAM );
174 }
175}
176
177public void OnMapStart()
178{
179 GetCurrentMap( g_cMapName, sizeof(g_cMapName) );
180}
181
182public void Timer_OnMapLoaded( int mapid )
183{
184 if( g_hDatabase != null )
185 {
186 SQL_LoadAllMapRecords();
187 }
188}
189
190public void Timer_OnDatabaseLoaded()
191{
192 if( g_hDatabase == null )
193 {
194 char error[256];
195 g_hDatabase = SQL_Connect( "Slidy-Timer", true, error, sizeof(error) );
196 SQL_CreateTables();
197 }
198}
199
200
201/*
202public void Timer_OnReplayLoadedPost( int track, int style, float time, int recordid, ArrayList frames )
203{
204 delete g_smSegmentPlayerNames[track][style];
205
206 if( Timer_StyleHasSetting( style, "tagteam" ) )
207 {
208 SQL_LoadSegments( track, style, recordid );
209 }
210}
211*/
212public Action Influx_OnTimerFinish( int client, int runid, int mode, int style, float time, int flags, char[] errormsg, int error_len )
213{
214 // not tagteam, dont bother
215 int targetmode = MODE_INVALID;
216 int recordid = 1;
217 targetmode = Influx_GetClientMode( client );
218 if(targetmode != MODE_TAGTEAM)
219 {
220 return;
221 }
222 //OnTimerFinishCustom();
223
224 if( g_bFinishedInsertingSegments[runid][mode] )
225 {
226 ////Timer_DebugPrint( "Timer_OnReplaySavedPost: Loading segments" );
227 SQL_LoadSegments( runid, mode, recordid );
228 }
229 else
230 {
231 ////Timer_DebugPrint( "Timer_OnReplaySavedPost: Waiting for segments to be inserted" );
232 g_bLoadSegmentsAfterInserting[runid][mode] = true;
233 }
234}
235
236public void OnClientPutInServer( int client )
237{
238 for( int i = 0; i < MAX_RUNS; i++ )
239 {
240 for( int j = 0; j < MAX_STYLES; j++ )
241 {
242 g_fPersonalBest[client][i][j] = 0.0;
243 g_iRecordId[client][i][j] = -1;
244 }
245 }
246
247 Format( g_cPlayerTeamName[client], sizeof(g_cPlayerTeamName[]), "Team %N", client );
248}
249
250public void Timer_OnClientLoaded( int client, int playerid, bool newplayer )
251{
252 if( !newplayer )
253 {
254 SQL_LoadAllPlayerTimes( client );
255 }
256}
257
258public void OnClientDisconnect( int client )
259{
260 if( !IsFakeClient( client ) && g_iTeamIndex[client] != -1 )
261 {
262 ExitTeam( client );
263 }
264}
265/*
266public Action OnPlayerRunCmd( int client )
267{
268 int tick = Timer_GetReplayBotCurrentFrame( client );
269 int track = Influx_GetReplayRunId( client );
270 int style = Timer_GetReplayBotStyle( client );
271
272 // not a valid replay bot or not currently replaying
273 if( tick == -1 || track == -1 || style == -1 )
274 {
275 return Plugin_Continue;
276 }
277
278 if( g_smSegmentPlayerNames[track][style] == null )
279 {
280 return Plugin_Continue;
281 }
282
283 char sTick[8];
284 IntToString( tick, sTick, sizeof(sTick) );
285 char name[MAX_NAME_LENGTH];
286 if( !g_smSegmentPlayerNames[track][style].GetString( sTick, name, sizeof(name) ) )
287 {
288 return Plugin_Continue;
289 }
290
291 for( int i = 1; i <= MaxClients; i++ )
292 {
293 if( i == client )
294 {
295 continue;
296 }
297
298 if( IsClientInGame( i ) && GetClientObserverTarget( i ) == client )
299 {
300 PrintToChat( i, "{primary}Current section by: {name}%s", name );
301 }
302 }
303
304 return Plugin_Continue;
305}
306*/
307
308public Action Influx_OnClientModeChange( int client, int mode, int lastmode )
309{
310 if ( mode == MODE_TAGTEAM )
311 {
312 if( g_iTeamIndex[client] == -1 ) // not in a team, make them create or join one before changing style
313 {
314 OpenInviteSelectMenu( client, 0, true );
315 PrintToChat( client, "{primary}Created {secondary}%s{primary}! Use {secondary}!teamname {primary}to set your team name.", g_cPlayerTeamName[client] );
316 return Plugin_Handled;
317 }
318
319 UnhookThinks( client );
320
321
322 if ( !Inf_SDKHook( client, SDKHook_PreThinkPost, E_PreThinkPost_Client ) )
323 {
324 return Plugin_Handled;
325 }
326
327 if ( !Inf_SDKHook( client, SDKHook_PostThinkPost, E_PostThinkPost_Client ) )
328 {
329 UnhookThinks( client );
330 return Plugin_Handled;
331 }
332
333
334 Inf_SendConVarValueFloat( client, g_ConVar_AirAccelerate, g_ConVar_Auto_AirAccelerate.FloatValue );
335 Inf_SendConVarValueBool( client, g_ConVar_EnableBunnyhopping, true );
336 Inf_SendConVarValueBool( client, g_ConVar_AutoBhop, true );
337 return Plugin_Continue;
338 }
339 else if ( lastmode == MODE_TAGTEAM )
340 {
341 if( g_iTeamIndex[client] != -1 && !g_bAllowStyleChange[client] )
342 {
343 PrintToChat( client, "{primary}You cannot change style until you leave the team! Type {secondary}!exitteam {primary}to leave your team" );
344 return Plugin_Handled;
345 }
346 if( g_bAllowStyleChange[client] )
347 {
348 g_bAllowStyleChange[client] = false;
349 }
350
351 UnhookThinks( client );
352
353 Inf_SendConVarValueBool( client, g_ConVar_AutoBhop, false );
354 return Plugin_Continue;
355 }
356
357 return Plugin_Continue;
358}
359
360stock void UnhookThinks( int client )
361{
362 SDKUnhook( client, SDKHook_PreThinkPost, E_PreThinkPost_Client );
363 SDKUnhook( client, SDKHook_PostThinkPost, E_PostThinkPost_Client );
364}
365
366public void E_CvarChange_Auto_AA( ConVar convar, const char[] oldval, const char[] newval )
367{
368 g_flAirAccelerate = convar.FloatValue;
369}
370
371public void E_PreThinkPost_Client( int client )
372{
373#if defined DEBUG_THINK
374 PrintToServer( INF_DEBUG_PRE..."PreThinkPost - Auto CS:GO (aa: %.0f)", g_flAirAccelerate );
375#endif
376
377 g_ConVar_AirAccelerate.FloatValue = g_flAirAccelerate;
378 g_ConVar_EnableBunnyhopping.BoolValue = true;
379 g_ConVar_AutoBhop.BoolValue = true;
380}
381
382public void E_PostThinkPost_Client( int client )
383{
384#if defined DEBUG_THINK
385 PrintToServer( INF_DEBUG_PRE..."PostThinkPost - Auto CS:GO (aa: %.0f)", g_flAirAccelerate );
386#endif
387
388 g_ConVar_AutoBhop.BoolValue = false;
389}
390
391/* Chat notify
392public Action Timer_OnTimerFinishPre( int client, int track, int style, float time )
393{
394 if( Timer_StyleHasSetting( style, "tagteam" ) )
395 {
396 char sTime[64];
397 Timer_FormatTime( time, sTime, sizeof(sTime) );
398
399 char sZoneTrack[64];
400 Timer_GetZoneTrackName( track, sZoneTrack, sizeof(sZoneTrack) );
401
402 char sStyleName[32];
403 Timer_GetStyleName( style, sStyleName, sizeof(sStyleName) );
404
405 PrintToChatAll( "[{secondary}%s{white}] {name}%s {primary}finished on {secondary}%s {primary}timer in {secondary}%ss", sStyleName, g_cTeamName[g_iTeamIndex[client]], sZoneTrack, sTime );
406
407 return Plugin_Changed;
408 }
409
410 return Plugin_Continue;
411}*/
412
413/* sm_r here xddd
414public Action Timer_OnClientTeleportToZonePre( int client, int zoneType, int zoneTrack, int subindex )
415{
416 if( g_iTeamIndex[client] != -1 && !g_bAllowReset[client] && !(g_nRelayCount[g_iTeamIndex[client]] == 0 && g_iCurrentPlayer[g_iTeamIndex[client]] == client) )
417 {
418 PrintToChat( client, "{primary}You cannot reset or teleport until you leave the team! Type {secondary}!exitteam {primary}to leave your team" );
419 return Plugin_Handled;
420 }
421 if( g_bAllowReset[client] )
422 {
423 g_bAllowReset[client] = false;
424 }
425
426 return Plugin_Continue;
427}
428*/
429void OpenInviteSelectMenu( int client, int firstItem, bool reset = false )
430{
431 if( reset )
432 {
433 for( int i = 1; i <= MaxClients; i++ )
434 {
435 g_bInvitedPlayer[client][i] = false;
436 }
437
438 g_bCreatingTeam[client] = true;
439 g_iInviteStyle[client] = MODE_TAGTEAM;
440 g_nDeclinedPlayers[client] = 0;
441
442 delete g_aAcceptedPlayers[client];
443 g_aAcceptedPlayers[client] = new ArrayList();
444
445 delete g_aInvitedPlayers[client];
446 g_aInvitedPlayers[client] = new ArrayList();
447 }
448
449 Menu menu = new Menu( InviteSelectMenu_Handler );
450 menu.SetTitle( "Select players to invite:\n \n" );
451
452 for( int i = 1; i <= MaxClients; i++ )
453 {
454 if( i == client || !IsClientInGame( i ) || IsFakeClient( i ) || g_iTeamIndex[i] != -1 )
455 {
456 continue;
457 }
458
459 char name[MAX_NAME_LENGTH + 32];
460 Format( name, sizeof(name), "[%s] %N", g_bInvitedPlayer[client][i] ? "X" : " ", i );
461
462 char userid[8];
463 IntToString( GetClientUserId( i ), userid, sizeof(userid) );
464
465 menu.AddItem( userid, name );
466 }
467
468 menu.AddItem( "send", "Send Invites!", g_aInvitedPlayers[client].Length == 0 ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT );
469
470 menu.DisplayAt( client, firstItem, MENU_TIME_FOREVER );
471}
472
473public int InviteSelectMenu_Handler( Menu menu, MenuAction action, int param1, int param2 )
474{
475 if( action == MenuAction_Select )
476 {
477 char info[8];
478 menu.GetItem( param2, info, sizeof(info) );
479
480 if( StrEqual( info, "send" ) ) // send the invites!
481 {
482 int length = g_aInvitedPlayers[param1].Length;
483 for( int i = 0; i < length; i++ )
484 {
485 SendInvite( param1, GetClientOfUserId( g_aInvitedPlayers[param1].Get( i ) ) );
486 }
487
488 PrintToChat( param1, "Invites sent!" );
489
490 OpenLobbyMenu( param1 );
491 }
492 else
493 {
494 int userid = StringToInt( info );
495 int target = GetClientOfUserId( userid );
496 if( 0 < target <= MaxClients )
497 {
498 g_bInvitedPlayer[param1][target] = !g_bInvitedPlayer[param1][target];
499 if( g_bInvitedPlayer[param1][target] )
500 {
501 g_aInvitedPlayers[param1].Push( userid );
502 }
503 else
504 {
505 int idx = g_aInvitedPlayers[param1].FindValue( userid );
506 if( idx != -1 )
507 {
508 g_aInvitedPlayers[param1].Erase( idx );
509 }
510 }
511 }
512
513 OpenInviteSelectMenu( param1, (param2 / 6) * 6 );
514 }
515 }
516 else if( action == MenuAction_End )
517 {
518 delete menu;
519 }
520}
521
522void SendInvite( int client, int target )
523{
524 Menu menu = new Menu( InviteMenu_Handler );
525
526 char buffer[256];
527 Format( buffer, sizeof(buffer), "%N has invited you to play tagteam!\nAccept?\n \n", client );
528 menu.SetTitle( buffer );
529
530 char userid[8];
531 IntToString( GetClientUserId( client ), userid, sizeof(userid) );
532
533 menu.AddItem( userid, "Yes" );
534 menu.AddItem( userid, "No" );
535
536 menu.Display( target, 20 );
537}
538
539public int InviteMenu_Handler( Menu menu, MenuAction action, int param1, int param2 )
540{
541 if( action == MenuAction_Select )
542 {
543 char info[8];
544 menu.GetItem( param2, info, sizeof(info) );
545
546 int client = GetClientOfUserId( StringToInt( info ) );
547 if( !( 0 < client <= MaxClients ) )
548 {
549 return 0;
550 }
551
552 if( param2 == 0 ) // yes
553 {
554 if( !g_bCreatingTeam[client] )
555 {
556 PrintToChat( param1, "The team has been cancelled or has already started the run" );
557 }
558 if( g_aAcceptedPlayers[client].Length >= MAX_TEAM_MEMBERS )
559 {
560 PrintToChat( param1, "The team is now full, cannot join" );
561 }
562 else
563 {
564 g_aAcceptedPlayers[client].Push( GetClientUserId( param1 ) );
565 OpenLobbyMenu( client );
566 }
567 }
568 else // no
569 {
570 g_nDeclinedPlayers[client]++;
571 PrintToChat( client, "{name}%N {primary}has declined your invite", param1 );
572 }
573
574 //Timer_DebugPrint( "InviteMenu_Handler: %i + %i, %i", g_aAcceptedPlayers[client].Length, g_nDeclinedPlayers[client], g_aInvitedPlayers[client].Length );
575 if( g_aAcceptedPlayers[client].Length + g_nDeclinedPlayers[client] == g_aInvitedPlayers[client].Length ) // everyone responded
576 {
577 FinishInvite( client );
578 }
579 }
580 else if( action == MenuAction_End )
581 {
582 delete menu;
583 }
584
585 return 0;
586}
587
588void OpenLobbyMenu( int client )
589{
590 Menu menu = new Menu( LobbyMenu_Handler );
591
592 char buffer[512];
593 Format( buffer, sizeof(buffer), "%s\n \nMembers:\n%N\n", g_cPlayerTeamName[client], client );
594
595 int length = g_aAcceptedPlayers[client].Length;
596 if( length == 0 )
597 {
598 Format( buffer, sizeof(buffer), "%s \n", buffer );
599 }
600
601 for( int i = 0; i < length; i++ )
602 {
603 Format( buffer, sizeof(buffer), "%s%N\n", buffer, GetClientOfUserId( g_aAcceptedPlayers[client].Get( i ) ) );
604
605 if( i == length - 1 )
606 {
607 Format( buffer, sizeof(buffer), "%s \n", buffer );
608 }
609 }
610
611 menu.SetTitle( buffer );
612
613 menu.AddItem( "start", "Start", (length > 0) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED );
614 menu.AddItem( "cancel", "Cancel" );
615
616 menu.ExitButton = false;
617 menu.Display( client, MENU_TIME_FOREVER );
618}
619
620public int LobbyMenu_Handler( Menu menu, MenuAction action, int param1, int param2 )
621{
622 if( action == MenuAction_Select )
623 {
624 if( param1 == 0 ) // start
625 {
626 FinishInvite( param1 );
627 }
628 else if( param1 == 1 ) // cancel
629 {
630 CancelInvite( param1 );
631 }
632 }
633 else if( action == MenuAction_End )
634 {
635 delete menu;
636 }
637}
638
639void FinishInvite( int client )
640{
641 g_bCreatingTeam[client] = false;
642
643 int length = g_aAcceptedPlayers[client].Length;
644
645 if( length < 1 )
646 {
647 PrintToChat( client, "{primary}Not enough players to create a team" );
648 return;
649 }
650
651 int[] members = new int[length + 1];
652
653 members[0] = client;
654 for( int i = 0; i < length; i++ )
655 {
656 members[i + 1] = GetClientOfUserId( g_aAcceptedPlayers[client].Get( i ) );
657 }
658
659 CreateTeam( members, length + 1, g_iInviteStyle[client] );
660
661 int letters;
662 char buffer[512];
663 for( int i = 0; i <= length; i++ )
664 {
665 letters += Format( buffer, sizeof(buffer), "%s{name}%N{primary}, ", buffer, members[i] );
666 }
667 buffer[letters - 3] = '\0';
668
669 PrintToTeam( g_iTeamIndex[client], "{secondary}%s {primary}has been assembled! Members: %s", g_cTeamName[g_iTeamIndex[client]], buffer );
670}
671
672void CancelInvite( int client )
673{
674 g_bCreatingTeam[client] = false;
675}
676
677void CreateTeam( int[] members, int memberCount, int style )
678{
679 //Timer_DebugPrint( "CreateTeam: memberCount=%i", memberCount );
680
681 int teamindex = -1;
682 for( int i = 0; i < MAX_TEAMS; i++ )
683 {
684 if( !g_bTeamTaken[i] )
685 {
686 teamindex = i;
687 break;
688 }
689 }
690
691 if( teamindex == -1 )
692 {
693 LogError( "Not enough teams" );
694 return;
695 }
696
697 g_nUndoCount[teamindex] = 0;
698 g_nPassCount[teamindex] = 0;
699 g_nRelayCount[teamindex] = 0;
700 g_bTeamTaken[teamindex] = true;
701 g_nTeamPlayerCount[teamindex] = memberCount;
702 strcopy( g_cTeamName[teamindex], sizeof(g_cTeamName[]), g_cPlayerTeamName[members[0]] );
703
704 delete g_aCurrentSegmentStartTicks[teamindex];
705 g_aCurrentSegmentStartTicks[teamindex] = new ArrayList();
706 delete g_aCurrentSegmentPlayers[teamindex];
707 g_aCurrentSegmentPlayers[teamindex] = new ArrayList();
708
709 g_aCurrentSegmentStartTicks[teamindex].Push( 2 ); // not zero so that it doesnt spam print during first tick freeze time
710 g_aCurrentSegmentPlayers[teamindex].Push( Influx_GetClientId( members[0] ) );
711
712 int next = members[0];
713 for( int i = memberCount - 1; i >= 0; i-- )
714 {
715 //Timer_DebugPrint( "CreateTeam: Adding member %N", members[i] );
716
717 g_iNextTeamMember[members[i]] = next;
718 next = members[i];
719
720 g_iTeamIndex[members[i]] = teamindex;
721
722 g_bAllowStyleChange[members[i]] = true;
723 Timer_ClearClientCheckpoints( members[i] );
724 Influx_SetClientMode( members[i], MODE_TAGTEAM);
725 }
726 Influx_SetClientRun( members[0], 1 );
727 Influx_TeleportToStart( members[0] );
728 //TeleportClientToZone( members[0], Zone_Start, ZoneTrack_Main );
729 Timer_OpenCheckpointsMenu( members[0] );
730 g_iCurrentPlayer[teamindex] = members[0];
731
732 for( int i = 1; i < memberCount; i++ )
733 {
734 //Timer_DebugPrint( "CreateTeam: Moving %N to spec %N", members[i], members[0] );
735
736 ChangeClientTeam( members[i], CS_TEAM_SPECTATOR );
737 SetEntPropEnt( members[i], Prop_Send, "m_hObserverTarget", members[0] );
738 SetEntProp( members[i], Prop_Send, "m_iObserverMode", 4 );
739 }
740}
741
742bool ExitTeam( int client )
743{
744 if( g_iTeamIndex[client] == -1 )
745 {
746 Influx_SetClientMode( client, MODE_AUTO );
747 Influx_TeleportToStart( client );
748 return false;
749 }
750
751 int teamidx = g_iTeamIndex[client];
752 g_iTeamIndex[client] = -1;
753
754 g_nTeamPlayerCount[teamidx]--;
755 if( g_nTeamPlayerCount[teamidx] <= 1 )
756 {
757 g_bTeamTaken[teamidx] = false;
758 for( int i = 1; i <= MaxClients; i++ )
759 {
760 if( i != client && g_iTeamIndex[i] == teamidx )
761 {
762 PrintToChat( i, "{primary}All your team members have left, your team has been disbanded!" );
763 ExitTeam( i );
764 break;
765 }
766 }
767 }
768 else
769 {
770 for( int i = 1; i <= MaxClients; i++ )
771 {
772 if( g_iNextTeamMember[i] == client )
773 {
774 g_iNextTeamMember[i] = g_iNextTeamMember[client];
775 }
776 }
777 }
778
779 g_iNextTeamMember[client] = -1;
780
781 Influx_SetClientMode( client, MODE_AUTO );
782 Influx_TeleportToStart( client );
783
784 return true;
785}
786
787public Action Timer_OnCPLoadedPre( int client, int idx )
788{
789 if( g_iTeamIndex[client] != -1 && g_iCurrentPlayer[g_iTeamIndex[client]] != client )
790 {
791 PrintToChat( client, "{primary}Cannot load when it is not your turn" );
792 return Plugin_Handled;
793 }
794
795 return Plugin_Continue;
796}
797
798public Action Timer_OnCPSavedPre( int client, int target, int idx )
799{
800 if( g_iTeamIndex[client] != -1 && client != target )
801 {
802 PrintToChat( client, "{primary}Cannot save when it is not your turn" );
803 return Plugin_Handled;
804 }
805
806 return Plugin_Continue;
807}
808
809public void Timer_OnCPSavedPost( int client, int target, int idx )
810{
811 if( g_iTeamIndex[client] != -1 )
812 {
813 int teamidx = g_iTeamIndex[client];
814
815 if( !g_bDidUndo[teamidx] )
816 {
817 delete g_LastCheckpoint[teamidx][CP_ReplayFrames];
818 }
819 Timer_GetClientCheckpoint( client, 0, g_LastCheckpoint[teamidx] );
820
821 // 'frames' will be deleted by PassToNext
822 ArrayList frames = g_LastCheckpoint[teamidx][CP_ReplayFrames];
823 g_LastCheckpoint[teamidx][CP_ReplayFrames] = frames.Clone();
824
825 g_nRelayCount[teamidx]++;
826 int next = g_iNextTeamMember[client];
827
828 any checkpoint[eCheckpoint];
829 Timer_GetClientCheckpoint( client, idx, checkpoint );
830
831 g_aCurrentSegmentStartTicks[teamidx].Push( checkpoint[CP_ReplayFrames].Length - 1 );
832 g_aCurrentSegmentPlayers[teamidx].Push( Influx_GetClientId( next ) );
833
834 PassToNext( client, next, checkpoint );
835
836 g_bDidUndo[teamidx] = false;
837 }
838}
839
840// Commands
841
842public Action Command_TeamName( int client, int args )
843{
844 GetCmdArgString( g_cPlayerTeamName[client], sizeof(g_cPlayerTeamName[]) );
845 if( g_iTeamIndex[client] != -1 )
846 {
847 strcopy( g_cTeamName[g_iTeamIndex[client]], sizeof(g_cTeamName[]), g_cPlayerTeamName[client] );
848 }
849
850 ReplyToCommand( client, "{primary}Team name set to: {secondary}%s", g_cPlayerTeamName[client] );
851
852 return Plugin_Handled;
853}
854
855public Action Command_ExitTeam( int client, int args )
856{
857 if( !ExitTeam( client ) )
858 {
859 ReplyToCommand( client, "{primary}You are not currently in a team" );
860 }
861
862 return Plugin_Handled;
863}
864
865public Action Command_Pass( int client, int args )
866{
867 if( g_iTeamIndex[client] == -1 )
868 {
869 ReplyToCommand( client, "{primary}You are not currently in a team" );
870 return Plugin_Handled;
871 }
872
873 int teamidx = g_iTeamIndex[client];
874 int maxPasses = g_cvMaxPasses.IntValue;
875
876 if( maxPasses > -1 && g_nPassCount[teamidx] >= maxPasses )
877 {
878 ReplyToCommand( client, "{primary}Your team has used all %i passes", maxPasses );
879 return Plugin_Handled;
880 }
881
882 if( g_iCurrentPlayer[teamidx] != client )
883 {
884 ReplyToCommand( client, "{primary}You cannot pass when it is not your turn" );
885 return Plugin_Handled;
886 }
887
888 g_nPassCount[teamidx]++;
889
890 any checkpoint[eCheckpoint];
891 bool usecp = Timer_GetTotalCheckpoints( client ) > 0;
892
893 if( usecp )
894 {
895 Timer_GetClientCheckpoint( client, 0, checkpoint );
896 }
897
898 PassToNext( client, g_iNextTeamMember[client], checkpoint, usecp );
899
900 int lastidx = g_aCurrentSegmentPlayers[teamidx].Length - 1;
901 g_aCurrentSegmentPlayers[teamidx].Set( lastidx, Influx_GetClientId( g_iNextTeamMember[client] ) );
902
903 if( maxPasses > -1 )
904 {
905 PrintToTeam( teamidx, "{name}%N {primary}has passed! It is now {name}%N{primary}'s turn. {secondary}%i/%i {primary}passes used.", client, g_iNextTeamMember[client], g_nPassCount[teamidx], maxPasses );
906 }
907 else
908 {
909 PrintToTeam( teamidx, "{name}%N {primary}has passed! It is now {name}%N{primary}'s turn.", client, g_iNextTeamMember[client] );
910 }
911
912 return Plugin_Handled;
913}
914
915public Action Command_Undo( int client, int args )
916{
917 if( g_iTeamIndex[client] == -1 )
918 {
919 ReplyToCommand( client, "{primary}You are not currently in a team" );
920 return Plugin_Handled;
921 }
922
923 int teamidx = g_iTeamIndex[client];
924
925 int maxUndos = g_cvMaxUndos.IntValue;
926 if( maxUndos == -1 || g_nUndoCount[teamidx] >= maxUndos )
927 {
928 ReplyToCommand( client, "{primary}Your team has already used all %i undos", maxUndos );
929 return Plugin_Handled;
930 }
931
932 if( g_iCurrentPlayer[teamidx] != client )
933 {
934 ReplyToCommand( client, "{primary}You cannot undo when it is not your turn" );
935 return Plugin_Handled;
936 }
937
938 if( g_nRelayCount[teamidx] == 0 )
939 {
940 ReplyToCommand( client, "{primary}Cannot undo when no one has saved!" );
941 return Plugin_Handled;
942 }
943
944 if( g_bDidUndo[teamidx] )
945 {
946 ReplyToCommand( client, "{primary}Your team has already undo-ed this turn" );
947 return Plugin_Handled;
948 }
949
950 int last = -1;
951
952 for( int i = 1; i <= MaxClients; i++ )
953 {
954 if( g_iNextTeamMember[i] == client )
955 {
956 last = i;
957 break;
958 }
959 }
960
961 if( last == -1 )
962 {
963 LogError( "Failed to find last player" );
964 return Plugin_Handled;
965 }
966
967 PassToNext( client, last, g_LastCheckpoint[teamidx] );
968 g_aCurrentSegmentStartTicks[teamidx].Erase( g_aCurrentSegmentStartTicks[teamidx].Length - 1 );
969 g_aCurrentSegmentPlayers[teamidx].Erase( g_aCurrentSegmentPlayers[teamidx].Length - 1 );
970 g_bDidUndo[teamidx] = true;
971 g_nUndoCount[teamidx]++;
972
973 if( maxUndos > -1 )
974 {
975 PrintToTeam( teamidx, "{name}%N {primary}used an undo! It is now {name}%N{primary}'s turn again. {secondary}%i/%i {primary}undos used.", client, last, g_nUndoCount[teamidx], maxUndos );
976 }
977 else
978 {
979 PrintToTeam( teamidx, "{name}%N {primary}used an undo! It is now {name}%N{primary}'s turn again.", client, last );
980 }
981 return Plugin_Handled;
982}
983
984// Records/Database stuff
985
986void OnTimerFinishCustom( int client, int track, int style, float time, Handle fwdInsertedPre, Handle fwdInsertedPost, Handle fwdUpdatedPre, Handle fwdUpdatedPost, Handle fwdWRBeaten )
987{
988 if( g_iTeamIndex[client] == -1 )
989 {
990 LogError( "%N finished on tagteam without a team!", client );
991 return;
992 }
993
994 float wr = g_aMapTopTimes[track][style].Length ? g_aMapTopTimes[track][style].Get( 0 ) : 0.0;
995 if( wr == 0.0 || time < wr )
996 {
997 Call_StartForward( fwdWRBeaten );
998 Call_PushCell( client );
999 Call_PushCell( track );
1000 Call_PushCell( style );
1001 Call_PushCell( time );
1002 Call_PushCell( wr );
1003 Call_Finish();
1004 }
1005
1006 ArrayList frames = Influx_GetClientReplayFrames( client );
1007
1008 for( int i = 1; i <= MaxClients; i++ )
1009 {
1010 if( i != client && g_iTeamIndex[i] == g_iTeamIndex[client] )
1011 {
1012 Influx_SetClientReplayFrames( i, frames );
1013 }
1014 }
1015
1016 delete frames;
1017
1018 SQL_InsertRecord( client, g_iTeamIndex[client], track, style, time, fwdInsertedPre, fwdInsertedPost, fwdUpdatedPre, fwdUpdatedPost );
1019}
1020
1021void SQL_CreateTables()
1022{
1023 if( g_hDatabase == null )
1024 {
1025 return;
1026 }
1027
1028 Transaction txn = new Transaction();
1029
1030 // makes a table that links multiple players to 1 record
1031 char query[512];
1032 Format( query, sizeof(query), "CREATE TABLE IF NOT EXISTS `t_tagteam_records` ( tt_recordid INT NOT NULL AUTO_INCREMENT, \
1033 teamname CHAR(64) NOT NULL, \
1034 mapid INT NOT NULL, \
1035 timestamp INT NOT NULL, \
1036 time FLOAT NOT NULL, \
1037 track INT NOT NULL, \
1038 style INT NOT NULL, \
1039 jumps INT NOT NULL, \
1040 strafes INT NOT NULL, \
1041 sync FLOAT NOT NULL, \
1042 strafetime FLOAT NOT NULL, \
1043 ssj INT NOT NULL, \
1044 PRIMARY KEY (`tt_recordid`) );" );
1045
1046 txn.AddQuery( query );
1047
1048 Format( query, sizeof(query), "CREATE TABLE IF NOT EXISTS `t_tagteam_pb` ( tt_timeid INT NOT NULL AUTO_INCREMENT, \
1049 playerid INT NOT NULL, \
1050 tt_recordid INT NOT NULL, \
1051 PRIMARY KEY (`tt_timeid`) );" );
1052
1053 txn.AddQuery( query );
1054
1055 Format( query, sizeof(query), "CREATE TABLE IF NOT EXISTS `t_tagteam_segments` ( tt_segmentid INT NOT NULL AUTO_INCREMENT, \
1056 playerid INT NOT NULL, \
1057 recordid INT NOT NULL, \
1058 starttick INT NOT NULL, \
1059 PRIMARY KEY (`tt_segmentid`) );" );
1060
1061 txn.AddQuery( query );
1062
1063 g_hDatabase.Execute( txn, CreateTableSuccess_Callback, CreateTableFailure_Callback, _, DBPrio_High );
1064}
1065
1066public void CreateTableSuccess_Callback( Database db, any data, int numQueries, DBResultSet[] results, any[] queryData )
1067{
1068 if( Influx_GetCurrentMapId() > -1 )
1069 {
1070 SQL_LoadAllMapRecords();
1071 }
1072
1073 for( int i = 1; i <= MaxClients; i++ )
1074 {
1075 if( Influx_IsClientCached( i ) )
1076 {
1077 SQL_LoadAllPlayerTimes( i );
1078 }
1079 }
1080}
1081
1082public void CreateTableFailure_Callback( Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData )
1083{
1084 SetFailState( "[SQL ERROR] (CreateTableFailure_Callback) - %s", error );
1085}
1086
1087void SQL_LoadAllPlayerTimes( int client )
1088{
1089 SQL_LoadPlayerTime( client, 1, MODE_TAGTEAM );
1090 SQL_LoadPlayerTime( client, 2, MODE_TAGTEAM );
1091}
1092
1093void SQL_LoadPlayerTime( int client, int track, int style )
1094{
1095 char query[512];
1096 Format( query, sizeof(query), "SELECT r.tt_recordid, r.time FROM `t_tagteam_records` r \
1097 JOIN `t_tagteam_pb` t ON r.tt_recordid = t.tt_recordid \
1098 WHERE t.playerid = '%i' AND r.mapid = '%i' AND r.track = '%i' AND r.style = '%i'",
1099 Influx_GetClientId( client ),
1100 Influx_GetCurrentMapId(),
1101 track,
1102 style );
1103
1104 DataPack pack = new DataPack();
1105 pack.WriteCell( GetClientUserId( client ) );
1106 pack.WriteCell( track );
1107 pack.WriteCell( style );
1108
1109 g_hDatabase.Query( LoadPlayerTime_Callback, query, pack );
1110}
1111
1112public void LoadPlayerTime_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1113{
1114 if( results == null )
1115 {
1116 LogError( "[SQL ERROR] (LoadPlayerTime_Callback) - %s", error );
1117 delete pack;
1118 return;
1119 }
1120
1121 pack.Reset();
1122 int client = GetClientOfUserId( pack.ReadCell() );
1123 int track = pack.ReadCell();
1124 int style = pack.ReadCell();
1125 delete pack;
1126
1127 if( !( 0 < client <= MaxClients ) )
1128 {
1129 return;
1130 }
1131
1132
1133 if( results.FetchRow() )
1134 {
1135 g_iRecordId[client][track][style] = results.FetchInt( 0 );
1136 g_fPersonalBest[client][track][style] = results.FetchFloat( 1 );
1137 //Timer_DebugPrint( "LoadPlayerTime_Callback: %N recordid=%i pb=%f", client, g_iRecordId[client][track][style], g_fPersonalBest[client][track][style] );
1138 }
1139}
1140
1141void SQL_InsertRecord( int client, int teamidx, int track, int style, float time, Handle fwdInsertedPre, Handle fwdInsertedPost, Handle fwdUpdatedPre, Handle fwdUpdatedPost )
1142{
1143 char query[512];
1144 Format( query, sizeof(query), "INSERT INTO `t_tagteam_records` (teamname, mapid, timestamp, time, track, style, jumps, strafes, sync, strafetime, ssj) \
1145 VALUES ('%s', '%i', '%i', '%f', '%i', '%i', '%i', '%i', '%f', '%f', '%i')",
1146 g_cTeamName[teamidx],
1147 Influx_GetCurrentMapId(),
1148 GetTime(),
1149 time,
1150 track,
1151 style,
1152 Timer_GetClientCurrentJumps( client ),
1153 Timer_GetClientCurrentStrafes( client ),
1154 Timer_GetClientCurrentSync( client ),
1155 Timer_GetClientCurrentStrafeTime( client ),
1156 Timer_GetClientCurrentSSJ( client ) );
1157
1158 DataPack pack = new DataPack();
1159 pack.WriteCell( teamidx );
1160 pack.WriteCell( track );
1161 pack.WriteCell( style );
1162 pack.WriteFloat( time );
1163 pack.WriteCell( fwdInsertedPre );
1164 pack.WriteCell( fwdInsertedPost );
1165 pack.WriteCell( fwdUpdatedPre );
1166 pack.WriteCell( fwdUpdatedPost );
1167
1168 g_hDatabase.Query( InsertRecord_Callback, query, pack );
1169}
1170
1171public void InsertRecord_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1172{
1173 if( results == null )
1174 {
1175 LogError( "[SQL ERROR] (InsertRecord_Callback) - %s", error );
1176 delete pack;
1177 return;
1178 }
1179
1180 int recordid = results.InsertId;
1181
1182 pack.Reset();
1183 int teamidx = pack.ReadCell();
1184 int track = pack.ReadCell();
1185 int style = pack.ReadCell();
1186 float time = pack.ReadFloat();
1187 Handle fwdInsertedPre = pack.ReadCell();
1188 Handle fwdInsertedPost = pack.ReadCell();
1189 Handle fwdUpdatedPre = pack.ReadCell();
1190 Handle fwdUpdatedPost = pack.ReadCell();
1191 delete pack;
1192
1193 for( int i = 1; i <= MaxClients; i++ )
1194 {
1195 if( g_iTeamIndex[i] == teamidx )
1196 {
1197 if( g_fPersonalBest[i][track][style] == 0.0 || time <= g_fPersonalBest[i][track][style] )
1198 {
1199 if( g_fPersonalBest[i][track][style] == 0.0 )
1200 {
1201 SQL_InsertPlayerTime( i, track, style, time, recordid, fwdInsertedPre, fwdInsertedPost );
1202 }
1203 else
1204 {
1205 SQL_UpdatePlayerTime( i, track, style, time, recordid, fwdUpdatedPre, fwdUpdatedPost );
1206 }
1207 }
1208 }
1209 }
1210
1211 g_bLoadSegmentsAfterInserting[track][style] = false;
1212 g_bFinishedInsertingSegments[track][style] = false;
1213 SQL_InsertSegments( teamidx, track, style, recordid );
1214
1215 SQL_LoadMapRecords( track, style );
1216}
1217
1218void SQL_InsertPlayerTime( int client, int track, int style, float time, int recordid, Handle fwdInsertedPre, Handle fwdInsertedPost )
1219{
1220 //Timer_DebugPrint( "Inserting time for %N", client );
1221
1222 any result = Plugin_Continue;
1223 Call_StartForward( fwdInsertedPre );
1224 Call_PushCell( client );
1225 Call_PushCell( track );
1226 Call_PushCell( style );
1227 Call_PushFloat( time );
1228 Call_Finish( result );
1229
1230 if( result == Plugin_Handled || result == Plugin_Stop )
1231 {
1232 return;
1233 }
1234
1235 g_iRecordId[client][track][style] = recordid;
1236 g_fPersonalBest[client][track][style] = time;
1237
1238 char query[512];
1239 Format( query, sizeof(query), "INSERT INTO `t_tagteam_pb` (playerid, tt_recordid) \
1240 VALUES ('%i', '%i')",
1241 Influx_GetClientId( client ), recordid );
1242
1243 DataPack pack = new DataPack();
1244 pack.WriteCell( GetClientUserId( client ) );
1245 pack.WriteCell( track );
1246 pack.WriteCell( style );
1247 pack.WriteFloat( time );
1248 pack.WriteCell( recordid );
1249 pack.WriteCell( fwdInsertedPost );
1250
1251 g_hDatabase.Query( InsertPlayerTime_Callback, query, pack );
1252}
1253
1254public void InsertPlayerTime_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1255{
1256 if( results == null )
1257 {
1258 LogError( "[SQL ERROR] (InsertPlayerTime_Callback) - %s", error );
1259 delete pack;
1260 return;
1261 }
1262
1263 pack.Reset();
1264 int client = GetClientOfUserId( pack.ReadCell() );
1265 int track = pack.ReadCell();
1266 int style = pack.ReadCell();
1267 float time = pack.ReadFloat();
1268 int recordid = pack.ReadCell();
1269 Handle fwdInsertedPost = pack.ReadCell();
1270 delete pack;
1271
1272 if( !( 0 < client <= MaxClients ) )
1273 {
1274 return;
1275 }
1276
1277 Call_StartForward( fwdInsertedPost );
1278 Call_PushCell( client );
1279 Call_PushCell( track );
1280 Call_PushCell( style );
1281 Call_PushFloat( time );
1282 Call_PushCell( recordid );
1283 Call_Finish();
1284}
1285
1286void SQL_UpdatePlayerTime( int client, int track, int style, float time, int recordid, Handle fwdUpdatedPre, Handle fwdUpdatedPost )
1287{
1288 any result = Plugin_Continue;
1289 Call_StartForward( fwdUpdatedPre );
1290 Call_PushCell( client );
1291 Call_PushCell( track );
1292 Call_PushCell( style );
1293 Call_PushFloat( time );
1294 Call_Finish( result );
1295
1296 if( result == Plugin_Handled || result == Plugin_Stop )
1297 {
1298 return;
1299 }
1300
1301 char query[512];
1302 Format( query, sizeof(query), "UPDATE `t_tagteam_pb` SET tt_recordid = '%i' \
1303 WHERE playerid = '%i' AND tt_recordid = '%i'",
1304 recordid, Influx_GetClientId( client ), g_iRecordId[client] );
1305
1306 DataPack pack = new DataPack();
1307 pack.WriteCell( GetClientUserId( client ) );
1308 pack.WriteCell( track );
1309 pack.WriteCell( style );
1310 pack.WriteFloat( time );
1311 pack.WriteCell( recordid );
1312 pack.WriteCell( fwdUpdatedPost );
1313
1314 g_hDatabase.Query( UpdatePlayerTime_Callback, query, pack );
1315
1316 g_iRecordId[client][track][style] = recordid;
1317 g_fPersonalBest[client][track][style] = time;
1318}
1319
1320public void UpdatePlayerTime_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1321{
1322 if( results == null )
1323 {
1324 LogError( "[SQL ERROR] (UpdatePlayerTime_Callback) - %s", error );
1325 delete pack;
1326 return;
1327 }
1328
1329 pack.Reset();
1330 int client = GetClientOfUserId( pack.ReadCell() );
1331 int track = pack.ReadCell();
1332 int style = pack.ReadCell();
1333 float time = pack.ReadFloat();
1334 int recordid = pack.ReadCell();
1335 Handle fwdUpdatedPost = pack.ReadCell();
1336 delete pack;
1337
1338 if( !( 0 < client <= MaxClients ) )
1339 {
1340 return;
1341 }
1342
1343 Call_StartForward( fwdUpdatedPost );
1344 Call_PushCell( client );
1345 Call_PushCell( track );
1346 Call_PushCell( style );
1347 Call_PushFloat( time );
1348 Call_PushCell( recordid );
1349 Call_Finish();
1350}
1351
1352void SQL_LoadAllMapRecords()
1353{
1354 SQL_LoadMapRecords( 1, MODE_TAGTEAM );
1355 SQL_LoadMapRecords( 2, MODE_TAGTEAM );
1356}
1357
1358void SQL_LoadMapRecords( int track, int style )
1359{
1360 //Timer_DebugPrint( "SQL_LoadMapRecords: Loading map records" );
1361
1362 char query[512];
1363 Format( query, sizeof(query), "SELECT tt_recordid, time, teamname FROM `t_tagteam_records` \
1364 WHERE mapid = '%i' AND track = '%i' AND style = '%i' \
1365 ORDER BY time ASC",
1366 Influx_GetCurrentMapId(), track, style );
1367
1368 DataPack pack = new DataPack();
1369 pack.WriteCell( track );
1370 pack.WriteCell( style );
1371
1372 g_hDatabase.Query( LoadMapRecords_Callback, query, pack, DBPrio_High );
1373}
1374
1375public void LoadMapRecords_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1376{
1377 if( results == null )
1378 {
1379 LogError( "[SQL ERROR] (LoadMapRecords_Callback) - %s", error );
1380 delete pack;
1381 return;
1382 }
1383
1384 pack.Reset();
1385 int track = pack.ReadCell();
1386 int style = pack.ReadCell();
1387 delete pack;
1388
1389 g_aMapTopRecordIds[track][style].Clear();
1390 g_aMapTopTimes[track][style].Clear();
1391 g_aMapTopNames[track][style].Clear();
1392
1393
1394 //Timer_DebugPrint( "LoadMapRecords_Callback: Got %i rows", results.RowCount );
1395
1396 char name[MAX_NAME_LENGTH];
1397 while( results.FetchRow() )
1398 {
1399 g_aMapTopRecordIds[track][style].Push( results.FetchInt( 0 ) );
1400 g_aMapTopTimes[track][style].Push( results.FetchFloat( 1 ) );
1401 results.FetchString( 2, name, sizeof(name) );
1402 g_aMapTopNames[track][style].PushString( name );
1403 }
1404}
1405
1406void SQL_InsertSegments( int teamidx, int track, int style, int recordid )
1407{
1408 char query[256];
1409
1410 int length = g_aCurrentSegmentStartTicks[teamidx].Length;
1411 for( int i = 0; i < length; i++ )
1412 {
1413 Format( query, sizeof(query), "INSERT INTO `t_tagteam_segments` (playerid, recordid, starttick) VALUES ('%i', '%i', '%i')",
1414 g_aCurrentSegmentPlayers[teamidx].Get( i ),
1415 recordid,
1416 g_aCurrentSegmentStartTicks[teamidx].Get( i ) );
1417
1418 DataPack pack = null;
1419 if( i == length - 1 )
1420 {
1421 pack = new DataPack();
1422 pack.WriteCell( track );
1423 pack.WriteCell( style );
1424 }
1425
1426 g_hDatabase.Query( InsertSegments_Callback, query, pack );
1427 }
1428}
1429
1430public void InsertSegments_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1431{
1432 if( results == null )
1433 {
1434 LogError( "[SQL ERROR] (InsertSegments_Callback) - %s", error );
1435 delete pack;
1436 return;
1437 }
1438
1439 if( pack == null )
1440 {
1441 return;
1442 }
1443
1444 pack.Reset();
1445 int track = pack.ReadCell();
1446 int style = pack.ReadCell();
1447 delete pack;
1448
1449 if( g_bLoadSegmentsAfterInserting[track][style] )
1450 {
1451 int recordid = Timer_GetReplayRecordId( track, style );
1452 //Timer_DebugPrint( "InsertSegments_Callback: Loading segments (recordid=%i)", recordid );
1453 if( recordid > -1 )
1454 {
1455 SQL_LoadSegments( track, style, recordid );
1456 }
1457 }
1458 else
1459 {
1460 //Timer_DebugPrint( "InsertSegments_Callback: Finished inserting segments" );
1461 g_bFinishedInsertingSegments[track][style] = true;
1462 }
1463}
1464
1465void SQL_LoadSegments( int track, int style, int recordid )
1466{
1467 char query[512];
1468 Format( query, sizeof(query), "SELECT s.starttick, p.lastname FROM `t_tagteam_segments` s \
1469 JOIN `t_players` p ON p.playerid = s.playerid \
1470 WHERE s.recordid = '%i' \
1471 ORDER BY s.starttick ASC", recordid );
1472
1473 DataPack pack = new DataPack();
1474 pack.WriteCell( track );
1475 pack.WriteCell( style );
1476
1477 g_hDatabase.Query( LoadSegments_Callback, query, pack );
1478}
1479
1480public void LoadSegments_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1481{
1482 if( results == null )
1483 {
1484 LogError( "[SQL ERROR] (LoadSegments_Callback) - %s", error );
1485 delete pack;
1486 return;
1487 }
1488
1489 pack.Reset();
1490 int track = pack.ReadCell();
1491 int style = pack.ReadCell();
1492 delete pack;
1493
1494 delete g_smSegmentPlayerNames[track][style];
1495 g_smSegmentPlayerNames[track][style] = new StringMap();
1496
1497 char sStartTick[8];
1498 char name[MAX_NAME_LENGTH];
1499 while( results.FetchRow() )
1500 {
1501 IntToString( results.FetchInt( 0 ), sStartTick, sizeof(sStartTick) );
1502 results.FetchString( 1, name, sizeof(name) );
1503
1504 g_smSegmentPlayerNames[track][style].SetString( sStartTick, name );
1505 }
1506}
1507
1508public Action Timer_OnClientRankRequested( int client, int track, int style, int& rank )
1509{
1510 if( Timer_StyleHasSetting( style, "tagteam" ) )
1511 {
1512 rank = GetClientMapRank( client, track, style );
1513 return Plugin_Handled;
1514 }
1515
1516 return Plugin_Continue;
1517}
1518
1519public Action Timer_OnClientPBTimeRequested( int client, int track, int style, float& time )
1520{
1521 if( Timer_StyleHasSetting( style, "tagteam" ) )
1522 {
1523 time = g_fPersonalBest[client][track][style];
1524 return Plugin_Handled;
1525 }
1526
1527 return Plugin_Continue;
1528}
1529
1530public Action Timer_OnWRTimeRequested( int track, int style, float& time )
1531{
1532 if( Timer_StyleHasSetting( style, "tagteam" ) )
1533 {
1534 if( g_aMapTopTimes[track][style].Length > 0 )
1535 {
1536 time = g_aMapTopTimes[track][style].Get( 0 );
1537 }
1538 else
1539 {
1540 time = 0.0;
1541 }
1542 return Plugin_Handled;
1543 }
1544
1545 return Plugin_Continue;
1546}
1547
1548public Action Timer_OnWRNameRequested( int track, int style, char name[MAX_NAME_LENGTH] )
1549{
1550 if( Timer_StyleHasSetting( style, "tagteam" ) )
1551 {
1552 g_aMapTopNames[track][style].GetString( 0, name, sizeof(name) );
1553 return Plugin_Handled;
1554 }
1555
1556 return Plugin_Continue;
1557}
1558
1559public Action Timer_OnRecordsCountRequested( int track, int style, int& recordcount )
1560{
1561 if( Timer_StyleHasSetting( style, "tagteam" ) )
1562 {
1563 recordcount = g_aMapTopTimes[track][style].Length;
1564 return Plugin_Handled;
1565 }
1566
1567 return Plugin_Continue;
1568}
1569
1570public Action Timer_OnLeaderboardRequested( int client, int track, int style )
1571{
1572 if( Timer_StyleHasSetting( style, "tagteam" ) )
1573 {
1574 int length = g_aMapTopTimes[track][style].Length;
1575
1576 if( length == 0 )
1577 {
1578 PrintToChat( client, "{primary}No records found" );
1579 return Plugin_Handled;
1580 }
1581
1582 Menu menu = new Menu( Leaderboard_Handler );
1583 menu.SetTitle( "Tagteam leaderboard\n \n" );
1584
1585 char buffer[256];
1586
1587 int max = length > 50 ? 50 : length;
1588 for( int i = 0; i < max; i++ )
1589 {
1590 char name[MAX_NAME_LENGTH];
1591 g_aMapTopNames[track][style].GetString( i, name, sizeof(name) );
1592
1593 float time = g_aMapTopTimes[track][style].Get( i );
1594 char sTime[32];
1595 Timer_FormatTime( time, sTime, sizeof(sTime) );
1596
1597 char sRecordId[8];
1598 IntToString( g_aMapTopRecordIds[track][style].Get( i ), sRecordId, sizeof(sRecordId) );
1599
1600 Format( buffer, sizeof(buffer), "[#%i] %s (%s)", i + 1, name, sTime );
1601
1602 menu.AddItem( sRecordId, buffer );
1603 }
1604
1605 menu.Display( client, MENU_TIME_FOREVER );
1606
1607 return Plugin_Handled;
1608 }
1609
1610 return Plugin_Continue;
1611}
1612
1613public int Leaderboard_Handler( Menu menu, MenuAction action, int param1, int param2 )
1614{
1615 if( action == MenuAction_Select )
1616 {
1617 char info[8];
1618 menu.GetItem( param2, info, sizeof(info) );
1619
1620 int recordid = StringToInt( info );
1621 SQL_ShowTeamStats( param1, recordid );
1622 }
1623 else if( action == MenuAction_End )
1624 {
1625 delete menu;
1626 }
1627}
1628
1629void SQL_ShowTeamStats( int client, int recordid )
1630{
1631 // tt_recordid INT NOT NULL AUTO_INCREMENT,
1632 // teamname CHAR(64) NOT NULL,
1633 // mapid INT NOT NULL,
1634 // timestamp INT NOT NULL,
1635 // time FLOAT NOT NULL,
1636 // track INT NOT NULL,
1637 // style INT NOT NULL,
1638 // jumps INT NOT NULL,
1639 // strafes INT NOT NULL,
1640 // sync FLOAT NOT NULL,
1641 // strafetime FLOAT NOT NULL,
1642 // ssj INT NOT NULL,
1643
1644 DataPack pack = new DataPack();
1645 pack.WriteCell( GetClientUserId( client ) );
1646 pack.WriteCell( recordid );
1647
1648 char query[1024];
1649 Format( query, sizeof(query), "SELECT pb.playerid, player.lastname, r.teamname, map.mapname, r.timestamp, r.time, r.track, r.style, r.jumps, r.strafes, r.sync, r.strafetime, r.ssj \
1650 FROM `t_tagteam_records` r \
1651 JOIN `t_tagteam_pb` pb ON pb.tt_recordid = r.tt_recordid \
1652 JOIN `t_players` player ON pb.playerid = player.playerid \
1653 JOIN `t_maps` map ON r.mapid = map.mapid \
1654 WHERE r.tt_recordid = '%i'",
1655 recordid );
1656
1657 g_hDatabase.Query( ShowTeamStats_Callback, query, pack );
1658}
1659
1660public void ShowTeamStats_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1661{
1662 if( results == null )
1663 {
1664 LogError( "[SQL ERROR] (ShowTeamStats_Callback) - %s", error );
1665 delete pack;
1666 return;
1667 }
1668
1669 pack.Reset();
1670 int client = GetClientOfUserId( pack.ReadCell() );
1671 int recordid = pack.ReadCell();
1672 delete pack;
1673
1674 if( !( 0 < client <= MaxClients ) )
1675 {
1676 return;
1677 }
1678
1679 ArrayList playerids = new ArrayList();
1680 ArrayList playernames = new ArrayList( ByteCountToCells( MAX_NAME_LENGTH ) );
1681
1682 char buffer[512];
1683 char teamname[64];
1684 char mapname[PLATFORM_MAX_PATH];
1685 int timestamp;
1686 float time;
1687 int track;
1688 int style;
1689 int jumps;
1690 int strafes;
1691 float sync;
1692 float strafetime;
1693 int ssj;
1694
1695 bool fetchedStats = false;
1696 while( results.FetchRow() )
1697 {
1698 playerids.Push( results.FetchInt( 0 ) );
1699 results.FetchString( 1, buffer, sizeof(buffer) );
1700 playernames.PushString( buffer );
1701
1702 // kind of aids, but i dont wan't to split this up into 2 queries.
1703 // just get the info once at the start, all rows should have the same stats
1704 if( !fetchedStats )
1705 {
1706 results.FetchString( 2, teamname, sizeof(teamname) );
1707 results.FetchString( 3, mapname, sizeof(mapname) );
1708 timestamp = results.FetchInt( 4 );
1709 time = results.FetchFloat( 5 );
1710 track = results.FetchInt( 6 );
1711 style = results.FetchInt( 7 );
1712 jumps = results.FetchInt( 8 );
1713 strafes = results.FetchInt( 9 );
1714 sync = results.FetchFloat( 10 );
1715 strafetime = results.FetchFloat( 11 );
1716 ssj = results.FetchInt( 12 );
1717
1718 fetchedStats = true;
1719 }
1720 }
1721
1722 Menu menu = new Menu( ShowTeamStats_Handler );
1723
1724 char date[128];
1725 FormatTime( date, sizeof(date), "%d/%m/%Y - %H:%M:%S", timestamp );
1726 char sTime[64];
1727 Timer_FormatTime( time, sTime, sizeof(sTime) );
1728
1729 char sTrack[16];
1730 Timer_GetZoneTrackName( track, sTrack, sizeof(sTrack) );
1731
1732 any settings[styleSettings];
1733 Timer_GetStyleSettings( style, settings );
1734
1735 char sSync[10];
1736 if( settings[Sync] )
1737 {
1738 Format( sSync, sizeof(sSync), "(%.2f)", sync );
1739 }
1740
1741 Format( buffer, sizeof(buffer), "%s - %s %s\n \n%s:", mapname, sTrack, settings[StyleName], teamname);
1742 menu.SetTitle( buffer );
1743
1744 char sInfo[8];
1745
1746 int playerCount = playerids.Length;
1747 for( int i = 0; i < playerCount; i++ )
1748 {
1749 IntToString( playerids.Get( i ), sInfo, sizeof(sInfo) );
1750 playernames.GetString( i, buffer, sizeof(buffer) );
1751
1752 if( i == playerCount - 1 )
1753 {
1754 Format( buffer, sizeof(buffer), "%s\n \n", buffer );
1755 Format( buffer, sizeof(buffer), "%sDate: %s\n", buffer, date );
1756 Format( buffer, sizeof(buffer), "%sTime: %s\n \n", buffer, sTime );
1757 Format( buffer, sizeof(buffer), "%sJumps: %i\n", buffer, jumps );
1758 Format( buffer, sizeof(buffer), "%sStrafes: %i %s\n", buffer, strafes, sSync );
1759 Format( buffer, sizeof(buffer), "%sStrafe Time %: %.2f\n", buffer, strafetime );
1760 Format( buffer, sizeof(buffer), "%sSSJ: %i\n \n", buffer, ssj );
1761 }
1762
1763 menu.AddItem( sInfo, buffer );
1764 }
1765
1766 if( CheckCommandAccess( client, "delete_time", ADMFLAG_RCON ) )
1767 {
1768 Format( buffer, sizeof(buffer), "delete;%i;%i;%i", track, style, recordid );
1769 menu.AddItem( buffer, "Delete Time" );
1770 }
1771
1772 menu.Display( client, MENU_TIME_FOREVER );
1773
1774 delete playerids;
1775 delete playernames;
1776}
1777
1778public int ShowTeamStats_Handler( Menu menu, MenuAction action, int param1, int param2 )
1779{
1780 if( action == MenuAction_Select )
1781 {
1782
1783 char sInfo[64];
1784 menu.GetItem( param2, sInfo, sizeof(sInfo) );
1785
1786 // user pressed delete
1787 if( StrContains( sInfo, "delete" ) )
1788 {
1789 char sRecordData[4][32];
1790 ExplodeString( sInfo, ";", sRecordData, sizeof(sRecordData), sizeof(sRecordData[]) );
1791
1792 int track = StringToInt( sRecordData[1] );
1793 int style = StringToInt( sRecordData[2] );
1794 int recordid = StringToInt( sRecordData[3] );
1795 SQL_DeleteRecord( track, style, recordid );
1796 }
1797 // user pressed player name
1798 else
1799 {
1800 // TODO: show player stats once its implemented in timer-records
1801 }
1802 }
1803 else if( action == MenuAction_End )
1804 {
1805 delete menu;
1806 }
1807}
1808
1809void SQL_DeleteRecord( int track, int style, int recordid )
1810{
1811 char query[512];
1812
1813 {
1814 DataPack pack = new DataPack();
1815 pack.WriteCell( track );
1816 pack.WriteCell( style );
1817 pack.WriteCell( recordid );
1818
1819 Format( query, sizeof(query), "DELETE FROM `t_tagteam_records` WHERE tt_recordid = '%i'", recordid );
1820 g_hDatabase.Query( DeleteRecord_Callback, query, pack );
1821 }
1822
1823 {
1824 DataPack pack = new DataPack();
1825 pack.WriteCell( track );
1826 pack.WriteCell( style );
1827 pack.WriteCell( recordid );
1828
1829 Format( query, sizeof(query), "DELETE FROM `t_tagteam_pb` WHERE tt_recordid = '%i'", recordid );
1830 g_hDatabase.Query( DeletePB_Callback, query, recordid );
1831 }
1832
1833 {
1834 Format( query, sizeof(query), "DELETE FROM `t_tagteam_segments` WHERE tt_recordid = '%i'", recordid );
1835 g_hDatabase.Query( DeleteSegments_Callback, query );
1836 }
1837}
1838
1839public void DeleteRecord_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1840{
1841 if( results == null )
1842 {
1843 LogError( "[SQL ERROR] (DeleteRecord_Callback) - %s", error );
1844 delete pack;
1845 return;
1846 }
1847
1848 pack.Reset();
1849 int track = pack.ReadCell();
1850 int style = pack.ReadCell();
1851 int recordid = pack.ReadCell();
1852 delete pack;
1853
1854 Timer_CallOnRecordDeleted( track, style, recordid );
1855
1856 SQL_LoadMapRecords( track, style );
1857}
1858
1859public void DeletePB_Callback( Database db, DBResultSet results, const char[] error, DataPack pack )
1860{
1861 if( results == null )
1862 {
1863 LogError( "[SQL ERROR] (DeletePB_Callback) - %s", error );
1864 delete pack;
1865 return;
1866 }
1867
1868 pack.Reset();
1869 int track = pack.ReadCell();
1870 int style = pack.ReadCell();
1871 int recordid = pack.ReadCell();
1872 delete pack;
1873
1874 for( int i = 1; i <= MaxClients; i++ )
1875 {
1876 if( g_iRecordId[i][track][style] == recordid )
1877 {
1878 g_iRecordId[i][track][style] = -1;
1879 g_fPersonalBest[i][track][style] = 0.0;
1880 }
1881 }
1882}
1883
1884public void DeleteSegments_Callback( Database db, DBResultSet results, const char[] error, any data )
1885{
1886 if( results == null )
1887 {
1888 LogError( "[SQL ERROR] (DeleteSegments_Callback) - %s", error );
1889 return;
1890 }
1891}
1892
1893int GetRankForTime( float time, int track, int style )
1894{
1895 if( time == 0.0 )
1896 {
1897 return 0;
1898 }
1899
1900 int nRecords = g_aMapTopTimes[track][style].Length;
1901
1902 if( nRecords == 0 )
1903 {
1904 return 1;
1905 }
1906
1907 for( int i = 0; i < nRecords; i++ )
1908 {
1909 float maptime = g_aMapTopTimes[track][style].Get( i );
1910 if( time < maptime )
1911 {
1912 return i + 1;
1913 }
1914 }
1915
1916 return nRecords + 1;
1917}
1918
1919int GetClientMapRank( int client, int track, int style )
1920{
1921 // subtract 1 because when times are equal, it counts as next rank
1922 return GetRankForTime( g_fPersonalBest[client][track][style], track, style ) - 1;
1923}
1924
1925// natives
1926
1927public int Native_IsClientInTagTeam( Handle handler, int numParams )
1928{
1929 int client = GetNativeCell( 1 );
1930 int teamidx = GetNativeCell( 2 );
1931
1932 if( teamidx == -1 )
1933 {
1934 return g_iTeamIndex[client] != -1;
1935 }
1936
1937 return g_iTeamIndex[client] == teamidx;
1938}
1939
1940public int Native_GetClientTeamIndex( Handle handler, int numParams )
1941{
1942 return g_iTeamIndex[GetNativeCell( 1 )];
1943}
1944
1945public int Native_GetTeamName( Handle handler, int numParams )
1946{
1947 int teamidx = GetNativeCell( 1 );
1948 if( !g_bTeamTaken[teamidx] )
1949 {
1950 return false;
1951 }
1952
1953 SetNativeString( 2, g_cTeamName[teamidx], GetNativeCell( 3 ) );
1954
1955 return true;
1956}
1957
1958// helper functions
1959
1960void TeleportClientToZone( int client, int zoneType, int zoneTrack )
1961{
1962 g_bAllowReset[client] = true;
1963 Timer_TeleportClientToZone( client, zoneType, zoneTrack );
1964}
1965
1966void PassToNext( int client, int next, any checkpoint[eCheckpoint], bool usecp = true )
1967{
1968 int length;
1969
1970 length = Timer_GetTotalCheckpoints( client );
1971 for( int i = 0; i < length; i++ )
1972 {
1973 any cp[eCheckpoint];
1974 Timer_GetClientCheckpoint( client, i, cp );
1975
1976 if( cp[CP_ReplayFrames] != checkpoint[CP_ReplayFrames] )
1977 {
1978 delete cp[CP_ReplayFrames];
1979 }
1980 }
1981
1982 length = Timer_GetTotalCheckpoints( next );
1983 for( int i = 0; i < length; i++ )
1984 {
1985 any cp[eCheckpoint];
1986 Timer_GetClientCheckpoint( next, i, cp );
1987
1988 if( cp[CP_ReplayFrames] != checkpoint[CP_ReplayFrames] )
1989 {
1990 delete cp[CP_ReplayFrames];
1991 }
1992 }
1993
1994 Timer_ClearClientCheckpoints( client );
1995 Timer_ClearClientCheckpoints( next );
1996
1997 if( usecp )
1998 {
1999 Timer_SetClientCheckpoint( next, -1, checkpoint );
2000 }
2001 ChangeClientTeam( next, CS_TEAM_SPECTATOR );
2002 ChangeClientTeam( next, CS_TEAM_T );
2003 CS_RespawnPlayer( next );
2004
2005 for( int i = 1; i <= MaxClients; i++ )
2006 {
2007 if( IsClientInGame( i ) && !IsFakeClient( i ) && IsClientObserver( i ) && GetEntPropEnt( client, Prop_Send, "m_hObserverTarget" ) == client )
2008 {
2009 SetEntPropEnt( client, Prop_Send, "m_hObserverTarget", next );
2010 SetEntProp( client, Prop_Send, "m_iObserverMode", 4 );
2011 }
2012 }
2013
2014 ChangeClientTeam( client, CS_TEAM_SPECTATOR );
2015 SetEntPropEnt( client, Prop_Send, "m_hObserverTarget", next );
2016 SetEntProp( client, Prop_Send, "m_iObserverMode", 4 );
2017
2018 g_iCurrentPlayer[g_iTeamIndex[client]] = next;
2019
2020 if( usecp )
2021 {
2022 Timer_TeleportClientToCheckpoint( next, 0 );
2023 }
2024 Timer_OpenCheckpointsMenu( next );
2025 Timer_OpenCheckpointsMenu( client );
2026}
2027
2028void PrintToTeam( int teamidx, char[] message, any ... )
2029{
2030 char buffer[512];
2031 VFormat( buffer, sizeof(buffer), message, 3 );
2032
2033 for( int i = 1; i <= MaxClients; i++ )
2034 {
2035 if( g_iTeamIndex[i] == teamidx )
2036 {
2037 PrintToChat( i, buffer );
2038 }
2039 }
2040}