diff --git a/CMakeLists.txt b/CMakeLists.txt index 907417532..0fff61f16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2462,6 +2462,7 @@ if(CLIENT) editor_trackers.cpp editor_trackers.h editor_ui.h + enums.h explanations.cpp layer_selector.cpp layer_selector.h @@ -2500,8 +2501,13 @@ if(CLIENT) mapitems/sound.cpp mapitems/sound.h popups.cpp + prompt.cpp + prompt.h proof_mode.cpp proof_mode.h + quick_action.h + quick_actions.cpp + quick_actions.h smooth_value.cpp smooth_value.h tileart.cpp diff --git a/data/languages/russian.txt b/data/languages/russian.txt index 896b15e92..5f2a9f2cd 100644 --- a/data/languages/russian.txt +++ b/data/languages/russian.txt @@ -631,7 +631,7 @@ Loading DDNet Client == Загрузка DDNet Client Normal message -== Обычное с. +== Обычное Connecting dummy == Подключение дамми @@ -643,10 +643,10 @@ Save ghost == Сохранять тень DDNet Client updated! -== DDNet Client обновлён! +== DDNet клиент обновлён! Highlighted message -== Выделенное с. +== Выделенное Demo == Демо @@ -724,7 +724,7 @@ Downloading %s: == Скачивание %s: Update failed! Check log… -== Ошибка. Проверьте логи… +== Не удалось обновиться! Подробности в логах… Restart == Рестарт @@ -844,7 +844,7 @@ DDNet == DDNet Friend message -== Дружеское с. +== Дружеское Save the best demo of each race == Сохранять лучшее демо каждой карты @@ -916,10 +916,10 @@ Ratio == Соотношение Net -== Сеть +== Сальдо FPM -== FPM +== У/мин Spree == Серия @@ -940,7 +940,7 @@ Grabs == 9+ упоминаний Client message -== Клиентское с. +== Клиентское Warning == Предупреждение @@ -973,7 +973,7 @@ Run server == Запустить сервер Server executable not found, can't run server -== Файл сервера не найден, невозможно запустить +== Файл сервера не найден, не удалось запустить сервер Editor == Редактор @@ -1883,7 +1883,7 @@ Could not resolve connect address '%s'. See local console for details. == Не удалось определить адрес подключения '%s'. Подробности в локальной консоли. Connect address error -== Ошибка подключения сервера +== Ошибка адреса подключения Could not connect dummy == Невозможно подключить дамми @@ -1892,34 +1892,34 @@ Dummy is not allowed on this server == Дамми не разрешен на этом сервере Please wait… -== Пожалуйста подождите… +== Пожалуйста, подождите… Show client IDs (scoreboard, chat, spectator) -== Показывать ID клиента (Табло, чат, наблюдатель) +== Показывать ID клиента (табло, чат, наблюдатель) Normal: == Обычный: Team: -== Команда: +== В команде: Dummy settings == Настройки дамми Toggle to edit your dummy settings -== Нажмите чтоб изменить настройки дамми +== Нажмите, чтобы изменить настройки дамми Randomize -== Перемешать +== Случайный Are you sure that you want to delete '%s'? -== Вы уверены что хотите удалить '%s'? +== Вы уверены, что хотите удалить '%s'? Delete skin == Удалить скин Basic -== Базовый +== Пресеты Custom == Кастомизация diff --git a/data/languages/swedish.txt b/data/languages/swedish.txt index d6ea9d722..e66a87f21 100644 --- a/data/languages/swedish.txt +++ b/data/languages/swedish.txt @@ -7,7 +7,7 @@ # 3edcxzaq1 2020-06-25 00:00:00 # cur.ie 2020-09-28 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 ##### ##### translated strings ##### @@ -1868,52 +1868,52 @@ https://wiki.ddnet.org/wiki/Mapping == https://wiki.ddnet.org/wiki/Mapping 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 -== +== Anslutnings problem Could not connect dummy -== +== Kunde inte ansluta dummy Dummy is not allowed on this server -== +== Dummy är inte tillåten på denna server Please wait… -== +== Vänligen vänta… Show client IDs (scoreboard, chat, spectator) -== +== Visa klient IDen (poänglistan, chatt, åskadarmeny) Normal: -== +== Normal: Team: -== +== Lag: Dummy settings -== +== Dummy inställningar Toggle to edit your dummy settings -== +== Växla för att ändra dina dummy inställningar Randomize -== +== Slumpa Are you sure that you want to delete '%s'? -== +== Är du säker att du vill ta bort '%s'? Delete skin -== +== Ta bort skin Basic -== +== Enkel Custom -== +== Anpassa Unable to delete skin -== +== Kunde inte ta bort skin Customize -== +== Ändra diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 9c9881b12..6813cde9f 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -228,10 +228,8 @@ int CSound::Init() return -1; } - m_MixingRate = g_Config.m_SndRate; - SDL_AudioSpec Format, FormatOut; - Format.freq = m_MixingRate; + Format.freq = g_Config.m_SndRate; Format.format = AUDIO_S16; Format.channels = 2; Format.samples = g_Config.m_SndBufferSize; @@ -239,7 +237,7 @@ int CSound::Init() Format.userdata = this; // 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) { dbg_msg("sound", "unable to open audio: %s", SDL_GetError()); @@ -248,6 +246,7 @@ int CSound::Init() else dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver()); + m_MixingRate = FormatOut.freq; m_MaxFrames = FormatOut.samples * 2; #if defined(CONF_VIDEORECORDER) m_MaxFrames = maximum(m_MaxFrames, 1024 * 2); // make the buffer bigger just in case diff --git a/src/engine/console.h b/src/engine/console.h index 0c04678eb..ce79012d8 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -53,7 +53,7 @@ public: virtual int GetInteger(unsigned Index) const = 0; virtual float GetFloat(unsigned Index) const = 0; virtual const char *GetString(unsigned Index) const = 0; - virtual ColorHSLA GetColor(unsigned Index, bool Light) const = 0; + virtual std::optional GetColor(unsigned Index, bool Light) const = 0; virtual void RemoveArgument(unsigned Index) = 0; diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp index 1d78696e3..6c71c4b11 100644 --- a/src/engine/shared/config.cpp +++ b/src/engine/shared/config.cpp @@ -111,22 +111,29 @@ void SIntConfigVariable::ResetToOld() void SColorConfigVariable::CommandCallback(IConsole::IResult *pResult, void *pUserData) { SColorConfigVariable *pData = static_cast(pUserData); - + char aBuf[IConsole::CMDLINE_LENGTH + 64]; if(pResult->NumArguments()) { if(pData->CheckReadOnly()) return; - const ColorHSLA Color = pResult->GetColor(0, pData->m_Light); - const unsigned Value = Color.Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha); + const auto Color = pResult->GetColor(0, pData->m_Light); + if(Color) + { + const unsigned Value = Color->Pack(pData->m_Light ? 0.5f : 0.0f, pData->m_Alpha); - *pData->m_pVariable = Value; - if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME) - pData->m_OldValue = Value; + *pData->m_pVariable = Value; + if(pResult->m_ClientId != IConsole::CLIENT_ID_GAME) + 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 { - char aBuf[256]; str_format(aBuf, sizeof(aBuf), "Value: %u", *pData->m_pVariable); 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(pVariable); 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); - pColorVariable->SetValue(Value.Pack(Darkest, pColorVariable->m_Alpha)); + const std::optional 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.value_or(ColorHSLA(0, 0, 0)).Pack(Darkest, pColorVariable->m_Alpha)); } else if(pVariable->m_Type == SConfigVariable::VAR_STRING) { diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 29610d796..7c8a84ad6 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -40,22 +40,29 @@ float CConsole::CResult::GetFloat(unsigned Index) const return str_tofloat(m_apArgs[Index]); } -ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const +std::optional CConsole::CResult::GetColor(unsigned Index, bool Light) const { if(Index >= m_NumArgs) - return ColorHSLA(0, 0, 0); + return std::nullopt; const char *pStr = m_apArgs[Index]; 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::max()) + return std::nullopt; + const ColorHSLA Hsla = ColorHSLA(Value, true); if(Light) return Hsla.UnclampLighting(); return Hsla; } else if(*pStr == '$') // Hex RGB/RGBA { - return color_cast(color_parse(pStr + 1).value_or(ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f))); + auto ParsedColor = color_parse(pStr + 1); + if(ParsedColor) + return color_cast(ParsedColor.value()); + else + return std::nullopt; } else if(!str_comp_nocase(pStr, "red")) 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")) return ColorHSLA(0, 0, 0); - return ColorHSLA(0, 0, 0); + return std::nullopt; } 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; } -int CConsole::ParseArgs(CResult *pResult, const char *pFormat) +int CConsole::ParseArgs(CResult *pResult, const char *pFormat, FCommandCallback pfnCallback) { char Command = *pFormat; char *pStr; int Optional = 0; - int Error = 0; + int Error = PARSEARGS_OK; pResult->ResetVictim(); @@ -155,7 +162,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat) { if(!Optional) { - Error = 1; + Error = PARSEARGS_MISSING_VALUE; break; } @@ -191,7 +198,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat) pStr++; // skip due to escape } else if(pStr[0] == 0) - return 1; // return error + return PARSEARGS_MISSING_VALUE; // return error *pDst = *pStr; pDst++; @@ -215,13 +222,7 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat) if(Command == 'r') // rest of the string break; - else if(Command == 'v') // validate victim - 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 + else if(Command == 'v' || Command == 'i' || Command == 'f' || Command == 's') pStr = str_skip_to_whitespace(pStr); if(pStr[0] != 0) // check for end of string @@ -230,6 +231,32 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat) 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::max() || Value == std::numeric_limits::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::max() || Value == std::numeric_limits::min()) + { + Error = PARSEARGS_INVALID_FLOAT; + break; + } + } + if(pVictim) { pResult->SetVictim(pVictim); @@ -487,10 +514,15 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientId, bo 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]; - str_format(aBuf, sizeof(aBuf), "Invalid arguments. Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); + char aBuf[CMDLINE_LENGTH + 64]; + 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); } else if(m_StoreCommands && pCommand->m_Flags & CFGFLAG_STORE) diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 8d8a85231..c3779c6a2 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -115,7 +115,7 @@ class CConsole : public IConsole const char *GetString(unsigned Index) const override; int GetInteger(unsigned Index) const override; float GetFloat(unsigned Index) const override; - ColorHSLA GetColor(unsigned Index, bool Light) const override; + std::optional GetColor(unsigned Index, bool Light) const override; void RemoveArgument(unsigned Index) override { @@ -144,7 +144,16 @@ class CConsole : public IConsole }; 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 diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index ea6a8865e..978716bbf 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1074,11 +1074,9 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) // proof button TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); - static int s_ProofButton = 0; - 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)) + if(DoButton_Ex(&m_QuickActionProof, m_QuickActionProof.Label(), m_QuickActionProof.Active(), &Button, 0, m_QuickActionProof.Description(), IGraphics::CORNER_L)) { - MapView()->ProofMode()->Toggle(); + m_QuickActionProof.Call(); } TB_Top.VSplitLeft(14.0f, &Button, &TB_Top); @@ -1254,10 +1252,9 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) // refocus button { TB_Bottom.VSplitLeft(50.0f, &Button, &TB_Bottom); - static int s_RefocusButton = 0; 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))) - MapView()->Focus(); + 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))) + m_QuickActionRefocus.Call(); TB_Bottom.VSplitLeft(5.0f, nullptr, &TB_Bottom); } @@ -4302,12 +4299,9 @@ void CEditor::RenderLayers(CUIRect LayersBox) if(s_ScrollRegion.AddRect(AddGroupButton)) { AddGroupButton.HSplitTop(RowHeight, &AddGroupButton, 0); - static int s_AddGroupButton = 0; - if(DoButton_Editor(&s_AddGroupButton, "Add group", 0, &AddGroupButton, IGraphics::CORNER_R, "Adds a new group")) + if(DoButton_Editor(&m_QuickActionAddGroup, m_QuickActionAddGroup.Label(), 0, &AddGroupButton, IGraphics::CORNER_R, m_QuickActionAddGroup.Description())) { - m_Map.NewGroup(); - m_SelectedGroup = m_Map.m_vpGroups.size() - 1; - m_EditorHistory.RecordAction(std::make_shared(this, m_SelectedGroup, false)); + m_QuickActionAddGroup.Call(); } } @@ -4806,8 +4800,8 @@ void CEditor::RenderImagesList(CUIRect ToolBox) { AddImageButton.HSplitTop(5.0f, nullptr, &AddImageButton); AddImageButton.HSplitTop(RowHeight, &AddImageButton, nullptr); - if(DoButton_Editor(&s_AddImageButton, "Add", 0, &AddImageButton, 0, "Load a new image to use in the map")) - InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add Image", "Add", "mapres", false, AddImage, this); + if(DoButton_Editor(&s_AddImageButton, m_QuickActionAddImage.Label(), 0, &AddImageButton, 0, m_QuickActionAddImage.Description())) + m_QuickActionAddImage.Call(); } s_ScrollRegion.End(); } @@ -5750,9 +5744,9 @@ void CEditor::RenderStatusbar(CUIRect View, CUIRect *pTooltipRect) CUIRect Button; View.VSplitRight(100.0f, &View, &Button); 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); @@ -7919,7 +7913,7 @@ void CEditor::Render() InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", true, CallbackSaveCopyMap, this); // ctrl+shift+s to save as 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 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_MapSettingsBackend); m_vComponents.emplace_back(m_LayerSelector); + m_vComponents.emplace_back(m_Prompt); for(CEditorComponent &Component : m_vComponents) Component.OnInit(this); diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index bead2c217..6206cea25 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,8 @@ #include "layer_selector.h" #include "map_view.h" #include "smooth_value.h" +#include +#include #include #include @@ -60,7 +63,8 @@ enum DIALOG_NONE = 0, DIALOG_FILE, - DIALOG_MAPSETTINGS_ERROR + DIALOG_MAPSETTINGS_ERROR, + DIALOG_QUICK_PROMPT, }; class CEditorImage; @@ -278,6 +282,7 @@ class CEditor : public IEditor std::vector> m_vComponents; CMapView m_MapView; CLayerSelector m_LayerSelector; + CPrompt m_Prompt; bool m_EditorWasUsedBefore = false; @@ -319,7 +324,20 @@ public: const CMapView *MapView() const { return &m_MapView; } 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 +#undef REGISTER_QUICK_ACTION + CEditor() : +#define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description) m_QuickAction##name(text, description, callback, disabled, active, button_color), +#include +#undef REGISTER_QUICK_ACTION m_ZoomEnvelopeX(1.0f, 0.1f, 600.0f), m_ZoomEnvelopeY(640.0f, 0.1f, 32000.0f), m_MapSettingsCommandContext(m_MapSettingsBackend.NewContext(&m_SettingsCommandInput)) diff --git a/src/game/editor/enums.h b/src/game/editor/enums.h new file mode 100644 index 000000000..d14679b95 --- /dev/null +++ b/src/game/editor/enums.h @@ -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 diff --git a/src/game/editor/mapitems/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp index 380af7d8f..665a0fec9 100644 --- a/src/game/editor/mapitems/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -693,93 +694,126 @@ void CLayerTiles::ShowInfo() Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } -CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) +void CLayerTiles::FillGameTiles(EGameTileOp Fill) { - CUIRect Button; - - const bool EntitiesLayer = IsEntitiesLayer(); + if(!CanFillGameTiles()) + return; std::shared_ptr 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 - if(!EntitiesLayer && !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100) + int Result = (int)Fill; + switch(Fill) { - 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(); - int Result = Selected; - switch(Selected) + case EGameTileOp::HOOKTHROUGH: + Result = TILE_THROUGH_CUT; + break; + case EGameTileOp::FREEZE: + Result = TILE_FREEZE; + break; + 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> vpActions; + std::shared_ptr 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: - 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> vpActions; - std::shared_ptr 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) { - if(pGLayer->m_Width < m_Width + OffsetX || pGLayer->m_Height < m_Height + OffsetY) + std::map> 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(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); + const std::shared_ptr &Action1 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); + vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH)); + const std::shared_ptr &Action2 = std::static_pointer_cast(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(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(m_pEditor, vpActions, aDisplay, true)); + } + else + { + if(!m_pEditor->m_Map.m_pTeleLayer) + { + std::shared_ptr pLayer = std::make_shared(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(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> 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; - 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); + pLayer->Resize(NewW, NewH); vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); const std::shared_ptr &Action1 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); vpActions.push_back(std::make_shared(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); 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(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(m_pEditor, vpActions, aDisplay, true)); } - else + + std::shared_ptr 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::shared_ptr pLayer = std::make_shared(m_pEditor, m_Width, m_Height); - m_pEditor->m_Map.MakeTeleLayer(pLayer); - m_pEditor->m_Map.m_pGameGroup->AddLayer(pLayer); + std::map> savedLayers; + savedLayers[LAYERTYPE_TILES] = pTLayer->Duplicate(); + savedLayers[LAYERTYPE_TELE] = savedLayers[LAYERTYPE_TILES]; - vpActions.push_back(std::make_shared(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 Action1, Action2; + vpActions.push_back(Action1 = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); + vpActions.push_back(Action2 = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH)); - if(m_Width != pGLayer->m_Width || m_Height > pGLayer->m_Height) - { - std::map> 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(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); - const std::shared_ptr &Action1 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); - vpActions.push_back(std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, GameLayerIndex, ETilesProp::PROP_HEIGHT, PrevH, NewH)); - const std::shared_ptr &Action2 = std::static_pointer_cast(vpActions[vpActions.size() - 1]); - - Action1->SetSavedLayers(savedLayers); - Action2->SetSavedLayers(savedLayers); - } - } - - std::shared_ptr 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> 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 Action1, Action2; - vpActions.push_back(Action1 = std::make_shared(m_pEditor, m_pEditor->m_SelectedGroup, TeleLayerIndex, ETilesProp::PROP_WIDTH, PrevW, NewW)); - vpActions.push_back(Action2 = std::make_shared(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(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(m_pEditor, vpActions, aDisplay, true)); + 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(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(m_pEditor, vpActions, aDisplay, true)); } } +} + +bool CLayerTiles::CanFillGameTiles() const +{ + const bool EntitiesLayer = IsEntitiesLayer(); + if(EntitiesLayer) + return false; + + std::shared_ptr 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) { diff --git a/src/game/editor/mapitems/layer_tiles.h b/src/game/editor/mapitems/layer_tiles.h index f5789a898..9af936281 100644 --- a/src/game/editor/mapitems/layer_tiles.h +++ b/src/game/editor/mapitems/layer_tiles.h @@ -2,6 +2,7 @@ #define GAME_EDITOR_MAPITEMS_LAYER_TILES_H #include +#include #include #include "layer.h" @@ -122,6 +123,8 @@ public: void BrushSelecting(CUIRect Rect) override; int BrushGrab(std::shared_ptr pBrush, CUIRect Rect) override; void FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) override; + void FillGameTiles(EGameTileOp Fill); + bool CanFillGameTiles() const; void BrushDraw(std::shared_ptr pBrush, float wx, float wy) override; void BrushFlipX() override; void BrushFlipY() override; diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 22dc9d07f..879626b89 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -68,17 +68,9 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect Vie View.HSplitTop(2.0f, nullptr, &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_PopupEventType = POPEVENT_LOADCURRENT; - pEditor->m_PopupEventActivated = true; - } - else - { - pEditor->LoadCurrentMap(); - } + pEditor->m_QuickActionLoadCurrentMap.Call(); 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(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; } @@ -314,20 +306,20 @@ CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect static int s_ButtonOff = 0; static int s_ButtonDec = 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; - pEditor->m_ShowEnvelopePreview = SHOWENV_NONE; + pAction->Call(); } - 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; - pEditor->m_ShowEnvelopePreview = SHOWENV_NONE; + pAction->Call(); } - 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; - pEditor->m_ShowEnvelopePreview = SHOWENV_NONE; + pAction->Call(); } } @@ -578,16 +570,9 @@ CUi::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, // new tile layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); - static int s_NewTileLayerButton = 0; - if(pEditor->DoButton_Editor(&s_NewTileLayerButton, "Add tile layer", 0, &Button, 0, "Creates a new tile layer")) + if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddTileLayer, pEditor->m_QuickActionAddTileLayer.Label(), 0, &Button, 0, pEditor->m_QuickActionAddTileLayer.Description())) { - std::shared_ptr pTileLayer = std::make_shared(pEditor, pEditor->m_Map.m_pGameLayer->m_Width, pEditor->m_Map.m_pGameLayer->m_Height); - 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(pEditor, pEditor->m_SelectedGroup, LayerIndex)); + pEditor->m_QuickActionAddTileLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } diff --git a/src/game/editor/prompt.cpp b/src/game/editor/prompt.cpp new file mode 100644 index 000000000..a72db4671 --- /dev/null +++ b/src/game/editor/prompt.cpp @@ -0,0 +1,169 @@ +#include +#include +#include + +#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 +#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; + } + } +} diff --git a/src/game/editor/prompt.h b/src/game/editor/prompt.h new file mode 100644 index 000000000..2d873e580 --- /dev/null +++ b/src/game/editor/prompt.h @@ -0,0 +1,29 @@ +#ifndef GAME_EDITOR_PROMPT_H +#define GAME_EDITOR_PROMPT_H + +#include +#include +#include + +#include "component.h" + +class CPrompt : public CEditorComponent +{ + bool m_ResetFilterResults = true; + CQuickAction *m_pLastAction = nullptr; + int m_PromptSelectedIndex = -1; + + std::vector m_vpFilteredPromptList; + std::vector 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 diff --git a/src/game/editor/quick_action.h b/src/game/editor/quick_action.h new file mode 100644 index 000000000..124713643 --- /dev/null +++ b/src/game/editor/quick_action.h @@ -0,0 +1,69 @@ +#ifndef GAME_EDITOR_QUICK_ACTION_H +#define GAME_EDITOR_QUICK_ACTION_H + +#include +#include + +typedef std::function FButtonClickCallback; +typedef std::function FButtonDisabledCallback; +typedef std::function FButtonActiveCallback; +typedef std::function 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 diff --git a/src/game/editor/quick_actions.cpp b/src/game/editor/quick_actions.cpp new file mode 100644 index 000000000..d1a063949 --- /dev/null +++ b/src/game/editor/quick_actions.cpp @@ -0,0 +1,71 @@ +#include + +#include "editor.h" + +#include "editor_actions.h" + +void CEditor::FillGameTiles(EGameTileOp FillTile) const +{ + std::shared_ptr pTileLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_TILES)); + if(pTileLayer) + pTileLayer->FillGameTiles(FillTile); +} + +bool CEditor::CanFillGameTiles() const +{ + std::shared_ptr pTileLayer = std::static_pointer_cast(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(this, m_SelectedGroup, false)); +} + +void CEditor::AddTileLayer() +{ + std::shared_ptr pTileLayer = std::make_shared(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(this, m_SelectedGroup, LayerIndex)); +} + +bool CEditor::IsNonGameTileLayerSelected() const +{ + std::shared_ptr 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 pLayer = GetSelectedLayer(0); + std::shared_ptr pTiles = std::static_pointer_cast(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()); +} diff --git a/src/game/editor/quick_actions.h b/src/game/editor/quick_actions.h new file mode 100644 index 000000000..d8012117e --- /dev/null +++ b/src/game/editor/quick_actions.h @@ -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 diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 59f078297..482e9f135 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -260,7 +260,7 @@ void CCharacter::HandleNinja() 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 SetWeapon(WEAPON_NINJA); @@ -442,7 +442,7 @@ void CCharacter::FireWeapon() if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1)) { 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; } @@ -459,7 +459,7 @@ void CCharacter::FireWeapon() { // reset objects Hit 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()); @@ -567,7 +567,7 @@ void CCharacter::FireWeapon() MouseTarget // MouseTarget ); - GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); + GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc) } 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_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; } @@ -2055,7 +2055,7 @@ void CCharacter::ForceSetRescue(int RescueMode) void CCharacter::DDRaceTick() { 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) m_LastMove = Server()->Tick(); diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp index 6c393c5b7..ba15d94f5 100644 --- a/src/game/server/entities/laser.cpp +++ b/src/game/server/entities/laser.cpp @@ -94,6 +94,7 @@ bool CLaser::HitCharacter(vec2 From, vec2 To) { pHit->UnFreeze(); } + pHit->TakeDamage(vec2(0, 0), 0, m_Owner, m_Type); return true; } diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp index 9579b0170..c2bdac75f 100644 --- a/src/game/server/entities/projectile.cpp +++ b/src/game/server/entities/projectile.cpp @@ -177,6 +177,8 @@ void CProjectile::Tick() pChr->Freeze(); } } + else if(pTargetChr) + pTargetChr->TakeDamage(vec2(0, 0), 0, m_Owner, m_Type); if(pOwnerChar && !GameLayerClipped(ColPos) && ((m_Type == WEAPON_GRENADE && pOwnerChar->HasTelegunGrenade()) || (m_Type == WEAPON_GUN && pOwnerChar->HasTelegunGun()))) diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h index 3f80e67eb..a732123b4 100644 --- a/src/game/server/gamecontroller.h +++ b/src/game/server/gamecontroller.h @@ -95,6 +95,7 @@ public: virtual void OnCharacterSpawn(class CCharacter *pChr); virtual void HandleCharacterTiles(class CCharacter *pChr, int MapIndex); + virtual void SetArmorProgress(CCharacter *pCharacer, int Progress){}; /* Function: OnEntity diff --git a/src/game/server/gamemodes/DDRace.cpp b/src/game/server/gamemodes/DDRace.cpp index da14b863a..a2959a885 100644 --- a/src/game/server/gamemodes/DDRace.cpp +++ b/src/game/server/gamemodes/DDRace.cpp @@ -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) { IGameController::OnPlayerConnect(pPlayer); diff --git a/src/game/server/gamemodes/DDRace.h b/src/game/server/gamemodes/DDRace.h index 17626f26a..bc307e053 100644 --- a/src/game/server/gamemodes/DDRace.h +++ b/src/game/server/gamemodes/DDRace.h @@ -13,6 +13,7 @@ public: CScore *Score(); void HandleCharacterTiles(class CCharacter *pChr, int MapIndex) override; + void SetArmorProgress(CCharacter *pCharacer, int Progress) override; void OnPlayerConnect(class CPlayer *pPlayer) override; void OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason) override; diff --git a/src/game/server/teeinfo.cpp b/src/game/server/teeinfo.cpp index 90432fbeb..5458a1cc2 100644 --- a/src/game/server/teeinfo.cpp +++ b/src/game/server/teeinfo.cpp @@ -78,16 +78,16 @@ void CTeeInfo::ToSixup() { int ColorBody = ColorHSLA(m_ColorBody).UnclampLighting().Pack(ms_DarkestLGT7); int ColorFeet = ColorHSLA(m_ColorFeet).UnclampLighting().Pack(ms_DarkestLGT7); - m_aUseCustomColors[0] = true; - m_aUseCustomColors[1] = true; - m_aUseCustomColors[2] = true; - m_aUseCustomColors[3] = true; - m_aUseCustomColors[4] = true; - m_aSkinPartColors[0] = ColorBody; - m_aSkinPartColors[1] = 0x22FFFFFF; - m_aSkinPartColors[2] = ColorBody; - m_aSkinPartColors[3] = ColorBody; - m_aSkinPartColors[4] = ColorFeet; + m_aUseCustomColors[protocol7::SKINPART_BODY] = true; + m_aUseCustomColors[protocol7::SKINPART_MARKING] = true; + m_aUseCustomColors[protocol7::SKINPART_DECORATION] = true; + m_aUseCustomColors[protocol7::SKINPART_HANDS] = true; + m_aUseCustomColors[protocol7::SKINPART_FEET] = true; + m_aSkinPartColors[protocol7::SKINPART_BODY] = ColorBody; + m_aSkinPartColors[protocol7::SKINPART_MARKING] = 0x22FFFFFF; + m_aSkinPartColors[protocol7::SKINPART_DECORATION] = ColorBody; + m_aSkinPartColors[protocol7::SKINPART_HANDS] = ColorBody; + 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)); m_UseCustomColor = true; - m_ColorBody = ColorHSLA(m_aUseCustomColors[0] ? m_aSkinPartColors[0] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT); - m_ColorFeet = ColorHSLA(m_aUseCustomColors[4] ? m_aSkinPartColors[4] : 255).UnclampLighting(ms_DarkestLGT7).Pack(ColorHSLA::DARKEST_LGT); + m_ColorBody = ColorHSLA(m_aUseCustomColors[protocol7::SKINPART_BODY] ? m_aSkinPartColors[protocol7::SKINPART_BODY] : 255) + .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); } diff --git a/src/rust-bridge/cpp/console.cpp b/src/rust-bridge/cpp/console.cpp index fb5e3cbd9..eeaab723b 100644 --- a/src/rust-bridge/cpp/console.cpp +++ b/src/rust-bridge/cpp/console.cpp @@ -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 { - ::ColorHSLA (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor; - new (return$) ::ColorHSLA((self.*GetColor$)(Index, Light)); + std::optional<::ColorHSLA> (::IConsole_IResult::*GetColor$)(::std::uint32_t, bool) const = &::IConsole_IResult::GetColor; + 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 {