Ver Fonte

Fix serialization, add new game settings

Ivan Savenko há 5 meses atrás
pai
commit
131b59e6cd

+ 18 - 2
config/gameConfig.json

@@ -340,16 +340,32 @@
 
 		"combat":
 		{
+			// defines bias used for percentage-based ability rolls, such Death Blow of Dread Knight
+			// If bias is set to 0, then all rolls will be completely independent - it is possible to get lucky and roll ability with 10% chance
+			// multiple times in a row, or be unlucky and not roll 50% ability multiple times in a row
+			// If bias is non-zero, game will adjust probability based on previous rolls while keeping average chance at desired value
+			// So matter what value is used for bias, actual probability for large (1000+) number of rolls is same as stated in description
+			// However, non-zero bias allows to prevent long streaks of "bad" rolls, and enforce actual probabilities even for small (10-20) number of rolls
+			// Recommended value is ~10-25. Excessively large values, like 100 can make rolls very predictable, for example rolling 20% ability every 5th roll
+			"abilityBias" : 25
+
 			// defines dice chance and dice size of a morale roll, based on creature's morale.
 			// Resulting chance is chanceValue / diceSize. If list contains 0 values, option will be disabled
 			"goodMoraleChance" : [ 1, 2, 3 ],
 			"badMoraleChance" : [ 2, 4, 6],
-            "moraleDiceSize" : 24,
+			"moraleDiceSize" : 24,
+			// Bias for morale rolls. See abilityBias for detailed description
+			// Recommended value is around moraleDiceSize / 4
+			"moraleBias" : 8
 
 			// defines dice chance and dice size of a luck roll, based on creature's luck
 			"goodLuckChance" : [ 1, 2, 3 ],
 			"badLuckChance" : [],
-            "luckDiceSize" : 24,
+			"luckDiceSize" : 24,
+			// Bias for luck rolls. See abilityBias for detailed description
+			// Recommended value is around luckDiceSize / 4
+			"luckBias" : 8
+			
 
 			// every 1 attack point damage influence in battle when attack points > defense points during creature attack
 			"attackPointDamageFactor": 0.05, 

+ 3 - 0
lib/GameSettings.cpp

@@ -45,6 +45,7 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION,                    "banks",     "showGuardsComposition"                },
 		{EGameSettings::BONUSES_GLOBAL,                                   "bonuses",   "global"                               },
 		{EGameSettings::BONUSES_PER_HERO,                                 "bonuses",   "perHero"                              },
+		{EGameSettings::COMBAT_ABILITY_BIAS,                              "combat",    "abilityBias"                          },
 		{EGameSettings::COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX,            "combat",    "areaShotCanTargetEmptyHex"            },
 		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR,                "combat",    "attackPointDamageFactor"              },
 		{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,            "combat",    "attackPointDamageFactorCap"           },
@@ -53,9 +54,11 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::COMBAT_GOOD_MORALE_CHANCE,                        "combat",    "goodMoraleChance"                     },
 		{EGameSettings::COMBAT_BAD_MORALE_CHANCE,                         "combat",    "badMoraleChance"                      },
 		{EGameSettings::COMBAT_MORALE_DICE_SIZE,                          "combat",    "moraleDiceSize"                       },
+		{EGameSettings::COMBAT_MORALE_BIAS,                               "combat",    "moraleBias"                           },
 		{EGameSettings::COMBAT_GOOD_LUCK_CHANCE,                          "combat",    "goodLuckChance"                       },
 		{EGameSettings::COMBAT_BAD_LUCK_CHANCE,                           "combat",    "badLuckChance"                        },
 		{EGameSettings::COMBAT_LUCK_DICE_SIZE,                            "combat",    "luckDiceSize"                         },
+		{EGameSettings::COMBAT_LUCK_BIAS,                                 "combat",    "luckBias"                             },
 		{EGameSettings::COMBAT_LAYOUTS,                                   "combat",    "layouts"                              },
 		{EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,                "combat",    "oneHexTriggersObstacles"              },
 		{EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,             "creatures", "allowAllForDoubleMonth"               },

