diff --git a/src/fheroes2/editor/editor_interface_panel.cpp b/src/fheroes2/editor/editor_interface_panel.cpp index 956687b879e..e8e272e84d6 100644 --- a/src/fheroes2/editor/editor_interface_panel.cpp +++ b/src/fheroes2/editor/editor_interface_panel.cpp @@ -20,20 +20,25 @@ #include "editor_interface_panel.h" -#include +#include +#include +#include #include "agg_image.h" #include "dialog.h" #include "dialog_system_options.h" #include "editor_interface.h" +#include "ground.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "localevent.h" #include "screen.h" +#include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" +#include "ui_text.h" namespace Interface { @@ -42,6 +47,7 @@ namespace Interface { int32_t icnIndex = 0; + // Editor Instruments go in this order in ICN: TERRAIN, OBJECT, DETAIL, STREAM, ROAD, ERASE. for ( fheroes2::Button & button : _instrumentButtons ) { button.setICNInfo( ICN::EDITBTNS, icnIndex, icnIndex + 1 ); icnIndex += 2; @@ -54,14 +60,22 @@ namespace Interface _buttonFile.setICNInfo( ICN::EDITBTNS, 20, 21 ); _buttonSystem.setICNInfo( ICN::EDITBTNS, 22, 23 ); - _instrumentButtons.front().press(); + // Brush Size buttons go in this order in ICN: SMALL (1x), MEDIUM (2x), LARGE (4x), AREA. + icnIndex = 24; + for ( fheroes2::Button & button : _brushSizeButtons ) { + button.setICNInfo( ICN::EDITBTNS, icnIndex, icnIndex + 1 ); + icnIndex += 2; + } + + _instrumentButtons[_selectedInstrument].press(); + _brushSizeButtons[_selectedBrushSize].press(); } void EditorPanel::setPos( const int32_t displayX, int32_t displayY ) { int32_t offsetX = displayX; - for ( uint8_t i = 0; i < instrumentsCount; ++i ) { + for ( size_t i = 0; i < _instrumentButtonsRect.size(); ++i ) { _instrumentButtons[i].setPosition( offsetX, displayY ); _instrumentButtonsRect[i] = _instrumentButtons[i].area(); @@ -83,8 +97,31 @@ namespace Interface const fheroes2::Sprite & instrumentPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, _selectedInstrument ); _rectInstrumentPanel = { displayX, displayY, instrumentPanel.width(), instrumentPanel.height() }; - // System buttons top row. + offsetX = displayX + 14; + int32_t offsetY = displayY + 128; + for ( size_t i = 0; i < _brushSizeButtonsRect.size(); ++i ) { + _brushSizeButtons[i].setPosition( offsetX, offsetY ); + _brushSizeButtonsRect[i] = _brushSizeButtons[i].area(); + offsetX += 30; + } + + offsetX = displayX + 30; + offsetY = displayY + 11; + for ( size_t i = 0; i < _terrainButtonsRect.size(); ++i ) { + _terrainButtonsRect[i] = { offsetX + static_cast( i % 3 ) * 29, offsetY + static_cast( i / 3 ) * 29, 27, 27 }; + } + + offsetX = displayX + 15; + ++offsetY; + for ( size_t i = 0; i < _objectButtonsRect.size(); ++i ) { + _objectButtonsRect[i] = { offsetX + static_cast( i % 4 ) * 29, offsetY + static_cast( i / 4 ) * 28, 27, 27 }; + } + // The last object button is located not next to previous one and needs to be shifted to the right. + _objectButtonsRect[Brush::TREASURES].x += 29 * 2; + displayY += _rectInstrumentPanel.height; + + // System buttons top row. _buttonMagnify.setPosition( displayX, displayY ); _rectMagnify = _buttonMagnify.area(); @@ -121,16 +158,123 @@ namespace Interface button.draw(); } + fheroes2::Display & display = fheroes2::Display::instance(); + const fheroes2::Sprite & instrumentPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, _selectedInstrument ); - fheroes2::Copy( instrumentPanel, 0, 0, fheroes2::Display::instance(), _rectEditorPanel.x, _rectInstruments.y + _rectInstruments.height, instrumentPanel.width(), + fheroes2::Copy( instrumentPanel, 0, 0, display, _rectEditorPanel.x, _rectInstruments.y + _rectInstruments.height, instrumentPanel.width(), instrumentPanel.height() ); + if ( _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::ERASE ) { + for ( const fheroes2::Button & button : _brushSizeButtons ) { + button.draw(); + } + } + + if ( _selectedInstrument == Instrument::TERRAIN ) { + const fheroes2::Sprite & selection = fheroes2::AGG::GetICN( ICN::TERRAINS, 9 ); + fheroes2::Blit( selection, 0, 0, display, _terrainButtonsRect[_selectedTerrain].x - 2, _terrainButtonsRect[_selectedTerrain].y - 2, selection.width(), + selection.height() ); + + const fheroes2::Text terrainText( _getTerrainTypeName( _selectedTerrain ), fheroes2::FontType::smallWhite() ); + terrainText.draw( _rectInstrumentPanel.x + 72 - terrainText.width() / 2, _rectInstrumentPanel.y + 107, display ); + } + else if ( _selectedInstrument == Instrument::OBJECT ) { + const fheroes2::Sprite & selection = fheroes2::AGG::GetICN( ICN::TERRAINS, 9 ); + fheroes2::Blit( selection, 0, 0, display, _objectButtonsRect[_selectedObject].x - 2, _objectButtonsRect[_selectedObject].y - 2, selection.width(), + selection.height() ); + + const fheroes2::Text terrainText( _getObjectTypeName( _selectedObject ), fheroes2::FontType::smallWhite() ); + terrainText.draw( _rectInstrumentPanel.x + 72 - terrainText.width() / 2, _rectInstrumentPanel.y + 135, display ); + } + _buttonMagnify.draw(); _buttonUndo.draw(); _buttonNew.draw(); _buttonSpecs.draw(); _buttonFile.draw(); _buttonSystem.draw(); + + display.render( _rectInstrumentPanel ); + } + + const char * EditorPanel::_getTerrainTypeName( const uint8_t brushId ) + { + int groundId = Maps::Ground::UNKNOWN; + switch ( brushId ) { + case Brush::WATER: + groundId = Maps::Ground::WATER; + break; + case Brush::GRASS: + groundId = Maps::Ground::GRASS; + break; + case Brush::SNOW: + groundId = Maps::Ground::SNOW; + break; + case Brush::SWAMP: + groundId = Maps::Ground::SWAMP; + break; + case Brush::LAVA: + groundId = Maps::Ground::LAVA; + break; + case Brush::DESERT: + groundId = Maps::Ground::DESERT; + break; + case Brush::DIRT: + groundId = Maps::Ground::DIRT; + break; + case Brush::WASTELAND: + groundId = Maps::Ground::WASTELAND; + break; + case Brush::BEACH: + groundId = Maps::Ground::BEACH; + break; + default: + // Have you added a new terrain type? + assert( 0 ); + break; + } + + return Maps::Ground::String( groundId ); + } + + const char * EditorPanel::_getObjectTypeName( const uint8_t brushId ) + { + switch ( brushId ) { + case Brush::WATER: + return _( "Ocean Objects" ); + case Brush::GRASS: + return _( "Grass Objects" ); + case Brush::SNOW: + return _( "Snow Objects" ); + case Brush::SWAMP: + return _( "Swamp Objects" ); + case Brush::LAVA: + return _( "Lava Objects" ); + case Brush::DESERT: + return _( "Desert Objects" ); + case Brush::DIRT: + return _( "Dirt Objects" ); + case Brush::WASTELAND: + return _( "Wasteland Objects" ); + case Brush::BEACH: + return _( "Beach Objects" ); + case Brush::TOWNS: + return _( "Towns" ); + case Brush::MONSTERS: + return _( "Monsters" ); + case Brush::HEROES: + return _( "Heroes" ); + case Brush::ARTIFACTS: + return _( "Artifacts" ); + case Brush::TREASURES: + return _( "Treasures" ); + default: + // Have you added a new object type? + assert( 0 ); + break; + } + + return "Unknown object type"; } fheroes2::GameMode EditorPanel::queueEventProcessing() @@ -139,10 +283,10 @@ namespace Interface fheroes2::GameMode res = fheroes2::GameMode::CANCEL; if ( le.MousePressLeft( _rectInstruments ) ) { - for ( uint8_t i = 0; i < instrumentsCount; ++i ) { + for ( size_t i = 0; i < _instrumentButtonsRect.size(); ++i ) { if ( le.MousePressLeft( _instrumentButtonsRect[i] ) ) { if ( _instrumentButtons[i].drawOnPress() ) { - _selectedInstrument = i; + _selectedInstrument = static_cast( i ); setRedraw(); } } @@ -152,6 +296,130 @@ namespace Interface } } + if ( _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::ERASE ) { + for ( size_t i = 0; i < _brushSizeButtonsRect.size(); ++i ) { + if ( le.MousePressLeft( _brushSizeButtonsRect[i] ) ) { + if ( _brushSizeButtons[i].drawOnPress() ) { + _selectedBrushSize = static_cast( i ); + } + } + else if ( i != _selectedBrushSize ) { + _brushSizeButtons[i].drawOnRelease(); + } + } + + const auto brushSizeText = []( const int brushSize, const bool isFillBrush ) { + std::string text + = isFillBrush ? _( "Draws terrain in\n%{size} by %{size} square increments." ) : _( "Erases objects in\n%{size} by %{size} square increments." ); + + StringReplace( text, "%{size}", brushSize ); + return text; + }; + + if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::SMALL] ) ) { + fheroes2::showStandardTextMessage( _( "Small Brush" ), brushSizeText( 1, _selectedInstrument == Instrument::TERRAIN ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::MEDIUM] ) ) { + fheroes2::showStandardTextMessage( _( "Medium Brush" ), brushSizeText( 2, _selectedInstrument == Instrument::TERRAIN ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::LARGE] ) ) { + fheroes2::showStandardTextMessage( _( "Large Brush" ), brushSizeText( 4, _selectedInstrument == Instrument::TERRAIN ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::AREA] ) ) { + if ( _selectedInstrument == Instrument::TERRAIN ) { + fheroes2::showStandardTextMessage( _( "Area Fill" ), _( "Used to click and drag for filling in large areas." ), Dialog::ZERO ); + } + else { + fheroes2::showStandardTextMessage( _( "Clear Area" ), _( "Used to click and drag for clearing large areas." ), Dialog::ZERO ); + } + } + } + + if ( _selectedInstrument == Instrument::TERRAIN ) { + for ( size_t i = 0; i < _terrainButtonsRect.size(); ++i ) { + if ( ( _selectedTerrain != i ) && le.MousePressLeft( _terrainButtonsRect[i] ) ) { + _selectedTerrain = static_cast( i ); + setRedraw(); + + // There is no need to continue the loop as only one button can be pressed at one moment. + break; + } + } + + const auto movePenaltyText = []( const std::string & rate ) { + std::string text = _( "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or eliminates the penalty.)" ); + StringReplace( text, "%{rate}", rate ); + return text; + }; + + if ( le.MousePressRight( _terrainButtonsRect[Brush::WATER] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::WATER ), _( "Traversable only by boat." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::GRASS] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::GRASS ), _( "No special modifiers." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::SNOW] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::SNOW ), movePenaltyText( "1.5" ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::SWAMP] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::SWAMP ), movePenaltyText( "1.75" ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::LAVA] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::LAVA ), _( "No special modifiers." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::DESERT] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::DESERT ), movePenaltyText( "2" ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::DIRT] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::DIRT ), _( "No special modifiers." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::WASTELAND] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::WASTELAND ), movePenaltyText( "1.25" ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _terrainButtonsRect[Brush::BEACH] ) ) { + fheroes2::showStandardTextMessage( _getTerrainTypeName( Brush::BEACH ), movePenaltyText( "1.25" ), Dialog::ZERO ); + } + } + + if ( _selectedInstrument == Instrument::OBJECT ) { + for ( size_t i = 0; i < _objectButtonsRect.size(); ++i ) { + if ( ( _selectedObject != i ) && le.MousePressLeft( _objectButtonsRect[i] ) ) { + _selectedObject = static_cast( i ); + setRedraw(); + + // There is no need to continue the loop as only one button can be pressed at one moment. + break; + } + } + + for ( uint8_t objectId = Brush::WATER; objectId < Brush::TOWNS; ++objectId ) { + if ( le.MousePressRight( _objectButtonsRect[objectId] ) ) { + std::string text = _( "Used to place objects most appropriate for use on %{terrain}." ); + StringReplaceWithLowercase( text, "%{terrain}", _getTerrainTypeName( objectId ) ); + fheroes2::showStandardTextMessage( _getObjectTypeName( objectId ), text, Dialog::ZERO ); + + // There is no need to continue the loop as only one button can be pressed at one moment. + break; + } + } + + if ( le.MousePressRight( _objectButtonsRect[Brush::TOWNS] ) ) { + fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::TOWNS ), _( "Used to place\na town or castle." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _objectButtonsRect[Brush::MONSTERS] ) ) { + fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::MONSTERS ), _( "Used to place\na monster group." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _objectButtonsRect[Brush::HEROES] ) ) { + fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::HEROES ), _( "Used to place a hero." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _objectButtonsRect[Brush::ARTIFACTS] ) ) { + fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::ARTIFACTS ), _( "Used to place an artifact." ), Dialog::ZERO ); + } + else if ( le.MousePressRight( _objectButtonsRect[Brush::TREASURES] ) ) { + fheroes2::showStandardTextMessage( _getObjectTypeName( Brush::TREASURES ), _( "Used to place\na resource or treasure." ), Dialog::ZERO ); + } + } + le.MousePressLeft( _rectMagnify ) ? _buttonMagnify.drawOnPress() : _buttonMagnify.drawOnRelease(); le.MousePressLeft( _rectUndo ) ? _buttonUndo.drawOnPress() : _buttonUndo.drawOnRelease(); le.MousePressLeft( _rectNew ) ? _buttonNew.drawOnPress() : _buttonNew.drawOnRelease(); @@ -179,36 +447,29 @@ namespace Interface // Replace this with Editor options dialog. fheroes2::showSystemOptionsDialog(); } - - if ( le.MousePressRight( _rectInstrumentPanel ) || le.MouseClickLeft( _rectInstrumentPanel ) ) { - // TODO: Implement instrument and brush select. - fheroes2::showStandardTextMessage( _( "Warning!" ), "The Map Editor is still in development. This panel is currently not functional.", - le.MousePressRight() ? Dialog::ZERO : Dialog::OK ); - } - - if ( le.MousePressRight( _instrumentButtonsRect[0] ) ) { + if ( le.MousePressRight( _instrumentButtonsRect[Instrument::TERRAIN] ) ) { fheroes2::showStandardTextMessage( _( "Terrain Mode" ), _( "Used to draw the underlying grass, dirt, water, etc. on the map." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _instrumentButtonsRect[1] ) ) { + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::OBJECT] ) ) { fheroes2::showStandardTextMessage( _( "Object Mode" ), _( "Used to place objects (mountains, trees, treasure, etc.) on the map." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _instrumentButtonsRect[2] ) ) { + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::DETAIL] ) ) { fheroes2::showStandardTextMessage( _( "Detail Mode" ), _( "Used for special editing of monsters, heroes and towns." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _instrumentButtonsRect[3] ) ) { + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::STREAM] ) ) { fheroes2::showStandardTextMessage( _( "Stream Mode" ), _( "Allows you to draw streams by clicking and dragging." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _instrumentButtonsRect[4] ) ) { + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::ROAD] ) ) { fheroes2::showStandardTextMessage( _( "Road Mode" ), _( "Allows you to draw roads by clicking and dragging." ), Dialog::ZERO ); } - else if ( le.MousePressRight( _instrumentButtonsRect[5] ) ) { + else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::ERASE] ) ) { fheroes2::showStandardTextMessage( _( "Erase Mode" ), _( "Used to erase objects off the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectMagnify ) ) { fheroes2::showStandardTextMessage( _( "Magnify" ), _( "Change between zoom and normal view." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectUndo ) ) { - fheroes2::showStandardTextMessage( _( "Undo" ), _( "Undo your last action. Press again to redo the action." ), Dialog::ZERO ); + fheroes2::showStandardTextMessage( _( "Undo" ), _( "Undo your last action. Press again to redo the action." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectNew ) ) { fheroes2::showStandardTextMessage( _( "New Map" ), _( "Create a new map either from scratch or using the random map generator." ), Dialog::ZERO ); diff --git a/src/fheroes2/editor/editor_interface_panel.h b/src/fheroes2/editor/editor_interface_panel.h index cde43327c2b..03a49092c33 100644 --- a/src/fheroes2/editor/editor_interface_panel.h +++ b/src/fheroes2/editor/editor_interface_panel.h @@ -58,12 +58,61 @@ namespace Interface private: Editor & _interface; - // Index of selected Map Editor instrument. - uint8_t _selectedInstrument{ 0 }; - const static uint8_t instrumentsCount = 6; + static const char * _getTerrainTypeName( const uint8_t brushId ); + static const char * _getObjectTypeName( const uint8_t brushId ); - std::array _instrumentButtons; - std::array _instrumentButtonsRect; + enum Instrument : uint8_t + { + // IMPORTANT. This enumeration corresponds with the order of instruments in original assets. Do not change this order. + TERRAIN = 0U, + OBJECT = 1U, + DETAIL = 2U, + STREAM = 3U, + ROAD = 4U, + ERASE = 5U, + + // The last element corresponds to the editor instruments count. + INSTRUMENTS_COUNT = 6U + }; + + enum Brush : uint8_t + { + // IMPORTANT. This enumeration corresponds with the order of instruments in original assets. Do not change this order. + WATER = 0U, + GRASS = 1U, + SNOW = 2U, + SWAMP = 3U, + LAVA = 4U, + DESERT = 5U, + DIRT = 6U, + WASTELAND = 7U, + BEACH = 8U, + + // This element corresponds to the editor terrain types count. + TERRAIN_COUNT = 9U, + + // For objects this enumeration continues. + TOWNS = 9U, + MONSTERS = 10U, + HEROES = 11U, + ARTIFACTS = 12U, + TREASURES = 13U, + + // The last element corresponds to the editor object types count. + OBJECT_COUNT = 14U + }; + + enum BrushSize : uint8_t + { + // IMPORTANT. This enumeration corresponds with the order of brush size in original assets. Do not change this order. + SMALL = 0U, + MEDIUM = 1U, + LARGE = 2U, + AREA = 3U, + + // The last element corresponds to the editor brush size count. + BRUSH_SIZE_COUNT = 4U + }; fheroes2::Button _buttonMagnify; fheroes2::Button _buttonUndo; @@ -82,5 +131,18 @@ namespace Interface fheroes2::Rect _rectInstruments; fheroes2::Rect _rectInstrumentPanel; fheroes2::Rect _rectEditorPanel; + + std::array _instrumentButtons; + std::array _brushSizeButtons; + + std::array _instrumentButtonsRect; + std::array _terrainButtonsRect; + std::array _objectButtonsRect; + std::array _brushSizeButtonsRect; + + uint8_t _selectedInstrument{ Instrument::TERRAIN }; + uint8_t _selectedTerrain{ Brush::WATER }; + uint8_t _selectedObject{ Brush::WATER }; + uint8_t _selectedBrushSize{ BrushSize::MEDIUM }; }; }