mirror of
https://github.com/ihhub/fheroes2.git
synced 2026-10-12 02:16:11 +00:00
Add ability to move an object within the Editor (#10684)
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -62,7 +63,6 @@
|
||||
#include "interface_radar.h"
|
||||
#include "localevent.h"
|
||||
#include "map_format_helper.h"
|
||||
#include "map_object_info.h"
|
||||
#include "maps.h"
|
||||
#include "maps_fileinfo.h"
|
||||
#include "maps_tiles.h"
|
||||
@@ -866,6 +866,167 @@ namespace
|
||||
return allowedMonsters;
|
||||
}
|
||||
|
||||
bool isObjectMovable( const Maps::ObjectGroup group )
|
||||
{
|
||||
switch ( group ) {
|
||||
case Maps::ObjectGroup::ADVENTURE_ARTIFACTS:
|
||||
case Maps::ObjectGroup::ADVENTURE_DWELLINGS:
|
||||
case Maps::ObjectGroup::ADVENTURE_MINES:
|
||||
case Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS:
|
||||
case Maps::ObjectGroup::ADVENTURE_POWER_UPS:
|
||||
case Maps::ObjectGroup::ADVENTURE_TREASURES:
|
||||
case Maps::ObjectGroup::ADVENTURE_WATER:
|
||||
case Maps::ObjectGroup::KINGDOM_HEROES:
|
||||
case Maps::ObjectGroup::KINGDOM_TOWNS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_MOUNTAINS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_ROCKS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_TREES:
|
||||
case Maps::ObjectGroup::LANDSCAPE_WATER:
|
||||
case Maps::ObjectGroup::MONSTERS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool getMovableObjectInfo( const Maps::Map_Format::MapFormat & mapFormat, const int32_t tileId, int32_t & type, Maps::ObjectGroup & group, uint32_t & objectUID )
|
||||
{
|
||||
// Check which object we really need to move.
|
||||
// The logic should be aligned with placeObjectOnTile() function from maps_tiles_helper.cpp file.
|
||||
const auto & tile = world.getTile( tileId );
|
||||
const MP2::MapObjectType objectType{ tile.getMainObjectType() };
|
||||
if ( objectType == MP2::OBJ_NONE ) {
|
||||
// No object is on the tile.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Many objects are located on more than one tile.
|
||||
// In this case we cannot rely just on MapFormat.
|
||||
//
|
||||
// First, find all possible objects in the area around the tile.
|
||||
const fheroes2::Point tilePos{ tileId % mapFormat.width, tileId / mapFormat.width };
|
||||
|
||||
const int32_t minX = std::max( 0, tilePos.x - Maps::maxObjectDimensions.width );
|
||||
const int32_t maxX = std::min( mapFormat.width, tilePos.x + Maps::maxObjectDimensions.width + 1 );
|
||||
const int32_t minY = std::max( 0, tilePos.y - Maps::maxObjectDimensions.height );
|
||||
const int32_t maxY = std::min( mapFormat.width, tilePos.y + Maps::maxObjectDimensions.height + 1 );
|
||||
|
||||
std::map<uint32_t, const Maps::Map_Format::TileObjectInfo *> potentialObjects;
|
||||
for ( int32_t y = minY; y < maxY; ++y ) {
|
||||
const int32_t tileOffsetY{ y * mapFormat.width };
|
||||
for ( int32_t x = minX; x < maxX; ++x ) {
|
||||
for ( const auto & object : mapFormat.tiles[x + tileOffsetY].objects ) {
|
||||
if ( isObjectMovable( object.group ) ) {
|
||||
potentialObjects.emplace( object.id, &object );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( potentialObjects.empty() ) {
|
||||
// No movable objects exist.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto foundObjectIter = potentialObjects.end();
|
||||
|
||||
auto checkMainObjectPart = [&tile, &potentialObjects, objectType]() {
|
||||
const auto & mainObjectPart = tile.getMainObjectPart();
|
||||
if ( mainObjectPart.isPassabilityTransparent() ) {
|
||||
return potentialObjects.end();
|
||||
}
|
||||
|
||||
// This is an action object. Only the main object part and then ground object parts should be searched.
|
||||
if ( Maps::getObjectTypeByIcn( mainObjectPart.icnType, mainObjectPart.icnIndex ) == objectType ) {
|
||||
return potentialObjects.find( mainObjectPart._uid );
|
||||
}
|
||||
|
||||
return potentialObjects.end();
|
||||
};
|
||||
|
||||
auto searchGroundObjectParts = [&tile, &potentialObjects, objectType]() {
|
||||
// We search from end to start of the container.
|
||||
const auto & groundObjectParts = tile.getGroundObjectParts();
|
||||
for ( auto objectPartIter = groundObjectParts.crbegin(); objectPartIter != groundObjectParts.crend(); ++objectPartIter ) {
|
||||
if ( objectPartIter->isPassabilityTransparent() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( Maps::getObjectTypeByIcn( objectPartIter->icnType, objectPartIter->icnIndex ) != objectType ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto potentialObjectIter = potentialObjects.find( objectPartIter->_uid );
|
||||
if ( potentialObjectIter != potentialObjects.end() ) {
|
||||
return potentialObjectIter;
|
||||
}
|
||||
}
|
||||
|
||||
return potentialObjects.end();
|
||||
};
|
||||
|
||||
auto searchTopObjectParts = [&tile, &potentialObjects, objectType]() {
|
||||
// We search from end to start of the container.
|
||||
const auto & topObjectParts = tile.getTopObjectParts();
|
||||
for ( auto objectPartIter = topObjectParts.crbegin(); objectPartIter != topObjectParts.crend(); ++objectPartIter ) {
|
||||
if ( Maps::getObjectTypeByIcn( objectPartIter->icnType, objectPartIter->icnIndex ) != objectType ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto potentialObjectIter = potentialObjects.find( objectPartIter->_uid );
|
||||
if ( potentialObjectIter != potentialObjects.end() ) {
|
||||
return potentialObjectIter;
|
||||
}
|
||||
}
|
||||
|
||||
return potentialObjects.end();
|
||||
};
|
||||
|
||||
if ( MP2::isOffGameActionObject( objectType ) ) {
|
||||
// This is an action object. Top level objects parts aren't considered for this case.
|
||||
foundObjectIter = checkMainObjectPart();
|
||||
|
||||
if ( foundObjectIter == potentialObjects.end() ) {
|
||||
foundObjectIter = searchGroundObjectParts();
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundObjectIter = searchTopObjectParts();
|
||||
|
||||
if ( foundObjectIter == potentialObjects.end() ) {
|
||||
foundObjectIter = checkMainObjectPart();
|
||||
}
|
||||
|
||||
if ( foundObjectIter == potentialObjects.end() ) {
|
||||
foundObjectIter = searchGroundObjectParts();
|
||||
}
|
||||
}
|
||||
|
||||
if ( foundObjectIter == potentialObjects.end() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The object has been found.
|
||||
assert( foundObjectIter->second != nullptr );
|
||||
const Maps::Map_Format::TileObjectInfo & object = *( foundObjectIter->second );
|
||||
|
||||
type = static_cast<int32_t>( object.index );
|
||||
if ( object.group == Maps::ObjectGroup::KINGDOM_TOWNS ) {
|
||||
// Castles and towns store their colors inside flags.
|
||||
// The chosen tile might not be the main tile of the castle.
|
||||
// We need to get flag information from the main tile.
|
||||
const int32_t mainTileIndex = Maps::Tile::getIndexOfMainTile( tile );
|
||||
|
||||
const int color = Maps::getTownColorIndex( mapFormat, ( mainTileIndex >= 0 ) ? mainTileIndex : tileId, object.id );
|
||||
type = Interface::EditorPanel::generateTownObjectProperties( type, color );
|
||||
}
|
||||
|
||||
group = object.group;
|
||||
objectUID = object.id;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined( WITH_DEBUG )
|
||||
int32_t getObjectIndex( const Maps::Map_Format::MapFormat & mapFormat, const uint32_t uid, const Maps::ObjectGroup group )
|
||||
{
|
||||
@@ -1436,7 +1597,6 @@ namespace Interface
|
||||
_brushTiles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
else if ( _editorPanel.isStreamDraw() ) {
|
||||
if ( le.isMouseLeftButtonPressed() ) {
|
||||
if ( _brushTiles.count( _tileUnderCursor ) == 0 ) {
|
||||
@@ -1455,6 +1615,36 @@ namespace Interface
|
||||
_brushTiles.clear();
|
||||
}
|
||||
}
|
||||
else if ( _editorPanel.isDetailEdit() ) {
|
||||
if ( le.isMouseLeftButtonPressed() ) {
|
||||
if ( _brushTiles.count( _tileUnderCursor ) == 0 ) {
|
||||
if ( _brushTiles.empty() ) {
|
||||
_movableObjectInfo = {};
|
||||
_movableObjectInfo.tileIndex = _tileUnderCursor;
|
||||
}
|
||||
|
||||
if ( _brushTiles.size() == 1 ) {
|
||||
assert( _movableObjectInfo.tileIndex >= 0 );
|
||||
|
||||
if ( getMovableObjectInfo( _mapFormat, _movableObjectInfo.tileIndex, _movableObjectInfo.objectType, _movableObjectInfo.groupType,
|
||||
_movableObjectInfo.objectUID ) ) {
|
||||
_editorPanel.setObjectBasedCursor( _movableObjectInfo.objectType, _movableObjectInfo.groupType );
|
||||
updateCursor( _tileUnderCursor );
|
||||
}
|
||||
}
|
||||
|
||||
_brushTiles.emplace( _tileUnderCursor );
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( le.isMouseLeftButtonReleased() ) {
|
||||
_tryToMoveObject( _movableObjectInfo, _tileUnderCursor );
|
||||
}
|
||||
|
||||
// Make sure to clear the action related information.
|
||||
_resetMovableObjectInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( _areaSelectionStartTileId != -1 ) {
|
||||
assert( _editorPanel.showAreaSelectRect() && isBrushEmpty );
|
||||
@@ -1728,6 +1918,9 @@ namespace Interface
|
||||
// Trigger an action only when metadata has been changed to avoid expensive computations and bloated list of actions.
|
||||
// Comparing a metadata structure is much faster than restoring the whole map.
|
||||
|
||||
// Since this is an edit mode, clear object movement mode information.
|
||||
_resetMovableObjectInfo();
|
||||
|
||||
for ( const auto & object : _mapFormat.tiles[tileIndex].objects ) {
|
||||
const auto & objectGroupInfo = Maps::getObjectsByGroup( object.group );
|
||||
assert( object.index <= objectGroupInfo.size() );
|
||||
@@ -2183,20 +2376,30 @@ namespace Interface
|
||||
return;
|
||||
}
|
||||
|
||||
const fheroes2::Point tilePos = tile.GetCenter();
|
||||
|
||||
const Maps::ObjectGroup groupType = _editorPanel.getSelectedObjectGroup();
|
||||
|
||||
if ( _moveExistingObject( tile.GetIndex(), groupType, objectType ) ) {
|
||||
if ( _tryToMoveObjectOnTop( tile.GetIndex(), groupType, objectType ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<fheroes2::ActionCreator> action;
|
||||
if ( _tryToPlaceObject( tile, objectType, groupType, true, action ) ) {
|
||||
assert( action.get() != nullptr );
|
||||
action->commit();
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorInterface::_tryToPlaceObject( Maps::Tile & tile, const int32_t objectType, const Maps::ObjectGroup groupType, const bool isNewObject,
|
||||
std::unique_ptr<fheroes2::ActionCreator> & action )
|
||||
{
|
||||
const fheroes2::Point tilePos = tile.GetCenter();
|
||||
|
||||
std::string errorMessage;
|
||||
|
||||
if ( groupType == Maps::ObjectGroup::KINGDOM_HEROES ) {
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto & objectInfo = Maps::getObjectInfo( groupType, objectType );
|
||||
@@ -2230,18 +2433,22 @@ namespace Interface
|
||||
std::string warning( _( "A maximum of %{count} heroes including jailed heroes can be placed on the map." ) );
|
||||
StringReplace( warning, "%{count}", AllHeroes::getMaximumAllowedHeroes() );
|
||||
_warningMessage.reset( std::move( warning ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( kingdomHeroCount >= GameStatic::GetKingdomMaxHeroes() ) {
|
||||
std::string warning( _( "A maximum of %{count} heroes of the same color can be placed on the map." ) );
|
||||
StringReplace( warning, "%{count}", GameStatic::GetKingdomMaxHeroes() );
|
||||
_warningMessage.reset( std::move( warning ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTileAsAction( tile, groupType, objectType ) ) {
|
||||
return;
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Heroes * hero = world.GetHeroForHire( Race::IndexToRace( static_cast<int>( objectInfo.metadata[1] ) ) );
|
||||
@@ -2261,7 +2468,7 @@ namespace Interface
|
||||
else if ( groupType == Maps::ObjectGroup::ADVENTURE_ARTIFACTS ) {
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto & objectInfo = Maps::getObjectInfo( groupType, objectType );
|
||||
@@ -2272,7 +2479,7 @@ namespace Interface
|
||||
for ( const auto & object : tileInfo.objects ) {
|
||||
if ( groupType == object.group && static_cast<uint32_t>( objectType ) == object.index ) {
|
||||
_warningMessage.reset( _( "Only one Random Ultimate Artifact can be placed on the map." ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2280,15 +2487,22 @@ namespace Interface
|
||||
|
||||
// For each Spell Scroll artifact we select a spell.
|
||||
if ( objectInfo.objectType == MP2::OBJ_ARTIFACT && objectInfo.metadata[0] == Artifact::SPELL_SCROLL ) {
|
||||
const int spellId = Dialog::selectSpell( Spell::RANDOM, true ).GetID();
|
||||
int spellId = Spell::NONE;
|
||||
if ( isNewObject ) {
|
||||
spellId = Dialog::selectSpell( Spell::RANDOM, true ).GetID();
|
||||
|
||||
if ( spellId == Spell::NONE ) {
|
||||
// We do not place the Spell Scroll artifact if the spell for it was not selected.
|
||||
return;
|
||||
if ( spellId == Spell::NONE ) {
|
||||
// We do not place the Spell Scroll artifact if the spell for it was not selected.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTileAsAction( tile, groupType, objectType ) ) {
|
||||
return;
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert( !_mapFormat.tiles[tile.GetIndex()].objects.empty() );
|
||||
@@ -2304,32 +2518,38 @@ namespace Interface
|
||||
Maps::setSpellOnTile( tile, spellId );
|
||||
}
|
||||
else {
|
||||
_setObjectOnTileAsAction( tile, groupType, objectType );
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( groupType == Maps::ObjectGroup::KINGDOM_TOWNS ) {
|
||||
int32_t type = -1;
|
||||
int32_t color = -1;
|
||||
_editorPanel.getTownObjectProperties( type, color );
|
||||
EditorPanel::getTownObjectProperties( objectType, type, color );
|
||||
if ( type < 0 || color < 0 ) {
|
||||
// Check your logic!
|
||||
assert( 0 );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, type, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
fheroes2::ActionCreator action( _historyManager, _mapFormat );
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_placeCastle( tilePos.x, tilePos.y, Color::IndexToColor( color ), type ) ) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
action.commit();
|
||||
|
||||
if ( !Maps::updateMapPlayers( _mapFormat ) ) {
|
||||
_warningMessage.reset( _( "Failed to update player information." ) );
|
||||
}
|
||||
@@ -2338,40 +2558,40 @@ namespace Interface
|
||||
if ( objectType < 0 ) {
|
||||
// Check your logic!
|
||||
assert( 0 );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
fheroes2::ActionCreator action( _historyManager, _mapFormat );
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
action.commit();
|
||||
}
|
||||
else if ( groupType == Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS ) {
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
fheroes2::ActionCreator action( _historyManager, _mapFormat );
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// For River Deltas we update the nearby Streams to properly connect to them.
|
||||
if ( const int riverDeltaDirection = Maps::getRiverDeltaDirectionByIndex( groupType, objectType ); riverDeltaDirection != Direction::UNKNOWN ) {
|
||||
Maps::updateStreamsToDeltaConnection( _mapFormat, tile.GetIndex(), riverDeltaDirection );
|
||||
}
|
||||
|
||||
action.commit();
|
||||
}
|
||||
else if ( groupType == Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS ) {
|
||||
const auto & objectInfo = Maps::getObjectInfo( groupType, objectType );
|
||||
@@ -2382,7 +2602,7 @@ namespace Interface
|
||||
std::string warning( _( "A maximum of %{count} obelisks can be placed on the map." ) );
|
||||
StringReplace( warning, "%{count}", numOfPuzzleTiles );
|
||||
_warningMessage.reset( std::move( warning ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( objectInfo.objectType == MP2::OBJ_JAIL ) {
|
||||
@@ -2401,25 +2621,35 @@ namespace Interface
|
||||
std::string warning( _( "A maximum of %{count} heroes including jailed heroes can be placed on the map." ) );
|
||||
StringReplace( warning, "%{count}", AllHeroes::getMaximumAllowedHeroes() );
|
||||
_warningMessage.reset( std::move( warning ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_setObjectOnTileAsAction( tile, groupType, objectType );
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( groupType == Maps::ObjectGroup::ADVENTURE_TREASURES ) {
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTileAsAction( tile, groupType, objectType ) ) {
|
||||
return;
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert( !_mapFormat.tiles[tile.GetIndex()].objects.empty() );
|
||||
@@ -2437,11 +2667,82 @@ namespace Interface
|
||||
else {
|
||||
if ( !verifyObjectPlacement( tilePos, groupType, objectType, errorMessage ) ) {
|
||||
_warningMessage.reset( std::move( errorMessage ) );
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_setObjectOnTileAsAction( tile, groupType, objectType );
|
||||
if ( !action ) {
|
||||
action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
}
|
||||
|
||||
if ( !_setObjectOnTile( tile, groupType, objectType ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorInterface::_tryToMoveObject( const MovableObjectInfo & movableObjectInfo, const int32_t destinationTile )
|
||||
{
|
||||
assert( movableObjectInfo.tileIndex >= 0 );
|
||||
assert( destinationTile >= 0 );
|
||||
|
||||
if ( movableObjectInfo.groupType == Maps::ObjectGroup::NONE ) {
|
||||
// No object to move.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( movableObjectInfo.tileIndex == destinationTile ) {
|
||||
_tryToMoveObjectOnTop( movableObjectInfo.tileIndex, movableObjectInfo.groupType, movableObjectInfo.objectType );
|
||||
return;
|
||||
}
|
||||
|
||||
Maps::Tile & tile = world.getTile( destinationTile );
|
||||
|
||||
// Since we want to preserve the object UID (and not to break translations)
|
||||
// we need to temporary set the last object UID here and then reset it back to what it should be before the changes.
|
||||
const uint32_t originalLastObjectUID = Maps::getLastObjectUID();
|
||||
|
||||
// Some objects have metadata. We need to save it before removing objects.
|
||||
// Ideally, we should save only the object's metadata but to make things easier, let's save everything for now.
|
||||
// TODO: save only the current object metadata.
|
||||
std::map<uint32_t, Maps::Map_Format::CastleMetadata> castleMetadata = _mapFormat.castleMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::HeroMetadata> heroMetadata = _mapFormat.heroMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::SphinxMetadata> sphinxMetadata = _mapFormat.sphinxMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::SignMetadata> signMetadata = _mapFormat.signMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::AdventureMapEventMetadata> adventureMapEventMetadata = _mapFormat.adventureMapEventMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::SelectionObjectMetadata> selectionObjectMetadata = _mapFormat.selectionObjectMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::CapturableObjectMetadata> capturableObjectsMetadata = _mapFormat.capturableObjectsMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::MonsterMetadata> monsterMetadata = _mapFormat.monsterMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::ArtifactMetadata> artifactMetadata = _mapFormat.artifactMetadata;
|
||||
std::map<uint32_t, Maps::Map_Format::ResourceMetadata> resourceMetadata = _mapFormat.resourceMetadata;
|
||||
|
||||
auto action = std::make_unique<fheroes2::ActionCreator>( _historyManager, _mapFormat );
|
||||
removeObjects( _mapFormat, { movableObjectInfo.objectUID }, { movableObjectInfo.groupType } );
|
||||
|
||||
Maps::setLastObjectUID( movableObjectInfo.objectUID - 1 );
|
||||
|
||||
if ( _tryToPlaceObject( tile, movableObjectInfo.objectType, movableObjectInfo.groupType, false, action ) ) {
|
||||
assert( action.get() != nullptr );
|
||||
|
||||
// If this assertion blows up then the code is invalid.
|
||||
assert( Maps::getLastObjectUID() == movableObjectInfo.objectUID );
|
||||
|
||||
_mapFormat.castleMetadata = std::move( castleMetadata );
|
||||
_mapFormat.heroMetadata = std::move( heroMetadata );
|
||||
_mapFormat.sphinxMetadata = std::move( sphinxMetadata );
|
||||
_mapFormat.signMetadata = std::move( signMetadata );
|
||||
_mapFormat.adventureMapEventMetadata = std::move( adventureMapEventMetadata );
|
||||
_mapFormat.selectionObjectMetadata = std::move( selectionObjectMetadata );
|
||||
_mapFormat.capturableObjectsMetadata = std::move( capturableObjectsMetadata );
|
||||
_mapFormat.monsterMetadata = std::move( monsterMetadata );
|
||||
_mapFormat.artifactMetadata = std::move( artifactMetadata );
|
||||
_mapFormat.resourceMetadata = std::move( resourceMetadata );
|
||||
|
||||
action->commit();
|
||||
}
|
||||
|
||||
Maps::setLastObjectUID( originalLastObjectUID );
|
||||
}
|
||||
|
||||
void EditorInterface::mouseCursorAreaPressRight( const int32_t tileIndex ) const
|
||||
@@ -2479,18 +2780,6 @@ namespace Interface
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorInterface::_setObjectOnTileAsAction( Maps::Tile & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex )
|
||||
{
|
||||
fheroes2::ActionCreator action( _historyManager, _mapFormat );
|
||||
|
||||
if ( _setObjectOnTile( tile, groupType, objectIndex ) ) {
|
||||
action.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EditorInterface::updateRandomMapConfiguration( const int32_t mapWidth )
|
||||
{
|
||||
Maps::Random_Generator::Configuration temp{ _randomMapConfig };
|
||||
@@ -2766,14 +3055,14 @@ namespace Interface
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorInterface::_moveExistingObject( const int32_t tileIndex, const Maps::ObjectGroup groupType, int32_t objectIndex )
|
||||
bool EditorInterface::_tryToMoveObjectOnTop( const int32_t tileIndex, const Maps::ObjectGroup groupType, int32_t objectIndex )
|
||||
{
|
||||
assert( tileIndex >= 0 && static_cast<size_t>( tileIndex ) < _mapFormat.tiles.size() );
|
||||
|
||||
if ( groupType == Maps::ObjectGroup::KINGDOM_TOWNS ) {
|
||||
int32_t type = -1;
|
||||
int32_t color = -1;
|
||||
_editorPanel.getTownObjectProperties( type, color );
|
||||
EditorPanel::getTownObjectProperties( objectIndex, type, color );
|
||||
if ( type < 0 || color < 0 ) {
|
||||
// Check your logic!
|
||||
assert( 0 );
|
||||
@@ -2782,17 +3071,6 @@ namespace Interface
|
||||
|
||||
objectIndex = type;
|
||||
}
|
||||
else if ( groupType == Maps::ObjectGroup::ADVENTURE_MINES ) {
|
||||
const int32_t type = _editorPanel.getSelectedObjectType();
|
||||
|
||||
if ( type < 0 ) {
|
||||
// Check your logic!
|
||||
assert( 0 );
|
||||
return false;
|
||||
}
|
||||
|
||||
objectIndex = type;
|
||||
}
|
||||
|
||||
for ( const auto & object : _mapFormat.tiles[tileIndex].objects ) {
|
||||
if ( object.group == groupType && object.index == static_cast<uint32_t>( objectIndex ) ) {
|
||||
@@ -2899,4 +3177,18 @@ namespace Interface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorInterface::_resetMovableObjectInfo()
|
||||
{
|
||||
if ( _movableObjectInfo.tileIndex < 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
_brushTiles.clear();
|
||||
_movableObjectInfo = {};
|
||||
|
||||
Cursor::Get().SetThemes( Cursor::POINTER );
|
||||
setCursorUpdater( {} );
|
||||
updateCursor( _tileUnderCursor );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "history_manager.h"
|
||||
#include "interface_base.h"
|
||||
#include "map_format_info.h"
|
||||
#include "map_object_info.h"
|
||||
#include "map_random_generator.h"
|
||||
#include "timing.h"
|
||||
|
||||
@@ -39,8 +41,6 @@ enum class PlayerColor : uint8_t;
|
||||
namespace Maps
|
||||
{
|
||||
class Tile;
|
||||
|
||||
enum class ObjectGroup : uint8_t;
|
||||
}
|
||||
|
||||
namespace Interface
|
||||
@@ -147,6 +147,14 @@ namespace Interface
|
||||
fheroes2::Time _timer;
|
||||
};
|
||||
|
||||
struct MovableObjectInfo final
|
||||
{
|
||||
int32_t tileIndex{ -1 };
|
||||
int32_t objectType{ -1 };
|
||||
uint32_t objectUID{ 0 };
|
||||
Maps::ObjectGroup groupType{ Maps::ObjectGroup::NONE };
|
||||
};
|
||||
|
||||
EditorInterface()
|
||||
: BaseInterface( true )
|
||||
, _editorPanel( *this )
|
||||
@@ -159,14 +167,19 @@ namespace Interface
|
||||
|
||||
bool _setObjectOnTile( Maps::Tile & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex );
|
||||
|
||||
bool _setObjectOnTileAsAction( Maps::Tile & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex );
|
||||
|
||||
void _handleObjectMouseLeftClick( Maps::Tile & tile );
|
||||
|
||||
// Returns true if the placement was successful.
|
||||
// 'action' input argument will be populated in case of success.
|
||||
bool _tryToPlaceObject( Maps::Tile & tile, const int32_t objectType, const Maps::ObjectGroup groupType, const bool isNewObject,
|
||||
std::unique_ptr<fheroes2::ActionCreator> & action );
|
||||
|
||||
void _tryToMoveObject( const MovableObjectInfo & movableObjectInfo, const int32_t destinationTile );
|
||||
|
||||
void _validateObjectsOnTerrainUpdate();
|
||||
|
||||
// Returns true if an existing object was moved.
|
||||
bool _moveExistingObject( const int32_t tileIndex, const Maps::ObjectGroup groupType, int32_t objectIndex );
|
||||
// Returns true if an existing object was moved on top.
|
||||
bool _tryToMoveObjectOnTop( const int32_t tileIndex, const Maps::ObjectGroup groupType, int32_t objectIndex );
|
||||
|
||||
void _updateObjectMetadata( const Maps::Map_Format::TileObjectInfo & object, const uint32_t newObjectUID );
|
||||
|
||||
@@ -174,6 +187,8 @@ namespace Interface
|
||||
|
||||
bool _placeCastle( const int32_t posX, const int32_t posY, const PlayerColor color, const int32_t type );
|
||||
|
||||
void _resetMovableObjectInfo();
|
||||
|
||||
EditorPanel _editorPanel;
|
||||
|
||||
int32_t _areaSelectionStartTileId{ -1 };
|
||||
@@ -181,6 +196,8 @@ namespace Interface
|
||||
|
||||
std::set<int32_t> _brushTiles;
|
||||
|
||||
MovableObjectInfo _movableObjectInfo;
|
||||
|
||||
Maps::Random_Generator::Configuration _randomMapConfig;
|
||||
|
||||
std::function<void( const int32_t )> _cursorUpdater;
|
||||
|
||||
@@ -553,7 +553,7 @@ namespace Interface
|
||||
}
|
||||
}
|
||||
else if ( _selectedInstrument == Instrument::DETAIL ) {
|
||||
const fheroes2::Text instrumentName( _( "Cell\nDetails" ), fheroes2::FontType::normalWhite() );
|
||||
const fheroes2::Text instrumentName( _( "Click to edit\n\nor\n\nclick and hold to move" ), fheroes2::FontType::normalWhite() );
|
||||
instrumentName.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 8, _rectInstrumentPanel.width - 10, display );
|
||||
}
|
||||
else if ( _selectedInstrument == Instrument::ROAD ) {
|
||||
@@ -620,16 +620,25 @@ namespace Interface
|
||||
if ( _selectedLandscapeObject < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert( static_cast<size_t>( _selectedLandscapeObject ) < _selectedLandscapeObjectType.size() );
|
||||
|
||||
return _selectedLandscapeObjectType[_selectedLandscapeObject];
|
||||
case Instrument::ADVENTURE_OBJECTS:
|
||||
if ( _selectedAdventureObject < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert( static_cast<size_t>( _selectedAdventureObject ) < _selectedAdventureObjectType.size() );
|
||||
|
||||
return _selectedAdventureObjectType[_selectedAdventureObject];
|
||||
case Instrument::KINGDOM_OBJECTS:
|
||||
if ( _selectedKingdomObject < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert( static_cast<size_t>( _selectedKingdomObject ) < _selectedKingdomObjectType.size() );
|
||||
|
||||
return _selectedKingdomObjectType[_selectedKingdomObject];
|
||||
default:
|
||||
// Why are you trying to get type for the non-object instrument. Check your logic!
|
||||
@@ -797,78 +806,24 @@ namespace Interface
|
||||
{
|
||||
switch ( _selectedInstrument ) {
|
||||
case Instrument::MONSTERS:
|
||||
_interface.setCursorUpdater(
|
||||
[type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } );
|
||||
setObjectBasedCursor( getSelectedObjectType(), getSelectedObjectGroup() );
|
||||
return;
|
||||
case Instrument::LANDSCAPE_OBJECTS:
|
||||
switch ( _selectedLandscapeObject ) {
|
||||
case LandscapeObjectBrush::LANDSCAPE_MISC:
|
||||
case LandscapeObjectBrush::MOUNTAINS:
|
||||
case LandscapeObjectBrush::ROCKS:
|
||||
case LandscapeObjectBrush::TREES:
|
||||
case LandscapeObjectBrush::WATER_OBJECTS:
|
||||
_interface.setCursorUpdater(
|
||||
[type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } );
|
||||
if ( _selectedLandscapeObject > -1 ) {
|
||||
setObjectBasedCursor( getSelectedObjectType(), getSelectedObjectGroup() );
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Instrument::ADVENTURE_OBJECTS:
|
||||
switch ( _selectedAdventureObject ) {
|
||||
case AdventureObjectBrush::ADVENTURE_MISC:
|
||||
case AdventureObjectBrush::ARTIFACTS:
|
||||
case AdventureObjectBrush::DWELLINGS:
|
||||
case AdventureObjectBrush::MINES:
|
||||
case AdventureObjectBrush::POWER_UPS:
|
||||
case AdventureObjectBrush::TREASURES:
|
||||
case AdventureObjectBrush::WATER_ADVENTURE:
|
||||
_interface.setCursorUpdater(
|
||||
[type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } );
|
||||
if ( _selectedAdventureObject > -1 ) {
|
||||
setObjectBasedCursor( getSelectedObjectType(), getSelectedObjectGroup() );
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Instrument::KINGDOM_OBJECTS:
|
||||
switch ( _selectedKingdomObject ) {
|
||||
case KingdomObjectBrush::HEROES:
|
||||
_interface.setCursorUpdater( [type = getSelectedObjectType()]( const int32_t /*tileIndex*/ ) {
|
||||
if ( type == -1 ) {
|
||||
// The object type is not set. We show the POINTER cursor for this case.
|
||||
Cursor::Get().SetThemes( Cursor::POINTER );
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: render ICN::MINIHERO from the existing hero images.
|
||||
const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::MINIHERO, type );
|
||||
|
||||
// Mini-hero images contain a pole with a flag.
|
||||
// This causes a situation that a selected tile does not properly correspond to current position of the cursor.
|
||||
// We need to add a hardcoded correction.
|
||||
const int32_t heroCorrectionY{ 12 };
|
||||
Cursor::Get().setCustomImage( image, { -image.width() / 2, -image.height() / 2 - heroCorrectionY } );
|
||||
} );
|
||||
if ( _selectedKingdomObject > -1 ) {
|
||||
setObjectBasedCursor( getSelectedObjectType(), getSelectedObjectGroup() );
|
||||
return;
|
||||
case KingdomObjectBrush::TOWNS:
|
||||
_interface.setCursorUpdater( [this]( const int32_t tileIndex ) {
|
||||
int32_t type = -1;
|
||||
int32_t color = -1;
|
||||
getTownObjectProperties( type, color );
|
||||
|
||||
if ( type == -1 || color == -1 ) {
|
||||
// The object type is not set. We show the POINTER cursor for this case.
|
||||
Cursor::Get().SetThemes( Cursor::POINTER );
|
||||
return;
|
||||
}
|
||||
|
||||
const fheroes2::Sprite & image = fheroes2::generateTownObjectImage( type, color, world.getTile( tileIndex ).GetGround() );
|
||||
|
||||
Cursor::Get().setCustomImage( image, { image.x(), image.y() } );
|
||||
} );
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -878,6 +833,68 @@ namespace Interface
|
||||
_interface.setCursorUpdater( {} );
|
||||
}
|
||||
|
||||
void EditorPanel::setObjectBasedCursor( const int32_t type, const Maps::ObjectGroup group )
|
||||
{
|
||||
if ( type < 0 ) {
|
||||
// The object type is not set. We show the POINTER cursor for this case.
|
||||
Cursor::Get().SetThemes( Cursor::POINTER );
|
||||
_interface.setCursorUpdater( {} );
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( group ) {
|
||||
case Maps::ObjectGroup::ADVENTURE_ARTIFACTS:
|
||||
case Maps::ObjectGroup::ADVENTURE_DWELLINGS:
|
||||
case Maps::ObjectGroup::ADVENTURE_MINES:
|
||||
case Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS:
|
||||
case Maps::ObjectGroup::ADVENTURE_POWER_UPS:
|
||||
case Maps::ObjectGroup::ADVENTURE_TREASURES:
|
||||
case Maps::ObjectGroup::ADVENTURE_WATER:
|
||||
case Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_MOUNTAINS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_ROCKS:
|
||||
case Maps::ObjectGroup::LANDSCAPE_TREES:
|
||||
case Maps::ObjectGroup::LANDSCAPE_WATER:
|
||||
case Maps::ObjectGroup::MONSTERS:
|
||||
_interface.setCursorUpdater( [group, type]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } );
|
||||
return;
|
||||
case Maps::ObjectGroup::KINGDOM_HEROES:
|
||||
_interface.setCursorUpdater( [type]( const int32_t /*tileIndex*/ ) {
|
||||
// TODO: render ICN::MINIHERO from the existing hero images.
|
||||
const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::MINIHERO, type );
|
||||
|
||||
// Mini-hero images contain a pole with a flag.
|
||||
// This causes a situation that a selected tile does not properly correspond to current position of the cursor.
|
||||
// We need to add a hardcoded correction.
|
||||
constexpr int32_t heroCorrectionY{ 12 };
|
||||
Cursor::Get().setCustomImage( image, { -image.width() / 2, -image.height() / 2 - heroCorrectionY } );
|
||||
} );
|
||||
return;
|
||||
case Maps::ObjectGroup::KINGDOM_TOWNS: {
|
||||
int32_t townType = -1;
|
||||
int32_t color = -1;
|
||||
getTownObjectProperties( type, townType, color );
|
||||
|
||||
_interface.setCursorUpdater( [townType, color]( const int32_t tileIndex ) {
|
||||
if ( townType == -1 || color == -1 ) {
|
||||
// The object type is not set. We show the POINTER cursor for this case.
|
||||
Cursor::Get().SetThemes( Cursor::POINTER );
|
||||
return;
|
||||
}
|
||||
|
||||
const fheroes2::Sprite & image = fheroes2::generateTownObjectImage( townType, color, world.getTile( tileIndex ).GetGround() );
|
||||
|
||||
Cursor::Get().setCustomImage( image, { image.x(), image.y() } );
|
||||
} );
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_interface.setCursorUpdater( {} );
|
||||
}
|
||||
|
||||
void EditorPanel::updateUndoRedoButtonsStates( const bool isUndoAvailable, const bool isRedoAvailable )
|
||||
{
|
||||
auto updateButtonState = []( fheroes2::ButtonBase & button, const bool isAvailable ) {
|
||||
@@ -1140,10 +1157,10 @@ namespace Interface
|
||||
int32_t type = -1;
|
||||
int32_t color = -1;
|
||||
|
||||
getTownObjectProperties( type, color );
|
||||
getTownObjectProperties( getSelectedObjectType(), type, color );
|
||||
Dialog::selectTownType( type, color );
|
||||
|
||||
return _generateTownObjectProperties( type, color );
|
||||
return generateTownObjectProperties( type, color );
|
||||
} );
|
||||
return res;
|
||||
}
|
||||
@@ -1213,7 +1230,7 @@ namespace Interface
|
||||
Dialog::ZERO );
|
||||
}
|
||||
else if ( le.isMouseRightButtonPressedInArea( _instrumentButtonsRect[Instrument::DETAIL] ) ) {
|
||||
fheroes2::showStandardTextMessage( _( "Detail Mode" ), _( "Used for special editing of action objects." ), Dialog::ZERO );
|
||||
fheroes2::showStandardTextMessage( _( "Detail Mode" ), _( "Used for moving or customizing objects." ), Dialog::ZERO );
|
||||
}
|
||||
else if ( le.isMouseRightButtonPressedInArea( _instrumentButtonsRect[Instrument::ADVENTURE_OBJECTS] ) ) {
|
||||
fheroes2::showStandardTextMessage( _( "Adventure Objects Mode" ),
|
||||
@@ -1285,7 +1302,7 @@ namespace Interface
|
||||
_interface.updateCursor( _interface.getGameArea().GetValidTileIdFromPoint( LocalEvent::Get().getMouseCursorPos() ) );
|
||||
}
|
||||
|
||||
void EditorPanel::getTownObjectProperties( int32_t & type, int32_t & color ) const
|
||||
void EditorPanel::getTownObjectProperties( const int32_t packedType, int32_t & type, int32_t & color )
|
||||
{
|
||||
const auto & townObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS );
|
||||
if ( townObjects.empty() ) {
|
||||
@@ -1296,11 +1313,11 @@ namespace Interface
|
||||
return;
|
||||
}
|
||||
|
||||
type = _selectedKingdomObjectType[KingdomObjectBrush::TOWNS] % static_cast<int32_t>( townObjects.size() );
|
||||
color = _selectedKingdomObjectType[KingdomObjectBrush::TOWNS] / static_cast<int32_t>( townObjects.size() );
|
||||
type = packedType % static_cast<int32_t>( townObjects.size() );
|
||||
color = packedType / static_cast<int32_t>( townObjects.size() );
|
||||
}
|
||||
|
||||
int32_t EditorPanel::_generateTownObjectProperties( const int32_t type, const int32_t color )
|
||||
int32_t EditorPanel::generateTownObjectProperties( const int32_t type, const int32_t color )
|
||||
{
|
||||
const auto & townObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS );
|
||||
if ( townObjects.empty() ) {
|
||||
|
||||
@@ -96,8 +96,17 @@ namespace Interface
|
||||
|
||||
bool useMouseDragMovement() const
|
||||
{
|
||||
return ( _selectedInstrument != Instrument::TERRAIN && _selectedInstrument != Instrument::ERASE && _selectedInstrument != Instrument::ROAD
|
||||
&& _selectedInstrument != Instrument::STREAM );
|
||||
switch ( _selectedInstrument ) {
|
||||
case Instrument::ADVENTURE_OBJECTS:
|
||||
case Instrument::KINGDOM_OBJECTS:
|
||||
case Instrument::LANDSCAPE_OBJECTS:
|
||||
case Instrument::MONSTERS:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set Editor panel positions on screen.
|
||||
@@ -116,12 +125,16 @@ namespace Interface
|
||||
|
||||
Maps::ObjectGroup getSelectedObjectGroup() const;
|
||||
|
||||
void getTownObjectProperties( int32_t & type, int32_t & color ) const;
|
||||
|
||||
static const char * getObjectGroupName( const Maps::ObjectGroup groupName );
|
||||
|
||||
void updateUndoRedoButtonsStates( const bool isUndoAvailable, const bool isRedoAvailable );
|
||||
|
||||
void setObjectBasedCursor( const int32_t type, const Maps::ObjectGroup group );
|
||||
|
||||
static void getTownObjectProperties( const int32_t packedType, int32_t & type, int32_t & color );
|
||||
|
||||
static int32_t generateTownObjectProperties( const int32_t type, const int32_t color );
|
||||
|
||||
private:
|
||||
static int _getGroundId( const uint8_t brushId );
|
||||
|
||||
@@ -135,8 +148,6 @@ namespace Interface
|
||||
static const char * _getKingdomObjectTypeName( const uint8_t brushId );
|
||||
static const char * _getEraseObjectTypeName( const uint32_t eraseObjectType );
|
||||
|
||||
static int32_t _generateTownObjectProperties( const int32_t type, const int32_t color );
|
||||
|
||||
void _setCursor();
|
||||
|
||||
void handleObjectMouseClick( const std::function<int( int )> & typeSelection );
|
||||
|
||||
@@ -1300,7 +1300,7 @@ void Heroes::SetVisited( const int32_t tileIndex, const Visit::Type type /* = Vi
|
||||
|
||||
// An object could be bigger than 1 tile so we need to check all its tiles.
|
||||
constexpr int32_t searchDist = []() constexpr {
|
||||
constexpr int32_t max = std::max( Maps::maxActionObjectDimensions.width, Maps::maxActionObjectDimensions.height );
|
||||
constexpr int32_t max = std::max( Maps::maxActionGroundObjectDimensions.width, Maps::maxActionGroundObjectDimensions.height );
|
||||
static_assert( max > 0 );
|
||||
|
||||
return max - 1;
|
||||
@@ -1342,7 +1342,7 @@ void Heroes::setVisitedForAllies( const int32_t tileIndex ) const
|
||||
|
||||
// An object could be bigger than 1 tile so we need to check all its tiles.
|
||||
constexpr int32_t searchDist = []() constexpr {
|
||||
constexpr int32_t max = std::max( Maps::maxActionObjectDimensions.width, Maps::maxActionObjectDimensions.height );
|
||||
constexpr int32_t max = std::max( Maps::maxActionGroundObjectDimensions.width, Maps::maxActionGroundObjectDimensions.height );
|
||||
static_assert( max > 0 );
|
||||
|
||||
return max - 1;
|
||||
|
||||
@@ -6156,24 +6156,45 @@ namespace
|
||||
}
|
||||
|
||||
// Calculate the maximum possible dimensions of the area occupied by the parts of multi-tile action objects and verify them.
|
||||
fheroes2::Size maxActionObjDim{ 1, 1 };
|
||||
fheroes2::Size maxObjDim{ 1, 1 };
|
||||
|
||||
for ( const auto & objects : objectData ) {
|
||||
for ( const auto & objectInfo : objects ) {
|
||||
if ( !MP2::isOffGameActionObject( objectInfo.objectType ) ) {
|
||||
continue;
|
||||
if ( MP2::isOffGameActionObject( objectInfo.objectType ) ) {
|
||||
fheroes2::Point minOffset;
|
||||
fheroes2::Point maxOffset;
|
||||
|
||||
for ( const auto & info : objectInfo.groundLevelParts ) {
|
||||
if ( info.objectType == objectInfo.objectType ) {
|
||||
minOffset.x = std::min( minOffset.x, info.tileOffset.x );
|
||||
minOffset.y = std::min( minOffset.y, info.tileOffset.y );
|
||||
maxOffset.x = std::max( maxOffset.x, info.tileOffset.x );
|
||||
maxOffset.y = std::max( maxOffset.y, info.tileOffset.y );
|
||||
}
|
||||
}
|
||||
|
||||
const fheroes2::Size objDim{ maxOffset.x - minOffset.x + 1, maxOffset.y - minOffset.y + 1 };
|
||||
|
||||
maxActionObjDim.width = std::max( maxActionObjDim.width, objDim.width );
|
||||
maxActionObjDim.height = std::max( maxActionObjDim.height, objDim.height );
|
||||
}
|
||||
|
||||
fheroes2::Point minOffset;
|
||||
fheroes2::Point maxOffset;
|
||||
|
||||
for ( const auto & info : objectInfo.groundLevelParts ) {
|
||||
if ( info.objectType == objectInfo.objectType ) {
|
||||
minOffset.x = std::min( minOffset.x, info.tileOffset.x );
|
||||
minOffset.y = std::min( minOffset.y, info.tileOffset.y );
|
||||
maxOffset.x = std::max( maxOffset.x, info.tileOffset.x );
|
||||
maxOffset.y = std::max( maxOffset.y, info.tileOffset.y );
|
||||
}
|
||||
minOffset.x = std::min( minOffset.x, info.tileOffset.x );
|
||||
minOffset.y = std::min( minOffset.y, info.tileOffset.y );
|
||||
maxOffset.x = std::max( maxOffset.x, info.tileOffset.x );
|
||||
maxOffset.y = std::max( maxOffset.y, info.tileOffset.y );
|
||||
}
|
||||
|
||||
for ( const auto & info : objectInfo.topLevelParts ) {
|
||||
minOffset.x = std::min( minOffset.x, info.tileOffset.x );
|
||||
minOffset.y = std::min( minOffset.y, info.tileOffset.y );
|
||||
maxOffset.x = std::max( maxOffset.x, info.tileOffset.x );
|
||||
maxOffset.y = std::max( maxOffset.y, info.tileOffset.y );
|
||||
}
|
||||
|
||||
const fheroes2::Size objDim{ maxOffset.x - minOffset.x + 1, maxOffset.y - minOffset.y + 1 };
|
||||
@@ -6185,7 +6206,8 @@ namespace
|
||||
|
||||
// If this assertion blows up then some in-game logic might break. If a new object of a non-standard
|
||||
// size has been added, consider updating the maximum action object dimensions.
|
||||
assert( maxObjDim == Maps::maxActionObjectDimensions );
|
||||
assert( maxActionObjDim == Maps::maxActionGroundObjectDimensions );
|
||||
assert( maxObjDim == Maps::maxObjectDimensions );
|
||||
#endif
|
||||
|
||||
isPopulated = true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/***************************************************************************
|
||||
* fheroes2: https://github.com/ihhub/fheroes2 *
|
||||
* Copyright (C) 2023 - 2025 *
|
||||
* Copyright (C) 2023 - 2026 *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
@@ -30,8 +30,11 @@
|
||||
|
||||
namespace Maps
|
||||
{
|
||||
// The maximum possible dimensions (width and height) for an action object on the map.
|
||||
constexpr fheroes2::Size maxActionObjectDimensions{ 4, 2 };
|
||||
// The maximum possible dimensions (width and height) for an action ground object on the map.
|
||||
constexpr fheroes2::Size maxActionGroundObjectDimensions{ 4, 2 };
|
||||
|
||||
// The maximum object dimensions possible, including top and bottom parts.
|
||||
constexpr fheroes2::Size maxObjectDimensions{ 8, 5 };
|
||||
|
||||
// An object usually contains of multiple parts / tiles. Each part has its own features like object layer type or image index.
|
||||
// An object always contains a main object part.
|
||||
|
||||
Reference in New Issue
Block a user