2023-10-19 16:19:09 +02:00
/*
* AdventureMapInterface . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "AdventureMapInterface.h"
# include "AdventureOptions.h"
# include "AdventureState.h"
# include "CInGameConsole.h"
# include "CMinimap.h"
# include "CList.h"
# include "CInfoBar.h"
# include "MapAudioPlayer.h"
# include "TurnTimerWidget.h"
# include "AdventureMapWidget.h"
# include "AdventureMapShortcuts.h"
# include "../mapView/mapHandler.h"
# include "../mapView/MapView.h"
# include "../windows/InfoWindows.h"
# include "../widgets/RadialMenu.h"
# include "../CGameInfo.h"
# include "../gui/CursorHandler.h"
# include "../gui/CGuiHandler.h"
# include "../gui/Shortcut.h"
# include "../gui/WindowHandler.h"
# include "../render/Canvas.h"
2024-01-18 01:47:18 +02:00
# include "../render/IRenderHandler.h"
2023-10-19 16:19:09 +02:00
# include "../CMT.h"
# include "../PlayerLocalState.h"
# include "../CPlayerInterface.h"
# include "../../CCallback.h"
2024-03-28 00:39:08 +02:00
# include "../../lib/GameSettings.h"
2023-10-19 16:19:09 +02:00
# include "../../lib/StartInfo.h"
# include "../../lib/CGeneralTextHandler.h"
# include "../../lib/spells/CSpellHandler.h"
# include "../../lib/mapObjects/CGHeroInstance.h"
# include "../../lib/mapObjects/CGTownInstance.h"
# include "../../lib/mapping/CMapDefines.h"
# include "../../lib/pathfinder/CGPathNode.h"
std : : shared_ptr < AdventureMapInterface > adventureInt ;
AdventureMapInterface : : AdventureMapInterface ( ) :
mapAudio ( new MapAudioPlayer ( ) ) ,
spellBeingCasted ( nullptr ) ,
scrollingWasActive ( false ) ,
scrollingWasBlocked ( false ) ,
backgroundDimLevel ( settings [ " adventure " ] [ " backgroundDimLevel " ] . Integer ( ) )
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE ;
pos . x = pos . y = 0 ;
pos . w = GH . screenDimensions ( ) . x ;
pos . h = GH . screenDimensions ( ) . y ;
shortcuts = std : : make_shared < AdventureMapShortcuts > ( * this ) ;
widget = std : : make_shared < AdventureMapWidget > ( shortcuts ) ;
shortcuts - > setState ( EAdventureState : : MAKING_TURN ) ;
widget - > getMapView ( ) - > onViewMapActivated ( ) ;
2023-12-19 17:41:20 +02:00
if ( LOCPLINT - > cb - > getStartInfo ( ) - > turnTimerInfo . turnTimer ! = 0 )
watches = std : : make_shared < TurnTimerWidget > ( Point ( 24 , 24 ) ) ;
2023-10-19 16:19:09 +02:00
addUsedEvents ( KEYBOARD | TIME ) ;
}
void AdventureMapInterface : : onMapViewMoved ( const Rect & visibleArea , int mapLevel )
{
shortcuts - > onMapViewMoved ( visibleArea , mapLevel ) ;
widget - > getMinimap ( ) - > onMapViewMoved ( visibleArea , mapLevel ) ;
widget - > onMapViewMoved ( visibleArea , mapLevel ) ;
}
void AdventureMapInterface : : onAudioResumed ( )
{
mapAudio - > onAudioResumed ( ) ;
}
void AdventureMapInterface : : onAudioPaused ( )
{
mapAudio - > onAudioPaused ( ) ;
}
void AdventureMapInterface : : onHeroMovementStarted ( const CGHeroInstance * hero )
{
if ( shortcuts - > optionMapViewActive ( ) )
{
widget - > getInfoBar ( ) - > popAll ( ) ;
widget - > getInfoBar ( ) - > showSelection ( ) ;
}
}
void AdventureMapInterface : : onHeroChanged ( const CGHeroInstance * h )
{
widget - > getHeroList ( ) - > updateElement ( h ) ;
if ( h & & h = = LOCPLINT - > localState - > getCurrentHero ( ) & & ! widget - > getInfoBar ( ) - > showingComponents ( ) )
widget - > getInfoBar ( ) - > showSelection ( ) ;
widget - > updateActiveState ( ) ;
}
void AdventureMapInterface : : onTownChanged ( const CGTownInstance * town )
{
widget - > getTownList ( ) - > updateElement ( town ) ;
if ( town & & town = = LOCPLINT - > localState - > getCurrentTown ( ) & & ! widget - > getInfoBar ( ) - > showingComponents ( ) )
widget - > getInfoBar ( ) - > showSelection ( ) ;
}
void AdventureMapInterface : : showInfoBoxMessage ( const std : : vector < Component > & components , std : : string message , int timer )
{
widget - > getInfoBar ( ) - > pushComponents ( components , message , timer ) ;
}
void AdventureMapInterface : : activate ( )
{
CIntObject : : activate ( ) ;
adjustActiveness ( ) ;
screenBuf = screen ;
if ( LOCPLINT )
{
LOCPLINT - > cingconsole - > activate ( ) ;
LOCPLINT - > cingconsole - > pos = this - > pos ;
}
GH . fakeMouseMove ( ) ; //to restore the cursor
// workaround for an edge case:
// if player unequips Angel Wings / Boots of Levitation of currently active hero
// game will correctly invalidate paths but current route will not be updated since verifyPath() is not called for current hero
if ( LOCPLINT - > makingTurn & & LOCPLINT - > localState - > getCurrentHero ( ) )
LOCPLINT - > localState - > verifyPath ( LOCPLINT - > localState - > getCurrentHero ( ) ) ;
}
void AdventureMapInterface : : deactivate ( )
{
CIntObject : : deactivate ( ) ;
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
if ( LOCPLINT )
LOCPLINT - > cingconsole - > deactivate ( ) ;
}
void AdventureMapInterface : : showAll ( Canvas & to )
{
CIntObject : : showAll ( to ) ;
dim ( to ) ;
LOCPLINT - > cingconsole - > show ( to ) ;
}
void AdventureMapInterface : : show ( Canvas & to )
{
CIntObject : : show ( to ) ;
dim ( to ) ;
LOCPLINT - > cingconsole - > show ( to ) ;
}
void AdventureMapInterface : : dim ( Canvas & to )
{
2024-01-18 01:47:18 +02:00
if ( settings [ " adventure " ] [ " hideBackground " ] . Bool ( ) )
for ( auto window : GH . windows ( ) . findWindows < IShowActivatable > ( ) )
{
if ( ! std : : dynamic_pointer_cast < AdventureMapInterface > ( window ) & & std : : dynamic_pointer_cast < CIntObject > ( window ) & & std : : dynamic_pointer_cast < CIntObject > ( window ) - > pos . w > = 800 & & std : : dynamic_pointer_cast < CIntObject > ( window ) - > pos . w > = 600 )
{
to . fillTexture ( GH . renderHandler ( ) . loadImage ( ImagePath : : builtin ( " DiBoxBck " ) ) ) ;
return ;
}
}
2023-10-19 16:19:09 +02:00
for ( auto window : GH . windows ( ) . findWindows < IShowActivatable > ( ) )
{
if ( ! std : : dynamic_pointer_cast < AdventureMapInterface > ( window ) & & ! std : : dynamic_pointer_cast < RadialMenu > ( window ) & & ! window - > isPopupWindow ( ) )
{
Rect targetRect ( 0 , 0 , GH . screenDimensions ( ) . x , GH . screenDimensions ( ) . y ) ;
ColorRGBA colorToFill ( 0 , 0 , 0 , std : : clamp < int > ( backgroundDimLevel , 0 , 255 ) ) ;
if ( backgroundDimLevel > 0 )
to . drawColorBlended ( targetRect , colorToFill ) ;
return ;
}
}
}
void AdventureMapInterface : : tick ( uint32_t msPassed )
{
handleMapScrollingUpdate ( msPassed ) ;
// we want animations to be active during enemy turn but map itself to be non-interactive
// so call timer update directly on inactive element
widget - > getMapView ( ) - > tick ( msPassed ) ;
}
void AdventureMapInterface : : handleMapScrollingUpdate ( uint32_t timePassed )
{
/// Width of window border, in pixels, that triggers map scrolling
static constexpr int32_t borderScrollWidth = 15 ;
int32_t scrollSpeedPixels = settings [ " adventure " ] [ " scrollSpeedPixels " ] . Float ( ) ;
int32_t scrollDistance = scrollSpeedPixels * timePassed / 1000 ;
Point cursorPosition = GH . getCursorPosition ( ) ;
Point scrollDirection ;
if ( cursorPosition . x < borderScrollWidth )
scrollDirection . x = - 1 ;
if ( cursorPosition . x > GH . screenDimensions ( ) . x - borderScrollWidth )
scrollDirection . x = + 1 ;
if ( cursorPosition . y < borderScrollWidth )
scrollDirection . y = - 1 ;
if ( cursorPosition . y > GH . screenDimensions ( ) . y - borderScrollWidth )
scrollDirection . y = + 1 ;
Point scrollDelta = scrollDirection * scrollDistance ;
bool cursorInScrollArea = scrollDelta ! = Point ( 0 , 0 ) ;
bool scrollingActive = cursorInScrollArea & & shortcuts - > optionMapScrollingActive ( ) & & ! scrollingWasBlocked ;
bool scrollingBlocked = GH . isKeyboardCtrlDown ( ) | | ! settings [ " adventure " ] [ " borderScroll " ] . Bool ( ) ;
if ( ! scrollingWasActive & & scrollingBlocked )
{
scrollingWasBlocked = true ;
return ;
}
if ( ! cursorInScrollArea & & scrollingWasBlocked )
{
scrollingWasBlocked = false ;
return ;
}
if ( scrollingActive )
widget - > getMapView ( ) - > onMapScrolled ( scrollDelta ) ;
if ( ! scrollingActive & & ! scrollingWasActive )
return ;
if ( scrollDelta . x > 0 )
{
if ( scrollDelta . y < 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_NORTHEAST ) ;
if ( scrollDelta . y > 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_SOUTHEAST ) ;
if ( scrollDelta . y = = 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_EAST ) ;
}
if ( scrollDelta . x < 0 )
{
if ( scrollDelta . y < 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_NORTHWEST ) ;
if ( scrollDelta . y > 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_SOUTHWEST ) ;
if ( scrollDelta . y = = 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_WEST ) ;
}
if ( scrollDelta . x = = 0 )
{
if ( scrollDelta . y < 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_NORTH ) ;
if ( scrollDelta . y > 0 )
CCS - > curh - > set ( Cursor : : Map : : SCROLL_SOUTH ) ;
if ( scrollDelta . y = = 0 )
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
}
scrollingWasActive = scrollingActive ;
}
void AdventureMapInterface : : centerOnTile ( int3 on )
{
widget - > getMapView ( ) - > onCenteredTile ( on ) ;
}
void AdventureMapInterface : : centerOnObject ( const CGObjectInstance * obj )
{
widget - > getMapView ( ) - > onCenteredObject ( obj ) ;
}
void AdventureMapInterface : : keyPressed ( EShortcut key )
{
if ( key = = EShortcut : : GLOBAL_CANCEL & & spellBeingCasted )
hotkeyAbortCastingMode ( ) ;
//fake mouse use to trigger onTileHovered()
GH . fakeMouseMove ( ) ;
}
void AdventureMapInterface : : onSelectionChanged ( const CArmedInstance * sel )
{
assert ( sel ) ;
widget - > getInfoBar ( ) - > popAll ( ) ;
mapAudio - > onSelectionChanged ( sel ) ;
bool centerView = ! settings [ " session " ] [ " autoSkip " ] . Bool ( ) ;
if ( centerView )
centerOnObject ( sel ) ;
if ( sel - > ID = = Obj : : TOWN )
{
auto town = dynamic_cast < const CGTownInstance * > ( sel ) ;
widget - > getInfoBar ( ) - > showTownSelection ( town ) ;
2024-01-16 21:02:39 +02:00
widget - > getTownList ( ) - > updateWidget ( ) ;
2023-10-19 16:19:09 +02:00
widget - > getTownList ( ) - > select ( town ) ;
widget - > getHeroList ( ) - > select ( nullptr ) ;
onHeroChanged ( nullptr ) ;
}
else //hero selected
{
auto hero = dynamic_cast < const CGHeroInstance * > ( sel ) ;
widget - > getInfoBar ( ) - > showHeroSelection ( hero ) ;
widget - > getHeroList ( ) - > select ( hero ) ;
widget - > getTownList ( ) - > select ( nullptr ) ;
LOCPLINT - > localState - > verifyPath ( hero ) ;
onHeroChanged ( hero ) ;
}
widget - > updateActiveState ( ) ;
widget - > getHeroList ( ) - > redraw ( ) ;
widget - > getTownList ( ) - > redraw ( ) ;
}
2023-10-22 17:36:41 +02:00
void AdventureMapInterface : : onTownOrderChanged ( )
{
widget - > getTownList ( ) - > updateWidget ( ) ;
}
2023-12-23 18:13:55 +02:00
void AdventureMapInterface : : onHeroOrderChanged ( )
{
widget - > getHeroList ( ) - > updateWidget ( ) ;
}
2023-10-19 16:19:09 +02:00
void AdventureMapInterface : : onMapTilesChanged ( boost : : optional < std : : unordered_set < int3 > > positions )
{
if ( positions )
widget - > getMinimap ( ) - > updateTiles ( * positions ) ;
else
widget - > getMinimap ( ) - > update ( ) ;
}
void AdventureMapInterface : : onHotseatWaitStarted ( PlayerColor playerID )
{
backgroundDimLevel = 255 ;
onCurrentPlayerChanged ( playerID ) ;
setState ( EAdventureState : : HOTSEAT_WAIT ) ;
}
void AdventureMapInterface : : onEnemyTurnStarted ( PlayerColor playerID , bool isHuman )
{
if ( settings [ " session " ] [ " spectate " ] . Bool ( ) )
return ;
mapAudio - > onEnemyTurnStarted ( ) ;
widget - > getMinimap ( ) - > setAIRadar ( ! isHuman ) ;
widget - > getInfoBar ( ) - > startEnemyTurn ( playerID ) ;
setState ( isHuman ? EAdventureState : : OTHER_HUMAN_PLAYER_TURN : EAdventureState : : AI_PLAYER_TURN ) ;
}
void AdventureMapInterface : : setState ( EAdventureState state )
{
shortcuts - > setState ( state ) ;
adjustActiveness ( ) ;
widget - > updateActiveState ( ) ;
}
void AdventureMapInterface : : adjustActiveness ( )
{
bool widgetMustBeActive = isActive ( ) & & shortcuts - > optionSidePanelActive ( ) ;
bool mapViewMustBeActive = isActive ( ) & & ( shortcuts - > optionMapViewActive ( ) ) ;
widget - > setInputEnabled ( widgetMustBeActive ) ;
widget - > getMapView ( ) - > setInputEnabled ( mapViewMustBeActive ) ;
}
void AdventureMapInterface : : onCurrentPlayerChanged ( PlayerColor playerID )
{
LOCPLINT - > localState - > setSelection ( nullptr ) ;
if ( playerID = = currentPlayerID )
return ;
currentPlayerID = playerID ;
widget - > setPlayer ( playerID ) ;
}
void AdventureMapInterface : : onPlayerTurnStarted ( PlayerColor playerID )
{
backgroundDimLevel = settings [ " adventure " ] [ " backgroundDimLevel " ] . Integer ( ) ;
onCurrentPlayerChanged ( playerID ) ;
setState ( EAdventureState : : MAKING_TURN ) ;
if ( playerID = = LOCPLINT - > playerID | | settings [ " session " ] [ " spectate " ] . Bool ( ) )
{
widget - > getMinimap ( ) - > setAIRadar ( false ) ;
widget - > getInfoBar ( ) - > showSelection ( ) ;
}
widget - > getHeroList ( ) - > updateWidget ( ) ;
widget - > getTownList ( ) - > updateWidget ( ) ;
const CGHeroInstance * heroToSelect = nullptr ;
// find first non-sleeping hero
for ( auto hero : LOCPLINT - > localState - > getWanderingHeroes ( ) )
{
if ( ! LOCPLINT - > localState - > isHeroSleeping ( hero ) )
{
heroToSelect = hero ;
break ;
}
}
//select first hero if available.
if ( heroToSelect ! = nullptr )
{
LOCPLINT - > localState - > setSelection ( heroToSelect ) ;
}
else if ( LOCPLINT - > localState - > getOwnedTowns ( ) . size ( ) )
{
LOCPLINT - > localState - > setSelection ( LOCPLINT - > localState - > getOwnedTown ( 0 ) ) ;
}
else
{
LOCPLINT - > localState - > setSelection ( LOCPLINT - > localState - > getWanderingHero ( 0 ) ) ;
}
//show new day animation and sound on infobar, except for 1st day of the game
if ( LOCPLINT - > cb - > getDate ( Date : : DAY ) ! = 1 )
widget - > getInfoBar ( ) - > showDate ( ) ;
onHeroChanged ( nullptr ) ;
Canvas canvas = Canvas : : createFromSurface ( screen ) ;
showAll ( canvas ) ;
mapAudio - > onPlayerTurnStarted ( ) ;
if ( settings [ " session " ] [ " autoSkip " ] . Bool ( ) & & ! GH . isKeyboardShiftDown ( ) )
{
if ( auto iw = GH . windows ( ) . topWindow < CInfoWindow > ( ) )
iw - > close ( ) ;
2023-12-03 15:58:03 +02:00
GH . dispatchMainThread ( [ this ] ( )
{
hotkeyEndingTurn ( ) ;
} ) ;
2023-10-19 16:19:09 +02:00
}
}
void AdventureMapInterface : : hotkeyEndingTurn ( )
{
if ( settings [ " session " ] [ " spectate " ] . Bool ( ) )
return ;
if ( ! settings [ " general " ] [ " startTurnAutosave " ] . Bool ( ) )
{
LOCPLINT - > performAutosave ( ) ;
}
LOCPLINT - > makingTurn = false ;
LOCPLINT - > cb - > endTurn ( ) ;
mapAudio - > onPlayerTurnEnded ( ) ;
2024-01-13 23:38:43 +02:00
// Normally, game will receive PlayerStartsTurn call almost instantly with new player ID that will switch UI to waiting mode
// However, when simturns are active it is possible for such call not to come because another player is still acting
// So find first player other than ours that is acting at the moment and update UI as if he had started turn
for ( auto player = PlayerColor ( 0 ) ; player < PlayerColor : : PLAYER_LIMIT ; + + player )
{
if ( player ! = LOCPLINT - > playerID & & LOCPLINT - > cb - > isPlayerMakingTurn ( player ) )
{
onEnemyTurnStarted ( player , LOCPLINT - > cb - > getStartInfo ( ) - > playerInfos . at ( player ) . isControlledByHuman ( ) ) ;
break ;
}
}
2023-10-19 16:19:09 +02:00
}
const CGObjectInstance * AdventureMapInterface : : getActiveObject ( const int3 & mapPos )
{
std : : vector < const CGObjectInstance * > bobjs = LOCPLINT - > cb - > getBlockingObjs ( mapPos ) ; //blocking objects at tile
if ( bobjs . empty ( ) )
return nullptr ;
return * boost : : range : : max_element ( bobjs , & CMapHandler : : compareObjectBlitOrder ) ;
}
2024-03-27 23:06:31 +02:00
void AdventureMapInterface : : onTileLeftClicked ( const int3 & targetPosition )
2023-10-19 16:19:09 +02:00
{
if ( ! shortcuts - > optionMapViewActive ( ) )
return ;
2024-03-27 23:06:31 +02:00
if ( ! LOCPLINT - > cb - > isVisible ( targetPosition ) )
2024-03-25 00:58:04 +02:00
{
if ( ! spellBeingCasted | | spellBeingCasted - > id ! = SpellID : : DIMENSION_DOOR )
return ;
}
2023-10-19 16:19:09 +02:00
if ( ! LOCPLINT - > makingTurn )
return ;
2024-03-27 23:06:31 +02:00
const CGObjectInstance * topBlocking = LOCPLINT - > cb - > isVisible ( targetPosition ) ? getActiveObject ( targetPosition ) : nullptr ;
2023-10-19 16:19:09 +02:00
int3 selPos = LOCPLINT - > localState - > getCurrentArmy ( ) - > getSightCenter ( ) ;
if ( spellBeingCasted )
{
assert ( shortcuts - > optionSpellcasting ( ) ) ;
2024-03-27 23:06:31 +02:00
if ( ! isInScreenRange ( selPos , targetPosition ) )
2023-10-19 16:19:09 +02:00
return ;
const TerrainTile * heroTile = LOCPLINT - > cb - > getTile ( selPos ) ;
switch ( spellBeingCasted - > id )
{
case SpellID : : SCUTTLE_BOAT : //Scuttle Boat
if ( topBlocking & & topBlocking - > ID = = Obj : : BOAT )
2024-03-27 23:06:31 +02:00
performSpellcasting ( targetPosition ) ;
2023-10-19 16:19:09 +02:00
break ;
case SpellID : : DIMENSION_DOOR :
2024-03-28 00:39:08 +02:00
bool allowOnlyToUncoveredTiles = VLC - > settings ( ) - > getBoolean ( EGameSettings : : DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES ) ;
const TerrainTile * targetTile = allowOnlyToUncoveredTiles
? LOCPLINT - > cb - > getTile ( targetPosition , false )
: LOCPLINT - > cb - > getTileForDimensionDoor ( targetPosition , LOCPLINT - > localState - > getCurrentHero ( ) ) ;
2024-03-25 00:58:04 +02:00
if ( targetTile & & targetTile - > isClear ( heroTile ) )
2024-03-27 23:06:31 +02:00
performSpellcasting ( targetPosition ) ;
2023-10-19 16:19:09 +02:00
break ;
}
return ;
}
//check if we can select this object
bool canSelect = topBlocking & & topBlocking - > ID = = Obj : : HERO & & topBlocking - > tempOwner = = LOCPLINT - > playerID ;
canSelect | = topBlocking & & topBlocking - > ID = = Obj : : TOWN & & LOCPLINT - > cb - > getPlayerRelations ( LOCPLINT - > playerID , topBlocking - > tempOwner ) ! = PlayerRelations : : ENEMIES ;
bool isHero = false ;
if ( LOCPLINT - > localState - > getCurrentArmy ( ) - > ID ! = Obj : : HERO ) //hero is not selected (presumably town)
{
if ( LOCPLINT - > localState - > getCurrentArmy ( ) = = topBlocking ) //selected town clicked
LOCPLINT - > openTownWindow ( static_cast < const CGTownInstance * > ( topBlocking ) ) ;
else if ( canSelect )
LOCPLINT - > localState - > setSelection ( static_cast < const CArmedInstance * > ( topBlocking ) ) ;
}
else if ( const CGHeroInstance * currentHero = LOCPLINT - > localState - > getCurrentHero ( ) ) //hero is selected
{
isHero = true ;
2024-03-27 23:06:31 +02:00
const CGPathNode * pn = LOCPLINT - > cb - > getPathsInfo ( currentHero ) - > getPathInfo ( targetPosition ) ;
2023-10-19 16:19:09 +02:00
if ( currentHero = = topBlocking ) //clicked selected hero
{
LOCPLINT - > openHeroWindow ( currentHero ) ;
return ;
}
else if ( canSelect & & pn - > turns = = 255 ) //selectable object at inaccessible tile
{
LOCPLINT - > localState - > setSelection ( static_cast < const CArmedInstance * > ( topBlocking ) ) ;
return ;
}
else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
{
if ( LOCPLINT - > localState - > hasPath ( currentHero ) & &
2024-03-27 23:06:31 +02:00
LOCPLINT - > localState - > getPath ( currentHero ) . endPos ( ) = = targetPosition ) //we'll be moving
2023-10-19 16:19:09 +02:00
{
assert ( ! CGI - > mh - > hasOngoingAnimations ( ) ) ;
if ( ! CGI - > mh - > hasOngoingAnimations ( ) & & LOCPLINT - > localState - > getPath ( currentHero ) . nextNode ( ) . turns = = 0 )
LOCPLINT - > moveHero ( currentHero , LOCPLINT - > localState - > getPath ( currentHero ) ) ;
return ;
}
else
{
if ( GH . isKeyboardCtrlDown ( ) ) //normal click behaviour (as no hero selected)
{
if ( canSelect )
LOCPLINT - > localState - > setSelection ( static_cast < const CArmedInstance * > ( topBlocking ) ) ;
}
else //remove old path and find a new one if we clicked on accessible tile
{
2024-03-27 23:06:31 +02:00
LOCPLINT - > localState - > setPath ( currentHero , targetPosition ) ;
2023-10-19 16:19:09 +02:00
onHeroChanged ( currentHero ) ;
}
}
}
} //end of hero is selected "case"
else
{
throw std : : runtime_error ( " Nothing is selected... " ) ;
}
const auto shipyard = ourInaccessibleShipyard ( topBlocking ) ;
if ( isHero & & shipyard ! = nullptr )
{
LOCPLINT - > showShipyardDialogOrProblemPopup ( shipyard ) ;
}
}
2024-03-27 23:06:31 +02:00
void AdventureMapInterface : : onTileHovered ( const int3 & targetPosition )
2023-10-19 16:19:09 +02:00
{
if ( ! shortcuts - > optionMapViewActive ( ) )
return ;
//may occur just at the start of game (fake move before full intiialization)
if ( ! LOCPLINT - > localState - > getCurrentArmy ( ) )
return ;
2024-03-27 23:06:31 +02:00
bool isTargetPositionVisible = LOCPLINT - > cb - > isVisible ( targetPosition ) ;
if ( ! isTargetPositionVisible )
2023-10-19 16:19:09 +02:00
{
2024-03-25 00:58:04 +02:00
GH . statusbar ( ) - > clear ( ) ;
if ( ! spellBeingCasted | | spellBeingCasted - > id ! = SpellID : : DIMENSION_DOOR )
{
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
return ;
}
2023-10-19 16:19:09 +02:00
}
2024-03-25 00:58:04 +02:00
2023-10-19 16:19:09 +02:00
auto objRelations = PlayerRelations : : ALLIES ;
2024-03-25 00:58:04 +02:00
2024-03-27 23:06:31 +02:00
const CGObjectInstance * objAtTile = isTargetPositionVisible ? getActiveObject ( targetPosition ) : nullptr ;
2023-10-19 16:19:09 +02:00
if ( objAtTile )
{
objRelations = LOCPLINT - > cb - > getPlayerRelations ( LOCPLINT - > playerID , objAtTile - > tempOwner ) ;
std : : string text = LOCPLINT - > localState - > getCurrentHero ( ) ? objAtTile - > getHoverText ( LOCPLINT - > localState - > getCurrentHero ( ) ) : objAtTile - > getHoverText ( LOCPLINT - > playerID ) ;
boost : : replace_all ( text , " \n " , " " ) ;
GH . statusbar ( ) - > write ( text ) ;
}
2024-03-27 23:06:31 +02:00
else if ( isTargetPositionVisible )
2023-10-19 16:19:09 +02:00
{
2024-03-27 23:06:31 +02:00
std : : string tileTooltipText = CGI - > mh - > getTerrainDescr ( targetPosition , false ) ;
GH . statusbar ( ) - > write ( tileTooltipText ) ;
2023-10-19 16:19:09 +02:00
}
if ( spellBeingCasted )
{
2024-03-27 23:48:53 +02:00
int3 heroPosition = LOCPLINT - > localState - > getCurrentArmy ( ) - > getSightCenter ( ) ;
if ( ! isInScreenRange ( heroPosition , targetPosition ) )
{
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
return ;
}
2023-10-19 16:19:09 +02:00
switch ( spellBeingCasted - > id )
{
case SpellID : : SCUTTLE_BOAT :
{
2024-03-27 23:48:53 +02:00
if ( objAtTile & & objAtTile - > ID = = Obj : : BOAT )
CCS - > curh - > set ( Cursor : : Map : : SCUTTLE_BOAT ) ;
else
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
return ;
2023-10-19 16:19:09 +02:00
}
case SpellID : : DIMENSION_DOOR :
{
2024-03-28 00:39:08 +02:00
bool allowOnlyToUncoveredTiles = VLC - > settings ( ) - > getBoolean ( EGameSettings : : DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES ) ;
const TerrainTile * t = allowOnlyToUncoveredTiles
? LOCPLINT - > cb - > getTile ( targetPosition , false )
: LOCPLINT - > cb - > getTileForDimensionDoor ( targetPosition , LOCPLINT - > localState - > getCurrentHero ( ) ) ;
2024-03-25 00:58:04 +02:00
if ( t & & t - > isClear ( LOCPLINT - > cb - > getTile ( heroPosition ) ) /* && isInScreenRange(hpos, mapPos)*/ )
2024-03-25 10:21:42 +02:00
CCS - > curh - > set ( Cursor : : Map : : TELEPORT ) ; //TODO: something wrong with beyond east spell range border cursor on arrogance after TP-ing near underground portal on previous day
2023-10-19 16:19:09 +02:00
else
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
return ;
}
2024-03-27 23:48:53 +02:00
default :
break ;
2023-10-19 16:19:09 +02:00
}
}
if ( LOCPLINT - > localState - > getCurrentArmy ( ) - > ID = = Obj : : TOWN | | GH . isKeyboardCtrlDown ( ) )
{
if ( objAtTile )
{
if ( objAtTile - > ID = = Obj : : TOWN & & objRelations ! = PlayerRelations : : ENEMIES )
CCS - > curh - > set ( Cursor : : Map : : TOWN ) ;
else if ( objAtTile - > ID = = Obj : : HERO & & objRelations = = PlayerRelations : : SAME_PLAYER )
CCS - > curh - > set ( Cursor : : Map : : HERO ) ;
else
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
}
else
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
}
else if ( const CGHeroInstance * hero = LOCPLINT - > localState - > getCurrentHero ( ) )
{
std : : array < Cursor : : Map , 4 > cursorMove = { Cursor : : Map : : T1_MOVE , Cursor : : Map : : T2_MOVE , Cursor : : Map : : T3_MOVE , Cursor : : Map : : T4_MOVE , } ;
std : : array < Cursor : : Map , 4 > cursorAttack = { Cursor : : Map : : T1_ATTACK , Cursor : : Map : : T2_ATTACK , Cursor : : Map : : T3_ATTACK , Cursor : : Map : : T4_ATTACK , } ;
std : : array < Cursor : : Map , 4 > cursorSail = { Cursor : : Map : : T1_SAIL , Cursor : : Map : : T2_SAIL , Cursor : : Map : : T3_SAIL , Cursor : : Map : : T4_SAIL , } ;
std : : array < Cursor : : Map , 4 > cursorDisembark = { Cursor : : Map : : T1_DISEMBARK , Cursor : : Map : : T2_DISEMBARK , Cursor : : Map : : T3_DISEMBARK , Cursor : : Map : : T4_DISEMBARK , } ;
std : : array < Cursor : : Map , 4 > cursorExchange = { Cursor : : Map : : T1_EXCHANGE , Cursor : : Map : : T2_EXCHANGE , Cursor : : Map : : T3_EXCHANGE , Cursor : : Map : : T4_EXCHANGE , } ;
std : : array < Cursor : : Map , 4 > cursorVisit = { Cursor : : Map : : T1_VISIT , Cursor : : Map : : T2_VISIT , Cursor : : Map : : T3_VISIT , Cursor : : Map : : T4_VISIT , } ;
std : : array < Cursor : : Map , 4 > cursorSailVisit = { Cursor : : Map : : T1_SAIL_VISIT , Cursor : : Map : : T2_SAIL_VISIT , Cursor : : Map : : T3_SAIL_VISIT , Cursor : : Map : : T4_SAIL_VISIT , } ;
2024-03-27 23:06:31 +02:00
const CGPathNode * pathNode = LOCPLINT - > cb - > getPathsInfo ( hero ) - > getPathInfo ( targetPosition ) ;
2023-10-19 16:19:09 +02:00
assert ( pathNode ) ;
if ( ( GH . isKeyboardAltDown ( ) | | settings [ " gameTweaks " ] [ " forceMovementInfo " ] . Bool ( ) ) & & pathNode - > reachable ( ) ) //overwrite status bar text with movement info
{
showMoveDetailsInStatusbar ( * hero , * pathNode ) ;
}
int turns = pathNode - > turns ;
vstd : : amin ( turns , 3 ) ;
switch ( pathNode - > action )
{
case EPathNodeAction : : NORMAL :
case EPathNodeAction : : TELEPORT_NORMAL :
if ( pathNode - > layer = = EPathfindingLayer : : LAND )
CCS - > curh - > set ( cursorMove [ turns ] ) ;
else
2024-01-16 00:12:21 +02:00
CCS - > curh - > set ( cursorSail [ turns ] ) ;
2023-10-19 16:19:09 +02:00
break ;
case EPathNodeAction : : VISIT :
case EPathNodeAction : : BLOCKING_VISIT :
case EPathNodeAction : : TELEPORT_BLOCKING_VISIT :
if ( objAtTile & & objAtTile - > ID = = Obj : : HERO )
{
if ( LOCPLINT - > localState - > getCurrentArmy ( ) = = objAtTile )
CCS - > curh - > set ( Cursor : : Map : : HERO ) ;
else
CCS - > curh - > set ( cursorExchange [ turns ] ) ;
}
else if ( pathNode - > layer = = EPathfindingLayer : : LAND )
CCS - > curh - > set ( cursorVisit [ turns ] ) ;
2024-01-16 00:12:21 +02:00
else if ( pathNode - > layer = = EPathfindingLayer : : SAIL & &
objAtTile & &
objAtTile - > isCoastVisitable ( ) & &
pathNode - > theNodeBefore & &
pathNode - > theNodeBefore - > layer = = EPathfindingLayer : : LAND )
{
// exception - when visiting shipwreck located on coast from land - show 'horse' cursor, not 'ship' cursor
CCS - > curh - > set ( cursorVisit [ turns ] ) ;
}
2023-10-19 16:19:09 +02:00
else
CCS - > curh - > set ( cursorSailVisit [ turns ] ) ;
break ;
case EPathNodeAction : : BATTLE :
case EPathNodeAction : : TELEPORT_BATTLE :
CCS - > curh - > set ( cursorAttack [ turns ] ) ;
break ;
case EPathNodeAction : : EMBARK :
CCS - > curh - > set ( cursorSail [ turns ] ) ;
break ;
case EPathNodeAction : : DISEMBARK :
CCS - > curh - > set ( cursorDisembark [ turns ] ) ;
break ;
default :
if ( objAtTile & & objRelations ! = PlayerRelations : : ENEMIES )
{
if ( objAtTile - > ID = = Obj : : TOWN )
CCS - > curh - > set ( Cursor : : Map : : TOWN ) ;
else if ( objAtTile - > ID = = Obj : : HERO & & objRelations = = PlayerRelations : : SAME_PLAYER )
CCS - > curh - > set ( Cursor : : Map : : HERO ) ;
else
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
}
else
CCS - > curh - > set ( Cursor : : Map : : POINTER ) ;
break ;
}
}
if ( ourInaccessibleShipyard ( objAtTile ) )
{
CCS - > curh - > set ( Cursor : : Map : : T1_SAIL ) ;
}
}
void AdventureMapInterface : : showMoveDetailsInStatusbar ( const CGHeroInstance & hero , const CGPathNode & pathNode )
{
const int maxMovementPointsAtStartOfLastTurn = pathNode . turns > 0 ? hero . movementPointsLimit ( pathNode . layer = = EPathfindingLayer : : LAND ) : hero . movementPointsRemaining ( ) ;
const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode . moveRemains ;
const int remainingPointsAfterMove = pathNode . turns = = 0 ? pathNode . moveRemains : 0 ;
std : : string result = VLC - > generaltexth - > translate ( " vcmi.adventureMap " , pathNode . turns > 0 ? " moveCostDetails " : " moveCostDetailsNoTurns " ) ;
boost : : replace_first ( result , " %TURNS " , std : : to_string ( pathNode . turns ) ) ;
boost : : replace_first ( result , " %POINTS " , std : : to_string ( movementPointsLastTurnCost ) ) ;
boost : : replace_first ( result , " %REMAINING " , std : : to_string ( remainingPointsAfterMove ) ) ;
GH . statusbar ( ) - > write ( result ) ;
}
void AdventureMapInterface : : onTileRightClicked ( const int3 & mapPos )
{
if ( ! shortcuts - > optionMapViewActive ( ) )
return ;
if ( spellBeingCasted )
{
hotkeyAbortCastingMode ( ) ;
return ;
}
if ( ! LOCPLINT - > cb - > isVisible ( mapPos ) )
{
CRClickPopup : : createAndPush ( VLC - > generaltexth - > allTexts [ 61 ] ) ; //Uncharted Territory
return ;
}
const CGObjectInstance * obj = getActiveObject ( mapPos ) ;
if ( ! obj )
{
// Bare or undiscovered terrain
const TerrainTile * tile = LOCPLINT - > cb - > getTile ( mapPos ) ;
if ( tile )
{
std : : string hlp = CGI - > mh - > getTerrainDescr ( mapPos , true ) ;
CRClickPopup : : createAndPush ( hlp ) ;
}
return ;
}
CRClickPopup : : createAndPush ( obj , GH . getCursorPosition ( ) , ETextAlignment : : CENTER ) ;
}
void AdventureMapInterface : : enterCastingMode ( const CSpell * sp )
{
assert ( sp - > id = = SpellID : : SCUTTLE_BOAT | | sp - > id = = SpellID : : DIMENSION_DOOR ) ;
spellBeingCasted = sp ;
Settings config = settings . write [ " session " ] [ " showSpellRange " ] ;
config - > Bool ( ) = true ;
setState ( EAdventureState : : CASTING_SPELL ) ;
}
void AdventureMapInterface : : exitCastingMode ( )
{
assert ( spellBeingCasted ) ;
spellBeingCasted = nullptr ;
setState ( EAdventureState : : MAKING_TURN ) ;
Settings config = settings . write [ " session " ] [ " showSpellRange " ] ;
config - > Bool ( ) = false ;
}
void AdventureMapInterface : : hotkeyAbortCastingMode ( )
{
exitCastingMode ( ) ;
LOCPLINT - > showInfoDialog ( CGI - > generaltexth - > allTexts [ 731 ] ) ; //Spell cancelled
}
void AdventureMapInterface : : performSpellcasting ( const int3 & dest )
{
SpellID id = spellBeingCasted - > id ;
exitCastingMode ( ) ;
LOCPLINT - > cb - > castSpell ( LOCPLINT - > localState - > getCurrentHero ( ) , id , dest ) ;
}
Rect AdventureMapInterface : : terrainAreaPixels ( ) const
{
return widget - > getMapView ( ) - > pos ;
}
const IShipyard * AdventureMapInterface : : ourInaccessibleShipyard ( const CGObjectInstance * obj ) const
{
2024-02-14 12:56:37 +02:00
const auto * ret = dynamic_cast < const IShipyard * > ( obj ) ;
2023-10-19 16:19:09 +02:00
if ( ! ret | |
obj - > tempOwner ! = currentPlayerID | |
( CCS - > curh - > get < Cursor : : Map > ( ) ! = Cursor : : Map : : T1_SAIL & & CCS - > curh - > get < Cursor : : Map > ( ) ! = Cursor : : Map : : POINTER ) )
return nullptr ;
return ret ;
}
void AdventureMapInterface : : hotkeyExitWorldView ( )
{
setState ( EAdventureState : : MAKING_TURN ) ;
widget - > getMapView ( ) - > onViewMapActivated ( ) ;
}
void AdventureMapInterface : : openWorldView ( int tileSize )
{
setState ( EAdventureState : : WORLD_VIEW ) ;
widget - > getMapView ( ) - > onViewWorldActivated ( tileSize ) ;
}
void AdventureMapInterface : : openWorldView ( )
{
openWorldView ( 11 ) ;
}
void AdventureMapInterface : : openWorldView ( const std : : vector < ObjectPosInfo > & objectPositions , bool showTerrain )
{
openWorldView ( 11 ) ;
widget - > getMapView ( ) - > onViewSpellActivated ( 11 , objectPositions , showTerrain ) ;
}
void AdventureMapInterface : : hotkeyNextTown ( )
{
widget - > getTownList ( ) - > selectNext ( ) ;
}
void AdventureMapInterface : : hotkeySwitchMapLevel ( )
{
widget - > getMapView ( ) - > onMapLevelSwitched ( ) ;
}
void AdventureMapInterface : : hotkeyZoom ( int delta )
{
widget - > getMapView ( ) - > onMapZoomLevelChanged ( delta ) ;
}
void AdventureMapInterface : : onScreenResize ( )
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE ;
// remember our activation state and reactive after reconstruction
// since othervice activate() calls for created elements will bypass virtual dispatch
// and will call directly CIntObject::activate() instead of dispatching virtual function call
bool widgetActive = isActive ( ) ;
if ( widgetActive )
deactivate ( ) ;
widget . reset ( ) ;
pos . x = pos . y = 0 ;
pos . w = GH . screenDimensions ( ) . x ;
pos . h = GH . screenDimensions ( ) . y ;
widget = std : : make_shared < AdventureMapWidget > ( shortcuts ) ;
widget - > getMapView ( ) - > onViewMapActivated ( ) ;
widget - > setPlayer ( currentPlayerID ) ;
widget - > updateActiveState ( ) ;
widget - > getMinimap ( ) - > update ( ) ;
widget - > getInfoBar ( ) - > showSelection ( ) ;
if ( LOCPLINT & & LOCPLINT - > localState - > getCurrentArmy ( ) )
widget - > getMapView ( ) - > onCenteredObject ( LOCPLINT - > localState - > getCurrentArmy ( ) ) ;
adjustActiveness ( ) ;
if ( widgetActive )
activate ( ) ;
}