Index: ps/trunk/build/premake/premake5.lua =================================================================== --- ps/trunk/build/premake/premake5.lua +++ ps/trunk/build/premake/premake5.lua @@ -1136,6 +1136,7 @@ "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", + "CustomControls/MapResizeDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.h +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.h @@ -32,6 +32,12 @@ public: CMiniMap(CGUI& pGUI); virtual ~CMiniMap(); + + /** + * @return The maximum height for unit passage in water. + */ + static float GetShallowPassageHeight(); + protected: virtual void Draw(); Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp @@ -49,7 +49,7 @@ #include "simulation2/helpers/Los.h" #include "simulation2/system/ParamNode.h" -#include +#include extern bool g_GameRestarted; @@ -79,14 +79,7 @@ // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); - // Get the maximum height for unit passage in water. - CParamNode externalParamNode; - CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); - const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); - if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) - m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); - else - m_ShallowPassageHeight = 0.0f; + m_ShallowPassageHeight = GetShallowPassageHeight(); m_AttributePos.type = GL_FLOAT; m_AttributePos.elems = 2; @@ -719,3 +712,15 @@ SAFE_ARRAY_DELETE(m_TerrainData); } + +// static +float CMiniMap::GetShallowPassageHeight() +{ + float shallowPassageHeight = 0.0f; + CParamNode externalParamNode; + CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); + const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); + if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) + shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); + return shallowPassageHeight; +} Index: ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h +++ ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h @@ -0,0 +1,51 @@ +/* Copyright (C) 2020 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_MAPRESIZEDIALOG +#define INCLUDED_MAPRESIZEDIALOG + +#include "PseudoMiniMapPanel.h" + +#include + +class MapResizeDialog : public wxDialog +{ +public: + MapResizeDialog(wxWindow* parent); + + /** + * Returns selected new size. + */ + ssize_t GetNewSize() const; + + /** + * Returns the offset from center. + */ + wxPoint GetOffset() const; + +private: + void OnCancel(wxCommandEvent& evt); + void OnOK(wxCommandEvent& evt); + void OnListBox(wxCommandEvent& evt); + + ssize_t m_NewSize; + PseudoMiniMapPanel* m_MiniMap; + + DECLARE_EVENT_TABLE(); +}; + +#endif // INCLUDED_MAPRESIZEDIALOG Index: ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp +++ ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp @@ -0,0 +1,119 @@ +/* Copyright (C) 2020 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 "MapResizeDialog.h" +#include "GameInterface/MessagePasser.h" +#include "GameInterface/Messages.h" +#include "ScenarioEditor/ScenarioEditor.h" + +#include + +MapResizeDialog::MapResizeDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxRESIZE_BORDER) +{ + Freeze(); + + AtlasMessage::qGetCurrentMapSize qrySize; + qrySize.Post(); + m_NewSize = qrySize.size; + + SetTitle(_("Resize/Recenter map")); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText* label = new wxStaticText(this, wxID_ANY, _("Select new map size and center."), wxDefaultPosition, wxDefaultSize); + sizer->Add(label, wxSizerFlags().Align(wxALIGN_CENTER_HORIZONTAL).Border(wxALL, 10)); + + wxBoxSizer* listAndMap = new wxBoxSizer(wxHORIZONTAL); + + wxListBox* listBox = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE | wxLB_HSCROLL); + // Load the map sizes list + AtlasMessage::qGetMapSizes qrySizes; + qrySizes.Post(); + AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); + for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) + { + wxString size = wxString::FromUTF8(s["Tiles"]); + listBox->Append(wxString::FromUTF8(s["Name"]), new wxStringClientData(size)); + if (m_NewSize == static_cast(wxAtoi(size))) + listBox->SetSelection(listBox->GetCount() - 1); + } + listAndMap->Add(listBox, wxSizerFlags().Align(wxALIGN_LEFT).Proportion(1).Expand()); + listAndMap->AddSpacer(10); + + m_MiniMap = new PseudoMiniMapPanel(this, m_NewSize); + listBox->Bind(wxEVT_LISTBOX, &PseudoMiniMapPanel::OnNewSize, m_MiniMap); + + listAndMap->Add(m_MiniMap, wxSizerFlags()); + sizer->Add(listAndMap, wxSizerFlags().Proportion(1).Expand().Border(wxLEFT | wxRIGHT, 10)); + + sizer->AddSpacer(5); + sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), wxSizerFlags().Expand().Border(wxRIGHT | wxLEFT, 7)); + sizer->AddSpacer(5); + + wxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL); + buttonSizer->Add(new wxButton(this, wxID_OK, _("OK"))); + buttonSizer->AddSpacer(5); + buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("Cancel"))); + + sizer->Add(buttonSizer, wxSizerFlags().Align(wxALIGN_RIGHT).Border(wxRIGHT | wxBOTTOM, 10)); + + SetSizerAndFit(sizer); + Layout(); + Thaw(); +} + +ssize_t MapResizeDialog::GetNewSize() const +{ + return m_NewSize; +} + +wxPoint MapResizeDialog::GetOffset() const +{ + return m_MiniMap->GetOffset(); +} + +void MapResizeDialog::OnListBox(wxCommandEvent& evt) +{ + if (!evt.IsSelection()) + return; + + const wxString str = static_cast(evt.GetClientObject())->GetData(); + long value = 0; + if (str.ToLong(&value)) + m_NewSize = static_cast(value); + + if (evt.GetEventType() == wxEVT_COMMAND_LISTBOX_DOUBLECLICKED) + EndModal(wxID_OK); +} + +void MapResizeDialog::OnCancel(wxCommandEvent& WXUNUSED(evt)) +{ + EndModal(wxID_CANCEL); +} + +void MapResizeDialog::OnOK(wxCommandEvent& WXUNUSED(evt)) +{ + EndModal(wxID_OK); +} + +BEGIN_EVENT_TABLE(MapResizeDialog, wxDialog) + EVT_BUTTON(wxID_CANCEL, MapResizeDialog::OnCancel) + EVT_BUTTON(wxID_OK, MapResizeDialog::OnOK) + EVT_LISTBOX(wxID_ANY, MapResizeDialog::OnListBox) + EVT_LISTBOX_DCLICK(wxID_ANY, MapResizeDialog::OnListBox) +END_EVENT_TABLE() Index: ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h +++ ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2020 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_PSEUDOMINIMAPPANEL +#define INCLUDED_PSEUDOMINIMAPPANEL + +#include +#include + +class PseudoMiniMapPanel : public wxPanel +{ +public: + PseudoMiniMapPanel(wxWindow* parent, int currentSize); + + void PaintEvent(wxPaintEvent& evt); + void EraseBackground(wxEraseEvent& evt); + + void OnNewSize(wxCommandEvent& evt); + + wxPoint GetOffset() const; +private: + void OnMouseDown(wxMouseEvent& evt); + void OnMouseUp(wxMouseEvent& evt); + void OnMouseMove(wxMouseEvent& evt); + void OnMouseLeave(wxMouseEvent& evt); + + const ssize_t m_CurrentSize; + wxImage m_Background; + std::map m_ScreenTones; + std::map m_Backgrounds; + + wxPoint m_LastMousePos; + bool m_Dragging; + wxPoint m_SelectionCenter; + int m_SelectionRadius; + bool m_SameOrGrowing; + ssize_t m_NewSize; + + DECLARE_EVENT_TABLE(); +}; + +#endif // INCLUDED_PSEUDOMINIMAPPANEL Index: ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp +++ ps/trunk/source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp @@ -0,0 +1,232 @@ +/* Copyright (C) 2020 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 "PseudoMiniMapPanel.h" + +#include "GameInterface/MessagePasser.h" +#include "GameInterface/Messages.h" +#include "ScenarioEditor/Tools/Common/Tools.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + const int PanelRadius = 64 + 1; + const wxPoint PanelCenter = wxPoint(PanelRadius + 1, PanelRadius + 1); + const wxPoint ScreenToneOffset(-2 * PanelRadius, -2 * PanelRadius); + const wxPen Rim(*wxBLACK, 3); + const wxPen BackgroundMask(*wxBLACK, 2 * PanelRadius); + + bool Within(const wxPoint& test, const wxPoint& center, int radius) + { + int dx = abs(test.x - center.x); + if (dx > radius) + return false; + int dy = abs(test.y - center.y); + if (dy > radius) + return false; + if (dx + dy <= radius) + return true; + return dx * dx + dy * dy <= radius * radius; + } +} + +PseudoMiniMapPanel::PseudoMiniMapPanel(wxWindow* parent, int currentSize) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(PanelRadius * 2 + 1, PanelRadius * 2 + 1)), + m_CurrentSize(currentSize), m_ScreenTones(), + m_LastMousePos(-1, -1), m_Dragging(false), + m_SelectionRadius(PanelRadius), m_SelectionCenter(PanelCenter), m_SameOrGrowing(true), m_NewSize(currentSize) +{ + AtlasMessage::qRasterizeMinimap qryBackground; + qryBackground.Post(); + int dim = qryBackground.dimension; + std::vector imageBytes = *qryBackground.imageBytes; + + // Data is destined for a wxImage, which uses free. + uint8_t* data = new uint8_t[imageBytes.size()]; + std::copy(imageBytes.cbegin(), imageBytes.cend(), data); + m_Background = wxImage(dim, dim, data); + m_Background.Rescale(PanelRadius * 2, PanelRadius * 2, wxIMAGE_QUALITY_BOX_AVERAGE); + m_Backgrounds[PanelRadius] = wxBitmap(m_Background); + + SetBackgroundStyle(wxBG_STYLE_PAINT); +} + +wxPoint PseudoMiniMapPanel::GetOffset() const +{ + // Since offset is from center, amplitude is (at most) half the largest size. + int size = std::max(m_CurrentSize, m_NewSize) / 2; + // If the map is growing, the display is opposite what the actual offset is. + float scalar = (m_SameOrGrowing ? 1.0 : -1.0) / PanelRadius * size; + // Rebase offsets to center. + int hOffset = m_SelectionCenter.x - PanelCenter.x; + int vOffset = m_SelectionCenter.y - PanelCenter.y; + return wxPoint(scalar * hOffset, scalar * vOffset); +} + +void PseudoMiniMapPanel::OnNewSize(wxCommandEvent& evt) +{ + if (!evt.IsSelection()) + return; + + evt.Skip(); + + m_NewSize = wxAtoi(static_cast(evt.GetClientObject())->GetData()); + + m_SameOrGrowing = m_NewSize >= m_CurrentSize; + m_SelectionRadius = std::min(m_NewSize, m_CurrentSize) * PanelRadius / std::max(m_NewSize, m_CurrentSize); + if (!m_SameOrGrowing && m_ScreenTones.find(m_SelectionRadius) == m_ScreenTones.cend()) + { + wxImage overlay = wxImage(PanelRadius * 4, PanelRadius * 4); + overlay.InitAlpha(); + wxGraphicsContext* gc = wxGraphicsContext::Create(overlay); + gc->SetBrush(*wxGREY_BRUSH); + gc->DrawRectangle(0, 0, PanelRadius * 4, PanelRadius * 4); + gc->SetBrush(*wxBLACK_BRUSH); + gc->DrawEllipse(PanelRadius * 2 - m_SelectionRadius, PanelRadius * 2 - m_SelectionRadius, m_SelectionRadius * 2, m_SelectionRadius * 2); + gc->SetPen(*wxWHITE_PEN); + gc->DrawEllipse(PanelRadius * 2 - m_SelectionRadius, PanelRadius * 2 - m_SelectionRadius, m_SelectionRadius * 2, m_SelectionRadius * 2); + delete gc; + // Black -> Converted to transparent. + // White -> converted to black. + overlay.ConvertColourToAlpha(0, 0, 0); + + m_ScreenTones[m_SelectionRadius] = wxBitmap(overlay); + } + else if (m_SameOrGrowing && m_Backgrounds.find(m_SelectionRadius) == m_Backgrounds.cend()) + { + wxImage rescaled = wxImage(m_Background); + rescaled.Rescale(2 * m_SelectionRadius, 2 * m_SelectionRadius, wxIMAGE_QUALITY_BOX_AVERAGE); + m_Backgrounds[m_SelectionRadius] = wxBitmap(rescaled); + } + + Refresh(); +} + +void PseudoMiniMapPanel::OnMouseDown(wxMouseEvent& evt) +{ + // Capture on button-down, so we can respond even when the mouse + // moves off the window + if (!m_Dragging && evt.ButtonDown() && + Within(evt.GetPosition(), PanelCenter, PanelRadius) && + Within(evt.GetPosition(), m_SelectionCenter, m_SelectionRadius)) + { + m_LastMousePos = evt.GetPosition(); + m_Dragging = true; + } +} + +void PseudoMiniMapPanel::OnMouseUp(wxMouseEvent& evt) +{ + if (m_Dragging && + !(evt.ButtonIsDown(wxMOUSE_BTN_LEFT) || evt.ButtonIsDown(wxMOUSE_BTN_MIDDLE) || evt.ButtonIsDown(wxMOUSE_BTN_RIGHT)) + ) + { + m_Dragging = false; + } +} + +void PseudoMiniMapPanel::OnMouseMove(wxMouseEvent& evt) +{ + if (m_Dragging && evt.Dragging()) + { + if (m_LastMousePos == evt.GetPosition()) + return; + + wxPoint delta = evt.GetPosition() - m_LastMousePos; + wxPoint moved = m_SelectionCenter + delta; + + if (!Within(moved, PanelCenter, PanelRadius)) + return; + + m_SelectionCenter = moved; + m_LastMousePos = evt.GetPosition(); + Refresh(); + } +} + +void PseudoMiniMapPanel::OnMouseLeave(wxMouseEvent& WXUNUSED(evt)) +{ + m_Dragging = false; +} + +void PseudoMiniMapPanel::PaintEvent(wxPaintEvent& WXUNUSED(evt)) +{ + wxAutoBufferedPaintDC dca(this); + // Background must be grabbed from paint dc, not gc, or color may be transparent. + wxColor background = dca.GetBackground().GetColour(); + wxGCDC dc(dca); + if (m_SameOrGrowing) + { + dc.DrawBitmap(m_Backgrounds[m_SelectionRadius], m_SelectionCenter - wxSize(m_SelectionRadius, m_SelectionRadius)); + + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(BackgroundMask); + dc.DrawCircle(m_SelectionCenter, PanelRadius + m_SelectionRadius); + + const wxPen BorderPen(*wxWHITE, 2); + dc.SetPen(BorderPen); + dc.DrawCircle(m_SelectionCenter, m_SelectionRadius); + } + else + { + dc.DrawBitmap(m_Backgrounds[PanelRadius], 0, 0); + // "fade out" trimmed areas by drawing a screentone ring ring. + dc.DrawBitmap(m_ScreenTones[m_SelectionRadius], ScreenToneOffset + m_SelectionCenter); + } + + // Centering markers. + dc.SetBrush(*wxBLACK_BRUSH); + dc.SetPen(*wxBLACK_PEN); + dc.DrawCircle(m_SelectionCenter, 2); + dc.SetPen(*wxWHITE_PEN); + dc.DrawLine(PanelRadius - 10, PanelRadius, PanelRadius + 10, PanelRadius); + dc.DrawLine(PanelRadius, PanelRadius + 10, PanelRadius, PanelRadius - 10); + + // Round border. + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(Rim); + dc.DrawCircle(PanelCenter, PanelRadius - 1); + wxPen mask(background, PanelRadius); + dc.SetPen(mask); + dc.DrawCircle(PanelCenter, PanelRadius + PanelRadius / 2 - 1); +} + +void PseudoMiniMapPanel::EraseBackground(wxEraseEvent& WXUNUSED(evt)) +{ + // Do nothing - don't erase to remove flicker. +} + +BEGIN_EVENT_TABLE(PseudoMiniMapPanel, wxPanel) + EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseUp) + EVT_LEFT_DOWN(PseudoMiniMapPanel::OnMouseDown) + EVT_LEFT_UP(PseudoMiniMapPanel::OnMouseUp) + EVT_RIGHT_DOWN(PseudoMiniMapPanel::OnMouseDown) + EVT_RIGHT_UP(PseudoMiniMapPanel::OnMouseUp) + EVT_MIDDLE_DOWN(PseudoMiniMapPanel::OnMouseDown) + EVT_MIDDLE_UP(PseudoMiniMapPanel::OnMouseUp) + EVT_MOTION(PseudoMiniMapPanel::OnMouseMove) + EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseLeave) + EVT_PAINT(PseudoMiniMapPanel::PaintEvent) +END_EVENT_TABLE() Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ #include "../Common/Sidebar.h" -#include "wx/collpane.h" +#include class MapSettingsControl; @@ -35,12 +35,13 @@ MapSettingsControl* m_MapSettingsCtrl; void OnCollapse(wxCollapsiblePaneEvent& evt); + void OnOpenPlayerPanel(wxCommandEvent& evt); + void OnRandomReseed(wxCommandEvent& evt); + void OnRandomGenerate(wxCommandEvent& evt); + void OnResizeMap(wxCommandEvent& evt); void OnSimPlay(wxCommandEvent& evt); void OnSimPause(wxCommandEvent& evt); void OnSimReset(wxCommandEvent& evt); - void OnRandomReseed(wxCommandEvent& evt); - void OnRandomGenerate(wxCommandEvent& evt); - void OnOpenPlayerPanel(wxCommandEvent& evt); void UpdateSimButtons(); int m_SimState; Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp @@ -22,6 +22,7 @@ #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" @@ -50,6 +51,7 @@ ID_RandomSeed, ID_RandomReseed, ID_RandomGenerate, + ID_ResizeMap, ID_SimPlay, ID_SimFast, ID_SimSlow, @@ -463,6 +465,14 @@ { ///////////////////////////////////////////////////////////////////////// + // 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)); @@ -696,6 +706,16 @@ 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) @@ -705,5 +725,6 @@ 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/Terrain/Terrain.h =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -30,11 +30,9 @@ private: void OnPassabilityChoice(wxCommandEvent& evt); void OnShowPriorities(wxCommandEvent& evt); - void OnResizeMap(wxCommandEvent& evt); wxChoice* m_PassabilityChoice; TexturePreviewPanel* m_TexturePreview; DECLARE_EVENT_TABLE(); }; - Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -26,12 +26,12 @@ #include "GameInterface/Messages.h" -#include "wx/spinctrl.h" -#include "wx/listctrl.h" -#include "wx/image.h" -#include "wx/imaglist.h" -#include "wx/busyinfo.h" -#include "wx/notebook.h" +#include +#include +#include +#include +#include +#include class TextureNotebook; @@ -47,8 +47,7 @@ enum { ID_Passability = 1, - ID_ShowPriorities, - ID_ResizeMap + ID_ShowPriorities }; // Helper function for adding tooltips @@ -247,14 +246,6 @@ _("Show terrain texture priorities"))); } - { - ///////////////////////////////////////////////////////////////////////// - // Misc tools - wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Misc tools")); - sizer->Add(new wxButton(scrolledWindow, ID_ResizeMap, _("Resize map")), wxSizerFlags().Expand()); - scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); - } - m_BottomBar = new TerrainBottomBar(scenarioEditor, bottomBarContainer); } @@ -283,37 +274,9 @@ POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked())); } -void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) -{ - wxArrayString sizeNames; - std::vector sizeTiles; - - // Load the map sizes list - AtlasMessage::qGetMapSizes qrySizes; - qrySizes.Post(); - AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); - for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) - { - sizeNames.Add(wxString::FromUTF8(s["Name"])); - sizeTiles.push_back((*s["Tiles"]).getLong()); - } - - // TODO: set default based on current map size - - wxSingleChoiceDialog dlg(this, _("Select new map size. WARNING: This probably only works reliably on blank maps."), - _("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(); ////////////////////////////////////////////////////////////////////////// Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -18,8 +18,9 @@ #include "precompiled.h" #include "MessageHandler.h" -#include "../GameLoop.h" #include "../CommandProc.h" +#include "../GameLoop.h" +#include "../MessagePasser.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" @@ -29,6 +30,7 @@ #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" +#include "gui/ObjectTypes/CMiniMap.h" #include "lib/bits.h" #include "lib/file/vfs/vfs_path.h" #include "lib/status.h" @@ -36,16 +38,22 @@ #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" +#include "ps/GameSetup/GameSetup.h" #include "ps/Loader.h" #include "ps/World.h" #include "renderer/Renderer.h" +#include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpTerrain.h" +#include "simulation2/components/ICmpVisual.h" +#include "simulation2/system/ParamNode.h" namespace { @@ -271,19 +279,130 @@ msg->sizes = g_Game->GetSimulation2()->GetMapSizes(); } +QUERYHANDLER(RasterizeMinimap) +{ + // TODO: remove the code duplication of the rasterization algorithm, using + // CMinimap version. + const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + const ssize_t dimension = terrain->GetVerticesPerSide() - 1; + const ssize_t bpp = 24; + const ssize_t imageDataSize = dimension * dimension * (bpp / 8); + + std::vector imageBytes(imageDataSize); + + float shallowPassageHeight = CMiniMap::GetShallowPassageHeight(); + + ssize_t w = dimension; + ssize_t h = dimension; + float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; + + for (ssize_t j = 0; j < h; ++j) + { + // Work backwards to vertically flip the image. + ssize_t position = 3 * (h - j - 1) * dimension; + for (ssize_t i = 0; i < w; ++i) + { + float avgHeight = (terrain->GetVertexGroundLevel(i, j) + + terrain->GetVertexGroundLevel(i + 1, j) + + terrain->GetVertexGroundLevel(i, j + 1) + + terrain->GetVertexGroundLevel(i + 1, j + 1) + ) / 4.0f; + + if (avgHeight < waterHeight && avgHeight > waterHeight - shallowPassageHeight) + { + // shallow water + imageBytes[position++] = 0x70; + imageBytes[position++] = 0x98; + imageBytes[position++] = 0xc0; + } + else if (avgHeight < waterHeight) + { + // Set water as constant color for consistency on different maps + imageBytes[position++] = 0x50; + imageBytes[position++] = 0x78; + imageBytes[position++] = 0xa0; + } + else + { + u32 color = std::numeric_limits::max(); + u32 hmap = static_cast(terrain->GetHeightMap()[j * dimension + i]) >> 8; + float scale = hmap / 3.0f + 170.0f / 255.0f; + + CMiniPatch* mp = terrain->GetTile(i, j); + if (mp) + { + CTerrainTextureEntry* tex = mp->GetTextureEntry(); + if (tex) + color = tex->GetBaseColor(); + } + + // Convert + imageBytes[position++] = static_cast(static_cast(color & 0xff) * scale); + imageBytes[position++] = static_cast(static_cast((color >> 8) & 0xff) * scale); + imageBytes[position++] = static_cast(static_cast((color >> 16) & 0xff) * scale); + } + } + } + + msg->imageBytes = std::move(imageBytes); + msg->dimension = dimension; +} + QUERYHANDLER(GetRMSData) { msg->data = g_Game->GetSimulation2()->GetRMSData(); } +QUERYHANDLER(GetCurrentMapSize) +{ + msg->size = g_Game->GetWorld()->GetTerrain()->GetTilesPerSide(); +} + BEGIN_COMMAND(ResizeMap) { - int m_OldTiles, m_NewTiles; + bool Within(const CFixedVector3D& pos, const int centerX, const int centerZ, const int radius) + { + int dx = abs(pos.X.ToInt_RoundToZero() - centerX); + if (dx > radius) + return false; + int dz = abs(pos.Z.ToInt_RoundToZero() - centerZ); + if (dz > radius) + return false; + if (dx + dz <= radius) + return true; + return dx * dx + dz * dz <= radius * radius; + } + + struct DeletedObject + { + entity_id_t entityId; + CStr templateName; + player_id_t owner; + CFixedVector3D pos; + CFixedVector3D rot; + u32 actorSeed; + }; + + ssize_t m_OldPatches, m_NewPatches; + int m_OffsetX, m_OffsetY; - cResizeMap() + u16* m_Heightmap; + CPatch* m_Patches; + + std::vector m_DeletedObjects; + std::vector> m_OldPositions; + std::vector> m_NewPositions; + + cResizeMap() : m_Heightmap(nullptr), m_Patches(nullptr) { } + ~cResizeMap() + { + delete[] m_Heightmap; + delete[] m_Patches; + } + void MakeDirty() { CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); @@ -295,41 +414,185 @@ g_Game->GetView()->GetLOSTexture().MakeDirty(); } - void ResizeTerrain(int tiles) + void ResizeTerrain(ssize_t patches, int offsetX, int offsetY) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + terrain->ResizeAndOffset(patches, -offsetX, -offsetY); + } + + void DeleteObjects(const std::vector& deletedObjects) + { + for (const DeletedObject& deleted : deletedObjects) + g_Game->GetSimulation2()->DestroyEntity(deleted.entityId); - const ssize_t newSize = tiles / PATCH_SIZE; - const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2; - terrain->ResizeAndOffset(newSize, offset, offset); + g_Game->GetSimulation2()->FlushDestroyedEntities(); + } - MakeDirty(); + void RestoreObjects(const std::vector& deletedObjects) + { + CSimulation2& sim = *g_Game->GetSimulation2(); + + for (const DeletedObject& deleted : deletedObjects) + { + entity_id_t ent = sim.AddEntity(deleted.templateName.FromUTF8(), deleted.entityId); + if (ent == INVALID_ENTITY) + { + LOGERROR("Failed to load entity template '%s'", deleted.templateName.c_str()); + } + else + { + CmpPtr cmpPosition(sim, deleted.entityId); + if (cmpPosition) + { + cmpPosition->JumpTo(deleted.pos.X, deleted.pos.Z); + cmpPosition->SetXZRotation(deleted.rot.X, deleted.rot.Z); + cmpPosition->SetYRotation(deleted.rot.Y); + } + + CmpPtr cmpOwnership(sim, deleted.entityId); + if (cmpOwnership) + cmpOwnership->SetOwner(deleted.owner); + + CmpPtr cmpVisual(sim, deleted.entityId); + if (cmpVisual) + cmpVisual->SetActorSeed(deleted.actorSeed); + } + } + } + + void SetMovedEntitiesPosition(const std::vector>& movedObjects) + { + for (const std::pair& obj : movedObjects) + { + const entity_id_t id = obj.first; + const CFixedVector3D position = obj.second; + CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); + ENSURE(cmpPosition); + cmpPosition->JumpTo(position.X, position.Z); + } } void Do() { - CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + CSimulation2& sim = *g_Game->GetSimulation2(); + CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY); + ENSURE(cmpTemplateManager); + + CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); if (!cmpTerrain) { - m_OldTiles = m_NewTiles = 0; + m_OldPatches = m_NewPatches = 0; + m_OffsetX = m_OffsetY = 0; } else { - m_OldTiles = (int)cmpTerrain->GetTilesPerSide(); - m_NewTiles = msg->tiles; + m_OldPatches = static_cast(cmpTerrain->GetTilesPerSide() / PATCH_SIZE); + m_NewPatches = msg->tiles / PATCH_SIZE; + m_OffsetX = msg->offsetX / PATCH_SIZE; + // Need to flip direction of vertical offset, due to screen mapping order. + m_OffsetY = -(msg->offsetY / PATCH_SIZE); + + CTerrain* terrain = cmpTerrain->GetCTerrain(); + m_Heightmap = new u16[(m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1)]; + std::copy_n(terrain->GetHeightMap(), (m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1), m_Heightmap); + m_Patches = new CPatch[m_OldPatches * m_OldPatches]; + for (ssize_t j = 0; j < m_OldPatches; ++j) + for (ssize_t i = 0; i < m_OldPatches; ++i) + { + CPatch& src = *(terrain->GetPatch(i, j)); + CPatch& dst = m_Patches[j * m_OldPatches + i]; + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } } - ResizeTerrain(m_NewTiles); + const int radiusInTerrainUnits = m_NewPatches * PATCH_SIZE * TERRAIN_TILE_SIZE / 2 * (1.f - 1e-6f); + // Opposite direction offset, as we move the destination onto the source, not the source into the destination. + const int mapCenterX = (m_OldPatches / 2 - m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const int mapCenterZ = (m_OldPatches / 2 - m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; + // The offset to move units by is opposite the direction the map is moved, and from the corner. + const int offsetX = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const int offsetZ = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const CFixedVector3D offset = CFixedVector3D(fixed::FromInt(offsetX), fixed::FromInt(0), fixed::FromInt(offsetZ)); + + const CSimulation2::InterfaceListUnordered& ents = sim.GetEntitiesWithInterfaceUnordered(IID_Selectable); + for (const std::pair& ent : ents) + { + const entity_id_t entityId = ent.first; + + CmpPtr cmpPosition(sim, entityId); + + if (cmpPosition && cmpPosition->IsInWorld() && Within(cmpPosition->GetPosition(), mapCenterX, mapCenterZ, radiusInTerrainUnits)) + { + CFixedVector3D position = cmpPosition->GetPosition(); + + m_NewPositions.emplace_back(entityId, position + offset); + m_OldPositions.emplace_back(entityId, position); + } + else + { + DeletedObject deleted; + deleted.entityId = entityId; + deleted.templateName = cmpTemplateManager->GetCurrentTemplateName(entityId); + + // If the entity has a position, but the ending position is not valid; + if (cmpPosition) + { + deleted.pos = cmpPosition->GetPosition(); + deleted.rot = cmpPosition->GetRotation(); + } + + CmpPtr cmpOwnership(sim, entityId); + if (cmpOwnership) + deleted.owner = cmpOwnership->GetOwner(); + + CmpPtr cmpVisual(sim, deleted.entityId); + if (cmpVisual) + deleted.actorSeed = cmpVisual->GetActorSeed(); + + m_DeletedObjects.push_back(deleted); + } + } + + DeleteObjects(m_DeletedObjects); + ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); + SetMovedEntitiesPosition(m_NewPositions); + MakeDirty(); } void Undo() { - ResizeTerrain(m_OldTiles); + if (m_Heightmap == nullptr || m_Patches == nullptr) + { + // If there previously was no data, just resize to old (probably not originally valid). + ResizeTerrain(m_OldPatches, -m_OffsetX, -m_OffsetY); + } + else + { + CSimulation2& sim = *g_Game->GetSimulation2(); + CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); + CTerrain* terrain = cmpTerrain->GetCTerrain(); + + terrain->Initialize(m_OldPatches, m_Heightmap); + // Copy terrain data back. + for (ssize_t j = 0; j < m_OldPatches; ++j) + for (ssize_t i = 0; i < m_OldPatches; ++i) + { + CPatch& src = m_Patches[j * m_OldPatches + i]; + CPatch& dst = *(terrain->GetPatch(i, j)); + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } + } + RestoreObjects(m_DeletedObjects); + SetMovedEntitiesPosition(m_OldPositions); + MakeDirty(); } void Redo() { - ResizeTerrain(m_NewTiles); + DeleteObjects(m_DeletedObjects); + ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); + SetMovedEntitiesPosition(m_NewPositions); + MakeDirty(); } }; END_COMMAND(ResizeMap) Index: ps/trunk/source/tools/atlas/GameInterface/Messages.h =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Messages.h +++ ps/trunk/source/tools/atlas/GameInterface/Messages.h @@ -195,6 +195,17 @@ ((std::string, sizes)) ); +QUERY(GetCurrentMapSize, + , + ((int, size)) + ); + +QUERY(RasterizeMinimap, + , + ((int, dimension)) + ((std::vector, imageBytes)) + ); + QUERY(GetRMSData, , ((std::vector, data)) @@ -202,6 +213,8 @@ COMMAND(ResizeMap, NOMERGE, ((int, tiles)) + ((int, offsetX)) + ((int, offsetY)) ); QUERY(VFSFileExists,