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"
2016-01-23 18:53:02 +02:00
# include "../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"
2014-06-25 17:11:07 +03:00
# include "../CModHandler.h"
2016-02-22 02:16:33 +02:00
# include "../CHeroHandler.h"
2017-08-30 12:35:23 +02:00
# include "../CSkillHandler.h"
2014-06-05 19:52:14 +03:00
# include "CObjectClassesHandler.h"
2015-02-02 10:25:26 +02:00
# include "../spells/CSpellHandler.h"
2014-06-25 17:11:07 +03:00
# include "../IGameCallback.h"
# include "../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"
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
static void openWindow ( const OpenWindow : : EWindow type , const int id1 , const int id2 = - 1 )
{
OpenWindow ow ;
ow . window = type ;
ow . id1 = id1 ;
ow . id2 = id2 ;
IObjectInterface : : cb - > sendAndApply ( & ow ) ;
}
2017-09-13 02:35:58 +02:00
static void showInfoDialog ( const PlayerColor playerID , const ui32 txtID , const ui16 soundID = 0 )
2014-06-05 19:52:14 +03:00
{
InfoWindow iw ;
2017-09-13 02:35:58 +02:00
if ( soundID )
iw . soundID = soundID ;
2014-06-05 19:52:14 +03:00
iw . player = playerID ;
iw . text . addTxt ( MetaString : : ADVOB_TXT , txtID ) ;
IObjectInterface : : cb - > sendAndApply ( & iw ) ;
}
2017-09-13 02:35:58 +02:00
static void showInfoDialog ( const CGHeroInstance * h , const ui32 txtID , const ui16 soundID = 0 )
2014-06-05 19:52:14 +03:00
{
const PlayerColor playerID = h - > getOwner ( ) ;
showInfoDialog ( playerID , txtID , soundID ) ;
}
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 ) ;
}
2017-06-30 22:39:37 +02:00
bool CTeamVisited : : wasVisited ( TeamID team ) const
2014-06-05 19:52:14 +03:00
{
for ( auto i : players )
{
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 ;
}
2014-06-24 20:39:36 +03:00
std : : string CGCreature : : getHoverText ( PlayerColor player ) const
2014-06-05 19:52:14 +03:00
{
if ( stacks . empty ( ) )
{
//should not happen...
2017-08-11 19:03:05 +02:00
logGlobal - > error ( " Invalid stack at tile %s: subID=%d; id=%d " , pos . toString ( ) , subID , id . getNum ( ) ) ;
return " INVALID_STACK " ;
2014-06-05 19:52:14 +03:00
}
2014-06-24 20:39:36 +03:00
std : : string hoverName ;
2014-06-05 19:52:14 +03:00
MetaString ms ;
int pom = stacks . begin ( ) - > second - > getQuantityID ( ) ;
pom = 172 + 3 * pom ;
ms . addTxt ( MetaString : : ARRAY_TXT , pom ) ;
ms < < " " ;
ms . addTxt ( MetaString : : CRE_PL_NAMES , subID ) ;
ms . toString ( hoverName ) ;
2014-06-24 20:39:36 +03:00
return hoverName ;
}
2014-06-05 19:52:14 +03:00
2014-06-24 20:39:36 +03:00
std : : string CGCreature : : getHoverText ( const CGHeroInstance * hero ) const
{
2015-02-06 14:42:09 +02:00
std : : string hoverName ;
2015-02-06 16:36:09 +02:00
if ( hero - > hasVisions ( this , 0 ) )
2015-11-16 15:30:40 +02:00
{
2015-02-06 14:42:09 +02:00
MetaString ms ;
ms < < stacks . begin ( ) - > second - > count ;
ms < < " " ;
ms . addTxt ( MetaString : : CRE_PL_NAMES , subID ) ;
2015-11-16 15:30:40 +02:00
2015-02-06 18:07:03 +02:00
ms < < " \n " ;
2015-11-16 15:30:40 +02:00
2015-02-06 18:07:03 +02:00
int decision = takenAction ( hero , true ) ;
2015-11-16 15:30:40 +02:00
2015-02-06 18:07:03 +02:00
switch ( decision )
{
case FIGHT :
ms . addTxt ( MetaString : : GENERAL_TXT , 246 ) ;
break ;
case FLEE :
ms . addTxt ( MetaString : : GENERAL_TXT , 245 ) ;
break ;
case JOIN_FOR_FREE :
ms . addTxt ( MetaString : : GENERAL_TXT , 243 ) ;
2015-11-16 15:30:40 +02:00
break ;
2015-02-06 18:07:03 +02:00
default : //decision = cost in gold
2015-11-16 15:30:40 +02:00
ms < < boost : : to_string ( boost : : format ( VLC - > generaltexth - > allTexts [ 244 ] ) % decision ) ;
2015-02-06 18:07:03 +02:00
break ;
2015-11-16 15:30:40 +02:00
}
2015-02-06 18:07:03 +02:00
2015-11-16 15:30:40 +02:00
ms . toString ( hoverName ) ;
2015-02-06 14:42:09 +02:00
}
else
{
2015-11-16 15:30:40 +02:00
hoverName = getHoverText ( hero - > tempOwner ) ;
}
2014-06-24 20:39:36 +03:00
2022-12-27 22:19:05 +02:00
hoverName + = VLC - > generaltexth - > translate ( " vcmi.adventureMap.monsterThreat.title " ) ;
2014-06-24 20:39:36 +03:00
int choice ;
double ratio = ( ( double ) getArmyStrength ( ) / hero - > getTotalStrength ( ) ) ;
if ( ratio < 0.1 ) choice = 0 ;
else if ( ratio < 0.25 ) choice = 1 ;
else if ( ratio < 0.6 ) choice = 2 ;
else if ( ratio < 0.9 ) choice = 3 ;
else if ( ratio < 1.1 ) choice = 4 ;
else if ( ratio < 1.3 ) choice = 5 ;
else if ( ratio < 1.8 ) choice = 6 ;
else if ( ratio < 2.5 ) choice = 7 ;
else if ( ratio < 4 ) choice = 8 ;
else if ( ratio < 8 ) choice = 9 ;
else if ( ratio < 20 ) choice = 10 ;
else choice = 11 ;
2022-12-27 22:19:05 +02:00
hoverName + = VLC - > generaltexth - > translate ( " vcmi.adventureMap.monsterThreat.levels. " + std : : to_string ( choice ) ) ;
2014-06-05 19:52:14 +03:00
return hoverName ;
}
2014-06-24 20:39:36 +03:00
2014-06-05 19:52:14 +03:00
void CGCreature : : onHeroVisit ( const CGHeroInstance * h ) const
{
int action = takenAction ( h ) ;
switch ( action ) //decide what we do...
{
case FIGHT :
fight ( h ) ;
break ;
2015-11-25 08:34:49 +02:00
case FLEE :
2014-06-05 19:52:14 +03:00
{
flee ( h ) ;
break ;
}
case JOIN_FOR_FREE : //join for free
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
ynd . text . addTxt ( MetaString : : ADVOB_TXT , 86 ) ;
ynd . text . addReplacement ( MetaString : : CRE_PL_NAMES , subID ) ;
cb - > showBlockingDialog ( & ynd ) ;
break ;
}
default : //join for gold
{
assert ( action > 0 ) ;
//ask if player agrees to pay gold
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
std : : string tmp = VLC - > generaltexth - > advobtxt [ 90 ] ;
boost : : algorithm : : replace_first ( tmp , " %d " , boost : : lexical_cast < std : : string > ( getStackCount ( SlotID ( 0 ) ) ) ) ;
boost : : algorithm : : replace_first ( tmp , " %d " , boost : : lexical_cast < std : : string > ( action ) ) ;
2023-01-02 18:00:51 +02:00
boost : : algorithm : : replace_first ( tmp , " %s " , VLC - > creh - > objects [ subID ] - > getNamePluralTranslated ( ) ) ;
2014-06-05 19:52:14 +03:00
ynd . text < < tmp ;
cb - > showBlockingDialog ( & ynd ) ;
break ;
}
}
}
2016-09-09 19:30:36 +02:00
void CGCreature : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
blockVisit = true ;
switch ( character )
{
case 0 :
character = - 4 ;
break ;
case 1 :
2016-09-09 19:30:36 +02:00
character = rand . nextInt ( 1 , 7 ) ;
2014-06-05 19:52:14 +03:00
break ;
case 2 :
2016-09-09 19:30:36 +02:00
character = rand . nextInt ( 1 , 10 ) ;
2014-06-05 19:52:14 +03:00
break ;
case 3 :
2016-09-09 19:30:36 +02:00
character = rand . nextInt ( 4 , 10 ) ;
2014-06-05 19:52:14 +03:00
break ;
case 4 :
character = 10 ;
break ;
}
stacks [ SlotID ( 0 ) ] - > setType ( CreatureID ( subID ) ) ;
TQuantity & amount = stacks [ SlotID ( 0 ) ] - > count ;
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
CCreature & c = * VLC - > creh - > objects [ subID ] ;
2014-06-05 19:52:14 +03:00
if ( amount = = 0 )
{
2016-09-09 19:30:36 +02:00
amount = rand . nextInt ( c . ammMin , c . ammMax ) ;
2014-06-05 19:52:14 +03:00
if ( amount = = 0 ) //armies with 0 creatures are illegal
{
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Stack %s cannot have 0 creatures. Check properties of %s " , nodeName ( ) , c . nodeName ( ) ) ;
2014-06-05 19:52:14 +03:00
amount = 1 ;
}
}
2016-11-26 19:54:07 +02:00
temppower = stacks [ SlotID ( 0 ) ] - > count * ( ui64 ) 1000 ;
2014-06-05 19:52:14 +03:00
refusedJoining = false ;
}
2016-09-09 19:30:36 +02:00
void CGCreature : : newTurn ( CRandomGenerator & rand ) const
2014-06-05 19:52:14 +03:00
{ //Works only for stacks of single type of size up to 2 millions
2015-02-22 21:54:09 +02:00
if ( ! notGrowingTeam )
2014-06-05 19:52:14 +03:00
{
2015-02-22 21:54:09 +02:00
if ( stacks . begin ( ) - > second - > count < VLC - > modh - > settings . CREEP_SIZE & & cb - > getDate ( Date : : DAY_OF_WEEK ) = = 1 & & cb - > getDate ( Date : : DAY ) > 1 )
{
2020-10-01 10:38:06 +02:00
ui32 power = static_cast < ui32 > ( temppower * ( 100 + VLC - > modh - > settings . WEEKLY_GROWTH ) / 100 ) ;
2015-02-22 21:54:09 +02:00
cb - > setObjProperty ( id , ObjProperty : : MONSTER_COUNT , std : : min ( power / 1000 , ( ui32 ) VLC - > modh - > settings . CREEP_SIZE ) ) ; //set new amount
cb - > setObjProperty ( id , ObjProperty : : MONSTER_POWER , power ) ; //increase temppower
}
2014-06-05 19:52:14 +03:00
}
if ( VLC - > modh - > modules . STACK_EXP )
cb - > setObjProperty ( id , ObjProperty : : MONSTER_EXP , VLC - > modh - > settings . NEUTRAL_STACK_EXP ) ; //for testing purpose
}
void CGCreature : : setPropertyDer ( ui8 what , ui32 val )
{
switch ( what )
{
case ObjProperty : : MONSTER_COUNT :
stacks [ SlotID ( 0 ) ] - > count = val ;
break ;
case ObjProperty : : MONSTER_POWER :
temppower = val ;
break ;
case ObjProperty : : MONSTER_EXP :
giveStackExp ( val ) ;
break ;
case ObjProperty : : MONSTER_RESTORE_TYPE :
formation . basicType = val ;
break ;
case ObjProperty : : MONSTER_REFUSED_JOIN :
refusedJoining = val ;
break ;
}
}
int CGCreature : : takenAction ( const CGHeroInstance * h , bool allowJoin ) const
{
//calculate relative strength of hero and creatures armies
double relStrength = double ( h - > getTotalStrength ( ) ) / getArmyStrength ( ) ;
int powerFactor ;
if ( relStrength > = 7 )
powerFactor = 11 ;
else if ( relStrength > = 1 )
powerFactor = ( int ) ( 2 * ( relStrength - 1 ) ) ;
else if ( relStrength > = 0.5 )
powerFactor = - 1 ;
else if ( relStrength > = 0.333 )
powerFactor = - 2 ;
else
powerFactor = - 3 ;
std : : set < CreatureID > myKindCres ; //what creatures are the same kind as we
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
const CCreature * myCreature = VLC - > creh - > objects [ subID ] ;
2014-06-05 19:52:14 +03:00
myKindCres . insert ( myCreature - > idNumber ) ; //we
myKindCres . insert ( myCreature - > upgrades . begin ( ) , myCreature - > upgrades . end ( ) ) ; //our upgrades
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
for ( ConstTransitivePtr < CCreature > & crea : VLC - > creh - > objects )
2014-06-05 19:52:14 +03:00
{
if ( vstd : : contains ( crea - > upgrades , myCreature - > idNumber ) ) //it's our base creatures
myKindCres . insert ( crea - > idNumber ) ;
}
int count = 0 , //how many creatures of similar kind has hero
totalCount = 0 ;
for ( auto & elem : h - > Slots ( ) )
{
if ( vstd : : contains ( myKindCres , elem . second - > type - > idNumber ) )
count + = elem . second - > count ;
totalCount + = elem . second - > count ;
}
int sympathy = 0 ; // 0 if hero have no similar creatures
if ( count )
sympathy + + ; // 1 - if hero have at least 1 similar creature
if ( count * 2 > totalCount )
sympathy + + ; // 2 - hero have similar creatures more that 50%
2017-08-28 13:33:19 +02:00
int diplomacy = h - > valOfBonuses ( Bonus : : SECONDARY_SKILL_PREMY , SecondarySkill : : DIPLOMACY ) ;
int charisma = powerFactor + diplomacy + sympathy ;
2014-06-05 19:52:14 +03:00
2015-11-25 08:34:49 +02:00
if ( charisma < character )
return FIGHT ;
2014-06-05 19:52:14 +03:00
if ( allowJoin )
{
2017-08-28 13:33:19 +02:00
if ( diplomacy + sympathy + 1 > = character )
2015-11-25 08:34:49 +02:00
return JOIN_FOR_FREE ;
2014-06-05 19:52:14 +03:00
2017-08-28 13:33:19 +02:00
else if ( diplomacy * 2 + sympathy + 1 > = character )
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 VLC - > creh - > objects [ subID ] - > cost [ 6 ] * getStackCount ( SlotID ( 0 ) ) ; //join for gold
2014-06-05 19:52:14 +03:00
}
//we are still here - creatures have not joined hero, flee or fight
2015-11-25 08:34:49 +02:00
if ( charisma > character & & ! neverFlees )
return FLEE ;
2014-06-05 19:52:14 +03:00
else
2015-11-25 08:34:49 +02:00
return FIGHT ;
2014-06-05 19:52:14 +03:00
}
void CGCreature : : fleeDecision ( const CGHeroInstance * h , ui32 pursue ) const
{
if ( refusedJoining )
cb - > setObjProperty ( id , ObjProperty : : MONSTER_REFUSED_JOIN , false ) ;
if ( pursue )
{
fight ( h ) ;
}
else
{
cb - > removeObject ( this ) ;
}
}
void CGCreature : : joinDecision ( const CGHeroInstance * h , int cost , ui32 accept ) const
{
if ( ! accept )
{
2015-11-25 08:34:49 +02:00
if ( takenAction ( h , false ) = = FLEE )
2014-06-05 19:52:14 +03:00
{
cb - > setObjProperty ( id , ObjProperty : : MONSTER_REFUSED_JOIN , true ) ;
flee ( h ) ;
}
else //they fight
{
showInfoDialog ( h , 87 , 0 ) ; //Insulted by your refusal of their offer, the monsters attack!
fight ( h ) ;
}
}
else //accepted
{
if ( cb - > getResource ( h - > tempOwner , Res : : GOLD ) < cost ) //player don't have enough gold!
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
iw . text < < std : : pair < ui8 , ui32 > ( 1 , 29 ) ; //You don't have enough gold
cb - > showInfoDialog ( & iw ) ;
//act as if player refused
joinDecision ( h , cost , false ) ;
return ;
}
//take gold
if ( cost )
cb - > giveResource ( h - > tempOwner , Res : : GOLD , - cost ) ;
2016-01-15 03:29:46 +02:00
giveReward ( h ) ;
2014-06-05 19:52:14 +03:00
cb - > tryJoiningArmy ( this , h , true , true ) ;
}
}
void CGCreature : : fight ( const CGHeroInstance * h ) const
{
//split stacks
//TODO: multiple creature types in a stack?
int basicType = stacks . begin ( ) - > second - > type - > idNumber ;
cb - > setObjProperty ( id , ObjProperty : : MONSTER_RESTORE_TYPE , basicType ) ; //store info about creature stack
2014-12-25 00:53:56 +02:00
int stacksCount = getNumberOfStacks ( h ) ;
2014-12-25 11:21:39 +02:00
//source: http://heroescommunity.com/viewthread.php3?TID=27539&PID=1266335#focus
int amount = getStackCount ( SlotID ( 0 ) ) ;
int m = amount / stacksCount ;
int b = stacksCount * ( m + 1 ) - amount ;
int a = stacksCount - b ;
2014-12-25 00:53:56 +02:00
2014-06-05 19:52:14 +03:00
SlotID sourceSlot = stacks . begin ( ) - > first ;
2014-12-25 11:21:39 +02:00
for ( int slotID = 1 ; slotID < a ; + + slotID )
2014-06-05 19:52:14 +03:00
{
2014-12-25 11:21:39 +02:00
int stackSize = m + 1 ;
cb - > moveStack ( StackLocation ( this , sourceSlot ) , StackLocation ( this , SlotID ( slotID ) ) , stackSize ) ;
}
for ( int slotID = a ; slotID < stacksCount ; + + slotID )
{
int stackSize = m ;
if ( slotID ) //don't do this when a = 0 -> stack is single
cb - > moveStack ( StackLocation ( this , sourceSlot ) , StackLocation ( this , SlotID ( slotID ) ) , stackSize ) ;
2014-06-05 19:52:14 +03:00
}
if ( stacksCount > 1 )
{
2014-12-25 00:53:56 +02:00
if ( containsUpgradedStack ( ) ) //upgrade
2014-06-05 19:52:14 +03:00
{
2020-10-01 10:38:06 +02:00
SlotID slotID = SlotID ( ( si32 ) ( std : : floor ( ( float ) stacks . size ( ) / 2.0f ) ) ) ;
2014-12-25 11:21:39 +02:00
const auto & upgrades = getStack ( slotID ) . type - > upgrades ;
2014-06-05 19:52:14 +03:00
if ( ! upgrades . empty ( ) )
{
2016-09-09 19:30:36 +02:00
auto it = RandomGeneratorUtil : : nextItem ( upgrades , CRandomGenerator : : getDefault ( ) ) ;
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
cb - > changeStackType ( StackLocation ( this , slotID ) , VLC - > creh - > objects [ * it ] ) ;
2014-06-05 19:52:14 +03:00
}
}
}
cb - > startBattleI ( h , this ) ;
}
void CGCreature : : flee ( const CGHeroInstance * h ) const
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
ynd . text . addTxt ( MetaString : : ADVOB_TXT , 91 ) ;
ynd . text . addReplacement ( MetaString : : CRE_PL_NAMES , subID ) ;
cb - > showBlockingDialog ( & ynd ) ;
}
void CGCreature : : battleFinished ( const CGHeroInstance * hero , const BattleResult & result ) const
{
2016-01-26 07:41:09 +02:00
if ( result . winner = = 0 )
2014-06-05 19:52:14 +03:00
{
2016-01-15 03:29:46 +02:00
giveReward ( hero ) ;
2014-06-05 19:52:14 +03:00
cb - > removeObject ( this ) ;
}
2016-01-26 07:41:09 +02:00
else if ( result . winner > 1 ) // draw
{
// guarded reward is lost forever on draw
cb - > removeObject ( this ) ;
}
2014-06-05 19:52:14 +03:00
else
{
//merge stacks into one
TSlots : : const_iterator i ;
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
CCreature * cre = VLC - > creh - > objects [ formation . basicType ] ;
2016-01-26 07:41:09 +02:00
for ( i = stacks . begin ( ) ; i ! = stacks . end ( ) ; i + + )
2014-06-05 19:52:14 +03:00
{
2016-01-26 07:41:09 +02:00
if ( cre - > isMyUpgrade ( i - > second - > type ) )
2014-06-05 19:52:14 +03:00
{
2016-01-26 07:41:09 +02:00
cb - > changeStackType ( StackLocation ( this , i - > first ) , cre ) ; //un-upgrade creatures
2014-06-05 19:52:14 +03:00
}
}
//first stack has to be at slot 0 -> if original one got killed, move there first remaining stack
if ( ! hasStackAtSlot ( SlotID ( 0 ) ) )
cb - > moveStack ( StackLocation ( this , stacks . begin ( ) - > first ) , StackLocation ( this , SlotID ( 0 ) ) , stacks . begin ( ) - > second - > count ) ;
2016-01-26 07:41:09 +02:00
while ( stacks . size ( ) > 1 ) //hopefully that's enough
2014-06-05 19:52:14 +03:00
{
// TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere)
i = stacks . end ( ) ;
i - - ;
SlotID slot = getSlotFor ( i - > second - > type ) ;
2016-01-26 07:41:09 +02:00
if ( slot = = i - > first ) //no reason to move stack to its own slot
2014-06-05 19:52:14 +03:00
break ;
else
2016-01-26 07:41:09 +02:00
cb - > moveStack ( StackLocation ( this , i - > first ) , StackLocation ( this , slot ) , i - > second - > count ) ;
2014-06-05 19:52:14 +03:00
}
cb - > setObjProperty ( id , ObjProperty : : MONSTER_POWER , stacks . begin ( ) - > second - > count * 1000 ) ; //remember casualties
}
}
void CGCreature : : blockingDialogAnswered ( const CGHeroInstance * hero , ui32 answer ) const
{
auto action = takenAction ( hero ) ;
if ( ! refusedJoining & & action > = JOIN_FOR_FREE ) //higher means price
joinDecision ( hero , action , answer ) ;
else if ( action ! = FIGHT )
fleeDecision ( hero , answer ) ;
else
assert ( 0 ) ;
}
2014-12-25 00:53:56 +02:00
bool CGCreature : : containsUpgradedStack ( ) const
{
//source http://heroescommunity.com/viewthread.php3?TID=27539&PID=830557#focus
2020-10-01 10:38:06 +02:00
float a = 2992.911117f ;
float b = 14174.264968f ;
float c = 5325.181015f ;
float d = 32788.727920f ;
2014-12-25 00:53:56 +02:00
2020-10-01 10:38:06 +02:00
int val = ( int ) std : : floor ( a * pos . x + b * pos . y + c * pos . z + d ) ;
2014-12-25 00:53:56 +02:00
return ( ( val % 32768 ) % 100 ) < 50 ;
}
int CGCreature : : getNumberOfStacks ( const CGHeroInstance * hero ) const
{
//source http://heroescommunity.com/viewthread.php3?TID=27539&PID=1266094#focus
2014-12-25 11:21:39 +02:00
double strengthRatio = ( double ) hero - > getArmyStrength ( ) / getArmyStrength ( ) ;
2014-12-25 00:53:56 +02:00
int split = 1 ;
if ( strengthRatio < 0.5f )
split = 7 ;
else if ( strengthRatio < 0.67f )
split = 6 ;
else if ( strengthRatio < 1 )
split = 5 ;
else if ( strengthRatio < 1.5f )
split = 4 ;
else if ( strengthRatio < 2 )
split = 3 ;
else
split = 2 ;
2018-04-28 19:51:30 +02:00
ui32 a = 1550811371u ;
ui32 b = 3359066809u ;
ui32 c = 1943276003u ;
ui32 d = 3174620878u ;
ui32 R1 = a * ui32 ( pos . x ) + b * ui32 ( pos . y ) + c * ui32 ( pos . z ) + d ;
ui32 R2 = ( R1 > > 16 ) & 0x7fff ;
int R4 = R2 % 100 + 1 ;
2014-12-25 00:53:56 +02:00
if ( R4 < = 20 )
split - = 1 ;
else if ( R4 > = 80 )
split + = 1 ;
vstd : : amin ( split , getStack ( SlotID ( 0 ) ) . count ) ; //can't divide into more stacks than creatures total
vstd : : amin ( split , 7 ) ; //can't have more than 7 stacks
return split ;
}
2016-01-15 03:29:46 +02:00
void CGCreature : : giveReward ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
if ( resources . size ( ) )
{
cb - > giveResources ( h - > tempOwner , resources ) ;
for ( int i = 0 ; i < resources . size ( ) ; i + + )
{
if ( resources [ i ] > 0 )
iw . components . push_back ( Component ( Component : : RESOURCE , i , resources [ i ] , 0 ) ) ;
}
}
if ( gainedArtifact ! = ArtifactID : : NONE )
{
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
cb - > giveHeroNewArtifact ( h , VLC - > arth - > objects [ gainedArtifact ] , ArtifactPosition : : FIRST_AVAILABLE ) ;
2016-01-15 03:29:46 +02:00
iw . components . push_back ( Component ( Component : : ARTIFACT , gainedArtifact , 0 , 0 ) ) ;
}
if ( iw . components . size ( ) )
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 183 ) ; // % has found treasure
2023-01-02 13:27:03 +02:00
iw . text . addReplacement ( h - > getNameTranslated ( ) ) ;
2016-01-15 03:29:46 +02:00
cb - > showInfoDialog ( & iw ) ;
}
}
2016-02-22 01:37:19 +02:00
static const std : : vector < std : : string > CHARACTER_JSON =
2016-02-03 03:18:03 +02:00
{
" compliant " , " friendly " , " aggressive " , " hostile " , " savage "
} ;
2016-02-22 01:37:19 +02:00
void CGCreature : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-11-13 12:38:42 +02:00
handler . serializeEnum ( " character " , character , CHARACTER_JSON ) ;
2015-11-16 15:30:40 +02:00
2016-02-22 01:37:19 +02:00
if ( handler . saving )
2016-02-03 03:18:03 +02:00
{
2016-02-22 01:37:19 +02:00
if ( hasStackAtSlot ( SlotID ( 0 ) ) )
{
si32 amount = getStack ( SlotID ( 0 ) ) . count ;
2016-11-13 12:38:42 +02:00
handler . serializeInt ( " amount " , amount , 0 ) ;
2016-02-22 01:37:19 +02:00
}
2016-02-04 07:44:46 +02:00
}
2016-02-22 01:37:19 +02:00
else
2016-02-03 03:18:03 +02:00
{
2016-02-22 01:37:19 +02:00
si32 amount = 0 ;
2016-11-13 12:38:42 +02:00
handler . serializeInt ( " amount " , amount ) ;
2016-02-22 01:37:19 +02:00
auto hlp = new CStackInstance ( ) ;
hlp - > count = amount ;
//type will be set during initialization
putStack ( SlotID ( 0 ) , hlp ) ;
}
2016-11-13 12:38:42 +02:00
resources . serializeJson ( handler , " rewardResources " ) ;
2017-07-20 06:08:49 +02:00
handler . serializeId ( " rewardArtifact " , gainedArtifact , ArtifactID ( ArtifactID : : NONE ) ) ;
2016-11-13 12:38:42 +02:00
2016-02-22 01:37:19 +02:00
handler . serializeBool ( " noGrowing " , notGrowingTeam ) ;
handler . serializeBool ( " neverFlees " , neverFlees ) ;
handler . serializeString ( " rewardMessage " , message ) ;
2015-11-16 15:30:40 +02:00
}
//CGMine
2014-06-05 19:52:14 +03:00
void CGMine : : onHeroVisit ( const CGHeroInstance * h ) const
{
int relations = cb - > gameState ( ) - > getPlayerRelations ( h - > tempOwner , tempOwner ) ;
if ( relations = = 2 ) //we're visiting our mine
{
cb - > showGarrisonDialog ( id , h - > id , true ) ;
return ;
}
else if ( relations = = 1 ) //ally
return ;
if ( stacksCount ( ) ) //Mine is guarded
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
ynd . text . addTxt ( MetaString : : ADVOB_TXT , subID = = 7 ? 84 : 187 ) ;
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 ) ;
2014-06-05 19:52:14 +03:00
auto troglodytes = new CStackInstance ( CreatureID : : TROGLODYTES , howManyTroglodytes ) ;
putStack ( SlotID ( 0 ) , troglodytes ) ;
//after map reading tempOwner placeholds bitmask for allowed resources
std : : vector < Res : : ERes > possibleResources ;
for ( int i = 0 ; i < PlayerColor : : PLAYER_LIMIT_I ; i + + )
if ( tempOwner . getNum ( ) & 1 < < i ) //NOTE: reuse of tempOwner
possibleResources . push_back ( static_cast < Res : : ERes > ( i ) ) ;
assert ( ! possibleResources . empty ( ) ) ;
2016-09-09 19:30:36 +02:00
producedResource = * RandomGeneratorUtil : : nextItem ( possibleResources , rand ) ;
2014-06-05 19:52:14 +03:00
tempOwner = PlayerColor : : NEUTRAL ;
}
else
{
producedResource = static_cast < Res : : ERes > ( subID ) ;
if ( tempOwner > = PlayerColor : : PLAYER_LIMIT )
tempOwner = PlayerColor : : NEUTRAL ;
}
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 ;
}
void CGMine : : flagMine ( PlayerColor player ) const
{
assert ( tempOwner ! = player ) ;
cb - > setOwner ( this , player ) ; //not ours? flag it!
2014-06-05 19:52:14 +03:00
InfoWindow iw ;
iw . soundID = soundBase : : FLAGMINE ;
iw . text . addTxt ( MetaString : : MINE_EVNTS , producedResource ) ; //not use subID, abandoned mines uses default mine texts
iw . player = player ;
iw . components . push_back ( Component ( Component : : RESOURCE , producedResource , producedQuantity , - 1 ) ) ;
cb - > showInfoDialog ( & iw ) ;
}
ui32 CGMine : : defaultResProduction ( )
{
switch ( producedResource )
{
case Res : : WOOD :
case Res : : ORE :
return 2 ;
case Res : : GOLD :
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
{
showInfoDialog ( hero - > tempOwner , 85 , 0 ) ;
}
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
{
2016-11-13 12:38:42 +02:00
CCreatureSet : : serializeJson ( handler , " army " , 7 ) ;
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 ) ;
2016-02-22 01:37:19 +02:00
for ( int i = 0 ; i < PlayerColor : : PLAYER_LIMIT_I ; i + + )
2017-07-20 06:08:49 +02:00
{
2016-02-22 01:37:19 +02:00
if ( tempOwner . getNum ( ) & 1 < < i )
{
2017-11-26 23:18:18 +02:00
JsonNode one ( JsonNode : : JsonType : : DATA_STRING ) ;
2016-02-22 01:37:19 +02:00
one . String ( ) = GameConstants : : RESOURCE_NAMES [ i ] ;
node . Vector ( ) . push_back ( one ) ;
}
2017-07-20 06:08:49 +02:00
}
handler . serializeRaw ( " possibleResources " , node , boost : : none ) ;
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-02-22 01:37:19 +02:00
std : : set < int > possibleResources ;
2016-01-23 18:53:02 +02:00
2017-07-20 06:08:49 +02:00
if ( node . getType ( ) ! = JsonNode : : JsonType : : DATA_VECTOR | | node . Vector ( ) . size ( ) = = 0 )
2016-01-23 18:53:02 +02:00
{
2016-02-22 01:37:19 +02:00
//assume all allowed
for ( int i = ( int ) Res : : WOOD ; i < ( int ) Res : : GOLD ; i + + )
possibleResources . insert ( i ) ;
2016-01-23 18:53:02 +02:00
}
2016-02-22 01:37:19 +02:00
else
{
auto names = node . convertTo < std : : vector < std : : string > > ( ) ;
2016-01-23 18:53:02 +02:00
2016-02-22 01:37:19 +02:00
for ( const std : : string & s : names )
{
int raw_res = vstd : : find_pos ( GameConstants : : RESOURCE_NAMES , s ) ;
if ( raw_res < 0 )
2017-08-11 19:03:05 +02:00
logGlobal - > error ( " Invalid resource name: %s " , s ) ;
2016-02-22 01:37:19 +02:00
else
possibleResources . insert ( raw_res ) ;
}
2016-01-23 18:53:02 +02:00
2016-02-22 01:37:19 +02:00
int tmp = 0 ;
for ( int r : possibleResources )
tmp | = ( 1 < < r ) ;
tempOwner = PlayerColor ( tmp ) ;
}
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 ] ;
}
2014-07-27 20:30:17 +03:00
CGResource : : CGResource ( )
{
2022-05-23 12:08:36 +02:00
amount = CGResource : : RANDOM_AMOUNT ;
2014-07-27 20:30:17 +03:00
}
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
{
switch ( subID )
{
2016-09-17 23:53:37 +02:00
case Res : : GOLD :
2016-09-17 23:22:13 +02:00
amount = rand . nextInt ( 5 , 10 ) * 100 ;
2014-06-05 19:52:14 +03:00
break ;
2016-09-17 23:53:37 +02:00
case Res : : WOOD : case Res : : 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 ( ) )
{
if ( message . size ( ) )
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > getOwner ( ) ;
ynd . text < < message ;
cb - > showBlockingDialog ( & ynd ) ;
}
else
{
blockingDialogAnswered ( h , true ) ; //behave as if player accepted battle
}
}
else
{
if ( message . length ( ) )
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
iw . text < < message ;
cb - > showInfoDialog ( & iw ) ;
}
collectRes ( h - > getOwner ( ) ) ;
}
}
void CGResource : : collectRes ( PlayerColor player ) const
{
cb - > giveResource ( player , static_cast < Res : : ERes > ( subID ) , amount ) ;
ShowInInfobox sii ;
sii . player = player ;
sii . c = Component ( Component : : RESOURCE , subID , amount , 0 ) ;
sii . text . addTxt ( MetaString : : ADVOB_TXT , 113 ) ;
sii . text . addReplacement ( MetaString : : RES_NAMES , subID ) ;
cb - > showCompInfo ( & sii ) ;
cb - > removeObject ( this ) ;
}
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
{
2016-11-13 12:38:42 +02:00
CCreatureSet : : serializeJson ( handler , " guards " , 7 ) ;
handler . serializeInt ( " amount " , amount , 0 ) ;
2016-02-22 01:37:19 +02:00
handler . serializeString ( " guardMessage " , message ) ;
2015-11-16 15:30:40 +02:00
}
2015-03-08 15:11:23 +02:00
CGTeleport : : CGTeleport ( ) :
type ( UNKNOWN ) , channel ( TeleportChannelID ( ) )
2014-06-05 19:52:14 +03: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 ;
}
bool CGTeleport : : isChannelEntrance ( ObjectInstanceID id ) const
{
2015-03-08 19:19:00 +02:00
return vstd : : contains ( getAllEntrances ( ) , id ) ;
2015-03-08 15:11:23 +02:00
}
bool CGTeleport : : isChannelExit ( ObjectInstanceID id ) const
{
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 ) ) ;
if ( passableExits . size ( ) )
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 )
{
2015-03-08 19:19:00 +02:00
return ( ( dynamic_cast < const CGTeleport * > ( obj ) ) ) ;
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 )
{
auto srcObj = dynamic_cast < const CGTeleport * > ( src ) ;
auto dstObj = dynamic_cast < const CGTeleport * > ( dst ) ;
return isConnected ( srcObj , dstObj ) ;
}
bool CGTeleport : : isExitPassable ( CGameState * gs , const CGHeroInstance * h , const CGObjectInstance * obj )
{
auto objTopVisObj = gs - > map - > getTile ( obj - > visitablePos ( ) ) . topVisitableObj ( ) ;
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 )
{
vstd : : erase_if ( exits , [ & ] ( ObjectInstanceID exit ) - > bool
{
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 ) ;
if ( tc - > entrances . size ( ) & & tc - > exits . size ( )
& & ( 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
}
TeleportChannelID CGMonolith : : findMeChannel ( std : : vector < Obj > IDs , int SubID ) const
{
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 ;
2015-03-08 15:11:23 +02:00
auto teleportObj = dynamic_cast < const CGTeleport * > ( cb - > getObj ( obj - > id ) ) ;
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
{
2018-03-10 21:19:55 +02:00
TeleportDialog td ( h - > tempOwner , 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 ) ;
for ( auto exit : exits )
{
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
showInfoDialog ( h , 70 , 0 ) ;
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
| | ( ! exits . size ( ) & & ! realExits . size ( ) ) // 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 ;
IDs . push_back ( Obj : : MONOLITH_ONE_WAY_EXIT ) ;
break ;
case Obj : : MONOLITH_ONE_WAY_EXIT :
type = EXIT ;
IDs . push_back ( Obj : : MONOLITH_ONE_WAY_ENTRANCE ) ;
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 ( ) )
2020-10-01 10:38:06 +02:00
channel = TeleportChannelID ( ( 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
{
2018-03-10 21:19:55 +02:00
TeleportDialog td ( h - > tempOwner , channel ) ;
2015-03-09 01:13:40 +02:00
if ( cb - > isTeleportChannelImpassable ( channel ) )
2015-03-08 15:11:23 +02:00
{
showInfoDialog ( h , 153 , 0 ) ; //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 ;
2015-03-11 16:17:21 +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
2020-10-01 10:38:06 +02:00
obj - > channel = TeleportChannelID ( ( 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
for ( size_t i = 0 ; i < gatesSplit [ 1 ] . size ( ) ; i + + )
assignToChannel ( gatesSplit [ 1 ] [ 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
{
2018-03-10 21:19:55 +02:00
TeleportDialog td ( h - > tempOwner , 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 ) ;
}
2020-10-01 10:38:06 +02:00
TQuantity countToTake = static_cast < TQuantity > ( h - > getStackCount ( targetstack ) * 0.5 ) ;
2015-03-08 15:11:23 +02:00
vstd : : amax ( countToTake , 1 ) ;
InfoWindow iw ;
iw . player = h - > tempOwner ;
iw . text . addTxt ( MetaString : : ADVOB_TXT , 168 ) ;
iw . components . push_back ( Component ( CStackBasicDescriptor ( h - > getCreature ( targetstack ) , countToTake ) ) ) ;
cb - > showInfoDialog ( & iw ) ;
cb - > changeStackCount ( StackLocation ( h , targetstack ) , - countToTake ) ;
}
else
2015-12-02 16:56:26 +02:00
{
auto exits = getAllExits ( ) ;
for ( auto exit : exits )
{
auto blockedPosList = cb - > getObj ( exit ) - > getBlockedPos ( ) ;
for ( 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 ( ) ;
if ( ! exits . size ( ) & & ! realExits . size ( ) )
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 ;
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
{
2019-03-20 21:58:15 +02:00
return h - > hasBonusOfType ( Bonus : : WHIRLPOOL_PROTECTION )
| | ( 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 )
{
auto a = new CArtifactInstance ( ) ;
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 ;
iw . player = h - > tempOwner ;
switch ( ID )
{
case Obj : : ARTIFACT :
{
2015-12-24 20:05:02 +02:00
iw . components . push_back ( Component ( Component : : ARTIFACT , subID , 0 , 0 ) ) ;
2014-06-05 19:52:14 +03:00
if ( message . length ( ) )
2015-12-24 20:05:02 +02:00
iw . text < < message ;
2014-06-05 19:52:14 +03:00
else
2023-01-02 15:58:56 +02:00
iw . text . addTxt ( MetaString : : ART_EVNTS , subID ) ;
2014-06-05 19:52:14 +03:00
}
break ;
case Obj : : SPELL_SCROLL :
{
int spellID = storedArtifact - > getGivenSpellID ( ) ;
2015-12-24 20:05:02 +02:00
iw . components . push_back ( Component ( Component : : SPELL , spellID , 0 , 0 ) ) ;
if ( message . length ( ) )
iw . text < < message ;
else
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 135 ) ;
iw . text . addReplacement ( MetaString : : SPELL_NAME , spellID ) ;
}
2014-06-05 19:52:14 +03:00
}
break ;
}
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 ( ) ;
if ( message . length ( ) )
ynd . text < < message ;
else
{
// TODO: Guard text is more complex in H3, see mantis issue 2325 for details
ynd . text . addTxt ( MetaString : : GENERAL_TXT , 420 ) ;
ynd . text . addReplacement ( " " ) ;
ynd . text . addReplacement ( getArmyDescription ( ) ) ;
ynd . text . addReplacement ( MetaString : : GENERAL_TXT , 43 ) ; // creatures
}
cb - > showBlockingDialog ( & ynd ) ;
}
break ;
case Obj : : SPELL_SCROLL :
{
if ( message . length ( ) )
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > getOwner ( ) ;
ynd . text < < message ;
cb - > showBlockingDialog ( & ynd ) ;
}
else
blockingDialogAnswered ( h , true ) ;
}
break ;
2014-06-05 19:52:14 +03:00
}
}
}
void CGArtifact : : pick ( const CGHeroInstance * h ) const
{
cb - > giveHeroArtifact ( h , storedArtifact , ArtifactPosition : : FIRST_AVAILABLE ) ;
cb - > removeObject ( this ) ;
}
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
2017-05-28 15:23:42 +02:00
if ( ID = = Obj : : SPELL_SCROLL & & storedArtifact & & storedArtifact - > id . getNum ( ) < 0 )
map - > addNewArtifactInstance ( storedArtifact ) ;
}
2016-02-22 01:37:19 +02:00
void CGArtifact : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 01:37:19 +02:00
handler . serializeString ( " guardMessage " , message ) ;
2016-11-13 12:38:42 +02:00
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
{
2020-11-11 21:43:40 +02:00
const std : : shared_ptr < Bonus > b = storedArtifact - > getBonusLocalFirst ( Selector : : type ( ) ( Bonus : : 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
}
2016-09-09 19:30:36 +02:00
void CGWitchHut : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
2014-07-05 20:35:46 +03:00
if ( allowedAbilities . empty ( ) ) //this can happen for RMG. regular maps load abilities from map file
{
2022-10-01 15:01:47 +02:00
// Necromancy can't be learned on random maps
2018-03-31 07:56:40 +02:00
for ( int i = 0 ; i < VLC - > skillh - > size ( ) ; i + + )
2022-10-01 15:01:47 +02:00
if ( VLC - > skillh - > getByIndex ( i ) - > getId ( ) ! = SecondarySkill : : NECROMANCY )
allowedAbilities . push_back ( i ) ;
2014-07-05 20:35:46 +03:00
}
2016-09-09 19:30:36 +02:00
ability = * RandomGeneratorUtil : : nextItem ( allowedAbilities , rand ) ;
2014-06-05 19:52:14 +03:00
}
void CGWitchHut : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > getOwner ( ) ;
if ( ! wasVisited ( h - > tempOwner ) )
2016-01-20 09:44:13 +02:00
cb - > setObjProperty ( id , CGWitchHut : : OBJPROP_VISITED , h - > tempOwner . getNum ( ) ) ;
2014-06-05 19:52:14 +03:00
ui32 txt_id ;
if ( h - > getSecSkillLevel ( SecondarySkill ( ability ) ) ) //you already know this skill
{
txt_id = 172 ;
}
else if ( ! h - > canLearnSkill ( ) ) //already all skills slots used
{
txt_id = 173 ;
}
else //give sec skill
{
iw . components . push_back ( Component ( Component : : SEC_SKILL , ability , 1 , 0 ) ) ;
txt_id = 171 ;
cb - > changeSecSkill ( h , SecondarySkill ( ability ) , 1 , true ) ;
}
iw . text . addTxt ( MetaString : : ADVOB_TXT , txt_id ) ;
iw . text . addReplacement ( MetaString : : SEC_SKILL_NAME , ability ) ;
cb - > showInfoDialog ( & iw ) ;
}
2014-06-24 20:39:36 +03:00
std : : string CGWitchHut : : 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 [ 356 ] ; // + (learn %s)
2023-01-01 22:20:41 +02:00
boost : : algorithm : : replace_first ( hoverName , " %s " , VLC - > skillh - > getByIndex ( ability ) - > getNameTranslated ( ) ) ;
2014-06-05 19:52:14 +03:00
}
return hoverName ;
}
2014-06-24 20:39:36 +03:00
std : : string CGWitchHut : : getHoverText ( const CGHeroInstance * hero ) const
{
std : : string hoverName = getHoverText ( hero - > tempOwner ) ;
2017-06-01 22:36:46 +02:00
if ( wasVisited ( hero - > tempOwner ) & & hero - > getSecSkillLevel ( SecondarySkill ( ability ) ) ) //hero knows that ability
2014-06-24 20:39:36 +03:00
hoverName + = " \n \n " + VLC - > generaltexth - > allTexts [ 357 ] ; // (Already learned)
return hoverName ;
}
2016-02-22 01:37:19 +02:00
void CGWitchHut : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 02:16:33 +02:00
//TODO: unify allowed abilities with others - make them std::vector<bool>
std : : vector < bool > temp ;
2018-03-31 07:56:40 +02:00
size_t skillCount = VLC - > skillh - > size ( ) ;
temp . resize ( skillCount , false ) ;
2016-02-22 02:16:33 +02:00
2018-03-31 07:56:40 +02:00
auto standard = VLC - > skillh - > getDefaultAllowed ( ) ; //todo: for WitchHut default is all except Leadership and Necromancy
2016-02-22 02:16:33 +02:00
2016-03-12 03:41:27 +02:00
if ( handler . saving )
2016-02-22 02:16:33 +02:00
{
2018-03-31 07:56:40 +02:00
for ( si32 i = 0 ; i < skillCount ; + + i )
2016-02-22 02:16:33 +02:00
if ( vstd : : contains ( allowedAbilities , i ) )
temp [ i ] = true ;
}
2018-03-31 07:56:40 +02:00
handler . serializeLIC ( " allowedSkills " , & CSkillHandler : : decodeSkill , & CSkillHandler : : encodeSkill , standard , temp ) ;
2016-02-22 02:16:33 +02:00
if ( ! handler . saving )
{
allowedAbilities . clear ( ) ;
2018-03-31 07:56:40 +02:00
for ( si32 i = 0 ; i < skillCount ; + + i )
2016-02-22 02:16:33 +02:00
if ( temp [ i ] )
allowedAbilities . push_back ( i ) ;
}
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 ;
iw . player = h - > tempOwner ;
switch ( ID )
{
case Obj : : REDWOOD_OBSERVATORY :
case Obj : : PILLAR_OF_FIRE :
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 98 + ( ID = = Obj : : PILLAR_OF_FIRE ) ) ;
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 :
{
iw . text . addTxt ( MetaString : : 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 ;
iw . player = h - > getOwner ( ) ;
iw . text . addTxt ( MetaString : : ADVOB_TXT , 127 + ID - 88 ) ;
iw . text . addTxt ( MetaString : : SPELL_NAME , spell ) ;
iw . text < < " . " ;
if ( ! h - > getArt ( ArtifactPosition : : SPELLBOOK ) )
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 131 ) ;
}
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
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 174 ) ;
}
2017-08-26 10:49:29 +02:00
else if ( ID = = Obj : : SHRINE_OF_MAGIC_THOUGHT & & h - > maxSpellLevel ( ) < 3 ) //it's third level spell and hero doesn't have wisdom
2016-09-13 22:28:21 +02:00
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 130 ) ;
}
2014-06-05 19:52:14 +03:00
else //give spell
{
std : : set < SpellID > spells ;
spells . insert ( spell ) ;
cb - > changeSpells ( h , true , spells ) ;
iw . components . push_back ( Component ( Component : : SPELL , spell , 0 , 0 ) ) ;
}
cb - > showInfoDialog ( & iw ) ;
}
2016-09-09 19:30:36 +02:00
void CGShrine : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
if ( spell = = SpellID : : NONE ) //spell not set
{
int level = ID - 87 ;
std : : vector < SpellID > possibilities ;
cb - > getAllowedSpells ( possibilities , level ) ;
if ( possibilities . empty ( ) )
{
2017-08-10 18:39:27 +02:00
logGlobal - > error ( " Error: cannot init shrine, no allowed spells! " ) ;
2014-06-05 19:52:14 +03:00
return ;
}
2016-09-09 19:30:36 +02:00
spell = * RandomGeneratorUtil : : nextItem ( possibilities , 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-01-02 00:06:42 +02:00
boost : : algorithm : : replace_first ( hoverName , " %s " , spell . toSpell ( ) - > getNameTextID ( ) ) ;
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 ) ;
message = VLC - > generaltexth - > translate ( 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 ( ) ;
iw . text < < message ;
cb - > showInfoDialog ( & iw ) ;
if ( ID = = Obj : : OCEAN_BOTTLE )
cb - > removeObject ( this ) ;
}
2016-02-22 01:37:19 +02:00
void CGSignBottle : : serializeJsonOptions ( JsonSerializeFormat & handler )
2015-11-16 15:30:40 +02:00
{
2016-02-22 01:37:19 +02:00
handler . serializeString ( " 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 ;
iw . player = h - > getOwner ( ) ;
iw . text . addTxt ( MetaString : : ADVOB_TXT , 115 ) ;
switch ( type )
{
case PRIM_SKILL :
cb - > changePrimSkill ( h , static_cast < PrimarySkill : : PrimarySkill > ( bid ) , + 1 ) ;
iw . components . push_back ( Component ( Component : : PRIM_SKILL , bid , + 1 , 0 ) ) ;
break ;
case SECONDARY_SKILL :
cb - > changeSecSkill ( h , SecondarySkill ( bid ) , + 1 ) ;
iw . components . push_back ( Component ( Component : : SEC_SKILL , bid , ssl + 1 , 0 ) ) ;
break ;
case SPELL :
{
std : : set < SpellID > hlp ;
hlp . insert ( SpellID ( bid ) ) ;
cb - > changeSpells ( h , true , hlp ) ;
iw . components . push_back ( Component ( Component : : SPELL , bid , 0 , 0 ) ) ;
}
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 ) ;
cb - > removeObject ( this ) ;
}
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 :
2020-10-01 10:38:06 +02:00
bonusID = rand . nextInt ( ( int ) VLC - > skillh - > size ( ) - 1 ) ;
2014-06-05 19:52:14 +03:00
break ;
case SPELL :
std : : vector < SpellID > possibilities ;
for ( int i = 1 ; i < 6 ; + + i )
cb - > getAllowedSpells ( possibilities , i ) ;
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 :
2017-07-20 06:08:49 +02:00
value = PrimarySkill : : names [ bonusID ] ;
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
{
2022-12-07 15:18:19 +02:00
auto raw = VLC - > modh - > identifiers . getIdentifier ( CModHandler : : scopeBuiltin ( ) , " primSkill " , json [ " rewardPrimSkill " ] . String ( ) ) ;
2016-02-22 01:37:19 +02:00
if ( raw )
{
bonusType = PRIM_SKILL ;
bonusID = raw . get ( ) ;
}
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
{
2022-12-07 15:18:19 +02:00
auto raw = VLC - > modh - > identifiers . getIdentifier ( CModHandler : : scopeBuiltin ( ) , " skill " , json [ " rewardSkill " ] . String ( ) ) ;
2016-02-22 01:37:19 +02:00
if ( raw )
{
bonusType = SECONDARY_SKILL ;
bonusID = raw . get ( ) ;
}
}
2022-11-15 02:20:55 +02:00
else if ( ! json [ " rewardSpell " ] . String ( ) . empty ( ) )
2016-02-22 01:37:19 +02:00
{
2022-12-07 15:18:19 +02:00
auto raw = VLC - > modh - > identifiers . getIdentifier ( CModHandler : : scopeBuiltin ( ) , " spell " , json [ " rewardSpell " ] . String ( ) ) ;
2016-02-22 01:37:19 +02:00
if ( raw )
{
bonusType = SPELL ;
bonusID = raw . get ( ) ;
}
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
{
int ally = cb - > gameState ( ) - > getPlayerRelations ( h - > tempOwner , tempOwner ) ;
if ( ! ally & & stacksCount ( ) > 0 ) {
//TODO: Find a way to apply magic garrison effects in battle.
cb - > startBattleI ( h , this ) ;
return ;
}
//New owner.
if ( ! ally )
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 ) ;
2016-11-13 12:38:42 +02:00
CCreatureSet : : serializeJson ( handler , " army " , 7 ) ;
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 )
{
2017-09-13 02:35:58 +02:00
showInfoDialog ( h , 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
for ( auto it : eyelist [ subID ] )
{
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 )
{
2017-09-13 02:35:58 +02:00
showInfoDialog ( h , 48 ) ;
2014-06-05 19:52:14 +03:00
}
}
2016-09-09 19:30:36 +02:00
void CGBoat : : initObj ( CRandomGenerator & rand )
2014-06-05 19:52:14 +03:00
{
hero = nullptr ;
}
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
{
2014-06-24 20:39:36 +03:00
return getObjectName ( ) + " " + visitedTxt ( hero - > hasBonusFrom ( Bonus : : OBJECT , ID ) ) ;
2014-06-05 19:52:14 +03:00
}
void CGSirens : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
if ( h - > hasBonusFrom ( Bonus : : OBJECT , ID ) ) //has already visited Sirens
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 133 ) ;
}
else
{
giveDummyBonus ( h - > id , Bonus : : ONE_BATTLE ) ;
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 ) ;
2017-07-04 13:24:46 +02:00
xp + = drown * i - > second - > type - > MaxHealth ( ) ;
2014-06-05 19:52:14 +03:00
}
}
if ( xp )
{
2020-10-01 10:38:06 +02:00
xp = h - > calculateXp ( ( int ) xp ) ;
2014-06-05 19:52:14 +03:00
iw . text . addTxt ( MetaString : : ADVOB_TXT , 132 ) ;
2020-10-01 10:38:06 +02:00
iw . text . addReplacement ( ( int ) xp ) ;
2014-06-05 19:52:14 +03:00
cb - > changePrimSkill ( h , PrimarySkill : : EXPERIENCE , xp , false ) ;
}
else
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 134 ) ;
}
}
cb - > showInfoDialog ( & iw ) ;
}
CGShipyard : : CGShipyard ( )
: IShipyard ( this )
{
}
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 = {
int3 ( - 3 , 0 , 0 ) , int3 ( 1 , 0 , 0 ) , //AB
2014-06-05 19:52:14 +03:00
int3 ( - 3 , 1 , 0 ) , int3 ( 1 , 1 , 0 ) , int3 ( - 2 , 1 , 0 ) , int3 ( 0 , 1 , 0 ) , int3 ( - 1 , 1 , 0 ) , //CDEFG
2014-10-03 23:34:13 +03:00
int3 ( - 3 , - 1 , 0 ) , int3 ( 1 , - 1 , 0 ) , int3 ( - 2 , - 1 , 0 ) , int3 ( 0 , - 1 , 0 ) , int3 ( - 1 , - 1 , 0 ) //HIJKL
2015-11-16 15:30:40 +02:00
} ;
2014-06-05 19:52:14 +03:00
}
void CGShipyard : : onHeroVisit ( const CGHeroInstance * h ) const
{
if ( ! cb - > gameState ( ) - > getPlayerRelations ( tempOwner , h - > tempOwner ) )
cb - > setOwner ( this , h - > tempOwner ) ;
auto s = shipyardStatus ( ) ;
if ( s ! = IBoatGenerator : : GOOD )
{
InfoWindow iw ;
iw . player = tempOwner ;
getProblemText ( iw . text , h ) ;
cb - > showInfoDialog ( & iw ) ;
}
else
{
openWindow ( OpenWindow : : SHIPYARD_WINDOW , id . getNum ( ) , h - > id . getNum ( ) ) ;
}
}
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
}
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
{
if ( cb - > getResource ( h - > tempOwner , Res : : GOLD ) > = 1000 ) //if he can afford a map
{
//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 ( ) ;
bd . text . addTxt ( MetaString : : ADVOB_TXT , text ) ;
cb - > showBlockingDialog ( & bd ) ;
}
else //if he cannot afford
{
2017-09-13 02:35:58 +02:00
showInfoDialog ( h , 28 ) ;
2014-06-05 19:52:14 +03:00
}
}
else //if he already visited carographer
{
2017-09-13 02:35:58 +02:00
showInfoDialog ( h , 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
{
2022-12-30 16:51:13 +02:00
cb - > giveResource ( hero - > tempOwner , Res : : 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
{
cb - > showThievesGuildWindow ( h - > tempOwner , id ) ;
}
void CGObelisk : : onHeroVisit ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
TeamState * ts = cb - > gameState ( ) - > getPlayerTeam ( h - > tempOwner ) ;
assert ( ts ) ;
TeamID team = ts - > id ;
if ( ! wasVisited ( team ) )
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 96 ) ;
cb - > sendAndApply ( & iw ) ;
2016-01-20 09:44:13 +02:00
// increment general visited obelisks counter
cb - > setObjProperty ( id , CGObelisk : : OBJPROP_INC , team . getNum ( ) ) ;
2014-06-05 19:52:14 +03:00
openWindow ( OpenWindow : : PUZZLE_MAP , h - > tempOwner . getNum ( ) ) ;
2016-01-20 10:14:03 +02:00
// mark that particular obelisk as visited for all players in the team
for ( auto & color : ts - > players )
{
cb - > setObjProperty ( id , CGObelisk : : OBJPROP_VISITED , color . getNum ( ) ) ;
}
2014-06-05 19:52:14 +03:00
}
else
{
iw . text . addTxt ( MetaString : : ADVOB_TXT , 97 ) ;
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!
2017-09-13 02:35:58 +02:00
showInfoDialog ( h , 69 ) ;
2014-06-05 19:52:14 +03:00
giveBonusTo ( h - > tempOwner ) ;
if ( oldOwner < PlayerColor : : PLAYER_LIMIT ) //remove bonus from old owner
{
RemoveBonus rb ( RemoveBonus : : PLAYER ) ;
rb . whoID = oldOwner . getNum ( ) ;
rb . source = Bonus : : OBJECT ;
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
{
if ( tempOwner < PlayerColor : : PLAYER_LIMIT )
{
2018-11-01 22:31:23 +02:00
// FIXME: This is dirty hack
giveBonusTo ( tempOwner , true ) ;
2014-06-05 19:52:14 +03:00
}
}
2018-11-01 22:31:23 +02:00
void CGLighthouse : : giveBonusTo ( PlayerColor player , bool onInit ) const
2014-06-05 19:52:14 +03:00
{
GiveBonus gb ( GiveBonus : : PLAYER ) ;
gb . bonus . type = Bonus : : SEA_MOVEMENT ;
gb . bonus . val = 500 ;
gb . id = player . getNum ( ) ;
gb . bonus . duration = Bonus : : PERMANENT ;
gb . bonus . source = Bonus : : OBJECT ;
gb . bonus . sid = id . getNum ( ) ;
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
}
VCMI_LIB_NAMESPACE_END