Merge branch 'ddnet:master' into patch-1

This commit is contained in:
veydzh3r 2024-09-01 11:57:42 +03:00 committed by GitHub
commit a8616ef2ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 981 additions and 322 deletions

View file

@ -2462,6 +2462,7 @@ if(CLIENT)
editor_trackers.cpp editor_trackers.cpp
editor_trackers.h editor_trackers.h
editor_ui.h editor_ui.h
enums.h
explanations.cpp explanations.cpp
layer_selector.cpp layer_selector.cpp
layer_selector.h layer_selector.h
@ -2500,8 +2501,13 @@ if(CLIENT)
mapitems/sound.cpp mapitems/sound.cpp
mapitems/sound.h mapitems/sound.h
popups.cpp popups.cpp
prompt.cpp
prompt.h
proof_mode.cpp proof_mode.cpp
proof_mode.h proof_mode.h
quick_action.h
quick_actions.cpp
quick_actions.h
smooth_value.cpp smooth_value.cpp
smooth_value.h smooth_value.h
tileart.cpp tileart.cpp

View file

@ -631,7 +631,7 @@ Loading DDNet Client
== Загрузка DDNet Client == Загрузка DDNet Client
Normal message Normal message
== Обычное с. == Обычное
Connecting dummy Connecting dummy
== Подключение дамми == Подключение дамми
@ -643,10 +643,10 @@ Save ghost
== Сохранять тень == Сохранять тень
DDNet Client updated! DDNet Client updated!
== DDNet Client обновлён! == DDNet клиент обновлён!
Highlighted message Highlighted message
== Выделенное с. == Выделенное
Demo Demo
== Демо == Демо
@ -724,7 +724,7 @@ Downloading %s:
== Скачивание %s: == Скачивание %s:
Update failed! Check log… Update failed! Check log…
== Ошибка. Проверьте логи == Не удалось обновиться! Подробности в логах
Restart Restart
== Рестарт == Рестарт
@ -844,7 +844,7 @@ DDNet
== DDNet == DDNet
Friend message Friend message
== Дружеское с. == Дружеское
Save the best demo of each race Save the best demo of each race
== Сохранять лучшее демо каждой карты == Сохранять лучшее демо каждой карты
@ -916,10 +916,10 @@ Ratio
== Соотношение == Соотношение
Net Net
== Сеть == Сальдо
FPM FPM
== FPM == У/мин
Spree Spree
== Серия == Серия
@ -940,7 +940,7 @@ Grabs
== 9+ упоминаний == 9+ упоминаний
Client message Client message
== Клиентское с. == Клиентское
Warning Warning
== Предупреждение == Предупреждение
@ -973,7 +973,7 @@ Run server
== Запустить сервер == Запустить сервер
Server executable not found, can't run server Server executable not found, can't run server
== Файл сервера не найден, невозможно запустить == Файл сервера не найден, не удалось запустить сервер
Editor Editor
== Редактор == Редактор
@ -1883,7 +1883,7 @@ Could not resolve connect address '%s'. See local console for details.
== Не удалось определить адрес подключения '%s'. Подробности в локальной консоли. == Не удалось определить адрес подключения '%s'. Подробности в локальной консоли.
Connect address error Connect address error
== Ошибка подключения сервера == Ошибка адреса подключения
Could not connect dummy Could not connect dummy
== Невозможно подключить дамми == Невозможно подключить дамми
@ -1892,34 +1892,34 @@ Dummy is not allowed on this server
== Дамми не разрешен на этом сервере == Дамми не разрешен на этом сервере
Please wait… Please wait…
== Пожалуйста подождите… == Пожалуйста, подождите…
Show client IDs (scoreboard, chat, spectator) Show client IDs (scoreboard, chat, spectator)
== Показывать ID клиента (Табло, чат, наблюдатель) == Показывать ID клиента (табло, чат, наблюдатель)
Normal: Normal:
== Обычный: == Обычный:
Team: Team:
== Команда: == В команде:
Dummy settings Dummy settings
== Настройки дамми == Настройки дамми
Toggle to edit your dummy settings Toggle to edit your dummy settings
== Нажмите чтоб изменить настройки дамми == Нажмите, чтобы изменить настройки дамми
Randomize Randomize
== Перемешать == Случайный
Are you sure that you want to delete '%s'? Are you sure that you want to delete '%s'?
== Вы уверены что хотите удалить '%s'? == Вы уверены, что хотите удалить '%s'?
Delete skin Delete skin
== Удалить скин == Удалить скин
Basic Basic
== Базовый == Пресеты
Custom Custom
== Кастомизация == Кастомизация

View file

@ -7,7 +7,7 @@
# 3edcxzaq1 2020-06-25 00:00:00 # 3edcxzaq1 2020-06-25 00:00:00
# cur.ie 2020-09-28 00:00:00 # cur.ie 2020-09-28 00:00:00
# simpygirl 2022-02-20 00:00:00 # simpygirl 2022-02-20 00:00:00
# furo 2024-07-17 00:00:00 # furo 2024-08-29 00:00:00
##### /authors ##### ##### /authors #####
##### translated strings ##### ##### translated strings #####
@ -1868,52 +1868,52 @@ https://wiki.ddnet.org/wiki/Mapping
== https://wiki.ddnet.org/wiki/Mapping == https://wiki.ddnet.org/wiki/Mapping
Could not resolve connect address '%s'. See local console for details. Could not resolve connect address '%s'. See local console for details.
== == Kunde inte förstå anslutnings adress '%s'. Se den lokala konsolen för detaljer.
Connect address error Connect address error
== == Anslutnings problem
Could not connect dummy Could not connect dummy
== == Kunde inte ansluta dummy
Dummy is not allowed on this server Dummy is not allowed on this server
== == Dummy är inte tillåten på denna server
Please wait… Please wait…
== == Vänligen vänta…
Show client IDs (scoreboard, chat, spectator) Show client IDs (scoreboard, chat, spectator)
== == Visa klient IDen (poänglistan, chatt, åskadarmeny)
Normal: Normal:
== == Normal:
Team: Team:
== == Lag:
Dummy settings Dummy settings
== == Dummy inställningar
Toggle to edit your dummy settings Toggle to edit your dummy settings
== == Växla för att ändra dina dummy inställningar
Randomize Randomize
== == Slumpa
Are you sure that you want to delete '%s'? Are you sure that you want to delete '%s'?
== == Är du säker att du vill ta bort '%s'?
Delete skin Delete skin
== == Ta bort skin
Basic Basic
== == Enkel
Custom Custom
== == Anpassa
Unable to delete skin Unable to delete skin
== == Kunde inte ta bort skin
Customize Customize
== == Ändra

View file

@ -228,10 +228,8 @@ int CSound::Init()
return -1; return -1;
} }
m_MixingRate = g_Config.m_SndRate;
SDL_AudioSpec Format, FormatOut; SDL_AudioSpec Format, FormatOut;
Format.freq = m_MixingRate; Format.freq = g_Config.m_SndRate;
Format.format = AUDIO_S16; Format.format = AUDIO_S16;
Format.channels = 2; Format.channels = 2;
Format.samples = g_Config.m_SndBufferSize; Format.samples = g_Config.m_SndBufferSize;
@ -239,7 +237,7 @@ int CSound::Init()
Format.userdata = this; Format.userdata = this;
// Open the audio device and start playing sound! // Open the audio device and start playing sound!
m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, 0); m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if(m_Device == 0) if(m_Device == 0)
{ {
dbg_msg("sound", "unable to open audio: %s", SDL_GetError()); dbg_msg("sound", "unable to open audio: %s", SDL_GetError());
@ -248,6 +246,7 @@ int CSound::Init()
else else
dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver()); dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver());
m_MixingRate = FormatOut.freq;
m_MaxFrames = FormatOut.samples * 2; m_MaxFrames = FormatOut.samples * 2;
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
m_MaxFrames = maximum<uint32_t>(m_MaxFrames, 1024 * 2); // make the buffer bigger just in case m_MaxFrames = maximum<uint32_t>(m_MaxFrames, 1024 * 2); // make the buffer bigger just in case

View file

@ -53,7 +53,7 @@ public:
virtual int GetInteger(unsigned Index) const = 0; virtual int GetInteger(unsigned Index) const = 0;
virtual float GetFloat(unsigned Index) const = 0; virtual float GetFloat(unsigned Index) const = 0;
virtual const char *GetString(unsigned Index) const = 0; virtual const char *GetString(unsigned Index) const = 0;
virtual ColorHSLA GetColor(unsigned Index, bool Light) const = 0; virtual std::optional<ColorHSLA> GetColor(unsigned Index, bool Light) const = 0;
virtual void RemoveArgument(unsigned Index) = 0; virtual void RemoveArgument(unsigned Index) = 0;

