gtkradiant/radiant/qe3.cpp
TTimo 1099f571fe basic architecture for game configuration at runtime. writes out a .game, no sanity checks, does not copy any game pak data
git-svn-id: svn://svn.icculus.org/gtkradiant/GtkRadiant/branches/ZeroRadiant@192 8a3a26a2-13c4-0310-b231-cf6edde360e5
2007-12-25 00:10:45 +00:00

1792 lines
42 KiB
C++

/*
Copyright (C) 1999-2007 id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
//
// Linux stuff
//
// Leonardo Zide (leo@lokigames.com)
//
#include "stdafx.h"
#include <gtk/gtk.h>
#include <sys/stat.h>
#include "gtkmisc.h"
#if defined (__linux__) || defined (__APPLE__)
#include <unistd.h>
#include <X11/keysym.h>
#include <gdk/gdkx.h>
#include <gdk/gdkprivate.h>
#endif
// for the logging part
#include <fcntl.h>
#include <sys/types.h>
QEGlobals_t g_qeglobals;
QEGlobals_GUI_t g_qeglobals_gui;
// leo: Track memory allocations for debugging
// NOTE TTimo this was never used and probably not relevant
// there are tools to do that
#ifdef MEM_DEBUG
static GList *memblocks;
void* debug_malloc (size_t size, const char* file, int line)
{
void *buf = g_malloc (size + 8);
*((const char**)buf) = file;
buf = (char*)buf + 4;
*((int*)buf) = line;
buf = (char*)buf + 4;
memblocks = g_list_append (memblocks, buf);
return buf;
}
void debug_free (void *buf, const char* file, int line)
{
const char *f;
int l;
if (g_list_find (memblocks, buf))
{
memblocks = g_list_remove (memblocks, buf);
buf = (char*)buf - 4;
l = *((int*)buf);
buf = (char*)buf - 4;
f = *((const char**)buf);
Sys_FPrintf (SYS_DBG, "free: %s %d", file, line);
Sys_FPrintf (SYS_DBG, " allocated: %s %d\n", f, l);
g_free (buf);
}
// else
// free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib
}
#endif
vec_t Rad_rint (vec_t in)
{
if (g_PrefsDlg.m_bNoClamp)
return in;
else
return (float)floor (in + 0.5);
}
void WINAPI QE_CheckOpenGLForErrors(void)
{
char strMsg[1024];
int i = qglGetError();
if (i != GL_NO_ERROR)
{
if (i == GL_OUT_OF_MEMORY)
{
sprintf(strMsg, "OpenGL out of memory error %s\nDo you wish to save before exiting?", qgluErrorString((GLenum)i));
if (gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg, "Radiant Error", MB_YESNO) == IDYES)
{
Map_SaveFile(NULL, false);
}
_exit(1);
}
else
{
Sys_Printf ("Warning: OpenGL Error %s\n", qgluErrorString((GLenum)i));
}
}
}
// NOTE: don't this function, use VFS instead
char *ExpandReletivePath (char *p)
{
static char temp[1024];
const char *base;
if (!p || !p[0])
return NULL;
if (p[0] == '/' || p[0] == '\\')
return p;
base = ValueForKey(g_qeglobals.d_project_entity, "basepath");
sprintf (temp, "%s/%s", base, p);
return temp;
}
char *copystring (char *s)
{
char *b;
b = (char*)malloc(strlen(s)+1);
strcpy (b,s);
return b;
}
bool DoesFileExist(const char* pBuff, long& lSize)
{
FileStream file;
if (file.Open(pBuff, "r"))
{
lSize += file.GetLength();
file.Close();
return true;
}
return false;
}
void Map_Snapshot()
{
CString strMsg;
// I hope the modified flag is kept correctly up to date
if (!modified)
return;
// we need to do the following
// 1. make sure the snapshot directory exists (create it if it doesn't)
// 2. find out what the lastest save is based on number
// 3. inc that and save the map
CString strOrgPath, strOrgFile;
ExtractPath_and_Filename(currentmap, strOrgPath, strOrgFile);
AddSlash(strOrgPath);
strOrgPath += "snapshots";
bool bGo = true;
struct stat Stat;
if (stat(strOrgPath, &Stat) == -1)
{
#ifdef _WIN32
bGo = (_mkdir(strOrgPath) != -1);
#endif
#if defined (__linux__) || defined (__APPLE__)
bGo = (mkdir(strOrgPath,0755) != -1);
#endif
}
AddSlash(strOrgPath);
if (bGo)
{
int nCount = 0;
long lSize = 0;
CString strNewPath;
strNewPath = strOrgPath;
strNewPath += strOrgFile;
CString strFile;
while (bGo)
{
char buf[PATH_MAX];
sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
strFile = buf;
bGo = DoesFileExist(strFile, lSize);
nCount++;
}
// strFile has the next available slot
Map_SaveFile(strFile, false);
// it is still a modified map (we enter this only if this is a modified map)
Sys_SetTitle (currentmap);
Sys_MarkMapModified();
if (lSize > 12 * 1024 * 1024) // total size of saves > 4 mb
{
Sys_Printf("The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer());
}
}
else
{
strMsg.Format("Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer());
gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg);
}
strOrgPath = "";
strOrgFile = "";
}
/*
===============
QE_CheckAutoSave
If five minutes have passed since making a change
and the map hasn't been saved, save it out.
===============
*/
void QE_CheckAutoSave( void )
{
static time_t s_start;
time_t now;
time (&now);
if (modified != 1 || !s_start)
{
s_start = now;
return;
}
if ((now - s_start) > (60 * g_PrefsDlg.m_nAutoSave))
{
if (g_PrefsDlg.m_bAutoSave)
{
CString strMsg;
strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";
Sys_Printf(strMsg);
Sys_Printf("\n");
Sys_Status (strMsg,0);
// only snapshot if not working on a default map
if (g_PrefsDlg.m_bSnapShots && stricmp(currentmap, "unnamed.map") != 0)
{
Map_Snapshot();
}
else
{
Map_SaveFile (ValueForKey(g_qeglobals.d_project_entity, "autosave"), false);
}
Sys_Status ("Autosaving...Saved.", 0 );
modified = 2;
}
else
{
Sys_Printf ("Autosave skipped...\n");
Sys_Status ("Autosave skipped...", 0 );
}
s_start = now;
}
}
// NOTE TTimo we don't like that BuildShortPathName too much
// the VFS provides a vfsCleanFileName which should perform the cleanup tasks
// in the long run I'd like to completely get rid of this
// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144
// used to be disabled, but caused problems
// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=291
// can't work with long win32 names until the BSP commands are not working differently
#ifdef _WIN32
int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
{
char *pFile = NULL;
int nResult = GetFullPathName(pPath, nBufferLen, pBuffer, &pFile);
nResult = GetShortPathName(pPath, pBuffer, nBufferLen);
if (nResult == 0)
strcpy(pBuffer, pPath); // Use long filename
return nResult;
}
#endif
#if defined (__linux__) || defined (__APPLE__)
int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
{
// remove /../ from directories
const char *scr = pPath; char *dst = pBuffer;
for (int i = 0; (i < nBufferLen) && (*scr != 0); i++)
{
if (*scr == '/' && *(scr+1) == '.' && *(scr+2) == '.')
{
scr += 3;
while (dst != pBuffer && *(--dst) != '/')
{
i--;
}
}
*dst = *scr;
scr++; dst++;
}
*dst = 0;
return strlen (pBuffer);
}
#endif
/*
const char *g_pPathFixups[]=
{
"basepath",
"autosave",
};
const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);
void QE_CheckProjectEntity()
{
char *pFile;
char pBuff[PATH_MAX];
char pNewPath[PATH_MAX];
for (int i = 0; i < g_nPathFixupCount; i++)
{
char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);
strcpy (pNewPath, pPath);
if (pPath[0] != '\\' && pPath[0] != '/')
if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))
strcpy (pNewPath, pBuff);
BuildShortPathName (pNewPath, pBuff, PATH_MAX);
// check it's not ending with a filename seperator
if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')
{
Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]);
pBuff[strlen(pBuff)-1]=0;
}
SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);
}
}
*/
void HandleXMLError( void* ctxt, const char* text, ... )
{
va_list argptr;
static char buf[32768];
va_start (argptr,text);
vsprintf (buf, text, argptr);
Sys_FPrintf (SYS_ERR, "XML %s\n", buf);
va_end (argptr);
}
#define DTD_BUFFER_LENGTH 1024
xmlDocPtr ParseXMLStream(IDataStream *stream, bool validate = false)
{
xmlDocPtr doc = NULL;
bool wellFormed = false, valid = false;
int res, size = 1024;
char chars[1024];
xmlParserCtxtPtr ctxt;
// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=433
//if(validate)
// xmlDoValidityCheckingDefaultValue = 1;
//else
xmlDoValidityCheckingDefaultValue = 0;
xmlSetGenericErrorFunc(NULL, HandleXMLError);
// SPoG
// HACK: use AppPath to resolve DTD location
// do a buffer-safe string copy and concatenate
int i;
char* w;
const char* r;
char buf[DTD_BUFFER_LENGTH];
w = buf;
i = 0;
// copy
//assert(g_strAppPath.GetBuffer() != NULL);
for(r = g_strAppPath.GetBuffer(); i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++) w[i] = *r;
// concatenate
for(r = "dtds/"; i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++) w[i] = *r;
// terminate
w[i] = '\0';
if(i == DTD_BUFFER_LENGTH)
{
HandleXMLError(NULL, "ERROR: buffer overflow: DTD path length too large\n");
return NULL;
}
res = stream->Read(chars, 4);
if (res > 0)
{
ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, buf);
while ((res = stream->Read(chars, size)) > 0)
{
xmlParseChunk(ctxt, chars, res, 0);
}
xmlParseChunk(ctxt, chars, 0, 1);
doc = ctxt->myDoc;
wellFormed = (ctxt->wellFormed == 1);
valid = (ctxt->valid == 1);
xmlFreeParserCtxt(ctxt);
}
if(wellFormed && (!validate || (validate && valid)))
return doc;
if(doc != NULL)
xmlFreeDoc(doc);
return NULL;
}
xmlDocPtr ParseXMLFile(const char* filename, bool validate = false)
{
FileStream stream;
if (stream.Open(filename, "r"))
return ParseXMLStream(&stream, validate);
Sys_FPrintf(SYS_ERR, "Failed to open file: %s\n",filename);
return NULL;
}
// copy a string r to a buffer w
// replace $string as appropriate
void ReplaceTemplates(char* w, const char* r)
{
const char *p;
const char *__ENGINEPATH = "TEMPLATEenginepath";
const char *__USERHOMEPATH = "TEMPLATEuserhomepath";
const char *__TOOLSPATH = "TEMPLATEtoolspath";
const char *__BASEDIR = "TEMPLATEbasedir";
const char *__APPPATH = "TEMPLATEapppath";
// iterate through string r
while(*r!='\0')
{
// check for special character
if(*r=='$')
{
if(strncmp(r+1, __ENGINEPATH, strlen(__ENGINEPATH)) == 0)
{
r+=strlen(__ENGINEPATH)+1;
p = g_pGameDescription->mEnginePath.GetBuffer();
}
else if(strncmp(r+1, __USERHOMEPATH, strlen(__USERHOMEPATH)) == 0)
{
r+=strlen(__USERHOMEPATH)+1;
p = g_qeglobals.m_strHomeGame.GetBuffer();
}
else if(strncmp(r+1, __BASEDIR, strlen(__BASEDIR)) == 0)
{
r+=strlen(__BASEDIR)+1;
p = g_pGameDescription->mBaseGame;
}
else if(strncmp(r+1, __TOOLSPATH, strlen(__TOOLSPATH)) == 0)
{
r+=strlen(__TOOLSPATH)+1;
p = g_strGameToolsPath.GetBuffer();
}
else if(strncmp(r+1, __APPPATH, strlen(__APPPATH)) == 0)
{
r+=strlen(__APPPATH)+1;
p = g_strAppPath.GetBuffer();
}
else
{
r++;
p = "$";
}
while(*p!='\0') *w++ = *p++;
}
else *w++ = *r++;
}
*w = '\0';
}
/*
===========
QE_LoadProject
TODO TODO TODO (don't think this got fully merged in)
TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings
version 3 was .. I don't remember .. version 4 adds q3map2 commands
TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk
===========
*/
/*\todo decide on a sensible location/name for project files.*/
bool QE_LoadProject (const char *projectfile)
{
char buf[1024];
xmlDocPtr doc;
xmlNodePtr node, project;
Sys_Printf("Loading project file: \"%s\"\n", projectfile);
doc = ParseXMLFile(projectfile, true);
if(doc == NULL) return false;
node=doc->children;
while(node != NULL && node->type != XML_DTD_NODE) node=node->next;
if(node == NULL || strcmp((char*)node->name, "project") != 0)
{
Sys_FPrintf(SYS_ERR, "ERROR: invalid file type\n");
return false;
}
while(node->type != XML_ELEMENT_NODE) node=node->next;
// <project>
project = node;
if(g_qeglobals.d_project_entity != NULL) Entity_Free(g_qeglobals.d_project_entity);
g_qeglobals.d_project_entity = Entity_Alloc();
for(node = project->children; node != NULL; node=node->next)
{
if(node->type != XML_ELEMENT_NODE) continue;
// <key>
ReplaceTemplates(buf, (char*)node->properties->next->children->content);
SetKeyValue(g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf);
}
xmlFreeDoc(doc);
// project file version checking
// add a version checking to avoid people loading later versions of the project file and bitching
int ver = IntForKey( g_qeglobals.d_project_entity, "version" );
if (ver > PROJECT_VERSION)
{
char strMsg[1024];
sprintf (strMsg, "This is a version %d project file. This build only supports <=%d project files.\n"
"Please choose another project file or upgrade your version of Radiant.", ver, PROJECT_VERSION);
gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);
// set the project file to nothing so we are sure we'll ask next time?
g_PrefsDlg.m_strLastProject = "";
g_PrefsDlg.SavePrefs();
return false;
}
// set here some default project settings you need
if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 )
{
SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" );
}
g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );
g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame;
const char* str = ValueForKey(g_qeglobals.d_project_entity, "gamename");
if(str[0] == '\0') str = g_pGameDescription->mBaseGame.GetBuffer();
g_qeglobals.m_strHomeMaps += str;
g_qeglobals.m_strHomeMaps += '/';
// don't forget to create the dirs
Q_mkdir(g_qeglobals.m_strHomeGame.GetBuffer(), 0775);
Q_mkdir(g_qeglobals.m_strHomeMaps.GetBuffer(), 0775);
// usefull for the log file and debuggin fucked up configurations from users:
// output the basic information of the .qe4 project file
// SPoG
// all these paths should be unix format, with a trailing slash at the end
// if not.. to debug, check that the project file paths are set up correctly
Sys_Printf("basepath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath") );
Sys_Printf("entitypath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) );
// check whether user_project key exists..
// if not, save the current project under a new name
if (ValueForKey(g_qeglobals.d_project_entity, "user_project")[0] == '\0')
{
Sys_Printf("Loaded a template project file\n");
// create the user_project key
SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );
// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672
if (IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION)
{
char strMsg[2048];
sprintf(strMsg,
"The template project '%s' has version %d. The editor binary is configured for version %d.\n"
"This indicates a problem in your setup. See http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672\n"
"I will keep going with this project till you fix this",
projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION);
gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);
}
// create the writable project file path
strcpy(buf, g_qeglobals.m_strHomeGame.GetBuffer());
strcat(buf, g_pGameDescription->mBaseGame.GetBuffer());
strcat(buf, "/scripts/");
// while the filename is already in use, increment the number we add to the end
int counter = 0;
char pUser[PATH_MAX];
while (1)
{
sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );
counter++;
if (access( pUser, R_OK) != 0)
{
// this is the one
strcpy( buf, pUser );
break;
}
}
// saving project will cause a save prefs
g_PrefsDlg.m_strLastProject = buf;
g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" );
QE_SaveProject(buf);
}
else
{
// update preferences::LastProject with path of this successfully-loaded project
// save preferences
Sys_Printf("Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() );
g_PrefsDlg.m_strLastProject = projectfile;
g_PrefsDlg.SavePrefs();
}
return true;
}
/*
===========
QE_SaveProject
TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project
===========
*/
qboolean QE_SaveProject (const char* filename)
{
Sys_Printf("Save project file '%s'\n", filename);
xmlNodePtr node;
xmlDocPtr doc = xmlNewDoc((xmlChar *)"1.0");
// create DTD node
xmlCreateIntSubset(doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd");
// create project node
doc->children->next = xmlNewDocNode(doc, NULL, (xmlChar *)"project", NULL);
for(epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next)
{
node = xmlNewChild(doc->children->next, NULL, (xmlChar *)"key", NULL);
xmlSetProp(node, (xmlChar*)"name", (xmlChar*)epair->key);
xmlSetProp(node, (xmlChar*)"value", (xmlChar*)epair->value);
}
CreateDirectoryPath(filename);
if (xmlSaveFormatFile(filename, doc, 1) != -1)
{
xmlFreeDoc(doc);
Sys_Printf("Setting current project in prefs to \"%s\"\n", filename );
g_PrefsDlg.m_strLastProject = filename;
g_PrefsDlg.SavePrefs();
return TRUE;
}
else
{
xmlFreeDoc(doc);
Sys_FPrintf(SYS_ERR, "failed to save project file: \"%s\"\n", filename);
return FALSE;
}
}
/*
===========
QE_KeyDown
===========
*/
#define SPEED_MOVE 32
#define SPEED_TURN 22.5
/*
===============
ConnectEntities
Sets target / targetname on the two entities selected
from the first selected to the secon
===============
*/
void ConnectEntities (void)
{
entity_t *e1, *e2;
const char *target;
char *newtarg = NULL;
if (g_qeglobals.d_select_count != 2)
{
Sys_Status ("Must have two brushes selected", 0);
Sys_Beep ();
return;
}
e1 = g_qeglobals.d_select_order[0]->owner;
e2 = g_qeglobals.d_select_order[1]->owner;
if (e1 == world_entity || e2 == world_entity)
{
Sys_Status ("Can't connect to the world", 0);
Sys_Beep ();
return;
}
if (e1 == e2)
{
Sys_Status ("Brushes are from same entity", 0);
Sys_Beep ();
return;
}
target = ValueForKey (e1, "target");
if (target && target[0])
newtarg = g_strdup(target);
else
{
target = ValueForKey(e2, "targetname");
if(target && target[0])
newtarg = g_strdup(target);
else
Entity_Connect(e1, e2);
}
if(newtarg != NULL)
{
SetKeyValue(e1, "target", newtarg);
SetKeyValue(e2, "targetname", newtarg);
g_free(newtarg);
}
Sys_UpdateWindows (W_XY | W_CAMERA);
Select_Deselect();
Select_Brush (g_qeglobals.d_select_order[1]);
}
qboolean QE_SingleBrush (bool bQuiet)
{
if ( (selected_brushes.next == &selected_brushes)
|| (selected_brushes.next->next != &selected_brushes) )
{
if (!bQuiet)
{
Sys_Printf ("Error: you must have a single brush selected\n");
}
return false;
}
if (selected_brushes.next->owner->eclass->fixedsize)
{
if (!bQuiet)
{
Sys_Printf ("Error: you cannot manipulate fixed size entities\n");
}
return false;
}
return true;
}
void QE_InitVFS (void)
{
// VFS initialization -----------------------
// we will call vfsInitDirectory, giving the directories to look in (for files in pk3's and for standalone files)
// we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
// *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
Str directory,prefabs;
// TTimo: let's leave this to HL mode for now
if (g_pGameDescription->mGameFile == "hl.game")
{
// Hydra: we search the "gametools" path first so that we can provide editor
// specific pk3's wads and misc files for use by the editor.
// the relevant map compiler tools will NOT use this directory, so this helps
// to ensure that editor files are not used/required in release versions of maps
// it also helps keep your editor files all in once place, with the editor modules,
// plugins, scripts and config files.
// it also helps when testing maps, as you'll know your files won't/can't be used
// by the game engine itself.
// <gametools>
directory = g_pGameDescription->mGameToolsPath;
vfsInitDirectory(directory.GetBuffer());
}
// NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix
// if we have a mod dir
if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
{
#if defined (__linux__) || defined (__APPLE__)
// ~/.<gameprefix>/<fs_game>
directory = g_qeglobals.m_strHomeGame.GetBuffer();
Q_mkdir (directory.GetBuffer (), 0775);
directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");
Q_mkdir (directory.GetBuffer (), 0775);
vfsInitDirectory(directory.GetBuffer());
AddSlash (directory);
prefabs = directory;
// also create the maps dir, it will be used as prompt for load/save
directory += "/maps";
Q_mkdir (directory, 0775);
// and the prefabs dir
prefabs += "/prefabs";
Q_mkdir (prefabs, 0775);
#endif
// <fs_basepath>/<fs_game>
directory = g_pGameDescription->mEnginePath;
directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");
Q_mkdir (directory.GetBuffer (), 0775);
vfsInitDirectory(directory.GetBuffer());
AddSlash(directory);
prefabs = directory;
// also create the maps dir, it will be used as prompt for load/save
directory += "/maps";
Q_mkdir (directory.GetBuffer (), 0775);
// and the prefabs dir
prefabs += "/prefabs";
Q_mkdir (prefabs, 0775);
}
#if defined (__linux__) || defined (__APPLE__)
// ~/.<gameprefix>/<fs_main>
directory = g_qeglobals.m_strHomeGame.GetBuffer();
directory += g_pGameDescription->mBaseGame;
vfsInitDirectory (directory.GetBuffer ());
#endif
// <fs_basepath>/<fs_main>
directory = g_pGameDescription->mEnginePath;
directory += g_pGameDescription->mBaseGame;
vfsInitDirectory(directory.GetBuffer());
}
void QE_Init (void)
{
/*
** initialize variables
*/
g_qeglobals.d_gridsize = 8;
g_qeglobals.d_showgrid = true;
QE_InitVFS();
Eclass_Init();
FillClassList(); // list in entity window
Map_Init();
FillTextureMenu();
FillBSPMenu();
/*
** other stuff
*/
Z_Init ();
}
void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src )
{
while ( *src )
{
if ( *src == '\\' )
*dst = '/';
else
*dst = *src;
dst++; src++;
}
*dst = 0;
}
int g_numbrushes, g_numentities;
void QE_CountBrushesAndUpdateStatusBar( void )
{
static int s_lastbrushcount, s_lastentitycount;
static qboolean s_didonce;
//entity_t *e;
brush_t *b, *next;
g_numbrushes = 0;
g_numentities = 0;
if ( active_brushes.next != NULL )
{
for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b=next)
{
next = b->next;
if (b->brush_faces )
{
if ( !b->owner->eclass->fixedsize)
g_numbrushes++;
else
g_numentities++;
}
}
}
/*
if ( entities.next != NULL )
{
for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)
{
g_numentities++;
}
}
*/
if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) )
{
Sys_UpdateStatusBar();
s_lastbrushcount = g_numbrushes;
s_lastentitycount = g_numentities;
s_didonce = true;
}
}
char com_token[1024];
qboolean com_eof;
/*
================
I_FloatTime
================
*/
double I_FloatTime (void)
{
time_t t;
time (&t);
return t;
#if 0
// more precise, less portable
struct timeval tp;
struct timezone tzp;
static int secbase;
gettimeofday(&tp, &tzp);
if (!secbase)
{
secbase = tp.tv_sec;
return tp.tv_usec/1000000.0;
}
return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;
#endif
}
/*
==============
COM_Parse
Parse a token out of a string
==============
*/
char *COM_Parse (char *data)
{
int c;
int len;
len = 0;
com_token[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ( (c = *data) <= ' ')
{
if (c == 0)
{
com_eof = true;
return NULL; // end of file;
}
data++;
}
// skip // comments
if (c=='/' && data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
// handle quoted strings specially
if (c == '\"')
{
data++;
do
{
c = *data++;
if (c=='\"')
{
com_token[len] = 0;
return data;
}
com_token[len] = c;
len++;
} while (1);
}
// parse single characters
if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
{
com_token[len] = c;
len++;
com_token[len] = 0;
return data+1;
}
// parse a regular word
do
{
com_token[len] = c;
data++;
len++;
c = *data;
if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
break;
} while (c>32);
com_token[len] = 0;
return data;
}
char* Get_COM_Token()
{
return com_token;
}
/*
=============================================================================
MISC FUNCTIONS
=============================================================================
*/
int argc;
char *argv[MAX_NUM_ARGVS];
/*
============
ParseCommandLine
============
*/
void ParseCommandLine (char *lpCmdLine)
{
argc = 1;
argv[0] = "programname";
while (*lpCmdLine && (argc < MAX_NUM_ARGVS))
{
while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
lpCmdLine++;
if (*lpCmdLine)
{
argv[argc] = lpCmdLine;
argc++;
while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
lpCmdLine++;
if (*lpCmdLine)
{
*lpCmdLine = 0;
lpCmdLine++;
}
}
}
}
/*
=================
CheckParm
Checks for the given parameter in the program's command line arguments
Returns the argument number (1 to argc-1) or 0 if not present
=================
*/
int CheckParm (char *check)
{
int i;
for (i = 1;i<argc;i++)
{
if ( stricmp(check, argv[i]) )
return i;
}
return 0;
}
/*
==============
ParseNum / ParseHex
==============
*/
int ParseHex (char *hex)
{
char *str;
int num;
num = 0;
str = hex;
while (*str)
{
num <<= 4;
if (*str >= '0' && *str <= '9')
num += *str-'0';
else if (*str >= 'a' && *str <= 'f')
num += 10 + *str-'a';
else if (*str >= 'A' && *str <= 'F')
num += 10 + *str-'A';
else
Error ("Bad hex number: %s",hex);
str++;
}
return num;
}
int ParseNum (char *str)
{
if (str[0] == '$')
return ParseHex (str+1);
if (str[0] == '0' && str[1] == 'x')
return ParseHex (str+2);
return atol (str);
}
// BSP frontend plugin
// global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin
_QERPlugBSPFrontendTable g_BSPFrontendTable;
// =============================================================================
// Sys_ functions
bool Sys_AltDown ()
{
#ifdef _WIN32
return (GetKeyState(VK_MENU) & 0x8000) != 0;
#endif
#if defined (__linux__) || defined (__APPLE__)
char keys[32];
int x;
XQueryKeymap(GDK_DISPLAY(), keys);
x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L);
if (keys[x/8] & (1 << (x % 8)))
return true;
x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R);
if (keys[x/8] & (1 << (x % 8)))
return true;
return false;
#endif
}
bool Sys_ShiftDown ()
{
#ifdef _WIN32
return (GetKeyState(VK_SHIFT) & 0x8000) != 0;
#endif
#if defined (__linux__) || defined (__APPLE__)
char keys[32];
int x;
XQueryKeymap(GDK_DISPLAY(), keys);
x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_L);
if (keys[x/8] & (1 << (x % 8)))
return true;
x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_R);
if (keys[x/8] & (1 << (x % 8)))
return true;
return false;
#endif
}
void Sys_MarkMapModified (void)
{
char title[PATH_MAX];
if (modified != 1)
{
modified = true; // mark the map as changed
sprintf (title, "%s *", currentmap);
QE_ConvertDOSToUnixName( title, title );
Sys_SetTitle (title);
}
}
void Sys_SetTitle (const char *text)
{
gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_main_window), text);
}
bool g_bWaitCursor = false;
void WINAPI Sys_BeginWait (void)
{
GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);
gdk_cursor_unref (cursor);
g_bWaitCursor = true;
}
void WINAPI Sys_EndWait (void)
{
GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);
gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);
gdk_cursor_unref (cursor);
g_bWaitCursor = false;
}
void Sys_GetCursorPos (int *x, int *y)
{
// FIXME: not multihead safe
gdk_window_get_pointer (NULL, x, y, NULL);
}
void Sys_SetCursorPos (int x, int y)
{
// NOTE: coordinates are in GDK space, not OS space
#ifdef _WIN32
int sys_x = x - g_pParentWnd->GetGDKOffsetX();
int sys_y = y - g_pParentWnd->GetGDKOffsetY();
SetCursorPos (sys_x, sys_y);
#endif
#if defined (__linux__) || defined (__APPLE__)
XWarpPointer (GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y);
#endif
}
void Sys_Beep (void)
{
#if defined (__linux__) || defined (__APPLE__)
gdk_beep ();
#else
MessageBeep (MB_ICONASTERISK);
#endif
}
double Sys_DoubleTime (void)
{
return clock()/ 1000.0;
}
/*
===============================================================
STATUS WINDOW
===============================================================
*/
void Sys_UpdateStatusBar( void )
{
extern int g_numbrushes, g_numentities;
char numbrushbuffer[100]="";
sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
g_pParentWnd->SetStatusText(2, numbrushbuffer);
//Sys_Status( numbrushbuffer, 2 );
}
void Sys_Status(const char *psz, int part )
{
g_pParentWnd->SetStatusText (part, psz);
}
// =============================================================================
// MRU
#define MRU_MAX 4
static GtkWidget *MRU_items[MRU_MAX];
static int MRU_used;
typedef char MRU_filename_t[PATH_MAX];
MRU_filename_t MRU_filenames[MRU_MAX];
static char* MRU_GetText (int index)
{
return MRU_filenames[index];
}
void buffer_write_escaped_mnemonic(char* buffer, const char* string)
{
while(*string != '\0')
{
if(*string == '_')
{
*buffer++ = '_';
}
*buffer++ = *string++;
}
*buffer = '\0';
}
static void MRU_SetText (int index, const char *filename)
{
strcpy(MRU_filenames[index], filename);
char mnemonic[PATH_MAX * 2 + 4];
mnemonic[0] = '_';
sprintf(mnemonic+1, "%d", index+1);
mnemonic[2] = '-';
mnemonic[3] = ' ';
buffer_write_escaped_mnemonic(mnemonic+4, filename);
gtk_label_set_text_with_mnemonic(GTK_LABEL (GTK_BIN (MRU_items[index])->child), mnemonic);
}
void MRU_Load ()
{
int i = g_PrefsDlg.m_nMRUCount;
if(i > 4)
i = 4; //FIXME: make this a define
for (; i > 0; i--)
MRU_AddFile (g_PrefsDlg.m_strMRUFiles[i-1].GetBuffer());
}
void MRU_Save ()
{
g_PrefsDlg.m_nMRUCount = MRU_used;
for (int i = 0; i < MRU_used; i++)
g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText (i);
}
void MRU_AddWidget (GtkWidget *widget, int pos)
{
if (pos < MRU_MAX)
MRU_items[pos] = widget;
}
void MRU_AddFile (const char *str)
{
int i;
char* text;
// check if file is already in our list
for (i = 0; i < MRU_used; i++)
{
text = MRU_GetText (i);
if (strcmp (text, str) == 0)
{
// reorder menu
for (; i > 0; i--)
MRU_SetText (i, MRU_GetText (i-1));
MRU_SetText (0, str);
return;
}
}
if (MRU_used < MRU_MAX)
MRU_used++;
// move items down
for (i = MRU_used-1; i > 0; i--)
MRU_SetText (i, MRU_GetText (i-1));
MRU_SetText (0, str);
gtk_widget_set_sensitive (MRU_items[0], TRUE);
gtk_widget_show (MRU_items[MRU_used-1]);
}
void MRU_Activate (int index)
{
char *text = MRU_GetText (index);
if (access (text, R_OK) == 0)
{
text = strdup (text);
MRU_AddFile (text);
Map_LoadFile (text);
free (text);
}
else
{
MRU_used--;
for (int i = index; i < MRU_used; i++)
MRU_SetText (i, MRU_GetText (i+1));
if (MRU_used == 0)
{
gtk_label_set_text (GTK_LABEL (GTK_BIN (MRU_items[0])->child), "Recent Files");
gtk_widget_set_sensitive (MRU_items[0], FALSE);
}
else
{
gtk_widget_hide (MRU_items[MRU_used]);
}
}
}
/*
======================================================================
FILE DIALOGS
======================================================================
*/
qboolean ConfirmModified ()
{
if (!modified)
return true;
if (gtk_MessageBox (g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL) == IDCANCEL)
return false;
return true;
}
void ProjectDialog ()
{
const char *filename;
char buffer[NAME_MAX];
/*
* Obtain the system directory name and
* store it in buffer.
*/
strcpy(buffer, g_qeglobals.m_strHomeGame.GetBuffer());
strcat(buffer, g_pGameDescription->mBaseGame.GetBuffer());
strcat (buffer, "/scripts/");
// Display the Open dialog box
filename = file_dialog (NULL, TRUE, "Open File", buffer, "project");
if (filename == NULL)
return; // canceled
// Open the file.
// NOTE: QE_LoadProject takes care of saving prefs with new path to the project file
if (!QE_LoadProject(filename))
Sys_Printf ("Failed to load project from file: %s\n", filename);
else
// FIXME TTimo QE_Init is probably broken if you don't call it during startup right now ..
QE_Init();
}
/*
=======================================================
Menu modifications
=======================================================
*/
/*
==================
FillBSPMenu
==================
*/
char *bsp_commands[256];
void FillBSPMenu ()
{
GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)
epair_t *ep;
GList *lst;
int i;
menu = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_bsp"));
while ((lst = gtk_container_children (GTK_CONTAINER (menu))) != NULL)
gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (lst->data));
if (g_PrefsDlg.m_bDetachableMenus) {
item = gtk_tearoff_menu_item_new ();
gtk_menu_append (GTK_MENU (menu), item);
gtk_widget_set_sensitive (item, TRUE);
gtk_widget_show (item);
}
if (g_qeglobals.bBSPFrontendPlugin)
{
CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();
char cTemp[1024];
strcpy(cTemp, str);
char* token = strtok(cTemp, ",;");
if (token && *token == ' ')
{
while (*token == ' ')
token++;
}
i = 0;
// first token is menu name
item = gtk_menu_get_attach_widget (GTK_MENU (menu));
gtk_label_set_text (GTK_LABEL (GTK_BIN (item)->child), token);
token = strtok(NULL, ",;");
while (token != NULL)
{
g_BSPFrontendCommands = g_slist_append (g_BSPFrontendCommands, g_strdup (token));
item = gtk_menu_item_new_with_label (token);
gtk_widget_show (item);
gtk_container_add (GTK_CONTAINER (menu), item);
gtk_signal_connect (GTK_OBJECT (item), "activate",
GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));
token = strtok(NULL, ",;");
i++;
}
}
else
{
i = 0;
for (ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next)
{
if (strncmp(ep->key, "bsp_", 4)==0)
{
bsp_commands[i] = ep->key;
item = gtk_menu_item_new_with_label (ep->key+4);
gtk_widget_show (item);
gtk_container_add (GTK_CONTAINER (menu), item);
gtk_signal_connect (GTK_OBJECT (item), "activate",
GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));
i++;
}
}
}
}
//==============================================
void AddSlash(CString& strPath)
{
if (strPath.GetLength() > 0)
{
if ((strPath.GetAt(strPath.GetLength()-1) != '/') &&
(strPath.GetAt(strPath.GetLength()-1) != '\\'))
strPath += '/';
}
}
bool ExtractPath_and_Filename(const char* pPath, CString& strPath, CString& strFilename)
{
CString strPathName;
strPathName = pPath;
int nSlash = strPathName.ReverseFind('\\');
if (nSlash == -1)
// TTimo: try forward slash, some are using forward
nSlash = strPathName.ReverseFind('/');
if (nSlash >= 0)
{
strPath = strPathName.Left(nSlash+1);
strFilename = strPathName.Right(strPathName.GetLength() - nSlash - 1);
}
// TTimo: try forward slash, some are using forward
else
strFilename = pPath;
return true;
}
//===========================================
//++timo FIXME: no longer used .. remove!
char *TranslateString (char *buf)
{
static char buf2[32768];
int i, l;
char *out;
l = strlen(buf);
out = buf2;
for (i=0 ; i<l ; i++)
{
if (buf[i] == '\n')
{
*out++ = '\r';
*out++ = '\n';
}
else
*out++ = buf[i];
}
*out++ = 0;
return buf2;
}
// called whenever we need to open/close/check the console log file
void Sys_LogFile (void)
{
if (g_PrefsDlg.mGamesDialog.m_bLogConsole && !g_qeglobals.hLogFile)
{
// settings say we should be logging and we don't have a log file .. so create it
// open a file to log the console (if user prefs say so)
// the file handle is g_qeglobals.hLogFile
// the log file is erased
Str name;
name = g_strTempPath;
name += "radiant.log";
#if defined (__linux__) || defined (__APPLE__)
g_qeglobals.hLogFile = open( name.GetBuffer(), O_TRUNC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );
#endif
#ifdef _WIN32
g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );
#endif
if (g_qeglobals.hLogFile)
{
Sys_Printf("Started logging to %s\n", name.GetBuffer());
time_t localtime;
time(&localtime);
Sys_Printf("Today is: %s", ctime(&localtime));
Sys_Printf("This is GtkRadiant '" RADIANT_VERSION "' compiled " __DATE__ "\n");
Sys_Printf(RADIANT_ABOUTMSG "\n");
}
else
gtk_MessageBox (NULL, "Failed to create log file, check write permissions in Radiant directory.\n",
"Console logging", MB_OK );
}
else if (!g_PrefsDlg.mGamesDialog.m_bLogConsole && g_qeglobals.hLogFile)
{
// settings say we should not be logging but still we have an active logfile .. close it
time_t localtime;
time(&localtime);
Sys_Printf("Closing log file at %s\n", ctime(&localtime));
#ifdef _WIN32
_close( g_qeglobals.hLogFile );
#endif
#if defined (__linux__) || defined (__APPLE__)
close( g_qeglobals.hLogFile );
#endif
g_qeglobals.hLogFile = 0;
}
}
void Sys_ClearPrintf (void)
{
GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));
gtk_text_buffer_set_text(buffer, "", -1);
}
// used to be around 32000, that should be way enough already
#define BUFFER_SIZE 4096
extern "C" void Sys_FPrintf_VA( int level, const char *text, va_list args ) {
char buf[BUFFER_SIZE];
buf[0] = 0;
vsnprintf( buf, BUFFER_SIZE, text, args );
buf[BUFFER_SIZE-1] = 0;
const unsigned int length = strlen(buf);
if ( g_qeglobals.hLogFile ) {
#ifdef _WIN32
_write( g_qeglobals.hLogFile, buf, length );
_commit( g_qeglobals.hLogFile );
#endif
#if defined (__linux__) || defined (__APPLE__)
write( g_qeglobals.hLogFile, buf, length );
#endif
}
if ( level != SYS_NOCON ) {
// TTimo: FIXME: killed the console to avoid GDI leak fuckup
if ( g_qeglobals_gui.d_edit != NULL ) {
GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));
GtkTextIter iter;
gtk_text_buffer_get_end_iter(buffer, &iter);
static GtkTextMark* end = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE);
const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };
static GtkTextTag* error_tag = gtk_text_buffer_create_tag (buffer, "red_foreground", "foreground-gdk", &red, NULL);
static GtkTextTag* warning_tag = gtk_text_buffer_create_tag (buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL);
static GtkTextTag* standard_tag = gtk_text_buffer_create_tag (buffer, "black_foreground", "foreground-gdk", &black, NULL);
GtkTextTag* tag;
switch (level)
{
case SYS_WRN:
tag = warning_tag;
break;
case SYS_ERR:
tag = error_tag;
break;
case SYS_STD:
case SYS_VRB:
default:
tag = standard_tag;
break;
}
gtk_text_buffer_insert_with_tags(buffer, &iter, buf, length, tag, NULL);
gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit), end);
// update console widget immediatly if we're doing something time-consuming
if( !g_bScreenUpdates && GTK_WIDGET_REALIZED( g_qeglobals_gui.d_edit ) )
{
gtk_grab_add(g_qeglobals_gui.d_edit);
while(gtk_events_pending())
gtk_main_iteration();
gtk_grab_remove(g_qeglobals_gui.d_edit);
}
}
}
}
// NOTE: this is the handler sent to synapse
// must match PFN_SYN_PRINTF_VA
extern "C" void Sys_Printf_VA (const char *text, va_list args)
{
Sys_FPrintf_VA (SYS_STD, text, args);
}
extern "C" void Sys_Printf( const char *text, ... ) {
va_list args;
va_start( args, text );
Sys_FPrintf_VA( SYS_STD, text, args );
va_end( args );
}
extern "C" void Sys_FPrintf (int level, const char *text, ...)
{
va_list args;
va_start (args, text);
Sys_FPrintf_VA (level, text, args);
va_end (args);
}