radiant: add optional pakpath support
[xonotic/netradiant.git] / radiant / qe3.cpp
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 /*
23    The following source code is licensed by Id Software and subject to the terms of
24    its LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with
25    GtkRadiant. If you did not receive a LIMITED USE SOFTWARE LICENSE AGREEMENT,
26    please contact Id Software immediately at info@idsoftware.com.
27  */
28
29 //
30 // Linux stuff
31 //
32 // Leonardo Zide (leo@lokigames.com)
33 //
34
35 #include "qe3.h"
36 #include "globaldefs.h"
37
38 #include <gtk/gtk.h>
39
40 #include "debugging/debugging.h"
41
42 #include "ifilesystem.h"
43 //#include "imap.h"
44
45 #include <map>
46
47 #include <uilib/uilib.h>
48
49 #include "stream/textfilestream.h"
50 #include "cmdlib.h"
51 #include "stream/stringstream.h"
52 #include "os/path.h"
53 #include "scenelib.h"
54
55 #include "gtkutil/messagebox.h"
56 #include "error.h"
57 #include "map.h"
58 #include "build.h"
59 #include "points.h"
60 #include "camwindow.h"
61 #include "mainframe.h"
62 #include "preferences.h"
63 #include "watchbsp.h"
64 #include "autosave.h"
65 #include "convert.h"
66
67 QEGlobals_t g_qeglobals;
68
69
70 #if GDEF_OS_WINDOWS
71 #define PATH_MAX 260
72 #endif
73
74
75 void QE_InitVFS(){
76         // VFS initialization -----------------------
77         // we will call GlobalFileSystem().initDirectory, giving the directories to look in (for files in pk3's and for standalone files)
78         // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
79         // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
80
81         const char* gamename = gamename_get();
82         const char* basegame = basegame_get();
83         const char* userRoot = g_qeglobals.m_userEnginePath.c_str();
84         const char* globalRoot = EnginePath_get();
85
86         // if we have a mod dir
87         if ( !string_equal( gamename, basegame ) ) {
88                 // ~/.<gameprefix>/<fs_game>
89                 if ( userRoot ) {
90                         StringOutputStream userGamePath( 256 );
91                         userGamePath << userRoot << gamename << '/';
92                         GlobalFileSystem().initDirectory( userGamePath.c_str() );
93                 }
94
95                 // <fs_basepath>/<fs_game>
96                 {
97                         StringOutputStream globalGamePath( 256 );
98                         globalGamePath << globalRoot << gamename << '/';
99                         GlobalFileSystem().initDirectory( globalGamePath.c_str() );
100                 }
101         }
102
103         // ~/.<gameprefix>/<fs_main>
104         if ( userRoot ) {
105                 StringOutputStream userBasePath( 256 );
106                 userBasePath << userRoot << basegame << '/';
107                 GlobalFileSystem().initDirectory( userBasePath.c_str() );
108         }
109
110         // <fs_basepath>/<fs_main>
111         {
112                 StringOutputStream globalBasePath( 256 );
113                 globalBasePath << globalRoot << basegame << '/';
114                 GlobalFileSystem().initDirectory( globalBasePath.c_str() );
115         }
116
117         // extra pakpaths
118         for ( int i = 0; i < g_pakPathCount; i++ ) {
119                 if (g_strcmp0( g_strPakPath[i].c_str(), "")) {
120                         GlobalFileSystem().initDirectory( g_strPakPath[i].c_str() );
121                 }
122         }
123 }
124
125 int g_numbrushes = 0;
126 int g_numentities = 0;
127
128 void QE_UpdateStatusBar(){
129         char buffer[128];
130         sprintf( buffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
131         g_pParentWnd->SetStatusText( g_pParentWnd->m_brushcount_status, buffer );
132 }
133
134 SimpleCounter g_brushCount;
135
136 void QE_brushCountChanged(){
137         g_numbrushes = int(g_brushCount.get() );
138         QE_UpdateStatusBar();
139 }
140
141 SimpleCounter g_entityCount;
142
143 void QE_entityCountChanged(){
144         g_numentities = int(g_entityCount.get() );
145         QE_UpdateStatusBar();
146 }
147
148 bool ConfirmModified( const char* title ){
149         if ( !Map_Modified( g_map ) ) {
150                 return true;
151         }
152
153         auto result = ui::alert( MainFrame_getWindow(), "The current map has changed since it was last saved.\nDo you want to save the current map before continuing?", title, ui::alert_type::YESNOCANCEL, ui::alert_icon::Question );
154         if ( result == ui::alert_response::CANCEL ) {
155                 return false;
156         }
157         if ( result == ui::alert_response::YES ) {
158                 if ( Map_Unnamed( g_map ) ) {
159                         return Map_SaveAs();
160                 }
161                 else
162                 {
163                         return Map_Save();
164                 }
165         }
166         return true;
167 }
168
169 void bsp_init(){
170         build_set_variable( "RadiantPath", AppPath_get() );
171         build_set_variable( "ExecutableType", RADIANT_EXECUTABLE );
172         build_set_variable( "EnginePath", EnginePath_get() );
173         build_set_variable( "UserEnginePath", g_qeglobals.m_userEnginePath.c_str() );
174         build_set_variable( "MonitorAddress", ( g_WatchBSP_Enabled ) ? "127.0.0.1:39000" : "" );
175         build_set_variable( "GameName", gamename_get() );
176
177         StringBuffer ExtraQ3map2Args;
178         // extra pakpaths
179         for ( int i = 0; i < g_pakPathCount; i++ ) {
180                 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
181                         ExtraQ3map2Args.push_string( " -fs_pakpath \"" );
182                         ExtraQ3map2Args.push_string( g_strPakPath[i].c_str() );
183                         ExtraQ3map2Args.push_string( "\"" );
184                 }
185         }
186         build_set_variable( "ExtraQ3map2Args", ExtraQ3map2Args.c_str() );
187
188         const char* mapname = Map_Name( g_map );
189         StringOutputStream name( 256 );
190         name << StringRange( mapname, path_get_filename_base_end( mapname ) ) << ".bsp";
191
192         build_set_variable( "MapFile", mapname );
193         build_set_variable( "BspFile", name.c_str() );
194 }
195
196 void bsp_shutdown(){
197         build_clear_variables();
198 }
199
200 class ArrayCommandListener : public CommandListener
201 {
202 GPtrArray* m_array;
203 public:
204 ArrayCommandListener(){
205         m_array = g_ptr_array_new();
206 }
207 ~ArrayCommandListener(){
208         g_ptr_array_free( m_array, TRUE );
209 }
210
211 void execute( const char* command ){
212         g_ptr_array_add( m_array, g_strdup( command ) );
213 }
214
215 GPtrArray* array() const {
216         return m_array;
217 }
218 };
219
220 class BatchCommandListener : public CommandListener
221 {
222 TextOutputStream& m_file;
223 std::size_t m_commandCount;
224 const char* m_outputRedirect;
225 public:
226 BatchCommandListener( TextOutputStream& file, const char* outputRedirect ) : m_file( file ), m_commandCount( 0 ), m_outputRedirect( outputRedirect ){
227 }
228
229 void execute( const char* command ){
230         m_file << command;
231         if ( m_commandCount == 0 ) {
232                 m_file << " > ";
233         }
234         else
235         {
236                 m_file << " >> ";
237         }
238         m_file << "\"" << m_outputRedirect << "\"";
239         m_file << "\n";
240         ++m_commandCount;
241 }
242 };
243
244 bool Region_cameraValid(){
245         Vector3 vOrig( vector3_snapped( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) ) );
246
247         for ( int i = 0 ; i < 3 ; i++ )
248         {
249                 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
250                         return false;
251                 }
252         }
253         return true;
254 }
255
256
257 void RunBSP( const char* name ){
258         // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503
259         // make sure we don't attempt to region compile a map with the camera outside the region
260         if ( region_active && !Region_cameraValid() ) {
261                 globalErrorStream() << "The camera must be in the region to start a region compile.\n";
262                 return;
263         }
264
265         SaveMap();
266
267         if ( Map_Unnamed( g_map ) ) {
268                 globalOutputStream() << "build cancelled\n";
269                 return;
270         }
271
272         if ( g_SnapShots_Enabled && !Map_Unnamed( g_map ) && Map_Modified( g_map ) ) {
273                 Map_Snapshot();
274         }
275
276         if ( region_active ) {
277                 const char* mapname = Map_Name( g_map );
278                 StringOutputStream name( 256 );
279                 name << StringRange( mapname, path_get_filename_base_end( mapname ) ) << ".reg";
280                 Map_SaveRegion( name.c_str() );
281         }
282
283         Pointfile_Delete();
284
285         bsp_init();
286
287         if ( g_WatchBSP_Enabled ) {
288                 ArrayCommandListener listener;
289                 build_run( name, listener );
290                 // grab the file name for engine running
291                 const char* fullname = Map_Name( g_map );
292                 StringOutputStream bspname( 64 );
293                 bspname << StringRange( path_get_filename_start( fullname ), path_get_filename_base_end( fullname ) );
294                 BuildMonitor_Run( listener.array(), bspname.c_str() );
295         }
296         else
297         {
298                 char junkpath[PATH_MAX];
299                 strcpy( junkpath, SettingsPath_get() );
300                 strcat( junkpath, "junk.txt" );
301
302                 char batpath[PATH_MAX];
303 #if GDEF_OS_POSIX
304                 strcpy( batpath, SettingsPath_get() );
305                 strcat( batpath, "qe3bsp.sh" );
306 #elif GDEF_OS_WINDOWS
307                 strcpy( batpath, SettingsPath_get() );
308                 strcat( batpath, "qe3bsp.bat" );
309 #else
310 #error "unsupported platform"
311 #endif
312                 bool written = false;
313                 {
314                         TextFileOutputStream batchFile( batpath );
315                         if ( !batchFile.failed() ) {
316 #if GDEF_OS_POSIX
317                                 batchFile << "#!/bin/sh \n\n";
318 #endif
319                                 BatchCommandListener listener( batchFile, junkpath );
320                                 build_run( name, listener );
321                                 written = true;
322                         }
323                 }
324                 if ( written ) {
325 #if GDEF_OS_POSIX
326                         chmod( batpath, 0744 );
327 #endif
328                         globalOutputStream() << "Writing the compile script to '" << batpath << "'\n";
329                         globalOutputStream() << "The build output will be saved in '" << junkpath << "'\n";
330                         Q_Exec( batpath, NULL, NULL, true, false );
331                 }
332         }
333
334         bsp_shutdown();
335 }
336
337 // =============================================================================
338 // Sys_ functions
339
340 void Sys_SetTitle( const char *text, bool modified ){
341         StringOutputStream title;
342         title << text;
343
344         if ( modified ) {
345                 title << " *";
346         }
347
348         gtk_window_set_title(MainFrame_getWindow(), title.c_str() );
349 }
350
351 bool g_bWaitCursor = false;
352
353 void Sys_BeginWait( void ){
354         ScreenUpdates_Disable( "Processing...", "Please Wait" );
355         GdkCursor *cursor = gdk_cursor_new( GDK_WATCH );
356         gdk_window_set_cursor( gtk_widget_get_window(MainFrame_getWindow()), cursor );
357         gdk_cursor_unref( cursor );
358         g_bWaitCursor = true;
359 }
360
361 void Sys_EndWait( void ){
362         ScreenUpdates_Enable();
363         gdk_window_set_cursor(gtk_widget_get_window(MainFrame_getWindow()), 0 );
364         g_bWaitCursor = false;
365 }
366
367 void Sys_Beep( void ){
368         gdk_beep();
369 }