1
0
forked from 0ad/0ad

Allow map to recenter during resize in Atlas. Fixes #1109.

Patch By: Clockwork-Muse
Tested By: Stan
Differential Revision: https://code.wildfiregames.com/D825
This was SVN commit r23859.
This commit is contained in:
Vladislav Belov 2020-07-21 02:08:50 +00:00
parent 25e8b38ba8
commit 3ed9df0d6c
13 changed files with 808 additions and 78 deletions

View File

@ -1136,6 +1136,7 @@ function setup_atlas_projects()
"CustomControls/FileHistory",
"CustomControls/HighResTimer",
"CustomControls/MapDialog",
"CustomControls/MapResizeDialog",
"CustomControls/SnapSplitterWindow",
"CustomControls/VirtualDirTreeCtrl",
"CustomControls/Windows",

View File

@ -49,7 +49,7 @@
#include "simulation2/helpers/Los.h"
#include "simulation2/system/ParamNode.h"
#include <math.h>
#include <cmath>
extern bool g_GameRestarted;
@ -79,14 +79,7 @@ CMiniMap::CMiniMap(CGUI& pGUI) :
// 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 @@ void CMiniMap::Destroy()
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;
}

View File

@ -32,6 +32,12 @@ class CMiniMap : public IGUIObject
public:
CMiniMap(CGUI& pGUI);
virtual ~CMiniMap();
/**
* @return The maximum height for unit passage in water.
*/
static float GetShallowPassageHeight();
protected:
virtual void Draw();

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "MapResizeDialog.h"
#include "GameInterface/MessagePasser.h"
#include "GameInterface/Messages.h"
#include "ScenarioEditor/ScenarioEditor.h"
#include <wx/statline.h>
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<ssize_t>(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<wxStringClientData*>(evt.GetClientObject())->GetData();
long value = 0;
if (str.ToLong(&value))
m_NewSize = static_cast<ssize_t>(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()

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_MAPRESIZEDIALOG
#define INCLUDED_MAPRESIZEDIALOG
#include "PseudoMiniMapPanel.h"
#include <wx/dialog.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "PseudoMiniMapPanel.h"
#include "GameInterface/MessagePasser.h"
#include "GameInterface/Messages.h"
#include "ScenarioEditor/Tools/Common/Tools.h"
#include <algorithm>
#include <cmath>
#include <wx/dcbuffer.h>
#include <wx/dcgraph.h>
#include <wx/defs.h>
#include <wx/event.h>
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<uint8_t> 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<wxStringClientData*>(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()

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_PSEUDOMINIMAPPANEL
#define INCLUDED_PSEUDOMINIMAPPANEL
#include <map>
#include <wx/panel.h>
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<int, wxBitmap> m_ScreenTones;
std::map<int, wxBitmap> 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

View File

@ -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 @@ enum
ID_RandomSeed,
ID_RandomReseed,
ID_RandomGenerate,
ID_ResizeMap,
ID_SimPlay,
ID_SimFast,
ID_SimSlow,
@ -461,6 +463,14 @@ MapSidebar::MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContaine
_("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
@ -696,6 +706,16 @@ 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)
@ -705,5 +725,6 @@ BEGIN_EVENT_TABLE(MapSidebar, Sidebar)
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();

View File

@ -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 <wx/collpane.h>
class MapSettingsControl;
@ -35,12 +35,13 @@ private:
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;

View File

@ -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 <wx/spinctrl.h>
#include <wx/listctrl.h>
#include <wx/image.h>
#include <wx/imaglist.h>
#include <wx/busyinfo.h>
#include <wx/notebook.h>
class TextureNotebook;
@ -47,8 +47,7 @@ private:
enum
{
ID_Passability = 1,
ID_ShowPriorities,
ID_ResizeMap
ID_ShowPriorities
};
// Helper function for adding tooltips
@ -247,14 +246,6 @@ TerrainSidebar::TerrainSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebar
_("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 @@ void TerrainSidebar::OnShowPriorities(wxCommandEvent& evt)
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked()));
}
void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt))
{
wxArrayString sizeNames;
std::vector<size_t> 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();
//////////////////////////////////////////////////////////////////////////

View File

@ -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 @@ protected:
private:
void OnPassabilityChoice(wxCommandEvent& evt);
void OnShowPriorities(wxCommandEvent& evt);
void OnResizeMap(wxCommandEvent& evt);
wxChoice* m_PassabilityChoice;
TexturePreviewPanel* m_TexturePreview;
DECLARE_EVENT_TABLE();
};

