Add editor undo/redo feature

This commit is contained in:
Corantin H 2023-10-02 15:14:38 +02:00
parent f910ac6a1c
commit 96e4c5f7cd
41 changed files with 5871 additions and 789 deletions

View file

@ -2285,13 +2285,22 @@ if(CLIENT)
component.h component.h
editor.cpp editor.cpp
editor.h editor.h
editor_action.h
editor_actions.cpp
editor_actions.h
editor_history.cpp
editor_history.h
editor_object.cpp editor_object.cpp
editor_object.h editor_object.h
editor_props.cpp
editor_trackers.cpp
editor_trackers.h
explanations.cpp explanations.cpp
map_grid.cpp map_grid.cpp
map_grid.h map_grid.h
map_view.cpp map_view.cpp
map_view.h map_view.h
mapitems.h
mapitems/envelope.cpp mapitems/envelope.cpp
mapitems/envelope.h mapitems/envelope.h
mapitems/image.cpp mapitems/image.cpp

View file

@ -248,6 +248,7 @@ MACRO_CONFIG_INT(ClRefreshRateInactive, cl_refresh_rate_inactive, 120, 0, 10000,
MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "Open the map editor") MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "Open the map editor")
MACRO_CONFIG_INT(ClEditorDilate, cl_editor_dilate, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically dilates embedded images") MACRO_CONFIG_INT(ClEditorDilate, cl_editor_dilate, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically dilates embedded images")
MACRO_CONFIG_STR(ClSkinFilterString, cl_skin_filter_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Skin filtering string") MACRO_CONFIG_STR(ClSkinFilterString, cl_skin_filter_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Skin filtering string")
MACRO_CONFIG_INT(ClEditorMaxHistory, cl_editor_max_history, 50, 1, 500, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of undo actions in the editor history (not shared between editor, envelope editor and server settings editor)")
MACRO_CONFIG_INT(ClAutoDemoRecord, cl_auto_demo_record, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically record demos") MACRO_CONFIG_INT(ClAutoDemoRecord, cl_auto_demo_record, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically record demos")
MACRO_CONFIG_INT(ClAutoDemoOnConnect, cl_auto_demo_on_connect, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Only start a new demo when connect while automatically record demos") MACRO_CONFIG_INT(ClAutoDemoOnConnect, cl_auto_demo_on_connect, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Only start a new demo when connect while automatically record demos")

View file

@ -140,6 +140,8 @@ MAYBE_UNUSED static const char *FONT_ICON_DICE_FIVE = "\xEF\x94\xA3";
MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6"; MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6";
MAYBE_UNUSED static const char *FONT_ICON_LAYER_GROUP = "\xEF\x97\xBD"; MAYBE_UNUSED static const char *FONT_ICON_LAYER_GROUP = "\xEF\x97\xBD";
MAYBE_UNUSED static const char *FONT_ICON_UNDO = "\xEF\x8B\xAA";
MAYBE_UNUSED static const char *FONT_ICON_REDO = "\xEF\x8B\xB9";
} // end namespace FontIcons } // end namespace FontIcons
enum ETextCursorSelectionMode enum ETextCursorSelectionMode

View file

@ -490,19 +490,37 @@ int CUI::DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRe
return ReturnValue; return ReturnValue;
} }
int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY) EEditState CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY)
{ {
static const void *s_pEditing = nullptr;
if(MouseHovered(pRect)) if(MouseHovered(pRect))
SetHotItem(pID); SetHotItem(pID);
EEditState Res = EEditState::EDITING;
if(HotItem() == pID && MouseButtonClicked(0)) if(HotItem() == pID && MouseButtonClicked(0))
{
SetActiveItem(pID); SetActiveItem(pID);
if(!s_pEditing)
{
s_pEditing = pID;
Res = EEditState::START;
}
}
if(CheckActiveItem(pID) && !MouseButton(0)) if(CheckActiveItem(pID) && !MouseButton(0))
{
SetActiveItem(nullptr); SetActiveItem(nullptr);
if(s_pEditing == pID)
{
s_pEditing = nullptr;
Res = EEditState::END;
}
}
if(!CheckActiveItem(pID)) if(!CheckActiveItem(pID) && Res == EEditState::EDITING)
return 0; return EEditState::NONE;
if(Input()->ShiftIsPressed()) if(Input()->ShiftIsPressed())
m_MouseSlow = true; m_MouseSlow = true;
@ -512,7 +530,7 @@ int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *
if(pY) if(pY)
*pY = clamp(m_MouseY - pRect->y, 0.0f, pRect->h); *pY = clamp(m_MouseY - pRect->y, 0.0f, pRect->h);
return 1; return Res;
} }
void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed) void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed)
@ -981,12 +999,19 @@ int CUI::DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pTex
} }
int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props) int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props)
{
return DoValueSelectorWithState(pID, pRect, pLabel, Current, Min, Max, Props).m_Value;
}
SEditResult<int64_t> CUI::DoValueSelectorWithState(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props)
{ {
// logic // logic
static float s_Value; static float s_Value;
static CLineInputNumber s_NumberInput; static CLineInputNumber s_NumberInput;
static const void *s_pLastTextID = pID; static const void *s_pLastTextID = pID;
const bool Inside = MouseInside(pRect); const bool Inside = MouseInside(pRect);
static const void *s_pEditing = nullptr;
EEditState State = EEditState::NONE;
if(Inside) if(Inside)
SetHotItem(pID); SetHotItem(pID);
@ -1091,7 +1116,23 @@ int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *
if(!m_ValueSelectorTextMode) if(!m_ValueSelectorTextMode)
s_NumberInput.Clear(); s_NumberInput.Clear();
return Current; if(s_pEditing == pID)
State = EEditState::EDITING;
bool MouseLocked = CheckMouseLock();
if((MouseLocked || m_ValueSelectorTextMode) && !s_pEditing)
{
State = EEditState::START;
s_pEditing = pID;
}
if(!CheckMouseLock() && !m_ValueSelectorTextMode && s_pEditing == pID)
{
State = EEditState::END;
s_pEditing = nullptr;
}
return SEditResult<int64_t>{State, Current};
} }
float CUI::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current) float CUI::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current)
@ -1686,6 +1727,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
{ {
SColorPickerPopupContext *pColorPicker = static_cast<SColorPickerPopupContext *>(pContext); SColorPickerPopupContext *pColorPicker = static_cast<SColorPickerPopupContext *>(pContext);
CUI *pUI = pColorPicker->m_pUI; CUI *pUI = pColorPicker->m_pUI;
pColorPicker->m_State = EEditState::NONE;
CUIRect ColorsArea, HueArea, BottomArea, ModeButtonArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect; CUIRect ColorsArea, HueArea, BottomArea, ModeButtonArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect;
@ -1759,10 +1801,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f); HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f);
} }
const auto &&RenderAlphaSelector = [&](unsigned OldA) -> unsigned { const auto &&RenderAlphaSelector = [&](unsigned OldA) -> SEditResult<int64_t> {
if(pColorPicker->m_Alpha) if(pColorPicker->m_Alpha)
{ {
return pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255); return pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255);
} }
else else
{ {
@ -1770,7 +1812,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
str_format(aBuf, sizeof(aBuf), "A: %d", OldA); str_format(aBuf, sizeof(aBuf), "A: %d", OldA);
pUI->DoLabel(&AlphaRect, aBuf, 10.0f, TEXTALIGN_MC); pUI->DoLabel(&AlphaRect, aBuf, 10.0f, TEXTALIGN_MC);
AlphaRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.65f), IGraphics::CORNER_ALL, 3.0f); AlphaRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.65f), IGraphics::CORNER_ALL, 3.0f);
return OldA; return {EEditState::NONE, OldA};
} }
}; };
@ -1782,10 +1824,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
const unsigned OldV = (unsigned)(PickerColorHSV.v * 255.0f); const unsigned OldV = (unsigned)(PickerColorHSV.v * 255.0f);
const unsigned OldA = (unsigned)(PickerColorHSV.a * 255.0f); const unsigned OldA = (unsigned)(PickerColorHSV.a * 255.0f);
const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255);
const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255);
const unsigned V = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255); const auto [StateV, V] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255);
const unsigned A = RenderAlphaSelector(OldA); const auto [StateA, A] = RenderAlphaSelector(OldA);
if(OldH != H || OldS != S || OldV != V || OldA != A) if(OldH != H || OldS != S || OldV != V || OldA != A)
{ {
@ -1793,6 +1835,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSL = color_cast<ColorHSLA>(PickerColorHSV); PickerColorHSL = color_cast<ColorHSLA>(PickerColorHSV);
PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL); PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL);
} }
for(auto State : {StateH, StateS, StateV, StateA})
{
if(State != EEditState::NONE)
{
pColorPicker->m_State = State;
break;
}
}
} }
else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_RGBA) else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_RGBA)
{ {
@ -1801,10 +1852,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
const unsigned OldB = (unsigned)(PickerColorRGB.b * 255.0f); const unsigned OldB = (unsigned)(PickerColorRGB.b * 255.0f);
const unsigned OldA = (unsigned)(PickerColorRGB.a * 255.0f); const unsigned OldA = (unsigned)(PickerColorRGB.a * 255.0f);
const unsigned R = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255); const auto [StateR, R] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255);
const unsigned G = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255); const auto [StateG, G] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255);
const unsigned B = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255); const auto [StateB, B] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255);
const unsigned A = RenderAlphaSelector(OldA); const auto [StateA, A] = RenderAlphaSelector(OldA);
if(OldR != R || OldG != G || OldB != B || OldA != A) if(OldR != R || OldG != G || OldB != B || OldA != A)
{ {
@ -1812,6 +1863,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSL = color_cast<ColorHSLA>(PickerColorRGB); PickerColorHSL = color_cast<ColorHSLA>(PickerColorRGB);
PickerColorHSV = color_cast<ColorHSVA>(PickerColorHSL); PickerColorHSV = color_cast<ColorHSVA>(PickerColorHSL);
} }
for(auto State : {StateR, StateG, StateB, StateA})
{
if(State != EEditState::NONE)
{
pColorPicker->m_State = State;
break;
}
}
} }
else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSLA) else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSLA)
{ {
@ -1820,10 +1880,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
const unsigned OldL = (unsigned)(PickerColorHSL.l * 255.0f); const unsigned OldL = (unsigned)(PickerColorHSL.l * 255.0f);
const unsigned OldA = (unsigned)(PickerColorHSL.a * 255.0f); const unsigned OldA = (unsigned)(PickerColorHSL.a * 255.0f);
const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); const auto [StateH, H] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255);
const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); const auto [StateS, S] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255);
const unsigned L = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255); const auto [StateL, L] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255);
const unsigned A = RenderAlphaSelector(OldA); const auto [StateA, A] = RenderAlphaSelector(OldA);
if(OldH != H || OldS != S || OldL != L || OldA != A) if(OldH != H || OldS != S || OldL != L || OldA != A)
{ {
@ -1831,6 +1891,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSV = color_cast<ColorHSVA>(PickerColorHSL); PickerColorHSV = color_cast<ColorHSVA>(PickerColorHSL);
PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL); PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL);
} }
for(auto State : {StateH, StateS, StateL, StateA})
{
if(State != EEditState::NONE)
{
pColorPicker->m_State = State;
break;
}
}
} }
else else
{ {
@ -1842,7 +1911,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
Props.m_IsHex = true; Props.m_IsHex = true;
Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6; Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6;
const unsigned OldHex = PickerColorRGB.PackAlphaLast(pColorPicker->m_Alpha); const unsigned OldHex = PickerColorRGB.PackAlphaLast(pColorPicker->m_Alpha);
const unsigned Hex = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props); auto [HexState, Hex] = pUI->DoValueSelectorWithState(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props);
if(OldHex != Hex) if(OldHex != Hex)
{ {
const float OldAlpha = PickerColorRGB.a; const float OldAlpha = PickerColorRGB.a;
@ -1853,21 +1922,28 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View
PickerColorHSV = color_cast<ColorHSVA>(PickerColorHSL); PickerColorHSV = color_cast<ColorHSVA>(PickerColorHSL);
} }
if(HexState != EEditState::NONE)
pColorPicker->m_State = HexState;
// Logic // Logic
float PickerX, PickerY; float PickerX, PickerY;
if(pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY)) EEditState ColorPickerRes = pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY);
if(ColorPickerRes != EEditState::NONE)
{ {
PickerColorHSV.y = PickerX / ColorsArea.w; PickerColorHSV.y = PickerX / ColorsArea.w;
PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h; PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h;
PickerColorHSL = color_cast<ColorHSLA>(PickerColorHSV); PickerColorHSL = color_cast<ColorHSLA>(PickerColorHSV);
PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL); PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL);
pColorPicker->m_State = ColorPickerRes;
} }
if(pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY)) EEditState HuePickerRes = pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY);
if(HuePickerRes != EEditState::NONE)
{ {
PickerColorHSV.x = 1.0f - PickerY / HueArea.h; PickerColorHSV.x = 1.0f - PickerY / HueArea.h;
PickerColorHSL = color_cast<ColorHSLA>(PickerColorHSV); PickerColorHSL = color_cast<ColorHSLA>(PickerColorHSV);
PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL); PickerColorRGB = color_cast<ColorRGBA>(PickerColorHSL);
pColorPicker->m_State = HuePickerRes;
} }
// Marker Color Area // Marker Color Area

View file

@ -18,6 +18,22 @@ class IClient;
class IGraphics; class IGraphics;
class IKernel; class IKernel;
enum class EEditState
{
NONE,
START,
EDITING,
END,
ONE_GO
};
template<typename T>
struct SEditResult
{
EEditState m_State;
T m_Value;
};
struct SUIAnimator struct SUIAnimator
{ {
bool m_Active; bool m_Active;
@ -488,7 +504,7 @@ public:
int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect); int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect);
int DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted); int DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted);
int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY); EEditState DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY);
void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f); void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f);
static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight = nullptr); static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight = nullptr);
@ -505,6 +521,7 @@ public:
int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true); int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding = 0.0f, bool TransparentInactive = false, bool Enabled = true);
// value selector // value selector
SEditResult<int64_t> DoValueSelectorWithState(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {});
int64_t DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {}); int64_t DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props = {});
bool IsValueSelectorTextMode() const { return m_ValueSelectorTextMode; } bool IsValueSelectorTextMode() const { return m_ValueSelectorTextMode; }
void SetValueSelectorTextMode(bool TextMode) { m_ValueSelectorTextMode = TextMode; } void SetValueSelectorTextMode(bool TextMode) { m_ValueSelectorTextMode = TextMode; }
@ -620,6 +637,7 @@ public:
const char m_ColorPickerId = 0; const char m_ColorPickerId = 0;
const char m_aValueSelectorIds[5] = {0}; const char m_aValueSelectorIds[5] = {0};
CButtonContainer m_aModeButtons[(int)MODE_HSLA + 1]; CButtonContainer m_aModeButtons[(int)MODE_HSLA + 1];
EEditState m_State;
}; };
void ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext); void ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext);

View file

@ -9,6 +9,7 @@
#include "auto_map.h" #include "auto_map.h"
#include "editor.h" // TODO: only needs CLayerTiles #include "editor.h" // TODO: only needs CLayerTiles
#include "editor_actions.h"
// Based on triple32inc from https://github.com/skeeto/hash-prospector/tree/79a6074062a84907df6e45b756134b74e2956760 // Based on triple32inc from https://github.com/skeeto/hash-prospector/tree/79a6074062a84907df6e45b756134b74e2956760
static uint32_t HashUInt32(uint32_t Num) static uint32_t HashUInt32(uint32_t Num)
@ -425,8 +426,10 @@ void CAutoMapper::ProceedLocalized(CLayerTiles *pLayer, int ConfigID, int Seed,
{ {
CTile *pIn = &pUpdateLayer->m_pTiles[(y - UpdateFromY) * pUpdateLayer->m_Width + x - UpdateFromX]; CTile *pIn = &pUpdateLayer->m_pTiles[(y - UpdateFromY) * pUpdateLayer->m_Width + x - UpdateFromX];
CTile *pOut = &pLayer->m_pTiles[y * pLayer->m_Width + x]; CTile *pOut = &pLayer->m_pTiles[y * pLayer->m_Width + x];
CTile Previous = *pOut;
pOut->m_Index = pIn->m_Index; pOut->m_Index = pIn->m_Index;
pOut->m_Flags = pIn->m_Flags; pOut->m_Flags = pIn->m_Flags;
pLayer->RecordStateChange(x, y, Previous, *pOut);
} }
} }
@ -442,6 +445,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO
Seed = rand(); Seed = rand();
CConfiguration *pConf = &m_vConfigs[ConfigID]; CConfiguration *pConf = &m_vConfigs[ConfigID];
pLayer->ClearHistory();
// for every run: copy tiles, automap, overwrite tiles // for every run: copy tiles, automap, overwrite tiles
for(size_t h = 0; h < pConf->m_vRuns.size(); ++h) for(size_t h = 0; h < pConf->m_vRuns.size(); ++h)
@ -534,8 +538,10 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO
if(RespectRules && if(RespectRules &&
(pIndexRule->m_RandomProbability >= 1.0f || HashLocation(Seed, h, i, x + SeedOffsetX, y + SeedOffsetY) < HASH_MAX * pIndexRule->m_RandomProbability)) (pIndexRule->m_RandomProbability >= 1.0f || HashLocation(Seed, h, i, x + SeedOffsetX, y + SeedOffsetY) < HASH_MAX * pIndexRule->m_RandomProbability))
{ {
CTile Previous = *pTile;
pTile->m_Index = pIndexRule->m_ID; pTile->m_Index = pIndexRule->m_ID;
pTile->m_Flags = pIndexRule->m_Flag; pTile->m_Flags = pIndexRule->m_Flag;
pLayer->RecordStateChange(x, y, Previous, *pTile);
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,8 @@
#include <engine/shared/jobs.h> #include <engine/shared/jobs.h>
#include "auto_map.h" #include "auto_map.h"
#include "editor_history.h"
#include "editor_trackers.h"
#include "map_view.h" #include "map_view.h"
#include "smooth_value.h" #include "smooth_value.h"
@ -452,7 +454,7 @@ public:
bool Save(const char *pFilename) override; bool Save(const char *pFilename) override;
bool Load(const char *pFilename, int StorageType) override; bool Load(const char *pFilename, int StorageType) override;
bool HandleMapDrop(const char *pFilename, int StorageType) override; bool HandleMapDrop(const char *pFilename, int StorageType) override;
bool Append(const char *pFilename, int StorageType); bool Append(const char *pFilename, int StorageType, bool IgnoreHistory = false);
void LoadCurrentMap(); void LoadCurrentMap();
void Render(); void Render();
@ -494,7 +496,10 @@ public:
bool IsTangentInSelected() const; bool IsTangentInSelected() const;
bool IsTangentOutSelected() const; bool IsTangentOutSelected() const;
bool IsTangentSelected() const; bool IsTangentSelected() const;
std::pair<int, int> EnvGetSelectedTimeAndValue() const;
template<typename E>
SEditResult<E> DoPropertiesWithState(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f));
int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f));
CUI::SColorPickerPopupContext m_ColorPickerPopupContext; CUI::SColorPickerPopupContext m_ColorPickerPopupContext;
@ -694,10 +699,11 @@ public:
EXTRAEDITOR_NONE = -1, EXTRAEDITOR_NONE = -1,
EXTRAEDITOR_ENVELOPES, EXTRAEDITOR_ENVELOPES,
EXTRAEDITOR_SERVER_SETTINGS, EXTRAEDITOR_SERVER_SETTINGS,
EXTRAEDITOR_HISTORY,
NUM_EXTRAEDITORS, NUM_EXTRAEDITORS,
}; };
EExtraEditor m_ActiveExtraEditor = EXTRAEDITOR_NONE; EExtraEditor m_ActiveExtraEditor = EXTRAEDITOR_NONE;
float m_aExtraEditorSplits[NUM_EXTRAEDITORS] = {250.0f, 250.0f}; float m_aExtraEditorSplits[NUM_EXTRAEDITORS] = {250.0f, 250.0f, 250.0f};
enum EShowEnvelope enum EShowEnvelope
{ {
@ -752,7 +758,7 @@ public:
CImageInfo m_TileartImageInfo; CImageInfo m_TileartImageInfo;
char m_aTileartFilename[IO_MAX_PATH_LENGTH]; char m_aTileartFilename[IO_MAX_PATH_LENGTH];
void AddTileart(); void AddTileart(bool IgnoreHistory = false);
void TileartCheckColors(); void TileartCheckColors();
void PlaceBorderTiles(); void PlaceBorderTiles();
@ -779,7 +785,7 @@ public:
void RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, float Size, float Brightness); void RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, float Size, float Brightness);
int UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree = false, bool IsHex = false, int corners = IGraphics::CORNER_ALL, ColorRGBA *pColor = nullptr, bool ShowValue = true); SEditResult<int> UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool IsDegree = false, bool IsHex = false, int corners = IGraphics::CORNER_ALL, ColorRGBA *pColor = nullptr, bool ShowValue = true);
static CUI::EPopupMenuFunctionResult PopupMenuFile(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupMenuFile(void *pContext, CUIRect View, bool Active);
static CUI::EPopupMenuFunctionResult PopupMenuTools(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupMenuTools(void *pContext, CUIRect View, bool Active);
@ -789,6 +795,7 @@ public:
{ {
CEditor *m_pEditor; CEditor *m_pEditor;
std::vector<std::shared_ptr<CLayerTiles>> m_vpLayers; std::vector<std::shared_ptr<CLayerTiles>> m_vpLayers;
std::vector<int> m_vLayerIndices;
CLayerTiles::SCommonPropState m_CommonPropState; CLayerTiles::SCommonPropState m_CommonPropState;
}; };
static CUI::EPopupMenuFunctionResult PopupLayer(void *pContext, CUIRect View, bool Active); static CUI::EPopupMenuFunctionResult PopupLayer(void *pContext, CUIRect View, bool Active);
@ -837,7 +844,7 @@ public:
void DoQuadEnvelopes(const std::vector<CQuad> &vQuads, IGraphics::CTextureHandle Texture = IGraphics::CTextureHandle()); void DoQuadEnvelopes(const std::vector<CQuad> &vQuads, IGraphics::CTextureHandle Texture = IGraphics::CTextureHandle());
void DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int pIndex); void DoQuadEnvPoint(const CQuad *pQuad, int QIndex, int pIndex);
void DoQuadPoint(CQuad *pQuad, int QuadIndex, int v); void DoQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int QuadIndex, int v);
void SetHotQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer); void SetHotQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer);
float TriangleArea(vec2 A, vec2 B, vec2 C); float TriangleArea(vec2 A, vec2 B, vec2 C);
@ -849,7 +856,7 @@ public:
void DoMapEditor(CUIRect View); void DoMapEditor(CUIRect View);
void DoToolbarLayers(CUIRect Toolbar); void DoToolbarLayers(CUIRect Toolbar);
void DoToolbarSounds(CUIRect Toolbar); void DoToolbarSounds(CUIRect Toolbar);
void DoQuad(CQuad *pQuad, int Index); void DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int Index);
ColorRGBA GetButtonColor(const void *pID, int Checked); ColorRGBA GetButtonColor(const void *pID, int Checked);
bool ReplaceImage(const char *pFilename, int StorageType, bool CheckDuplicate); bool ReplaceImage(const char *pFilename, int StorageType, bool CheckDuplicate);
@ -874,6 +881,7 @@ public:
void RenderEnvelopeEditor(CUIRect View); void RenderEnvelopeEditor(CUIRect View);
void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast); void RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEditorLast);
void RenderEditorHistory(CUIRect View);
void RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar); void RenderExtraEditorDragBar(CUIRect View, CUIRect DragBar);
@ -883,7 +891,7 @@ public:
void RenderFileDialog(); void RenderFileDialog();
void SelectGameLayer(); void SelectGameLayer();
void SortImages(); std::vector<int> SortImages();
bool SelectLayerByTile(); bool SelectLayerByTile();
void DoAudioPreview(CUIRect View, const void *pPlayPauseButtonID, const void *pStopButtonID, const void *pSeekBarID, const int SampleID); void DoAudioPreview(CUIRect View, const void *pPlayPauseButtonID, const void *pStopButtonID, const void *pSeekBarID, const int SampleID);
@ -1002,6 +1010,17 @@ public:
unsigned char m_SwitchNum; unsigned char m_SwitchNum;
unsigned char m_SwitchDelay; unsigned char m_SwitchDelay;
// Undo/Redo
CEditorHistory m_EditorHistory;
CEditorHistory m_ServerSettingsHistory;
CEditorHistory m_EnvelopeEditorHistory;
CQuadEditTracker m_QuadTracker;
CEnvelopeEditorOperationTracker m_EnvOpTracker;
private:
void UndoLastAction();
void RedoLastAction();
}; };
// make sure to inline this function // make sure to inline this function

View file

