2017-07-13 10:26:03 +02:00
/*
* CGameHandler . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
2014-03-17 22:51:07 +03:00
# include "StdInc.h"
2023-07-11 14:16:02 +02:00
# include "CGameHandler.h"
2023-07-23 23:00:37 +02:00
# include "CVCMIServer.h"
2024-04-26 12:26:33 +02:00
# include "TurnTimerHandler.h"
2023-07-11 14:16:02 +02:00
# include "ServerNetPackVisitors.h"
# include "ServerSpellCastEnvironment.h"
2023-07-23 23:20:35 +02:00
# include "battles/BattleProcessor.h"
# include "processors/HeroPoolProcessor.h"
# include "processors/PlayerMessageProcessor.h"
2023-08-22 17:45:13 +02:00
# include "processors/TurnOrderProcessor.h"
2023-07-23 23:46:29 +02:00
# include "queries/QueriesProcessor.h"
# include "queries/MapQueries.h"
2014-03-17 22:51:07 +03:00
2023-05-17 15:52:16 +02:00
# include "../lib/ArtifactUtils.h"
2014-03-17 22:51:07 +03:00
# include "../lib/CArtHandler.h"
2024-07-16 17:12:02 +02:00
# include "../lib/CConfigHandler.h"
2023-07-23 23:00:37 +02:00
# include "../lib/CCreatureHandler.h"
# include "../lib/CCreatureSet.h"
2024-07-20 14:55:17 +02:00
# include "../lib/texts/CGeneralTextHandler.h"
2023-07-23 23:00:37 +02:00
# include "../lib/CHeroHandler.h"
2023-11-11 00:39:08 +02:00
# include "../lib/CPlayerState.h"
2024-06-01 17:28:17 +02:00
# include "../lib/CRandomGenerator.h"
2023-07-23 23:00:37 +02:00
# include "../lib/CSoundBase.h"
# include "../lib/CThreadHelper.h"
# include "../lib/GameConstants.h"
2023-08-07 16:03:39 +02:00
# include "../lib/UnlockGuard.h"
2023-03-15 21:34:29 +02:00
# include "../lib/GameSettings.h"
2023-07-23 23:00:37 +02:00
# include "../lib/ScriptHandler.h"
# include "../lib/StartInfo.h"
# include "../lib/TerrainHandler.h"
# include "../lib/VCMIDirs.h"
2014-03-17 22:51:07 +03:00
# include "../lib/VCMI_Lib.h"
2023-07-23 23:00:37 +02:00
# include "../lib/int3.h"
2023-08-25 17:23:15 +02:00
# include "../lib/battle/BattleInfo.h"
2024-07-21 12:49:40 +02:00
# include "../lib/entities/building/CBuilding.h"
# include "../lib/entities/faction/CTownHandler.h"
2023-07-23 23:00:37 +02:00
# include "../lib/filesystem/FileInfo.h"
# include "../lib/filesystem/Filesystem.h"
2024-07-21 12:49:40 +02:00
2023-07-23 23:00:37 +02:00
# include "../lib/gameState/CGameState.h"
2014-03-17 22:51:07 +03:00
# include "../lib/mapping/CMap.h"
2017-07-20 06:08:49 +02:00
# include "../lib/mapping/CMapService.h"
2024-07-21 12:49:40 +02:00
2024-07-12 18:51:27 +02:00
# include "../lib/mapObjects/CGCreature.h"
2023-11-11 00:39:08 +02:00
# include "../lib/mapObjects/CGMarket.h"
2024-08-16 16:49:42 +02:00
# include "../lib/mapObjects/TownBuildingInstance.h"
2024-01-09 16:43:36 +02:00
# include "../lib/mapObjects/CGTownInstance.h"
# include "../lib/mapObjects/MiscObjects.h"
2024-07-12 18:51:27 +02:00
# include "../lib/mapObjectConstructors/AObjectTypeHandler.h"
# include "../lib/mapObjectConstructors/CObjectClassesHandler.h"
2024-07-21 12:49:40 +02:00
2023-07-30 19:12:25 +02:00
# include "../lib/modding/ModIncompatibility.h"
2024-07-21 12:49:40 +02:00
2023-10-23 12:59:15 +02:00
# include "../lib/networkPacks/StackLocation.h"
2024-07-21 12:49:40 +02:00
2023-07-23 23:00:37 +02:00
# include "../lib/pathfinder/CPathfinder.h"
# include "../lib/pathfinder/PathfinderOptions.h"
# include "../lib/pathfinder/TurnInfo.h"
2023-11-11 00:39:08 +02:00
# include "../lib/registerTypes/RegisterTypesServerPacks.h"
2023-07-23 23:00:37 +02:00
# include "../lib/rmg/CMapGenOptions.h"
2023-11-11 00:39:08 +02:00
# include "../lib/serializer/CSaveFile.h"
# include "../lib/serializer/CLoadFile.h"
2023-11-18 16:34:18 +02:00
# include "../lib/serializer/Connection.h"
2023-07-23 23:00:37 +02:00
# include "../lib/spells/CSpellHandler.h"
2024-06-01 17:28:17 +02:00
# include <vstd/RNG.h>
# include <vstd/CLoggerBase.h>
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
# include <vcmi/events/EventBus.h>
# include <vcmi/events/GenericEvents.h>
# include <vcmi/events/AdventureEvents.h>
2014-03-17 22:51:07 +03:00
2016-10-12 17:16:26 +02:00
# define COMPLAIN_RET_IF(cond, txt) do {if (cond){complain(txt); return;}} while(0)
# define COMPLAIN_RET_FALSE_IF(cond, txt) do {if (cond){complain(txt); return false;}} while(0)
2014-03-17 22:51:07 +03:00
# define COMPLAIN_RET(txt) {complain(txt); return false;}
# define COMPLAIN_RETF(txt, FORMAT) {complain(boost::str(boost::format(txt) % FORMAT)); return false;}
template < typename T > class CApplyOnGH ;
class CBaseForGHApply
{
public :
2024-02-12 12:53:10 +02:00
virtual bool applyOnGH ( CGameHandler * gh , CGameState * gs , CPack * pack ) const = 0 ;
2014-03-17 22:51:07 +03:00
virtual ~ CBaseForGHApply ( ) { }
template < typename U > static CBaseForGHApply * getApplier ( const U * t = nullptr )
{
2017-07-16 11:58:05 +02:00
return new CApplyOnGH < U > ( ) ;
2014-03-17 22:51:07 +03:00
}
} ;
template < typename T > class CApplyOnGH : public CBaseForGHApply
{
public :
2024-02-12 12:53:10 +02:00
bool applyOnGH ( CGameHandler * gh , CGameState * gs , CPack * pack ) const override
2014-03-17 22:51:07 +03:00
{
T * ptr = static_cast < T * > ( pack ) ;
2018-01-25 19:21:34 +02:00
try
{
2023-08-22 17:45:13 +02:00
ApplyGhNetPackVisitor applier ( * gh ) ;
2023-02-12 09:23:39 +02:00
ptr - > visit ( applier ) ;
return applier . getResult ( ) ;
2018-01-25 19:21:34 +02:00
}
catch ( ExceptionNotAllowedAction & e )
{
2020-10-01 10:38:06 +02:00
( void ) e ;
2018-01-25 19:21:34 +02:00
return false ;
}
2014-03-17 22:51:07 +03:00
}
} ;
2015-10-28 22:53:44 +02:00
template < >
2014-03-17 22:51:07 +03:00
class CApplyOnGH < CPack > : public CBaseForGHApply
{
public :
2024-02-12 12:53:10 +02:00
bool applyOnGH ( CGameHandler * gh , CGameState * gs , CPack * pack ) const override
2014-03-17 22:51:07 +03:00
{
2016-08-30 00:11:54 +02:00
logGlobal - > error ( " Cannot apply on GH plain CPack! " ) ;
2014-03-17 22:51:07 +03:00
assert ( 0 ) ;
return false ;
}
} ;
static inline double distance ( int3 a , int3 b )
{
2016-10-12 17:16:26 +02:00
return std : : sqrt ( ( double ) ( a . x - b . x ) * ( a . x - b . x ) + ( a . y - b . y ) * ( a . y - b . y ) ) ;
2014-03-17 22:51:07 +03:00
}
2017-01-26 22:24:01 +02:00
2014-03-17 22:51:07 +03:00
template < typename T >
void callWith ( std : : vector < T > args , std : : function < void ( T ) > fun , ui32 which )
{
fun ( args [ which ] ) ;
}
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 Services * CGameHandler : : services ( ) const
{
return VLC ;
}
2023-08-28 16:43:57 +02:00
const CGameHandler : : BattleCb * CGameHandler : : battle ( const BattleID & battleID ) const
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
{
2023-08-28 16:43:57 +02:00
return gs - > getBattle ( battleID ) ;
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 CGameHandler : : GameCb * CGameHandler : : game ( ) const
{
return this ;
}
vstd : : CLoggerBase * CGameHandler : : logger ( ) const
{
return logGlobal ;
}
events : : EventBus * CGameHandler : : eventBus ( ) const
{
return serverEventBus . get ( ) ;
}
2023-07-12 20:13:17 +02:00
CVCMIServer * CGameHandler : : gameLobby ( ) const
{
return lobby ;
}
2014-03-17 22:51:07 +03:00
void CGameHandler : : levelUpHero ( const CGHeroInstance * hero , SecondarySkill skill )
{
changeSecSkill ( hero , skill , 1 , 0 ) ;
expGiven ( hero ) ;
}
void CGameHandler : : levelUpHero ( const CGHeroInstance * hero )
{
// required exp for at least 1 lvl-up hasn't been reached
2016-10-12 17:16:26 +02:00
if ( ! hero - > gainsLevel ( ) )
2014-03-17 22:51:07 +03:00
{
return ;
}
2014-04-10 20:11:09 +03:00
// give primary skill
2023-01-02 13:27:03 +02:00
logGlobal - > trace ( " %s got level %d " , hero - > getNameTranslated ( ) , hero - > level ) ;
2016-08-23 18:12:10 +02:00
auto primarySkill = hero - > nextPrimarySkill ( getRandomGenerator ( ) ) ;
2014-03-17 22:51:07 +03:00
SetPrimSkill sps ;
sps . id = hero - > id ;
2014-04-10 20:11:09 +03:00
sps . which = primarySkill ;
2014-03-17 22:51:07 +03:00
sps . abs = false ;
sps . val = 1 ;
sendAndApply ( & sps ) ;
HeroLevelUp hlu ;
2018-03-10 21:19:55 +02:00
hlu . player = hero - > tempOwner ;
hlu . heroId = hero - > id ;
2014-04-10 20:11:09 +03:00
hlu . primskill = primarySkill ;
2023-11-20 18:44:27 +02:00
hlu . skills = hero - > getLevelUpProposedSecondarySkills ( heroPool - > getHeroSkillsRandomGenerator ( hero - > getHeroType ( ) ) ) ;
2014-03-17 22:51:07 +03:00
2016-10-12 17:16:26 +02:00
if ( hlu . skills . size ( ) = = 0 )
2014-03-17 22:51:07 +03:00
{
sendAndApply ( & hlu ) ;
levelUpHero ( hero ) ;
}
2023-11-20 18:44:27 +02:00
else if ( hlu . skills . size ( ) = = 1 | | ! hero - > getOwner ( ) . isValidPlayer ( ) )
2014-03-17 22:51:07 +03:00
{
sendAndApply ( & hlu ) ;
2023-11-20 18:44:27 +02:00
levelUpHero ( hero , hlu . skills . front ( ) ) ;
2014-03-17 22:51:07 +03:00
}
2016-10-12 17:16:26 +02:00
else if ( hlu . skills . size ( ) > 1 )
2014-03-17 22:51:07 +03:00
{
2018-03-10 21:19:55 +02:00
auto levelUpQuery = std : : make_shared < CHeroLevelUpDialogQuery > ( this , hlu , hero ) ;
2014-03-17 22:51:07 +03:00
hlu . queryID = levelUpQuery - > queryID ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( levelUpQuery ) ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & hlu ) ;
//level up will be called on query reply
}
}
void CGameHandler : : levelUpCommander ( const CCommanderInstance * c , int skill )
{
SetCommanderProperty scp ;
auto hero = dynamic_cast < const CGHeroInstance * > ( c - > armyObj ) ;
if ( hero )
scp . heroid = hero - > id ;
else
{
complain ( " Commander is not led by hero! " ) ;
return ;
}
scp . accumulatedBonus . additionalInfo = 0 ;
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . duration = BonusDuration : : PERMANENT ;
2014-03-17 22:51:07 +03:00
scp . accumulatedBonus . turnsRemain = 0 ;
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . source = BonusSource : : COMMANDER ;
scp . accumulatedBonus . valType = BonusValueType : : BASE_NUMBER ;
2014-03-17 22:51:07 +03:00
if ( skill < = ECommander : : SPELL_POWER )
{
scp . which = SetCommanderProperty : : BONUS ;
auto difference = [ ] ( std : : vector < std : : vector < ui8 > > skillLevels , std : : vector < ui8 > secondarySkills , int skill ) - > int
{
int s = std : : min ( skill , ( int ) ECommander : : SPELL_POWER ) ; //spell power level controls also casts and resistance
return skillLevels . at ( skill ) . at ( secondarySkills . at ( s ) ) - ( secondarySkills . at ( s ) ? skillLevels . at ( skill ) . at ( secondarySkills . at ( s ) - 1 ) : 0 ) ;
} ;
switch ( skill )
{
case ECommander : : ATTACK :
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : PRIMARY_SKILL ;
2023-10-21 13:50:42 +02:00
scp . accumulatedBonus . subtype = BonusSubtypeID ( PrimarySkill : : ATTACK ) ;
2014-03-17 22:51:07 +03:00
break ;
case ECommander : : DEFENSE :
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : PRIMARY_SKILL ;
2023-10-21 13:50:42 +02:00
scp . accumulatedBonus . subtype = BonusSubtypeID ( PrimarySkill : : DEFENSE ) ;
2014-03-17 22:51:07 +03:00
break ;
case ECommander : : HEALTH :
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : STACK_HEALTH ;
2023-12-23 21:35:40 +02:00
scp . accumulatedBonus . valType = BonusValueType : : PERCENT_TO_ALL ; //TODO: check how it accumulates in original WoG with artifacts such as vial of life blood, elixir of life etc.
2014-03-17 22:51:07 +03:00
break ;
case ECommander : : DAMAGE :
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : CREATURE_DAMAGE ;
2023-12-23 21:33:15 +02:00
scp . accumulatedBonus . subtype = BonusCustomSubtype : : creatureDamageBoth ;
2023-12-23 21:16:29 +02:00
scp . accumulatedBonus . valType = BonusValueType : : PERCENT_TO_ALL ;
2014-03-17 22:51:07 +03:00
break ;
case ECommander : : SPEED :
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : STACKS_SPEED ;
2014-03-17 22:51:07 +03:00
break ;
case ECommander : : SPELL_POWER :
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : MAGIC_RESISTANCE ;
2014-03-17 22:51:07 +03:00
scp . accumulatedBonus . val = difference ( VLC - > creh - > skillLevels , c - > secondarySkills , ECommander : : RESISTANCE ) ;
sendAndApply ( & scp ) ; //additional pack
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : CREATURE_SPELL_POWER ;
2014-03-17 22:51:07 +03:00
scp . accumulatedBonus . val = difference ( VLC - > creh - > skillLevels , c - > secondarySkills , ECommander : : SPELL_POWER ) * 100 ; //like hero with spellpower = ability level
sendAndApply ( & scp ) ; //additional pack
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : CASTS ;
2014-03-17 22:51:07 +03:00
scp . accumulatedBonus . val = difference ( VLC - > creh - > skillLevels , c - > secondarySkills , ECommander : : CASTS ) ;
sendAndApply ( & scp ) ; //additional pack
2023-05-01 00:20:01 +02:00
scp . accumulatedBonus . type = BonusType : : CREATURE_ENCHANT_POWER ; //send normally
2014-03-17 22:51:07 +03:00
break ;
}
scp . accumulatedBonus . val = difference ( VLC - > creh - > skillLevels , c - > secondarySkills , skill ) ;
sendAndApply ( & scp ) ;
scp . which = SetCommanderProperty : : SECONDARY_SKILL ;
scp . additionalInfo = skill ;
scp . amount = c - > secondarySkills . at ( skill ) + 1 ;
sendAndApply ( & scp ) ;
}
else if ( skill > = 100 )
{
scp . which = SetCommanderProperty : : SPECIAL_SKILL ;
scp . accumulatedBonus = * VLC - > creh - > skillRequirements . at ( skill - 100 ) . first ;
scp . additionalInfo = skill ; //unnormalized
sendAndApply ( & scp ) ;
}
expGiven ( hero ) ;
}
void CGameHandler : : levelUpCommander ( const CCommanderInstance * c )
{
if ( ! c - > gainsLevel ( ) )
{
return ;
}
CommanderLevelUp clu ;
auto hero = dynamic_cast < const CGHeroInstance * > ( c - > armyObj ) ;
2018-03-10 21:19:55 +02:00
if ( hero )
{
clu . heroId = hero - > id ;
clu . player = hero - > tempOwner ;
}
2014-03-17 22:51:07 +03:00
else
{
complain ( " Commander is not led by hero! " ) ;
return ;
}
//picking sec. skills for choice
for ( int i = 0 ; i < = ECommander : : SPELL_POWER ; + + i )
{
if ( c - > secondarySkills . at ( i ) < ECommander : : MAX_SKILL_LEVEL )
clu . skills . push_back ( i ) ;
}
int i = 100 ;
for ( auto specialSkill : VLC - > creh - > skillRequirements )
{
2023-09-14 12:54:07 +02:00
if ( c - > secondarySkills . at ( specialSkill . second . first ) > = ECommander : : MAX_SKILL_LEVEL - 1
& & c - > secondarySkills . at ( specialSkill . second . second ) > = ECommander : : MAX_SKILL_LEVEL - 1
2023-09-13 23:08:22 +02:00
& & ! vstd : : contains ( c - > specialSkills , i ) )
2014-03-17 22:51:07 +03:00
clu . skills . push_back ( i ) ;
+ + i ;
}
2020-10-01 10:38:06 +02:00
int skillAmount = static_cast < int > ( clu . skills . size ( ) ) ;
2014-03-17 22:51:07 +03:00
2016-10-12 17:16:26 +02:00
if ( ! skillAmount )
2014-03-17 22:51:07 +03:00
{
sendAndApply ( & clu ) ;
levelUpCommander ( c ) ;
}
2016-10-12 17:16:26 +02:00
else if ( skillAmount = = 1 | | hero - > tempOwner = = PlayerColor : : NEUTRAL ) //choose skill automatically
2014-03-17 22:51:07 +03:00
{
sendAndApply ( & clu ) ;
2016-09-07 23:54:26 +02:00
levelUpCommander ( c , * RandomGeneratorUtil : : nextItem ( clu . skills , getRandomGenerator ( ) ) ) ;
2014-03-17 22:51:07 +03:00
}
2016-10-12 17:16:26 +02:00
else if ( skillAmount > 1 ) //apply and ask for secondary skill
2014-03-17 22:51:07 +03:00
{
2018-03-10 21:19:55 +02:00
auto commanderLevelUp = std : : make_shared < CCommanderLevelUpDialogQuery > ( this , clu , hero ) ;
2014-03-17 22:51:07 +03:00
clu . queryID = commanderLevelUp - > queryID ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( commanderLevelUp ) ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & clu ) ;
}
}
void CGameHandler : : expGiven ( const CGHeroInstance * hero )
{
2016-10-12 17:16:26 +02:00
if ( hero - > gainsLevel ( ) )
2014-03-17 22:51:07 +03:00
levelUpHero ( hero ) ;
2016-10-12 17:16:26 +02:00
else if ( hero - > commander & & hero - > commander - > gainsLevel ( ) )
2014-03-17 22:51:07 +03:00
levelUpCommander ( hero - > commander ) ;
2016-10-12 17:16:26 +02:00
//if (hero->commander && hero->level > hero->commander->level && hero->commander->gainsLevel())
2014-03-17 22:51:07 +03:00
// levelUpCommander(hero->commander);
// else
// levelUpHero(hero);
}
2024-01-04 23:57:36 +02:00
void CGameHandler : : giveExperience ( const CGHeroInstance * hero , TExpType amountToGain )
2014-03-17 22:51:07 +03:00
{
2024-01-04 23:57:36 +02:00
TExpType maxExp = VLC - > heroh - > reqExp ( VLC - > heroh - > maxSupportedLevel ( ) ) ;
TExpType currExp = hero - > exp ;
if ( gs - > map - > levelLimit ! = 0 )
maxExp = VLC - > heroh - > reqExp ( gs - > map - > levelLimit ) ;
TExpType canGainExp = 0 ;
if ( maxExp > currExp )
canGainExp = maxExp - currExp ;
if ( amountToGain > canGainExp )
2014-03-17 22:51:07 +03:00
{
2024-01-04 23:57:36 +02:00
// set given experience to max possible, but don't decrease if hero already over top
amountToGain = canGainExp ;
2014-03-17 22:51:07 +03:00
2024-01-04 23:57:36 +02:00
InfoWindow iw ;
iw . player = hero - > tempOwner ;
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 1 ) ; //can gain no more XP
2024-04-08 17:21:40 +02:00
iw . text . replaceTextID ( hero - > getNameTextID ( ) ) ;
2024-01-04 23:57:36 +02:00
sendAndApply ( & iw ) ;
2014-03-17 22:51:07 +03:00
}
SetPrimSkill sps ;
sps . id = hero - > id ;
2024-01-04 23:57:36 +02:00
sps . which = PrimarySkill : : EXPERIENCE ;
sps . abs = false ;
sps . val = amountToGain ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & sps ) ;
2024-01-04 23:57:36 +02:00
//hero may level up
if ( hero - > commander & & hero - > commander - > alive )
2014-03-17 22:51:07 +03:00
{
2024-01-04 23:57:36 +02:00
//FIXME: trim experience according to map limit?
SetCommanderProperty scp ;
scp . heroid = hero - > id ;
scp . which = SetCommanderProperty : : EXPERIENCE ;
scp . amount = amountToGain ;
sendAndApply ( & scp ) ;
CBonusSystemNode : : treeHasChanged ( ) ;
2014-03-17 22:51:07 +03:00
}
2024-01-04 23:57:36 +02:00
expGiven ( hero ) ;
}
void CGameHandler : : changePrimSkill ( const CGHeroInstance * hero , PrimarySkill which , si64 val , bool abs )
{
SetPrimSkill sps ;
sps . id = hero - > id ;
sps . which = which ;
sps . abs = abs ;
sps . val = val ;
sendAndApply ( & sps ) ;
2014-03-17 22:51:07 +03:00
}
2017-07-15 13:08:20 +02:00
void CGameHandler : : changeSecSkill ( const CGHeroInstance * hero , SecondarySkill which , int val , bool abs )
2014-03-17 22:51:07 +03:00
{
2016-11-25 20:17:24 +02:00
if ( ! hero )
{
logGlobal - > error ( " changeSecSkill provided no hero " ) ;
return ;
}
2014-03-17 22:51:07 +03:00
SetSecSkill sss ;
sss . id = hero - > id ;
sss . which = which ;
sss . val = val ;
sss . abs = abs ;
sendAndApply ( & sss ) ;
2023-02-27 12:57:21 +02:00
if ( hero - > visitedTown )
giveSpells ( hero - > visitedTown , hero ) ;
2024-07-08 22:56:17 +02:00
// Our scouting range may have changed - update it
if ( hero - > getOwner ( ) . isValidPlayer ( ) )
changeFogOfWar ( hero - > getSightCenter ( ) , hero - > getSightRadius ( ) , hero - > getOwner ( ) , ETileVisibility : : REVEALED ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
void CGameHandler : : handleClientDisconnection ( std : : shared_ptr < CConnection > c )
2014-03-17 22:51:07 +03:00
{
2023-08-22 17:45:13 +02:00
if ( lobby - > getState ( ) = = EServerState : : SHUTDOWN | | ! gs | | ! gs - > scenarioOps )
2024-02-03 19:08:45 +02:00
{
assert ( 0 ) ; // game should have shut down before reaching this point!
2023-07-23 23:00:37 +02:00
return ;
2024-02-03 19:08:45 +02:00
}
2023-07-23 23:00:37 +02:00
for ( auto & playerConnections : connections )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
PlayerColor playerId = playerConnections . first ;
auto * playerSettings = gs - > scenarioOps - > getPlayersSettings ( playerId . getNum ( ) ) ;
if ( ! playerSettings )
continue ;
auto playerConnection = vstd : : find ( playerConnections . second , c ) ;
if ( playerConnection ! = playerConnections . second . end ( ) )
{
std : : string messageText = boost : : str ( boost : : format ( " %s (cid %d) was disconnected " ) % playerSettings - > name % c - > connectionID ) ;
playerMessages - > broadcastMessage ( playerId , messageText ) ;
}
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
void CGameHandler : : handleReceivedPack ( CPackForServer * pack )
{
//prepare struct informing that action was applied
2024-06-24 03:23:26 +02:00
auto sendPackageResponse = [ & ] ( bool successfullyApplied )
2023-07-23 23:00:37 +02:00
{
PackageApplied applied ;
applied . player = pack - > player ;
2024-06-24 03:23:26 +02:00
applied . result = successfullyApplied ;
2023-11-08 22:05:36 +02:00
applied . packType = CTypeList : : getInstance ( ) . getTypeID ( pack ) ;
2023-07-23 23:00:37 +02:00
applied . requestID = pack - > requestID ;
pack - > c - > sendPack ( & applied ) ;
} ;
2014-03-17 22:51:07 +03:00
2023-11-08 22:05:36 +02:00
CBaseForGHApply * apply = applier - > getApplier ( CTypeList : : getInstance ( ) . getTypeID ( pack ) ) ; //and appropriate applier object
2023-07-23 23:00:37 +02:00
if ( isBlockedByQueries ( pack , pack - > player ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
sendPackageResponse ( false ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
else if ( apply )
2023-07-27 17:40:47 +02:00
{
2023-07-23 23:00:37 +02:00
const bool result = apply - > applyOnGH ( this , this - > gs , pack ) ;
if ( result )
logGlobal - > trace ( " Message %s successfully applied! " , typeid ( * pack ) . name ( ) ) ;
else
complain ( ( boost : : format ( " Got false in applying %s... that request must have been fishy! " )
% typeid ( * pack ) . name ( ) ) . str ( ) ) ;
sendPackageResponse ( true ) ;
2023-07-27 17:40:47 +02:00
}
else
2023-04-06 20:49:38 +02:00
{
2023-07-23 23:00:37 +02:00
logGlobal - > error ( " Message cannot be applied, cannot find applier (unregistered type)! " ) ;
sendPackageResponse ( false ) ;
2023-04-06 20:49:38 +02:00
}
2023-07-27 17:40:47 +02:00
2023-07-23 23:00:37 +02:00
vstd : : clear_pointer ( pack ) ;
}
CGameHandler : : CGameHandler ( CVCMIServer * lobby )
: lobby ( lobby )
, heroPool ( std : : make_unique < HeroPoolProcessor > ( this ) )
, battles ( std : : make_unique < BattleProcessor > ( this ) )
2023-08-22 17:45:13 +02:00
, turnOrder ( std : : make_unique < TurnOrderProcessor > ( this ) )
2023-07-23 23:46:29 +02:00
, queries ( std : : make_unique < QueriesProcessor > ( ) )
2023-07-23 23:00:37 +02:00
, playerMessages ( std : : make_unique < PlayerMessageProcessor > ( this ) )
2024-07-19 15:57:06 +02:00
, randomNumberGenerator ( std : : make_unique < CRandomGenerator > ( ) )
2023-07-23 23:00:37 +02:00
, complainNoCreatures ( " No creatures to split " )
, complainNotEnoughCreatures ( " Cannot split that stack, not enough creatures! " )
, complainInvalidSlot ( " Invalid slot accessed! " )
2024-04-26 12:15:39 +02:00
, turnTimerHandler ( std : : make_unique < TurnTimerHandler > ( * this ) )
2023-07-23 23:00:37 +02:00
{
QID = 1 ;
applier = std : : make_shared < CApplier < CBaseForGHApply > > ( ) ;
registerTypesServerPacks ( * applier ) ;
spellEnv = new ServerSpellCastEnvironment ( this ) ;
}
CGameHandler : : ~ CGameHandler ( )
{
delete spellEnv ;
delete gs ;
2023-12-17 19:31:32 +02:00
gs = nullptr ;
2023-07-23 23:00:37 +02:00
}
void CGameHandler : : reinitScripting ( )
{
serverEventBus = std : : make_unique < events : : EventBus > ( ) ;
# if SCRIPTING_ENABLED
serverScripts . reset ( new scripting : : PoolImpl ( this , spellEnv ) ) ;
# endif
2022-11-05 01:23:31 +02:00
}
2014-03-17 22:51:07 +03:00
2023-08-22 18:10:20 +02:00
void CGameHandler : : init ( StartInfo * si , Load : : ProgressAccumulator & progressTracking )
2022-11-05 01:23:31 +02:00
{
2024-07-16 17:12:02 +02:00
int requestedSeed = settings [ " server " ] [ " seed " ] . Integer ( ) ;
if ( requestedSeed ! = 0 )
randomNumberGenerator - > setSeed ( requestedSeed ) ;
2024-06-01 18:20:32 +02:00
logGlobal - > info ( " Using random seed: %d " , randomNumberGenerator - > nextInt ( ) ) ;
2024-06-01 18:09:14 +02:00
2023-07-23 23:00:37 +02:00
CMapService mapService ;
gs = new CGameState ( ) ;
2024-01-01 16:37:48 +02:00
gs - > preInit ( VLC , this ) ;
2023-07-23 23:00:37 +02:00
logGlobal - > info ( " Gamestate created! " ) ;
2023-08-21 22:46:54 +02:00
gs - > init ( & mapService , si , progressTracking ) ;
2023-07-23 23:00:37 +02:00
logGlobal - > info ( " Gamestate initialized! " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
for ( auto & elem : gs - > players )
2023-08-22 17:45:13 +02:00
turnOrder - > addPlayer ( elem . first ) ;
2014-03-17 22:51:07 +03:00
2023-11-20 18:44:27 +02:00
for ( auto & elem : gs - > map - > allHeroes )
2023-11-22 17:09:42 +02:00
{
if ( elem )
heroPool - > getHeroSkillsRandomGenerator ( elem - > getHeroType ( ) ) ; // init RMG seed
}
2023-11-20 18:44:27 +02:00
2023-07-23 23:00:37 +02:00
reinitScripting ( ) ;
}
void CGameHandler : : setPortalDwelling ( const CGTownInstance * town , bool forced = false , bool clear = false )
{ // bool forced = true - if creature should be replaced, if false - only if no creature was set
const PlayerState * p = getPlayerState ( town - > tempOwner ) ;
if ( ! p )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
assert ( town - > tempOwner = = PlayerColor : : NEUTRAL ) ;
return ;
}
2021-03-23 16:47:07 +02:00
2024-08-05 21:15:47 +02:00
if ( forced | | town - > creatures . at ( town - > town - > creatures . size ( ) ) . second . empty ( ) ) //we need to change creature
2016-01-21 19:23:45 +02:00
{
2023-07-23 23:00:37 +02:00
SetAvailableCreatures ssi ;
ssi . tid = town - > id ;
ssi . creatures = town - > creatures ;
2024-08-05 21:15:47 +02:00
ssi . creatures [ town - > town - > creatures . size ( ) ] . second . clear ( ) ; //remove old one
2023-07-23 23:00:37 +02:00
const std : : vector < ConstTransitivePtr < CGDwelling > > & dwellings = p - > dwellings ;
if ( dwellings . empty ( ) ) //no dwellings - just remove
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
sendAndApply ( & ssi ) ;
return ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
auto dwelling = * RandomGeneratorUtil : : nextItem ( dwellings , getRandomGenerator ( ) ) ;
// for multi-creature dwellings like Golem Factory
auto creatureId = RandomGeneratorUtil : : nextItem ( dwelling - > creatures , getRandomGenerator ( ) ) - > second [ 0 ] ;
if ( clear )
2014-03-17 22:51:07 +03:00
{
2024-08-05 21:15:47 +02:00
ssi . creatures [ town - > town - > creatures . size ( ) ] . first = std : : max ( 1 , ( VLC - > creh - > objects . at ( creatureId ) - > getGrowth ( ) ) / 2 ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
else
2014-03-17 22:51:07 +03:00
{
2024-08-05 21:15:47 +02:00
ssi . creatures [ town - > town - > creatures . size ( ) ] . first = VLC - > creh - > objects . at ( creatureId ) - > getGrowth ( ) ;
2014-03-17 22:51:07 +03:00
}
2024-08-05 21:15:47 +02:00
ssi . creatures [ town - > town - > creatures . size ( ) ] . second . push_back ( creatureId ) ;
2023-07-23 23:00:37 +02:00
sendAndApply ( & ssi ) ;
2014-03-17 22:51:07 +03:00
}
}
2023-08-22 17:45:13 +02:00
void CGameHandler : : onPlayerTurnStarted ( PlayerColor which )
{
events : : PlayerGotTurn : : defaultExecute ( serverEventBus . get ( ) , which ) ;
2024-04-26 12:15:39 +02:00
turnTimerHandler - > onPlayerGetTurn ( which ) ;
2024-08-12 12:38:18 +02:00
2024-08-12 15:30:03 +02:00
const auto * playerState = gs - > getPlayerState ( which ) ;
2024-08-12 12:38:18 +02:00
2024-08-12 15:30:03 +02:00
handleTimeEvents ( which ) ;
for ( auto t : playerState - > towns )
2024-08-12 12:38:18 +02:00
handleTownEvents ( t ) ;
2024-08-12 15:30:03 +02:00
for ( auto t : playerState - > towns )
{
//garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
if ( t - > garrisonHero ! = nullptr )
objectVisited ( t , t - > garrisonHero ) ;
if ( t - > visitingHero ! = nullptr )
objectVisited ( t , t - > visitingHero ) ;
}
2023-08-22 17:45:13 +02:00
}
void CGameHandler : : onPlayerTurnEnded ( PlayerColor which )
{
2023-08-24 20:35:01 +02:00
const auto * playerState = gs - > getPlayerState ( which ) ;
assert ( playerState - > status = = EPlayerStatus : : INGAME ) ;
if ( playerState - > towns . empty ( ) )
{
DaysWithoutTown pack ;
pack . player = which ;
pack . daysWithoutCastle = playerState - > daysWithoutCastle . value_or ( 0 ) + 1 ;
sendAndApply ( & pack ) ;
}
else
{
if ( playerState - > daysWithoutCastle . has_value ( ) )
{
DaysWithoutTown pack ;
pack . player = which ;
pack . daysWithoutCastle = std : : nullopt ;
sendAndApply ( & pack ) ;
}
}
// check for 7 days without castle
2023-08-23 17:46:30 +02:00
checkVictoryLossConditionsForPlayer ( which ) ;
2023-08-24 17:53:58 +02:00
bool newWeek = getDate ( Date : : DAY_OF_WEEK ) = = 7 ; // end of 7th day
if ( newWeek ) //new heroes in tavern
heroPool - > onNewWeek ( which ) ;
2023-08-22 17:45:13 +02:00
}
2024-07-27 02:11:26 +02:00
void CGameHandler : : addStatistics ( )
{
2024-08-12 20:26:30 +02:00
for ( const auto & elem : gs - > players )
2024-07-27 02:11:26 +02:00
{
if ( elem . first = = PlayerColor : : NEUTRAL | | ! elem . first . isValidPlayer ( ) )
continue ;
2024-08-01 21:30:53 +02:00
auto data = StatisticDataSet : : createEntry ( & elem . second , gs ) ;
2024-07-27 02:11:26 +02:00
gameState ( ) - > statistic . add ( data ) ;
}
}
2023-08-22 17:45:13 +02:00
void CGameHandler : : onNewTurn ( )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
logGlobal - > trace ( " Turn %d " , gs - > day + 1 ) ;
NewTurn n ;
n . specialWeek = NewTurn : : NO_ACTION ;
n . creatureid = CreatureID : : NONE ;
n . day = gs - > day + 1 ;
2022-09-19 16:45:48 +02:00
2023-07-23 23:00:37 +02:00
bool firstTurn = ! getDate ( Date : : DAY ) ;
bool newWeek = getDate ( Date : : DAY_OF_WEEK ) = = 7 ; //day numbers are confusing, as day was not yet switched
bool newMonth = getDate ( Date : : DAY_OF_MONTH ) = = 28 ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
std : : map < PlayerColor , si32 > hadGold ; //starting gold - for buildings like dwarven treasury
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( firstTurn )
2015-10-28 22:53:44 +02:00
{
2023-07-23 23:00:37 +02:00
for ( auto obj : gs - > map - > objects )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( obj & & obj - > ID = = Obj : : PRISON ) //give imprisoned hero 0 exp to level him up. easiest to do at this point
2014-03-17 22:51:07 +03:00
{
2024-01-04 23:57:36 +02:00
giveExperience ( getHero ( obj - > id ) , 0 ) ;
2014-03-17 22:51:07 +03:00
}
}
2023-07-23 23:00:37 +02:00
}
2015-10-28 22:53:44 +02:00
2024-08-12 20:26:30 +02:00
for ( const auto & player : gs - > players )
2024-08-02 17:04:07 +02:00
{
if ( player . second . status ! = EPlayerStatus : : INGAME )
continue ;
if ( player . second . heroes . empty ( ) & & player . second . towns . empty ( ) )
throw std : : runtime_error ( " Invalid player in player state! Player " + std : : to_string ( player . first . getNum ( ) ) + " , map name: " + gs - > map - > name . toString ( ) + " , map description: " + gs - > map - > description . toString ( ) ) ;
}
2023-07-23 23:00:37 +02:00
if ( newWeek & & ! firstTurn )
{
n . specialWeek = NewTurn : : NORMAL ;
bool deityOfFireBuilt = false ;
for ( const CGTownInstance * t : gs - > map - > towns )
2014-11-27 23:36:14 +02:00
{
2023-07-23 23:00:37 +02:00
if ( t - > hasBuilt ( BuildingID : : GRAIL , ETownType : : INFERNO ) )
2014-11-27 23:36:14 +02:00
{
2023-07-23 23:00:37 +02:00
deityOfFireBuilt = true ;
break ;
2014-11-27 23:36:14 +02: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
2023-07-23 23:00:37 +02:00
if ( deityOfFireBuilt )
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
{
2023-07-23 23:00:37 +02:00
n . specialWeek = NewTurn : : DEITYOFFIRE ;
n . creatureid = CreatureID : : IMP ;
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
}
2023-07-23 23:00:37 +02:00
else if ( VLC - > settings ( ) - > getBoolean ( EGameSettings : : CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS ) )
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
{
2023-07-23 23:00:37 +02:00
int monthType = getRandomGenerator ( ) . nextInt ( 99 ) ;
if ( newMonth ) //new month
{
if ( monthType < 40 ) //double growth
{
n . specialWeek = NewTurn : : DOUBLE_GROWTH ;
if ( VLC - > settings ( ) - > getBoolean ( EGameSettings : : CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH ) )
{
n . creatureid = VLC - > creh - > pickRandomMonster ( getRandomGenerator ( ) ) ;
}
else if ( VLC - > creh - > doubledCreatures . size ( ) )
{
n . creatureid = * RandomGeneratorUtil : : nextItem ( VLC - > creh - > doubledCreatures , getRandomGenerator ( ) ) ;
}
else
{
complain ( " Cannot find creature that can be spawned! " ) ;
n . specialWeek = NewTurn : : NORMAL ;
}
}
else if ( monthType < 50 )
n . specialWeek = NewTurn : : PLAGUE ;
}
else //it's a week, but not full month
{
if ( monthType < 25 )
{
n . specialWeek = NewTurn : : BONUS_GROWTH ; //+5
std : : pair < int , CreatureID > newMonster ( 54 , CreatureID ( ) ) ;
do
{
newMonster . second = VLC - > creh - > pickRandomMonster ( getRandomGenerator ( ) ) ;
} while ( VLC - > creh - > objects [ newMonster . second ] & &
( * VLC - > townh ) [ VLC - > creatures ( ) - > getById ( newMonster . second ) - > getFaction ( ) ] - > town = = nullptr ) ; // find first non neutral creature
n . creatureid = newMonster . second ;
}
}
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
}
}
2022-09-19 16:45:48 +02:00
2023-07-23 23:00:37 +02:00
for ( auto & elem : gs - > players )
2022-09-19 16:45:48 +02:00
{
2023-07-23 23:00:37 +02:00
if ( elem . first = = PlayerColor : : NEUTRAL )
continue ;
2023-08-24 17:53:58 +02:00
assert ( elem . first . isValidPlayer ( ) ) ; //illegal player number!
2024-07-25 02:51:00 +02:00
auto playerSettings = gameState ( ) - > scenarioOps - > getIthPlayersSettings ( elem . first ) ;
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
2023-07-23 23:00:37 +02:00
std : : pair < PlayerColor , si32 > playerGold ( elem . first , elem . second . resources [ EGameResID : : GOLD ] ) ;
hadGold . insert ( playerGold ) ;
2017-07-20 06:08:49 +02:00
2023-08-24 17:53:58 +02:00
if ( firstTurn )
2023-07-23 23:00:37 +02:00
heroPool - > onNewWeek ( elem . first ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
n . res [ elem . first ] = elem . second . resources ;
2017-07-20 06:08:49 +02:00
2024-04-15 21:18:45 +02:00
if ( ! firstTurn )
2023-07-23 23:00:37 +02:00
{
2024-04-15 21:18:45 +02:00
for ( GameResID k = GameResID : : WOOD ; k < GameResID : : COUNT ; k + + )
2017-07-20 06:08:49 +02:00
{
2024-07-25 02:51:00 +02:00
n . res [ elem . first ] [ k ] + = elem . second . valOfBonuses ( BonusType : : RESOURCES_CONSTANT_BOOST , BonusSubtypeID ( k ) ) * playerSettings . handicap . percentIncome / 100 ;
n . res [ elem . first ] [ k ] + = elem . second . valOfBonuses ( BonusType : : RESOURCES_TOWN_MULTIPLYING_BOOST , BonusSubtypeID ( k ) ) * elem . second . towns . size ( ) * playerSettings . handicap . percentIncome / 100 ;
2017-07-20 06:08:49 +02:00
}
2024-04-15 21:18:45 +02:00
if ( newWeek ) //weekly crystal generation if 1 or more crystal dragons in any hero army or town garrison
2017-07-20 06:08:49 +02:00
{
2024-04-15 21:18:45 +02:00
bool hasCrystalGenCreature = false ;
for ( CGHeroInstance * hero : elem . second . heroes )
2023-07-23 23:00:37 +02:00
{
2024-04-15 21:18:45 +02:00
for ( auto stack : hero - > stacks )
2023-07-23 23:00:37 +02:00
{
if ( stack . second - > hasBonusOfType ( BonusType : : SPECIAL_CRYSTAL_GENERATION ) )
{
hasCrystalGenCreature = true ;
break ;
}
}
}
2024-04-15 21:18:45 +02:00
if ( ! hasCrystalGenCreature ) //not found in armies, check towns
{
for ( CGTownInstance * town : elem . second . towns )
{
for ( auto stack : town - > stacks )
{
if ( stack . second - > hasBonusOfType ( BonusType : : SPECIAL_CRYSTAL_GENERATION ) )
{
hasCrystalGenCreature = true ;
break ;
}
}
}
}
if ( hasCrystalGenCreature )
2024-07-25 02:51:00 +02:00
n . res [ elem . first ] [ EGameResID : : CRYSTAL ] + = 3 * playerSettings . handicap . percentIncome / 100 ;
2017-07-20 06:08:49 +02:00
}
}
2023-07-23 23:00:37 +02:00
for ( CGHeroInstance * h : ( elem ) . second . heroes )
2023-01-04 17:55:19 +02:00
{
2023-07-23 23:00:37 +02:00
if ( h - > visitedTown )
giveSpells ( h - > visitedTown , h ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
NewTurn : : Hero hth ;
hth . id = h - > id ;
auto ti = std : : make_unique < TurnInfo > ( h , 1 ) ;
// TODO: this code executed when bonuses of previous day not yet updated (this happen in NewTurn::applyGs). See issue 2356
hth . move = h - > movementPointsLimitCached ( gs - > map - > getTile ( h - > visitablePos ( ) ) . terType - > isLand ( ) , ti . get ( ) ) ;
hth . mana = h - > getManaNewTurn ( ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
n . heroes . insert ( hth ) ;
2022-09-21 09:42:42 +02:00
2023-07-23 23:00:37 +02:00
if ( ! firstTurn ) //not first day
2023-01-04 17:55:19 +02:00
{
2023-10-05 17:18:14 +02:00
for ( GameResID k = GameResID : : WOOD ; k < GameResID : : COUNT ; k + + )
2023-07-23 23:00:37 +02:00
{
2024-07-25 02:51:00 +02:00
n . res [ elem . first ] [ k ] + = h - > valOfBonuses ( BonusType : : GENERATE_RESOURCE , BonusSubtypeID ( k ) ) * playerSettings . handicap . percentIncome / 100 ;
2023-07-23 23:00:37 +02:00
}
2023-01-04 17:55:19 +02:00
}
2022-09-21 09:42:42 +02:00
}
2017-07-20 06:08:49 +02:00
}
2023-07-23 23:00:37 +02:00
for ( CGTownInstance * t : gs - > map - > towns )
2017-07-20 06:08:49 +02:00
{
2023-07-23 23:00:37 +02:00
PlayerColor player = t - > tempOwner ;
if ( newWeek ) //first day of week
{
if ( t - > hasBuilt ( BuildingSubID : : PORTAL_OF_SUMMONING ) )
setPortalDwelling ( t , true , ( n . specialWeek = = NewTurn : : PLAGUE ? true : false ) ) ; //set creatures for Portal of Summoning
2017-07-04 13:24:46 +02:00
2023-07-23 23:00:37 +02:00
if ( ! firstTurn )
2023-08-27 00:35:38 +02:00
if ( t - > hasBuilt ( BuildingSubID : : TREASURY ) & & player . isValidPlayer ( ) )
2023-07-23 23:00:37 +02:00
n . res [ player ] [ EGameResID : : GOLD ] + = hadGold . at ( player ) / 10 ; //give 10% of starting gold
2017-01-20 16:48:45 +02:00
2023-07-23 23:00:37 +02:00
if ( ! vstd : : contains ( n . cres , t - > id ) )
2017-01-29 15:45:36 +02:00
{
2023-07-23 23:00:37 +02:00
n . cres [ t - > id ] . tid = t - > id ;
n . cres [ t - > id ] . creatures = t - > creatures ;
2017-01-29 15:45:36 +02:00
}
2023-07-23 23:00:37 +02:00
auto & sac = n . cres . at ( t - > id ) ;
2014-03-17 22:51:07 +03:00
2024-08-05 21:15:47 +02:00
for ( int k = 0 ; k < t - > town - > creatures . size ( ) ; k + + ) //creature growths
2023-07-23 23:00:37 +02:00
{
if ( ! t - > creatures . at ( k ) . second . empty ( ) ) // there are creatures at this level
{
ui32 & availableCount = sac . creatures . at ( k ) . first ;
2023-12-31 23:43:35 +02:00
const CCreature * cre = t - > creatures . at ( k ) . second . back ( ) . toCreature ( ) ;
2022-09-19 16:45:48 +02:00
2023-07-23 23:00:37 +02:00
if ( n . specialWeek = = NewTurn : : PLAGUE )
availableCount = t - > creatures . at ( k ) . first / 2 ; //halve their number, no growth
else
{
if ( firstTurn ) //first day of game: use only basic growths
availableCount = cre - > getGrowth ( ) ;
else
availableCount + = t - > creatureGrowth ( k ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//Deity of fire week - upgrade both imps and upgrades
if ( n . specialWeek = = NewTurn : : DEITYOFFIRE & & vstd : : contains ( t - > creatures . at ( k ) . second , n . creatureid ) )
availableCount + = 15 ;
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
2023-07-23 23:00:37 +02:00
if ( cre - > getId ( ) = = n . creatureid ) //bonus week, effect applies only to identical creatures
{
if ( n . specialWeek = = NewTurn : : DOUBLE_GROWTH )
availableCount * = 2 ;
else if ( n . specialWeek = = NewTurn : : BONUS_GROWTH )
availableCount + = 5 ;
}
}
}
}
}
2023-08-27 00:35:38 +02:00
if ( ! firstTurn & & player . isValidPlayer ( ) ) //not the first day and town not neutral
2023-07-23 23:00:37 +02:00
{
n . res [ player ] = n . res [ player ] + t - > dailyIncome ( ) ;
}
if ( t - > hasBuilt ( BuildingID : : GRAIL )
& & t - > town - > buildings . at ( BuildingID : : GRAIL ) - > height = = CBuilding : : HEIGHT_SKYSHIP )
{
// Skyship, probably easier to handle same as Veil of darkness
//do it every new day after veils apply
if ( player ! = PlayerColor : : NEUTRAL ) //do not reveal fow for neutral player
{
FoWChange fw ;
2023-10-04 14:31:42 +02:00
fw . mode = ETileVisibility : : REVEALED ;
2023-07-23 23:00:37 +02:00
fw . player = player ;
// find all hidden tiles
2023-11-13 12:09:55 +02:00
const auto & fow = getPlayerTeam ( player ) - > fogOfWarMap ;
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
2024-05-07 21:17:05 +02:00
auto shape = fow . shape ( ) ;
2023-07-23 23:00:37 +02:00
for ( size_t z = 0 ; z < shape [ 0 ] ; z + + )
for ( size_t x = 0 ; x < shape [ 1 ] ; x + + )
for ( size_t y = 0 ; y < shape [ 2 ] ; y + + )
2024-05-07 21:17:05 +02:00
if ( ! fow [ z ] [ x ] [ y ] )
2023-07-23 23:00:37 +02:00
fw . tiles . insert ( int3 ( x , y , z ) ) ;
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
2023-07-23 23:00:37 +02:00
sendAndApply ( & fw ) ;
}
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
}
2023-07-23 23:00:37 +02:00
if ( t - > hasBonusOfType ( BonusType : : DARKNESS ) )
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
{
2023-07-23 23:00:37 +02:00
for ( auto & player : gs - > players )
{
if ( getPlayerStatus ( player . first ) = = EPlayerStatus : : INGAME & &
getPlayerRelations ( player . first , t - > tempOwner ) = = PlayerRelations : : ENEMIES )
2024-05-10 18:48:44 +02:00
changeFogOfWar ( t - > getSightCenter ( ) , t - > getFirstBonus ( Selector : : type ( ) ( BonusType : : DARKNESS ) ) - > val , player . first , ETileVisibility : : HIDDEN ) ;
2023-07-23 23:00:37 +02: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
}
}
2024-06-01 18:38:41 +02:00
if ( newWeek )
n . newRumor = gameState ( ) - > pickNewRumor ( ) ;
2023-07-23 23:00:37 +02:00
if ( newMonth )
2017-05-31 08:45:26 +02:00
{
2023-07-23 23:00:37 +02:00
SetAvailableArtifacts saa ;
2023-11-06 18:27:16 +02:00
saa . id = ObjectInstanceID : : NONE ;
2023-07-23 23:00:37 +02:00
pickAllowedArtsSet ( saa . arts , getRandomGenerator ( ) ) ;
sendAndApply ( & saa ) ;
2022-10-04 22:51:34 +02:00
}
2023-07-23 23:00:37 +02:00
sendAndApply ( & n ) ;
2016-08-30 00:11:54 +02:00
2023-07-23 23:00:37 +02:00
if ( newWeek )
2018-01-05 19:21:07 +02:00
{
2023-07-23 23:00:37 +02:00
//spawn wandering monsters
if ( newMonth & & ( n . specialWeek = = NewTurn : : DOUBLE_GROWTH | | n . specialWeek = = NewTurn : : DEITYOFFIRE ) )
{
spawnWanderingMonsters ( n . creatureid ) ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//new week info popup
if ( ! firstTurn )
{
InfoWindow iw ;
switch ( n . specialWeek )
{
case NewTurn : : DOUBLE_GROWTH :
iw . text . appendLocalString ( EMetaText : : ARRAY_TXT , 131 ) ;
2023-11-02 22:01:49 +02:00
iw . text . replaceNameSingular ( n . creatureid ) ;
iw . text . replaceNameSingular ( n . creatureid ) ;
2023-07-23 23:00:37 +02:00
break ;
case NewTurn : : PLAGUE :
iw . text . appendLocalString ( EMetaText : : ARRAY_TXT , 132 ) ;
break ;
case NewTurn : : BONUS_GROWTH :
iw . text . appendLocalString ( EMetaText : : ARRAY_TXT , 134 ) ;
2023-11-02 22:01:49 +02:00
iw . text . replaceNameSingular ( n . creatureid ) ;
iw . text . replaceNameSingular ( n . creatureid ) ;
2023-07-23 23:00:37 +02:00
break ;
case NewTurn : : DEITYOFFIRE :
iw . text . appendLocalString ( EMetaText : : ARRAY_TXT , 135 ) ;
2023-11-02 22:01:49 +02:00
iw . text . replaceNameSingular ( CreatureID : : IMP ) ; //%s imp
iw . text . replaceNameSingular ( CreatureID : : IMP ) ; //%s imp
iw . text . replacePositiveNumber ( 15 ) ; //%+d 15
iw . text . replaceNameSingular ( CreatureID : : FAMILIAR ) ; //%s familiar
iw . text . replacePositiveNumber ( 15 ) ; //%+d 15
2023-07-23 23:00:37 +02:00
break ;
default :
if ( newMonth )
{
iw . text . appendLocalString ( EMetaText : : ARRAY_TXT , ( 130 ) ) ;
iw . text . replaceLocalString ( EMetaText : : ARRAY_TXT , getRandomGenerator ( ) . nextInt ( 32 , 41 ) ) ;
}
else
{
iw . text . appendLocalString ( EMetaText : : ARRAY_TXT , ( 133 ) ) ;
iw . text . replaceLocalString ( EMetaText : : ARRAY_TXT , getRandomGenerator ( ) . nextInt ( 43 , 57 ) ) ;
}
}
for ( auto & elem : gs - > players )
{
iw . player = elem . first ;
sendAndApply ( & iw ) ;
}
}
2017-05-31 08:45:26 +02:00
}
2018-01-05 19:21:07 +02:00
2023-08-24 21:02:24 +02:00
if ( ! firstTurn )
checkVictoryLossConditionsForAll ( ) ; // check for map turn limit
2023-07-23 23:00:37 +02:00
logGlobal - > trace ( " Info about turn %d has been sent! " , n . day ) ;
//call objects
for ( auto & elem : gs - > map - > objects )
2015-02-14 21:42:47 +02:00
{
2023-07-23 23:00:37 +02:00
if ( elem )
elem - > newTurn ( getRandomGenerator ( ) ) ;
2015-02-14 21:42:47 +02:00
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
synchronizeArtifactHandlerLists ( ) ; //new day events may have changed them. TODO better of managing that
2024-07-27 02:11:26 +02:00
addStatistics ( ) ;
2014-03-17 22:51:07 +03:00
}
2023-08-22 17:45:13 +02:00
2023-11-18 16:34:18 +02:00
void CGameHandler : : start ( bool resume )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
LOG_TRACE_PARAMS ( logGlobal , " resume=%d " , resume ) ;
2014-03-17 22:51:07 +03:00
2023-11-18 16:34:18 +02:00
for ( auto cc : lobby - > activeConnections )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
auto players = lobby - > getAllClientPlayers ( cc - > connectionID ) ;
std : : stringstream sbuffer ;
sbuffer < < " Connection " < < cc - > connectionID < < " will handle " < < players . size ( ) < < " player: " ;
for ( PlayerColor color : players )
{
sbuffer < < color < < " " ;
2024-02-03 17:04:14 +02:00
connections [ color ] . insert ( cc ) ;
2023-07-23 23:00:37 +02:00
}
logGlobal - > info ( sbuffer . str ( ) ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
# if SCRIPTING_ENABLED
services ( ) - > scripts ( ) - > run ( serverScripts ) ;
# endif
2017-07-01 10:34:00 +02:00
2023-08-22 17:45:13 +02:00
if ( ! resume )
2014-03-17 22:51:07 +03:00
{
2023-08-22 17:45:13 +02:00
onNewTurn ( ) ;
events : : TurnStarted : : defaultExecute ( serverEventBus . get ( ) ) ;
for ( auto & player : gs - > players )
2024-04-26 12:15:39 +02:00
turnTimerHandler - > onGameplayStart ( player . first ) ;
2014-03-17 22:51:07 +03:00
}
2023-08-22 17:45:13 +02:00
else
events : : GameResumed : : defaultExecute ( serverEventBus . get ( ) ) ;
2014-03-17 22:51:07 +03:00
2023-08-22 17:45:13 +02:00
turnOrder - > onGameStarted ( ) ;
2023-11-18 16:34:18 +02:00
}
2015-10-28 22:53:44 +02:00
2023-11-18 16:34:18 +02:00
void CGameHandler : : tick ( int millisecondsPassed )
{
2024-04-26 12:15:39 +02:00
turnTimerHandler - > update ( millisecondsPassed ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
void CGameHandler : : giveSpells ( const CGTownInstance * t , const CGHeroInstance * h )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ! h - > hasSpellbook ( ) )
return ; //hero hasn't spellbook
ChangeSpells cs ;
cs . hid = h - > id ;
cs . learn = true ;
if ( t - > hasBuilt ( BuildingID : : GRAIL , ETownType : : CONFLUX ) & & t - > hasBuilt ( BuildingID : : MAGES_GUILD_1 ) )
2023-03-25 21:29:33 +02:00
{
2023-07-23 23:00:37 +02:00
// Aurora Borealis give spells of all levels even if only level 1 mages guild built
for ( int i = 0 ; i < h - > maxSpellLevel ( ) ; i + + )
{
std : : vector < SpellID > spells ;
getAllowedSpells ( spells , i + 1 ) ;
for ( auto & spell : spells )
cs . spells . insert ( spell ) ;
}
2023-03-25 21:29:33 +02:00
}
2023-07-23 23:00:37 +02:00
else
{
for ( int i = 0 ; i < std : : min ( t - > mageGuildLevel ( ) , h - > maxSpellLevel ( ) ) ; i + + )
{
for ( int j = 0 ; j < t - > spellsAtLevel ( i + 1 , true ) & & j < t - > spells . at ( i ) . size ( ) ; j + + )
{
if ( ! h - > spellbookContainsSpell ( t - > spells . at ( i ) . at ( j ) ) )
cs . spells . insert ( t - > spells . at ( i ) . at ( j ) ) ;
}
}
}
if ( ! cs . spells . empty ( ) )
sendAndApply ( & cs ) ;
2014-03-17 22:51:07 +03:00
}
2023-09-18 21:09:55 +02:00
bool CGameHandler : : removeObject ( const CGObjectInstance * obj , const PlayerColor & initiator )
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
{
2023-07-23 23:00:37 +02:00
if ( ! obj | | ! getObj ( obj - > id ) )
{
logGlobal - > error ( " Something wrong, that object already has been removed or hasn't existed! " ) ;
return false ;
}
RemoveObject ro ;
2023-09-18 21:09:55 +02:00
ro . objectID = obj - > id ;
ro . initiator = initiator ;
2023-07-23 23:00:37 +02:00
sendAndApply ( & ro ) ;
checkVictoryLossConditionsForAll ( ) ; //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
return true ;
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
}
2024-05-07 22:05:23 +02:00
bool CGameHandler : : moveHero ( ObjectInstanceID hid , int3 dst , EMovementMode movementMode , bool transit , PlayerColor asker )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CGHeroInstance * h = getHero ( hid ) ;
// not turn of that hero or player can't simply teleport hero (at least not with this function)
2024-05-07 22:05:23 +02:00
if ( ! h | | ( asker ! = PlayerColor : : NEUTRAL & & movementMode ! = EMovementMode : : STANDARD ) )
2014-03-17 22:51:07 +03:00
{
2023-08-21 22:54:38 +02:00
if ( h & & getStartInfo ( ) - > turnTimerInfo . isEnabled ( ) & & gs - > players [ h - > getOwner ( ) ] . turnTimer . turnTimer = = 0 )
return true ; //timer expired, no error
2023-07-23 23:00:37 +02:00
logGlobal - > error ( " Illegal call to move hero! " ) ;
return false ;
2014-03-17 22:51:07 +03:00
}
2023-09-04 21:21:02 +02:00
logGlobal - > trace ( " Player %d (%s) wants to move hero %d from %s to %s " , asker , asker . toString ( ) , hid . getNum ( ) , h - > pos . toString ( ) , dst . toString ( ) ) ;
2023-07-23 23:00:37 +02:00
const int3 hmpos = h - > convertToVisitablePos ( dst ) ;
2014-04-10 20:11:09 +03:00
2023-07-23 23:00:37 +02:00
if ( ! gs - > map - > isInTheMap ( hmpos ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
logGlobal - > error ( " Destination tile is outside the map! " ) ;
return false ;
2014-03-17 22:51:07 +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
2023-07-23 23:00:37 +02:00
const TerrainTile t = * getTile ( hmpos ) ;
const int3 guardPos = gs - > guardingCreaturePosition ( hmpos ) ;
2023-09-19 23:05:11 +02:00
CGObjectInstance * objectToVisit = nullptr ;
CGObjectInstance * guardian = nullptr ;
2014-03-17 22:51:07 +03:00
2023-09-19 23:05:11 +02:00
if ( ! t . visitableObjects . empty ( ) )
objectToVisit = t . visitableObjects . back ( ) ;
if ( isInTheMap ( guardPos ) )
2024-06-04 16:13:12 +02:00
{
for ( auto const & object : getTile ( guardPos ) - > visitableObjects )
if ( object - > ID = = MapObjectID : : MONSTER ) // exclude other objects, such as hero flying above monster
guardian = object ;
}
2023-09-19 23:05:11 +02:00
const bool embarking = ! h - > boat & & objectToVisit & & objectToVisit - > ID = = Obj : : BOAT ;
2023-07-23 23:00:37 +02:00
const bool disembarking = h - > boat
& & t . terType - > isLand ( )
& & ( dst = = h - > pos
| | ( h - > boat - > layer = = EPathfindingLayer : : SAIL & & ! t . blocked ) ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//result structure for start - movement failed, no move points used
TryMoveHero tmh ;
tmh . id = hid ;
tmh . start = h - > pos ;
tmh . end = dst ;
tmh . result = TryMoveHero : : FAILED ;
tmh . movePoints = h - > movementPointsRemaining ( ) ;
2023-06-16 18:12:07 +02:00
2023-07-23 23:00:37 +02:00
//check if destination tile is available
auto pathfinderHelper = std : : make_unique < CPathfinderHelper > ( gs , h , PathfinderOptions ( ) ) ;
auto ti = pathfinderHelper - > getTurnInfo ( ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
const bool canFly = pathfinderHelper - > hasBonusOfType ( BonusType : : FLYING_MOVEMENT ) | | ( h - > boat & & h - > boat - > layer = = EPathfindingLayer : : AIR ) ;
const bool canWalkOnSea = pathfinderHelper - > hasBonusOfType ( BonusType : : WATER_WALKING ) | | ( h - > boat & & h - > boat - > layer = = EPathfindingLayer : : WATER ) ;
const int cost = pathfinderHelper - > getMovementCost ( h - > visitablePos ( ) , hmpos , nullptr , nullptr , h - > movementPointsRemaining ( ) ) ;
2014-03-17 22:51:07 +03:00
2023-09-27 20:25:04 +02:00
const bool movingOntoObstacle = t . blocked & & ! t . visitable ;
const bool objectCoastVisitable = objectToVisit & & objectToVisit - > isCoastVisitable ( ) ;
const bool movingOntoWater = ! h - > boat & & t . terType - > isWater ( ) & & ! objectCoastVisitable ;
2023-09-18 21:09:55 +02:00
const auto complainRet = [ & ] ( const std : : string & message )
{
2014-03-17 22:51:07 +03:00
//send info about movement failure
2023-09-16 19:07:02 +02:00
complain ( message ) ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & tmh ) ;
return false ;
2023-09-16 19:07:02 +02:00
} ;
2023-09-26 14:55:07 +02:00
if ( guardian & & getVisitingHero ( guardian ) ! = nullptr )
2024-01-13 19:40:19 +02:00
return complainRet ( " You cannot move your hero there. Simultaneous turns are active and another player is interacting with this wandering monster! " ) ;
2023-09-19 23:05:11 +02:00
2023-12-10 19:18:15 +02:00
if ( objectToVisit & & getVisitingHero ( objectToVisit ) ! = nullptr & & getVisitingHero ( objectToVisit ) ! = h )
2024-01-13 19:40:19 +02:00
return complainRet ( " You cannot move your hero there. Simultaneous turns are active and another player is interacting with this map object! " ) ;
2023-09-19 23:05:11 +02:00
if ( objectToVisit & &
objectToVisit - > getOwner ( ) . isValidPlayer ( ) & &
getPlayerRelations ( objectToVisit - > getOwner ( ) , h - > getOwner ( ) ) = = PlayerRelations : : ENEMIES & &
! turnOrder - > isContactAllowed ( objectToVisit - > getOwner ( ) , h - > getOwner ( ) ) )
2024-01-13 19:40:19 +02:00
return complainRet ( " You cannot move your hero there. This object belongs to another player and simultaneous turns are still active! " ) ;
2023-09-19 23:05:11 +02:00
2023-09-16 19:07:02 +02:00
//it's a rock or blocked and not visitable tile
//OR hero is on land and dest is water and (there is not present only one object - boat)
2023-09-27 20:25:04 +02:00
if ( ! t . terType - > isPassable ( ) | | ( movingOntoObstacle & & ! canFly ) )
2023-09-26 14:55:07 +02:00
return complainRet ( " Cannot move hero, destination tile is blocked! " ) ;
2023-09-16 19:07:02 +02:00
//hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276)
2023-09-27 20:25:04 +02:00
if ( movingOntoWater & & ! canFly & & ! canWalkOnSea )
2023-09-26 14:55:07 +02:00
return complainRet ( " Cannot move hero, destination tile is on water! " ) ;
2023-09-16 19:07:02 +02:00
if ( h - > boat & & h - > boat - > layer = = EPathfindingLayer : : SAIL & & t . terType - > isLand ( ) & & t . blocked )
2023-09-26 14:55:07 +02:00
return complainRet ( " Cannot disembark hero, tile is blocked! " ) ;
2023-09-16 19:07:02 +02:00
2024-05-07 22:05:23 +02:00
if ( distance ( h - > pos , dst ) > = 1.5 & & movementMode = = EMovementMode : : STANDARD )
2023-09-26 14:55:07 +02:00
return complainRet ( " Tiles are not neighboring! " ) ;
2023-09-16 19:07:02 +02:00
if ( h - > inTownGarrison )
2023-09-26 14:55:07 +02:00
return complainRet ( " Can not move garrisoned hero! " ) ;
2023-09-16 19:07:02 +02:00
2024-05-07 22:05:23 +02:00
if ( h - > movementPointsRemaining ( ) < cost & & dst ! = h - > pos & & movementMode = = EMovementMode : : STANDARD )
2023-09-26 14:55:07 +02:00
return complainRet ( " Hero doesn't have any movement points left! " ) ;
2023-09-16 19:07:02 +02:00
2023-09-20 21:48:48 +02:00
if ( transit & & ! canFly & & ! ( canWalkOnSea & & t . terType - > isWater ( ) ) & & ! CGTeleport : : isTeleport ( objectToVisit ) )
2023-09-26 14:55:07 +02:00
return complainRet ( " Hero cannot transit over this tile! " ) ;
2014-03-17 22:51:07 +03:00
//several generic blocks of code
// should be called if hero changes tile but before applying TryMoveHero package
auto leaveTile = [ & ] ( )
{
2016-10-12 17:16:26 +02:00
for ( CGObjectInstance * obj : gs - > map - > getTile ( int3 ( h - > pos . x - 1 , h - > pos . y , h - > pos . z ) ) . visitableObjects )
2014-03-17 22:51:07 +03:00
{
obj - > onHeroLeave ( h ) ;
}
2023-10-04 14:31:42 +02:00
this - > getTilesInRange ( tmh . fowRevealed , h - > getSightCenter ( ) + ( tmh . end - tmh . start ) , h - > getSightRadius ( ) , ETileVisibility : : HIDDEN , h - > tempOwner ) ;
2014-03-17 22:51:07 +03:00
} ;
auto doMove = [ & ] ( TryMoveHero : : EResult result , EGuardLook lookForGuards ,
EVisitDest visitDest , ELEaveTile leavingTile ) - > bool
{
2023-01-02 13:27:03 +02:00
LOG_TRACE_PARAMS ( logGlobal , " Hero %s starts movement from %s to %s " , h - > getNameTranslated ( ) % tmh . start . toString ( ) % tmh . end . toString ( ) ) ;
2014-03-17 22:51:07 +03:00
2017-06-06 06:53:51 +02:00
auto moveQuery = std : : make_shared < CHeroMovementQuery > ( this , tmh , h ) ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( moveQuery ) ;
2014-03-17 22:51:07 +03:00
2016-10-12 17:16:26 +02:00
if ( leavingTile = = LEAVING_TILE )
2014-03-17 22:51:07 +03:00
leaveTile ( ) ;
2024-02-07 20:27:02 +02:00
if ( lookForGuards = = CHECK_FOR_GUARDS & & isInTheMap ( guardPos ) )
2023-04-16 19:42:56 +02:00
tmh . attackedFrom = std : : make_optional ( guardPos ) ;
2023-03-31 21:42:32 +02:00
2014-03-17 22:51:07 +03:00
tmh . result = result ;
sendAndApply ( & tmh ) ;
2023-09-20 21:48:48 +02:00
if ( visitDest = = VISIT_DEST & & objectToVisit & & objectToVisit - > id = = h - > id )
2024-04-01 20:18:35 +02:00
{ // Hero should be always able to visit any object he is staying on even if there are guards around
2015-02-22 19:12:49 +02:00
visitObjectOnTile ( t , h ) ;
}
2023-03-31 21:42:32 +02:00
else if ( lookForGuards = = CHECK_FOR_GUARDS & & isInTheMap ( guardPos ) )
2014-03-17 22:51:07 +03:00
{
2023-09-19 23:05:11 +02:00
objectVisited ( guardian , h ) ;
2014-03-17 22:51:07 +03:00
moveQuery - > visitDestAfterVictory = visitDest = = VISIT_DEST ;
}
2016-10-12 17:16:26 +02:00
else if ( visitDest = = VISIT_DEST )
2014-03-17 22:51:07 +03:00
{
2015-11-05 09:02:13 +02:00
visitObjectOnTile ( t , h ) ;
2014-03-17 22:51:07 +03:00
}
2023-09-02 01:17:51 +02:00
queries - > popIfTop ( moveQuery ) ;
2023-01-02 13:27:03 +02:00
logGlobal - > trace ( " Hero %s ends movement " , h - > getNameTranslated ( ) ) ;
2014-03-17 22:51:07 +03:00
return result ! = TryMoveHero : : FAILED ;
} ;
//interaction with blocking object (like resources)
auto blockingVisit = [ & ] ( ) - > bool
{
2016-10-12 17:16:26 +02:00
for ( CGObjectInstance * obj : t . visitableObjects )
2014-03-17 22:51:07 +03:00
{
2023-06-21 15:49:44 +02:00
if ( h - > boat & & ! obj - > isBlockedVisitable ( ) & & ! h - > boat - > onboardVisitAllowed )
2023-04-19 00:11:24 +02:00
return doMove ( TryMoveHero : : SUCCESS , this - > IGNORE_GUARDS , DONT_VISIT_DEST , REMAINING_ON_TILE ) ;
2023-06-21 15:49:44 +02:00
if ( obj ! = h & & obj - > isBlockedVisitable ( ) & & ! obj - > passableFor ( h - > tempOwner ) )
2014-03-17 22:51:07 +03:00
{
2023-04-19 00:11:24 +02:00
EVisitDest visitDest = VISIT_DEST ;
if ( h - > boat & & ! h - > boat - > onboardVisitAllowed )
visitDest = DONT_VISIT_DEST ;
return doMove ( TryMoveHero : : BLOCKING_VISIT , this - > IGNORE_GUARDS , visitDest , REMAINING_ON_TILE ) ;
2014-03-17 22:51:07 +03:00
}
}
return false ;
} ;
2016-10-12 17:16:26 +02:00
if ( ! transit & & embarking )
2014-03-17 22:51:07 +03:00
{
2023-06-21 19:38:26 +02:00
tmh . movePoints = h - > movementPointsAfterEmbark ( h - > movementPointsRemaining ( ) , cost , false , ti ) ;
2014-03-17 22:51:07 +03:00
return doMove ( TryMoveHero : : EMBARK , IGNORE_GUARDS , DONT_VISIT_DEST , LEAVING_TILE ) ;
2015-12-15 18:13:55 +02:00
// In H3 embark ignore guards
2014-03-17 22:51:07 +03:00
}
2016-10-12 17:16:26 +02:00
if ( disembarking )
2014-03-17 22:51:07 +03:00
{
2023-06-21 19:38:26 +02:00
tmh . movePoints = h - > movementPointsAfterEmbark ( h - > movementPointsRemaining ( ) , cost , true , ti ) ;
2014-03-17 22:51:07 +03:00
return doMove ( TryMoveHero : : DISEMBARK , CHECK_FOR_GUARDS , VISIT_DEST , LEAVING_TILE ) ;
}
2024-05-07 22:05:23 +02:00
if ( movementMode ! = EMovementMode : : STANDARD )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( blockingVisit ( ) ) // e.g. hero on the other side of teleporter
2014-03-17 22:51:07 +03:00
return true ;
2024-05-07 22:05:23 +02:00
EGuardLook guardsCheck = ( VLC - > settings ( ) - > getBoolean ( EGameSettings : : DIMENSION_DOOR_TRIGGERS_GUARDS ) & & movementMode = = EMovementMode : : DIMENSION_DOOR )
2024-04-01 19:00:24 +02:00
? CHECK_FOR_GUARDS
: IGNORE_GUARDS ;
doMove ( TryMoveHero : : TELEPORTATION , guardsCheck , DONT_VISIT_DEST , LEAVING_TILE ) ;
2014-03-17 22:51:07 +03:00
// visit town for town portal \ castle gates
// do not use generic visitObjectOnTile to avoid double-teleporting
// if this moveHero call was triggered by teleporter
2023-09-19 23:05:11 +02:00
if ( objectToVisit )
2014-03-17 22:51:07 +03:00
{
2023-09-19 23:05:11 +02:00
if ( CGTownInstance * town = dynamic_cast < CGTownInstance * > ( objectToVisit ) )
2014-03-17 22:51:07 +03:00
town - > onHeroVisit ( h ) ;
}
return true ;
}
2023-04-19 00:11:24 +02:00
2014-03-17 22:51:07 +03:00
//still here? it is standard movement!
{
2023-06-21 19:38:26 +02:00
tmh . movePoints = ( int ) h - > movementPointsRemaining ( ) > = cost
? h - > movementPointsRemaining ( ) - cost
2014-03-17 22:51:07 +03:00
: 0 ;
2015-11-05 09:02:13 +02:00
EGuardLook lookForGuards = CHECK_FOR_GUARDS ;
EVisitDest visitDest = VISIT_DEST ;
2016-10-12 17:16:26 +02:00
if ( transit )
2015-11-05 09:02:13 +02:00
{
2023-09-20 21:48:48 +02:00
if ( CGTeleport : : isTeleport ( objectToVisit ) )
2015-11-05 09:02:13 +02:00
visitDest = DONT_VISIT_DEST ;
2023-09-18 17:17:26 +02:00
if ( canFly | | ( canWalkOnSea & & t . terType - > isWater ( ) ) )
2015-11-05 09:02:13 +02:00
{
lookForGuards = IGNORE_GUARDS ;
visitDest = DONT_VISIT_DEST ;
}
}
2016-10-12 17:16:26 +02:00
else if ( blockingVisit ( ) )
2014-03-17 22:51:07 +03:00
return true ;
2023-04-19 00:11:24 +02:00
if ( h - > boat & & ! h - > boat - > onboardAssaultAllowed )
2023-09-20 21:48:48 +02:00
lookForGuards = IGNORE_GUARDS ;
2014-03-17 22:51:07 +03:00
2024-04-26 12:15:39 +02:00
turnTimerHandler - > setEndTurnAllowed ( h - > getOwner ( ) , ! movingOntoWater & & ! movingOntoObstacle ) ;
2015-11-05 09:02:13 +02:00
doMove ( TryMoveHero : : SUCCESS , lookForGuards , visitDest , LEAVING_TILE ) ;
2024-08-07 21:26:22 +02:00
gs - > statistic . accumulatedValues [ asker ] . movementPointsUsed + = tmh . movePoints ;
2014-03-17 22:51:07 +03:00
return true ;
}
}
2017-07-15 13:08:20 +02:00
bool CGameHandler : : teleportHero ( ObjectInstanceID hid , ObjectInstanceID dstid , ui8 source , PlayerColor asker )
2014-03-17 22:51:07 +03:00
{
const CGHeroInstance * h = getHero ( hid ) ;
const CGTownInstance * t = getTown ( dstid ) ;
2023-08-25 00:08:48 +02:00
if ( ! h | | ! t )
2016-11-27 21:50:37 +02:00
COMPLAIN_RET ( " Invalid call to teleportHero! " ) ;
2014-03-17 22:51:07 +03:00
const CGTownInstance * from = h - > visitedTown ;
2016-10-12 17:16:26 +02:00
if ( ( ( h - > getOwner ( ) ! = t - > getOwner ( ) )
2014-03-17 22:51:07 +03:00
& & complain ( " Cannot teleport hero to another player " ) )
2020-10-02 23:55:46 +02:00
2023-01-04 15:17:50 +02:00
| | ( from - > town - > faction - > getId ( ) ! = t - > town - > faction - > getId ( )
2020-10-02 23:55:46 +02:00
& & complain ( " Source town and destination town should belong to the same faction " ) )
| | ( ( ! from | | ! from - > hasBuilt ( BuildingSubID : : CASTLE_GATE ) )
2014-03-17 22:51:07 +03:00
& & complain ( " Hero must be in town with Castle gate for teleporting " ) )
2020-10-02 23:55:46 +02:00
| | ( ! t - > hasBuilt ( BuildingSubID : : CASTLE_GATE )
2014-03-17 22:51:07 +03:00
& & complain ( " Cannot teleport hero to town without Castle gate in it " ) ) )
return false ;
2022-12-09 14:42:47 +02:00
int3 pos = h - > convertFromVisitablePos ( t - > visitablePos ( ) ) ;
2024-05-07 22:05:23 +02:00
moveHero ( hid , pos , EMovementMode : : CASTLE_GATE ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2021-03-23 16:47:07 +02:00
void CGameHandler : : setOwner ( const CGObjectInstance * obj , const PlayerColor owner )
2014-03-17 22:51:07 +03:00
{
PlayerColor oldOwner = getOwner ( obj - > id ) ;
2023-11-06 18:27:16 +02:00
setObjPropertyID ( obj - > id , ObjProperty : : OWNER , owner ) ;
2014-03-17 22:51:07 +03:00
2014-10-02 18:43:46 +03:00
std : : set < PlayerColor > playerColors = { owner , oldOwner } ;
2014-03-17 22:51:07 +03:00
checkVictoryLossConditions ( playerColors ) ;
2016-11-27 21:50:37 +02:00
const CGTownInstance * town = dynamic_cast < const CGTownInstance * > ( obj ) ;
if ( town ) //town captured
2014-03-17 22:51:07 +03:00
{
2023-08-27 00:35:38 +02:00
if ( owner . isValidPlayer ( ) ) //new owner is real player
2015-10-24 18:03:00 +02:00
{
2020-10-15 14:03:01 +02:00
if ( town - > hasBuilt ( BuildingSubID : : PORTAL_OF_SUMMONING ) )
2015-10-24 18:03:00 +02:00
setPortalDwelling ( town , true , false ) ;
}
2014-03-17 22:51:07 +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
const PlayerState * p = getPlayerState ( owner ) ;
2014-03-17 22:51:07 +03:00
2016-10-12 17:16:26 +02:00
if ( ( obj - > ID = = Obj : : CREATURE_GENERATOR1 | | obj - > ID = = Obj : : CREATURE_GENERATOR4 ) & & p & & p - > dwellings . size ( ) = = 1 ) //first dwelling captured
2014-03-17 22:51:07 +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
for ( const CGTownInstance * t : getPlayerState ( owner ) - > towns )
2014-03-17 22:51:07 +03:00
{
2020-10-15 14:03:01 +02:00
if ( t - > hasBuilt ( BuildingSubID : : PORTAL_OF_SUMMONING ) )
2014-03-17 22:51:07 +03:00
setPortalDwelling ( t ) ; //set initial creatures for all portals of summoning
}
}
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : showBlockingDialog ( BlockingDialog * iw )
2014-03-17 22:51:07 +03:00
{
2017-06-06 06:53:51 +02:00
auto dialogQuery = std : : make_shared < CBlockingDialogQuery > ( this , * iw ) ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( dialogQuery ) ;
2014-03-17 22:51:07 +03:00
iw - > queryID = dialogQuery - > queryID ;
sendToAllClients ( iw ) ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : showTeleportDialog ( TeleportDialog * iw )
2015-03-08 15:52:50 +02:00
{
2017-06-06 06:53:51 +02:00
auto dialogQuery = std : : make_shared < CTeleportDialogQuery > ( this , * iw ) ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( dialogQuery ) ;
2015-03-08 15:52:50 +02:00
iw - > queryID = dialogQuery - > queryID ;
2014-03-17 22:51:07 +03:00
sendToAllClients ( iw ) ;
}
2023-04-05 02:26:29 +02:00
void CGameHandler : : giveResource ( PlayerColor player , GameResID which , int val ) //TODO: cap according to Bersy's suggestion
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( ! val ) return ; //don't waste time on empty call
2016-11-26 14:14:43 +02:00
TResources resources ;
2024-07-25 02:51:00 +02:00
resources [ which ] = val ;
2016-11-26 14:14:43 +02:00
giveResources ( player , resources ) ;
2014-03-17 22:51:07 +03:00
}
void CGameHandler : : giveResources ( PlayerColor player , TResources resources )
{
2016-11-26 14:14:43 +02:00
SetResources sr ;
sr . abs = false ;
sr . player = player ;
sr . res = resources ;
2016-11-28 04:19:44 +02:00
sendAndApply ( & sr ) ;
2014-03-17 22:51:07 +03:00
}
void CGameHandler : : giveCreatures ( const CArmedInstance * obj , const CGHeroInstance * h , const CCreatureSet & creatures , bool remove )
{
COMPLAIN_RET_IF ( ! creatures . stacksCount ( ) , " Strange, giveCreatures called without args! " ) ;
COMPLAIN_RET_IF ( obj - > stacksCount ( ) , " Cannot give creatures from not-cleared object! " ) ;
COMPLAIN_RET_IF ( creatures . stacksCount ( ) > GameConstants : : ARMY_SIZE , " Too many stacks to give! " ) ;
//first we move creatures to give to make them army of object-source
for ( auto & elem : creatures . Slots ( ) )
{
addToSlot ( StackLocation ( obj , obj - > getSlotFor ( elem . second - > type ) ) , elem . second - > type , elem . second - > count ) ;
}
tryJoiningArmy ( obj , h , remove , true ) ;
}
void CGameHandler : : takeCreatures ( ObjectInstanceID objid , const std : : vector < CStackBasicDescriptor > & creatures )
{
std : : vector < CStackBasicDescriptor > cres = creatures ;
if ( cres . size ( ) < = 0 )
return ;
const CArmedInstance * obj = static_cast < const CArmedInstance * > ( getObj ( objid ) ) ;
2016-10-12 17:16:26 +02:00
for ( CStackBasicDescriptor & sbd : cres )
2014-03-17 22:51:07 +03:00
{
TQuantity collected = 0 ;
while ( collected < sbd . count )
{
bool foundSth = false ;
2016-10-12 17:16:26 +02:00
for ( auto i = obj - > Slots ( ) . begin ( ) ; i ! = obj - > Slots ( ) . end ( ) ; i + + )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( i - > second - > type = = sbd . type )
2014-03-17 22:51:07 +03:00
{
TQuantity take = std : : min ( sbd . count - collected , i - > second - > count ) ; //collect as much cres as we can
changeStackCount ( StackLocation ( obj , i - > first ) , - take , false ) ;
collected + = take ;
foundSth = true ;
break ;
}
}
2016-10-12 17:16:26 +02:00
if ( ! foundSth ) //we went through the whole loop and haven't found appropriate cres
2014-03-17 22:51:07 +03:00
{
complain ( " Unexpected failure during taking creatures! " ) ;
return ;
}
}
}
}
void CGameHandler : : heroVisitCastle ( const CGTownInstance * obj , const CGHeroInstance * hero )
{
2024-08-12 15:06:32 +02:00
if ( obj - > visitingHero ! = hero & & obj - > garrisonHero ! = hero )
{
HeroVisitCastle vc ;
vc . hid = hero - > id ;
vc . tid = obj - > id ;
vc . flags | = 1 ;
sendAndApply ( & vc ) ;
}
2020-09-28 00:32:33 +02:00
visitCastleObjects ( obj , hero ) ;
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
giveSpells ( obj , hero ) ;
2024-01-15 22:29:08 +02:00
if ( obj - > visitingHero & & obj - > garrisonHero )
useScholarSkill ( obj - > visitingHero - > id , obj - > garrisonHero - > id ) ;
2014-03-17 22:51:07 +03:00
checkVictoryLossConditionsForPlayer ( hero - > tempOwner ) ; //transported artifact?
}
2020-09-28 00:32:33 +02:00
void CGameHandler : : visitCastleObjects ( const CGTownInstance * t , const CGHeroInstance * h )
2023-02-12 09:23:39 +02:00
{
2024-08-16 16:49:42 +02:00
for ( auto & building : t - > rewardableBuildings )
building . second - > onHeroVisit ( h ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
void CGameHandler : : stopHeroVisitCastle ( const CGTownInstance * obj , const CGHeroInstance * hero )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
HeroVisitCastle vc ;
vc . hid = hero - > id ;
vc . tid = obj - > id ;
sendAndApply ( & vc ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
void CGameHandler : : removeArtifact ( const ArtifactLocation & al )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
EraseArtifact ea ;
ea . al = al ;
sendAndApply ( & ea ) ;
2014-03-17 22:51:07 +03:00
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : changeSpells ( const CGHeroInstance * hero , bool give , const std : : set < SpellID > & spells )
2014-03-17 22:51:07 +03:00
{
ChangeSpells cs ;
cs . hid = hero - > id ;
cs . spells = spells ;
cs . learn = give ;
sendAndApply ( & cs ) ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : giveHeroBonus ( GiveBonus * bonus )
2014-03-17 22:51:07 +03:00
{
sendAndApply ( bonus ) ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : setMovePoints ( SetMovePoints * smp )
2014-03-17 22:51:07 +03:00
{
sendAndApply ( smp ) ;
}
2024-01-15 22:25:52 +02:00
void CGameHandler : : setMovePoints ( ObjectInstanceID hid , int val , bool absolute )
{
SetMovePoints smp ;
smp . hid = hid ;
smp . val = val ;
smp . absolute = absolute ;
sendAndApply ( & smp ) ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : setManaPoints ( ObjectInstanceID hid , int val )
2014-03-17 22:51:07 +03:00
{
SetMana sm ;
sm . hid = hid ;
sm . val = val ;
2014-11-26 12:30:55 +02:00
sm . absolute = true ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & sm ) ;
}
2023-06-08 09:17:08 +02:00
void CGameHandler : : giveHero ( ObjectInstanceID id , PlayerColor player , ObjectInstanceID boatId )
2014-03-17 22:51:07 +03:00
{
GiveHero gh ;
gh . id = id ;
gh . player = player ;
2023-06-08 09:17:08 +02:00
gh . boatId = boatId ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & gh ) ;
2023-06-07 17:29:07 +02:00
//Reveal fow around new hero, especially released from Prison
auto h = getHero ( id ) ;
2024-05-10 18:48:44 +02:00
changeFogOfWar ( h - > getSightCenter ( ) , h - > getSightRadius ( ) , player , ETileVisibility : : REVEALED ) ;
2014-03-17 22:51:07 +03:00
}
2023-09-18 21:09:55 +02:00
void CGameHandler : : changeObjPos ( ObjectInstanceID objid , int3 newPos , const PlayerColor & initiator )
2014-03-17 22:51:07 +03:00
{
ChangeObjPos cop ;
cop . objid = objid ;
cop . nPos = newPos ;
2023-09-18 21:09:55 +02:00
cop . initiator = initiator ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & cop ) ;
}
void CGameHandler : : useScholarSkill ( ObjectInstanceID fromHero , ObjectInstanceID toHero )
{
const CGHeroInstance * h1 = getHero ( fromHero ) ;
const CGHeroInstance * h2 = getHero ( toHero ) ;
2023-10-05 17:18:14 +02:00
int h1_scholarSpellLevel = h1 - > valOfBonuses ( BonusType : : LEARN_MEETING_SPELL_LIMIT ) ;
int h2_scholarSpellLevel = h2 - > valOfBonuses ( BonusType : : LEARN_MEETING_SPELL_LIMIT ) ;
2014-03-17 22:51:07 +03:00
2020-04-04 11:17:49 +02:00
if ( h1_scholarSpellLevel < h2_scholarSpellLevel )
2014-03-17 22:51:07 +03:00
{
std : : swap ( h1 , h2 ) ; //1st hero need to have higher scholar level for correct message
std : : swap ( fromHero , toHero ) ;
}
2020-04-04 11:17:49 +02:00
int ScholarSpellLevel = std : : max ( h1_scholarSpellLevel , h2_scholarSpellLevel ) ; //heroes can trade up to this level
if ( ! ScholarSpellLevel | | ! h1 - > hasSpellbook ( ) | | ! h2 - > hasSpellbook ( ) )
2014-03-17 22:51:07 +03:00
return ; //no scholar skill or no spellbook
2024-01-10 00:38:54 +02:00
int h1Lvl = std : : min ( ScholarSpellLevel , h1 - > maxSpellLevel ( ) ) ; //heroes can receive these levels
int h2Lvl = std : : min ( ScholarSpellLevel , h2 - > maxSpellLevel ( ) ) ;
2014-03-17 22:51:07 +03:00
ChangeSpells cs1 ;
cs1 . learn = true ;
cs1 . hid = toHero ; //giving spells to first hero
2018-12-20 23:42:31 +02:00
for ( auto it : h1 - > getSpellsInSpellbook ( ) )
2023-08-19 20:13:57 +02:00
if ( h2Lvl > = it . toSpell ( ) - > getLevel ( ) & & ! h2 - > spellbookContainsSpell ( it ) ) //hero can learn it and don't have it yet
2014-03-17 22:51:07 +03:00
cs1 . spells . insert ( it ) ; //spell to learn
ChangeSpells cs2 ;
cs2 . learn = true ;
cs2 . hid = fromHero ;
2018-12-20 23:42:31 +02:00
for ( auto it : h2 - > getSpellsInSpellbook ( ) )
2023-08-19 20:13:57 +02:00
if ( h1Lvl > = it . toSpell ( ) - > getLevel ( ) & & ! h1 - > spellbookContainsSpell ( it ) )
2014-03-17 22:51:07 +03:00
cs2 . spells . insert ( it ) ;
if ( ! cs1 . spells . empty ( ) | | ! cs2 . spells . empty ( ) ) //create a message
{
2023-10-31 11:09:56 +02:00
SecondarySkill scholarSkill = SecondarySkill : : SCHOLAR ;
int scholarSkillLevel = std : : max ( h1 - > getSecSkillLevel ( scholarSkill ) , h2 - > getSecSkillLevel ( scholarSkill ) ) ;
2014-03-17 22:51:07 +03:00
InfoWindow iw ;
iw . player = h1 - > tempOwner ;
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : SEC_SKILL , scholarSkill , scholarSkillLevel ) ;
2014-03-17 22:51:07 +03:00
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 139 ) ; //"%s, who has studied magic extensively,
2024-04-08 17:21:40 +02:00
iw . text . replaceTextID ( h1 - > getNameTextID ( ) ) ;
2014-03-17 22:51:07 +03:00
if ( ! cs2 . spells . empty ( ) ) //if found new spell - apply
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 140 ) ; //learns
2020-10-01 10:38:06 +02:00
int size = static_cast < int > ( cs2 . spells . size ( ) ) ;
2016-10-12 17:16:26 +02:00
for ( auto it : cs2 . spells )
2014-03-17 22:51:07 +03:00
{
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : SPELL , it ) ;
2023-11-02 22:01:49 +02:00
iw . text . appendName ( it ) ;
2014-03-17 22:51:07 +03:00
switch ( size - - )
{
2023-06-17 22:15:55 +02:00
case 2 :
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 141 ) ;
2023-06-17 22:15:55 +02:00
case 1 :
break ;
default :
2023-06-18 11:18:25 +02:00
iw . text . appendRawString ( " , " ) ;
2014-03-17 22:51:07 +03:00
}
}
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 142 ) ; //from %s
2024-04-08 17:21:40 +02:00
iw . text . replaceTextID ( h2 - > getNameTextID ( ) ) ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & cs2 ) ;
}
2016-10-12 17:16:26 +02:00
if ( ! cs1 . spells . empty ( ) & & ! cs2 . spells . empty ( ) )
2014-03-17 22:51:07 +03:00
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 141 ) ; //and
2014-03-17 22:51:07 +03:00
}
if ( ! cs1 . spells . empty ( ) )
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 147 ) ; //teaches
2020-10-01 10:38:06 +02:00
int size = static_cast < int > ( cs1 . spells . size ( ) ) ;
2016-10-12 17:16:26 +02:00
for ( auto it : cs1 . spells )
2014-03-17 22:51:07 +03:00
{
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : SPELL , it ) ;
2023-11-02 22:01:49 +02:00
iw . text . appendName ( it ) ;
2014-03-17 22:51:07 +03:00
switch ( size - - )
{
2023-06-17 22:15:55 +02:00
case 2 :
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 141 ) ;
2023-06-17 22:15:55 +02:00
case 1 :
break ;
default :
2023-06-18 11:18:25 +02:00
iw . text . appendRawString ( " , " ) ;
2023-02-24 13:40:06 +02:00
}
}
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 148 ) ; //from %s
2024-04-08 17:21:40 +02:00
iw . text . replaceTextID ( h2 - > getNameTextID ( ) ) ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & cs1 ) ;
}
sendAndApply ( & iw ) ;
}
}
void CGameHandler : : heroExchange ( ObjectInstanceID hero1 , ObjectInstanceID hero2 )
{
2024-01-10 00:38:54 +02:00
auto h1 = getHero ( hero1 ) ;
auto h2 = getHero ( hero2 ) ;
2014-03-17 22:51:07 +03:00
2023-08-19 20:43:50 +02:00
if ( getPlayerRelations ( h1 - > getOwner ( ) , h2 - > getOwner ( ) ) ! = PlayerRelations : : ENEMIES )
2014-03-17 22:51:07 +03:00
{
2017-06-06 06:53:51 +02:00
auto exchange = std : : make_shared < CGarrisonDialogQuery > ( this , h1 , h2 ) ;
2014-03-17 22:51:07 +03:00
ExchangeDialog hex ;
hex . queryID = exchange - > queryID ;
2018-03-10 21:19:55 +02:00
hex . player = h1 - > getOwner ( ) ;
hex . hero1 = hero1 ;
hex . hero2 = hero2 ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & hex ) ;
2018-03-10 21:19:55 +02:00
2014-03-17 22:51:07 +03:00
useScholarSkill ( hero1 , hero2 ) ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( exchange ) ;
2014-03-17 22:51:07 +03:00
}
}
2018-02-25 18:16:15 +02:00
void CGameHandler : : sendToAllClients ( CPackForClient * pack )
2014-03-17 22:51:07 +03:00
{
2018-02-25 18:16:15 +02:00
logNetwork - > trace ( " \t Sending to all clients: %s " , typeid ( * pack ) . name ( ) ) ;
2023-11-18 16:34:18 +02:00
for ( auto c : lobby - > activeConnections )
2018-02-25 18:16:15 +02:00
c - > sendPack ( pack ) ;
2014-03-17 22:51:07 +03:00
}
2018-02-25 18:16:15 +02:00
void CGameHandler : : sendAndApply ( CPackForClient * pack )
2014-03-17 22:51:07 +03:00
{
2018-02-25 18:16:15 +02:00
sendToAllClients ( pack ) ;
gs - > apply ( pack ) ;
logNetwork - > trace ( " \t Applied on gs: %s " , typeid ( * pack ) . name ( ) ) ;
2014-03-17 22:51:07 +03:00
}
2018-02-25 18:16:15 +02:00
void CGameHandler : : sendAndApply ( CGarrisonOperationPack * pack )
2014-03-17 22:51:07 +03:00
{
2018-02-25 18:16:15 +02:00
sendAndApply ( static_cast < CPackForClient * > ( pack ) ) ;
2014-03-17 22:51:07 +03:00
checkVictoryLossConditionsForAll ( ) ;
}
2018-02-25 18:16:15 +02:00
void CGameHandler : : sendAndApply ( SetResources * pack )
2014-03-17 22:51:07 +03:00
{
2018-02-25 18:16:15 +02:00
sendAndApply ( static_cast < CPackForClient * > ( pack ) ) ;
checkVictoryLossConditionsForPlayer ( pack - > player ) ;
2014-03-17 22:51:07 +03:00
}
2018-02-25 18:16:15 +02:00
void CGameHandler : : sendAndApply ( NewStructures * pack )
2014-03-17 22:51:07 +03:00
{
2018-02-25 18:16:15 +02:00
sendAndApply ( static_cast < CPackForClient * > ( pack ) ) ;
checkVictoryLossConditionsForPlayer ( getTown ( pack - > tid ) - > tempOwner ) ;
2014-03-17 22:51:07 +03:00
}
2023-02-12 09:23:39 +02:00
bool CGameHandler : : isPlayerOwns ( CPackForServer * pack , ObjectInstanceID id )
{
2023-08-25 00:08:48 +02:00
return pack - > player = = getOwner ( id ) & & hasPlayerAt ( getOwner ( id ) , pack - > c ) ;
2023-02-12 09:23:39 +02:00
}
void CGameHandler : : throwNotAllowedAction ( CPackForServer * pack )
{
if ( pack - > c )
2023-07-12 20:13:17 +02:00
playerMessages - > sendSystemMessage ( pack - > c , " You are not allowed to perform this action! " ) ;
2023-02-12 09:23:39 +02:00
logNetwork - > error ( " Player is not allowed to perform this action! " ) ;
throw ExceptionNotAllowedAction ( ) ;
}
void CGameHandler : : wrongPlayerMessage ( CPackForServer * pack , PlayerColor expectedplayer )
{
std : : ostringstream oss ;
2023-08-25 00:08:48 +02:00
oss < < " You were identified as player " < < pack - > player < < " while expecting " < < expectedplayer ;
2023-02-12 09:23:39 +02:00
logNetwork - > error ( oss . str ( ) ) ;
2023-07-12 20:13:17 +02:00
2023-02-12 09:23:39 +02:00
if ( pack - > c )
2023-07-12 20:13:17 +02:00
playerMessages - > sendSystemMessage ( pack - > c , oss . str ( ) ) ;
2023-02-12 09:23:39 +02:00
}
2023-08-25 00:08:48 +02:00
void CGameHandler : : throwIfWrongOwner ( CPackForServer * pack , ObjectInstanceID id )
2023-02-12 09:23:39 +02:00
{
if ( ! isPlayerOwns ( pack , id ) )
{
wrongPlayerMessage ( pack , getOwner ( id ) ) ;
throwNotAllowedAction ( pack ) ;
}
}
2024-04-27 11:36:24 +02:00
void CGameHandler : : throwIfPlayerNotActive ( CPackForServer * pack )
{
if ( ! turnOrder - > isPlayerMakingTurn ( pack - > player ) )
throwNotAllowedAction ( pack ) ;
}
2023-08-25 00:08:48 +02:00
void CGameHandler : : throwIfWrongPlayer ( CPackForServer * pack )
{
throwIfWrongPlayer ( pack , pack - > player ) ;
}
void CGameHandler : : throwIfWrongPlayer ( CPackForServer * pack , PlayerColor player )
2023-02-12 09:23:39 +02:00
{
2023-08-25 00:08:48 +02:00
if ( ! hasPlayerAt ( player , pack - > c ) | | pack - > player ! = player )
2023-02-12 09:23:39 +02:00
{
wrongPlayerMessage ( pack , player ) ;
throwNotAllowedAction ( pack ) ;
}
}
void CGameHandler : : throwAndComplain ( CPackForServer * pack , std : : string txt )
{
complain ( txt ) ;
throwNotAllowedAction ( pack ) ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : save ( const std : : string & filename )
2014-03-17 22:51:07 +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
logGlobal - > info ( " Saving to %s " , filename ) ;
2016-01-26 15:51:38 +02:00
const auto stem = FileInfo : : GetPathStem ( filename ) ;
const auto savefname = stem . to_string ( ) + " .vsgm1 " ;
2023-09-01 23:26:14 +02:00
ResourcePath savePath ( stem . to_string ( ) , EResType : : SAVEGAME ) ;
2016-01-26 15:51:38 +02:00
CResourceHandler : : get ( " local " ) - > createResource ( savefname ) ;
2014-03-17 22:51:07 +03:00
try
{
{
2023-09-01 23:26:14 +02:00
CSaveFile save ( * CResourceHandler : : get ( " local " ) - > getResourceName ( savePath ) ) ;
2014-03-17 22:51:07 +03:00
saveCommonState ( save ) ;
2016-08-30 00:11:54 +02:00
logGlobal - > info ( " Saving server state " ) ;
2014-03-17 22:51:07 +03:00
save < < * this ;
}
2016-08-30 00:11:54 +02:00
logGlobal - > info ( " Game has been successfully saved! " ) ;
2014-03-17 22:51:07 +03:00
}
catch ( std : : exception & e )
{
2016-08-30 00:11:54 +02:00
logGlobal - > error ( " Failed to save game: %s " , e . what ( ) ) ;
2014-03-17 22:51:07 +03:00
}
}
2022-09-23 13:02:19 +02:00
bool CGameHandler : : load ( const std : : string & filename )
2014-03-17 22:51:07 +03:00
{
2018-01-05 19:21:07 +02:00
logGlobal - > info ( " Loading from %s " , filename ) ;
const auto stem = FileInfo : : GetPathStem ( filename ) ;
2014-03-17 22:51:07 +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
reinitScripting ( ) ;
2018-01-05 19:21:07 +02:00
try
2017-05-31 08:45:26 +02:00
{
{
2024-01-20 20:34:51 +02:00
CLoadFile lf ( * CResourceHandler : : get ( ) - > getResourceName ( ResourcePath ( stem . to_string ( ) , EResType : : SAVEGAME ) ) , ESerializationVersion : : MINIMAL ) ;
2024-01-16 20:47:09 +02:00
lf . serializer . cb = this ;
2018-01-05 19:21:07 +02:00
loadCommonState ( lf ) ;
logGlobal - > info ( " Loading server state " ) ;
lf > > * this ;
2017-05-31 08:45:26 +02:00
}
2018-01-05 19:21:07 +02:00
logGlobal - > info ( " Game has been successfully loaded! " ) ;
}
2023-07-30 19:12:25 +02:00
catch ( const ModIncompatibility & e )
2018-01-05 19:21:07 +02:00
{
logGlobal - > error ( " Failed to load game: %s " , e . what ( ) ) ;
2023-09-23 00:32:48 +02:00
std : : string errorMsg ;
if ( ! e . whatMissing ( ) . empty ( ) )
{
errorMsg + = VLC - > generaltexth - > translate ( " vcmi.server.errors.modsToEnable " ) + ' \n ' ;
errorMsg + = e . whatMissing ( ) ;
}
if ( ! e . whatExcessive ( ) . empty ( ) )
{
errorMsg + = VLC - > generaltexth - > translate ( " vcmi.server.errors.modsToDisable " ) + ' \n ' ;
errorMsg + = e . whatExcessive ( ) ;
}
2022-09-23 13:20:11 +02:00
lobby - > announceMessage ( errorMsg ) ;
2022-09-23 13:02:19 +02:00
return false ;
}
2023-11-19 19:30:55 +02:00
catch ( const IdentifierResolutionException & e )
{
logGlobal - > error ( " Failed to load game: %s " , e . what ( ) ) ;
MetaString errorMsg ;
errorMsg . appendTextID ( " vcmi.server.errors.unknownEntity " ) ;
errorMsg . replaceRawString ( e . identifierName ) ;
lobby - > announceMessage ( errorMsg . toString ( ) ) ; //FIXME: should be localized on client side
return false ;
}
2022-09-23 13:02:19 +02:00
catch ( const std : : exception & e )
{
logGlobal - > error ( " Failed to load game: %s " , e . what ( ) ) ;
2023-11-19 19:30:55 +02:00
lobby - > announceMessage ( std : : string ( " Failed to load game: " ) + e . what ( ) ) ;
2022-09-23 13:02:19 +02:00
return false ;
2017-05-31 08:45:26 +02:00
}
2024-01-01 16:37:48 +02:00
gs - > preInit ( VLC , this ) ;
2018-01-05 19:21:07 +02:00
gs - > updateOnLoad ( lobby - > si . get ( ) ) ;
2022-09-23 13:02:19 +02:00
return true ;
2017-05-31 08:45:26 +02:00
}
2021-11-28 14:57:38 +02:00
bool CGameHandler : : bulkSplitStack ( SlotID slotSrc , ObjectInstanceID srcOwner , si32 howMany )
{
if ( ! slotSrc . validSlot ( ) & & complain ( complainInvalidSlot ) )
return false ;
const CArmedInstance * army = static_cast < const CArmedInstance * > ( getObjInstance ( srcOwner ) ) ;
const CCreatureSet & creatureSet = * army ;
if ( ( ! vstd : : contains ( creatureSet . stacks , slotSrc ) & & complain ( complainNoCreatures ) )
| | ( howMany < 1 & & complain ( " Invalid split parameter! " ) ) )
{
return false ;
}
auto actualAmount = army - > getStackCount ( slotSrc ) ;
if ( actualAmount < = howMany & & complain ( complainNotEnoughCreatures ) ) // '<=' because it's not intended just for moving a stack
return false ;
auto freeSlots = creatureSet . getFreeSlots ( ) ;
if ( freeSlots . empty ( ) & & complain ( " No empty stacks " ) )
return false ;
BulkRebalanceStacks bulkRS ;
for ( auto slot : freeSlots )
{
RebalanceStacks rs ;
rs . srcArmy = army - > id ;
rs . dstArmy = army - > id ;
rs . srcSlot = slotSrc ;
rs . dstSlot = slot ;
rs . count = howMany ;
bulkRS . moves . push_back ( rs ) ;
actualAmount - = howMany ;
if ( actualAmount < = howMany )
break ;
}
sendAndApply ( & bulkRS ) ;
return true ;
}
bool CGameHandler : : bulkMergeStacks ( SlotID slotSrc , ObjectInstanceID srcOwner )
{
if ( ! slotSrc . validSlot ( ) & & complain ( complainInvalidSlot ) )
return false ;
const CArmedInstance * army = static_cast < const CArmedInstance * > ( getObjInstance ( srcOwner ) ) ;
const CCreatureSet & creatureSet = * army ;
if ( ! vstd : : contains ( creatureSet . stacks , slotSrc ) & & complain ( complainNoCreatures ) )
return false ;
auto actualAmount = creatureSet . getStackCount ( slotSrc ) ;
if ( actualAmount < 1 & & complain ( complainNoCreatures ) )
return false ;
auto currentCreature = creatureSet . getCreature ( slotSrc ) ;
if ( ! currentCreature & & complain ( complainNoCreatures ) )
return false ;
auto creatureSlots = creatureSet . getCreatureSlots ( currentCreature , slotSrc ) ;
if ( ! creatureSlots . size ( ) )
return false ;
BulkRebalanceStacks bulkRS ;
for ( auto slot : creatureSlots )
{
RebalanceStacks rs ;
rs . srcArmy = army - > id ;
rs . dstArmy = army - > id ;
rs . srcSlot = slot ;
rs . dstSlot = slotSrc ;
rs . count = creatureSet . getStackCount ( slot ) ;
bulkRS . moves . push_back ( rs ) ;
}
sendAndApply ( & bulkRS ) ;
return true ;
}
bool CGameHandler : : bulkMoveArmy ( ObjectInstanceID srcArmy , ObjectInstanceID destArmy , SlotID srcSlot )
{
if ( ! srcSlot . validSlot ( ) & & complain ( complainInvalidSlot ) )
return false ;
const CArmedInstance * armySrc = static_cast < const CArmedInstance * > ( getObjInstance ( srcArmy ) ) ;
const CCreatureSet & setSrc = * armySrc ;
if ( ! vstd : : contains ( setSrc . stacks , srcSlot ) & & complain ( complainNoCreatures ) )
return false ;
const CArmedInstance * armyDest = static_cast < const CArmedInstance * > ( getObjInstance ( destArmy ) ) ;
const CCreatureSet & setDest = * armyDest ;
auto freeSlots = setDest . getFreeSlotsQueue ( ) ;
typedef std : : map < SlotID , std : : pair < SlotID , TQuantity > > TRebalanceMap ;
TRebalanceMap moves ;
auto srcQueue = setSrc . getCreatureQueue ( srcSlot ) ; // Exclude srcSlot, it should be moved last
auto slotsLeft = setSrc . stacksCount ( ) ;
auto destMap = setDest . getCreatureMap ( ) ;
TMapCreatureSlot : : key_compare keyComp = destMap . key_comp ( ) ;
while ( ! srcQueue . empty ( ) )
{
auto pair = srcQueue . top ( ) ;
srcQueue . pop ( ) ;
auto currCreature = pair . first ;
auto currSlot = pair . second ;
const auto quantity = setSrc . getStackCount ( currSlot ) ;
TMapCreatureSlot : : iterator lb = destMap . lower_bound ( currCreature ) ;
const bool alreadyExists = ( lb ! = destMap . end ( ) & & ! ( keyComp ( currCreature , lb - > first ) ) ) ;
if ( ! alreadyExists )
{
if ( freeSlots . empty ( ) )
continue ;
auto currFreeSlot = freeSlots . front ( ) ;
freeSlots . pop ( ) ;
destMap . insert ( lb , TMapCreatureSlot : : value_type ( currCreature , currFreeSlot ) ) ;
}
moves . insert ( std : : make_pair ( currSlot , std : : make_pair ( destMap [ currCreature ] , quantity ) ) ) ;
slotsLeft - - ;
}
if ( slotsLeft = = 1 )
{
auto lastCreature = setSrc . getCreature ( srcSlot ) ;
auto slotToMove = SlotID ( ) ;
// Try to find a slot for last creature
if ( destMap . find ( lastCreature ) = = destMap . end ( ) )
{
if ( ! freeSlots . empty ( ) )
slotToMove = freeSlots . front ( ) ;
}
else
{
slotToMove = destMap [ lastCreature ] ;
}
if ( slotToMove ! = SlotID ( ) )
{
const bool needsLastStack = armySrc - > needsLastStack ( ) ;
const auto quantity = setSrc . getStackCount ( srcSlot ) - ( needsLastStack ? 1 : 0 ) ;
2023-12-23 16:32:21 +02:00
if ( quantity > 0 ) //0 may happen when we need last creature and we have exactly 1 amount of that creature - amount of "rest we can transfer" becomes 0
moves . insert ( std : : make_pair ( srcSlot , std : : make_pair ( slotToMove , quantity ) ) ) ;
2021-11-28 14:57:38 +02:00
}
}
BulkRebalanceStacks bulkRS ;
for ( auto & move : moves )
{
RebalanceStacks rs ;
rs . srcArmy = armySrc - > id ;
rs . dstArmy = armyDest - > id ;
rs . srcSlot = move . first ;
rs . dstSlot = move . second . first ;
rs . count = move . second . second ;
bulkRS . moves . push_back ( rs ) ;
}
sendAndApply ( & bulkRS ) ;
return true ;
}
bool CGameHandler : : bulkSmartSplitStack ( SlotID slotSrc , ObjectInstanceID srcOwner )
{
if ( ! slotSrc . validSlot ( ) & & complain ( complainInvalidSlot ) )
return false ;
const CArmedInstance * army = static_cast < const CArmedInstance * > ( getObjInstance ( srcOwner ) ) ;
const CCreatureSet & creatureSet = * army ;
if ( ! vstd : : contains ( creatureSet . stacks , slotSrc ) & & complain ( complainNoCreatures ) )
return false ;
auto actualAmount = creatureSet . getStackCount ( slotSrc ) ;
if ( actualAmount < = 1 & & complain ( complainNoCreatures ) )
return false ;
auto freeSlot = creatureSet . getFreeSlot ( ) ;
auto currentCreature = creatureSet . getCreature ( slotSrc ) ;
if ( freeSlot = = SlotID ( ) & & creatureSet . isCreatureBalanced ( currentCreature ) )
return true ;
auto creatureSlots = creatureSet . getCreatureSlots ( currentCreature , SlotID ( - 1 ) , 1 ) ; // Ignore slots where's only 1 creature, don't ignore slotSrc
TQuantity totalCreatures = 0 ;
for ( auto slot : creatureSlots )
totalCreatures + = creatureSet . getStackCount ( slot ) ;
if ( totalCreatures < = 1 & & complain ( " Total creatures number is invalid " ) )
return false ;
if ( freeSlot ! = SlotID ( ) )
creatureSlots . push_back ( freeSlot ) ;
if ( creatureSlots . empty ( ) & & complain ( " No available slots for smart rebalancing " ) )
return false ;
const auto totalCreatureSlots = creatureSlots . size ( ) ;
const auto rem = totalCreatures % totalCreatureSlots ;
const auto quotient = totalCreatures / totalCreatureSlots ;
// totalCreatures == rem * (quotient + 1) + (totalCreatureSlots - rem) * quotient;
// Proof: r(q+1)+(s-r)q = rq+r+qs-rq = r+qs = total, where total/s = q+r/s
BulkSmartRebalanceStacks bulkSRS ;
if ( freeSlot ! = SlotID ( ) )
{
RebalanceStacks rs ;
rs . srcArmy = rs . dstArmy = army - > id ;
rs . srcSlot = slotSrc ;
rs . dstSlot = freeSlot ;
rs . count = 1 ;
bulkSRS . moves . push_back ( rs ) ;
}
auto currSlot = 0 ;
auto check = 0 ;
for ( auto slot : creatureSlots )
{
ChangeStackCount csc ;
csc . army = army - > id ;
csc . slot = slot ;
csc . count = ( currSlot < rem )
? quotient + 1
: quotient ;
csc . absoluteValue = true ;
bulkSRS . changes . push_back ( csc ) ;
currSlot + + ;
check + = csc . count ;
}
if ( check ! = totalCreatures )
{
complain ( ( boost : : format ( " Failure: totalCreatures=%d but check=%d " ) % totalCreatures % check ) . str ( ) ) ;
return false ;
}
sendAndApply ( & bulkSRS ) ;
return true ;
}
2016-10-12 17:16:26 +02:00
bool CGameHandler : : arrangeStacks ( ObjectInstanceID id1 , ObjectInstanceID id2 , ui8 what , SlotID p1 , SlotID p2 , si32 val , PlayerColor player )
2014-03-17 22:51:07 +03:00
{
2023-09-19 15:31:06 +02:00
const CArmedInstance * s1 = static_cast < const CArmedInstance * > ( getObjInstance ( id1 ) ) ;
const CArmedInstance * s2 = static_cast < const CArmedInstance * > ( getObjInstance ( id2 ) ) ;
const CCreatureSet & S1 = * s1 ;
const CCreatureSet & S2 = * s2 ;
2024-01-10 00:38:54 +02:00
StackLocation sl1 ( s1 , p1 ) ;
StackLocation sl2 ( s2 , p2 ) ;
2023-09-19 15:31:06 +02:00
if ( s1 = = nullptr | | s2 = = nullptr )
{
complain ( " Cannot exchange stacks between non-existing objects!! \n " ) ;
return false ;
}
2016-10-12 17:16:26 +02:00
if ( ! sl1 . slot . validSlot ( ) | | ! sl2 . slot . validSlot ( ) )
2014-03-17 22:51:07 +03:00
{
2021-11-28 14:57:38 +02:00
complain ( complainInvalidSlot ) ;
2014-03-17 22:51:07 +03:00
return false ;
}
2016-10-12 17:16:26 +02:00
if ( ! isAllowedExchange ( id1 , id2 ) )
2014-03-17 22:51:07 +03:00
{
complain ( " Cannot exchange stacks between these two objects! \n " ) ;
return false ;
}
2016-09-19 02:20:44 +02:00
// We can always put stacks into locked garrison, but not take them out of it
auto notRemovable = [ & ] ( const CArmedInstance * army )
{
2016-10-12 17:16:26 +02:00
if ( id1 ! = id2 ) // Stack arrangement inside locked garrison is allowed
2016-09-19 02:20:44 +02:00
{
auto g = dynamic_cast < const CGGarrison * > ( army ) ;
2023-07-23 23:00:37 +02:00
if ( g & & ! g - > removableUnits )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
complain ( " Stacks in this garrison are not removable! \n " ) ;
return true ;
2014-03-17 22:51:07 +03:00
}
}
2021-03-23 16:47:07 +02:00
return false ;
2014-03-17 22:51:07 +03:00
} ;
2023-07-23 23:00:37 +02:00
if ( what = = 1 ) //swap
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ( ( s1 - > tempOwner ! = player & & s1 - > tempOwner ! = PlayerColor : : UNFLAGGABLE ) & & s1 - > getStackCount ( p1 ) )
| | ( ( s2 - > tempOwner ! = player & & s2 - > tempOwner ! = PlayerColor : : UNFLAGGABLE ) & & s2 - > getStackCount ( p2 ) ) )
{
complain ( " Can't take troops from another player! " ) ;
return false ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( sl1 . army = = sl2 . army & & sl1 . slot = = sl2 . slot )
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
{
2023-07-23 23:00:37 +02:00
complain ( " Cannot swap stacks - slots are the same! " ) ;
2014-03-17 22:51:07 +03:00
return false ;
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
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! s1 - > slotEmpty ( p1 ) & & ! s2 - > slotEmpty ( p2 ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( notRemovable ( sl1 . army ) | | notRemovable ( sl2 . army ) )
return false ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
if ( s1 - > slotEmpty ( p1 ) & & notRemovable ( sl2 . army ) )
return false ;
else if ( s2 - > slotEmpty ( p2 ) & & notRemovable ( sl1 . army ) )
return false ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
swapStacks ( sl1 , sl2 ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
else if ( what = = 2 ) //merge
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ( s1 - > getCreature ( p1 ) ! = s2 - > getCreature ( p2 ) & & complain ( " Cannot merge different creatures stacks! " ) )
| | ( ( ( s1 - > tempOwner ! = player & & s1 - > tempOwner ! = PlayerColor : : UNFLAGGABLE ) & & s2 - > getStackCount ( p2 ) ) & & complain ( " Can't take troops from another player! " ) ) )
return false ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( s1 - > slotEmpty ( p1 ) | | s2 - > slotEmpty ( p2 ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
complain ( " Cannot merge empty stack! " ) ;
2014-03-17 22:51:07 +03:00
return false ;
}
2023-07-23 23:00:37 +02:00
else if ( notRemovable ( sl1 . army ) )
return false ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
moveStack ( sl1 , sl2 ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
else if ( what = = 3 ) //split
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const int countToMove = val - s2 - > getStackCount ( p2 ) ;
const int countLeftOnSrc = s1 - > getStackCount ( p1 ) - countToMove ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ( s1 - > tempOwner ! = player & & countLeftOnSrc < s1 - > getStackCount ( p1 ) )
| | ( s2 - > tempOwner ! = player & & val < s2 - > getStackCount ( p2 ) ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
complain ( " Can't move troops of another player! " ) ;
return false ;
}
2017-10-28 01:25:44 +02:00
2023-07-23 23:00:37 +02:00
//general conditions checking
if ( ( ! vstd : : contains ( S1 . stacks , p1 ) & & complain ( complainNoCreatures ) )
| | ( val < 1 & & complain ( complainNoCreatures ) ) )
{
return false ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
if ( vstd : : contains ( S2 . stacks , p2 ) ) //dest. slot not free - it must be "rebalancing"...
{
int total = s1 - > getStackCount ( p1 ) + s2 - > getStackCount ( p2 ) ;
if ( ( total < val & & complain ( " Cannot split that stack, not enough creatures! " ) )
| | ( s1 - > getCreature ( p1 ) ! = s2 - > getCreature ( p2 ) & & complain ( " Cannot rebalance different creatures stacks! " ) )
)
{
return false ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( notRemovable ( sl1 . army ) )
{
if ( s1 - > getStackCount ( p1 ) > countLeftOnSrc )
return false ;
}
else if ( notRemovable ( sl2 . army ) )
{
if ( s2 - > getStackCount ( p1 ) < countLeftOnSrc )
return false ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
moveStack ( sl1 , sl2 , countToMove ) ;
//S2.slots[p2]->count = val;
//S1.slots[p1]->count = total - val;
}
else //split one stack to the two
{
if ( s1 - > getStackCount ( p1 ) < val ) //not enough creatures
{
complain ( complainNotEnoughCreatures ) ;
return false ;
}
2016-03-13 10:31:09 +02:00
2023-07-23 23:00:37 +02:00
if ( notRemovable ( sl1 . army ) )
return false ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
moveStack ( sl1 , sl2 , val ) ;
}
2014-03-17 22:51:07 +03:00
2023-03-18 12:44:01 +02:00
}
2014-03-17 22:51:07 +03:00
return true ;
}
2021-01-14 00:02:13 +02:00
2023-07-23 23:00:37 +02:00
bool CGameHandler : : hasPlayerAt ( PlayerColor player , std : : shared_ptr < CConnection > c ) const
2014-03-17 22:51:07 +03:00
{
2023-12-23 18:11:48 +02:00
return connections . count ( player ) & & connections . at ( player ) . count ( c ) ;
2014-03-17 22:51:07 +03:00
}
2023-09-18 21:09:16 +02:00
bool CGameHandler : : hasBothPlayersAtSameConnection ( PlayerColor left , PlayerColor right ) const
{
2023-12-23 18:11:48 +02:00
return connections . count ( left ) & & connections . count ( right ) & & connections . at ( left ) = = connections . at ( right ) ;
2023-09-18 21:09:16 +02:00
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : disbandCreature ( ObjectInstanceID id , SlotID pos )
{
const CArmedInstance * s1 = static_cast < const CArmedInstance * > ( getObjInstance ( id ) ) ;
if ( ! vstd : : contains ( s1 - > stacks , pos ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
complain ( " Illegal call to disbandCreature - no such stack in army! " ) ;
2014-03-17 22:51:07 +03:00
return false ;
}
2023-07-23 23:00:37 +02:00
eraseStack ( StackLocation ( s1 , pos ) ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : buildStructure ( ObjectInstanceID tid , BuildingID requestedID , bool force )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CGTownInstance * t = getTown ( tid ) ;
if ( ! t )
COMPLAIN_RETF ( " No such town (ID=%s)! " , tid ) ;
if ( ! t - > town - > buildings . count ( requestedID ) )
COMPLAIN_RETF ( " Town of faction %s does not have info about building ID=%s! " , t - > town - > faction - > getNameTranslated ( ) % requestedID ) ;
if ( t - > hasBuilt ( requestedID ) )
COMPLAIN_RETF ( " Building %s is already built in %s " , t - > town - > buildings . at ( requestedID ) - > getNameTranslated ( ) % t - > getNameTranslated ( ) ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
const CBuilding * requestedBuilding = t - > town - > buildings . at ( requestedID ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//Vector with future list of built building and buildings in auto-mode that are not yet built.
std : : vector < const CBuilding * > remainingAutoBuildings ;
std : : set < BuildingID > buildingsThatWillBe ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//Check validity of request
if ( ! force )
{
switch ( requestedBuilding - > mode )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
case CBuilding : : BUILD_NORMAL :
if ( canBuildStructure ( t , requestedID ) ! = EBuildingState : : ALLOWED )
COMPLAIN_RET ( " Cannot build that building! " ) ;
break ;
2023-03-07 13:13:30 +02:00
2023-07-23 23:00:37 +02:00
case CBuilding : : BUILD_AUTO :
case CBuilding : : BUILD_SPECIAL :
COMPLAIN_RET ( " This building can not be constructed normally! " ) ;
case CBuilding : : BUILD_GRAIL :
if ( requestedBuilding - > mode = = CBuilding : : BUILD_GRAIL ) //needs grail
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ! t - > visitingHero | | ! t - > visitingHero - > hasArt ( ArtifactID : : GRAIL ) )
COMPLAIN_RET ( " Cannot build this without grail! " )
else
2023-10-14 21:00:39 +02:00
removeArtifact ( ArtifactLocation ( t - > visitingHero - > id , t - > visitingHero - > getArtPos ( ArtifactID : : GRAIL , false ) ) ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
break ;
}
}
//Performs stuff that has to be done before new building is built
auto processBeforeBuiltStructure = [ t , this ] ( const BuildingID buildingID )
2023-03-07 13:13:30 +02:00
{
2023-07-23 23:00:37 +02:00
if ( buildingID > = BuildingID : : DWELL_FIRST ) //dwelling
2023-03-18 12:44:01 +02:00
{
2024-08-06 23:28:45 +02:00
int level = BuildingID : : getLevelFromDwelling ( buildingID ) ;
int upgradeNumber = BuildingID : : getUpgradedFromDwelling ( buildingID ) ;
2023-03-07 13:13:30 +02:00
2023-07-23 23:00:37 +02:00
if ( upgradeNumber > = t - > town - > creatures . at ( level ) . size ( ) )
2014-03-17 22:51:07 +03:00
{
2024-06-24 03:23:26 +02:00
complain ( boost : : str ( boost : : format ( " Error encountered when building dwelling (bid=%s): "
2023-07-23 23:00:37 +02:00
" no creature found (upgrade number %d, level %d! " )
% buildingID % upgradeNumber % level ) ) ;
2014-03-17 22:51:07 +03:00
return ;
}
2023-07-23 23:00:37 +02:00
2023-12-31 23:43:35 +02:00
const CCreature * crea = t - > town - > creatures . at ( level ) . at ( upgradeNumber ) . toCreature ( ) ;
2023-07-23 23:00:37 +02:00
SetAvailableCreatures ssi ;
ssi . tid = t - > id ;
ssi . creatures = t - > creatures ;
if ( ssi . creatures [ level ] . second . empty ( ) ) // first creature in a dwelling
ssi . creatures [ level ] . first = crea - > getGrowth ( ) ;
ssi . creatures [ level ] . second . push_back ( crea - > getId ( ) ) ;
sendAndApply ( & ssi ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
if ( t - > town - > buildings . at ( buildingID ) - > subId = = BuildingSubID : : PORTAL_OF_SUMMONING )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
setPortalDwelling ( t ) ;
2014-03-17 22:51:07 +03:00
}
2023-03-07 13:13:30 +02:00
} ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//Performs stuff that has to be done after new building is built
auto processAfterBuiltStructure = [ t , this ] ( const BuildingID buildingID )
2022-11-06 23:59:30 +02:00
{
2023-07-23 23:00:37 +02:00
auto isMageGuild = ( buildingID < = BuildingID : : MAGES_GUILD_5 & & buildingID > = BuildingID : : MAGES_GUILD_1 ) ;
auto isLibrary = isMageGuild ? false
: t - > town - > buildings . at ( buildingID ) - > subId = = BuildingSubID : : EBuildingSubID : : LIBRARY ;
2021-03-23 16:47:07 +02:00
2023-10-28 11:27:10 +02:00
if ( isMageGuild | | isLibrary | | ( t - > getFaction ( ) = = ETownType : : CONFLUX & & buildingID = = BuildingID : : GRAIL ) )
2022-11-11 01:01:55 +02:00
{
2023-07-23 23:00:37 +02:00
if ( t - > visitingHero )
giveSpells ( t , t - > visitingHero ) ;
if ( t - > garrisonHero )
giveSpells ( t , t - > garrisonHero ) ;
}
} ;
2021-03-23 16:47:07 +02:00
2023-07-23 23:00:37 +02:00
//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
2024-06-24 03:23:26 +02:00
auto areRequirementsFulfilled = [ & ] ( const BuildingID & buildID )
2022-11-06 23:59:30 +02:00
{
2023-07-23 23:00:37 +02:00
return buildingsThatWillBe . count ( buildID ) ;
} ;
2021-03-23 16:47:07 +02:00
2023-07-23 23:00:37 +02:00
//Init the vectors
for ( auto & build : t - > town - > buildings )
2021-03-23 16:47:07 +02:00
{
2023-07-23 23:00:37 +02:00
if ( t - > hasBuilt ( build . first ) )
2022-11-06 23:59:30 +02:00
{
2023-07-23 23:00:37 +02:00
buildingsThatWillBe . insert ( build . first ) ;
2022-11-10 19:09:37 +02:00
}
2023-07-23 23:00:37 +02:00
else
2022-11-10 19:09:37 +02:00
{
2023-07-23 23:00:37 +02:00
if ( build . second - > mode = = CBuilding : : BUILD_AUTO ) //not built auto building
remainingAutoBuildings . push_back ( build . second ) ;
2022-11-06 23:59:30 +02:00
}
2021-03-23 16:47:07 +02:00
}
2023-07-23 23:00:37 +02:00
//Prepare structure (list of building ids will be filled later)
NewStructures ns ;
ns . tid = tid ;
2024-06-24 03:23:26 +02:00
ns . built = force ? t - > built : ( t - > built + 1 ) ;
2021-03-23 16:47:07 +02:00
2023-07-23 23:00:37 +02:00
std : : queue < const CBuilding * > buildingsToAdd ;
buildingsToAdd . push ( requestedBuilding ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
while ( ! buildingsToAdd . empty ( ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
auto b = buildingsToAdd . front ( ) ;
buildingsToAdd . pop ( ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
ns . bid . insert ( b - > bid ) ;
buildingsThatWillBe . insert ( b - > bid ) ;
remainingAutoBuildings - = b ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
for ( auto autoBuilding : remainingAutoBuildings )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
auto actualRequirements = t - > genBuildingRequirements ( autoBuilding - > bid ) ;
2024-06-24 03:23:26 +02:00
if ( actualRequirements . test ( areRequirementsFulfilled ) )
2023-07-23 23:00:37 +02:00
buildingsToAdd . push ( autoBuilding ) ;
2014-03-17 22:51:07 +03:00
}
}
2023-07-23 23:00:37 +02:00
// FIXME: it's done before NewStructures applied because otherwise town window wont be properly updated on client. That should be actually fixed on client and not on server.
for ( auto builtID : ns . bid )
processBeforeBuiltStructure ( builtID ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//Take cost
if ( ! force )
2024-08-03 19:53:05 +02:00
{
2023-07-23 23:00:37 +02:00
giveResources ( t - > tempOwner , - requestedBuilding - > resources ) ;
2024-08-07 21:26:22 +02:00
gs - > statistic . accumulatedValues [ t - > tempOwner ] . spentResourcesForBuildings + = requestedBuilding - > resources ;
2024-08-03 19:53:05 +02:00
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//We know what has been built, apply changes. Do this as final step to properly update town window
sendAndApply ( & ns ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
for ( auto builtID : ns . bid )
processAfterBuiltStructure ( builtID ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
// now when everything is built - reveal tiles for lookout tower
2023-10-04 14:31:42 +02:00
changeFogOfWar ( t - > getSightCenter ( ) , t - > getSightRadius ( ) , t - > getOwner ( ) , ETileVisibility : : REVEALED ) ;
2014-03-17 22:51:07 +03:00
2024-05-11 20:21:02 +02:00
if ( t - > garrisonHero ) //garrison hero first - consistent with original H3 Mana Vortex and Battle Scholar Academy levelup windows order
2024-08-12 15:06:32 +02:00
objectVisited ( t , t - > garrisonHero ) ;
2023-07-23 23:00:37 +02:00
if ( t - > visitingHero )
2024-08-12 15:06:32 +02:00
objectVisited ( t , t - > visitingHero ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
checkVictoryLossConditionsForPlayer ( t - > tempOwner ) ;
2023-04-07 23:41:55 +02:00
return true ;
}
2014-03-17 22:51:07 +03:00
2024-08-04 15:44:57 +02:00
bool CGameHandler : : triggerTownSpecialBuildingAction ( ObjectInstanceID tid , BuildingSubID : : EBuildingSubID sid )
{
const CGTownInstance * t = getTown ( tid ) ;
2024-08-09 23:35:47 +02:00
if ( t - > town - > getBuildingType ( sid ) = = BuildingID : : NONE )
2024-08-04 15:44:57 +02:00
return false ;
if ( sid = = BuildingSubID : : EBuildingSubID : : BANK )
{
TResources res ;
res [ EGameResID : : GOLD ] = 2500 ;
giveResources ( t - > getOwner ( ) , res ) ;
2024-08-04 17:52:40 +02:00
setObjPropertyValue ( t - > id , ObjProperty : : BONUS_VALUE_SECOND , 2500 ) ;
2024-08-04 15:44:57 +02:00
}
return true ;
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : razeStructure ( ObjectInstanceID tid , BuildingID bid )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
///incomplete, simply erases target building
const CGTownInstance * t = getTown ( tid ) ;
2024-08-17 21:06:48 +02:00
if ( ! t - > hasBuilt ( bid ) )
2023-07-23 23:00:37 +02:00
return false ;
RazeStructures rs ;
rs . tid = tid ;
rs . bid . insert ( bid ) ;
rs . destroyed = t - > destroyed + 1 ;
sendAndApply ( & rs ) ;
//TODO: Remove dwellers
// if (t->subID == 4 && bid == 17) //Veil of Darkness
// {
// RemoveBonus rb(RemoveBonus::TOWN);
// rb.whoID = t->id;
// rb.source = BonusSource::TOWN_STRUCTURE;
// rb.id = 17;
// sendAndApply(&rb);
// }
return true ;
2014-03-17 22:51:07 +03:00
}
2023-09-05 22:12:57 +02:00
bool CGameHandler : : recruitCreatures ( ObjectInstanceID objid , ObjectInstanceID dstid , CreatureID crid , ui32 cram , si32 fromLvl , PlayerColor player )
2014-03-17 22:51:07 +03:00
{
2023-09-05 22:12:57 +02:00
const CGDwelling * dwelling = dynamic_cast < const CGDwelling * > ( getObj ( objid ) ) ;
const CGTownInstance * town = dynamic_cast < const CGTownInstance * > ( getObj ( objid ) ) ;
const CArmedInstance * army = dynamic_cast < const CArmedInstance * > ( getObj ( dstid ) ) ;
const CGHeroInstance * hero = dynamic_cast < const CGHeroInstance * > ( getObj ( dstid ) ) ;
2023-12-31 23:43:35 +02:00
const CCreature * c = crid . toCreature ( ) ;
2023-09-05 22:12:57 +02:00
2023-07-23 23:00:37 +02:00
const bool warMachine = c - > warMachine ! = ArtifactID : : NONE ;
2016-08-18 05:35:29 +02:00
2023-09-05 22:12:57 +02:00
//TODO: check if hero is actually visiting object
COMPLAIN_RET_FALSE_IF ( ! dwelling | | ! army , " Cannot recruit: invalid object! " ) ;
COMPLAIN_RET_FALSE_IF ( dwelling - > getOwner ( ) ! = player & & dwelling - > getOwner ( ) ! = PlayerColor : : UNFLAGGABLE , " Cannot recruit: dwelling not owned! " ) ;
2014-03-17 22:51:07 +03:00
2023-09-05 22:12:57 +02:00
if ( town )
{
COMPLAIN_RET_FALSE_IF ( town ! = army & & ! hero , " Cannot recruit: invalid destination! " ) ;
COMPLAIN_RET_FALSE_IF ( hero ! = town - > garrisonHero & & hero ! = town - > visitingHero , " Cannot recruit: can only recruit to town or hero in town!! " ) ;
}
else
{
2023-09-26 14:55:07 +02:00
COMPLAIN_RET_FALSE_IF ( getVisitingHero ( dwelling ) ! = hero , " Cannot recruit: can only recruit by visiting hero! " ) ;
2023-09-05 22:12:57 +02:00
COMPLAIN_RET_FALSE_IF ( ! hero | | hero - > getOwner ( ) ! = player , " Cannot recruit: can only recruit to owned hero! " ) ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//verify
bool found = false ;
int level = 0 ;
2014-03-17 22:51:07 +03:00
2023-09-05 22:12:57 +02:00
for ( ; level < dwelling - > creatures . size ( ) ; level + + ) //iterate through all levels
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ( fromLvl ! = - 1 ) & & ( level ! = fromLvl ) )
continue ;
2023-09-05 22:12:57 +02:00
const auto & cur = dwelling - > creatures . at ( level ) ; //current level info <amount, list of cr. ids>
2023-07-23 23:00:37 +02:00
int i = 0 ;
for ( ; i < cur . second . size ( ) ; i + + ) //look for crid among available creatures list on current level
if ( cur . second . at ( i ) = = crid )
break ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( i < cur . second . size ( ) )
2023-01-15 17:46:42 +02:00
{
2014-03-17 22:51:07 +03:00
found = true ;
2023-07-23 23:00:37 +02:00
cram = std : : min ( cram , cur . first ) ; //reduce recruited amount up to available amount
2014-03-17 22:51:07 +03:00
break ;
2023-01-15 17:46:42 +02:00
}
2014-03-17 22:51:07 +03:00
}
2023-09-05 22:12:57 +02:00
SlotID slot = army - > getSlotFor ( crid ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ( ! found & & complain ( " Cannot recruit: no such creatures! " ) )
2023-09-05 22:12:57 +02:00
| | ( ( si32 ) cram > VLC - > creh - > objects . at ( crid ) - > maxAmount ( getPlayerState ( army - > tempOwner ) - > resources ) & & complain ( " Cannot recruit: lack of resources! " ) )
2023-07-23 23:00:37 +02:00
| | ( cram < = 0 & & complain ( " Cannot recruit: cram <= 0! " ) )
| | ( ! slot . validSlot ( ) & & ! warMachine & & complain ( " Cannot recruit: no available slot! " ) ) )
{
return false ;
2022-12-29 20:39:01 +02:00
}
2022-11-06 23:59:30 +02:00
2023-07-23 23:00:37 +02:00
//recruit
2024-08-03 19:53:05 +02:00
TResources cost = ( c - > getFullRecruitCost ( ) * cram ) ;
giveResources ( army - > tempOwner , - cost ) ;
2024-08-07 21:26:22 +02:00
gs - > statistic . accumulatedValues [ army - > tempOwner ] . spentResourcesForArmy + = cost ;
2022-11-06 23:59:30 +02:00
2023-07-23 23:00:37 +02:00
SetAvailableCreatures sac ;
sac . tid = objid ;
2023-09-05 22:12:57 +02:00
sac . creatures = dwelling - > creatures ;
2023-07-23 23:00:37 +02:00
sac . creatures [ level ] . first - = cram ;
sendAndApply ( & sac ) ;
2022-11-06 23:59:30 +02:00
2023-07-23 23:00:37 +02:00
if ( warMachine )
{
ArtifactID artId = c - > warMachine ;
const CArtifact * art = artId . toArtifact ( ) ;
2014-03-17 22:51:07 +03:00
2023-09-05 22:12:57 +02:00
COMPLAIN_RET_FALSE_IF ( ! hero , " Only hero can buy war machines " ) ;
COMPLAIN_RET_FALSE_IF ( artId = = ArtifactID : : CATAPULT , " Catapult cannot be recruited! " ) ;
2023-07-23 23:00:37 +02:00
COMPLAIN_RET_FALSE_IF ( nullptr = = art , " Invalid war machine artifact " ) ;
2014-03-17 22:51:07 +03:00
2023-09-05 22:12:57 +02:00
return giveHeroNewArtifact ( hero , art ) ;
2022-11-06 23:59:30 +02:00
}
2022-11-10 19:09:37 +02:00
else
2022-11-06 23:59:30 +02:00
{
2023-09-05 22:12:57 +02:00
addToSlot ( StackLocation ( army , slot ) , c , cram ) ;
2022-11-06 23:59:30 +02:00
}
2014-03-17 22:51:07 +03:00
return true ;
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : upgradeCreature ( ObjectInstanceID objid , SlotID pos , CreatureID upgID )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CArmedInstance * obj = static_cast < const CArmedInstance * > ( getObjInstance ( objid ) ) ;
if ( ! obj - > hasStackAtSlot ( pos ) )
2014-03-17 22:51:07 +03:00
{
2023-08-20 22:45:41 +02:00
COMPLAIN_RET ( " Cannot upgrade, no stack at slot " + std : : to_string ( pos ) ) ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
UpgradeInfo ui ;
fillUpgradeInfo ( obj , pos , ui ) ;
PlayerColor player = obj - > tempOwner ;
const PlayerState * p = getPlayerState ( player ) ;
int crQuantity = obj - > stacks . at ( pos ) - > count ;
int newIDpos = vstd : : find_pos ( ui . newID , upgID ) ; //get position of new id in UpgradeInfo
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//check if upgrade is possible
2023-08-19 19:48:28 +02:00
if ( ( ui . oldID = = CreatureID : : NONE | | newIDpos = = - 1 ) & & complain ( " That upgrade is not possible! " ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
return false ;
2016-10-12 17:16:26 +02:00
}
2023-07-23 23:00:37 +02:00
TResources totalCost = ui . cost . at ( newIDpos ) * crQuantity ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//check if player has enough resources
if ( ! p - > resources . canAfford ( totalCost ) )
COMPLAIN_RET ( " Cannot upgrade, not enough resources! " ) ;
2023-03-18 12:44:01 +02:00
2023-07-23 23:00:37 +02:00
//take resources
giveResources ( player , - totalCost ) ;
2024-08-07 21:26:22 +02:00
gs - > statistic . accumulatedValues [ player ] . spentResourcesForArmy + = totalCost ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
//upgrade creature
2023-12-31 23:43:35 +02:00
changeStackType ( StackLocation ( obj , pos ) , upgID . toCreature ( ) ) ;
2016-01-23 14:20:51 +02:00
return true ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : changeStackType ( const StackLocation & sl , const CCreature * c )
2023-04-07 23:41:55 +02:00
{
2023-07-23 23:00:37 +02:00
if ( ! sl . army - > hasStackAtSlot ( sl . slot ) )
COMPLAIN_RET ( " Cannot find a stack to change type " ) ;
2023-04-07 23:41:55 +02:00
2023-07-23 23:00:37 +02:00
SetStackType sst ;
sst . army = sl . army - > id ;
sst . slot = sl . slot ;
sst . type = c - > getId ( ) ;
sendAndApply ( & sst ) ;
2023-04-07 23:41:55 +02:00
return true ;
}
2023-07-23 23:00:37 +02:00
void CGameHandler : : moveArmy ( const CArmedInstance * src , const CArmedInstance * dst , bool allowMerging )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
assert ( src - > canBeMergedWith ( * dst , allowMerging ) ) ;
while ( src - > stacksCount ( ) ) //while there are unmoved creatures
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
auto i = src - > Slots ( ) . begin ( ) ; //iterator to stack to move
StackLocation sl ( src , i - > first ) ; //location of stack to move
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
SlotID pos = dst - > getSlotFor ( i - > second - > type ) ;
if ( ! pos . validSlot ( ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
//try to merge two other stacks to make place
std : : pair < SlotID , SlotID > toMerge ;
2024-06-24 03:23:26 +02:00
if ( dst - > mergeableStacks ( toMerge , i - > first ) & & allowMerging )
2023-07-23 23:00:37 +02:00
{
moveStack ( StackLocation ( dst , toMerge . first ) , StackLocation ( dst , toMerge . second ) ) ; //merge toMerge.first into toMerge.second
assert ( ! dst - > hasStackAtSlot ( toMerge . first ) ) ; //we have now a new free slot
moveStack ( sl , StackLocation ( dst , toMerge . first ) ) ; //move stack to freed slot
}
else
{
complain ( " Unexpected failure during an attempt to move army from " + src - > nodeName ( ) + " to " + dst - > nodeName ( ) + " ! " ) ;
return ;
}
2014-03-17 22:51:07 +03:00
}
else
2023-07-23 23:00:37 +02:00
{
moveStack ( sl , StackLocation ( dst , pos ) ) ;
}
2014-03-17 22:51:07 +03:00
}
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : swapGarrisonOnSiege ( ObjectInstanceID tid )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CGTownInstance * town = getTown ( tid ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! town - > garrisonHero = = ! town - > visitingHero )
return false ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
SetHeroesInTown intown ;
intown . tid = tid ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( town - > garrisonHero ) //garrison -> vising
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
intown . garrison = ObjectInstanceID ( ) ;
intown . visiting = town - > garrisonHero - > id ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
else //visiting -> garrison
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( town - > armedGarrison ( ) )
town - > mergeGarrisonOnSiege ( ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
intown . visiting = ObjectInstanceID ( ) ;
intown . garrison = town - > visitingHero - > id ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
sendAndApply ( & intown ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : garrisonSwap ( ObjectInstanceID tid )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CGTownInstance * town = getTown ( tid ) ;
if ( ! town - > garrisonHero & & town - > visitingHero ) //visiting => garrison, merge armies: town army => hero army
{
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! town - > visitingHero - > canBeMergedWith ( * town ) )
{
complain ( " Cannot make garrison swap, not enough free slots! " ) ;
return false ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
moveArmy ( town , town - > visitingHero , true ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
SetHeroesInTown intown ;
intown . tid = tid ;
intown . visiting = ObjectInstanceID ( ) ;
intown . garrison = town - > visitingHero - > id ;
sendAndApply ( & intown ) ;
return true ;
}
else if ( town - > garrisonHero & & ! town - > visitingHero ) //move hero out of the garrison
{
//check if moving hero out of town will break 8 wandering heroes limit
if ( getHeroCount ( town - > garrisonHero - > tempOwner , false ) > = 8 )
{
complain ( " Cannot move hero out of the garrison, there are already 8 wandering heroes! " ) ;
return false ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
SetHeroesInTown intown ;
intown . tid = tid ;
intown . garrison = ObjectInstanceID ( ) ;
intown . visiting = town - > garrisonHero - > id ;
sendAndApply ( & intown ) ;
return true ;
}
else if ( ! ! town - > garrisonHero & & town - > visitingHero ) //swap visiting and garrison hero
2020-10-19 21:39:57 +02:00
{
2023-07-23 23:00:37 +02:00
SetHeroesInTown intown ;
intown . tid = tid ;
intown . garrison = town - > visitingHero - > id ;
intown . visiting = town - > garrisonHero - > id ;
sendAndApply ( & intown ) ;
return true ;
}
else
{
complain ( " Cannot swap garrison hero! " ) ;
return false ;
}
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
// With the amount of changes done to the function, it's more like transferArtifacts.
// Function moves artifact from src to dst. If dst is not a backpack and is already occupied, old dst art goes to backpack and is replaced.
2024-03-07 16:52:50 +02:00
bool CGameHandler : : moveArtifact ( const PlayerColor & player , const ArtifactLocation & src , const ArtifactLocation & dst )
2014-03-17 22:51:07 +03:00
{
2023-11-07 22:12:32 +02:00
const auto srcArtSet = getArtSet ( src ) ;
const auto dstArtSet = getArtSet ( dst ) ;
2023-10-23 18:37:18 +02:00
assert ( srcArtSet ) ;
assert ( dstArtSet ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
// Make sure exchange is even possible between the two heroes.
2023-11-07 22:12:32 +02:00
if ( ! isAllowedExchange ( src . artHolder , dst . artHolder ) )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " That heroes cannot make any exchange! " ) ;
2014-03-17 22:51:07 +03:00
2024-04-30 11:39:20 +02:00
COMPLAIN_RET_FALSE_IF ( ! ArtifactUtils : : checkIfSlotValid ( * srcArtSet , src . slot ) , " moveArtifact: wrong artifact source slot " ) ;
2023-11-07 22:12:32 +02:00
const auto srcArtifact = srcArtSet - > getArt ( src . slot ) ;
2024-04-30 11:39:20 +02:00
auto dstSlot = dst . slot ;
if ( dstSlot = = ArtifactPosition : : FIRST_AVAILABLE )
dstSlot = ArtifactUtils : : getArtAnyPosition ( dstArtSet , srcArtifact - > getTypeId ( ) ) ;
if ( ! ArtifactUtils : : checkIfSlotValid ( * dstArtSet , dstSlot ) )
return true ;
const auto dstArtifact = dstArtSet - > getArt ( dstSlot ) ;
2024-04-09 22:58:35 +02:00
const bool isDstSlotOccupied = dstArtSet - > bearerType ( ) = = ArtBearer : : ALTAR ? false : dstArtifact ! = nullptr ;
2024-04-30 11:39:20 +02:00
const bool isDstSlotBackpack = dstArtSet - > bearerType ( ) = = ArtBearer : : HERO ? ArtifactUtils : : isSlotBackpack ( dstSlot ) : false ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( srcArtifact = = nullptr )
COMPLAIN_RET ( " No artifact to move! " ) ;
2024-01-12 23:57:19 +02:00
if ( isDstSlotOccupied & & getOwner ( src . artHolder ) ! = getOwner ( dst . artHolder ) & & ! isDstSlotBackpack )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " Can't touch artifact on hero of another player! " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
// Check if src/dest slots are appropriate for the artifacts exchanged.
// Moving to the backpack is always allowed.
2024-04-30 11:39:20 +02:00
if ( ( ! srcArtifact | | ! isDstSlotBackpack ) & & ! srcArtifact - > canBePutAt ( dstArtSet , dstSlot , true ) )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " Cannot move artifact! " ) ;
2014-03-17 22:51:07 +03:00
2023-11-07 22:12:32 +02:00
auto srcSlotInfo = srcArtSet - > getSlot ( src . slot ) ;
2024-04-30 11:39:20 +02:00
auto dstSlotInfo = dstArtSet - > getSlot ( dstSlot ) ;
2014-03-17 22:51:07 +03:00
2023-10-14 21:00:39 +02:00
if ( ( srcSlotInfo & & srcSlotInfo - > locked ) | | ( dstSlotInfo & & dstSlotInfo - > locked ) )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " Cannot move artifact locks. " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( isDstSlotBackpack & & srcArtifact - > artType - > isBig ( ) )
COMPLAIN_RET ( " Cannot put big artifacts in backpack! " ) ;
2024-04-30 11:39:20 +02:00
if ( src . slot = = ArtifactPosition : : MACH4 | | dstSlot = = ArtifactPosition : : MACH4 )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " Cannot move catapult! " ) ;
2023-11-07 22:12:32 +02:00
if ( isDstSlotBackpack & & ! ArtifactUtils : : isBackpackFreeSlots ( dstArtSet ) )
COMPLAIN_RET ( " Backpack is full! " ) ;
2014-03-17 22:51:07 +03:00
2024-04-30 11:39:20 +02:00
dstSlot = std : : min ( dstSlot , ArtifactPosition ( ArtifactPosition : : BACKPACK_START + dstArtSet - > artifactsInBackpack . size ( ) ) ) ;
2014-03-17 22:51:07 +03:00
2023-11-07 22:12:32 +02:00
if ( src . slot = = dstSlot & & src . artHolder = = dst . artHolder )
COMPLAIN_RET ( " Won't move artifact: Dest same as source! " ) ;
2024-03-07 16:52:50 +02:00
BulkMoveArtifacts ma ( player , src . artHolder , dst . artHolder , false ) ;
2023-11-07 22:12:32 +02:00
ma . srcCreature = src . creature ;
ma . dstCreature = dst . creature ;
// Check if dst slot is occupied
2024-01-12 23:57:19 +02:00
if ( ! isDstSlotBackpack & & isDstSlotOccupied )
2016-10-12 17:16:26 +02:00
{
2024-04-09 22:58:35 +02:00
// Previous artifact must be swapped
COMPLAIN_RET_FALSE_IF ( ! dstArtifact - > canBePutAt ( srcArtSet , src . slot , true ) , " Cannot swap artifacts! " ) ;
2023-11-07 22:12:32 +02:00
ma . artsPack1 . push_back ( BulkMoveArtifacts : : LinkedSlots ( dstSlot , src . slot ) ) ;
2014-03-17 22:51:07 +03:00
}
2023-11-09 12:38:50 +02:00
auto hero = getHero ( dst . artHolder ) ;
if ( ArtifactUtils : : checkSpellbookIsNeeded ( hero , srcArtifact - > artType - > getId ( ) , dstSlot ) )
2023-12-31 23:43:35 +02:00
giveHeroNewArtifact ( hero , ArtifactID ( ArtifactID : : SPELLBOOK ) . toArtifact ( ) , ArtifactPosition : : SPELLBOOK ) ;
2014-03-17 22:51:07 +03:00
2023-11-07 22:12:32 +02:00
ma . artsPack0 . push_back ( BulkMoveArtifacts : : LinkedSlots ( src . slot , dstSlot ) ) ;
if ( src . artHolder ! = dst . artHolder )
2024-06-22 18:29:39 +02:00
ma . artsPack0 . back ( ) . askAssemble = true ;
2023-11-07 22:12:32 +02:00
sendAndApply ( & ma ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2024-03-07 16:52:50 +02:00
bool CGameHandler : : bulkMoveArtifacts ( const PlayerColor & player , ObjectInstanceID srcId , ObjectInstanceID dstId , bool swap , bool equipped , bool backpack )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
// Make sure exchange is even possible between the two heroes.
2024-01-16 16:22:40 +02:00
if ( ! isAllowedExchange ( srcId , dstId ) )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " That heroes cannot make any exchange! " ) ;
2014-03-17 22:51:07 +03:00
2024-01-16 16:22:40 +02:00
auto psrcSet = getArtSet ( srcId ) ;
auto pdstSet = getArtSet ( dstId ) ;
if ( ( ! psrcSet ) | | ( ! pdstSet ) )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " bulkMoveArtifacts: wrong hero's ID " ) ;
2014-03-17 22:51:07 +03:00
2024-03-07 16:52:50 +02:00
BulkMoveArtifacts ma ( player , srcId , dstId , swap ) ;
2023-07-23 23:00:37 +02:00
auto & slotsSrcDst = ma . artsPack0 ;
auto & slotsDstSrc = ma . artsPack1 ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
// Temporary fitting set for artifacts. Used to select available slots before sending data.
2024-01-16 16:22:40 +02:00
CArtifactFittingSet artFittingSet ( pdstSet - > bearerType ( ) ) ;
2014-03-17 22:51:07 +03:00
2024-01-16 16:22:40 +02:00
auto moveArtifact = [ this , & artFittingSet , dstId ] ( const CArtifactInstance * artifact ,
ArtifactPosition srcSlot , std : : vector < BulkMoveArtifacts : : LinkedSlots > & slots ) - > void
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
assert ( artifact ) ;
auto dstSlot = ArtifactUtils : : getArtAnyPosition ( & artFittingSet , artifact - > getTypeId ( ) ) ;
if ( dstSlot ! = ArtifactPosition : : PRE_FIRST )
{
artFittingSet . putArtifact ( dstSlot , static_cast < ConstTransitivePtr < CArtifactInstance > > ( artifact ) ) ;
slots . push_back ( BulkMoveArtifacts : : LinkedSlots ( srcSlot , dstSlot ) ) ;
2014-03-17 22:51:07 +03:00
2024-01-16 16:22:40 +02:00
// TODO Shouldn't be here. Possibly in callback after equipping the artifact
if ( auto dstHero = getHero ( dstId ) )
{
if ( ArtifactUtils : : checkSpellbookIsNeeded ( dstHero , artifact - > getTypeId ( ) , dstSlot ) )
giveHeroNewArtifact ( dstHero , ArtifactID ( ArtifactID : : SPELLBOOK ) . toArtifact ( ) , ArtifactPosition : : SPELLBOOK ) ;
}
2023-07-23 23:00:37 +02:00
}
} ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( swap )
2016-09-08 18:29:15 +02:00
{
2024-01-16 16:22:40 +02:00
auto moveArtsWorn = [ moveArtifact ] ( const CArtifactSet * srcArtSet , std : : vector < BulkMoveArtifacts : : LinkedSlots > & slots )
2014-03-17 22:51:07 +03:00
{
2024-01-16 16:22:40 +02:00
for ( auto & artifact : srcArtSet - > artifactsWorn )
2023-07-23 23:00:37 +02:00
{
if ( ArtifactUtils : : isArtRemovable ( artifact ) )
2024-01-16 16:22:40 +02:00
moveArtifact ( artifact . second . getArt ( ) , artifact . first , slots ) ;
2023-07-23 23:00:37 +02:00
}
} ;
auto moveArtsInBackpack = [ ] ( const CArtifactSet * artSet ,
std : : vector < BulkMoveArtifacts : : LinkedSlots > & slots ) - > void
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
for ( auto & slotInfo : artSet - > artifactsInBackpack )
{
auto slot = artSet - > getArtPos ( slotInfo . artifact ) ;
slots . push_back ( BulkMoveArtifacts : : LinkedSlots ( slot , slot ) ) ;
}
} ;
2023-07-24 18:09:17 +02:00
if ( equipped )
{
// Move over artifacts that are worn srcHero -> dstHero
2024-01-16 16:22:40 +02:00
moveArtsWorn ( psrcSet , slotsSrcDst ) ;
2023-07-24 18:09:17 +02:00
artFittingSet . artifactsWorn . clear ( ) ;
// Move over artifacts that are worn dstHero -> srcHero
2024-01-16 16:22:40 +02:00
moveArtsWorn ( pdstSet , slotsDstSrc ) ;
2023-07-24 18:09:17 +02:00
}
if ( backpack )
{
// Move over artifacts that are in backpack srcHero -> dstHero
2024-01-16 16:22:40 +02:00
moveArtsInBackpack ( psrcSet , slotsSrcDst ) ;
2023-07-24 18:09:17 +02:00
// Move over artifacts that are in backpack dstHero -> srcHero
2024-01-16 16:22:40 +02:00
moveArtsInBackpack ( pdstSet , slotsDstSrc ) ;
2023-07-24 18:09:17 +02:00
}
2016-09-08 18:29:15 +02:00
}
2023-07-23 23:00:37 +02:00
else
{
2024-01-16 16:22:40 +02:00
artFittingSet . artifactsInBackpack = pdstSet - > artifactsInBackpack ;
artFittingSet . artifactsWorn = pdstSet - > artifactsWorn ;
2023-07-24 18:09:17 +02:00
if ( equipped )
2014-03-17 22:51:07 +03:00
{
2023-07-24 18:09:17 +02:00
// Move over artifacts that are worn
2024-01-16 16:22:40 +02:00
for ( auto & artInfo : psrcSet - > artifactsWorn )
2014-03-17 22:51:07 +03:00
{
2023-07-24 18:09:17 +02:00
if ( ArtifactUtils : : isArtRemovable ( artInfo ) )
{
2024-01-16 16:22:40 +02:00
moveArtifact ( psrcSet - > getArt ( artInfo . first ) , artInfo . first , slotsSrcDst ) ;
2023-07-24 18:09:17 +02:00
}
2014-03-17 22:51:07 +03:00
}
}
2023-07-24 18:09:17 +02:00
if ( backpack )
2014-03-17 22:51:07 +03:00
{
2023-07-24 18:09:17 +02:00
// Move over artifacts that are in backpack
2024-01-16 16:22:40 +02:00
for ( auto & slotInfo : psrcSet - > artifactsInBackpack )
2023-07-24 18:09:17 +02:00
{
2024-01-16 16:22:40 +02:00
moveArtifact ( psrcSet - > getArt ( psrcSet - > getArtPos ( slotInfo . artifact ) ) ,
psrcSet - > getArtPos ( slotInfo . artifact ) , slotsSrcDst ) ;
2023-07-24 18:09:17 +02:00
}
2014-03-17 22:51:07 +03:00
}
}
2023-07-23 23:00:37 +02:00
sendAndApply ( & ma ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2024-03-07 16:52:50 +02:00
bool CGameHandler : : scrollBackpackArtifacts ( const PlayerColor & player , const ObjectInstanceID heroID , bool left )
2024-03-06 15:16:35 +02:00
{
2024-03-28 00:28:31 +02:00
const auto artSet = getArtSet ( heroID ) ;
2024-03-06 15:16:35 +02:00
COMPLAIN_RET_FALSE_IF ( artSet = = nullptr , " scrollBackpackArtifacts: wrong hero's ID " ) ;
2024-03-07 16:52:50 +02:00
BulkMoveArtifacts bma ( player , heroID , heroID , false ) ;
2024-03-06 15:16:35 +02:00
const auto backpackEnd = ArtifactPosition ( ArtifactPosition : : BACKPACK_START + artSet - > artifactsInBackpack . size ( ) - 1 ) ;
if ( backpackEnd > ArtifactPosition : : BACKPACK_START )
{
if ( left )
bma . artsPack0 . push_back ( BulkMoveArtifacts : : LinkedSlots ( backpackEnd , ArtifactPosition : : BACKPACK_START ) ) ;
else
bma . artsPack0 . push_back ( BulkMoveArtifacts : : LinkedSlots ( ArtifactPosition : : BACKPACK_START , backpackEnd ) ) ;
sendAndApply ( & bma ) ;
}
return true ;
}
2024-04-27 01:08:47 +02:00
bool CGameHandler : : saveArtifactsCostume ( const PlayerColor & player , const ObjectInstanceID heroID , uint32_t costumeIdx )
2024-03-30 17:18:50 +02:00
{
auto artSet = getArtSet ( heroID ) ;
COMPLAIN_RET_FALSE_IF ( artSet = = nullptr , " saveArtifactsCostume: wrong hero's ID " ) ;
ChangeArtifactsCostume costume ( player , costumeIdx ) ;
2024-04-19 16:14:41 +02:00
for ( const auto & slot : ArtifactUtils : : commonWornSlots ( ) )
2024-03-30 17:18:50 +02:00
{
2024-04-19 16:14:41 +02:00
if ( const auto slotInfo = artSet - > getSlot ( slot ) ; slotInfo ! = nullptr & & ! slotInfo - > locked )
costume . costumeSet . emplace ( slot , slotInfo - > getArt ( ) - > getTypeId ( ) ) ;
2024-03-30 17:18:50 +02:00
}
sendAndApply ( & costume ) ;
return true ;
}
2024-04-27 01:08:47 +02:00
bool CGameHandler : : switchArtifactsCostume ( const PlayerColor & player , const ObjectInstanceID heroID , uint32_t costumeIdx )
2024-04-19 16:14:41 +02:00
{
const auto artSet = getArtSet ( heroID ) ;
COMPLAIN_RET_FALSE_IF ( artSet = = nullptr , " switchArtifactsCostume: wrong hero's ID " ) ;
const auto playerState = getPlayerState ( player ) ;
COMPLAIN_RET_FALSE_IF ( playerState = = nullptr , " switchArtifactsCostume: wrong player " ) ;
if ( auto costume = playerState - > costumesArtifacts . find ( costumeIdx ) ; costume ! = playerState - > costumesArtifacts . end ( ) )
{
CArtifactFittingSet artFittingSet ( * artSet ) ;
BulkMoveArtifacts bma ( player , heroID , heroID , false ) ;
auto costumeArtMap = costume - > second ;
auto estimateBackpackSize = artSet - > artifactsInBackpack . size ( ) ;
// First, find those artifacts that are already in place
for ( const auto & slot : ArtifactUtils : : commonWornSlots ( ) )
{
if ( const auto * slotInfo = artFittingSet . getSlot ( slot ) ; slotInfo ! = nullptr & & ! slotInfo - > locked )
if ( const auto artPos = costumeArtMap . find ( slot ) ; artPos ! = costumeArtMap . end ( ) & & artPos - > second = = slotInfo - > getArt ( ) - > getTypeId ( ) )
{
costumeArtMap . erase ( artPos ) ;
artFittingSet . removeArtifact ( slot ) ;
}
}
// Second, find the necessary artifacts for the costume
for ( const auto & artPos : costumeArtMap )
{
if ( const auto availableArts = artFittingSet . getAllArtPositions ( artPos . second , false , false , false ) ; ! availableArts . empty ( ) )
{
bma . artsPack0 . emplace_back ( BulkMoveArtifacts : : LinkedSlots
{
2024-03-28 16:16:10 +02:00
artSet - > getArtPos ( artFittingSet . getArt ( availableArts . front ( ) ) ) ,
2024-04-19 16:14:41 +02:00
artPos . first
} ) ;
artFittingSet . removeArtifact ( availableArts . front ( ) ) ;
if ( ArtifactUtils : : isSlotBackpack ( availableArts . front ( ) ) )
estimateBackpackSize - - ;
}
}
// Third, put unnecessary artifacts into backpack
for ( const auto & slot : ArtifactUtils : : commonWornSlots ( ) )
if ( artFittingSet . getArt ( slot ) )
{
bma . artsPack0 . emplace_back ( BulkMoveArtifacts : : LinkedSlots { slot , ArtifactPosition : : BACKPACK_START } ) ;
estimateBackpackSize + + ;
}
const auto backpackCap = VLC - > settings ( ) - > getInteger ( EGameSettings : : HEROES_BACKPACK_CAP ) ;
if ( ( backpackCap < 0 | | estimateBackpackSize < = backpackCap ) & & ! bma . artsPack0 . empty ( ) )
sendAndApply ( & bma ) ;
}
return true ;
}
2023-07-23 23:00:37 +02:00
/**
* Assembles or disassembles a combination artifact .
* @ param heroID ID of hero holding the artifact ( s ) .
* @ param artifactSlot The worn slot ID of the combination - or constituent artifact .
* @ param assemble True for assembly operation , false for disassembly .
* @ param assembleTo If assemble is true , this represents the artifact ID of the combination
* artifact to assemble to . Otherwise it ' s not used .
*/
2023-09-12 17:30:48 +02:00
bool CGameHandler : : assembleArtifacts ( ObjectInstanceID heroID , ArtifactPosition artifactSlot , bool assemble , ArtifactID assembleTo )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CGHeroInstance * hero = getHero ( heroID ) ;
const CArtifactInstance * destArtifact = hero - > getArt ( artifactSlot ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! destArtifact )
COMPLAIN_RET ( " assembleArtifacts: there is no such artifact instance! " ) ;
2014-03-17 22:51:07 +03:00
2023-10-14 21:00:39 +02:00
const auto dstLoc = ArtifactLocation ( hero - > id , artifactSlot ) ;
2023-07-23 23:00:37 +02:00
if ( assemble )
2014-03-17 22:51:07 +03:00
{
2023-12-31 23:43:35 +02:00
const CArtifact * combinedArt = assembleTo . toArtifact ( ) ;
2023-07-23 23:00:37 +02:00
if ( ! combinedArt - > isCombined ( ) )
COMPLAIN_RET ( " assembleArtifacts: Artifact being attempted to assemble is not a combined artifacts! " ) ;
2023-09-12 17:30:48 +02:00
if ( ! vstd : : contains ( ArtifactUtils : : assemblyPossibilities ( hero , destArtifact - > getTypeId ( ) ) , combinedArt ) )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " assembleArtifacts: It's impossible to assemble requested artifact! " ) ;
2014-03-17 22:51:07 +03:00
}
2024-01-18 12:49:08 +02:00
if ( ! destArtifact - > canBePutAt ( hero , artifactSlot , true )
& & ! destArtifact - > canBePutAt ( hero , ArtifactPosition : : BACKPACK_START , true ) )
2023-09-12 17:30:48 +02:00
{
COMPLAIN_RET ( " assembleArtifacts: It's impossible to give the artholder requested artifact! " ) ;
}
2023-07-23 23:00:37 +02:00
if ( ArtifactUtils : : checkSpellbookIsNeeded ( hero , assembleTo , artifactSlot ) )
2023-12-31 23:43:35 +02:00
giveHeroNewArtifact ( hero , ArtifactID ( ArtifactID : : SPELLBOOK ) . toArtifact ( ) , ArtifactPosition : : SPELLBOOK ) ;
2020-10-19 21:39:57 +02:00
2023-07-23 23:00:37 +02:00
AssembledArtifact aa ;
2023-09-12 17:30:48 +02:00
aa . al = dstLoc ;
2023-07-23 23:00:37 +02:00
aa . builtArt = combinedArt ;
sendAndApply ( & aa ) ;
}
else
2020-10-19 21:39:57 +02:00
{
2023-07-23 23:00:37 +02:00
if ( ! destArtifact - > isCombined ( ) )
COMPLAIN_RET ( " assembleArtifacts: Artifact being attempted to disassemble is not a combined artifact! " ) ;
2020-10-19 21:39:57 +02:00
2023-07-23 23:00:37 +02:00
if ( ArtifactUtils : : isSlotBackpack ( artifactSlot )
& & ! ArtifactUtils : : isBackpackFreeSlots ( hero , destArtifact - > artType - > getConstituents ( ) . size ( ) - 1 ) )
COMPLAIN_RET ( " assembleArtifacts: Artifact being attempted to disassemble but backpack is full! " ) ;
2020-10-19 21:39:57 +02:00
2023-07-23 23:00:37 +02:00
DisassembledArtifact da ;
2023-09-12 17:30:48 +02:00
da . al = dstLoc ;
2023-07-23 23:00:37 +02:00
sendAndApply ( & da ) ;
2020-10-19 21:39:57 +02:00
}
2014-03-17 22:51:07 +03:00
return true ;
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : eraseArtifactByClient ( const ArtifactLocation & al )
2016-01-29 21:43:35 +02:00
{
2023-10-14 21:00:39 +02:00
const auto * hero = getHero ( al . artHolder ) ;
2023-07-23 23:00:37 +02:00
if ( hero = = nullptr )
COMPLAIN_RET ( " eraseArtifactByClient: wrong hero's ID " ) ;
2023-01-13 15:44:42 +02:00
2023-10-14 21:00:39 +02:00
const auto * art = hero - > getArt ( al . slot ) ;
2023-07-23 23:00:37 +02:00
if ( art = = nullptr )
COMPLAIN_RET ( " Cannot remove artifact! " ) ;
2023-01-13 15:44:42 +02:00
2023-10-14 21:00:39 +02:00
if ( art - > canBePutAt ( hero ) | | al . slot ! = ArtifactPosition : : TRANSITION_POS )
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " Illegal artifact removal request " ) ;
2023-01-13 15:44:42 +02:00
2023-07-23 23:00:37 +02:00
removeArtifact ( al ) ;
return true ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
bool CGameHandler : : buyArtifact ( ObjectInstanceID hid , ArtifactID aid )
{
const CGHeroInstance * hero = getHero ( hid ) ;
COMPLAIN_RET_FALSE_IF ( nullptr = = hero , " Invalid hero index " ) ;
const CGTownInstance * town = hero - > visitedTown ;
COMPLAIN_RET_FALSE_IF ( nullptr = = town , " Hero not in town " ) ;
2023-01-13 15:44:42 +02:00
2023-07-23 23:00:37 +02:00
if ( aid = = ArtifactID : : SPELLBOOK )
2016-01-29 21:43:35 +02:00
{
2023-07-23 23:00:37 +02:00
if ( ( ! town - > hasBuilt ( BuildingID : : MAGES_GUILD_1 ) & & complain ( " Cannot buy a spellbook, no mage guild in the town! " ) )
| | ( getResource ( hero - > getOwner ( ) , EGameResID : : GOLD ) < GameConstants : : SPELLBOOK_GOLD_COST & & complain ( " Cannot buy a spellbook, not enough gold! " ) )
| | ( hero - > getArt ( ArtifactPosition : : SPELLBOOK ) & & complain ( " Cannot buy a spellbook, hero already has a one! " ) )
)
return false ;
2023-01-13 15:44:42 +02:00
2023-07-23 23:00:37 +02:00
giveResource ( hero - > getOwner ( ) , EGameResID : : GOLD , - GameConstants : : SPELLBOOK_GOLD_COST ) ;
2023-12-31 23:43:35 +02:00
giveHeroNewArtifact ( hero , ArtifactID ( ArtifactID : : SPELLBOOK ) . toArtifact ( ) , ArtifactPosition : : SPELLBOOK ) ;
2023-07-23 23:00:37 +02:00
assert ( hero - > getArt ( ArtifactPosition : : SPELLBOOK ) ) ;
giveSpells ( town , hero ) ;
return true ;
2023-01-13 15:44:42 +02:00
}
2023-07-23 23:00:37 +02:00
else
2023-01-13 15:44:42 +02:00
{
2023-07-23 23:00:37 +02:00
const CArtifact * art = aid . toArtifact ( ) ;
COMPLAIN_RET_FALSE_IF ( nullptr = = art , " Invalid artifact index to buy " ) ;
COMPLAIN_RET_FALSE_IF ( art - > getWarMachine ( ) = = CreatureID : : NONE , " War machine artifact required " ) ;
COMPLAIN_RET_FALSE_IF ( hero - > hasArt ( aid ) , " Hero already has this machine! " ) ;
const int price = art - > getPrice ( ) ;
COMPLAIN_RET_FALSE_IF ( getPlayerState ( hero - > getOwner ( ) ) - > resources [ EGameResID : : GOLD ] < price , " Not enough gold! " ) ;
2023-01-13 15:44:42 +02:00
2023-07-23 23:00:37 +02:00
if ( ( town - > hasBuilt ( BuildingID : : BLACKSMITH ) & & town - > town - > warMachine = = aid )
| | ( town - > hasBuilt ( BuildingSubID : : BALLISTA_YARD ) & & aid = = ArtifactID : : BALLISTA ) )
{
giveResource ( hero - > getOwner ( ) , EGameResID : : GOLD , - price ) ;
return giveHeroNewArtifact ( hero , art ) ;
}
2023-01-13 15:44:42 +02:00
else
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " This machine is unavailable here! " ) ;
2016-01-29 21:43:35 +02:00
}
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : buyArtifact ( const IMarket * m , const CGHeroInstance * h , GameResID rid , ArtifactID aid )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ! h )
COMPLAIN_RET ( " Only hero can buy artifacts! " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! vstd : : contains ( m - > availableItemsIds ( EMarketMode : : RESOURCE_ARTIFACT ) , aid ) )
COMPLAIN_RET ( " That artifact is unavailable! " ) ;
2017-07-20 06:08:49 +02:00
2024-01-10 00:38:54 +02:00
int b1 ;
int b2 ;
2023-07-23 23:00:37 +02:00
m - > getOffer ( rid , aid , b1 , b2 , EMarketMode : : RESOURCE_ARTIFACT ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( getResource ( h - > tempOwner , rid ) < b1 )
COMPLAIN_RET ( " You can't afford to buy this artifact! " ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
giveResource ( h - > tempOwner , rid , - b1 ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
SetAvailableArtifacts saa ;
if ( dynamic_cast < const CGTownInstance * > ( m ) )
2014-03-17 22:51:07 +03:00
{
2023-11-06 18:27:16 +02:00
saa . id = ObjectInstanceID : : NONE ;
2024-01-09 16:43:36 +02:00
saa . arts = gs - > map - > townMerchantArtifacts ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
else if ( const CGBlackMarket * bm = dynamic_cast < const CGBlackMarket * > ( m ) ) //black market
2016-11-26 18:46:34 +02:00
{
2023-11-06 18:27:16 +02:00
saa . id = bm - > id ;
2023-07-23 23:00:37 +02:00
saa . arts = bm - > artifacts ;
}
else
COMPLAIN_RET ( " Wrong marktet... " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
bool found = false ;
for ( const CArtifact * & art : saa . arts )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( art & & art - > getId ( ) = = aid )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
art = nullptr ;
found = true ;
2014-03-17 22:51:07 +03:00
break ;
}
2023-07-23 23:00:37 +02:00
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! found )
COMPLAIN_RET ( " Cannot find selected artifact on the list " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
sendAndApply ( & saa ) ;
2023-12-31 23:43:35 +02:00
giveHeroNewArtifact ( h , aid . toArtifact ( ) , ArtifactPosition : : FIRST_AVAILABLE ) ;
2023-07-23 23:00:37 +02:00
return true ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
bool CGameHandler : : sellArtifact ( const IMarket * m , const CGHeroInstance * h , ArtifactInstanceID aid , GameResID rid )
{
COMPLAIN_RET_FALSE_IF ( ( ! h ) , " Only hero can sell artifacts! " ) ;
const CArtifactInstance * art = h - > getArtByInstanceId ( aid ) ;
COMPLAIN_RET_FALSE_IF ( ( ! art ) , " There is no artifact to sell! " ) ;
COMPLAIN_RET_FALSE_IF ( ( ! art - > artType - > isTradable ( ) ) , " Cannot sell a war machine or spellbook! " ) ;
2023-02-26 00:19:39 +02:00
2024-01-10 00:38:54 +02:00
int resVal = 0 ;
int dump = 1 ;
2023-07-23 23:00:37 +02:00
m - > getOffer ( art - > artType - > getId ( ) , rid , dump , resVal , EMarketMode : : ARTIFACT_RESOURCE ) ;
2023-02-26 00:19:39 +02:00
2023-10-14 21:00:39 +02:00
removeArtifact ( ArtifactLocation ( h - > id , h - > getArtPos ( art ) ) ) ;
2023-07-23 23:00:37 +02:00
giveResource ( h - > tempOwner , rid , resVal ) ;
return true ;
}
2017-08-19 20:39:24 +02:00
2023-07-23 23:00:37 +02:00
bool CGameHandler : : buySecSkill ( const IMarket * m , const CGHeroInstance * h , SecondarySkill skill )
{
if ( ! h )
COMPLAIN_RET ( " You need hero to buy a skill! " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( h - > getSecSkillLevel ( SecondarySkill ( skill ) ) )
COMPLAIN_RET ( " Hero already know this skill " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( ! h - > canLearnSkill ( ) )
COMPLAIN_RET ( " Hero can't learn any more skills " ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
if ( ! h - > canLearnSkill ( skill ) )
COMPLAIN_RET ( " The hero can't learn this skill! " ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
if ( ! vstd : : contains ( m - > availableItemsIds ( EMarketMode : : RESOURCE_SKILL ) , skill ) )
COMPLAIN_RET ( " That skill is unavailable! " ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
if ( getResource ( h - > tempOwner , EGameResID : : GOLD ) < GameConstants : : SKILL_GOLD_COST ) //TODO: remove hardcoded resource\summ?
COMPLAIN_RET ( " You can't afford to buy this skill " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
giveResource ( h - > tempOwner , EGameResID : : GOLD , - GameConstants : : SKILL_GOLD_COST ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
changeSecSkill ( h , skill , 1 , true ) ;
return true ;
}
2014-03-17 22:51:07 +03:00
2023-11-06 18:27:16 +02:00
bool CGameHandler : : tradeResources ( const IMarket * market , ui32 amountToSell , PlayerColor player , GameResID toSell , GameResID toBuy )
2023-07-23 23:00:37 +02:00
{
2023-11-06 18:27:16 +02:00
TResourceCap haveToSell = getPlayerState ( player ) - > resources [ toSell ] ;
2023-02-26 00:19:39 +02:00
2023-11-06 18:27:16 +02:00
vstd : : amin ( amountToSell , haveToSell ) ; //can't trade more resources than have
2017-02-04 11:33:45 +02:00
2024-01-10 00:38:54 +02:00
int b1 ; //base quantities for trade
int b2 ;
2023-11-06 18:27:16 +02:00
market - > getOffer ( toSell , toBuy , b1 , b2 , EMarketMode : : RESOURCE_RESOURCE ) ;
int amountToBoy = amountToSell / b1 ; //how many base quantities we trade
2014-03-17 22:51:07 +03:00
2023-11-06 18:27:16 +02:00
if ( amountToSell % b1 ! = 0 ) //all offered units of resource should be used, if not -> somewhere in calculations must be an error
2023-07-23 23:00:37 +02:00
{
COMPLAIN_RET ( " Invalid deal, not all offered units of resource were used. " ) ;
2014-03-17 22:51:07 +03:00
}
2017-07-20 06:08:49 +02:00
2023-11-06 18:27:16 +02:00
giveResource ( player , toSell , - b1 * amountToBoy ) ;
giveResource ( player , toBuy , b2 * amountToBoy ) ;
2017-07-20 06:08:49 +02:00
2024-08-07 21:26:22 +02:00
gs - > statistic . accumulatedValues [ player ] . tradeVolume [ toSell ] + = - b1 * amountToBoy ;
gs - > statistic . accumulatedValues [ player ] . tradeVolume [ toBuy ] + = b2 * amountToBoy ;
2024-08-03 19:53:05 +02:00
2023-07-23 23:00:37 +02:00
return true ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : sellCreatures ( ui32 count , const IMarket * market , const CGHeroInstance * hero , SlotID slot , GameResID resourceID )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
if ( ! hero )
COMPLAIN_RET ( " Only hero can sell creatures! " ) ;
if ( ! vstd : : contains ( hero - > Slots ( ) , slot ) )
COMPLAIN_RET ( " Hero doesn't have any creature in that slot! " ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
const CStackInstance & s = hero - > getStack ( slot ) ;
2015-09-16 09:08:40 +02:00
2023-07-23 23:00:37 +02:00
if ( s . count < ( TQuantity ) count //can't sell more creatures than have
| | ( hero - > stacksCount ( ) = = 1 & & hero - > needsLastStack ( ) & & s . count = = count ) ) //can't sell last stack
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " Not enough creatures in army! " ) ;
}
2014-03-17 22:51:07 +03:00
2024-01-10 00:38:54 +02:00
int b1 ; //base quantities for trade
int b2 ;
2023-07-23 23:00:37 +02:00
market - > getOffer ( s . type - > getId ( ) , resourceID , b1 , b2 , EMarketMode : : CREATURE_RESOURCE ) ;
int units = count / b1 ; //how many base quantities we trade
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( count % b1 ) //all offered units of resource should be used, if not -> somewhere in calculations must be an error
{
//TODO: complain?
assert ( 0 ) ;
}
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
changeStackCount ( StackLocation ( hero , slot ) , - ( TQuantity ) count ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
giveResource ( hero - > tempOwner , resourceID , b2 * units ) ;
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
2023-07-23 23:00:37 +02:00
return true ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : transformInUndead ( const IMarket * market , const CGHeroInstance * hero , SlotID slot )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
const CArmedInstance * army = nullptr ;
if ( hero )
army = hero ;
else
army = dynamic_cast < const CGTownInstance * > ( market ) ;
2016-08-30 00:11:54 +02:00
2023-07-23 23:00:37 +02:00
if ( ! army )
COMPLAIN_RET ( " Incorrect call to transform in undead! " ) ;
if ( ! army - > hasStackAtSlot ( slot ) )
COMPLAIN_RET ( " Army doesn't have any creature in that slot! " ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
const CStackInstance & s = army - > getStack ( slot ) ;
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
2023-07-23 23:00:37 +02:00
//resulting creature - bone dragons or skeletons
CreatureID resCreature = CreatureID : : SKELETON ;
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
2023-07-23 23:00:37 +02:00
if ( ( s . hasBonusOfType ( BonusType : : DRAGON_NATURE )
& & ! ( s . hasBonusOfType ( BonusType : : UNDEAD ) ) )
| | ( s . getCreatureID ( ) = = CreatureID : : HYDRA )
| | ( s . getCreatureID ( ) = = CreatureID : : CHAOS_HYDRA ) )
resCreature = CreatureID : : BONE_DRAGON ;
changeStackType ( StackLocation ( army , slot ) , resCreature . toCreature ( ) ) ;
return true ;
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
bool CGameHandler : : sendResources ( ui32 val , PlayerColor player , GameResID r1 , PlayerColor r2 )
2016-10-28 23:37:45 +02:00
{
2023-07-23 23:00:37 +02:00
const PlayerState * p2 = getPlayerState ( r2 , false ) ;
if ( ! p2 | | p2 - > status ! = EPlayerStatus : : INGAME )
2016-10-29 13:45:08 +02:00
{
2023-07-23 23:00:37 +02:00
complain ( " Dest player must be in game! " ) ;
return false ;
}
2015-10-28 22:53:44 +02:00
2023-07-23 23:00:37 +02:00
TResourceCap curRes1 = getPlayerState ( player ) - > resources [ r1 ] ;
2015-10-28 22:53:44 +02:00
2023-07-23 23:00:37 +02:00
vstd : : amin ( val , curRes1 ) ;
2014-11-25 21:00:04 +02:00
2023-07-23 23:00:37 +02:00
giveResource ( player , r1 , - ( int ) val ) ;
giveResource ( r2 , r1 , val ) ;
2014-11-25 21:00:04 +02:00
2023-07-23 23:00:37 +02:00
return true ;
2014-03-17 22:51:07 +03:00
}
2023-11-06 18:27:16 +02:00
bool CGameHandler : : setFormation ( ObjectInstanceID hid , EArmyFormation formation )
2016-10-28 23:37:45 +02:00
{
2023-07-23 23:00:37 +02:00
const CGHeroInstance * h = getHero ( hid ) ;
if ( ! h )
2016-10-29 13:45:08 +02:00
{
2023-07-23 23:00:37 +02:00
logGlobal - > error ( " Hero doesn't exist! " ) ;
return false ;
2016-10-29 13:45:08 +02:00
}
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
ChangeFormation cf ;
cf . hid = hid ;
cf . formation = formation ;
sendAndApply ( & cf ) ;
2017-07-20 06:08:49 +02:00
2023-07-23 23:00:37 +02:00
return true ;
2016-10-28 23:37:45 +02:00
}
2023-09-19 22:17:25 +02:00
bool CGameHandler : : queryReply ( QueryID qid , std : : optional < int32_t > answer , PlayerColor player )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:00:37 +02:00
logGlobal - > trace ( " Player %s attempts answering query %d with answer: " , player , qid ) ;
2023-09-19 22:17:25 +02:00
if ( answer )
logGlobal - > trace ( " %d " , * answer ) ;
2023-07-23 23:00:37 +02:00
2023-07-23 23:10:01 +02:00
auto topQuery = queries - > topQuery ( player ) ;
2023-07-23 23:00:37 +02:00
COMPLAIN_RET_FALSE_IF ( ! topQuery , " This player doesn't have any queries! " ) ;
if ( topQuery - > queryID ! = qid )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:10:01 +02:00
auto currentQuery = queries - > getQuery ( qid ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
if ( currentQuery ! = nullptr & & currentQuery - > endsByPlayerAnswer ( ) )
currentQuery - > setReply ( answer ) ;
2014-03-17 22:51:07 +03:00
2023-07-23 23:00:37 +02:00
COMPLAIN_RET ( " This player top query has different ID! " ) ; //topQuery->queryID != qid
2014-03-17 22:51:07 +03:00
}
2023-07-23 23:00:37 +02:00
COMPLAIN_RET_FALSE_IF ( ! topQuery - > endsByPlayerAnswer ( ) , " This query cannot be ended by player's answer! " ) ;
topQuery - > setReply ( answer ) ;
2023-07-23 23:10:01 +02:00
queries - > popQuery ( topQuery ) ;
2023-07-23 23:00:37 +02:00
return true ;
2014-03-17 22:51:07 +03:00
}
2024-08-12 12:38:18 +02:00
void CGameHandler : : handleTimeEvents ( PlayerColor color )
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
for ( auto const & event : gs - > map - > events )
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
if ( ! event . occursToday ( gs - > day ) )
continue ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
if ( ! event . affectsPlayer ( color , getPlayerState ( color ) - > isHuman ( ) ) )
continue ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
InfoWindow iw ;
iw . player = color ;
iw . text = event . message ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
//give resources
if ( ! event . resources . empty ( ) )
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
giveResources ( color , event . resources ) ;
for ( GameResID i : GameResID : : ALL_RESOURCES ( ) )
if ( event . resources [ i ] )
iw . components . emplace_back ( ComponentType : : RESOURCE , i , event . resources [ i ] ) ;
2014-03-17 22:51:07 +03:00
}
2024-08-12 12:38:18 +02:00
sendAndApply ( & iw ) ; //show dialog
2014-03-17 22:51:07 +03:00
}
}
2024-08-12 12:38:18 +02:00
void CGameHandler : : handleTownEvents ( CGTownInstance * town )
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
for ( auto const & event : town - > events )
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
if ( ! event . occursToday ( gs - > day ) )
continue ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
PlayerColor player = town - > getOwner ( ) ;
if ( ! event . affectsPlayer ( player , getPlayerState ( player ) - > isHuman ( ) ) )
continue ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
// dialog
InfoWindow iw ;
iw . player = player ;
iw . text = event . message ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
if ( event . resources . nonZero ( ) )
{
giveResources ( player , event . resources ) ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
for ( GameResID i : GameResID : : ALL_RESOURCES ( ) )
if ( event . resources [ i ] )
iw . components . emplace_back ( ComponentType : : RESOURCE , i , event . resources [ i ] ) ;
}
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
for ( auto & i : event . buildings )
{
// Only perform action if:
// 1. Building exists in town (don't attempt to build Lvl 5 guild in Fortress
// 2. Building was not built yet
// othervice, silently ignore / skip it
if ( town - > town - > buildings . count ( i ) & & ! town - > hasBuilt ( i ) )
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
buildStructure ( town - > id , i , true ) ;
iw . components . emplace_back ( ComponentType : : BUILDING , BuildingTypeUniqueID ( town - > getFaction ( ) , i ) ) ;
2014-03-17 22:51:07 +03:00
}
2024-08-12 12:38:18 +02:00
}
if ( ! event . creatures . empty ( ) )
{
SetAvailableCreatures sac ;
sac . tid = town - > id ;
sac . creatures = town - > creatures ;
2014-03-17 22:51:07 +03:00
2024-08-12 12:38:18 +02:00
for ( si32 i = 0 ; i < event . creatures . size ( ) ; i + + ) //creature growths
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
if ( ! town - > creatures . at ( i ) . second . empty ( ) & & event . creatures . at ( i ) > 0 ) //there is dwelling
2014-03-17 22:51:07 +03:00
{
2024-08-12 12:38:18 +02:00
sac . creatures [ i ] . first + = event . creatures . at ( i ) ;
iw . components . emplace_back ( ComponentType : : CREATURE , town - > creatures . at ( i ) . second . back ( ) , event . creatures . at ( i ) ) ;
2014-03-17 22:51:07 +03:00
}
}
}
2024-08-12 12:38:18 +02:00
sendAndApply ( & iw ) ; //show dialog
2014-03-17 22:51:07 +03:00
}
}
2016-10-12 17:16:26 +02:00
bool CGameHandler : : complain ( const std : : string & problem )
2014-03-17 22:51:07 +03:00
{
2024-04-25 17:52:53 +02:00
# ifndef ENABLE_GOLDMASTER
2023-07-12 20:13:17 +02:00
playerMessages - > broadcastSystemMessage ( " Server encountered a problem: " + problem ) ;
2024-04-25 17:52:53 +02:00
# endif
2016-08-30 00:11:54 +02:00
logGlobal - > error ( problem ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : showGarrisonDialog ( ObjectInstanceID upobj , ObjectInstanceID hid , bool removableUnits )
2014-03-17 22:51:07 +03:00
{
//PlayerColor player = getOwner(hid);
auto upperArmy = dynamic_cast < const CArmedInstance * > ( getObj ( upobj ) ) ;
auto lowerArmy = dynamic_cast < const CArmedInstance * > ( getObj ( hid ) ) ;
assert ( lowerArmy ) ;
assert ( upperArmy ) ;
2017-06-06 06:53:51 +02:00
auto garrisonQuery = std : : make_shared < CGarrisonDialogQuery > ( this , upperArmy , lowerArmy ) ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( garrisonQuery ) ;
2014-03-17 22:51:07 +03:00
GarrisonDialog gd ;
gd . hid = hid ;
gd . objid = upobj ;
gd . removableUnits = removableUnits ;
gd . queryID = garrisonQuery - > queryID ;
sendAndApply ( & gd ) ;
}
2023-09-28 00:17:05 +02:00
void CGameHandler : : showObjectWindow ( const CGObjectInstance * object , EOpenWindowMode window , const CGHeroInstance * visitor , bool addQuery )
2014-03-17 22:51:07 +03:00
{
2023-09-28 00:17:05 +02:00
OpenWindow pack ;
pack . window = window ;
pack . object = object - > id ;
pack . visitor = visitor - > id ;
if ( addQuery )
{
auto windowQuery = std : : make_shared < OpenWindowQuery > ( this , visitor , window ) ;
pack . queryID = windowQuery - > queryID ;
queries - > addQuery ( windowQuery ) ;
}
sendAndApply ( & pack ) ;
2014-03-17 22:51:07 +03:00
}
2016-10-12 17:16:26 +02:00
bool CGameHandler : : isAllowedExchange ( ObjectInstanceID id1 , ObjectInstanceID id2 )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( id1 = = id2 )
2014-03-17 22:51:07 +03:00
return true ;
2024-01-10 00:38:54 +02:00
const CGObjectInstance * o1 = getObj ( id1 ) ;
const CGObjectInstance * o2 = getObj ( id2 ) ;
2016-10-12 17:16:26 +02:00
if ( ! o1 | | ! o2 )
2014-03-17 22:51:07 +03:00
return true ; //arranging stacks within an object should be always allowed
if ( o1 & & o2 )
{
2016-10-12 17:16:26 +02:00
if ( o1 - > ID = = Obj : : TOWN )
2014-03-17 22:51:07 +03:00
{
const CGTownInstance * t = static_cast < const CGTownInstance * > ( o1 ) ;
2016-10-12 17:16:26 +02:00
if ( t - > visitingHero = = o2 | | t - > garrisonHero = = o2 )
2014-03-17 22:51:07 +03:00
return true ;
}
2016-10-12 17:16:26 +02:00
if ( o2 - > ID = = Obj : : TOWN )
2014-03-17 22:51:07 +03:00
{
const CGTownInstance * t = static_cast < const CGTownInstance * > ( o2 ) ;
2016-10-12 17:16:26 +02:00
if ( t - > visitingHero = = o1 | | t - > garrisonHero = = o1 )
2014-03-17 22:51:07 +03:00
return true ;
}
2024-08-20 16:15:50 +02:00
auto market = getMarket ( id1 ) ;
2024-02-01 10:43:16 +02:00
if ( market = = nullptr )
2024-08-20 16:15:50 +02:00
market = getMarket ( id2 ) ;
2024-02-01 10:43:16 +02:00
if ( market )
return market - > allowsTrade ( EMarketMode : : ARTIFACT_EXP ) ;
2014-03-17 22:51:07 +03:00
if ( o1 - > ID = = Obj : : HERO & & o2 - > ID = = Obj : : HERO )
{
const CGHeroInstance * h1 = static_cast < const CGHeroInstance * > ( o1 ) ;
const CGHeroInstance * h2 = static_cast < const CGHeroInstance * > ( o2 ) ;
// two heroes in same town (garrisoned and visiting)
if ( h1 - > visitedTown ! = nullptr & & h2 - > visitedTown ! = nullptr & & h1 - > visitedTown = = h2 - > visitedTown )
return true ;
}
2022-09-09 19:30:24 +02:00
//Ongoing garrison exchange - usually picking from top garison (from o1 to o2), but who knows
2023-07-23 23:10:01 +02:00
auto dialog = std : : dynamic_pointer_cast < CGarrisonDialogQuery > ( queries - > topQuery ( o1 - > tempOwner ) ) ;
2022-09-09 19:30:24 +02:00
if ( ! dialog )
2014-03-17 22:51:07 +03:00
{
2023-07-23 23:10:01 +02:00
dialog = std : : dynamic_pointer_cast < CGarrisonDialogQuery > ( queries - > topQuery ( o2 - > tempOwner ) ) ;
2022-09-09 19:30:24 +02:00
}
if ( dialog )
{
auto topArmy = dialog - > exchangingArmies . at ( 0 ) ;
auto bottomArmy = dialog - > exchangingArmies . at ( 1 ) ;
2022-09-22 10:22:14 +02:00
if ( ( topArmy = = o1 & & bottomArmy = = o2 ) | | ( bottomArmy = = o1 & & topArmy = = o2 ) )
2014-03-17 22:51:07 +03:00
return true ;
}
}
return false ;
}
2016-10-12 17:16:26 +02:00
void CGameHandler : : objectVisited ( const CGObjectInstance * obj , const CGHeroInstance * h )
2014-03-17 22:51:07 +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
using events : : ObjectVisitStarted ;
2023-10-28 11:27:10 +02:00
logGlobal - > debug ( " %s visits %s (%d) " , h - > nodeName ( ) , obj - > getObjectName ( ) , obj - > ID ) ;
2014-03-17 22:51:07 +03:00
2023-09-26 14:55:07 +02:00
if ( getVisitingHero ( obj ) ! = nullptr )
{
logGlobal - > error ( " Attempt to visit object that is being visited by another hero! " ) ;
throw std : : runtime_error ( " Can not visit object that is being visited " ) ;
}
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
std : : shared_ptr < CObjectVisitQuery > visitQuery ;
auto startVisit = [ & ] ( ObjectVisitStarted & event )
{
2021-10-27 00:37:45 +02:00
auto visitedObject = obj ;
if ( obj - > ID = = Obj : : HERO )
{
auto visitedHero = static_cast < const CGHeroInstance * > ( obj ) ;
const auto visitedTown = visitedHero - > visitedTown ;
if ( visitedTown )
{
const bool isEnemy = visitedHero - > getOwner ( ) ! = h - > getOwner ( ) ;
if ( isEnemy & & ! visitedTown - > isBattleOutsideTown ( visitedHero ) )
visitedObject = visitedTown ;
}
}
visitQuery = std : : make_shared < CObjectVisitQuery > ( this , visitedObject , h , visitedObject - > visitablePos ( ) ) ;
2023-07-23 23:10:01 +02:00
queries - > addQuery ( visitQuery ) ; //TODO real visit pos
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
HeroVisit hv ;
hv . objId = obj - > id ;
hv . heroId = h - > id ;
hv . player = h - > tempOwner ;
hv . starting = true ;
sendAndApply ( & hv ) ;
obj - > onHeroVisit ( h ) ;
} ;
2014-03-17 22:51:07 +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
ObjectVisitStarted : : defaultExecute ( serverEventBus . get ( ) , startVisit , h - > tempOwner , h - > id , obj - > id ) ;
2014-03-17 22:51:07 +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 ( visitQuery )
2023-07-23 23:10:01 +02:00
queries - > popIfTop ( visitQuery ) ; //visit ends here if no queries were created
2014-03-17 22:51:07 +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
void CGameHandler : : objectVisitEnded ( const CObjectVisitQuery & query )
2014-03-17 22:51:07 +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
using events : : ObjectVisitEnded ;
2016-08-30 00:11:54 +02:00
logGlobal - > debug ( " %s visit ends. \n " , query . visitingHero - > nodeName ( ) ) ;
2014-03-17 22:51:07 +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
auto endVisit = [ & ] ( ObjectVisitEnded & event )
{
HeroVisit hv ;
hv . player = event . getPlayer ( ) ;
hv . heroId = event . getHero ( ) ;
hv . starting = false ;
sendAndApply ( & hv ) ;
} ;
//TODO: ObjectVisitEnded should also have id of visited object,
//but this requires object being deleted only by `removeAfterVisit()` but not `removeObject()`
ObjectVisitEnded : : defaultExecute ( serverEventBus . get ( ) , endVisit , query . players . front ( ) , query . visitingHero - > id ) ;
2014-03-17 22:51:07 +03:00
}
2023-03-31 19:02:09 +02:00
bool CGameHandler : : buildBoat ( ObjectInstanceID objid , PlayerColor playerID )
2014-03-17 22:51:07 +03:00
{
2024-02-14 12:56:37 +02:00
const auto * obj = dynamic_cast < const IShipyard * > ( getObj ( objid ) ) ;
2014-03-17 22:51:07 +03:00
2016-10-12 17:16:26 +02:00
if ( obj - > shipyardStatus ( ) ! = IBoatGenerator : : GOOD )
2014-03-17 22:51:07 +03:00
{
complain ( " Cannot build boat in this shipyard! " ) ;
return false ;
}
TResources boatCost ;
obj - > getBoatCost ( boatCost ) ;
2024-06-24 03:23:26 +02:00
TResources available = getPlayerState ( playerID ) - > resources ;
2014-03-17 22:51:07 +03:00
2024-06-24 03:23:26 +02:00
if ( ! available . canAfford ( boatCost ) )
2014-03-17 22:51:07 +03:00
{
complain ( " Not enough resources to build a boat! " ) ;
return false ;
}
int3 tile = obj - > bestLocation ( ) ;
2016-10-12 17:16:26 +02:00
if ( ! gs - > map - > isInTheMap ( tile ) )
2014-03-17 22:51:07 +03:00
{
complain ( " Cannot find appropriate tile for a boat! " ) ;
return false ;
}
2016-11-26 14:14:43 +02:00
giveResources ( playerID , - boatCost ) ;
2024-07-12 18:51:27 +02:00
createBoat ( tile , obj - > getBoatType ( ) , playerID ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
void CGameHandler : : checkVictoryLossConditions ( const std : : set < PlayerColor > & playerColors )
{
2016-10-12 17:16:26 +02:00
for ( auto playerColor : playerColors )
2014-03-17 22:51:07 +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 ( getPlayerState ( playerColor , false ) )
2014-03-17 22:51:07 +03:00
checkVictoryLossConditionsForPlayer ( playerColor ) ;
}
}
void CGameHandler : : checkVictoryLossConditionsForAll ( )
{
std : : set < PlayerColor > playerColors ;
2016-10-12 17:16:26 +02:00
for ( int i = 0 ; i < PlayerColor : : PLAYER_LIMIT_I ; + + i )
2014-03-17 22:51:07 +03:00
{
playerColors . insert ( PlayerColor ( i ) ) ;
}
checkVictoryLossConditions ( playerColors ) ;
}
void CGameHandler : : checkVictoryLossConditionsForPlayer ( PlayerColor player )
{
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 PlayerState * p = getPlayerState ( player ) ;
2021-07-15 23:32:13 +02:00
if ( ! p | | p - > status ! = EPlayerStatus : : INGAME ) return ;
2014-03-17 22:51:07 +03:00
auto victoryLossCheckResult = gs - > checkForVictoryAndLoss ( player ) ;
2016-10-12 17:16:26 +02:00
if ( victoryLossCheckResult . victory ( ) | | victoryLossCheckResult . loss ( ) )
2014-03-17 22:51:07 +03:00
{
InfoWindow iw ;
getVictoryLossMessage ( player , victoryLossCheckResult , iw ) ;
sendAndApply ( & iw ) ;
PlayerEndsGame peg ;
peg . player = player ;
peg . victoryLossCheckResult = victoryLossCheckResult ;
sendAndApply ( & peg ) ;
2023-08-24 12:36:35 +02:00
turnOrder - > onPlayerEndsGame ( player ) ;
2016-10-12 17:16:26 +02:00
if ( victoryLossCheckResult . victory ( ) )
2014-03-17 22:51:07 +03:00
{
//one player won -> all enemies lost
for ( auto i = gs - > players . cbegin ( ) ; i ! = gs - > players . cend ( ) ; 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
if ( i - > first ! = player & & getPlayerState ( i - > first ) - > status = = EPlayerStatus : : INGAME )
2014-03-17 22:51:07 +03:00
{
peg . player = i - > first ;
2016-09-18 10:53:51 +02:00
peg . victoryLossCheckResult = getPlayerRelations ( player , i - > first ) = = PlayerRelations : : ALLIES ?
2014-03-17 22:51:07 +03:00
victoryLossCheckResult : victoryLossCheckResult . invert ( ) ; // ally of winner
InfoWindow iw ;
getVictoryLossMessage ( player , peg . victoryLossCheckResult , iw ) ;
iw . player = i - > first ;
sendAndApply ( & iw ) ;
sendAndApply ( & peg ) ;
}
}
2018-01-05 19:21:07 +02:00
if ( p - > human )
2014-03-17 22:51:07 +03:00
{
2024-02-03 19:08:45 +02:00
lobby - > setState ( EServerState : : SHUTDOWN ) ;
2014-03-17 22:51:07 +03:00
}
}
else
{
2015-12-10 11:43:55 +02:00
//copy heroes vector to avoid iterator invalidation as removal change PlayerState
auto hlp = p - > heroes ;
2016-10-12 17:16:26 +02:00
for ( auto h : hlp ) //eliminate heroes
2015-12-10 11:43:55 +02:00
{
2016-10-12 17:16:26 +02:00
if ( h . get ( ) )
2023-09-18 21:09:55 +02:00
removeObject ( h , player ) ;
2015-12-10 11:43:55 +02:00
}
2014-03-17 22:51:07 +03:00
2015-12-10 11:43:55 +02:00
//player lost -> all his objects become unflagged (neutral)
2015-10-24 15:09:46 +02:00
for ( auto obj : gs - > map - > objects ) //unflag objs
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( obj . get ( ) & & obj - > tempOwner = = player )
2015-10-24 15:09:46 +02:00
setOwner ( obj , PlayerColor : : NEUTRAL ) ;
2014-03-17 22:51:07 +03:00
}
//eliminating one player may cause victory of another:
std : : set < PlayerColor > playerColors ;
2015-10-24 17:15:21 +02:00
//do not copy player state (CBonusSystemNode) by value
2015-10-25 08:07:01 +02:00
for ( auto & p : gs - > players ) //players may have different colors, iterate over players and not integers
2014-03-17 22:51:07 +03:00
{
2015-10-25 08:07:01 +02:00
if ( p . first ! = player )
playerColors . insert ( p . first ) ;
2014-03-17 22:51:07 +03:00
}
//notify all players
2015-10-24 15:09:46 +02:00
for ( auto pc : playerColors )
2014-03-17 22:51:07 +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 ( getPlayerState ( pc ) - > status = = EPlayerStatus : : INGAME )
2014-03-17 22:51:07 +03:00
{
InfoWindow iw ;
getVictoryLossMessage ( player , victoryLossCheckResult . invert ( ) , iw ) ;
2015-10-24 15:09:46 +02:00
iw . player = pc ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & iw ) ;
}
}
checkVictoryLossConditions ( playerColors ) ;
}
}
}
2014-06-25 17:11:07 +03:00
void CGameHandler : : getVictoryLossMessage ( PlayerColor player , const EVictoryLossCheckResult & victoryLossCheckResult , InfoWindow & out ) const
2014-03-17 22:51:07 +03:00
{
out . player = player ;
2023-06-18 17:59:04 +02:00
out . text = victoryLossCheckResult . messageToSelf ;
2023-11-02 22:01:49 +02:00
out . text . replaceName ( player ) ;
2023-10-31 11:09:56 +02:00
out . components . emplace_back ( ComponentType : : FLAG , player ) ;
2014-03-17 22:51:07 +03:00
}
2016-10-12 17:16:26 +02:00
bool CGameHandler : : dig ( const CGHeroInstance * h )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( h - > diggingStatus ( ) ! = EDiggingStatus : : CAN_DIG ) //checks for terrain and movement
2023-08-19 20:43:50 +02:00
COMPLAIN_RETF ( " Hero cannot dig (error code %d)! " , static_cast < int > ( h - > diggingStatus ( ) ) ) ;
2014-03-17 22:51:07 +03:00
2024-07-12 18:51:27 +02:00
createHole ( h - > visitablePos ( ) , h - > getOwner ( ) ) ;
2014-03-17 22:51:07 +03:00
//take MPs
SetMovePoints smp ;
smp . hid = h - > id ;
smp . val = 0 ;
sendAndApply ( & smp ) ;
InfoWindow iw ;
2023-03-07 03:09:19 +02:00
iw . type = EInfoWindowMode : : AUTO ;
2014-03-17 22:51:07 +03:00
iw . player = h - > tempOwner ;
2022-12-07 21:50:45 +02:00
if ( gs - > map - > grailPos = = h - > visitablePos ( ) )
2014-03-17 22:51:07 +03:00
{
2023-11-02 22:01:49 +02:00
ArtifactID grail = ArtifactID : : GRAIL ;
2024-02-01 18:22:11 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 58 ) ; //"Congratulations! After spending many hours digging here, your hero has uncovered the " ...
iw . text . appendName ( grail ) ; // ... " The Grail"
2014-03-17 22:51:07 +03:00
iw . soundID = soundBase : : ULTIMATEARTIFACT ;
2023-11-02 22:01:49 +02:00
giveHeroNewArtifact ( h , grail . toArtifact ( ) , ArtifactPosition : : FIRST_AVAILABLE ) ; //give grail
2014-03-17 22:51:07 +03:00
sendAndApply ( & iw ) ;
iw . soundID = soundBase : : invalid ;
2023-11-02 22:01:49 +02:00
iw . components . emplace_back ( ComponentType : : ARTIFACT , grail ) ;
2014-03-17 22:51:07 +03:00
iw . text . clear ( ) ;
2023-11-02 22:01:49 +02:00
iw . text . appendTextID ( grail . toArtifact ( ) - > getDescriptionTextID ( ) ) ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & iw ) ;
}
else
{
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 59 ) ; //"Nothing here. \n Where could it be?"
2014-03-17 22:51:07 +03:00
iw . soundID = soundBase : : Dig ;
sendAndApply ( & iw ) ;
}
return true ;
}
void CGameHandler : : visitObjectOnTile ( const TerrainTile & t , const CGHeroInstance * h )
{
if ( ! t . visitableObjects . empty ( ) )
{
//to prevent self-visiting heroes on space press
2016-10-12 17:16:26 +02:00
if ( t . visitableObjects . back ( ) ! = h )
2014-03-17 22:51:07 +03:00
objectVisited ( t . visitableObjects . back ( ) , h ) ;
2016-10-12 17:16:26 +02:00
else if ( t . visitableObjects . size ( ) > 1 )
2014-03-17 22:51:07 +03:00
objectVisited ( * ( t . visitableObjects . end ( ) - 2 ) , h ) ;
}
}
2017-10-28 11:04:55 +02:00
bool CGameHandler : : sacrificeCreatures ( const IMarket * market , const CGHeroInstance * hero , const std : : vector < SlotID > & slot , const std : : vector < ui32 > & count )
2014-03-17 22:51:07 +03:00
{
2016-11-27 21:50:37 +02:00
if ( ! hero )
COMPLAIN_RET ( " You need hero to sacrifice creature! " ) ;
2017-10-14 21:30:56 +02:00
int expSum = 0 ;
auto finish = [ this , & hero , & expSum ] ( )
{
2024-01-04 23:57:36 +02:00
giveExperience ( hero , hero - > calculateXp ( expSum ) ) ;
2017-10-14 21:30:56 +02:00
} ;
2017-10-28 11:04:55 +02:00
for ( int i = 0 ; i < slot . size ( ) ; + + i )
2017-10-14 21:30:56 +02:00
{
int oldCount = hero - > getStackCount ( slot [ i ] ) ;
2020-10-01 10:38:06 +02:00
if ( oldCount < ( int ) count [ i ] )
2017-10-14 21:30:56 +02:00
{
finish ( ) ;
COMPLAIN_RET ( " Not enough creatures to sacrifice! " )
}
2017-10-28 11:04:55 +02:00
else if ( oldCount = = count [ i ] & & hero - > stacksCount ( ) = = 1 & & hero - > needsLastStack ( ) )
2017-10-14 21:30:56 +02:00
{
finish ( ) ;
COMPLAIN_RET ( " Cannot sacrifice last creature! " ) ;
}
2014-03-17 22:51:07 +03:00
2023-04-05 02:26:29 +02:00
int crid = hero - > getStack ( slot [ i ] ) . type - > getId ( ) ;
2014-03-17 22:51:07 +03:00
2020-10-01 10:38:06 +02:00
changeStackCount ( StackLocation ( hero , slot [ i ] ) , - ( TQuantity ) count [ i ] ) ;
2014-03-17 22:51:07 +03:00
2024-01-10 00:38:54 +02:00
int dump ;
int exp ;
2017-10-14 21:30:56 +02:00
market - > getOffer ( crid , 0 , dump , exp , EMarketMode : : CREATURE_EXP ) ;
exp * = count [ i ] ;
expSum + = exp ;
}
2014-03-17 22:51:07 +03:00
2017-10-14 21:30:56 +02:00
finish ( ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
2024-08-17 21:06:48 +02:00
bool CGameHandler : : sacrificeArtifact ( const IMarket * market , const CGHeroInstance * hero , const std : : vector < ArtifactInstanceID > & arts )
2014-03-17 22:51:07 +03:00
{
2016-11-27 21:50:37 +02:00
if ( ! hero )
COMPLAIN_RET ( " You need hero to sacrifice artifact! " ) ;
2024-01-16 21:54:00 +02:00
if ( hero - > getAlignment ( ) = = EAlignment : : EVIL )
COMPLAIN_RET ( " Evil hero can't sacrifice artifact! " ) ;
2024-08-17 19:19:48 +02:00
assert ( market ) ;
2024-08-20 16:15:50 +02:00
const auto artSet = market - > getArtifactsStorage ( ) ;
2016-11-27 21:50:37 +02:00
2017-10-14 21:30:56 +02:00
int expSum = 0 ;
auto finish = [ this , & hero , & expSum ] ( )
{
2024-01-04 23:57:36 +02:00
giveExperience ( hero , hero - > calculateXp ( expSum ) ) ;
2017-10-14 21:30:56 +02:00
} ;
2024-01-16 21:54:00 +02:00
for ( const auto & artInstId : arts )
2017-10-14 21:30:56 +02:00
{
2024-08-17 19:19:48 +02:00
if ( auto art = artSet - > getArtByInstanceId ( artInstId ) )
2017-10-14 21:30:56 +02:00
{
2024-01-16 21:54:00 +02:00
if ( art - > artType - > isTradable ( ) )
{
int dmp ;
int expToGive ;
2024-08-17 19:19:48 +02:00
market - > getOffer ( art - > getTypeId ( ) , 0 , dmp , expToGive , EMarketMode : : ARTIFACT_EXP ) ;
2024-01-16 21:54:00 +02:00
expSum + = expToGive ;
2024-08-20 16:15:50 +02:00
removeArtifact ( ArtifactLocation ( market - > getObjInstanceID ( ) , artSet - > getArtPos ( art ) ) ) ;
2024-01-16 21:54:00 +02:00
}
else
{
COMPLAIN_RET ( " Cannot sacrifice not tradable artifact! " ) ;
}
2017-10-14 21:30:56 +02:00
}
2024-01-16 21:54:00 +02:00
else
2017-10-14 21:30:56 +02:00
{
finish ( ) ;
2024-01-16 21:54:00 +02:00
COMPLAIN_RET ( " Cannot find artifact to sacrifice! " ) ;
2017-10-14 21:30:56 +02:00
}
}
2016-11-26 21:06:12 +02:00
2017-10-14 21:30:56 +02:00
finish ( ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
bool CGameHandler : : insertNewStack ( const StackLocation & sl , const CCreature * c , TQuantity count )
{
2016-10-12 17:16:26 +02:00
if ( sl . army - > hasStackAtSlot ( sl . slot ) )
2014-03-17 22:51:07 +03:00
COMPLAIN_RET ( " Slot is already taken! " ) ;
2016-10-12 17:16:26 +02:00
if ( ! sl . slot . validSlot ( ) )
2014-03-17 22:51:07 +03:00
COMPLAIN_RET ( " Cannot insert stack to that slot! " ) ;
InsertNewStack ins ;
2018-03-10 23:19:36 +02:00
ins . army = sl . army - > id ;
ins . slot = sl . slot ;
2023-04-05 02:26:29 +02:00
ins . type = c - > getId ( ) ;
2018-03-10 23:19:36 +02:00
ins . count = count ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & ins ) ;
return true ;
}
2017-07-15 13:08:20 +02:00
bool CGameHandler : : eraseStack ( const StackLocation & sl , bool forceRemoval )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( ! sl . army - > hasStackAtSlot ( sl . slot ) )
2014-03-17 22:51:07 +03:00
COMPLAIN_RET ( " Cannot find a stack to erase " ) ;
2016-10-12 17:16:26 +02:00
if ( sl . army - > stacksCount ( ) = = 1 //from the last stack
2014-03-17 22:51:07 +03:00
& & sl . army - > needsLastStack ( ) //that must be left
& & ! forceRemoval ) //ignore above conditions if we are forcing removal
{
COMPLAIN_RET ( " Cannot erase the last stack! " ) ;
}
EraseStack es ;
2018-03-10 23:19:36 +02:00
es . army = sl . army - > id ;
es . slot = sl . slot ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & es ) ;
return true ;
}
2017-07-15 13:08:20 +02:00
bool CGameHandler : : changeStackCount ( const StackLocation & sl , TQuantity count , bool absoluteValue )
2014-03-17 22:51:07 +03:00
{
TQuantity currentCount = sl . army - > getStackCount ( sl . slot ) ;
2016-10-12 17:16:26 +02:00
if ( ( absoluteValue & & count < 0 )
2014-03-17 22:51:07 +03:00
| | ( ! absoluteValue & & - count > currentCount ) )
{
COMPLAIN_RET ( " Cannot take more stacks than present! " ) ;
}
2016-10-12 17:16:26 +02:00
if ( ( currentCount = = - count & & ! absoluteValue )
2014-03-17 22:51:07 +03:00
| | ( ! count & & absoluteValue ) )
{
eraseStack ( sl ) ;
}
else
{
ChangeStackCount csc ;
2018-03-10 23:19:36 +02:00
csc . army = sl . army - > id ;
csc . slot = sl . slot ;
2014-03-17 22:51:07 +03:00
csc . count = count ;
csc . absoluteValue = absoluteValue ;
sendAndApply ( & csc ) ;
}
return true ;
}
bool CGameHandler : : addToSlot ( const StackLocation & sl , const CCreature * c , TQuantity count )
{
const CCreature * slotC = sl . army - > getCreature ( sl . slot ) ;
2016-10-12 17:16:26 +02:00
if ( ! slotC ) //slot is empty
2014-03-17 22:51:07 +03:00
insertNewStack ( sl , c , count ) ;
2016-10-12 17:16:26 +02:00
else if ( c = = slotC )
2014-03-17 22:51:07 +03:00
changeStackCount ( sl , count ) ;
else
{
2023-01-02 18:00:51 +02:00
COMPLAIN_RET ( " Cannot add " + c - > getNamePluralTranslated ( ) + " to slot " + boost : : lexical_cast < std : : string > ( sl . slot ) + " ! " ) ;
2014-03-17 22:51:07 +03:00
}
return true ;
}
void CGameHandler : : tryJoiningArmy ( const CArmedInstance * src , const CArmedInstance * dst , bool removeObjWhenFinished , bool allowMerging )
{
2016-10-12 17:16:26 +02:00
if ( removeObjWhenFinished )
2014-03-17 22:51:07 +03:00
removeAfterVisit ( src ) ;
2016-10-12 17:16:26 +02:00
if ( ! src - > canBeMergedWith ( * dst , allowMerging ) )
2014-03-17 22:51:07 +03:00
{
if ( allowMerging ) //do that, add all matching creatures.
{
bool cont = true ;
while ( cont )
{
2016-10-12 17:16:26 +02:00
for ( auto i = src - > stacks . begin ( ) ; i ! = src - > stacks . end ( ) ; i + + ) //while there are unmoved creatures
2014-03-17 22:51:07 +03:00
{
SlotID pos = dst - > getSlotFor ( i - > second - > type ) ;
2016-10-12 17:16:26 +02:00
if ( pos . validSlot ( ) )
2014-03-17 22:51:07 +03:00
{
moveStack ( StackLocation ( src , i - > first ) , StackLocation ( dst , pos ) ) ;
cont = true ;
break ; //or iterator crashes
}
cont = false ;
}
}
}
showGarrisonDialog ( src - > id , dst - > id , true ) ; //show garrison window and optionally remove ourselves from map when player ends
}
else //merge
{
moveArmy ( src , dst , allowMerging ) ;
}
}
bool CGameHandler : : moveStack ( const StackLocation & src , const StackLocation & dst , TQuantity count )
{
2016-10-12 17:16:26 +02:00
if ( ! src . army - > hasStackAtSlot ( src . slot ) )
2014-03-17 22:51:07 +03:00
COMPLAIN_RET ( " No stack to move! " ) ;
2016-10-12 17:16:26 +02:00
if ( dst . army - > hasStackAtSlot ( dst . slot ) & & dst . army - > getCreature ( dst . slot ) ! = src . army - > getCreature ( src . slot ) )
2014-03-17 22:51:07 +03:00
COMPLAIN_RET ( " Cannot move: stack of different type at destination pos! " ) ;
2016-10-12 17:16:26 +02:00
if ( ! dst . slot . validSlot ( ) )
2014-03-17 22:51:07 +03:00
COMPLAIN_RET ( " Cannot move stack to that slot! " ) ;
2016-10-12 17:16:26 +02:00
if ( count = = - 1 )
2014-03-17 22:51:07 +03:00
{
count = src . army - > getStackCount ( src . slot ) ;
}
2016-10-12 17:16:26 +02:00
if ( src . army ! = dst . army //moving away
2014-03-17 22:51:07 +03:00
& & count = = src . army - > getStackCount ( src . slot ) //all creatures
2015-12-24 20:30:57 +02:00
& & src . army - > stacksCount ( ) = = 1 //from the last stack
2014-03-17 22:51:07 +03:00
& & src . army - > needsLastStack ( ) ) //that must be left
{
COMPLAIN_RET ( " Cannot move away the last creature! " ) ;
}
RebalanceStacks rs ;
2018-03-10 23:19:36 +02:00
rs . srcArmy = src . army - > id ;
rs . dstArmy = dst . army - > id ;
rs . srcSlot = src . slot ;
rs . dstSlot = dst . slot ;
2014-03-17 22:51:07 +03:00
rs . count = count ;
sendAndApply ( & rs ) ;
return true ;
}
2023-04-10 03:40:03 +02:00
void CGameHandler : : castSpell ( const spells : : Caster * caster , SpellID spellID , const int3 & pos )
2023-04-10 02:34:24 +02:00
{
2024-01-20 16:41:10 +02:00
if ( ! spellID . hasValue ( ) )
2023-04-10 02:34:24 +02:00
return ;
AdventureSpellCastParameters p ;
2023-04-10 03:40:03 +02:00
p . caster = caster ;
2023-04-10 02:34:24 +02:00
p . pos = pos ;
2024-01-20 16:41:10 +02:00
const CSpell * s = spellID . toSpell ( ) ;
2023-04-10 02:34:24 +02:00
s - > adventureCast ( spellEnv , p ) ;
}
2018-03-10 23:19:36 +02:00
bool CGameHandler : : swapStacks ( const StackLocation & sl1 , const StackLocation & sl2 )
2014-03-17 22:51:07 +03:00
{
2018-03-10 23:19:36 +02:00
if ( ! sl1 . army - > hasStackAtSlot ( sl1 . slot ) )
{
2014-03-17 22:51:07 +03:00
return moveStack ( sl2 , sl1 ) ;
2018-03-10 23:19:36 +02:00
}
else if ( ! sl2 . army - > hasStackAtSlot ( sl2 . slot ) )
{
2014-03-17 22:51:07 +03:00
return moveStack ( sl1 , sl2 ) ;
2018-03-10 23:19:36 +02:00
}
2014-03-17 22:51:07 +03:00
else
{
SwapStacks ss ;
2018-03-10 23:19:36 +02:00
ss . srcArmy = sl1 . army - > id ;
ss . dstArmy = sl2 . army - > id ;
ss . srcSlot = sl1 . slot ;
ss . dstSlot = sl2 . slot ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & ss ) ;
return true ;
}
}
2023-11-08 19:54:02 +02:00
bool CGameHandler : : putArtifact ( const ArtifactLocation & al , const CArtifactInstance * art , std : : optional < bool > askAssemble )
2014-03-17 22:51:07 +03:00
{
2023-11-08 19:54:02 +02:00
assert ( art & & art - > artType ) ;
ArtifactLocation dst ( al . artHolder , ArtifactPosition : : PRE_FIRST ) ;
dst . creature = al . creature ;
auto putTo = getArtSet ( al ) ;
assert ( putTo ) ;
2014-03-17 22:51:07 +03:00
2023-11-08 19:54:02 +02:00
if ( al . slot = = ArtifactPosition : : FIRST_AVAILABLE )
2014-03-17 22:51:07 +03:00
{
2023-11-08 19:54:02 +02:00
dst . slot = ArtifactUtils : : getArtAnyPosition ( putTo , art - > getTypeId ( ) ) ;
2014-03-17 22:51:07 +03:00
}
2023-11-08 19:54:02 +02:00
else if ( ArtifactUtils : : isSlotBackpack ( al . slot ) & & ! al . creature . has_value ( ) )
2014-03-17 22:51:07 +03:00
{
2023-11-08 19:54:02 +02:00
dst . slot = ArtifactUtils : : getArtBackpackPosition ( putTo , art - > getTypeId ( ) ) ;
2014-03-17 22:51:07 +03:00
}
else
{
2023-11-08 19:54:02 +02:00
dst . slot = al . slot ;
}
if ( ! askAssemble . has_value ( ) )
{
if ( ! dst . creature . has_value ( ) & & ArtifactUtils : : isSlotEquipment ( dst . slot ) )
askAssemble = true ;
else
askAssemble = false ;
2014-03-17 22:51:07 +03:00
}
2023-11-08 19:54:02 +02:00
if ( art - > canBePutAt ( putTo , dst . slot ) )
{
PutArtifact pa ( dst , askAssemble . value ( ) ) ;
pa . art = art ;
sendAndApply ( & pa ) ;
return true ;
}
2023-03-18 22:58:39 +02:00
else
2023-11-08 19:54:02 +02:00
{
2023-03-18 22:58:39 +02:00
return false ;
2023-11-08 19:54:02 +02:00
}
2014-03-17 22:51:07 +03:00
}
2023-03-20 01:18:32 +02:00
bool CGameHandler : : giveHeroNewArtifact ( const CGHeroInstance * h , const CArtifact * artType , ArtifactPosition pos )
2017-05-26 18:51:45 +02:00
{
2023-03-20 01:18:32 +02:00
assert ( artType ) ;
2023-05-09 15:48:52 +02:00
if ( pos = = ArtifactPosition : : FIRST_AVAILABLE )
{
if ( ! artType - > canBePutAt ( h , ArtifactUtils : : getArtAnyPosition ( h , artType - > getId ( ) ) ) )
COMPLAIN_RET ( " Cannot put artifact in that slot! " ) ;
}
else if ( ArtifactUtils : : isSlotBackpack ( pos ) )
{
if ( ! artType - > canBePutAt ( h , ArtifactUtils : : getArtBackpackPosition ( h , artType - > getId ( ) ) ) )
COMPLAIN_RET ( " Cannot put artifact in that slot! " ) ;
}
else
{
2023-03-20 01:18:32 +02:00
COMPLAIN_RET_FALSE_IF ( ! artType - > canBePutAt ( h , pos , false ) , " Cannot put artifact in that slot! " ) ;
2023-05-09 15:48:52 +02:00
}
2017-05-26 18:51:45 +02:00
2023-06-18 14:21:35 +02:00
auto * newArtInst = new CArtifactInstance ( ) ;
2023-03-20 01:18:32 +02:00
newArtInst - > artType = artType ; // *NOT* via settype -> all bonus-related stuff must be done by NewArtifact apply
2023-05-09 15:48:52 +02:00
NewArtifact na ;
na . art = newArtInst ;
2023-06-18 14:21:35 +02:00
sendAndApply ( & na ) ; // -> updates newArtInst!!!
2023-05-09 15:48:52 +02:00
2023-11-08 19:54:02 +02:00
if ( putArtifact ( ArtifactLocation ( h - > id , pos ) , newArtInst , false ) )
2023-03-20 01:18:32 +02:00
return true ;
2014-03-17 22:51:07 +03:00
else
2023-03-20 01:18:32 +02:00
return false ;
2014-03-17 22:51:07 +03:00
}
void CGameHandler : : spawnWanderingMonsters ( CreatureID creatureID )
{
std : : vector < int3 > : : iterator tile ;
std : : vector < int3 > tiles ;
getFreeTiles ( tiles ) ;
2020-10-01 10:38:06 +02:00
ui32 amount = ( ui32 ) tiles . size ( ) / 200 ; //Chance is 0.5% for each tile
2016-09-09 21:11:13 +02:00
RandomGeneratorUtil : : randomShuffle ( tiles , getRandomGenerator ( ) ) ;
2016-08-30 00:11:54 +02:00
logGlobal - > trace ( " Spawning wandering monsters. Found %d free tiles. Creature type: %d " , tiles . size ( ) , creatureID . num ) ;
2023-12-31 23:43:35 +02:00
const CCreature * cre = creatureID . toCreature ( ) ;
2020-10-01 10:38:06 +02:00
for ( int i = 0 ; i < ( int ) amount ; + + i )
2014-03-17 22:51:07 +03:00
{
tile = tiles . begin ( ) ;
2017-08-12 13:36:04 +02:00
logGlobal - > trace ( " \t Spawning monster at %s " , tile - > toString ( ) ) ;
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
{
auto count = cre - > getRandomAmount ( std : : rand ) ;
2024-07-12 18:51:27 +02:00
createWanderingMonster ( * tile , creatureID ) ;
2023-06-20 21:25:49 +02:00
auto monsterId = getTopObj ( * tile ) - > id ;
2023-11-06 18:27:16 +02:00
setObjPropertyValue ( monsterId , ObjProperty : : MONSTER_COUNT , count ) ;
setObjPropertyValue ( monsterId , ObjProperty : : MONSTER_POWER , ( si64 ) 1000 * 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
}
2014-03-17 22:51:07 +03:00
tiles . erase ( tile ) ; //not use it again
}
}
void CGameHandler : : synchronizeArtifactHandlerLists ( )
{
UpdateArtHandlerLists uahl ;
2023-11-07 14:27:25 +02:00
uahl . allocatedArtifacts = gs - > allocatedArtifacts ;
2014-03-17 22:51:07 +03:00
sendAndApply ( & uahl ) ;
}
bool CGameHandler : : isValidObject ( const CGObjectInstance * obj ) const
{
return vstd : : contains ( gs - > map - > objects , obj ) ;
}
bool CGameHandler : : isBlockedByQueries ( const CPack * pack , PlayerColor player )
{
2016-10-12 17:16:26 +02:00
if ( ! strcmp ( typeid ( * pack ) . name ( ) , typeid ( PlayerMessage ) . name ( ) ) )
2014-03-17 22:51:07 +03:00
return false ;
2023-07-23 23:10:01 +02:00
auto query = queries - > topQuery ( player ) ;
2016-10-12 17:16:26 +02:00
if ( query & & query - > blocksPack ( pack ) )
2014-03-17 22:51:07 +03:00
{
2021-11-28 14:57:38 +02:00
complain ( boost : : str ( boost : : format (
" \r \n | Player \" %s \" has to answer queries before attempting any further actions. \r \n | Top Query: \" %s \" \r \n " )
2023-09-04 21:21:02 +02:00
% boost : : to_upper_copy < std : : string > ( player . toString ( ) )
2021-11-28 14:57:38 +02:00
% query - > toString ( )
) ) ;
2014-03-17 22:51:07 +03:00
return true ;
}
return false ;
}
void CGameHandler : : removeAfterVisit ( const CGObjectInstance * object )
{
//If the object is being visited, there must be a matching query
2023-07-23 23:10:01 +02:00
for ( const auto & query : queries - > allQueries ( ) )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( auto someVistQuery = std : : dynamic_pointer_cast < CObjectVisitQuery > ( query ) )
2014-03-17 22:51:07 +03:00
{
2016-10-12 17:16:26 +02:00
if ( someVistQuery - > visitedObject = = object )
2014-03-17 22:51:07 +03:00
{
someVistQuery - > removeObjectAfterVisit = true ;
return ;
}
}
2017-07-12 21:01:10 +02:00
}
2014-03-17 22:51:07 +03:00
//If we haven't returned so far, there is no query and no visit, call was wrong
assert ( " This function needs to be called during the object visit! " ) ;
}
2023-10-04 14:31:42 +02:00
void CGameHandler : : changeFogOfWar ( int3 center , ui32 radius , PlayerColor player , ETileVisibility mode )
2014-06-24 14:50:27 +03:00
{
2023-04-16 00:48:49 +02:00
std : : unordered_set < int3 > tiles ;
2023-10-04 14:31:42 +02:00
if ( mode = = ETileVisibility : : HIDDEN )
2014-07-01 10:13:19 +03:00
{
2023-10-04 14:31:42 +02:00
getTilesInRange ( tiles , center , radius , ETileVisibility : : REVEALED , player ) ;
2023-04-16 00:48:49 +02:00
std : : unordered_set < int3 > observedTiles ; //do not hide tiles observed by heroes. May lead to disastrous AI problems
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
auto p = getPlayerState ( player ) ;
2014-07-01 10:13:19 +03:00
for ( auto h : p - > heroes )
{
2023-10-04 14:31:42 +02:00
getTilesInRange ( observedTiles , h - > getSightCenter ( ) , h - > getSightRadius ( ) , ETileVisibility : : REVEALED , h - > tempOwner ) ;
2014-07-01 10:13:19 +03:00
}
for ( auto t : p - > towns )
{
2023-10-04 14:31:42 +02:00
getTilesInRange ( observedTiles , t - > getSightCenter ( ) , t - > getSightRadius ( ) , ETileVisibility : : REVEALED , t - > tempOwner ) ;
2014-07-01 10:13:19 +03:00
}
for ( auto tile : observedTiles )
vstd : : erase_if_present ( tiles , tile ) ;
}
2023-10-04 14:31:42 +02:00
else
{
getTilesInRange ( tiles , center , radius , ETileVisibility : : HIDDEN , player ) ;
}
changeFogOfWar ( tiles , player , mode ) ;
2014-06-24 14:50:27 +03:00
}
2023-10-04 14:31:42 +02:00
void CGameHandler : : changeFogOfWar ( std : : unordered_set < int3 > & tiles , PlayerColor player , ETileVisibility mode )
2014-06-24 14:50:27 +03:00
{
2024-07-08 22:56:17 +02:00
if ( tiles . empty ( ) )
return ;
2014-06-24 14:50:27 +03:00
FoWChange fow ;
fow . tiles = tiles ;
fow . player = player ;
2023-10-04 14:31:42 +02:00
fow . mode = mode ;
2014-06-24 14:50:27 +03:00
sendAndApply ( & fow ) ;
}
2023-09-26 14:55:07 +02:00
const CGHeroInstance * CGameHandler : : getVisitingHero ( const CGObjectInstance * obj )
2023-09-19 22:44:03 +02:00
{
2023-09-26 14:55:07 +02:00
assert ( obj ) ;
2023-09-19 22:44:03 +02:00
2023-10-31 11:09:56 +02:00
for ( const auto & query : queries - > allQueries ( ) )
2023-09-19 22:44:03 +02:00
{
auto visit = std : : dynamic_pointer_cast < const CObjectVisitQuery > ( query ) ;
2023-09-26 14:55:07 +02:00
if ( visit & & visit - > visitedObject = = obj )
return visit - > visitingHero ;
2023-09-19 22:44:03 +02:00
}
2023-09-26 14:55:07 +02:00
return nullptr ;
2023-09-19 22:44:03 +02:00
}
2024-02-29 00:13:51 +02:00
const CGObjectInstance * CGameHandler : : getVisitingObject ( const CGHeroInstance * hero )
{
assert ( hero ) ;
for ( const auto & query : queries - > allQueries ( ) )
{
auto visit = std : : dynamic_pointer_cast < const CObjectVisitQuery > ( query ) ;
if ( visit & & visit - > visitingHero = = hero )
return visit - > visitedObject ;
}
return nullptr ;
}
2014-03-17 22:51:07 +03:00
bool CGameHandler : : isVisitCoveredByAnotherQuery ( const CGObjectInstance * obj , const CGHeroInstance * hero )
{
2023-09-26 14:55:07 +02:00
assert ( obj ) ;
assert ( hero ) ;
assert ( getVisitingHero ( obj ) = = hero ) ;
2023-09-19 22:44:03 +02:00
// Check top query of targeted player:
// If top query is NOT visit to targeted object then we assume that
// visitation query is covered by other query that must be answered first
2023-07-23 23:10:01 +02:00
if ( auto topQuery = queries - > topQuery ( hero - > getOwner ( ) ) )
2016-10-12 17:16:26 +02:00
if ( auto visit = std : : dynamic_pointer_cast < const CObjectVisitQuery > ( topQuery ) )
2014-03-17 22:51:07 +03:00
return ! ( visit - > visitedObject = = obj & & visit - > visitingHero = = hero ) ;
return true ;
}
2023-11-06 18:27:16 +02:00
void CGameHandler : : setObjPropertyValue ( ObjectInstanceID objid , ObjProperty prop , int32_t value )
{
SetObjectProperty sob ;
sob . id = objid ;
sob . what = prop ;
sob . identifier = NumericID ( value ) ;
sendAndApply ( & sob ) ;
}
void CGameHandler : : setObjPropertyID ( ObjectInstanceID objid , ObjProperty prop , ObjPropertyID identifier )
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
{
SetObjectProperty sob ;
sob . id = objid ;
sob . what = prop ;
2023-11-06 18:27:16 +02:00
sob . identifier = identifier ;
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
sendAndApply ( & sob ) ;
}
2024-07-13 17:46:55 +02:00
void CGameHandler : : setBankObjectConfiguration ( ObjectInstanceID objid , const BankConfig & configuration )
{
SetBankConfiguration srb ;
srb . objectID = objid ;
srb . configuration = configuration ;
sendAndApply ( & srb ) ;
}
2024-07-12 17:10:03 +02:00
void CGameHandler : : setRewardableObjectConfiguration ( ObjectInstanceID objid , const Rewardable : : Configuration & configuration )
{
SetRewardableConfiguration srb ;
srb . objectID = objid ;
srb . configuration = configuration ;
sendAndApply ( & srb ) ;
}
void CGameHandler : : setRewardableObjectConfiguration ( ObjectInstanceID townInstanceID , BuildingID buildingID , const Rewardable : : Configuration & configuration )
{
SetRewardableConfiguration srb ;
srb . objectID = townInstanceID ;
srb . buildingID = buildingID ;
srb . configuration = configuration ;
sendAndApply ( & srb ) ;
}
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
void CGameHandler : : showInfoDialog ( InfoWindow * iw )
{
sendAndApply ( iw ) ;
}
2024-06-01 17:28:17 +02:00
vstd : : RNG & CGameHandler : : getRandomGenerator ( )
2016-09-09 19:30:36 +02:00
{
2024-06-01 17:28:17 +02:00
return * randomNumberGenerator ;
2016-09-09 19:30:36 +02:00
}
2022-09-21 18:31:14 +02:00
# if SCRIPTING_ENABLED
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
scripting : : Pool * CGameHandler : : getGlobalContextPool ( ) const
{
return serverScripts . get ( ) ;
}
2023-08-31 15:22:58 +02:00
//scripting::Pool * CGameHandler::getContextPool() const
//{
// return serverScripts.get();
//}
2022-09-21 18:31:14 +02:00
# endif
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
2024-07-12 18:51:27 +02:00
CGObjectInstance * CGameHandler : : createNewObject ( const int3 & visitablePosition , MapObjectID objectID , MapObjectSubID subID )
{
TerrainId terrainType = ETerrainId : : NONE ;
if ( ! gs - > isInTheMap ( visitablePosition ) )
throw std : : runtime_error ( " Attempt to create object outside map at " + visitablePosition . toString ( ) ) ;
const TerrainTile & t = gs - > map - > getTile ( visitablePosition ) ;
terrainType = t . terType - > getId ( ) ;
auto handler = VLC - > objtypeh - > getHandlerFor ( objectID , subID ) ;
CGObjectInstance * o = handler - > create ( gs - > callback , nullptr ) ;
handler - > configureObject ( o , getRandomGenerator ( ) ) ;
assert ( o - > ID = = objectID ) ;
assert ( ! handler - > getTemplates ( terrainType ) . empty ( ) ) ;
if ( handler - > getTemplates ( ) . empty ( ) )
throw std : : runtime_error ( " Attempt to create object ( " + std : : to_string ( objectID ) + " , " + std : : to_string ( subID . getNum ( ) ) + " ) with no templates! " ) ;
if ( ! handler - > getTemplates ( terrainType ) . empty ( ) )
o - > appearance = handler - > getTemplates ( terrainType ) . front ( ) ;
else
o - > appearance = handler - > getTemplates ( ) . front ( ) ;
o - > pos = visitablePosition + o - > getVisitableOffset ( ) ;
return o ;
}
void CGameHandler : : createWanderingMonster ( const int3 & visitablePosition , CreatureID creature )
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
{
2024-07-12 18:51:27 +02:00
auto createdObject = createNewObject ( visitablePosition , Obj : : MONSTER , creature ) ;
auto * cre = dynamic_cast < CGCreature * > ( createdObject ) ;
assert ( cre ) ;
cre - > notGrowingTeam = cre - > neverFlees = false ;
cre - > character = 2 ;
cre - > gainedArtifact = ArtifactID : : NONE ;
cre - > identifier = - 1 ;
cre - > addToSlot ( SlotID ( 0 ) , new CStackInstance ( creature , - 1 ) ) ; //add placeholder stack
newObject ( createdObject , PlayerColor : : NEUTRAL ) ;
}
void CGameHandler : : createBoat ( const int3 & visitablePosition , BoatId type , PlayerColor initiator )
{
auto createdObject = createNewObject ( visitablePosition , Obj : : BOAT , type ) ;
newObject ( createdObject , initiator ) ;
}
void CGameHandler : : createHole ( const int3 & visitablePosition , PlayerColor initiator )
{
auto createdObject = createNewObject ( visitablePosition , Obj : : HOLE , 0 ) ;
newObject ( createdObject , initiator ) ;
}
void CGameHandler : : newObject ( CGObjectInstance * object , PlayerColor initiator )
{
object - > initObj ( gs - > getRandomGenerator ( ) ) ;
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
NewObject no ;
2024-07-12 18:51:27 +02:00
no . newObject = object ;
2023-09-18 21:09:55 +02:00
no . initiator = initiator ;
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
sendAndApply ( & no ) ;
2023-04-10 02:34:24 +02:00
}
2023-07-11 19:29:44 +02:00
2023-07-23 23:00:37 +02:00
void CGameHandler : : startBattlePrimary ( const CArmedInstance * army1 , const CArmedInstance * army2 , int3 tile , const CGHeroInstance * hero1 , const CGHeroInstance * hero2 , bool creatureBank , const CGTownInstance * town )
{
battles - > startBattlePrimary ( army1 , army2 , tile , hero1 , hero2 , creatureBank , town ) ;
}
void CGameHandler : : startBattleI ( const CArmedInstance * army1 , const CArmedInstance * army2 , int3 tile , bool creatureBank )
{
battles - > startBattleI ( army1 , army2 , tile , creatureBank ) ;
}
void CGameHandler : : startBattleI ( const CArmedInstance * army1 , const CArmedInstance * army2 , bool creatureBank )
{
battles - > startBattleI ( army1 , army2 , creatureBank ) ;
}