+ 3 - 0
lib/IGameSettings.h

@@ -18,6 +18,7 @@ enum class EGameSettings
 	BANKS_SHOW_GUARDS_COMPOSITION,
 	BONUSES_GLOBAL,
 	BONUSES_PER_HERO,
+	COMBAT_ABILITY_BIAS,
 	COMBAT_AREA_SHOT_CAN_TARGET_EMPTY_HEX,
 	COMBAT_ATTACK_POINT_DAMAGE_FACTOR,
 	COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP,
@@ -26,9 +27,11 @@ enum class EGameSettings
 	COMBAT_GOOD_MORALE_CHANCE, 
 	COMBAT_BAD_MORALE_CHANCE, 
 	COMBAT_MORALE_DICE_SIZE,
+	COMBAT_MORALE_BIAS,
 	COMBAT_GOOD_LUCK_CHANCE,
 	COMBAT_BAD_LUCK_CHANCE,
 	COMBAT_LUCK_DICE_SIZE,
+	COMBAT_LUCK_BIAS,
 	COMBAT_LAYOUTS,
 	COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
 	CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,

+ 10 - 7
lib/callback/GameRandomizer.cpp

@@ -61,11 +61,12 @@ GameRandomizer::GameRandomizer(const IGameInfoCallback & gameInfo)
 GameRandomizer::~GameRandomizer() = default;
 
 
-bool GameRandomizer::rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWithBias> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings diceSize, EGameSettings diceWeights)
+bool GameRandomizer::rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWithBias> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings biasValueSetting, EGameSettings diceSize, EGameSettings diceWeights)
 {
 	assert(moraleLuckValue > 0);
 	auto goodLuckChanceVector = gameInfo.getSettings().getVector(diceWeights);
 	int luckDiceSize = gameInfo.getSettings().getInteger(diceSize);
+	int biasValue = gameInfo.getSettings().getInteger(biasValueSetting);
 	size_t chanceIndex = std::min<size_t>(goodLuckChanceVector.size(), moraleLuckValue) - 1; // array index, so 0-indexed
 
 	if(!seeds.count(actor))
@@ -74,27 +75,27 @@ bool GameRandomizer::rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWi
 	if(goodLuckChanceVector.size() == 0)
 		return false;
 
-	return seeds.at(actor).roll(goodLuckChanceVector[chanceIndex], luckDiceSize, biasValueLuckMorale);
+	return seeds.at(actor).roll(goodLuckChanceVector[chanceIndex], luckDiceSize, biasValue);
 }
 
 bool GameRandomizer::rollGoodMorale(ObjectInstanceID actor, int moraleValue)
 {
-	return rollMoraleLuck(goodMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_DICE_SIZE, EGameSettings::COMBAT_GOOD_MORALE_CHANCE);
+	return rollMoraleLuck(goodMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_BIAS, 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);
+	return rollMoraleLuck(badMoraleSeed, actor, moraleValue, EGameSettings::COMBAT_MORALE_BIAS, 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);
+	return rollMoraleLuck(goodLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_BIAS, 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);
+	return rollMoraleLuck(badLuckSeed, actor, luckValue, EGameSettings::COMBAT_LUCK_BIAS, EGameSettings::COMBAT_LUCK_DICE_SIZE, EGameSettings::COMBAT_BAD_LUCK_CHANCE);
 }
 
 bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageChance)
@@ -108,7 +109,9 @@ bool GameRandomizer::rollCombatAbility(ObjectInstanceID actor, int percentageCha
 	if(percentageChance >= 100)
 		return true;
 
-	return combatAbilitySeed.at(actor).roll(percentageChance, 100, biasValueAbility);
+	int biasValue = gameInfo.getSettings().getInteger(EGameSettings::COMBAT_ABILITY_BIAS);
+
+	return combatAbilitySeed.at(actor).roll(percentageChance, 100, biasValue);
 }
 
 CreatureID GameRandomizer::rollCreature()

+ 37 - 9
lib/callback/GameRandomizer.h