@ -0,0 +1,30 @@
#ifndef GAME_EDITOR_EDITOR_ACTION_H
#define GAME_EDITOR_EDITOR_ACTION_H
#include <string>
class CEditor;
class IEditorAction
{
public:
IEditorAction(CEditor *pEditor) :
m_pEditor(pEditor) {}
IEditorAction() = default;
virtual ~IEditorAction() = default;
virtual void Undo() = 0;
virtual void Redo() = 0;
virtual bool IsEmpty() { return false; }
const char *DisplayText() const { return m_aDisplayText; }
protected:
CEditor *m_pEditor;
char m_aDisplayText[256];
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,648 @@
#ifndef GAME_EDITOR_EDITOR_ACTIONS_H
#define GAME_EDITOR_EDITOR_ACTIONS_H
#include "editor.h"
#include "editor_action.h"
class CEditorActionLayerBase : public IEditorAction
{
public:
CEditorActionLayerBase(CEditor *pEditor, int GroupIndex, int LayerIndex);
virtual void Undo() override {}
virtual void Redo() override {}
protected:
int m_GroupIndex;
int m_LayerIndex;
std::shared_ptr<CLayer> m_pLayer;
};
class CEditorBrushDrawAction : public IEditorAction
{
public:
CEditorBrushDrawAction(CEditor *pEditor, int Group);
void Undo() override;
void Redo() override;
bool IsEmpty() override;
private:
int m_Group;
// m_vTileChanges is a list of changes for each layer that was modified.
// The std::pair is used to pair one layer (index) with its history (2D map).
// EditorTileStateChangeHistory<T> is a 2D map, storing a change item at a specific y,x position.
std::vector<std::pair<int, EditorTileStateChangeHistory<STileStateChange>>> m_vTileChanges;
EditorTileStateChangeHistory<STeleTileStateChange> m_TeleTileChanges;
EditorTileStateChangeHistory<SSpeedupTileStateChange> m_SpeedupTileChanges;
EditorTileStateChangeHistory<SSwitchTileStateChange> m_SwitchTileChanges;
EditorTileStateChangeHistory<STuneTileStateChange> m_TuneTileChanges;
int m_TotalTilesDrawn;
int m_TotalLayers;
void Apply(bool Undo);
void SetInfos();
};
// ---------------------------------------------------------
class CEditorActionQuadPlace : public CEditorActionLayerBase
{
public:
CEditorActionQuadPlace(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector<CQuad> &vBrush);
void Undo() override;
void Redo() override;
private:
std::vector<CQuad> m_vBrush;
};
class CEditorActionSoundPlace : public CEditorActionLayerBase
{
public:
CEditorActionSoundPlace(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector<CSoundSource> &vBrush);
void Undo() override;
void Redo() override;
private:
std::vector<CSoundSource> m_vBrush;
};
// -------------------------------------------------------------
class CEditorActionDeleteQuad : public CEditorActionLayerBase
{
public:
CEditorActionDeleteQuad(CEditor *pEditor, int GroupIndex, int LayerIndex, std::vector<int> const &vQuadsIndices, std::vector<CQuad> const &vDeletedQuads);
void Undo() override;
void Redo() override;
private:
std::vector<int> m_vQuadsIndices;
std::vector<CQuad> m_vDeletedQuads;
};
// -------------------------------------------------------------
class CEditorActionEditQuadPoint : public CEditorActionLayerBase
{
public:
CEditorActionEditQuadPoint(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, std::vector<CPoint> const &vPreviousPoints, std::vector<CPoint> const &vCurrentPoints);
void Undo() override;
void Redo() override;
private:
int m_QuadIndex;
std::vector<CPoint> m_vPreviousPoints;
std::vector<CPoint> m_vCurrentPoints;
};
class CEditorActionEditQuadProp : public CEditorActionLayerBase
{
public:
CEditorActionEditQuadProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, EQuadProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_QuadIndex;
EQuadProp m_Prop;
int m_Previous;
int m_Current;
void Apply(int Value);
};
class CEditorActionEditQuadPointProp : public CEditorActionLayerBase
{
public:
CEditorActionEditQuadPointProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int QuadIndex, int PointIndex, EQuadPointProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_QuadIndex;
int m_PointIndex;
EQuadPointProp m_Prop;
int m_Previous;
int m_Current;
void Apply(int Value);
};
// -------------------------------------------------------------
class CEditorActionBulk : public IEditorAction
{
public:
CEditorActionBulk(CEditor *pEditor, const std::vector<std::shared_ptr<IEditorAction>> &vpActions, const char *pDisplay = nullptr, bool Reverse = false);
void Undo() override;
void Redo() override;
private:
std::vector<std::shared_ptr<IEditorAction>> m_vpActions;
std::string m_Display;
bool m_Reverse;
};
//
class CEditorActionAutoMap : public CEditorActionLayerBase
{
public:
CEditorActionAutoMap(CEditor *pEditor, int GroupIndex, int LayerIndex, const EditorTileStateChangeHistory<STileStateChange> &Changes);
void Undo() override;
void Redo() override;
private:
EditorTileStateChangeHistory<STileStateChange> m_Changes;
int m_TotalChanges;
void ComputeInfos();
void Apply(bool Undo);
};
// -------------------------------------------------------------
class CEditorActionAddLayer : public CEditorActionLayerBase
{
public:
CEditorActionAddLayer(CEditor *pEditor, int GroupIndex, int LayerIndex, bool Duplicate = false);
void Undo() override;
void Redo() override;
private:
bool m_Duplicate;
};
class CEditorActionDeleteLayer : public CEditorActionLayerBase
{
public:
CEditorActionDeleteLayer(CEditor *pEditor, int GroupIndex, int LayerIndex);
void Undo() override;
void Redo() override;
};
class CEditorActionGroup : public IEditorAction
{
public:
CEditorActionGroup(CEditor *pEditor, int GroupIndex, bool Delete);
void Undo() override;
void Redo() override;
private:
int m_GroupIndex;
bool m_Delete;
std::shared_ptr<CLayerGroup> m_pGroup;
};
class CEditorActionEditGroupProp : public IEditorAction
{
public:
CEditorActionEditGroupProp(CEditor *pEditor, int GroupIndex, EGroupProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_GroupIndex;
EGroupProp m_Prop;
int m_Previous;
int m_Current;
void Apply(int Value);
};
template<typename E>
class CEditorActionEditLayerPropBase : public CEditorActionLayerBase
{
public:
CEditorActionEditLayerPropBase(CEditor *pEditor, int GroupIndex, int LayerIndex, E Prop, int Previous, int Current);
virtual void Undo() override {}
virtual void Redo() override {}
protected:
E m_Prop;
int m_Previous;
int m_Current;
};
class CEditorActionEditLayerProp : public CEditorActionEditLayerPropBase<ELayerProp>
{
public:
CEditorActionEditLayerProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
void Apply(int Value);
};
class CEditorActionEditLayerTilesProp : public CEditorActionEditLayerPropBase<ETilesProp>
{
public:
CEditorActionEditLayerTilesProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ETilesProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
void SetSavedLayers(const std::map<int, std::shared_ptr<CLayer>> &SavedLayers);
private:
std::map<int, std::shared_ptr<CLayer>> m_SavedLayers;
void RestoreLayer(int Layer, const std::shared_ptr<CLayerTiles> &pLayerTiles);
};
class CEditorActionEditLayerQuadsProp : public CEditorActionEditLayerPropBase<ELayerQuadsProp>
{
public:
CEditorActionEditLayerQuadsProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerQuadsProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
void Apply(int Value);
};
class CEditorActionEditLayersGroupAndOrder : public IEditorAction
{
public:
CEditorActionEditLayersGroupAndOrder(CEditor *pEditor, int GroupIndex, const std::vector<int> &LayerIndices, int NewGroupIndex, const std::vector<int> &NewLayerIndices);
void Undo() override;
void Redo() override;
private:
int m_GroupIndex;
std::vector<int> m_LayerIndices;
int m_NewGroupIndex;
std::vector<int> m_NewLayerIndices;
};
// --------------
class CEditorActionAppendMap : public IEditorAction
{
public:
struct SPrevInfo
{
int m_Groups;
int m_Images;
int m_Sounds;
int m_Envelopes;
};
public:
CEditorActionAppendMap(CEditor *pEditor, const char *pMapName, const SPrevInfo &PrevInfo, std::vector<int> &vImageIndexMap);
void Undo() override;
void Redo() override;
private:
char m_aMapName[IO_MAX_PATH_LENGTH];
SPrevInfo m_PrevInfo;
std::vector<int> m_vImageIndexMap;
};
// --------------
class CEditorActionTileArt : public IEditorAction
{
public:
CEditorActionTileArt(CEditor *pEditor, int PreviousImageCount, const char *pTileArtFile, std::vector<int> &vImageIndexMap);
void Undo() override;
void Redo() override;
private:
int m_PreviousImageCount;
char m_aTileArtFile[IO_MAX_PATH_LENGTH];
std::vector<int> m_vImageIndexMap;
};
// ----------------------
class CEditorCommandAction : public IEditorAction
{
public:
enum class EType
{
DELETE,
ADD,
EDIT,
MOVE_UP,
MOVE_DOWN
};
CEditorCommandAction(CEditor *pEditor, EType Type, int *pSelectedCommandIndex, int CommandIndex, const char *pPreviousCommand = nullptr, const char *pCurrentCommand = nullptr);
void Undo() override;
void Redo() override;
private:
EType m_Type;
int *m_pSelectedCommandIndex;
int m_CommandIndex;
std::string m_PreviousCommand;
std::string m_CurrentCommand;
};
// ------------------------------
class CEditorActionEnvelopeAdd : public IEditorAction
{
public:
CEditorActionEnvelopeAdd(CEditor *pEditor, const std::shared_ptr<CEnvelope> &pEnv);
void Undo() override;
void Redo() override;
private:
std::shared_ptr<CEnvelope> m_pEnv;
};
class CEditorActionEveloppeDelete : public IEditorAction
{
public:
CEditorActionEveloppeDelete(CEditor *pEditor, int EnvelopeIndex);
void Undo() override;
void Redo() override;
private:
int m_EnvelopeIndex;
std::shared_ptr<CEnvelope> m_pEnv;
};
class CEditorActionEnvelopeEdit : public IEditorAction
{
public:
enum class EEditType
{
SYNC,
ORDER
};
CEditorActionEnvelopeEdit(CEditor *pEditor, int EnvelopeIndex, EEditType EditType, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_EnvelopeIndex;
EEditType m_EditType;
int m_Previous;
int m_Current;
std::shared_ptr<CEnvelope> m_pEnv;
};
class CEditorActionEnvelopeEditPoint : public IEditorAction
{
public:
enum class EEditType
{
TIME,
VALUE,
CURVE_TYPE,
HANDLE
};
CEditorActionEnvelopeEditPoint(CEditor *pEditor, int EnvelopeIndex, int PointIndex, int Channel, EEditType EditType, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_EnvelopeIndex;
int m_PointIndex;
int m_Channel;
EEditType m_EditType;
int m_Previous;
int m_Current;
std::shared_ptr<CEnvelope> m_pEnv;
void Apply(int Value);
};
class CEditorActionAddEnvelopePoint : public IEditorAction
{
public:
CEditorActionAddEnvelopePoint(CEditor *pEditor, int EnvIndex, int Time, ColorRGBA Channels);
void Undo() override;
void Redo() override;
private:
int m_EnvIndex;
int m_Time;
ColorRGBA m_Channels;
};
class CEditorActionDeleteEnvelopePoint : public IEditorAction
{
public:
CEditorActionDeleteEnvelopePoint(CEditor *pEditor, int EnvIndex, int PointIndex);
void Undo() override;
void Redo() override;
private:
int m_EnvIndex;
int m_PointIndex;
CEnvPoint_runtime m_Point;
};
class CEditorActionEditEnvelopePointValue : public IEditorAction
{
public:
enum class EType
{
TANGENT_IN,
TANGENT_OUT,
POINT
};
CEditorActionEditEnvelopePointValue(CEditor *pEditor, int EnvIndex, int PointIndex, int Channel, EType Type, int OldTime, int OldValue, int NewTime, int NewValue);
void Undo() override;
void Redo() override;
private:
int m_EnvIndex;
int m_PtIndex;
int m_Channel;
EType m_Type;
int m_OldTime;
int m_OldValue;
int m_NewTime;
int m_NewValue;
void Apply(bool Undo);
};
class CEditorActionResetEnvelopePointTangent : public IEditorAction
{
public:
CEditorActionResetEnvelopePointTangent(CEditor *pEditor, int EnvIndex, int PointIndex, int Channel, bool In);
void Undo() override;
void Redo() override;
private:
int m_EnvIndex;
int m_PointIndex;
int m_Channel;
bool m_In;
int m_Previous[2];
};
class CEditorActionEditLayerSoundsProp : public CEditorActionEditLayerPropBase<ELayerSoundsProp>
{
public:
CEditorActionEditLayerSoundsProp(CEditor *pEditor, int GroupIndex, int LayerIndex, ELayerSoundsProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
void Apply(int Value);
};
class CEditorActionDeleteSoundSource : public CEditorActionLayerBase
{
public:
CEditorActionDeleteSoundSource(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex);
void Undo() override;
void Redo() override;
private:
int m_SourceIndex;
CSoundSource m_Source;
};
class CEditorActionEditSoundSource : public CEditorActionLayerBase
{
public:
enum class EEditType
{
SHAPE
};
CEditorActionEditSoundSource(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, EEditType Type, int Value);
~CEditorActionEditSoundSource() override;
void Undo() override;
void Redo() override;
private:
int m_SourceIndex;
EEditType m_EditType;
int m_CurrentValue;
std::vector<int> m_vOriginalValues;
void *m_pSavedObject;
void Save();
};
class CEditorActionEditSoundSourceProp : public CEditorActionEditLayerPropBase<ESoundProp>
{
public:
CEditorActionEditSoundSourceProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ESoundProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_SourceIndex;
private:
void Apply(int Value);
};
class CEditorActionEditRectSoundSourceShapeProp : public CEditorActionEditLayerPropBase<ERectangleShapeProp>
{
public:
CEditorActionEditRectSoundSourceShapeProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ERectangleShapeProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_SourceIndex;
private:
void Apply(int Value);
};
class CEditorActionEditCircleSoundSourceShapeProp : public CEditorActionEditLayerPropBase<ECircleShapeProp>
{
public:
CEditorActionEditCircleSoundSourceShapeProp(CEditor *pEditor, int GroupIndex, int LayerIndex, int SourceIndex, ECircleShapeProp Prop, int Previous, int Current);
void Undo() override;
void Redo() override;
private:
int m_SourceIndex;
private:
void Apply(int Value);
};
class CEditorActionNewEmptySound : public CEditorActionLayerBase
{
public:
CEditorActionNewEmptySound(CEditor *pEditor, int GroupIndex, int LayerIndex, int x, int y);
void Undo() override;
void Redo() override;
private:
int m_X;
int m_Y;
};
class CEditorActionNewEmptyQuad : public CEditorActionLayerBase
{
public:
CEditorActionNewEmptyQuad(CEditor *pEditor, int GroupIndex, int LayerIndex, int x, int y);
void Undo() override;
void Redo() override;
private:
int m_X;
int m_Y;
};
class CEditorActionNewQuad : public CEditorActionLayerBase
{
public:
CEditorActionNewQuad(CEditor *pEditor, int GroupIndex, int LayerIndex);
void Undo() override;
void Redo() override;
private:
CQuad m_Quad;
};
#endif

View file

@ -0,0 +1,65 @@
#include <engine/shared/config.h>
#include "editor.h"
#include "editor_actions.h"
#include "editor_history.h"
void CEditorHistory::RecordAction(const std::shared_ptr<IEditorAction> &pAction)
{
RecordAction(pAction, nullptr);
}
void CEditorHistory::Execute(const std::shared_ptr<IEditorAction> &pAction, const char *pDisplay)
{
pAction->Redo();
RecordAction(pAction, pDisplay);
}
void CEditorHistory::RecordAction(const std::shared_ptr<IEditorAction> &pAction, const char *pDisplay)
{
m_vpRedoActions.clear();
if((int)m_vpUndoActions.size() >= g_Config.m_ClEditorMaxHistory)
{
m_vpUndoActions.pop_front();
}
if(pDisplay == nullptr)
m_vpUndoActions.emplace_back(pAction);
else
m_vpUndoActions.emplace_back(std::make_shared<CEditorActionBulk>(m_pEditor, std::vector<std::shared_ptr<IEditorAction>>{pAction}, pDisplay));
}
bool CEditorHistory::Undo()
{
if(m_vpUndoActions.empty())
return false;
auto pLastAction = m_vpUndoActions.back();
m_vpUndoActions.pop_back();
pLastAction->Undo();
m_vpRedoActions.emplace_back(pLastAction);
return true;
}
bool CEditorHistory::Redo()
{
if(m_vpRedoActions.empty())
return false;
auto pLastAction = m_vpRedoActions.back();
m_vpRedoActions.pop_back();
pLastAction->Redo();
m_vpUndoActions.emplace_back(pLastAction);
return true;
}
void CEditorHistory::Clear()
{
m_vpUndoActions.clear();
m_vpRedoActions.clear();
}

View file

@ -0,0 +1,37 @@
#ifndef GAME_EDITOR_EDITOR_HISTORY_H
#define GAME_EDITOR_EDITOR_HISTORY_H
#include "editor_action.h"
#include <queue>
class CEditorHistory
{
public:
CEditorHistory()
{
m_pEditor = nullptr;
}
~CEditorHistory()
{
Clear();
}
void RecordAction(const std::shared_ptr<IEditorAction> &pAction);
void RecordAction(const std::shared_ptr<IEditorAction> &pAction, const char *pDisplay);
void Execute(const std::shared_ptr<IEditorAction> &pAction, const char *pDisplay = nullptr);
bool Undo();
bool Redo();
void Clear();
bool CanUndo() const { return !m_vpUndoActions.empty(); }
bool CanRedo() const { return !m_vpRedoActions.empty(); }
CEditor *m_pEditor;
std::deque<std::shared_ptr<IEditorAction>> m_vpUndoActions;
std::deque<std::shared_ptr<IEditorAction>> m_vpRedoActions;
};
#endif

View file

@ -0,0 +1,282 @@
#include "editor.h"
#include <game/editor/mapitems/image.h>
#include <game/editor/mapitems/sound.h>
int CEditor::DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color)
{
auto Res = DoPropertiesWithState<int>(pToolbox, pProps, pIDs, pNewVal, Color);
return Res.m_Value;
}
template<typename E>
SEditResult<E> CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color)
{
int Change = -1;
EEditState State = EEditState::EDITING;
for(int i = 0; pProps[i].m_pName; i++)
{
CUIRect Slot;
pToolBox->HSplitTop(13.0f, &Slot, pToolBox);
CUIRect Label, Shifter;
Slot.VSplitMid(&Label, &Shifter);
Shifter.HMargin(1.0f, &Shifter);
UI()->DoLabel(&Label, pProps[i].m_pName, 10.0f, TEXTALIGN_ML);
if(pProps[i].m_Type == PROPTYPE_INT_STEP)
{
CUIRect Inc, Dec;
char aBuf[64];
Shifter.VSplitRight(10.0f, &Shifter, &Inc);
Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
str_from_int(pProps[i].m_Value, aBuf);
auto NewValueRes = UiDoValueSelector((char *)&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0, &Color);
int NewValue = NewValueRes.m_Value;
if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING)
{
*pNewVal = NewValue;
Change = i;
State = NewValueRes.m_State;
}
if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease"))
{
*pNewVal = pProps[i].m_Value - 1;
Change = i;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Increase"))
{
*pNewVal = pProps[i].m_Value + 1;
Change = i;
State = EEditState::ONE_GO;
}
}
else if(pProps[i].m_Type == PROPTYPE_BOOL)
{
CUIRect No, Yes;
Shifter.VSplitMid(&No, &Yes);
if(DoButton_ButtonDec(&pIDs[i], "No", !pProps[i].m_Value, &No, 0, ""))
{
*pNewVal = 0;
Change = i;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonInc(((char *)&pIDs[i]) + 1, "Yes", pProps[i].m_Value, &Yes, 0, ""))
{
*pNewVal = 1;
Change = i;
State = EEditState::ONE_GO;
}
}
else if(pProps[i].m_Type == PROPTYPE_INT_SCROLL)
{
auto NewValueRes = UiDoValueSelector(&pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.");
int NewValue = NewValueRes.m_Value;
if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING)
{
*pNewVal = NewValue;
Change = i;
State = NewValueRes.m_State;
}
}
else if(pProps[i].m_Type == PROPTYPE_ANGLE_SCROLL)
{
CUIRect Inc, Dec;
Shifter.VSplitRight(10.0f, &Shifter, &Inc);
Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
const bool Shift = Input()->ShiftIsPressed();
int Step = Shift ? 1 : 45;
int Value = pProps[i].m_Value;
auto NewValueRes = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, pProps[i].m_Max, Shift ? 1 : 45, Shift ? 1.0f : 10.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0);
int NewValue = NewValueRes.m_Value;
if(DoButton_ButtonDec(&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Decrease"))
{
NewValue = (std::ceil((pProps[i].m_Value / (float)Step)) - 1) * Step;
if(NewValue < 0)
NewValue += 360;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonInc(&pIDs[i] + 2, nullptr, 0, &Inc, 0, "Increase"))
{
NewValue = (pProps[i].m_Value + Step) / Step * Step;
State = EEditState::ONE_GO;
}
if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING)
{
*pNewVal = NewValue % 360;
Change = i;
State = NewValueRes.m_State;
}
}
else if(pProps[i].m_Type == PROPTYPE_COLOR)
{
const auto &&SetColor = [&](ColorRGBA NewColor) {
const int NewValue = NewColor.PackAlphaLast();
if(NewValue != pProps[i].m_Value || m_ColorPickerPopupContext.m_State != EEditState::EDITING)
{
*pNewVal = NewValue;
Change = i;
State = m_ColorPickerPopupContext.m_State;
}
};
DoColorPickerButton(&pIDs[i], &Shifter, ColorRGBA::UnpackAlphaLast<ColorRGBA>(pProps[i].m_Value), SetColor);
}
else if(pProps[i].m_Type == PROPTYPE_IMAGE)
{
const char *pName;
if(pProps[i].m_Value < 0)
pName = "None";
else
pName = m_Map.m_vpImages[pProps[i].m_Value]->m_aName;
if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL))
PopupSelectImageInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
int r = PopupSelectImageResult();
if(r >= -1)
{
*pNewVal = r;
Change = i;
State = EEditState::ONE_GO;
}
}
else if(pProps[i].m_Type == PROPTYPE_SHIFT)
{
CUIRect Left, Right, Up, Down;
Shifter.VSplitMid(&Left, &Up, 2.0f);
Left.VSplitLeft(10.0f, &Left, &Shifter);
Shifter.VSplitRight(10.0f, &Shifter, &Right);
Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f);
UI()->DoLabel(&Shifter, "X", 10.0f, TEXTALIGN_MC);
Up.VSplitLeft(10.0f, &Up, &Shifter);
Shifter.VSplitRight(10.0f, &Shifter, &Down);
Shifter.Draw(ColorRGBA(1, 1, 1, 0.5f), 0, 0.0f);
UI()->DoLabel(&Shifter, "Y", 10.0f, TEXTALIGN_MC);
if(DoButton_ButtonDec(&pIDs[i], "-", 0, &Left, 0, "Left"))
{
*pNewVal = DIRECTION_LEFT;
Change = i;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonInc(((char *)&pIDs[i]) + 3, "+", 0, &Right, 0, "Right"))
{
*pNewVal = DIRECTION_RIGHT;
Change = i;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonDec(((char *)&pIDs[i]) + 1, "-", 0, &Up, 0, "Up"))
{
*pNewVal = DIRECTION_UP;
Change = i;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, "+", 0, &Down, 0, "Down"))
{
*pNewVal = DIRECTION_DOWN;
Change = i;
State = EEditState::ONE_GO;
}
}
else if(pProps[i].m_Type == PROPTYPE_SOUND)
{
const char *pName;
if(pProps[i].m_Value < 0)
pName = "None";
else
pName = m_Map.m_vpSounds[pProps[i].m_Value]->m_aName;
if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL))
PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
int r = PopupSelectSoundResult();
if(r >= -1)
{
*pNewVal = r;
Change = i;
State = EEditState::ONE_GO;
}
}
else if(pProps[i].m_Type == PROPTYPE_AUTOMAPPER)
{
const char *pName;
if(pProps[i].m_Value < 0 || pProps[i].m_Min < 0 || pProps[i].m_Min >= (int)m_Map.m_vpImages.size())
pName = "None";
else
pName = m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(pProps[i].m_Value);
if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL))
PopupSelectConfigAutoMapInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY());
int r = PopupSelectConfigAutoMapResult();
if(r >= -1)
{
*pNewVal = r;
Change = i;
State = EEditState::ONE_GO;
}
}
else if(pProps[i].m_Type == PROPTYPE_ENVELOPE)
{
CUIRect Inc, Dec;
char aBuf[8];
int CurValue = pProps[i].m_Value;
Shifter.VSplitRight(10.0f, &Shifter, &Inc);
Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
if(CurValue <= 0)
str_copy(aBuf, "None:");
else if(m_Map.m_vpEnvelopes[CurValue - 1]->m_aName[0])
{
str_format(aBuf, sizeof(aBuf), "%s:", m_Map.m_vpEnvelopes[CurValue - 1]->m_aName);
if(!str_endswith(aBuf, ":"))
{
aBuf[sizeof(aBuf) - 2] = ':';
aBuf[sizeof(aBuf) - 1] = '\0';
}
}
else
aBuf[0] = '\0';
auto NewValueRes = UiDoValueSelector((char *)&pIDs[i], &Shifter, aBuf, CurValue, 0, m_Map.m_vpEnvelopes.size(), 1, 1.0f, "Set Envelope", false, false, IGraphics::CORNER_NONE);
int NewVal = NewValueRes.m_Value;
if(NewVal != CurValue || NewValueRes.m_State != EEditState::EDITING)
{
*pNewVal = NewVal;
Change = i;
State = NewValueRes.m_State;
}
if(DoButton_ButtonDec((char *)&pIDs[i] + 1, nullptr, 0, &Dec, 0, "Previous Envelope"))
{
*pNewVal = pProps[i].m_Value - 1;
Change = i;
State = EEditState::ONE_GO;
}
if(DoButton_ButtonInc(((char *)&pIDs[i]) + 2, nullptr, 0, &Inc, 0, "Next Envelope"))
{
*pNewVal = pProps[i].m_Value + 1;
Change = i;
State = EEditState::ONE_GO;
}
}
}
return SEditResult<E>{State, static_cast<E>(Change)};
}
template SEditResult<ECircleShapeProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ERectangleShapeProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<EGroupProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ELayerProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ELayerQuadsProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ETilesProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ETilesCommonProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ELayerSoundsProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<EQuadProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<EQuadPointProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);
template SEditResult<ESoundProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, ColorRGBA);

