· 6 years ago · Jul 29, 2019, 10:28 AM
1#include <sourcemod>
2#include <sdktools>
3
4#pragma semicolon 1
5
6#define PLUGIN_VERSION "1.0.0"
7#define MAX_CONFIG_NAME 16 // TODO: 16 should be enough
8#define MAX_CUSTOM_CFG 4 // TODO: increase once tested
9// https://github.com/VSES/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/se2007/engine/baseclient.cpp#L1008
10#define MAX_RENDER_LINES 64
11#define MINIMUM_VALUE_COMPRESSED 0.1
12#define UPDATE_INTERVAL 100
13
14enum // Presets
15{
16 PRESET_CLIP,
17 PRESET_MONSTERCLIP,
18 PRESET_PLAYERCLIP,
19 PRESET_MAX
20};
21
22enum // Colors
23{
24 COLOR_RED,
25 COLOR_GREEN,
26 COLOR_BLUE,
27 COLOR_PINK,
28 COLOR_MAX
29};
30
31int g_colors[COLOR_MAX][4] = {
32 {255, 0, 0, 255},
33 {0, 255, 0, 255},
34 {0, 0, 255, 255},
35 {255, 0, 255, 255}
36};
37
38// Rendering
39enum struct vector_t
40{
41 float x;
42 float y;
43 float z;
44};
45
46enum struct line_t
47{
48 vector_t start;
49 vector_t end;
50};
51
52enum struct renderline_t
53{
54 vector_t start;
55 vector_t end;
56 int color[4];
57 int contents;
58};
59
60enum struct renderset_t
61{
62 ArrayList lines; // <line_t> this must be 4 bytes not yea but
63 int contents;
64};
65
66ArrayList g_render_array; // <renderset_t>
67ArrayList g_render_lines[MAXPLAYERS + 1];
68int g_iBeamSprite;
69
70// Render profile / config
71enum struct rendercfg_t
72{
73 int contents; // bitflags
74 int color[4]; // rgba
75 char name[MAX_CONFIG_NAME]; // name of cfg
76};
77
78rendercfg_t g_presets[PRESET_MAX];
79
80// Client settings
81bool g_bEnablePreset[ MAXPLAYERS + 1 ][ PRESET_MAX ];
82bool g_bEnableCustomCfg[ MAXPLAYERS + 1 ][ MAX_CUSTOM_CFG ];
83rendercfg_t g_customcfg[ MAXPLAYERS + 1 ][ MAX_CUSTOM_CFG ];
84int g_Index_Of_RenderedLine[MAXPLAYERS + 1];
85
86// Database
87Database g_db;
88
89// Menus
90Menu g_menu_main;
91Menu g_menu_presets;
92
93public Plugin myinfo =
94{
95 name = "Leafvis Renderer (cliptool)",
96 author = "Kamay && ici",
97 description = "Draws invisible brushes on the map",
98 version = PLUGIN_VERSION,
99 url = "https://gitlab.com/xutaxkamay/cliptool"
100};
101
102/**
103 * Clears and initialises g_render_array
104 */
105void initRenderArray()
106{
107 if (g_render_array != null)
108 {
109 // clean up nested arrays
110 int size = g_render_array.Length;
111
112 for (int i = 0; i < size; ++i)
113 {
114 renderset_t renderset;
115 g_render_array.GetArray(i, renderset, sizeof(renderset));
116
117 if (renderset.lines != null)
118 {
119 delete renderset.lines;
120 renderset.lines = null;
121 }
122 }
123 g_render_array.Clear();
124 return;
125 }
126 g_render_array = new ArrayList( sizeof(renderset_t) );
127}
128
129/*
130 * Returns the total number of lines
131 */
132int getTotalLines()
133{
134 int sum = 0;
135 int size = g_render_array.Length;
136
137 for (int i = 0; i < size; ++i)
138 {
139 renderset_t renderset;
140 g_render_array.GetArray(i, renderset, sizeof(renderset));
141
142 if (renderset.lines != null)
143 {
144 sum += renderset.lines.Length;
145 }
146 }
147 return sum;
148}
149
150/**
151 * Parses the .dat file output by the C++ cliptool plugin
152 */
153void parseRenderFile()
154{
155 File file;
156
157 char path[ PLATFORM_MAX_PATH ] = "addons/cliptool/";
158 char map[ 64 ];
159
160 GetCurrentMap( map, sizeof( map ) );
161
162 StrCat( path, sizeof( path ), map );
163 StrCat( path, sizeof( path ), ".dat" );
164
165 file = OpenFile( path, "rb", true, NULL_STRING );
166
167 if ( file == null )
168 {
169 SetFailState( "Couldn't open \"%s\"", path );
170 return;
171 }
172
173 initRenderArray();
174
175 int size;
176 file.ReadInt32( size );
177
178 for (int i = 0; i < size; ++i)
179 {
180 int numlines, temp;
181 file.ReadInt32( numlines );
182
183 renderset_t renderset;
184 renderset.lines = new ArrayList( sizeof(line_t) );
185
186 for (int j = 0; j < numlines; ++j)
187 {
188 line_t line;
189
190 file.ReadInt32( temp );
191 line.start.x = view_as<float>( temp );
192 file.ReadInt32( temp );
193 line.start.y = view_as<float>( temp );
194 file.ReadInt32( temp );
195 line.start.z = view_as<float>( temp );
196
197 file.ReadInt32( temp );
198 line.end.x = view_as<float>( temp );
199 file.ReadInt32( temp );
200 line.end.y = view_as<float>( temp );
201 file.ReadInt32( temp );
202 line.end.z = view_as<float>( temp );
203
204 renderset.lines.PushArray(line, sizeof(line_t));
205 }
206
207 file.ReadInt32( renderset.contents );
208
209 g_render_array.PushArray( renderset, sizeof(renderset) );
210 }
211
212 LogMessage( "Parsed file successfully leafvis(%i), lines(%i)",
213 GetArraySize(g_render_array),
214 getTotalLines() );
215
216 delete file;
217}
218
219/**
220 * Creates the local sqlite db structure to hold client configs and connect to it
221 */
222void initDB()
223{
224 if (g_db != null)
225 {
226 delete g_db;
227 g_db = null;
228 }
229
230 Database.Connect(callback_initDB, "cliptool");
231}
232
233void callback_initDB(Database db, const char[] error, any data)
234{
235 if (db == null)
236 {
237 SetFailState("[cliptool] Database connection failure: %s", error);
238 }
239 g_db = db;
240 g_db.SetCharset("utf8");
241
242 SQL_LockDatabase(g_db);
243 SQL_FastQuery(g_db, "CREATE TABLE IF NOT EXISTS cliptool (id INT PRIMARY KEY, steam_id INT NOT NULL UNIQUE, contents INT NOT NULL, name CHAR(16) NOT NULL, red INT NOT NULL, green INT NOT NULL, blue INT NOT NULL, alpha INT NOT NULL);");
244 SQL_UnlockDatabase(g_db);
245}
246
247/**
248 * Creates the default render presets for common brush types
249 */
250void makePresets()
251{
252 // Clip
253 g_presets[PRESET_CLIP].contents = CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP;
254 array_copy( g_presets[PRESET_CLIP].color, g_colors[COLOR_RED], 4);
255 strcopy(g_presets[PRESET_CLIP].name, MAX_CONFIG_NAME, "Clip");
256
257 // Monsterclip
258 g_presets[PRESET_MONSTERCLIP].contents = CONTENTS_MONSTERCLIP;
259 array_copy( g_presets[PRESET_MONSTERCLIP].color, g_colors[COLOR_GREEN], 4);
260 strcopy(g_presets[PRESET_MONSTERCLIP].name, MAX_CONFIG_NAME, "Monster Clip");
261
262 // Playerclip
263 g_presets[PRESET_PLAYERCLIP].contents = CONTENTS_PLAYERCLIP;
264 array_copy( g_presets[PRESET_PLAYERCLIP].color, g_colors[COLOR_PINK], 4);
265 strcopy(g_presets[PRESET_PLAYERCLIP].name, MAX_CONFIG_NAME, "Player Clip");
266}
267
268void updateLinesToRender(int client)
269{
270 g_Index_Of_RenderedLine[client] = 0;
271
272 if (g_render_lines[client] != null)
273 {
274 delete g_render_lines[client];
275 g_render_lines[client] = null;
276 }
277
278 g_render_lines[client] = new ArrayList( sizeof(renderline_t) );
279
280 for (int preset = 0; preset < PRESET_MAX; preset++)
281 {
282 if (g_bEnablePreset[client][preset])
283 {
284 int contents = g_presets[preset].contents;
285
286 for (int render = 0; render < g_render_array.Length; render++)
287 {
288 renderset_t renderset;
289 g_render_array.GetArray(render, renderset, sizeof(renderset));
290
291 if ((renderset.contents & contents) == contents)
292 {
293 for (int l = 0; l < renderset.lines.Length; l++)
294 {
295 line_t line;
296 renderset.lines.GetArray(l, line, sizeof(line_t));
297
298 renderline_t renderline;
299 renderline.start.x = line.start.x;
300 renderline.start.y = line.start.y;
301 renderline.start.z = line.start.z;
302 renderline.end.x = line.end.x;
303 renderline.end.y = line.end.y;
304 renderline.end.z = line.end.z;
305 renderline.color[0] = g_presets[preset].color[0];
306 renderline.color[1] = g_presets[preset].color[1];
307 renderline.color[2] = g_presets[preset].color[2];
308 renderline.color[3] = g_presets[preset].color[3];
309 renderline.contents = renderset.contents;
310
311 // replace overlapping lines which have fewer matching contents bits
312 int overlap = findOverlappingLine( client, renderline );
313 if ( overlap != -1 )
314 {
315 // replace
316 g_render_lines[client].SetArray(overlap, renderline, sizeof(renderline_t));
317 }
318 else
319 {
320 g_render_lines[client].PushArray(renderline, sizeof(renderline_t));
321 }
322 }
323 }
324 }
325 }
326 }
327
328 if ( !g_render_lines[client].Length )
329 {
330 // nothing to render
331 delete g_render_lines[client];
332 g_render_lines[client] = null;
333 }
334}
335
336int findOverlappingLine(int client, const renderline_t renderline)
337{
338 if ( g_render_lines[client] == null )
339 {
340 return -1;
341 }
342
343 int size = g_render_lines[client].Length;
344 for (int i = 0; i < size; ++i)
345 {
346 renderline_t against; // line to check against
347 g_render_lines[client].GetArray(i, against, sizeof(against));
348
349 // do positions match?
350 if ( renderline.start.x == against.start.x &&
351 renderline.start.y == against.start.y &&
352 renderline.start.z == against.start.z &&
353 renderline.end.x == against.end.x &&
354 renderline.end.y == against.end.y &&
355 renderline.end.z == against.end.z )
356 {
357 // do any of the flags match
358 if ( against.contents & renderline.contents )
359 {
360 // does the new one have more?
361 if ( num_contents_set(against.contents) <= num_contents_set(renderline.contents) )
362 {
363 return i;
364 }
365 }
366 }
367 }
368 return -1;
369}
370
371int num_contents_set(int contents)
372{
373 int sum = 0;
374
375 for (int i = 0; i < 31; ++i)
376 {
377 if (contents & 1)
378 {
379 ++sum;
380 }
381 contents = contents >> 1;
382 }
383 return sum;
384}
385
386public void OnPluginStart()
387{
388 // TODO: work on db and custom render cfg
389 //initDB();
390 //for(int i = 0; i <= MAXPLAYERS; i++)
391 // updateLinesToRender(i);
392
393 makePresets();
394 menu_main_create();
395 menu_presets_create();
396
397 PrintToServer("sizeof(vector_t) = %i", sizeof(vector_t));
398 PrintToServer("sizeof(line_t) = %i", sizeof(line_t));
399 PrintToServer("sizeof(renderset_t) = %i", sizeof(renderset_t));
400 PrintToServer("sizeof(rendercfg_t) = %i", sizeof(rendercfg_t));
401
402 RegConsoleCmd( "sm_clips", SM_Clips, "Command to dynamically toggle clip brushes visibility" );
403 RegConsoleCmd( "sm_clipsmenu", SM_ClipsMenu, "Clips Menu" );
404}
405
406public void OnMapStart()
407{
408 parseRenderFile();
409
410 // TODO: load a see-through walls sprite (.vmt with ignorez flag)
411 g_iBeamSprite = PrecacheModel( "materials/sprites/laserbeam.vmt" );
412}
413
414public void OnClientPutInServer(int client)
415{
416 // TODO: add cookies later
417 for(int i = 0; i < PRESET_MAX; i++)
418 g_bEnablePreset[ client ][ i ] = false;
419
420 updateLinesToRender( client );
421}
422
423public Action SM_Clips( int client, int args )
424{
425 // Can't use this cmd from within the server console
426 if ( !client )
427 {
428 return Plugin_Handled;
429 }
430
431 g_bEnablePreset[ client ][ PRESET_PLAYERCLIP ] = !g_bEnablePreset[ client ][ PRESET_PLAYERCLIP ];
432
433 updateLinesToRender( client );
434
435 if ( g_bEnablePreset[ client ][ PRESET_PLAYERCLIP ] )
436 {
437 PrintToChat( client, "Showing clip brushes." );
438 }
439 else
440 {
441 PrintToChat( client, "Stopped showing clip brushes." );
442 }
443
444 return Plugin_Handled;
445}
446
447public Action SM_ClipsMenu( int client, int args )
448{
449 // Can't use this cmd from within the server console
450 if ( !client )
451 {
452 return Plugin_Handled;
453 }
454
455 g_menu_main.Display(client, MENU_TIME_FOREVER);
456 return Plugin_Handled;
457}
458
459void menu_main_create()
460{
461 if ( g_menu_main != null )
462 {
463 delete g_menu_main;
464 }
465
466 g_menu_main = new Menu(menu_main_callback);
467 g_menu_main.SetTitle("Leafvis Renderer (cliptool)");
468 g_menu_main.AddItem("presets", "Presets");
469 g_menu_main.AddItem("customcfg", "Custom Profiles");
470 g_menu_main.ExitButton = true;
471
472 // TODO: revisit menu structure
473}
474
475public int menu_main_callback(Menu menu, MenuAction action, int client, int item)
476{
477 switch (action)
478 {
479 case MenuAction_Select:
480 {
481 char info[32];
482 menu.GetItem(item, info, sizeof(info));
483
484 if ( !strcmp(info, "presets") )
485 {
486 g_menu_presets.Display(client, MENU_TIME_FOREVER);
487 }
488 else if ( !strcmp(info, "customcfg") )
489 {
490 // TODO: send customcfg menu
491 }
492 }
493 }
494}
495
496void menu_presets_create()
497{
498 if ( g_menu_presets != null )
499 {
500 delete g_menu_presets;
501 }
502
503 g_menu_presets = new Menu(menu_presets_callback, MENU_ACTIONS_DEFAULT | MenuAction_DisplayItem);
504 g_menu_presets.SetTitle("Presets");
505
506 char info[ 4 ];
507 for ( int i = 0; i < PRESET_MAX; ++i )
508 {
509 IntToString( i, info, sizeof(info) );
510 g_menu_presets.AddItem( info, g_presets[ i ].name );
511 }
512
513 g_menu_presets.ExitButton = true;
514 g_menu_presets.ExitBackButton = true;
515}
516
517public int menu_presets_callback(Menu menu, MenuAction action, int client, int item)
518{
519 switch (action)
520 {
521 case MenuAction_Select:
522 {
523 char info[ 4 ];
524 menu.GetItem(item, info, sizeof(info));
525
526 // toggle preset
527 int preset = StringToInt(info);
528 g_bEnablePreset[ client ][ preset ] = !g_bEnablePreset[ client ][ preset ];
529 g_menu_presets.Display(client, MENU_TIME_FOREVER);
530 updateLinesToRender( client );
531 }
532 case MenuAction_DisplayItem:
533 {
534 // [ x ] : display the proper string /toggle
535 char info[ 4 ], display[ MAX_CONFIG_NAME + 8 ];
536 menu.GetItem(item, info, sizeof(info), _, display, sizeof(display));
537
538 int preset = StringToInt(info);
539
540 Format(display, sizeof(display), "[%s] - %s",
541 (g_bEnablePreset[ client ][ preset ] ? "x" : " "),
542 display);
543
544 return RedrawMenuItem(display);
545 }
546 case MenuAction_Cancel:
547 {
548 if (item == MenuCancel_ExitBack)
549 {
550 g_menu_main.Display(client, MENU_TIME_FOREVER);
551 }
552 }
553 }
554 return 0;
555}
556
557public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2])
558{
559 if ( IsFakeClient( client ) )
560 return Plugin_Continue;
561
562 if (g_render_lines[client] == null)
563 return Plugin_Continue;
564
565 // Render every 10 ticks.
566 if (tickcount % UPDATE_INTERVAL)
567 return Plugin_Continue;
568
569 // Don't draw anything if there isn't anything to render..
570 if (g_render_lines[client].Length)
571 {
572 // When is the next time that the beam at this index will be rendered
573 // = (Number of lines to render) / (Number of lines that will be rendered this tick) * tick_interval * 10
574 // We multiply by two the tick interval so it doesn't blink.
575 //float flLifeTime = ( ((float(MAX_RENDER_LINES) / g_render_lines[client].Length) / float(MAX_RENDER_LINES)) * (GetTickInterval() * UPDATE_INTERVAL) );
576
577 // There is a problem with clamping values due to compression of netvars when its sent to client.
578 // When it's lower than < MINIMUM_VALUE_COMPRESSED , the server sends the value as 0 and you get an infinite lifetime.
579 // Good job valve.
580 // TODO: Find proper minimal value
581 //flLifeTime = (flLifeTime < MINIMUM_VALUE_COMPRESSED) ? MINIMUM_VALUE_COMPRESSED : flLifeTime + MINIMUM_VALUE_COMPRESSED;
582 float flLifeTime = 1.0;
583
584 // Have we rendered enough lines on this tick for this client?
585 int renderedLines = 0;
586 while (renderedLines < MAX_RENDER_LINES)
587 {
588 // Restart from index 0 for this client if we go above the limit of the lines that
589 // Needs to be rendered.
590 //if (g_Index_Of_RenderedLine[client] >= g_render_lines[client].Length)
591 //{
592 // g_Index_Of_RenderedLine[client] = 0;
593 //}
594
595 renderline_t renderline;
596 g_render_lines[client].GetArray(g_Index_Of_RenderedLine[client] % g_render_lines[client].Length, renderline, sizeof(renderline_t));
597
598 int color_copy[4];
599 float start_copy[3];
600 float end_copy[3];
601
602 array_copy(color_copy, renderline.color, 4);
603 array_copy(start_copy, renderline.start, 3);
604 array_copy(end_copy, renderline.end, 3);
605
606 // TODO: We'll need to use the render_ignorez and render vmts depending on what the player chosen.
607 TE_SetupBeamPoints(start_copy, end_copy, g_iBeamSprite, 0, 0, 0, flLifeTime, 0.5, 0.5, 0, 0.0, color_copy, 0);
608 TE_SendToClient(client);
609
610 // Increment the index for our next beam that needs to be drawn.
611 ++g_Index_Of_RenderedLine[client];
612
613 // Increment the lines rendered
614 renderedLines++;
615 }
616 }
617
618
619 // Don't delete me it might be useful one day.
620 /*int countContent = 0;
621
622 for (int preset = 0; preset < PRESET_MAX; preset++)
623 {
624 if (g_bEnablePreset[client][preset])
625 {
626 int contents = g_presets[preset].contents;
627
628 for (int render = 0; render < g_render_array.Length; render++)
629 {
630 renderset_t renderset;
631 g_render_array.GetArray(render, renderset, sizeof(renderset));
632
633 if ((renderset.contents & contents) == contents)
634 {
635 countContent++;
636
637 if(g_rendered_contents[client] >= countContent)
638 continue;
639
640 int old_rendered_lines = g_count_rendered_lines[client];
641
642 bool bNeedsRemainder = false;
643
644 while (g_count_rendered_lines[client] <= (old_rendered_lines + (MAX_RENDER_LINES - g_lines_remainder[client])))
645 {
646 if (g_count_rendered_lines[client] >= renderset.lines.Length)
647 {
648 g_rendered_contents[client]++;
649
650 if (g_rendered_contents[client] >= g_maxCountContents[client])
651 {
652 g_rendered_contents[client] = 0;
653 }
654
655 g_lines_remainder[client] = (g_count_rendered_lines[client] % MAX_RENDER_LINES);
656 g_count_rendered_lines[client] = 0;
657 bNeedsRemainder = true;
658 break;
659 }
660
661 line_t line;
662 renderset.lines.GetArray(g_count_rendered_lines[client], line, sizeof(line));
663
664 // thanks sm
665 int color_copy[4];
666 float start_copy[3];
667 float end_copy[3];
668
669 array_copy(color_copy, g_presets[preset].color, 4);
670 array_copy(start_copy, line.start, 3);
671 array_copy(end_copy, line.end, 3);
672
673 TE_SetupBeamPoints(start_copy, end_copy, g_iBeamSprite, 0, 0, 0, flLifeTime, 0.5, 0.5, 0, 0.0, color_copy, 0);
674 TE_SendToClient(client);
675
676 g_count_rendered_lines[client]++;
677 }
678
679 if(!bNeedsRemainder)
680 {
681 g_lines_remainder[client] = 0;
682 }
683 }
684 }
685 }
686 }*/
687
688 return Plugin_Continue;
689}
690
691void array_copy(any[] dest, const any[] src, int size)
692{
693 for ( int i = 0; i < size; ++i )
694 {
695 dest[i] = src[i];
696 }
697}
698
699/*
700bool is_array_equal(const any[] arr1, const any[] arr2, int size)
701{
702 for (int i = 0; i < size; ++i)
703 {
704 if (arr1[i] != arr2[i])
705 {
706 return false;
707 }
708 }
709 return true;
710}
711
712bool are_vectors_equal(const vector_t vec1, const vector_t vec2)
713{
714 return vec1.x == vec2.x && vec1.y == vec2.y && vec1.z == vec2.z;
715}
716
717void copy_vector(vector_t dest, const vector_t src)
718{
719 dest = src;
720}
721
722bool are_lines_equal(const line_t line1, const line_t line2)
723{
724 return are_vectors_equal(line1.start, line2.start) &&
725 are_vectors_equal(line1.end, line2.end);
726}
727*/