2023-06-17 14:21:42 +02:00
/*
* CGCreature . 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 "CGCreature.h"
2023-10-23 12:59:15 +02:00
# include "CGHeroInstance.h"
2023-06-17 14:21:42 +02:00
2024-07-20 14:55:17 +02:00
# include "../texts/CGeneralTextHandler.h"
2023-06-17 14:21:42 +02:00
# include "../CConfigHandler.h"
2024-08-31 13:00:36 +02:00
# include "../IGameSettings.h"
2023-06-17 14:21:42 +02:00
# include "../IGameCallback.h"
2024-06-01 17:28:17 +02:00
# include "../gameState/CGameState.h"
2024-04-22 15:40:43 +02:00
# include "../mapObjectConstructors/CObjectClassesHandler.h"
2023-10-23 12:59:15 +02:00
# include "../networkPacks/PacksForClient.h"
# include "../networkPacks/PacksForClientBattle.h"
# include "../networkPacks/StackLocation.h"
2023-06-17 14:21:42 +02:00
# include "../serializer/JsonSerializeFormat.h"
2024-09-18 22:39:53 +02:00
# include "../entities/faction/CTownHandler.h"
2024-06-01 17:28:17 +02:00
# include <vstd/RNG.h>
2023-06-17 14:21:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
std : : string CGCreature : : getHoverText ( PlayerColor player ) const
{
if ( stacks . empty ( ) )
{
//should not happen...
2024-10-02 18:40:06 +02:00
logGlobal - > error ( " Invalid stack at tile %s: subID=%d; id=%d " , anchorPos ( ) . toString ( ) , getCreature ( ) , id . getNum ( ) ) ;
2023-06-17 14:21:42 +02:00
return " INVALID_STACK " ;
}
MetaString ms ;
CCreature : : CreatureQuantityId monsterQuantityId = stacks . begin ( ) - > second - > getQuantityID ( ) ;
int quantityTextIndex = 172 + 3 * ( int ) monsterQuantityId ;
if ( settings [ " gameTweaks " ] [ " numericCreaturesQuantities " ] . Bool ( ) )
2023-06-18 11:18:25 +02:00
ms . appendRawString ( CCreature : : getQuantityRangeStringForId ( monsterQuantityId ) ) ;
2023-06-17 14:21:42 +02:00
else
2023-06-18 11:18:25 +02:00
ms . appendLocalString ( EMetaText : : ARRAY_TXT , quantityTextIndex ) ;
ms . appendRawString ( " " ) ;
2024-10-05 21:37:52 +02:00
ms . appendNamePlural ( getCreatureID ( ) ) ;
2023-11-02 15:49:21 +02:00
return ms . toString ( ) ;
2023-06-17 14:21:42 +02:00
}
std : : string CGCreature : : getHoverText ( const CGHeroInstance * hero ) const
{
2023-10-21 13:50:42 +02:00
if ( hero - > hasVisions ( this , BonusCustomSubtype : : visionsMonsters ) )
2023-06-17 14:21:42 +02:00
{
MetaString ms ;
2023-06-17 22:52:42 +02:00
ms . appendNumber ( stacks . begin ( ) - > second - > count ) ;
2023-06-18 11:18:25 +02:00
ms . appendRawString ( " " ) ;
2024-10-05 21:37:52 +02:00
ms . appendName ( getCreatureID ( ) , stacks . begin ( ) - > second - > count ) ;
2023-11-02 15:49:21 +02:00
return ms . toString ( ) ;
}
else
{
return getHoverText ( hero - > tempOwner ) ;
}
}
2023-06-17 14:21:42 +02:00
2024-10-02 13:08:40 +02:00
std : : string CGCreature : : getMonsterLevelText ( ) const
{
std : : string monsterLevel = VLC - > generaltexth - > translate ( " vcmi.adventureMap.monsterLevel " ) ;
2024-10-05 21:37:52 +02:00
bool isRanged = getCreature ( ) - > getBonusBearer ( ) - > hasBonusOfType ( BonusType : : SHOOTER ) ;
2024-10-02 13:08:40 +02:00
std : : string attackTypeKey = isRanged ? " vcmi.adventureMap.monsterRangedType " : " vcmi.adventureMap.monsterMeleeType " ;
std : : string attackType = VLC - > generaltexth - > translate ( attackTypeKey ) ;
2024-10-05 21:37:52 +02:00
boost : : replace_first ( monsterLevel , " %TOWN " , getCreature ( ) - > getFactionID ( ) . toEntity ( VLC ) - > getNameTranslated ( ) ) ;
boost : : replace_first ( monsterLevel , " %LEVEL " , std : : to_string ( getCreature ( ) - > getLevel ( ) ) ) ;
2024-10-02 13:08:40 +02:00
boost : : replace_first ( monsterLevel , " %ATTACK_TYPE " , attackType ) ;
return monsterLevel ;
}
2023-11-02 15:49:21 +02:00
std : : string CGCreature : : getPopupText ( const CGHeroInstance * hero ) const
{
std : : string hoverName ;
if ( hero - > hasVisions ( this , BonusCustomSubtype : : visionsMonsters ) )
{
MetaString ms ;
ms . appendRawString ( getHoverText ( hero ) ) ;
2023-08-21 14:42:41 +02:00
ms . appendRawString ( " \n \n " ) ;
2023-06-17 14:21:42 +02:00
int decision = takenAction ( hero , true ) ;
switch ( decision )
{
case FIGHT :
2023-06-18 11:18:25 +02:00
ms . appendLocalString ( EMetaText : : GENERAL_TXT , 246 ) ;
2023-06-17 14:21:42 +02:00
break ;
case FLEE :
2023-06-18 11:18:25 +02:00
ms . appendLocalString ( EMetaText : : GENERAL_TXT , 245 ) ;
2023-06-17 14:21:42 +02:00
break ;
case JOIN_FOR_FREE :
2023-06-18 11:18:25 +02:00
ms . appendLocalString ( EMetaText : : GENERAL_TXT , 243 ) ;
2023-06-17 14:21:42 +02:00
break ;
default : //decision = cost in gold
2023-11-02 15:49:21 +02:00
ms . appendLocalString ( EMetaText : : GENERAL_TXT , 244 ) ;
ms . replaceNumber ( decision ) ;
2023-06-17 14:21:42 +02:00
break ;
}
2023-06-18 11:18:25 +02:00
hoverName = ms . toString ( ) ;
2023-06-17 14:21:42 +02:00
}
else
{
hoverName = getHoverText ( hero - > tempOwner ) ;
}
2023-11-02 15:49:21 +02:00
if ( settings [ " general " ] [ " enableUiEnhancements " ] . Bool ( ) )
{
2024-10-02 13:08:40 +02:00
hoverName + = getMonsterLevelText ( ) ;
2023-11-02 15:49:21 +02:00
hoverName + = VLC - > generaltexth - > translate ( " vcmi.adventureMap.monsterThreat.title " ) ;
int choice ;
2024-10-02 21:48:08 +02:00
uint64_t armyStrength = getArmyStrength ( ) ;
uint64_t heroStrength = hero - > getTotalStrength ( ) ;
double ratio = static_cast < double > ( armyStrength ) / heroStrength ;
2023-11-02 15:49:21 +02:00
if ( ratio < 0.1 ) choice = 0 ;
else if ( ratio < 0.25 ) choice = 1 ;
else if ( ratio < 0.6 ) choice = 2 ;
else if ( ratio < 0.9 ) choice = 3 ;
else if ( ratio < 1.1 ) choice = 4 ;
else if ( ratio < 1.3 ) choice = 5 ;
else if ( ratio < 1.8 ) choice = 6 ;
else if ( ratio < 2.5 ) choice = 7 ;
else if ( ratio < 4 ) choice = 8 ;
else if ( ratio < 8 ) choice = 9 ;
else if ( ratio < 20 ) choice = 10 ;
else choice = 11 ;
hoverName + = VLC - > generaltexth - > translate ( " vcmi.adventureMap.monsterThreat.levels. " + std : : to_string ( choice ) ) ;
}
2023-06-17 14:21:42 +02:00
return hoverName ;
}
2023-11-02 15:49:21 +02:00
std : : string CGCreature : : getPopupText ( PlayerColor player ) const
{
2024-10-02 13:08:40 +02:00
std : : string hoverName = getHoverText ( player ) ;
if ( settings [ " general " ] [ " enableUiEnhancements " ] . Bool ( ) )
hoverName + = getMonsterLevelText ( ) ;
return hoverName ;
2023-11-02 15:49:21 +02:00
}
std : : vector < Component > CGCreature : : getPopupComponents ( PlayerColor player ) const
{
return {
2024-10-05 21:37:52 +02:00
Component ( ComponentType : : CREATURE , getCreatureID ( ) )
2023-11-02 15:49:21 +02:00
} ;
}
2023-06-17 14:21:42 +02:00
void CGCreature : : onHeroVisit ( const CGHeroInstance * h ) const
{
2023-09-23 02:35:21 +02:00
//show message
if ( ! message . empty ( ) )
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
2023-09-27 23:25:19 +02:00
iw . text = message ;
2023-09-23 02:35:21 +02:00
iw . type = EInfoWindowMode : : MODAL ;
cb - > showInfoDialog ( & iw ) ;
}
2023-06-17 14:21:42 +02:00
int action = takenAction ( h ) ;
switch ( action ) //decide what we do...
{
case FIGHT :
fight ( h ) ;
break ;
case FLEE :
{
flee ( h ) ;
break ;
}
case JOIN_FOR_FREE : //join for free
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
2023-06-18 11:18:25 +02:00
ynd . text . appendLocalString ( EMetaText : : ADVOB_TXT , 86 ) ;
2024-10-05 21:37:52 +02:00
ynd . text . replaceName ( getCreatureID ( ) , getStackCount ( SlotID ( 0 ) ) ) ;
2024-09-04 17:14:56 +02:00
cb - > showBlockingDialog ( this , & ynd ) ;
2023-06-17 14:21:42 +02:00
break ;
}
default : //join for gold
{
assert ( action > 0 ) ;
//ask if player agrees to pay gold
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
2024-02-07 22:57:25 +02:00
ynd . components . emplace_back ( ComponentType : : RESOURCE , GameResID ( GameResID : : GOLD ) , action ) ;
2023-06-17 14:21:42 +02:00
std : : string tmp = VLC - > generaltexth - > advobtxt [ 90 ] ;
boost : : algorithm : : replace_first ( tmp , " %d " , std : : to_string ( getStackCount ( SlotID ( 0 ) ) ) ) ;
boost : : algorithm : : replace_first ( tmp , " %d " , std : : to_string ( action ) ) ;
2024-10-05 21:37:52 +02:00
boost : : algorithm : : replace_first ( tmp , " %s " , getCreature ( ) - > getNamePluralTranslated ( ) ) ;
2023-06-18 11:18:25 +02:00
ynd . text . appendRawString ( tmp ) ;
2024-09-04 17:14:56 +02:00
cb - > showBlockingDialog ( this , & ynd ) ;
2023-06-17 14:21:42 +02:00
break ;
}
}
}
2024-10-05 21:37:52 +02:00
CreatureID CGCreature : : getCreatureID ( ) const
2023-10-24 16:11:25 +02:00
{
return CreatureID ( getObjTypeIndex ( ) . getNum ( ) ) ;
}
2024-10-05 21:37:52 +02:00
const CCreature * CGCreature : : getCreature ( ) const
{
return getCreatureID ( ) . toCreature ( ) ;
}
2024-06-01 17:28:17 +02:00
void CGCreature : : pickRandomObject ( vstd : : RNG & rand )
2023-10-25 12:50:11 +02:00
{
2023-11-02 16:29:59 +02:00
switch ( ID . toEnum ( ) )
2023-10-25 12:50:11 +02:00
{
case MapObjectID : : RANDOM_MONSTER :
subID = VLC - > creh - > pickRandomMonster ( rand ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L1 :
subID = VLC - > creh - > pickRandomMonster ( rand , 1 ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L2 :
subID = VLC - > creh - > pickRandomMonster ( rand , 2 ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L3 :
subID = VLC - > creh - > pickRandomMonster ( rand , 3 ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L4 :
subID = VLC - > creh - > pickRandomMonster ( rand , 4 ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L5 :
subID = VLC - > creh - > pickRandomMonster ( rand , 5 ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L6 :
subID = VLC - > creh - > pickRandomMonster ( rand , 6 ) ;
break ;
case MapObjectID : : RANDOM_MONSTER_L7 :
subID = VLC - > creh - > pickRandomMonster ( rand , 7 ) ;
break ;
}
2024-04-22 15:40:43 +02:00
try {
// sanity check
2024-04-24 21:57:13 +02:00
VLC - > objtypeh - > getHandlerFor ( MapObjectID : : MONSTER , subID ) ;
2024-04-22 15:40:43 +02:00
}
catch ( const std : : out_of_range & )
{
// Try to generate some debug information if sanity check failed
CreatureID creatureID ( subID . getNum ( ) ) ;
2024-06-24 03:23:26 +02:00
throw std : : out_of_range ( " Failed to find handler for creature " + std : : to_string ( creatureID . getNum ( ) ) + " , identifier: " + creatureID . toEntity ( VLC ) - > getJsonKey ( ) ) ;
2024-04-22 15:40:43 +02:00
}
2023-10-25 12:50:11 +02:00
ID = MapObjectID : : MONSTER ;
2023-10-26 15:50:29 +02:00
setType ( ID , subID ) ;
2023-10-25 12:50:11 +02:00
}
2024-06-01 17:28:17 +02:00
void CGCreature : : initObj ( vstd : : RNG & rand )
2023-06-17 14:21:42 +02:00
{
blockVisit = true ;
switch ( character )
{
case 0 :
character = - 4 ;
break ;
case 1 :
character = rand . nextInt ( 1 , 7 ) ;
break ;
case 2 :
character = rand . nextInt ( 1 , 10 ) ;
break ;
case 3 :
character = rand . nextInt ( 4 , 10 ) ;
break ;
case 4 :
character = 10 ;
break ;
}
2023-10-24 16:11:25 +02:00
stacks [ SlotID ( 0 ) ] - > setType ( getCreature ( ) ) ;
2023-06-17 14:21:42 +02:00
TQuantity & amount = stacks [ SlotID ( 0 ) ] - > count ;
2024-10-05 21:37:52 +02:00
const Creature * c = getCreature ( ) ;
2023-06-17 14:21:42 +02:00
if ( amount = = 0 )
{
2023-10-24 16:11:25 +02:00
amount = rand . nextInt ( c - > getAdvMapAmountMin ( ) , c - > getAdvMapAmountMax ( ) ) ;
2023-06-17 14:21:42 +02:00
if ( amount = = 0 ) //armies with 0 creatures are illegal
{
2023-10-24 16:11:25 +02:00
logGlobal - > warn ( " Stack cannot have 0 creatures. Check properties of %s " , c - > getJsonKey ( ) ) ;
2023-06-17 14:21:42 +02:00
amount = 1 ;
}
}
2024-05-16 20:53:49 +02:00
temppower = stacks [ SlotID ( 0 ) ] - > count * static_cast < int64_t > ( 1000 ) ;
2023-06-17 14:21:42 +02:00
refusedJoining = false ;
}
2024-06-01 17:28:17 +02:00
void CGCreature : : newTurn ( vstd : : RNG & rand ) const
2023-06-17 14:21:42 +02:00
{ //Works only for stacks of single type of size up to 2 millions
if ( ! notGrowingTeam )
{
2024-08-31 13:00:36 +02:00
if ( stacks . begin ( ) - > second - > count < cb - > getSettings ( ) . getInteger ( EGameSettings : : CREATURES_WEEKLY_GROWTH_CAP ) & & cb - > getDate ( Date : : DAY_OF_WEEK ) = = 1 & & cb - > getDate ( Date : : DAY ) > 1 )
2023-06-17 14:21:42 +02:00
{
2024-08-31 13:00:36 +02:00
ui32 power = static_cast < ui32 > ( temppower * ( 100 + cb - > getSettings ( ) . getInteger ( EGameSettings : : CREATURES_WEEKLY_GROWTH_PERCENT ) ) / 100 ) ;
cb - > setObjPropertyValue ( id , ObjProperty : : MONSTER_COUNT , std : : min < uint32_t > ( power / 1000 , cb - > getSettings ( ) . getInteger ( EGameSettings : : CREATURES_WEEKLY_GROWTH_CAP ) ) ) ; //set new amount
2023-11-06 18:27:16 +02:00
cb - > setObjPropertyValue ( id , ObjProperty : : MONSTER_POWER , power ) ; //increase temppower
2023-06-17 14:21:42 +02:00
}
}
2024-08-31 13:00:36 +02:00
if ( cb - > getSettings ( ) . getBoolean ( EGameSettings : : MODULE_STACK_EXPERIENCE ) )
cb - > setObjPropertyValue ( id , ObjProperty : : MONSTER_EXP , cb - > getSettings ( ) . getInteger ( EGameSettings : : CREATURES_DAILY_STACK_EXPERIENCE ) ) ; //for testing purpose
2023-06-17 14:21:42 +02:00
}
2023-11-06 18:27:16 +02:00
void CGCreature : : setPropertyDer ( ObjProperty what , ObjPropertyID identifier )
2023-06-17 14:21:42 +02:00
{
switch ( what )
{
case ObjProperty : : MONSTER_COUNT :
2023-11-06 18:27:16 +02:00
stacks [ SlotID ( 0 ) ] - > count = identifier . getNum ( ) ;
2023-06-17 14:21:42 +02:00
break ;
case ObjProperty : : MONSTER_POWER :
2023-11-06 18:27:16 +02:00
temppower = identifier . getNum ( ) ;
2023-06-17 14:21:42 +02:00
break ;
case ObjProperty : : MONSTER_EXP :
2023-11-06 18:27:16 +02:00
giveStackExp ( identifier . getNum ( ) ) ;
2023-06-17 14:21:42 +02:00
break ;
case ObjProperty : : MONSTER_REFUSED_JOIN :
2023-11-06 18:27:16 +02:00
refusedJoining = identifier . getNum ( ) ;
2023-06-17 14:21:42 +02:00
break ;
}
}
int CGCreature : : takenAction ( const CGHeroInstance * h , bool allowJoin ) const
{
//calculate relative strength of hero and creatures armies
double relStrength = static_cast < double > ( h - > getTotalStrength ( ) ) / getArmyStrength ( ) ;
int powerFactor ;
if ( relStrength > = 7 )
powerFactor = 11 ;
else if ( relStrength > = 1 )
powerFactor = static_cast < int > ( 2 * ( relStrength - 1 ) ) ;
else if ( relStrength > = 0.5 )
powerFactor = - 1 ;
else if ( relStrength > = 0.333 )
powerFactor = - 2 ;
else
powerFactor = - 3 ;
int count = 0 ; //how many creatures of similar kind has hero
int totalCount = 0 ;
for ( const auto & elem : h - > Slots ( ) )
{
2024-10-05 21:37:52 +02:00
bool isOurUpgrade = vstd : : contains ( getCreature ( ) - > upgrades , elem . second - > getCreatureID ( ) ) ;
2024-10-12 18:02:35 +02:00
bool isOurDowngrade = vstd : : contains ( elem . second - > getCreature ( ) - > upgrades , getCreatureID ( ) ) ;
2024-05-02 16:36:44 +02:00
if ( isOurUpgrade | | isOurDowngrade )
2023-06-17 14:21:42 +02:00
count + = elem . second - > count ;
totalCount + = elem . second - > count ;
}
int sympathy = 0 ; // 0 if hero have no similar creatures
if ( count )
sympathy + + ; // 1 - if hero have at least 1 similar creature
if ( count * 2 > totalCount )
sympathy + + ; // 2 - hero have similar creatures more that 50%
int diplomacy = h - > valOfBonuses ( BonusType : : WANDERING_CREATURES_JOIN_BONUS ) ;
int charisma = powerFactor + diplomacy + sympathy ;
if ( charisma < character )
return FIGHT ;
if ( allowJoin )
{
if ( diplomacy + sympathy + 1 > = character )
return JOIN_FOR_FREE ;
2024-05-02 16:32:38 +02:00
if ( diplomacy * 2 + sympathy + 1 > = character )
{
2024-10-05 21:37:52 +02:00
int32_t recruitCost = getCreature ( ) - > getRecruitCost ( EGameResID : : GOLD ) ;
2024-05-02 16:36:44 +02:00
int32_t stackCount = getStackCount ( SlotID ( 0 ) ) ;
2024-05-02 16:32:38 +02:00
return recruitCost * stackCount ; //join for gold
}
2023-06-17 14:21:42 +02:00
}
//we are still here - creatures have not joined hero, flee or fight
if ( charisma > character & & ! neverFlees )
return FLEE ;
else
return FIGHT ;
}
void CGCreature : : fleeDecision ( const CGHeroInstance * h , ui32 pursue ) const
{
if ( refusedJoining )
2023-11-06 18:27:16 +02:00
cb - > setObjPropertyValue ( id , ObjProperty : : MONSTER_REFUSED_JOIN , false ) ;
2023-06-17 14:21:42 +02:00
if ( pursue )
{
fight ( h ) ;
}
else
{
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , h - > getOwner ( ) ) ;
2023-06-17 14:21:42 +02:00
}
}
void CGCreature : : joinDecision ( const CGHeroInstance * h , int cost , ui32 accept ) const
{
if ( ! accept )
{
if ( takenAction ( h , false ) = = FLEE )
{
2023-11-06 18:27:16 +02:00
cb - > setObjPropertyValue ( id , ObjProperty : : MONSTER_REFUSED_JOIN , true ) ;
2023-06-17 14:21:42 +02:00
flee ( h ) ;
}
else //they fight
{
h - > showInfoDialog ( 87 , 0 , EInfoWindowMode : : MODAL ) ; //Insulted by your refusal of their offer, the monsters attack!
fight ( h ) ;
}
}
else //accepted
{
if ( cb - > getResource ( h - > tempOwner , EGameResID : : GOLD ) < cost ) //player don't have enough gold!
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : GENERAL_TXT , 29 ) ; //You don't have enough gold
2023-06-17 14:21:42 +02:00
cb - > showInfoDialog ( & iw ) ;
//act as if player refused
joinDecision ( h , cost , false ) ;
return ;
}
//take gold
if ( cost )
cb - > giveResource ( h - > tempOwner , EGameResID : : GOLD , - cost ) ;
giveReward ( h ) ;
cb - > tryJoiningArmy ( this , h , true , true ) ;
}
}
void CGCreature : : fight ( const CGHeroInstance * h ) const
{
//split stacks
int stacksCount = getNumberOfStacks ( h ) ;
//source: http://heroescommunity.com/viewthread.php3?TID=27539&PID=1266335#focus
int amount = getStackCount ( SlotID ( 0 ) ) ;
int m = amount / stacksCount ;
int b = stacksCount * ( m + 1 ) - amount ;
int a = stacksCount - b ;
SlotID sourceSlot = stacks . begin ( ) - > first ;
for ( int slotID = 1 ; slotID < a ; + + slotID )
{
int stackSize = m + 1 ;
cb - > moveStack ( StackLocation ( this , sourceSlot ) , StackLocation ( this , SlotID ( slotID ) ) , stackSize ) ;
}
for ( int slotID = a ; slotID < stacksCount ; + + slotID )
{
int stackSize = m ;
if ( slotID ) //don't do this when a = 0 -> stack is single
cb - > moveStack ( StackLocation ( this , sourceSlot ) , StackLocation ( this , SlotID ( slotID ) ) , stackSize ) ;
}
if ( stacksCount > 1 )
{
if ( containsUpgradedStack ( ) ) //upgrade
{
SlotID slotID = SlotID ( static_cast < si32 > ( std : : floor ( static_cast < float > ( stacks . size ( ) ) / 2.0f ) ) ) ;
2024-10-12 18:02:35 +02:00
const auto & upgrades = getStack ( slotID ) . getCreature ( ) - > upgrades ;
2023-06-17 14:21:42 +02:00
if ( ! upgrades . empty ( ) )
{
2024-06-01 17:28:17 +02:00
auto it = RandomGeneratorUtil : : nextItem ( upgrades , cb - > gameState ( ) - > getRandomGenerator ( ) ) ;
2023-11-02 18:45:46 +02:00
cb - > changeStackType ( StackLocation ( this , slotID ) , it - > toCreature ( ) ) ;
2023-06-17 14:21:42 +02:00
}
}
}
2024-08-31 23:04:32 +02:00
cb - > startBattle ( h , this ) ;
2023-06-17 14:21:42 +02:00
}
void CGCreature : : flee ( const CGHeroInstance * h ) const
{
BlockingDialog ynd ( true , false ) ;
ynd . player = h - > tempOwner ;
2023-06-18 11:18:25 +02:00
ynd . text . appendLocalString ( EMetaText : : ADVOB_TXT , 91 ) ;
2024-10-05 21:37:52 +02:00
ynd . text . replaceName ( getCreatureID ( ) , getStackCount ( SlotID ( 0 ) ) ) ;
2024-09-04 17:14:56 +02:00
cb - > showBlockingDialog ( this , & ynd ) ;
2023-06-17 14:21:42 +02:00
}
void CGCreature : : battleFinished ( const CGHeroInstance * hero , const BattleResult & result ) const
{
2024-08-11 22:22:35 +02:00
if ( result . winner = = BattleSide : : ATTACKER )
2023-06-17 14:21:42 +02:00
{
giveReward ( hero ) ;
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , hero - > getOwner ( ) ) ;
2023-06-17 14:21:42 +02:00
}
2024-08-11 22:22:35 +02:00
else if ( result . winner = = BattleSide : : NONE ) // draw
2023-06-17 14:21:42 +02:00
{
// guarded reward is lost forever on draw
2023-09-18 21:09:55 +02:00
cb - > removeObject ( this , hero - > getOwner ( ) ) ;
2023-06-17 14:21:42 +02:00
}
else
{
//merge stacks into one
TSlots : : const_iterator i ;
2024-10-05 21:37:52 +02:00
const CCreature * cre = getCreature ( ) ;
2023-06-17 14:21:42 +02:00
for ( i = stacks . begin ( ) ; i ! = stacks . end ( ) ; i + + )
{
2024-10-12 18:02:35 +02:00
if ( cre - > isMyUpgrade ( i - > second - > getCreature ( ) ) )
2023-06-17 14:21:42 +02:00
{
cb - > changeStackType ( StackLocation ( this , i - > first ) , cre ) ; //un-upgrade creatures
}
}
//first stack has to be at slot 0 -> if original one got killed, move there first remaining stack
if ( ! hasStackAtSlot ( SlotID ( 0 ) ) )
cb - > moveStack ( StackLocation ( this , stacks . begin ( ) - > first ) , StackLocation ( this , SlotID ( 0 ) ) , stacks . begin ( ) - > second - > count ) ;
while ( stacks . size ( ) > 1 ) //hopefully that's enough
{
// TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere)
i = stacks . end ( ) ;
i - - ;
2024-10-12 18:02:35 +02:00
SlotID slot = getSlotFor ( i - > second - > getCreature ( ) ) ;
2023-06-17 14:21:42 +02:00
if ( slot = = i - > first ) //no reason to move stack to its own slot
break ;
else
cb - > moveStack ( StackLocation ( this , i - > first ) , StackLocation ( this , slot ) , i - > second - > count ) ;
}
2023-11-06 18:27:16 +02:00
cb - > setObjPropertyValue ( id , ObjProperty : : MONSTER_POWER , stacks . begin ( ) - > second - > count * 1000 ) ; //remember casualties
2023-06-17 14:21:42 +02:00
}
}
2024-08-09 00:28:28 +02:00
void CGCreature : : blockingDialogAnswered ( const CGHeroInstance * hero , int32_t answer ) const
2023-06-17 14:21:42 +02:00
{
auto action = takenAction ( hero ) ;
if ( ! refusedJoining & & action > = JOIN_FOR_FREE ) //higher means price
joinDecision ( hero , action , answer ) ;
else if ( action ! = FIGHT )
fleeDecision ( hero , answer ) ;
else
assert ( 0 ) ;
}
bool CGCreature : : containsUpgradedStack ( ) const
{
//source http://heroescommunity.com/viewthread.php3?TID=27539&PID=830557#focus
float a = 2992.911117f ;
float b = 14174.264968f ;
float c = 5325.181015f ;
float d = 32788.727920f ;
2024-10-02 18:40:06 +02:00
int val = static_cast < int > ( std : : floor ( a * visitablePos ( ) . x + b * visitablePos ( ) . y + c * visitablePos ( ) . z + d ) ) ;
2023-06-17 14:21:42 +02:00
return ( ( val % 32768 ) % 100 ) < 50 ;
}
int CGCreature : : getNumberOfStacks ( const CGHeroInstance * hero ) const
{
//source http://heroescommunity.com/viewthread.php3?TID=27539&PID=1266094#focus
double strengthRatio = static_cast < double > ( hero - > getArmyStrength ( ) ) / getArmyStrength ( ) ;
int split = 1 ;
if ( strengthRatio < 0.5f )
split = 7 ;
else if ( strengthRatio < 0.67f )
split = 6 ;
else if ( strengthRatio < 1 )
split = 5 ;
else if ( strengthRatio < 1.5f )
split = 4 ;
else if ( strengthRatio < 2 )
split = 3 ;
else
split = 2 ;
ui32 a = 1550811371u ;
ui32 b = 3359066809u ;
ui32 c = 1943276003u ;
ui32 d = 3174620878u ;
2024-10-02 18:40:06 +02:00
ui32 R1 = a * static_cast < ui32 > ( visitablePos ( ) . x ) + b * static_cast < ui32 > ( visitablePos ( ) . y ) + c * static_cast < ui32 > ( visitablePos ( ) . z ) + d ;
2023-06-17 14:21:42 +02:00
ui32 R2 = ( R1 > > 16 ) & 0x7fff ;
int R4 = R2 % 100 + 1 ;
if ( R4 < = 20 )
split - = 1 ;
else if ( R4 > = 80 )
split + = 1 ;
vstd : : amin ( split , getStack ( SlotID ( 0 ) ) . count ) ; //can't divide into more stacks than creatures total
vstd : : amin ( split , 7 ) ; //can't have more than 7 stacks
return split ;
}
void CGCreature : : giveReward ( const CGHeroInstance * h ) const
{
InfoWindow iw ;
iw . player = h - > tempOwner ;
if ( ! resources . empty ( ) )
{
cb - > giveResources ( h - > tempOwner , resources ) ;
2024-05-02 16:32:38 +02:00
for ( const auto & res : GameResID : : ALL_RESOURCES ( ) )
2023-06-17 14:21:42 +02:00
{
2023-10-31 11:09:56 +02:00
if ( resources [ res ] > 0 )
iw . components . emplace_back ( ComponentType : : RESOURCE , res , resources [ res ] ) ;
2023-06-17 14:21:42 +02:00
}
}
if ( gainedArtifact ! = ArtifactID : : NONE )
{
2024-09-04 19:39:13 +02:00
cb - > giveHeroNewArtifact ( h , gainedArtifact , ArtifactPosition : : FIRST_AVAILABLE ) ;
2023-10-31 11:09:56 +02:00
iw . components . emplace_back ( ComponentType : : ARTIFACT , gainedArtifact ) ;
2023-06-17 14:21:42 +02:00
}
if ( ! iw . components . empty ( ) )
{
iw . type = EInfoWindowMode : : AUTO ;
2023-06-18 11:18:25 +02:00
iw . text . appendLocalString ( EMetaText : : ADVOB_TXT , 183 ) ; // % has found treasure
iw . text . replaceRawString ( h - > getNameTranslated ( ) ) ;
2023-06-17 14:21:42 +02:00
cb - > showInfoDialog ( & iw ) ;
}
}
static const std : : vector < std : : string > CHARACTER_JSON =
{
" compliant " , " friendly " , " aggressive " , " hostile " , " savage "
} ;
void CGCreature : : serializeJsonOptions ( JsonSerializeFormat & handler )
{
handler . serializeEnum ( " character " , character , CHARACTER_JSON ) ;
if ( handler . saving )
{
if ( hasStackAtSlot ( SlotID ( 0 ) ) )
{
si32 amount = getStack ( SlotID ( 0 ) ) . count ;
handler . serializeInt ( " amount " , amount , 0 ) ;
}
}
else
{
si32 amount = 0 ;
handler . serializeInt ( " amount " , amount ) ;
auto * hlp = new CStackInstance ( ) ;
hlp - > count = amount ;
//type will be set during initialization
putStack ( SlotID ( 0 ) , hlp ) ;
}
resources . serializeJson ( handler , " rewardResources " ) ;
handler . serializeId ( " rewardArtifact " , gainedArtifact , ArtifactID ( ArtifactID : : NONE ) ) ;
handler . serializeBool ( " noGrowing " , notGrowingTeam ) ;
handler . serializeBool ( " neverFlees " , neverFlees ) ;
2023-09-27 23:25:19 +02:00
handler . serializeStruct ( " rewardMessage " , message ) ;
2023-06-17 14:21:42 +02:00
}
VCMI_LIB_NAMESPACE_END