View file

@ -0,0 +1,632 @@
#include "editor_trackers.h"
#include <game/editor/mapitems/layer_group.h>
#include <game/editor/mapitems/layer_tiles.h>
#include "editor.h"
#include "editor_actions.h"
CQuadEditTracker::CQuadEditTracker() :
m_pEditor(nullptr), m_TrackedProp(EQuadProp::PROP_NONE) {}
CQuadEditTracker::~CQuadEditTracker()
{
m_InitalPoints.clear();
m_vSelectedQuads.clear();
}
void CQuadEditTracker::BeginQuadTrack(const std::shared_ptr<CLayerQuads> &pLayer, const std::vector<int> &vSelectedQuads)
{
if(m_Tracking)
return;
m_Tracking = true;
m_vSelectedQuads.clear();
m_pLayer = pLayer;
// Init all points
for(auto QuadIndex : vSelectedQuads)
{
auto &pQuad = pLayer->m_vQuads[QuadIndex];
m_InitalPoints[QuadIndex] = std::vector<CPoint>(pQuad.m_aPoints, pQuad.m_aPoints + 5);
m_vSelectedQuads.push_back(QuadIndex);
}
}
void CQuadEditTracker::EndQuadTrack()
{
if(!m_Tracking)
return;
m_Tracking = false;
int GroupIndex = m_pEditor->m_SelectedGroup;
int LayerIndex = m_pEditor->m_vSelectedLayers[0];
// Record all moved stuff
std::vector<std::shared_ptr<IEditorAction>> vpActions;
for(auto QuadIndex : m_vSelectedQuads)
{
auto &pQuad = m_pLayer->m_vQuads[QuadIndex];
auto vCurrentPoints = std::vector<CPoint>(pQuad.m_aPoints, pQuad.m_aPoints + 5);
vpActions.push_back(std::make_shared<CEditorActionEditQuadPoint>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints));
}
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions));
}
void CQuadEditTracker::BeginQuadPropTrack(const std::shared_ptr<CLayerQuads> &pLayer, const std::vector<int> &vSelectedQuads, EQuadProp Prop)
{
if(m_TrackedProp != EQuadProp::PROP_NONE)
return;
m_TrackedProp = Prop;
m_pLayer = pLayer;
m_vSelectedQuads = vSelectedQuads;
m_PreviousValues.clear();
for(auto QuadIndex : vSelectedQuads)
{
auto &Quad = pLayer->m_vQuads[QuadIndex];
if(Prop == EQuadProp::PROP_POS_X || Prop == EQuadProp::PROP_POS_Y)
m_InitalPoints[QuadIndex] = std::vector<CPoint>(Quad.m_aPoints, Quad.m_aPoints + 5);
else if(Prop == EQuadProp::PROP_POS_ENV)
m_PreviousValues[QuadIndex] = Quad.m_PosEnv;
else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET)
m_PreviousValues[QuadIndex] = Quad.m_PosEnvOffset;
else if(Prop == EQuadProp::PROP_COLOR_ENV)
m_PreviousValues[QuadIndex] = Quad.m_ColorEnv;
else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET)
m_PreviousValues[QuadIndex] = Quad.m_ColorEnvOffset;
}
}
void CQuadEditTracker::EndQuadPropTrack(EQuadProp Prop)
{
if(m_TrackedProp != Prop)
return;
m_TrackedProp = EQuadProp::PROP_NONE;
std::vector<std::shared_ptr<IEditorAction>> vpActions;
int GroupIndex = m_pEditor->m_SelectedGroup;
int LayerIndex = m_pEditor->m_vSelectedLayers[0];
for(auto QuadIndex : m_vSelectedQuads)
{
auto &Quad = m_pLayer->m_vQuads[QuadIndex];
if(Prop == EQuadProp::PROP_POS_X || Prop == EQuadProp::PROP_POS_Y)
{
auto vCurrentPoints = std::vector<CPoint>(Quad.m_aPoints, Quad.m_aPoints + 5);
vpActions.push_back(std::make_shared<CEditorActionEditQuadPoint>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints));
}
else
{
int Value = 0;
if(Prop == EQuadProp::PROP_POS_ENV)
Value = Quad.m_PosEnv;
else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET)
Value = Quad.m_PosEnvOffset;
else if(Prop == EQuadProp::PROP_COLOR_ENV)
Value = Quad.m_ColorEnv;
else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET)
Value = Quad.m_ColorEnvOffset;
if(Value != m_PreviousValues[QuadIndex])
vpActions.push_back(std::make_shared<CEditorActionEditQuadProp>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, Prop, m_PreviousValues[QuadIndex], Value));
}
}
if(!vpActions.empty())
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions));
}
void CQuadEditTracker::BeginQuadPointPropTrack(const std::shared_ptr<CLayerQuads> &pLayer, const std::vector<int> &vSelectedQuads, int SelectedQuadPoints)
{
if(!m_vTrackedProps.empty())
return;
m_pLayer = pLayer;
m_SelectedQuadPoints = SelectedQuadPoints;
m_vSelectedQuads = vSelectedQuads;
m_PreviousValuesPoint.clear();
for(auto QuadIndex : vSelectedQuads)
{
m_PreviousValuesPoint[QuadIndex] = std::vector<std::map<EQuadPointProp, int>>(4);
}
}
void CQuadEditTracker::AddQuadPointPropTrack(EQuadPointProp Prop)
{
if(std::find(m_vTrackedProps.begin(), m_vTrackedProps.end(), Prop) != m_vTrackedProps.end())
return;
m_vTrackedProps.push_back(Prop);
for(auto QuadIndex : m_vSelectedQuads)
{
auto &Quad = m_pLayer->m_vQuads[QuadIndex];
if(Prop == EQuadPointProp::PROP_POS_X || Prop == EQuadPointProp::PROP_POS_Y)
m_InitalPoints[QuadIndex] = std::vector<CPoint>(Quad.m_aPoints, Quad.m_aPoints + 5);
else if(Prop == EQuadPointProp::PROP_COLOR)
{
for(int v = 0; v < 4; v++)
{
if(m_SelectedQuadPoints & (1 << v))
{
int Color = PackColor(Quad.m_aColors[v]);
m_PreviousValuesPoint[QuadIndex][v][Prop] = Color;
}
}
}
else if(Prop == EQuadPointProp::PROP_TEX_U)
{
for(int v = 0; v < 4; v++)
{
if(m_SelectedQuadPoints & (1 << v))
{
m_PreviousValuesPoint[QuadIndex][v][Prop] = Quad.m_aTexcoords[v].x;
}
}
}
else if(Prop == EQuadPointProp::PROP_TEX_V)
{
for(int v = 0; v < 4; v++)
{
if(m_SelectedQuadPoints & (1 << v))
{
m_PreviousValuesPoint[QuadIndex][v][Prop] = Quad.m_aTexcoords[v].y;
}
}
}
}
}
void CQuadEditTracker::EndQuadPointPropTrack(EQuadPointProp Prop)
{
auto It = std::find(m_vTrackedProps.begin(), m_vTrackedProps.end(), Prop);
if(It == m_vTrackedProps.end())
return;
m_vTrackedProps.erase(It);
std::vector<std::shared_ptr<IEditorAction>> vpActions;
int GroupIndex = m_pEditor->m_SelectedGroup;
int LayerIndex = m_pEditor->m_vSelectedLayers[0];
for(auto QuadIndex : m_vSelectedQuads)
{
auto &Quad = m_pLayer->m_vQuads[QuadIndex];
if(Prop == EQuadPointProp::PROP_POS_X || Prop == EQuadPointProp::PROP_POS_Y)
{
auto vCurrentPoints = std::vector<CPoint>(Quad.m_aPoints, Quad.m_aPoints + 5);
vpActions.push_back(std::make_shared<CEditorActionEditQuadPoint>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints));
}
else
{
int Value = 0;
for(int v = 0; v < 4; v++)
{
if(m_SelectedQuadPoints & (1 << v))
{
if(Prop == EQuadPointProp::PROP_COLOR)
{
Value = PackColor(Quad.m_aColors[v]);
}
else if(Prop == EQuadPointProp::PROP_TEX_U)
{
Value = Quad.m_aTexcoords[v].x;
}
else if(Prop == EQuadPointProp::PROP_TEX_V)
{
Value = Quad.m_aTexcoords[v].y;
}
if(Value != m_PreviousValuesPoint[QuadIndex][v][Prop])
vpActions.push_back(std::make_shared<CEditorActionEditQuadPointProp>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, v, Prop, m_PreviousValuesPoint[QuadIndex][v][Prop], Value));
}
}
}
}
if(!vpActions.empty())
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions));
}
void CQuadEditTracker::EndQuadPointPropTrackAll()
{
std::vector<std::shared_ptr<IEditorAction>> vpActions;
for(auto &Prop : m_vTrackedProps)
{
int GroupIndex = m_pEditor->m_SelectedGroup;
int LayerIndex = m_pEditor->m_vSelectedLayers[0];
for(auto QuadIndex : m_vSelectedQuads)
{
auto &Quad = m_pLayer->m_vQuads[QuadIndex];
if(Prop == EQuadPointProp::PROP_POS_X || Prop == EQuadPointProp::PROP_POS_Y)
{
auto vCurrentPoints = std::vector<CPoint>(Quad.m_aPoints, Quad.m_aPoints + 5);
vpActions.push_back(std::make_shared<CEditorActionEditQuadPoint>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, m_InitalPoints[QuadIndex], vCurrentPoints));
}
else
{
int Value = 0;
for(int v = 0; v < 4; v++)
{
if(m_SelectedQuadPoints & (1 << v))
{
if(Prop == EQuadPointProp::PROP_COLOR)
{
Value = PackColor(Quad.m_aColors[v]);
}
else if(Prop == EQuadPointProp::PROP_TEX_U)
{
Value = Quad.m_aTexcoords[v].x;
}
else if(Prop == EQuadPointProp::PROP_TEX_V)
{
Value = Quad.m_aTexcoords[v].y;
}
if(Value != m_PreviousValuesPoint[QuadIndex][v][Prop])
vpActions.push_back(std::make_shared<CEditorActionEditQuadPointProp>(m_pEditor, GroupIndex, LayerIndex, QuadIndex, v, Prop, m_PreviousValuesPoint[QuadIndex][v][Prop], Value));
}
}
}
}
}
if(!vpActions.empty())
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions));
m_vTrackedProps.clear();
}
void CEditorPropTracker::BeginPropTrack(int Prop, int Value)
{
if(m_TrackedProp != -1 || m_TrackedProp == Prop)
return;
m_TrackedProp = Prop;
m_PreviousValue = Value;
}
void CEditorPropTracker::StopPropTrack(int Prop, int Value)
{
if(Prop != m_TrackedProp)
return;
m_TrackedProp = -1;
m_CurrentValue = Value;
}
// -----------------------------
void CEnvelopeEditorOperationTracker::Begin(EEnvelopeEditorOp Operation)
{
if(m_TrackedOp == Operation)
return;
if(m_TrackedOp != EEnvelopeEditorOp::OP_NONE)
{
Stop(true);
}
m_TrackedOp = Operation;
if(Operation == EEnvelopeEditorOp::OP_DRAG_POINT || Operation == EEnvelopeEditorOp::OP_DRAG_POINT_X || Operation == EEnvelopeEditorOp::OP_DRAG_POINT_Y || Operation == EEnvelopeEditorOp::OP_SCALE)
{
HandlePointDragStart();
}
}
void CEnvelopeEditorOperationTracker::Stop(bool Switch)
{
if(m_TrackedOp == EEnvelopeEditorOp::OP_NONE)
return;
if(m_TrackedOp == EEnvelopeEditorOp::OP_DRAG_POINT || m_TrackedOp == EEnvelopeEditorOp::OP_DRAG_POINT_X || m_TrackedOp == EEnvelopeEditorOp::OP_DRAG_POINT_Y || m_TrackedOp == EEnvelopeEditorOp::OP_SCALE)
{
HandlePointDragEnd(Switch);
}
m_TrackedOp = EEnvelopeEditorOp::OP_NONE;
}
void CEnvelopeEditorOperationTracker::HandlePointDragStart()
{
// Figure out which points are selected and which channels
// Save their X and Y position (time and value)
auto pEnv = m_pEditor->m_Map.m_vpEnvelopes[m_pEditor->m_SelectedEnvelope];
for(auto [PointIndex, Channel] : m_pEditor->m_vSelectedEnvelopePoints)
{
auto &Point = pEnv->m_vPoints[PointIndex];
auto &Data = m_SavedValues[PointIndex];
Data.m_Values[Channel] = Point.m_aValues[Channel];
if(Data.m_Used)
continue;
Data.m_Time = Point.m_Time;
Data.m_Used = true;
}
}
void CEnvelopeEditorOperationTracker::HandlePointDragEnd(bool Switch)
{
if(Switch && m_TrackedOp != EEnvelopeEditorOp::OP_SCALE)
return;
int EnvIndex = m_pEditor->m_SelectedEnvelope;
auto pEnv = m_pEditor->m_Map.m_vpEnvelopes[EnvIndex];
std::vector<std::shared_ptr<IEditorAction>> vpActions;
for(auto const &Entry : m_SavedValues)
{
int PointIndex = Entry.first;
auto &Point = pEnv->m_vPoints[PointIndex];
const auto &Data = Entry.second;
if(Data.m_Time != Point.m_Time)
{ // Save time
vpActions.push_back(std::make_shared<CEditorActionEnvelopeEditPoint>(m_pEditor, EnvIndex, PointIndex, 0, CEditorActionEnvelopeEditPoint::EEditType::TIME, Data.m_Time, Point.m_Time));
}
for(auto Value : Data.m_Values)
{
// Value.second is the saved value, Value.first is the channel
int Channel = Value.first;
if(Value.second != Point.m_aValues[Channel])
{ // Save value
vpActions.push_back(std::make_shared<CEditorActionEnvelopeEditPoint>(m_pEditor, EnvIndex, PointIndex, Channel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, Value.second, Point.m_aValues[Channel]));
}
}
}
if(!vpActions.empty())
{
m_pEditor->m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, "Envelope point drag"));
}
m_SavedValues.clear();
}
// -----------------------------------------------------------------------
void CLayerPropTracker::OnEnd(ELayerProp Prop, int Value)
{
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionEditLayerProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value));
}
int CLayerPropTracker::PropToValue(ELayerProp Prop)
{
switch(Prop)
{
case ELayerProp::PROP_GROUP: return m_pEditor->m_SelectedGroup;
case ELayerProp::PROP_HQ: return m_pObject->m_Flags;
case ELayerProp::PROP_ORDER: return m_pEditor->m_vSelectedLayers[0];
default: return 0;
}
}
// -----------------------------------------------------------------------
bool CLayerTilesPropTracker::EndChecker(ETilesProp Prop, EEditState State, int Value)
{
return (State == EEditState::END || State == EEditState::ONE_GO) && (Value != m_OriginalValue || Prop == ETilesProp::PROP_SHIFT);
}
void CLayerTilesPropTracker::OnStart(ETilesProp Prop)
{
if(Prop == ETilesProp::PROP_WIDTH || Prop == ETilesProp::PROP_HEIGHT)
{
m_SavedLayers[LAYERTYPE_TILES] = m_pObject->Duplicate();
if(m_pObject->m_Game || m_pObject->m_Front || m_pObject->m_Switch || m_pObject->m_Speedup || m_pObject->m_Tune || m_pObject->m_Tele)
{ // Need to save all entities layers when any entity layer
if(m_pEditor->m_Map.m_pFrontLayer && !m_pObject->m_Front)
m_SavedLayers[LAYERTYPE_FRONT] = m_pEditor->m_Map.m_pFrontLayer->Duplicate();
if(m_pEditor->m_Map.m_pTeleLayer && !m_pObject->m_Tele)
m_SavedLayers[LAYERTYPE_TELE] = m_pEditor->m_Map.m_pTeleLayer->Duplicate();
if(m_pEditor->m_Map.m_pSwitchLayer && !m_pObject->m_Switch)
m_SavedLayers[LAYERTYPE_SWITCH] = m_pEditor->m_Map.m_pSwitchLayer->Duplicate();
if(m_pEditor->m_Map.m_pSpeedupLayer && !m_pObject->m_Speedup)
m_SavedLayers[LAYERTYPE_SPEEDUP] = m_pEditor->m_Map.m_pSpeedupLayer->Duplicate();
if(m_pEditor->m_Map.m_pTuneLayer && !m_pObject->m_Tune)
m_SavedLayers[LAYERTYPE_TUNE] = m_pEditor->m_Map.m_pTuneLayer->Duplicate();
if(!m_pObject->m_Game)
m_SavedLayers[LAYERTYPE_GAME] = m_pEditor->m_Map.m_pGameLayer->Duplicate();
}
}
else if(Prop == ETilesProp::PROP_SHIFT)
{
m_SavedLayers[LAYERTYPE_TILES] = m_pObject->Duplicate();
}
}
void CLayerTilesPropTracker::OnEnd(ETilesProp Prop, int Value)
{
auto pAction = std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value);
pAction->SetSavedLayers(m_SavedLayers);
m_SavedLayers.clear();
m_pEditor->m_EditorHistory.RecordAction(pAction);
}
int CLayerTilesPropTracker::PropToValue(ETilesProp Prop)
{
switch(Prop)
{
case ETilesProp::PROP_AUTOMAPPER: return m_pObject->m_AutoMapperConfig;
case ETilesProp::PROP_COLOR: return PackColor(m_pObject->m_Color);
case ETilesProp::PROP_COLOR_ENV: return m_pObject->m_ColorEnv;
case ETilesProp::PROP_COLOR_ENV_OFFSET: return m_pObject->m_ColorEnvOffset;
case ETilesProp::PROP_HEIGHT: return m_pObject->m_Height;
case ETilesProp::PROP_WIDTH: return m_pObject->m_Width;
case ETilesProp::PROP_IMAGE: return m_pObject->m_Image;
case ETilesProp::PROP_SEED: return m_pObject->m_Seed;
case ETilesProp::PROP_SHIFT_BY: return m_pEditor->m_ShiftBy;
default: return 0;
}
}
// ------------------------------
void CLayerTilesCommonPropTracker::OnStart(ETilesCommonProp Prop)
{
for(auto &pLayer : m_vpLayers)
{
if(Prop == ETilesCommonProp::PROP_SHIFT)
{
m_SavedLayers[pLayer][LAYERTYPE_TILES] = pLayer->Duplicate();
}
}
}
void CLayerTilesCommonPropTracker::OnEnd(ETilesCommonProp Prop, int Value)
{
std::vector<std::shared_ptr<IEditorAction>> vpActions;
static std::map<ETilesCommonProp, ETilesProp> s_PropMap{
{ETilesCommonProp::PROP_COLOR, ETilesProp::PROP_COLOR},
{ETilesCommonProp::PROP_SHIFT, ETilesProp::PROP_SHIFT},
{ETilesCommonProp::PROP_SHIFT_BY, ETilesProp::PROP_SHIFT_BY}};
int j = 0;
for(auto &pLayer : m_vpLayers)
{
int LayerIndex = m_vLayerIndices[j++];
auto pAction = std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, LayerIndex, s_PropMap[Prop], m_OriginalValue, Value);
pAction->SetSavedLayers(m_SavedLayers[pLayer]);
vpActions.push_back(pAction);
}
char aDisplay[256];
static const char *s_apNames[] = {
"width",
"height",
"shift",
"shift by",
"color",
};
str_format(aDisplay, sizeof(aDisplay), "Edit %d layers common property: %s", (int)m_vpLayers.size(), s_apNames[(int)Prop]);
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay));
}
bool CLayerTilesCommonPropTracker::EndChecker(ETilesCommonProp Prop, EEditState State, int Value)
{
return (State == EEditState::END || State == EEditState::ONE_GO) && (Value != m_OriginalValue || Prop == ETilesCommonProp::PROP_SHIFT);
}
int CLayerTilesCommonPropTracker::PropToValue(ETilesCommonProp Prop)
{
if(Prop == ETilesCommonProp::PROP_SHIFT_BY)
return m_pEditor->m_ShiftBy;
return 0;
}
// ------------------------------
void CLayerGroupPropTracker::OnEnd(EGroupProp Prop, int Value)
{
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionEditGroupProp>(m_pEditor, m_pEditor->m_SelectedGroup, Prop, m_OriginalValue, Value));
}
int CLayerGroupPropTracker::PropToValue(EGroupProp Prop)
{
switch(Prop)
{
case EGroupProp::PROP_ORDER: return m_pEditor->m_SelectedGroup;
case EGroupProp::PROP_POS_X: return m_pObject->m_OffsetX;
case EGroupProp::PROP_POS_Y: return m_pObject->m_OffsetY;
case EGroupProp::PROP_PARA_X: return m_pObject->m_ParallaxX;
case EGroupProp::PROP_PARA_Y: return m_pObject->m_ParallaxY;
case EGroupProp::PROP_USE_CLIPPING: return m_pObject->m_UseClipping;
case EGroupProp::PROP_CLIP_X: return m_pObject->m_ClipX;
case EGroupProp::PROP_CLIP_Y: return m_pObject->m_ClipY;
case EGroupProp::PROP_CLIP_W: return m_pObject->m_ClipW;
case EGroupProp::PROP_CLIP_H: return m_pObject->m_ClipH;
default: return 0;
}
}
// ------------------------------------------------------------------
void CLayerQuadsPropTracker::OnEnd(ELayerQuadsProp Prop, int Value)
{
auto pAction = std::make_shared<CEditorActionEditLayerQuadsProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value);
m_pEditor->m_EditorHistory.RecordAction(pAction);
}
int CLayerQuadsPropTracker::PropToValue(ELayerQuadsProp Prop)
{
if(Prop == ELayerQuadsProp::PROP_IMAGE)
return m_pObject->m_Image;
return 0;
}
// -------------------------------------------------------------------
void CLayerSoundsPropTracker::OnEnd(ELayerSoundsProp Prop, int Value)
{
auto pAction = std::make_shared<CEditorActionEditLayerSoundsProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], Prop, m_OriginalValue, Value);
m_pEditor->m_EditorHistory.RecordAction(pAction);
}
int CLayerSoundsPropTracker::PropToValue(ELayerSoundsProp Prop)
{
if(Prop == ELayerSoundsProp::PROP_SOUND)
return m_pObject->m_Sound;
return 0;
}
// ----
void CSoundSourcePropTracker::OnEnd(ESoundProp Prop, int Value)
{
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionEditSoundSourceProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_pEditor->m_SelectedSource, Prop, m_OriginalValue, Value));
}
int CSoundSourcePropTracker::PropToValue(ESoundProp Prop)
{
switch(Prop)
{
case ESoundProp::PROP_POS_X: return m_pObject->m_Position.x;
case ESoundProp::PROP_POS_Y: return m_pObject->m_Position.y;
case ESoundProp::PROP_LOOP: return m_pObject->m_Loop;
case ESoundProp::PROP_PAN: return m_pObject->m_Pan;
case ESoundProp::PROP_TIME_DELAY: return m_pObject->m_TimeDelay;
case ESoundProp::PROP_FALLOFF: return m_pObject->m_Falloff;
case ESoundProp::PROP_POS_ENV: return m_pObject->m_PosEnv;
case ESoundProp::PROP_POS_ENV_OFFSET: return m_pObject->m_PosEnvOffset;
case ESoundProp::PROP_SOUND_ENV: return m_pObject->m_SoundEnv;
case ESoundProp::PROP_SOUND_ENV_OFFSET: return m_pObject->m_SoundEnvOffset;
default: return 0;
}
}
// ----
void CSoundSourceRectShapePropTracker::OnEnd(ERectangleShapeProp Prop, int Value)
{
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionEditRectSoundSourceShapeProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_pEditor->m_SelectedSource, Prop, m_OriginalValue, Value));
}
int CSoundSourceRectShapePropTracker::PropToValue(ERectangleShapeProp Prop)
{
switch(Prop)
{
case ERectangleShapeProp::PROP_RECTANGLE_WIDTH: return m_pObject->m_Shape.m_Rectangle.m_Width;
case ERectangleShapeProp::PROP_RECTANGLE_HEIGHT: return m_pObject->m_Shape.m_Rectangle.m_Height;
default: return 0;
}
}
void CSoundSourceCircleShapePropTracker::OnEnd(ECircleShapeProp Prop, int Value)
{
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionEditCircleSoundSourceShapeProp>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_pEditor->m_SelectedSource, Prop, m_OriginalValue, Value));
}
int CSoundSourceCircleShapePropTracker::PropToValue(ECircleShapeProp Prop)
{
switch(Prop)
{
case ECircleShapeProp::PROP_CIRCLE_RADIUS: return m_pObject->m_Shape.m_Circle.m_Radius;
default: return 0;
}
}

