mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-27 22:49:25 +02:00
Random-with-history for luck & morale rolls
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
#include "../../lib/GameLibrary.h"
|
#include "../../lib/GameLibrary.h"
|
||||||
#include "../../lib/CCreatureHandler.h"
|
#include "../../lib/CCreatureHandler.h"
|
||||||
#include "../../lib/CSkillHandler.h"
|
#include "../../lib/CSkillHandler.h"
|
||||||
|
#include "../../lib/IGameSettings.h"
|
||||||
#include "../../lib/entities/artifact/CArtHandler.h"
|
#include "../../lib/entities/artifact/CArtHandler.h"
|
||||||
#include "../../lib/entities/artifact/EArtifactClass.h"
|
#include "../../lib/entities/artifact/EArtifactClass.h"
|
||||||
#include "../../lib/entities/hero/CHeroClass.h"
|
#include "../../lib/entities/hero/CHeroClass.h"
|
||||||
@@ -23,15 +24,26 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
bool BiasedRandomizer::roll(vstd::RNG &generator, int successChance, int biasValue)
|
BiasedRandomizer::BiasedRandomizer(int seed)
|
||||||
|
: seed(seed)
|
||||||
{
|
{
|
||||||
int failChance = 100 - successChance;
|
}
|
||||||
int newRoll = generator.nextInt(0,99);
|
|
||||||
bool success = newRoll + accumulatedBias >= successChance;
|
bool BiasedRandomizer::roll(int successChance, int totalWeight, int biasValue)
|
||||||
|
{
|
||||||
|
assert(successChance > 0);
|
||||||
|
assert(totalWeight >= successChance);
|
||||||
|
|
||||||
|
int failChance = totalWeight - successChance;
|
||||||
|
int newRoll = seed.nextInt(1,totalWeight);
|
||||||
|
// accumulated bias is stored as premultiplied to avoid precision loss on division
|
||||||
|
// so multiply everything else in equation to compensate
|
||||||
|
// precision loss is small, and generally insignificant, but better to play it safe
|
||||||
|
bool success = newRoll * totalWeight - accumulatedBias <= successChance * totalWeight;
|
||||||
if (success)
|
if (success)
|
||||||
accumulatedBias -= failChance * biasValue / 100;
|
accumulatedBias -= failChance * biasValue;
|
||||||
else
|
else
|
||||||
accumulatedBias += successChance * biasValue / 100;
|
accumulatedBias += successChance * biasValue;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@@ -43,26 +55,43 @@ GameRandomizer::GameRandomizer(const IGameInfoCallback & gameInfo)
|
|||||||
|
|
||||||
GameRandomizer::~GameRandomizer() = default;
|
GameRandomizer::~GameRandomizer() = default;
|
||||||
|
|
||||||
//bool GameRandomizer::rollGoodMorale(ObjectInstanceID actor, int moraleValue)
|
|
||||||
//{
|
bool GameRandomizer::rollMoraleLuck(std::map<ObjectInstanceID, BiasedRandomizer> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings diceSize, EGameSettings diceWeights)
|
||||||
//
|
{
|
||||||
//}
|
assert(moraleLuckValue > 0);
|
||||||
//
|
auto goodLuckChanceVector = gameInfo.getSettings().getVector(diceWeights);
|
||||||
//bool GameRandomizer::rollBadMorale(ObjectInstanceID actor, int moraleValue)
|
int luckDiceSize = gameInfo.getSettings().getInteger(diceSize);
|
||||||
//{
|
size_t chanceIndex = std::min<size_t>(goodLuckChanceVector.size(), moraleLuckValue) - 1; // array index, so 0-indexed
|
||||||
//
|
|
||||||
//}
|
if (!seeds.count(actor))
|
||||||
//
|
seeds.emplace(actor, getDefault().nextInt());
|
||||||
//bool GameRandomizer::rollGoodLuck(ObjectInstanceID actor, int luckValue)
|
|
||||||
//{
|
if(goodLuckChanceVector.size() == 0)
|
||||||
//
|
return false;
|
||||||
//}
|
|
||||||
//
|
return seeds.at(actor).roll(goodLuckChanceVector[chanceIndex], luckDiceSize, biasValue);
|
||||||
//bool GameRandomizer::rollBadLuck(ObjectInstanceID actor, int luckValue)
|
}
|
||||||
//{
|
|
||||||
//
|
bool GameRandomizer::rollGoodMorale(ObjectInstanceID actor, int moraleValue)
|
||||||
//}
|
{
|
||||||
//
|
return rollMoraleLuck(goodMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameRandomizer::rollBadMorale(ObjectInstanceID actor, int moraleValue)
|
||||||
|
{
|
||||||
|
return rollMoraleLuck(badMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_BAD_MORALE_CHANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameRandomizer::rollGoodLuck(ObjectInstanceID actor, int luckValue)
|
||||||
|
{
|
||||||
|
return rollMoraleLuck(goodLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_GOOD_LUCK_CHANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameRandomizer::rollBadLuck(ObjectInstanceID actor, int luckValue)
|
||||||
|
{
|
||||||
|
return rollMoraleLuck(badLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_BAD_LUCK_CHANCE);
|
||||||
|
}
|
||||||
|
|
||||||
//bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageChance)
|
//bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageChance)
|
||||||
//{
|
//{
|
||||||
//
|
//
|
||||||
@@ -199,6 +228,9 @@ void GameRandomizer::setSeed(int newSeed)
|
|||||||
|
|
||||||
PrimarySkill GameRandomizer::rollPrimarySkillForLevelup(const CGHeroInstance * hero)
|
PrimarySkill GameRandomizer::rollPrimarySkillForLevelup(const CGHeroInstance * hero)
|
||||||
{
|
{
|
||||||
|
if (!heroSkillSeed.count(hero->getHeroTypeID()))
|
||||||
|
heroSkillSeed.emplace(hero->getHeroTypeID(), getDefault().nextInt());
|
||||||
|
|
||||||
const bool isLowLevelHero = hero->level < GameConstants::HERO_HIGH_LEVEL;
|
const bool isLowLevelHero = hero->level < GameConstants::HERO_HIGH_LEVEL;
|
||||||
const auto & skillChances = isLowLevelHero ? hero->getHeroClass()->primarySkillLowLevel : hero->getHeroClass()->primarySkillHighLevel;
|
const auto & skillChances = isLowLevelHero ? hero->getHeroClass()->primarySkillLowLevel : hero->getHeroClass()->primarySkillHighLevel;
|
||||||
auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
|
auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
|
||||||
@@ -214,6 +246,9 @@ PrimarySkill GameRandomizer::rollPrimarySkillForLevelup(const CGHeroInstance * h
|
|||||||
|
|
||||||
SecondarySkill GameRandomizer::rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & options)
|
SecondarySkill GameRandomizer::rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & options)
|
||||||
{
|
{
|
||||||
|
if (!heroSkillSeed.count(hero->getHeroTypeID()))
|
||||||
|
heroSkillSeed.emplace(hero->getHeroTypeID(), getDefault().nextInt());
|
||||||
|
|
||||||
auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
|
auto & heroRng = heroSkillSeed.at(hero->getHeroTypeID());
|
||||||
|
|
||||||
auto getObligatorySkills = [](CSkill::Obligatory obl)
|
auto getObligatorySkills = [](CSkill::Obligatory obl)
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
enum class EGameSettings;
|
||||||
|
|
||||||
class CGHeroInstance;
|
class CGHeroInstance;
|
||||||
|
|
||||||
/// Biased randomizer that has following properties:
|
/// Biased randomizer that has following properties:
|
||||||
@@ -24,17 +26,25 @@ class CGHeroInstance;
|
|||||||
/// Its goal is to simulate human expectations of random distributions and reduce frustration from "bad" rolls
|
/// Its goal is to simulate human expectations of random distributions and reduce frustration from "bad" rolls
|
||||||
class BiasedRandomizer
|
class BiasedRandomizer
|
||||||
{
|
{
|
||||||
int accumulatedBias;
|
CRandomGenerator seed;
|
||||||
|
int32_t accumulatedBias = 0;
|
||||||
public:
|
public:
|
||||||
|
explicit BiasedRandomizer(int seed);
|
||||||
/// Performs coin flip with specified success chance
|
/// Performs coin flip with specified success chance
|
||||||
/// Returns true with probability successChance percents, and false with probability 100-successChance percents
|
/// Returns true with probability successChance percents, and false with probability 100-successChance percents
|
||||||
bool roll(vstd::RNG & generator, int successChance, int biasValue);
|
bool roll(int successChance, int totalWeight, int biasValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
|
class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
|
||||||
{
|
{
|
||||||
struct HeroSkillGenerator
|
static constexpr int biasValue = 10;
|
||||||
|
|
||||||
|
struct HeroSkillRandomizer
|
||||||
{
|
{
|
||||||
|
HeroSkillRandomizer(int seed)
|
||||||
|
:seed(seed)
|
||||||
|
{}
|
||||||
|
|
||||||
CRandomGenerator seed;
|
CRandomGenerator seed;
|
||||||
int8_t magicSchoolCounter = 1;
|
int8_t magicSchoolCounter = 1;
|
||||||
int8_t wisdomCounter = 1;
|
int8_t wisdomCounter = 1;
|
||||||
@@ -48,7 +58,7 @@ class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
|
|||||||
/// Stores number of times each artifact was placed on map via randomization
|
/// Stores number of times each artifact was placed on map via randomization
|
||||||
std::map<ArtifactID, int> allocatedArtifacts;
|
std::map<ArtifactID, int> allocatedArtifacts;
|
||||||
|
|
||||||
std::map<HeroTypeID, HeroSkillGenerator> heroSkillSeed;
|
std::map<HeroTypeID, HeroSkillRandomizer> heroSkillSeed;
|
||||||
std::map<PlayerColor, CRandomGenerator> playerTavern;
|
std::map<PlayerColor, CRandomGenerator> playerTavern;
|
||||||
|
|
||||||
std::map<ObjectInstanceID, BiasedRandomizer> goodMoraleSeed;
|
std::map<ObjectInstanceID, BiasedRandomizer> goodMoraleSeed;
|
||||||
@@ -58,17 +68,18 @@ class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
|
|||||||
|
|
||||||
std::map<ObjectInstanceID, BiasedRandomizer> combatAbilitySeed;
|
std::map<ObjectInstanceID, BiasedRandomizer> combatAbilitySeed;
|
||||||
|
|
||||||
|
bool rollMoraleLuck(std::map<ObjectInstanceID, BiasedRandomizer> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings diceSize, EGameSettings diceWeights);
|
||||||
public:
|
public:
|
||||||
explicit GameRandomizer(const IGameInfoCallback & gameInfo);
|
explicit GameRandomizer(const IGameInfoCallback & gameInfo);
|
||||||
~GameRandomizer();
|
~GameRandomizer();
|
||||||
|
|
||||||
PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero) override;
|
PrimarySkill rollPrimarySkillForLevelup(const CGHeroInstance * hero) override;
|
||||||
SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & candidates) override;
|
SecondarySkill rollSecondarySkillForLevelup(const CGHeroInstance * hero, const std::set<SecondarySkill> & candidates) override;
|
||||||
//
|
|
||||||
// bool rollGoodMorale(ObjectInstanceID actor, int moraleValue);
|
bool rollGoodMorale(ObjectInstanceID actor, int moraleValue);
|
||||||
// bool rollBadMorale(ObjectInstanceID actor, int moraleValue);
|
bool rollBadMorale(ObjectInstanceID actor, int moraleValue);
|
||||||
// bool rollGoodLuck(ObjectInstanceID actor, int luckValue);
|
bool rollGoodLuck(ObjectInstanceID actor, int luckValue);
|
||||||
// bool rollBadLuck(ObjectInstanceID actor, int luckValue);
|
bool rollBadLuck(ObjectInstanceID actor, int luckValue);
|
||||||
//
|
//
|
||||||
// bool rollCombatAbility(ObjectInstanceID actor, int percentageChance);
|
// bool rollCombatAbility(ObjectInstanceID actor, int percentageChance);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "../../lib/battle/CObstacleInstance.h"
|
#include "../../lib/battle/CObstacleInstance.h"
|
||||||
#include "../../lib/battle/IBattleState.h"
|
#include "../../lib/battle/IBattleState.h"
|
||||||
#include "../../lib/battle/BattleAction.h"
|
#include "../../lib/battle/BattleAction.h"
|
||||||
|
#include "../../lib/callback/GameRandomizer.h"
|
||||||
#include "../../lib/entities/building/TownFortifications.h"
|
#include "../../lib/entities/building/TownFortifications.h"
|
||||||
#include "../../lib/gameState/CGameState.h"
|
#include "../../lib/gameState/CGameState.h"
|
||||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||||
@@ -940,21 +941,15 @@ void BattleActionProcessor::makeAttack(const CBattleInfoCallback & battle, const
|
|||||||
|
|
||||||
if(attackerLuck > 0)
|
if(attackerLuck > 0)
|
||||||
{
|
{
|
||||||
auto goodLuckChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_GOOD_LUCK_CHANCE);
|
ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(attacker->unitSide())->id;
|
||||||
int luckDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_LUCK_DICE_SIZE);
|
if (gameHandler->randomizer->rollGoodLuck(ownerArmy, attackerLuck))
|
||||||
size_t chanceIndex = std::min<size_t>(goodLuckChanceVector.size(), attackerLuck) - 1; // array index, so 0-indexed
|
|
||||||
|
|
||||||
if(goodLuckChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, luckDiceSize) <= goodLuckChanceVector[chanceIndex])
|
|
||||||
bat.flags |= BattleAttack::LUCKY;
|
bat.flags |= BattleAttack::LUCKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(attackerLuck < 0)
|
if(attackerLuck < 0)
|
||||||
{
|
{
|
||||||
auto badLuckChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_BAD_LUCK_CHANCE);
|
ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(attacker->unitSide())->id;
|
||||||
int luckDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_LUCK_DICE_SIZE);
|
if (gameHandler->randomizer->rollBadLuck(ownerArmy, -attackerLuck))
|
||||||
size_t chanceIndex = std::min<size_t>(badLuckChanceVector.size(), -attackerLuck) - 1; // array index, so 0-indexed
|
|
||||||
|
|
||||||
if(badLuckChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, luckDiceSize) <= badLuckChanceVector[chanceIndex])
|
|
||||||
bat.flags |= BattleAttack::UNLUCKY;
|
bat.flags |= BattleAttack::UNLUCKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "../../lib/IGameSettings.h"
|
#include "../../lib/IGameSettings.h"
|
||||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||||
#include "../../lib/battle/IBattleState.h"
|
#include "../../lib/battle/IBattleState.h"
|
||||||
|
#include "../../lib/callback/GameRandomizer.h"
|
||||||
#include "../../lib/entities/building/TownFortifications.h"
|
#include "../../lib/entities/building/TownFortifications.h"
|
||||||
#include "../../lib/gameState/CGameState.h"
|
#include "../../lib/gameState/CGameState.h"
|
||||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||||
@@ -347,11 +348,8 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CBattleInfoCallback & bat
|
|||||||
int nextStackMorale = next->moraleVal();
|
int nextStackMorale = next->moraleVal();
|
||||||
if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
|
if(!next->hadMorale && !next->waited() && nextStackMorale < 0)
|
||||||
{
|
{
|
||||||
auto badMoraleChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_CHANCE);
|
ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(next->unitSide())->id;
|
||||||
int moraleDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_MORALE_DICE_SIZE);
|
if (gameHandler->randomizer->rollBadMorale(ownerArmy, -nextStackMorale))
|
||||||
size_t chanceIndex = std::min<size_t>(badMoraleChanceVector.size(), -nextStackMorale) - 1; // array index, so 0-indexed
|
|
||||||
|
|
||||||
if(badMoraleChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, moraleDiceSize) <= badMoraleChanceVector[chanceIndex])
|
|
||||||
{
|
{
|
||||||
//unit loses its turn - empty freeze action
|
//unit loses its turn - empty freeze action
|
||||||
BattleAction ba;
|
BattleAction ba;
|
||||||
@@ -527,11 +525,8 @@ bool BattleFlowProcessor::rollGoodMorale(const CBattleInfoCallback & battle, con
|
|||||||
&& next->canMove()
|
&& next->canMove()
|
||||||
&& nextStackMorale > 0)
|
&& nextStackMorale > 0)
|
||||||
{
|
{
|
||||||
auto goodMoraleChanceVector = gameHandler->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
|
ObjectInstanceID ownerArmy = battle.getBattle()->getSideArmy(next->unitSide())->id;
|
||||||
int moraleDiceSize = gameHandler->getSettings().getInteger(EGameSettings::COMBAT_MORALE_DICE_SIZE);
|
if (gameHandler->randomizer->rollGoodMorale(ownerArmy, nextStackMorale))
|
||||||
size_t chanceIndex = std::min<size_t>(goodMoraleChanceVector.size(), nextStackMorale) - 1; // array index, so 0-indexed
|
|
||||||
|
|
||||||
if(goodMoraleChanceVector.size() > 0 && gameHandler->getRandomGenerator().nextInt(1, moraleDiceSize) <= goodMoraleChanceVector[chanceIndex])
|
|
||||||
{
|
{
|
||||||
BattleTriggerEffect bte;
|
BattleTriggerEffect bte;
|
||||||
bte.battleID = battle.getBattle()->getBattleID();
|
bte.battleID = battle.getBattle()->getBattleID();
|
||||||
|
|||||||
Reference in New Issue
Block a user