2014-06-05 19:52:14 +03:00
/*
2014-06-05 20:26:50 +03:00
* MiscObjects . cpp , part of VCMI engine
2014-06-05 19:52:14 +03:00
*
* 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 "MiscObjects.h"
2023-11-02 15:51:08 +02:00
# include "../ArtifactUtils.h"
2023-12-21 19:09:33 +02:00
# include "../bonuses/Propagators.h"
2023-08-19 23:22:31 +02:00
# include "../constants/StringConstants.h"
2023-11-02 15:51:08 +02:00
# include "../CConfigHandler.h"
2024-07-20 14:55:17 +02:00
# include "../texts/CGeneralTextHandler.h"
2014-06-05 23:51:24 +03:00
# include "../CSoundBase.h"
2017-08-30 12:35:23 +02:00
# include "../CSkillHandler.h"
2015-02-02 10:25:26 +02:00
# include "../spells/CSpellHandler.h"
2014-06-25 17:11:07 +03:00
# include "../IGameCallback.h"
2023-06-23 17:02:48 +02:00
# include "../gameState/CGameState.h"
2015-12-02 21:05:10 +02:00
# include "../mapping/CMap.h"
2015-12-02 21:39:53 +02:00
# include "../CPlayerState.h"
2016-02-22 01:37:19 +02:00
# include "../serializer/JsonSerializeFormat.h"
2023-06-06 17:34:04 +02:00
# include "../mapObjectConstructors/AObjectTypeHandler.h"
# include "../mapObjectConstructors/CObjectClassesHandler.h"
2023-10-23 12:59:15 +02:00
# include "../mapObjects/CGHeroInstance.h"
2023-07-30 19:12:25 +02:00
# include "../modding/ModScope.h"
2023-10-23 12:59:15 +02:00
# include "../networkPacks/PacksForClient.h"
# include "../networkPacks/PacksForClientBattle.h"
# include "../networkPacks/StackLocation.h"
2014-06-05 19:52:14 +03:00
2024-06-01 17:28:17 +02:00
# include <vstd/RNG.h>
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2014-06-05 19:52:14 +03:00
///helpers
2023-01-01 20:54:05 +02:00
static std : : string visitedTxt ( const bool visited )
2014-06-05 19:52:14 +03:00
{
int id = visited ? 352 : 353 ;
return VLC - > generaltexth - > allTexts [ id ] ;
}
2023-11-06 18:27:16 +02:00
void CTeamVisited : : setPropertyDer ( ObjProperty what , ObjPropertyID identifier )
2014-06-05 19:52:14 +03:00
{
2023-11-06 18:27:16 +02:00
if ( what = = ObjProperty : : VISITED )
players . insert ( identifier . as < PlayerColor > ( ) ) ;
2014-06-05 19:52:14 +03:00
}
2017-06-30 22:39:37 +02:00
bool CTeamVisited : : wasVisited ( PlayerColor player ) const
2014-06-05 19:52:14 +03:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
return wasVisited ( cb - > getPlayerState ( player ) - > team ) ;
2014-06-05 19:52:14 +03:00
}
2022-12-07 21:50:45 +02:00
bool CTeamVisited : : wasVisited ( const CGHeroInstance * h ) const
{
return wasVisited ( h - > tempOwner ) ;
}
2023-02-12 22:39:17 +02:00
bool CTeamVisited : : wasVisited ( const TeamID & team ) const
2014-06-05 19:52:14 +03:00
{
2023-02-12 22:39:17 +02:00
for ( const auto & i : players )
2014-06-05 19:52:14 +03:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( cb - > getPlayerState ( i ) - > team = = team )
2014-06-05 19:52:14 +03:00
return true ;
}
return false ;
}
2015-11-16 15:30:40 +02:00
//CGMine
2014-06-05 19:52:14 +03:00
void CGMine : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-08-19 20:43:50 +02:00
auto relations = cb - > gameState ( ) - > getPlayerRelations ( h - > tempOwner , tempOwner ) ;
2014-06-05 19:52:14 +03:00
2023-08-19 20:43:50 +02:00
if ( relations = = PlayerRelations : : SAME_PLAYER ) //we're visiting our mine
2014-06-05 19:52:14 +03:00
{
cb - > showGarrisonDialog ( id , h - > id , true ) ;
return ;
}
2023-08-19 20:43:50 +02:00
else if ( relations = = PlayerRelations : : ALLIES ) //ally
2014-06-05 19:52:14 +03:00
return ;
if ( stacksCount ( ) ) //Mine is guarded
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
2023-10-24 16:11:25 +02:00
ynd . text . appendLocalString ( EMetaText : : ADVOB_TXT , isAbandoned ( ) ? 84 : 187 ) ;
2014-06-05 19:52:14 +03:00
cb - > showBlockingDialog ( & ynd ) ;
return ;
}
flagMine ( h - > tempOwner ) ;
}
2024-06-01 17:28:17 +02:00
void CGMine : : newTurn ( vstd : : RNG & rand ) const
2014-06-05 19:52:14 +03:00
{
if ( cb - > getDate ( ) = = 1 )
return ;
if ( tempOwner = = PlayerColor : : NEUTRAL )
return ;
cb - > giveResource ( tempOwner , producedResource , producedQuantity ) ;
}
2024-06-01 17:28:17 +02:00
void CGMine : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
2016-01-23 18:53:02 +02:00
if ( isAbandoned ( ) )
2014-06-05 19:52:14 +03:00
{
//set guardians
2016-09-09 19:30:36 +02:00
int howManyTroglodytes = rand . nextInt ( 100 , 199 ) ;
2023-02-12 22:39:17 +02:00
auto * troglodytes = new CStackInstance ( CreatureID : : TROGLODYTES , howManyTroglodytes ) ;
2014-06-05 19:52:14 +03:00
putStack ( SlotID ( 0 ) , troglodytes ) ;
2023-04-02 18:56:10 +02:00
assert ( ! abandonedMineResources . empty ( ) ) ;
2023-12-24 22:05:47 +02:00
if ( ! abandonedMineResources . empty ( ) )
{
producedResource = * RandomGeneratorUtil : : nextItem ( abandonedMineResources , rand ) ;
}
else
{
logGlobal - > error ( " Abandoned mine at (%s) has no valid resource candidates! " , pos . toString ( ) ) ;
producedResource = GameResID : : GOLD ;
}
2014-06-05 19:52:14 +03:00
}
else
{
2023-11-02 17:48:48 +02:00
producedResource = GameResID ( getObjTypeIndex ( ) . getNum ( ) ) ;
2014-06-05 19:52:14 +03:00
}
producedQuantity = defaultResProduction ( ) ;
}
2016-01-23 18:53:02 +02:00
bool CGMine : : isAbandoned ( ) const
{
2023-11-02 17:12:58 +02:00
return subID . getNum ( ) > = 7 ;
2016-01-23 18:53:02 +02:00
}
2023-10-28 11:27:10 +02:00
ResourceSet CGMine : : dailyIncome ( ) const
{
ResourceSet result ;
result [ producedResource ] + = defaultResProduction ( ) ;
return result ;
}
2014-06-24 20:39:36 +03:00
std : : string CGMine : : getObjectName ( ) const
2014-06-05 19:52:14 +03:00
{
2023-10-24 16:11:25 +02:00
return VLC - > generaltexth - > translate ( " core.minename " , getObjTypeIndex ( ) ) ;
2014-06-24 20:39:36 +03:00
}
2014-06-05 19:52:14 +03:00
2014-06-24 20:39:36 +03:00
std : : string CGMine : : getHoverText ( PlayerColor player ) const
{
2023-01-21 13:25:43 +02:00
std : : string hoverName = CArmedInstance : : getHoverText ( player ) ;
2014-06-24 20:39:36 +03:00
if ( tempOwner ! = PlayerColor : : NEUTRAL )
2023-11-02 18:45:46 +02:00
hoverName + = " \n ( " + VLC - > generaltexth - > restypes [ producedResource . getNum ( ) ] + " ) " ;
2014-06-24 20:39:36 +03:00
2015-12-24 20:05:02 +02:00
if ( stacksCount ( ) )
2014-06-24 20:39:36 +03:00
{
hoverName + = " \n " ;
2015-12-24 20:05:02 +02:00
hoverName + = VLC - > generaltexth - > allTexts [ 202 ] ; //Guarded by
hoverName + = " " ;
hoverName + = getArmyDescription ( ) ;
2014-06-24 20:39:36 +03:00
}
return hoverName ;
}
2023-02-12 22:39:17 +02:00
void CGMine : : flagMine ( const PlayerColor & player ) const
2014-06-24 20:39:36 +03:00
{
assert ( tempOwner ! = player ) ;
cb - > setOwner ( this , player ) ; //not ours? flag it!
2014-06-05 19:52:14 +03:00
InfoWindow iw ;
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2023-11-02 22:01:49 +02:00
iw . text . appendTextID ( TextIdentifier ( " core.mineevnt " , producedResource . getNum ( ) ) . get ( ) ) ; //not use subID, abandoned mines uses default mine texts
2014-06-05 19:52:14 +03:00
iw . player = player ;
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : RESOURCE_PER_DAY , producedResource , producedQuantity ) ;
2014-06-05 19:52:14 +03:00
cb - > showInfoDialog ( & iw ) ;
}
2023-02-12 22:39:17 +02:00
ui32 CGMine : : defaultResProduction ( ) const
2014-06-05 19:52:14 +03:00
{
2023-04-05 02:26:29 +02:00
switch ( producedResource . toEnum ( ) )
2014-06-05 19:52:14 +03:00
{
2023-04-05 02:26:29 +02:00
case EGameResID : : WOOD :
case EGameResID : : ORE :
2014-06-05 19:52:14 +03:00
return 2 ;
2023-04-05 02:26:29 +02:00
case EGameResID : : GOLD :
2014-06-05 19:52:14 +03:00
return 1000 ;
default :
return 1 ;
}
}
void CGMine : : battleFinished ( const CGHeroInstance * hero , const BattleResult & result ) const
{
if ( result . winner = = 0 ) //attacker won
{
2016-01-23 18:53:02 +02:00
if ( isAbandoned ( ) )
2014-06-05 19:52:14 +03:00
{
2023-03-08 00:32:21 +02:00
hero - > showInfoDialog ( 85 ) ;
2014-06-05 19:52:14 +03:00
}
flagMine ( hero - > tempOwner ) ;
}
}
void CGMine : : blockingDialogAnswered ( const CGHeroInstance * hero , ui32 answer ) const
{
if ( answer )
cb - > startBattleI ( hero , this ) ;
}
2016-02-22 01:37:19 +02:00
void CGMine : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2023-09-17 22:19:45 +02:00
CArmedInstance : : serializeJsonOptions ( handler ) ;
2023-09-21 16:44:13 +02:00
serializeJsonOwner ( handler ) ;
2016-01-23 18:53:02 +02:00
if ( isAbandoned ( ) )
{
2016-02-22 01:37:19 +02:00
if ( handler . saving )
2016-01-23 18:53:02 +02:00
{
2024-02-13 13:18:10 +02:00
JsonNode node ;
2023-10-24 16:11:25 +02:00
for ( const auto & resID : abandonedMineResources )
2024-02-13 13:18:10 +02:00
node . Vector ( ) . emplace_back ( GameConstants : : RESOURCE_NAMES [ resID . getNum ( ) ] ) ;
2023-04-16 19:42:56 +02:00
handler . serializeRaw ( " possibleResources " , node , std : : nullopt ) ;
2016-01-23 18:53:02 +02:00
}
else
{
2017-07-20 06:08:49 +02:00
auto guard = handler . enterArray ( " possibleResources " ) ;
const JsonNode & node = handler . getCurrent ( ) ;
2016-01-23 18:53:02 +02:00
2023-04-02 18:56:10 +02:00
auto names = node . convertTo < std : : vector < std : : string > > ( ) ;
2016-01-23 18:53:02 +02:00
2023-04-02 18:56:10 +02:00
for ( const std : : string & s : names )
{
int raw_res = vstd : : find_pos ( GameConstants : : RESOURCE_NAMES , s ) ;
if ( raw_res < 0 )
logGlobal - > error ( " Invalid resource name: %s " , s ) ;
else
2023-04-25 12:38:57 +02:00
abandonedMineResources . emplace ( raw_res ) ;
2016-02-22 01:37:19 +02:00
}
2016-01-23 18:53:02 +02:00
}
}
2015-11-16 15:30:40 +02:00
}
2023-10-24 16:11:25 +02:00
GameResID CGResource : : resourceID ( ) const
{
return getObjTypeIndex ( ) . getNum ( ) ;
}
2014-06-24 20:39:36 +03:00
std : : string CGResource : : getHoverText ( PlayerColor player ) const
{
2023-11-02 18:45:46 +02:00
return VLC - > generaltexth - > restypes [ resourceID ( ) . getNum ( ) ] ;
2014-06-24 20:39:36 +03:00
}
2024-06-01 17:28:17 +02:00
void CGResource : : pickRandomObject ( vstd : : RNG & rand )
2023-10-25 12:50:11 +02:00
{
assert ( ID = = Obj : : RESOURCE | | ID = = Obj : : RANDOM_RESOURCE ) ;
if ( ID = = Obj : : RANDOM_RESOURCE )
{
ID = Obj : : RESOURCE ;
subID = rand . nextInt ( EGameResID : : WOOD , EGameResID : : GOLD ) ;
2023-10-26 15:50:29 +02:00
setType ( ID , subID ) ;
2024-06-17 22:08:07 +02:00
if ( subID = = EGameResID : : GOLD & & amount ! = CGResource : : RANDOM_AMOUNT )
amount * = CGResource : : GOLD_AMOUNT_MULTIPLIER ;
2023-10-25 12:50:11 +02:00
}
}
2024-06-01 17:28:17 +02:00
void CGResource : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
blockVisit = true ;
2022-05-23 12:08:36 +02:00
if ( amount = = CGResource : : RANDOM_AMOUNT )
2014-06-05 19:52:14 +03:00
{
2023-11-02 16:29:59 +02:00
switch ( resourceID ( ) . toEnum ( ) )
2014-06-05 19:52:14 +03:00
{
2023-04-05 02:26:29 +02:00
case EGameResID : : GOLD :
2024-06-17 22:08:07 +02:00
amount = rand . nextInt ( 5 , 10 ) * CGResource : : GOLD_AMOUNT_MULTIPLIER ;
2014-06-05 19:52:14 +03:00
break ;
2023-04-05 02:26:29 +02:00
case EGameResID : : WOOD : case EGameResID : : ORE :
2016-09-09 19:30:36 +02:00
amount = rand . nextInt ( 6 , 10 ) ;
2014-06-05 19:52:14 +03:00
break ;
default :
2016-09-09 19:30:36 +02:00
amount = rand . nextInt ( 3 , 5 ) ;
2014-06-05 19:52:14 +03:00
break ;
}
}
}
void CGResource : : onHeroVisit ( const CGHeroInstance * h ) const
{
if ( stacksCount ( ) )
{
2023-02-12 22:39:17 +02:00
if ( ! message . empty ( ) )
2014-06-05 19:52:14 +03:00
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > getOwner ( ) ;
2023-09-27 23:11:11 +02:00
ynd . text = message ;
2014-06-05 19:52:14 +03:00
cb - > showBlockingDialog ( & ynd ) ;
}
else
{
blockingDialogAnswered ( h , true ) ; //behave as if player accepted battle
}
}
else
collectRes ( h - > getOwner ( ) ) ;
}
2023-02-12 22:39:17 +02:00
void CGResource : : collectRes ( const PlayerColor & player ) const
2014-06-05 19:52:14 +03:00
{
2023-10-24 16:11:25 +02:00
cb - > giveResource ( player , resourceID ( ) , amount ) ;
2023-03-06 01:30:21 +02:00
InfoWindow sii ;
2014-06-05 19:52:14 +03:00
sii . player = player ;
2023-03-06 01:30:21 +02:00
if ( ! message . empty ( ) )
{
sii . type = EInfoWindowMode : : AUTO ;
2023-09-27 23:11:11 +02:00
sii . text = message ;
2023-03-06 01:30:21 +02:00
}
else
{
sii . type = EInfoWindowMode : : INFO ;
2023-06-18 11:18:25 +02:00
sii . text . appendLocalString ( EMetaText : : ADVOB_TXT , 113 ) ;
2023-11-08 16:13:08 +02:00
sii . text . replaceName ( resourceID ( ) ) ;
2023-03-06 01:30:21 +02:00
}
2023-10-31 11:09:56 +02:00
sii . components . emplace_back ( ComponentType : : RESOURCE , resourceID ( ) , amount ) ;
2024-06-01 17:28:17 +02:00
sii . soundID = soundBase : : pickup01 + cb - > gameState ( ) - > getRandomGenerator ( ) . nextInt ( 6 ) ;
2023-03-06 01:30:21 +02:00
cb - > showInfoDialog ( & sii ) ;
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , player ) ;
2014-06-05 19:52:14 +03:00
}
void CGResource : : battleFinished ( const CGHeroInstance * hero , const BattleResult & result ) const
{
if ( result . winner = = 0 ) //attacker won
collectRes ( hero - > getOwner ( ) ) ;
}
void CGResource : : blockingDialogAnswered ( const CGHeroInstance * hero , ui32 answer ) const
{
if ( answer )
cb - > startBattleI ( hero , this ) ;
}
2016-02-22 01:37:19 +02:00
void CGResource : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2023-09-17 22:19:45 +02:00
CArmedInstance : : serializeJsonOptions ( handler ) ;
if ( ! handler . saving & & ! handler . getCurrent ( ) [ " guards " ] . Vector ( ) . empty ( ) )
CCreatureSet : : serializeJson ( handler , " guards " , 7 ) ;
2016-11-13 12:38:42 +02:00
handler . serializeInt ( " amount " , amount , 0 ) ;
2023-09-27 23:11:11 +02:00
handler . serializeStruct ( " guardMessage " , message ) ;
2015-11-16 15:30:40 +02:00
}
2015-03-08 15:11:23 +02:00
bool CGTeleport : : isEntrance ( ) const
{
return type = = BOTH | | type = = ENTRANCE ;
}
bool CGTeleport : : isExit ( ) const
{
return type = = BOTH | | type = = EXIT ;
}
2023-02-12 22:39:17 +02:00
bool CGTeleport : : isChannelEntrance ( const ObjectInstanceID & id ) const
2015-03-08 15:11:23 +02:00
{
2015-03-08 19:19:00 +02:00
return vstd : : contains ( getAllEntrances ( ) , id ) ;
2015-03-08 15:11:23 +02:00
}
2023-02-12 22:39:17 +02:00
bool CGTeleport : : isChannelExit ( const ObjectInstanceID & id ) const
2015-03-08 15:11:23 +02:00
{
2015-03-08 19:19:00 +02:00
return vstd : : contains ( getAllExits ( ) , id ) ;
2015-03-08 15:11:23 +02:00
}
std : : vector < ObjectInstanceID > CGTeleport : : getAllEntrances ( bool excludeCurrent ) const
{
2024-06-24 03:23:26 +02:00
auto ret = cb - > getTeleportChannelEntrances ( channel ) ;
2015-03-08 15:11:23 +02:00
if ( excludeCurrent )
2015-03-09 14:44:48 +02:00
vstd : : erase_if_present ( ret , id ) ;
2015-03-08 15:11:23 +02:00
return ret ;
}
std : : vector < ObjectInstanceID > CGTeleport : : getAllExits ( bool excludeCurrent ) const
{
2015-03-09 14:44:48 +02:00
auto ret = cb - > getTeleportChannelExits ( channel ) ;
2015-03-08 15:11:23 +02:00
if ( excludeCurrent )
2015-03-09 14:44:48 +02:00
vstd : : erase_if_present ( ret , id ) ;
2015-03-08 15:11:23 +02:00
return ret ;
}
ObjectInstanceID CGTeleport : : getRandomExit ( const CGHeroInstance * h ) const
{
auto passableExits = getPassableExits ( cb - > gameState ( ) , h , getAllExits ( true ) ) ;
2023-02-12 22:39:17 +02:00
if ( ! passableExits . empty ( ) )
2024-06-01 17:28:17 +02:00
return * RandomGeneratorUtil : : nextItem ( passableExits , cb - > gameState ( ) - > getRandomGenerator ( ) ) ;
2015-03-08 15:11:23 +02:00
return ObjectInstanceID ( ) ;
}
bool CGTeleport : : isTeleport ( const CGObjectInstance * obj )
{
2023-09-20 21:48:48 +02:00
return dynamic_cast < const CGTeleport * > ( obj ) ! = nullptr ;
2015-03-08 15:11:23 +02:00
}
bool CGTeleport : : isConnected ( const CGTeleport * src , const CGTeleport * dst )
{
2015-12-02 16:56:26 +02:00
return src & & dst & & src - > isChannelExit ( dst - > id ) ;
2015-03-08 15:11:23 +02:00
}
bool CGTeleport : : isConnected ( const CGObjectInstance * src , const CGObjectInstance * dst )
{
2023-02-12 22:39:17 +02:00
const auto * srcObj = dynamic_cast < const CGTeleport * > ( src ) ;
const auto * dstObj = dynamic_cast < const CGTeleport * > ( dst ) ;
2015-03-08 15:11:23 +02:00
return isConnected ( srcObj , dstObj ) ;
}
bool CGTeleport : : isExitPassable ( CGameState * gs , const CGHeroInstance * h , const CGObjectInstance * obj )
{
2023-02-12 22:39:17 +02:00
auto * objTopVisObj = gs - > map - > getTile ( obj - > visitablePos ( ) ) . topVisitableObj ( ) ;
2015-03-08 15:11:23 +02:00
if ( objTopVisObj - > ID = = Obj : : HERO )
2014-06-05 19:52:14 +03:00
{
2015-03-08 15:11:23 +02:00
if ( h - > id = = objTopVisObj - > id ) // Just to be sure it's won't happen.
return false ;
// Check if it's friendly hero or not
2021-10-17 11:18:16 +02:00
if ( gs - > getPlayerRelations ( h - > tempOwner , objTopVisObj - > tempOwner ) ! = PlayerRelations : : ENEMIES )
2014-06-05 19:52:14 +03:00
{
2015-03-08 15:11:23 +02:00
// Exchange between heroes only possible via subterranean gates
if ( ! dynamic_cast < const CGSubterraneanGate * > ( obj ) )
return false ;
2014-06-05 19:52:14 +03:00
}
}
2015-03-08 15:11:23 +02:00
return true ;
}
2014-06-05 19:52:14 +03:00
2015-03-08 15:11:23 +02:00
std : : vector < ObjectInstanceID > CGTeleport : : getPassableExits ( CGameState * gs , const CGHeroInstance * h , std : : vector < ObjectInstanceID > exits )
{
2023-02-12 22:39:17 +02:00
vstd : : erase_if ( exits , [ & ] ( const ObjectInstanceID & exit ) - > bool
2015-03-08 15:11:23 +02:00
{
return ! isExitPassable ( gs , h , gs - > getObj ( exit ) ) ;
} ) ;
return exits ;
}
2015-12-29 04:43:33 +02:00
void CGTeleport : : addToChannel ( std : : map < TeleportChannelID , std : : shared_ptr < TeleportChannel > > & channelsList , const CGTeleport * obj )
2015-03-08 15:11:23 +02:00
{
2015-12-29 04:43:33 +02:00
std : : shared_ptr < TeleportChannel > tc ;
2015-03-08 15:11:23 +02:00
if ( channelsList . find ( obj - > channel ) = = channelsList . end ( ) )
{
2015-12-29 04:43:33 +02:00
tc = std : : make_shared < TeleportChannel > ( ) ;
2015-03-08 15:11:23 +02:00
channelsList . insert ( std : : make_pair ( obj - > channel , tc ) ) ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
else
tc = channelsList [ obj - > channel ] ;
if ( obj - > isEntrance ( ) & & ! vstd : : contains ( tc - > entrances , obj - > id ) )
tc - > entrances . push_back ( obj - > id ) ;
if ( obj - > isExit ( ) & & ! vstd : : contains ( tc - > exits , obj - > id ) )
tc - > exits . push_back ( obj - > id ) ;
2023-02-12 22:39:17 +02:00
if ( ! tc - > entrances . empty ( ) & & ! tc - > exits . empty ( )
2015-03-08 15:11:23 +02:00
& & ( tc - > entrances . size ( ) ! = 1 | | tc - > entrances ! = tc - > exits ) )
2014-06-05 19:52:14 +03:00
{
2015-03-08 15:11:23 +02:00
tc - > passability = TeleportChannel : : PASSABLE ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
}
2023-11-02 17:12:58 +02:00
TeleportChannelID CGMonolith : : findMeChannel ( const std : : vector < Obj > & IDs , MapObjectSubID SubID ) const
2015-03-08 15:11:23 +02:00
{
for ( auto obj : cb - > gameState ( ) - > map - > objects )
2014-06-05 19:52:14 +03:00
{
2017-07-14 15:50:29 +02:00
if ( ! obj )
continue ;
2023-10-28 11:27:10 +02:00
const auto * teleportObj = dynamic_cast < const CGMonolith * > ( cb - > getObj ( obj - > id ) ) ;
2015-03-08 15:11:23 +02:00
if ( teleportObj & & vstd : : contains ( IDs , teleportObj - > ID ) & & teleportObj - > subID = = SubID )
return teleportObj - > channel ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
return TeleportChannelID ( ) ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
void CGMonolith : : onHeroVisit ( const CGHeroInstance * h ) const
2014-06-05 19:52:14 +03:00
{
2023-09-15 21:18:36 +02:00
TeleportDialog td ( h - > id , channel ) ;
2015-03-08 15:11:23 +02:00
if ( isEntrance ( ) )
2014-06-05 19:52:14 +03:00
{
2015-03-09 01:13:40 +02:00
if ( cb - > isTeleportChannelBidirectional ( channel ) & & 1 < cb - > getTeleportChannelExits ( channel ) . size ( ) )
2015-12-02 16:56:26 +02:00
{
auto exits = cb - > getTeleportChannelExits ( channel ) ;
2023-02-12 22:39:17 +02:00
for ( const auto & exit : exits )
2015-12-02 16:56:26 +02:00
{
2024-06-11 16:31:11 +02:00
td . exits . push_back ( std : : make_pair ( exit , cb - > getObj ( exit ) - > visitablePos ( ) ) ) ;
2015-12-02 16:56:26 +02:00
}
}
2015-03-08 15:11:23 +02:00
2015-03-09 01:13:40 +02:00
if ( cb - > isTeleportChannelImpassable ( channel ) )
2014-06-05 19:52:14 +03:00
{
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " Cannot find corresponding exit monolith for %d at %s " , id . getNum ( ) , pos . toString ( ) ) ;
2015-03-08 15:11:23 +02:00
td . impassable = true ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
else if ( getRandomExit ( h ) = = ObjectInstanceID ( ) )
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " All exits blocked for monolith %d at %s " , id . getNum ( ) , pos . toString ( ) ) ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 17:07:06 +02:00
else
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 70 ) ;
2015-03-08 15:11:23 +02:00
cb - > showTeleportDialog ( & td ) ;
}
2015-12-02 16:56:26 +02:00
void CGMonolith : : teleportDialogAnswered ( const CGHeroInstance * hero , ui32 answer , TTeleportExitsList exits ) const
2015-03-08 15:11:23 +02:00
{
2015-12-02 16:56:26 +02:00
int3 dPos ;
2015-12-18 00:08:19 +02:00
auto randomExit = getRandomExit ( hero ) ;
2015-03-08 15:11:23 +02:00
auto realExits = getAllExits ( true ) ;
if ( ! isEntrance ( ) // Do nothing if hero visited exit only object
2023-02-12 22:39:17 +02:00
| | ( exits . empty ( ) & & realExits . empty ( ) ) // Do nothing if there no exits on this channel
2015-12-18 00:08:19 +02:00
| | ObjectInstanceID ( ) = = randomExit ) // Do nothing if all exits are blocked by friendly hero and it's not subterranean gate
2014-06-05 19:52:14 +03:00
{
return ;
}
2015-12-02 16:56:26 +02:00
else if ( vstd : : isValidIndex ( exits , answer ) )
dPos = exits [ answer ] . second ;
2014-06-05 19:52:14 +03:00
else
2024-06-11 16:31:11 +02:00
dPos = cb - > getObj ( randomExit ) - > visitablePos ( ) ;
2015-03-08 15:11:23 +02:00
2024-06-11 16:31:11 +02:00
cb - > moveHero ( hero - > id , hero - > convertFromVisitablePos ( dPos ) , EMovementMode : : MONOLITH ) ;
2014-06-05 19:52:14 +03:00
}
2024-06-01 17:28:17 +02:00
void CGMonolith : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
2015-03-08 15:11:23 +02:00
std : : vector < Obj > IDs ;
IDs . push_back ( ID ) ;
2023-11-02 16:29:59 +02:00
switch ( ID . toEnum ( ) )
2014-06-05 19:52:14 +03:00
{
2015-03-09 00:27:40 +02:00
case Obj : : MONOLITH_ONE_WAY_ENTRANCE :
type = ENTRANCE ;
2023-02-12 22:39:17 +02:00
IDs . emplace_back ( Obj : : MONOLITH_ONE_WAY_EXIT ) ;
2015-03-09 00:27:40 +02:00
break ;
case Obj : : MONOLITH_ONE_WAY_EXIT :
type = EXIT ;
2023-02-12 22:39:17 +02:00
IDs . emplace_back ( Obj : : MONOLITH_ONE_WAY_ENTRANCE ) ;
2015-03-09 00:27:40 +02:00
break ;
case Obj : : MONOLITH_TWO_WAY :
2014-06-05 19:52:14 +03:00
default :
2015-03-09 00:27:40 +02:00
type = BOTH ;
2014-06-05 19:52:14 +03:00
break ;
}
2015-03-08 15:11:23 +02:00
channel = findMeChannel ( IDs , subID ) ;
if ( channel = = TeleportChannelID ( ) )
2023-02-12 22:39:17 +02:00
channel = TeleportChannelID ( static_cast < si32 > ( cb - > gameState ( ) - > map - > teleportChannels . size ( ) ) ) ;
2015-03-08 15:11:23 +02:00
addToChannel ( cb - > gameState ( ) - > map - > teleportChannels , this ) ;
}
void CGSubterraneanGate : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-09-15 21:18:36 +02:00
TeleportDialog td ( h - > id , channel ) ;
2015-03-09 01:13:40 +02:00
if ( cb - > isTeleportChannelImpassable ( channel ) )
2015-03-08 15:11:23 +02:00
{
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 153 ) ; //Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " Cannot find exit subterranean gate for %d at %s " , id . getNum ( ) , pos . toString ( ) ) ;
2015-03-08 15:11:23 +02:00
td . impassable = true ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
else
2015-12-02 16:56:26 +02:00
{
auto exit = getRandomExit ( h ) ;
2024-06-11 16:31:11 +02:00
td . exits . push_back ( std : : make_pair ( exit , cb - > getObj ( exit ) - > visitablePos ( ) ) ) ;
2015-12-02 16:56:26 +02:00
}
2015-03-08 15:11:23 +02:00
cb - > showTeleportDialog ( & td ) ;
2014-06-05 19:52:14 +03:00
}
2024-06-01 17:28:17 +02:00
void CGSubterraneanGate : : initObj ( vstd : : RNG & rand )
2015-03-08 15:11:23 +02:00
{
type = BOTH ;
2014-06-05 19:52:14 +03:00
}
2024-01-01 16:37:48 +02:00
void CGSubterraneanGate : : postInit ( IGameCallback * cb ) //matches subterranean gates into pairs
2014-06-05 19:52:14 +03:00
{
//split on underground and surface gates
2015-03-08 15:11:23 +02:00
std : : vector < CGSubterraneanGate * > gatesSplit [ 2 ] ; //surface and underground gates
for ( auto & obj : cb - > gameState ( ) - > map - > objects )
2014-06-05 19:52:14 +03:00
{
2024-01-01 16:37:48 +02:00
if ( ! obj )
2017-07-14 14:03:55 +02:00
continue ;
2023-02-12 22:39:17 +02:00
auto * hlp = dynamic_cast < CGSubterraneanGate * > ( cb - > gameState ( ) - > getObjInstance ( obj - > id ) ) ;
2015-03-08 15:11:23 +02:00
if ( hlp )
gatesSplit [ hlp - > pos . z ] . push_back ( hlp ) ;
2014-06-05 19:52:14 +03:00
}
//sort by position
std : : sort ( gatesSplit [ 0 ] . begin ( ) , gatesSplit [ 0 ] . end ( ) , [ ] ( const CGObjectInstance * a , const CGObjectInstance * b )
{
return a - > pos < b - > pos ;
} ) ;
2015-03-11 16:17:21 +02:00
auto assignToChannel = [ & ] ( CGSubterraneanGate * obj )
{
if ( obj - > channel = = TeleportChannelID ( ) )
{ // if object not linked to channel then create new channel
2023-02-12 22:39:17 +02:00
obj - > channel = TeleportChannelID ( static_cast < si32 > ( cb - > gameState ( ) - > map - > teleportChannels . size ( ) ) ) ;
2015-03-11 16:17:21 +02:00
addToChannel ( cb - > gameState ( ) - > map - > teleportChannels , obj ) ;
}
} ;
2014-06-05 19:52:14 +03:00
for ( size_t i = 0 ; i < gatesSplit [ 0 ] . size ( ) ; i + + )
{
2015-03-08 15:11:23 +02:00
CGSubterraneanGate * objCurrent = gatesSplit [ 0 ] [ i ] ;
2014-06-05 19:52:14 +03:00
//find nearest underground exit
std : : pair < int , si32 > best ( - 1 , std : : numeric_limits < si32 > : : max ( ) ) ; //pair<pos_in_vector, distance^2>
for ( int j = 0 ; j < gatesSplit [ 1 ] . size ( ) ; j + + )
{
2015-03-08 15:11:23 +02:00
CGSubterraneanGate * checked = gatesSplit [ 1 ] [ j ] ;
2015-03-11 16:17:21 +02:00
if ( checked - > channel ! = TeleportChannelID ( ) )
2014-06-05 19:52:14 +03:00
continue ;
2015-03-08 15:11:23 +02:00
si32 hlp = checked - > pos . dist2dSQ ( objCurrent - > pos ) ;
2014-06-05 19:52:14 +03:00
if ( hlp < best . second )
{
best . first = j ;
best . second = hlp ;
}
}
2015-03-11 16:17:21 +02:00
assignToChannel ( objCurrent ) ;
2014-06-05 19:52:14 +03:00
if ( best . first > = 0 ) //found pair
{
2015-03-08 15:11:23 +02:00
gatesSplit [ 1 ] [ best . first ] - > channel = objCurrent - > channel ;
addToChannel ( cb - > gameState ( ) - > map - > teleportChannels , gatesSplit [ 1 ] [ best . first ] ) ;
2014-06-05 19:52:14 +03:00
}
}
2015-03-11 16:17:21 +02:00
// we should assign empty channels to underground gates if they don't have matching overground gates
2023-02-12 22:39:17 +02:00
for ( auto & i : gatesSplit [ 1 ] )
assignToChannel ( i ) ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
void CGWhirlpool : : onHeroVisit ( const CGHeroInstance * h ) const
2014-06-05 19:52:14 +03:00
{
2023-09-15 21:18:36 +02:00
TeleportDialog td ( h - > id , channel ) ;
2015-03-09 01:13:40 +02:00
if ( cb - > isTeleportChannelImpassable ( channel ) )
2014-06-05 19:52:14 +03:00
{
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " Cannot find exit whirlpool for %d at %s " , id . getNum ( ) , pos . toString ( ) ) ;
2015-03-08 15:11:23 +02:00
td . impassable = true ;
2014-06-05 19:52:14 +03:00
}
2015-03-08 15:11:23 +02:00
else if ( getRandomExit ( h ) = = ObjectInstanceID ( ) )
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " All exits are blocked for whirlpool %d at %s " , id . getNum ( ) , pos . toString ( ) ) ;
2014-06-05 19:52:14 +03:00
2015-03-08 15:11:23 +02:00
if ( ! isProtected ( h ) )
{
SlotID targetstack = h - > Slots ( ) . begin ( ) - > first ; //slot numbers may vary
for ( auto i = h - > Slots ( ) . rbegin ( ) ; i ! = h - > Slots ( ) . rend ( ) ; i + + )
{
if ( h - > getPower ( targetstack ) > h - > getPower ( i - > first ) )
targetstack = ( i - > first ) ;
}
2023-02-12 22:39:17 +02:00
auto countToTake = static_cast < TQuantity > ( h - > getStackCount ( targetstack ) * 0.5 ) ;
2015-03-08 15:11:23 +02:00
vstd : : amax ( countToTake , 1 ) ;
InfoWindow iw ;
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2015-03-08 15:11:23 +02:00
iw . player = h - > tempOwner ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 168 ) ;
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : CREATURE , h - > getCreature ( targetstack ) - > getId ( ) , - countToTake ) ;
2015-03-08 15:11:23 +02:00
cb - > showInfoDialog ( & iw ) ;
cb - > changeStackCount ( StackLocation ( h , targetstack ) , - countToTake ) ;
}
else
2015-12-02 16:56:26 +02:00
{
auto exits = getAllExits ( ) ;
2023-02-12 22:39:17 +02:00
for ( const auto & exit : exits )
2015-12-02 16:56:26 +02:00
{
auto blockedPosList = cb - > getObj ( exit ) - > getBlockedPos ( ) ;
2023-02-12 22:39:17 +02:00
for ( const auto & bPos : blockedPosList )
2024-06-11 16:31:11 +02:00
td . exits . push_back ( std : : make_pair ( exit , bPos ) ) ;
2015-12-02 16:56:26 +02:00
}
}
2015-03-08 15:11:23 +02:00
cb - > showTeleportDialog ( & td ) ;
}
2015-12-02 16:56:26 +02:00
void CGWhirlpool : : teleportDialogAnswered ( const CGHeroInstance * hero , ui32 answer , TTeleportExitsList exits ) const
2015-03-08 15:11:23 +02:00
{
2015-12-02 16:56:26 +02:00
int3 dPos ;
2015-03-08 15:11:23 +02:00
auto realExits = getAllExits ( ) ;
2023-02-12 22:39:17 +02:00
if ( exits . empty ( ) & & realExits . empty ( ) )
2015-03-08 15:11:23 +02:00
return ;
2015-12-02 16:56:26 +02:00
else if ( vstd : : isValidIndex ( exits , answer ) )
dPos = exits [ answer ] . second ;
2015-03-08 15:11:23 +02:00
else
{
2021-10-17 11:18:16 +02:00
auto exit = getRandomExit ( hero ) ;
if ( exit = = ObjectInstanceID ( ) )
return ;
2023-02-12 22:39:17 +02:00
const auto * obj = cb - > getObj ( exit ) ;
2015-03-08 15:11:23 +02:00
std : : set < int3 > tiles = obj - > getBlockedPos ( ) ;
2024-07-12 14:15:30 +02:00
dPos = * RandomGeneratorUtil : : nextItem ( tiles , cb - > gameState ( ) - > getRandomGenerator ( ) ) ;
2015-03-08 15:11:23 +02:00
}
2015-12-02 16:56:26 +02:00
2024-06-11 16:31:11 +02:00
cb - > moveHero ( hero - > id , hero - > convertFromVisitablePos ( dPos ) , EMovementMode : : MONOLITH ) ;
2015-03-08 15:11:23 +02:00
}
2015-12-24 20:30:57 +02:00
bool CGWhirlpool : : isProtected ( const CGHeroInstance * h )
2015-03-08 15:11:23 +02:00
{
2023-05-01 00:20:01 +02:00
return h - > hasBonusOfType ( BonusType : : WHIRLPOOL_PROTECTION )
2024-04-20 14:26:25 +02:00
| | ( h - > stacksCount ( ) = = 1 & & h - > Slots ( ) . begin ( ) - > second - > count = = 1 )
| | ( h - > stacksCount ( ) = = 0 & & h - > commander & & h - > commander - > alive ) ;
2014-06-05 19:52:14 +03:00
}
2023-10-24 16:11:25 +02:00
ArtifactID CGArtifact : : getArtifact ( ) const
{
if ( ID = = Obj : : SPELL_SCROLL )
return ArtifactID : : SPELL_SCROLL ;
else
return getObjTypeIndex ( ) . getNum ( ) ;
}
2024-06-01 17:28:17 +02:00
void CGArtifact : : pickRandomObject ( vstd : : RNG & rand )
2023-10-25 12:50:11 +02:00
{
2023-11-02 16:29:59 +02:00
switch ( ID . toEnum ( ) )
2023-10-25 12:50:11 +02:00
{
case MapObjectID : : RANDOM_ART :
2023-11-07 14:27:25 +02:00
subID = cb - > gameState ( ) - > pickRandomArtifact ( rand , CArtifact : : ART_TREASURE | CArtifact : : ART_MINOR | CArtifact : : ART_MAJOR | CArtifact : : ART_RELIC ) ;
2023-10-25 12:50:11 +02:00
break ;
case MapObjectID : : RANDOM_TREASURE_ART :
2023-11-07 14:27:25 +02:00
subID = cb - > gameState ( ) - > pickRandomArtifact ( rand , CArtifact : : ART_TREASURE ) ;
2023-10-25 12:50:11 +02:00
break ;
case MapObjectID : : RANDOM_MINOR_ART :
2023-11-07 14:27:25 +02:00
subID = cb - > gameState ( ) - > pickRandomArtifact ( rand , CArtifact : : ART_MINOR ) ;
2023-10-25 12:50:11 +02:00
break ;
case MapObjectID : : RANDOM_MAJOR_ART :
2023-11-07 14:27:25 +02:00
subID = cb - > gameState ( ) - > pickRandomArtifact ( rand , CArtifact : : ART_MAJOR ) ;
2023-10-25 12:50:11 +02:00
break ;
case MapObjectID : : RANDOM_RELIC_ART :
2023-11-07 14:27:25 +02:00
subID = cb - > gameState ( ) - > pickRandomArtifact ( rand , CArtifact : : ART_RELIC ) ;
2023-10-25 12:50:11 +02:00
break ;
}
2023-11-26 00:41:49 +02:00
if ( ID ! = MapObjectID : : SPELL_SCROLL & & ID ! = MapObjectID : : ARTIFACT )
{
ID = MapObjectID : : ARTIFACT ;
setType ( ID , subID ) ;
}
else if ( ID ! = MapObjectID : : SPELL_SCROLL )
2023-10-25 12:50:11 +02:00
ID = MapObjectID : : ARTIFACT ;
}
2024-06-01 17:28:17 +02:00
void CGArtifact : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
blockVisit = true ;
if ( ID = = Obj : : ARTIFACT )
{
2014-07-05 20:35:46 +03:00
if ( ! storedArtifact )
{
2023-02-12 22:39:17 +02:00
auto * a = new CArtifactInstance ( ) ;
2014-07-05 20:35:46 +03:00
cb - > gameState ( ) - > map - > addNewArtifactInstance ( a ) ;
storedArtifact = a ;
}
2014-06-05 19:52:14 +03:00
if ( ! storedArtifact - > artType )
2023-12-31 23:43:35 +02:00
storedArtifact - > setType ( getArtifact ( ) . toArtifact ( ) ) ;
2014-06-05 19:52:14 +03:00
}
if ( ID = = Obj : : SPELL_SCROLL )
subID = 1 ;
assert ( storedArtifact - > artType ) ;
2024-01-16 17:45:43 +02:00
assert ( ! storedArtifact - > getParentNodes ( ) . empty ( ) ) ;
2014-06-05 19:52:14 +03:00
//assert(storedArtifact->artType->id == subID); //this does not stop desync
}
2014-06-24 20:39:36 +03:00
std : : string CGArtifact : : getObjectName ( ) const
{
2023-11-02 17:48:48 +02:00
return VLC - > artifacts ( ) - > getById ( getArtifact ( ) ) - > getNameTranslated ( ) ;
2014-06-24 20:39:36 +03:00
}
2023-11-02 15:51:08 +02:00
std : : string CGArtifact : : getPopupText ( PlayerColor player ) const
{
if ( settings [ " general " ] [ " enableUiEnhancements " ] . Bool ( ) )
{
std : : string description = VLC - > artifacts ( ) - > getById ( getArtifact ( ) ) - > getDescriptionTranslated ( ) ;
2023-11-13 00:08:14 +02:00
if ( getArtifact ( ) = = ArtifactID : : SPELL_SCROLL )
ArtifactUtils : : insertScrrollSpellName ( description , SpellID : : NONE ) ; // erase text placeholder
2023-11-02 15:51:08 +02:00
return description ;
}
else
return getObjectName ( ) ;
}
std : : string CGArtifact : : getPopupText ( const CGHeroInstance * hero ) const
{
return getPopupText ( hero - > getOwner ( ) ) ;
}
std : : vector < Component > CGArtifact : : getPopupComponents ( PlayerColor player ) const
{
return {
Component ( ComponentType : : ARTIFACT , getArtifact ( ) )
} ;
}
2015-12-24 20:05:02 +02:00
void CGArtifact : : onHeroVisit ( const CGHeroInstance * h ) const
2014-06-05 19:52:14 +03:00
{
if ( ! stacksCount ( ) )
{
InfoWindow iw ;
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2014-06-05 19:52:14 +03:00
iw . player = h - > tempOwner ;
2023-03-18 22:58:39 +02:00
2023-03-21 12:13:53 +02:00
if ( storedArtifact - > artType - > canBePutAt ( h ) )
2014-06-05 19:52:14 +03:00
{
2023-11-02 16:29:59 +02:00
switch ( ID . toEnum ( ) )
2023-03-18 22:58:39 +02:00
{
case Obj : : ARTIFACT :
2014-06-05 19:52:14 +03:00
{
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : ARTIFACT , getArtifact ( ) ) ;
2023-09-27 23:11:11 +02:00
if ( ! message . empty ( ) )
iw . text = message ;
2014-06-05 19:52:14 +03:00
else
2023-11-02 22:01:49 +02:00
iw . text . appendTextID ( getArtifact ( ) . toArtifact ( ) - > getEventTextID ( ) ) ;
2014-06-05 19:52:14 +03:00
}
break ;
2023-03-18 22:58:39 +02:00
case Obj : : SPELL_SCROLL :
2014-06-05 19:52:14 +03:00
{
2023-11-02 13:18:40 +02:00
SpellID spell = storedArtifact - > getScrollSpellID ( ) ;
iw . components . emplace_back ( ComponentType : : SPELL , spell ) ;
2023-09-27 23:11:11 +02:00
if ( ! message . empty ( ) )
iw . text = message ;
2015-12-24 20:05:02 +02:00
else
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 135 ) ;
2023-11-02 22:01:49 +02:00
iw . text . replaceName ( spell ) ;
2015-12-24 20:05:02 +02:00
}
2014-06-05 19:52:14 +03:00
}
break ;
2023-03-18 22:58:39 +02:00
}
}
else
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 2 ) ;
2014-06-05 19:52:14 +03:00
}
cb - > showInfoDialog ( & iw ) ;
pick ( h ) ;
}
else
{
2023-11-02 16:29:59 +02:00
switch ( ID . toEnum ( ) )
2014-06-05 19:52:14 +03:00
{
2015-12-24 20:05:02 +02:00
case Obj : : ARTIFACT :
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > getOwner ( ) ;
2023-09-27 23:11:11 +02:00
if ( ! message . empty ( ) )
ynd . text = message ;
2015-12-24 20:05:02 +02:00
else
{
// TODO: Guard text is more complex in H3, see mantis issue 2325 for details
2023-06-18 11:18:25 +02:00
ynd . text . appendLocalString ( EMetaText : : GENERAL_TXT , 420 ) ;
ynd . text . replaceRawString ( " " ) ;
ynd . text . replaceRawString ( getArmyDescription ( ) ) ;
ynd . text . replaceLocalString ( EMetaText : : GENERAL_TXT , 43 ) ; // creatures
2015-12-24 20:05:02 +02:00
}
cb - > showBlockingDialog ( & ynd ) ;
}
break ;
case Obj : : SPELL_SCROLL :
{
2023-09-27 23:11:11 +02:00
if ( ! message . empty ( ) )
2015-12-24 20:05:02 +02:00
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > getOwner ( ) ;
2023-09-27 23:11:11 +02:00
ynd . text = message ;
2015-12-24 20:05:02 +02:00
cb - > showBlockingDialog ( & ynd ) ;
}
else
blockingDialogAnswered ( h , true ) ;
}
break ;
2014-06-05 19:52:14 +03:00
}
}
}
void CGArtifact : : pick ( const CGHeroInstance * h ) const
{
2023-11-08 19:54:02 +02:00
if ( cb - > putArtifact ( ArtifactLocation ( h - > id , ArtifactPosition : : FIRST_AVAILABLE ) , storedArtifact ) )
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , h - > getOwner ( ) ) ;
2014-06-05 19:52:14 +03:00
}
2022-07-09 18:00:03 +02:00
BattleField CGArtifact : : getBattlefield ( ) const
{
return BattleField : : NONE ;
}
2014-06-05 19:52:14 +03:00
void CGArtifact : : battleFinished ( const CGHeroInstance * hero , const BattleResult & result ) const
{
if ( result . winner = = 0 ) //attacker won
pick ( hero ) ;
}
void CGArtifact : : blockingDialogAnswered ( const CGHeroInstance * hero , ui32 answer ) const
{
if ( answer )
cb - > startBattleI ( hero , this ) ;
}
2017-05-28 15:23:42 +02:00
void CGArtifact : : afterAddToMap ( CMap * map )
{
2022-09-17 13:04:01 +02:00
//Artifacts from map objects are never removed
//FIXME: This should be revertible in map editor
2023-07-03 22:11:56 +02:00
if ( ID = = Obj : : SPELL_SCROLL & & storedArtifact & & storedArtifact - > getId ( ) . getNum ( ) < 0 )
2017-05-28 15:23:42 +02:00
map - > addNewArtifactInstance ( storedArtifact ) ;
}
2016-02-22 01:37:19 +02:00
void CGArtifact : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2023-09-27 23:11:11 +02:00
handler . serializeStruct ( " guardMessage " , message ) ;
2023-09-17 22:19:45 +02:00
CArmedInstance : : serializeJsonOptions ( handler ) ;
if ( ! handler . saving & & ! handler . getCurrent ( ) [ " guards " ] . Vector ( ) . empty ( ) )
CCreatureSet : : serializeJson ( handler , " guards " , 7 ) ;
2016-02-22 01:37:19 +02:00
if ( handler . saving & & ID = = Obj : : SPELL_SCROLL )
2016-02-13 18:43:05 +02:00
{
2024-01-16 18:14:40 +02:00
const auto & b = storedArtifact - > getFirstBonus ( Selector : : type ( ) ( BonusType : : SPELL ) ) ;
2023-10-05 15:13:52 +02:00
SpellID spellId ( b - > subtype . as < SpellID > ( ) ) ;
2016-02-13 18:43:05 +02:00
2017-07-20 06:08:49 +02:00
handler . serializeId ( " spell " , spellId , SpellID : : NONE ) ;
2016-02-13 18:43:05 +02:00
}
2015-11-16 15:30:40 +02:00
}
2024-06-01 17:28:17 +02:00
void CGSignBottle : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
//if no text is set than we pick random from the predefined ones
if ( message . empty ( ) )
{
2022-12-27 22:19:05 +02:00
auto vector = VLC - > generaltexth - > findStringsWithPrefix ( " core.randsign " ) ;
2022-12-28 01:14:41 +02:00
std : : string messageIdentifier = * RandomGeneratorUtil : : nextItem ( vector , rand ) ;
2023-12-09 18:09:57 +02:00
message . appendTextID ( messageIdentifier ) ;
2014-06-05 19:52:14 +03:00
}
if ( ID = = Obj : : OCEAN_BOTTLE )
{
blockVisit = true ;
}
}
void CGSignBottle : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > getOwner ( ) ;
2023-09-27 23:11:11 +02:00
iw . text = message ;
2014-06-05 19:52:14 +03:00
cb - > showInfoDialog ( & iw ) ;
if ( ID = = Obj : : OCEAN_BOTTLE )
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , h - > getOwner ( ) ) ;
2014-06-05 19:52:14 +03:00
}
2016-02-22 01:37:19 +02:00
void CGSignBottle : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2023-09-27 23:11:11 +02:00
handler . serializeStruct ( " text " , message ) ;
2015-11-16 15:30:40 +02:00
}
2014-06-05 19:52:14 +03:00
void CGGarrison : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-08-19 20:43:50 +02:00
auto relations = cb - > gameState ( ) - > getPlayerRelations ( h - > tempOwner , tempOwner ) ;
if ( relations = = PlayerRelations : : ENEMIES & & stacksCount ( ) > 0 ) {
2014-06-05 19:52:14 +03:00
//TODO: Find a way to apply magic garrison effects in battle.
cb - > startBattleI ( h , this ) ;
return ;
}
//New owner.
2023-08-19 20:43:50 +02:00
if ( relations = = PlayerRelations : : ENEMIES )
2014-06-05 19:52:14 +03:00
cb - > setOwner ( this , h - > tempOwner ) ;
cb - > showGarrisonDialog ( id , h - > id , removableUnits ) ;
}
2014-06-24 02:26:36 +03:00
bool CGGarrison : : passableFor ( PlayerColor player ) const
2014-06-05 19:52:14 +03:00
{
2014-06-24 02:26:36 +03:00
//FIXME: identical to same method in CGTownInstance
2014-06-05 19:52:14 +03:00
if ( ! stacksCount ( ) ) //empty - anyone can visit
2014-06-24 02:26:36 +03:00
return true ;
2014-06-05 19:52:14 +03:00
if ( tempOwner = = PlayerColor : : NEUTRAL ) //neutral guarded - no one can visit
2014-06-24 02:26:36 +03:00
return false ;
2014-06-05 19:52:14 +03:00
2014-06-24 02:26:36 +03:00
if ( cb - > getPlayerRelations ( tempOwner , player ) ! = PlayerRelations : : ENEMIES )
return true ;
return false ;
2014-06-05 19:52:14 +03:00
}
void CGGarrison : : battleFinished ( const CGHeroInstance * hero , const BattleResult & result ) const
{
if ( result . winner = = 0 )
onHeroVisit ( hero ) ;
}
2016-02-22 01:37:19 +02:00
void CGGarrison : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 01:37:19 +02:00
handler . serializeBool ( " removableUnits " , removableUnits ) ;
serializeJsonOwner ( handler ) ;
2023-09-17 22:19:45 +02:00
CArmedInstance : : serializeJsonOptions ( handler ) ;
2015-11-16 15:30:40 +02:00
}
2024-06-01 17:28:17 +02:00
void CGGarrison : : initObj ( vstd : : RNG & rand )
2023-12-21 19:09:33 +02:00
{
if ( this - > subID = = MapObjectSubID : : decode ( this - > ID , " antiMagic " ) )
addAntimagicGarrisonBonus ( ) ;
}
void CGGarrison : : addAntimagicGarrisonBonus ( )
{
auto bonus = std : : make_shared < Bonus > ( ) ;
bonus - > type = BonusType : : BLOCK_ALL_MAGIC ;
bonus - > source = BonusSource : : OBJECT_TYPE ;
bonus - > sid = BonusSourceID ( this - > ID ) ;
bonus - > propagator = std : : make_shared < CPropagatorNodeType > ( CBonusSystemNode : : BATTLE ) ;
bonus - > duration = BonusDuration : : PERMANENT ;
this - > addNewBonus ( bonus ) ;
}
2024-06-01 17:28:17 +02:00
void CGMagi : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
if ( ID = = Obj : : EYE_OF_MAGI )
blockVisit = true ;
}
2023-11-17 21:18:34 +02:00
2014-06-05 19:52:14 +03:00
void CGMagi : : onHeroVisit ( const CGHeroInstance * h ) const
{
if ( ID = = Obj : : HUT_OF_MAGI )
{
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 61 ) ;
2014-06-05 19:52:14 +03:00
2023-11-17 21:18:34 +02:00
std : : vector < const CGObjectInstance * > eyes ;
for ( auto object : cb - > gameState ( ) - > map - > objects )
{
if ( object & & object - > ID = = Obj : : EYE_OF_MAGI & & object - > subID = = this - > subID )
eyes . push_back ( object ) ;
}
if ( ! eyes . empty ( ) )
2014-06-05 19:52:14 +03:00
{
CenterView cv ;
cv . player = h - > tempOwner ;
cv . focusTime = 2000 ;
FoWChange fw ;
fw . player = h - > tempOwner ;
2023-10-04 14:31:42 +02:00
fw . mode = ETileVisibility : : REVEALED ;
2014-06-21 16:41:05 +03:00
fw . waitForDialogs = true ;
2014-06-05 19:52:14 +03:00
2023-11-17 21:18:34 +02:00
for ( const auto & eye : eyes )
2014-06-05 19:52:14 +03:00
{
2023-10-04 14:31:42 +02:00
cb - > getTilesInRange ( fw . tiles , eye - > pos , 10 , ETileVisibility : : HIDDEN , h - > tempOwner ) ;
2014-06-05 19:52:14 +03:00
cb - > sendAndApply ( & fw ) ;
cv . pos = eye - > pos ;
cb - > sendAndApply ( & cv ) ;
}
2022-12-07 22:10:08 +02:00
cv . pos = h - > visitablePos ( ) ;
2016-01-22 23:35:41 +02:00
cv . focusTime = 0 ;
2014-06-05 19:52:14 +03:00
cb - > sendAndApply ( & cv ) ;
}
}
else if ( ID = = Obj : : EYE_OF_MAGI )
{
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 48 ) ;
2014-06-05 19:52:14 +03:00
}
2023-06-07 23:42:47 +02:00
}
2014-06-05 19:52:14 +03:00
2024-01-01 16:37:48 +02:00
CGBoat : : CGBoat ( IGameCallback * cb )
: CGObjectInstance ( cb )
2023-06-07 23:42:47 +02:00
{
hero = nullptr ;
direction = 4 ;
2023-08-19 23:22:31 +02:00
layer = EPathfindingLayer : : SAIL ;
2014-06-05 19:52:14 +03:00
}
2023-06-07 23:42:47 +02:00
2023-06-21 15:49:44 +02:00
bool CGBoat : : isCoastVisitable ( ) const
{
return true ;
}
2024-06-01 17:28:17 +02:00
void CGSirens : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
blockVisit = true ;
}
2014-06-24 20:39:36 +03:00
std : : string CGSirens : : getHoverText ( const CGHeroInstance * hero ) const
2014-06-05 19:52:14 +03:00
{
2023-10-21 14:06:18 +02:00
return getObjectName ( ) + " " + visitedTxt ( hero - > hasBonusFrom ( BonusSource : : OBJECT_TYPE , BonusSourceID ( ID ) ) ) ;
2014-06-05 19:52:14 +03:00
}
void CGSirens : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
2023-10-21 14:06:18 +02:00
if ( h - > hasBonusFrom ( BonusSource : : OBJECT_TYPE , BonusSourceID ( ID ) ) ) //has already visited Sirens
2014-06-05 19:52:14 +03:00
{
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 133 ) ;
2014-06-05 19:52:14 +03:00
}
else
{
2023-05-01 00:20:01 +02:00
giveDummyBonus ( h - > id , BonusDuration : : ONE_BATTLE ) ;
2014-06-05 19:52:14 +03:00
TExpType xp = 0 ;
for ( auto i = h - > Slots ( ) . begin ( ) ; i ! = h - > Slots ( ) . end ( ) ; i + + )
{
2023-01-06 23:29:33 +02:00
// 1-sized stacks are not affected by sirens
if ( i - > second - > count = = 1 )
continue ;
// tested H3 behavior: 30% (rounded up) of stack drowns
TQuantity drown = std : : ceil ( i - > second - > count * 0.3 ) ;
2014-06-05 19:52:14 +03:00
if ( drown )
{
cb - > changeStackCount ( StackLocation ( h , i - > first ) , - drown ) ;
2023-05-01 19:29:53 +02:00
xp + = drown * i - > second - > type - > getMaxHealth ( ) ;
2014-06-05 19:52:14 +03:00
}
}
if ( xp )
{
2023-02-12 22:39:17 +02:00
xp = h - > calculateXp ( static_cast < int > ( xp ) ) ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 132 ) ;
iw . text . replaceNumber ( static_cast < int > ( xp ) ) ;
2024-01-04 23:57:36 +02:00
cb - > giveExperience ( h , xp ) ;
2014-06-05 19:52:14 +03:00
}
else
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 134 ) ;
2014-06-05 19:52:14 +03:00
}
}
cb - > showInfoDialog ( & iw ) ;
}
void CGShipyard : : getOutOffsets ( std : : vector < int3 > & offsets ) const
{
// H J L K I
// A x S x B
// C E G F D
2014-10-03 23:34:13 +03:00
offsets = {
2023-06-20 21:38:47 +02:00
{ - 2 , 0 , 0 } , // A
{ + 2 , 0 , 0 } , // B
{ - 2 , 1 , 0 } , // C
{ + 2 , 1 , 0 } , // D
{ - 1 , 1 , 0 } , // E
{ + 1 , 1 , 0 } , // F
{ 0 , 1 , 0 } , // G
{ - 2 , - 1 , 0 } , // H
{ + 2 , - 1 , 0 } , // I
{ - 1 , - 1 , 0 } , // G
{ + 1 , - 1 , 0 } , // K
{ 0 , - 1 , 0 } , // L
2015-11-16 15:30:40 +02:00
} ;
2014-06-05 19:52:14 +03:00
}
2023-06-07 00:55:21 +02:00
const IObjectInterface * CGShipyard : : getObject ( ) const
{
return this ;
}
2014-06-05 19:52:14 +03:00
void CGShipyard : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-06-20 22:19:54 +02:00
if ( cb - > gameState ( ) - > getPlayerRelations ( tempOwner , h - > tempOwner ) = = PlayerRelations : : ENEMIES )
2014-06-05 19:52:14 +03:00
cb - > setOwner ( this , h - > tempOwner ) ;
2023-06-20 22:19:54 +02:00
if ( shipyardStatus ( ) ! = IBoatGenerator : : GOOD )
2014-06-05 19:52:14 +03:00
{
InfoWindow iw ;
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2014-06-05 19:52:14 +03:00
iw . player = tempOwner ;
getProblemText ( iw . text , h ) ;
cb - > showInfoDialog ( & iw ) ;
}
else
{
2023-09-28 00:17:05 +02:00
cb - > showObjectWindow ( this , EOpenWindowMode : : SHIPYARD_WINDOW , h , false ) ;
2014-06-05 19:52:14 +03:00
}
}
2016-02-22 01:37:19 +02:00
void CGShipyard : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 01:37:19 +02:00
serializeJsonOwner ( handler ) ;
2015-11-16 15:30:40 +02:00
}
2023-06-07 00:55:21 +02:00
BoatId CGShipyard : : getBoatType ( ) const
{
2023-06-20 21:06:45 +02:00
return createdBoat ;
2023-06-07 00:55:21 +02:00
}
2014-06-05 19:52:14 +03:00
void CGDenOfthieves : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-09-28 00:17:05 +02:00
cb - > showObjectWindow ( this , EOpenWindowMode : : THIEVES_GUILD , h , false ) ;
2014-06-05 19:52:14 +03:00
}
void CGObelisk : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2014-06-05 19:52:14 +03:00
iw . player = h - > tempOwner ;
TeamState * ts = cb - > gameState ( ) - > getPlayerTeam ( h - > tempOwner ) ;
assert ( ts ) ;
TeamID team = ts - > id ;
if ( ! wasVisited ( team ) )
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 96 ) ;
2014-06-05 19:52:14 +03:00
cb - > sendAndApply ( & iw ) ;
2016-01-20 09:44:13 +02:00
// increment general visited obelisks counter
2023-11-06 18:27:16 +02:00
cb - > setObjPropertyID ( id , ObjProperty : : OBELISK_VISITED , team ) ;
2023-09-28 00:17:05 +02:00
cb - > showObjectWindow ( this , EOpenWindowMode : : PUZZLE_MAP , h , false ) ;
2014-06-05 19:52:14 +03:00
2016-01-20 10:14:03 +02:00
// mark that particular obelisk as visited for all players in the team
2023-02-12 22:39:17 +02:00
for ( const auto & color : ts - > players )
2016-01-20 10:14:03 +02:00
{
2023-11-06 18:27:16 +02:00
cb - > setObjPropertyID ( id , ObjProperty : : VISITED , color ) ;
2016-01-20 10:14:03 +02:00
}
2014-06-05 19:52:14 +03:00
}
else
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 97 ) ;
2014-06-05 19:52:14 +03:00
cb - > sendAndApply ( & iw ) ;
}
}
2024-06-01 17:28:17 +02:00
void CGObelisk : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
2024-01-09 16:43:36 +02:00
cb - > gameState ( ) - > map - > obeliskCount + + ;
2016-01-20 12:02:52 +02:00
}
2014-06-24 20:39:36 +03:00
std : : string CGObelisk : : getHoverText ( PlayerColor player ) const
2014-06-05 19:52:14 +03:00
{
2014-06-24 20:39:36 +03:00
return getObjectName ( ) + " " + visitedTxt ( wasVisited ( player ) ) ;
2014-06-05 19:52:14 +03:00
}
2023-11-06 18:27:16 +02:00
void CGObelisk : : setPropertyDer ( ObjProperty what , ObjPropertyID identifier )
2014-06-05 19:52:14 +03:00
{
switch ( what )
{
2023-11-06 18:27:16 +02:00
case ObjProperty : : OBELISK_VISITED :
2016-01-20 09:44:13 +02:00
{
2024-01-09 16:43:36 +02:00
auto progress = + + cb - > gameState ( ) - > map - > obelisksVisited [ identifier . as < TeamID > ( ) ] ;
logGlobal - > debug ( " Player %d: obelisk progress %d / %d " , identifier . getNum ( ) , static_cast < int > ( progress ) , static_cast < int > ( cb - > gameState ( ) - > map - > obeliskCount ) ) ;
2014-06-05 19:52:14 +03:00
2024-01-09 16:43:36 +02:00
if ( progress > cb - > gameState ( ) - > map - > obeliskCount )
2016-01-20 09:44:13 +02:00
{
2024-01-09 16:43:36 +02:00
logGlobal - > error ( " Visited %d of %d " , static_cast < int > ( progress ) , cb - > gameState ( ) - > map - > obeliskCount ) ;
2024-04-22 15:40:43 +02:00
throw std : : runtime_error ( " Player visited " + std : : to_string ( progress ) + " obelisks out of " + std : : to_string ( cb - > gameState ( ) - > map - > obeliskCount ) + " present on map! " ) ;
2016-01-20 09:44:13 +02:00
}
2014-06-05 19:52:14 +03:00
2016-01-20 09:44:13 +02:00
break ;
}
default :
2023-11-06 18:27:16 +02:00
CTeamVisited : : setPropertyDer ( what , identifier ) ;
2016-01-20 09:44:13 +02:00
break ;
2014-06-05 19:52:14 +03:00
}
}
void CGLighthouse : : onHeroVisit ( const CGHeroInstance * h ) const
{
if ( h - > tempOwner ! = tempOwner )
{
PlayerColor oldOwner = tempOwner ;
cb - > setOwner ( this , h - > tempOwner ) ; //not ours? flag it!
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 69 ) ;
2014-06-05 19:52:14 +03:00
giveBonusTo ( h - > tempOwner ) ;
2023-08-27 00:35:38 +02:00
if ( oldOwner . isValidPlayer ( ) ) //remove bonus from old owner
2014-06-05 19:52:14 +03:00
{
2023-03-28 13:46:53 +02:00
RemoveBonus rb ( GiveBonus : : ETarget : : PLAYER ) ;
2023-11-06 18:27:16 +02:00
rb . whoID = oldOwner ;
2023-10-21 14:06:18 +02:00
rb . source = BonusSource : : OBJECT_INSTANCE ;
2023-10-21 13:50:42 +02:00
rb . id = BonusSourceID ( id ) ;
2014-06-05 19:52:14 +03:00
cb - > sendAndApply ( & rb ) ;
}
}
}
2024-06-01 17:28:17 +02:00
void CGLighthouse : : initObj ( vstd : : RNG & rand )
2014-06-05 19:52:14 +03:00
{
2023-08-27 00:35:38 +02:00
if ( tempOwner . isValidPlayer ( ) )
2014-06-05 19:52:14 +03:00
{
2018-11-01 22:31:23 +02:00
// FIXME: This is dirty hack
giveBonusTo ( tempOwner , true ) ;
2014-06-05 19:52:14 +03:00
}
}
2023-02-12 22:39:17 +02:00
void CGLighthouse : : giveBonusTo ( const PlayerColor & player , bool onInit ) const
2014-06-05 19:52:14 +03:00
{
2023-03-28 13:46:53 +02:00
GiveBonus gb ( GiveBonus : : ETarget : : PLAYER ) ;
2023-05-01 00:20:01 +02:00
gb . bonus . type = BonusType : : MOVEMENT ;
2014-06-05 19:52:14 +03:00
gb . bonus . val = 500 ;
2023-11-06 18:27:16 +02:00
gb . id = player ;
2023-05-01 00:20:01 +02:00
gb . bonus . duration = BonusDuration : : PERMANENT ;
2023-10-21 14:06:18 +02:00
gb . bonus . source = BonusSource : : OBJECT_INSTANCE ;
2023-10-21 13:50:42 +02:00
gb . bonus . sid = BonusSourceID ( id ) ;
gb . bonus . subtype = BonusCustomSubtype : : heroMovementSea ;
2018-11-01 22:31:23 +02:00
// FIXME: This is really dirty hack
// Proper fix would be to make CGLighthouse into bonus system node
// Unfortunately this will cause saves breakage
if ( onInit )
gb . applyGs ( cb - > gameState ( ) ) ;
else
cb - > sendAndApply ( & gb ) ;
2014-06-05 19:52:14 +03:00
}
2015-11-16 15:30:40 +02:00
2016-02-22 01:37:19 +02:00
void CGLighthouse : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 01:37:19 +02:00
serializeJsonOwner ( handler ) ;
2022-07-26 15:07:42 +02:00
}
2023-06-06 18:19:30 +02:00
void HillFort : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-09-28 00:17:05 +02:00
cb - > showObjectWindow ( this , EOpenWindowMode : : HILL_FORT_WINDOW , h , false ) ;
2023-06-06 18:19:30 +02:00
}
void HillFort : : fillUpgradeInfo ( UpgradeInfo & info , const CStackInstance & stack ) const
{
2023-06-06 23:17:39 +02:00
int32_t level = stack . type - > getLevel ( ) ;
int32_t index = std : : clamp < int32_t > ( level - 1 , 0 , upgradeCostPercentage . size ( ) - 1 ) ;
int costModifier = upgradeCostPercentage [ index ] ;
if ( costModifier < 0 )
return ; // upgrade not allowed
2023-06-06 18:19:30 +02:00
for ( const auto & nid : stack . type - > upgrades )
{
info . newID . push_back ( nid ) ;
info . cost . push_back ( ( nid . toCreature ( ) - > getFullRecruitCost ( ) - stack . type - > getFullRecruitCost ( ) ) * costModifier / 100 ) ;
}
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END