View file

@ -0,0 +1,271 @@
#ifndef GAME_EDITOR_EDITOR_TRACKERS_H
#define GAME_EDITOR_EDITOR_TRACKERS_H
#include <game/editor/mapitems.h>
#include <game/editor/mapitems/layer_quads.h>
#include <game/mapitems.h>
#include <map>
#include <memory>
#include <vector>
class CEditor;
class CLayerTiles;
class CLayerGroup;
class CLayerSounds;
struct CSoundSource;
class CQuadEditTracker
{
public:
CQuadEditTracker();
~CQuadEditTracker();
void BeginQuadTrack(const std::shared_ptr<CLayerQuads> &pLayer, const std::vector<int> &vSelectedQuads);
void EndQuadTrack();
void BeginQuadPropTrack(const std::shared_ptr<CLayerQuads> &pLayer, const std::vector<int> &vSelectedQuads, EQuadProp Prop);
void EndQuadPropTrack(EQuadProp Prop);
void BeginQuadPointPropTrack(const std::shared_ptr<CLayerQuads> &pLayer, const std::vector<int> &vSelectedQuads, int SelectedQuadPoints);
void AddQuadPointPropTrack(EQuadPointProp Prop);
void EndQuadPointPropTrack(EQuadPointProp Prop);
void EndQuadPointPropTrackAll();
CEditor *m_pEditor;
private:
std::vector<int> m_vSelectedQuads;
int m_SelectedQuadPoints;
std::map<int, std::vector<CPoint>> m_InitalPoints;
bool m_Tracking = false;
std::shared_ptr<CLayerQuads> m_pLayer;
EQuadProp m_TrackedProp;
std::vector<EQuadPointProp> m_vTrackedProps;
std::map<int, int> m_PreviousValues;
std::map<int, std::vector<std::map<EQuadPointProp, int>>> m_PreviousValuesPoint;
};
class CEditorPropTracker
{
public:
CEditorPropTracker() = default;
void BeginPropTrack(int Prop, int Value);
void StopPropTrack(int Prop, int Value);
inline void Reset() { m_TrackedProp = -1; }
CEditor *m_pEditor;
int m_PreviousValue;
int m_CurrentValue;
private:
int m_TrackedProp = -1;
};
enum class EEnvelopeEditorOp
{
OP_NONE = 0,
OP_SELECT,
OP_DRAG_POINT,
OP_DRAG_POINT_X,
OP_DRAG_POINT_Y,
OP_CONTEXT_MENU,
OP_BOX_SELECT,
OP_SCALE
};
class CEnvelopeEditorOperationTracker
{
public:
CEnvelopeEditorOperationTracker() = default;
void Begin(EEnvelopeEditorOp Operation);
void Stop(bool Switch = true);
inline void Reset() { m_TrackedOp = EEnvelopeEditorOp::OP_NONE; }
CEditor *m_pEditor;
private:
EEnvelopeEditorOp m_TrackedOp = EEnvelopeEditorOp::OP_NONE;
struct SPointData
{
bool m_Used;
int m_Time;
std::map<int, int> m_Values;
};
std::map<int, SPointData> m_SavedValues;
void HandlePointDragStart();
void HandlePointDragEnd(bool Switch);
};
template<typename T, typename E>
class CPropTracker
{
public:
CPropTracker(CEditor *pEditor) :
m_pEditor(pEditor), m_OriginalValue(0) {}
CEditor *m_pEditor;
void Begin(T *pObject, E Prop, EEditState State)
{
if(Prop == static_cast<E>(-1))
return;
m_pObject = pObject;
int Value = PropToValue(Prop);
if(StartChecker(Prop, State, Value))
{
m_OriginalValue = Value;
OnStart(Prop);
}
}
void End(E Prop, EEditState State)
{
if(Prop == static_cast<E>(-1))
return;
int Value = PropToValue(Prop);
if(EndChecker(Prop, State, Value))
{
OnEnd(Prop, Value);
}
}
protected:
virtual void OnStart(E Prop) {}
virtual void OnEnd(E Prop, int Value) {}
virtual int PropToValue(E Prop) { return 0; }
virtual bool StartChecker(E Prop, EEditState State, int Value)
{
return State == EEditState::START || State == EEditState::ONE_GO;
}
virtual bool EndChecker(E Prop, EEditState State, int Value)
{
return (State == EEditState::END || State == EEditState::ONE_GO) && (Value != m_OriginalValue);
}
int m_OriginalValue;
T *m_pObject;
};
class CLayerPropTracker : public CPropTracker<CLayer, ELayerProp>
{
public:
CLayerPropTracker(CEditor *pEditor) :
CPropTracker<CLayer, ELayerProp>(pEditor){};
protected:
void OnEnd(ELayerProp Prop, int Value) override;
int PropToValue(ELayerProp Prop) override;
};
class CLayerTilesPropTracker : public CPropTracker<CLayerTiles, ETilesProp>
{
public:
CLayerTilesPropTracker(CEditor *pEditor) :
CPropTracker<CLayerTiles, ETilesProp>(pEditor){};
protected:
void OnStart(ETilesProp Prop) override;
void OnEnd(ETilesProp Prop, int Value) override;
bool EndChecker(ETilesProp Prop, EEditState State, int Value) override;
int PropToValue(ETilesProp Prop) override;
private:
std::map<int, std::shared_ptr<CLayer>> m_SavedLayers;
};
class CLayerTilesCommonPropTracker : public CPropTracker<CLayerTiles, ETilesCommonProp>
{
public:
CLayerTilesCommonPropTracker(CEditor *pEditor) :
CPropTracker<CLayerTiles, ETilesCommonProp>(pEditor){};
protected:
void OnStart(ETilesCommonProp Prop) override;
void OnEnd(ETilesCommonProp Prop, int Value) override;
bool EndChecker(ETilesCommonProp Prop, EEditState State, int Value) override;
int PropToValue(ETilesCommonProp Prop) override;
private:
std::map<std::shared_ptr<CLayerTiles>, std::map<int, std::shared_ptr<CLayer>>> m_SavedLayers;
public:
std::vector<std::shared_ptr<CLayerTiles>> m_vpLayers;
std::vector<int> m_vLayerIndices;
};
class CLayerGroupPropTracker : public CPropTracker<CLayerGroup, EGroupProp>
{
public:
CLayerGroupPropTracker(CEditor *pEditor) :
CPropTracker<CLayerGroup, EGroupProp>(pEditor){};
protected:
void OnEnd(EGroupProp Prop, int Value) override;
int PropToValue(EGroupProp Prop) override;
};
class CLayerQuadsPropTracker : public CPropTracker<CLayerQuads, ELayerQuadsProp>
{
public:
CLayerQuadsPropTracker(CEditor *pEditor) :
CPropTracker<CLayerQuads, ELayerQuadsProp>(pEditor){};
protected:
void OnEnd(ELayerQuadsProp Prop, int Value) override;
int PropToValue(ELayerQuadsProp Prop) override;
};
class CLayerSoundsPropTracker : public CPropTracker<CLayerSounds, ELayerSoundsProp>
{
public:
CLayerSoundsPropTracker(CEditor *pEditor) :
CPropTracker<CLayerSounds, ELayerSoundsProp>(pEditor){};
protected:
void OnEnd(ELayerSoundsProp Prop, int Value) override;
int PropToValue(ELayerSoundsProp Prop) override;
};
class CSoundSourcePropTracker : public CPropTracker<CSoundSource, ESoundProp>
{
public:
CSoundSourcePropTracker(CEditor *pEditor) :
CPropTracker<CSoundSource, ESoundProp>(pEditor) {}
protected:
void OnEnd(ESoundProp Prop, int Value) override;
int PropToValue(ESoundProp Prop) override;
};
class CSoundSourceRectShapePropTracker : public CPropTracker<CSoundSource, ERectangleShapeProp>
{
public:
CSoundSourceRectShapePropTracker(CEditor *pEditor) :
CPropTracker<CSoundSource, ERectangleShapeProp>(pEditor) {}
protected:
void OnEnd(ERectangleShapeProp Prop, int Value) override;
int PropToValue(ERectangleShapeProp Prop) override;
};
class CSoundSourceCircleShapePropTracker : public CPropTracker<CSoundSource, ECircleShapeProp>
{
public:
CSoundSourceCircleShapePropTracker(CEditor *pEditor) :
CPropTracker<CSoundSource, ECircleShapeProp>(pEditor) {}
protected:
void OnEnd(ECircleShapeProp Prop, int Value) override;
int PropToValue(ECircleShapeProp Prop) override;
};
#endif

125
src/game/editor/mapitems.h Normal file
View file

@ -0,0 +1,125 @@
#ifndef GAME_EDITOR_MAPITEMS_H
#define GAME_EDITOR_MAPITEMS_H
enum class EQuadProp
{
PROP_NONE = -1,
PROP_ORDER,
PROP_POS_X,
PROP_POS_Y,
PROP_POS_ENV,
PROP_POS_ENV_OFFSET,
PROP_COLOR_ENV,
PROP_COLOR_ENV_OFFSET,
NUM_PROPS,
};
enum class EQuadPointProp
{
PROP_NONE = -1,
PROP_POS_X,
PROP_POS_Y,
PROP_COLOR,
PROP_TEX_U,
PROP_TEX_V,
NUM_PROPS,
};
enum class ESoundProp
{
PROP_NONE = -1,
PROP_POS_X,
PROP_POS_Y,
PROP_LOOP,
PROP_PAN,
PROP_TIME_DELAY,
PROP_FALLOFF,
PROP_POS_ENV,
PROP_POS_ENV_OFFSET,
PROP_SOUND_ENV,
PROP_SOUND_ENV_OFFSET,
NUM_PROPS,
};
enum class ERectangleShapeProp
{
PROP_NONE = -1,
PROP_RECTANGLE_WIDTH,
PROP_RECTANGLE_HEIGHT,
NUM_RECTANGLE_PROPS,
};
enum class ECircleShapeProp
{
PROP_NONE = -1,
PROP_CIRCLE_RADIUS,
NUM_CIRCLE_PROPS,
};
enum class ELayerProp
{
PROP_NONE = -1,
PROP_GROUP,
PROP_ORDER,
PROP_HQ,
NUM_PROPS,
};
enum class ETilesProp
{
PROP_NONE = -1,
PROP_WIDTH,
PROP_HEIGHT,
PROP_SHIFT,
PROP_SHIFT_BY,
PROP_IMAGE,
PROP_COLOR,
PROP_COLOR_ENV,
PROP_COLOR_ENV_OFFSET,
PROP_AUTOMAPPER,
PROP_SEED,
NUM_PROPS
};
enum class ETilesCommonProp
{
PROP_NONE = -1,
PROP_WIDTH,
PROP_HEIGHT,
PROP_SHIFT,
PROP_SHIFT_BY,
PROP_COLOR,
NUM_PROPS,
};
enum class EGroupProp
{
PROP_NONE = -1,
PROP_ORDER,
PROP_POS_X,
PROP_POS_Y,
PROP_PARA_X,
PROP_PARA_Y,
PROP_USE_CLIPPING,
PROP_CLIP_X,
PROP_CLIP_Y,
PROP_CLIP_W,
PROP_CLIP_H,
NUM_PROPS,
};
enum class ELayerQuadsProp
{
PROP_NONE = -1,
PROP_IMAGE,
NUM_PROPS,
};
enum class ELayerSoundsProp
{
PROP_NONE = -1,
PROP_SOUND,
NUM_PROPS,
};
#endif

View file

@ -25,6 +25,7 @@ public:
void AddPoint(int Time, int v0, int v1 = 0, int v2 = 0, int v3 = 0); void AddPoint(int Time, int v0, int v1 = 0, int v2 = 0, int v3 = 0);
float EndTime() const; float EndTime() const;
int GetChannels() const; int GetChannels() const;
EType Type() const { return m_Type; }
private: private:
void Resort(); void Resort();

View file

@ -62,6 +62,7 @@ public:
virtual void ModifySoundIndex(FIndexModifyFunction pfnFunc) {} virtual void ModifySoundIndex(FIndexModifyFunction pfnFunc) {}
virtual std::shared_ptr<CLayer> Duplicate() const = 0; virtual std::shared_ptr<CLayer> Duplicate() const = 0;
virtual const char *TypeName() const = 0;
virtual void GetSize(float *pWidth, float *pHeight) virtual void GetSize(float *pWidth, float *pHeight)
{ {

View file

@ -45,3 +45,8 @@ void CLayerFront::Resize(int NewW, int NewH)
if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH)
m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH);
} }
const char *CLayerFront::TypeName() const
{
return "front";
}

View file

@ -10,6 +10,7 @@ public:
void Resize(int NewW, int NewH) override; void Resize(int NewW, int NewH) override;
void SetTile(int x, int y, CTile Tile) override; void SetTile(int x, int y, CTile Tile) override;
const char *TypeName() const override;
}; };
#endif #endif

View file

@ -72,3 +72,8 @@ CUI::EPopupMenuFunctionResult CLayerGame::RenderProperties(CUIRect *pToolbox)
m_Image = -1; m_Image = -1;
return Result; return Result;
} }
const char *CLayerGame::TypeName() const
{
return "game";
}

View file

@ -11,6 +11,7 @@ public:
CTile GetTile(int x, int y) override; CTile GetTile(int x, int y) override;
void SetTile(int x, int y, CTile Tile) override; void SetTile(int x, int y, CTile Tile) override;
const char *TypeName() const override;
CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override; CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override;
}; };

View file

@ -3,6 +3,7 @@
#include "layer_quads.h" #include "layer_quads.h"
#include <game/editor/editor.h> #include <game/editor/editor.h>
#include <game/editor/editor_actions.h>
#include "image.h" #include "image.h"
@ -140,6 +141,7 @@ int CLayerQuads::BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect)
void CLayerQuads::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy) void CLayerQuads::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy)
{ {
std::shared_ptr<CLayerQuads> pQuadLayer = std::static_pointer_cast<CLayerQuads>(pBrush); std::shared_ptr<CLayerQuads> pQuadLayer = std::static_pointer_cast<CLayerQuads>(pBrush);
std::vector<CQuad> vAddedQuads;
for(const auto &Quad : pQuadLayer->m_vQuads) for(const auto &Quad : pQuadLayer->m_vQuads)
{ {
CQuad n = Quad; CQuad n = Quad;
@ -151,7 +153,9 @@ void CLayerQuads::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy)
} }
m_vQuads.push_back(n); m_vQuads.push_back(n);
vAddedQuads.push_back(n);
} }
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionQuadPlace>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], vAddedQuads));
m_pEditor->m_Map.OnModify(); m_pEditor->m_Map.OnModify();
} }
@ -219,26 +223,23 @@ void CLayerQuads::GetSize(float *pWidth, float *pHeight)
CUI::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox) CUI::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox)
{ {
enum
{
PROP_IMAGE = 0,
NUM_PROPS,
};
CProperty aProps[] = { CProperty aProps[] = {
{"Image", m_Image, PROPTYPE_IMAGE, -1, 0}, {"Image", m_Image, PROPTYPE_IMAGE, -1, 0},
{nullptr}, {nullptr},
}; };
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)ELayerQuadsProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); auto [State, Prop] = m_pEditor->DoPropertiesWithState<ELayerQuadsProp>(pToolBox, aProps, s_aIds, &NewVal);
if(Prop != -1) if(Prop != ELayerQuadsProp::PROP_NONE)
{ {
m_pEditor->m_Map.OnModify(); m_pEditor->m_Map.OnModify();
} }
if(Prop == PROP_IMAGE) static CLayerQuadsPropTracker s_Tracker(m_pEditor);
s_Tracker.Begin(this, Prop, State);
if(Prop == ELayerQuadsProp::PROP_IMAGE)
{ {
if(NewVal >= 0) if(NewVal >= 0)
m_Image = NewVal % m_pEditor->m_Map.m_vpImages.size(); m_Image = NewVal % m_pEditor->m_Map.m_vpImages.size();
@ -246,6 +247,8 @@ CUI::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox)
m_Image = -1; m_Image = -1;
} }
s_Tracker.End(Prop, State);
return CUI::POPUP_KEEP_OPEN; return CUI::POPUP_KEEP_OPEN;
} }
@ -280,3 +283,8 @@ int CLayerQuads::SwapQuads(int Index0, int Index1)
std::swap(m_vQuads[Index0], m_vQuads[Index1]); std::swap(m_vQuads[Index0], m_vQuads[Index1]);
return Index1; return Index1;
} }
const char *CLayerQuads::TypeName() const
{
return "quads";
}

View file

@ -28,6 +28,7 @@ public:
void GetSize(float *pWidth, float *pHeight) override; void GetSize(float *pWidth, float *pHeight) override;
std::shared_ptr<CLayer> Duplicate() const override; std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
int m_Image; int m_Image;
std::vector<CQuad> m_vQuads; std::vector<CQuad> m_vQuads;

View file

@ -1,7 +1,7 @@
#include "layer_sounds.h" #include "layer_sounds.h"
#include <game/editor/editor.h> #include <game/editor/editor.h>
#include <game/editor/editor_actions.h>
#include <game/generated/client_data.h> #include <game/generated/client_data.h>
static const float s_SourceVisualSize = 32.0f; static const float s_SourceVisualSize = 32.0f;
@ -171,6 +171,7 @@ int CLayerSounds::BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect)
void CLayerSounds::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy) void CLayerSounds::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy)
{ {
std::shared_ptr<CLayerSounds> pSoundLayer = std::static_pointer_cast<CLayerSounds>(pBrush); std::shared_ptr<CLayerSounds> pSoundLayer = std::static_pointer_cast<CLayerSounds>(pBrush);
std::vector<CSoundSource> vAddedSources;
for(const auto &Source : pSoundLayer->m_vSources) for(const auto &Source : pSoundLayer->m_vSources)
{ {
CSoundSource n = Source; CSoundSource n = Source;
@ -179,32 +180,31 @@ void CLayerSounds::BrushPlace(std::shared_ptr<CLayer> pBrush, float wx, float wy
n.m_Position.y += f2fx(wy); n.m_Position.y += f2fx(wy);
m_vSources.push_back(n); m_vSources.push_back(n);
vAddedSources.push_back(n);
} }
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionSoundPlace>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], vAddedSources));
m_pEditor->m_Map.OnModify(); m_pEditor->m_Map.OnModify();
} }
CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox) CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox)
{ {
enum
{
PROP_SOUND = 0,
NUM_PROPS,
};
CProperty aProps[] = { CProperty aProps[] = {
{"Sound", m_Sound, PROPTYPE_SOUND, -1, 0}, {"Sound", m_Sound, PROPTYPE_SOUND, -1, 0},
{nullptr}, {nullptr},
}; };
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)ELayerSoundsProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); auto [State, Prop] = m_pEditor->DoPropertiesWithState<ELayerSoundsProp>(pToolBox, aProps, s_aIds, &NewVal);
if(Prop != -1) if(Prop != ELayerSoundsProp::PROP_NONE)
{ {
m_pEditor->m_Map.OnModify(); m_pEditor->m_Map.OnModify();
} }
if(Prop == PROP_SOUND) static CLayerSoundsPropTracker s_Tracker(m_pEditor);
s_Tracker.Begin(this, Prop, State);
if(Prop == ELayerSoundsProp::PROP_SOUND)
{ {
if(NewVal >= 0) if(NewVal >= 0)
m_Sound = NewVal % m_pEditor->m_Map.m_vpSounds.size(); m_Sound = NewVal % m_pEditor->m_Map.m_vpSounds.size();
@ -212,6 +212,8 @@ CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox)
m_Sound = -1; m_Sound = -1;
} }
s_Tracker.End(Prop, State);
return CUI::POPUP_KEEP_OPEN; return CUI::POPUP_KEEP_OPEN;
} }
@ -233,3 +235,8 @@ std::shared_ptr<CLayer> CLayerSounds::Duplicate() const
{ {
return std::make_shared<CLayerSounds>(*this); return std::make_shared<CLayerSounds>(*this);
} }
const char *CLayerSounds::TypeName() const
{
return "sounds";
}

View file

@ -23,6 +23,7 @@ public:
void ModifySoundIndex(FIndexModifyFunction pfnFunc) override; void ModifySoundIndex(FIndexModifyFunction pfnFunc) override;
std::shared_ptr<CLayer> Duplicate() const override; std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
int m_Sound; int m_Sound;
std::vector<CSoundSource> m_vSources; std::vector<CSoundSource> m_vSources;

View file

@ -12,6 +12,16 @@ CLayerSpeedup::CLayerSpeedup(CEditor *pEditor, int w, int h) :
mem_zero(m_pSpeedupTile, (size_t)w * h * sizeof(CSpeedupTile)); mem_zero(m_pSpeedupTile, (size_t)w * h * sizeof(CSpeedupTile));
} }
CLayerSpeedup::CLayerSpeedup(const CLayerSpeedup &Other) :
CLayerTiles(Other)
{
str_copy(m_aName, "Speedup copy");
m_Speedup = 1;
m_pSpeedupTile = new CSpeedupTile[m_Width * m_Height];
mem_copy(m_pSpeedupTile, Other.m_pSpeedupTile, (size_t)m_Width * m_Height * sizeof(CSpeedupTile));
}
CLayerSpeedup::~CLayerSpeedup() CLayerSpeedup::~CLayerSpeedup()
{ {
delete[] m_pSpeedupTile; delete[] m_pSpeedupTile;
@ -84,53 +94,78 @@ void CLayerSpeedup::BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy
if(!Destructive && GetTile(fx, fy).m_Index) if(!Destructive && GetTile(fx, fy).m_Index)
continue; continue;
int Index = fy * m_Width + fx;
SSpeedupTileStateChange::SData Previous{
m_pSpeedupTile[Index].m_Force,
m_pSpeedupTile[Index].m_Angle,
m_pSpeedupTile[Index].m_MaxSpeed,
m_pSpeedupTile[Index].m_Type,
m_pTiles[Index].m_Index};
if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index)) && pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index != TILE_AIR) if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index)) && pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index != TILE_AIR)
{ {
if(m_pEditor->m_SpeedupAngle != pSpeedupLayer->m_SpeedupAngle || m_pEditor->m_SpeedupForce != pSpeedupLayer->m_SpeedupForce || m_pEditor->m_SpeedupMaxSpeed != pSpeedupLayer->m_SpeedupMaxSpeed) if(m_pEditor->m_SpeedupAngle != pSpeedupLayer->m_SpeedupAngle || m_pEditor->m_SpeedupForce != pSpeedupLayer->m_SpeedupForce || m_pEditor->m_SpeedupMaxSpeed != pSpeedupLayer->m_SpeedupMaxSpeed)
{ {
m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; m_pSpeedupTile[Index].m_Force = m_pEditor->m_SpeedupForce;
m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; m_pSpeedupTile[Index].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed;
m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; m_pSpeedupTile[Index].m_Angle = m_pEditor->m_SpeedupAngle;
m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; m_pSpeedupTile[Index].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index;
m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; m_pTiles[Index].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index;
} }
else if(pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force) else if(pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force)
{ {
m_pSpeedupTile[fy * m_Width + fx].m_Force = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force; m_pSpeedupTile[Index].m_Force = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force;
m_pSpeedupTile[fy * m_Width + fx].m_Angle = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Angle; m_pSpeedupTile[Index].m_Angle = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Angle;
m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_MaxSpeed; m_pSpeedupTile[Index].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_MaxSpeed;
m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; m_pSpeedupTile[Index].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index;
m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; m_pTiles[Index].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index;
} }
else if(m_pEditor->m_SpeedupForce) else if(m_pEditor->m_SpeedupForce)
{ {
m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; m_pSpeedupTile[Index].m_Force = m_pEditor->m_SpeedupForce;
m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; m_pSpeedupTile[Index].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed;
m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; m_pSpeedupTile[Index].m_Angle = m_pEditor->m_SpeedupAngle;
m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; m_pSpeedupTile[Index].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index;
m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; m_pTiles[Index].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index;
} }
else else
{ {
m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; m_pSpeedupTile[Index].m_Force = 0;
m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; m_pSpeedupTile[Index].m_MaxSpeed = 0;
m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; m_pSpeedupTile[Index].m_Angle = 0;
m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; m_pSpeedupTile[Index].m_Type = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
} }
} }
else else
{ {
m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; m_pSpeedupTile[Index].m_Force = 0;
m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; m_pSpeedupTile[Index].m_MaxSpeed = 0;
m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; m_pSpeedupTile[Index].m_Angle = 0;
m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; m_pSpeedupTile[Index].m_Type = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
} }
SSpeedupTileStateChange::SData Current{
m_pSpeedupTile[Index].m_Force,
m_pSpeedupTile[Index].m_Angle,
m_pSpeedupTile[Index].m_MaxSpeed,
m_pSpeedupTile[Index].m_Type,
m_pTiles[Index].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
FlagModified(sx, sy, pSpeedupLayer->m_Width, pSpeedupLayer->m_Height); FlagModified(sx, sy, pSpeedupLayer->m_Width, pSpeedupLayer->m_Height);
} }
void CLayerSpeedup::RecordStateChange(int x, int y, SSpeedupTileStateChange::SData Previous, SSpeedupTileStateChange::SData Current)
{
if(!m_History[y][x].m_Changed)
m_History[y][x] = SSpeedupTileStateChange{true, Previous, Current};
else
m_History[y][x].m_Current = Current;
}
void CLayerSpeedup::BrushFlipX() void CLayerSpeedup::BrushFlipX()
{ {
CLayerTiles::BrushFlipX(); CLayerTiles::BrushFlipX();
@ -211,6 +246,13 @@ void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CU
const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height);
const int TgtIndex = fy * m_Width + fx; const int TgtIndex = fy * m_Width + fx;
SSpeedupTileStateChange::SData Previous{
m_pSpeedupTile[TgtIndex].m_Force,
m_pSpeedupTile[TgtIndex].m_Angle,
m_pSpeedupTile[TgtIndex].m_MaxSpeed,
m_pSpeedupTile[TgtIndex].m_Type,
m_pTiles[TgtIndex].m_Index};
if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSpeedupTile((pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSpeedupTile((pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset
{ {
m_pTiles[TgtIndex].m_Index = 0; m_pTiles[TgtIndex].m_Index = 0;
@ -240,7 +282,26 @@ void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CU
m_pSpeedupTile[TgtIndex].m_MaxSpeed = pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed; m_pSpeedupTile[TgtIndex].m_MaxSpeed = pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed;
} }
} }
SSpeedupTileStateChange::SData Current{
m_pSpeedupTile[TgtIndex].m_Force,
m_pSpeedupTile[TgtIndex].m_Angle,
m_pSpeedupTile[TgtIndex].m_MaxSpeed,
m_pSpeedupTile[TgtIndex].m_Type,
m_pTiles[TgtIndex].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
} }
FlagModified(sx, sy, w, h); FlagModified(sx, sy, w, h);
} }
std::shared_ptr<CLayer> CLayerSpeedup::Duplicate() const
{
return std::make_shared<CLayerSpeedup>(*this);
}
const char *CLayerSpeedup::TypeName() const
{
return "speedup";
}

