2023-07-11 14:16:02 +02:00
/*
* HeroPoolProcessor . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "HeroPoolProcessor.h"
2023-08-22 17:45:13 +02:00
# include "TurnOrderProcessor.h"
2023-07-23 23:20:35 +02:00
# include "../CGameHandler.h"
2023-07-11 14:16:02 +02:00
2024-06-01 17:28:17 +02:00
# include "../../lib/CRandomGenerator.h"
2023-07-23 23:20:35 +02:00
# include "../../lib/CHeroHandler.h"
# include "../../lib/CPlayerState.h"
2024-08-31 13:00:36 +02:00
# include "../../lib/IGameSettings.h"
2023-07-23 23:20:35 +02:00
# include "../../lib/StartInfo.h"
# include "../../lib/mapObjects/CGTownInstance.h"
2023-10-23 12:59:15 +02:00
# include "../../lib/mapObjects/CGHeroInstance.h"
# include "../../lib/networkPacks/PacksForClient.h"
2023-07-23 23:20:35 +02:00
# include "../../lib/gameState/CGameState.h"
# include "../../lib/gameState/TavernHeroesPool.h"
# include "../../lib/gameState/TavernSlot.h"
2024-08-31 13:00:36 +02:00
# include "../../lib/IGameSettings.h"
2023-07-11 14:16:02 +02:00
2023-07-11 17:49:19 +02:00
HeroPoolProcessor : : HeroPoolProcessor ( CGameHandler * gameHandler )
: gameHandler ( gameHandler )
2023-07-11 14:16:02 +02:00
{
}
2023-07-11 22:08:30 +02:00
TavernHeroSlot HeroPoolProcessor : : selectSlotForRole ( const PlayerColor & player , TavernSlotRole roleID )
{
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-11 22:08:30 +02:00
2023-07-12 11:29:05 +02:00
const auto & heroes = heroesPool - > getHeroesFor ( player ) ;
2023-07-11 22:08:30 +02:00
// if tavern has empty slot - use it
if ( heroes . size ( ) = = 0 )
return TavernHeroSlot : : NATIVE ;
if ( heroes . size ( ) = = 1 )
return TavernHeroSlot : : RANDOM ;
// try to find "better" slot to overwrite
// we want to avoid overwriting retreated heroes when tavern still has slot with random hero
// as well as avoid overwriting surrendered heroes if we can overwrite retreated hero
2023-10-28 11:27:10 +02:00
auto roleLeft = heroesPool - > getSlotRole ( heroes [ 0 ] - > getHeroType ( ) ) ;
auto roleRight = heroesPool - > getSlotRole ( heroes [ 1 ] - > getHeroType ( ) ) ;
2023-07-11 22:08:30 +02:00
if ( roleLeft > roleRight )
return TavernHeroSlot : : RANDOM ;
if ( roleLeft < roleRight )
return TavernHeroSlot : : NATIVE ;
// both slots are equal in "value", so select randomly
if ( getRandomGenerator ( player ) . nextInt ( 100 ) > 50 )
return TavernHeroSlot : : RANDOM ;
else
return TavernHeroSlot : : NATIVE ;
}
2023-07-11 14:16:02 +02:00
void HeroPoolProcessor : : onHeroSurrendered ( const PlayerColor & color , const CGHeroInstance * hero )
{
SetAvailableHero sah ;
2023-08-24 17:53:58 +02:00
sah . roleID = TavernSlotRole : : SURRENDERED ;
2023-07-11 22:37:17 +02:00
sah . slotID = selectSlotForRole ( color , sah . roleID ) ;
2023-07-11 14:16:02 +02:00
sah . player = color ;
2023-10-28 11:27:10 +02:00
sah . hid = hero - > getHeroType ( ) ;
2024-01-15 22:25:52 +02:00
sah . replenishPoints = false ;
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & sah ) ;
}
void HeroPoolProcessor : : onHeroEscaped ( const PlayerColor & color , const CGHeroInstance * hero )
{
SetAvailableHero sah ;
2023-08-24 17:53:58 +02:00
sah . roleID = TavernSlotRole : : RETREATED ;
2023-07-11 22:37:17 +02:00
sah . slotID = selectSlotForRole ( color , sah . roleID ) ;
2023-07-11 14:16:02 +02:00
sah . player = color ;
2023-10-28 11:27:10 +02:00
sah . hid = hero - > getHeroType ( ) ;
2023-08-07 18:13:02 +02:00
sah . army . clearSlots ( ) ;
2023-07-12 11:29:05 +02:00
sah . army . setCreature ( SlotID ( 0 ) , hero - > type - > initialArmy . at ( 0 ) . creature , 1 ) ;
2024-01-15 22:25:52 +02:00
sah . replenishPoints = false ;
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & sah ) ;
}
void HeroPoolProcessor : : clearHeroFromSlot ( const PlayerColor & color , TavernHeroSlot slot )
{
SetAvailableHero sah ;
sah . player = color ;
2023-07-11 22:08:30 +02:00
sah . roleID = TavernSlotRole : : NONE ;
sah . slotID = slot ;
2023-07-11 14:16:02 +02:00
sah . hid = HeroTypeID : : NONE ;
2024-01-15 22:25:52 +02:00
sah . replenishPoints = false ;
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & sah ) ;
}
2024-01-20 02:09:55 +02:00
void HeroPoolProcessor : : selectNewHeroForSlot ( const PlayerColor & color , TavernHeroSlot slot , bool needNativeHero , bool giveArmy , const HeroTypeID & nextHero )
2023-07-11 14:16:02 +02:00
{
SetAvailableHero sah ;
sah . player = color ;
2023-07-11 22:08:30 +02:00
sah . slotID = slot ;
2024-01-15 22:25:52 +02:00
sah . replenishPoints = true ;
2023-07-11 14:16:02 +02:00
2024-01-21 19:12:46 +02:00
CGHeroInstance * newHero = ( nextHero = = HeroTypeID : : NONE ) ? pickHeroFor ( needNativeHero , color ) : gameHandler - > gameState ( ) - > heroesPool - > unusedHeroesFromPool ( ) [ nextHero ] ;
2023-07-11 14:16:02 +02:00
2023-07-12 11:29:05 +02:00
if ( newHero )
2023-07-11 14:16:02 +02:00
{
2023-10-28 11:27:10 +02:00
sah . hid = newHero - > getHeroType ( ) ;
2023-07-11 16:51:14 +02:00
if ( giveArmy )
{
2023-07-11 22:08:30 +02:00
sah . roleID = TavernSlotRole : : FULL_ARMY ;
2023-07-12 11:29:05 +02:00
newHero - > initArmy ( getRandomGenerator ( color ) , & sah . army ) ;
2023-07-11 16:51:14 +02:00
}
else
{
2023-07-11 22:08:30 +02:00
sah . roleID = TavernSlotRole : : SINGLE_UNIT ;
2023-08-07 18:13:02 +02:00
sah . army . clearSlots ( ) ;
2023-07-12 11:29:05 +02:00
sah . army . setCreature ( SlotID ( 0 ) , newHero - > type - > initialArmy [ 0 ] . creature , 1 ) ;
2023-07-11 16:51:14 +02:00
}
2023-07-11 14:16:02 +02:00
}
else
{
2023-09-28 18:43:04 +02:00
sah . hid = HeroTypeID : : NONE ;
2023-07-11 14:16:02 +02:00
}
2024-01-15 22:25:52 +02:00
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & sah ) ;
}
void HeroPoolProcessor : : onNewWeek ( const PlayerColor & color )
{
2023-08-24 17:53:58 +02:00
clearHeroFromSlot ( color , TavernHeroSlot : : NATIVE ) ;
clearHeroFromSlot ( color , TavernHeroSlot : : RANDOM ) ;
selectNewHeroForSlot ( color , TavernHeroSlot : : NATIVE , true , true ) ;
selectNewHeroForSlot ( color , TavernHeroSlot : : RANDOM , false , true ) ;
2023-07-11 14:16:02 +02:00
}
2024-01-20 02:09:55 +02:00
bool HeroPoolProcessor : : hireHero ( const ObjectInstanceID & objectID , const HeroTypeID & heroToRecruit , const PlayerColor & player , const HeroTypeID & nextHero )
2023-07-11 14:16:02 +02:00
{
const PlayerState * playerState = gameHandler - > getPlayerState ( player ) ;
2023-07-12 11:29:05 +02:00
const CGObjectInstance * mapObject = gameHandler - > getObj ( objectID ) ;
const CGTownInstance * town = gameHandler - > getTown ( objectID ) ;
2024-01-21 19:12:46 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-12 11:29:05 +02:00
if ( ! mapObject & & gameHandler - > complain ( " Invalid map object! " ) )
return false ;
if ( ! playerState & & gameHandler - > complain ( " Invalid player! " ) )
return false ;
2023-07-11 14:16:02 +02:00
if ( playerState - > resources [ EGameResID : : GOLD ] < GameConstants : : HERO_GOLD_COST & & gameHandler - > complain ( " Not enough gold for buying hero! " ) )
return false ;
2024-08-31 13:00:36 +02:00
if ( gameHandler - > getHeroCount ( player , false ) > = gameHandler - > getSettings ( ) . getInteger ( EGameSettings : : HEROES_PER_PLAYER_ON_MAP_CAP ) & & gameHandler - > complain ( " Cannot hire hero, too many wandering heroes already! " ) )
2023-07-11 14:16:02 +02:00
return false ;
2024-08-31 13:00:36 +02:00
if ( gameHandler - > getHeroCount ( player , true ) > = gameHandler - > getSettings ( ) . getInteger ( EGameSettings : : HEROES_PER_PLAYER_TOTAL_CAP ) & & gameHandler - > complain ( " Cannot hire hero, too many heroes garrizoned and wandering already! " ) )
2023-07-11 14:16:02 +02:00
return false ;
2024-01-22 21:19:49 +02:00
if ( nextHero ! = HeroTypeID : : NONE ) // player attempts to invite next hero
{
2024-08-31 13:00:36 +02:00
if ( ! gameHandler - > getSettings ( ) . getBoolean ( EGameSettings : : HEROES_TAVERN_INVITE ) & & gameHandler - > complain ( " Inviting heroes not allowed! " ) )
2024-01-22 21:19:49 +02:00
return false ;
if ( ! heroesPool - > unusedHeroesFromPool ( ) . count ( nextHero ) & & gameHandler - > complain ( " Cannot invite specified hero! " ) )
return false ;
if ( ! heroesPool - > isHeroAvailableFor ( nextHero , player ) & & gameHandler - > complain ( " Cannot invite specified hero! " ) )
2024-01-21 19:12:46 +02:00
return false ;
2024-01-22 21:22:15 +02:00
}
2024-01-21 19:12:46 +02:00
2023-07-11 16:51:14 +02:00
if ( town ) //tavern in town
2023-07-11 14:16:02 +02:00
{
2023-07-12 11:29:05 +02:00
if ( gameHandler - > getPlayerRelations ( mapObject - > tempOwner , player ) = = PlayerRelations : : ENEMIES & & gameHandler - > complain ( " Can't buy hero in enemy town! " ) )
return false ;
2023-07-11 16:51:14 +02:00
if ( ! town - > hasBuilt ( BuildingID : : TAVERN ) & & gameHandler - > complain ( " No tavern! " ) )
2023-07-11 14:16:02 +02:00
return false ;
2023-07-11 16:51:14 +02:00
if ( town - > visitingHero & & gameHandler - > complain ( " There is visiting hero - no place! " ) )
2023-07-11 14:16:02 +02:00
return false ;
}
2023-07-12 11:29:05 +02:00
if ( mapObject - > ID = = Obj : : TAVERN )
2023-07-11 14:16:02 +02:00
{
2023-09-26 14:55:07 +02:00
const CGHeroInstance * visitor = gameHandler - > getVisitingHero ( mapObject ) ;
if ( ! visitor | | visitor - > getOwner ( ) ! = player )
{
gameHandler - > complain ( " Can't buy hero in tavern not being visited! " ) ;
2023-09-19 22:44:03 +02:00
return false ;
2023-09-26 14:55:07 +02:00
}
2023-09-19 22:44:03 +02:00
2023-07-12 11:29:05 +02:00
if ( gameHandler - > getTile ( mapObject - > visitablePos ( ) ) - > visitableObjects . back ( ) ! = mapObject & & gameHandler - > complain ( " Tavern entry must be unoccupied! " ) )
2023-07-11 14:16:02 +02:00
return false ;
}
2024-01-21 19:12:46 +02:00
auto recruitableHeroes = heroesPool - > getHeroesFor ( player ) ;
2023-07-11 14:16:02 +02:00
2023-07-11 16:51:14 +02:00
const CGHeroInstance * recruitedHero = nullptr ;
2023-07-11 14:16:02 +02:00
for ( const auto & hero : recruitableHeroes )
{
2023-10-28 11:27:10 +02:00
if ( hero - > getHeroType ( ) = = heroToRecruit )
2023-07-11 14:16:02 +02:00
recruitedHero = hero ;
}
2023-07-11 16:51:14 +02:00
if ( ! recruitedHero )
2023-07-11 14:16:02 +02:00
{
2023-07-11 16:51:14 +02:00
gameHandler - > complain ( " Hero is not available for hiring! " ) ;
2023-07-11 14:16:02 +02:00
return false ;
}
2023-08-01 18:51:33 +02:00
const auto targetPos = mapObject - > visitablePos ( ) ;
2023-07-11 14:16:02 +02:00
HeroRecruited hr ;
2023-07-12 11:29:05 +02:00
hr . tid = mapObject - > id ;
2023-10-28 11:27:10 +02:00
hr . hid = recruitedHero - > getHeroType ( ) ;
2023-07-11 14:16:02 +02:00
hr . player = player ;
2023-08-01 18:51:33 +02:00
hr . tile = recruitedHero - > convertFromVisitablePos ( targetPos ) ;
2023-08-17 09:25:31 +02:00
if ( gameHandler - > getTile ( targetPos ) - > isWater ( ) & & ! recruitedHero - > boat )
2023-07-11 14:16:02 +02:00
{
//Create a new boat for hero
2024-07-12 18:51:27 +02:00
gameHandler - > createBoat ( targetPos , recruitedHero - > getBoatType ( ) , player ) ;
2023-08-01 18:51:33 +02:00
hr . boatId = gameHandler - > getTopObj ( targetPos ) - > id ;
2023-07-11 14:16:02 +02:00
}
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
// apply netpack -> this will remove hired hero from pool
2023-07-11 14:16:02 +02:00
gameHandler - > sendAndApply ( & hr ) ;
2023-07-11 16:51:14 +02:00
if ( recruitableHeroes [ 0 ] = = recruitedHero )
2024-01-20 02:09:55 +02:00
selectNewHeroForSlot ( player , TavernHeroSlot : : NATIVE , false , false , nextHero ) ;
2023-07-11 14:16:02 +02:00
else
2024-01-20 02:09:55 +02:00
selectNewHeroForSlot ( player , TavernHeroSlot : : RANDOM , false , false , nextHero ) ;
2023-07-11 14:16:02 +02:00
gameHandler - > giveResource ( player , EGameResID : : GOLD , - GameConstants : : HERO_GOLD_COST ) ;
if ( town )
2024-08-12 15:06:32 +02:00
gameHandler - > objectVisited ( town , recruitedHero ) ;
2024-05-10 18:48:20 +02:00
// If new hero has scouting he might reveal more terrain than we saw before
gameHandler - > changeFogOfWar ( recruitedHero - > getSightCenter ( ) , recruitedHero - > getSightRadius ( ) , player , ETileVisibility : : REVEALED ) ;
2023-07-11 14:16:02 +02:00
return true ;
}
2023-07-11 16:51:14 +02:00
2023-07-11 19:29:44 +02:00
std : : vector < const CHeroClass * > HeroPoolProcessor : : findAvailableClassesFor ( const PlayerColor & player ) const
2023-07-11 16:51:14 +02:00
{
2023-07-11 19:29:44 +02:00
std : : vector < const CHeroClass * > result ;
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-11 17:49:19 +02:00
FactionID factionID = gameHandler - > getPlayerSettings ( player ) - > castle ;
2023-07-11 16:51:14 +02:00
2024-01-19 23:02:00 +02:00
for ( const auto & elem : heroesPool - > unusedHeroesFromPool ( ) )
2023-07-11 16:51:14 +02:00
{
2023-07-11 19:29:44 +02:00
if ( vstd : : contains ( result , elem . second - > type - > heroClass ) )
continue ;
2023-07-12 11:29:05 +02:00
bool heroAvailable = heroesPool - > isHeroAvailableFor ( elem . first , player ) ;
2023-12-31 23:43:35 +02:00
bool heroClassBanned = elem . second - > type - > heroClass - > tavernProbability ( factionID ) = = 0 ;
2023-07-11 16:51:14 +02:00
2023-07-11 17:49:19 +02:00
if ( heroAvailable & & ! heroClassBanned )
2023-07-11 19:29:44 +02:00
result . push_back ( elem . second - > type - > heroClass ) ;
2023-07-11 17:49:19 +02:00
}
2023-07-11 16:51:14 +02:00
2023-07-11 17:49:19 +02:00
return result ;
}
2023-07-11 16:51:14 +02:00
2023-07-11 19:29:44 +02:00
std : : vector < CGHeroInstance * > HeroPoolProcessor : : findAvailableHeroesFor ( const PlayerColor & player , const CHeroClass * heroClass ) const
2023-07-11 17:49:19 +02:00
{
2023-07-11 19:29:44 +02:00
std : : vector < CGHeroInstance * > result ;
2023-07-11 16:51:14 +02:00
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
2023-07-11 16:51:14 +02:00
2024-01-19 23:02:00 +02:00
for ( const auto & elem : heroesPool - > unusedHeroesFromPool ( ) )
2023-07-11 16:51:14 +02:00
{
2023-07-11 19:29:44 +02:00
assert ( ! vstd : : contains ( result , elem . second ) ) ;
2023-07-12 11:29:05 +02:00
bool heroAvailable = heroesPool - > isHeroAvailableFor ( elem . first , player ) ;
2023-07-11 17:49:19 +02:00
bool heroClassMatches = elem . second - > type - > heroClass = = heroClass ;
2023-07-11 16:51:14 +02:00
2023-07-11 17:49:19 +02:00
if ( heroAvailable & & heroClassMatches )
2023-07-11 19:29:44 +02:00
result . push_back ( elem . second ) ;
2023-07-11 16:51:14 +02:00
}
2023-07-11 17:49:19 +02:00
return result ;
}
2023-07-11 18:23:14 +02:00
const CHeroClass * HeroPoolProcessor : : pickClassFor ( bool isNative , const PlayerColor & player )
2023-07-11 17:49:19 +02:00
{
2023-08-27 00:35:38 +02:00
if ( ! player . isValidPlayer ( ) )
2023-07-11 17:49:19 +02:00
{
2023-09-04 21:21:02 +02:00
logGlobal - > error ( " Cannot pick hero for player %d. Wrong owner! " , player . toString ( ) ) ;
2023-07-11 17:49:19 +02:00
return nullptr ;
}
2023-07-11 18:23:14 +02:00
FactionID factionID = gameHandler - > getPlayerSettings ( player ) - > castle ;
2023-07-12 11:29:05 +02:00
const auto & heroesPool = gameHandler - > gameState ( ) - > heroesPool ;
const auto & currentTavern = heroesPool - > getHeroesFor ( player ) ;
2023-07-11 17:49:19 +02:00
2023-07-11 19:29:44 +02:00
std : : vector < const CHeroClass * > potentialClasses = findAvailableClassesFor ( player ) ;
std : : vector < const CHeroClass * > possibleClasses ;
2023-07-11 17:49:19 +02:00
if ( potentialClasses . empty ( ) )
2023-07-11 16:51:14 +02:00
{
2023-09-04 21:21:02 +02:00
logGlobal - > error ( " There are no heroes available for player %s! " , player . toString ( ) ) ;
2023-07-11 16:51:14 +02:00
return nullptr ;
}
2023-07-11 17:49:19 +02:00
for ( const auto & heroClass : potentialClasses )
{
if ( isNative & & heroClass - > faction ! = factionID )
continue ;
bool hasSameClass = vstd : : contains_if ( currentTavern , [ & ] ( const CGHeroInstance * hero ) {
return hero - > type - > heroClass = = heroClass ;
} ) ;
if ( hasSameClass )
continue ;
2023-07-11 19:29:44 +02:00
possibleClasses . push_back ( heroClass ) ;
2023-07-11 17:49:19 +02:00
}
if ( possibleClasses . empty ( ) )
{
2023-09-04 21:21:02 +02:00
logGlobal - > error ( " Cannot pick native hero for %s. Picking any... " , player . toString ( ) ) ;
2023-07-11 17:49:19 +02:00
possibleClasses = potentialClasses ;
}
int totalWeight = 0 ;
for ( const auto & heroClass : possibleClasses )
2023-12-31 23:43:35 +02:00
totalWeight + = heroClass - > tavernProbability ( factionID ) ;
2023-07-11 17:49:19 +02:00
2023-07-11 18:23:14 +02:00
int roll = getRandomGenerator ( player ) . nextInt ( totalWeight - 1 ) ;
2023-07-11 19:29:44 +02:00
2023-07-11 17:49:19 +02:00
for ( const auto & heroClass : possibleClasses )
2023-07-11 16:51:14 +02:00
{
2023-12-31 23:43:35 +02:00
roll - = heroClass - > tavernProbability ( factionID ) ;
2023-07-11 16:51:14 +02:00
if ( roll < 0 )
2023-07-11 17:49:19 +02:00
return heroClass ;
2023-07-11 16:51:14 +02:00
}
2023-07-11 17:49:19 +02:00
return * possibleClasses . rbegin ( ) ;
}
2023-07-11 18:23:14 +02:00
CGHeroInstance * HeroPoolProcessor : : pickHeroFor ( bool isNative , const PlayerColor & player )
2023-07-11 17:49:19 +02:00
{
2023-07-11 18:23:14 +02:00
const CHeroClass * heroClass = pickClassFor ( isNative , player ) ;
2023-07-11 17:49:19 +02:00
if ( ! heroClass )
return nullptr ;
2023-07-11 19:29:44 +02:00
std : : vector < CGHeroInstance * > possibleHeroes = findAvailableHeroesFor ( player , heroClass ) ;
2023-07-11 17:49:19 +02:00
assert ( ! possibleHeroes . empty ( ) ) ;
if ( possibleHeroes . empty ( ) )
return nullptr ;
2023-07-11 18:23:14 +02:00
return * RandomGeneratorUtil : : nextItem ( possibleHeroes , getRandomGenerator ( player ) ) ;
}
2024-06-01 17:28:17 +02:00
vstd : : RNG & HeroPoolProcessor : : getHeroSkillsRandomGenerator ( const HeroTypeID & hero )
2023-11-20 18:44:27 +02:00
{
if ( heroSeed . count ( hero ) = = 0 )
{
int seed = gameHandler - > getRandomGenerator ( ) . nextInt ( ) ;
heroSeed . emplace ( hero , std : : make_unique < CRandomGenerator > ( seed ) ) ;
}
return * heroSeed . at ( hero ) ;
}
2024-06-01 17:28:17 +02:00
vstd : : RNG & HeroPoolProcessor : : getRandomGenerator ( const PlayerColor & player )
2023-07-11 18:23:14 +02:00
{
if ( playerSeed . count ( player ) = = 0 )
2023-07-11 19:29:44 +02:00
{
int seed = gameHandler - > getRandomGenerator ( ) . nextInt ( ) ;
playerSeed . emplace ( player , std : : make_unique < CRandomGenerator > ( seed ) ) ;
}
2023-07-11 18:23:14 +02:00
2023-07-11 19:29:44 +02:00
return * playerSeed . at ( player ) ;
2023-07-11 16:51:14 +02:00
}