View File

@ -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,17 +279,128 @@ QUERYHANDLER(GetMapSizes)
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<u8> 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<u32>::max();
u32 hmap = static_cast<u32>(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<u8>(static_cast<float>(color & 0xff) * scale);
imageBytes[position++] = static_cast<u8>(static_cast<float>((color >> 8) & 0xff) * scale);
imageBytes[position++] = static_cast<u8>(static_cast<float>((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;
cResizeMap()
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;
u16* m_Heightmap;
CPatch* m_Patches;
std::vector<DeletedObject> m_DeletedObjects;
std::vector<std::pair<entity_id_t, CFixedVector3D>> m_OldPositions;
std::vector<std::pair<entity_id_t, CFixedVector3D>> m_NewPositions;
cResizeMap() : m_Heightmap(nullptr), m_Patches(nullptr)
{
}
~cResizeMap()
{
delete[] m_Heightmap;
delete[] m_Patches;
}
void MakeDirty()
@ -295,41 +414,185 @@ BEGIN_COMMAND(ResizeMap)
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);
}
const ssize_t newSize = tiles / PATCH_SIZE;
const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2;
terrain->ResizeAndOffset(newSize, offset, offset);
void DeleteObjects(const std::vector<DeletedObject>& deletedObjects)
{
for (const DeletedObject& deleted : deletedObjects)
g_Game->GetSimulation2()->DestroyEntity(deleted.entityId);
MakeDirty();
g_Game->GetSimulation2()->FlushDestroyedEntities();
}
void RestoreObjects(const std::vector<DeletedObject>& 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<ICmpPosition> 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<ICmpOwnership> cmpOwnership(sim, deleted.entityId);
if (cmpOwnership)
cmpOwnership->SetOwner(deleted.owner);
CmpPtr<ICmpVisual> cmpVisual(sim, deleted.entityId);
if (cmpVisual)
cmpVisual->SetActorSeed(deleted.actorSeed);
}
}
}
void SetMovedEntitiesPosition(const std::vector<std::pair<entity_id_t, CFixedVector3D>>& movedObjects)
{
for (const std::pair<entity_id_t, CFixedVector3D>& obj : movedObjects)
{
const entity_id_t id = obj.first;
const CFixedVector3D position = obj.second;
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
ENSURE(cmpPosition);
cmpPosition->JumpTo(position.X, position.Z);
}
}
void Do()
{
CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
CSimulation2& sim = *g_Game->GetSimulation2();
CmpPtr<ICmpTemplateManager> cmpTemplateManager(sim, SYSTEM_ENTITY);
ENSURE(cmpTemplateManager);
CmpPtr<ICmpTerrain> 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<ssize_t>(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<entity_id_t, IComponent*>& ent : ents)
{
const entity_id_t entityId = ent.first;
CmpPtr<ICmpPosition> 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<ICmpOwnership> cmpOwnership(sim, entityId);
if (cmpOwnership)
deleted.owner = cmpOwnership->GetOwner();
CmpPtr<ICmpVisual> 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<ICmpTerrain> 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)

View File

@ -195,6 +195,17 @@ QUERY(GetMapSizes,
((std::string, sizes))
);
QUERY(GetCurrentMapSize,
,
((int, size))
);
QUERY(RasterizeMinimap,
,
((int, dimension))
((std::vector<uint8_t>, imageBytes))
);
QUERY(GetRMSData,
,
((std::vector<std::string>, data))
@ -202,6 +213,8 @@ QUERY(GetRMSData,
COMMAND(ResizeMap, NOMERGE,
((int, tiles))
((int, offsetX))
((int, offsetY))
);
QUERY(VFSFileExists,