Index: ps/trunk/source/ps/ConfigDB.cpp
===================================================================
--- ps/trunk/source/ps/ConfigDB.cpp (revision 7112)
+++ ps/trunk/source/ps/ConfigDB.cpp (revision 7113)
@@ -1,372 +1,372 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Pyrogenesis.h"
#include "Parser.h"
#include "ConfigDB.h"
#include "CLogger.h"
#include "Filesystem.h"
#include "scripting/ScriptingHost.h"
#define LOG_CATEGORY "config"
typedef std::map TConfigMap;
TConfigMap CConfigDB::m_Map[CFG_LAST];
CStr CConfigDB::m_ConfigFile[CFG_LAST];
bool CConfigDB::m_UseVFS[CFG_LAST];
namespace ConfigNamespace_JS
{
JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
CStr propName = g_ScriptingHost.ValueToString(id);
CConfigValue *val=g_ConfigDB.GetValue(cfgNs, propName);
if (val)
{
JSString *js_str=JS_NewStringCopyN(cx, val->m_String.c_str(), val->m_String.size());
*vp = STRING_TO_JSVAL(js_str);
}
return JS_TRUE;
}
JSBool SetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp )
{
EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
CStr propName = g_ScriptingHost.ValueToString(id);
CConfigValue *val=g_ConfigDB.CreateValue(cfgNs, propName);
char *str;
if (JS_ConvertArguments(cx, 1, vp, "s", &str))
{
val->m_String=str;
return JS_TRUE;
}
else
return JS_FALSE;
}
JSClass Class = {
"ConfigNamespace", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
GetProperty, SetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub
};
JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval )
{
if (argc != 0)
return JS_FALSE;
JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj);
*rval=OBJECT_TO_JSVAL(newObj);
return JS_TRUE;
}
void SetNamespace(JSContext *cx, JSObject *obj, EConfigNamespace cfgNs)
{
JS_SetPrivate(cx, obj, (void *)cfgNs);
}
JSBool WriteFile( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval )
{
EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
if (argc != 2)
return JS_FALSE;
JSBool useVFS;
char *path;
if (JS_ConvertArguments(cx, 2, argv, "bs", &useVFS, &path))
{
JSBool res=g_ConfigDB.WriteFile(cfgNs, useVFS?true:false, path);
*rval = BOOLEAN_TO_JSVAL(res);
return JS_TRUE;
}
else
return JS_FALSE;
}
JSBool Reload( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval )
{
if (argc != 0)
return JS_FALSE;
EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
JSBool ret=g_ConfigDB.Reload(cfgNs);
*rval = BOOLEAN_TO_JSVAL(ret);
return JS_TRUE;
}
JSBool SetFile( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* UNUSED(rval) )
{
if (argc != 0)
return JS_FALSE;
EConfigNamespace cfgNs=(EConfigNamespace)(intptr_t)JS_GetPrivate(cx, obj);
if (cfgNs < 0 || cfgNs >= CFG_LAST)
return JS_FALSE;
JSBool useVFS;
char *path;
if (JS_ConvertArguments(cx, 2, argv, "bs", &useVFS, &path))
{
g_ConfigDB.SetConfigFile(cfgNs, useVFS?true:false, path);
return JS_TRUE;
}
else
return JS_FALSE;
}
JSFunctionSpec Funcs[] = {
{ "writeFile", WriteFile, 2, 0, 0},
{ "reload", Reload, 0, 0, 0},
{ "setFile", SetFile, 2, 0, 0},
{0}
};
};
namespace ConfigDB_JS
{
JSClass Class = {
"ConfigDB", 0,
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub
};
JSPropertySpec Props[] = {
{0}
};
JSFunctionSpec Funcs[] = {
{0}
};
JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval )
{
if (argc != 0)
return JS_FALSE;
JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj);
*rval=OBJECT_TO_JSVAL(newObj);
int flags=JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT;
#define cfg_ns(_propname, _enum) STMT (\
JSObject *nsobj=g_ScriptingHost.CreateCustomObject("ConfigNamespace"); \
debug_assert(nsobj); \
ConfigNamespace_JS::SetNamespace(cx, nsobj, _enum); \
debug_assert(JS_DefineProperty(cx, newObj, _propname, OBJECT_TO_JSVAL(nsobj), NULL, NULL, flags)); )
cfg_ns("default", CFG_DEFAULT);
cfg_ns("system", CFG_SYSTEM);
cfg_ns("user", CFG_USER);
cfg_ns("mod", CFG_MOD);
#undef cfg_ns
return JS_TRUE;
}
};
CConfigDB::CConfigDB()
{
g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL);
g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL);
JSObject *js_ConfigDB=g_ScriptingHost.CreateCustomObject("ConfigDB");
g_ScriptingHost.SetGlobal("g_ConfigDB", OBJECT_TO_JSVAL(js_ConfigDB));
}
CConfigValue *CConfigDB::GetValue(EConfigNamespace ns, const CStr& name)
{
CConfigValueSet* values = GetValues( ns, name );
if( !values ) return( NULL );
return &( (*values)[0] );
}
CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name )
{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return NULL;
}
TConfigMap::iterator it = m_Map[CFG_COMMAND].find( name );
if( it != m_Map[CFG_COMMAND].end() )
return &( it->second );
for( int search_ns = ns; search_ns >= 0; search_ns-- )
{
TConfigMap::iterator it = m_Map[search_ns].find(name);
if (it != m_Map[search_ns].end())
return &( it->second );
}
return( NULL );
}
CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name)
{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return NULL;
}
CConfigValue *ret=GetValue(ns, name);
if (ret) return ret;
TConfigMap::iterator it=m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet( 1 )));
return &(it->second[0]);
}
void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStr& path)
{
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return;
}
m_ConfigFile[ns]=path;
m_UseVFS[ns]=useVFS;
}
bool CConfigDB::Reload(EConfigNamespace ns)
{
// Set up CParser
CParser parser;
CParserLine parserLine;
parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]");
parser.InputTaskType("CommentOrBlank", "_[;[$rest]]");
// Open file with VFS
shared_ptr buffer; size_t buflen;
{
// Handle missing files quietly
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
{
LOG(CLogger::Warning, LOG_CATEGORY, "Cannot find config file \"%s\" - ignoring", m_ConfigFile[ns].c_str());
return false;
}
else
{
LibError ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen);
if (ret != INFO::OK)
{
- LOG(CLogger::Error, LOG_CATEGORY, "vfs_load for \"%s\" failed: return was %lld", m_ConfigFile[ns].c_str(), ret);
+ LOG(CLogger::Error, LOG_CATEGORY, "vfs_load for \"%s\" failed: return was %ld", m_ConfigFile[ns].c_str(), ret);
return false;
}
}
}
TConfigMap newMap;
char *filebuf=(char *)buffer.get();
char *filebufend=filebuf+buflen;
// Read file line by line
char *next=filebuf-1;
do
{
char *pos=next+1;
next=(char *)memchr(pos, '\n', filebufend-pos);
if (!next) next=filebufend;
char *lend=next;
if (*(lend-1) == '\r') lend--;
// Send line to parser
bool parseOk=parserLine.ParseString(parser, std::string(pos, lend));
// Get name and value from parser
std::string name;
std::string value;
if (parseOk &&
parserLine.GetArgCount()>=2 &&
parserLine.GetArgString(0, name) &&
parserLine.GetArgString(1, value))
{
// Add name and value to the map
size_t argCount = parserLine.GetArgCount();
newMap[name].clear();
for( size_t t = 0; t < argCount; t++ )
{
if( !parserLine.GetArgString( (int)t + 1, value ) )
continue;
CConfigValue argument;
argument.m_String = value;
newMap[name].push_back( argument );
LOG(CLogger::Normal, LOG_CATEGORY, "Loaded config std::string \"%s\" = \"%s\"", name.c_str(), value.c_str());
}
}
}
while (next < filebufend);
m_Map[ns].swap(newMap);
return true;
}
bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStr& path)
{
debug_assert(useVFS);
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return false;
}
const char *filepath=path.c_str();
shared_ptr buf = io_Allocate(1*MiB);
char* pos = (char*)buf.get();
TConfigMap &map=m_Map[ns];
for(TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it)
{
pos += sprintf(pos, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str());
}
const size_t len = pos - (char*)buf.get();
LibError ret = g_VFS->CreateFile(filepath, buf, len);
if(ret < 0)
{
LOG(CLogger::Error, LOG_CATEGORY, "CConfigDB::WriteFile(): CreateFile \"%s\" failed (error: %d)", filepath, (int)ret);
return false;
}
return true;
}
Index: ps/trunk/source/ps/XML/XMLWriter.cpp
===================================================================
--- ps/trunk/source/ps/XML/XMLWriter.cpp (revision 7112)
+++ ps/trunk/source/ps/XML/XMLWriter.cpp (revision 7113)
@@ -1,260 +1,260 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "XMLWriter.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
// TODO (maybe): Write to the file frequently, instead of buffering
// the entire file, so that large files get written faster.
namespace
{
CStr escapeAttributeValue(const char* input)
{
// Spec says:
// AttValue ::= '"' ([^<&"] | Reference)* '"'
// so > is allowed in attribute values, so we don't bother escaping it.
CStr ret = input;
ret.Replace("&", "&");
ret.Replace("<", "<");
ret.Replace("\"", """);
return ret;
}
CStr escapeCharacterData(const char* input)
{
// CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
CStr ret = input;
ret.Replace("&", "&");
ret.Replace("<", "<");
ret.Replace("]]>", "]]>");
return ret;
}
CStr escapeComment(const char* input)
{
// Comment ::= ''
// This just avoids double-hyphens, and doesn't enforce the no-hyphen-at-end
// rule, since it's only used in contexts where there's already a space
// between this data and the -->.
CStr ret = input;
ret.Replace("--", "\xE2\x80\x90\xE2\x80\x90");
// replace with U+2010 HYPHEN, because it's close enough and it's
// probably nicer than inserting spaces or deleting hyphens or
// any alternative
return ret;
}
}
enum { EL_ATTR, EL_TEXT, EL_SUBEL };
XMLWriter_File::XMLWriter_File()
: m_Indent(0), m_LastElement(NULL),
m_PrettyPrint(true)
{
// Encoding is always UTF-8 - that's one of the only two guaranteed to be
// supported by XML parsers (along with UTF-16), and there's not much need
// to let people choose another.
m_Data = "\n";
}
bool XMLWriter_File::StoreVFS(const char* filename)
{
if (m_LastElement) debug_warn("ERROR: Saving XML while an element is still open");
const size_t size = m_Data.length();
shared_ptr data = io_Allocate(size);
cpu_memcpy(data.get(), m_Data.data(), size);
LibError ret = g_VFS->CreateFile(filename, data, size);
if (ret < 0)
{
- LOG(CLogger::Error, "xml", "Error saving XML data through VFS: %d", ret);
+ LOG(CLogger::Error, "xml", "Error saving XML data through VFS: %ld", ret);
return false;
}
return true;
}
const CStr& XMLWriter_File::GetOutput()
{
return m_Data;
}
void XMLWriter_File::Comment(const char* text)
{
ElementStart(NULL, "!-- ");
m_Data += escapeComment(text);
m_Data += " -->";
--m_Indent;
}
CStr XMLWriter_File::Indent()
{
return std::string(m_Indent, '\t');
}
void XMLWriter_File::ElementStart(XMLWriter_Element* element, const char* name)
{
if (m_LastElement) m_LastElement->Close(EL_SUBEL);
m_LastElement = element;
if (m_PrettyPrint)
{
m_Data += "\n";
m_Data += Indent();
}
m_Data += "<";
m_Data += name;
++m_Indent;
}
void XMLWriter_File::ElementClose()
{
m_Data += ">";
}
void XMLWriter_File::ElementEnd(const char* name, int type)
{
--m_Indent;
m_LastElement = NULL;
switch (type)
{
case EL_ATTR:
m_Data += "/>";
break;
case EL_TEXT:
m_Data += "";
m_Data += name;
m_Data += ">";
break;
case EL_SUBEL:
if (m_PrettyPrint)
{
m_Data += "\n";
m_Data += Indent();
}
m_Data += "";
m_Data += name;
m_Data += ">";
break;
default:
debug_assert(0);
}
}
void XMLWriter_File::ElementText(const char* text)
{
m_Data += escapeCharacterData(text);
}
XMLWriter_Element::XMLWriter_Element(XMLWriter_File& file, const char* name)
: m_File(&file), m_Name(name), m_Type(EL_ATTR)
{
m_File->ElementStart(this, name);
}
XMLWriter_Element::~XMLWriter_Element()
{
m_File->ElementEnd(m_Name, m_Type);
}
void XMLWriter_Element::Close(int type)
{
if (m_Type == type)
return;
m_File->ElementClose();
m_Type = type;
}
// Template specialisations for various string types:
template <> void XMLWriter_Element::Text(const char* text)
{
Close(EL_TEXT);
m_File->ElementText(text);
}
template <> void XMLWriter_Element::Text(const wchar_t* text)
{
Text( CStrW(text).ToUTF8().c_str() );
}
//
template <> void XMLWriter_File::ElementAttribute(const char* name, const char* const& value, bool newelement)
{
if (newelement)
{
ElementStart(NULL, name);
m_Data += ">";
ElementText(value);
ElementEnd(name, EL_TEXT);
}
else
{
debug_assert(m_LastElement && m_LastElement->m_Type == EL_ATTR);
m_Data += " ";
m_Data += name;
m_Data += "=\"";
m_Data += escapeAttributeValue(value);
m_Data += "\"";
}
}
// Attribute/setting value-to-string template specialisations.
//
// These only deal with basic types. Anything more complicated should
// be converted into a basic type by whatever is making use of XMLWriter,
// to keep game-related logic out of the not-directly-game-related code here.
template <> void XMLWriter_File::ElementAttribute(const char* name, const CStr& value, bool newelement)
{
ElementAttribute(name, value.c_str(), newelement);
}
// Use CStr's conversion for most types:
#define TYPE2(ID_T, ARG_T) \
template <> void XMLWriter_File::ElementAttribute(const char* name, ARG_T value, bool newelement) \
{ \
ElementAttribute(name, CStr(value).c_str(), newelement); \
}
#define TYPE(T) TYPE2(T, const T &)
TYPE(int)
TYPE(unsigned int)
TYPE(float)
TYPE(double)
// Encode Unicode strings as UTF-8
template <> void XMLWriter_File::ElementAttribute(const char* name, const CStrW& value, bool newelement)
{
ElementAttribute(name, value.ToUTF8(), newelement);
}
Index: ps/trunk/source/ps/Filesystem.cpp
===================================================================
--- ps/trunk/source/ps/Filesystem.cpp (revision 7112)
+++ ps/trunk/source/ps/Filesystem.cpp (revision 7113)
@@ -1,88 +1,88 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Filesystem.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY "file"
PIVFS g_VFS;
bool FileExists(const char* pathname)
{
return g_VFS->GetFileInfo(pathname, 0) == INFO::OK;
}
bool FileExists(const VfsPath& pathname)
{
return g_VFS->GetFileInfo(pathname, 0) == INFO::OK;
}
CVFSFile::CVFSFile()
{
}
CVFSFile::~CVFSFile()
{
}
PSRETURN CVFSFile::Load(const VfsPath& filename)
{
// Load should never be called more than once, so complain
if (m_Buffer)
{
debug_assert(0);
return PSRETURN_CVFSFile_AlreadyLoaded;
}
LibError ret = g_VFS->LoadFile(filename, m_Buffer, m_BufferSize);
if (ret != INFO::OK)
{
- LOG(CLogger::Error, LOG_CATEGORY, "CVFSFile: file %s couldn't be opened (vfs_load: %d)", filename.string().c_str(), ret);
+ LOG(CLogger::Error, LOG_CATEGORY, "CVFSFile: file %s couldn't be opened (vfs_load: %ld)", filename.string().c_str(), ret);
return PSRETURN_CVFSFile_LoadFailed;
}
return PSRETURN_OK;
}
const u8* CVFSFile::GetBuffer() const
{
// Die in a very obvious way, to avoid subtle problems caused by
// accidentally forgetting to check that the open succeeded
if (!m_Buffer)
{
debug_warn("GetBuffer() called with no file loaded");
throw PSERROR_CVFSFile_InvalidBufferAccess();
}
return m_Buffer.get();
}
size_t CVFSFile::GetBufferSize() const
{
return m_BufferSize;
}
CStr CVFSFile::GetAsString() const
{
return std::string((char*)GetBuffer(), GetBufferSize());
}
Index: ps/trunk/source/ps/Pyrogenesis.cpp
===================================================================
--- ps/trunk/source/ps/Pyrogenesis.cpp (revision 7112)
+++ ps/trunk/source/ps/Pyrogenesis.cpp (revision 7113)
@@ -1,119 +1,119 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include
#include "Pyrogenesis.h"
#include "ps/i18n.h"
#include "lib/sysdep/sysdep.h"
#include "lib/path_util.h"
#include "lib/svn_revision.h"
DEFINE_ERROR(PS_OK, "OK");
DEFINE_ERROR(PS_FAIL, "Fail");
static const wchar_t* translate_no_mem = L"(no mem)";
// overrides ah_translate. registered in GameSetup.cpp
const wchar_t* psTranslate(const wchar_t* text)
{
// make sure i18n system is (already|still) initialized.
if(g_CurrentLocale)
{
// be prepared for this to fail, because translation potentially
// involves script code and the JS context might be corrupted.
try
{
CStrW ret = I18n::translate(text);
const wchar_t* ret_dup = wcsdup(ret.c_str());
return ret_dup? ret_dup : translate_no_mem;
}
catch(...)
{
}
}
// i18n not available: at least try and return the text (unchanged)
const wchar_t* ret_dup = wcsdup(text);
return ret_dup? ret_dup : translate_no_mem;
}
void psTranslateFree(const wchar_t* text)
{
if(text != translate_no_mem)
free((void*)text);
}
// convert contents of file from char to wchar_t and
// append to file.
static void AppendAsciiFile(FILE* out, const char* in_filename)
{
FILE* in = fopen(in_filename, "rb");
if(!in)
{
fwprintf(out, L"(unavailable)");
return;
}
const size_t buf_size = 1024;
char buf[buf_size+1]; // include space for trailing '\0'
while(!feof(in))
{
size_t bytes_read = fread(buf, 1, buf_size, in);
if(!bytes_read)
break;
buf[bytes_read] = 0; // 0-terminate
fwprintf(out, L"%hs", buf);
}
fclose(in);
}
// for user convenience, bundle all logs into this file:
void psBundleLogs(FILE* f)
{
- fwprintf(f, L"SVN Revision: %s\n\n", svn_revision);
+ fwprintf(f, L"SVN Revision: %ls\n\n", svn_revision);
fwprintf(f, L"System info:\n\n");
fs::path path1(fs::path(psLogDir())/"system_info.txt");
AppendAsciiFile(f, path1.external_file_string().c_str());
fwprintf(f, L"\n\n====================================\n\n");
fwprintf(f, L"Main log:\n\n");
fs::path path2(fs::path(psLogDir())/"mainlog.html");
AppendAsciiFile(f, path2.external_file_string().c_str());
fwprintf(f, L"\n\n====================================\n\n");
}
static char logDir[PATH_MAX];
void psSetLogDir(const char* path)
{
path_copy(logDir, path);
}
const char* psLogDir()
{
return logDir;
}
Index: ps/trunk/source/ps/Interact.cpp
===================================================================
--- ps/trunk/source/ps/Interact.cpp (revision 7112)
+++ ps/trunk/source/ps/Interact.cpp (revision 7113)
@@ -1,1634 +1,1634 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Interact.h"
#include "graphics/GameView.h"
#include "graphics/HFTracer.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/Terrain.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "gui/CGUI.h"
#include "gui/MiniMap.h"
#include "lib/input.h"
#include "lib/res/graphics/unifont.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "network/NetMessage.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Player.h"
#include "ps/Filesystem.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "scripting/GameEvents.h"
#include "scripting/ScriptableComplex.inl"
#include "simulation/BoundingObjects.h"
#include "simulation/Collision.h"
#include "simulation/Entity.h"
#include "simulation/EntityFormation.h"
#include "simulation/EntityManager.h"
#include "simulation/EntityTemplate.h"
#include "simulation/EntityTemplateCollection.h"
#include "simulation/EventHandlers.h"
#include "simulation/FormationManager.h"
#include "simulation/Simulation.h"
#include "simulation/TerritoryManager.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY "world"
extern CStr g_CursorName;
extern float g_xres, g_yres;
static const double SELECT_DBLCLICK_RATE = 0.5;
const int ORDER_DELAY = 5;
bool customSelectionMode=false;
void CSelectedEntities::AddSelection( HEntity entity )
{
m_group = -1;
debug_assert( !IsSelected( entity ) );
m_selected.push_back( entity );
entity->m_selected = true;
m_selectionChanged = true;
}
void CSelectedEntities::RemoveSelection( HEntity entity )
{
m_group = -1;
debug_assert( IsSelected( entity ) );
entity->m_selected = false;
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
{
if( (*it) == entity )
{
m_selected.erase( it );
m_selectionChanged = true;
break;
}
}
}
void CSelectedEntities::RenderSelectionOutlines()
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderSelectionOutline();
if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderSelectionOutline( 0.5f );
glDisable( GL_BLEND );
}
}
void CSelectedEntities::RenderBars()
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderBars();
/*if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderBars();
glDisable( GL_BLEND );
}*/
}
void CSelectedEntities::RenderAuras()
{
std::vector::iterator it;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
for ( it = m_selected.begin(); it != m_selected.end(); ++it )
(*it)->RenderAuras();
}
void CSelectedEntities::RenderHealthBars()
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderHealthBar();
if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderHealthBar();
glDisable( GL_BLEND );
}
}
void CSelectedEntities::RenderStaminaBars()
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderStaminaBar();
if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderStaminaBar();
glDisable( GL_BLEND );
}
}
void CSelectedEntities::RenderBarBorders()
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderBarBorders();
if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderBarBorders();
glDisable( GL_BLEND );
}
}
void CSelectedEntities::RenderRanks()
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderRank();
if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderRank();
glDisable( GL_BLEND );
}
}
void CSelectedEntities::RenderOverlays()
{
CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain();
CCamera *pCamera=g_Game->GetView()->GetCamera();
glPushMatrix();
glEnable( GL_TEXTURE_2D );
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
{
if( (*it)->m_grouped != -1 )
{
if( !(*it)->m_bounds ) continue;
glLoadIdentity();
float x, y;
CVector3D labelpos = (*it)->m_graphics_position - pCamera->m_Orientation.GetLeft() * (*it)->m_bounds->m_radius;
#ifdef SELECTION_TERRAIN_CONFORMANCE
labelpos.Y = pTerrain->GetExactGroundLevel( labelpos.X, labelpos.Z );
#endif
pCamera->GetScreenCoordinates( labelpos, x, y );
glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f );
glScalef( 1.0f, -1.0f, 1.0f );
glwprintf( L"%d", (*it)->m_grouped );
}
}
if( m_group_highlight != -1 )
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
{
if( !(*it)->m_bounds ) continue;
glLoadIdentity();
float x, y;
CVector3D labelpos = (*it)->m_graphics_position - pCamera->m_Orientation.GetLeft() * (*it)->m_bounds->m_radius;
#ifdef SELECTION_TERRAIN_CONFORMANCE
labelpos.Y = pTerrain->GetExactGroundLevel( labelpos.X, labelpos.Z );
#endif
pCamera->GetScreenCoordinates( labelpos, x, y );
glColor4f( 1.0f, 1.0f, 1.0f, 0.5f );
glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f );
glScalef( 1.0f, -1.0f, 1.0f );
glwprintf( L"%d", (*it)->m_grouped );
}
glDisable( GL_BLEND );
}
/*
glLoadIdentity();
glTranslatef( (float)( g_mouse_x + 16 ), (float)( g_Renderer.GetHeight() - g_mouse_y - 8 ), 0.0f );
glScalef( 1.0f, -1.0f, 1.0f );
glColor4f( 1.0f, 1.0f, 1.0f, 0.5f );
switch( m_contextOrder )
{
case CEntityOrder::ORDER_GOTO:
glwprintf( L"Go to" );
break;
case CEntityOrder::ORDER_PATROL:
glwprintf( L"Patrol to" );
break;
case CEntityOrder::ORDER_ATTACK_MELEE:
glwprintf( L"Attack" );
break;
case CEntityOrder::ORDER_GATHER:
glwprintf( L"Gather" );
break;
}
*/
glDisable( GL_TEXTURE_2D );
glPopMatrix();
}
void CSelectedEntities::RenderRallyPoints()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->RenderRallyPoint();
if( m_group_highlight != -1 )
{
std::vector::iterator it;
for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ )
(*it)->RenderRallyPoint();
}
glDisable( GL_BLEND );
}
void CSelectedEntities::SetSelection( HEntity entity )
{
m_group = -1;
ClearSelection();
m_selected.push_back( entity );
}
void CSelectedEntities::ClearSelection()
{
m_group = -1;
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->m_selected = false;
m_selected.clear();
m_selectionChanged = true;
}
void CSelectedEntities::RemoveAll( HEntity entity )
{
// Remove a reference to an entity from everywhere
// (for use when said entity is being destroyed)
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
{
if( (*it) == entity )
{
m_selected.erase( it );
m_selectionChanged = true;
break;
}
}
for( u8 group = 0; group < MAX_GROUPS; group++ )
{
for( it = m_groups[group].begin(); it < m_groups[group].end(); it++ )
{
if( (*it) == entity )
{
m_groups[group].erase( it );
m_selectionChanged = true;
break;
}
}
}
}
CVector3D CSelectedEntities::GetSelectionPosition()
{
CVector3D avg;
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
avg += (*it)->m_graphics_position;
return( avg * ( 1.0f / m_selected.size() ) );
}
void CSelectedEntities::SaveGroup( i8 groupid )
{
std::vector::iterator it;
// Clear all entities in the group...
for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ )
(*it)->m_grouped = -1;
m_groups[groupid].clear();
// Remove selected entities from each group they're in, and flag them as
// members of the new group
for( it = m_selected.begin(); it < m_selected.end(); it++ )
{
if( (*it)->m_grouped != -1 )
{
std::vector& group = m_groups[(*it)->m_grouped];
std::vector::iterator it2;
for( it2 = group.begin(); it2 < group.end(); it2++ )
{
if( (*it2) == &(**it) )
{
group.erase( it2 );
break;
}
}
}
(*it)->m_grouped = groupid;
}
// Copy the group across
m_groups[groupid] = m_selected;
// Set the group selection memory
m_group = groupid;
}
void CSelectedEntities::AddToGroup( i8 groupid, HEntity entity )
{
std::vector::iterator it;
// Remove selected entities from each group they're in, and flag them as
// members of the new group
if( entity->m_grouped != -1 )
{
std::vector& group = m_groups[(*it)->m_grouped];
std::vector::iterator it2;
for( it2 = group.begin(); it2 < group.end(); it2++ )
{
if( (*it2) == entity )
{
group.erase( it2 );
break;
}
}
}
entity->m_grouped = groupid;
m_groups[groupid].push_back( entity );
}
void CSelectedEntities::LoadGroup( i8 groupid )
{
if( m_group == groupid )
return;
if( groupid >= MAX_GROUPS )
{
debug_warn( "Invalid group id" );
return;
}
ClearSelection();
m_selected = m_groups[groupid];
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->m_selected = true;
m_group = groupid;
m_selectionChanged = true;
}
void CSelectedEntities::AddGroup( i8 groupid )
{
std::vector::iterator it;
for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ )
{
if( !IsSelected( *it ) )
AddSelection( *it );
}
for( it = m_selected.begin(); it < m_selected.end(); it++ )
(*it)->m_selected = true;
}
void CSelectedEntities::ChangeGroup( HEntity entity, i8 groupid )
{
// Remove from current group
ssize_t current = entity->m_grouped;
if( current != -1 )
{
std::vector::iterator it;
for( it = m_groups[current].begin(); it < m_groups[current].end(); it++ )
{
if( (*it) == entity )
{
m_groups[current].erase( it );
break;
}
}
}
if( groupid != -1 )
m_groups[groupid].push_back( entity );
entity->m_grouped = groupid;
}
bool CSelectedEntities::IsSelected( HEntity entity )
{
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
{
if( (*it) == entity )
return( true );
}
return( false );
}
void CSelectedEntities::HighlightGroup( i8 groupid )
{
if( m_group_highlight != -1 )
return;
if( !GetGroupCount( groupid ) )
return;
m_group_highlight = groupid;
g_Game->GetView()->PushCameraTarget( GetGroupPosition( groupid ) );
}
void CSelectedEntities::HighlightNone()
{
if( m_group_highlight != -1 )
g_Game->GetView()->PopCameraTarget();
m_group_highlight = -1;
}
int CSelectedEntities::GetGroupCount( i8 groupid )
{
return( (int)m_groups[groupid].size() );
}
CVector3D CSelectedEntities::GetGroupPosition( i8 groupid )
{
CVector3D avg;
std::vector::iterator it;
for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ )
avg += (*it)->m_graphics_position;
return( avg * ( 1.0f / m_groups[groupid].size() ) );
}
void CSelectedEntities::Update()
{
static std::vector lastSelection;
// Drop out immediately if we're in some special interaction mode
if (customSelectionMode)
return;
if( !( m_selected == lastSelection ) )
{
g_JSGameEvents.FireSelectionChanged( m_selectionChanged );
lastSelection = m_selected;
}
if( m_selectionChanged || g_Mouseover.m_targetChanged )
{
// Can't order anything off the map
if( !g_Game->GetWorld()->GetTerrain()->IsOnMap( g_Mouseover.m_worldposition ) )
{
m_defaultCommand = -1;
m_secondaryCommand = -1;
return;
}
// Quick count to see which is the modal default order.
const int numCommands=NMT_COMMAND_LAST - NMT_COMMAND_FIRST;
int defaultPoll[numCommands];
std::map defaultCursor[numCommands];
std::map defaultAction[numCommands];
int secondaryPoll[numCommands];
std::map secondaryCursor[numCommands];
std::map secondaryAction[numCommands];
int t, vote, secvote;
for( t = 0; t < numCommands; t++ )
{
defaultPoll[t] = 0;
secondaryPoll[t] = 0;
}
std::vector::iterator it;
for( it = m_selected.begin(); it < m_selected.end(); it++ )
{
CEventTargetChanged evt( g_Mouseover.m_target );
(*it)->DispatchEvent( &evt );
vote = evt.m_defaultOrder - NMT_COMMAND_FIRST;
secvote = evt.m_secondaryOrder - NMT_COMMAND_FIRST;
if( ( vote >= 0 ) && ( vote < numCommands ) )
{
defaultPoll[vote]++;
defaultCursor[vote][evt.m_defaultCursor]++;
defaultAction[vote][evt.m_defaultAction]++;
}
if( ( secvote >= 0 ) && ( secvote < numCommands ) )
{
secondaryPoll[secvote]++;
secondaryCursor[secvote][evt.m_secondaryCursor]++;
secondaryAction[secvote][evt.m_secondaryAction]++;
}
}
// Don't count GOTO as a majority action unless everything else has 0 votes.
defaultPoll[NMT_GOTO - NMT_COMMAND_FIRST] = 0;
vote = -1;
secvote = -1;
for( t = 0; t < numCommands; t++ )
{
if( ( vote == -1 ) || ( defaultPoll[t] > defaultPoll[vote] ) )
vote = t;
if( ( secvote == -1 ) || ( secondaryPoll[t] > secondaryPoll[secvote] ) )
secvote = t;
}
std::map::iterator itv;
std::map::iterator iti;
m_defaultCommand = vote + NMT_COMMAND_FIRST;
m_secondaryCommand = secvote + NMT_COMMAND_FIRST;
// Now find the most appropriate cursor
t = 0;
for( itv = defaultCursor[vote].begin(); itv != defaultCursor[vote].end(); itv++ )
{
if( itv->second > t )
{
t = itv->second;
g_CursorName = itv->first;
}
}
/*
TODO: provide secondary cursor name?
t = 0;
for( itv = secondaryCursor[secvote].begin(); itv != secondaryCursor[secvote].end(); itv++ )
{
if( itv->second > t )
{
t = itv->second;
g_CursorName = itv->first;
}
}*/
// Find the most appropriate action parameter too
t = 0;
for( iti = defaultAction[vote].begin(); iti != defaultAction[vote].end(); iti++ )
{
if( iti->second > t )
{
t = iti->second;
m_defaultAction = iti->first;
}
}
t = 0;
for( iti = secondaryAction[secvote].begin(); iti != secondaryAction[secvote].end(); iti++ )
{
if( iti->second > t )
{
t = iti->second;
m_secondaryAction = iti->first;
}
}
m_selectionChanged = false;
g_Mouseover.m_targetChanged = false;
}
if( ( m_group_highlight != -1 ) && GetGroupCount( m_group_highlight ) )
g_Game->GetView()->SetCameraTarget( GetGroupPosition( m_group_highlight ) );
}
void CMouseoverEntities::Update( float timestep )
{
CCamera *pCamera=g_Game->GetView()->GetCamera();
//CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain();
CVector3D origin, dir;
pCamera->BuildCameraRay( origin, dir );
CUnit* hit = g_Game->GetWorld()->GetUnitManager().PickUnit( origin, dir, true );
m_worldposition = pCamera->GetWorldCoordinates(true);
if( hit && hit->GetEntity() && hit->GetEntity()->m_extant )
{
m_target = hit->GetEntity()->me;
}
else
m_target = HEntity();
if( m_target != m_lastTarget )
{
m_targetChanged = true;
m_lastTarget = m_target;
}
if( m_viewall )
{
// 'O' key. Show selection outlines for all player units on screen
// (as if bandboxing them all).
// These aren't selectable; clicking when 'O' is pressed won't select
// all units on screen.
m_mouseover.clear();
std::vector onscreen;
g_EntityManager.GetMatchingAsHandles( onscreen, IsOnScreen );
for(std::vector::iterator it = onscreen.begin(); it < onscreen.end(); it++ )
{
HEntity entity = *it;
if( entity->m_extant && ( entity->GetPlayer() == g_Game->GetLocalPlayer() ) )
m_mouseover.push_back( SMouseoverFader( entity, m_fademaximum, false ) );
}
}
else if( m_bandbox )
{
m_x2 = g_mouse_x;
m_y2 = g_mouse_y;
// Here's the fun bit:
// Get the screen-space coordinates of all onscreen entities
// then find the ones falling within the box.
//
// Fade in the ones in the box at (in+out) speed, then fade everything
// out at (out) speed.
std::vector onscreen;
g_EntityManager.GetMatchingAsHandles( onscreen, IsOnScreen );
// Reset active flags on everything...
for(std::vector::iterator it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ )
it2->isActive = false;
for(std::vector::iterator it = onscreen.begin(); it < onscreen.end(); it++ )
{
HEntity entity = *it;
if( !entity->m_extant )
continue;
// Can only bandbox units the local player controls.
if( entity->GetPlayer() != g_Game->GetLocalPlayer() )
continue;
CVector3D worldspace = entity->m_graphics_position;
float x, y;
pCamera->GetScreenCoordinates( worldspace, x, y );
bool inBox;
if( m_x1 < m_x2 )
{
inBox = ( x >= m_x1 ) && ( x < m_x2 );
}
else
{
inBox = ( x >= m_x2 ) && ( x < m_x1 );
}
if( m_y1 < m_y2 )
{
inBox &= ( y >= m_y1 ) && ( y < m_y2 );
}
else
{
inBox &= ( y >= m_y2 ) && ( y < m_y1 );
}
if( inBox )
{
bool found = false;
for(std::vector::iterator it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ )
if( it2->entity == &(**it) )
{
found = true;
it2->fade += ( m_fadeinrate + m_fadeoutrate ) * timestep;
it2->isActive = true;
}
if( !found )
m_mouseover.push_back( SMouseoverFader( *it, ( m_fadeinrate + m_fadeoutrate ) * timestep ) );
}
}
for(std::vector::iterator it2 = m_mouseover.begin(); it2 < m_mouseover.end(); )
{
it2->fade -= m_fadeoutrate * timestep;
if( it2->fade > m_fademaximum ) it2->fade = m_fademaximum;
if( it2->fade < 0.0f )
{
it2 = m_mouseover.erase( it2 );
}
else
it2++;
}
}
else
{
bool found = false;
for(std::vector::iterator it = m_mouseover.begin(); it < m_mouseover.end(); )
{
if( it->entity == m_target )
{
found = true;
it->fade += m_fadeinrate * timestep;
if( it->fade > m_fademaximum ) it->fade = m_fademaximum;
it->isActive = true;
it++; continue;
}
else
{
it->fade -= m_fadeoutrate * timestep;
if( it->fade <= 0.0f )
{
it = m_mouseover.erase( it ); continue;
}
it++; continue;
}
}
if( !found && (bool)m_target )
{
float initial = m_fadeinrate * timestep;
if( initial > m_fademaximum ) initial = m_fademaximum;
m_mouseover.push_back( SMouseoverFader( m_target, initial ) );
}
}
}
void CMouseoverEntities::AddSelection()
{
// Rules for shift-click selection:
// If selecting a non-allied unit, you can only select one. You can't
// select a mix of allied and non-allied units. Therefore:
// Forbid shift-click of enemy units unless the selection is empty
// Forbid shift-click of allied units if the selection contains one
// or more enemy units.
if( ( m_mouseover.size() != 0 ) &&
( m_mouseover.front().entity->GetPlayer() != g_Game->GetLocalPlayer() ) &&
( g_Selection.m_selected.size() != 0 ) )
return;
if( ( g_Selection.m_selected.size() != 0 ) &&
( g_Selection.m_selected.front()->GetPlayer() != g_Game->GetLocalPlayer() ) )
return;
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
if( it->isActive && !g_Selection.IsSelected( it->entity ) )
g_Selection.AddSelection( it->entity );
}
void CMouseoverEntities::RemoveSelection()
{
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
if( it->isActive && g_Selection.IsSelected( it->entity ) )
g_Selection.RemoveSelection( it->entity );
}
void CMouseoverEntities::SetSelection()
{
g_Selection.ClearSelection();
AddSelection();
}
void CMouseoverEntities::ExpandAcrossScreen()
{
std::vector activeset;
g_EntityManager.GetMatchingAsHandles(
activeset,
CEntityManager::EntityPredicateLogicalAnd
);
m_mouseover.clear();
for(std::vector::iterator it = activeset.begin(); it < activeset.end(); it++ )
{
HEntity entity = *it;
if( entity->m_extant )
m_mouseover.push_back( SMouseoverFader( entity ) );
}
}
void CMouseoverEntities::ExpandAcrossWorld()
{
std::vector activeset;
g_EntityManager.GetMatchingAsHandles( activeset, IsMouseoverType );
m_mouseover.clear();
for(std::vector::iterator it = activeset.begin(); it < activeset.end(); it++ )
{
HEntity entity = *it;
if( entity->m_extant )
m_mouseover.push_back( SMouseoverFader( entity ) );
}
}
void CMouseoverEntities::RenderSelectionOutlines()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
{
if( !g_Selection.IsSelected(it->entity) )
it->entity->RenderSelectionOutline( it->fade );
}
glDisable( GL_BLEND );
}
void CMouseoverEntities::RenderAuras()
{
std::vector::iterator it;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
for ( it = m_mouseover.begin(); it != m_mouseover.end(); ++it )
{
if( !g_Selection.IsSelected(it->entity) )
it->entity->RenderAuras();
}
}
void CMouseoverEntities::RenderBars()
{
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
{
if( !g_Selection.IsSelected(it->entity) )
it->entity->RenderBars();
}
}
void CMouseoverEntities::RenderHealthBars()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
it->entity->RenderHealthBar();
glDisable( GL_BLEND );
}
void CMouseoverEntities::RenderStaminaBars()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
it->entity->RenderStaminaBar();
glDisable( GL_BLEND );
}
void CMouseoverEntities::RenderRanks()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
it->entity->RenderRank();
glDisable( GL_BLEND );
}
void CMouseoverEntities::RenderBarBorders()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
it->entity->RenderBarBorders();
glDisable( GL_BLEND );
}
void CMouseoverEntities::RenderRallyPoints()
{
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
it->entity->RenderRallyPoint();
glDisable( GL_BLEND );
}
// Helper function for CSelectedEntities::LoadUnitUiTextures
static LibError LoadUnitUIThunk( const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), uintptr_t cbData )
{
std::map* textures = (std::map*)cbData;
CStr name(pathname.string());
if ( !tex_is_known_extension(name.c_str()) )
return INFO::CB_CONTINUE;
Handle tmp = ogl_tex_load(name.c_str());
if (tmp <= 0)
{
- LOG(CLogger::Error, LOG_CATEGORY, "Rank Textures", "loadRankTextures failed on \"%s\"", name.c_str());
+ LOG(CLogger::Error, LOG_CATEGORY, "loadRankTextures failed on \"%s\"", name.c_str());
return INFO::CB_CONTINUE;
}
name.Remove("art/textures/ui/session/icons/"); //Names are relative to this directory
(*textures)[name] = tmp;
ogl_tex_upload(tmp);
return INFO::CB_CONTINUE;
}
int CSelectedEntities::LoadUnitUiTextures()
{
THROW_ERR( fs_util::ForEachFile(g_VFS, "art/textures/ui/session/icons/", LoadUnitUIThunk, (uintptr_t)&m_unitUITextures, 0, fs_util::DIR_RECURSIVE));
return 0;
}
void CSelectedEntities::DestroyUnitUiTextures()
{
for ( std::map::iterator it=m_unitUITextures.begin(); it != m_unitUITextures.end(); it++ )
{
ogl_tex_free(it->second);
it->second = 0;
}
}
void CMouseoverEntities::RenderOverlays()
{
CCamera *pCamera=g_Game->GetView()->GetCamera();
CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain();
glLoadIdentity();
glDisable( GL_TEXTURE_2D );
if( m_bandbox )
{
//glPushMatrix();
glColor3f( 1.0f, 1.0f, 1.0f );
glBegin( GL_LINE_LOOP );
glVertex2i( m_x1, g_Renderer.GetHeight() - m_y1 );
glVertex2i( m_x2, g_Renderer.GetHeight() - m_y1 );
glVertex2i( m_x2, g_Renderer.GetHeight() - m_y2 );
glVertex2i( m_x1, g_Renderer.GetHeight() - m_y2 );
glEnd();
//glPopMatrix();
}
glEnable( GL_TEXTURE_2D );
std::vector::iterator it;
for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ )
{
if( it->entity->m_grouped != -1 )
{
if( !it->entity->m_bounds ) continue;
glPushMatrix();
glEnable( GL_TEXTURE_2D );
glLoadIdentity();
float x, y;
CVector3D labelpos = it->entity->m_graphics_position - pCamera->m_Orientation.GetLeft() * it->entity->m_bounds->m_radius;
#ifdef SELECTION_TERRAIN_CONFORMANCE
labelpos.Y = pTerrain->GetExactGroundLevel( labelpos.X, labelpos.Z );
#endif
pCamera->GetScreenCoordinates( labelpos, x, y );
glColor4f( 1.0f, 1.0f, 1.0f, it->fade );
glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f );
glScalef( 1.0f, -1.0f, 1.0f );
glwprintf( L"%d", it->entity->m_grouped );
glDisable( GL_TEXTURE_2D );
glPopMatrix();
}
}
}
void CMouseoverEntities::StartBandbox( u16 x, u16 y )
{
m_bandbox = true;
m_x1 = x; m_y1 = y;
}
void CMouseoverEntities::StopBandbox()
{
m_bandbox = false;
}
void FireWorldClickEvent(int button, int clicks)
{
//debug_printf("FireWorldClickEvent: button %d, clicks %d\n", button, clicks);
//If we're clicking on the minimap, use its world click handler
if ( g_Selection.m_mouseOverMM )
return;
g_JSGameEvents.FireWorldClick(
button,
clicks,
g_Selection.m_defaultCommand,
g_Selection.m_defaultAction,
g_Selection.m_secondaryCommand, // FIXME Secondary order, depends entity scripts etc
g_Selection.m_secondaryAction, // FIXME Secondary action, depends entity scripts etc
g_Mouseover.m_target,
g_Mouseover.m_worldposition.x,
g_Mouseover.m_worldposition.y);
//Reset duplication flag- after this, it isn't the same order
std::vector::iterator it=g_Selection.m_selected.begin();
for ( ; it != g_Selection.m_selected.end(); it++ )
{
if ( (*it)->m_formation >= 0)
(*it)->GetFormation()->SetDuplication(false);
}
}
void MouseButtonUpHandler(const SDL_Event_* ev, int clicks)
{
FireWorldClickEvent(ev->ev.button.button, clicks);
switch( ev->ev.button.button )
{
case SDL_BUTTON_LEFT:
if( g_BuildingPlacer.m_active )
{
g_BuildingPlacer.MouseReleased();
break;
}
if (customSelectionMode)
break;
if( g_Mouseover.m_viewall )
break;
if( clicks == 2 )
{
// Double click
g_Mouseover.ExpandAcrossScreen();
}
else if( clicks == 3 )
{
// Triple click
g_Mouseover.ExpandAcrossWorld();
}
g_Mouseover.StopBandbox();
if( hotkeys[HOTKEY_SELECTION_ADD] )
{
g_Mouseover.AddSelection();
}
else if( hotkeys[HOTKEY_SELECTION_REMOVE] )
{
g_Mouseover.RemoveSelection();
}
else
g_Mouseover.SetSelection();
break;
case SDL_BUTTON_RIGHT:
if( g_BuildingPlacer.m_active )
{
g_BuildingPlacer.Deactivate();
break;
}
}
}
InReaction InteractInputHandler( const SDL_Event_* ev )
{
if (!g_app_has_focus || !g_Game)
return IN_PASS;
CGameView *pView=g_Game->GetView();
//CCamera *pCamera=pView->GetCamera();
//CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain();
// One entry for each mouse button
// note: to store these in an array, we make assumptions as to the
// SDL_BUTTON_* values; these are verified at compile time.
cassert(SDL_BUTTON_LEFT == 1 && SDL_BUTTON_MIDDLE == 2 && SDL_BUTTON_RIGHT == 3 && \
SDL_BUTTON_WHEELUP == 4 && SDL_BUTTON_WHEELDOWN == 5);
const int SDL_BUTTON_INDEX_COUNT = 6;
static double lastclicktime[SDL_BUTTON_INDEX_COUNT];
static HEntity lastclickobject[SDL_BUTTON_INDEX_COUNT];
static u8 clicks[SDL_BUTTON_INDEX_COUNT];
ONCE(
for (int i=0;iev.type != SDL_MOUSEBUTTONUP)
return IN_PASS;
switch( ev->ev.type )
{
case SDL_HOTKEYDOWN:
switch( ev->ev.user.code )
{
case HOTKEY_HIGHLIGHTALL:
g_Mouseover.m_viewall = true;
break;
case HOTKEY_SELECTION_SNAP:
if( g_Selection.m_selected.size() )
pView->SetCameraTarget( g_Selection.GetSelectionPosition() );
break;
case HOTKEY_CAMERA_UNIT_VIEW:
{
if ( pView->IsAttached() )
break; //Should only exit unit view through unit view hotkey
if ( pView->IsUnitView() )
{
//If already in unit view, exit it
pView->ToUnitView( NULL, NULL );
break;
}
if ( g_Selection.m_selected.empty() )
break;
std::vector& Props = g_Selection.m_selected.front()->m_actor->GetModel()->GetProps();
for (size_t x=0; xm_Name == "head" )
{
pView->ToUnitView( g_Selection.m_selected.front(), Props[x].m_Model);
break;
}
}
break;
}
case HOTKEY_CAMERA_UNIT_ATTACH:
{
if ( pView->IsUnitView() )
break; //Should only exit unit view through unit view hotkey
if ( pView->IsAttached() )
{
//If already in unit view, exit it
pView->AttachToUnit( NULL );
break;
}
if ( g_Selection.m_selected.empty() )
break;
pView->AttachToUnit( g_Selection.m_selected.front() );
break;
}
default:
if( ( ev->ev.user.code >= HOTKEY_SELECTION_GROUP_0 ) && ( ev->ev.user.code <= HOTKEY_SELECTION_GROUP_19 ) )
{
// The above test limits it to 20 groups, so don't worry about overflowing
i8 id = (i8)( ev->ev.user.code - HOTKEY_SELECTION_GROUP_0 );
if( hotkeys[HOTKEY_SELECTION_GROUP_ADD] )
{
g_Selection.AddGroup( id );
}
else if( hotkeys[HOTKEY_SELECTION_GROUP_SAVE] )
{
g_Selection.SaveGroup( id );
}
else if( hotkeys[HOTKEY_SELECTION_GROUP_SNAP] )
{
g_Selection.HighlightGroup( id );
}
else
{
if( ( g_Selection.m_group == id ) && g_Selection.GetGroupCount( id ) )
{
pView->SetCameraTarget( g_Selection.GetGroupPosition( id ) );
}
else
g_Selection.LoadGroup( id );
}
return( IN_HANDLED );
}
return( IN_PASS );
}
return( IN_HANDLED );
case SDL_HOTKEYUP:
switch( ev->ev.user.code )
{
case HOTKEY_SELECTION_GROUP_SNAP:
if( g_Selection.m_group_highlight != -1 )
g_Selection.HighlightNone();
break;
case HOTKEY_HIGHLIGHTALL:
g_Mouseover.m_viewall = false;
break;
default:
return( IN_PASS );
}
return( IN_HANDLED );
case SDL_MOUSEBUTTONUP:
{
int button = ev->ev.button.button;
// Only process buttons within the range for which we have button state
// arrays above.
if (button >= 0 && button < SDL_BUTTON_INDEX_COUNT)
{
double time = timer_Time();
// Reset clicks counter if too slow or if the cursor's
// hovering over something else now.
if( time - lastclicktime[button] >= SELECT_DBLCLICK_RATE )
clicks[button] = 0;
if( g_Mouseover.m_target != lastclickobject[button] )
clicks[button] = 0;
clicks[button]++;
lastclicktime[button] = time;
lastclickobject[button] = g_Mouseover.m_target;
if(ev->ev.button.button == SDL_BUTTON_LEFT )
button_down = false;
if(ev->ev.button.button == SDL_BUTTON_RIGHT )
right_button_down = false;
MouseButtonUpHandler(ev, clicks[button]);
}
break;
}
case SDL_MOUSEBUTTONDOWN:
switch( ev->ev.button.button )
{
case SDL_BUTTON_LEFT:
button_down = true;
button_down_x = ev->ev.button.x;
button_down_y = ev->ev.button.y;
button_down_time = timer_Time();
if( g_BuildingPlacer.m_active )
{
g_BuildingPlacer.MousePressed();
}
break;
case SDL_BUTTON_RIGHT:
right_button_down = true;
}
break;
case SDL_MOUSEMOTION:
if( !g_Mouseover.IsBandbox() && button_down && !g_BuildingPlacer.m_active && !right_button_down )
{
int deltax = ev->ev.motion.x - button_down_x;
int deltay = ev->ev.motion.y - button_down_y;
if( abs( deltax ) > 2 || abs( deltay ) > 2 )
g_Mouseover.StartBandbox( button_down_x, button_down_y );
}
break;
}
return( IN_PASS );
}
bool IsOnScreen( CEntity* e, void* UNUSED(userdata) )
{
CCamera *pCamera=g_Game->GetView()->GetCamera();
CFrustum frustum = pCamera->GetFrustum();
if( e->m_actor )
return( frustum.IsBoxVisible( CVector3D(), e->m_actor->GetModel()->GetBounds() ) );
else
// If there's no actor, just treat the entity as a point
return( frustum.IsBoxVisible( e->m_graphics_position, CBound() ) );
}
bool IsMouseoverType( CEntity* e, void* UNUSED(userdata) )
{
std::vector::iterator it;
for( it = g_Mouseover.m_mouseover.begin(); it < g_Mouseover.m_mouseover.end(); it++ )
{
if( it->isActive && ( (CEntityTemplate*)it->entity->m_base == (CEntityTemplate*)e->m_base )
&& ( it->entity->GetPlayer() == e->GetPlayer() ) )
return( true );
}
return( false );
}
void StartCustomSelection()
{
customSelectionMode = true;
}
void ResetInteraction()
{
customSelectionMode = false;
}
static const float angleBias = 3*PI/4; // Atlas does the same
bool CBuildingPlacer::Activate(CStrW& templateName)
{
if(m_active)
{
return false;
}
m_templateName = templateName;
m_active = true;
m_clicked = false;
m_dragged = false;
m_angle = angleBias;
m_timeSinceClick = 0;
m_totalTime = 0;
m_valid = false;
m_template = g_EntityTemplateCollection.GetTemplate( m_templateName );
if( !m_template )
{
Deactivate();
return false;
}
CStr actorName ( m_template->m_actorName ); // convert CStrW->CStr8
std::set selections;
m_actor = g_Game->GetWorld()->GetUnitManager().CreateUnit( actorName, 0, selections );
m_actor->SetPlayerID( g_Game->GetLocalPlayer()->GetPlayerID() );
// m_bounds
if( m_template->m_bound_type == CBoundingObject::BOUND_CIRCLE )
{
m_bounds = new CBoundingCircle( 0, 0, m_template->m_bound_circle );
}
else if( m_template->m_bound_type == CBoundingObject::BOUND_OABB )
{
m_bounds = new CBoundingBox( 0, 0, CVector2D(1, 0), m_template->m_bound_box );
}
return true;
}
void CBuildingPlacer::MousePressed()
{
CCamera &camera=*g_Game->GetView()->GetCamera();
if( m_template->m_socket == L"" )
clickPos = camera.GetWorldCoordinates();
m_clicked = true;
}
void CBuildingPlacer::MouseReleased()
{
Deactivate(); // do it first in case we fail for any reason
m_clicked = false;
if( m_valid )
{
// issue a place object command across the network
CPlaceObjectMessage *msg = new CPlaceObjectMessage();
msg->m_IsQueued = hotkeys[HOTKEY_ORDER_QUEUE];
msg->m_Entities = g_Selection.m_selected;
msg->m_Template = m_templateName;
msg->m_X = (u32) (clickPos.X * 1000);
msg->m_Y = (u32) (clickPos.Y * 1000);
msg->m_Z = (u32) (clickPos.Z * 1000);
msg->m_Angle = (u32) (m_angle * 1000);
g_Game->GetSimulation()->QueueLocalCommand(msg);
}
if( hotkeys[HOTKEY_ORDER_QUEUE] )
{
Activate( m_templateName ); // reactivate so we can place more buildings of the same type
}
}
void CBuildingPlacer::Deactivate()
{
m_active = false;
g_Game->GetWorld()->GetUnitManager().RemoveUnit( m_actor );
delete m_actor;
m_actor = 0;
delete m_bounds;
m_bounds = 0;
}
void CBuildingPlacer::Update( float timeStep )
{
if(!m_active)
return;
m_totalTime += timeStep;
if( m_clicked && m_template->m_socket == L"" )
{
// Rotate object
m_timeSinceClick += timeStep;
CCamera &camera = *g_Game->GetView()->GetCamera();
CVector3D mousePos = camera.GetWorldCoordinates();
CVector3D dif = mousePos - clickPos;
float x = dif.X, z = dif.Z;
if(x*x + z*z < 3*3)
{
if(m_dragged || m_timeSinceClick > 0.2f)
m_angle += timeStep * PI;
}
else
{
m_dragged = true;
m_angle = atan2(x, z);
}
}
CVector3D pos;
if( m_clicked )
{
pos = clickPos;
}
else
{
CCamera &camera = *g_Game->GetView()->GetCamera();
pos = camera.GetWorldCoordinates();
}
bool onSocket = false;
if( m_template->m_socket != L"" )
{
// If we're on a socket of our type, remember that and snap ourselves to it
m_bounds->SetPosition(pos.X, pos.Z); // first, move bounds to mouse pos
CEntity* ent = GetCollisionEntity( m_bounds, 0 ); // now, check what we intersect
if( ent && ent->m_classes.IsMember( m_template->m_socket ) ) // if it's a socket, snap to it
{
onSocket = true;
m_angle = atan2f( ent->m_ahead.x, ent->m_ahead.y );
pos = ent->m_position;
clickPos = ent->m_position;
}
}
// Set position and angle to the location we decided on
CMatrix3D m;
m.SetYRotation(m_angle + PI);
m.Translate(pos);
m_actor->GetModel()->SetTransform( m );
m_bounds->SetPosition(pos.X, pos.Z);
if( m_bounds->m_type == CBoundingObject::BOUND_OABB )
{
CBoundingBox* box = (CBoundingBox*) m_bounds;
box->SetOrientation( m_angle );
}
// Validate placement location.
CheckValidLocation(pos, onSocket);
// Flash our actor red if the position is invalid.
CColor col;
if( m_valid )
{
col = CColor(1.0f, 1.0f, 1.0f, 1.0f);
}
else
{
float add = ( sin(4*PI*m_totalTime) + 1.0f ) * 0.08f;
col = CColor( 1.4f+add, 0.4f+add, 0.4f+add, 1.0f );
}
m_actor->GetModel()->SetShadingColor( col );
}
/**
* Check whether the placement location is valid (look at whether we're
* on the map, who owns the territory, whether we are on a socket, and
* whether we are colliding with anything).
*
* @param pos position under the cursor
* @param onSocket whether on a socket or not
*/
void CBuildingPlacer::CheckValidLocation( CVector3D pos, bool onSocket )
{
CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain();
if( pTerrain->IsOnMap( pos.X, pos.Z ) )
{
// Check that we are being placed in a valid territory; currently, m_territoryRestriction
// can be either "Allied" for placing in allied territories, or nothing.
// Special case: If the territory has no centre unit, that means the map contains no
// Settlements; for testing purposes, let us build anywhere.
CTerritory* territory = g_Game->GetWorld()->GetTerritoryManager()->GetTerritory( pos.X, pos.Z );
if( m_template->m_territoryRestriction == L"Allied" && (bool)territory->centre
&& territory->owner != g_Game->GetLocalPlayer() )
{
m_valid = false;
}
else
{
// It's valid to place the object here if the position is unobstructed by
// anything except possibly our socket (which we find out by passing an
// ignoreClass to GetCollisionObject); also, if we are a socketed object,
// we check that we are actually on a socket, using onSocket (set above).
// UPDATED: Check for territorial building limit
m_valid = ( m_template->m_socket == L"" || onSocket )
&& ( GetCollisionObject( m_bounds, 0, &m_template->m_socket ) == 0 )
&& IsWithinLimit(pos); // Is this building within its appropriate territorial limit?
}
}
else
{
m_valid = false;
}
}
/**
* Checks whether there is space (territorial limit) in the current territory
*
* @param pos position under the cursor
*
* @returns true if within limit, false otherwise
*/
bool CBuildingPlacer::IsWithinLimit( CVector3D pos )
{
// Get the territorial building limit based on its category
CStrW category = m_template->m_buildingLimitCategory;
jsval param = ToJSVal(category);
int limit = ToPrimitive(g_ScriptingHost.CallFunction("getBuildingLimit", ¶m, 1));
if( limit == 0 )
{
return true;
}
else
{
CTerritory* territory = g_Game->GetWorld()->GetTerritoryManager()->GetTerritory( pos.X, pos.Z );
std::vector extantEntities;
std::vector::iterator entIter;
int buildingCount = 0; // Number of buildings in the current territory
g_EntityManager.GetExtant(extantEntities);
// NOTE: This loop runs continuously because the function is called in Update()
for( entIter = extantEntities.begin(); entIter != extantEntities.end(); entIter++ )
{
if((*entIter)->m_buildingLimitCategory == m_template->m_buildingLimitCategory
&& g_Game->GetWorld()->GetTerritoryManager()->GetTerritory( (*entIter)->m_position.X, (*entIter)->m_position.Z ) == territory)
{
buildingCount++;
}
}
if(buildingCount < limit)
return true;
else
return false;
}
}
Index: ps/trunk/source/gui/CGUI.cpp
===================================================================
--- ps/trunk/source/gui/CGUI.cpp (revision 7112)
+++ ps/trunk/source/gui/CGUI.cpp (revision 7113)
@@ -1,1915 +1,1915 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
/*
CGUI
*/
#include "precompiled.h"
#include
#include
#include "lib/res/graphics/unifont.h"
#include "GUI.h"
// Types - when including them into the engine.
#include "CButton.h"
#include "CImage.h"
#include "CText.h"
#include "CCheckBox.h"
#include "CRadioButton.h"
#include "CInput.h"
#include "CList.h"
#include "CDropDown.h"
#include "CProgressBar.h"
#include "CTooltip.h"
#include "MiniMap.h"
#include "ps/XML/Xeromyces.h"
#include "ps/Font.h"
#include "ps/Pyrogenesis.h"
#include "lib/input.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/sysdep/sysdep.h"
// TODO Gee: Whatever include CRect/CPos/CSize
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "scripting/ScriptingHost.h"
#include "ps/Hotkey.h"
#include "ps/Globals.h"
#include "ps/Filesystem.h"
const double SELECT_DBLCLICK_RATE = 0.5;
#include "ps/CLogger.h"
#define LOG_CATEGORY "gui"
// Class for global JavaScript object
JSClass GUIClass = {
"GUIClass", 0,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
};
//-------------------------------------------------------------------
// called from main loop when (input) events are received.
// event is passed to other handlers if false is returned.
// trampoline: we don't want to make the implementation (in CGUI) static
//-------------------------------------------------------------------
InReaction gui_handler(const SDL_Event_* ev)
{
PROFILE( "GUI event handler" );
return g_GUI.HandleEvent(ev);
}
InReaction CGUI::HandleEvent(const SDL_Event_* ev)
{
InReaction ret = IN_PASS;
if (ev->ev.type == SDL_GUIHOTKEYPRESS)
{
const CStr& objectName = *(CStr*) ev->ev.user.data1;
IGUIObject* object = FindObjectByName(objectName);
if (! object)
{
LOG(CLogger::Error, LOG_CATEGORY, "Cannot find hotkeyed object '%s'", objectName.c_str());
}
else
{
object->HandleMessage( SGUIMessage( GUIM_PRESSED ) );
object->ScriptEvent("press");
}
}
else if (ev->ev.type == SDL_MOUSEMOTION)
{
// Yes the mouse position is stored as float to avoid
// constant conversions when operating in a
// float-based environment.
m_MousePos = CPos((float)ev->ev.motion.x, (float)ev->ev.motion.y);
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::HandleMessage,
SGUIMessage(GUIM_MOUSE_MOTION));
}
// Update m_MouseButtons. (BUTTONUP is handled later.)
else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_MIDDLE:
m_MouseButtons |= Bit(ev->ev.button.button);
break;
default:
break;
}
}
// JW: (pre|post)process omitted; what're they for? why would we need any special button_released handling?
// Only one object can be hovered
IGUIObject *pNearest = NULL;
// TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
try
{
PROFILE( "mouse events" );
// TODO Gee: Optimizations needed!
// these two recursive function are quite overhead heavy.
// pNearest will after this point at the hovered object, possibly NULL
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::ChooseMouseOverAndClosest,
pNearest);
// Is placed in the UpdateMouseOver function
//if (ev->ev.type == SDL_MOUSEMOTION && pNearest)
// pNearest->ScriptEvent("mousemove");
// Now we'll call UpdateMouseOver on *all* objects,
// we'll input the one hovered, and they will each
// update their own data and send messages accordingly
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::UpdateMouseOver,
pNearest);
if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
if (pNearest)
{
if (pNearest != m_FocusedObject)
{
// Update focused object
if (m_FocusedObject)
m_FocusedObject->HandleMessage(SGUIMessage(GUIM_LOST_FOCUS));
m_FocusedObject = pNearest;
m_FocusedObject->HandleMessage(SGUIMessage(GUIM_GOT_FOCUS));
}
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT));
pNearest->ScriptEvent("mouseleftpress");
// Block event, so things on the map (behind the GUI) won't be pressed
ret = IN_HANDLED;
}
else if (m_FocusedObject)
{
m_FocusedObject->HandleMessage(SGUIMessage(GUIM_LOST_FOCUS));
//if (m_FocusedObject-> TODO SelfishFocus?
m_FocusedObject = 0;
}
break;
case SDL_BUTTON_WHEELDOWN: // wheel down
if (pNearest)
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN));
pNearest->ScriptEvent("mousewheeldown");
ret = IN_HANDLED;
}
break;
case SDL_BUTTON_WHEELUP: // wheel up
if (pNearest)
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP));
pNearest->ScriptEvent("mousewheelup");
ret = IN_HANDLED;
}
break;
default:
break;
}
}
else
if (ev->ev.type == SDL_MOUSEBUTTONUP)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
if (pNearest)
{
double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT];
pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time();
//Double click?
if (timeElapsed < SELECT_DBLCLICK_RATE)
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_DBLCLICK_LEFT));
pNearest->ScriptEvent("mouseleftdoubleclick");
}
else
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_LEFT));
pNearest->ScriptEvent("mouseleftrelease");
}
ret = IN_HANDLED;
}
break;
case SDL_BUTTON_RIGHT:
if (pNearest)
{
double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT];
pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time();
//Double click?
if (timeElapsed < SELECT_DBLCLICK_RATE)
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_DBLCLICK_RIGHT));
//pNearest->ScriptEvent("mouserightdoubleclick");
}
else
{
pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_RIGHT));
//pNearest->ScriptEvent("mouserightrelease");
}
ret = IN_HANDLED;
}
break;
}
// Reset all states on all visible objects
GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject,
&IGUIObject::ResetStates);
// It will have reset the mouse over of the current hovered, so we'll
// have to restore that
if (pNearest)
pNearest->m_MouseHovering = true;
}
}
catch (PS_RESULT e)
{
UNUSED2(e);
debug_warn("CGUI::HandleEvent error");
// TODO Gee: Handle
}
// JW: what's the difference between mPress and mDown? what's the code below responsible for?
/*
// Generally if just mouse is clicked
if (m_pInput->mDown(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT);
}
*/
// BUTTONUP's effect on m_MouseButtons is handled after
// everything else, so that e.g. 'press' handlers (activated
// on button up) see which mouse button had been pressed.
if (ev->ev.type == SDL_MOUSEBUTTONUP)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_MIDDLE:
m_MouseButtons &= ~Bit(ev->ev.button.button);
break;
default:
break;
}
}
// Handle keys for input boxes
if (GetFocusedObject())
{
if (
(ev->ev.type == SDL_KEYDOWN &&
ev->ev.key.keysym.sym != SDLK_ESCAPE &&
!g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
!g_keys[SDLK_LALT] && !g_keys[SDLK_RALT])
|| ev->ev.type == SDL_HOTKEYDOWN
)
{
ret = GetFocusedObject()->ManuallyHandleEvent(ev);
}
// else will return IN_PASS because we never used the button.
}
return ret;
}
void CGUI::TickObjects()
{
CStr action = "tick";
GUI::RecurseObject(0, m_BaseObject,
&IGUIObject::ScriptEvent, action);
// Also update tooltips:
// TODO: Efficiency
IGUIObject* pNearest = NULL;
GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
&IGUIObject::ChooseMouseOverAndClosest,
pNearest);
m_Tooltip.Update(pNearest, m_MousePos, this);
}
void CGUI::SendEventToAll(const CStr& EventName)
{
// janwas 2006-03-03: spoke with Ykkrosh about EventName case.
// when registering, case is converted to lower - this avoids surprise
// if someone were to get the case wrong and then not notice their
// handler is never called. however, until now, the other end
// (sending events here) wasn't converting to lower case,
// leading to a similar problem.
// now fixed; case is irrelevant since all are converted to lower.
GUI::RecurseObject(0, m_BaseObject,
&IGUIObject::ScriptEvent, EventName.LowerCase());
}
//-------------------------------------------------------------------
// Constructor / Destructor
//-------------------------------------------------------------------
CGUI::CGUI() : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0)
{
m_BaseObject = new CGUIDummyObject;
m_BaseObject->SetGUI(this);
// Construct the parent object for all GUI JavaScript things
m_ScriptObject = JS_NewObject(g_ScriptingHost.getContext(), &GUIClass, NULL, NULL);
debug_assert(m_ScriptObject != NULL); // How should it handle errors?
JS_AddRoot(g_ScriptingHost.getContext(), &m_ScriptObject);
// This will make this invisible, not add
//m_BaseObject->SetName(BASE_OBJECT_NAME);
}
CGUI::~CGUI()
{
if (m_BaseObject)
delete m_BaseObject;
if (m_ScriptObject)
// Let it be garbage-collected
JS_RemoveRoot(g_ScriptingHost.getContext(), &m_ScriptObject);
}
//-------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------
IGUIObject *CGUI::ConstructObject(const CStr& str)
{
if (m_ObjectTypes.count(str) > 0)
return (*m_ObjectTypes[str])();
else
{
// Error reporting will be handled with the NULL return.
return NULL;
}
}
void CGUI::Initialize()
{
// Add base types!
// You can also add types outside the GUI to extend the flexibility of the GUI.
// Pyrogenesis though will have all the object types inserted from here.
AddObjectType("empty", &CGUIDummyObject::ConstructObject);
AddObjectType("button", &CButton::ConstructObject);
AddObjectType("image", &CImage::ConstructObject);
AddObjectType("text", &CText::ConstructObject);
AddObjectType("checkbox", &CCheckBox::ConstructObject);
AddObjectType("radiobutton", &CRadioButton::ConstructObject);
AddObjectType("progressbar", &CProgressBar::ConstructObject);
AddObjectType("minimap", &CMiniMap::ConstructObject);
AddObjectType("input", &CInput::ConstructObject);
// The following line was commented out, I don't know if that's me or not, or
// for what reason, but I'm gonna uncomment, if anything breaks, just let me
// know, or if it wasn't I that commented it out, do let me know why.
// -- Gee 20-07-2005
AddObjectType("list", &CList::ConstructObject);
//
AddObjectType("dropdown", &CDropDown::ConstructObject);
}
void CGUI::Process()
{
/*
// TODO Gee: check if m_pInput is valid, otherwise return
/// debug_assert(m_pInput);
// Pre-process all objects
try
{
GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_PREPROCESS);
}
catch (PS_RESULT e)
{
return;
}
// Check mouse over
try
{
// Only one object can be hovered
// check which one it is, if any !
IGUIObject *pNearest = NULL;
GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest);
// Now we'll call UpdateMouseOver on *all* objects,
// we'll input the one hovered, and they will each
// update their own data and send messages accordingly
GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest);
// If pressed
if (m_pInput->mPress(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_PRESS_LEFT);
}
else
// If released
if (m_pInput->mRelease(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_RELEASE_LEFT);
}
// Generally if just mouse is clicked
if (m_pInput->mDown(NEMM_BUTTON1) && pNearest)
{
pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT);
}
}
catch (PS_RESULT e)
{
return;
}
// Post-process all objects
try
{
GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_POSTPROCESS);
}
catch (PS_RESULT e)
{
return;
}
*/
}
void CGUI::Draw()
{
// Clear the depth buffer, so the GUI is
// drawn on top of everything else
glClear(GL_DEPTH_BUFFER_BIT);
glPushMatrix();
guiLoadIdentity();
try
{
// Recurse IGUIObject::Draw() with restriction: hidden
// meaning all hidden objects won't call Draw (nor will it recurse its children)
GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw);
}
catch (PS_RESULT e)
{
UNUSED2(e);
glPopMatrix();
// TODO Gee: Report error.
debug_warn("CGUI::Draw error");
return;
}
glPopMatrix();
}
void CGUI::DrawSprite(const CGUISpriteInstance& Sprite,
int CellID,
const float& Z,
const CRect& Rect,
const CRect& UNUSED(Clipping))
{
// If the sprite doesn't exist (name == ""), don't bother drawing anything
if (Sprite.IsEmpty())
return;
// TODO: Clipping?
glPushMatrix();
glTranslatef(0.0f, 0.0f, Z);
Sprite.Draw(Rect, CellID, m_Sprites);
glPopMatrix();
}
void CGUI::Destroy()
{
// We can use the map to delete all
// now we don't want to cancel all if one Destroy fails
map_pObjects::iterator it;
for (it = m_pAllObjects.begin(); it != m_pAllObjects.end(); ++it)
{
try
{
it->second->Destroy();
}
catch (PS_RESULT e)
{
UNUSED2(e);
debug_warn("CGUI::Destroy error");
// TODO Gee: Handle
}
delete it->second;
}
for (std::map::iterator it2 = m_Sprites.begin(); it2 != m_Sprites.end(); ++it2)
for (std::vector::iterator it3 = it2->second.m_Images.begin(); it3 != it2->second.m_Images.end(); ++it3)
delete it3->m_Effects;
// Clear all
m_pAllObjects.clear();
m_Sprites.clear();
m_Icons.clear();
}
void CGUI::UpdateResolution()
{
// Update ALL cached
GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize );
}
void CGUI::AddObject(IGUIObject* pObject)
{
try
{
// Add CGUI pointer
GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this);
// Add child to base object
m_BaseObject->AddChild(pObject); // can throw
// Cache tree
GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
// Loaded
GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_LOAD));
}
catch (PS_RESULT e)
{
throw e;
}
}
void CGUI::UpdateObjects()
{
// We'll fill a temporary map until we know everything
// succeeded
map_pObjects AllObjects;
try
{
// Fill freshly
GUI< map_pObjects >::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects );
}
catch (PS_RESULT e)
{
// Throw the same error
throw e;
}
// Else actually update the real one
m_pAllObjects.swap(AllObjects);
}
bool CGUI::ObjectExists(const CStr& Name) const
{
return m_pAllObjects.count(Name) != 0;
}
IGUIObject* CGUI::FindObjectByName(const CStr& Name) const
{
map_pObjects::const_iterator it = m_pAllObjects.find(Name);
if (it == m_pAllObjects.end())
return NULL;
else
return it->second;
}
// private struct used only in GenerateText(...)
struct SGenerateTextImage
{
float m_YFrom, // The image's starting location in Y
m_YTo, // The image's end location in Y
m_Indentation; // The image width in other words
// Some help functions
// TODO Gee: CRect => CPoint ?
void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall &SpriteCall,
const float width, const float y,
const CSize &Size, const CStr& TextureName,
const float BufferZone, const int CellID)
{
// TODO Gee: Temp hardcoded values
SpriteCall.m_Area.top = y+BufferZone;
SpriteCall.m_Area.bottom = y+BufferZone + Size.cy;
if (Left)
{
SpriteCall.m_Area.left = BufferZone;
SpriteCall.m_Area.right = Size.cx+BufferZone;
}
else
{
SpriteCall.m_Area.left = width-BufferZone - Size.cx;
SpriteCall.m_Area.right = width-BufferZone;
}
SpriteCall.m_CellID = CellID;
SpriteCall.m_Sprite = TextureName;
m_YFrom = SpriteCall.m_Area.top-BufferZone;
m_YTo = SpriteCall.m_Area.bottom+BufferZone;
m_Indentation = Size.cx+BufferZone*2;
}
};
SGUIText CGUI::GenerateText(const CGUIString &string,
const CStr& Font, const float &Width, const float &BufferZone,
const IGUIObject *pObject)
{
SGUIText Text; // object we're generating
if (string.m_Words.size() == 0)
return Text;
float x=BufferZone, y=BufferZone; // drawing pointer
int from=0;
bool done=false;
bool FirstLine = true; // Necessary because text in the first line is shorter
// (it doesn't count the line spacing)
// Images on the left or the right side.
std::vector Images[2];
int pos_last_img=-1; // Position in the string where last img (either left or right) were encountered.
// in order to avoid duplicate processing.
// Easier to read.
bool WordWrapping = (Width != 0);
// Go through string word by word
for (int i=0; i<(int)string.m_Words.size()-1 && !done; ++i)
{
// Pre-process each line one time, so we know which floating images
// will be added for that line.
// Generated stuff is stored in Feedback.
CGUIString::SFeedback Feedback;
// Preliminary line_height, used for word-wrapping with floating images.
float prelim_line_height=0.f;
// Width and height of all text calls generated.
string.GenerateTextCall(Feedback, Font,
string.m_Words[i], string.m_Words[i+1],
FirstLine);
// Loop through our images queues, to see if images has been added.
// Check if this has already been processed.
// Also, floating images are only applicable if Word-Wrapping is on
if (WordWrapping && i > pos_last_img)
{
// Loop left/right
for (int j=0; j<2; ++j)
{
for (std::vector::const_iterator it = Feedback.m_Images[j].begin();
it != Feedback.m_Images[j].end();
++it)
{
SGUIText::SSpriteCall SpriteCall;
SGenerateTextImage Image;
// Y is if no other floating images is above, y. Else it is placed
// after the last image, like a stack downwards.
float _y;
if (Images[j].size() > 0)
_y = std::max(y, Images[j].back().m_YTo);
else
_y = y;
// Get Size from Icon database
SGUIIcon icon = GetIcon(*it);
CSize size = icon.m_Size;
Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID);
// Check if image is the lowest thing.
Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo);
Images[j].push_back(Image);
Text.m_SpriteCalls.push_back(SpriteCall);
}
}
}
pos_last_img = std::max(pos_last_img, i);
x += Feedback.m_Size.cx;
prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.cy);
// If Width is 0, then there's no word-wrapping, disable NewLine.
if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2)
{
// Change 'from' to 'i', but first keep a copy of its value.
int temp_from = from;
from = i;
static const int From=0, To=1;
//int width_from=0, width_to=width;
float width_range[2];
width_range[From] = BufferZone;
width_range[To] = Width - BufferZone;
// Floating images are only applicable if word-wrapping is enabled.
if (WordWrapping)
{
// Decide width of the line. We need to iterate our floating images.
// this won't be exact because we're assuming the line_height
// will be as our preliminary calculation said. But that may change,
// although we'd have to add a couple of more loops to try straightening
// this problem out, and it is very unlikely to happen noticeably if one
// structures his text in a stylistically pure fashion. Even if not, it
// is still quite unlikely it will happen.
// Loop through left and right side, from and to.
for (int j=0; j<2; ++j)
{
for (std::vector::const_iterator it = Images[j].begin();
it != Images[j].end();
++it)
{
// We're working with two intervals here, the image's and the line height's.
// let's find the union of these two.
float union_from, union_to;
union_from = std::max(y, it->m_YFrom);
union_to = std::min(y+prelim_line_height, it->m_YTo);
// The union is not empty
if (union_to > union_from)
{
if (j == From)
width_range[From] = std::max(width_range[From], it->m_Indentation);
else
width_range[To] = std::min(width_range[To], Width - it->m_Indentation);
}
}
}
}
// Reset X for the next loop
x = width_range[From];
// Now we'll do another loop to figure out the height of
// the line (the height of the largest character). This
// couldn't be determined in the first loop (main loop)
// because it didn't regard images, so we don't know
// if all characters processed, will actually be involved
// in that line.
float line_height=0.f;
for (int j=temp_from; j<=i; ++j)
{
// We don't want to use Feedback now, so we'll have to use
// another.
CGUIString::SFeedback Feedback2;
// Don't attach object, it'll suppress the errors
// we want them to be reported in the final GenerateTextCall()
// so that we don't get duplicates.
string.GenerateTextCall(Feedback2, Font,
string.m_Words[j], string.m_Words[j+1],
FirstLine);
// Append X value.
x += Feedback2.m_Size.cx;
if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine)
break;
// Let line_height be the maximum m_Height we encounter.
line_height = std::max(line_height, Feedback2.m_Size.cy);
if (WordWrapping && Feedback2.m_NewLine)
break;
}
// Reset x once more
x = width_range[From];
// Move down, because font drawing starts from the baseline
y += line_height;
// Do the real processing now
for (int j=temp_from; j<=i; ++j)
{
// We don't want to use Feedback now, so we'll have to use
// another one.
CGUIString::SFeedback Feedback2;
// Defaults
string.GenerateTextCall(Feedback2, Font,
string.m_Words[j], string.m_Words[j+1],
FirstLine, pObject);
// Iterate all and set X/Y values
// Since X values are not set, we need to make an internal
// iteration with an increment that will append the internal
// x, that is what x_pointer is for.
float x_pointer=0.f;
std::vector::iterator it;
for (it = Feedback2.m_TextCalls.begin(); it != Feedback2.m_TextCalls.end(); ++it)
{
it->m_Pos = CPos(x + x_pointer, y);
x_pointer += it->m_Size.cx;
if (it->m_pSpriteCall)
{
it->m_pSpriteCall->m_Area += it->m_Pos - CSize(0,it->m_pSpriteCall->m_Area.GetHeight());
}
}
// Append X value.
x += Feedback2.m_Size.cx;
Text.m_Size.cx = std::max(Text.m_Size.cx, x+BufferZone);
// The first word overrides the width limit, what we
// do, in those cases, are just drawing that word even
// though it'll extend the object.
if (WordWrapping) // only if word-wrapping is applicable
{
if (Feedback2.m_NewLine)
{
from = j+1;
// Sprite call can exist within only a newline segment,
// therefore we need this.
Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
break;
}
else
if (x > width_range[To] && j==temp_from)
{
from = j+1;
// do not break, since we want it to be added to m_TextCalls
}
else
if (x > width_range[To])
{
from = j;
break;
}
}
// Add the whole Feedback2.m_TextCalls to our m_TextCalls.
Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end());
Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
if (j == (int)string.m_Words.size()-2)
done = true;
}
// Reset X
x = 0.f;
// Update height of all
Text.m_Size.cy = std::max(Text.m_Size.cy, y+BufferZone);
FirstLine = false;
// Now if we entered as from = i, then we want
// i being one minus that, so that it will become
// the same i in the next loop. The difference is that
// we're on a new line now.
i = from-1;
}
}
return Text;
}
void CGUI::DrawText(SGUIText &Text, const CColor &DefaultColor,
const CPos &pos, const float &z, const CRect &clipping)
{
// TODO Gee: All these really necessary? Some
// are defaults and if you changed them
// the opposite value at the end of the functions,
// some things won't be drawn correctly.
glEnable(GL_TEXTURE_2D);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glDisable(GL_ALPHA_TEST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
if (clipping != CRect())
{
double eq[4][4] =
{
{ 0.0, 1.0, 0.0, -clipping.top },
{ 1.0, 0.0, 0.0, -clipping.left },
{ 0.0, -1.0, 0.0, clipping.bottom },
{ -1.0, 0.0, 0.0, clipping.right }
};
for (int i=0; i<4; ++i)
{
glClipPlane(GL_CLIP_PLANE0+i, eq[i]);
glEnable(GL_CLIP_PLANE0+i);
}
}
CFont* font = NULL;
CStr LastFontName;
for (std::vector::const_iterator it = Text.m_TextCalls.begin();
it != Text.m_TextCalls.end();
++it)
{
// If this is just a placeholder for a sprite call, continue
if (it->m_pSpriteCall)
continue;
// Switch fonts when necessary, but remember the last one used
if (it->m_Font != LastFontName)
{
delete font;
font = new CFont(it->m_Font);
font->Bind();
LastFontName = it->m_Font;
}
CColor color = it->m_UseCustomColor ? it->m_Color : DefaultColor;
glPushMatrix();
// TODO Gee: (2004-09-04) Why are font corrupted if inputted float value?
glTranslatef((GLfloat)int(pos.x+it->m_Pos.x), (GLfloat)int(pos.y+it->m_Pos.y), z);
glColor4fv(color.FloatArray());
glwprintf(L"%ls", it->m_String.c_str()); // "%ls" is necessary in case m_String contains % symbols
glPopMatrix();
}
if (font)
delete font;
for (std::list::iterator it=Text.m_SpriteCalls.begin();
it!=Text.m_SpriteCalls.end();
++it)
{
DrawSprite(it->m_Sprite, it->m_CellID, z, it->m_Area + pos);
}
// TODO To whom it may concern: Thing were not reset, so
// I added this line, modify if incorrect --
if (clipping != CRect())
{
for (int i=0; i<4; ++i)
glDisable(GL_CLIP_PLANE0+i);
}
glDisable(GL_TEXTURE_2D);
// -- GL
}
bool CGUI::GetPreDefinedColor(const CStr& name, CColor &Output)
{
if (m_PreDefinedColors.count(name) == 0)
{
return false;
}
else
{
Output = m_PreDefinedColors[name];
return true;
}
}
void CGUI::ReportParseError(const char *str, ...)
{
va_list argp;
char buffer[512];
memset(buffer,0,sizeof(buffer));
va_start(argp, str);
sys_vsnprintf(buffer, sizeof(buffer), str, argp);
va_end(argp);
// Print header
if (m_Errors==0)
{
LOG(CLogger::Error, LOG_CATEGORY, "*** GUI Tree Creation Errors:");
}
// Important, set ParseError to true
++m_Errors;
LOG(CLogger::Error, LOG_CATEGORY, buffer);
}
/**
* @callgraph
*/
void CGUI::LoadXmlFile(const std::string &Filename)
{
// Reset parse error
// we can later check if this has increased
m_Errors = 0;
CXeromyces XeroFile;
if (XeroFile.Load(Filename.c_str()) != PSRETURN_OK)
// Fail silently
return;
XMBElement node = XeroFile.GetRoot();
// Check root element's (node) name so we know what kind of
// data we'll be expecting
CStr root_name (XeroFile.GetElementString(node.GetNodeName()));
try
{
if (root_name == "objects")
{
Xeromyces_ReadRootObjects(node, &XeroFile);
// Re-cache all values so these gets cached too.
//UpdateResolution();
}
else
if (root_name == "sprites")
{
Xeromyces_ReadRootSprites(node, &XeroFile);
}
else
if (root_name == "styles")
{
Xeromyces_ReadRootStyles(node, &XeroFile);
}
else
if (root_name == "setup")
{
Xeromyces_ReadRootSetup(node, &XeroFile);
}
else
{
debug_warn("CGUI::LoadXmlFile error");
// TODO Gee: Output in log
}
}
catch (PSERROR_GUI& e)
{
- LOG(CLogger::Error, LOG_CATEGORY, "Errors loading GUI file %s (%s)", Filename.c_str(), e.getCode());
+ LOG(CLogger::Error, LOG_CATEGORY, "Errors loading GUI file %s (%d)", Filename.c_str(), e.getCode());
return;
}
// Now report if any other errors occured
if (m_Errors > 0)
{
/// g_console.submit("echo GUI Tree Creation Reports %d errors", m_Errors);
}
}
//===================================================================
// XML Reading Xeromyces Specific Sub-Routines
//===================================================================
void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile)
{
int el_script = pFile->GetElementID("script");
// Iterate main children
// they should all be