Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random terrain generation #8600

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion fheroes2-vs2019.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\fheroes2\maps\map_generator.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\fheroes2\maps\map_generator.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{DD8F214C-C405-4951-8F98-66B969BA8E08}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
Expand Down Expand Up @@ -48,4 +54,4 @@
<Import Project="VisualStudio\fheroes2\Release.props" />
</ImportGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>
idshibanov marked this conversation as resolved.
Show resolved Hide resolved
86 changes: 32 additions & 54 deletions src/fheroes2/editor/editor_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
#include <string>
#include <vector>

#include <logging.h>
#include <rand.h>

#include "agg_image.h"
#include "artifact.h"
#include "audio_manager.h"
Expand All @@ -55,6 +58,7 @@
#include "interface_radar.h"
#include "localevent.h"
#include "map_format_helper.h"
#include "map_generator.h"
#include "map_object_info.h"
#include "maps.h"
#include "maps_tiles.h"
Expand Down Expand Up @@ -103,60 +107,6 @@ namespace
return { startPos.x + startPos.y * worldWidth, endPos.x + endPos.y * worldWidth };
}

bool isObjectPlacementAllowed( const Maps::ObjectInfo & info, const fheroes2::Point & mainTilePos )
{
// Run through all tile offsets and check that all objects parts can be put on the map.
for ( const auto & objectPart : info.groundLevelParts ) {
if ( objectPart.layerType == Maps::SHADOW_LAYER ) {
// Shadow layer parts are ignored.
continue;
}

if ( !Maps::isValidAbsPoint( mainTilePos.x + objectPart.tileOffset.x, mainTilePos.y + objectPart.tileOffset.y ) ) {
return false;
}
}

for ( const auto & objectPart : info.topLevelParts ) {
if ( !Maps::isValidAbsPoint( mainTilePos.x + objectPart.tileOffset.x, mainTilePos.y + objectPart.tileOffset.y ) ) {
return false;
}
}

return true;
}

bool isActionObjectAllowed( const Maps::ObjectInfo & info, const fheroes2::Point & mainTilePos )
{
// Active action object parts must be placed on a tile without any other objects.
// Only ground parts should be checked for this condition.
for ( const auto & objectPart : info.groundLevelParts ) {
if ( objectPart.layerType == Maps::SHADOW_LAYER || objectPart.layerType == Maps::TERRAIN_LAYER ) {
// Shadow and terrain layer parts are ignored.
continue;
}

const fheroes2::Point pos{ mainTilePos.x + objectPart.tileOffset.x, mainTilePos.y + objectPart.tileOffset.y };
if ( !Maps::isValidAbsPoint( pos.x, pos.y ) ) {
return false;
}

const auto & tile = world.GetTiles( pos.x, pos.y );

if ( MP2::isActionObject( tile.GetObject() ) ) {
// An action already exist. We cannot allow to put anything on top of it.
return false;
}

if ( MP2::isActionObject( objectPart.objectType ) && !Maps::isClearGround( tile ) ) {
// We are trying to place an action object on a tile that has some other objects.
return false;
}
}

return true;
}

bool isConditionValid( const std::vector<fheroes2::Point> & offsets, const fheroes2::Point & mainTilePos,
const std::function<bool( const Maps::Tiles & tile )> & condition )
{
Expand Down Expand Up @@ -581,6 +531,34 @@ namespace Interface
else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_VIEW_WORLD ) ) {
eventViewWorld();
}
#if defined( WITH_DEBUG )
else if ( HotKeyPressEvent( Game::HotKeyEvent::EDITOR_RANDOM_MAP_GENERATION ) ) {
fheroes2::ActionCreator action( _historyManager, _mapFormat );

Maps::Generator::Configuration rmgConfig;
rmgConfig.playerCount = _playerCount;
idshibanov marked this conversation as resolved.
Show resolved Hide resolved
rmgConfig.regionSizeLimit = _regionSizeLimit;
idshibanov marked this conversation as resolved.
Show resolved Hide resolved

if ( Maps::Generator::generateWorld( _mapFormat, rmgConfig ) ) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially, we are generating map so would generateMap be a better name?

_redraw |= mapUpdateFlags;

action.commit();
}
else {
_warningMessage.reset( _( "Not able to generate a map with given parameters." ) );
}
}
else if ( HotKeyPressEvent( Game::HotKeyEvent::EDITOR_RANDOM_MAP_CONFIGURATION ) ) {
uint32_t newCount = _playerCount;
if ( Dialog::SelectCount( "Pick player count", 2, 6, newCount ) ) {
_playerCount = newCount;
}
newCount = _regionSizeLimit;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that region size is an understandable entity for map makers. Can we come up with something more user friendly metric? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with renaming it but can't think of more suitable name.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we attach the number of regions to the number of players minus the size of water on map? I assume we don't generate water area for now which is easier. In this case we won't need this parameter.

if ( Dialog::SelectCount( "Limit region size", 100, 10000, newCount ) ) {
_regionSizeLimit = newCount;
}
}
#endif
// map scrolling control
else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_LEFT ) ) {
_gameArea.SetScroll( SCROLL_LEFT );
Expand Down
3 changes: 3 additions & 0 deletions src/fheroes2/editor/editor_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ namespace Interface
int32_t _selectedTile{ -1 };
int32_t _tileUnderCursor{ -1 };

uint32_t _playerCount = 2;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we store Configuration object instead of these 2 members?

uint32_t _regionSizeLimit = 600;

std::function<void( const int32_t )> _cursorUpdater;

fheroes2::HistoryManager _historyManager;
Expand Down
7 changes: 7 additions & 0 deletions src/fheroes2/game/game_hotkeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ namespace
hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_TO_GAME_MAIN_MENU )]
= { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|open game main menu" ), fheroes2::Key::KEY_M };

#if defined( WITH_DEBUG )
hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_RANDOM_MAP_GENERATION )]
= { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|generate random map" ), fheroes2::Key::KEY_F5 };
hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_RANDOM_MAP_CONFIGURATION )]
= { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|configure random map generator" ), fheroes2::Key::KEY_F6 };
#endif

hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_ROLAND )]
= { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|roland campaign" ), fheroes2::Key::KEY_1 };
hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_ARCHIBALD )]
Expand Down
6 changes: 6 additions & 0 deletions src/fheroes2/game/game_hotkeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ namespace Game
EDITOR_REDO_LAST_ACTION,
EDITOR_TO_GAME_MAIN_MENU,