View file

@ -3,10 +3,24 @@
#include "layer_tiles.h" #include "layer_tiles.h"
struct SSpeedupTileStateChange
{
bool m_Changed;
struct SData
{
int m_Force;
int m_Angle;
int m_MaxSpeed;
int m_Type;
int m_Index;
} m_Previous, m_Current;
};
class CLayerSpeedup : public CLayerTiles class CLayerSpeedup : public CLayerTiles
{ {
public: public:
CLayerSpeedup(CEditor *pEditor, int w, int h); CLayerSpeedup(CEditor *pEditor, int w, int h);
CLayerSpeedup(const CLayerSpeedup &Other);
~CLayerSpeedup(); ~CLayerSpeedup();
CSpeedupTile *m_pSpeedupTile; CSpeedupTile *m_pSpeedupTile;
@ -22,6 +36,19 @@ public:
void BrushFlipY() override; void BrushFlipY() override;
void BrushRotate(float Amount) override; void BrushRotate(float Amount) override;
void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override; void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override;
EditorTileStateChangeHistory<SSpeedupTileStateChange> m_History;
void ClearHistory() override
{
CLayerTiles::ClearHistory();
m_History.clear();
}
std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
private:
void RecordStateChange(int x, int y, SSpeedupTileStateChange::SData Previous, SSpeedupTileStateChange::SData Current);
}; };
#endif #endif

View file

@ -12,6 +12,16 @@ CLayerSwitch::CLayerSwitch(CEditor *pEditor, int w, int h) :
mem_zero(m_pSwitchTile, (size_t)w * h * sizeof(CSwitchTile)); mem_zero(m_pSwitchTile, (size_t)w * h * sizeof(CSwitchTile));
} }
CLayerSwitch::CLayerSwitch(const CLayerSwitch &Other) :
CLayerTiles(Other)
{
str_copy(m_aName, "Switch copy");
m_Switch = 1;
m_pSwitchTile = new CSwitchTile[m_Width * m_Height];
mem_copy(m_pSwitchTile, Other.m_pSwitchTile, (size_t)m_Width * m_Height * sizeof(CSwitchTile));
}
CLayerSwitch::~CLayerSwitch() CLayerSwitch::~CLayerSwitch()
{ {
delete[] m_pSwitchTile; delete[] m_pSwitchTile;
@ -83,54 +93,79 @@ void CLayerSwitch::BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy)
if(!Destructive && GetTile(fx, fy).m_Index) if(!Destructive && GetTile(fx, fy).m_Index)
continue; continue;
int Index = fy * m_Width + fx;
SSwitchTileStateChange::SData Previous{
m_pSwitchTile[Index].m_Number,
m_pSwitchTile[Index].m_Type,
m_pSwitchTile[Index].m_Flags,
m_pSwitchTile[Index].m_Delay,
m_pTiles[Index].m_Index};
if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) && pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) && pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR)
{ {
if(m_pEditor->m_SwitchNum != pSwitchLayer->m_SwitchNumber || m_pEditor->m_SwitchDelay != pSwitchLayer->m_SwitchDelay) if(m_pEditor->m_SwitchNum != pSwitchLayer->m_SwitchNumber || m_pEditor->m_SwitchDelay != pSwitchLayer->m_SwitchDelay)
{ {
m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; m_pSwitchTile[Index].m_Number = m_pEditor->m_SwitchNum;
m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; m_pSwitchTile[Index].m_Delay = m_pEditor->m_SwitchDelay;
} }
else if(pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number) else if(pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number)
{ {
m_pSwitchTile[fy * m_Width + fx].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number; m_pSwitchTile[Index].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number;
m_pSwitchTile[fy * m_Width + fx].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay; m_pSwitchTile[Index].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay;
} }
else else
{ {
m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; m_pSwitchTile[Index].m_Number = m_pEditor->m_SwitchNum;
m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; m_pSwitchTile[Index].m_Delay = m_pEditor->m_SwitchDelay;
} }
m_pSwitchTile[fy * m_Width + fx].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; m_pSwitchTile[Index].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index;
m_pSwitchTile[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; m_pSwitchTile[Index].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags;
m_pTiles[fy * m_Width + fx].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; m_pTiles[Index].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index;
m_pTiles[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; m_pTiles[Index].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags;
if(!IsSwitchTileFlagsUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) if(!IsSwitchTileFlagsUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index))
{ {
m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; m_pSwitchTile[Index].m_Flags = 0;
} }
if(!IsSwitchTileNumberUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) if(!IsSwitchTileNumberUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index))
{ {
m_pSwitchTile[fy * m_Width + fx].m_Number = 0; m_pSwitchTile[Index].m_Number = 0;
} }
if(!IsSwitchTileDelayUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) if(!IsSwitchTileDelayUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index))
{ {
m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; m_pSwitchTile[Index].m_Delay = 0;
} }
} }
else else
{ {
m_pSwitchTile[fy * m_Width + fx].m_Number = 0; m_pSwitchTile[Index].m_Number = 0;
m_pSwitchTile[fy * m_Width + fx].m_Type = 0; m_pSwitchTile[Index].m_Type = 0;
m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; m_pSwitchTile[Index].m_Flags = 0;
m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; m_pSwitchTile[Index].m_Delay = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
} }
SSwitchTileStateChange::SData Current{
m_pSwitchTile[Index].m_Number,
m_pSwitchTile[Index].m_Type,
m_pSwitchTile[Index].m_Flags,
m_pSwitchTile[Index].m_Delay,
m_pTiles[Index].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
FlagModified(sx, sy, pSwitchLayer->m_Width, pSwitchLayer->m_Height); FlagModified(sx, sy, pSwitchLayer->m_Width, pSwitchLayer->m_Height);
} }
void CLayerSwitch::RecordStateChange(int x, int y, SSwitchTileStateChange::SData Previous, SSwitchTileStateChange::SData Current)
{
if(!m_History[y][x].m_Changed)
m_History[y][x] = SSwitchTileStateChange{true, Previous, Current};
else
m_History[y][x].m_Current = Current;
}
void CLayerSwitch::BrushFlipX() void CLayerSwitch::BrushFlipX()
{ {
CLayerTiles::BrushFlipX(); CLayerTiles::BrushFlipX();
@ -217,6 +252,13 @@ void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUI
const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height);
const int TgtIndex = fy * m_Width + fx; const int TgtIndex = fy * m_Width + fx;
SSwitchTileStateChange::SData Previous{
m_pSwitchTile[TgtIndex].m_Number,
m_pSwitchTile[TgtIndex].m_Type,
m_pSwitchTile[TgtIndex].m_Flags,
m_pSwitchTile[TgtIndex].m_Delay,
m_pTiles[TgtIndex].m_Index};
if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile((pLt->m_pTiles[SrcIndex]).m_Index))) if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile((pLt->m_pTiles[SrcIndex]).m_Index)))
{ {
m_pTiles[TgtIndex].m_Index = 0; m_pTiles[TgtIndex].m_Index = 0;
@ -250,6 +292,15 @@ void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUI
m_pSwitchTile[TgtIndex].m_Flags = pLt->m_pSwitchTile[SrcIndex].m_Flags; m_pSwitchTile[TgtIndex].m_Flags = pLt->m_pSwitchTile[SrcIndex].m_Flags;
} }
} }
SSwitchTileStateChange::SData Current{
m_pSwitchTile[TgtIndex].m_Number,
m_pSwitchTile[TgtIndex].m_Type,
m_pSwitchTile[TgtIndex].m_Flags,
m_pSwitchTile[TgtIndex].m_Delay,
m_pTiles[TgtIndex].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
} }
FlagModified(sx, sy, w, h); FlagModified(sx, sy, w, h);
@ -270,3 +321,13 @@ bool CLayerSwitch::ContainsElementWithId(int Id)
return false; return false;
} }
std::shared_ptr<CLayer> CLayerSwitch::Duplicate() const
{
return std::make_shared<CLayerSwitch>(*this);
}
const char *CLayerSwitch::TypeName() const
{
return "switch";
}

View file

@ -3,10 +3,24 @@
#include "layer_tiles.h" #include "layer_tiles.h"
struct SSwitchTileStateChange
{
bool m_Changed;
struct SData
{
int m_Number;
int m_Type;
int m_Flags;
int m_Delay;
int m_Index;
} m_Previous, m_Current;
};
class CLayerSwitch : public CLayerTiles class CLayerSwitch : public CLayerTiles
{ {
public: public:
CLayerSwitch(CEditor *pEditor, int w, int h); CLayerSwitch(CEditor *pEditor, int w, int h);
CLayerSwitch(const CLayerSwitch &Other);
~CLayerSwitch(); ~CLayerSwitch();
CSwitchTile *m_pSwitchTile; CSwitchTile *m_pSwitchTile;
@ -22,6 +36,19 @@ public:
void BrushRotate(float Amount) override; void BrushRotate(float Amount) override;
void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override; void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override;
virtual bool ContainsElementWithId(int Id); virtual bool ContainsElementWithId(int Id);
EditorTileStateChangeHistory<SSwitchTileStateChange> m_History;
inline void ClearHistory() override
{
CLayerTiles::ClearHistory();
m_History.clear();
}
std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
private:
void RecordStateChange(int x, int y, SSwitchTileStateChange::SData Previous, SSwitchTileStateChange::SData Current);
}; };
#endif #endif

View file

@ -12,6 +12,16 @@ CLayerTele::CLayerTele(CEditor *pEditor, int w, int h) :
mem_zero(m_pTeleTile, (size_t)w * h * sizeof(CTeleTile)); mem_zero(m_pTeleTile, (size_t)w * h * sizeof(CTeleTile));
} }
CLayerTele::CLayerTele(const CLayerTele &Other) :
CLayerTiles(Other)
{
str_copy(m_aName, "Tele copy");
m_Tele = 1;
m_pTeleTile = new CTeleTile[m_Width * m_Height];
mem_copy(m_pTeleTile, Other.m_pTeleTile, (size_t)m_Width * m_Height * sizeof(CTeleTile));
}
CLayerTele::~CLayerTele() CLayerTele::~CLayerTele()
{ {
delete[] m_pTeleTile; delete[] m_pTeleTile;
@ -80,48 +90,80 @@ void CLayerTele::BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy)
if(!Destructive && GetTile(fx, fy).m_Index) if(!Destructive && GetTile(fx, fy).m_Index)
continue; continue;
int Index = fy * m_Width + fx;
STeleTileStateChange::SData Previous{
m_pTeleTile[Index].m_Number,
m_pTeleTile[Index].m_Type,
m_pTiles[Index].m_Index};
if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) && pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR) if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) && pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR)
{ {
if(!IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) if(!IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index))
{ {
// Tele tile number is unused. Set a known value which is not 0, // Tele tile number is unused. Set a known value which is not 0,
// as tiles with number 0 would be ignored by previous versions. // as tiles with number 0 would be ignored by previous versions.
m_pTeleTile[fy * m_Width + fx].m_Number = 255; m_pTeleTile[Index].m_Number = 255;
} }
else if(m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum) else if(m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum)
{ {
m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumber;
} }
else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number) else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number)
{ {
m_pTeleTile[fy * m_Width + fx].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number; m_pTeleTile[Index].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number;
} }
else else
{ {
if(!m_pEditor->m_TeleNumber) if(!m_pEditor->m_TeleNumber)
{ {
m_pTeleTile[fy * m_Width + fx].m_Number = 0; m_pTeleTile[Index].m_Number = 0;
m_pTeleTile[fy * m_Width + fx].m_Type = 0; m_pTeleTile[Index].m_Type = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
STeleTileStateChange::SData Current{
m_pTeleTile[Index].m_Number,
m_pTeleTile[Index].m_Type,
m_pTiles[Index].m_Index};
RecordStateChange(fx, fy, Previous, Current);
continue; continue;
} }
else else
m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; {
m_pTeleTile[Index].m_Number = m_pEditor->m_TeleNumber;
}
} }
m_pTeleTile[fy * m_Width + fx].m_Type = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; m_pTeleTile[Index].m_Type = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index;
m_pTiles[fy * m_Width + fx].m_Index = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; m_pTiles[Index].m_Index = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index;
} }
else else
{ {
m_pTeleTile[fy * m_Width + fx].m_Number = 0; m_pTeleTile[Index].m_Number = 0;
m_pTeleTile[fy * m_Width + fx].m_Type = 0; m_pTeleTile[Index].m_Type = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
} }
STeleTileStateChange::SData Current{
m_pTeleTile[Index].m_Number,
m_pTeleTile[Index].m_Type,
m_pTiles[Index].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
FlagModified(sx, sy, pTeleLayer->m_Width, pTeleLayer->m_Height); FlagModified(sx, sy, pTeleLayer->m_Width, pTeleLayer->m_Height);
} }
void CLayerTele::RecordStateChange(int x, int y, STeleTileStateChange::SData Previous, STeleTileStateChange::SData Current)
{
if(!m_History[y][x].m_Changed)
m_History[y][x] = STeleTileStateChange{true, Previous, Current};
else
{
m_History[y][x].m_Current = Current;
}
}
void CLayerTele::BrushFlipX() void CLayerTele::BrushFlipX()
{ {
CLayerTiles::BrushFlipX(); CLayerTiles::BrushFlipX();
@ -202,6 +244,11 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRe
const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height);
const int TgtIndex = fy * m_Width + fx; const int TgtIndex = fy * m_Width + fx;
STeleTileStateChange::SData Previous{
m_pTeleTile[TgtIndex].m_Number,
m_pTeleTile[TgtIndex].m_Type,
m_pTiles[TgtIndex].m_Index};
if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTeleTile((pLt->m_pTiles[SrcIndex]).m_Index))) if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTeleTile((pLt->m_pTiles[SrcIndex]).m_Index)))
{ {
m_pTiles[TgtIndex].m_Index = 0; m_pTiles[TgtIndex].m_Index = 0;
@ -227,6 +274,13 @@ void CLayerTele::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRe
m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number; m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number;
} }
} }
STeleTileStateChange::SData Current{
m_pTeleTile[TgtIndex].m_Number,
m_pTeleTile[TgtIndex].m_Type,
m_pTiles[TgtIndex].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
} }
FlagModified(sx, sy, w, h); FlagModified(sx, sy, w, h);
@ -247,3 +301,13 @@ bool CLayerTele::ContainsElementWithId(int Id)
return false; return false;
} }
std::shared_ptr<CLayer> CLayerTele::Duplicate() const
{
return std::make_shared<CLayerTele>(*this);
}
const char *CLayerTele::TypeName() const
{
return "tele";
}

View file

@ -3,10 +3,22 @@
#include "layer_tiles.h" #include "layer_tiles.h"
struct STeleTileStateChange
{
bool m_Changed;
struct SData
{
int m_Number;
int m_Type;
int m_Index;
} m_Previous, m_Current;
};
class CLayerTele : public CLayerTiles class CLayerTele : public CLayerTiles
{ {
public: public:
CLayerTele(CEditor *pEditor, int w, int h); CLayerTele(CEditor *pEditor, int w, int h);
CLayerTele(const CLayerTele &Other);
~CLayerTele(); ~CLayerTele();
CTeleTile *m_pTeleTile; CTeleTile *m_pTeleTile;
@ -21,6 +33,21 @@ public:
void BrushRotate(float Amount) override; void BrushRotate(float Amount) override;
void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override; void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override;
virtual bool ContainsElementWithId(int Id); virtual bool ContainsElementWithId(int Id);
EditorTileStateChangeHistory<STeleTileStateChange> m_History;
inline void ClearHistory() override
{
CLayerTiles::ClearHistory();
m_History.clear();
}
std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
private:
void RecordStateChange(int x, int y, STeleTileStateChange::SData Previous, STeleTileStateChange::SData Current);
friend class CLayerTiles;
}; };
#endif #endif

View file

