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 += ""; break; case EL_SUBEL: if (m_PrettyPrint) { m_Data += "\n"; m_Data += Indent(); } 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 or