#if defined( WITH_DEBUG )
// This hotkey is only for debug mode as of now.
EDITOR_RANDOM_MAP_GENERATION,
EDITOR_RANDOM_MAP_CONFIGURATION,
#endif

CAMPAIGN_ROLAND,
CAMPAIGN_ARCHIBALD,
CAMPAIGN_PRICE_OF_LOYALTY,
Expand Down
101 changes: 101 additions & 0 deletions src/fheroes2/gui/ui_map_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,105 @@ namespace fheroes2

return 0;
}

constexpr int32_t mineIndexFromGroundType( const int groundType )
{
switch ( groundType ) {
case Maps::Ground::WATER:
// Logically Water is not allowed but let's do this.
assert( 0 );
return 0;
case Maps::Ground::GRASS:
return 1;
case Maps::Ground::SNOW:
return 2;
case Maps::Ground::SWAMP:
return 3;
case Maps::Ground::LAVA:
return 4;
case Maps::Ground::DESERT:
return 5;
case Maps::Ground::DIRT:
return 6;
case Maps::Ground::WASTELAND:
return 7;
case Maps::Ground::BEACH:
return 0;
default:
// Have you added a new ground? Add the logic above!
assert( 0 );
break;
}
return 0;
}

constexpr int32_t sawmillIndexFromGroundType( const int groundType )
{
switch ( groundType ) {
case Maps::Ground::WATER:
// Logically Water is not allowed but let's do this.
assert( 0 );
return 0;
case Maps::Ground::GRASS:
return 0;
case Maps::Ground::SNOW:
return 1;
case Maps::Ground::SWAMP:
return 0;
case Maps::Ground::LAVA:
return 2;
case Maps::Ground::DESERT:
return 3;
case Maps::Ground::DIRT:
return 4;
case Maps::Ground::WASTELAND:
return 5;
case Maps::Ground::BEACH:
return 3;
default:
// Have you added a new ground? Add the logic above!
assert( 0 );
break;
}
return 0;
}

int32_t getMineObjectInfoId( const int resource, const int groundType )
{
// 8 terrain and 5 resources
// 2 abandoned mines: grass & dirt
// Sawmills for different terrains: Grass/Swamp, Snow, Lava, Desert, Dirt, Wasteland.
// 2 alchemists labs: regular and snow

// if you add new mine type update this logic!
assert( Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_MINES ).size() == 50 );

const int groundIndex = mineIndexFromGroundType( groundType );

switch ( resource ) {
case Resource::ORE:
return groundIndex * 5;
case Resource::SULFUR:
return groundIndex * 5 + 1;
case Resource::CRYSTAL:
return groundIndex * 5 + 2;
case Resource::GEMS:
return groundIndex * 5 + 3;
case Resource::GOLD:
return groundIndex * 5 + 4;
case Resource::WOOD:
return 5 * 8 + 2 + sawmillIndexFromGroundType( groundType );
case Resource::MERCURY:
return groundType == Maps::Ground::SNOW ? 49 : 48;
case Resource::UNKNOWN:
// must be an abandoned mine
return groundType == Maps::Ground::GRASS ? 40 : 41;
default:
// Have you added a new resource type?!
assert( 0 );
break;
}

return 0;
}
}
3 changes: 2 additions & 1 deletion src/fheroes2/gui/ui_map_object.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2023 *
* Copyright (C) 2023 - 2024 *
* *
* 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 *
Expand Down Expand Up @@ -35,4 +35,5 @@ namespace fheroes2
Sprite generateTownObjectImage( const int townType, const int color, const int groundId );

int32_t getTownBasementId( const int groundType );
int32_t getMineObjectInfoId( const int resource, const int groundType );
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an empty line to differentiate 2 functions.

}
Loading
Loading