@@ -18,7 +18,7 @@ enum class EGameSettings;
 
 class CGHeroInstance;
 
-class RandomizationBias
+class DLL_LINKAGE RandomizationBias
 {
 	int32_t accumulatedBias = 0;
 
@@ -26,6 +26,12 @@ public:
 	/// Performs coin flip with specified success chance
 	/// Returns true with probability successChance percents, and false with probability totalWeight-successChance percents
 	bool roll(vstd::RNG & generator, int successChance, int totalWeight, int biasValue);
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & accumulatedBias;
+	}
 };
 
 /// Biased randomizer that has following properties:
@@ -34,32 +40,44 @@ public:
 /// - at bias value between 1..99 similar guarantee is also provided, but with larger number of rolls
 /// No matter what bias is, statistical probability on large number of rolls remains the same
 /// Its goal is to simulate human expectations of random distributions and reduce frustration from "bad" rolls
-class RandomGeneratorWithBias
+class DLL_LINKAGE RandomGeneratorWithBias
 {
 	CRandomGenerator generator;
 	RandomizationBias bias;
 
 public:
-	explicit RandomGeneratorWithBias(int seed);
+	explicit RandomGeneratorWithBias(int seed = 0);
 	/// Performs coin flip with specified success chance
 	/// Returns true with probability successChance percents, and false with probability 100-successChance percents
 	bool roll(int successChance, int totalWeight, int biasValue);
+
+	template<typename Handler>
+	void serialize(Handler & h)
+	{
+		h & generator;
+		h & bias;
+	}
 };
 
 class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
 {
-	static constexpr int biasValueLuckMorale = 10;
-	static constexpr int biasValueAbility = 25;
-
 	struct HeroSkillRandomizer
 	{
-		explicit HeroSkillRandomizer(int seed)
+		explicit HeroSkillRandomizer(int seed = 0)
 			: seed(seed)
 		{}
 
 		CRandomGenerator seed;
 		int8_t magicSchoolCounter = 1;
 		int8_t wisdomCounter = 1;
+
+		template<typename Handler>
+		void serialize(Handler & h)
+		{
+			h & seed;
+			h & magicSchoolCounter;
+			h & wisdomCounter;
+		}
 	};
 
 	const IGameInfoCallback & gameInfo;
@@ -71,7 +89,6 @@ class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
 	std::map<ArtifactID, int> allocatedArtifacts;
 
 	std::map<HeroTypeID, HeroSkillRandomizer> heroSkillSeed;
-	std::map<PlayerColor, CRandomGenerator> playerTavern;
 
 	std::map<ObjectInstanceID, RandomGeneratorWithBias> goodMoraleSeed;
 	std::map<ObjectInstanceID, RandomGeneratorWithBias> badMoraleSeed;
@@ -79,7 +96,7 @@ class DLL_LINKAGE GameRandomizer final : public IGameRandomizer
 	std::map<ObjectInstanceID, RandomGeneratorWithBias> badLuckSeed;
 	std::map<ObjectInstanceID, RandomGeneratorWithBias> combatAbilitySeed;
 
-	bool rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWithBias> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings diceSize, EGameSettings diceWeights);
+	bool rollMoraleLuck(std::map<ObjectInstanceID, RandomGeneratorWithBias> & seeds, ObjectInstanceID actor, int moraleLuckValue, EGameSettings biasValue, EGameSettings diceSize, EGameSettings diceWeights);
 
 public:
 	explicit GameRandomizer(const IGameInfoCallback & gameInfo);
@@ -111,6 +128,17 @@ public:
 	void serialize(Handler & h)
 	{
 		h & globalRandomNumberGenerator;
+
+		if (h.hasFeature(Handler::Version::RANDOMIZATION_REWORK))
+		{
+			h & allocatedArtifacts;
+			h & heroSkillSeed;
+			h & goodMoraleSeed;
+			h & badMoraleSeed;
+			h & goodLuckSeed;
+			h & badLuckSeed;
+			h & combatAbilitySeed;
+		}
 	}
 };