@ -2,10 +2,13 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#include "layer_tiles.h" #include "layer_tiles.h"
#include <game/editor/editor.h>
#include <engine/keys.h> #include <engine/keys.h>
#include <engine/shared/map.h> #include <engine/shared/map.h>
#include <game/editor/editor.h>
#include <game/editor/editor_actions.h>
#include <iterator>
#include <numeric>
#include "image.h" #include "image.h"
@ -75,10 +78,25 @@ CTile CLayerTiles::GetTile(int x, int y)
} }
void CLayerTiles::SetTile(int x, int y, CTile Tile) void CLayerTiles::SetTile(int x, int y, CTile Tile)
{
auto CurrentTile = m_pTiles[y * m_Width + x];
SetTileIgnoreHistory(x, y, Tile);
RecordStateChange(x, y, CurrentTile, Tile);
}
void CLayerTiles::SetTileIgnoreHistory(int x, int y, CTile Tile)
{ {
m_pTiles[y * m_Width + x] = Tile; m_pTiles[y * m_Width + x] = Tile;
} }
void CLayerTiles::RecordStateChange(int x, int y, CTile Previous, CTile Tile)
{
if(!m_TilesHistory[y][x].m_Changed)
m_TilesHistory[y][x] = STileStateChange{true, Previous, Tile};
else
m_TilesHistory[y][x].m_Current = Tile;
}
void CLayerTiles::PrepareForSave() void CLayerTiles::PrepareForSave()
{ {
for(int y = 0; y < m_Height; y++) for(int y = 0; y < m_Height; y++)
@ -579,6 +597,11 @@ std::shared_ptr<CLayer> CLayerTiles::Duplicate() const
return std::make_shared<CLayerTiles>(*this); return std::make_shared<CLayerTiles>(*this);
} }
const char *CLayerTiles::TypeName() const
{
return "tiles";
}
void CLayerTiles::Resize(int NewW, int NewH) void CLayerTiles::Resize(int NewW, int NewH)
{ {
CTile *pNewData = new CTile[NewW * NewH]; CTile *pNewData = new CTile[NewW * NewH];
@ -678,7 +701,6 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
static int s_GameTilesButton = 0; static int s_GameTilesButton = 0;
if(m_pEditor->DoButton_Editor(&s_GameTilesButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer")) if(m_pEditor->DoButton_Editor(&s_GameTilesButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer"))
m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY()); m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->UI()->MouseX(), m_pEditor->UI()->MouseY());
int Result = m_pEditor->PopupSelectGameTileOpResult(); int Result = m_pEditor->PopupSelectGameTileOpResult();
switch(Result) switch(Result)
{ {
@ -717,17 +739,49 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
const int OffsetX = -pGroup->m_OffsetX / 32; const int OffsetX = -pGroup->m_OffsetX / 32;
const int OffsetY = -pGroup->m_OffsetY / 32; const int OffsetY = -pGroup->m_OffsetY / 32;
static const char *s_apGametileOpNames[] = {
"Air",
"Hookable",
"Death",
"Unhookable",
"Hookthrough",
"Freeze",
"Unfreeze",
"Deep Freeze",
"Deep Unfreeze",
"Blue Check-Tele",
"Red Check-Tele",
"Live Freeze",
"Live Unfreeze",
};
std::vector<std::shared_ptr<IEditorAction>> vpActions;
std::shared_ptr<CLayerTiles> pGLayer = m_pEditor->m_Map.m_pGameLayer;
int GameLayerIndex = std::find(m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(), m_pEditor->m_Map.m_pGameGroup->m_vpLayers.end(), pGLayer) - m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin();
if(Result != TILE_TELECHECKIN && Result != TILE_TELECHECKINEVIL) if(Result != TILE_TELECHECKIN && Result != TILE_TELECHECKINEVIL)
{ {
std::shared_ptr<CLayerTiles> pGLayer = m_pEditor->m_Map.m_pGameLayer;
if(pGLayer->m_Width < m_Width + OffsetX || pGLayer->m_Height < m_Height + OffsetY) if(pGLayer->m_Width < m_Width + OffsetX || pGLayer->m_Height < m_Height + OffsetY)
{ {
std::map<int, std::shared_ptr<CLayer>> savedLayers;
savedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate();
savedLayers[LAYERTYPE_GAME] = savedLayers[LAYERTYPE_TILES];
int PrevW = pGLayer->m_Width;
int PrevH = pGLayer->m_Height;
const int NewW = pGLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pGLayer->m_Width; const int NewW = pGLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pGLayer->m_Width;
const int NewH = pGLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pGLayer->m_Height; const int NewH = pGLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pGLayer->m_Height;
pGLayer->Resize(NewW, NewH); pGLayer->Resize(NewW, NewH);
vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW));
const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action1 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(vpActions[vpActions.size() - 1]);
vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH));
const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action2 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(vpActions[vpActions.size() - 1]);
Action1->SetSavedLayers(savedLayers);
Action2->SetSavedLayers(savedLayers);
} }
int Changes = 0;
for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++) for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++)
{ {
for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++) for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
@ -736,40 +790,111 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
{ {
const CTile ResultTile = {(unsigned char)Result}; const CTile ResultTile = {(unsigned char)Result};
pGLayer->SetTile(x + OffsetX, y + OffsetY, ResultTile); pGLayer->SetTile(x + OffsetX, y + OffsetY, ResultTile);
Changes++;
} }
} }
} }
vpActions.push_back(std::make_shared<CEditorBrushDrawAction>(m_pEditor, m_pEditor->m_SelectedGroup));
char aDisplay[256];
str_format(aDisplay, sizeof(aDisplay), "Construct '%s' game tiles (x%d)", s_apGametileOpNames[Result], Changes);
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay, true));
} }
else else
{ {
if(!m_pEditor->m_Map.m_pTeleLayer) if(!m_pEditor->m_Map.m_pTeleLayer)
{ {
std::shared_ptr<CLayer> pLayer = std::make_shared<CLayerTele>(m_pEditor, m_Width, m_Height); std::shared_ptr<CLayerTele> pLayer = std::make_shared<CLayerTele>(m_pEditor, m_Width, m_Height);
m_pEditor->m_Map.MakeTeleLayer(pLayer); m_pEditor->m_Map.MakeTeleLayer(pLayer);
m_pEditor->m_Map.m_pGameGroup->AddLayer(pLayer); m_pEditor->m_Map.m_pGameGroup->AddLayer(pLayer);
vpActions.push_back(std::make_shared<CEditorActionAddLayer>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_Map.m_pGameGroup->m_vpLayers.size() - 1));
if(m_Width != pGLayer->m_Width || m_Height > pGLayer->m_Height)
{
std::map<int, std::shared_ptr<CLayer>> savedLayers;
savedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate();
savedLayers[LAYERTYPE_GAME] = savedLayers[LAYERTYPE_TILES];
int NewW = pGLayer->m_Width;
int NewH = pGLayer->m_Height;
if(m_Width > pGLayer->m_Width)
{
NewW = m_Width;
}
if(m_Height > pGLayer->m_Height)
{
NewH = m_Height;
}
int PrevW = pGLayer->m_Width;
int PrevH = pGLayer->m_Height;
pLayer->Resize(NewW, NewH);
vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW));
const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action1 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(vpActions[vpActions.size() - 1]);
vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH));
const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action2 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(vpActions[vpActions.size() - 1]);
Action1->SetSavedLayers(savedLayers);
Action2->SetSavedLayers(savedLayers);
}
} }
std::shared_ptr<CLayerTele> pTLayer = m_pEditor->m_Map.m_pTeleLayer; std::shared_ptr<CLayerTele> pTLayer = m_pEditor->m_Map.m_pTeleLayer;
int TeleLayerIndex = std::find(m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin(), m_pEditor->m_Map.m_pGameGroup->m_vpLayers.end(), pTLayer) - m_pEditor->m_Map.m_pGameGroup->m_vpLayers.begin();
if(pTLayer->m_Width < m_Width + OffsetX || pTLayer->m_Height < m_Height + OffsetY) if(pTLayer->m_Width < m_Width + OffsetX || pTLayer->m_Height < m_Height + OffsetY)
{ {
std::map<int, std::shared_ptr<CLayer>> savedLayers;
savedLayers[LAYERTYPE_TILES] = pTLayer->Duplicate();
savedLayers[LAYERTYPE_TELE] = savedLayers[LAYERTYPE_TILES];
int PrevW = pTLayer->m_Width;
int PrevH = pTLayer->m_Height;
int NewW = pTLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pTLayer->m_Width; int NewW = pTLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pTLayer->m_Width;
int NewH = pTLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pTLayer->m_Height; int NewH = pTLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pTLayer->m_Height;
pTLayer->Resize(NewW, NewH); pTLayer->Resize(NewW, NewH);
std::shared_ptr<CEditorActionEditLayerTilesProp> Action1, Action2;
vpActions.push_back(Action1 = std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW));
vpActions.push_back(Action2 = std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH));
Action1->SetSavedLayers(savedLayers);
Action2->SetSavedLayers(savedLayers);
} }
int Changes = 0;
for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++) for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++)
{ {
for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++) for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
{ {
if(GetTile(x, y).m_Index) if(GetTile(x, y).m_Index)
{ {
pTLayer->m_pTiles[(y + OffsetY) * pTLayer->m_Width + x + OffsetX].m_Index = TILE_AIR + Result; auto TileIndex = (y + OffsetY) * pTLayer->m_Width + x + OffsetX;
pTLayer->m_pTeleTile[(y + OffsetY) * pTLayer->m_Width + x + OffsetX].m_Number = 1; Changes++;
pTLayer->m_pTeleTile[(y + OffsetY) * pTLayer->m_Width + x + OffsetX].m_Type = TILE_AIR + Result;
STeleTileStateChange::SData Previous{
pTLayer->m_pTeleTile[TileIndex].m_Number,
pTLayer->m_pTeleTile[TileIndex].m_Type,
pTLayer->m_pTiles[TileIndex].m_Index};
pTLayer->m_pTiles[TileIndex].m_Index = TILE_AIR + Result;
pTLayer->m_pTeleTile[TileIndex].m_Number = 1;
pTLayer->m_pTeleTile[TileIndex].m_Type = TILE_AIR + Result;
STeleTileStateChange::SData Current{
pTLayer->m_pTeleTile[TileIndex].m_Number,
pTLayer->m_pTeleTile[TileIndex].m_Type,
pTLayer->m_pTiles[TileIndex].m_Index};
pTLayer->RecordStateChange(x, y, Previous, Current);
} }
} }
} }
vpActions.push_back(std::make_shared<CEditorBrushDrawAction>(m_pEditor, m_pEditor->m_SelectedGroup));
char aDisplay[256];
str_format(aDisplay, sizeof(aDisplay), "Construct 'tele' game tiles (x%d)", Changes);
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay, true));
} }
} }
} }
@ -790,6 +915,12 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
{ {
m_AutoAutoMap = !m_AutoAutoMap; m_AutoAutoMap = !m_AutoAutoMap;
FlagModified(0, 0, m_Width, m_Height); FlagModified(0, 0, m_Width, m_Height);
if(!m_TilesHistory.empty()) // Sometimes pressing that button causes the automap to run so we should be able to undo that
{
// record undo
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAutoMap>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_TilesHistory));
ClearHistory();
}
} }
} }
@ -797,31 +928,15 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
if(m_pEditor->DoButton_Editor(&s_AutoMapperButton, "Automap", 0, &Button, 0, "Run the automapper")) if(m_pEditor->DoButton_Editor(&s_AutoMapperButton, "Automap", 0, &Button, 0, "Run the automapper"))
{ {
m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.Proceed(this, m_AutoMapperConfig, m_Seed); m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.Proceed(this, m_AutoMapperConfig, m_Seed);
// record undo
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAutoMap>(m_pEditor, m_pEditor->m_SelectedGroup, m_pEditor->m_vSelectedLayers[0], m_TilesHistory));
ClearHistory();
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
} }
enum int Color = PackColor(m_Color);
{
PROP_WIDTH = 0,
PROP_HEIGHT,
PROP_SHIFT,
PROP_SHIFT_BY,
PROP_IMAGE,
PROP_COLOR,
PROP_COLOR_ENV,
PROP_COLOR_ENV_OFFSET,
PROP_AUTOMAPPER,
PROP_SEED,
NUM_PROPS,
};
int Color = 0;
Color |= m_Color.r << 24;
Color |= m_Color.g << 16;
Color |= m_Color.b << 8;
Color |= m_Color.a;
CProperty aProps[] = { CProperty aProps[] = {
{"Width", m_Width, PROPTYPE_INT_SCROLL, 1, 100000}, {"Width", m_Width, PROPTYPE_INT_SCROLL, 1, 100000},
@ -839,21 +954,24 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
if(EntitiesLayer) // remove the image and color properties if this is a game layer if(EntitiesLayer) // remove the image and color properties if this is a game layer
{ {
aProps[PROP_IMAGE].m_pName = nullptr; aProps[(int)ETilesProp::PROP_IMAGE].m_pName = nullptr;
aProps[PROP_COLOR].m_pName = nullptr; aProps[(int)ETilesProp::PROP_COLOR].m_pName = nullptr;
aProps[PROP_AUTOMAPPER].m_pName = nullptr; aProps[(int)ETilesProp::PROP_AUTOMAPPER].m_pName = nullptr;
} }
if(m_Image == -1) if(m_Image == -1)
{ {
aProps[PROP_AUTOMAPPER].m_pName = nullptr; aProps[(int)ETilesProp::PROP_AUTOMAPPER].m_pName = nullptr;
aProps[PROP_SEED].m_pName = nullptr; aProps[(int)ETilesProp::PROP_SEED].m_pName = nullptr;
} }
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)ETilesProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); auto [State, Prop] = m_pEditor->DoPropertiesWithState<ETilesProp>(pToolBox, aProps, s_aIds, &NewVal);
if(Prop == PROP_WIDTH && NewVal > 1) static CLayerTilesPropTracker s_Tracker(m_pEditor);
s_Tracker.Begin(this, Prop, State);
if(Prop == ETilesProp::PROP_WIDTH && NewVal > 1)
{ {
if(NewVal > 1000 && !m_pEditor->m_LargeLayerWasWarned) if(NewVal > 1000 && !m_pEditor->m_LargeLayerWasWarned)
{ {
@ -863,7 +981,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
} }
Resize(NewVal, m_Height); Resize(NewVal, m_Height);
} }
else if(Prop == PROP_HEIGHT && NewVal > 1) else if(Prop == ETilesProp::PROP_HEIGHT && NewVal > 1)
{ {
if(NewVal > 1000 && !m_pEditor->m_LargeLayerWasWarned) if(NewVal > 1000 && !m_pEditor->m_LargeLayerWasWarned)
{ {
@ -873,15 +991,15 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
} }
Resize(m_Width, NewVal); Resize(m_Width, NewVal);
} }
else if(Prop == PROP_SHIFT) else if(Prop == ETilesProp::PROP_SHIFT)
{ {
Shift(NewVal); Shift(NewVal);
} }
else if(Prop == PROP_SHIFT_BY) else if(Prop == ETilesProp::PROP_SHIFT_BY)
{ {
m_pEditor->m_ShiftBy = NewVal; m_pEditor->m_ShiftBy = NewVal;
} }
else if(Prop == PROP_IMAGE) else if(Prop == ETilesProp::PROP_IMAGE)
{ {
m_Image = NewVal; m_Image = NewVal;
if(NewVal == -1) if(NewVal == -1)
@ -901,14 +1019,14 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
} }
} }
} }
else if(Prop == PROP_COLOR) else if(Prop == ETilesProp::PROP_COLOR)
{ {
m_Color.r = (NewVal >> 24) & 0xff; m_Color.r = (NewVal >> 24) & 0xff;
m_Color.g = (NewVal >> 16) & 0xff; m_Color.g = (NewVal >> 16) & 0xff;
m_Color.b = (NewVal >> 8) & 0xff; m_Color.b = (NewVal >> 8) & 0xff;
m_Color.a = NewVal & 0xff; m_Color.a = NewVal & 0xff;
} }
else if(Prop == PROP_COLOR_ENV) else if(Prop == ETilesProp::PROP_COLOR_ENV)
{ {
int Index = clamp(NewVal - 1, -1, (int)m_pEditor->m_Map.m_vpEnvelopes.size() - 1); int Index = clamp(NewVal - 1, -1, (int)m_pEditor->m_Map.m_vpEnvelopes.size() - 1);
const int Step = (Index - m_ColorEnv) % 2; const int Step = (Index - m_ColorEnv) % 2;
@ -924,15 +1042,15 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
} }
} }
} }
else if(Prop == PROP_COLOR_ENV_OFFSET) else if(Prop == ETilesProp::PROP_COLOR_ENV_OFFSET)
{ {
m_ColorEnvOffset = NewVal; m_ColorEnvOffset = NewVal;
} }
else if(Prop == PROP_SEED) else if(Prop == ETilesProp::PROP_SEED)
{ {
m_Seed = NewVal; m_Seed = NewVal;
} }
else if(Prop == PROP_AUTOMAPPER) else if(Prop == ETilesProp::PROP_AUTOMAPPER)
{ {
if(m_Image >= 0 && m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum() > 0 && NewVal >= 0) if(m_Image >= 0 && m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum() > 0 && NewVal >= 0)
m_AutoMapperConfig = NewVal % m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum(); m_AutoMapperConfig = NewVal % m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum();
@ -940,15 +1058,17 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
m_AutoMapperConfig = -1; m_AutoMapperConfig = -1;
} }
if(Prop != -1) if(Prop != ETilesProp::PROP_NONE)
{ {
FlagModified(0, 0, m_Width, m_Height); FlagModified(0, 0, m_Width, m_Height);
} }
s_Tracker.End(Prop, State);
return CUI::POPUP_KEEP_OPEN; return CUI::POPUP_KEEP_OPEN;
} }
CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector<std::shared_ptr<CLayerTiles>> &vpLayers) CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector<std::shared_ptr<CLayerTiles>> &vpLayers, std::vector<int> &vLayerIndices)
{ {
if(State.m_Modified) if(State.m_Modified)
{ {
@ -957,22 +1077,77 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta
static int s_CommitButton = 0; static int s_CommitButton = 0;
if(pEditor->DoButton_Editor(&s_CommitButton, "Commit", 0, &Commit, 0, "Applies the changes")) if(pEditor->DoButton_Editor(&s_CommitButton, "Commit", 0, &Commit, 0, "Applies the changes"))
{ {
bool HasModifiedSize = (State.m_Modified & SCommonPropState::MODIFIED_SIZE) != 0;
bool HasModifiedColor = (State.m_Modified & SCommonPropState::MODIFIED_COLOR) != 0;
std::vector<std::shared_ptr<IEditorAction>> vpActions;
int j = 0;
int GroupIndex = pEditor->m_SelectedGroup;
for(auto &pLayer : vpLayers) for(auto &pLayer : vpLayers)
{ {
if((State.m_Modified & SCommonPropState::MODIFIED_SIZE) != 0) int LayerIndex = vLayerIndices[j++];
if(HasModifiedSize)
{
std::map<int, std::shared_ptr<CLayer>> SavedLayers;
SavedLayers[LAYERTYPE_TILES] = pLayer->Duplicate();
if(pLayer->m_Game || pLayer->m_Front || pLayer->m_Switch || pLayer->m_Speedup || pLayer->m_Tune || pLayer->m_Tele)
{ // Need to save all entities layers when any entity layer
if(pEditor->m_Map.m_pFrontLayer && !pLayer->m_Front)
SavedLayers[LAYERTYPE_FRONT] = pEditor->m_Map.m_pFrontLayer->Duplicate();
if(pEditor->m_Map.m_pTeleLayer && !pLayer->m_Tele)
SavedLayers[LAYERTYPE_TELE] = pEditor->m_Map.m_pTeleLayer->Duplicate();
if(pEditor->m_Map.m_pSwitchLayer && !pLayer->m_Switch)
SavedLayers[LAYERTYPE_SWITCH] = pEditor->m_Map.m_pSwitchLayer->Duplicate();
if(pEditor->m_Map.m_pSpeedupLayer && !pLayer->m_Speedup)
SavedLayers[LAYERTYPE_SPEEDUP] = pEditor->m_Map.m_pSpeedupLayer->Duplicate();
if(pEditor->m_Map.m_pTuneLayer && !pLayer->m_Tune)
SavedLayers[LAYERTYPE_TUNE] = pEditor->m_Map.m_pTuneLayer->Duplicate();
if(!pLayer->m_Game)
SavedLayers[LAYERTYPE_GAME] = pEditor->m_Map.m_pGameLayer->Duplicate();
}
int PrevW = pLayer->m_Width;
int PrevH = pLayer->m_Height;
pLayer->Resize(State.m_Width, State.m_Height); pLayer->Resize(State.m_Width, State.m_Height);
if((State.m_Modified & SCommonPropState::MODIFIED_COLOR) != 0) if(PrevW != State.m_Width)
{ {
std::shared_ptr<CEditorActionEditLayerTilesProp> pAction;
vpActions.push_back(pAction = std::make_shared<CEditorActionEditLayerTilesProp>(pEditor, GroupIndex, LayerIndex, ETilesProp::PROP_WIDTH, PrevW, State.m_Width));
pAction->SetSavedLayers(SavedLayers);
}
if(PrevH != State.m_Height)
{
std::shared_ptr<CEditorActionEditLayerTilesProp> pAction;
vpActions.push_back(pAction = std::make_shared<CEditorActionEditLayerTilesProp>(pEditor, GroupIndex, LayerIndex, ETilesProp::PROP_HEIGHT, PrevH, State.m_Height));
pAction->SetSavedLayers(SavedLayers);
}
}
if(HasModifiedColor)
{
int Color = 0;
Color |= pLayer->m_Color.r << 24;
Color |= pLayer->m_Color.g << 16;
Color |= pLayer->m_Color.b << 8;
Color |= pLayer->m_Color.a;
pLayer->m_Color.r = (State.m_Color >> 24) & 0xff; pLayer->m_Color.r = (State.m_Color >> 24) & 0xff;
pLayer->m_Color.g = (State.m_Color >> 16) & 0xff; pLayer->m_Color.g = (State.m_Color >> 16) & 0xff;
pLayer->m_Color.b = (State.m_Color >> 8) & 0xff; pLayer->m_Color.b = (State.m_Color >> 8) & 0xff;
pLayer->m_Color.a = State.m_Color & 0xff; pLayer->m_Color.a = State.m_Color & 0xff;
vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(pEditor, GroupIndex, LayerIndex, ETilesProp::PROP_COLOR, Color, State.m_Color));
} }
pLayer->FlagModified(0, 0, pLayer->m_Width, pLayer->m_Height); pLayer->FlagModified(0, 0, pLayer->m_Width, pLayer->m_Height);
} }
State.m_Modified = 0; State.m_Modified = 0;
char aDisplay[256];
str_format(aDisplay, sizeof(aDisplay), "Edit %d layers common properties: %s", (int)vpLayers.size(), HasModifiedColor && HasModifiedSize ? "color, size" : (HasModifiedColor ? "color" : "size"));
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(pEditor, vpActions, aDisplay));
} }
} }
else else
@ -999,16 +1174,6 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta
pToolbox->HSplitTop(2.0f, nullptr, pToolbox); pToolbox->HSplitTop(2.0f, nullptr, pToolbox);
} }
enum
{
PROP_WIDTH = 0,
PROP_HEIGHT,
PROP_SHIFT,
PROP_SHIFT_BY,
PROP_COLOR,
NUM_PROPS,
};
CProperty aProps[] = { CProperty aProps[] = {
{"Width", State.m_Width, PROPTYPE_INT_SCROLL, 1, 100000}, {"Width", State.m_Width, PROPTYPE_INT_SCROLL, 1, 100000},
{"Height", State.m_Height, PROPTYPE_INT_SCROLL, 1, 100000}, {"Height", State.m_Height, PROPTYPE_INT_SCROLL, 1, 100000},
@ -1018,11 +1183,17 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta
{nullptr}, {nullptr},
}; };
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)ETilesCommonProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = pEditor->DoProperties(pToolbox, aProps, s_aIds, &NewVal); auto [PropState, Prop] = pEditor->DoPropertiesWithState<ETilesCommonProp>(pToolbox, aProps, s_aIds, &NewVal);
if(Prop == PROP_WIDTH && NewVal > 1) static CLayerTilesCommonPropTracker s_Tracker(pEditor);
s_Tracker.m_vpLayers = vpLayers;
s_Tracker.m_vLayerIndices = vLayerIndices;
s_Tracker.Begin(nullptr, Prop, PropState);
if(Prop == ETilesCommonProp::PROP_WIDTH && NewVal > 1)
{ {
if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned) if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned)
{ {
@ -1032,7 +1203,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta
} }
State.m_Width = NewVal; State.m_Width = NewVal;
} }
else if(Prop == PROP_HEIGHT && NewVal > 1) else if(Prop == ETilesCommonProp::PROP_HEIGHT && NewVal > 1)
{ {
if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned) if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned)
{ {
@ -1042,25 +1213,27 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta
} }
State.m_Height = NewVal; State.m_Height = NewVal;
} }
else if(Prop == PROP_SHIFT) else if(Prop == ETilesCommonProp::PROP_SHIFT)
{ {
for(auto &pLayer : vpLayers) for(auto &pLayer : vpLayers)
pLayer->Shift(NewVal); pLayer->Shift(NewVal);
} }
else if(Prop == PROP_SHIFT_BY) else if(Prop == ETilesCommonProp::PROP_SHIFT_BY)
{ {
pEditor->m_ShiftBy = NewVal; pEditor->m_ShiftBy = NewVal;
} }
else if(Prop == PROP_COLOR) else if(Prop == ETilesCommonProp::PROP_COLOR)
{ {
State.m_Color = NewVal; State.m_Color = NewVal;
} }
if(Prop == PROP_WIDTH || Prop == PROP_HEIGHT) s_Tracker.End(Prop, PropState);
if(Prop == ETilesCommonProp::PROP_WIDTH || Prop == ETilesCommonProp::PROP_HEIGHT)
{ {
State.m_Modified |= SCommonPropState::MODIFIED_SIZE; State.m_Modified |= SCommonPropState::MODIFIED_SIZE;
} }
else if(Prop == PROP_COLOR) else if(Prop == ETilesCommonProp::PROP_COLOR)
{ {
State.m_Modified |= SCommonPropState::MODIFIED_COLOR; State.m_Modified |= SCommonPropState::MODIFIED_COLOR;
} }

View file

@ -1,8 +1,21 @@
#ifndef GAME_EDITOR_MAPITEMS_LAYER_TILES_H #ifndef GAME_EDITOR_MAPITEMS_LAYER_TILES_H
#define GAME_EDITOR_MAPITEMS_LAYER_TILES_H #define GAME_EDITOR_MAPITEMS_LAYER_TILES_H
#include <game/editor/editor_trackers.h>
#include <map>
#include "layer.h" #include "layer.h"
struct STileStateChange
{
bool m_Changed;
CTile m_Previous;
CTile m_Current;
};
template<typename T>
using EditorTileStateChangeHistory = std::map<int, std::map<int, T>>;
enum enum
{ {
DIRECTION_LEFT = 0, DIRECTION_LEFT = 0,
@ -89,6 +102,7 @@ public:
virtual CTile GetTile(int x, int y); virtual CTile GetTile(int x, int y);
virtual void SetTile(int x, int y, CTile Tile); virtual void SetTile(int x, int y, CTile Tile);
void SetTileIgnoreHistory(int x, int y, CTile Tile);
virtual void Resize(int NewW, int NewH); virtual void Resize(int NewW, int NewH);
virtual void Shift(int Direction); virtual void Shift(int Direction);
@ -114,6 +128,7 @@ public:
void BrushRotate(float Amount) override; void BrushRotate(float Amount) override;
std::shared_ptr<CLayer> Duplicate() const override; std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
virtual void ShowInfo(); virtual void ShowInfo();
CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override; CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override;
@ -130,7 +145,7 @@ public:
int m_Height = -1; int m_Height = -1;
int m_Color = 0; int m_Color = 0;
}; };
static CUI::EPopupMenuFunctionResult RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector<std::shared_ptr<CLayerTiles>> &vpLayers); static CUI::EPopupMenuFunctionResult RenderCommonProperties(SCommonPropState &State, CEditor *pEditor, CUIRect *pToolbox, std::vector<std::shared_ptr<CLayerTiles>> &vpLayers, std::vector<int> &vLayerIndices);
void ModifyImageIndex(FIndexModifyFunction pfnFunc) override; void ModifyImageIndex(FIndexModifyFunction pfnFunc) override;
void ModifyEnvelopeIndex(FIndexModifyFunction pfnFunc) override; void ModifyEnvelopeIndex(FIndexModifyFunction pfnFunc) override;
@ -166,6 +181,14 @@ public:
int m_Switch; int m_Switch;
int m_Tune; int m_Tune;
char m_aFileName[IO_MAX_PATH_LENGTH]; char m_aFileName[IO_MAX_PATH_LENGTH];
EditorTileStateChangeHistory<STileStateChange> m_TilesHistory;
inline virtual void ClearHistory() { m_TilesHistory.clear(); }
protected:
void RecordStateChange(int x, int y, CTile Previous, CTile Tile);
friend class CAutoMapper;
}; };
#endif #endif

View file

