1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4 
5    This file is part of GtkRadiant.
6 
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "plugin.h"
23 #include "version.h"
24 
25 /*! \file plugin.cpp
26     \brief HydraToolz!
27 
28     HydraToolz by Dominic Clifton - Hydra (Hydra@Hydras-World.com)
29 
30     Overview
31     ========
32 
33 
34     Version History
35     ===============
36 
37     v0.1 - 28/May/2002
38       - Initial version.
39 
40 
41     ToDo
42     ====
43 
44  * Code it ? :)
45 
46  */
47 
48 // =============================================================================
49 // Globals
50 
51 _QERFuncTable_1 g_FuncTable;
52 _QERFileSystemTable g_FileSystemTable;
53 _QEREntityTable g_EntityTable;
54 
55 // cast to GtkWidget*
56 void *g_pMainWnd;
57 
58 // =============================================================================
59 // Ripped from TexTool.cpp
60 
dialog_button_callback(GtkWidget * widget,gpointer data)61 static void dialog_button_callback( GtkWidget *widget, gpointer data ){
62 	GtkWidget *parent;
63 	int *loop, *ret;
64 
65 	parent = gtk_widget_get_toplevel( widget );
66 	loop = (int*)gtk_object_get_data( GTK_OBJECT( parent ), "loop" );
67 	ret = (int*)gtk_object_get_data( GTK_OBJECT( parent ), "ret" );
68 
69 	*loop = 0;
70 	*ret = gpointer_to_int( data );
71 }
72 
dialog_delete_callback(GtkWidget * widget,GdkEvent * event,gpointer data)73 static gint dialog_delete_callback( GtkWidget *widget, GdkEvent* event, gpointer data ){
74 	int *loop;
75 
76 	gtk_widget_hide( widget );
77 	loop = (int*)gtk_object_get_data( GTK_OBJECT( widget ), "loop" );
78 	*loop = 0;
79 
80 	return TRUE;
81 }
82 
DoMessageBox(const char * lpText,const char * lpCaption,guint32 uType)83 int DoMessageBox( const char* lpText, const char* lpCaption, guint32 uType ){
84 	GtkWidget *window, *w, *vbox, *hbox;
85 	int mode = ( uType & MB_TYPEMASK ), ret, loop = 1;
86 
87 	window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
88 	gtk_signal_connect( GTK_OBJECT( window ), "delete_event",
89 						GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL );
90 	gtk_signal_connect( GTK_OBJECT( window ), "destroy",
91 						GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL );
92 	gtk_window_set_title( GTK_WINDOW( window ), lpCaption );
93 	gtk_container_border_width( GTK_CONTAINER( window ), 10 );
94 	gtk_object_set_data( GTK_OBJECT( window ), "loop", &loop );
95 	gtk_object_set_data( GTK_OBJECT( window ), "ret", &ret );
96 	gtk_widget_realize( window );
97 
98 	vbox = gtk_vbox_new( FALSE, 10 );
99 	gtk_container_add( GTK_CONTAINER( window ), vbox );
100 	gtk_widget_show( vbox );
101 
102 	w = gtk_label_new( lpText );
103 	gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 2 );
104 	gtk_label_set_justify( GTK_LABEL( w ), GTK_JUSTIFY_LEFT );
105 	gtk_widget_show( w );
106 
107 	w = gtk_hseparator_new();
108 	gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 2 );
109 	gtk_widget_show( w );
110 
111 	hbox = gtk_hbox_new( FALSE, 10 );
112 	gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 2 );
113 	gtk_widget_show( hbox );
114 
115 	if ( mode == MB_OK ) {
116 		w = gtk_button_new_with_label( "Ok" );
117 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
118 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
119 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
120 		GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
121 		gtk_widget_grab_default( w );
122 		gtk_widget_show( w );
123 		ret = IDOK;
124 	}
125 	else if ( mode ==  MB_OKCANCEL ) {
126 		w = gtk_button_new_with_label( "Ok" );
127 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
128 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
129 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDOK ) );
130 		GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
131 		gtk_widget_grab_default( w );
132 		gtk_widget_show( w );
133 
134 		w = gtk_button_new_with_label( "Cancel" );
135 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
136 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
137 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
138 		gtk_widget_show( w );
139 		ret = IDCANCEL;
140 	}
141 	else if ( mode == MB_YESNOCANCEL ) {
142 		w = gtk_button_new_with_label( "Yes" );
143 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
144 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
145 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDYES ) );
146 		GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
147 		gtk_widget_grab_default( w );
148 		gtk_widget_show( w );
149 
150 		w = gtk_button_new_with_label( "No" );
151 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
152 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
153 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDNO ) );
154 		gtk_widget_show( w );
155 
156 		w = gtk_button_new_with_label( "Cancel" );
157 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
158 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
159 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDCANCEL ) );
160 		gtk_widget_show( w );
161 		ret = IDCANCEL;
162 	}
163 	else /* if (mode == MB_YESNO) */
164 	{
165 		w = gtk_button_new_with_label( "Yes" );
166 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
167 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
168 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDYES ) );
169 		GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
170 		gtk_widget_grab_default( w );
171 		gtk_widget_show( w );
172 
173 		w = gtk_button_new_with_label( "No" );
174 		gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 );
175 		gtk_signal_connect( GTK_OBJECT( w ), "clicked",
176 							GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( IDNO ) );
177 		gtk_widget_show( w );
178 		ret = IDNO;
179 	}
180 
181 	gtk_widget_show( window );
182 	gtk_grab_add( window );
183 
184 	while ( loop )
185 		gtk_main_iteration();
186 
187 	gtk_grab_remove( window );
188 	gtk_widget_destroy( window );
189 
190 	return ret;
191 }
192 
193 // End of rip from TexTool.cpp
194 
195 // =============================================================================
196 // Ripped from cmdlib.cpp
197 
198 /*
199    ====================
200    Extract file parts
201    ====================
202  */
ExtractFilePath(const char * path,char * dest)203 void ExtractFilePath( const char *path, char *dest ){
204 	const char *src;
205 
206 	src = path + strlen( path ) - 1;
207 
208 //
209 // back up until a \ or the start
210 //
211 	while ( src != path && *( src - 1 ) != '/' && *( src - 1 ) != '\\' )
212 		src--;
213 
214 	memcpy( dest, path, src - path );
215 	dest[src - path] = 0;
216 }
217 
ExtractFileName(const char * path,char * dest)218 void ExtractFileName( const char *path, char *dest ){
219 	const char *src;
220 
221 	src = path + strlen( path ) - 1;
222 
223 //
224 // back up until a \ or the start
225 //
226 	while ( src != path && *( src - 1 ) != '/'
227 			&& *( src - 1 ) != '\\' )
228 		src--;
229 
230 	while ( *src )
231 	{
232 		*dest++ = *src++;
233 	}
234 	*dest = 0;
235 }
236 
ConvertDOSToUnixName(char * dst,const char * src)237 void ConvertDOSToUnixName( char *dst, const char *src ){
238 	while ( *src )
239 	{
240 		if ( *src == '\\' ) {
241 			*dst = '/';
242 		}
243 		else{
244 			*dst = *src;
245 		}
246 		dst++; src++;
247 	}
248 	*dst = 0;
249 }
250 
251 // End of rip from cmdlib.cpp
252 
253 // =============================================================================
254 // Actual Plugin Code
255 
256 // get the wad name from the shader name (or an actual wadname) and add to a list of wad names making
257 // sure we don't add duplicates.
258 
AddToWadList(GSList * wadlist,const char * shadername,const char * wad)259 GSList *AddToWadList( GSList *wadlist, const char *shadername, const char *wad ){
260 	char tmpstr[QER_MAX_NAMELEN];
261 	char *wadname;
262 	if ( !shadername && !wad ) {
263 		return wadlist;
264 	}
265 
266 	if ( shadername ) {
267 		if ( strcmp( shadername,"color" ) == 0 ) {
268 			return wadlist;
269 		}
270 		ExtractFilePath( shadername,tmpstr );
271 		// Sys_Printf("checking: %s\n",shadername);
272 
273 		int l = strlen( tmpstr ) - 1;
274 
275 		if ( tmpstr[l] == '/' || tmpstr[l] == '\\' ) {
276 			tmpstr[l] = 0;
277 		}
278 		else
279 		{
280 			Sys_Printf( "WARNING: Unknown wad file for shader %s\n",shadername );
281 			return wadlist;
282 		}
283 
284 		ExtractFileName( tmpstr,tmpstr );
285 
286 		wadname = (char *)malloc( strlen( tmpstr ) + 5 );
287 		sprintf( wadname,"%s.wad",tmpstr );
288 	}
289 	else
290 	{
291 		wadname = strdup( wad );
292 	}
293 
294 	for ( GSList *l = wadlist; l != NULL ; l = l->next )
295 	{
296 		if ( string_equal_nocase( (char *)l->data,wadname ) ) {
297 			free( wadname );
298 			return wadlist;
299 		}
300 	}
301 
302 	Sys_Printf( "Adding Wad File to WAD list: %s (reason: ",wadname );
303 	if ( shadername ) {
304 		Sys_Printf( "see shader \"%s\")\n", shadername );
305 	}
306 	else{
307 		Sys_Printf( "already in WAD key. )\n" );
308 	}
309 	return ( g_slist_append( wadlist, wadname ) );
310 }
311 
UpdateWadKeyPair(void)312 void UpdateWadKeyPair( void ){
313 	int i,nb;
314 
315 	char wads[2048]; // change to CString usage ?
316 	wads[0] = 0;
317 	char *p1,*p2;
318 	entity_t *pEntity;
319 	epair_t *pEpair;
320 	GSList *wadlist = NULL;
321 	face_t  *f;
322 	brush_t *b;
323 	char cleanwadname[QER_MAX_NAMELEN];
324 	const char *actualwad;
325 
326 
327 	pEntity = (entity_t *)g_FuncTable.m_pfnGetEntityHandle( 0 ); // get the worldspawn ent
328 
329 	Sys_Printf( "Searching for in-use wad files...\n" );
330 	for ( pEpair = pEntity->epairs; pEpair != NULL; pEpair = pEpair->next )
331 	{
332 		if ( string_equal_nocase( pEpair->key,"wad" ) ) {
333 			strcpy( wads,pEpair->value );
334 			ConvertDOSToUnixName( wads,wads );
335 
336 			// ok, we got the list of ; delimited wads, now split it into a GSList that contains
337 			// just the wad names themselves.
338 
339 			p1 = wads;
340 
341 			do
342 			{
343 				p2 = strchr( p1,';' );
344 				if ( p2 ) {
345 					*p2 = 0; // swap the ; with a null terminator
346 
347 				}
348 				if ( strchr( p1,'/' ) || strchr( p1,'\\' ) ) {
349 					ExtractFileName( p1,cleanwadname );
350 					wadlist = AddToWadList( wadlist, NULL, cleanwadname );
351 				}
352 				else
353 				{
354 					wadlist = AddToWadList( wadlist, NULL, p1 );
355 				}
356 				if ( p2 ) {
357 					p1 = p2 + 1; // point back to the remainder of the string
358 				}
359 				else{
360 					p1 = NULL; // make it so we exit the loop.
361 
362 				}
363 			} while ( p1 );
364 
365 			// ok, now we have a list of wads in GSList.
366 			// now we need to add any new wadfiles (with their paths) to this list
367 			// so scan all brushes and see what wads are in use
368 			// FIXME: scan brushes only in the region ?
369 
370 			break; // we don't need to process any more key/pairs.
371 		}
372 	}
373 
374 	nb = g_FuncTable.m_pfnAllocateActiveBrushHandles();
375 	for ( i = 0; i < nb; i++ )
376 	{
377 		b = (brush_t *)g_FuncTable.m_pfnGetActiveBrushHandle( i );
378 		if ( b->patchBrush ) { // patches in halflife ?
379 			wadlist = AddToWadList( wadlist, b->pPatch->pShader->getName(),NULL );
380 		}
381 		else
382 		{
383 			for ( f = b->brush_faces ; f ; f = f->next )
384 			{
385 				wadlist = AddToWadList( wadlist, f->pShader->getName(),NULL );
386 			}
387 		}
388 	}
389 	g_FuncTable.m_pfnReleaseActiveBrushHandles();
390 
391 	nb = g_FuncTable.m_pfnAllocateSelectedBrushHandles();
392 	for ( i = 0; i < nb; i++ )
393 	{
394 		b = (brush_t *)g_FuncTable.m_pfnGetSelectedBrushHandle( i );
395 		if ( b->patchBrush ) { // patches in halflife ?
396 			wadlist = AddToWadList( wadlist, b->pPatch->pShader->getName(),NULL );
397 		}
398 		else
399 		{
400 			for ( f = b->brush_faces ; f ; f = f->next )
401 			{
402 				wadlist = AddToWadList( wadlist, f->pShader->getName(),NULL );
403 			}
404 		}
405 	}
406 	g_FuncTable.m_pfnReleaseSelectedBrushHandles();
407 
408 	// Now we have a complete list of wadnames (without paths) so we just have to turn this
409 	// back to a ; delimited list.
410 
411 	wads[0] = 0;
412 	while ( wadlist )
413 	{
414 		if ( string_equal_nocase( (char *)wadlist->data,"common-hydra.wad" ) ) {
415 			Sys_Printf( "Skipping radiant-supplied wad file %s\n",(char *)wadlist->data );
416 		}
417 		else
418 		{
419 			if ( wads[0] ) {
420 				strcat( wads,";" );
421 			}
422 
423 			actualwad = vfsGetFullPath( (char *)wadlist->data );
424 
425 			if ( actualwad ) {
426 				strcat( wads, actualwad );
427 			}
428 			else
429 			{
430 				Sys_Printf( "WARNING: could not locate wad file %s\n",(char *)wadlist->data );
431 				strcat( wads, (char *)wadlist->data );
432 			}
433 		}
434 
435 		free( wadlist->data );
436 		wadlist = g_slist_remove( wadlist, wadlist->data );
437 	}
438 
439 	// store the wad list back in the worldspawn.
440 	if ( wads[0] ) {
441 		//free(pEpair->value);
442 		//pEpair->value = strdup(wads);
443 		SetKeyValue( pEntity, "wad", wads );
444 	}
445 
446 }
447 
448 // =============================================================================
449 // PLUGIN INTERFACE STUFF
450 
451 // plugin name
452 const char *PLUGIN_NAME = "Q3 Texture Tools";
453 
454 // commands in the menu
455 const char *PLUGIN_COMMANDS = "About...;Create/Update WAD keypair";
456 
457 const char *PLUGIN_ABOUT = "HydraToolz for GTKRadiant\n\n"
458 						   "By Hydra!";
459 
QERPlug_GetFuncTable()460 extern "C" void* WINAPI QERPlug_GetFuncTable(){
461 	return &g_FuncTable;
462 }
463 
QERPlug_Init(void * hApp,void * pWidget)464 const char* QERPlug_Init( void* hApp, void *pWidget ){
465 	GtkWidget* pMainWidget = static_cast<GtkWidget*>( pWidget );
466 
467 	g_pMainWnd = pMainWidget;
468 	memset( &g_FuncTable, 0, sizeof( _QERFuncTable_1 ) );
469 	g_FuncTable.m_nSize = sizeof( _QERFuncTable_1 );
470 	return "HydraToolz for GTKRadiant"; // do we need this ? hmmm
471 }
472 
QERPlug_GetName()473 const char* QERPlug_GetName(){
474 	return (char*)PLUGIN_NAME;
475 }
476 
QERPlug_GetCommandList()477 const char* QERPlug_GetCommandList(){
478 	return PLUGIN_COMMANDS;
479 }
480 
QERPlug_Dispatch(const char * p,vec3_t vMin,vec3_t vMax,bool bSingleBrush)481 extern "C" void QERPlug_Dispatch( const char* p, vec3_t vMin, vec3_t vMax, bool bSingleBrush ){
482 	if ( !strcmp( p, "Create/Update WAD keypair" ) ) {
483 		UpdateWadKeyPair();
484 	}
485 	else if ( !strcmp( p, "About..." ) ) {
486 		g_FuncTable.m_pfnMessageBox( (GtkWidget*)NULL, PLUGIN_ABOUT, "About", eMB_OK );
487 	}
488 }
489 
490 // =============================================================================
491 // SYNAPSE
492 
493 CSynapseServer* g_pSynapseServer = NULL;
494 CSynapseClientHydraToolz g_SynapseClient;
495 
Synapse_EnumerateInterfaces(const char * version,CSynapseServer * pServer)496 extern "C" CSynapseClient * SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces( const char *version, CSynapseServer *pServer ){
497 	if ( strcmp( version, SYNAPSE_VERSION ) ) {
498 		Syn_Printf( "ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version );
499 		return NULL;
500 	}
501 	g_pSynapseServer = pServer;
502 	g_pSynapseServer->IncRef();
503 	Set_Syn_Printf( g_pSynapseServer->Get_Syn_Printf() );
504 
505 	g_SynapseClient.AddAPI( PLUGIN_MAJOR, "HydraToolz", sizeof( _QERPluginTable ) );
506 	g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable );
507 	g_SynapseClient.AddAPI( VFS_MAJOR, "wad", sizeof( g_FileSystemTable ), SYN_REQUIRE, &g_FileSystemTable );
508 	g_SynapseClient.AddAPI( ENTITY_MAJOR, NULL, sizeof( g_EntityTable ), SYN_REQUIRE, &g_EntityTable );
509 	return &g_SynapseClient;
510 }
511 
RequestAPI(APIDescriptor_t * pAPI)512 bool CSynapseClientHydraToolz::RequestAPI( APIDescriptor_t *pAPI ){
513 	if ( !strcmp( pAPI->major_name, PLUGIN_MAJOR ) ) {
514 		_QERPluginTable *pTable = static_cast<_QERPluginTable*>( pAPI->mpTable );
515 		pTable->m_pfnQERPlug_Init = QERPlug_Init;
516 		pTable->m_pfnQERPlug_GetName = QERPlug_GetName;
517 		pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList;
518 		pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch;
519 		return true;
520 	}
521 
522 	Syn_Printf( "ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo() );
523 	return false;
524 }
525 
GetInfo()526 const char* CSynapseClientHydraToolz::GetInfo(){
527 	return "HydraToolz plugin built " __DATE__ " " RADIANT_VERSION;
528 }
529