From dc2035efc936660b9052c0abe7d02b6807bb1ea1 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Sun, 29 May 2011 15:02:02 +0000 Subject: [PATCH] Move Atlas map settings from JS to C++. Replace New dialog box with separate tools for resizing maps and replacing terrain textures, to provide more power and to simplify the problem of initialising map settings. Fix engine to cope with dynamic map resizing. Add JSON support to AtObj, to let C++ interact with JSON more easily. This was SVN commit r9566. --- build/premake/premake.lua | 2 +- source/graphics/LOSTexture.cpp | 19 +- source/graphics/LOSTexture.h | 1 + .../simulation2/components/CCmpPathfinder.cpp | 8 + .../components/CCmpPathfinder_Tile.cpp | 5 + source/simulation2/components/CCmpTerrain.cpp | 2 + source/simulation2/components/ICmpTerrain.h | 4 +- source/tools/atlas/AtlasObject/AtlasObject.h | 14 + .../atlas/AtlasObject/AtlasObjectImpl.cpp | 55 +++ .../tools/atlas/AtlasObject/AtlasObjectImpl.h | 1 + .../tools/atlas/AtlasObject/AtlasObjectJS.cpp | 268 +++++++++++ .../atlas/AtlasScript/ScriptInterface.cpp | 2 + .../tools/atlas/AtlasScript/ScriptInterface.h | 3 +- .../CustomControls/NewDialog/NewDialog.cpp | 121 ----- .../CustomControls/NewDialog/NewDialog.h | 53 --- .../AtlasUI/ScenarioEditor/ScenarioEditor.cpp | 96 +--- .../AtlasUI/ScenarioEditor/ScenarioEditor.h | 4 +- .../AtlasUI/ScenarioEditor/SectionLayout.cpp | 20 +- .../AtlasUI/ScenarioEditor/SectionLayout.h | 2 + .../ScenarioEditor/Sections/Common/Sidebar.h | 2 + .../Sections/Environment/Environment.cpp | 10 +- .../Sections/Environment/Environment.h | 2 + .../ScenarioEditor/Sections/Map/Map.cpp | 417 ++++++++++++++++-- .../AtlasUI/ScenarioEditor/Sections/Map/Map.h | 23 +- .../ScenarioEditor/Sections/Object/Object.cpp | 2 +- .../Sections/Terrain/Terrain.cpp | 62 ++- .../ScenarioEditor/Sections/Terrain/Terrain.h | 1 + .../ScenarioEditor/Tools/Common/Brushes.cpp | 14 +- .../ScenarioEditor/Tools/Common/Brushes.h | 5 +- .../ScenarioEditor/Tools/Common/Tools.cpp | 5 + .../ScenarioEditor/Tools/Common/Tools.h | 1 + .../ScenarioEditor/Tools/ReplaceTerrain.cpp | 77 ++++ .../GameInterface/Handlers/MapHandlers.cpp | 42 ++ .../Handlers/TerrainHandlers.cpp | 213 ++++++--- source/tools/atlas/GameInterface/Messages.h | 9 + 35 files changed, 1195 insertions(+), 370 deletions(-) create mode 100644 source/tools/atlas/AtlasObject/AtlasObjectJS.cpp delete mode 100644 source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.cpp delete mode 100644 source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.h create mode 100644 source/tools/atlas/AtlasUI/ScenarioEditor/Tools/ReplaceTerrain.cpp diff --git a/build/premake/premake.lua b/build/premake/premake.lua index 301756393c..186838f3e5 100644 --- a/build/premake/premake.lua +++ b/build/premake/premake.lua @@ -766,6 +766,7 @@ function setup_atlas_packages() },{ -- include },{ -- extern_libs "libxml2", + "spidermonkey", "wxwidgets" },{ -- extra_params }) @@ -812,7 +813,6 @@ function setup_atlas_packages() "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", - "CustomControls/NewDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", diff --git a/source/graphics/LOSTexture.cpp b/source/graphics/LOSTexture.cpp index 18833c877c..e4e8deffdd 100644 --- a/source/graphics/LOSTexture.cpp +++ b/source/graphics/LOSTexture.cpp @@ -56,10 +56,13 @@ CLOSTexture::CLOSTexture(CSimulation2& simulation) : CLOSTexture::~CLOSTexture() { if (m_Texture) - { - glDeleteTextures(1, &m_Texture); - m_Texture = 0; - } + DeleteTexture(); +} + +void CLOSTexture::DeleteTexture() +{ + glDeleteTextures(1, &m_Texture); + m_Texture = 0; } void CLOSTexture::MakeDirty() @@ -149,6 +152,14 @@ void CLOSTexture::ConstructTexture(int unit) void CLOSTexture::RecomputeTexture(int unit) { + // If the map was resized, delete and regenerate the texture + if (m_Texture) + { + CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); + if (!cmpTerrain.null() && m_MapSize != cmpTerrain->GetVerticesPerSide()) + DeleteTexture(); + } + if (!m_Texture) ConstructTexture(unit); diff --git a/source/graphics/LOSTexture.h b/source/graphics/LOSTexture.h index a84c25e83d..ce572dbce6 100644 --- a/source/graphics/LOSTexture.h +++ b/source/graphics/LOSTexture.h @@ -71,6 +71,7 @@ public: const float* GetMinimapTextureMatrix(); private: + void DeleteTexture(); void ConstructTexture(int unit); void RecomputeTexture(int unit); diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index 417967c09c..fe511a9ad8 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -277,6 +277,14 @@ const Grid& CCmpPathfinder::GetPassabilityGrid() void CCmpPathfinder::UpdateGrid() { + // If the terrain was resized then delete the old grid data + if (m_Grid && m_MapSize != GetSimContext().GetTerrain().GetTilesPerSide()) + { + SAFE_DELETE(m_Grid); + SAFE_DELETE(m_ObstructionGrid); + m_TerrainDirty = true; + } + // Initialise the terrain data when first needed if (!m_Grid) { diff --git a/source/simulation2/components/CCmpPathfinder_Tile.cpp b/source/simulation2/components/CCmpPathfinder_Tile.cpp index 4fe1983036..761d537c50 100644 --- a/source/simulation2/components/CCmpPathfinder_Tile.cpp +++ b/source/simulation2/components/CCmpPathfinder_Tile.cpp @@ -107,6 +107,11 @@ public: { } + virtual void StartRender() + { + m_Pathfinder.UpdateGrid(); + } + virtual void ProcessTile(ssize_t i, ssize_t j) { if (m_Pathfinder.m_Grid && !IS_PASSABLE(m_Pathfinder.m_Grid->get(i, j), m_Pathfinder.m_DebugPassClass)) diff --git a/source/simulation2/components/CCmpTerrain.cpp b/source/simulation2/components/CCmpTerrain.cpp index fb9fcafc89..9cadf75435 100644 --- a/source/simulation2/components/CCmpTerrain.cpp +++ b/source/simulation2/components/CCmpTerrain.cpp @@ -109,6 +109,8 @@ public: entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE), m_Terrain->GetVerticesPerSide()); } + + MakeDirty(0, 0, m_Terrain->GetTilesPerSide()+1, m_Terrain->GetTilesPerSide()+1); } virtual void MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) diff --git a/source/simulation2/components/ICmpTerrain.h b/source/simulation2/components/ICmpTerrain.h index f3f9f475ce..fd5b13e632 100644 --- a/source/simulation2/components/ICmpTerrain.h +++ b/source/simulation2/components/ICmpTerrain.h @@ -47,8 +47,8 @@ public: virtual void ReloadTerrain() = 0; /** - * Indicate that the terrain within the given region (inclusive lower bound, - * exclusive upper bound) has been changed. CMessageTerrainChanged will be + * Indicate that terrain tiles within the given region (inclusive lower bound, + * exclusive upper bound) have been changed. CMessageTerrainChanged will be * sent to any components that care about terrain changes. */ virtual void MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) = 0; diff --git a/source/tools/atlas/AtlasObject/AtlasObject.h b/source/tools/atlas/AtlasObject/AtlasObject.h index 9f4a44f9b8..528f2048be 100644 --- a/source/tools/atlas/AtlasObject/AtlasObject.h +++ b/source/tools/atlas/AtlasObject/AtlasObject.h @@ -27,6 +27,8 @@ #include // for wchar_t #include +typedef struct JSContext JSContext; + ////////////////////////////////////////////////////////////////////////// // Mostly-private bits: @@ -103,6 +105,8 @@ public: bool defined() const; // Return whether this iterator is pointing to a non-contentless AtObj bool hasContent() const; + // Return the number of AtObjs that will be iterated over (including the current one) + size_t count() const; // Return an iterator to the children matching 'key'. (That is, children // of the AtObj currently pointed to by this iterator) @@ -148,7 +152,10 @@ public: void add(const char* key, AtObj& data); void set(const char* key, const wchar_t* value); void set(const char* key, AtObj& data); + void setBool(const char* key, bool value); + void setInt(const char* key, int value); void setString(const wchar_t* value); + void addOverlay(AtObj& data); AtSmartPtr p; }; @@ -160,10 +167,17 @@ namespace AtlasObject // Returns AtObj() on failure - test with AtObj::defined() AtObj LoadFromXML(const std::string& xml); + // Returns AtObj() on failure - test with AtObj::defined() + AtObj LoadFromJSON(JSContext* cx, const std::string& json); + // Returns UTF-8-encoded XML document string. // Returns empty string on failure. std::string SaveToXML(AtObj& obj); + // Returns UTF-8-encoded JSON string. + // Returns empty string on failure. + std::string SaveToJSON(JSContext* cx, AtObj& obj); + AtObj TrimEmptyChildren(AtObj& obj); } diff --git a/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp b/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp index e33dd2660a..88e79e7497 100644 --- a/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp +++ b/source/tools/atlas/AtlasObject/AtlasObjectImpl.cpp @@ -19,6 +19,7 @@ #include "AtlasObjectImpl.h" #include +#include #define ATSMARTPTR_IMPL(T) \ template<> void AtSmartPtr::inc_ref() \ @@ -100,6 +101,14 @@ bool AtIter::hasContent() const return p->iter->second->hasContent(); } +size_t AtIter::count() const +{ + if (!p) + return 0; + + return std::distance(p->iter, p->iter_upperbound); +} + ////////////////////////////////////////////////////////////////////////// const AtIter AtObj::operator [] (const char* key) const @@ -155,6 +164,30 @@ void AtObj::set(const char* key, const wchar_t* value) p = p->setChild(key, AtNode::Ptr(o)); } +void AtObj::setBool(const char* key, bool value) +{ + AtNode* o = new AtNode(value ? L"true" : L"false"); + o->children.insert(AtNode::child_pairtype("@boolean", AtNode::Ptr(new AtNode()))); + + if (!p) + p = new AtNode(); + + p = p->setChild(key, AtNode::Ptr(o)); +} + +void AtObj::setInt(const char* key, int value) +{ + std::wstringstream str; + str << value; + AtNode* o = new AtNode(str.str().c_str()); + o->children.insert(AtNode::child_pairtype("@number", AtNode::Ptr(new AtNode()))); + + if (!p) + p = new AtNode(); + + p = p->setChild(key, AtNode::Ptr(o)); +} + void AtObj::setString(const wchar_t* value) { if (!p) @@ -163,6 +196,14 @@ void AtObj::setString(const wchar_t* value) p = p->setValue(value); } +void AtObj::addOverlay(AtObj& data) +{ + if (!p) + p = new AtNode(); + + p = p->addOverlay(data.p); +} + bool AtObj::hasContent() const { if (!p) @@ -222,6 +263,20 @@ const AtNode::Ptr AtNode::addChild(const char* key, const AtNode::Ptr &data) con return AtNode::Ptr(newNode); } +const AtNode::Ptr AtNode::addOverlay(const AtNode::Ptr &data) const +{ + AtNode* newNode = new AtNode(this); + + // Delete old childs that are also in the overlay + for (AtNode::child_maptype::const_iterator it = data->children.begin(); it != data->children.end(); ++it) + newNode->children.erase(it->first); + + // Add the overlay childs back in + for (AtNode::child_maptype::const_iterator it = data->children.begin(); it != data->children.end(); ++it) + newNode->children.insert(*it); + + return AtNode::Ptr(newNode); +} ////////////////////////////////////////////////////////////////////////// AtObj AtlasObject::TrimEmptyChildren(AtObj& obj) diff --git a/source/tools/atlas/AtlasObject/AtlasObjectImpl.h b/source/tools/atlas/AtlasObject/AtlasObjectImpl.h index 5885c28089..0ec0cdf4df 100644 --- a/source/tools/atlas/AtlasObject/AtlasObjectImpl.h +++ b/source/tools/atlas/AtlasObject/AtlasObjectImpl.h @@ -48,6 +48,7 @@ public: const AtNode::Ptr setValue(const wchar_t* value) const; const AtNode::Ptr addChild(const char* key, const AtNode::Ptr &data) const; const AtNode::Ptr setChild(const char* key, const AtNode::Ptr &data) const; + const AtNode::Ptr addOverlay(const AtNode::Ptr &data) const; const AtIter getChild(const char* key) const; // Check recursively for any 'value' data diff --git a/source/tools/atlas/AtlasObject/AtlasObjectJS.cpp b/source/tools/atlas/AtlasObject/AtlasObjectJS.cpp new file mode 100644 index 0000000000..38a5d572b9 --- /dev/null +++ b/source/tools/atlas/AtlasObject/AtlasObjectJS.cpp @@ -0,0 +1,268 @@ +/* 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 "AtlasObject.h" +#include "AtlasObjectImpl.h" + +#include "../AtlasScript/ScriptInterface.h" + +#include "wx/log.h" + +#include + +static AtSmartPtr ConvertNode(JSContext* cx, jsval node); + +AtObj AtlasObject::LoadFromJSON(JSContext* cx, const std::string& json) +{ + // Convert UTF8 to UTF16 + wxString jsonW(json.c_str(), wxConvUTF8); + size_t json16len; + wxCharBuffer json16 = wxMBConvUTF16().cWC2MB(jsonW.c_str(), jsonW.Length(), &json16len); + + jsval vp = JSVAL_NULL; + JSONParser* parser = JS_BeginJSONParse(cx, &vp); + if (!parser) + { + wxLogError(_T("ParseJSON failed to begin")); + return AtObj(); + } + + if (!JS_ConsumeJSONText(cx, parser, reinterpret_cast(json16.data()), (uint32)(json16len/2))) + { + wxLogError(_T("ParseJSON failed to consume")); + return AtObj(); + } + + if (!JS_FinishJSONParse(cx, parser, JSVAL_NULL)) + { + wxLogError(_T("ParseJSON failed to finish")); + return AtObj(); + } + + AtObj obj; + obj.p = ConvertNode(cx, vp); + + return obj; +} + +// Convert from a jsval to an AtNode +static AtSmartPtr ConvertNode(JSContext* cx, jsval node) +{ + AtSmartPtr obj (new AtNode()); + + // Non-objects get converted into strings + if (!JSVAL_IS_OBJECT(node)) + { + JSString* str = JS_ValueToString(cx, node); + if (!str) + return obj; // error + size_t valueLen; + const jschar* valueChars = JS_GetStringCharsAndLength(str, &valueLen); + if (!valueChars) + return obj; // error + wxString valueWx(reinterpret_cast(valueChars), wxMBConvUTF16(), valueLen*2); + + obj->value = valueWx.c_str(); + + // Annotate numbers/booleans specially, to allow round-tripping + if (JSVAL_IS_NUMBER(node)) + { + obj->children.insert(AtNode::child_pairtype( + "@number", AtSmartPtr(new AtNode()) + )); + } + else if (JSVAL_IS_BOOLEAN(node)) + { + obj->children.insert(AtNode::child_pairtype( + "@boolean", AtSmartPtr(new AtNode()) + )); + } + + return obj; + } + + JSObject* it = JS_NewPropertyIterator(cx, JSVAL_TO_OBJECT(node)); + if (!it) + return obj; // error + + while (true) + { + jsid idp; + jsval val; + if (! JS_NextProperty(cx, it, &idp) || ! JS_IdToValue(cx, idp, &val)) + return obj; // error + if (val == JSVAL_VOID) + break; // end of iteration + if (! JSVAL_IS_STRING(val)) + continue; // ignore integer properties + + JSString* name = JSVAL_TO_STRING(val); + size_t len = JS_GetStringLength(name); + jschar* chars = JS_GetStringChars(name); + wxString nameWx(reinterpret_cast(chars), wxMBConvUTF16(), len*2); + std::string nameStr(nameWx.ToUTF8().data()); + + jsval vp; + if (!JS_GetPropertyById(cx, JSVAL_TO_OBJECT(node), idp, &vp)) + return obj; // error + + // Unwrap arrays into a special format like <$name>$i0... + // (This assumes arrays aren't nested) + if (JSVAL_IS_OBJECT(vp) && JS_IsArrayObject(cx, JSVAL_TO_OBJECT(vp))) + { + AtSmartPtr child(new AtNode()); + child->children.insert(AtNode::child_pairtype( + "@array", AtSmartPtr(new AtNode()) + )); + + jsuint arrayLength; + if (!JS_GetArrayLength(cx, JSVAL_TO_OBJECT(vp), &arrayLength)) + return obj; // error + + for (jsuint i = 0; i < arrayLength; ++i) + { + jsval val; + if (!JS_GetElement(cx, JSVAL_TO_OBJECT(vp), i, &val)) + return obj; // error + + child->children.insert(AtNode::child_pairtype( + "item", ConvertNode(cx, val) + )); + } + + obj->children.insert(AtNode::child_pairtype( + nameStr, child + )); + } + else + { + obj->children.insert(AtNode::child_pairtype( + nameStr, ConvertNode(cx, vp) + )); + } + } + + return obj; +} + + +jsval BuildJSVal(JSContext* cx, AtNode::Ptr p) +{ + if (!p) + return JSVAL_VOID; + + // Special case for numbers/booleans to allow round-tripping + if (p->children.count("@number")) + { + // Convert to double + std::wstringstream str; + str << p->value; + double val = 0; + str >> val; + + jsval rval; + if (!JS_NewNumberValue(cx, val, &rval)) + return JSVAL_VOID; // error + return rval; + } + else if (p->children.count("@boolean")) + { + bool val = false; + if (p->value == L"true") + val = true; + + return BOOLEAN_TO_JSVAL(val); + } + + // If no children, then use the value string instead + if (p->children.empty()) + { + size_t val16len; + wxCharBuffer val16 = wxMBConvUTF16().cWC2MB(p->value.c_str(), p->value.length(), &val16len); + + JSString* str = JS_NewUCStringCopyN(cx, reinterpret_cast(val16.data()), (uint32)(val16len/2)); + if (!str) + return JSVAL_VOID; // error + return STRING_TO_JSVAL(str); + } + + if (p->children.find("@array") != p->children.end()) + { + JSObject* obj = JS_NewArrayObject(cx, 0, NULL); + if (!obj) + return JSVAL_VOID; // error + + // Find the children + AtNode::child_maptype::const_iterator lower = p->children.lower_bound("item"); + AtNode::child_maptype::const_iterator upper = p->children.upper_bound("item"); + + jsint idx = 0; + for (AtNode::child_maptype::const_iterator it = lower; it != upper; ++it) + { + jsval val = BuildJSVal(cx, it->second); + if (!JS_SetElement(cx, obj, idx, &val)) + return JSVAL_VOID; // error + + ++idx; + } + + return OBJECT_TO_JSVAL(obj); + } + else + { + JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + if (!obj) + return JSVAL_VOID; // error + + for (AtNode::child_maptype::const_iterator it = p->children.begin(); it != p->children.end(); ++it) + { + jsval val = BuildJSVal(cx, it->second); + if (!JS_SetProperty(cx, obj, it->first.c_str(), &val)) + return JSVAL_VOID; // error + } + + return OBJECT_TO_JSVAL(obj); + } +} + +struct Stringifier +{ + static JSBool callback(const jschar* buf, uint32 len, void* data) + { + wxString textWx(reinterpret_cast(buf), wxMBConvUTF16(), len*2); + std::string textStr(textWx.ToUTF8().data()); + + static_cast(data)->stream << textStr; + return JS_TRUE; + } + + std::stringstream stream; +}; + +std::string AtlasObject::SaveToJSON(JSContext* cx, AtObj& obj) +{ + jsval root = BuildJSVal(cx, obj.p); + + Stringifier str; + if (!JS_Stringify(cx, &root, NULL, JSVAL_VOID, &Stringifier::callback, &str)) + { + wxLogError(_T("SaveToJSON failed")); + return ""; + } + + return str.stream.str(); +} diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.cpp b/source/tools/atlas/AtlasScript/ScriptInterface.cpp index 1ce19f81ce..1cc075397f 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.cpp +++ b/source/tools/atlas/AtlasScript/ScriptInterface.cpp @@ -609,6 +609,8 @@ AtlasScriptInterface_impl::AtlasScriptInterface_impl() | JSOPTION_XML // "ECMAScript for XML support: parse as a token" ); + JS_SetVersion(m_cx, JSVERSION_LATEST); + m_glob = JS_NewGlobalObject(m_cx, &global_class); JS_InitStandardClasses(m_cx, m_glob); diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.h b/source/tools/atlas/AtlasScript/ScriptInterface.h index e0df128a1b..7f5c49da13 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.h +++ b/source/tools/atlas/AtlasScript/ScriptInterface.h @@ -80,8 +80,9 @@ public: bool AddRoot(jsval* ptr); bool RemoveRoot(jsval* ptr); -private: JSContext* GetContext(); + +private: bool SetValue_(const wxString& name, jsval val); bool GetValue_(const wxString& name, jsval& ret); bool CallFunction_(jsval val, const char* name, std::vector& args, jsval& ret); diff --git a/source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.cpp b/source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.cpp deleted file mode 100644 index 71c7e1f355..0000000000 --- a/source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* 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 "NewDialog.h" - -#include "AtlasScript/ScriptInterface.h" -#include "General/Datafile.h" - - -enum { - ID_CH_SIZE = wxID_HIGHEST+1, - ID_SP_HEIGHT -}; - -IMPLEMENT_CLASS(NewDialog, wxDialog) - -BEGIN_EVENT_TABLE(NewDialog, wxDialog) - EVT_CHOICE(ID_CH_SIZE, NewDialog::OnSizeChange) - EVT_SPINCTRL(ID_SP_HEIGHT, NewDialog::OnHeightChange) -END_EVENT_TABLE() - - -NewDialog::NewDialog(wxWindow* parent, const wxString& title, const wxSize& size, ScenarioEditor& scenarioEditor) - : wxDialog(parent, -1, title, wxDefaultPosition, size, - wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU | wxCLOSE_BOX) -{ - wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); - SetSizer(mainSizer); - - m_SelectedSize = 0; - m_BaseHeight = 0; - - m_Panel = new wxPanel(this); - mainSizer->Add(m_Panel, wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT, 5)); - - // Get available map sizes - AtObj sizes(Datafile::ReadList("mapsizes")); - for (AtIter s = sizes["size"]; s.defined(); ++s) - { - if (s["@name"].defined() && s["@tiles"].defined()) - { - m_SizeArray.Add(wxString(s["@name"])); - - size_t size; - std::wstringstream stream; - stream << (std::wstring)s["@tiles"]; - stream >> size; - - m_TilesArray.push_back(size); - } - } - - // Map size - wxBoxSizer* mapSizeSizer = new wxBoxSizer(wxHORIZONTAL); - mainSizer->Add(mapSizeSizer, wxSizerFlags().Expand().Align(wxALIGN_TOP|wxALIGN_RIGHT).Border(wxALL, 5)); - - mapSizeSizer->Add(new wxStaticText(this, wxID_ANY, _("Choose map size")), wxSizerFlags().Border(wxRIGHT, 25)); - wxChoice* sizeCtrl = new wxChoice(this, ID_CH_SIZE, wxDefaultPosition, wxDefaultSize, m_SizeArray); - sizeCtrl->SetSelection(m_SelectedSize); - mapSizeSizer->Add(sizeCtrl, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); - - // Base height - wxBoxSizer* baseHeightSizer = new wxBoxSizer(wxHORIZONTAL); - mainSizer->Add(baseHeightSizer, wxSizerFlags().Expand().Align(wxALIGN_TOP|wxALIGN_RIGHT).Border(wxALL, 5)); - - baseHeightSizer->Add(new wxStaticText(this, wxID_ANY, _("Choose base height")), wxSizerFlags().Border(wxRIGHT, 25)); - wxSpinCtrl* heightCtrl = new wxSpinCtrl(this, ID_SP_HEIGHT, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 65535); - baseHeightSizer->Add(heightCtrl, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); - - // Map base terrain - wxBoxSizer* mapTerrainSizer = new wxBoxSizer(wxVERTICAL); - mainSizer->Add(mapTerrainSizer, wxSizerFlags().Proportion(1).Expand().Border(wxALL, 5)); - - mapTerrainSizer->Add(new wxStaticText(this, wxID_ANY, _("Choose map terrain")), wxSizerFlags().Border(wxRIGHT, 25)); - wxPanel* terrainPanel = scenarioEditor.GetScriptInterface().LoadScriptAsPanel(_T("terrainpreview"), this); - mapTerrainSizer->Add(terrainPanel, wxSizerFlags().Proportion(1).Expand().Align(wxALIGN_BOTTOM|wxALIGN_RIGHT).Border(wxALL, 5)); - - // OK/Cancel buttons - wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL); - mainSizer->Add(buttonSizer, wxSizerFlags().Expand().Align(wxALIGN_RIGHT).Border(wxALL, 5)); - - buttonSizer->Add(new wxButton(this, wxID_OK, _("OK")), wxSizerFlags().Border(wxRIGHT, 25)); - buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("Cancel")), wxSizerFlags().Border(wxRIGHT, 5)); - -} - -void NewDialog::OnSizeChange(wxCommandEvent& event) -{ - m_SelectedSize = (size_t)event.GetSelection(); -} - -void NewDialog::OnHeightChange(wxSpinEvent& event) -{ - m_BaseHeight = (size_t)event.GetSelection(); -} - -size_t NewDialog::GetSelectedSize() -{ - return m_TilesArray[m_SelectedSize]; -} - -size_t NewDialog::GetBaseHeight() -{ - return m_BaseHeight; -} diff --git a/source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.h b/source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.h deleted file mode 100644 index a9bdbc8628..0000000000 --- a/source/tools/atlas/AtlasUI/CustomControls/NewDialog/NewDialog.h +++ /dev/null @@ -1,53 +0,0 @@ -/* 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_NEWDIALOG -#define INCLUDED_NEWDIALOG - -#include "ScenarioEditor/ScenarioEditor.h" - -#include "wx/dialog.h" -#include "wx/spinctrl.h" - -class NewDialog : public wxDialog -{ - DECLARE_CLASS(NewDialog) - DECLARE_EVENT_TABLE() - -public: - NewDialog(wxWindow* parent, const wxString& title, const wxSize& size, ScenarioEditor& scenarioEditor); - virtual ~NewDialog() {} - - size_t GetSelectedSize(); - size_t GetBaseHeight(); - -protected: - wxPanel* m_Panel; - -private: - void OnSizeChange(wxCommandEvent& event); - void OnHeightChange(wxSpinEvent& event); - - size_t m_SelectedSize; - size_t m_BaseHeight; - - wxArrayString m_SizeArray; - std::vector m_TilesArray; - -}; - -#endif // INCLUDED_NEWDIALOG diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp index fc36e4f506..144334f774 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -34,7 +34,6 @@ #include "CustomControls/HighResTimer/HighResTimer.h" #include "CustomControls/Buttons/ToolButton.h" #include "CustomControls/Canvas/Canvas.h" -#include "CustomControls/NewDialog/NewDialog.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" @@ -590,12 +589,12 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac // a valid map loaded) POST_MESSAGE(LoadMap, (_T("_default"))); + // Select the initial sidebar (after the map has loaded) + m_SectionLayout.SelectPage(_T("MapSidebar")); + // Wait for blank map qry.Post(); - // Notify UI scripts that map settings have been loaded - m_ScriptInterface.Eval(_T("Atlas.State.mapSettings.notifyObservers()")); - POST_MESSAGE(RenderEnable, (eRenderView::GAME)); // Set up a timer to make sure tool-updates happen frequently (in addition @@ -682,54 +681,11 @@ void ScenarioEditor::OnRedo(wxCommandEvent&) void ScenarioEditor::OnNew(wxCommandEvent& WXUNUSED(event)) { - NewDialog dlg(NULL, _("Create new map"), wxSize(600, 400), *this); - - if(dlg.ShowModal() == wxID_OK) - { - wxBusyInfo busy(_("Creating blank map")); - - // Generate new blank map - size_t tiles = dlg.GetSelectedSize(); - size_t height = dlg.GetBaseHeight(); - - // Get terrain texture - // TODO: Support choosing multiple textures - std::vector textures; - textures.push_back(g_SelectedTexture); - - // TODO: This seems like a nasty way to do this - std::string settings; - m_ScriptInterface.SetValue(_T("Atlas.State.mapSettings.settings.Size"), tiles); - m_ScriptInterface.SetValue(_T("Atlas.State.mapSettings.settings.Seed"), 0); - m_ScriptInterface.SetValue(_T("Atlas.State.mapSettings.settings.BaseTerrain"), textures); - m_ScriptInterface.SetValue(_T("Atlas.State.mapSettings.settings.BaseHeight"), height); - m_ScriptInterface.Eval(_T("JSON.stringify(Atlas.State.mapSettings.settings)"), settings); - - // Generate map - qGenerateMap qry(L"blank.js", settings); - - - - // Wait for map generation to finish - qry.Post(); - - if (qry.status < 0) - { // Map generation failed - wxMessageDialog msgDlg(NULL, _T("Random map script 'blank.js'. Loading blank map."), _T("Error"), wxICON_ERROR); - - qPing pingQry; - POST_MESSAGE(LoadMap, (_T("_default"))); - - // Wait for blank map - pingQry.Post(); - } - - // Notify UI scripts that map settings have been loaded - m_ScriptInterface.Eval(_T("Atlas.State.mapSettings.notifyObservers()")); - } + if (wxMessageBox(_("Discard current map and start blank new map?"), _("New map"), wxOK|wxCANCEL|wxICON_QUESTION, this) == wxOK) + OpenFile(_T("_default"), _T("")); } -void ScenarioEditor::OpenFile(const wxString& name) +void ScenarioEditor::OpenFile(const wxString& name, const wxString& filename) { wxBusyInfo busy(_("Loading map")); wxBusyCursor busyc; @@ -744,14 +700,13 @@ void ScenarioEditor::OpenFile(const wxString& name) POST_MESSAGE(LoadMap, (map)); - SetOpenFilename(name); + SetOpenFilename(filename); // Wait for it to load, while the wxBusyInfo is telling the user that we're doing that qPing qry; qry.Post(); - // Notify UI scripts that map settings have been loaded - m_ScriptInterface.Eval(_T("Atlas.State.mapSettings.notifyObservers()")); + NotifyOnMapReload(); // TODO: Make this a non-undoable command } @@ -768,7 +723,7 @@ void ScenarioEditor::OnOpen(wxCommandEvent& WXUNUSED(event)) wxString cwd = wxFileName::GetCwd(); if (dlg.ShowModal() == wxID_OK) - OpenFile(dlg.GetFilename()); + OpenFile(dlg.GetFilename(), dlg.GetFilename()); wxCHECK_RET(cwd == wxFileName::GetCwd(), _T("cwd changed")); // paranoia - MSDN says OFN_NOCHANGEDIR (used when we don't give wxCHANGE_DIR) @@ -782,7 +737,7 @@ void ScenarioEditor::OnMRUFile(wxCommandEvent& event) wxString filename(m_FileHistory.GetHistoryFile(event.GetId() - wxID_FILE1)); if (filename.Len()) { - OpenFile(filename); + OpenFile(filename, filename); } else { //Remove from MRU @@ -807,22 +762,11 @@ void ScenarioEditor::OnSave(wxCommandEvent& event) // the preview units.) m_ToolManager.SetCurrentTool(_T("")); - qPing qry; - - // Save map settings - std::string settings; - if (m_ScriptInterface.Eval(_T("JSON.stringify(Atlas.State.mapSettings.settings)"), settings)) - { - POST_MESSAGE(SetMapSettings, (settings)); - - // Wait for it to finish saving - qry.Post(); - } - std::wstring map = m_OpenFilename.c_str(); POST_MESSAGE(SaveMap, (map)); // Wait for it to finish saving + qPing qry; qry.Post(); } } @@ -840,18 +784,6 @@ void ScenarioEditor::OnSaveAs(wxCommandEvent& WXUNUSED(event)) m_ToolManager.SetCurrentTool(_T("")); - qPing qry; - - // Save map settings - std::string settings; - if (m_ScriptInterface.Eval(_T("JSON.stringify(Atlas.State.mapSettings.settings)"), settings)) - { - POST_MESSAGE(SetMapSettings, (settings)); - - // Wait for it to finish saving - qry.Post(); - } - // TODO: Work when the map is not in .../maps/scenarios/ std::wstring map = dlg.GetFilename().c_str(); POST_MESSAGE(SaveMap, (map)); @@ -859,6 +791,7 @@ void ScenarioEditor::OnSaveAs(wxCommandEvent& WXUNUSED(event)) SetOpenFilename(dlg.GetFilename()); // Wait for it to finish saving + qPing qry; qry.Post(); } } @@ -874,6 +807,11 @@ void ScenarioEditor::SetOpenFilename(const wxString& filename) m_FileHistory.AddFileToHistory(filename); } +void ScenarioEditor::NotifyOnMapReload() +{ + m_SectionLayout.OnMapReload(); +} + ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OnWireframe(wxCommandEvent& event) diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h index db9c92989c..bc84c4ee7b 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h @@ -54,7 +54,9 @@ public: void OnRenderPath(wxCommandEvent& event); void OnDumpState(wxCommandEvent& event); - void OpenFile(const wxString& name); + void OpenFile(const wxString& name, const wxString& filename); + + void NotifyOnMapReload(); static AtlasWindowCommandProc& GetCommandProc(); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp index 1f8f002737..44aab7bfa7 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp @@ -131,10 +131,6 @@ public: sidebar->Show(false); - // If this is the first page, make it selected by default - if (m_Pages.size() == 1) - SetSelection(0); - return true; } @@ -186,6 +182,12 @@ public: } } + void OnMapReload() + { + for (size_t i = 0; i < m_Pages.size(); ++i) + m_Pages[i].bar->OnMapReload(); + } + protected: void OnPageChanged(SidebarPage oldPage, SidebarPage newPage) @@ -314,9 +316,8 @@ void SectionLayout::Build(ScenarioEditor& scenarioEditor) sidebar->GetBottomBar()->Show(false); \ m_SidebarBook->AddPage(sidebar, icon, tooltip); \ m_PageMappings.insert(std::make_pair(name, (int)m_SidebarBook->GetPageCount()-1)); - - ADD_SIDEBAR_SCRIPT(_T("map"), _T("map.png"), _("Map")); -// ADD_SIDEBAR(MapSidebar, _T("map.png"), _("Map")); + + ADD_SIDEBAR(MapSidebar, _T("map.png"), _("Map")); ADD_SIDEBAR(TerrainSidebar, _T("terrain.png"), _("Terrain")); ADD_SIDEBAR(ObjectSidebar, _T("object.png"), _("Object")); ADD_SIDEBAR(EnvironmentSidebar, _T("environment.png"), _("Environment")); @@ -339,3 +340,8 @@ void SectionLayout::SelectPage(const wxString& classname) if (it != m_PageMappings.end()) m_SidebarBook->SetSelection(it->second); } + +void SectionLayout::OnMapReload() +{ + m_SidebarBook->OnMapReload(); +} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h index 96fa2f8354..cb8d128d4f 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h @@ -39,6 +39,8 @@ public: void SelectPage(const wxString& classname); + void OnMapReload(); + private: SidebarBook* m_SidebarBook; wxWindow* m_Canvas; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h index d34d11f66c..95216ad624 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h @@ -30,6 +30,8 @@ public: wxWindow* GetBottomBar() { return m_BottomBar; } + virtual void OnMapReload() {} + protected: ScenarioEditor& m_ScenarioEditor; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp index a975fd6df3..91f2fe6d18 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.cpp @@ -258,5 +258,13 @@ void EnvironmentSidebar::OnFirstDisplay() g_EnvironmentSettings.NotifyObservers(); - // TODO: reupdate everything when loading a new map... +} + +void EnvironmentSidebar::OnMapReload() +{ + AtlasMessage::qGetEnvironmentSettings qry_env; + qry_env.Post(); + g_EnvironmentSettings = qry_env.settings; + + g_EnvironmentSettings.NotifyObservers(); } diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h index 9aed4a9e1a..f51a4babd3 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Environment/Environment.h @@ -26,6 +26,8 @@ class EnvironmentSidebar : public Sidebar public: EnvironmentSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer); + virtual void OnMapReload(); + protected: virtual void OnFirstDisplay(); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp index a2282e3e14..d11248b96f 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp @@ -21,15 +21,28 @@ #include "General/Datafile.h" #include "ScenarioEditor/Tools/Common/Tools.h" +#include "ScenarioEditor/ScenarioEditor.h" +#include "AtlasScript/ScriptInterface.h" #include "GameInterface/Messages.h" +#include "wx/busyinfo.h" #include "wx/filename.h" enum { - ID_GenerateMap, - ID_GenerateRMS, + ID_MapName, + ID_MapDescription, + ID_MapReveal, + ID_MapType, + ID_MapNumPlayers, + ID_MapKW_Demo, + ID_MapKW_Hidden, + ID_RandomScript, + ID_RandomSize, + ID_RandomSeed, + ID_RandomReseed, + ID_RandomGenerate, ID_SimPlay, ID_SimFast, ID_SimSlow, @@ -47,20 +60,303 @@ enum }; bool IsPlaying(int s) { return (s == SimPlaying || s == SimPlayingFast || s == SimPlayingSlow); } +// Helper function for adding tooltips +static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) +{ + window->SetToolTip(tip); + return window; +} + +// Helper class for storing AtObjs +class AtObjClientData : public wxClientData +{ +public: + AtObjClientData(const AtObj& obj) : obj(obj) {} + virtual ~AtObjClientData() {} + AtObj GetValue() { return obj; } +private: + AtObj obj; +}; + +// TODO: Some of these helper things should be moved out of this file +// and into shared locations + +class MapSettings : public wxPanel +{ +public: + MapSettings(wxWindow* parent, ScenarioEditor& scenarioEditor); + void CreateWidgets(); + void ReadFromEngine(); + AtObj UpdateSettingsObject(); +private: + void SendToEngine(); + + void OnEdit(wxCommandEvent& WXUNUSED(evt)) + { + SendToEngine(); + } + + void OnEditSpin(wxSpinEvent& WXUNUSED(evt)) + { + SendToEngine(); + } + + static const size_t MAX_NUM_PLAYERS = 8; + + AtObj m_MapSettings; + std::set m_MapSettingsKeywords; + + std::vector m_PlayerCivChoices; + + ScenarioEditor& m_ScenarioEditor; + + DECLARE_EVENT_TABLE(); +}; + +BEGIN_EVENT_TABLE(MapSettings, wxPanel) + EVT_TEXT(ID_MapName, MapSettings::OnEdit) + EVT_TEXT(ID_MapDescription, MapSettings::OnEdit) + EVT_CHECKBOX(wxID_ANY, MapSettings::OnEdit) + EVT_CHOICE(wxID_ANY, MapSettings::OnEdit) + EVT_SPINCTRL(ID_MapNumPlayers, MapSettings::OnEditSpin) +END_EVENT_TABLE(); + +MapSettings::MapSettings(wxWindow* parent, ScenarioEditor& scenarioEditor) + : wxPanel(parent, wxID_ANY), m_ScenarioEditor(scenarioEditor) +{ + wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Map settings")); + SetSizer(sizer); +} + +void MapSettings::CreateWidgets() +{ + wxSizer* sizer = GetSizer(); + + wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); + nameSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); + nameSizer->Add(8, 0); + nameSizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapName), + _("Displayed name of the map")), wxSizerFlags().Proportion(1)); + sizer->Add(nameSizer, wxSizerFlags().Expand()); + + sizer->Add(0, 2); + + sizer->Add(new wxStaticText(this, wxID_ANY, _("Description"))); + sizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapDescription, wxEmptyString, wxDefaultPosition, wxSize(-1, 100), wxTE_MULTILINE), + _("Short description used on the map selection screen")), wxSizerFlags().Expand()); + + wxArrayString gameTypes; + gameTypes.Add(_T("conquest")); + gameTypes.Add(_T("endless")); + + wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2); + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Reveal map")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + gridSizer->Add(new wxCheckBox(this, ID_MapReveal, wxEmptyString)); + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Game type")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + gridSizer->Add(new wxChoice(this, ID_MapType, wxDefaultPosition, wxDefaultSize, gameTypes)); + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Num players")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(this, ID_MapNumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1)); + numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS); + numPlayersSpin->SetValue(MAX_NUM_PLAYERS); + gridSizer->Add(numPlayersSpin); + + sizer->Add(gridSizer); + + + wxArrayString civNames; + wxArrayString civCodes; + AtlasMessage::qGetCivData qryCiv; + qryCiv.Post(); + std::vector civData = *qryCiv.data; + for (size_t i = 0; i < civData.size(); ++i) + { + AtObj civ = AtlasObject::LoadFromJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), civData[i]); + civNames.Add(wxString(civ["Name"])); + civCodes.Add(wxString(civ["Code"])); + } + + wxCollapsiblePane* playersPane = new wxCollapsiblePane(this, wxID_ANY, _("Player settings"), wxDefaultPosition, wxDefaultSize, wxCP_NO_TLW_RESIZE); + wxFlexGridSizer* playersPaneSizer = new wxFlexGridSizer(2); + playersPaneSizer->Add(new wxStaticText(playersPane->GetPane(), wxID_ANY, _T(""))); + playersPaneSizer->Add(new wxStaticText(playersPane->GetPane(), wxID_ANY, _("Civ"))); + for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i) + { + wxString idStr; + idStr << (i+1); + playersPaneSizer->Add(new wxStaticText(playersPane->GetPane(), wxID_ANY, idStr), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + wxChoice* civChoice = new wxChoice(playersPane->GetPane(), wxID_ANY); + for (size_t j = 0; j < civNames.Count(); ++j) + civChoice->Append(civNames[j], new wxStringClientData(civCodes[j])); + m_PlayerCivChoices.push_back(civChoice); + playersPaneSizer->Add(civChoice); + + // TODO: Team + // TODO: Resources? + } + playersPane->GetPane()->SetSizer(playersPaneSizer); + sizer->Add(playersPane, wxSizerFlags().Expand()); + + wxStaticBoxSizer* keywordsSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Keywords")); + wxFlexGridSizer* kwGridSizer = new wxFlexGridSizer(2); + kwGridSizer->Add(new wxStaticText(this, wxID_ANY, _("Demo")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + kwGridSizer->Add(new wxCheckBox(this, ID_MapKW_Demo, wxEmptyString)); + kwGridSizer->Add(new wxStaticText(this, wxID_ANY, _("Hidden")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + kwGridSizer->Add(new wxCheckBox(this, ID_MapKW_Hidden, wxEmptyString)); + keywordsSizer->Add(kwGridSizer); + sizer->Add(keywordsSizer, wxSizerFlags().Expand()); +} + +void MapSettings::ReadFromEngine() +{ + AtlasMessage::qGetMapSettings qry; + qry.Post(); + m_MapSettings = AtlasObject::LoadFromJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), *qry.settings); + + m_MapSettingsKeywords.clear(); + for (AtIter keyword = m_MapSettings["Keywords"]["item"]; keyword.defined(); ++keyword) + m_MapSettingsKeywords.insert(std::wstring(keyword)); + + wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Name"])); + + wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Description"])); + + wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->SetValue(wxString(m_MapSettings["RevealMap"]) == L"true"); + + wxDynamicCast(FindWindow(ID_MapType), wxChoice)->SetStringSelection(wxString(m_MapSettings["GameType"])); + + size_t numPlayers = m_MapSettings["PlayerData"]["item"].count(); + wxDynamicCast(FindWindow(ID_MapNumPlayers), wxSpinCtrl)->SetValue(numPlayers); + + AtIter player = m_MapSettings["PlayerData"]["item"]; + for (size_t i = 0; i < numPlayers && i < MAX_NUM_PLAYERS; ++i, ++player) + { + wxChoice* choice = m_PlayerCivChoices[i]; + choice->Enable(true); + wxString civCode(player["Civ"]); + for (size_t j = 0; j < choice->GetCount(); ++j) + { + wxStringClientData* str = dynamic_cast(choice->GetClientObject(j)); + if (str->GetData() == civCode) + { + choice->SetSelection(j); + break; + } + } + } + for (size_t i = numPlayers; i < MAX_NUM_PLAYERS; ++i) + { + wxChoice* choice = m_PlayerCivChoices[i]; + choice->SetSelection(0); + choice->Enable(false); + } + + wxDynamicCast(FindWindow(ID_MapKW_Demo), wxCheckBox)->SetValue(m_MapSettingsKeywords.count(L"demo") != 0); + wxDynamicCast(FindWindow(ID_MapKW_Hidden), wxCheckBox)->SetValue(m_MapSettingsKeywords.count(L"hidden") != 0); +} + +AtObj MapSettings::UpdateSettingsObject() +{ + m_MapSettings.set("Name", wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->GetValue()); + + m_MapSettings.set("Description", wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->GetValue()); + + m_MapSettings.setBool("RevealMap", wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->GetValue()); + + m_MapSettings.set("MapType", wxDynamicCast(FindWindow(ID_MapType), wxChoice)->GetStringSelection()); + + AtIter oldPlayer = m_MapSettings["PlayerData"]["item"]; + AtObj players; + players.set("@array", L""); + size_t numPlayers = (size_t)wxDynamicCast(FindWindow(ID_MapNumPlayers), wxSpinCtrl)->GetValue(); + for (size_t i = 0; i < numPlayers && i < MAX_NUM_PLAYERS; ++i) + { + wxChoice* choice = m_PlayerCivChoices[i]; + choice->Enable(true); + AtObj player = *oldPlayer; + if (choice->GetSelection() >= 0) + { + wxStringClientData* str = dynamic_cast(choice->GetClientObject(choice->GetSelection())); + player.set("Civ", str->GetData()); + } + players.add("item", player); + if (oldPlayer.defined()) + ++oldPlayer; + } + for (size_t i = numPlayers; i < MAX_NUM_PLAYERS; ++i) + { + wxChoice* choice = m_PlayerCivChoices[i]; + choice->Enable(false); + } + m_MapSettings.set("PlayerData", players); + + if (wxDynamicCast(FindWindow(ID_MapKW_Demo), wxCheckBox)->GetValue()) + m_MapSettingsKeywords.insert(L"demo"); + else + m_MapSettingsKeywords.erase(L"demo"); + + if (wxDynamicCast(FindWindow(ID_MapKW_Hidden), wxCheckBox)->GetValue()) + m_MapSettingsKeywords.insert(L"hidden"); + else + m_MapSettingsKeywords.erase(L"hidden"); + + AtObj keywords; + keywords.set("@array", L""); + for (std::set::iterator it = m_MapSettingsKeywords.begin(); it != m_MapSettingsKeywords.end(); ++it) + keywords.add("item", it->c_str()); + m_MapSettings.set("Keywords", keywords); + + return m_MapSettings; +} + +void MapSettings::SendToEngine() +{ + UpdateSettingsObject(); + + std::string json = AtlasObject::SaveToJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), m_MapSettings); + + // TODO: would be nice if we supported undo for settings changes + + POST_MESSAGE(SetMapSettings, (json)); +} + + MapSidebar::MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_SimState(SimInactive) { - // TODO: Less ugliness - // TODO: Intercept arrow keys and send them to the GL window - - m_MainSizer->Add(new wxButton(this, ID_GenerateMap, _("Generate empty map"))); + m_MapSettings = new MapSettings(this, m_ScenarioEditor); + m_MainSizer->Add(m_MapSettings, wxSizerFlags().Expand()); { - wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); - m_RMSText = new wxTextCtrl(this, wxID_ANY, _T("cantabrian_highlands")); - sizer->Add(m_RMSText); - sizer->Add(new wxButton(this, ID_GenerateRMS, _("Generate RMS"))); - m_MainSizer->Add(sizer); + wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Random map")); + + sizer->Add(new wxChoice(this, ID_RandomScript), wxSizerFlags().Expand()); + + wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2); + gridSizer->AddGrowableCol(1); + + wxChoice* sizeChoice = new wxChoice(this, ID_RandomSize); + AtObj sizes(Datafile::ReadList("mapsizes")); + for (AtIter s = sizes["size"]; s.defined(); ++s) + { + long tiles = 0; + wxString(s["@tiles"]).ToLong(&tiles); + sizeChoice->Append(wxString(s["@name"]), (void*)(intptr_t)tiles); + } + sizeChoice->SetSelection(0); + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Map size")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + gridSizer->Add(sizeChoice, wxSizerFlags().Expand()); + + gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Random seed")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + wxBoxSizer* seedSizer = new wxBoxSizer(wxHORIZONTAL); + seedSizer->Add(new wxTextCtrl(this, ID_RandomSeed, _T("0")), wxSizerFlags(1).Expand()); + seedSizer->Add(new wxButton(this, ID_RandomReseed, _("R"), wxDefaultPosition, wxSize(24, -1))); + gridSizer->Add(seedSizer, wxSizerFlags().Expand()); + + sizer->Add(gridSizer, wxSizerFlags().Expand()); + + sizer->Add(new wxButton(this, ID_RandomGenerate, _("Generate map")), wxSizerFlags().Expand()); + + m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 16)); } { @@ -75,25 +371,38 @@ MapSidebar::MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContaine } } -void MapSidebar::GenerateMap(wxCommandEvent& WXUNUSED(event)) +void MapSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt)) { -// qGenerateMap qry(); -// qry.Post(); -// POST_MESSAGE(GenerateMap, (9)); + // Toggling the collapsing doesn't seem to update the sidebar layout + // automatically, so do it explicitly here + Layout(); } -void MapSidebar::GenerateRMS(wxCommandEvent& WXUNUSED(event)) +void MapSidebar::OnFirstDisplay() { - wxChar* argv[] = { _T("rmgen.exe"), 0, _T("_atlasrm"), 0 }; - wxString scriptName = m_RMSText->GetValue(); - argv[1] = const_cast(scriptName.c_str()); + m_MapSettings->CreateWidgets(); + m_MapSettings->ReadFromEngine(); - wxString cwd = wxFileName::GetCwd(); - wxFileName::SetCwd(Datafile::GetDataDirectory()); - wxExecute(argv, wxEXEC_SYNC); - wxFileName::SetCwd(cwd); + // Load the RMS script list: - POST_MESSAGE(LoadMap, (L"_atlasrm.pmp")); + AtlasMessage::qGetRMSData qry; + qry.Post(); + std::vector scripts = *qry.data; + + wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice); + scriptChoice->Clear(); + for (size_t i = 0; i < scripts.size(); ++i) + { + AtObj data = AtlasObject::LoadFromJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), scripts[i]); + wxString name(data["settings"]["Name"]); + scriptChoice->Append(name, new AtObjClientData(*data["settings"])); + } + scriptChoice->SetSelection(0); +} + +void MapSidebar::OnMapReload() +{ + m_MapSettings->ReadFromEngine(); } void MapSidebar::UpdateSimButtons() @@ -139,6 +448,7 @@ void MapSidebar::OnSimPlay(wxCommandEvent& event) if (m_SimState == SimInactive) { POST_MESSAGE(SimStateSave, (L"default")); + POST_MESSAGE(GuiSwitchPage, (L"page_session.xml")); POST_MESSAGE(SimPlay, (speed)); m_SimState = newState; } @@ -166,22 +476,77 @@ void MapSidebar::OnSimReset(wxCommandEvent& WXUNUSED(event)) { POST_MESSAGE(SimPlay, (0.f)); POST_MESSAGE(SimStateRestore, (L"default")); + POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; } else if (m_SimState == SimPaused) { POST_MESSAGE(SimStateRestore, (L"default")); + POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; } UpdateSimButtons(); } +void MapSidebar::OnRandomReseed(wxCommandEvent& WXUNUSED(evt)) +{ + // Pick a shortish randomish value + wxString seed; + seed << (int)floor((rand() / (float)RAND_MAX) * 10000.f); + wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->SetValue(seed); +} + +void MapSidebar::OnRandomGenerate(wxCommandEvent& WXUNUSED(evt)) +{ + wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice); + + if (scriptChoice->GetSelection() < 0) + return; + + // TODO: this settings thing seems a bit of a mess, + // since it's mixing data from three different sources + + AtObj settings = m_MapSettings->UpdateSettingsObject(); + + AtObj scriptSettings = dynamic_cast(scriptChoice->GetClientObject(scriptChoice->GetSelection()))->GetValue(); + + settings.addOverlay(scriptSettings); + + wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice); + wxString size; + size << (intptr_t)sizeChoice->GetClientData(sizeChoice->GetSelection()); + AtObj sizeObj; + sizeObj.setString(size); + sizeObj.set("@number", L""); + settings.set("Size", sizeObj); + + AtObj seedObj; + seedObj.setString(wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->GetValue()); + seedObj.set("@number", L""); + settings.set("Seed", seedObj); + + std::string json = AtlasObject::SaveToJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), settings); + + wxBusyInfo(_("Generating map")); + + wxString scriptName(settings["Script"]); + + AtlasMessage::qGenerateMap qry(scriptName.c_str(), json); + qry.Post(); + + if (qry.status < 0) + wxLogError(_("Random map script '%ls' failed"), scriptName.c_str()); + + m_ScenarioEditor.NotifyOnMapReload(); +} + BEGIN_EVENT_TABLE(MapSidebar, Sidebar) - EVT_BUTTON(ID_GenerateMap, MapSidebar::GenerateMap) - EVT_BUTTON(ID_GenerateRMS, MapSidebar::GenerateRMS) + EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, MapSidebar::OnCollapse) EVT_BUTTON(ID_SimPlay, MapSidebar::OnSimPlay) EVT_BUTTON(ID_SimFast, MapSidebar::OnSimPlay) EVT_BUTTON(ID_SimSlow, MapSidebar::OnSimPlay) EVT_BUTTON(ID_SimPause, MapSidebar::OnSimPause) EVT_BUTTON(ID_SimReset, MapSidebar::OnSimReset) + EVT_BUTTON(ID_RandomReseed, MapSidebar::OnRandomReseed) + EVT_BUTTON(ID_RandomGenerate, MapSidebar::OnRandomGenerate) END_EVENT_TABLE(); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h index 1dc82a64d3..de1996577a 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h @@ -17,20 +17,29 @@ #include "../Common/Sidebar.h" +#include "wx/collpane.h" + +class MapSettings; + class MapSidebar : public Sidebar { public: MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer); + virtual void OnMapReload(); + +protected: + virtual void OnFirstDisplay(); + private: - void GenerateMap(wxCommandEvent& event); - void GenerateRMS(wxCommandEvent& event); + MapSettings* m_MapSettings; - wxTextCtrl* m_RMSText; - - void OnSimPlay(wxCommandEvent& event); - void OnSimPause(wxCommandEvent& event); - void OnSimReset(wxCommandEvent& event); + void OnCollapse(wxCollapsiblePaneEvent& evt); + void OnSimPlay(wxCommandEvent& evt); + void OnSimPause(wxCommandEvent& evt); + void OnSimReset(wxCommandEvent& evt); + void OnRandomReseed(wxCommandEvent& evt); + void OnRandomGenerate(wxCommandEvent& evt); void UpdateSimButtons(); int m_SimState; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp index f524213719..7bb02f58bf 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp @@ -46,7 +46,7 @@ enum }; // Helper function for adding tooltips -wxWindow* Tooltipped(wxWindow* window, const wxString& tip) +static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) { window->SetToolTip(tip); return window; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp index 4b55b9954f..91c72d5ea4 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp @@ -49,18 +49,29 @@ private: enum { ID_Passability = 1, - ID_ShowPriorities + ID_ShowPriorities, + ID_ResizeMap }; TerrainSidebar::TerrainSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer) { { - wxSizer* sizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Elevation tools")); - sizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Modify"), _T("AlterElevation")), wxSizerFlags().Proportion(1)); - sizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Smooth"), _T("SmoothElevation")), wxSizerFlags().Proportion(1)); - sizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Flatten"), _T("FlattenElevation")), wxSizerFlags().Proportion(1)); - sizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Paint"), _T("PaintTerrain")), wxSizerFlags().Proportion(1)); + wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Elevation tools")); + wxSizer* gridSizer = new wxGridSizer(3); + gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Modify"), _T("AlterElevation")), wxSizerFlags().Expand()); + gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Smooth"), _T("SmoothElevation")), wxSizerFlags().Expand()); + gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Flatten"), _T("FlattenElevation")), wxSizerFlags().Expand()); + sizer->Add(gridSizer, wxSizerFlags().Expand()); + m_MainSizer->Add(sizer, wxSizerFlags().Expand()); + } + + { + wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Texture tools")); + wxSizer* gridSizer = new wxGridSizer(3); + gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Paint"), _T("PaintTerrain")), wxSizerFlags().Expand()); + gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Replace"), _T("ReplaceTerrain")), wxSizerFlags().Expand()); + sizer->Add(gridSizer, wxSizerFlags().Expand()); m_MainSizer->Add(sizer, wxSizerFlags().Expand()); } @@ -88,6 +99,12 @@ TerrainSidebar::TerrainSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebar visSizer->Add(new wxCheckBox(this, ID_ShowPriorities, _(""))); } + { + wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Misc tools")); + sizer->Add(new wxButton(this, ID_ResizeMap, _("Resize map")), wxSizerFlags().Expand()); + m_MainSizer->Add(sizer, wxSizerFlags().Expand()); + } + m_BottomBar = new TerrainBottomBar(scenarioEditor, bottomBarContainer); } @@ -115,9 +132,36 @@ void TerrainSidebar::OnShowPriorities(wxCommandEvent& evt) POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked())); } +void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) +{ + wxArrayString sizeNames; + std::vector sizeTiles; + + AtObj sizes(Datafile::ReadList("mapsizes")); + for (AtIter s = sizes["size"]; s.defined(); ++s) + { + long tiles = 0; + wxString(s["@tiles"]).ToLong(&tiles); + sizeNames.Add(wxString(s["@name"])); + sizeTiles.push_back((size_t)tiles); + } + + // TODO: set default based on current map size + + wxSingleChoiceDialog dlg(this, _("Select new map size. WARNING: This probably only works reliably on blank maps, and cannot be undone."), + _("Resize map"), sizeNames); + + if (dlg.ShowModal() != wxID_OK) + return; + + size_t tiles = sizeTiles.at(dlg.GetSelection()); + POST_COMMAND(ResizeMap, (tiles)); +} + BEGIN_EVENT_TABLE(TerrainSidebar, Sidebar) EVT_CHOICE(ID_Passability, TerrainSidebar::OnPassabilityChoice) EVT_CHECKBOX(ID_ShowPriorities, TerrainSidebar::OnShowPriorities) + EVT_BUTTON(ID_ResizeMap, TerrainSidebar::OnResizeMap) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// @@ -232,7 +276,11 @@ public: button->SetBackgroundColour(wxColour(255, 255, 0)); m_LastTerrainSelection = button; - m_ScenarioEditor.GetToolManager().SetCurrentTool(L"PaintTerrain"); + // Slight hack: Default to Paint mode because that's probably what the user wanted + // when they selected a terrain; unless already explicitly in Replace mode, because + // then the user probably wanted that instead + if (m_ScenarioEditor.GetToolManager().GetCurrentToolName() != _T("ReplaceTerrain")) + m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PaintTerrain")); } void OnSize(wxSizeEvent& evt) diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h index d9bb46cb4e..eae1d7aa2c 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h @@ -28,6 +28,7 @@ protected: private: void OnPassabilityChoice(wxCommandEvent& evt); void OnShowPriorities(wxCommandEvent& evt); + void OnResizeMap(wxCommandEvent& evt); wxChoice* m_PassabilityChoice; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp index ccbc8027e1..c7a9bb521c 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp @@ -1,4 +1,4 @@ -/* 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 @@ -114,6 +114,18 @@ void Brush::SetStrength(float strength) m_Strength = strength; } +void Brush::SetCircle(int size) +{ + m_Shape = CIRCLE; + m_Size = size; +} + +void Brush::SetSquare(int size) +{ + m_Shape = SQUARE; + m_Size = size; +} + ////////////////////////////////////////////////////////////////////////// class BrushShapeCtrl : public wxRadioBox diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h index 06d207b27a..cc7a6591b4 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.h @@ -1,4 +1,4 @@ -/* 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 @@ -37,6 +37,9 @@ public: int GetHeight() const; std::vector GetData() const; + void SetCircle(int size); + void SetSquare(int size); + float GetStrength() const; void SetStrength(float strength); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.cpp index d9224f06f5..7327362e4d 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.cpp @@ -53,6 +53,11 @@ ObservablePtr& ToolManager::GetCurrentTool() return m->CurrentTool; } +wxString ToolManager::GetCurrentToolName() +{ + return m->CurrentTool->GetClassInfo()->GetClassName(); +} + void SetActive(bool active, const wxString& name); void ToolManager::SetCurrentTool(const wxString& name, void* initData) diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.h index 5e606d3522..59be5299b4 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Tools.h @@ -46,6 +46,7 @@ public: ToolManager(ScenarioEditor* scenarioEditor); ~ToolManager(); ObservablePtr& GetCurrentTool(); + wxString GetCurrentToolName(); void SetCurrentTool(const wxString& name, void* initData = NULL); private: ToolManagerImpl* m; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/ReplaceTerrain.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/ReplaceTerrain.cpp new file mode 100644 index 0000000000..02856fa7df --- /dev/null +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/ReplaceTerrain.cpp @@ -0,0 +1,77 @@ +/* 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 "ScenarioEditor/ScenarioEditor.h" +#include "Common/Tools.h" +#include "Common/Brushes.h" +#include "Common/MiscState.h" +#include "GameInterface/Messages.h" + +using AtlasMessage::Position; + +class ReplaceTerrain : public StateDrivenTool +{ + DECLARE_DYNAMIC_CLASS(ReplaceTerrain); + + Position m_Pos; + Brush m_Brush; + +public: + ReplaceTerrain() + { + m_Brush.SetSquare(2); + SetState(&Waiting); + } + + void OnEnable() + { + m_Brush.MakeActive(); + } + + void OnDisable() + { + POST_MESSAGE(BrushPreview, (false, Position())); + } + + struct sWaiting : public State + { + bool OnMouse(ReplaceTerrain* WXUNUSED(obj), wxMouseEvent& evt) + { + if (evt.LeftDown()) + { + Position pos(evt.GetPosition()); + POST_MESSAGE(BrushPreview, (true, pos)); + POST_COMMAND(ReplaceTerrain, (pos, g_SelectedTexture.c_str())); + return true; + } + else if (evt.Moving()) + { + POST_MESSAGE(BrushPreview, (true, Position(evt.GetPosition()))); + return true; + } + else + { + return false; + } + } + } + Waiting; +}; + +IMPLEMENT_DYNAMIC_CLASS(ReplaceTerrain, StateDrivenTool); diff --git a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp index bad580e492..e743d4f9b1 100644 --- a/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -19,8 +19,10 @@ #include "MessageHandler.h" #include "../GameLoop.h" +#include "../CommandProc.h" #include "graphics/GameView.h" +#include "graphics/LOSTexture.h" #include "graphics/MapWriter.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" @@ -36,6 +38,7 @@ #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTerrain.h" namespace { @@ -146,4 +149,43 @@ QUERYHANDLER(GetRMSData) msg->data = g_Game->GetSimulation2()->GetRMSData(); } +BEGIN_COMMAND(ResizeMap) +{ + cResizeMap() + { + } + + void MakeDirty() + { + CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (!cmpTerrain.null()) + cmpTerrain->ReloadTerrain(); + + // The LOS texture won't normally get updated when running Atlas + // (since there's no simulation updates), so explicitly dirty it + g_Game->GetView()->GetLOSTexture().MakeDirty(); + } + + void Do() + { + Redo(); + } + + void Undo() + { + // TODO + debug_warn(L"Can't undo ResizeMap"); + } + + void Redo() + { + CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + + terrain->Resize(msg->tiles / PATCH_SIZE); + + MakeDirty(); + } +}; +END_COMMAND(ResizeMap) + } diff --git a/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp index d1bcdb764b..58fdceb43f 100644 --- a/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp @@ -136,65 +136,91 @@ QUERYHANDLER(GetTerrainPassabilityClasses) ////////////////////////////////////////////////////////////////////////// +namespace { + +struct TerrainTile +{ + TerrainTile(CTerrainTextureEntry* t, ssize_t p) : tex(t), priority(p) {} + CTerrainTextureEntry* tex; + ssize_t priority; +}; + +class TerrainArray : public DeltaArray2D +{ +public: + void Init() + { + m_Terrain = g_Game->GetWorld()->GetTerrain(); + m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); + } + + void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority) + { + CMiniPatch* tile = m_Terrain->GetTile(x, y); + if (!tile) + return; // tile was out-of-bounds + + // If this tile matches the current texture, we just want to match its + // priority; otherwise we want to exceed its priority + if (tile->GetTextureEntry() == tex) + priority = std::max(priority, tile->GetPriority()*priorityScale); + else + priority = std::max(priority, tile->GetPriority()*priorityScale + 1); + } + + CTerrainTextureEntry* GetTexEntry(ssize_t x, ssize_t y) + { + if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1)) + return NULL; + + return get(x, y).tex; + } + + ssize_t GetPriority(ssize_t x, ssize_t y) + { + if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1)) + return 0; + + return get(x, y).priority; + } + + void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority) + { + // Ignore out-of-bounds tiles + if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1)) + return; + + set(x,y, TerrainTile(tex, priority)); + } + + ssize_t GetTilesPerSide() + { + return m_VertsPerSide-1; + } + +protected: + TerrainTile getOld(ssize_t x, ssize_t y) + { + CMiniPatch* mp = m_Terrain->GetTile(x, y); + ENSURE(mp); + return TerrainTile(mp->Tex, mp->Priority); + } + void setNew(ssize_t x, ssize_t y, const TerrainTile& val) + { + CMiniPatch* mp = m_Terrain->GetTile(x, y); + ENSURE(mp); + mp->Tex = val.tex; + mp->Priority = val.priority; + } + + CTerrain* m_Terrain; + ssize_t m_VertsPerSide; +}; + +} + BEGIN_COMMAND(PaintTerrain) { - struct TerrainTile - { - TerrainTile(CTerrainTextureEntry* t, ssize_t p) : tex(t), priority(p) {} - CTerrainTextureEntry* tex; - ssize_t priority; - }; - class TerrainArray : public DeltaArray2D - { - public: - void Init() - { - m_Terrain = g_Game->GetWorld()->GetTerrain(); - m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); - } - - void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority) - { - CMiniPatch* tile = m_Terrain->GetTile(x, y); - if (!tile) - return; // tile was out-of-bounds - - // If this tile matches the current texture, we just want to match its - // priority; otherwise we want to exceed its priority - if (tile->GetTextureEntry() == tex) - priority = std::max(priority, tile->GetPriority()*priorityScale); - else - priority = std::max(priority, tile->GetPriority()*priorityScale + 1); - } - - void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority) - { - // Ignore out-of-bounds tiles - if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1)) - return; - - set(x,y, TerrainTile(tex, priority)); - } - - protected: - TerrainTile getOld(ssize_t x, ssize_t y) - { - CMiniPatch* mp = m_Terrain->GetTile(x, y); - ENSURE(mp); - return TerrainTile(mp->Tex, mp->Priority); - } - void setNew(ssize_t x, ssize_t y, const TerrainTile& val) - { - CMiniPatch* mp = m_Terrain->GetTile(x, y); - ENSURE(mp); - mp->Tex = val.tex; - mp->Priority = val.priority; - } - - CTerrain* m_Terrain; - ssize_t m_VertsPerSide; - }; - TerrainArray m_TerrainDelta; ssize_t m_i0, m_j0, m_i1, m_j1; @@ -280,5 +306,78 @@ BEGIN_COMMAND(PaintTerrain) }; END_COMMAND(PaintTerrain) +////////////////////////////////////////////////////////////////////////// + +BEGIN_COMMAND(ReplaceTerrain) +{ + TerrainArray m_TerrainDelta; + ssize_t m_i0, m_j0, m_i1, m_j1; + + cReplaceTerrain() + { + m_TerrainDelta.Init(); + } + + void MakeDirty() + { + g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES); + CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (!cmpTerrain.null()) + cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1); + } + + void Do() + { + g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(); + + ssize_t x0, y0; + g_CurrentBrush.GetBottomLeft(x0, y0); + + m_i0 = m_i1 = x0; + m_j0 = m_j1 = y0; + + CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8()); + if (! texentry) + { + debug_warn(L"Can't find texentry"); // TODO: nicer error handling + return; + } + + CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0); + + ssize_t tiles = m_TerrainDelta.GetTilesPerSide(); + + for (ssize_t j = 0; j < tiles; ++j) + { + for (ssize_t i = 0; i < tiles; ++i) + { + if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex) + { + m_i0 = std::min(m_i0, i); + m_j0 = std::min(m_j0, j); + m_i1 = std::max(m_i1, i+1); + m_j1 = std::max(m_j1, j+1); + m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j)); + } + } + } + + MakeDirty(); + } + + void Undo() + { + m_TerrainDelta.Undo(); + MakeDirty(); + } + + void Redo() + { + m_TerrainDelta.Redo(); + MakeDirty(); + } +}; +END_COMMAND(ReplaceTerrain) + } diff --git a/source/tools/atlas/GameInterface/Messages.h b/source/tools/atlas/GameInterface/Messages.h index d57f56cbfc..d995ecc814 100644 --- a/source/tools/atlas/GameInterface/Messages.h +++ b/source/tools/atlas/GameInterface/Messages.h @@ -161,6 +161,10 @@ QUERY(GetRMSData, ((std::vector, data)) ); +COMMAND(ResizeMap, NOMERGE, + ((int, tiles)) + ); + ////////////////////////////////////////////////////////////////////////// // Messages for player panel @@ -431,6 +435,11 @@ COMMAND(PaintTerrain, MERGE, ((int, priority)) // ePaintTerrainPriority ); +COMMAND(ReplaceTerrain, NOMERGE, + ((Position, pos)) + ((std::wstring, texture)) + ); + ////////////////////////////////////////////////////////////////////////// QUERY(PickObject,