Index: ps/trunk/source/ps/XML/RelaxNG.cpp =================================================================== --- ps/trunk/source/ps/XML/RelaxNG.cpp (revision 9122) +++ ps/trunk/source/ps/XML/RelaxNG.cpp (revision 9123) @@ -1,98 +1,103 @@ /* Copyright (C) 2010 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 "RelaxNG.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include TIMER_ADD_CLIENT(xml_validation); RelaxNGValidator::RelaxNGValidator() : m_Schema(NULL) { } RelaxNGValidator::~RelaxNGValidator() { if (m_Schema) xmlRelaxNGFree(m_Schema); } bool RelaxNGValidator::LoadGrammar(const std::string& grammar) { TIMER_ACCRUE(xml_validation); debug_assert(m_Schema == NULL); xmlRelaxNGParserCtxtPtr ctxt = xmlRelaxNGNewMemParserCtxt(grammar.c_str(), (int)grammar.size()); m_Schema = xmlRelaxNGParse(ctxt); xmlRelaxNGFreeParserCtxt(ctxt); if (m_Schema == NULL) { LOGERROR(L"RelaxNGValidator: Failed to compile schema"); return false; } return true; } bool RelaxNGValidator::Validate(const std::wstring& filename, const std::wstring& document) { + std::string docutf8 = "" + utf8_from_wstring(document); + + return ValidateEncoded(filename, docutf8); +} + +bool RelaxNGValidator::ValidateEncoded(const std::wstring& filename, const std::string& document) +{ TIMER_ACCRUE(xml_validation); if (!m_Schema) { LOGERROR(L"RelaxNGValidator: No grammar loaded"); return false; } - std::string docutf8 = "" + utf8_from_wstring(document); - - xmlDocPtr doc = xmlReadMemory(docutf8.c_str(), (int)docutf8.size(), utf8_from_wstring(filename).c_str(), NULL, XML_PARSE_NONET); + xmlDocPtr doc = xmlReadMemory(document.c_str(), (int)document.size(), utf8_from_wstring(filename).c_str(), NULL, XML_PARSE_NONET); if (doc == NULL) { LOGERROR(L"RelaxNGValidator: Failed to parse document"); return false; } xmlRelaxNGValidCtxtPtr ctxt = xmlRelaxNGNewValidCtxt(m_Schema); int ret = xmlRelaxNGValidateDoc(ctxt, doc); xmlRelaxNGFreeValidCtxt(ctxt); xmlFreeDoc(doc); if (ret == 0) { return true; } else if (ret > 0) { LOGERROR(L"RelaxNGValidator: Validation failed"); return false; } else { LOGERROR(L"RelaxNGValidator: Internal error %d", ret); return false; } } Index: ps/trunk/source/ps/XML/XMLWriter.h =================================================================== --- ps/trunk/source/ps/XML/XMLWriter.h (revision 9122) +++ ps/trunk/source/ps/XML/XMLWriter.h (revision 9123) @@ -1,159 +1,167 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 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 . */ #ifndef INCLUDED_XMLWRITER #define INCLUDED_XMLWRITER /* System for writing simple XML files, with human-readable formatting. Example usage: XML_Start(); { XML_Element("Scenario"); { XML_Element("Entities"); for (...) { XML_Element("Entity"); { XML_Element("Template"); XML_Text(entity.name); } // Or equivalently: XML_Setting("Template", entity.name); { XML_Element("Position"); XML_Attribute("x", entity.x); XML_Attribute("y", entity.y); XML_Attribute("z", entity.z); } { XML_Element("Orientation"); XML_Attribute("angle", entity.angle); } } } } Handle h = vfs_open("/test.xml", FILE_WRITE|FILE_NO_AIO); XML_StoreVFS(h); In general, "{ XML_Element(name); ... }" means " ... " -- the scoping braces are important to indicate where an element ends. XML_Attribute/XML_Setting are templated. To support more types, alter the end of XMLWriter.cpp. */ // Starts generating a new XML file. #define XML_Start() XMLWriter_File xml_file_ // Set pretty printing (newlines, tabs). Defaults to true. #define XML_SetPrettyPrint(enabled) xml_file_.SetPrettyPrint(false) // Add a comment to the XML file: #define XML_Comment(text) xml_file_.Comment(text) // Start a new element: #define XML_Element(name) XMLWriter_Element xml_element_ (xml_file_, name) // Add text to the interior of the current element: <...>text #define XML_Text(text) xml_element_.Text(text, false) // Add CDATA-escaped text to the interior of the current element: <...> #define XML_CDATA(text) xml_element_.Text(text, true) // Add an attribute to the current element: <... name="value" ...> #define XML_Attribute(name, value) xml_element_.Attribute(name, value) // Add a 'setting': value #define XML_Setting(name, value) xml_element_.Setting(name, value) +#define XML_WriteXMB(xero) xml_file_.XMB(xero) + // Create a VFS file from the XML data. // Returns true on success, false (and logs an error) on failure. #define XML_StoreVFS(vfs, pathname) xml_file_.StoreVFS(vfs, pathname) // Returns the contents of the XML file as a UTF-8 byte stream in a const CStr& // string. (Use CStr::FromUTF8 to get a Unicode string back.) #define XML_GetOutput() xml_file_.GetOutput() #include "ps/CStr.h" #include "lib/file/vfs/vfs.h" +class XMBElement; +class XMBFile; class XMLWriter_Element; class XMLWriter_File { public: XMLWriter_File(); void SetPrettyPrint(bool enabled) { m_PrettyPrint = enabled; } void Comment(const char* text); + void XMB(const XMBFile& file); + bool StoreVFS(const PIVFS& vfs, const VfsPath& pathname); const CStr& GetOutput(); private: friend class XMLWriter_Element; + void ElementXMB(const XMBFile& file, XMBElement el); + void ElementStart(XMLWriter_Element* element, const char* name); void ElementText(const char* text, bool cdata); template void ElementAttribute(const char* name, const T& value, bool newelement); void ElementClose(); void ElementEnd(const char* name, int type); CStr Indent(); bool m_PrettyPrint; CStr m_Data; int m_Indent; XMLWriter_Element* m_LastElement; }; class XMLWriter_Element { public: XMLWriter_Element(XMLWriter_File& file, const char* name); ~XMLWriter_Element(); template void Text(constCharPtr text, bool cdata); template void Attribute(const char* name, T value) { m_File->ElementAttribute(name, value, false); } template void Setting(const char* name, T value) { m_File->ElementAttribute(name, value, true); } void Close(int type); private: friend class XMLWriter_File; XMLWriter_File* m_File; CStr m_Name; int m_Type; }; #endif // INCLUDED_XMLWRITER Index: ps/trunk/source/ps/XML/RelaxNG.h =================================================================== --- ps/trunk/source/ps/XML/RelaxNG.h (revision 9122) +++ ps/trunk/source/ps/XML/RelaxNG.h (revision 9123) @@ -1,38 +1,40 @@ /* Copyright (C) 2010 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 . */ #ifndef INCLUDED_RELAXNG #define INCLUDED_RELAXNG typedef struct _xmlRelaxNG xmlRelaxNG; typedef xmlRelaxNG *xmlRelaxNGPtr; class RelaxNGValidator { public: RelaxNGValidator(); ~RelaxNGValidator(); bool LoadGrammar(const std::string& grammar); bool Validate(const std::wstring& filename, const std::wstring& document); + bool ValidateEncoded(const std::wstring& filename, const std::string& document); + private: xmlRelaxNGPtr m_Schema; }; #endif // INCLUDED_RELAXNG Index: ps/trunk/source/ps/XML/XMLWriter.cpp =================================================================== --- ps/trunk/source/ps/XML/XMLWriter.cpp (revision 9122) +++ ps/trunk/source/ps/XML/XMLWriter.cpp (revision 9123) @@ -1,306 +1,323 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2011 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" +#include "ps/XML/Xeromyces.h" #include "lib/utf8.h" #include "lib/sysdep/cpu.h" #include "maths/Fixed.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 escapeCDATA(const char* input) { CStr ret = input; ret.Replace("]]>", "]]>]]>' // 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 PIVFS& vfs, const VfsPath& pathname) { if (m_LastElement) debug_warn(L"ERROR: Saving XML while an element is still open"); const size_t size = m_Data.length(); shared_ptr data = io_Allocate(size); memcpy(data.get(), m_Data.data(), size); LibError ret = vfs->CreateFile(pathname, data, size); if (ret < 0) { LOGERROR(L"Error saving XML data through VFS: %ld", ret); return false; } return true; } const CStr& XMLWriter_File::GetOutput() { return m_Data; } +void XMLWriter_File::XMB(const XMBFile& file) +{ + ElementXMB(file, file.GetRoot()); +} + +void XMLWriter_File::ElementXMB(const XMBFile& file, XMBElement el) +{ + XMLWriter_Element writer(*this, file.GetElementString(el.GetNodeName()).c_str()); + + XERO_ITER_ATTR(el, attr) + writer.Attribute(file.GetAttributeString(attr.Name).c_str(), attr.Value); + + XERO_ITER_EL(el, child) + ElementXMB(file, child); +} + 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, bool cdata) { if (cdata) { m_Data += ""; } else { 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.c_str(), 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, bool cdata) { Close(EL_TEXT); m_File->ElementText(text, cdata); } template <> void XMLWriter_Element::Text(const wchar_t* text, bool cdata) { Text( CStrW(text).ToUTF8().c_str(), cdata ); } // template <> void XMLWriter_File::ElementAttribute(const char* name, const char* const& value, bool newelement) { if (newelement) { ElementStart(NULL, name); m_Data += ">"; ElementText(value, false); 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); } template <> void XMLWriter_File::ElementAttribute(const char* name, const std::string& value, bool newelement) { ElementAttribute(name, value.c_str(), newelement); } // Encode Unicode strings as UTF-8 template <> void XMLWriter_File::ElementAttribute(const char* name, const CStrW& value, bool newelement) { ElementAttribute(name, value.ToUTF8(), newelement); } template <> void XMLWriter_File::ElementAttribute(const char* name, const std::wstring& value, bool newelement) { ElementAttribute(name, utf8_from_wstring(value).c_str(), newelement); } template <> void XMLWriter_File::ElementAttribute(const char* name, const fixed& value, bool newelement) { ElementAttribute(name, value.ToString().c_str(), newelement); } template <> void XMLWriter_File::ElementAttribute(const char* name, const int& value, bool newelement) { std::stringstream ss; ss << value; ElementAttribute(name, ss.str().c_str(), newelement); } template <> void XMLWriter_File::ElementAttribute(const char* name, const unsigned int& value, bool newelement) { std::stringstream ss; ss << value; ElementAttribute(name, ss.str().c_str(), newelement); } template <> void XMLWriter_File::ElementAttribute(const char* name, const float& value, bool newelement) { std::stringstream ss; ss << value; ElementAttribute(name, ss.str().c_str(), newelement); } template <> void XMLWriter_File::ElementAttribute(const char* name, const double& value, bool newelement) { std::stringstream ss; ss << value; ElementAttribute(name, ss.str().c_str(), newelement); } Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 9122) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 9123) @@ -1,1104 +1,1106 @@ /* Copyright (C) 2011 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 "lib/app_hooks.h" #include "lib/input.h" #include "lib/lockfree.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/external_libraries/sdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/cursor.h" #include "lib/res/sound/snd_mgr.h" #include "lib/sysdep/cursor.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #if OS_WIN #include "lib/sysdep/os/win/wversion.h" #endif #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/ParticleEngine.h" #include "graphics/TerrainTextureManager.h" #include "renderer/Renderer.h" #include "renderer/VertexBufferManager.h" #include "maths/MathUtil.h" #include "simulation2/Simulation2.h" #include "scripting/ScriptingHost.h" #include "scripting/ScriptGlue.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptStats.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "ps/scripting/JSInterface_Console.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/ScriptFunctions.h" #include "sound/JSI_Sound.h" #include "network/NetServer.h" #include "network/NetClient.h" #include "ps/Pyrogenesis.h" // psSetLogDir #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/HWDetect.h" #if !(OS_WIN || OS_MACOSX) // assume all other platforms use X11 for wxWidgets #define MUST_INIT_X11 1 #include #else #define MUST_INIT_X11 0 #endif #include ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); bool g_DoRenderGui = true; bool g_DoRenderLogger = true; bool g_DoRenderCursor = true; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(int quality) { int q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn(L"SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { g_ScriptingHost.GetScriptInterface().SetGlobal("g_Progress", percent, true); g_ScriptingHost.GetScriptInterface().SetGlobal("g_LoadDescription", pending_task, true); g_GUI->SendEventToAll("progress"); } void Render() { ogl_WarnIfError(); CStr skystring = "255 0 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring.FromUTF8(), skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); + // prepare before starting the renderer frame + if (g_Game && g_Game->IsGameStarted()) + g_Game->GetView()->BeginFrame(); + // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) - { g_Game->GetView()->Render(); - } ogl_WarnIfError(); // set up overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); ogl_WarnIfError(); g_Renderer.RenderTextOverlays(); // Temp GUI message GeeTODO PROFILE_START("render gui"); if(g_DoRenderGui) g_GUI->Draw(); PROFILE_END("render gui"); ogl_WarnIfError(); // Particle Engine Updating CParticleEngine::GetInstance()->UpdateEmitters(); ogl_WarnIfError(); // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Added -- glEnable(GL_TEXTURE_2D); // -- GL glLoadIdentity(); PROFILE_START("render console"); g_Console->Render(); PROFILE_END("render console"); ogl_WarnIfError(); PROFILE_START("render logger"); if(g_DoRenderLogger) g_Logger->Render(); PROFILE_END("render logger"); ogl_WarnIfError(); // Profile information PROFILE_START("render profiling"); g_ProfileViewer.RenderProfile(); PROFILE_END("render profiling"); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) if (g_DoRenderCursor) { PROFILE("render cursor"); CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y); } else { if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y) < 0) LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); } } // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); g_Renderer.EndFrame(); ogl_WarnIfError(); } static void RegisterJavascriptInterfaces() { // maths JSI_Vector3D::init(); // graphics CGameView::ScriptingInit(); // renderer CRenderer::ScriptingInit(); // sound JSI_Sound::ScriptingInit(); // ps JSI_Console::init(); // GUI CGUI::ScriptingInit(); GuiScriptingInit(g_ScriptingHost.GetScriptInterface()); } static void InitScripting() { TIMER(L"InitScripting"); // Create the scripting host. This needs to be done before the GUI is created. // [7ms] new ScriptingHost; RegisterJavascriptInterfaces(); } #if 0 // disabled because the file cache doesn't work (http://trac.wildfiregames.com/ticket/611) static size_t OperatingSystemFootprint() { #if OS_WIN switch(wversion_Number()) { case WVERSION_2K: case WVERSION_XP: return 150; case WVERSION_XP64: return 200; default: // don't warn about newer Windows versions case WVERSION_VISTA: return 300; case WVERSION_7: return 250; } #else return 200; #endif } static size_t ChooseCacheSize() { // (all sizes in MiB) const size_t total = os_cpu_MemorySize(); const size_t available = os_cpu_MemoryAvailable(); debug_assert(total >= available); const size_t inUse = total-available; size_t os = OperatingSystemFootprint(); debug_assert(total >= os/2); size_t apps = (inUse > os)? (inUse - os) : 0; size_t game = 300; size_t cache = 200; // plenty of memory if(os + apps + game + cache < total) { cache = total - os - apps - game; } else // below min-spec { // assume kernel+other apps will be swapped out os = 9*os/10; apps = 2*apps/3; game = 3*game/4; if(os + apps + game + cache < total) cache = total - os - apps - game; else { cache = 50; debug_printf(L"Warning: memory size (%d MiB, %d used) is rather low\n", total, available); } } // total data is currently about 500 MiB, and not all of it // is used at once, so don't use more than that. // (this also ensures the byte count will fit in size_t and // avoids using up too much address space) cache = std::min(cache, (size_t)500); debug_printf(L"Cache: %d (total: %d; available: %d)\n", cache, total, available); return cache*MiB; } #else static size_t ChooseCacheSize() { return 32*MiB; } #endif ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags)) { // If we're fullscreen, then sometimes (at least on some particular drivers on Linux) // displaying the error dialog hangs the desktop since the dialog box is behind the // fullscreen window. So we just force the game to windowed mode before displaying the dialog. // (But only if we're in the main thread, and not if we're being reentrant.) if (ThreadUtil::IsMainThread()) { static bool reentering = false; if (!reentering) { reentering = true; g_VideoMode.SetFullscreen(false); reentering = false; } } // We don't actually implement the error display here, so return appropriately return ERI_NOT_IMPLEMENTED; } static void InitVfs(const CmdLineArgs& args) { TIMER(L"InitVfs"); const Paths paths(args); OsPath logs(paths.Logs()); CreateDirectories(logs, 0700); psSetLogDir(logs); // desired location for crashlog is now known. update AppHooks ASAP // (particularly before the following error-prone operations): AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; hooks.display_error = psDisplayError; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); g_VFS = CreateVfs(cacheSize); g_VFS->Mount(L"screenshots/", paths.Data()/"screenshots/"); const OsPath readonlyConfig = paths.RData()/"config/"; g_VFS->Mount(L"config/", readonlyConfig); if(readonlyConfig != paths.Config()) g_VFS->Mount(L"config/", paths.Config()); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) std::vector mods = args.GetMultiple("mod"); mods.push_back("public"); if(!args.Has("onlyPublicFiles")) mods.push_back("internal"); OsPath modArchivePath = paths.Cache()/"mods"; OsPath modLoosePath = paths.RData()/"mods"; for (size_t i = 0; i < mods.size(); ++i) { size_t priority = i; size_t flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_MUST_EXIST; OsPath modName(mods[i]); g_VFS->Mount(L"", modLoosePath / modName/"", flags, priority); g_VFS->Mount(L"", modArchivePath / modName/"", flags, priority); } // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } static void InitPs(bool setup_gui, const CStrW& gui_page, CScriptVal initData) { { // console TIMER(L"ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font(CONSOLE_FONT); g_Console->m_iFontHeight = font.GetLineSpacing(); g_Console->m_iFontWidth = font.GetCharacterWidth(L'C'); g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 7; } // hotkeys { TIMER(L"ps_lang_hotkeys"); LoadHotkeys(); } if (!setup_gui) { // We do actually need *some* kind of GUI loaded, so use the // (currently empty) Atlas one g_GUI->SwitchPage(L"page_atlas.xml", initData); return; } // GUI uses VFS, so this must come after VFS init. g_GUI->SwitchPage(gui_page, initData); } static void InitInput() { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); g_Joystick.Initialise(); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(conInputHandler); in_add_handler(HotkeyInputHandler); // gui_handler needs to be registered after (i.e. called before!) the // hotkey handler so that input boxes can be typed in without // setting off hotkeys. in_add_handler(gui_handler); // must be registered after (called before) the GUI which relies on these globals in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y); } static void InitRenderer() { TIMER(L"InitRenderer"); if(g_NoGLS3TC) ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); if(g_NoGLAutoMipmap) ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_NOFRAMEBUFFEROBJECT,g_NoGLFramebufferObject); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_FANCYWATER,g_FancyWater); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTerrainTextureManager; // create the material manager new CMaterialManager; g_Renderer.Open(g_xres,g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; g_Renderer.SetViewport(vp); ColorActivateFastImpl(); } static void InitSDL() { if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOGERROR(L"SDL library initialization failed: %hs", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } static void ShutdownSDL() { SDL_Quit(); sys_cursor_reset(); } void EndGame() { SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } void Shutdown(int UNUSED(flags)) { EndGame(); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost in_reset_handlers(); // destroy actor related stuff TIMER_BEGIN(L"shutdown actor stuff"); delete &g_MaterialManager; TIMER_END(L"shutdown actor stuff"); // destroy terrain related stuff TIMER_BEGIN(L"shutdown TexMan"); delete &g_TexMan; TIMER_END(L"shutdown TexMan"); // destroy renderer TIMER_BEGIN(L"shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END(L"shutdown Renderer"); tex_codec_unregister_all(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); g_VideoMode.Shutdown(); TIMER_BEGIN(L"shutdown UserReporter"); g_UserReporter.Deinitialize(); TIMER_END(L"shutdown UserReporter"); TIMER_BEGIN(L"shutdown ScriptingHost"); delete &g_ScriptingHost; TIMER_END(L"shutdown ScriptingHost"); TIMER_BEGIN(L"shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END(L"shutdown ConfigDB"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN(L"resource modules"); snd_shutdown(); g_VFS.reset(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); file_stats_dump(); TIMER_END(L"resource modules"); TIMER_BEGIN(L"shutdown misc"); timer_DisplayClientTotals(); CNetHost::Deinitialize(); SAFE_DELETE(g_ScriptStatsTable); // should be last, since the above use them SAFE_DELETE(g_Logger); delete &g_Profiler; delete &g_ProfileViewer; TIMER_END(L"shutdown misc"); } #if OS_UNIX void SetDefaultIfLocaleInvalid() { // On misconfigured systems with incorrect locale settings, we'll die // with a C++ exception when some code tries to use locales. // To avoid death, we'll detect the problem here and warn the user and // reset to the default C locale. // For informing the user of the problem, use the list of env vars that // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.) const char* const LocaleEnvVars[] = { "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", "LANG" }; try { // this constructor is similar to setlocale(LC_ALL, ""), // but instead of returning NULL, it throws runtime_error // when the first locale env variable found contains an invalid value std::locale(""); } catch (std::runtime_error&) { LOGWARNING(L"Invalid locale settings"); for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++) { if (char* envval = getenv(LocaleEnvVars[i])) LOGWARNING(L" %hs=\"%hs\"", LocaleEnvVars[i], envval); else LOGWARNING(L" %hs=\"(unset)\"", LocaleEnvVars[i]); } // We should set LC_ALL since it overrides LANG if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1)) debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable."); else LOGWARNING(L"Setting LC_ALL env variable to: %hs", getenv("LC_ALL")); } } #else void SetDefaultIfLocaleInvalid() { // Do nothing on Windows } #endif void EarlyInit() { // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); ThreadUtil::SetMainThread(); debug_SetThreadName("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add(L"TIMER"); debug_filter_add(L"HRT"); cpu_ConfigureFloatingPoint(); timer_LatchStartTime(); SetDefaultIfLocaleInvalid(); // Because we do GL calls from a secondary thread, Xlib needs to // be told to support multiple threads safely. // This is needed for Atlas, but we have to call it before any other // Xlib functions (e.g. the ones used when drawing the main menu // before launching Atlas) #if MUST_INIT_X11 int status = XInitThreads(); if (status == 0) debug_printf(L"Error enabling thread-safety via XInitThreads\n"); #endif // Initialise the low-quality rand function srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } static bool Autostart(const CmdLineArgs& args); void Init(const CmdLineArgs& args, int UNUSED(flags)) { h_mgr_init(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. InitVfs(args); // This must come after VFS init, which sets the current directory // (required for finding our output log files). g_Logger = new CLogger; // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time // on anything else.) if (args.Has("dumpSchema")) { CSimulation2 sim(NULL, NULL); sim.LoadDefaultScripts(); std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc); f << sim.GenerateSchema(); std::cout << "Generated entity.rng\n"; exit(0); } // override ah_translate with our i18n code. AppHooks hooks = {0}; hooks.translate = psTranslate; hooks.translate_free = psTranslateFree; app_hooks_update(&hooks); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); CNetHost::Initialize(); new CProfileViewer; new CProfileManager; // before any script code g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CONFIG_Init(args); if (!g_Quickstart) g_UserReporter.Initialize(); // after config } void InitGraphics(const CmdLineArgs& args, int flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; if(setup_vmode) { InitSDL(); if (!g_VideoMode.InitSDL()) throw PSERROR_System_VmodeFailed(); // abort startup SDL_WM_SetCaption("0 A.D.", "0 A.D."); } // needed by ogl_tex to detect broken gfx card/driver combos, // but takes a while due to WMI startup, so make it optional. if(!g_Quickstart) gfx_detect(); RunHardwareDetection(); tex_codec_register_all(); const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file SetTextureQuality(quality); ogl_WarnIfError(); if(!g_Quickstart) { WriteSystemInfo(); // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } if(g_DisableAudio) { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); // (must come after SetVideoMode, since it calls ogl_Init) const char* missing = ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", NULL); if(missing) { wchar_t buf[500]; swprintf_s(buf, ARRAY_SIZE(buf), L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card.", missing ); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar")) { DEBUG_DISPLAY_ERROR( L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer." L" Shadows are not available and overall graphics quality might suffer." L" You are advised to try installing newer drivers and/or upgrade your graphics card."); g_Shadows = false; } ogl_WarnIfError(); InitRenderer(); InitInput(); ogl_WarnIfError(); if (!Autostart(args)) { const bool setup_gui = ((flags & INIT_NO_GUI) == 0); InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID); } } void RenderGui(bool RenderingState) { g_DoRenderGui = RenderingState; } void RenderLogger(bool RenderingState) { g_DoRenderLogger = RenderingState; } void RenderCursor(bool RenderingState) { g_DoRenderCursor = RenderingState; } static bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=mapname -- single-player * -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players * -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1 * -autostart=scriptname -autostart-random=104 -- random map, seed 104 (default is 0, for random choose -1) */ CStr autoStartName = args.Get("autostart"); if (autoStartName.empty()) return false; g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); CScriptValRooted attrs; scriptInterface.Eval("({})", attrs); CScriptVal settings; scriptInterface.Eval("({})", settings); CScriptVal playerData; scriptInterface.Eval("([])", playerData); // Set different attributes for random or scenario game if (args.Has("autostart-random")) { CStr seedArg = args.Get("autostart-random"); // Default seed is 0 uint32 seed = 0; if (!seedArg.empty()) { if (seedArg.compare("-1") == 0) { // Random seed value seed = rand(); } else { seed = seedArg.ToULong(); } } scriptInterface.SetProperty(attrs.get(), "script", std::string(autoStartName), false); // RMS name scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random"), false); // For random map, there are special settings // TODO: Get these from command line - using defaults for now scriptInterface.SetProperty(settings.get(), "Size", 12); // Random map size (in patches) scriptInterface.SetProperty(settings.get(), "Seed", seed); // Random seed scriptInterface.SetProperty(settings.get(), "BaseTerrain", std::string("grass1_spring")); // Base terrain texture scriptInterface.SetProperty(settings.get(), "BaseHeight", 0); // Base terrain height // Define players // TODO: Get these from command line? - using defaults for now size_t numPlayers = 2; for (size_t i = 0; i < numPlayers; ++i) { CScriptVal player; scriptInterface.Eval("({})", player); scriptInterface.SetProperty(player.get(), "Civ", std::string("hele")); scriptInterface.SetPropertyInt(playerData.get(), i, player); } } else { scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName), false); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false); } // Set player data for AIs // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }: /* * Handle command-line options for AI: * -autostart-ai=1:dummybot -autostart-ai=2:dummybot -- adds the dummybot AI to players 1 and 2 */ if (args.Has("autostart-ai")) { std::vector aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { CScriptVal player; scriptInterface.Eval("({})", player); int playerID = aiArgs[i].BeforeFirst(":").ToInt(); CStr name = aiArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player.get(), "AI", std::string(name)); scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } // Add player data to map settings scriptInterface.SetProperty(settings.get(), "PlayerData", playerData); // Add map settings to game attributes scriptInterface.SetProperty(attrs.get(), "settings", settings); CScriptVal mpInitData; g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData); g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs", CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false); if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", mpInitData.get()); size_t maxPlayers = 2; if (args.Has("autostart-players")) maxPlayers = args.Get("autostart-players").ToUInt(); g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); bool ok = g_NetServer->SetupConnection(); debug_assert(ok); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", mpInitData.get()); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc CStr ip = "127.0.0.1"; if (args.Has("autostart-ip")) ip = args.Get("autostart-ip"); bool ok = g_NetClient->SetupConnection(ip); debug_assert(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(attrs); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", JSVAL_VOID); } return true; } Index: ps/trunk/source/ps/Game.h =================================================================== --- ps/trunk/source/ps/Game.h (revision 9122) +++ ps/trunk/source/ps/Game.h (revision 9123) @@ -1,162 +1,172 @@ /* Copyright (C) 2011 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 . */ #ifndef INCLUDED_GAME #define INCLUDED_GAME #include "ps/Errors.h" #include #include "scriptinterface/ScriptVal.h" class CWorld; class CSimulation2; class CGameView; class CNetTurnManager; class IReplayLogger; struct CColor; /** * The container that holds the rules, resources and attributes of the game. * The CGame object is responsible for creating a game that is defined by * a set of attributes provided. The CGame object is also responsible for * maintaining the relations between CPlayer and CWorld, CSimulation and CWorld. **/ class CGame { NONCOPYABLE(CGame); /** * pointer to the CWorld object representing the game world. **/ CWorld *m_World; /** * pointer to the CSimulation2 object operating on the game world. **/ CSimulation2 *m_Simulation2; /** * pointer to the CGameView object representing the view into the game world. **/ CGameView *m_GameView; /** * the game has been initialized and ready for use if true. **/ bool m_GameStarted; /** * scale multiplier for simulation rate. **/ float m_SimRate; int m_PlayerID; CNetTurnManager* m_TurnManager; public: enum ENetStatus { NET_WAITING_FOR_CONNECT, /// we have loaded the game; waiting for other players to finish loading NET_NORMAL /// running the game }; CGame(bool disableGraphics = false); ~CGame(); /** * the game is paused and no updates will be performed if true. **/ bool m_Paused; void StartGame(const CScriptValRooted& attribs); PSRETURN ReallyStartGame(); /* Perform all per-frame updates */ bool Update(double deltaTime, bool doInterpolate = true); void Interpolate(float frameLength); int GetPlayerID(); void SetPlayerID(int playerID); + /** + * Retrieving player colours from scripts is slow, so this updates an + * internal cache of all players' colours. + * Call this just before rendering, so it will always have the latest + * colours. + */ + void CachePlayerColours(); + CColor GetPlayerColour(int player) const; /** * Get m_GameStarted. * * @return bool the value of m_GameStarted. **/ inline bool IsGameStarted() const { return m_GameStarted; } /** * Get the pointer to the game world object. * * @return CWorld * the value of m_World. **/ inline CWorld *GetWorld() { return m_World; } /** * Get the pointer to the game view object. * * @return CGameView * the value of m_GameView. **/ inline CGameView *GetView() { return m_GameView; } /** * Get the pointer to the simulation2 object. * * @return CSimulation2 * the value of m_Simulation2. **/ inline CSimulation2 *GetSimulation2() { return m_Simulation2; } /** * Set the simulation scale multiplier. * * @param simRate Float value to set m_SimRate to. * Because m_SimRate is also used to * scale TimeSinceLastFrame it must be * clamped to 0.0f. **/ inline void SetSimRate(float simRate) { m_SimRate = std::max(simRate, 0.0f); } /** * Replace the current turn manager. * This class will take ownership of the pointer. */ void SetTurnManager(CNetTurnManager* turnManager); CNetTurnManager* GetTurnManager() const { return m_TurnManager; } IReplayLogger& GetReplayLogger() const { return *m_ReplayLogger; } private: void RegisterInit(const CScriptValRooted& attribs); IReplayLogger* m_ReplayLogger; CScriptValRooted m_RegisteredAttribs; + + std::vector m_PlayerColours; }; extern CGame *g_Game; #endif Index: ps/trunk/source/ps/Game.cpp =================================================================== --- ps/trunk/source/ps/Game.cpp (revision 9122) +++ ps/trunk/source/ps/Game.cpp (revision 9123) @@ -1,292 +1,313 @@ /* Copyright (C) 2011 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 "Game.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/UnitManager.h" #include "lib/timer.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "network/NetTurnManager.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/Replay.h" #include "ps/World.h" #include "scripting/ScriptingHost.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "gui/GUIManager.h" extern bool g_GameRestarted; /** * Globally accessible pointer to the CGame object. **/ CGame *g_Game=NULL; /** * Constructor * **/ CGame::CGame(bool disableGraphics): m_World(new CWorld(this)), m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), m_World->GetTerrain())), m_GameView(disableGraphics ? NULL : new CGameView(this)), m_GameStarted(false), m_Paused(false), m_SimRate(1.0f), m_PlayerID(-1) { m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface()); // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps? // Need to set the CObjectManager references after various objects have // been initialised, so do it here rather than via the initialisers above. if (m_GameView) m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager()); m_TurnManager = new CNetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client m_Simulation2->LoadDefaultScripts(); } /** * Destructor * **/ CGame::~CGame() { // Clear rooted value before destroying its context m_RegisteredAttribs = CScriptValRooted(); // Again, the in-game call tree is going to be different to the main menu one. if (CProfileManager::IsInitialised()) g_Profiler.StructuralReset(); delete m_TurnManager; delete m_GameView; delete m_Simulation2; delete m_World; delete m_ReplayLogger; } void CGame::SetTurnManager(CNetTurnManager* turnManager) { if (m_TurnManager) delete m_TurnManager; m_TurnManager = turnManager; if (m_TurnManager) m_TurnManager->SetPlayerID(m_PlayerID); } /** * Initializes the game with the set of attributes provided. * Makes calls to initialize the game view, world, and simulation objects. * Calls are made to facilitate progress reporting of the initialization. **/ void CGame::RegisterInit(const CScriptValRooted& attribs) { m_RegisteredAttribs = attribs; // save the attributes for ReallyStartGame std::string mapType; m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "mapType", mapType); LDR_BeginRegistering(); RegMemFun(m_Simulation2, &CSimulation2::ProgressiveLoad, L"Simulation init", 1000); // RC, 040804 - GameView needs to be initialized before World, otherwise GameView initialization // overwrites anything stored in the map file that gets loaded by CWorld::Initialize with default // values. At the minute, it's just lighting settings, but could be extended to store camera position. // Storing lighting settings in the game view seems a little odd, but it's no big deal; maybe move it at // some point to be stored in the world object? if (m_GameView) m_GameView->RegisterInit(); if (mapType == "scenario") { // Load scenario attributes std::wstring mapFile; m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "map", mapFile); m_World->RegisterInit(mapFile, m_PlayerID); } else if (mapType == "random") { // Load random map attributes std::wstring scriptFile; CScriptValRooted settings; m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "script", scriptFile); m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "settings", settings); m_World->RegisterInitRMS(scriptFile, settings, m_PlayerID); } LDR_EndRegistering(); } /** * Game initialization has been completed. Set game started flag and start the session. * * @return PSRETURN 0 **/ PSRETURN CGame::ReallyStartGame() { CScriptVal settings; m_Simulation2->GetScriptInterface().GetProperty(m_RegisteredAttribs.get(), "settings", settings); m_Simulation2->InitGame(settings); // Call the reallyStartGame GUI function, but only if it exists if (g_GUI && g_GUI->HasPages()) { jsval fval, rval; JSBool ok = JS_GetProperty(g_ScriptingHost.getContext(), g_GUI->GetScriptObject(), "reallyStartGame", &fval); debug_assert(ok); if (ok && !JSVAL_IS_VOID(fval)) ok = JS_CallFunctionValue(g_ScriptingHost.getContext(), g_GUI->GetScriptObject(), fval, 0, NULL, &rval); } if (g_NetClient) g_NetClient->LoadFinished(); // We need to do an initial Interpolate call to set up all the models etc, // because Update might never interpolate (e.g. if the game starts paused) // and we could end up rendering before having set up any models (so they'd // all be invisible) Interpolate(0); debug_printf(L"GAME STARTED, ALL INIT COMPLETE\n"); m_GameStarted=true; // The call tree we've built for pregame probably isn't useful in-game. if (CProfileManager::IsInitialised()) g_Profiler.StructuralReset(); // Mark terrain as modified so the minimap can repaint (is there a cleaner way of handling this?) g_GameRestarted = true; return 0; } int CGame::GetPlayerID() { return m_PlayerID; } void CGame::SetPlayerID(int playerID) { m_PlayerID = playerID; if (m_TurnManager) m_TurnManager->SetPlayerID(m_PlayerID); } void CGame::StartGame(const CScriptValRooted& attribs) { m_ReplayLogger->StartGame(attribs); RegisterInit(attribs); } // TODO: doInterpolate is optional because Atlas interpolates explicitly, // so that it has more control over the update rate. The game might want to // do the same, and then doInterpolate should be redundant and removed. /** * Periodic heartbeat that controls the process. * Simulation update is called and game status update is called. * * @param deltaTime Double. Elapsed time since last beat in seconds. * @param doInterpolate Bool. Perform interpolation if true. * @return bool false if it can't keep up with the desired simulation rate * indicating that you might want to render less frequently. **/ bool CGame::Update(double deltaTime, bool doInterpolate) { if (m_Paused) return true; if (!m_TurnManager) return true; deltaTime *= m_SimRate; bool ok = true; if (deltaTime) { // To avoid confusing the profiler, we need to trigger the new turn // while we're not nested inside any PROFILE blocks if (m_TurnManager->WillUpdate(deltaTime)) g_Profiler.Turn(); // At the normal sim rate, we currently want to render at least one // frame per simulation turn, so let maxTurns be 1. But for fast-forward // sim rates we want to allow more, so it's not bounded by framerate, // so just use the sim rate itself as the number of turns per frame. size_t maxTurns = (size_t)m_SimRate; PROFILE("simulation update"); if (m_TurnManager->Update(deltaTime, maxTurns)) { g_GUI->SendEventToAll("SimulationUpdate"); GetView()->GetLOSTexture().MakeDirty(); } } if (doInterpolate) { PROFILE("interpolate"); m_TurnManager->Interpolate(deltaTime); } return ok; } void CGame::Interpolate(float frameLength) { if (!m_TurnManager) return; m_TurnManager->Interpolate(frameLength); } + static CColor BrokenColor(0.3f, 0.3f, 0.3f, 1.0f); -CColor CGame::GetPlayerColour(int player) const + +void CGame::CachePlayerColours() { + m_PlayerColours.clear(); + CmpPtr cmpPlayerManager(*m_Simulation2, SYSTEM_ENTITY); if (cmpPlayerManager.null()) + return; + + int numPlayers = cmpPlayerManager->GetNumPlayers(); + m_PlayerColours.resize(numPlayers); + + for (int i = 0; i < numPlayers; ++i) + { + CmpPtr cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(i)); + if (cmpPlayer.null()) + m_PlayerColours[i] = BrokenColor; + else + m_PlayerColours[i] = cmpPlayer->GetColour(); + } +} + + +CColor CGame::GetPlayerColour(int player) const +{ + if (player < 0 || player >= (int)m_PlayerColours.size()) return BrokenColor; - CmpPtr cmpPlayer(*m_Simulation2, cmpPlayerManager->GetPlayerByID(player)); - if (cmpPlayer.null()) - return BrokenColor; - return cmpPlayer->GetColour(); + + return m_PlayerColours[player]; } Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h (revision 9122) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h (revision 9123) @@ -1,84 +1,85 @@ /* 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 . */ #ifndef INCLUDED_SCENARIOEDITOR #define INCLUDED_SCENARIOEDITOR #include "General/AtlasWindowCommandProc.h" #include "General/Observable.h" #include "Tools/Common/ObjectSettings.h" #include "Tools/Common/Tools.h" #include "CustomControls/FileHistory/FileHistory.h" #include "SectionLayout.h" class ScriptInterface; class ScenarioEditor : public wxFrame { public: ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterface); void OnClose(wxCloseEvent& event); void OnTimer(wxTimerEvent& event); void OnIdle(wxIdleEvent& event); // void OnNew(wxCommandEvent& event); void OnOpen(wxCommandEvent& event); void OnSave(wxCommandEvent& event); void OnSaveAs(wxCommandEvent& event); void OnMRUFile(wxCommandEvent& event); void OnQuit(wxCommandEvent& event); void OnUndo(wxCommandEvent& event); void OnRedo(wxCommandEvent& event); void OnWireframe(wxCommandEvent& event); void OnMessageTrace(wxCommandEvent& event); void OnScreenshot(wxCommandEvent& event); void OnMediaPlayer(wxCommandEvent& event); void OnJavaScript(wxCommandEvent& event); void OnCameraReset(wxCommandEvent& event); + void OnRenderPath(wxCommandEvent& event); void OpenFile(const wxString& name); static AtlasWindowCommandProc& GetCommandProc(); static float GetSpeedModifier(); ScriptInterface& GetScriptInterface() const { return m_ScriptInterface; } ObjectSettings& GetObjectSettings() { return m_ObjectSettings; } ToolManager& GetToolManager() { return m_ToolManager; } private: ScriptInterface& m_ScriptInterface; ToolManager m_ToolManager; wxTimer m_Timer; SectionLayout m_SectionLayout; Observable m_ObjectSettings; void SetOpenFilename(const wxString& filename); wxString m_OpenFilename; FileHistory m_FileHistory; DECLARE_EVENT_TABLE(); }; #endif // INCLUDED_SCENARIOEDITOR Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp (revision 9122) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp (revision 9123) @@ -1,253 +1,262 @@ /* 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 "Environment.h" #include "LightControl.h" #include "GameInterface/Messages.h" #include "ScenarioEditor/ScenarioEditor.h" #include "General/Observable.h" #include "CustomControls/ColourDialog/ColourDialog.h" using AtlasMessage::Shareable; static Observable g_EnvironmentSettings; const float M_PIf = 3.14159265f; ////////////////////////////////////////////////////////////////////////// class VariableSliderBox : public wxPanel { static const int range = 1024; public: VariableSliderBox(wxWindow* parent, const wxString& label, Shareable& var, float min, float max) : wxPanel(parent), m_Var(var), m_Min(min), m_Max(max) { m_Conn = g_EnvironmentSettings.RegisterObserver(0, &VariableSliderBox::OnSettingsChange, this); m_Sizer = new wxStaticBoxSizer(wxVERTICAL, this, label); SetSizer(m_Sizer); m_Slider = new wxSlider(this, -1, 0, 0, range); m_Sizer->Add(m_Slider, wxSizerFlags().Expand()); } void OnSettingsChange(const AtlasMessage::sEnvironmentSettings& WXUNUSED(env)) { m_Slider->SetValue((m_Var - m_Min) * (range / (m_Max - m_Min))); } void OnScroll(wxScrollEvent& evt) { m_Var = m_Min + (m_Max - m_Min)*(evt.GetInt() / (float)range); g_EnvironmentSettings.NotifyObserversExcept(m_Conn); } private: ObservableScopedConnection m_Conn; wxStaticBoxSizer* m_Sizer; wxSlider* m_Slider; Shareable& m_Var; float m_Min, m_Max; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(VariableSliderBox, wxPanel) EVT_SCROLL(VariableSliderBox::OnScroll) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////////////// class VariableListBox : public wxPanel { public: VariableListBox(wxWindow* parent, const wxString& label, Shareable& var) : wxPanel(parent), m_Var(var) { m_Conn = g_EnvironmentSettings.RegisterObserver(0, &VariableListBox::OnSettingsChange, this); m_Sizer = new wxStaticBoxSizer(wxVERTICAL, this, label); SetSizer(m_Sizer); m_Combo = new wxComboBox(this, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxArrayString(), wxCB_READONLY), m_Sizer->Add(m_Combo, wxSizerFlags().Expand()); } void SetChoices(const std::vector& choices) { wxArrayString choices_arraystr; for (size_t i = 0; i < choices.size(); ++i) choices_arraystr.Add(choices[i].c_str()); m_Combo->Clear(); m_Combo->Append(choices_arraystr); m_Combo->SetValue(m_Var.c_str()); } void OnSettingsChange(const AtlasMessage::sEnvironmentSettings& WXUNUSED(env)) { m_Combo->SetValue(m_Var.c_str()); } void OnSelect(wxCommandEvent& WXUNUSED(evt)) { m_Var = std::wstring(m_Combo->GetValue().c_str()); g_EnvironmentSettings.NotifyObserversExcept(m_Conn); } private: ObservableScopedConnection m_Conn; wxStaticBoxSizer* m_Sizer; wxComboBox* m_Combo; Shareable& m_Var; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(VariableListBox, wxPanel) EVT_COMBOBOX(wxID_ANY, VariableListBox::OnSelect) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////////////// class VariableColourBox : public wxPanel { public: VariableColourBox(wxWindow* parent, const wxString& label, Shareable& colour) : wxPanel(parent), m_Colour(colour) { m_Conn = g_EnvironmentSettings.RegisterObserver(0, &VariableColourBox::OnSettingsChange, this); m_Sizer = new wxStaticBoxSizer(wxVERTICAL, this, label); SetSizer(m_Sizer); m_Button = new wxButton(this, -1); m_Sizer->Add(m_Button, wxSizerFlags().Expand()); } void OnSettingsChange(const AtlasMessage::sEnvironmentSettings& WXUNUSED(env)) { UpdateButton(); } void OnClick(wxCommandEvent& WXUNUSED(evt)) { ColourDialog dlg (this, _T("Scenario Editor/LightingColour"), wxColour(m_Colour->r, m_Colour->g, m_Colour->b)); if (dlg.ShowModal() == wxID_OK) { wxColour& c = dlg.GetColourData().GetColour(); m_Colour = AtlasMessage::Colour(c.Red(), c.Green(), c.Blue()); UpdateButton(); g_EnvironmentSettings.NotifyObserversExcept(m_Conn); } } void UpdateButton() { m_Button->SetBackgroundColour(wxColour(m_Colour->r, m_Colour->g, m_Colour->b)); m_Button->SetLabel(wxString::Format(_T("%02X %02X %02X"), m_Colour->r, m_Colour->g, m_Colour->b)); int y = 3*m_Colour->r + 6*m_Colour->g + 1*m_Colour->b; if (y > 1280) m_Button->SetForegroundColour(wxColour(0, 0, 0)); else m_Button->SetForegroundColour(wxColour(255, 255, 255)); } private: ObservableScopedConnection m_Conn; wxStaticBoxSizer* m_Sizer; wxButton* m_Button; Shareable& m_Colour; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(VariableColourBox, wxPanel) EVT_BUTTON(wxID_ANY, VariableColourBox::OnClick) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////////////// static void SendToGame(const AtlasMessage::sEnvironmentSettings& settings) { POST_COMMAND(SetEnvironmentSettings, (settings)); } EnvironmentSidebar::EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer) { wxSizer* waterSizer = new wxGridSizer(2); m_MainSizer->Add(waterSizer, wxSizerFlags().Expand()); waterSizer->Add(new VariableSliderBox(this, _("Water height"), g_EnvironmentSettings.waterheight, 0.f, 1.2f), wxSizerFlags().Expand()); waterSizer->Add(new VariableSliderBox(this, _("Water shininess"), g_EnvironmentSettings.watershininess, 0.f, 250.f), wxSizerFlags().Expand()); waterSizer->Add(new VariableSliderBox(this, _("Water waviness"), g_EnvironmentSettings.waterwaviness, 0.f, 10.f), wxSizerFlags().Expand()); waterSizer->Add(new VariableSliderBox(this, _("Water murkiness"), g_EnvironmentSettings.watermurkiness, 0.f, 1.f), wxSizerFlags().Expand()); waterSizer->Add(new VariableColourBox(this, _("Water colour"), g_EnvironmentSettings.watercolour), wxSizerFlags().Expand()); waterSizer->Add(new VariableColourBox(this, _("Water tint"), g_EnvironmentSettings.watertint), wxSizerFlags().Expand()); waterSizer->Add(new VariableColourBox(this, _("Reflection tint"), g_EnvironmentSettings.waterreflectiontint), wxSizerFlags().Expand()); waterSizer->Add(new VariableSliderBox(this, _("Refl. tint strength"), g_EnvironmentSettings.waterreflectiontintstrength, 0.f, 1.f), wxSizerFlags().Expand()); wxSizer* sunSizer = new wxGridSizer(2); m_MainSizer->Add(sunSizer, wxSizerFlags().Expand().Border(wxTOP, 8)); sunSizer->Add(new VariableSliderBox(this, _("Sun rotation"), g_EnvironmentSettings.sunrotation, -M_PIf, M_PIf), wxSizerFlags().Expand()); sunSizer->Add(new VariableSliderBox(this, _("Sun elevation"), g_EnvironmentSettings.sunelevation, -M_PIf/2, M_PIf/2), wxSizerFlags().Expand()); sunSizer->Add(new VariableSliderBox(this, _("Sun overbrightness"), g_EnvironmentSettings.sunoverbrightness, 1.0f, 3.0f), wxSizerFlags().Expand()); + sunSizer->Add(m_LightingModelList = new VariableListBox(this, _("Light model"), g_EnvironmentSettings.lightingmodel), wxSizerFlags().Expand()); + m_MainSizer->Add(new LightControl(this, wxSize(150, 150), g_EnvironmentSettings)); m_MainSizer->Add(m_SkyList = new VariableListBox(this, _("Sky set"), g_EnvironmentSettings.skyset)); m_MainSizer->Add(new VariableColourBox(this, _("Sun colour"), g_EnvironmentSettings.suncolour)); m_MainSizer->Add(new VariableColourBox(this, _("Terrain ambient colour"), g_EnvironmentSettings.terraincolour)); m_MainSizer->Add(new VariableColourBox(this, _("Object ambient colour"), g_EnvironmentSettings.unitcolour)); m_Conn = g_EnvironmentSettings.RegisterObserver(0, &SendToGame); } void EnvironmentSidebar::OnFirstDisplay() { // Load the list of skies. (Can only be done now rather than in the constructor, // after the game has been initialised.) AtlasMessage::qGetSkySets qry_skysets; qry_skysets.Post(); m_SkyList->SetChoices(*qry_skysets.skysets); AtlasMessage::qGetEnvironmentSettings qry_env; qry_env.Post(); g_EnvironmentSettings = qry_env.settings; + + std::vector lightingModels; + lightingModels.push_back(L"old"); + lightingModels.push_back(L"standard"); + m_LightingModelList->SetChoices(lightingModels); + + g_EnvironmentSettings.NotifyObservers(); // TODO: reupdate everything when loading a new map... } Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h (revision 9122) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h (revision 9123) @@ -1,35 +1,36 @@ /* 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 "../Common/Sidebar.h" #include "General/Observable.h" class VariableListBox; class EnvironmentSidebar : public Sidebar { public: EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer); protected: virtual void OnFirstDisplay(); private: + VariableListBox* m_LightingModelList; VariableListBox* m_SkyList; ObservableScopedConnection m_Conn; }; Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp (revision 9122) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp (revision 9123) @@ -1,874 +1,902 @@ /* Copyright (C) 2010 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 "ScenarioEditor.h" #include "wx/busyinfo.h" #include "wx/config.h" #include "wx/evtloop.h" #include "wx/ffile.h" #include "wx/filename.h" #include "wx/image.h" #include "wx/tooltip.h" #include "General/AtlasEventLoop.h" #include "General/Datafile.h" #include "CustomControls/HighResTimer/HighResTimer.h" #include "CustomControls/Buttons/ToolButton.h" #include "CustomControls/Canvas/Canvas.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" #include "AtlasScript/ScriptInterface.h" #include "Misc/KeyMap.h" #include "Tools/Common/Tools.h" #include "Tools/Common/Brushes.h" #include "Tools/Common/MiscState.h" static HighResTimer g_Timer; using namespace AtlasMessage; ////////////////////////////////////////////////////////////////////////// // GL functions exported from DLL, and called by game (in a separate // thread to the standard wx one) ATLASDLLIMPEXP void Atlas_GLSetCurrent(void* canvas) { static_cast(canvas)->SetCurrent(); } ATLASDLLIMPEXP void Atlas_GLSwapBuffers(void* canvas) { static_cast(canvas)->SwapBuffers(); } ////////////////////////////////////////////////////////////////////////// class GameCanvas : public Canvas { public: GameCanvas(ScenarioEditor& scenarioEditor, wxWindow* parent, int* attribList) : Canvas(parent, attribList, wxWANTS_CHARS), m_ScenarioEditor(scenarioEditor), m_MouseState(NONE), m_LastMouseState(NONE) { } private: bool KeyScroll(wxKeyEvent& evt, bool enable) { int dir; switch (evt.GetKeyCode()) { case 'A': case WXK_LEFT: dir = eScrollConstantDir::LEFT; break; case 'D': case WXK_RIGHT: dir = eScrollConstantDir::RIGHT; break; case 'W': case WXK_UP: dir = eScrollConstantDir::FORWARDS; break; case 'S': case WXK_DOWN: dir = eScrollConstantDir::BACKWARDS; break; case 'E': case ']': dir = eScrollConstantDir::CLOCKWISE; break; case 'Q': case '[': dir = eScrollConstantDir::ANTICLOCKWISE; break; case WXK_SHIFT: case WXK_CONTROL: dir = -1; break; default: return false; } float speed = 120.f * ScenarioEditor::GetSpeedModifier(); if (dir == -1) // changed modifier keys - update all currently-scrolling directions { if (wxGetKeyState(WXK_LEFT)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, speed)); if (wxGetKeyState(WXK_RIGHT)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, speed)); if (wxGetKeyState(WXK_UP)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, speed)); if (wxGetKeyState(WXK_DOWN)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, speed)); if (wxGetKeyState((wxKeyCode)']')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, speed)); if (wxGetKeyState((wxKeyCode)'[')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, speed)); return false; } else { POST_MESSAGE(ScrollConstant, (eRenderView::GAME, dir, enable ? speed : 0.0f)); return true; } } void OnKeyDown(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool().OnKey(evt, ITool::KEY_DOWN)) { // Key event has been handled by the tool, so don't try // to use it for camera motion too return; } if (KeyScroll(evt, true)) return; // Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead if (evt.GetKeyCode() >= 256) POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), true)); evt.Skip(); } void OnKeyUp(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool().OnKey(evt, ITool::KEY_UP)) return; if (KeyScroll(evt, false)) return; // Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead if (evt.GetKeyCode() >= 256) POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), false)); evt.Skip(); } void OnChar(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool().OnKey(evt, ITool::KEY_CHAR)) return; // Alt+enter toggles fullscreen if (evt.GetKeyCode() == WXK_RETURN && wxGetKeyState(WXK_ALT)) { if (m_ScenarioEditor.IsFullScreen()) m_ScenarioEditor.ShowFullScreen(false); else m_ScenarioEditor.ShowFullScreen(true, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); return; } if (evt.GetKeyCode() == 'c') { POST_MESSAGE(CameraReset, ()); return; } int dir = 0; if (evt.GetKeyCode() == '-' || evt.GetKeyCode() == '_') dir = -1; else if (evt.GetKeyCode() == '+' || evt.GetKeyCode() == '=') dir = +1; // TODO: internationalisation (-/_ and +/= don't always share a key) if (dir) { float speed = 16.f * ScenarioEditor::GetSpeedModifier(); POST_MESSAGE(SmoothZoom, (eRenderView::GAME, speed*dir)); } else { // Slight hack: Only pass 'normal' keys; special keys will generate a KeyDown/KeyUp event instead if (evt.GetKeyCode() < 256) POST_MESSAGE(GuiCharEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey())); evt.Skip(); } } void OnKillFocus(wxFocusEvent& evt) { // Stop any scrolling, since otherwise we'll carry on forever if // we lose focus and the KeyUp events go to a different window POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, 0.0f)); evt.Skip(); } virtual void HandleMouseEvent(wxMouseEvent& evt) { // TODO or at least to think about: When using other controls in the // editor, it's annoying that keyboard/scrollwheel no longer navigate // around the world until you click on it. // Setting focus back whenever the mouse moves over the GL window // feels like a fairly natural solution to me, since I can use // e.g. brush-editing controls normally, and then move the mouse to // see the brush outline and magically get given back full control // of the camera. if (evt.Moving()) SetFocus(); if (m_ScenarioEditor.GetToolManager().GetCurrentTool().OnMouse(evt)) { // Mouse event has been handled by the tool, so don't try // to use it for camera motion too return; } // Global mouse event handlers (for camera motion) if (evt.GetWheelRotation()) { float speed = 16.f * ScenarioEditor::GetSpeedModifier(); POST_MESSAGE(SmoothZoom, (eRenderView::GAME, evt.GetWheelRotation() * speed / evt.GetWheelDelta())); } else { if (evt.MiddleIsDown()) { if (wxGetKeyState(WXK_CONTROL) || evt.RightIsDown()) m_MouseState = ROTATEAROUND; else m_MouseState = SCROLL; } else m_MouseState = NONE; if (m_MouseState != m_LastMouseState) { switch (m_MouseState) { case NONE: break; case SCROLL: POST_MESSAGE(Scroll, (eRenderView::GAME, eScrollType::FROM, evt.GetPosition())); break; case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::FROM, evt.GetPosition())); break; default: wxFAIL; } m_LastMouseState = m_MouseState; } else if (evt.Dragging()) { switch (m_MouseState) { case NONE: break; case SCROLL: POST_MESSAGE(Scroll, (eRenderView::GAME, eScrollType::TO, evt.GetPosition())); break; case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::TO, evt.GetPosition())); break; default: wxFAIL; } } } if (evt.ButtonDown()) POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), true, evt.GetPosition())); else if (evt.ButtonUp()) POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), false, evt.GetPosition())); else if (evt.GetEventType() == wxEVT_MOTION) POST_MESSAGE(GuiMouseMotionEvent, (evt.GetPosition())); } enum { NONE, SCROLL, ROTATEAROUND }; int m_MouseState, m_LastMouseState; ScenarioEditor& m_ScenarioEditor; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(GameCanvas, Canvas) EVT_KEY_DOWN(GameCanvas::OnKeyDown) EVT_KEY_UP(GameCanvas::OnKeyUp) EVT_CHAR(GameCanvas::OnChar) EVT_KILL_FOCUS(GameCanvas::OnKillFocus) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////////////// volatile bool g_FrameHasEnded; // Called from game thread ATLASDLLIMPEXP void Atlas_NotifyEndOfFrame() { g_FrameHasEnded = true; } enum { ID_Quit = 1, // ID_New, ID_Open, ID_Save, ID_SaveAs, ID_Wireframe, ID_MessageTrace, ID_Screenshot, ID_JavaScript, ID_CameraReset, + ID_RenderPathFixed, + ID_RenderPathVertexShader, + ID_RenderPathShader, ID_Toolbar // must be last in the list }; BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame) EVT_CLOSE(ScenarioEditor::OnClose) EVT_TIMER(wxID_ANY, ScenarioEditor::OnTimer) // EVT_MENU(ID_New, ScenarioEditor::OnNew) EVT_MENU(ID_Open, ScenarioEditor::OnOpen) EVT_MENU(ID_Save, ScenarioEditor::OnSave) EVT_MENU(ID_SaveAs, ScenarioEditor::OnSaveAs) EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, ScenarioEditor::OnMRUFile) EVT_MENU(ID_Quit, ScenarioEditor::OnQuit) EVT_MENU(wxID_UNDO, ScenarioEditor::OnUndo) EVT_MENU(wxID_REDO, ScenarioEditor::OnRedo) EVT_MENU(ID_Wireframe, ScenarioEditor::OnWireframe) EVT_MENU(ID_MessageTrace, ScenarioEditor::OnMessageTrace) EVT_MENU(ID_Screenshot, ScenarioEditor::OnScreenshot) EVT_MENU(ID_JavaScript, ScenarioEditor::OnJavaScript) EVT_MENU(ID_CameraReset, ScenarioEditor::OnCameraReset) + EVT_MENU(ID_RenderPathFixed, ScenarioEditor::OnRenderPath) + EVT_MENU(ID_RenderPathVertexShader, ScenarioEditor::OnRenderPath) + EVT_MENU(ID_RenderPathShader, ScenarioEditor::OnRenderPath) EVT_IDLE(ScenarioEditor::OnIdle) END_EVENT_TABLE() static AtlasWindowCommandProc g_CommandProc; AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; } namespace { // Wrapper functions for scripts void SetCurrentTool_(void* cbdata, wxString name) { static_cast(cbdata)->GetToolManager().SetCurrentTool(name); } void SetCurrentToolWith(void* cbdata, wxString name, wxString arg) { static_cast(cbdata)->GetToolManager().SetCurrentTool(name, &arg); } void SetCurrentToolWithVal(void* cbdata, wxString name, CScriptVal arg) { jsval tool = arg.get(); static_cast(cbdata)->GetToolManager().SetCurrentTool(name, &tool); } wxString GetDataDirectory(void*) { return Datafile::GetDataDirectory(); } // TODO: see comment in terrain.js, and remove this when/if it's no longer necessary void SetBrushStrength(void*, float strength) { g_Brush_Elevation.SetStrength(strength); } void SetSelectedTexture(void*, wxString name) { g_SelectedTexture = name; } } ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterface) : wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(1024, 768)) , m_FileHistory(_T("Scenario Editor")), m_ScriptInterface(scriptInterface) , m_ObjectSettings(g_SelectedObjects, m_ScriptInterface) , m_ToolManager(this) { // Global application initialisation: // wxLog::SetTraceMask(wxTraceMessages); SetOpenFilename(_T("")); #if defined(__WXMSW__) SetIcon(wxIcon(_T("ICON_ScenarioEditor"))); // load from atlas.rc #else { const wxString relativePath (_T("tools/atlas/icons/ScenarioEditor.ico")); wxFileName filename (relativePath, wxPATH_UNIX); filename.MakeAbsolute(Datafile::GetDataDirectory()); SetIcon(wxIcon(filename.GetFullPath())); } #endif wxToolTip::Enable(true); wxImage::AddHandler(new wxPNGHandler); ////////////////////////////////////////////////////////////////////////// // Script interface functions GetScriptInterface().SetCallbackData(static_cast(this)); GetScriptInterface().RegisterFunction("GetDataDirectory"); GetScriptInterface().RegisterFunction("SetCurrentTool"); GetScriptInterface().RegisterFunction("SetCurrentToolWith"); GetScriptInterface().RegisterFunction("SetCurrentToolWithVal"); GetScriptInterface().RegisterFunction("SetBrushStrength"); GetScriptInterface().RegisterFunction("SetSelectedTexture"); { const wxString relativePath (_T("tools/atlas/scripts/main.js")); wxFileName filename (relativePath, wxPATH_UNIX); filename.MakeAbsolute(Datafile::GetDataDirectory()); wxFFile file (filename.GetFullPath()); wxString script; if (! file.ReadAll(&script)) wxLogError(_("Failed to read script")); GetScriptInterface().LoadScript(filename.GetFullName(), script); } // Initialise things that rely on scripts m_ObjectSettings.Init(AtlasMessage::eRenderView::GAME); ////////////////////////////////////////////////////////////////////////// // Do some early game initialisation: // (This must happen before constructing the GL canvas.) POST_MESSAGE(Init, ()); // Wait for it to finish running Init qPing qry; qry.Post(); ////////////////////////////////////////////////////////////////////////// // Menu wxMenuBar* menuBar = new wxMenuBar; SetMenuBar(menuBar); wxMenu *menuFile = new wxMenu; menuBar->Append(menuFile, _("&File")); { // menuFile->Append(ID_New, _("&New")); menuFile->Append(ID_Open, _("&Open...")); menuFile->Append(ID_Save, _("&Save")); menuFile->Append(ID_SaveAs, _("Save &As...")); menuFile->AppendSeparator();//----------- menuFile->Append(ID_Quit, _("E&xit")); m_FileHistory.UseMenu(menuFile);//------- m_FileHistory.AddFilesToMenu(); } // m_menuItem_Save = menuFile->FindItem(ID_Save); // remember this item, to let it be greyed out // wxASSERT(m_menuItem_Save); wxMenu *menuEdit = new wxMenu; menuBar->Append(menuEdit, _("&Edit")); { menuEdit->Append(wxID_UNDO, _("&Undo")); menuEdit->Append(wxID_REDO, _("&Redo")); } GetCommandProc().SetEditMenu(menuEdit); GetCommandProc().Initialize(); wxMenu *menuMisc = new wxMenu; menuBar->Append(menuMisc, _("&Misc hacks")); { menuMisc->AppendCheckItem(ID_Wireframe, _("&Wireframe")); menuMisc->AppendCheckItem(ID_MessageTrace, _("Message debug trace")); menuMisc->Append(ID_Screenshot, _("&Screenshot")); menuMisc->Append(ID_JavaScript, _("&JS console")); menuMisc->Append(ID_CameraReset, _("&Reset camera")); + + wxMenu *menuRP = new wxMenu; + menuMisc->AppendSubMenu(menuRP, _("Render &path")); + menuRP->Append(ID_RenderPathFixed, _("&Fixed function")); + menuRP->Append(ID_RenderPathVertexShader, _("&Vertex shader (old)")); + menuRP->Append(ID_RenderPathShader, _("&Shader (new)")); } m_FileHistory.Load(*wxConfigBase::Get()); m_SectionLayout.SetWindow(this); // Toolbar: ToolButtonBar* toolbar = new ToolButtonBar(m_ToolManager, this, &m_SectionLayout, ID_Toolbar); // TODO: configurable small vs large icon images // (button label; tooltip text; image; internal tool name; section to switch to) toolbar->AddToolButton(_("Default"), _("Default"), _T("default.png"), _T(""), _T("")); toolbar->AddToolButton(_("Move"), _("Move/rotate object"), _T("moveobject.png"), _T("TransformObject"), _T("")/*_T("ObjectSidebar")*/); toolbar->AddToolButton(_("Elevation"), _("Alter terrain elevation"), _T("alterelevation.png"), _T("AlterElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Smooth"), _("Smooth terrain elevation"), _T("smoothelevation.png"), _T("SmoothElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Flatten"), _("Flatten terrain elevation"), _T("flattenelevation.png"), _T("FlattenElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Paint Terrain"), _("Paint terrain texture"), _T("paintterrain.png"), _T("PaintTerrain"), _T("")/*_T("TerrainSidebar")*/); toolbar->Realize(); SetToolBar(toolbar); // Set the default tool to be selected m_ToolManager.SetCurrentTool(_T("")); // Set up GL canvas: int glAttribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, // TODO: wx documentation doesn't say 24 is valid WX_GL_STENCIL_SIZE, 8, WX_GL_BUFFER_SIZE, 24, // colour bits WX_GL_MIN_ALPHA, 8, // alpha bits 0 }; Canvas* canvas = new GameCanvas(*this, m_SectionLayout.GetCanvasParent(), glAttribList); m_SectionLayout.SetCanvas(canvas); // Set up sidebars: m_SectionLayout.Build(*this); #if defined(__WXMSW__) // The canvas' context gets made current on creation; but it can only be // current for one thread at a time, and it needs to be current for the // thread that is doing the draw calls, so disable it for this one. wglMakeCurrent(NULL, NULL); #elif defined(__WXGTK__) // Need to make sure the canvas is realized by GTK, so that its context is valid Show(true); wxSafeYield(); #endif // Send setup messages to game engine: POST_MESSAGE(SetCanvas, (static_cast(canvas))); POST_MESSAGE(InitGraphics, ()); canvas->InitSize(); // Start with a blank map (so that the editor can assume there's always // a valid map loaded) POST_MESSAGE(GenerateMap, (9)); POST_MESSAGE(RenderEnable, (eRenderView::GAME)); // Set up a timer to make sure tool-updates happen frequently (in addition // to the idle handler (which makes them happen more frequently if there's nothing // else to do)) m_Timer.SetOwner(this); m_Timer.Start(20); #ifdef __WXGTK__ // HACK: because of how we fiddle with stuff earlier to make sure the canvas // is displayed, the layout gets messed up, and it only seems to be fixable // by changing the window's size SetSize(GetSize() + wxSize(1, 0)); #endif } float ScenarioEditor::GetSpeedModifier() { if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL)) return 1.f/64.f; else if (wxGetKeyState(WXK_CONTROL)) return 1.f/4.f; else if (wxGetKeyState(WXK_SHIFT)) return 4.f; else return 1.f; } void ScenarioEditor::OnClose(wxCloseEvent&) { m_ToolManager.SetCurrentTool(_T("")); m_FileHistory.Save(*wxConfigBase::Get()); POST_MESSAGE(Shutdown, ()); qExit().Post(); // blocks until engine has noticed the message, so we won't be // destroying the GLCanvas while it's still rendering Destroy(); } static void UpdateTool(ToolManager& toolManager) { // Don't keep posting events if the game can't keep up if (g_FrameHasEnded) { g_FrameHasEnded = false; // (thread safety doesn't matter here) // TODO: Smoother timing stuff? static double last = g_Timer.GetTime(); double time = g_Timer.GetTime(); toolManager.GetCurrentTool().OnTick(time-last); last = time; } } void ScenarioEditor::OnTimer(wxTimerEvent&) { UpdateTool(m_ToolManager); } void ScenarioEditor::OnIdle(wxIdleEvent&) { UpdateTool(m_ToolManager); } void ScenarioEditor::OnQuit(wxCommandEvent&) { Close(); } void ScenarioEditor::OnUndo(wxCommandEvent&) { GetCommandProc().Undo(); } void ScenarioEditor::OnRedo(wxCommandEvent&) { GetCommandProc().Redo(); } ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OpenFile(const wxString& name) { wxBusyInfo busy(_("Loading map")); wxBusyCursor busyc; // TODO: Work when the map is not in .../maps/scenarios/ std::wstring map = name.c_str(); // Deactivate tools, so they don't carry forwards into the new CWorld // and crash. m_ToolManager.SetCurrentTool(_T("")); // TODO: clear the undo buffer, etc POST_MESSAGE(LoadMap, (map)); SetOpenFilename(name); // Wait for it to load, while the wxBusyInfo is telling the user that we're doing that qPing qry; qry.Post(); // TODO: Make this a non-undoable command } // TODO (eventually): replace all this file-handling stuff with the Workspace Editor void ScenarioEditor::OnOpen(wxCommandEvent& WXUNUSED(event)) { wxFileDialog dlg (NULL, wxFileSelectorPromptStr, Datafile::GetDataDirectory() + _T("/mods/public/maps/scenarios"), m_OpenFilename, _T("PMP files (*.pmp)|*.pmp|All files (*.*)|*.*"), wxOPEN); wxString cwd = wxFileName::GetCwd(); if (dlg.ShowModal() == wxID_OK) OpenFile(dlg.GetFilename()); wxCHECK_RET(cwd == wxFileName::GetCwd(), _T("cwd changed")); // paranoia - MSDN says OFN_NOCHANGEDIR (used when we don't give wxCHANGE_DIR) // "is ineffective for GetOpenFileName", but it seems to work anyway // TODO: Make this a non-undoable command } void ScenarioEditor::OnMRUFile(wxCommandEvent& event) { wxString file (m_FileHistory.GetHistoryFile(event.GetId() - wxID_FILE1)); if (file.Len()) OpenFile(file); } void ScenarioEditor::OnSave(wxCommandEvent& event) { if (m_OpenFilename.IsEmpty()) OnSaveAs(event); else { wxBusyInfo busy(_("Saving map")); // Deactivate tools, so things like unit previews don't get saved. // (TODO: Would be nicer to leave the tools active, and just not save // the preview units.) m_ToolManager.SetCurrentTool(_T("")); std::wstring map = m_OpenFilename.c_str(); POST_MESSAGE(SaveMap, (map)); // Wait for it to finish saving qPing qry; qry.Post(); } } void ScenarioEditor::OnSaveAs(wxCommandEvent& WXUNUSED(event)) { wxFileDialog dlg (NULL, wxFileSelectorPromptStr, Datafile::GetDataDirectory() + _T("/mods/public/maps/scenarios"), m_OpenFilename, _T("PMP files (*.pmp)|*.pmp|All files (*.*)|*.*"), wxSAVE | wxOVERWRITE_PROMPT); if (dlg.ShowModal() == wxID_OK) { wxBusyInfo busy(_("Saving map")); m_ToolManager.SetCurrentTool(_T("")); // TODO: Work when the map is not in .../maps/scenarios/ std::wstring map = dlg.GetFilename().c_str(); POST_MESSAGE(SaveMap, (map)); SetOpenFilename(dlg.GetFilename()); // Wait for it to finish saving qPing qry; qry.Post(); } } void ScenarioEditor::SetOpenFilename(const wxString& filename) { SetTitle(wxString::Format(_("Atlas - Scenario Editor - %s"), (filename.IsEmpty() ? wxString(_("(untitled)")) : filename).c_str())); m_OpenFilename = filename; if (! filename.IsEmpty()) m_FileHistory.AddFileToHistory(filename); } ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OnWireframe(wxCommandEvent& event) { POST_MESSAGE(RenderStyle, (event.IsChecked())); } void ScenarioEditor::OnMessageTrace(wxCommandEvent& event) { POST_MESSAGE(MessageTrace, (event.IsChecked())); } void ScenarioEditor::OnScreenshot(wxCommandEvent& WXUNUSED(event)) { POST_MESSAGE(Screenshot, (10)); } void ScenarioEditor::OnJavaScript(wxCommandEvent& WXUNUSED(event)) { wxString cmd = ::wxGetTextFromUser(_T(""), _("JS command"), _T(""), this); if (cmd.IsEmpty()) return; POST_MESSAGE(JavaScript, (cmd.c_str())); } void ScenarioEditor::OnCameraReset(wxCommandEvent& WXUNUSED(event)) { POST_MESSAGE(CameraReset, ()); } +void ScenarioEditor::OnRenderPath(wxCommandEvent& event) +{ + switch (event.GetId()) + { + case ID_RenderPathFixed: + POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"fixed")); + break; + case ID_RenderPathVertexShader: + POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"vertexshader")); + break; + case ID_RenderPathShader: + POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"shader")); + break; + } +} + ////////////////////////////////////////////////////////////////////////// Position::Position(const wxPoint& pt) : type(1) { type1.x = pt.x; type1.y = pt.y; } ////////////////////////////////////////////////////////////////////////// /* Disabled (and should be removed if it turns out to be unnecessary) - see MessagePasserImpl.cpp for information static void QueryCallback() { // If this thread completely blocked on the semaphore inside Query, it would // never respond to window messages, and the system deadlocks if the // game tries to display an assertion failure dialog. (See // WaitForSingleObject on MSDN.) // So, this callback is called occasionally, and gives wx a change to // handle messages. // This is kind of like wxYield, but without the ProcessPendingEvents - // it's enough to make Windows happy and stop deadlocking, without actually // calling the event handlers (which could lead to nasty recursion) // while (wxEventLoop::GetActive()->Pending()) // wxEventLoop::GetActive()->Dispatch(); // Oh dear, we can't use that either - it (at least in wx 2.6.3) still // processes messages, which causes reentry into various things that we // don't want to be reentrant. So do it all manually, accepting Windows // messages and sticking them on a list for later processing (in a custom // event loop class): // (TODO: Rethink this entire process on Linux) // (Alt TODO: Could we make the game never pop up windows (or use the Win32 // GUI in any other way) when it's running under Atlas, so we wouldn't need // to do any message processing here at all?) #ifdef _WIN32 AtlasEventLoop* evtLoop = (AtlasEventLoop*)wxEventLoop::GetActive(); // evtLoop might be NULL, particularly if we're still initialising windows // and haven't got into the normal event loop yet. But we'd have to process // messages anyway, to avoid the deadlocks that this is for. So, don't bother // with that and just crash instead. // (Maybe it could be solved better by constructing/finding an event loop // object here and setting it as the global one, assuming it's not overwritten // later by wx.) while (evtLoop->Pending()) { // Based on src/msw/evtloop.cpp's wxEventLoop::Dispatch() MSG msg; BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0); if (rc == 0) { // got WM_QUIT return; } if (rc == -1) { wxLogLastError(wxT("GetMessage")); return; } // Our special bits: if (msg.message == WM_PAINT) { // "GetMessage does not remove WM_PAINT messages from the queue. // The messages remain in the queue until processed." // So let's process them, to avoid infinite loops... PAINTSTRUCT paint; ::BeginPaint(msg.hwnd, &paint); ::EndPaint(msg.hwnd, &paint); // Remember that some painting was needed - we'll just repaint // the whole screen when this is finished. evtLoop->NeedsPaint(); } else { // Add this message to a queue for later processing. (That's // probably kind of valid, at least in most cases.) MSG* pMsg = new MSG(msg); evtLoop->AddMessage(pMsg); } } #endif } */ void QueryMessage::Post() { // g_MessagePasser->Query(this, &QueryCallback); g_MessagePasser->Query(this, NULL); } Index: ps/trunk/source/tools/atlas/GameInterface/View.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 9122) +++ ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 9123) @@ -1,379 +1,383 @@ /* Copyright (C) 2010 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 "View.h" #include "ActorViewer.h" #include "GameLoop.h" #include "Messages.h" #include "SimState.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/SColor.h" #include "graphics/UnitManager.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/Game.h" #include "ps/GameSetup/GameSetup.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "simulation/Simulation.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpPathfinder.h" extern void (*Atlas_GLSwapBuffers)(void* context); extern int g_xres, g_yres; ////////////////////////////////////////////////////////////////////////// void View::SetParam(const std::wstring& UNUSED(name), bool UNUSED(value)) { } void View::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Colour& UNUSED(value)) { } void View::SetParam(const std::wstring& UNUSED(name), const std::wstring& UNUSED(value)) { } ////////////////////////////////////////////////////////////////////////// ViewActor::ViewActor() : m_SpeedMultiplier(1.f), m_ActorViewer(new ActorViewer()) { } ViewActor::~ViewActor() { delete m_ActorViewer; } void ViewActor::Update(float frameLength) { m_ActorViewer->Update(frameLength * m_SpeedMultiplier); } void ViewActor::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); camera.UpdateFrustum(); m_ActorViewer->Render(); Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas); } CCamera& ViewActor::GetCamera() { return m_Camera; } CSimulation2* ViewActor::GetSimulation2() { return m_ActorViewer->GetSimulation2(); } entity_id_t ViewActor::GetEntityId(AtlasMessage::ObjectID UNUSED(obj)) { return m_ActorViewer->GetEntity(); } bool ViewActor::WantsHighFramerate() { if (m_SpeedMultiplier != 0.f) return true; return false; } void ViewActor::SetSpeedMultiplier(float speed) { m_SpeedMultiplier = speed; } ActorViewer& ViewActor::GetActorViewer() { return *m_ActorViewer; } void ViewActor::SetParam(const std::wstring& name, bool value) { if (name == L"wireframe") g_Renderer.SetModelRenderMode(value ? WIREFRAME : SOLID); else if (name == L"walk") m_ActorViewer->SetWalkEnabled(value); else if (name == L"ground") m_ActorViewer->SetGroundEnabled(value); else if (name == L"shadows") m_ActorViewer->SetShadowsEnabled(value); else if (name == L"stats") m_ActorViewer->SetStatsEnabled(value); } void ViewActor::SetParam(const std::wstring& name, const AtlasMessage::Colour& value) { if (name == L"background") { m_ActorViewer->SetBackgroundColour(SColor4ub(value.r, value.g, value.b, 255)); } } ////////////////////////////////////////////////////////////////////////// template static void delete_pair_2nd(std::pair v) { delete v.second; } ViewGame::ViewGame() : m_SpeedMultiplier(0.f) { debug_assert(g_Game); } ViewGame::~ViewGame() { std::for_each(m_SavedStates.begin(), m_SavedStates.end(), delete_pair_2nd); } CSimulation2* ViewGame::GetSimulation2() { return g_Game->GetSimulation2(); } void ViewGame::Update(float frameLength) { float actualFrameLength = frameLength * m_SpeedMultiplier; // Clean up any entities destroyed during UI message processing g_Game->GetSimulation2()->FlushDestroyedEntities(); if (m_SpeedMultiplier == 0.f) { // Update unit interpolation g_Game->Interpolate(0.0); } else { // Update the whole world // (Tell the game update not to interpolate graphics - we'll do that // ourselves) bool ok = g_Game->Update(actualFrameLength, false); if (! ok) { // Whoops, we're trying to go faster than the simulation can manage. // It's probably better to run at the right sim rate, at the expense // of framerate, so let's try simulating a few more times. double t = timer_Time(); while (!ok && timer_Time() < t + 0.1) // don't go much worse than 10fps { ok = g_Game->Update(0.0, false); // don't add on any extra sim time } } // Interpolate the graphics - we only want to do this once per visual frame, // not in every call to g_Game->Update g_Game->Interpolate(actualFrameLength); } // Cinematic motion should be independent of simulation update, so we can // preview the cinematics by themselves if (g_Game->GetView()->GetCinema()->IsPlaying()) g_Game->GetView()->GetCinema()->Update(frameLength); } void ViewGame::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV); camera.UpdateFrustum(); // Update the pathfinder display if necessary if (!m_DisplayPassability.empty()) { CmpPtr cmpObstructionMan(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpObstructionMan.null()) { cmpObstructionMan->SetDebugOverlay(true); } CmpPtr cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpPathfinder.null()) { cmpPathfinder->SetDebugOverlay(true); // Kind of a hack to make it update the terrain grid ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, fixed::Zero(), fixed::Zero() }; u8 passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability); u8 costClass = cmpPathfinder->GetCostClass("default"); cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass, costClass); } } ::Render(); Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas); } void ViewGame::SetParam(const std::wstring& name, bool value) { if (name == L"priorities") g_Renderer.SetDisplayTerrainPriorities(value); } void ViewGame::SetParam(const std::wstring& name, const std::wstring& value) { if (name == L"passability") { m_DisplayPassability = CStrW(value).ToUTF8(); CmpPtr cmpObstructionMan(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpObstructionMan.null()) cmpObstructionMan->SetDebugOverlay(!value.empty()); CmpPtr cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (!cmpPathfinder.null()) cmpPathfinder->SetDebugOverlay(!value.empty()); } + else if (name == L"renderpath") + { + g_Renderer.SetRenderPath(g_Renderer.GetRenderPathByName(CStrW(value).ToUTF8())); + } } CCamera& ViewGame::GetCamera() { return *g_Game->GetView()->GetCamera(); } bool ViewGame::WantsHighFramerate() { if (g_Game->GetView()->GetCinema()->IsPlaying()) return true; if (m_SpeedMultiplier != 0.f) return true; return false; } void ViewGame::SetSpeedMultiplier(float speed) { m_SpeedMultiplier = speed; } void ViewGame::SaveState(const std::wstring& label) { delete m_SavedStates[label]; // in case it already exists m_SavedStates[label] = SimState::Freeze(); } void ViewGame::RestoreState(const std::wstring& label) { SimState* simState = m_SavedStates[label]; if (! simState) return; simState->Thaw(); } std::wstring ViewGame::DumpState(bool binary) { std::stringstream stream; if (binary) { if (! g_Game->GetSimulation2()->SerializeState(stream)) return L"(internal error)"; // We can't return raw binary data, because we want to handle it with wxJS which // doesn't like \0 bytes in strings, so return it as hex static const char digits[] = "0123456789abcdef"; std::string str = stream.str(); std::wstring ret; ret.reserve(str.length()*3); for (size_t i = 0; i < str.length(); ++i) { ret += digits[(unsigned char)str[i] >> 4]; ret += digits[(unsigned char)str[i] & 0x0f]; ret += ' '; } return ret; } else { if (! g_Game->GetSimulation2()->DumpDebugState(stream)) return L"(internal error)"; return wstring_from_utf8(stream.str()); } } ////////////////////////////////////////////////////////////////////////// ViewNone* view_None = NULL; ViewGame* view_Game = NULL; ViewActor* view_Actor = NULL; View::~View() { } View* View::GetView(int /*eRenderView*/ view) { switch (view) { case AtlasMessage::eRenderView::NONE: return View::GetView_None(); case AtlasMessage::eRenderView::GAME: return View::GetView_Game(); case AtlasMessage::eRenderView::ACTOR: return View::GetView_Actor(); default: debug_warn(L"Invalid view type"); return View::GetView_None(); } } View* View::GetView_None() { if (! view_None) view_None = new ViewNone(); return view_None; } ViewGame* View::GetView_Game() { if (! view_Game) view_Game = new ViewGame(); return view_Game; } ViewActor* View::GetView_Actor() { if (! view_Actor) view_Actor = new ViewActor(); return view_Actor; } void View::DestroyViews() { delete view_None; view_None = NULL; delete view_Game; view_Game = NULL; delete view_Actor; view_Actor = NULL; } Index: ps/trunk/source/tools/atlas/GameInterface/Messages.h =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Messages.h (revision 9122) +++ ps/trunk/source/tools/atlas/GameInterface/Messages.h (revision 9123) @@ -1,510 +1,514 @@ /* Copyright (C) 2010 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 . */ #ifndef INCLUDED_MESSAGES #define INCLUDED_MESSAGES #ifndef MESSAGES_SKIP_SETUP #include "MessagesSetup.h" #endif #include #include // TODO: organisation, documentation, etc ////////////////////////////////////////////////////////////////////////// // Initialise some engine code. Must be called before anything else. MESSAGE(Init, ); // Initialise graphics-related code. Must be called after the first SetCanvas, // and before much else. MESSAGE(InitGraphics, ); // Shut down engine/graphics code. MESSAGE(Shutdown, ); struct eRenderView { enum renderViews { NONE, GAME, ACTOR }; }; MESSAGE(RenderEnable, ((int, view)) // eRenderView ); // SetViewParam: used for hints to the renderer, e.g. to set wireframe mode; // unrecognised param names are ignored MESSAGE(SetViewParamB, ((int, view)) // eRenderView ((std::wstring, name)) ((bool, value)) ); MESSAGE(SetViewParamC, ((int, view)) // eRenderView ((std::wstring, name)) ((Colour, value)) ); MESSAGE(SetViewParamS, ((int, view)) // eRenderView ((std::wstring, name)) ((std::wstring, value)) ); MESSAGE(JavaScript, ((std::wstring, command)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(GuiSwitchPage, ((std::wstring, page)) ); MESSAGE(GuiMouseButtonEvent, ((int, button)) ((bool, pressed)) ((Position, pos)) ); MESSAGE(GuiMouseMotionEvent, ((Position, pos)) ); MESSAGE(GuiKeyEvent, ((int, sdlkey)) // SDLKey code ((int, unichar)) // Unicode character ((bool, pressed)) ); MESSAGE(GuiCharEvent, ((int, sdlkey)) ((int, unichar)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(SimStateSave, ((std::wstring, label)) // named slot to store saved data ); MESSAGE(SimStateRestore, ((std::wstring, label)) // named slot to find saved data ); QUERY(SimStateDebugDump, ((bool, binary)) , ((std::wstring, dump)) ); MESSAGE(SimPlay, ((float, speed)) // 0 for pause, 1 for normal speed ); ////////////////////////////////////////////////////////////////////////// QUERY(Ping, , ); ////////////////////////////////////////////////////////////////////////// MESSAGE(SetCanvas, ((void*, canvas)) ); MESSAGE(ResizeScreen, ((int, width)) ((int, height)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(GenerateMap, ((int, size)) // size in number of patches ); MESSAGE(LoadMap, ((std::wstring, filename)) ); MESSAGE(SaveMap, ((std::wstring, filename)) ); ////////////////////////////////////////////////////////////////////////// MESSAGE(RenderStyle, ((bool, wireframe)) ); MESSAGE(MessageTrace, ((bool, enable)) ); MESSAGE(Screenshot, ((int, tiles)) // the final image will be (640*tiles)x(480*tiles) ); #ifndef MESSAGES_SKIP_STRUCTS struct sCinemaRecordCB { unsigned char* buffer; }; SHAREABLE_STRUCT(sCinemaRecordCB); #endif QUERY(CinemaRecord, ((std::wstring, path)) ((int, framerate)) ((float, duration)) ((int, width)) ((int, height)) ((Callback, cb)) , ); ////////////////////////////////////////////////////////////////////////// MESSAGE(Brush, ((int, width)) // number of vertices ((int, height)) ((std::vector, data)) // width*height array ); MESSAGE(BrushPreview, ((bool, enable)) ((Position, pos)) // only used if enable==true ); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// QUERY(GetTerrainGroups, , // no inputs ((std::vector, groupnames)) ); #ifndef MESSAGES_SKIP_STRUCTS struct sTerrainGroupPreview { Shareable name; Shareable loaded; Shareable imagewidth; Shareable imageheight; Shareable > imagedata; // RGB*width*height }; SHAREABLE_STRUCT(sTerrainGroupPreview); #endif QUERY(GetTerrainGroupPreviews, ((std::wstring, groupname)) ((int, imagewidth)) ((int, imageheight)) , ((std::vector, previews)) ); QUERY(GetTerrainPassabilityClasses, , // no inputs ((std::vector, classnames)) ); ////////////////////////////////////////////////////////////////////////// #ifndef MESSAGES_SKIP_STRUCTS struct sObjectsListItem { Shareable id; Shareable name; Shareable type; // 0 = entity, 1 = actor }; SHAREABLE_STRUCT(sObjectsListItem); #endif QUERY(GetObjectsList, , // no inputs ((std::vector, objects)) // sorted by .name ); #ifndef MESSAGES_SKIP_STRUCTS struct sObjectSettings { Shareable player; Shareable > selections; // Some settings are immutable and therefore are ignored (and should be left // empty) when passed from the editor to the game: Shareable > > variantgroups; }; SHAREABLE_STRUCT(sObjectSettings); #endif // Preview object in the game world - creates a temporary unit at the given // position, and removes it when the preview is next changed MESSAGE(ObjectPreview, ((std::wstring, id)) // or empty string => disable ((sObjectSettings, settings)) ((Position, pos)) ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((Position, target)) ((float, angle)) ); COMMAND(CreateObject, NOMERGE, ((std::wstring, id)) ((sObjectSettings, settings)) ((Position, pos)) ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((Position, target)) ((float, angle)) ); // Set an actor to be previewed on its own (i.e. without the game world). // (Use RenderEnable to make it visible.) MESSAGE(SetActorViewer, ((std::wstring, id)) ((std::wstring, animation)) ((float, speed)) ((bool, flushcache)) // true => unload all actor files before starting the preview (because we don't have proper hotloading yet) ); ////////////////////////////////////////////////////////////////////////// QUERY(Exit,,); // no inputs nor outputs ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// struct eScrollConstantDir { enum { FORWARDS, BACKWARDS, LEFT, RIGHT, CLOCKWISE, ANTICLOCKWISE }; }; MESSAGE(ScrollConstant, // set a constant scrolling(/rotation) rate ((int, view)) // eRenderView ((int, dir)) // eScrollConstantDir ((float, speed)) // set speed 0.0f to stop scrolling ); struct eScrollType { enum { FROM, TO }; }; MESSAGE(Scroll, // for scrolling by dragging the mouse FROM somewhere TO elsewhere ((int, view)) // eRenderView ((int, type)) // eScrollType ((Position, pos)) ); MESSAGE(SmoothZoom, ((int, view)) // eRenderView ((float, amount)) ); struct eRotateAroundType { enum { FROM, TO }; }; MESSAGE(RotateAround, ((int, view)) // eRenderView ((int, type)) // eRotateAroundType ((Position, pos)) ); MESSAGE(LookAt, ((int, view)) // eRenderView ((Position, pos)) ((Position, target)) ); MESSAGE(CameraReset, ); ////////////////////////////////////////////////////////////////////////// #ifndef MESSAGES_SKIP_STRUCTS struct sEnvironmentSettings { Shareable waterheight; // range 0..1 corresponds to min..max terrain height; out-of-bounds values allowed Shareable watershininess; // range ??? Shareable waterwaviness; // range ??? Shareable watermurkiness; // range ??? Shareable watercolour; Shareable watertint; Shareable waterreflectiontint; Shareable waterreflectiontintstrength; // range ??? Shareable sunrotation; // range -pi..+pi Shareable sunelevation; // range -pi/2 .. +pi/2 // emulate 'HDR' by allowing overly bright suncolour. this is // multiplied on to suncolour after converting to float // (struct Colour stores as normal u8, 0..255) Shareable sunoverbrightness; // range 1..3 + // support different lighting models ("old" for the version compatible with old scenarios, + // "standard" for the new normal model that supports much brighter lighting) + Shareable lightingmodel; + Shareable skyset; Shareable suncolour; Shareable terraincolour; Shareable unitcolour; }; SHAREABLE_STRUCT(sEnvironmentSettings); #endif QUERY(GetEnvironmentSettings, // no inputs , ((sEnvironmentSettings, settings)) ); COMMAND(SetEnvironmentSettings, MERGE, ((sEnvironmentSettings, settings)) ); QUERY(GetSkySets, // no inputs , ((std::vector, skysets)) ); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// COMMAND(AlterElevation, MERGE, ((Position, pos)) ((float, amount)) ); COMMAND(SmoothElevation, MERGE, ((Position, pos)) ((float, amount)) ); COMMAND(FlattenElevation, MERGE, ((Position, pos)) ((float, amount)) ); struct ePaintTerrainPriority { enum { HIGH, LOW }; }; COMMAND(PaintTerrain, MERGE, ((Position, pos)) ((std::wstring, texture)) ((int, priority)) // ePaintTerrainPriority ); ////////////////////////////////////////////////////////////////////////// QUERY(PickObject, ((Position, pos)) , ((ObjectID, id)) ((int, offsetx)) // offset of object centre from input position ((int, offsety)) // ); COMMAND(MoveObject, MERGE, ((ObjectID, id)) ((Position, pos)) ); COMMAND(RotateObject, MERGE, ((ObjectID, id)) ((bool, usetarget)) // true => use 'target' for orientation; false => use 'angle' ((Position, target)) ((float, angle)) ); COMMAND(DeleteObject, NOMERGE, ((ObjectID, id)) ); MESSAGE(SetSelectionPreview, ((std::vector, ids)) ); QUERY(GetObjectSettings, ((int, view)) // eRenderView ((ObjectID, id)) , ((sObjectSettings, settings)) ); COMMAND(SetObjectSettings, NOMERGE, ((int, view)) // eRenderView ((ObjectID, id)) ((sObjectSettings, settings)) ); ////////////////////////////////////////////////////////////////////////// QUERY(GetCinemaPaths, , // no inputs ((std::vector , paths)) ); QUERY(GetCameraInfo, , ((AtlasMessage::sCameraInfo, info)) ); COMMAND(SetCinemaPaths, NOMERGE, ((std::vector, paths)) ); MESSAGE(CinemaEvent, ((std::wstring, path)) ((int, mode)) ((float, t)) ((bool, drawCurrent)) ((bool, lines)) ); ////////////////////////////////////////////////////////////////////////// enum eTriggerListType { CINEMA_LIST, TRIGGER_LIST, TRIG_GROUP_LIST //list of trigger groups // [Eventually include things like entities and areas as the editor progresses...] }; QUERY(GetTriggerData, , //no inputs ((std::vector, groups)) ((std::vector, conditions)) ((std::vector, effects)) ); QUERY(GetTriggerChoices, ((std::wstring, name)), ((std::vector, choices)) ((std::vector, translations)) ); COMMAND(SetAllTriggers, NOMERGE, ((std::vector, groups)) ); QUERY(GetWorldPosition, ((int, x)) ((int, y)), ((Position, position)) ); MESSAGE(TriggerToggleSelector, ((bool, enable)) ((Position, position)) ); #ifndef MESSAGES_SKIP_SETUP #include "MessagesSetup.h" #endif #endif // INCLUDED_MESSAGES Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp (revision 9122) +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/EnvironmentHandlers.cpp (revision 9123) @@ -1,158 +1,162 @@ /* 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 "MessageHandler.h" #include "../CommandProc.h" #include "graphics/LightEnv.h" #include "graphics/Terrain.h" #include "maths/MathUtil.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpWaterManager.h" namespace AtlasMessage { sEnvironmentSettings GetSettings() { sEnvironmentSettings s; CmpPtr cmpWaterMan(*g_Game->GetSimulation2(), SYSTEM_ENTITY); debug_assert(!cmpWaterMan.null()); s.waterheight = cmpWaterMan->GetExactWaterLevel(0, 0) / (65536.f * HEIGHT_SCALE); WaterManager* wm = g_Renderer.GetWaterManager(); s.watershininess = wm->m_Shininess; s.waterwaviness = wm->m_Waviness; s.watermurkiness = wm->m_Murkiness; s.waterreflectiontintstrength = wm->m_ReflectionTintStrength; // CColor colours #define COLOUR(A, B) A = Colour((int)(B.r*255), (int)(B.g*255), (int)(B.b*255)) COLOUR(s.watercolour, wm->m_WaterColor); COLOUR(s.watertint, wm->m_WaterTint); COLOUR(s.waterreflectiontint, wm->m_ReflectionTint); #undef COLOUR float sunrotation = g_LightEnv.GetRotation(); if (sunrotation > (float)M_PI) sunrotation -= (float)M_PI*2; s.sunrotation = sunrotation; s.sunelevation = g_LightEnv.GetElevation(); + s.lightingmodel = CStr(g_LightEnv.GetLightingModel()).FromUTF8(); + s.skyset = g_Renderer.GetSkyManager()->GetSkySet(); // RGBColor (CVector3D) colours #define COLOUR(A, B) A = Colour((int)(B.X*255), (int)(B.Y*255), (int)(B.Z*255)) s.sunoverbrightness = MaxComponent(g_LightEnv.m_SunColor); // clamp color to [0..1] before packing into u8 triplet if(s.sunoverbrightness > 1.0f) g_LightEnv.m_SunColor *= 1.0/s.sunoverbrightness; // (there's no operator/=) // no component was above 1.0, so reset scale factor (don't want to darken) else s.sunoverbrightness = 1.0f; COLOUR(s.suncolour, g_LightEnv.m_SunColor); COLOUR(s.terraincolour, g_LightEnv.m_TerrainAmbientColor); COLOUR(s.unitcolour, g_LightEnv.m_UnitsAmbientColor); #undef COLOUR return s; } void SetSettings(const sEnvironmentSettings& s) { CmpPtr cmpWaterMan(*g_Game->GetSimulation2(), SYSTEM_ENTITY); debug_assert(!cmpWaterMan.null()); cmpWaterMan->SetWaterLevel(entity_pos_t::FromFloat(s.waterheight * (65536.f * HEIGHT_SCALE))); WaterManager* wm = g_Renderer.GetWaterManager(); wm->m_Shininess = s.watershininess; wm->m_Waviness = s.waterwaviness; wm->m_Murkiness = s.watermurkiness; wm->m_ReflectionTintStrength = s.waterreflectiontintstrength; #define COLOUR(A, B) B = CColor(A->r/255.f, A->g/255.f, A->b/255.f, 1.f) COLOUR(s.watercolour, wm->m_WaterColor); COLOUR(s.watertint, wm->m_WaterTint); COLOUR(s.waterreflectiontint, wm->m_ReflectionTint); #undef COLOUR g_LightEnv.SetRotation(s.sunrotation); g_LightEnv.SetElevation(s.sunelevation); + g_LightEnv.SetLightingModel(CStrW(*s.lightingmodel).ToUTF8()); + CStrW skySet = *s.skyset; if (skySet.length() == 0) skySet = L"default"; g_Renderer.GetSkyManager()->SetSkySet(skySet); #define COLOUR(A, B) B = RGBColor(A->r/255.f, A->g/255.f, A->b/255.f) COLOUR(s.suncolour, g_LightEnv.m_SunColor); g_LightEnv.m_SunColor *= s.sunoverbrightness; COLOUR(s.terraincolour, g_LightEnv.m_TerrainAmbientColor); COLOUR(s.unitcolour, g_LightEnv.m_UnitsAmbientColor); #undef COLOUR } BEGIN_COMMAND(SetEnvironmentSettings) { sEnvironmentSettings m_OldSettings, m_NewSettings; void Do() { m_OldSettings = GetSettings(); m_NewSettings = msg->settings; Redo(); } void Redo() { SetSettings(m_NewSettings); } void Undo() { SetSettings(m_OldSettings); } void MergeIntoPrevious(cSetEnvironmentSettings* prev) { prev->m_NewSettings = m_NewSettings; } }; END_COMMAND(SetEnvironmentSettings) QUERYHANDLER(GetEnvironmentSettings) { msg->settings = GetSettings(); } QUERYHANDLER(GetSkySets) { std::vector skies = g_Renderer.GetSkyManager()->GetSkySets(); msg->skysets = std::vector(skies.begin(), skies.end()); } } Index: ps/trunk/source/graphics/MapReader.cpp =================================================================== --- ps/trunk/source/graphics/MapReader.cpp (revision 9122) +++ ps/trunk/source/graphics/MapReader.cpp (revision 9123) @@ -1,1307 +1,1317 @@ /* Copyright (C) 2011 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 "MapReader.h" #include "graphics/Camera.h" #include "graphics/CinemaTrack.h" #include "graphics/Entity.h" #include "graphics/GameView.h" #include "graphics/MapGenerator.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/XML/Xeromyces.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpWaterManager.h" #include CMapReader::CMapReader() : xml_reader(0), m_PatchesPerSide(0) { cur_terrain_tex = 0; // important - resets generator state + + // Maps that don't override the default probably want the old lighting model + m_LightEnv.SetLightingModel("old"); } // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CSimulation2 *pSimulation2_, int playerID_) { // latch parameters (held until DelayedLoadFinished) pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pSimulation2 = pSimulation2_; m_PlayerID = playerID_; m_CameraStartupTarget = INVALID_ENTITY; filename_xml = pathname.ChangeExtension(L".xml"); // In some cases (particularly tests) we don't want to bother storing a large // mostly-empty .pmp file, so we let the XML file specify basic terrain instead. // If there's an .xml file and no .pmp, then we're probably in this XML-only mode only_xml = false; if (!VfsFileExists(pathname) && VfsFileExists(filename_xml)) { only_xml = true; } file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp if (!only_xml) { // [25ms] unpacker.Read(pathname, "PSMP"); file_format_version = unpacker.GetVersion(); } // check oldest supported version if (file_format_version < FILE_READ_VERSION) throw PSERROR_File_InvalidVersion(); // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); // load map settings script RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // unpack the data if (!only_xml) RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200); // read the corresponding XML file RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 5800); // apply data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful void CMapReader::LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted& settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CSimulation2 *pSimulation2_, int playerID_) { // latch parameters (held until DelayedLoadFinished) m_ScriptFile = scriptFile; m_ScriptSettings = settings; pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pSimulation2 = pSimulation2_; m_PlayerID = playerID_; m_CameraStartupTarget = INVALID_ENTITY; // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); only_xml = false; // copy random map settings (before entity creation) RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // load map generator with random map script RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 2000); // parse RMS results into terrain structure RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500); // parse RMS results into environment settings RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5); // parse RMS results into camera settings RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5); // parse RMS results into entities RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000); // apply data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // UnpackMap: unpack the given data from the raw data stream into local variables int CMapReader::UnpackMap() { // now unpack everything into local data int ret = UnpackTerrain(); if(ret != 0) // failed or timed out return ret; if (unpacker.GetVersion() < 4) debug_warn(L"Old unsupported map version - objects and lighting will be lost"); return 0; } // UnpackTerrain: unpack the terrain from the end of the input data stream // - data: map size, heightmap, list of textures used by map, texture tile assignments int CMapReader::UnpackTerrain() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; // first call to generator (this is skipped after first call, // i.e. when the loop below was interrupted) if (cur_terrain_tex == 0) { m_PatchesPerSide = (ssize_t)unpacker.UnpackSize(); // unpack heightmap [600us] size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1; m_Heightmap.resize(SQR(verticesPerSide)); unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16)); // unpack # textures num_terrain_tex = unpacker.UnpackSize(); m_TerrainTextures.reserve(num_terrain_tex); } // unpack texture names; find handle for each texture. // interruptible. while (cur_terrain_tex < num_terrain_tex) { CStr texturename; unpacker.UnpackString(texturename); debug_assert(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename); m_TerrainTextures.push_back(texentry); cur_terrain_tex++; LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex); } // unpack tile data [3ms] ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE; m_Tiles.resize(size_t(SQR(tilesPerSide))); unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size()); // reset generator state. cur_terrain_tex = 0; return 0; } // ApplyData: take all the input data, and rebuild the scene from it int CMapReader::ApplyData() { if (m_PatchesPerSide == 0) { debug_warn(L"Map has no terrain data"); return -1; // we'll probably crash when trying to use this map later } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } // copy over the lighting parameters if (pLightEnv) *pLightEnv = m_LightEnv; if (pGameView) { pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus()); if (m_CameraStartupTarget != INVALID_ENTITY) { CmpPtr cmpPosition(*pSimulation2, m_CameraStartupTarget); if (!cmpPosition.null()) { CFixedVector3D pos = cmpPosition->GetPosition(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } } } CmpPtr cmpTerrain(*pSimulation2, SYSTEM_ENTITY); if (!cmpTerrain.null()) cmpTerrain->ReloadTerrain(); return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname) { VfsPath filename_xml = pathname.ChangeExtension(L".xml"); CXeromyces xmb_file; if (xmb_file.Load(g_VFS, filename_xml) != PSRETURN_OK) return PSRETURN_File_ReadFailed; // Define all the relevant elements used in the XML file #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(scenario); EL(scriptsettings); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); debug_assert(root.GetNodeName() == el_scenario); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_scriptsettings) { m_ScriptSettings = child.GetText(); } } return PSRETURN_OK; } CScriptValRooted CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface) { CScriptValRooted data; scriptInterface.Eval("({})", data); if (!m_ScriptSettings.empty()) scriptInterface.SetProperty(data.get(), "settings", scriptInterface.ParseJSON(m_ScriptSettings), false); return data; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Holds various state data while reading maps, so that loading can be // interrupted (e.g. to update the progress display) then later resumed. class CXMLReader { NONCOPYABLE(CXMLReader); public: CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader) : m_MapReader(mapReader) { Init(xml_filename); } CStr ReadScriptSettings(); // return semantics: see Loader.cpp!LoadFunc. int ProgressiveRead(); private: CXeromyces xmb_file; CMapReader& m_MapReader; int el_entity; int el_tracks; int el_template, el_player; int el_position, el_orientation; int el_nonentity; int el_actor; int at_x, at_y, at_z; int at_id; int at_angle; int at_uid; XMBElementList nodes; // children of root // loop counters int node_idx; int entity_idx, nonentity_idx; // # entities+nonentities processed and total (for progress calc) int completed_jobs, total_jobs; // maximum used entity ID, so we can safely allocate new ones entity_id_t max_uid; void Init(const VfsPath& xml_filename); void ReadTerrain(XMBElement parent); void ReadEnvironment(XMBElement parent); void ReadCamera(XMBElement parent); void ReadCinema(XMBElement parent); void ReadTriggers(XMBElement parent); int ReadEntities(XMBElement parent, double end_time); int ReadOldEntities(XMBElement parent, double end_time); int ReadNonEntities(XMBElement parent, double end_time); }; void CXMLReader::Init(const VfsPath& xml_filename) { // must only assign once, so do it here node_idx = entity_idx = nonentity_idx = 0; if (xmb_file.Load(g_VFS, xml_filename) != PSRETURN_OK) throw PSERROR_File_ReadFailed(); // define the elements and attributes that are frequently used in the XML file, // so we don't need to do lots of string construction and comparison when // reading the data. // (Needs to be synchronised with the list in CXMLReader - ugh) #define EL(x) el_##x = xmb_file.GetElementID(#x) #define AT(x) at_##x = xmb_file.GetAttributeID(#x) EL(entity); EL(tracks); EL(template); EL(player); EL(position); EL(orientation); EL(nonentity); EL(actor); AT(x); AT(y); AT(z); AT(angle); AT(uid); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); debug_assert(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); // find out total number of entities+nonentities // (used when calculating progress) completed_jobs = 0; total_jobs = 0; for (int i = 0; i < nodes.Count; i++) total_jobs += nodes.Item(i).GetChildNodes().Count; // Find the maximum entity ID, so we can safely allocate new IDs without conflicts max_uid = SYSTEM_ENTITY; XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities")); XERO_ITER_EL(ents, ent) { CStr uid = ent.GetAttributes().GetNamedItem(at_uid); max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt()); } } CStr CXMLReader::ReadScriptSettings() { XMBElement root = xmb_file.GetRoot(); debug_assert(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings")); return settings.GetText(); } void CXMLReader::ReadTerrain(XMBElement parent) { #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) AT(patches); AT(texture); AT(priority); AT(height); #undef AT ssize_t patches = 9; CStr texture = "grass1_spring"; int priority = 0; u16 height = 16384; XERO_ITER_ATTR(parent, attr) { if (attr.Name == at_patches) patches = attr.Value.ToInt(); else if (attr.Name == at_texture) texture = attr.Value; else if (attr.Name == at_priority) priority = attr.Value.ToInt(); else if (attr.Name == at_height) height = (u16)attr.Value.ToInt(); } m_MapReader.m_PatchesPerSide = patches; // Load the texture debug_assert(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture); m_MapReader.pTerrain->Initialize(patches, NULL); // Fill the heightmap u16* heightmap = m_MapReader.pTerrain->GetHeightMap(); ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide(); for (ssize_t i = 0; i < SQR(verticesPerSide); ++i) heightmap[i] = height; // Fill the texture map for (ssize_t pz = 0; pz < patches; ++pz) { for (ssize_t px = 0; px < patches; ++px) { CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail for (ssize_t z = 0; z < PATCH_SIZE; ++z) { for (ssize_t x = 0; x < PATCH_SIZE; ++x) { patch->m_MiniPatches[z][x].Tex = texentry; patch->m_MiniPatches[z][x].Priority = priority; } } } } } void CXMLReader::ReadEnvironment(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) + EL(lightingmodel); EL(skyset); EL(suncolour); EL(sunelevation); EL(sunrotation); EL(terrainambientcolour); EL(unitsambientcolour); EL(terrainshadowtransparency); EL(water); EL(waterbody); EL(type); EL(colour); EL(height); EL(shininess); EL(waviness); EL(murkiness); EL(tint); EL(reflectiontint); EL(reflectiontintstrength); AT(r); AT(g); AT(b); #undef AT #undef EL XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); - if (element_name == el_skyset) + if (element_name == el_lightingmodel) + { + m_MapReader.m_LightEnv.SetLightingModel(element.GetText()); + } + else if (element_name == el_skyset) { if (m_MapReader.pSkyMan) m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8()); } else if (element_name == el_suncolour) { m_MapReader.m_LightEnv.m_SunColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_sunelevation) { m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_sunrotation) { m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_terrainambientcolour) { m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_unitsambientcolour) { m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_terrainshadowtransparency) { m_MapReader.m_LightEnv.SetTerrainShadowTransparency(element.GetText().ToFloat()); } else if (element_name == el_water) { XERO_ITER_EL(element, waterbody) { debug_assert(waterbody.GetNodeName() == el_waterbody); XERO_ITER_EL(waterbody, waterelement) { int element_name = waterelement.GetNodeName(); if (element_name == el_height) { CmpPtr cmpWaterMan(*m_MapReader.pSimulation2, SYSTEM_ENTITY); debug_assert(!cmpWaterMan.null()); cmpWaterMan->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText())); continue; } // The rest are purely graphical effects, and should be ignored if // graphics are disabled if (!m_MapReader.pWaterMan) continue; if (element_name == el_type) { // TODO: implement this, when WaterManager supports it } #define READ_COLOUR(el, out) \ else if (element_name == el) \ { \ XMBAttributeList attrs = waterelement.GetAttributes(); \ out = CColor( \ attrs.GetNamedItem(at_r).ToFloat(), \ attrs.GetNamedItem(at_g).ToFloat(), \ attrs.GetNamedItem(at_b).ToFloat(), \ 1.f); \ } #define READ_FLOAT(el, out) \ else if (element_name == el) \ { \ out = waterelement.GetText().ToFloat(); \ } \ READ_COLOUR(el_colour, m_MapReader.pWaterMan->m_WaterColor) READ_FLOAT(el_shininess, m_MapReader.pWaterMan->m_Shininess) READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness) READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness) READ_COLOUR(el_tint, m_MapReader.pWaterMan->m_WaterTint) READ_COLOUR(el_reflectiontint, m_MapReader.pWaterMan->m_ReflectionTint) READ_FLOAT(el_reflectiontintstrength, m_MapReader.pWaterMan->m_ReflectionTintStrength) #undef READ_FLOAT #undef READ_COLOUR else debug_warn(L"Invalid map XML data"); } } } else debug_warn(L"Invalid map XML data"); } m_MapReader.m_LightEnv.CalculateSunDirection(); } void CXMLReader::ReadCamera(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(declination); EL(rotation); EL(position); AT(angle); AT(x); AT(y); AT(z); #undef AT #undef EL float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f); CVector3D translation = CVector3D(100, 150, -100); XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_declination) { declination = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_rotation) { rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_position) { translation = CVector3D( attrs.GetNamedItem(at_x).ToFloat(), attrs.GetNamedItem(at_y).ToFloat(), attrs.GetNamedItem(at_z).ToFloat()); } else debug_warn(L"Invalid map XML data"); } if (m_MapReader.pGameView) { m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination); m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation); m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation); m_MapReader.pGameView->GetCamera()->UpdateFrustum(); } } void CXMLReader::ReadCinema(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(path); EL(rotation); EL(distortion); EL(node); EL(position); EL(time); AT(name); AT(timescale); AT(mode); AT(style); AT(growth); AT(switch); AT(x); AT(y); AT(z); #undef EL #undef AT std::map pathList; XERO_ITER_EL(parent, element) { int elementName = element.GetNodeName(); if ( elementName == el_path ) { XMBAttributeList attrs = element.GetAttributes(); CStrW name(attrs.GetNamedItem(at_name).FromUTF8()); float timescale = attrs.GetNamedItem(at_timescale).ToFloat(); CCinemaData pathData; pathData.m_Timescale = timescale; TNSpline spline, backwardSpline; XERO_ITER_EL(element, pathChild) { elementName = pathChild.GetNodeName(); attrs = pathChild.GetAttributes(); //Load distortion attributes if ( elementName == el_distortion ) { pathData.m_Mode = attrs.GetNamedItem(at_mode).ToInt(); pathData.m_Style = attrs.GetNamedItem(at_style).ToInt(); pathData.m_Growth = attrs.GetNamedItem(at_growth).ToInt(); pathData.m_Switch = attrs.GetNamedItem(at_switch).ToInt(); } //Load node data used for spline else if ( elementName == el_node ) { SplineData data; XERO_ITER_EL(pathChild, nodeChild) { elementName = nodeChild.GetNodeName(); attrs = nodeChild.GetAttributes(); //Fix?: assumes that time is last element if ( elementName == el_position ) { data.Position.X = attrs.GetNamedItem(at_x).ToFloat(); data.Position.Y = attrs.GetNamedItem(at_y).ToFloat(); data.Position.Z = attrs.GetNamedItem(at_z).ToFloat(); continue; } else if ( elementName == el_rotation ) { data.Rotation.X = attrs.GetNamedItem(at_x).ToFloat(); data.Rotation.Y = attrs.GetNamedItem(at_y).ToFloat(); data.Rotation.Z = attrs.GetNamedItem(at_z).ToFloat(); continue; } else if ( elementName == el_time ) data.Distance = nodeChild.GetText().ToFloat(); else debug_warn(L"Invalid cinematic element for node child"); backwardSpline.AddNode(data.Position, data.Rotation, data.Distance); } } else debug_warn(L"Invalid cinematic element for path child"); } //Construct cinema path with data gathered CCinemaPath temp(pathData, backwardSpline); const std::vector& nodes = temp.GetAllNodes(); if ( nodes.empty() ) { debug_warn(L"Failure loading cinematics"); return; } for ( std::vector::const_reverse_iterator it = nodes.rbegin(); it != nodes.rend(); ++it ) { spline.AddNode(it->Position, it->Rotation, it->Distance); } CCinemaPath path(pathData, spline); pathList[name] = path; } else debug_assert("Invalid cinema child"); } if (m_MapReader.pCinema) m_MapReader.pCinema->SetAllPaths(pathList); } void CXMLReader::ReadTriggers(XMBElement UNUSED(parent)) { } int CXMLReader::ReadEntities(XMBElement parent, double end_time) { XMBElementList entities = parent.GetChildNodes(); while (entity_idx < entities.Count) { // all new state at this scope and below doesn't need to be // wrapped, since we only yield after a complete iteration. XMBElement entity = entities.Item(entity_idx++); debug_assert(entity.GetNodeName() == el_entity); XMBAttributeList attrs = entity.GetAttributes(); CStr uid = attrs.GetNamedItem(at_uid); debug_assert(!uid.empty()); int EntityUid = uid.ToInt(); CStrW TemplateName; int PlayerID = 0; CFixedVector3D Position; CFixedVector3D Orientation; XERO_ITER_EL(entity, setting) { int element_name = setting.GetNodeName(); //