View file

@ -111,22 +111,29 @@ void SIntConfigVariable::ResetToOld()
void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUserData) void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUserData)
{ {
SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData); SColorConfigVariable *pData = static_cast<SColorConfigVariable *>(pUserData);
char aBuf[IConsole::CMDLINE_LENGTH + 64];
if(pResult->NumArguments()) if(pResult->NumArguments())
{ {
if(pData->CheckReadOnly()) if(pData->CheckReadOnly())
return; return;
const ColorHSLA Color = pResult->GetColor(0, pData->m_Light); const auto Color = pResult->GetColor(0, pData->m_Light);
const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha); if(Color)
{
const unsigned Value = Color->Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha);
*pData->m_pVariable = Value; *pData->m_pVariable = Value;
if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME) if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME)
pData->m_OldValue = Value; pData->m_OldValue = Value;
}
else
{
str_format(aBuf, sizeof(aBuf), "%s is not a valid color.", pResult->GetString(0));
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
}
} }
else else
{ {
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable); str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable);
pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf); pData->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "config", aBuf);
@ -493,8 +500,8 @@ void CConfigManager::Con_Toggle(IConsole::IResult *pResult, void *pUserData)
{ {
SColorConfigVariable *pColorVariable = static_cast<SColorConfigVariable *>(pVariable); SColorConfigVariable *pColorVariable = static_cast<SColorConfigVariable *>(pVariable);
const float Darkest = pColorVariable->m_Light ? 0.5f : 0.0f; const float Darkest = pColorVariable->m_Light ? 0.5f : 0.0f;
const ColorHSLA Value = *pColorVariable->m_pVariable == pResult->GetColor(1, pColorVariable->m_Light).Pack(Darkest, pColorVariable->m_Alpha) ? pResult->GetColor(2, pColorVariable->m_Light) : pResult->GetColor(1, pColorVariable->m_Light); const std::optional<ColorHSLA> Value = *pColorVariable->m_pVariable == pResult->GetColor(1, pColorVariable->m_Light).value_or(ColorHSLA(0, 0, 0)).Pack(Darkest, pColorVariable->m_Alpha) ? pResult->GetColor(2, pColorVariable->m_Light) : pResult->GetColor(1, pColorVariable->m_Light);
pColorVariable->SetValue(Value.Pack(Darkest, pColorVariable->m_Alpha)); pColorVariable->SetValue(Value.value_or(ColorHSLA(0, 0, 0)).Pack(Darkest, pColorVariable->m_Alpha));
} }
else if(pVariable->m_Type == SConfigVariable::VAR_STRING) else if(pVariable->m_Type == SConfigVariable::VAR_STRING)
{ {

View file

@ -40,22 +40,29 @@ float CConsole::CResult::GetFloat(unsigned Index) const
return str_tofloat(m_apArgs[Index]); return str_tofloat(m_apArgs[Index]);
} }
ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const std::optional<ColorHSLA> CConsole::CResult::GetColor(unsigned Index, bool Light) const
{ {
if(Index >= m_NumArgs) if(Index >= m_NumArgs)
return ColorHSLA(0, 0, 0); return std::nullopt;
const char *pStr = m_apArgs[Index]; const char *pStr = m_apArgs[Index];
if(str_isallnum(pStr) || ((pStr[0] == '-' || pStr[0] == '+') && str_isallnum(pStr + 1))) // Teeworlds Color (Packed HSL) if(str_isallnum(pStr) || ((pStr[0] == '-' || pStr[0] == '+') && str_isallnum(pStr + 1))) // Teeworlds Color (Packed HSL)
{ {
const ColorHSLA Hsla = ColorHSLA(str_toulong_base(pStr, 10), true); unsigned long Value = str_toulong_base(pStr, 10);
if(Value == std::numeric_limits<unsigned long>::max())
return std::nullopt;
const ColorHSLA Hsla = ColorHSLA(Value, true);
if(Light) if(Light)
return Hsla.UnclampLighting(); return Hsla.UnclampLighting();
return Hsla; return Hsla;
} }
else if(*pStr == '$') // Hex RGB/RGBA else if(*pStr == '$') // Hex RGB/RGBA
{ {
return color_cast<ColorHSLA>(color_parse<ColorRGBA>(pStr + 1).value_or(ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f))); auto ParsedColor = color_parse<ColorRGBA>(pStr + 1);
if(ParsedColor)
return color_cast<ColorHSLA>(ParsedColor.value());
else
return std::nullopt;
} }
else if(!str_comp_nocase(pStr, "red")) else if(!str_comp_nocase(pStr, "red"))
return ColorHSLA(0.0f / 6.0f, 1, .5f); return ColorHSLA(0.0f / 6.0f, 1, .5f);
@ -76,7 +83,7 @@ ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const
else if(!str_comp_nocase(pStr, "black")) else if(!str_comp_nocase(pStr, "black"))
return ColorHSLA(0, 0, 0); return ColorHSLA(0, 0, 0);
return ColorHSLA(0, 0, 0); return std::nullopt;
} }
const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const
@ -129,12 +136,12 @@ int CConsole::ParseStart(CResult *pResult, const char *pString, int Length)
return 0; return 0;
} }
int CConsole::ParseArgs(CResult *pResult, const char *pFormat) int CConsole::ParseArgs(CResult *pResult, const char *pFormat, FCommandCallback pfnCallback)
{ {
char Command = *pFormat; char Command = *pFormat;
char *pStr; char *pStr;
int Optional = 0; int Optional = 0;
int Error = 0; int Error = PARSEARGS_OK;
pResult->ResetVictim(); pResult->ResetVictim();
@ -155,7 +162,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
{ {
if(!Optional) if(!Optional)
{ {
Error = 1; Error = PARSEARGS_MISSING_VALUE;
break; break;
} }
@ -191,7 +198,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
pStr++; // skip due to escape pStr++; // skip due to escape
} }
else if(pStr[0] == 0) else if(pStr[0] == 0)
return 1; // return error return PARSEARGS_MISSING_VALUE; // return error
*pDst = *pStr; *pDst = *pStr;
pDst++; pDst++;
@ -215,13 +222,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
if(Command == 'r') // rest of the string if(Command == 'r') // rest of the string
break; break;
else if(Command == 'v') // validate victim else if(Command == 'v' || Command == 'i' || Command == 'f' || Command == 's')
pStr = str_skip_to_whitespace(pStr);
else if(Command == 'i') // validate int
pStr = str_skip_to_whitespace(pStr);
else if(Command == 'f') // validate float
pStr = str_skip_to_whitespace(pStr);
else if(Command == 's') // validate string
pStr = str_skip_to_whitespace(pStr); pStr = str_skip_to_whitespace(pStr);
if(pStr[0] != 0) // check for end of string if(pStr[0] != 0) // check for end of string
@ -230,6 +231,32 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat)
pStr++; pStr++;
} }
// validate args
if(Command == 'i')
{
// don't validate colors here
if(pfnCallback != &SColorConfigVariable::CommandCallback)
{
int Value;
if(!str_toint(pResult->GetString(pResult->NumArguments() - 1), &Value) ||
Value == std::numeric_limits<int>::max() || Value == std::numeric_limits<int>::min())
{
Error = PARSEARGS_INVALID_INTEGER;
break;
}
}
}
else if(Command == 'f')
{
float Value;
if(!str_tofloat(pResult->GetString(pResult->NumArguments() - 1), &Value) ||
Value == std::numeric_limits<float>::max() || Value == std::numeric_limits<float>::min())
{
Error = PARSEARGS_INVALID_FLOAT;
break;
}
}
if(pVictim) if(pVictim)
{ {
pResult->SetVictim(pVictim); pResult->SetVictim(pVictim);
@ -487,10 +514,15 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bo
if(Stroke || IsStrokeCommand) if(Stroke || IsStrokeCommand)
{ {
if(ParseArgs(&Result, pCommand->m_pParams)) if(int Error = ParseArgs(&Result, pCommand->m_pParams, pCommand->m_pfnCallback))
{ {
char aBuf[TEMPCMD_NAME_LENGTH + TEMPCMD_PARAMS_LENGTH + 32]; char aBuf[CMDLINE_LENGTH + 64];
str_format(aBuf, sizeof(aBuf), "Invalid arguments. Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); if(Error == PARSEARGS_INVALID_INTEGER)
str_format(aBuf, sizeof(aBuf), "%s is not a valid integer.", Result.GetString(Result.NumArguments() - 1));
else if(Error == PARSEARGS_INVALID_FLOAT)
str_format(aBuf, sizeof(aBuf), "%s is not a valid decimal number.", Result.GetString(Result.NumArguments() - 1));
else
str_format(aBuf, sizeof(aBuf), "Invalid arguments. Usage: %s %s", pCommand->m_pName, pCommand->m_pParams);
Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf);
} }
else if(m_StoreCommands && pCommand->m_Flags & CFGFLAG_STORE) else if(m_StoreCommands && pCommand->m_Flags & CFGFLAG_STORE)

View file

@ -115,7 +115,7 @@ class CConsole : public IConsole
const char *GetString(unsigned Index) const override; const char *GetString(unsigned Index) const override;
int GetInteger(unsigned Index) const override; int GetInteger(unsigned Index) const override;
float GetFloat(unsigned Index) const override; float GetFloat(unsigned Index) const override;
ColorHSLA GetColor(unsigned Index, bool Light) const override; std::optional<ColorHSLA> GetColor(unsigned Index, bool Light) const override;
void RemoveArgument(unsigned Index) override void RemoveArgument(unsigned Index) override
{ {
@ -144,7 +144,16 @@ class CConsole : public IConsole
}; };
int ParseStart(CResult *pResult, const char *pString, int Length); int ParseStart(CResult *pResult, const char *pString, int Length);
int ParseArgs(CResult *pResult, const char *pFormat);
enum
{
PARSEARGS_OK = 0,
PARSEARGS_MISSING_VALUE,
PARSEARGS_INVALID_INTEGER,
PARSEARGS_INVALID_FLOAT,
};
int ParseArgs(CResult *pResult, const char *pFormat, FCommandCallback pfnCallback = 0);
/* /*
this function will set pFormat to the next parameter (i,s,r,v,?) it contains and this function will set pFormat to the next parameter (i,s,r,v,?) it contains and

View file

@ -1074,11 +1074,9 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
// proof button // proof button
TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); TB_Top.VSplitLeft(40.0f, &Button, &TB_Top);
static int s_ProofButton = 0; if(DoButton_Ex(&m_QuickActionProof, m_QuickActionProof.Label(), m_QuickActionProof.Active(), &Button, 0, m_QuickActionProof.Description(), IGraphics::CORNER_L))
if(DoButton_Ex(&s_ProofButton, "Proof", MapView()->ProofMode()->IsEnabled(), &Button, 0, "[ctrl+p] Toggles proof borders. These borders represent the area that a player can see with default zoom.", IGraphics::CORNER_L) ||
(m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_P) && ModPressed))
{ {
MapView()->ProofMode()->Toggle(); m_QuickActionProof.Call();
} }
TB_Top.VSplitLeft(14.0f, &Button, &TB_Top); TB_Top.VSplitLeft(14.0f, &Button, &TB_Top);
@ -1254,10 +1252,9 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
// refocus button // refocus button
{ {
TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom); TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom);
static int s_RefocusButton = 0;
int FocusButtonChecked = MapView()->IsFocused() ? -1 : 1; int FocusButtonChecked = MapView()->IsFocused() ? -1 : 1;
if(DoButton_Editor(&s_RefocusButton, "Refocus", FocusButtonChecked, &Button, 0, "[HOME] Restore map focus") || (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_HOME))) if(DoButton_Editor(&m_QuickActionRefocus, m_QuickActionRefocus.Label(), FocusButtonChecked, &Button, 0, m_QuickActionRefocus.Description()) || (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_HOME)))
MapView()->Focus(); m_QuickActionRefocus.Call();
TB_Bottom.VSplitLeft(5.0f, nullptr, &TB_Bottom); TB_Bottom.VSplitLeft(5.0f, nullptr, &TB_Bottom);
} }
@ -4302,12 +4299,9 @@ void CEditor::RenderLayers(CUIRect LayersBox)
if(s_ScrollRegion.AddRect(AddGroupButton)) if(s_ScrollRegion.AddRect(AddGroupButton))
{ {
AddGroupButton.HSplitTop(RowHeight, &AddGroupButton, 0); AddGroupButton.HSplitTop(RowHeight, &AddGroupButton, 0);
static int s_AddGroupButton = 0; if(DoButton_Editor(&m_QuickActionAddGroup, m_QuickActionAddGroup.Label(), 0, &AddGroupButton, IGraphics::CORNER_R, m_QuickActionAddGroup.Description()))
if(DoButton_Editor(&s_AddGroupButton, "Add group", 0, &AddGroupButton, IGraphics::CORNER_R, "Adds a new group"))
{ {
m_Map.NewGroup(); m_QuickActionAddGroup.Call();
m_SelectedGroup = m_Map.m_vpGroups.size() - 1;
m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(this, m_SelectedGroup, false));
} }
} }
@ -4806,8 +4800,8 @@ void CEditor::RenderImagesList(CUIRect ToolBox)
{ {
AddImageButton.HSplitTop(5.0f, nullptr, &AddImageButton); AddImageButton.HSplitTop(5.0f, nullptr, &AddImageButton);
AddImageButton.HSplitTop(RowHeight, &AddImageButton, nullptr); AddImageButton.HSplitTop(RowHeight, &AddImageButton, nullptr);
if(DoButton_Editor(&s_AddImageButton, "Add", 0, &AddImageButton, 0, "Load a new image to use in the map")) if(DoButton_Editor(&s_AddImageButton, m_QuickActionAddImage.Label(), 0, &AddImageButton, 0, m_QuickActionAddImage.Description()))
InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", false, AddImage, this); m_QuickActionAddImage.Call();
} }
s_ScrollRegion.End(); s_ScrollRegion.End();
} }
@ -5750,9 +5744,9 @@ void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect)
CUIRect Button; CUIRect Button;
View.VSplitRight(100.0f, &View, &Button); View.VSplitRight(100.0f, &View, &Button);
static int s_EnvelopeButton = 0; static int s_EnvelopeButton = 0;
if(DoButton_Editor(&s_EnvelopeButton, "Envelopes", ButtonsDisabled ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES, &Button, 0, "Toggles the envelope editor.") == 1) if(DoButton_Editor(&s_EnvelopeButton, m_QuickActionEnvelopes.Label(), m_QuickActionEnvelopes.Color(), &Button, 0, m_QuickActionEnvelopes.Description()) == 1)
{ {
m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES ? EXTRAEDITOR_NONE : EXTRAEDITOR_ENVELOPES; m_QuickActionEnvelopes.Call();
} }
View.VSplitRight(10.0f, &View, nullptr); View.VSplitRight(10.0f, &View, nullptr);
@ -7919,7 +7913,7 @@ void CEditor::Render()
InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", true, CallbackSaveCopyMap, this); InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", true, CallbackSaveCopyMap, this);
// ctrl+shift+s to save as // ctrl+shift+s to save as
else if(Input()->KeyPress(KEY_S) && ModPressed && ShiftPressed) else if(Input()->KeyPress(KEY_S) && ModPressed && ShiftPressed)
InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", true, CallbackSaveMap, this); m_QuickActionSaveAs.Call();
// ctrl+s to save // ctrl+s to save
else if(Input()->KeyPress(KEY_S) && ModPressed) else if(Input()->KeyPress(KEY_S) && ModPressed)
{ {
@ -8362,6 +8356,7 @@ void CEditor::Init()
m_vComponents.emplace_back(m_MapView); m_vComponents.emplace_back(m_MapView);
m_vComponents.emplace_back(m_MapSettingsBackend); m_vComponents.emplace_back(m_MapSettingsBackend);
m_vComponents.emplace_back(m_LayerSelector); m_vComponents.emplace_back(m_LayerSelector);
m_vComponents.emplace_back(m_Prompt);
for(CEditorComponent &Component : m_vComponents) for(CEditorComponent &Component : m_vComponents)
Component.OnInit(this); Component.OnInit(this);

View file

@ -11,6 +11,7 @@
#include <game/client/ui_listbox.h> #include <game/client/ui_listbox.h>
#include <game/mapitems.h> #include <game/mapitems.h>
#include <game/editor/enums.h>
#include <game/editor/mapitems/envelope.h> #include <game/editor/mapitems/envelope.h>
#include <game/editor/mapitems/layer.h> #include <game/editor/mapitems/layer.h>
#include <game/editor/mapitems/layer_front.h> #include <game/editor/mapitems/layer_front.h>
@ -38,6 +39,8 @@
#include "layer_selector.h" #include "layer_selector.h"
#include "map_view.h" #include "map_view.h"
#include "smooth_value.h" #include "smooth_value.h"
#include <game/editor/prompt.h>
#include <game/editor/quick_action.h>
#include <deque> #include <deque>
#include <functional> #include <functional>
@ -60,7 +63,8 @@ enum
DIALOG_NONE = 0, DIALOG_NONE = 0,
DIALOG_FILE, DIALOG_FILE,
DIALOG_MAPSETTINGS_ERROR DIALOG_MAPSETTINGS_ERROR,
DIALOG_QUICK_PROMPT,
}; };
class CEditorImage; class CEditorImage;
@ -278,6 +282,7 @@ class CEditor : public IEditor
std::vector<std::reference_wrapper<CEditorComponent>> m_vComponents; std::vector<std::reference_wrapper<CEditorComponent>> m_vComponents;
CMapView m_MapView; CMapView m_MapView;
CLayerSelector m_LayerSelector; CLayerSelector m_LayerSelector;
CPrompt m_Prompt;
bool m_EditorWasUsedBefore = false; bool m_EditorWasUsedBefore = false;
@ -319,7 +324,20 @@ public:
const CMapView *MapView() const { return &m_MapView; } const CMapView *MapView() const { return &m_MapView; }
CLayerSelector *LayerSelector() { return &m_LayerSelector; } CLayerSelector *LayerSelector() { return &m_LayerSelector; }
void FillGameTiles(EGameTileOp FillTile) const;
bool CanFillGameTiles() const;
void AddGroup();
void AddTileLayer();
void LayerSelectImage();
bool IsNonGameTileLayerSelected() const;
#define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description) CQuickAction m_QuickAction##name;
#include <game/editor/quick_actions.h>
#undef REGISTER_QUICK_ACTION
CEditor() : CEditor() :
#define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description) m_QuickAction##name(text, description, callback, disabled, active, button_color),
#include <game/editor/quick_actions.h>
#undef REGISTER_QUICK_ACTION
m_ZoomEnvelopeX(1.0f, 0.1f, 600.0f), m_ZoomEnvelopeX(1.0f, 0.1f, 600.0f),
m_ZoomEnvelopeY(640.0f, 0.1f, 32000.0f), m_ZoomEnvelopeY(640.0f, 0.1f, 32000.0f),
m_MapSettingsCommandContext(m_MapSettingsBackend.NewContext(&m_SettingsCommandInput)) m_MapSettingsCommandContext(m_MapSettingsBackend.NewContext(&m_SettingsCommandInput))

36
src/game/editor/enums.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef GAME_EDITOR_ENUMS_H
#define GAME_EDITOR_ENUMS_H
constexpr const char *g_apGametileOpNames[] = {
"Air",
"Hookable",
"Death",
"Unhookable",
"Hookthrough",
"Freeze",
"Unfreeze",
"Deep Freeze",
"Deep Unfreeze",
"Blue Check-Tele",
"Red Check-Tele",
"Live Freeze",
"Live Unfreeze",
};
enum class EGameTileOp
{
AIR,
HOOKABLE,
DEATH,
UNHOOKABLE,
HOOKTHROUGH,
FREEZE,
UNFREEZE,
DEEP_FREEZE,
DEEP_UNFREEZE,
BLUE_CHECK_TELE,
RED_CHECK_TELE,
LIVE_FREEZE,
LIVE_UNFREEZE,
};
#endif

View file

@ -6,6 +6,7 @@
#include <engine/shared/map.h> #include <engine/shared/map.h>
#include <game/editor/editor.h> #include <game/editor/editor.h>
#include <game/editor/editor_actions.h> #include <game/editor/editor_actions.h>
#include <game/editor/enums.h>
#include <iterator> #include <iterator>
#include <numeric> #include <numeric>
@ -693,93 +694,126 @@ void CLayerTiles::ShowInfo()
Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1);
} }
CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) void CLayerTiles::FillGameTiles(EGameTileOp Fill)
{ {
CUIRect Button; if(!CanFillGameTiles())
return;
const bool EntitiesLayer = IsEntitiesLayer();
std::shared_ptr<CLayerGroup> pGroup = m_pEditor->m_Map.m_vpGroups[m_pEditor->m_SelectedGroup]; std::shared_ptr<CLayerGroup> pGroup = m_pEditor->m_Map.m_vpGroups[m_pEditor->m_SelectedGroup];
// Game tiles can only be constructed if the layer is relative to the game layer int Result = (int)Fill;
if(!EntitiesLayer && !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100) switch(Fill)
{ {
pToolBox->HSplitBottom(12.0f, pToolBox, &Button); case EGameTileOp::HOOKTHROUGH:
static int s_GameTilesButton = 0; Result = TILE_THROUGH_CUT;
if(m_pEditor->DoButton_Editor(&s_GameTilesButton, "Game tiles", 0, &Button, 0, "Constructs game tiles from this layer")) break;
m_pEditor->PopupSelectGametileOpInvoke(m_pEditor->Ui()->MouseX(), m_pEditor->Ui()->MouseY()); case EGameTileOp::FREEZE:
const int Selected = m_pEditor->PopupSelectGameTileOpResult(); Result = TILE_FREEZE;
int Result = Selected; break;
switch(Selected) case EGameTileOp::UNFREEZE:
Result = TILE_UNFREEZE;
break;
case EGameTileOp::DEEP_FREEZE:
Result = TILE_DFREEZE;
break;
case EGameTileOp::DEEP_UNFREEZE:
Result = TILE_DUNFREEZE;
break;
case EGameTileOp::BLUE_CHECK_TELE:
Result = TILE_TELECHECKIN;
break;
case EGameTileOp::RED_CHECK_TELE:
Result = TILE_TELECHECKINEVIL;
break;
case EGameTileOp::LIVE_FREEZE:
Result = TILE_LFREEZE;
break;
case EGameTileOp::LIVE_UNFREEZE:
Result = TILE_LUNFREEZE;
break;
default:
break;
}
if(Result > -1)
{
const int OffsetX = -pGroup->m_OffsetX / 32;
const int OffsetY = -pGroup->m_OffsetY / 32;
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)
{ {
case 4: if(pGLayer->m_Width < m_Width + OffsetX || pGLayer->m_Height < m_Height + OffsetY)
Result = TILE_THROUGH_CUT;
break;
case 5:
Result = TILE_FREEZE;
break;
case 6:
Result = TILE_UNFREEZE;
break;
case 7:
Result = TILE_DFREEZE;
break;
case 8:
Result = TILE_DUNFREEZE;
break;
case 9:
Result = TILE_TELECHECKIN;
break;
case 10:
Result = TILE_TELECHECKINEVIL;
break;
case 11:
Result = TILE_LFREEZE;
break;
case 12:
Result = TILE_LUNFREEZE;
break;
default:
break;
}
if(Result > -1)
{
const int OffsetX = -pGroup->m_OffsetX / 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(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 NewH = pGLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pGLayer->m_Height;
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 x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
{
if(GetTile(x, y).m_Index)
{
const CTile ResultTile = {(unsigned char)Result};
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)", g_apGametileOpNames[(int)Fill], Changes);
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay, true));
}
else
{
if(!m_pEditor->m_Map.m_pTeleLayer)
{
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.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; std::map<int, std::shared_ptr<CLayer>> savedLayers;
savedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate(); savedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate();
savedLayers[LAYERTYPE_GAME] = savedLayers[LAYERTYPE_TILES]; 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 PrevW = pGLayer->m_Width;
int PrevH = pGLayer->m_Height; int PrevH = pGLayer->m_Height;
const int NewW = pGLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pGLayer->m_Width; pLayer->Resize(NewW, NewH);
const int NewH = pGLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pGLayer->m_Height;
pGLayer->Resize(NewW, NewH);
vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); 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]); 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)); vpActions.push_back(std::make_shared<CEditorActionEditLayerTilesProp>(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH));
@ -788,124 +822,94 @@ CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
Action1->SetSavedLayers(savedLayers); Action1->SetSavedLayers(savedLayers);
Action2->SetSavedLayers(savedLayers); Action2->SetSavedLayers(savedLayers);
} }
int Changes = 0;
for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++)
{
for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
{
if(GetTile(x, y).m_Index)
{
const CTile ResultTile = {(unsigned char)Result};
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[Selected], Changes);
m_pEditor->m_EditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(m_pEditor, vpActions, aDisplay, true));
} }
else
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(!m_pEditor->m_Map.m_pTeleLayer) std::map<int, std::shared_ptr<CLayer>> savedLayers;
{ savedLayers[LAYERTYPE_TILES] = pTLayer->Duplicate();
std::shared_ptr<CLayerTele> pLayer = std::make_shared<CLayerTele>(m_pEditor, m_Width, m_Height); savedLayers[LAYERTYPE_TELE] = savedLayers[LAYERTYPE_TILES];
m_pEditor->m_Map.MakeTeleLayer(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)); 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 NewH = pTLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pTLayer->m_Height;
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));
if(m_Width != pGLayer->m_Width || m_Height > pGLayer->m_Height) Action1->SetSavedLayers(savedLayers);
{ Action2->SetSavedLayers(savedLayers);
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;
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)
{
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 NewH = pTLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pTLayer->m_Height;
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 x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
{
if(GetTile(x, y).m_Index)
{
auto TileIndex = (y + OffsetY) * pTLayer->m_Width + x + OffsetX;
Changes++;
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));
} }
int Changes = 0;
for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++)
{
for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
{
if(GetTile(x, y).m_Index)
{
auto TileIndex = (y + OffsetY) * pTLayer->m_Width + x + OffsetX;
Changes++;
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));
} }
} }
}
bool CLayerTiles::CanFillGameTiles() const
{
const bool EntitiesLayer = IsEntitiesLayer();
if(EntitiesLayer)
return false;
std::shared_ptr<CLayerGroup> pGroup = m_pEditor->m_Map.m_vpGroups[m_pEditor->m_SelectedGroup];
// Game tiles can only be constructed if the layer is relative to the game layer
return !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100;
}
CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
{
CUIRect Button;
const bool EntitiesLayer = IsEntitiesLayer();
if(CanFillGameTiles())
{
pToolBox->HSplitBottom(12.0f, pToolBox, &Button);
static int s_GameTilesButton = 0;
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());
const int Selected = m_pEditor->PopupSelectGameTileOpResult();
FillGameTiles((EGameTileOp)Selected);
}
if(m_pEditor->m_Map.m_pGameLayer.get() != this) if(m_pEditor->m_Map.m_pGameLayer.get() != this)
{ {

View file

@ -2,6 +2,7 @@
#define GAME_EDITOR_MAPITEMS_LAYER_TILES_H #define GAME_EDITOR_MAPITEMS_LAYER_TILES_H
#include <game/editor/editor_trackers.h> #include <game/editor/editor_trackers.h>
#include <game/editor/enums.h>
#include <map> #include <map>
#include "layer.h" #include "layer.h"
@ -122,6 +123,8 @@ public:
void BrushSelecting(CUIRect Rect) override; void BrushSelecting(CUIRect Rect) override;
int BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect) override; int BrushGrab(std::shared_ptr<CLayerGroup> pBrush, CUIRect Rect) 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;
void FillGameTiles(EGameTileOp Fill);
bool CanFillGameTiles() const;
void BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy) override; void BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy) override;
void BrushFlipX() override; void BrushFlipX() override;
void BrushFlipY() override; void BrushFlipY() override;

View file

@ -68,17 +68,9 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect Vie
View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View); View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_OpenCurrentMapButton, "Load Current Map", 0, &Slot, 0, "Opens the current in game map for editing (ctrl+alt+l)")) if(pEditor->DoButton_MenuItem(&s_OpenCurrentMapButton, pEditor->m_QuickActionLoadCurrentMap.Label(), 0, &Slot, 0, pEditor->m_QuickActionLoadCurrentMap.Description()))
{ {
if(pEditor->HasUnsavedData()) pEditor->m_QuickActionLoadCurrentMap.Call();
{
pEditor->m_PopupEventType = POPEVENT_LOADCURRENT;
pEditor->m_PopupEventActivated = true;
}
else
{
pEditor->LoadCurrentMap();
}
return CUi::POPUP_CLOSE_CURRENT; return CUi::POPUP_CLOSE_CURRENT;
} }
@ -107,9 +99,9 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect Vie
View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View); View.HSplitTop(12.0f, &Slot, &View);
if(pEditor->DoButton_MenuItem(&s_SaveAsButton, "Save As", 0, &Slot, 0, "Saves the current map under a new name (ctrl+shift+s)")) if(pEditor->DoButton_MenuItem(&s_SaveAsButton, pEditor->m_QuickActionSaveAs.Label(), 0, &Slot, 0, pEditor->m_QuickActionSaveAs.Description()))
{ {
pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", true, CEditor::CallbackSaveMap, pEditor); pEditor->m_QuickActionSaveAs.Call();
return CUi::POPUP_CLOSE_CURRENT; return CUi::POPUP_CLOSE_CURRENT;
} }
@ -314,20 +306,20 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect
static int s_ButtonOff = 0; static int s_ButtonOff = 0;
static int s_ButtonDec = 0; static int s_ButtonDec = 0;
static int s_ButtonHex = 0; static int s_ButtonHex = 0;
if(pEditor->DoButton_Ex(&s_ButtonOff, "Off", pEditor->m_ShowTileInfo == SHOW_TILE_OFF, &Off, 0, "Do not show tile information", IGraphics::CORNER_L)) CQuickAction *pAction = &pEditor->m_QuickActionShowInfoOff;
if(pEditor->DoButton_Ex(&s_ButtonOff, pAction->LabelShort(), pAction->Active(), &Off, 0, pAction->Description(), IGraphics::CORNER_L))
{ {
pEditor->m_ShowTileInfo = SHOW_TILE_OFF; pAction->Call();
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
} }
if(pEditor->DoButton_Ex(&s_ButtonDec, "Dec", pEditor->m_ShowTileInfo == SHOW_TILE_DECIMAL, &Dec, 0, "[ctrl+i] Show tile information", IGraphics::CORNER_NONE)) pAction = &pEditor->m_QuickActionShowInfoDec;
if(pEditor->DoButton_Ex(&s_ButtonDec, pAction->LabelShort(), pAction->Active(), &Dec, 0, pAction->Description(), IGraphics::CORNER_NONE))
{ {
pEditor->m_ShowTileInfo = SHOW_TILE_DECIMAL; pAction->Call();
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
} }
if(pEditor->DoButton_Ex(&s_ButtonHex, "Hex", pEditor->m_ShowTileInfo == SHOW_TILE_HEXADECIMAL, &Hex, 0, "[ctrl+shift+i] Show tile information in hexadecimal", IGraphics::CORNER_R)) pAction = &pEditor->m_QuickActionShowInfoHex;
if(pEditor->DoButton_Ex(&s_ButtonHex, pAction->LabelShort(), pAction->Active(), &Hex, 0, pAction->Description(), IGraphics::CORNER_R))
{ {
pEditor->m_ShowTileInfo = SHOW_TILE_HEXADECIMAL; pAction->Call();
pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
} }
} }
@ -578,16 +570,9 @@ CUi::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View,
// new tile layer // new tile layer
View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(5.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &Button); View.HSplitBottom(12.0f, &View, &Button);
static int s_NewTileLayerButton = 0; if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddTileLayer, pEditor->m_QuickActionAddTileLayer.Label(), 0, &Button, 0, pEditor->m_QuickActionAddTileLayer.Description()))
if(pEditor->DoButton_Editor(&s_NewTileLayerButton, "Add tile layer", 0, &Button, 0, "Creates a new tile layer"))
{ {
std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); pEditor->m_QuickActionAddTileLayer.Call();
pTileLayer->m_pEditor = pEditor;
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pTileLayer);
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_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor, pEditor->m_SelectedGroup, LayerIndex));
return CUi::POPUP_CLOSE_CURRENT; return CUi::POPUP_CLOSE_CURRENT;
} }

169
src/game/editor/prompt.cpp Normal file
View file

@ -0,0 +1,169 @@
#include <engine/keys.h>
#include <game/client/ui_listbox.h>
#include <game/editor/quick_action.h>
#include "editor.h"
#include "prompt.h"
bool FuzzyMatch(const char *pHaystack, const char *pNeedle)
{
if(!pNeedle || !pNeedle[0])
return false;
char aBuf[2] = {0};
const char *pHit = pHaystack;
int NeedleLen = str_length(pNeedle);
for(int i = 0; i < NeedleLen; i++)
{
if(!pHit)
return false;
aBuf[0] = pNeedle[i];
pHit = str_find_nocase(pHit, aBuf);
if(pHit)
pHit++;
}
return pHit;
}
bool CPrompt::IsActive()
{
return CEditorComponent::IsActive() || Editor()->m_Dialog == DIALOG_QUICK_PROMPT;
}
void CPrompt::SetActive()
{
Editor()->m_Dialog = DIALOG_QUICK_PROMPT;
CEditorComponent::SetActive();
Ui()->SetActiveItem(&m_PromptInput);
}
void CPrompt::SetInactive()
{
m_ResetFilterResults = true;
m_PromptInput.Clear();
if(Editor()->m_Dialog == DIALOG_QUICK_PROMPT)
Editor()->m_Dialog = DIALOG_NONE;
CEditorComponent::SetInactive();
}
bool CPrompt::OnInput(const IInput::CEvent &Event)
{
if(Input()->ModifierIsPressed() && Input()->KeyIsPressed(KEY_P))
{
SetActive();
}
return false;
}
void CPrompt::OnInit(CEditor *pEditor)
{
CEditorComponent::OnInit(pEditor);
#define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description) m_vQuickActions.emplace_back(&Editor()->m_QuickAction##name);
#include <game/editor/quick_actions.h>
#undef REGISTER_QUICK_ACTION
}
void CPrompt::OnRender(CUIRect _)
{
if(!IsActive())
return;
if(Ui()->ConsumeHotkey(CUi::HOTKEY_ESCAPE))
{
SetInactive();
return;
}
static CListBox s_ListBox;
CUIRect Prompt, PromptBox;
CUIRect Suggestions;
Ui()->MapScreen();
CUIRect Overlay = *Ui()->Screen();
Overlay.Draw(ColorRGBA(0, 0, 0, 0.33f), IGraphics::CORNER_NONE, 0.0f);
CUIRect Background;
Overlay.VMargin(150.0f, &Background);
Background.HMargin(50.0f, &Background);
Background.Draw(ColorRGBA(0, 0, 0, 0.80f), IGraphics::CORNER_ALL, 5.0f);
Background.Margin(10.0f, &Prompt);
Prompt.VSplitMid(nullptr, &PromptBox);
Prompt.HSplitTop(16.0f, &PromptBox, &Suggestions);
PromptBox.Draw(ColorRGBA(0, 0, 0, 0.75f), IGraphics::CORNER_ALL, 2.0f);
Suggestions.y += 6.0f;
if(Ui()->DoClearableEditBox(&m_PromptInput, &PromptBox, 10.0f) || m_ResetFilterResults)
{
m_PromptSelectedIndex = 0;
m_vpFilteredPromptList.clear();
if(m_ResetFilterResults && m_pLastAction && !m_pLastAction->Disabled())
{
m_vpFilteredPromptList.push_back(m_pLastAction);
}
for(auto *pQuickAction : m_vQuickActions)
{
if(pQuickAction->Disabled())
continue;
if(m_PromptInput.IsEmpty() || FuzzyMatch(pQuickAction->Label(), m_PromptInput.GetString()))
{
bool Skip = false;
if(m_ResetFilterResults)
if(pQuickAction == m_pLastAction)
Skip = true;
if(!Skip)
m_vpFilteredPromptList.push_back(pQuickAction);
}
}
m_ResetFilterResults = false;
}
s_ListBox.SetActive(!Ui()->IsPopupOpen());
s_ListBox.DoStart(15.0f, m_vpFilteredPromptList.size(), 1, 5, m_PromptSelectedIndex, &Suggestions, false);
for(size_t i = 0; i < m_vpFilteredPromptList.size(); i++)
{
const CListboxItem Item = s_ListBox.DoNextItem(m_vpFilteredPromptList[i], m_PromptSelectedIndex >= 0 && (size_t)m_PromptSelectedIndex == i);
if(!Item.m_Visible)
continue;
CUIRect LabelColumn, DescColumn;
Item.m_Rect.VSplitLeft(5.0f, nullptr, &LabelColumn);
LabelColumn.VSplitLeft(100.0f, &LabelColumn, &DescColumn);
LabelColumn.VSplitRight(5.0f, &LabelColumn, nullptr);
SLabelProperties Props;
Props.m_MaxWidth = LabelColumn.w;
Props.m_EllipsisAtEnd = true;
Ui()->DoLabel(&LabelColumn, m_vpFilteredPromptList[i]->Label(), 10.0f, TEXTALIGN_ML, Props);
Props.m_MaxWidth = DescColumn.w;
ColorRGBA DescColor = TextRender()->DefaultTextColor();
DescColor.a = Item.m_Selected ? 1.0f : 0.8f;
TextRender()->TextColor(DescColor);
Ui()->DoLabel(&DescColumn, m_vpFilteredPromptList[i]->Description(), 10.0f, TEXTALIGN_MR, Props);
TextRender()->TextColor(TextRender()->DefaultTextColor());
}
const int NewSelected = s_ListBox.DoEnd();
if(m_PromptSelectedIndex != NewSelected)
{
m_PromptSelectedIndex = NewSelected;
}
if(s_ListBox.WasItemActivated())
{
if(m_PromptSelectedIndex >= 0)
{
CQuickAction *pBtn = m_vpFilteredPromptList[m_PromptSelectedIndex];
SetInactive();
pBtn->Call();
m_pLastAction = pBtn;
}
}
}

29
src/game/editor/prompt.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef GAME_EDITOR_PROMPT_H
#define GAME_EDITOR_PROMPT_H
#include <game/client/lineinput.h>
#include <game/client/ui_rect.h>
#include <game/editor/quick_action.h>
#include "component.h"
class CPrompt : public CEditorComponent
{
bool m_ResetFilterResults = true;
CQuickAction *m_pLastAction = nullptr;
int m_PromptSelectedIndex = -1;
std::vector<CQuickAction *> m_vpFilteredPromptList;
std::vector<CQuickAction *> m_vQuickActions;
CLineInputBuffered<512> m_PromptInput;
public:
void OnInit(CEditor *pEditor) override;
bool OnInput(const IInput::CEvent &Event) override;
void OnRender(CUIRect _) override;
bool IsActive();
void SetActive();
void SetInactive();
};
#endif

View file

@ -0,0 +1,69 @@
#ifndef GAME_EDITOR_QUICK_ACTION_H
#define GAME_EDITOR_QUICK_ACTION_H
#include <functional>
#include <utility>
typedef std::function<void()> FButtonClickCallback;
typedef std::function<bool()> FButtonDisabledCallback;
typedef std::function<bool()> FButtonActiveCallback;
typedef std::function<int()> FButtonColorCallback;
class CQuickAction
{
private:
const char *m_pLabel;
const char *m_pDescription;
FButtonClickCallback m_pfnCallback;
FButtonDisabledCallback m_pfnDisabledCallback;
FButtonActiveCallback m_pfnActiveCallback;
FButtonColorCallback m_pfnColorCallback;
public:
CQuickAction(
const char *pLabel,
const char *pDescription,
FButtonClickCallback pfnCallback,
FButtonDisabledCallback pfnDisabledCallback,
FButtonActiveCallback pfnActiveCallback,
FButtonColorCallback pfnColorCallback) :
m_pLabel(pLabel),
m_pDescription(pDescription),
m_pfnCallback(std::move(pfnCallback)),
m_pfnDisabledCallback(std::move(pfnDisabledCallback)),
m_pfnActiveCallback(std::move(pfnActiveCallback)),
m_pfnColorCallback(std::move(pfnColorCallback))
{
}
// code to run when the action is triggered
void Call() const { m_pfnCallback(); }
// bool that indicates if the action can be performed not or not
bool Disabled() { return m_pfnDisabledCallback(); }
// bool that indicates if the action is currently running
// only applies to actions that can be turned on or off like proof borders
bool Active() { return m_pfnActiveCallback(); }
// color "enum" that represents the state of the quick actions button
// used as Checked argument for DoButton_Editor()
int Color() { return m_pfnColorCallback(); }
const char *Label() const { return m_pLabel; }
// skips to the part of the label after the first colon
// useful for buttons that only show the state
const char *LabelShort() const
{
const char *pShort = str_find(m_pLabel, ": ");
if(!pShort)
return m_pLabel;
return pShort + 2;
}
const char *Description() const { return m_pDescription; }
};
#endif

View file

@ -0,0 +1,71 @@
#include <game/mapitems.h>
#include "editor.h"
#include "editor_actions.h"
void CEditor::FillGameTiles(EGameTileOp FillTile) const
{
std::shared_ptr<CLayerTiles> pTileLayer = std::static_pointer_cast<CLayerTiles>(GetSelectedLayerType(0, LAYERTYPE_TILES));
if(pTileLayer)
pTileLayer->FillGameTiles(FillTile);
}
bool CEditor::CanFillGameTiles() const
{
std::shared_ptr<CLayerTiles> pTileLayer = std::static_pointer_cast<CLayerTiles>(GetSelectedLayerType(0, LAYERTYPE_TILES));
if(pTileLayer)
return pTileLayer->CanFillGameTiles();
return false;
}
void CEditor::AddGroup()
{
m_Map.NewGroup();
m_SelectedGroup = m_Map.m_vpGroups.size() - 1;
m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(this, m_SelectedGroup, false));
}
void CEditor::AddTileLayer()
{
std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(this, m_Map.m_pGameLayer->m_Width, m_Map.m_pGameLayer->m_Height);
pTileLayer->m_pEditor = this;
m_Map.m_vpGroups[m_SelectedGroup]->AddLayer(pTileLayer);
int LayerIndex = m_Map.m_vpGroups[m_SelectedGroup]->m_vpLayers.size() - 1;
SelectLayer(LayerIndex);
m_Map.m_vpGroups[m_SelectedGroup]->m_Collapse = false;
m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(this, m_SelectedGroup, LayerIndex));
}
bool CEditor::IsNonGameTileLayerSelected() const
{
std::shared_ptr<CLayer> pLayer = GetSelectedLayer(0);
if(!pLayer)
return false;
if(pLayer->m_Type != LAYERTYPE_TILES)
return false;
if(
pLayer == m_Map.m_pGameLayer ||
pLayer == m_Map.m_pFrontLayer ||
pLayer == m_Map.m_pSwitchLayer ||
pLayer == m_Map.m_pTeleLayer ||
pLayer == m_Map.m_pSpeedupLayer ||
pLayer == m_Map.m_pTuneLayer)
return false;
return true;
}
void CEditor::LayerSelectImage()
{
if(!IsNonGameTileLayerSelected())
return;
std::shared_ptr<CLayer> pLayer = GetSelectedLayer(0);
std::shared_ptr<CLayerTiles> pTiles = std::static_pointer_cast<CLayerTiles>(pLayer);
static SLayerPopupContext s_LayerPopupContext = {};
s_LayerPopupContext.m_pEditor = this;
Ui()->DoPopupMenu(&s_LayerPopupContext, Ui()->MouseX(), Ui()->MouseY(), 120, 270, &s_LayerPopupContext, PopupLayer);
PopupSelectImageInvoke(pTiles->m_Image, Ui()->MouseX(), Ui()->MouseY());
}

View file

@ -0,0 +1,213 @@
// This file can be included several times.
#ifndef REGISTER_QUICK_ACTION
#define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description)
#endif
#define ALWAYS_FALSE []() -> bool { return false; }
#define DEFAULT_BTN []() -> int { return -1; }
REGISTER_QUICK_ACTION(
GameTilesAir,
"Game tiles: Air",
[&]() { FillGameTiles(EGameTileOp::AIR); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesHookable,
"Game tiles: Hookable",
[&]() { FillGameTiles(EGameTileOp::HOOKABLE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesDeath,
"Game tiles: Death",
[&]() { FillGameTiles(EGameTileOp::DEATH); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesUnhookable,
"Game tiles: Unhookable",
[&]() { FillGameTiles(EGameTileOp::UNHOOKABLE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesHookthrough,
"Game tiles: Hookthrough",
[&]() { FillGameTiles(EGameTileOp::HOOKTHROUGH); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesFreeze,
"Game tiles: Freeze",
[&]() { FillGameTiles(EGameTileOp::FREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesUnfreeze,
"Game tiles: Unfreeze",
[&]() { FillGameTiles(EGameTileOp::UNFREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesDeepFreeze,
"Game tiles: Deep Freeze",
[&]() { FillGameTiles(EGameTileOp::DEEP_FREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesDeepUnfreeze,
"Game tiles: Deep Unfreeze",
[&]() { FillGameTiles(EGameTileOp::DEEP_UNFREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesBlueCheckTele,
"Game tiles: Blue Check Tele",
[&]() { FillGameTiles(EGameTileOp::BLUE_CHECK_TELE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesRedCheckTele,
"Game tiles: Red Check Tele",
[&]() { FillGameTiles(EGameTileOp::RED_CHECK_TELE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesLiveFreeze,
"Game tiles: Live Freeze",
[&]() { FillGameTiles(EGameTileOp::LIVE_FREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
GameTilesLiveUnfreeze,
"Game tiles: Live Unfreeze",
[&]() { FillGameTiles(EGameTileOp::LIVE_UNFREEZE); },
[&]() -> bool { return !CanFillGameTiles(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"")
REGISTER_QUICK_ACTION(
AddGroup, "Add group", [&]() { AddGroup(); }, ALWAYS_FALSE, ALWAYS_FALSE, DEFAULT_BTN, "Adds a new group")
REGISTER_QUICK_ACTION(
Refocus, "Refocus", [&]() { MapView()->Focus(); }, ALWAYS_FALSE, ALWAYS_FALSE, DEFAULT_BTN, "[HOME] Restore map focus")
REGISTER_QUICK_ACTION(
Proof,
"Proof",
[&]() { MapView()->ProofMode()->Toggle(); },
ALWAYS_FALSE,
[&]() -> bool { return MapView()->ProofMode()->IsEnabled(); },
DEFAULT_BTN,
"Toggles proof borders. These borders represent the area that a player can see with default zoom.")
REGISTER_QUICK_ACTION(
AddTileLayer, "Add tile layer", [&]() { AddTileLayer(); }, ALWAYS_FALSE, ALWAYS_FALSE, DEFAULT_BTN, "Creates a new tile layer.")
REGISTER_QUICK_ACTION(
SaveAs,
"Save As",
[&]() { InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save As", "maps", true, CEditor::CallbackSaveMap, this); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"Saves the current map under a new name (ctrl+shift+s)")
REGISTER_QUICK_ACTION(
LoadCurrentMap,
"Load Current Map",
[&]() {
if(HasUnsavedData())
{
m_PopupEventType = POPEVENT_LOADCURRENT;
m_PopupEventActivated = true;
}
else
{
LoadCurrentMap();
}
},
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"Opens the current in game map for editing (ctrl+alt+l)")
REGISTER_QUICK_ACTION(
Envelopes,
"Envelopes",
[&]() { m_ActiveExtraEditor = m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES ? EXTRAEDITOR_NONE : EXTRAEDITOR_ENVELOPES; },
ALWAYS_FALSE,
ALWAYS_FALSE,
[&]() -> int { return m_ShowPicker ? -1 : m_ActiveExtraEditor == EXTRAEDITOR_ENVELOPES; },
"Toggles the envelope editor.")
REGISTER_QUICK_ACTION(
AddImage,
"Add Image",
[&]() { InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", false, AddImage, this); },
ALWAYS_FALSE,
ALWAYS_FALSE,
DEFAULT_BTN,
"Load a new image to use in the map")
REGISTER_QUICK_ACTION(
LayerPropAddImage,
"Layer: Add Image",
[&]() { LayerSelectImage(); },
[&]() -> bool { return !IsNonGameTileLayerSelected(); },
ALWAYS_FALSE,
DEFAULT_BTN,
"Pick mapres image for currently selected layer")
REGISTER_QUICK_ACTION(
ShowInfoOff,
"Show Info: Off",
[&]() {
m_ShowTileInfo = SHOW_TILE_OFF;
m_ShowEnvelopePreview = SHOWENV_NONE;
},
ALWAYS_FALSE,
[&]() -> bool { return m_ShowTileInfo == SHOW_TILE_OFF; },
DEFAULT_BTN,
"Do not show tile information")
REGISTER_QUICK_ACTION(
ShowInfoDec,
"Show Info: Dec",
[&]() {
m_ShowTileInfo = SHOW_TILE_DECIMAL;
m_ShowEnvelopePreview = SHOWENV_NONE;
},
ALWAYS_FALSE,
[&]() -> bool { return m_ShowTileInfo == SHOW_TILE_DECIMAL; },
DEFAULT_BTN,
"[ctrl+i] Show tile information")
REGISTER_QUICK_ACTION(
ShowInfoHex,
"Show Info: Hex",
[&]() {
m_ShowTileInfo = SHOW_TILE_HEXADECIMAL;
m_ShowEnvelopePreview = SHOWENV_NONE;
},
ALWAYS_FALSE,
[&]() -> bool { return m_ShowTileInfo == SHOW_TILE_HEXADECIMAL; },
DEFAULT_BTN,
"[ctrl+shift+i] Show tile information in hexadecimal")
#undef ALWAYS_FALSE
#undef DEFAULT_BTN

View file

@ -260,7 +260,7 @@ void CCharacter::HandleNinja()
GameServer()->CreateDamageInd(m_Pos, 0, NinjaTime / Server()->TickSpeed(), TeamMask() & GameServer()->ClientsMaskExcludeClientVersionAndHigher(VERSION_DDNET_NEW_HUD)); GameServer()->CreateDamageInd(m_Pos, 0, NinjaTime / Server()->TickSpeed(), TeamMask() & GameServer()->ClientsMaskExcludeClientVersionAndHigher(VERSION_DDNET_NEW_HUD));
} }
m_Armor = clamp(10 - (NinjaTime / 15), 0, 10); GameServer()->m_pController->SetArmorProgress(this, NinjaTime);
// force ninja Weapon // force ninja Weapon
SetWeapon(WEAPON_NINJA); SetWeapon(WEAPON_NINJA);
@ -442,7 +442,7 @@ void CCharacter::FireWeapon()
if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1)) if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1))
{ {
m_PainSoundTimer = 1 * Server()->TickSpeed(); m_PainSoundTimer = 1 * Server()->TickSpeed();
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
} }
return; return;
} }
@ -459,7 +459,7 @@ void CCharacter::FireWeapon()
{ {
// reset objects Hit // reset objects Hit
m_NumObjectsHit = 0; m_NumObjectsHit = 0;
GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
Antibot()->OnHammerFire(m_pPlayer->GetCid()); Antibot()->OnHammerFire(m_pPlayer->GetCid());
@ -567,7 +567,7 @@ void CCharacter::FireWeapon()
MouseTarget // MouseTarget MouseTarget // MouseTarget
); );
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
} }
break; break;
@ -589,7 +589,7 @@ void CCharacter::FireWeapon()
m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000; m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000;
m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel); m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel);
GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, TeamMask()); GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
} }
break; break;
} }
@ -2055,7 +2055,7 @@ void CCharacter::ForceSetRescue(int RescueMode)
void CCharacter::DDRaceTick() void CCharacter::DDRaceTick()
{ {
mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input)); mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input));
m_Armor = clamp(10 - (m_FreezeTime / 15), 0, 10); GameServer()->m_pController->SetArmorProgress(this, m_FreezeTime);
if(m_Input.m_Direction != 0 || m_Input.m_Jump != 0) if(m_Input.m_Direction != 0 || m_Input.m_Jump != 0)
m_LastMove = Server()->Tick(); m_LastMove = Server()->Tick();

View file

@ -94,6 +94,7 @@ bool CLaser::HitCharacter(vec2 From, vec2 To)
{ {
pHit->UnFreeze(); pHit->UnFreeze();
} }
pHit->TakeDamage(vec2(0, 0), 0, m_Owner, m_Type);
return true; return true;
} }

View file

@ -177,6 +177,8 @@ void CProjectile::Tick()
pChr->Freeze(); pChr->Freeze();
} }
} }
else if(pTargetChr)
pTargetChr->TakeDamage(vec2(0, 0), 0, m_Owner, m_Type);
if(pOwnerChar && !GameLayerClipped(ColPos) && if(pOwnerChar && !GameLayerClipped(ColPos) &&
((m_Type == WEAPON_GRENADE && pOwnerChar->HasTelegunGrenade()) || (m_Type == WEAPON_GUN && pOwnerChar->HasTelegunGun()))) ((m_Type == WEAPON_GRENADE && pOwnerChar->HasTelegunGrenade()) || (m_Type == WEAPON_GUN && pOwnerChar->HasTelegunGun())))

View file

@ -95,6 +95,7 @@ public:
virtual void OnCharacterSpawn(class CCharacter *pChr); virtual void OnCharacterSpawn(class CCharacter *pChr);
virtual void HandleCharacterTiles(class CCharacter *pChr, int MapIndex); virtual void HandleCharacterTiles(class CCharacter *pChr, int MapIndex);
virtual void SetArmorProgress(CCharacter *pCharacer, int Progress){};
/* /*
Function: OnEntity Function: OnEntity

View file

@ -112,6 +112,11 @@ void CGameControllerDDRace::HandleCharacterTiles(CCharacter *pChr, int MapIndex)
} }
} }
void CGameControllerDDRace::SetArmorProgress(CCharacter *pCharacer, int Progress)
{
pCharacer->SetArmor(clamp(10 - (Progress / 15), 0, 10));
}
void CGameControllerDDRace::OnPlayerConnect(CPlayer *pPlayer) void CGameControllerDDRace::OnPlayerConnect(CPlayer *pPlayer)
{ {
IGameController::OnPlayerConnect(pPlayer); IGameController::OnPlayerConnect(pPlayer);

View file

@ -13,6 +13,7 @@ public:
CScore *Score(); CScore *Score();
void HandleCharacterTiles(class CCharacter *pChr, int MapIndex) override; void HandleCharacterTiles(class CCharacter *pChr, int MapIndex) override;
void SetArmorProgress(CCharacter *pCharacer, int Progress) override;
void OnPlayerConnect(class CPlayer *pPlayer) override; void OnPlayerConnect(class CPlayer *pPlayer) override;
void OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason) override; void OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason) override;

View file

@ -78,16 +78,16 @@ void CTeeInfo::ToSixup()
{ {
int ColorBody = ColorHSLA(m_ColorBody).UnclampLighting().Pack(ms_DarkestLGT7); int ColorBody = ColorHSLA(m_ColorBody).UnclampLighting().Pack(ms_DarkestLGT7);
int ColorFeet = ColorHSLA(m_ColorFeet).UnclampLighting().Pack(ms_DarkestLGT7); int ColorFeet = ColorHSLA(m_ColorFeet).UnclampLighting().Pack(ms_DarkestLGT7);
m_aUseCustomColors[0] = true; m_aUseCustomColors[protocol7::SKINPART_BODY] = true;
m_aUseCustomColors[1] = true; m_aUseCustomColors[protocol7::SKINPART_MARKING] = true;
m_aUseCustomColors[2] = true; m_aUseCustomColors[protocol7::SKINPART_DECORATION] = true;
m_aUseCustomColors[3] = true; m_aUseCustomColors[protocol7::SKINPART_HANDS] = true;
m_aUseCustomColors[4] = true; m_aUseCustomColors[protocol7::SKINPART_FEET] = true;
m_aSkinPartColors[0] = ColorBody; m_aSkinPartColors[protocol7::SKINPART_BODY] = ColorBody;
m_aSkinPartColors[1] = 0x22FFFFFF; m_aSkinPartColors[protocol7::SKINPART_MARKING] = 0x22FFFFFF;
m_aSkinPartColors[2] = ColorBody; m_aSkinPartColors[protocol7::SKINPART_DECORATION] = ColorBody;
m_aSkinPartColors[3] = ColorBody; m_aSkinPartColors[protocol7::SKINPART_HANDS] = ColorBody;
m_aSkinPartColors[4] = ColorFeet; m_aSkinPartColors[protocol7::SKINPART_FEET] = ColorFeet;
} }
} }
@ -137,6 +137,10 @@ void CTeeInfo::FromSixup()
str_copy(m_aSkinName, g_aStdSkins[BestSkin].m_aSkinName, sizeof(m_aSkinName)); str_copy(m_aSkinName, g_aStdSkins[BestSkin].m_aSkinName, sizeof(m_aSkinName));
m_UseCustomColor = true; m_UseCustomColor = true;
m_ColorBody = ColorHSLA(m_aUseCustomColors[0] ? m_aSkinPartColors[0] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT); m_ColorBody = ColorHSLA(m_aUseCustomColors[protocol7::SKINPART_BODY] ? m_aSkinPartColors[protocol7::SKINPART_BODY] : 255)
m_ColorFeet = ColorHSLA(m_aUseCustomColors[4] ? m_aSkinPartColors[4] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT); .UnclampLighting(ms_DarkestLGT7)
.Pack(ColorHSLA::DARKEST_LGT);
m_ColorFeet = ColorHSLA(m_aUseCustomColors[protocol7::SKINPART_FEET] ? m_aSkinPartColors[protocol7::SKINPART_FEET] : 255)
.UnclampLighting(ms_DarkestLGT7)
.Pack(ColorHSLA::DARKEST_LGT);
} }

View file

@ -109,8 +109,8 @@ void cxxbridge1$IConsole_IResult$GetString(const ::IConsole_IResult &self, ::std
} }
void cxxbridge1$IConsole_IResult$GetColor(const ::IConsole_IResult &self, ::std::uint32_t Index, bool Light, ::ColorHSLA *return$) noexcept { void cxxbridge1$IConsole_IResult$GetColor(const ::IConsole_IResult &self, ::std::uint32_t Index, bool Light, ::ColorHSLA *return$) noexcept {
::ColorHSLA (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor; std::optional<::ColorHSLA> (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor;
new (return$) ::ColorHSLA((self.*GetColor$)(Index, Light)); new(return$)::ColorHSLA((self.*GetColor$)(Index, Light).value_or(::ColorHSLA(0, 0, 0)));
} }
::std::int32_t cxxbridge1$IConsole_IResult$NumArguments(const ::IConsole_IResult &self) noexcept { ::std::int32_t cxxbridge1$IConsole_IResult$NumArguments(const ::IConsole_IResult &self) noexcept {