@ -12,6 +12,16 @@ CLayerTune::CLayerTune(CEditor *pEditor, int w, int h) :
mem_zero(m_pTuneTile, (size_t)w * h * sizeof(CTuneTile)); mem_zero(m_pTuneTile, (size_t)w * h * sizeof(CTuneTile));
} }
CLayerTune::CLayerTune(const CLayerTune &Other) :
CLayerTiles(Other)
{
str_copy(m_aName, "Tune copy");
m_Tune = 1;
m_pTuneTile = new CTuneTile[m_Width * m_Height];
mem_copy(m_pTuneTile, Other.m_pTuneTile, (size_t)m_Width * m_Height * sizeof(CTuneTile));
}
CLayerTune::~CLayerTune() CLayerTune::~CLayerTune()
{ {
delete[] m_pTuneTile; delete[] m_pTuneTile;
@ -82,40 +92,61 @@ void CLayerTune::BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy)
if(!Destructive && GetTile(fx, fy).m_Index) if(!Destructive && GetTile(fx, fy).m_Index)
continue; continue;
int Index = fy * m_Width + fx;
STuneTileStateChange::SData Previous{
m_pTuneTile[Index].m_Number,
m_pTuneTile[Index].m_Type,
m_pTiles[Index].m_Index};
if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index)) && pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index != TILE_AIR) if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index)) && pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index != TILE_AIR)
{ {
if(m_pEditor->m_TuningNum != pTuneLayer->m_TuningNumber) if(m_pEditor->m_TuningNum != pTuneLayer->m_TuningNumber)
{ {
m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; m_pTuneTile[Index].m_Number = m_pEditor->m_TuningNum;
} }
else if(pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number) else if(pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number)
m_pTuneTile[fy * m_Width + fx].m_Number = pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number; m_pTuneTile[Index].m_Number = pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number;
else else
{ {
if(!m_pEditor->m_TuningNum) if(!m_pEditor->m_TuningNum)
{ {
m_pTuneTile[fy * m_Width + fx].m_Number = 0; m_pTuneTile[Index].m_Number = 0;
m_pTuneTile[fy * m_Width + fx].m_Type = 0; m_pTuneTile[Index].m_Type = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
continue; continue;
} }
else else
m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; m_pTuneTile[Index].m_Number = m_pEditor->m_TuningNum;
} }
m_pTuneTile[fy * m_Width + fx].m_Type = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; m_pTuneTile[Index].m_Type = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index;
m_pTiles[fy * m_Width + fx].m_Index = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; m_pTiles[Index].m_Index = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index;
} }
else else
{ {
m_pTuneTile[fy * m_Width + fx].m_Number = 0; m_pTuneTile[Index].m_Number = 0;
m_pTuneTile[fy * m_Width + fx].m_Type = 0; m_pTuneTile[Index].m_Type = 0;
m_pTiles[fy * m_Width + fx].m_Index = 0; m_pTiles[Index].m_Index = 0;
} }
STuneTileStateChange::SData Current{
m_pTuneTile[Index].m_Number,
m_pTuneTile[Index].m_Type,
m_pTiles[Index].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
FlagModified(sx, sy, pTuneLayer->m_Width, pTuneLayer->m_Height); FlagModified(sx, sy, pTuneLayer->m_Width, pTuneLayer->m_Height);
} }
void CLayerTune::RecordStateChange(int x, int y, STuneTileStateChange::SData Previous, STuneTileStateChange::SData Current)
{
if(!m_History[y][x].m_Changed)
m_History[y][x] = STuneTileStateChange{true, Previous, Current};
else
m_History[y][x].m_Current = Current;
}
void CLayerTune::BrushFlipX() void CLayerTune::BrushFlipX()
{ {
CLayerTiles::BrushFlipX(); CLayerTiles::BrushFlipX();
@ -194,6 +225,11 @@ void CLayerTune::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRe
const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height);
const int TgtIndex = fy * m_Width + fx; const int TgtIndex = fy * m_Width + fx;
STuneTileStateChange::SData Previous{
m_pTuneTile[TgtIndex].m_Number,
m_pTuneTile[TgtIndex].m_Type,
m_pTiles[TgtIndex].m_Index};
if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTuneTile((pLt->m_pTiles[SrcIndex]).m_Index))) if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTuneTile((pLt->m_pTiles[SrcIndex]).m_Index)))
{ {
m_pTiles[TgtIndex].m_Index = 0; m_pTiles[TgtIndex].m_Index = 0;
@ -213,8 +249,25 @@ void CLayerTune::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRe
m_pTuneTile[TgtIndex].m_Number = pLt->m_pTuneTile[SrcIndex].m_Number; m_pTuneTile[TgtIndex].m_Number = pLt->m_pTuneTile[SrcIndex].m_Number;
} }
} }
STuneTileStateChange::SData Current{
m_pTuneTile[TgtIndex].m_Number,
m_pTuneTile[TgtIndex].m_Type,
m_pTiles[TgtIndex].m_Index};
RecordStateChange(fx, fy, Previous, Current);
} }
} }
FlagModified(sx, sy, w, h); FlagModified(sx, sy, w, h);
} }
std::shared_ptr<CLayer> CLayerTune::Duplicate() const
{
return std::make_shared<CLayerTune>(*this);
}
const char *CLayerTune::TypeName() const
{
return "tune";
}

View file

@ -3,10 +3,22 @@
#include "layer_tiles.h" #include "layer_tiles.h"
struct STuneTileStateChange
{
bool m_Changed;
struct SData
{
int m_Number;
int m_Type;
int m_Index;
} m_Previous, m_Current;
};
class CLayerTune : public CLayerTiles class CLayerTune : public CLayerTiles
{ {
public: public:
CLayerTune(CEditor *pEditor, int w, int h); CLayerTune(CEditor *pEditor, int w, int h);
CLayerTune(const CLayerTune &Other);
~CLayerTune(); ~CLayerTune();
CTuneTile *m_pTuneTile; CTuneTile *m_pTuneTile;
@ -20,6 +32,19 @@ public:
void BrushFlipY() override; void BrushFlipY() override;
void BrushRotate(float Amount) override; void BrushRotate(float Amount) override;
void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override; void FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) override;
EditorTileStateChangeHistory<STuneTileStateChange> m_History;
inline void ClearHistory() override
{
CLayerTiles::ClearHistory();
m_History.clear();
}
std::shared_ptr<CLayer> Duplicate() const override;
const char *TypeName() const override;
private:
void RecordStateChange(int x, int y, STuneTileStateChange::SData Previous, STuneTileStateChange::SData Current);
}; };
#endif #endif

View file

