Index: ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 25814) +++ ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 25815) @@ -1,293 +1,294 @@ { "Title": "Programming", "Content": [ { "Title": "Programming managers", "List": [ { "nick": "Acumen", "name": "Stuart Walpole" }, { "nick": "Dak Lozar", "name": "Dave Loeser" }, { "nick": "h20", "name": "Daniel Wilhelm" }, { "nick": "Janwas", "name": "Jan Wassenberg" }, { "nick": "Raj", "name": "Raj Sharma" } ] }, { "Subtitle": "Special thanks to", "List": [ { "nick": "leper", "name": "Georg Kilzer" }, { "nick": "Ykkrosh", "name": "Philip Taylor" } ] }, { "List": [ { "nick": "01d55" }, { "nick": "aBothe", "name": "Alexander Bothe" }, { "nick": "Acumen", "name": "Stuart Walpole" }, { "nick": "adrian", "name": "Adrian Boguszewszki" }, { "name": "Adrian Fatol" }, { "nick": "AI-Amsterdam" }, { "nick": "Alan", "name": "Alan Kemp" }, { "nick": "Alex", "name": "Alexander Yakobovich" }, { "nick": "alpha123", "name": "Peter P. Cannici" }, { "nick": "Ampaex", "name": "Antonio Vazquez" }, { "name": "André Puel" }, { "nick": "andy5995", "name": "Andy Alt" }, { "nick": "Angen" }, { "nick": "Arfrever", "name": "Arfrever Frehtes Taifersar Arahesis" }, { "nick": "ArnH", "name": "Arno Hemelhof" }, { "nick": "Aurium", "name": "Aurélio Heckert" }, { "nick": "azayrahmad", "name": "Aziz Rahmad" }, { "nick": "badmadblacksad", "name": "Martin F" }, { "nick": "badosu", "name": "Amadeus Folego" }, { "nick": "bb", "name": "Bouke Jansen" }, { "nick": "Bellaz89", "name": "Andrea Bellandi" }, { "nick": "Ben", "name": "Ben Vinegar" }, { "nick": "Bird" }, { "nick": "Blue", "name": "Richard Welsh" }, { "nick": "bmwiedemann" }, { "nick": "boeseRaupe", "name": "Michael Kluge" }, { "nick": "bog_dan_ro", "name": "BogDan Vatra" }, { "nick": "Bonk", "name": "Christopher Ebbert" }, { "nick": "Boudica" }, { "nick": "Caius", "name": "Lars Kemmann" }, { "nick": "Calefaction", "name": "Matt Holmes" }, { "nick": "Calvinh", "name": "Carl-Johan Höiby" }, { "nick": "causative", "name": "Bart Parkis" }, { "name": "Cédric Houbart" }, { "nick": "Chakakhan", "name": "Kenny Long" }, { "nick": "Clockwork-Muse", "name": "Stephen A. Imhoff" }, { "nick": "cpc", "name": "Clément Pit-Claudel" }, { "nick": "Cracker78", "name": "Chad Heim" }, { "nick": "Crynux", "name": "Stephen J. Fewer" }, { "nick": "cwprogger" }, { "nick": "cygal", "name": "Quentin Pradet" }, { "nick": "Dak Lozar", "name": "Dave Loeser" }, { "nick": "dalerank", "name": "Sergey Kushnirenko" }, { "nick": "dan", "name": "Dan Strandberg" }, { "nick": "DanCar", "name": "Daniel Cardenas" }, { "nick": "danger89", "name": "Melroy van den Berg" }, { "name": "Daniel Trevitz" }, { "nick": "Dariost", "name": "Dario Ostuni" }, { "nick": "Dave", "name": "David Protasowski" }, { "name": "David Marshall" }, { "nick": "dax", "name": "Dacian Fiordean" }, { "nick": "deebee", "name": "Deepak Anthony" }, { "nick": "Deiz" }, { "nick": "Dietger", "name": "Dietger van Antwerpen" }, { "nick": "DigitalSeraphim", "name": "Nick Owens" }, { "nick": "dp304" }, { "nick": "dpiquet", "name": "Damien Piquet" }, { "nick": "dumbo" }, { "nick": "Dunedan", "name": "Daniel Roschka" }, { "nick": "dvangennip", "name": "Doménique" }, { "nick": "DynamoFox" }, { "nick": "Echelon9", "name": "Rhys Kidd" }, { "nick": "echotangoecho" }, { "nick": "eihrul", "name": "Lee Salzman" }, { "nick": "elexis", "name": "Alexander Heinsius" }, { "nick": "EmjeR", "name": "Matthijs de Rijk" }, { "nick": "EMontana" }, { "nick": "ericb" }, { "nick": "evanssthomas", "name": "Evans Thomas" }, { "nick": "Evulant", "name": "Alexander S." }, { "nick": "fabio", "name": "Fabio Pedretti" }, { "nick": "falsevision", "name": "Mahdi Khodadadifard" }, { "nick": "fatherbushido", "name": "Nicolas Tisserand" }, { "nick": "fcxSanya", "name": "Alexander Olkhovskiy" }, { "nick": "FeXoR", "name": "Florian Finke" }, { "nick": "Fire Giant", "name": "Malte Schwarzkopf" }, { "name": "Fork AD" }, { "nick": "fpre", "name": "Frederick Stallmeyer" }, { "nick": "Freagarach" }, { "nick": "freenity", "name": "Anton Galitch" }, { "nick": "Gallaecio", "name": "Adrián Chaves" }, { "nick": "gbish (aka Iny)", "name": "Grant Bishop" }, { "nick": "Gee", "name": "Gustav Larsson" }, { "nick": "Gentz", "name": "Hal Gentz" }, { "nick": "gerbilOFdoom" }, { "nick": "godlikeldh" }, { "nick": "greybeard", "name": "Joe Cocovich" }, { "nick": "grillaz" }, { "nick": "Grugnas", "name": "Giuseppe Tranchese" }, { "nick": "gudo" }, { "nick": "Guuts", "name": "Matthew Guttag" }, { "nick": "h20", "name": "Daniel Wilhelm" }, { "nick": "Hannibal_Barca", "name": "Clive Juhász S." }, { "nick": "Haommin" }, { "nick": "happyconcepts", "name": "Ben Bird" }, { "nick": "historic_bruno", "name": "Ben Brian" }, { "nick": "idanwin" }, { "nick": "Imarok", "name": "J. S." }, { "nick": "Inari" }, { "nick": "infyquest", "name": "Vijay Kiran Kamuju" }, { "nick": "irishninja", "name": "Brian Broll" }, { "nick": "IronNerd", "name": "Matthew McMullan" }, { "nick": "Itms", "name": "Nicolas Auvray" }, { "nick": "Jaison", "name": "Marco tom Suden" }, { "nick": "jammus", "name": "James Scott" }, { "nick": "Janwas", "name": "Jan Wassenberg" }, { "nick": "javiergodas", "name": "Javier Godas Vieitez" }, { "nick": "JCWasmx86" }, { "nick": "Jgwman" }, { "nick": "JonBaer", "name": "Jon Baer" }, { "nick": "Josh", "name": "Joshua J. Bakita" }, { "nick": "joskar", "name": "Johnny Oskarsson" }, { "nick": "jP_wanN", "name": "Jonas Platte" }, { "nick": "Jubalbarca", "name": "James Baillie" }, { "nick": "JubJub", "name": "Sebastian Vetter" }, { "nick": "jurgemaister" }, { "nick": "kabzerek", "name": "Grzegorz Kabza" }, { "nick": "Kai", "name": "Kai Chen" }, { "name": "Kareem Ergawy" }, { "nick": "kevmo", "name": "Kevin Caffrey" }, { "nick": "kezz", "name": "Graeme Kerry" }, { "nick": "kingadami", "name": "Adam Winsor" }, { "nick": "kingbasil", "name": "Giannis Fafalios" }, { "nick": "Krinkle", "name": "Timo Tijhof" }, { "nick": "Kuba386", "name": "Jakub Kośmicki" }, { "nick": "lafferjm", "name": "Justin Lafferty" }, { "nick": "Langbart" }, { "nick": "LeanderH", "name": "Leander Hemelhof" }, { "nick": "leper", "name": "Georg Kilzer" }, { "nick": "Link Mauve", "name": "Emmanuel Gil Peyrot" }, { "nick": "LittleDev" }, { "nick": "livingaftermidnight", "name": "Will Dull" }, { "nick": "lonehawk", "name": "Vignesh Krishnan" }, { "nick": "Louhike" }, { "nick": "lsdh" }, { "nick": "Ludovic", "name": "Ludovic Rousseau" }, { "nick": "luiko", "name": "Luis Carlos Garcia Barajas" }, { "nick": "m0l0t0ph", "name": "Christoph Gielisch" }, { "nick": "madmax", "name": "Abhijit Nandy" }, { "nick": "madpilot", "name": "Guido Falsi" }, { "nick": "mammadori", "name": "Marco Amadori" }, { "nick": "marder", "name": "Stefan R. F." }, { "nick": "markcho" }, { "nick": "MarkT", "name": "Mark Thompson" }, { "nick": "Markus" }, { "nick": "Mate-86", "name": "Mate Kovacs" }, { "nick": "Matei", "name": "Matei Zaharia" }, + { "nick": "MatSharrow" }, { "nick": "MattDoerksen", "name": "Matt Doerksen" }, { "nick": "mattlott", "name": "Matt Lott" }, { "nick": "maveric", "name": "Anton Protko" }, { "nick": "Micnasty", "name": "Travis Gorkin" }, { "name": "Mikołaj \"Bajter\" Korcz" }, { "nick": "mimo" }, { "nick": "mk12", "name": "Mitchell Kember" }, { "nick": "mmayfield45", "name": "Michael Mayfield" }, { "nick": "mmoanis", "name": "Mohamed Moanis" }, { "nick": "Molotov", "name": "Dario Alvarez" }, { "nick": "mpmoreti", "name": "Marcos Paulo Moreti" }, { "nick": "mreiland", "name": "Michael Reiland" }, { "nick": "myconid" }, { "nick": "n1xc0d3r", "name": "Luis Guerrero" }, { "nick": "nani", "name": "S. N." }, { "nick": "nd3c3nt", "name": "Gavin Fowler" }, { "nick": "nephele" }, { "nick": "Nescio" }, { "nick": "niektb", "name": "Niek ten Brinke" }, { "nick": "nikagra", "name": "Mikita Hradovich" }, { "nick": "njm" }, { "nick": "NoMonkey", "name": "John Mena" }, { "nick": "norsnor" }, { "nick": "notpete", "name": "Rich Cross" }, { "nick": "nwtour" }, { "nick": "odoaker", "name": "Ágoston Sipos" }, { "nick": "Offensive ePeen", "name": "Jared Ryan Bills" }, { "nick": "Ols", "name": "Oliver Whiteman" }, { "nick": "olsner", "name": "Simon Brenner" }, { "nick": "OptimusShepard", "name": "Pirmin Stanglmeier" }, { "nick": "otero" }, { "nick": "Palaxin", "name": "David A. Freitag" }, { "name": "Paul Withers" }, { "nick": "paulobezerr", "name": "Paulo George Gomes Bezerra" }, { "nick": "pcpa", "name": "Paulo Andrade" }, { "nick": "Pendingchaos" }, { "nick": "PeteVasi", "name": "Pete Vasiliauskas" }, { "nick": "pilino1234" }, { "nick": "PingvinBetyar", "name": "Schronk Tamás" }, { "nick": "plugwash", "name": "Peter Michael Green" }, { "nick": "Polakrity" }, { "nick": "Poya", "name": "Poya Manouchehri" }, { "nick": "prefect", "name": "Nicolai Hähnle" }, { "nick": "Prodigal Son" }, { "nick": "pstumpf", "name": "Pascal Stumpf" }, { "nick": "pyrolink", "name": "Andrew Decker" }, { "nick": "quantumstate", "name": "Jonathan Waller" }, { "nick": "QuickShot", "name": "Walter Krawec" }, { "nick": "quonter" }, { "nick": "qwertz" }, { "nick": "Radagast" }, { "nick": "Raj", "name": "Raj Sharma" }, { "nick": "ramtzok1", "name": "Ram" }, { "nick": "rapidelectron", "name": "Christian Weihsbach" }, { "nick": "r-a-sattarov", "name": "Ramil Sattarov" }, { "nick": "RedFox", "name": "Jorma Rebane" }, { "nick": "RefinedCode" }, { "nick": "Riemer" }, { "name": "Rolf Sievers" }, { "nick": "s0600204", "name": "Matthew Norwood" }, { "nick": "sacha_vrand", "name": "Sacha Vrand" }, { "nick": "SafaAlfulaij" }, { "name": "Samuel Guarnieri" }, { "nick": "Samulis", "name": "Sam Gossner" }, { "nick": "Sandarac" }, { "nick": "sanderd17", "name": "Sander Deryckere" }, { "nick": "sathyam", "name": "Sathyam Vellal" }, { "nick": "sbirmi", "name": "Sharad Birmiwal" }, { "nick": "sbte", "name": "Sven Baars" }, { "nick": "scroogie", "name": "André Gemünd" }, { "nick": "scythetwirler", "name": "Casey X." }, { "nick": "serveurix" }, { "nick": "Shane", "name": "Shane Grant" }, { "nick": "shh" }, { "nick": "Silk", "name": "Josh Godsiff" }, { "nick": "silure" }, { "nick": "Simikolon", "name": "Yannick & Simon" }, { "nick": "smiley", "name": "M. L." }, { "nick": "Spahbod", "name": "Omid Davoodi" }, { "nick": "Stan", "name": "Stanislas Dolcini" }, { "nick": "Stefan" }, { "nick": "StefanBruens", "name": "Stefan Brüns" }, { "nick": "stilz", "name": "Sławomir Zborowski" }, { "nick": "stwf", "name": "Steven Fuchs" }, { "nick": "svott", "name": "Sven Ott" }, { "nick": "t4nk004" }, { "nick": "tau" }, { "nick": "tbm", "name": "Martin Michlmayr" }, { "nick": "Teiresias" }, { "nick": "temple" }, { "nick": "texane" }, { "nick": "thamlett", "name": "Timothy Hamlett" }, { "nick": "thedrunkyak", "name": "Dan Fuhr" }, { "nick": "Tobbi" }, { "nick": "Toonijn", "name": "Toon Baeyens" }, { "nick": "TrinityDeath", "name": "Jethro Lu" }, { "nick": "triumvir", "name": "Corin Schedler" }, { "nick": "trompetin17", "name": "Juan Guillermo" }, { "nick": "tpearson", "name": "Timothy Pearson" }, { "nick": "user1", "name": "A. C." }, { "nick": "usey11" }, { "nick": "vincent_c", "name": "Vincent Cheng" }, { "nick": "vinhig", "name": "Vincent Higginson" }, { "nick": "vladislavbelov", "name": "Vladislav Belov" }, { "nick": "voroskoi" }, { "nick": "vts", "name": "Jeroen DR" }, { "nick": "wacko", "name": "Andrew Spiering" }, { "nick": "WhiteTreePaladin", "name": "Brian Ashley" }, { "nick": "wraitii", "name": "Lancelot de Ferrière le Vayer" }, { "nick": "Xentelian", "name": "Mark Strawson" }, { "nick": "Xienen", "name": "Dayle Flowers" }, { "nick": "xtizer", "name": "Matt Green" }, { "nick": "yashi", "name": "Yasushi Shoji" }, { "nick": "Ykkrosh", "name": "Philip Taylor" }, { "nick": "Yves" }, { "nick": "Zeusthor", "name": "Jeffrey Tavares" }, { "nick": "zoot" }, { "nick": "zsol", "name": "Zsolt Dollenstein" }, { "nick": "ztamas", "name": "Tamas Zolnai" }, { "nick": "Zyi", "name": "Charles De Meulenaer" } ] } ] } Index: ps/trunk/source/tools/atlas/AtlasUI/ActorEditor/ActorEditor.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ActorEditor/ActorEditor.cpp (revision 25814) +++ ps/trunk/source/tools/atlas/AtlasUI/ActorEditor/ActorEditor.cpp (revision 25815) @@ -1,451 +1,451 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include #include "ActorEditor.h" #include "ActorEditorListCtrl.h" #include "AtlasObject/AtlasObject.h" #include "AtlasObject/AtlasObjectText.h" #include "General/Datafile.h" #ifdef __WXMAC__ #include #endif #include "wx/file.h" BEGIN_EVENT_TABLE(ActorEditor, AtlasWindow) EVT_MENU(ID_CreateEntity, ActorEditor::OnCreateEntity) END_EVENT_TABLE() ActorEditor::ActorEditor(wxWindow* parent) : AtlasWindow(parent, _("Actor Editor"), wxSize(1024, 450)) { SetIcon(wxIcon(_T("ICON_ActorEditor"))); #ifdef __WXMAC__ ProcessSerialNumber PSN; GetCurrentProcess(&PSN); TransformProcessType(&PSN,kProcessTransformToForegroundApplication); #endif wxMenu* menu = new wxMenu; menu->Append(ID_CreateEntity, _("Create &entity...")); AddCustomMenu(menu, _("&Actor")); ////////////////////////////////////////////////////////////////////////// wxPanel* mainPanel = new wxPanel(this); m_ActorEditorListCtrl = new ActorEditorListCtrl(mainPanel); wxBoxSizer* vertSizer = new wxBoxSizer(wxVERTICAL); mainPanel->SetSizer(vertSizer); wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL); vertSizer->Add(topSizer, wxSizerFlags().Border(wxLEFT|wxRIGHT, 5)); vertSizer->Add( m_ActorEditorListCtrl, wxSizerFlags().Proportion(1).Expand().Border(wxALL, 10)); ////////////////////////////////////////////////////////////////////////// // Properties panel: wxPanel* propertiesPanel = new wxPanel(mainPanel); topSizer->Add(propertiesPanel, wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT, 5)); wxSizer* propertiesSizer = new wxStaticBoxSizer( new wxStaticBox(propertiesPanel, wxID_ANY, _("Actor properties")), wxHORIZONTAL); propertiesPanel->SetSizer(propertiesSizer); m_CastShadows = new wxCheckBox(propertiesPanel, wxID_ANY, _("Cast shadow")); propertiesSizer->Add(m_CastShadows, wxSizerFlags().Border(wxALL, 5)); m_Float = new wxCheckBox(propertiesPanel, wxID_ANY, _("Float on water")); propertiesSizer->Add(m_Float, wxSizerFlags().Border(wxALL, 5)); // TODO: Orientation property. ////////////////////////////////////////////////////////////////////////// // Materials box: wxPanel* materialsPanel = new wxPanel(mainPanel); topSizer->Add(materialsPanel, wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT, 5)); wxSizer* materialsSizer = new wxStaticBoxSizer( new wxStaticBox(materialsPanel, wxID_ANY, _("Material")), wxHORIZONTAL); materialsPanel->SetSizer(materialsSizer); // Get the list of XML materials wxArrayString materials = Datafile::EnumerateDataFiles(_T("mods/public/art/materials/"), _T("*.xml")); // Extract the filenames and discard the path for (size_t i = 0; i < materials.Count(); ++i) materials[i] = wxFileName(materials[i]).GetFullName(); m_Material = new wxComboBox(materialsPanel, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, materials); materialsSizer->Add(m_Material, wxSizerFlags().Border(wxALL, 2)); } void ActorEditor::ThawData(AtObj& in) { AtObj actor (*in["actor"]); m_ActorEditorListCtrl->ThawData(actor); m_CastShadows->SetValue(actor["castshadow"].defined()); m_Float->SetValue(actor["float"].defined()); m_Material->SetValue((wxString)actor["material"]); } AtObj ActorEditor::FreezeData() { AtObj actor (m_ActorEditorListCtrl->FreezeData()); if (m_CastShadows->IsChecked()) actor.set("castshadow", ""); if (m_Float->IsChecked()) actor.set("float", ""); if (m_Material->GetValue().length()) - actor.set("material", m_Material->GetValue()); + actor.set("material", m_Material->GetValue().utf8_str()); AtObj out; out.set("actor", actor); return out; } static AtObj ConvertToLatestFormat(AtObj in) { if (! in.defined()) { // 'Importing' a new blank file. Fill it in with default values: AtObj actor; actor.add("@version", "1"); in.add("actor", actor); } // Determine the file format version long version; if (in["Object"].defined()) { // old-style actor format version = -1; } else if (in["qualitylevels"].defined()) { // New-style, multiple-quality-levels actor. wxLogError(_("Cannot edit actors with multiple quality levels. If you want to use the actor editor, use the `` format and edit the referenced files.")); return AtObj(); } else if (in["actor"].defined()) version = (in["actor"]["@version"].defined()) ? (*in["actor"]["@version"]).getLong() : 0; else { wxLogError(_("Failed to determine actor file format version")); return AtObj(); } // Do any necessary conversions into the most recent format: if (version == -1) { AtObj actor; AtIter castShadows = in["Object"]["Properties"]["@castshadows"]; // Handle the global actor properties if (castShadows.defined() && (*castShadows).getInt() == 1) actor.set("castshadow", ""); // Things to strip leading strings (for converting filenames, since the // new format doesn't store the entire path) #define THING1(out,outname, in,inname, prefix) \ wxASSERT( wxString::FromUTF8(in["Object"][inname]).StartsWith(_T(prefix)) ); \ - out.add(outname, wxString::FromUTF8(in["Object"][inname]).Mid(wxString(_T(prefix)).Length())) + out.add(outname, wxString::FromUTF8(in["Object"][inname]).Mid(wxString(_T(prefix)).Length()).utf8_str()) #define THING2(out,outname, in,inname, prefix) \ wxASSERT( wxString::FromUTF8(in[inname]).StartsWith(_T(prefix)) ); \ - out.add(outname, wxString::FromUTF8(in[inname]).Mid(wxString(_T(prefix)).Length())) + out.add(outname, wxString::FromUTF8(in[inname]).Mid(wxString(_T(prefix)).Length()).utf8_str()) if (wxString::FromUTF8(in["Object"]["Material"]).Len()) { THING1(actor,"material", in,"Material", "art/materials/"); } // Create a single variant to contain all the old data AtObj var; var.add("@name", "Base"); var.add("@frequency", "100"); // 100 == default frequency THING1(var,"mesh", in,"ModelName", "art/meshes/"); // XXX std::string textureName(in["Object"]["TextureName"]); if (boost::algorithm::starts_with(textureName, "art/textures/ui/session/portraits/ui_portrait_sheet_civ_")) var.add("texture", ("temp/" + textureName.substr(strlen("art/textures/ui/session/portraits/"))).c_str()); else THING1(var,"texture", in,"TextureName", "art/textures/skins/"); AtObj anims; for (AtIter animit = in["Object"]["Animations"]["Animation"]; animit.defined(); ++animit) { if (strlen(animit["@file"])) { AtObj anim; anim.add("@name", animit["@name"]); anim.add("@speed", animit["@speed"]); THING2(anim,"@file", animit,"@file", "art/animation/"); anims.add("animation", anim); } } var.add("animations", anims); AtObj props; for (AtIter propit = in["Object"]["Props"]["Prop"]; propit.defined(); ++propit) { AtObj prop; prop.add("@attachpoint", propit["@attachpoint"]); prop.add("@actor", propit["@model"]); props.add("prop", prop); } var.add("props", props); AtObj grp; grp.add("variant", var); actor.add("group", grp); #undef THING1 #undef THING2 actor.set("@version", "1"); in = AtObj(); in.set("actor", actor); } else if (version == 0) { AtObj actor; if (in["actor"]["castshadow"].defined()) actor.add("castshadow", in["actor"]["castshadow"]); if (in["actor"]["material"].defined()) actor.add("material", in["actor"]["material"]); for (AtIter grpit = in["actor"]["group"]; grpit.defined(); ++grpit) { AtObj grp; for (AtIter varit = grpit["variant"]; varit.defined(); ++varit) { AtObj var; var.add("@name", varit["name"]); var.add("@frequency", varit["frequency"]); var.add("mesh", varit["mesh"]); var.add("texture", varit["texture"]); AtObj anims; for (AtIter animit = varit["animations"]["animation"]; animit.defined(); ++animit) { AtObj anim; anim.add("@name", animit["name"]); anim.add("@file", animit["file"]); anim.add("@speed", animit["speed"]); anims.add("animation", anim); } var.add("animations", anims); AtObj props; for (AtIter propit = varit["props"]["prop"]; propit.defined(); ++propit) { AtObj prop; prop.add("@attachpoint", propit["attachpoint"]); prop.add("@actor", propit["model"]); props.add("prop", prop); } var.add("props", props); grp.add("variant", var); } actor.add("group", grp); } actor.set("@version", "1"); in.set("actor", actor); } else if (version == 1) { // current format } else { // ??? unknown format - this should have been noticed earlier wxFAIL_MSG(_T("Invalid actor format")); } return in; } void ActorEditor::ImportData(AtObj& in) { AtObj data = ConvertToLatestFormat(in); if (! data.defined()) return; // Copy the data into the appropriate UI controls: AtObj actor (*data["actor"]); m_ActorEditorListCtrl->ImportData(actor); m_Actor = actor; m_CastShadows->SetValue(actor["castshadow"].defined()); m_Float->SetValue(actor["float"].defined()); m_Material->SetValue((wxString)actor["material"]); } AtObj ActorEditor::ExportData() { // Export the group/variant/etc data AtObj actor (m_ActorEditorListCtrl->ExportData()); actor.set("@version", "1"); AtObj castShadow = *m_Actor["castshadow"]; if (m_CastShadows->IsChecked() && castShadow.defined()) actor.set("castshadow", castShadow); else if (m_CastShadows->IsChecked()) actor.set("castshadow", ""); AtObj floatObj = *m_Actor["float"]; if (m_Float->IsChecked() && floatObj) actor.set("float", floatObj); else if (m_Float->IsChecked()) actor.set("float", ""); AtObj material = *m_Actor["material"]; actor.set("material", material); if (m_Material->GetValue().length()) - actor.set("material", m_Material->GetValue()); + actor.set("material", m_Material->GetValue().utf8_str()); AtObj out; out.set("actor", actor); return out; } void ActorEditor::OnCreateEntity(wxCommandEvent& WXUNUSED(event)) { // Create a very basic entity for this actor. // The output should be an XML file like: // // // // units/celt_csw_a.xml // // // 'Actor' comes from this actor's filename. // 'Parent' comes from the filename of a user-selected entity. // The file will be saved into a user-selected location. // Get the entity's expected name wxFileName currentFilename (GetCurrentFilename()); if (! currentFilename.IsOk()) { wxMessageDialog(this, _("Please save this actor before attempting to create an entity for it."), _("Gentle reminder"), wxOK | wxICON_INFORMATION).ShowModal(); return; } wxString entityName = currentFilename.GetName(); // Work out where the entities are stored wxFileName entityPath (_T("mods/public/entities/")); entityPath.MakeAbsolute(Datafile::GetDataDirectory()); // Make sure the user knows what's going on static bool instructed = false; // only tell them once per session if (! instructed) { instructed = true; if (wxMessageBox(_("To create an entity, you will first be asked to choose a parent entity, and then asked where save the new entity."), _("Usage instructions"), wxICON_INFORMATION|wxOK|wxCANCEL, this) != wxOK) return; // cancelled by user } wxString parentEntityFilename (wxFileSelector(_("Choose a parent entity"), entityPath.GetPath(), _T(""), _T("xml"), _("XML files (*.xml)|*.xml|All files (*.*)|*.*"), wxFD_OPEN, this)); if (! parentEntityFilename.Length()) return; // cancelled by user // Get the parent's name wxString parentName (wxFileName(parentEntityFilename).GetName()); wxString outputEntityFilename (wxFileSelector(_("Choose a filename to save as"), entityPath.GetPath(), entityName, _T("xml"), _("XML files (*.xml)|*.xml|All files (*.*)|*.*"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT, this)); if (! outputEntityFilename.Length()) return; // cancelled by user // Get this actor's filename, relative to actors/ wxFileName actorPath (_T("mods/public/art/actors/")); actorPath.MakeAbsolute(Datafile::GetDataDirectory()); wxFileName actorFilename (currentFilename); actorFilename.MakeRelativeTo(actorPath.GetFullPath()); // Create the XML data to be written // TODO: Native line endings wxString xml = _T("\r\n") _T("\r\n") _T("\r\n") _T("\t") + actorFilename.GetFullPath(wxPATH_UNIX) + _T("\r\n") _T("\r\n"); wxFile file (outputEntityFilename.c_str(), wxFile::write); if (! file.IsOpened()) { wxLogError(_("Failed to open file")); return; } if (! file.Write(xml)) { wxLogError(_("Failed to write XML data to file")); return; } } wxString ActorEditor::GetDefaultOpenDirectory() { wxFileName dir (_T("mods/public/art/actors/"), wxPATH_UNIX); dir.MakeAbsolute(Datafile::GetDataDirectory()); return dir.GetPath(); } Index: ps/trunk/source/tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrl.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrl.cpp (revision 25814) +++ ps/trunk/source/tools/atlas/AtlasUI/CustomControls/EditableListCtrl/EditableListCtrl.cpp (revision 25815) @@ -1,319 +1,319 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 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 "EditableListCtrl.h" #include "EditableListCtrlCommands.h" #include "FieldEditCtrl.h" #include "General/AtlasWindowCommandProc.h" #include "AtlasObject/AtlasObject.h" #include "AtlasObject/AtlasObjectText.h" #include "General/AtlasClipboard.h" const int BlanksAtEnd = 2; EditableListCtrl::EditableListCtrl(wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name) : wxListCtrl(parent, id, pos, size, style | wxLC_VIRTUAL, validator, name) { m_ListItemAttr[0].SetBackgroundColour(wxColor(0xff, 0xff, 0xff)); m_ListItemAttr[1].SetBackgroundColour(wxColor(0xee, 0xee, 0xee)); wxASSERT_MSG(style & wxLC_REPORT, _T("EditableListCtrl must be LC_REPORT")); UpdateDisplay(); } EditableListCtrl::~EditableListCtrl() { size_t count = m_ColumnTypes.size(); for (size_t n = 0; n < count; ++n) delete (FieldEditCtrl*)m_ColumnTypes[n].ctrl; m_ColumnTypes.clear(); } void EditableListCtrl::AddColumnType(const wxString& title, int width, const char* objectkey, FieldEditCtrl* ctrl) { int n = GetColumnCount(); wxASSERT(m_ColumnTypes.size() == (size_t) n); // check internal consistency InsertColumn(n, title, wxLIST_FORMAT_LEFT, width); m_ColumnTypes.push_back(ColumnData(objectkey, ctrl)); } void EditableListCtrl::OnMouseEvent(wxMouseEvent& event) { // Double-clicking/right-clicking on a cell lets the user edit it. // The editing method depends on what column the cell is in. if (event.LeftDClick() || event.RightDown()) { // Work out what cell was clicked on: wxPoint pt = event.GetPosition(); int col = GetColumnAtPosition(pt); if (col < 0 || col >= static_cast(m_ColumnTypes.size())) return; int flags; long row = HitTest(pt, flags); if (row != wxNOT_FOUND && (flags & wxLIST_HITTEST_ONITEM)) { // Calculate the exact positioning of the clicked cell wxRect rect; GetCellRect(row, col, rect); // Execute the appropriate FieldEditCtrl FieldEditCtrl* editor = (FieldEditCtrl*)m_ColumnTypes[col].ctrl; editor->StartEdit(this, rect, row, col); } } } void EditableListCtrl::OnKeyDown(wxKeyEvent& event) { // TODO: Don't use magic key-code numbers // Check for Copy if ((event.GetKeyCode() == 3) || // ctrl+c (event.GetKeyCode() == WXK_INSERT && event.ControlDown())) // ctrl+insert { AtObj row; long selection = GetSelection(); if (selection >= 0 && selection < (long)m_ListData.size()) row = m_ListData[selection]; AtlasClipboard::SetClipboard(row); } // Check for Paste else if ((event.GetKeyCode() == 22) || // ctrl+v (event.GetKeyCode() == WXK_INSERT && event.ShiftDown())) // shift+insert { AtObj row; if (AtlasClipboard::GetClipboard(row)) { long selection = GetSelection(); AtlasWindowCommandProc* commandProc = AtlasWindowCommandProc::GetFromParentFrame(this); commandProc->Submit(new PasteCommand(this, selection, row)); } } else event.Skip(); } int EditableListCtrl::GetColumnAtPosition(wxPoint& pos) { // Find the column which pos is in. // Get the origin of the table, in case it's scrolled horizontally wxRect rect; GetItemRect(0, rect); int x = rect.GetX(); // Loop through each column int numCols = GetColumnCount(); for (int i = 0; i < numCols; ++i) { // Calculate the position of this column's right-hand edge x += GetColumnWidth(i); // Test if pos was within this column (and assume it wasn't in an earlier one) if (pos.x <= x) return i; } // Point is outside the table's right edge return -1; } void EditableListCtrl::GetCellRect(long row, int col, wxRect& rect) { wxASSERT(col >= 0 && col < GetColumnCount()); wxASSERT(row >= 0 && row < GetItemCount()); GetItemRect(row, rect); for (int i = 0; i < col; ++i) rect.x += GetColumnWidth(i); rect.width = GetColumnWidth(col); } bool EditableListCtrl::IsRowBlank(int n) { return ! m_ListData[n].hasContent(); } void EditableListCtrl::TrimBlankEnds() { while (m_ListData.size() && !m_ListData.back().defined()) m_ListData.pop_back(); } void EditableListCtrl::SetSelection(long item) { SetItemState(item, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED); } long EditableListCtrl::GetSelection() { for (long item = 0; item < GetItemCount(); ++item) if (GetItemState(item, wxLIST_STATE_SELECTED)) return item; return 0; } void EditableListCtrl::MakeSizeAtLeast(int n) { if ((int)m_ListData.size() < n) m_ListData.resize(n); } void EditableListCtrl::AddRow(AtObj& obj) { m_ListData.push_back(obj); } void EditableListCtrl::AddRow(AtIter& iter) { AtObj obj = *iter; AddRow(obj); } void EditableListCtrl::UpdateDisplay() { TrimBlankEnds(); SetItemCount((int)m_ListData.size() + BlanksAtEnd); Refresh(); } void EditableListCtrl::CloneListData(std::vector& out) { out = m_ListData; } void EditableListCtrl::SetListData(std::vector& in) { m_ListData = in; } void EditableListCtrl::DeleteData() { m_ListData.clear(); } wxString EditableListCtrl::GetCellString(long item, long column) const { wxCHECK(item >= 0 && column >= 0 && column < (int)m_ColumnTypes.size(), _T("")); if (item >= (int)m_ListData.size()) return _T(""); AtObj cell = *m_ListData[item][m_ColumnTypes[column].key]; return AtlasObject::ConvertToString(cell).c_str(); } AtObj EditableListCtrl::GetCellObject(long item, long column) const { wxCHECK(item >= 0 && column >= 0 && column < (int)m_ColumnTypes.size(), AtObj()); if (item >= (int)m_ListData.size()) return AtObj(); return *m_ListData[item][m_ColumnTypes[column].key]; } void EditableListCtrl::SetCellString(long item, long column, wxString& str) { wxCHECK(item >= 0 && column >= 0 && column < (int)m_ColumnTypes.size(), ); MakeSizeAtLeast(item+1); - m_ListData[item].set(m_ColumnTypes[column].key, str); + m_ListData[item].set(m_ColumnTypes[column].key, str.utf8_str()); } void EditableListCtrl::SetCellObject(long item, long column, AtObj& obj) { wxCHECK(item >= 0 && column >= 0 && column < (int)m_ColumnTypes.size(), ); MakeSizeAtLeast(item+1); m_ListData[item].set(m_ColumnTypes[column].key, obj); } wxString EditableListCtrl::OnGetItemText(long item, long column) const { return GetCellString(item, column); } wxListItemAttr* EditableListCtrl::OnGetItemAttr(long item) const { // Make the last two rows white if (item >= (long)m_ListData.size()) return const_cast(&m_ListItemAttr[0]); // Make the background colors of rows alternate else return const_cast(&m_ListItemAttr[item%2]); } void EditableListCtrl::ImportData(AtObj& in) { return DoImport(in); } AtObj EditableListCtrl::ExportData() { return DoExport(); } void EditableListCtrl::ThawData(AtObj& in) { m_ListData.clear(); for (AtIter it = in["item"]; it.defined(); ++it) m_ListData.push_back(*it); UpdateDisplay(); } AtObj EditableListCtrl::FreezeData() { AtObj out; for (std::vector::iterator it = m_ListData.begin(); it != m_ListData.end(); ++it) out.add("item", *it); return out; } BEGIN_EVENT_TABLE(EditableListCtrl, wxListCtrl) EVT_LEFT_DCLICK(EditableListCtrl::OnMouseEvent) EVT_RIGHT_DOWN(EditableListCtrl::OnMouseEvent) EVT_CHAR(EditableListCtrl::OnKeyDown) END_EVENT_TABLE() Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp (revision 25814) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp (revision 25815) @@ -1,747 +1,747 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 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 "Map.h" #include "AtlasObject/AtlasObject.h" #include "AtlasObject/JSONSpiritInclude.h" #include "GameInterface/Messages.h" #include "MapResizeDialog/MapResizeDialog.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/Tools.h" #include #include #include #define CREATE_CHECKBOX(window, parentSizer, name, description, ID) \ parentSizer->Add(new wxStaticText(window, wxID_ANY, _(name)), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); \ parentSizer->Add(Tooltipped(new wxCheckBox(window, ID, wxEmptyString), _(description))); enum { ID_MapName, ID_MapDescription, ID_MapReveal, ID_MapType, ID_MapPreview, ID_MapTeams, ID_MapKW_Demo, ID_MapKW_Naval, ID_MapKW_New, ID_MapKW_Trigger, ID_RandomScript, ID_RandomSize, ID_RandomNomad, ID_RandomSeed, ID_RandomReseed, ID_RandomGenerate, ID_ResizeMap, ID_SimPlay, ID_SimFast, ID_SimSlow, ID_SimPause, ID_SimReset, ID_OpenPlayerPanel }; enum { SimInactive, SimPlaying, SimPlayingFast, SimPlayingSlow, SimPaused }; bool IsPlaying(int s) { return (s == SimPlaying || s == SimPlayingFast || s == SimPlayingSlow); } // TODO: Some of these helper things should be moved out of this file // and into shared locations // 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; }; class MapSettingsControl : public wxPanel { public: MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor); void CreateWidgets(); void ReadFromEngine(); void SetMapSettings(const AtObj& obj); AtObj UpdateSettingsObject(); private: void SendToEngine(); void OnVictoryConditionChanged(long controlId); void OnEdit(wxCommandEvent& evt) { long id = static_cast(evt.GetId()); if (std::any_of(m_VictoryConditions.begin(), m_VictoryConditions.end(), [id](const std::pair& vc) { return vc.first == id; })) OnVictoryConditionChanged(id); SendToEngine(); } std::map m_VictoryConditions; std::set m_MapSettingsKeywords; std::set m_MapSettingsVictoryConditions; std::vector m_PlayerCivChoices; Observable& m_MapSettings; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(MapSettingsControl, wxPanel) EVT_TEXT(ID_MapName, MapSettingsControl::OnEdit) EVT_TEXT(ID_MapDescription, MapSettingsControl::OnEdit) EVT_TEXT(ID_MapPreview, MapSettingsControl::OnEdit) EVT_CHECKBOX(wxID_ANY, MapSettingsControl::OnEdit) EVT_CHOICE(wxID_ANY, MapSettingsControl::OnEdit) END_EVENT_TABLE(); MapSettingsControl::MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor) : wxPanel(parent, wxID_ANY), m_MapSettings(scenarioEditor.GetMapSettings()) { wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Map settings")); SetSizer(sizer); } void MapSettingsControl::CreateWidgets() { wxSizer* sizer = GetSizer(); ///////////////////////////////////////////////////////////////////////// // Map settings 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()); sizer->AddSpacer(5); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5); gridSizer->AddGrowableCol(1); // TODO: have preview selector tool? gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Preview")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapPreview, wxEmptyString), _("Texture used for map preview")), wxSizerFlags().Expand()); CREATE_CHECKBOX(this, gridSizer, "Reveal map", "If checked, players won't need to explore", ID_MapReveal); CREATE_CHECKBOX(this, gridSizer, "Lock teams", "If checked, teams will be locked", ID_MapTeams); sizer->Add(gridSizer, wxSizerFlags().Expand()); sizer->AddSpacer(5); wxStaticBoxSizer* victoryConditionSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Victory Conditions")); wxFlexGridSizer* vcGridSizer = new wxFlexGridSizer(2, 0, 5); vcGridSizer->AddGrowableCol(1); AtlasMessage::qGetVictoryConditionData qryVictoryCondition; qryVictoryCondition.Post(); std::vector victoryConditionData = *qryVictoryCondition.data; for (const std::string& victoryConditionJson : victoryConditionData) { AtObj victoryCondition = AtlasObject::LoadFromJSON(victoryConditionJson); long index = wxWindow::NewControlId(); wxString title = wxString::FromUTF8(victoryCondition["Data"]["Title"]); - std::string escapedTitle = wxString::FromUTF8(title).Lower().ToStdString(); + std::string escapedTitle = title.Lower().ToStdString(); std::replace(escapedTitle.begin(), escapedTitle.end(), ' ', '_'); AtObj updateCondition = *(victoryCondition["Data"]["Title"]); updateCondition.setString(escapedTitle.c_str()); m_VictoryConditions.insert(std::pair(index, victoryCondition)); CREATE_CHECKBOX(this, vcGridSizer, title, "Select " + title + " victory condition.", index); } victoryConditionSizer->Add(vcGridSizer); sizer->Add(victoryConditionSizer, wxSizerFlags().Expand()); sizer->AddSpacer(5); wxStaticBoxSizer* keywordsSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Keywords")); wxFlexGridSizer* kwGridSizer = new wxFlexGridSizer(4, 5, 15); CREATE_CHECKBOX(this, kwGridSizer, "Demo", "If checked, map will only be visible using filters in game setup", ID_MapKW_Demo); CREATE_CHECKBOX(this, kwGridSizer, "Naval", "If checked, map will only be visible using filters in game setup", ID_MapKW_Naval); CREATE_CHECKBOX(this, kwGridSizer, "New", "If checked, the map will appear in the list of new maps", ID_MapKW_New); CREATE_CHECKBOX(this, kwGridSizer, "Trigger", "If checked, the map will appear in the list of maps with trigger scripts", ID_MapKW_Trigger); keywordsSizer->Add(kwGridSizer); sizer->Add(keywordsSizer, wxSizerFlags().Expand()); } void MapSettingsControl::ReadFromEngine() { AtlasMessage::qGetMapSettings qry; qry.Post(); if (!(*qry.settings).empty()) { // Prevent error if there's no map settings to parse m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings); } // map name wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->ChangeValue(wxString::FromUTF8(m_MapSettings["Name"])); // map description wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->ChangeValue(wxString::FromUTF8(m_MapSettings["Description"])); // map preview wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->ChangeValue(wxString::FromUTF8(m_MapSettings["Preview"])); // reveal map wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->SetValue(wxString::FromUTF8(m_MapSettings["RevealMap"]) == "true"); // victory conditions m_MapSettingsVictoryConditions.clear(); for (AtIter victoryCondition = m_MapSettings["VictoryConditions"]["item"]; victoryCondition.defined(); ++victoryCondition) m_MapSettingsVictoryConditions.insert(std::string(victoryCondition)); // Clear Checkboxes before loading data. We don't update victory condition just yet because it might reenable some of the checkboxes. for (const std::pair& vc : m_VictoryConditions) { wxCheckBox* checkBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); if (!checkBox) continue; checkBox->SetValue(false); checkBox->Enable(true); } for (const std::pair& vc : m_VictoryConditions) { std::string escapedTitle = wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(); std::replace(escapedTitle.begin(), escapedTitle.end(), ' ', '_'); if (m_MapSettingsVictoryConditions.find(escapedTitle) == m_MapSettingsVictoryConditions.end()) continue; wxCheckBox* checkBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); if (!checkBox) continue; checkBox->SetValue(true); OnVictoryConditionChanged(vc.first); } // lock teams wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->SetValue(wxString::FromUTF8(m_MapSettings["LockTeams"]) == "true"); // keywords { m_MapSettingsKeywords.clear(); for (AtIter keyword = m_MapSettings["Keywords"]["item"]; keyword.defined(); ++keyword) m_MapSettingsKeywords.insert(std::string(keyword)); wxWindow* window; #define INIT_CHECKBOX(ID, mapSettings, value) \ window = FindWindow(ID); \ if (window != nullptr) \ wxDynamicCast(window, wxCheckBox)->SetValue(mapSettings.count(value) != 0); INIT_CHECKBOX(ID_MapKW_Demo, m_MapSettingsKeywords, "demo"); INIT_CHECKBOX(ID_MapKW_Naval, m_MapSettingsKeywords, "naval"); INIT_CHECKBOX(ID_MapKW_New, m_MapSettingsKeywords, "new"); INIT_CHECKBOX(ID_MapKW_Trigger, m_MapSettingsKeywords, "trigger"); #undef INIT_CHECKBOX } } void MapSettingsControl::SetMapSettings(const AtObj& obj) { m_MapSettings = obj; m_MapSettings.NotifyObservers(); SendToEngine(); } void MapSettingsControl::OnVictoryConditionChanged(long controlId) { AtObj victoryCondition; wxCheckBox* modifiedCheckbox = wxDynamicCast(FindWindow(controlId), wxCheckBox); for (const std::pair& vc : m_VictoryConditions) { if (vc.first != controlId) continue; victoryCondition = vc.second; break; } if (modifiedCheckbox->GetValue()) { for (AtIter victoryConditionPair = victoryCondition["Data"]["ChangeOnChecked"]; victoryConditionPair.defined(); ++victoryConditionPair) { for (const std::pair& vc : m_VictoryConditions) { std::string escapedTitle = wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(); std::replace(escapedTitle.begin(), escapedTitle.end(), ' ', '_'); if (victoryConditionPair[escapedTitle.c_str()].defined()) { wxCheckBox* victoryConditionCheckBox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); victoryConditionCheckBox->SetValue(wxString::FromUTF8(victoryConditionPair[escapedTitle.c_str()]).Lower().ToStdString() == "true"); } } } } for (const std::pair& vc : m_VictoryConditions) { if (vc.first == controlId) continue; wxCheckBox* otherCheckbox = wxDynamicCast(FindWindow(vc.first), wxCheckBox); otherCheckbox->Enable(true); for (const std::pair& vc2 : m_VictoryConditions) { for (AtIter victoryConditionTitle = vc2.second["Data"]["DisabledWhenChecked"]; victoryConditionTitle.defined(); ++victoryConditionTitle) { std::string escapedTitle = wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(); std::replace(escapedTitle.begin(), escapedTitle.end(), ' ', '_'); if (escapedTitle == wxString::FromUTF8(victoryConditionTitle["item"]).ToStdString() && wxDynamicCast(FindWindow(vc2.first), wxCheckBox)->GetValue()) { otherCheckbox->Enable(false); otherCheckbox->SetValue(false); break; } } } } } AtObj MapSettingsControl::UpdateSettingsObject() { // map name m_MapSettings.set("Name", wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->GetValue().utf8_str()); // map description m_MapSettings.set("Description", wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->GetValue().utf8_str()); // map preview m_MapSettings.set("Preview", wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->GetValue().utf8_str()); // reveal map m_MapSettings.setBool("RevealMap", wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->GetValue()); // victory conditions #define INSERT_VICTORY_CONDITION_CHECKBOX(name, ID) \ if (wxDynamicCast(FindWindow(ID), wxCheckBox)->GetValue()) \ m_MapSettingsVictoryConditions.insert(name); \ else \ m_MapSettingsVictoryConditions.erase(name); for (const std::pair& vc : m_VictoryConditions) { std::string escapedTitle = wxString::FromUTF8(vc.second["Data"]["Title"]).Lower().ToStdString(); std::replace(escapedTitle.begin(), escapedTitle.end(), ' ', '_'); INSERT_VICTORY_CONDITION_CHECKBOX(escapedTitle, vc.first) } #undef INSERT_VICTORY_CONDITION_CHECKBOX AtObj victoryConditions; victoryConditions.set("@array", ""); for (const std::string& victoryCondition : m_MapSettingsVictoryConditions) victoryConditions.add("item", victoryCondition.c_str()); m_MapSettings.set("VictoryConditions", victoryConditions); // keywords { #define INSERT_KEYWORDS_CHECKBOX(name, ID) \ if (wxDynamicCast(FindWindow(ID), wxCheckBox)->GetValue()) \ m_MapSettingsKeywords.insert(name); \ else \ m_MapSettingsKeywords.erase(name); INSERT_KEYWORDS_CHECKBOX("demo", ID_MapKW_Demo); INSERT_KEYWORDS_CHECKBOX("naval", ID_MapKW_Naval); INSERT_KEYWORDS_CHECKBOX("new", ID_MapKW_New); INSERT_KEYWORDS_CHECKBOX("trigger", ID_MapKW_Trigger); #undef INSERT_KEYWORDS_CHECKBOX AtObj keywords; keywords.set("@array", ""); for (const std::string& keyword : m_MapSettingsKeywords) keywords.add("item", keyword.c_str()); m_MapSettings.set("Keywords", keywords); } // teams locked m_MapSettings.setBool("LockTeams", wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->GetValue()); // default AI RNG seed m_MapSettings.setInt("AISeed", 0); return m_MapSettings; } void MapSettingsControl::SendToEngine() { UpdateSettingsObject(); std::string json = AtlasObject::SaveToJSON(m_MapSettings); // TODO: would be nice if we supported undo for settings changes POST_COMMAND(SetMapSettings, (json)); } MapSidebar::MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_SimState(SimInactive) { wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); scrolledWindow->SetScrollRate(10, 10); scrolledWindow->SetSizer(scrollSizer); m_MainSizer->Add(scrolledWindow, wxSizerFlags().Expand().Proportion(1)); m_MapSettingsCtrl = new MapSettingsControl(scrolledWindow, m_ScenarioEditor); scrollSizer->Add(m_MapSettingsCtrl, wxSizerFlags().Expand()); { ///////////////////////////////////////////////////////////////////////// // Random map settings wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Random map")); scrollSizer->Add(sizer, wxSizerFlags().Expand()); sizer->Add(new wxChoice(scrolledWindow, ID_RandomScript), wxSizerFlags().Expand()); sizer->AddSpacer(5); sizer->Add(new wxButton(scrolledWindow, ID_OpenPlayerPanel, _T("Change players")), wxSizerFlags().Expand()); sizer->AddSpacer(5); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5); gridSizer->AddGrowableCol(1); wxChoice* sizeChoice = new wxChoice(scrolledWindow, ID_RandomSize); gridSizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Map size")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(sizeChoice, wxSizerFlags().Expand()); CREATE_CHECKBOX(scrolledWindow, gridSizer, "Nomad", "Place only some units instead of starting bases.", ID_RandomNomad); gridSizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Random seed")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); wxBoxSizer* seedSizer = new wxBoxSizer(wxHORIZONTAL); seedSizer->Add(Tooltipped(new wxTextCtrl(scrolledWindow, ID_RandomSeed, _T("0"), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator(wxFILTER_NUMERIC)), _("Seed value for random map")), wxSizerFlags(1).Expand()); seedSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_RandomReseed, _("R"), wxDefaultPosition, wxSize(40, -1)), _("New random seed"))); gridSizer->Add(seedSizer, wxSizerFlags().Expand()); sizer->Add(gridSizer, wxSizerFlags().Expand()); sizer->AddSpacer(5); sizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_RandomGenerate, _("Generate map")), _("Run selected random map script")), wxSizerFlags().Expand()); } { ///////////////////////////////////////////////////////////////////////// // Misc tools wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Misc tools")); sizer->Add(new wxButton(scrolledWindow, ID_ResizeMap, _("Resize/Recenter map")), wxSizerFlags().Expand()); scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Simulation buttons wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Simulation test")); scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 8)); wxGridSizer* gridSizer = new wxGridSizer(5); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimPlay, _("Play"), wxDefaultPosition, wxSize(48, -1)), _("Run the simulation at normal speed")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimFast, _("Fast"), wxDefaultPosition, wxSize(48, -1)), _("Run the simulation at 8x speed")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimSlow, _("Slow"), wxDefaultPosition, wxSize(48, -1)), _("Run the simulation at 1/8x speed")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimPause, _("Pause"), wxDefaultPosition, wxSize(48, -1)), _("Pause the simulation")), wxSizerFlags().Expand()); gridSizer->Add(Tooltipped(new wxButton(scrolledWindow, ID_SimReset, _("Reset"), wxDefaultPosition, wxSize(48, -1)), _("Reset the editor to initial state")), wxSizerFlags().Expand()); sizer->Add(gridSizer, wxSizerFlags().Expand()); UpdateSimButtons(); } } void MapSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt)) { Freeze(); // Toggling the collapsing doesn't seem to update the sidebar layout // automatically, so do it explicitly here Layout(); Refresh(); // fixes repaint glitch on Windows Thaw(); } void MapSidebar::OnFirstDisplay() { // We do this here becase messages are used which requires simulation to be init'd m_MapSettingsCtrl->CreateWidgets(); m_MapSettingsCtrl->ReadFromEngine(); // Load the map sizes list AtlasMessage::qGetMapSizes qrySizes; qrySizes.Post(); AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice); for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) sizeChoice->Append(wxString::FromUTF8(s["Name"]), reinterpret_cast((*s["Tiles"]).getLong())); sizeChoice->SetSelection(0); // Load the RMS script list 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(scripts[i]); wxString name = wxString::FromUTF8(data["settings"]["Name"]); if (!name.IsEmpty()) scriptChoice->Append(name, new AtObjClientData(*data["settings"])); } scriptChoice->SetSelection(0); Layout(); } void MapSidebar::OnMapReload() { m_MapSettingsCtrl->ReadFromEngine(); // Reset sim test buttons POST_MESSAGE(SimPlay, (0.f, false)); POST_MESSAGE(SimStopMusic, ()); POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; UpdateSimButtons(); } void MapSidebar::UpdateSimButtons() { wxButton* button; button = wxDynamicCast(FindWindow(ID_SimPlay), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimPlaying); button = wxDynamicCast(FindWindow(ID_SimFast), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimPlayingFast); button = wxDynamicCast(FindWindow(ID_SimSlow), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimPlayingSlow); button = wxDynamicCast(FindWindow(ID_SimPause), wxButton); wxCHECK(button, ); button->Enable(IsPlaying(m_SimState)); button = wxDynamicCast(FindWindow(ID_SimReset), wxButton); wxCHECK(button, ); button->Enable(m_SimState != SimInactive); } void MapSidebar::OnSimPlay(wxCommandEvent& event) { float speed = 1.f; int newState = SimPlaying; if (event.GetId() == ID_SimFast) { speed = 8.f; newState = SimPlayingFast; } else if (event.GetId() == ID_SimSlow) { speed = 0.125f; newState = SimPlayingSlow; } if (m_SimState == SimInactive) { // Force update of player settings POST_MESSAGE(LoadPlayerSettings, (false)); POST_MESSAGE(SimStateSave, (L"default")); POST_MESSAGE(GuiSwitchPage, (L"page_session.xml")); POST_MESSAGE(SimPlay, (speed, true)); m_SimState = newState; } else // paused or already playing at a different speed { POST_MESSAGE(SimPlay, (speed, true)); m_SimState = newState; } UpdateSimButtons(); } void MapSidebar::OnSimPause(wxCommandEvent& WXUNUSED(event)) { if (IsPlaying(m_SimState)) { POST_MESSAGE(SimPlay, (0.f, true)); m_SimState = SimPaused; } UpdateSimButtons(); } void MapSidebar::OnSimReset(wxCommandEvent& WXUNUSED(event)) { if (IsPlaying(m_SimState)) { POST_MESSAGE(SimPlay, (0.f, true)); POST_MESSAGE(SimStateRestore, (L"default")); POST_MESSAGE(SimStopMusic, ()); POST_MESSAGE(SimPlay, (0.f, false)); POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml")); m_SimState = SimInactive; } else if (m_SimState == SimPaused) { POST_MESSAGE(SimPlay, (0.f, true)); POST_MESSAGE(SimStateRestore, (L"default")); POST_MESSAGE(SimStopMusic, ()); POST_MESSAGE(SimPlay, (0.f, false)); 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)) { if (m_ScenarioEditor.DiscardChangesDialog()) return; 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_MapSettingsCtrl->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()); settings.setInt("Size", wxAtoi(size)); settings.setBool("Nomad", wxDynamicCast(FindWindow(ID_RandomNomad), wxCheckBox)->GetValue()); settings.setInt("Seed", wxAtoi(wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->GetValue())); std::string json = AtlasObject::SaveToJSON(settings); wxBusyInfo busy(_("Generating map")); wxBusyCursor busyc; wxString scriptName = wxString::FromUTF8(settings["Script"]); // Copy the old map settings, so we don't lose them if the map generation fails AtObj oldSettings = settings; AtlasMessage::qGenerateMap qry((std::wstring)scriptName.wc_str(), json); qry.Post(); if (qry.status < 0) { // Display error message and revert to old map settings wxLogError(_("Random map script '%s' failed"), scriptName.c_str()); m_MapSettingsCtrl->SetMapSettings(oldSettings); } m_ScenarioEditor.NotifyOnMapReload(); } void MapSidebar::OnOpenPlayerPanel(wxCommandEvent& WXUNUSED(evt)) { m_ScenarioEditor.SelectPage(_T("PlayerSidebar")); } void MapSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) { MapResizeDialog dlg(this); if (dlg.ShowModal() != wxID_OK) return; wxPoint offset = dlg.GetOffset(); POST_COMMAND(ResizeMap, (dlg.GetNewSize(), offset.x, offset.y)); } BEGIN_EVENT_TABLE(MapSidebar, Sidebar) 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) EVT_BUTTON(ID_ResizeMap, MapSidebar::OnResizeMap) EVT_BUTTON(ID_OpenPlayerPanel, MapSidebar::OnOpenPlayerPanel) END_EVENT_TABLE(); Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp (revision 25814) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp (revision 25815) @@ -1,1005 +1,1002 @@ /* Copyright (C) 2021 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 "Player.h" #include "AtlasObject/AtlasObject.h" #include "CustomControls/ColorDialog/ColorDialog.h" #include "ScenarioEditor/ScenarioEditor.h" #include "wx/choicebk.h" enum { ID_NumPlayers, ID_PlayerFood, ID_PlayerWood, ID_PlayerMetal, ID_PlayerStone, ID_PlayerPop, ID_PlayerColor, ID_DefaultName, ID_DefaultCiv, ID_DefaultColor, ID_DefaultAI, ID_DefaultFood, ID_DefaultWood, ID_DefaultMetal, ID_DefaultStone, ID_DefaultPop, ID_DefaultTeam, ID_CameraSet, ID_CameraView, ID_CameraClear }; // TODO: Some of these helper things should be moved out of this file // and into shared locations // Helper function for adding tooltips static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) { window->SetToolTip(tip); return window; } ////////////////////////////////////////////////////////////////////////// class DefaultCheckbox : public wxCheckBox { public: DefaultCheckbox(wxWindow* parent, wxWindowID id, wxWindow* control, bool initialValue = false) : wxCheckBox(parent, id, wxEmptyString), m_Control(control) { SetValue(initialValue); } virtual void SetValue(bool value) { m_Control->Enable(value); wxCheckBox::SetValue(value); } void OnChecked(wxCommandEvent& evt) { m_Control->Enable(evt.IsChecked()); evt.Skip(); } private: wxWindow* m_Control; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(DefaultCheckbox, wxCheckBox) EVT_CHECKBOX(wxID_ANY, DefaultCheckbox::OnChecked) END_EVENT_TABLE(); class PlayerNotebookPage : public wxPanel { public: PlayerNotebookPage(wxWindow* parent, const wxString& name, size_t playerID) : wxPanel(parent, wxID_ANY), m_Name(name), m_PlayerID(playerID) { m_Controls.page = this; Freeze(); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); SetSizer(sizer); { ///////////////////////////////////////////////////////////////////////// // Player Info wxStaticBoxSizer* playerInfoSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player info")); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5); gridSizer->AddGrowableCol(2); wxTextCtrl* nameCtrl = new wxTextCtrl(this, wxID_ANY); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultName, nameCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(nameCtrl, wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.name = nameCtrl; wxChoice* civChoice = new wxChoice(this, wxID_ANY); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultCiv, civChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Civilisation")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(civChoice, wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.civ = civChoice; wxButton* colorButton = new wxButton(this, ID_PlayerColor); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultColor, colorButton), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Color")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(colorButton, _("Set player color")), wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.color = colorButton; wxChoice* aiChoice = new wxChoice(this, wxID_ANY); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultAI, aiChoice), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("AI")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(aiChoice, _("Select AI")), wxSizerFlags(1).Expand().Align(wxALIGN_RIGHT)); m_Controls.ai = aiChoice; playerInfoSizer->Add(gridSizer, wxSizerFlags(1).Expand()); sizer->Add(playerInfoSizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Resources wxStaticBoxSizer* resourceSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Resources")); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5); gridSizer->AddGrowableCol(2); wxSpinCtrl* foodCtrl = new wxSpinCtrl(this, ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultFood, foodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Food")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(foodCtrl, _("Initial value of food resource")), wxSizerFlags().Expand()); m_Controls.food = foodCtrl; wxSpinCtrl* woodCtrl = new wxSpinCtrl(this, ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultWood, woodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Wood")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(woodCtrl, _("Initial value of wood resource")), wxSizerFlags().Expand()); m_Controls.wood = woodCtrl; wxSpinCtrl* metalCtrl = new wxSpinCtrl(this, ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultMetal, metalCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Metal")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(metalCtrl, _("Initial value of metal resource")), wxSizerFlags().Expand()); m_Controls.metal = metalCtrl; wxSpinCtrl* stoneCtrl = new wxSpinCtrl(this, ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultStone, stoneCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Stone")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(stoneCtrl, _("Initial value of stone resource")), wxSizerFlags().Expand()); m_Controls.stone = stoneCtrl; wxSpinCtrl* popCtrl = new wxSpinCtrl(this, ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(this, ID_DefaultPop, popCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Pop limit")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(popCtrl, _("Population limit for this player")), wxSizerFlags().Expand()); m_Controls.pop = popCtrl; resourceSizer->Add(gridSizer, wxSizerFlags(1).Expand()); sizer->Add(resourceSizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Diplomacy wxStaticBoxSizer* diplomacySizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Diplomacy")); wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL); wxChoice* teamCtrl = new wxChoice(this, wxID_ANY); boxSizer->Add(new DefaultCheckbox(this, ID_DefaultTeam, teamCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); boxSizer->AddSpacer(5); boxSizer->Add(new wxStaticText(this, wxID_ANY, _("Team")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); boxSizer->AddSpacer(5); teamCtrl->Append(_("None")); teamCtrl->Append(_T("1")); teamCtrl->Append(_T("2")); teamCtrl->Append(_T("3")); teamCtrl->Append(_T("4")); boxSizer->Add(teamCtrl); m_Controls.team = teamCtrl; diplomacySizer->Add(boxSizer, wxSizerFlags(1).Expand()); // TODO: possibly have advanced panel where each player's diplomacy can be set? // Advanced panel /*wxCollapsiblePane* advPane = new wxCollapsiblePane(this, wxID_ANY, _("Advanced")); wxWindow* pane = advPane->GetPane(); diplomacySizer->Add(advPane, 0, wxGROW | wxALL, 2);*/ sizer->Add(diplomacySizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Camera wxStaticBoxSizer* cameraSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Starting Camera")); wxGridSizer* gridSizer = new wxGridSizer(3); wxButton* cameraSet = new wxButton(this, ID_CameraSet, _("Set"), wxDefaultPosition, wxSize(48, -1)); gridSizer->Add(Tooltipped(cameraSet, _("Set player camera to this view")), wxSizerFlags().Expand()); wxButton* cameraView = new wxButton(this, ID_CameraView, _("View"), wxDefaultPosition, wxSize(48, -1)); cameraView->Enable(false); gridSizer->Add(Tooltipped(cameraView, _("View the player camera")), wxSizerFlags().Expand()); wxButton* cameraClear = new wxButton(this, ID_CameraClear, _("Clear"), wxDefaultPosition, wxSize(48, -1)); cameraClear->Enable(false); gridSizer->Add(Tooltipped(cameraClear, _("Clear player camera")), wxSizerFlags().Expand()); cameraSizer->Add(gridSizer, wxSizerFlags().Expand()); sizer->Add(cameraSizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } Layout(); Thaw(); } void OnDisplay() { } PlayerPageControls GetControls() { return m_Controls; } wxString GetPlayerName() { return m_Name; } size_t GetPlayerID() { return m_PlayerID; } bool IsCameraDefined() { return m_CameraDefined; } sCameraInfo GetCamera() { return m_Camera; } void SetCamera(sCameraInfo info, bool isDefined = true) { m_Camera = info; m_CameraDefined = isDefined; // Enable/disable controls wxDynamicCast(FindWindow(ID_CameraView), wxButton)->Enable(isDefined); wxDynamicCast(FindWindow(ID_CameraClear), wxButton)->Enable(isDefined); } private: void OnColor(wxCommandEvent& evt) { // Show color dialog ColorDialog colorDlg(this, _T("Scenario Editor/PlayerColor"), m_Controls.color->GetBackgroundColour()); if (colorDlg.ShowModal() == wxID_OK) { m_Controls.color->SetBackgroundColour(colorDlg.GetColourData().GetColour()); // Pass event on to next handler evt.Skip(); } } void OnCameraSet(wxCommandEvent& evt) { AtlasMessage::qGetView qryView; qryView.Post(); SetCamera(qryView.info, true); // Pass event on to next handler evt.Skip(); } void OnCameraView(wxCommandEvent& WXUNUSED(evt)) { POST_MESSAGE(SetView, (m_Camera)); } void OnCameraClear(wxCommandEvent& evt) { SetCamera(sCameraInfo(), false); // Pass event on to next handler evt.Skip(); } sCameraInfo m_Camera; bool m_CameraDefined; wxString m_Name; size_t m_PlayerID; PlayerPageControls m_Controls; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerNotebookPage, wxPanel) EVT_BUTTON(ID_PlayerColor, PlayerNotebookPage::OnColor) EVT_BUTTON(ID_CameraSet, PlayerNotebookPage::OnCameraSet) EVT_BUTTON(ID_CameraView, PlayerNotebookPage::OnCameraView) EVT_BUTTON(ID_CameraClear, PlayerNotebookPage::OnCameraClear) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class PlayerNotebook : public wxChoicebook { public: PlayerNotebook(wxWindow *parent) : wxChoicebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/) { } PlayerPageControls AddPlayer(wxString name, size_t player) { PlayerNotebookPage* playerPage = new PlayerNotebookPage(this, name, player); AddPage(playerPage, name); m_Pages.push_back(playerPage); return playerPage->GetControls(); } void ResizePlayers(size_t numPlayers) { wxASSERT(numPlayers <= m_Pages.size()); // We don't really want to destroy the windows corresponding // to the tabs, so we've kept them in a vector and will // only remove and add them to the notebook as needed int selection = GetSelection(); size_t pageCount = GetPageCount(); if (numPlayers > pageCount) { // Add previously removed pages for (size_t i = pageCount; i < numPlayers; ++i) { AddPage(m_Pages[i], m_Pages[i]->GetPlayerName()); } } else { // Remove previously added pages // we have to manually hide them or they remain visible for (size_t i = pageCount - 1; i >= numPlayers; --i) { m_Pages[i]->Hide(); RemovePage(i); } } // Workaround for bug on wxGTK 2.8: wxChoice selection doesn't update // (in fact it loses its selection when adding/removing pages) GetChoiceCtrl()->SetSelection(selection); } protected: void OnPageChanged(wxChoicebookEvent& evt) { if (evt.GetSelection() >= 0 && evt.GetSelection() < (int)GetPageCount()) { static_cast(GetPage(evt.GetSelection()))->OnDisplay(); } evt.Skip(); } private: std::vector m_Pages; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerNotebook, wxChoicebook) EVT_CHOICEBOOK_PAGE_CHANGED(wxID_ANY, PlayerNotebook::OnPageChanged) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class PlayerSettingsControl : public wxPanel { public: PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor); void CreateWidgets(); void LoadDefaults(); void ReadFromEngine(); AtObj UpdateSettingsObject(); private: void SendToEngine(); void OnEdit(wxCommandEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) { SendToEngine(); } } void OnEditSpin(wxSpinEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) { SendToEngine(); } } void OnPlayerColor(wxCommandEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) { SendToEngine(); // Update player settings, to show new color POST_MESSAGE(LoadPlayerSettings, (false)); } } void OnNumPlayersText(wxCommandEvent& WXUNUSED(evt)) { // Ignore because it will also trigger EVT_SPINCTRL // and we don't want to handle the same event twice } void OnNumPlayersSpin(wxSpinEvent& evt) { if (!m_InGUIUpdate) { wxASSERT(evt.GetInt() > 0); // When wxMessageBox pops up, wxSpinCtrl loses focus, which // forces another EVT_SPINCTRL event, which we don't want // to handle, so we check here for a change if (evt.GetInt() == (int)m_NumPlayers) { return; // No change } size_t oldNumPlayers = m_NumPlayers; m_NumPlayers = evt.GetInt(); if (m_NumPlayers < oldNumPlayers) { // Remove players, but check if they own any entities bool notified = false; for (size_t i = oldNumPlayers; i > m_NumPlayers; --i) { qGetPlayerObjects objectsQry(i); objectsQry.Post(); std::vector ids = *objectsQry.ids; if (ids.size() > 0) { if (!notified) { // TODO: Add option to reassign objects? if (wxMessageBox(_("WARNING: All objects belonging to the removed players will be deleted. Continue anyway?"), _("Remove player confirmation"), wxICON_EXCLAMATION | wxYES_NO) != wxYES) { // Restore previous player count m_NumPlayers = oldNumPlayers; wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers); return; } notified = true; } // Delete objects // TODO: Merge multiple commands? POST_COMMAND(DeleteObjects, (ids)); } } } m_Players->ResizePlayers(m_NumPlayers); SendToEngine(); // Reload players, notify observers POST_MESSAGE(LoadPlayerSettings, (true)); m_MapSettings.NotifyObservers(); } } // TODO: we shouldn't hardcode this, but instead dynamically create // new player notebook pages on demand; of course the default data // will be limited by the entries in player_defaults.json static const size_t MAX_NUM_PLAYERS = 8; bool m_InGUIUpdate; AtObj m_PlayerDefaults; PlayerNotebook* m_Players; std::vector m_PlayerControls; Observable& m_MapSettings; size_t m_NumPlayers; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerSettingsControl, wxPanel) EVT_BUTTON(ID_PlayerColor, PlayerSettingsControl::OnPlayerColor) EVT_BUTTON(ID_CameraSet, PlayerSettingsControl::OnEdit) EVT_BUTTON(ID_CameraClear, PlayerSettingsControl::OnEdit) EVT_CHECKBOX(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_CHOICE(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_TEXT(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersText) EVT_TEXT(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_SPINCTRL(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersSpin) EVT_SPINCTRL(ID_PlayerFood, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerWood, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerStone, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerPop, PlayerSettingsControl::OnEditSpin) END_EVENT_TABLE(); PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor) : wxPanel(parent, wxID_ANY), m_InGUIUpdate(false), m_MapSettings(scenarioEditor.GetMapSettings()), m_NumPlayers(0) { // To prevent recursion, don't handle GUI events right now m_InGUIUpdate = true; wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Player settings")); SetSizer(sizer); wxBoxSizer* boxSizer = new wxBoxSizer(wxHORIZONTAL); boxSizer->Add(new wxStaticText(this, wxID_ANY, _("Num players")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(this, ID_NumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1)); numPlayersSpin->SetValue(MAX_NUM_PLAYERS); numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS); boxSizer->Add(numPlayersSpin); sizer->Add(boxSizer, wxSizerFlags().Expand().Proportion(0)); sizer->AddSpacer(5); m_Players = new PlayerNotebook(this); sizer->Add(m_Players, wxSizerFlags().Expand().Proportion(1)); m_InGUIUpdate = false; } void PlayerSettingsControl::CreateWidgets() { // To prevent recursion, don't handle GUI events right now m_InGUIUpdate = true; // Load default civ and player data 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(civData[i]); civNames.Add(wxString::FromUTF8(civ["Name"])); civCodes.Add(wxString::FromUTF8(civ["Code"])); } // Load AI data ArrayOfAIData ais(AIData::CompareAIData); AtlasMessage::qGetAIData qryAI; qryAI.Post(); AtObj aiData = AtlasObject::LoadFromJSON(*qryAI.data); for (AtIter a = aiData["AIData"]["item"]; a.defined(); ++a) { ais.Add(new AIData(wxString::FromUTF8(a["id"]), wxString::FromUTF8(a["data"]["name"]))); } // Create player pages AtIter playerDefs = m_PlayerDefaults["item"]; if (playerDefs.defined()) ++playerDefs; // Skip gaia for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i) { // Create new player tab and get controls wxString name(_("Unknown")); if (playerDefs["Name"].defined()) name = playerDefs["Name"]; PlayerPageControls controls = m_Players->AddPlayer(name, i); m_PlayerControls.push_back(controls); // Populate civ choice box wxChoice* civChoice = controls.civ; for (size_t j = 0; j < civNames.Count(); ++j) civChoice->Append(civNames[j], new wxStringClientData(civCodes[j])); civChoice->SetSelection(0); // Populate ai choice box wxChoice* aiChoice = controls.ai; aiChoice->Append(_(""), new wxStringClientData()); for (size_t j = 0; j < ais.Count(); ++j) aiChoice->Append(ais[j]->GetName(), new wxStringClientData(ais[j]->GetID())); aiChoice->SetSelection(0); if (playerDefs.defined()) ++playerDefs; } m_InGUIUpdate = false; } void PlayerSettingsControl::LoadDefaults() { AtlasMessage::qGetPlayerDefaults qryPlayers; qryPlayers.Post(); AtObj playerData = AtlasObject::LoadFromJSON(*qryPlayers.defaults); m_PlayerDefaults = *playerData["PlayerData"]; } void PlayerSettingsControl::ReadFromEngine() { AtlasMessage::qGetMapSettings qry; qry.Post(); if (!(*qry.settings).empty()) { // Prevent error if there's no map settings to parse m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings); } else { // Use blank object, it will be created next m_MapSettings = AtObj(); } AtIter player = m_MapSettings["PlayerData"]["item"]; if (!m_MapSettings.defined() || !player.defined() || player.count() == 0) { // Player data missing - set number of players to max m_NumPlayers = MAX_NUM_PLAYERS; } else { ++player; // skip gaia m_NumPlayers = player.count(); } wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS && m_NumPlayers != 0); // To prevent recursion, don't handle GUI events right now m_InGUIUpdate = true; wxDynamicCast(FindWindow(ID_NumPlayers), wxSpinCtrl)->SetValue(m_NumPlayers); // Remove / add extra player pages as needed m_Players->ResizePlayers(m_NumPlayers); // Update player controls with player data AtIter playerDefs = m_PlayerDefaults["item"]; if (playerDefs.defined()) ++playerDefs; // skip gaia for (size_t i = 0; i < MAX_NUM_PLAYERS; ++i) { const PlayerPageControls& controls = m_PlayerControls[i]; // name wxString name(_("Unknown")); bool defined = player["Name"].defined(); if (defined) name = wxString::FromUTF8(player["Name"]); else if (playerDefs["Name"].defined()) name = wxString::FromUTF8(playerDefs["Name"]); controls.name->SetValue(name); wxDynamicCast(FindWindowById(ID_DefaultName, controls.page), DefaultCheckbox)->SetValue(defined); // civ wxChoice* choice = controls.civ; defined = player["Civ"].defined(); wxString civCode = wxString::FromUTF8(defined ? player["Civ"] : playerDefs["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; } } wxDynamicCast(FindWindowById(ID_DefaultCiv, controls.page), DefaultCheckbox)->SetValue(defined); // color wxColor color; AtObj clrObj = *player["Color"]; defined = clrObj.defined(); if (!defined) clrObj = *playerDefs["Color"]; color = wxColor((*clrObj["r"]).getInt(), (*clrObj["g"]).getInt(), (*clrObj["b"]).getInt()); controls.color->SetBackgroundColour(color); wxDynamicCast(FindWindowById(ID_DefaultColor, controls.page), DefaultCheckbox)->SetValue(defined); // player type defined = player["AI"].defined(); wxString aiID = wxString::FromUTF8(defined ? player["AI"] : playerDefs["AI"]); choice = controls.ai; if (!aiID.empty()) { // AI for (size_t j = 0; j < choice->GetCount(); ++j) { wxStringClientData* str = dynamic_cast(choice->GetClientObject(j)); if (str->GetData() == aiID) { choice->SetSelection(j); break; } } } else // Human choice->SetSelection(0); wxDynamicCast(FindWindowById(ID_DefaultAI, controls.page), DefaultCheckbox)->SetValue(defined); // resources AtObj resObj = *player["Resources"]; defined = resObj.defined() && resObj["food"].defined(); if (defined) controls.food->SetValue((*resObj["food"]).getInt()); else controls.food->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultFood, controls.page), DefaultCheckbox)->SetValue(defined); defined = resObj.defined() && resObj["wood"].defined(); if (defined) controls.wood->SetValue((*resObj["wood"]).getInt()); else controls.wood->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultWood, controls.page), DefaultCheckbox)->SetValue(defined); defined = resObj.defined() && resObj["metal"].defined(); if (defined) controls.metal->SetValue((*resObj["metal"]).getInt()); else controls.metal->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultMetal, controls.page), DefaultCheckbox)->SetValue(defined); defined = resObj.defined() && resObj["stone"].defined(); if (defined) controls.stone->SetValue((*resObj["stone"]).getInt()); else controls.stone->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultStone, controls.page), DefaultCheckbox)->SetValue(defined); // population limit defined = player["PopulationLimit"].defined(); if (defined) controls.pop->SetValue((*player["PopulationLimit"]).getInt()); else controls.pop->SetValue(0); wxDynamicCast(FindWindowById(ID_DefaultPop, controls.page), DefaultCheckbox)->SetValue(defined); // team defined = player["Team"].defined(); if (defined) controls.team->SetSelection((*player["Team"]).getInt() + 1); else controls.team->SetSelection(0); wxDynamicCast(FindWindowById(ID_DefaultTeam, controls.page), DefaultCheckbox)->SetValue(defined); // camera if (player["StartingCamera"].defined()) { sCameraInfo info; // Don't use wxAtof because it depends on locales which // may cause problems with decimal points // see: http://www.wxwidgets.org/docs/faqgtk.htm#locale AtObj camPos = *player["StartingCamera"]["Position"]; info.pX = (float)(*camPos["x"]).getDouble(); info.pY = (float)(*camPos["y"]).getDouble(); info.pZ = (float)(*camPos["z"]).getDouble(); AtObj camRot = *player["StartingCamera"]["Rotation"]; info.rX = (float)(*camRot["x"]).getDouble(); info.rY = (float)(*camRot["y"]).getDouble(); info.rZ = (float)(*camRot["z"]).getDouble(); controls.page->SetCamera(info, true); } else controls.page->SetCamera(sCameraInfo(), false); if (player.defined()) ++player; if (playerDefs.defined()) ++playerDefs; } // Send default properties to engine, since they might not be set SendToEngine(); m_InGUIUpdate = false; } AtObj PlayerSettingsControl::UpdateSettingsObject() { // Update player data in the map settings AtObj players; players.set("@array", ""); wxASSERT(m_NumPlayers <= MAX_NUM_PLAYERS); AtIter playerDefs = m_PlayerDefaults["item"]; if (playerDefs.defined()) ++playerDefs; // Skip gaia for (size_t i = 0; i < m_NumPlayers; ++i) { PlayerPageControls controls = m_PlayerControls[i]; AtObj player; // name wxTextCtrl* text = controls.name; if (text->IsEnabled()) player.set("Name", text->GetValue().utf8_str()); // civ wxChoice* choice = controls.civ; if (choice->IsEnabled() && choice->GetSelection() >= 0) { wxStringClientData* str = dynamic_cast(choice->GetClientObject(choice->GetSelection())); - player.set("Civ", str->GetData()); + player.set("Civ", str->GetData().utf8_str()); } else player.unset("Civ"); // color if (controls.color->IsEnabled()) { wxColor color = controls.color->GetBackgroundColour(); AtObj clrObj; clrObj.setInt("r", (int)color.Red()); clrObj.setInt("g", (int)color.Green()); clrObj.setInt("b", (int)color.Blue()); player.set("Color", clrObj); } // player type choice = controls.ai; - if (choice->IsEnabled()) + if (choice->IsEnabled() && choice->GetSelection() > 0) { - if (choice->GetSelection() > 0) - { - // ai - get id - wxStringClientData* str = dynamic_cast(choice->GetClientObject(choice->GetSelection())); - player.set("AI", str->GetData()); - } - else // human - player.set("AI", _("")); + // ai - get id + wxStringClientData* str = dynamic_cast(choice->GetClientObject(choice->GetSelection())); + player.set("AI", str->GetData().utf8_str()); } + else // human + player.unset("AI"); // resources AtObj resObj; if (controls.food->IsEnabled()) resObj.setInt("food", controls.food->GetValue()); if (controls.wood->IsEnabled()) resObj.setInt("wood", controls.wood->GetValue()); if (controls.metal->IsEnabled()) resObj.setInt("metal", controls.metal->GetValue()); if (controls.stone->IsEnabled()) resObj.setInt("stone", controls.stone->GetValue()); if (resObj.defined()) player.set("Resources", resObj); // population limit if (controls.pop->IsEnabled()) player.setInt("PopulationLimit", controls.pop->GetValue()); // team choice = controls.team; if (choice->IsEnabled() && choice->GetSelection() >= 0) player.setInt("Team", choice->GetSelection() - 1); // camera AtObj camObj; if (controls.page->IsCameraDefined()) { sCameraInfo cam = controls.page->GetCamera(); AtObj camPos; camPos.setDouble("x", cam.pX); camPos.setDouble("y", cam.pY); camPos.setDouble("z", cam.pZ); camObj.set("Position", camPos); AtObj camRot; camRot.setDouble("x", cam.rX); camRot.setDouble("y", cam.rY); camRot.setDouble("z", cam.rZ); camObj.set("Rotation", camRot); } player.set("StartingCamera", camObj); players.add("item", player); if (playerDefs.defined()) ++playerDefs; } m_MapSettings.set("PlayerData", players); return m_MapSettings; } void PlayerSettingsControl::SendToEngine() { UpdateSettingsObject(); std::string json = AtlasObject::SaveToJSON(m_MapSettings); // TODO: would be nice if we supported undo for settings changes POST_COMMAND(SetMapSettings, (json)); } ////////////////////////////////////////////////////////////////////////// PlayerSidebar::PlayerSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_Loaded(false) { wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); scrolledWindow->SetScrollRate(10, 10); scrolledWindow->SetSizer(scrollSizer); m_MainSizer->Add(scrolledWindow, wxSizerFlags().Proportion(1).Expand()); m_PlayerSettingsCtrl = new PlayerSettingsControl(scrolledWindow, m_ScenarioEditor); scrollSizer->Add(m_PlayerSettingsCtrl, wxSizerFlags().Expand()); } void PlayerSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt)) { Freeze(); // Toggling the collapsing doesn't seem to update the sidebar layout // automatically, so do it explicitly here Layout(); Refresh(); // fixes repaint glitch on Windows Thaw(); } void PlayerSidebar::OnFirstDisplay() { // We do this here becase messages are used which requires simulation to be init'd m_PlayerSettingsCtrl->LoadDefaults(); m_PlayerSettingsCtrl->CreateWidgets(); m_PlayerSettingsCtrl->ReadFromEngine(); m_Loaded = true; Layout(); } void PlayerSidebar::OnMapReload() { // Make sure we've loaded the controls if (m_Loaded) { m_PlayerSettingsCtrl->ReadFromEngine(); } } BEGIN_EVENT_TABLE(PlayerSidebar, Sidebar) EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, PlayerSidebar::OnCollapse) END_EVENT_TABLE();