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-08-19 23:22:31 +02:00
# include "../constants/StringConstants.h"
2014-06-05 20:26:50 +03:00
# include "../NetPacks.h"
# include "../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-07-30 19:12:25 +02:00
# include "../modding/ModScope.h"
2014-06-05 19:52:14 +03:00
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2014-06-05 19:52:14 +03:00
std : : map < si32 , std : : vector < ObjectInstanceID > > CGMagi : : eyelist ;
2016-01-20 11:43:07 +02:00
ui8 CGObelisk : : obeliskCount = 0 ; //how many obelisks are on map
2014-06-05 19:52:14 +03:00
std : : map < TeamID , ui8 > CGObelisk : : visited ; //map: team_id => how many obelisks has been visited
///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 ] ;
}
2017-06-30 22:39:37 +02:00
void CTeamVisited : : setPropertyDer ( ui8 what , ui32 val )
2014-06-05 19:52:14 +03:00
{
2017-06-30 22:39:37 +02:00
if ( what = = CTeamVisited : : OBJPROP_VISITED )
2014-06-05 19:52:14 +03:00
players . insert ( PlayerColor ( val ) ) ;
}
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-06-18 11:18:25 +02:00
ynd . text . appendLocalString ( EMetaText : : ADVOB_TXT , subID = = 7 ? 84 : 187 ) ;
2014-06-05 19:52:14 +03:00
cb - > showBlockingDialog ( & ynd ) ;
return ;
}
flagMine ( h - > tempOwner ) ;
}
2016-09-09 19:30:36 +02:00
void CGMine : : newTurn ( CRandomGenerator & 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 ) ;
}
2016-09-09 19:30:36 +02:00
void CGMine : : initObj ( CRandomGenerator & 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 ( ) ) ;
producedResource = * RandomGeneratorUtil : : nextItem ( abandonedMineResources , rand ) ;
2014-06-05 19:52:14 +03:00
}
else
{
2023-04-05 02:26:29 +02:00
producedResource = GameResID ( subID ) ;
2014-06-05 19:52:14 +03:00
}
producedQuantity = defaultResProduction ( ) ;
}
2016-01-23 18:53:02 +02:00
bool CGMine : : isAbandoned ( ) const
{
return ( subID > = 7 ) ;
}
2014-06-24 20:39:36 +03:00
std : : string CGMine : : getObjectName ( ) const
2014-06-05 19:52:14 +03:00
{
2022-12-27 22:19:05 +02:00
return VLC - > generaltexth - > translate ( " core.minename " , subID ) ;
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 )
hoverName + = " \n ( " + VLC - > generaltexth - > restypes [ producedResource ] + " ) " ;
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 ;
2014-06-05 19:52:14 +03:00
iw . soundID = soundBase : : FLAGMINE ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : MINE_EVNTS , producedResource ) ; //not use subID, abandoned mines uses default mine texts
2014-06-05 19:52:14 +03:00
iw . player = player ;
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : RESOURCE , producedResource , producedQuantity , - 1 ) ;
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 ) ;
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
{
2017-07-20 06:08:49 +02:00
JsonNode node ( JsonNode : : JsonType : : DATA_VECTOR ) ;
2023-04-02 18:56:10 +02:00
for ( auto const & resID : abandonedMineResources )
2017-07-20 06:08:49 +02:00
{
2023-04-02 18:56:10 +02:00
JsonNode one ( JsonNode : : JsonType : : DATA_STRING ) ;
one . String ( ) = GameConstants : : RESOURCE_NAMES [ resID ] ;
node . Vector ( ) . push_back ( one ) ;
2017-07-20 06:08:49 +02:00
}
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
}
}
else
{
2016-02-22 01:37:19 +02:00
serializeJsonOwner ( handler ) ;
2016-01-23 18:53:02 +02:00
}
2015-11-16 15:30:40 +02:00
}
2014-06-24 20:39:36 +03:00
std : : string CGResource : : getHoverText ( PlayerColor player ) const
{
return VLC - > generaltexth - > restypes [ subID ] ;
}
2016-09-09 19:30:36 +02:00
void CGResource : : initObj ( CRandomGenerator & 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-04-05 02:26:29 +02:00
switch ( static_cast < EGameResID > ( subID ) )
2014-06-05 19:52:14 +03:00
{
2023-04-05 02:26:29 +02:00
case EGameResID : : GOLD :
2016-09-17 23:22:13 +02:00
amount = rand . nextInt ( 5 , 10 ) * 100 ;
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-04-05 02:26:29 +02:00
cb - > giveResource ( player , static_cast < EGameResID > ( subID ) , 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 ) ;
sii . text . replaceLocalString ( EMetaText : : RES_NAMES , subID ) ;
2023-03-06 01:30:21 +02:00
}
2023-03-10 14:54:12 +02:00
sii . components . emplace_back ( Component : : EComponentType : : RESOURCE , subID , amount , 0 ) ;
2023-03-06 01:30:21 +02:00
sii . soundID = soundBase : : pickup01 + CRandomGenerator : : getDefault ( ) . nextInt ( 6 ) ;
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
{
2015-03-09 14:44:48 +02:00
auto ret = cb - > getTeleportChannelEntraces ( 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 ( ) )
2016-09-09 19:30:36 +02:00
return * RandomGeneratorUtil : : nextItem ( passableExits , CRandomGenerator : : getDefault ( ) ) ;
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-02-12 22:39:17 +02:00
TeleportChannelID CGMonolith : : findMeChannel ( const std : : vector < Obj > & IDs , int 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-02-12 22:39:17 +02:00
const auto * teleportObj = dynamic_cast < const CGTeleport * > ( 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
{
2022-12-09 14:42:47 +02:00
td . exits . push_back ( std : : make_pair ( exit , h - > convertFromVisitablePos ( 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
2022-12-09 14:42:47 +02:00
dPos = hero - > convertFromVisitablePos ( cb - > getObj ( randomExit ) - > visitablePos ( ) ) ;
2015-03-08 15:11:23 +02:00
2015-12-02 16:56:26 +02:00
cb - > moveHero ( hero - > id , dPos , true ) ;
2014-06-05 19:52:14 +03:00
}
2016-09-09 19:30:36 +02:00
void CGMonolith : : initObj ( CRandomGenerator & 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 ) ;
switch ( ID )
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 ) ;
2022-12-09 14:42:47 +02:00
td . exits . push_back ( std : : make_pair ( exit , h - > convertFromVisitablePos ( 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
}
2016-09-09 19:30:36 +02:00
void CGSubterraneanGate : : initObj ( CRandomGenerator & rand )
2015-03-08 15:11:23 +02:00
{
type = BOTH ;
2014-06-05 19:52:14 +03:00
}
2015-03-11 16:17:21 +02:00
void CGSubterraneanGate : : postInit ( ) //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
{
2017-07-14 14:03:55 +02:00
if ( ! obj ) // FIXME: Find out why there are nullptr objects right after initialization
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-03-11 20:21:34 +02:00
iw . components . emplace_back ( CStackBasicDescriptor ( h - > getCreature ( targetstack ) , - 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 )
2022-12-09 14:42:47 +02:00
td . exits . push_back ( std : : make_pair ( exit , h - > convertFromVisitablePos ( 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 ( ) ;
2022-12-09 14:42:47 +02:00
dPos = hero - > convertFromVisitablePos ( * RandomGeneratorUtil : : nextItem ( tiles , CRandomGenerator : : getDefault ( ) ) ) ;
2015-03-08 15:11:23 +02:00
}
2015-12-02 16:56:26 +02:00
cb - > moveHero ( hero - > id , dPos , true ) ;
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 )
2019-03-20 21:58:15 +02:00
| | ( h - > stacksCount ( ) = = 1 & & h - > Slots ( ) . begin ( ) - > second - > count = = 1 ) ;
2014-06-05 19:52:14 +03:00
}
2016-09-09 19:30:36 +02:00
void CGArtifact : : initObj ( CRandomGenerator & 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 )
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
storedArtifact - > setType ( VLC - > arth - > objects [ subID ] ) ;
2014-06-05 19:52:14 +03:00
}
if ( ID = = Obj : : SPELL_SCROLL )
subID = 1 ;
assert ( storedArtifact - > artType ) ;
assert ( storedArtifact - > getParentNodes ( ) . size ( ) ) ;
//assert(storedArtifact->artType->id == subID); //this does not stop desync
}
2014-06-24 20:39:36 +03:00
std : : string CGArtifact : : getObjectName ( ) const
{
2023-01-02 15:58:56 +02:00
return VLC - > artifacts ( ) - > getByIndex ( subID ) - > getNameTranslated ( ) ;
2014-06-24 20:39:36 +03:00
}
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-03-18 22:58:39 +02:00
switch ( ID )
{
case Obj : : ARTIFACT :
2014-06-05 19:52:14 +03:00
{
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : ARTIFACT , subID , 0 , 0 ) ;
2023-09-27 23:11:11 +02:00
if ( ! message . empty ( ) )
iw . text = message ;
2014-06-05 19:52:14 +03:00
else
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ART_EVNTS , subID ) ;
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-04-13 19:40:36 +02:00
int spellID = storedArtifact - > getScrollSpellID ( ) ;
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : SPELL , spellID , 0 , 0 ) ;
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 ) ;
iw . text . replaceLocalString ( EMetaText : : SPELL_NAME , spellID ) ;
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
{
2015-12-24 20:05:02 +02:00
switch ( ID )
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-03-18 22:58:39 +02:00
if ( cb - > giveHeroArtifact ( h , storedArtifact , ArtifactPosition : : FIRST_AVAILABLE ) )
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
{
2023-05-01 00:20:01 +02:00
const std : : shared_ptr < Bonus > b = storedArtifact - > getBonusLocalFirst ( Selector : : type ( ) ( BonusType : : SPELL ) ) ;
2016-02-13 18:43:05 +02:00
SpellID spellId ( b - > subtype ) ;
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
}
2014-06-05 19:52:14 +03:00
void CGObservatory : : 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 ;
switch ( ID )
{
case Obj : : REDWOOD_OBSERVATORY :
case Obj : : PILLAR_OF_FIRE :
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 98 + ( ID = = Obj : : PILLAR_OF_FIRE ) ) ;
2014-06-05 19:52:14 +03:00
FoWChange fw ;
fw . player = h - > tempOwner ;
fw . mode = 1 ;
cb - > getTilesInRange ( fw . tiles , pos , 20 , h - > tempOwner , 1 ) ;
cb - > sendAndApply ( & fw ) ;
break ;
}
case Obj : : COVER_OF_DARKNESS :
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 31 ) ;
2014-06-29 17:23:06 +03:00
for ( auto & player : cb - > gameState ( ) - > players )
2014-06-24 14:50:27 +03:00
{
if ( cb - > getPlayerStatus ( player . first ) = = EPlayerStatus : : INGAME & &
cb - > getPlayerRelations ( player . first , h - > tempOwner ) = = PlayerRelations : : ENEMIES )
cb - > changeFogOfWar ( visitablePos ( ) , 20 , player . first , true ) ;
}
2014-06-05 19:52:14 +03:00
break ;
}
}
cb - > showInfoDialog ( & iw ) ;
}
void CGShrine : : onHeroVisit ( const CGHeroInstance * h ) const
{
if ( spell = = SpellID : : NONE )
{
2017-08-10 18:39:27 +02:00
logGlobal - > error ( " Not initialized shrine visited! " ) ;
2014-06-05 19:52:14 +03:00
return ;
}
if ( ! wasVisited ( h - > tempOwner ) )
2016-01-20 09:44:13 +02:00
cb - > setObjProperty ( id , CGShrine : : OBJPROP_VISITED , h - > tempOwner . getNum ( ) ) ;
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 = h - > getOwner ( ) ;
2023-06-06 17:34:04 +02:00
iw . text = visitText ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : SPELL_NAME , spell ) ;
iw . text . appendRawString ( " . " ) ;
2014-06-05 19:52:14 +03:00
if ( ! h - > getArt ( ArtifactPosition : : SPELLBOOK ) )
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 131 ) ;
2014-06-05 19:52:14 +03:00
}
2018-12-20 23:42:31 +02:00
else if ( h - > spellbookContainsSpell ( spell ) ) //hero already knows the spell
2014-06-05 19:52:14 +03:00
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 174 ) ;
2014-06-05 19:52:14 +03:00
}
2023-06-06 17:34:04 +02:00
else if ( spell . toSpell ( ) - > getLevel ( ) > h - > maxSpellLevel ( ) ) //it's third level spell and hero doesn't have wisdom
2016-09-13 22:28:21 +02:00
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 130 ) ;
2016-09-13 22:28:21 +02:00
}
2014-06-05 19:52:14 +03:00
else //give spell
{
std : : set < SpellID > spells ;
spells . insert ( spell ) ;
cb - > changeSpells ( h , true , spells ) ;
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : SPELL , spell , 0 , 0 ) ;
2014-06-05 19:52:14 +03:00
}
cb - > showInfoDialog ( & iw ) ;
}
2016-09-09 19:30:36 +02:00
void CGShrine : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
2023-06-06 17:34:04 +02:00
VLC - > objtypeh - > getHandlerFor ( ID , subID ) - > configureObject ( this , rand ) ;
2014-06-05 19:52:14 +03:00
}
2014-06-24 20:39:36 +03:00
std : : string CGShrine : : getHoverText ( PlayerColor player ) const
2014-06-05 19:52:14 +03:00
{
2014-06-24 20:39:36 +03:00
std : : string hoverName = getObjectName ( ) ;
if ( wasVisited ( player ) )
2014-06-05 19:52:14 +03:00
{
hoverName + = " \n " + VLC - > generaltexth - > allTexts [ 355 ] ; // + (learn %s)
2023-02-24 13:40:06 +02:00
boost : : algorithm : : replace_first ( hoverName , " %s " , spell . toSpell ( ) - > getNameTranslated ( ) ) ;
2014-06-05 19:52:14 +03:00
}
return hoverName ;
}
2014-06-24 20:39:36 +03:00
std : : string CGShrine : : getHoverText ( const CGHeroInstance * hero ) const
{
std : : string hoverName = getHoverText ( hero - > tempOwner ) ;
2018-12-20 23:42:31 +02:00
if ( wasVisited ( hero - > tempOwner ) & & hero - > spellbookContainsSpell ( spell ) ) //know what spell there is and hero knows that spell
2014-06-24 20:39:36 +03:00
hoverName + = " \n \n " + VLC - > generaltexth - > allTexts [ 354 ] ; // (Already learned)
return hoverName ;
}
2017-07-20 06:08:49 +02:00
void CGShrine : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2017-07-20 06:08:49 +02:00
handler . serializeId ( " spell " , spell , SpellID : : NONE ) ;
2015-11-16 15:30:40 +02:00
}
2016-09-09 19:30:36 +02:00
void CGSignBottle : : initObj ( CRandomGenerator & 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-09-27 23:11:11 +02:00
message . appendTextID ( TextIdentifier ( " core " , " randsign " , messageIdentifier ) . get ( ) ) ;
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 CGScholar : : onHeroVisit ( const CGHeroInstance * h ) const
{
EBonusType type = bonusType ;
int bid = bonusID ;
//check if the bonus if applicable, if not - give primary skill (always possible)
int ssl = h - > getSecSkillLevel ( SecondarySkill ( bid ) ) ; //current sec skill level, used if bonusType == 1
2016-10-09 12:39:49 +02:00
if ( ( type = = SECONDARY_SKILL & & ( ( ssl = = 3 ) | | ( ! ssl & & ! h - > canLearnSkill ( ) ) ) ) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot)
| | ( type = = SPELL & & ! h - > canLearnSpell ( SpellID ( bid ) . toSpell ( ) ) ) )
2014-06-05 19:52:14 +03:00
{
type = PRIM_SKILL ;
2016-09-09 19:30:36 +02:00
bid = CRandomGenerator : : getDefault ( ) . nextInt ( GameConstants : : PRIMARY_SKILLS - 1 ) ;
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 = h - > getOwner ( ) ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 115 ) ;
2014-06-05 19:52:14 +03:00
switch ( type )
{
case PRIM_SKILL :
2023-08-19 20:43:50 +02:00
cb - > changePrimSkill ( h , static_cast < PrimarySkill > ( bid ) , + 1 ) ;
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : PRIM_SKILL , bid , + 1 , 0 ) ;
2014-06-05 19:52:14 +03:00
break ;
case SECONDARY_SKILL :
cb - > changeSecSkill ( h , SecondarySkill ( bid ) , + 1 ) ;
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : SEC_SKILL , bid , ssl + 1 , 0 ) ;
2014-06-05 19:52:14 +03:00
break ;
case SPELL :
{
std : : set < SpellID > hlp ;
hlp . insert ( SpellID ( bid ) ) ;
cb - > changeSpells ( h , true , hlp ) ;
2023-03-10 14:54:12 +02:00
iw . components . emplace_back ( Component : : EComponentType : : SPELL , bid , 0 , 0 ) ;
2014-06-05 19:52:14 +03:00
}
break ;
default :
2017-08-12 14:43:41 +02:00
logGlobal - > error ( " Error: wrong bonus type (%d) for Scholar! \n " , static_cast < int > ( type ) ) ;
2014-06-05 19:52:14 +03:00
return ;
}
cb - > showInfoDialog ( & iw ) ;
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , h - > getOwner ( ) ) ;
2014-06-05 19:52:14 +03:00
}
2016-09-09 19:30:36 +02:00
void CGScholar : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
blockVisit = true ;
if ( bonusType = = RANDOM )
{
2016-09-09 19:30:36 +02:00
bonusType = static_cast < EBonusType > ( rand . nextInt ( 2 ) ) ;
2014-06-05 19:52:14 +03:00
switch ( bonusType )
{
case PRIM_SKILL :
2016-09-09 19:30:36 +02:00
bonusID = rand . nextInt ( GameConstants : : PRIMARY_SKILLS - 1 ) ;
2014-06-05 19:52:14 +03:00
break ;
case SECONDARY_SKILL :
2023-02-12 22:39:17 +02:00
bonusID = rand . nextInt ( static_cast < int > ( VLC - > skillh - > size ( ) ) - 1 ) ;
2014-06-05 19:52:14 +03:00
break ;
case SPELL :
std : : vector < SpellID > possibilities ;
2023-06-06 17:34:04 +02:00
cb - > getAllowedSpells ( possibilities ) ;
2016-09-09 19:30:36 +02:00
bonusID = * RandomGeneratorUtil : : nextItem ( possibilities , rand ) ;
2014-06-05 19:52:14 +03:00
break ;
}
}
}
2016-02-22 01:37:19 +02:00
void CGScholar : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 01:37:19 +02:00
if ( handler . saving )
2016-02-09 08:02:34 +02:00
{
2017-07-20 06:08:49 +02:00
std : : string value ;
2016-02-22 01:37:19 +02:00
switch ( bonusType )
2016-02-09 08:02:34 +02:00
{
2016-02-22 01:37:19 +02:00
case PRIM_SKILL :
2023-08-19 20:43:50 +02:00
value = NPrimarySkill : : names [ bonusID ] ;
2017-07-20 06:08:49 +02:00
handler . serializeString ( " rewardPrimSkill " , value ) ;
2016-02-22 01:37:19 +02:00
break ;
case SECONDARY_SKILL :
2018-03-31 07:56:40 +02:00
value = CSkillHandler : : encodeSkill ( bonusID ) ;
2017-07-20 06:08:49 +02:00
handler . serializeString ( " rewardSkill " , value ) ;
2016-02-22 01:37:19 +02:00
break ;
case SPELL :
2021-02-15 14:03:32 +02:00
value = SpellID : : encode ( bonusID ) ;
2017-07-20 06:08:49 +02:00
handler . serializeString ( " rewardSpell " , value ) ;
2016-02-22 01:37:19 +02:00
break ;
case RANDOM :
break ;
2016-02-09 08:02:34 +02:00
}
}
2016-02-22 01:37:19 +02:00
else
2016-02-09 08:02:34 +02:00
{
2017-07-20 06:08:49 +02:00
//TODO: unify
const JsonNode & json = handler . getCurrent ( ) ;
2016-02-22 01:37:19 +02:00
bonusType = RANDOM ;
2022-11-15 02:20:55 +02:00
if ( ! json [ " rewardPrimSkill " ] . String ( ) . empty ( ) )
2016-02-09 08:02:34 +02:00
{
2023-07-30 19:12:25 +02:00
auto raw = VLC - > identifiers ( ) - > getIdentifier ( ModScope : : scopeBuiltin ( ) , " primSkill " , json [ " rewardPrimSkill " ] . String ( ) ) ;
2016-02-22 01:37:19 +02:00
if ( raw )
{
bonusType = PRIM_SKILL ;
2023-04-16 19:42:56 +02:00
bonusID = raw . value ( ) ;
2016-02-22 01:37:19 +02:00
}
2016-02-09 08:02:34 +02:00
}
2022-11-15 02:20:55 +02:00
else if ( ! json [ " rewardSkill " ] . String ( ) . empty ( ) )
2016-02-09 08:02:34 +02:00
{
2023-07-30 19:12:25 +02:00
auto raw = VLC - > identifiers ( ) - > getIdentifier ( ModScope : : scopeBuiltin ( ) , " skill " , json [ " rewardSkill " ] . String ( ) ) ;
2016-02-22 01:37:19 +02:00
if ( raw )
{
bonusType = SECONDARY_SKILL ;
2023-04-16 19:42:56 +02:00
bonusID = raw . value ( ) ;
2016-02-22 01:37:19 +02:00
}
}
2022-11-15 02:20:55 +02:00
else if ( ! json [ " rewardSpell " ] . String ( ) . empty ( ) )
2016-02-22 01:37:19 +02:00
{
2023-07-30 19:12:25 +02:00
auto raw = VLC - > identifiers ( ) - > getIdentifier ( ModScope : : scopeBuiltin ( ) , " spell " , json [ " rewardSpell " ] . String ( ) ) ;
2016-02-22 01:37:19 +02:00
if ( raw )
{
bonusType = SPELL ;
2023-04-16 19:42:56 +02:00
bonusID = raw . value ( ) ;
2016-02-22 01:37:19 +02:00
}
2016-02-09 08:02:34 +02:00
}
}
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
}
2016-08-25 14:52:20 +02:00
void CGMagi : : reset ( )
{
eyelist . clear ( ) ;
}
2016-09-09 19:30:36 +02:00
void CGMagi : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
if ( ID = = Obj : : EYE_OF_MAGI )
{
blockVisit = true ;
eyelist [ subID ] . push_back ( id ) ;
}
}
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
if ( ! eyelist [ subID ] . empty ( ) )
{
CenterView cv ;
cv . player = h - > tempOwner ;
cv . focusTime = 2000 ;
FoWChange fw ;
fw . player = h - > tempOwner ;
fw . mode = 1 ;
2014-06-21 16:41:05 +03:00
fw . waitForDialogs = true ;
2014-06-05 19:52:14 +03:00
2023-02-12 22:39:17 +02:00
for ( const auto & it : eyelist [ subID ] )
2014-06-05 19:52:14 +03:00
{
const CGObjectInstance * eye = cb - > getObj ( it ) ;
cb - > getTilesInRange ( fw . tiles , eye - > pos , 10 , h - > tempOwner , 1 ) ;
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
2023-06-07 23:42:47 +02:00
CGBoat : : CGBoat ( )
{
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 ;
}
2016-09-09 19:30:36 +02:00
void CGSirens : : initObj ( CRandomGenerator & 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-05-01 00:20:01 +02:00
return getObjectName ( ) + " " + visitedTxt ( hero - > hasBonusFrom ( BonusSource : : OBJECT , ID ) ) ;
2014-06-05 19:52:14 +03:00
}
void CGSirens : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
2023-05-01 00:20:01 +02:00
if ( h - > hasBonusFrom ( BonusSource : : OBJECT , 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 ) ) ;
2014-06-05 19:52:14 +03:00
cb - > changePrimSkill ( h , PrimarySkill : : EXPERIENCE , xp , false ) ;
}
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 CCartographer : : onHeroVisit ( const CGHeroInstance * h ) const
{
2014-12-09 13:01:32 +02:00
//if player has not bought map of this subtype yet and underground exist for stalagmite cartographer
if ( ! wasVisited ( h - > getOwner ( ) ) & & ( subID ! = 2 | | cb - > gameState ( ) - > map - > twoLevel ) )
2014-06-05 19:52:14 +03:00
{
2023-04-05 02:26:29 +02:00
if ( cb - > getResource ( h - > tempOwner , EGameResID : : GOLD ) > = 1000 ) //if he can afford a map
2014-06-05 19:52:14 +03:00
{
//ask if he wants to buy one
int text = 0 ;
switch ( subID )
{
case 0 :
text = 25 ;
break ;
case 1 :
text = 26 ;
break ;
case 2 :
text = 27 ;
break ;
default :
2017-08-10 18:39:27 +02:00
logGlobal - > warn ( " Unrecognized subtype of cartographer " ) ;
2014-06-05 19:52:14 +03:00
}
assert ( text ) ;
BlockingDialog bd ( true , false ) ;
bd . player = h - > getOwner ( ) ;
2023-06-18 11:18:25 +02:00
bd . text . appendLocalString ( EMetaText : : ADVOB_TXT , text ) ;
2014-06-05 19:52:14 +03:00
cb - > showBlockingDialog ( & bd ) ;
}
else //if he cannot afford
{
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 28 ) ;
2014-06-05 19:52:14 +03:00
}
}
else //if he already visited carographer
{
2023-03-08 00:32:21 +02:00
h - > showInfoDialog ( 24 ) ;
2014-06-05 19:52:14 +03:00
}
}
void CCartographer : : blockingDialogAnswered ( const CGHeroInstance * hero , ui32 answer ) const
{
2022-12-30 16:51:13 +02:00
if ( answer ) //if hero wants to buy map
2014-06-05 19:52:14 +03:00
{
2023-04-05 02:26:29 +02:00
cb - > giveResource ( hero - > tempOwner , EGameResID : : GOLD , - 1000 ) ;
2014-06-05 19:52:14 +03:00
FoWChange fw ;
fw . mode = 1 ;
fw . player = hero - > tempOwner ;
//subIDs of different types of cartographers:
//water = 0; land = 1; underground = 2;
2022-12-30 16:09:09 +02:00
2022-12-30 16:51:13 +02:00
IGameCallback : : MapTerrainFilterMode tileFilterMode = IGameCallback : : MapTerrainFilterMode : : NONE ;
switch ( subID )
{
case 0 :
tileFilterMode = CPrivilegedInfoCallback : : MapTerrainFilterMode : : WATER ;
break ;
case 1 :
tileFilterMode = CPrivilegedInfoCallback : : MapTerrainFilterMode : : LAND_CARTOGRAPHER ;
break ;
case 2 :
tileFilterMode = CPrivilegedInfoCallback : : MapTerrainFilterMode : : UNDERGROUND_CARTOGRAPHER ;
break ;
}
cb - > getAllTiles ( fw . tiles , hero - > tempOwner , - 1 , tileFilterMode ) ; //reveal appropriate tiles
cb - > sendAndApply ( & fw ) ;
cb - > setObjProperty ( id , CCartographer : : OBJPROP_VISITED , hero - > tempOwner . getNum ( ) ) ;
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
cb - > setObjProperty ( id , CGObelisk : : OBJPROP_INC , team . getNum ( ) ) ;
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
{
cb - > setObjProperty ( id , CGObelisk : : OBJPROP_VISITED , color . getNum ( ) ) ;
}
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 ) ;
}
}
2016-09-09 19:30:36 +02:00
void CGObelisk : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
obeliskCount + + ;
}
2016-01-20 12:02:52 +02:00
void CGObelisk : : reset ( )
{
obeliskCount = 0 ;
visited . clear ( ) ;
}
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
}
void CGObelisk : : setPropertyDer ( ui8 what , ui32 val )
{
switch ( what )
{
2016-01-20 09:44:13 +02:00
case CGObelisk : : OBJPROP_INC :
{
auto progress = + + visited [ TeamID ( val ) ] ;
2017-08-10 20:59:55 +02:00
logGlobal - > debug ( " Player %d: obelisk progress %d / %d " , val , static_cast < int > ( progress ) , static_cast < int > ( obeliskCount ) ) ;
2014-06-05 19:52:14 +03:00
2016-01-20 09:44:13 +02:00
if ( progress > obeliskCount )
{
2017-08-11 19:03:05 +02:00
logGlobal - > error ( " Visited %d of %d " , static_cast < int > ( progress ) , obeliskCount ) ;
throw std : : runtime_error ( " internal error " ) ;
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 :
2017-06-30 22:39:37 +02:00
CTeamVisited : : setPropertyDer ( what , val ) ;
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 ) ;
2014-06-05 19:52:14 +03:00
rb . whoID = oldOwner . getNum ( ) ;
2023-05-01 00:20:01 +02:00
rb . source = vstd : : to_underlying ( BonusSource : : OBJECT ) ;
2014-06-05 19:52:14 +03:00
rb . id = id . getNum ( ) ;
cb - > sendAndApply ( & rb ) ;
}
}
}
2016-09-09 19:30:36 +02:00
void CGLighthouse : : initObj ( CRandomGenerator & 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 ;
gb . id = player . getNum ( ) ;
2023-05-01 00:20:01 +02:00
gb . bonus . duration = BonusDuration : : PERMANENT ;
gb . bonus . source = BonusSource : : OBJECT ;
2014-06-05 19:52:14 +03:00
gb . bonus . sid = id . getNum ( ) ;
2023-04-29 13:03:19 +02:00
gb . bonus . subtype = 0 ;
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