@ -16,6 +16,7 @@
#include <game/editor/mapitems/sound.h> #include <game/editor/mapitems/sound.h>
#include "editor.h" #include "editor.h"
#include "editor_actions.h"
CUI::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect View, bool Active) CUI::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect View, bool Active)
{ {
@ -344,6 +345,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
{ {
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete group", 0, &Button, 0, "Delete group")) if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete group", 0, &Button, 0, "Delete group"))
{ {
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(pEditor, pEditor->m_SelectedGroup, true));
pEditor->m_Map.DeleteGroup(pEditor->m_SelectedGroup); pEditor->m_Map.DeleteGroup(pEditor->m_SelectedGroup);
pEditor->m_SelectedGroup = maximum(0, pEditor->m_SelectedGroup - 1); pEditor->m_SelectedGroup = maximum(0, pEditor->m_SelectedGroup - 1);
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
@ -403,8 +405,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
std::shared_ptr<CLayer> pTeleLayer = std::make_shared<CLayerTele>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pTeleLayer = std::make_shared<CLayerTele>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeTeleLayer(pTeleLayer); pEditor->m_Map.MakeTeleLayer(pTeleLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTeleLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTeleLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_pBrush->Clear(); pEditor->m_pBrush->Clear();
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -420,8 +424,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
std::shared_ptr<CLayer> pSpeedupLayer = std::make_shared<CLayerSpeedup>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pSpeedupLayer = std::make_shared<CLayerSpeedup>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeSpeedupLayer(pSpeedupLayer); pEditor->m_Map.MakeSpeedupLayer(pSpeedupLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSpeedupLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSpeedupLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_pBrush->Clear(); pEditor->m_pBrush->Clear();
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -437,8 +443,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
std::shared_ptr<CLayer> pTuneLayer = std::make_shared<CLayerTune>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pTuneLayer = std::make_shared<CLayerTune>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeTuneLayer(pTuneLayer); pEditor->m_Map.MakeTuneLayer(pTuneLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTuneLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTuneLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_pBrush->Clear(); pEditor->m_pBrush->Clear();
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -454,8 +462,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
std::shared_ptr<CLayer> pFrontLayer = std::make_shared<CLayerFront>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pFrontLayer = std::make_shared<CLayerFront>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeFrontLayer(pFrontLayer); pEditor->m_Map.MakeFrontLayer(pFrontLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pFrontLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pFrontLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_pBrush->Clear(); pEditor->m_pBrush->Clear();
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -471,8 +481,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
std::shared_ptr<CLayer> pSwitchLayer = std::make_shared<CLayerSwitch>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pSwitchLayer = std::make_shared<CLayerSwitch>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pEditor->m_Map.MakeSwitchLayer(pSwitchLayer); pEditor->m_Map.MakeSwitchLayer(pSwitchLayer);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSwitchLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSwitchLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_pBrush->Clear(); pEditor->m_pBrush->Clear();
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -485,8 +497,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
{ {
std::shared_ptr<CLayer> pQuadLayer = std::make_shared<CLayerQuads>(pEditor); std::shared_ptr<CLayer> pQuadLayer = std::make_shared<CLayerQuads>(pEditor);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pQuadLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pQuadLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -499,8 +513,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height);
pTileLayer->m_pEditor = pEditor; pTileLayer->m_pEditor = pEditor;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTileLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTileLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -512,8 +528,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
{ {
std::shared_ptr<CLayer> pSoundLayer = std::make_shared<CLayerSounds>(pEditor); std::shared_ptr<CLayer> pSoundLayer = std::make_shared<CLayerSounds>(pEditor);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSoundLayer); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pSoundLayer);
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1); int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
pEditor->SelectLayer(LayerIndex);
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -530,21 +548,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
enum
{
PROP_ORDER = 0,
PROP_POS_X,
PROP_POS_Y,
PROP_PARA_X,
PROP_PARA_Y,
PROP_USE_CLIPPING,
PROP_CLIP_X,
PROP_CLIP_Y,
PROP_CLIP_W,
PROP_CLIP_H,
NUM_PROPS,
};
CProperty aProps[] = { CProperty aProps[] = {
{"Order", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1}, {"Order", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1},
{"Pos X", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {"Pos X", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX, PROPTYPE_INT_SCROLL, -1000000, 1000000},
@ -561,17 +564,20 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
// cut the properties that aren't needed // cut the properties that aren't needed
if(pEditor->GetSelectedGroup()->m_GameGroup) if(pEditor->GetSelectedGroup()->m_GameGroup)
aProps[PROP_POS_X].m_pName = nullptr; aProps[(int)EGroupProp::PROP_POS_X].m_pName = nullptr;
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)EGroupProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); auto [State, Prop] = pEditor->DoPropertiesWithState<EGroupProp>(&View, aProps, s_aIds, &NewVal);
if(Prop != -1) if(Prop != EGroupProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
if(Prop == PROP_ORDER) static CLayerGroupPropTracker s_Tracker(pEditor);
s_Tracker.Begin(pEditor->GetSelectedGroup().get(), Prop, State);
if(Prop == EGroupProp::PROP_ORDER)
{ {
pEditor->m_SelectedGroup = pEditor->m_Map.SwapGroups(pEditor->m_SelectedGroup, NewVal); pEditor->m_SelectedGroup = pEditor->m_Map.SwapGroups(pEditor->m_SelectedGroup, NewVal);
} }
@ -579,44 +585,46 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
// these can not be changed on the game group // these can not be changed on the game group
if(!pEditor->GetSelectedGroup()->m_GameGroup) if(!pEditor->GetSelectedGroup()->m_GameGroup)
{ {
if(Prop == PROP_PARA_X) if(Prop == EGroupProp::PROP_PARA_X)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal;
} }
else if(Prop == PROP_PARA_Y) else if(Prop == EGroupProp::PROP_PARA_Y)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal;
} }
else if(Prop == PROP_POS_X) else if(Prop == EGroupProp::PROP_POS_X)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = NewVal;
} }
else if(Prop == PROP_POS_Y) else if(Prop == EGroupProp::PROP_POS_Y)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = NewVal;
} }
else if(Prop == PROP_USE_CLIPPING) else if(Prop == EGroupProp::PROP_USE_CLIPPING)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal;
} }
else if(Prop == PROP_CLIP_X) else if(Prop == EGroupProp::PROP_CLIP_X)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal;
} }
else if(Prop == PROP_CLIP_Y) else if(Prop == EGroupProp::PROP_CLIP_Y)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal;
} }
else if(Prop == PROP_CLIP_W) else if(Prop == EGroupProp::PROP_CLIP_W)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal;
} }
else if(Prop == PROP_CLIP_H) else if(Prop == EGroupProp::PROP_CLIP_H)
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal; pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal;
} }
} }
s_Tracker.End(Prop, State);
return CUI::POPUP_KEEP_OPEN; return CUI::POPUP_KEEP_OPEN;
} }
@ -630,7 +638,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
if(pPopup->m_vpLayers.size() > 1) if(pPopup->m_vpLayers.size() > 1)
{ {
return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers); return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers, pPopup->m_vLayerIndices);
} }
const bool EntitiesLayer = pCurrentLayer->IsEntitiesLayer(); const bool EntitiesLayer = pCurrentLayer->IsEntitiesLayer();
@ -643,6 +651,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
static int s_DeleteButton = 0; static int s_DeleteButton = 0;
if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &DeleteButton, 0, "Deletes the layer")) if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &DeleteButton, 0, "Deletes the layer"))
{ {
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionDeleteLayer>(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0]));
if(pCurrentLayer == pEditor->m_Map.m_pFrontLayer) if(pCurrentLayer == pEditor->m_Map.m_pFrontLayer)
pEditor->m_Map.m_pFrontLayer = nullptr; pEditor->m_Map.m_pFrontLayer = nullptr;
if(pCurrentLayer == pEditor->m_Map.m_pTeleLayer) if(pCurrentLayer == pEditor->m_Map.m_pTeleLayer)
@ -654,6 +664,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
if(pCurrentLayer == pEditor->m_Map.m_pTuneLayer) if(pCurrentLayer == pEditor->m_Map.m_pTuneLayer)
pEditor->m_Map.m_pTuneLayer = nullptr; pEditor->m_Map.m_pTuneLayer = nullptr;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DeleteLayer(pEditor->m_vSelectedLayers[0]); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DeleteLayer(pEditor->m_vSelectedLayers[0]);
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -668,6 +679,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
if(pEditor->DoButton_Editor(&s_DuplicationButton, "Duplicate layer", 0, &DuplicateButton, 0, "Duplicates the layer")) if(pEditor->DoButton_Editor(&s_DuplicationButton, "Duplicate layer", 0, &DuplicateButton, 0, "Duplicates the layer"))
{ {
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(pEditor->m_vSelectedLayers[0]); pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(pEditor->m_vSelectedLayers[0]);
pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0] + 1, true));
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -690,14 +702,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
if(!EntitiesLayer || pEditor->m_Map.m_pGameLayer != pCurrentLayer) if(!EntitiesLayer || pEditor->m_Map.m_pGameLayer != pCurrentLayer)
View.HSplitBottom(10.0f, &View, nullptr); View.HSplitBottom(10.0f, &View, nullptr);
enum
{
PROP_GROUP = 0,
PROP_ORDER,
PROP_HQ,
NUM_PROPS,
};
CProperty aProps[] = { CProperty aProps[] = {
{"Group", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1}, {"Group", pEditor->m_SelectedGroup, PROPTYPE_INT_STEP, 0, (int)pEditor->m_Map.m_vpGroups.size() - 1},
{"Order", pEditor->m_vSelectedLayers[0], PROPTYPE_INT_STEP, 0, (int)pCurrentGroup->m_vpLayers.size() - 1}, {"Order", pEditor->m_vSelectedLayers[0], PROPTYPE_INT_STEP, 0, (int)pCurrentGroup->m_vpLayers.size() - 1},
@ -718,19 +722,23 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
aProps[2].m_Type = PROPTYPE_NULL; aProps[2].m_Type = PROPTYPE_NULL;
} }
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)ELayerProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); auto [State, Prop] = pEditor->DoPropertiesWithState<ELayerProp>(&View, aProps, s_aIds, &NewVal);
if(Prop != -1) if(Prop != ELayerProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
if(Prop == PROP_ORDER) static CLayerPropTracker s_Tracker(pEditor);
s_Tracker.Begin(pCurrentLayer.get(), Prop, State);
if(Prop == ELayerProp::PROP_ORDER)
{ {
pEditor->SelectLayer(pCurrentGroup->SwapLayers(pEditor->m_vSelectedLayers[0], NewVal)); int NewIndex = pCurrentGroup->SwapLayers(pEditor->m_vSelectedLayers[0], NewVal);
pEditor->SelectLayer(NewIndex);
} }
else if(Prop == PROP_GROUP) else if(Prop == ELayerProp::PROP_GROUP)
{ {
if(NewVal >= 0 && (size_t)NewVal < pEditor->m_Map.m_vpGroups.size()) if(NewVal >= 0 && (size_t)NewVal < pEditor->m_Map.m_vpGroups.size())
{ {
@ -742,13 +750,15 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View,
pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.size() - 1); pEditor->SelectLayer(pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.size() - 1);
} }
} }
else if(Prop == PROP_HQ) else if(Prop == ELayerProp::PROP_HQ)
{ {
pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL; pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL;
if(NewVal) if(NewVal)
pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL; pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL;
} }
s_Tracker.End(Prop, State);
return pCurrentLayer->RenderProperties(&View); return pCurrentLayer->RenderProperties(&View);
} }
@ -782,6 +792,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
static int s_AspectRatioButton = 0; static int s_AspectRatioButton = 0;
if(pEditor->DoButton_Editor(&s_AspectRatioButton, "Aspect ratio", 0, &Button, 0, "Resizes the current Quad based on the aspect ratio of the image")) if(pEditor->DoButton_Editor(&s_AspectRatioButton, "Aspect ratio", 0, &Button, 0, "Resizes the current Quad based on the aspect ratio of the image"))
{ {
pEditor->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads);
for(auto &pQuad : vpQuads) for(auto &pQuad : vpQuads)
{ {
int Top = pQuad->m_aPoints[0].y; int Top = pQuad->m_aPoints[0].y;
@ -810,6 +821,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
pQuad->m_aPoints[3].y = Top + Height; pQuad->m_aPoints[3].y = Top + Height;
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
pEditor->m_QuadTracker.EndQuadTrack();
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
} }
@ -820,6 +833,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
static int s_AlignButton = 0; static int s_AlignButton = 0;
if(pEditor->DoButton_Editor(&s_AlignButton, "Align", 0, &Button, 0, "Aligns coordinates of the quad points")) if(pEditor->DoButton_Editor(&s_AlignButton, "Align", 0, &Button, 0, "Aligns coordinates of the quad points"))
{ {
pEditor->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads);
for(auto &pQuad : vpQuads) for(auto &pQuad : vpQuads)
{ {
for(int k = 1; k < 4; k++) for(int k = 1; k < 4; k++)
@ -829,6 +843,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
} }
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
pEditor->m_QuadTracker.EndQuadTrack();
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -838,6 +853,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
static int s_Button = 0; static int s_Button = 0;
if(pEditor->DoButton_Editor(&s_Button, "Square", 0, &Button, 0, "Squares the current quad")) if(pEditor->DoButton_Editor(&s_Button, "Square", 0, &Button, 0, "Squares the current quad"))
{ {
pEditor->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads);
for(auto &pQuad : vpQuads) for(auto &pQuad : vpQuads)
{ {
int Top = pQuad->m_aPoints[0].y; int Top = pQuad->m_aPoints[0].y;
@ -867,6 +883,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
pQuad->m_aPoints[3].y = Bottom; pQuad->m_aPoints[3].y = Bottom;
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
pEditor->m_QuadTracker.EndQuadTrack();
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -881,18 +898,6 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
enum
{
PROP_ORDER = 0,
PROP_POS_X,
PROP_POS_Y,
PROP_POS_ENV,
PROP_POS_ENV_OFFSET,
PROP_COLOR_ENV,
PROP_COLOR_ENV_OFFSET,
NUM_PROPS,
};
const int NumQuads = pLayer ? (int)pLayer->m_vQuads.size() : 0; const int NumQuads = pLayer ? (int)pLayer->m_vQuads.size() : 0;
CProperty aProps[] = { CProperty aProps[] = {
{"Order", pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], PROPTYPE_INT_STEP, 0, NumQuads}, {"Order", pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], PROPTYPE_INT_STEP, 0, NumQuads},
@ -905,18 +910,23 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
{nullptr}, {nullptr},
}; };
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)EQuadProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); auto PropRes = pEditor->DoPropertiesWithState<EQuadProp>(&View, aProps, s_aIds, &NewVal);
if(Prop != -1) EQuadProp Prop = PropRes.m_Value;
if(Prop != EQuadProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
if(PropRes.m_State == EEditState::START || PropRes.m_State == EEditState::ONE_GO)
{
pEditor->m_QuadTracker.BeginQuadPropTrack(pLayer, pEditor->m_vSelectedQuads, Prop);
}
} }
const float OffsetX = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].x; const float OffsetX = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].x;
const float OffsetY = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].y; const float OffsetY = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].y;
if(Prop == PROP_ORDER && pLayer) if(Prop == EQuadProp::PROP_ORDER && pLayer)
{ {
const int QuadIndex = pLayer->SwapQuads(pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], NewVal); const int QuadIndex = pLayer->SwapQuads(pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], NewVal);
pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex] = QuadIndex; pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex] = QuadIndex;
@ -924,17 +934,17 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
for(auto &pQuad : vpQuads) for(auto &pQuad : vpQuads)
{ {
if(Prop == PROP_POS_X) if(Prop == EQuadProp::PROP_POS_X)
{ {
for(auto &Point : pQuad->m_aPoints) for(auto &Point : pQuad->m_aPoints)
Point.x += OffsetX; Point.x += OffsetX;
} }
else if(Prop == PROP_POS_Y) else if(Prop == EQuadProp::PROP_POS_Y)
{ {
for(auto &Point : pQuad->m_aPoints) for(auto &Point : pQuad->m_aPoints)
Point.y += OffsetY; Point.y += OffsetY;
} }
else if(Prop == PROP_POS_ENV) else if(Prop == EQuadProp::PROP_POS_ENV)
{ {
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1; int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1;
@ -950,11 +960,11 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
} }
} }
} }
else if(Prop == PROP_POS_ENV_OFFSET) else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET)
{ {
pQuad->m_PosEnvOffset = NewVal; pQuad->m_PosEnvOffset = NewVal;
} }
else if(Prop == PROP_COLOR_ENV) else if(Prop == EQuadProp::PROP_COLOR_ENV)
{ {
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1; int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1;
@ -970,12 +980,20 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
} }
} }
} }
else if(Prop == PROP_COLOR_ENV_OFFSET) else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET)
{ {
pQuad->m_ColorEnvOffset = NewVal; pQuad->m_ColorEnvOffset = NewVal;
} }
} }
if(Prop != EQuadProp::PROP_NONE)
{
if(PropRes.m_State == EEditState::END || PropRes.m_State == EEditState::ONE_GO)
{
pEditor->m_QuadTracker.EndQuadPropTrack(Prop);
}
}
return CUI::POPUP_KEEP_OPEN; return CUI::POPUP_KEEP_OPEN;
} }
@ -983,6 +1001,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
{ {
CEditor *pEditor = static_cast<CEditor *>(pContext); CEditor *pEditor = static_cast<CEditor *>(pContext);
CSoundSource *pSource = pEditor->GetSelectedSource(); CSoundSource *pSource = pEditor->GetSelectedSource();
if(!pSource)
return CUI::POPUP_CLOSE_CURRENT;
CUIRect Button; CUIRect Button;
@ -994,9 +1014,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
std::shared_ptr<CLayerSounds> pLayer = std::static_pointer_cast<CLayerSounds>(pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS)); std::shared_ptr<CLayerSounds> pLayer = std::static_pointer_cast<CLayerSounds>(pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS));
if(pLayer) if(pLayer)
{ {
pEditor->m_Map.OnModify(); pEditor->m_EditorHistory.Execute(std::make_shared<CEditorActionDeleteSoundSource>(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0], pEditor->m_SelectedSource));
pLayer->m_vSources.erase(pLayer->m_vSources.begin() + pEditor->m_SelectedSource);
pEditor->m_SelectedSource--;
} }
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -1015,39 +1033,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
static int s_ShapeTypeButton = 0; static int s_ShapeTypeButton = 0;
if(pEditor->DoButton_Editor(&s_ShapeTypeButton, s_apShapeNames[pSource->m_Shape.m_Type], 0, &ShapeButton, 0, "Change shape")) if(pEditor->DoButton_Editor(&s_ShapeTypeButton, s_apShapeNames[pSource->m_Shape.m_Type], 0, &ShapeButton, 0, "Change shape"))
{ {
pSource->m_Shape.m_Type = (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES; pEditor->m_EditorHistory.Execute(std::make_shared<CEditorActionEditSoundSource>(pEditor, pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0], pEditor->m_SelectedSource, CEditorActionEditSoundSource::EEditType::SHAPE, (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES));
// set default values
switch(pSource->m_Shape.m_Type)
{
case CSoundShape::SHAPE_CIRCLE:
{
pSource->m_Shape.m_Circle.m_Radius = 1000.0f;
break;
} }
case CSoundShape::SHAPE_RECTANGLE:
{
pSource->m_Shape.m_Rectangle.m_Width = f2fx(1000.0f);
pSource->m_Shape.m_Rectangle.m_Height = f2fx(800.0f);
break;
}
}
}
enum
{
PROP_POS_X = 0,
PROP_POS_Y,
PROP_LOOP,
PROP_PAN,
PROP_TIME_DELAY,
PROP_FALLOFF,
PROP_POS_ENV,
PROP_POS_ENV_OFFSET,
PROP_SOUND_ENV,
PROP_SOUND_ENV_OFFSET,
NUM_PROPS,
};
CProperty aProps[] = { CProperty aProps[] = {
{"Pos X", pSource->m_Position.x / 1000, PROPTYPE_INT_SCROLL, -1000000, 1000000}, {"Pos X", pSource->m_Position.x / 1000, PROPTYPE_INT_SCROLL, -1000000, 1000000},
@ -1063,39 +1050,42 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
{nullptr}, {nullptr},
}; };
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)ESoundProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); auto [State, Prop] = pEditor->DoPropertiesWithState<ESoundProp>(&View, aProps, s_aIds, &NewVal);
if(Prop != -1) if(Prop != ESoundProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
if(Prop == PROP_POS_X) static CSoundSourcePropTracker s_Tracker(pEditor);
s_Tracker.Begin(pSource, Prop, State);
if(Prop == ESoundProp::PROP_POS_X)
{ {
pSource->m_Position.x = NewVal * 1000; pSource->m_Position.x = NewVal * 1000;
} }
else if(Prop == PROP_POS_Y) else if(Prop == ESoundProp::PROP_POS_Y)
{ {
pSource->m_Position.y = NewVal * 1000; pSource->m_Position.y = NewVal * 1000;
} }
else if(Prop == PROP_LOOP) else if(Prop == ESoundProp::PROP_LOOP)
{ {
pSource->m_Loop = NewVal; pSource->m_Loop = NewVal;
} }
else if(Prop == PROP_PAN) else if(Prop == ESoundProp::PROP_PAN)
{ {
pSource->m_Pan = NewVal; pSource->m_Pan = NewVal;
} }
else if(Prop == PROP_TIME_DELAY) else if(Prop == ESoundProp::PROP_TIME_DELAY)
{ {
pSource->m_TimeDelay = NewVal; pSource->m_TimeDelay = NewVal;
} }
else if(Prop == PROP_FALLOFF) else if(Prop == ESoundProp::PROP_FALLOFF)
{ {
pSource->m_Falloff = NewVal; pSource->m_Falloff = NewVal;
} }
else if(Prop == PROP_POS_ENV) else if(Prop == ESoundProp::PROP_POS_ENV)
{ {
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1; const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1;
@ -1108,11 +1098,11 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
} }
} }
} }
else if(Prop == PROP_POS_ENV_OFFSET) else if(Prop == ESoundProp::PROP_POS_ENV_OFFSET)
{ {
pSource->m_PosEnvOffset = NewVal; pSource->m_PosEnvOffset = NewVal;
} }
else if(Prop == PROP_SOUND_ENV) else if(Prop == ESoundProp::PROP_SOUND_ENV)
{ {
int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1); int Index = clamp(NewVal - 1, -1, (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1; const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1;
@ -1125,75 +1115,72 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
} }
} }
} }
else if(Prop == PROP_SOUND_ENV_OFFSET) else if(Prop == ESoundProp::PROP_SOUND_ENV_OFFSET)
{ {
pSource->m_SoundEnvOffset = NewVal; pSource->m_SoundEnvOffset = NewVal;
} }
s_Tracker.End(Prop, State);
// source shape properties // source shape properties
switch(pSource->m_Shape.m_Type) switch(pSource->m_Shape.m_Type)
{ {
case CSoundShape::SHAPE_CIRCLE: case CSoundShape::SHAPE_CIRCLE:
{ {
enum
{
PROP_CIRCLE_RADIUS = 0,
NUM_CIRCLE_PROPS,
};
CProperty aCircleProps[] = { CProperty aCircleProps[] = {
{"Radius", pSource->m_Shape.m_Circle.m_Radius, PROPTYPE_INT_SCROLL, 0, 1000000}, {"Radius", pSource->m_Shape.m_Circle.m_Radius, PROPTYPE_INT_SCROLL, 0, 1000000},
{nullptr}, {nullptr},
}; };
static int s_aCircleIds[NUM_CIRCLE_PROPS] = {0}; static int s_aCircleIds[(int)ECircleShapeProp::NUM_CIRCLE_PROPS] = {0};
NewVal = 0; NewVal = 0;
Prop = pEditor->DoProperties(&View, aCircleProps, s_aCircleIds, &NewVal); auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ECircleShapeProp>(&View, aCircleProps, s_aCircleIds, &NewVal);
if(Prop != -1) if(LocalProp != ECircleShapeProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
if(Prop == PROP_CIRCLE_RADIUS) static CSoundSourceCircleShapePropTracker s_ShapeTracker(pEditor);
s_ShapeTracker.Begin(pSource, LocalProp, LocalState);
if(LocalProp == ECircleShapeProp::PROP_CIRCLE_RADIUS)
{ {
pSource->m_Shape.m_Circle.m_Radius = NewVal; pSource->m_Shape.m_Circle.m_Radius = NewVal;
} }
s_ShapeTracker.End(LocalProp, LocalState);
break; break;
} }
case CSoundShape::SHAPE_RECTANGLE: case CSoundShape::SHAPE_RECTANGLE:
{ {
enum
{
PROP_RECTANGLE_WIDTH = 0,
PROP_RECTANGLE_HEIGHT,
NUM_RECTANGLE_PROPS,
};
CProperty aRectangleProps[] = { CProperty aRectangleProps[] = {
{"Width", pSource->m_Shape.m_Rectangle.m_Width / 1024, PROPTYPE_INT_SCROLL, 0, 1000000}, {"Width", pSource->m_Shape.m_Rectangle.m_Width / 1024, PROPTYPE_INT_SCROLL, 0, 1000000},
{"Height", pSource->m_Shape.m_Rectangle.m_Height / 1024, PROPTYPE_INT_SCROLL, 0, 1000000}, {"Height", pSource->m_Shape.m_Rectangle.m_Height / 1024, PROPTYPE_INT_SCROLL, 0, 1000000},
{nullptr}, {nullptr},
}; };
static int s_aRectangleIds[NUM_RECTANGLE_PROPS] = {0}; static int s_aRectangleIds[(int)ERectangleShapeProp::NUM_RECTANGLE_PROPS] = {0};
NewVal = 0; NewVal = 0;
Prop = pEditor->DoProperties(&View, aRectangleProps, s_aRectangleIds, &NewVal); auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ERectangleShapeProp>(&View, aRectangleProps, s_aRectangleIds, &NewVal);
if(Prop != -1) if(LocalProp != ERectangleShapeProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
} }
if(Prop == PROP_RECTANGLE_WIDTH) static CSoundSourceRectShapePropTracker s_ShapeTracker(pEditor);
s_ShapeTracker.Begin(pSource, LocalProp, LocalState);
if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_WIDTH)
{ {
pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024; pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024;
} }
else if(Prop == PROP_RECTANGLE_HEIGHT) else if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT)
{ {
pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024; pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024;
} }
s_ShapeTracker.End(LocalProp, LocalState);
break; break;
} }
} }
@ -1208,22 +1195,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View,
if(!in_range<int>(pEditor->m_SelectedQuadIndex, 0, vpQuads.size() - 1)) if(!in_range<int>(pEditor->m_SelectedQuadIndex, 0, vpQuads.size() - 1))
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex]; CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex];
std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS));
enum int Color = PackColor(pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint]);
{
PROP_POS_X = 0,
PROP_POS_Y,
PROP_COLOR,
PROP_TEX_U,
PROP_TEX_V,
NUM_PROPS,
};
int Color = 0;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].r << 24;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].g << 16;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].b << 8;
Color |= pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint].a;
const int X = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].x); const int X = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].x);
const int Y = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].y); const int Y = fx2i(pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].y);
@ -1239,29 +1213,35 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View,
{nullptr}, {nullptr},
}; };
static int s_aIds[NUM_PROPS] = {0}; static int s_aIds[(int)EQuadPointProp::NUM_PROPS] = {0};
int NewVal = 0; int NewVal = 0;
int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); auto PropRes = pEditor->DoPropertiesWithState<EQuadPointProp>(&View, aProps, s_aIds, &NewVal);
if(Prop != -1) EQuadPointProp Prop = PropRes.m_Value;
if(Prop != EQuadPointProp::PROP_NONE)
{ {
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
if(PropRes.m_State == EEditState::START || PropRes.m_State == EEditState::ONE_GO)
{
pEditor->m_QuadTracker.BeginQuadPointPropTrack(pLayer, pEditor->m_vSelectedQuads, pEditor->m_SelectedQuadPoints);
pEditor->m_QuadTracker.AddQuadPointPropTrack(Prop);
}
} }
for(CQuad *pQuad : vpQuads) for(CQuad *pQuad : vpQuads)
{ {
if(Prop == PROP_POS_X) if(Prop == EQuadPointProp::PROP_POS_X)
{ {
for(int v = 0; v < 4; v++) for(int v = 0; v < 4; v++)
if(pEditor->IsQuadCornerSelected(v)) if(pEditor->IsQuadCornerSelected(v))
pQuad->m_aPoints[v].x = i2fx(fx2i(pQuad->m_aPoints[v].x) + NewVal - X); pQuad->m_aPoints[v].x = i2fx(fx2i(pQuad->m_aPoints[v].x) + NewVal - X);
} }
else if(Prop == PROP_POS_Y) else if(Prop == EQuadPointProp::PROP_POS_Y)
{ {
for(int v = 0; v < 4; v++) for(int v = 0; v < 4; v++)
if(pEditor->IsQuadCornerSelected(v)) if(pEditor->IsQuadCornerSelected(v))
pQuad->m_aPoints[v].y = i2fx(fx2i(pQuad->m_aPoints[v].y) + NewVal - Y); pQuad->m_aPoints[v].y = i2fx(fx2i(pQuad->m_aPoints[v].y) + NewVal - Y);
} }
else if(Prop == PROP_COLOR) else if(Prop == EQuadPointProp::PROP_COLOR)
{ {
for(int v = 0; v < 4; v++) for(int v = 0; v < 4; v++)
{ {
@ -1274,13 +1254,13 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View,
} }
} }
} }
else if(Prop == PROP_TEX_U) else if(Prop == EQuadPointProp::PROP_TEX_U)
{ {
for(int v = 0; v < 4; v++) for(int v = 0; v < 4; v++)
if(pEditor->IsQuadCornerSelected(v)) if(pEditor->IsQuadCornerSelected(v))
pQuad->m_aTexcoords[v].x = f2fx(fx2f(pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f); pQuad->m_aTexcoords[v].x = f2fx(fx2f(pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f);
} }
else if(Prop == PROP_TEX_V) else if(Prop == EQuadPointProp::PROP_TEX_V)
{ {
for(int v = 0; v < 4; v++) for(int v = 0; v < 4; v++)
if(pEditor->IsQuadCornerSelected(v)) if(pEditor->IsQuadCornerSelected(v))
@ -1288,12 +1268,24 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View,
} }
} }
if(Prop != EQuadPointProp::PROP_NONE)
{
pEditor->m_Map.OnModify();
if(PropRes.m_State == EEditState::END || PropRes.m_State == EEditState::ONE_GO)
{
pEditor->m_QuadTracker.EndQuadPointPropTrack(Prop);
}
}
return CUI::POPUP_KEEP_OPEN; return CUI::POPUP_KEEP_OPEN;
} }
CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect View, bool Active) CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect View, bool Active)
{ {
CEditor *pEditor = static_cast<CEditor *>(pContext); CEditor *pEditor = static_cast<CEditor *>(pContext);
if(pEditor->m_SelectedEnvelope < 0 || pEditor->m_SelectedEnvelope >= (int)pEditor->m_Map.m_vpEnvelopes.size())
return CUI::POPUP_CLOSE_CURRENT;
const float RowHeight = 12.0f; const float RowHeight = 12.0f;
CUIRect Row, Label, EditBox; CUIRect Row, Label, EditBox;
@ -1309,14 +1301,41 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie
Row.VSplitLeft(10.0f, nullptr, &EditBox); Row.VSplitLeft(10.0f, nullptr, &EditBox);
pEditor->UI()->DoLabel(&Label, "Color:", RowHeight - 2.0f, TEXTALIGN_ML); pEditor->UI()->DoLabel(&Label, "Color:", RowHeight - 2.0f, TEXTALIGN_ML);
const auto [SelectedIndex, _] = pEditor->m_vSelectedEnvelopePoints.front(); const auto SelectedPoint = pEditor->m_vSelectedEnvelopePoints.front();
const int SelectedIndex = SelectedPoint.first;
auto *pValues = pEnvelope->m_vPoints[SelectedIndex].m_aValues; auto *pValues = pEnvelope->m_vPoints[SelectedIndex].m_aValues;
const ColorRGBA Color = ColorRGBA(fx2f(pValues[0]), fx2f(pValues[1]), fx2f(pValues[2]), fx2f(pValues[3])); const ColorRGBA Color = ColorRGBA(fx2f(pValues[0]), fx2f(pValues[1]), fx2f(pValues[2]), fx2f(pValues[3]));
const auto &&SetColor = [&](ColorRGBA NewColor) { const auto &&SetColor = [&](ColorRGBA NewColor) {
if(Color == NewColor) if(Color == NewColor && pEditor->m_ColorPickerPopupContext.m_State == EEditState::EDITING)
return; return;
static int s_Values[4];
if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::START || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO)
{
for(int Channel = 0; Channel < 4; ++Channel) for(int Channel = 0; Channel < 4; ++Channel)
s_Values[Channel] = pValues[Channel];
}
for(int Channel = 0; Channel < 4; ++Channel)
{
pValues[Channel] = f2fx(NewColor[Channel]); pValues[Channel] = f2fx(NewColor[Channel]);
}
if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::END || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO)
{
std::vector<std::shared_ptr<IEditorAction>> vpActions(4);
for(int Channel = 0; Channel < 4; ++Channel)
{
vpActions[Channel] = std::make_shared<CEditorActionEnvelopeEditPoint>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, Channel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, s_Values[Channel], f2fx(NewColor[Channel]));
}
char aDisplay[256];
str_format(aDisplay, sizeof(aDisplay), "Edit color of point %d of envelope %d", SelectedIndex, pEditor->m_SelectedEnvelope);
pEditor->m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(pEditor, vpActions, aDisplay));
}
pEditor->m_UpdateEnvPointInfo = true; pEditor->m_UpdateEnvPointInfo = true;
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
}; };
@ -1327,37 +1346,23 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie
static CLineInputNumber s_CurValueInput; static CLineInputNumber s_CurValueInput;
static CLineInputNumber s_CurTimeInput; static CLineInputNumber s_CurTimeInput;
static float s_CurrentTime = 0;
static float s_CurrentValue = 0;
if(pEditor->m_UpdateEnvPointInfo) if(pEditor->m_UpdateEnvPointInfo)
{ {
pEditor->m_UpdateEnvPointInfo = false; pEditor->m_UpdateEnvPointInfo = false;
int CurrentTime; auto TimeAndValue = pEditor->EnvGetSelectedTimeAndValue();
int CurrentValue; int CurrentTime = TimeAndValue.first;
if(pEditor->IsTangentInSelected()) int CurrentValue = TimeAndValue.second;
{
auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel];
CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel];
}
else if(pEditor->IsTangentOutSelected())
{
auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel];
CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel];
}
else
{
auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time;
CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel];
}
// update displayed text // update displayed text
s_CurValueInput.SetFloat(fx2f(CurrentValue)); s_CurValueInput.SetFloat(fx2f(CurrentValue));
s_CurTimeInput.SetFloat(CurrentTime / 1000.0f); s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
s_CurrentTime = s_CurTimeInput.GetFloat();
s_CurrentValue = s_CurValueInput.GetFloat();
} }
View.HSplitTop(RowHeight, &Row, &View); View.HSplitTop(RowHeight, &Row, &View);
@ -1377,40 +1382,31 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie
{ {
float CurrentTime = s_CurTimeInput.GetFloat(); float CurrentTime = s_CurTimeInput.GetFloat();
float CurrentValue = s_CurValueInput.GetFloat(); float CurrentValue = s_CurValueInput.GetFloat();
if(!(absolute(CurrentTime - s_CurrentTime) < 0.0001f && absolute(CurrentValue - s_CurrentValue) < 0.0001f))
{
auto [OldTime, OldValue] = pEditor->EnvGetSelectedTimeAndValue();
if(pEditor->IsTangentInSelected()) if(pEditor->IsTangentInSelected())
{ {
auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel] = minimum<int>(CurrentTime * 1000.0f - pEnvelope->m_vPoints[SelectedIndex].m_Time, 0); pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionEditEnvelopePointValue>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::TANGENT_IN, static_cast<int>(OldTime * 1000.0f), f2fx(OldValue), static_cast<int>(CurrentTime * 1000.0f), f2fx(CurrentValue)));
CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]) / 1000.0f; CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]) / 1000.0f;
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel] = f2fx(CurrentValue) - pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel];
} }
else if(pEditor->IsTangentOutSelected()) else if(pEditor->IsTangentOutSelected())
{ {
auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel] = maximum<int>(CurrentTime * 1000.0f - pEnvelope->m_vPoints[SelectedIndex].m_Time, 0); pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionEditEnvelopePointValue>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::TANGENT_OUT, static_cast<int>(OldTime * 1000.0f), f2fx(OldValue), static_cast<int>(CurrentTime * 1000.0f), f2fx(CurrentValue)));
CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]) / 1000.0f; CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]) / 1000.0f;
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel] = f2fx(CurrentValue) - pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel];
} }
else else
{ {
auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
if(pEnvelope->GetChannels() == 4) pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionEditEnvelopePointValue>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::POINT, static_cast<int>(OldTime * 1000.0f), f2fx(OldValue), static_cast<int>(CurrentTime * 1000.0f), f2fx(CurrentValue)));
CurrentValue = clamp(CurrentValue, 0.0f, 1.0f);
pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] = f2fx(CurrentValue);
if(SelectedIndex != 0) if(SelectedIndex != 0)
{ {
pEnvelope->m_vPoints[SelectedIndex].m_Time = CurrentTime * 1000.0f;
if(pEnvelope->m_vPoints[SelectedIndex].m_Time < pEnvelope->m_vPoints[SelectedIndex - 1].m_Time)
pEnvelope->m_vPoints[SelectedIndex].m_Time = pEnvelope->m_vPoints[SelectedIndex - 1].m_Time + 1;
if(static_cast<size_t>(SelectedIndex) + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[SelectedIndex].m_Time > pEnvelope->m_vPoints[SelectedIndex + 1].m_Time)
pEnvelope->m_vPoints[SelectedIndex].m_Time = pEnvelope->m_vPoints[SelectedIndex + 1].m_Time - 1;
CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time / 1000.0f; CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time / 1000.0f;
} }
else else
@ -1423,7 +1419,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie
s_CurTimeInput.SetFloat(static_cast<int>(CurrentTime * 1000.0f) / 1000.0f); s_CurTimeInput.SetFloat(static_cast<int>(CurrentTime * 1000.0f) / 1000.0f);
s_CurValueInput.SetFloat(fx2f(f2fx(CurrentValue))); s_CurValueInput.SetFloat(fx2f(f2fx(CurrentValue)));
pEditor->m_Map.OnModify(); s_CurrentTime = s_CurTimeInput.GetFloat();
s_CurrentValue = s_CurValueInput.GetFloat();
}
} }
View.HSplitTop(6.0f, nullptr, &View); View.HSplitTop(6.0f, nullptr, &View);
@ -1436,24 +1434,18 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect Vie
if(pEditor->IsTangentInSelected()) if(pEditor->IsTangentInSelected())
{ {
auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionResetEnvelopePointTangent>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, true));
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel] = 0.0f;
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel] = 0.0f;
} }
else if(pEditor->IsTangentOutSelected()) else if(pEditor->IsTangentOutSelected())
{ {
auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionResetEnvelopePointTangent>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, false));
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel] = 0.0f;
pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel] = 0.0f;
} }
else else
{ {
auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
pEditor->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionDeleteEnvelopePoint>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex));
pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + SelectedIndex);
} }
pEditor->m_Map.OnModify();
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }
@ -1515,6 +1507,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CU
if(pEditor->DoButton_MenuItem(&s_ButtonSmoothID, "Smooth", 0, &ButtonSmooth)) if(pEditor->DoButton_MenuItem(&s_ButtonSmoothID, "Smooth", 0, &ButtonSmooth))
CurveType = CURVETYPE_SMOOTH; CurveType = CURVETYPE_SMOOTH;
std::vector<std::shared_ptr<IEditorAction>> vpActions;
if(CurveType >= 0) if(CurveType >= 0)
{ {
std::shared_ptr<CEnvelope> pEnvelope = pEditor->m_Map.m_vpEnvelopes.at(pEditor->m_SelectedEnvelope); std::shared_ptr<CEnvelope> pEnvelope = pEditor->m_Map.m_vpEnvelopes.at(pEditor->m_SelectedEnvelope);
@ -1551,13 +1545,20 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CU
CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex]; CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex];
ColorRGBA Channels; ColorRGBA Channels;
HelperEnvelope.Eval(CurrentPoint.m_Time / 1000.0f, Channels); HelperEnvelope.Eval(CurrentPoint.m_Time / 1000.0f, Channels);
int PrevValue = CurrentPoint.m_aValues[c];
CurrentPoint.m_aValues[c] = f2fx(Channels.r); CurrentPoint.m_aValues[c] = f2fx(Channels.r);
vpActions.push_back(std::make_shared<CEditorActionEnvelopeEditPoint>(pEditor, pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, PrevValue, CurrentPoint.m_aValues[c]));
} }
} }
} }
} }
} }
if(!vpActions.empty())
{
pEditor->m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(pEditor, vpActions, "Project points"));
}
pEditor->m_Map.OnModify(); pEditor->m_Map.OnModify();
return CUI::POPUP_CLOSE_CURRENT; return CUI::POPUP_CLOSE_CURRENT;
} }

View file

@ -1,4 +1,5 @@
#include "editor.h" #include "editor.h"
#include "editor_actions.h"
#include <game/editor/mapitems/image.h> #include <game/editor/mapitems/image.h>
@ -184,10 +185,15 @@ static void SetTilelayerIndices(const std::shared_ptr<CLayerTiles> &pLayer, cons
} }
} }
void CEditor::AddTileart() void CEditor::AddTileart(bool IgnoreHistory)
{ {
char aTileArtFileName[IO_MAX_PATH_LENGTH];
IStorage::StripPathAndExtension(m_aTileartFilename, aTileArtFileName, sizeof(aTileArtFileName));
std::shared_ptr<CLayerGroup> pGroup = m_Map.NewGroup(); std::shared_ptr<CLayerGroup> pGroup = m_Map.NewGroup();
str_copy(pGroup->m_aName, m_aTileartFilename); str_copy(pGroup->m_aName, aTileArtFileName);
int ImageCount = m_Map.m_vpImages.size();
auto vUniqueColors = GetUniqueColors(m_TileartImageInfo); auto vUniqueColors = GetUniqueColors(m_TileartImageInfo);
auto vaColorGroups = GroupColors(vUniqueColors); auto vaColorGroups = GroupColors(vUniqueColors);
@ -195,11 +201,16 @@ void CEditor::AddTileart()
char aImageName[IO_MAX_PATH_LENGTH]; char aImageName[IO_MAX_PATH_LENGTH];
for(size_t i = 0; i < vColorImages.size(); i++) for(size_t i = 0; i < vColorImages.size(); i++)
{ {
str_format(aImageName, sizeof(aImageName), "%s %" PRIzu, m_aTileartFilename, i + 1); str_format(aImageName, sizeof(aImageName), "%s %" PRIzu, aTileArtFileName, i + 1);
std::shared_ptr<CLayerTiles> pLayer = AddLayerWithImage(this, pGroup, m_TileartImageInfo.m_Width, m_TileartImageInfo.m_Height, vColorImages[i], aImageName); std::shared_ptr<CLayerTiles> pLayer = AddLayerWithImage(this, pGroup, m_TileartImageInfo.m_Width, m_TileartImageInfo.m_Height, vColorImages[i], aImageName);
SetTilelayerIndices(pLayer, vaColorGroups[i], m_TileartImageInfo); SetTilelayerIndices(pLayer, vaColorGroups[i], m_TileartImageInfo);
} }
SortImages(); auto IndexMap = SortImages();
if(!IgnoreHistory)
{
m_EditorHistory.RecordAction(std::make_shared<CEditorActionTileArt>(this, ImageCount, m_aTileartFilename, IndexMap));
}
free(m_TileartImageInfo.m_pData); free(m_TileartImageInfo.m_pData);
m_TileartImageInfo.m_pData = nullptr; m_TileartImageInfo.m_pData = nullptr;
@ -237,7 +248,7 @@ bool CEditor::CallbackAddTileart(const char *pFilepath, int StorageType, void *p
return false; return false;
} }
IStorage::StripPathAndExtension(pFilepath, pEditor->m_aTileartFilename, sizeof(pEditor->m_aTileartFilename)); str_copy(pEditor->m_aTileartFilename, pFilepath);
if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000) if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000)
{ {
pEditor->m_PopupEventType = CEditor::POPEVENT_PIXELART_BIG_IMAGE; pEditor->m_PopupEventType = CEditor::POPEVENT_PIXELART_BIG_IMAGE;

View file

@ -150,3 +150,13 @@ bool IsCreditsTile(int TileIndex)
(TILE_CREDITS_7 == TileIndex) || (TILE_CREDITS_7 == TileIndex) ||
(TILE_CREDITS_8 == TileIndex)); (TILE_CREDITS_8 == TileIndex));
} }
int PackColor(CColor Color)
{
int Res = 0;
Res |= Color.r << 24;
Res |= Color.g << 16;
Res |= Color.b << 8;
Res |= Color.a;
return Res;
}

View file

@ -578,5 +578,6 @@ bool IsValidTuneTile(int Index);
bool IsValidEntity(int Index); bool IsValidEntity(int Index);
bool IsRotatableTile(int Index); bool IsRotatableTile(int Index);
bool IsCreditsTile(int TileIndex); bool IsCreditsTile(int TileIndex);
int PackColor(CColor Color);
#endif #endif