1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #4533 from IvanSavenko/town_growth

Change logic of neutral towns garrisons to match H3
This commit is contained in:
Ivan Savenko 2024-08-31 13:52:36 +03:00 committed by GitHub
commit 71c7beb7a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 162 additions and 63 deletions

View File

@ -271,7 +271,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town)
: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("TOWNQVBK"), toScreen(position))
{
InfoAboutTown iah;
LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentTown()); //todo: should this be nearest hero?
LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentArmy()); //todo: should this be nearest hero?
OBJECT_CONSTRUCTION;
tooltip = std::make_shared<CTownTooltip>(Point(9, 10), iah);
@ -281,7 +281,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero)
: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("HEROQVBK"), toScreen(position))
{
InfoAboutHero iah;
LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentHero()); //todo: should this be nearest hero?
LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentArmy()); //todo: should this be nearest hero?
OBJECT_CONSTRUCTION;
tooltip = std::make_shared<CHeroTooltip>(Point(9, 10), iah);

View File

@ -225,6 +225,9 @@ TResources CGTownInstance::dailyIncome() const
}
}
if (!getOwner().isValidPlayer())
return ret;
const auto & playerSettings = cb->getPlayerSettings(getOwner());
ret.applyHandicap(playerSettings->handicap.percentIncome);
return ret;
@ -473,67 +476,50 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
}
}
initializeConfigurableBuildings(rand);
initializeNeutralTownGarrison(rand);
recreateBuildingsBonuses();
updateAppearance();
}
void CGTownInstance::initializeNeutralTownGarrison(vstd::RNG & rand)
{
struct RandomGuardsInfo{
int tier;
int chance;
int min;
int max;
};
constexpr std::array<RandomGuardsInfo, 4> randomGuards = {
RandomGuardsInfo{ 0, 33, 8, 15 },
RandomGuardsInfo{ 1, 33, 5, 7 },
RandomGuardsInfo{ 2, 20, 3, 5 },
RandomGuardsInfo{ 3, 14, 1, 3 },
};
// Only neutral towns may get initial garrison
if (getOwner().isValidPlayer())
return;
// Only towns with garrison not set in map editor may get initial garrison
// FIXME: H3 editor allow explicitly empty garrison, but vcmi loses this flag on load
if (stacksCount() > 0)
return;
for (auto const & guard : randomGuards)
{
if (rand.nextInt(99) >= guard.chance)
continue;
CreatureID guardID = getTown()->creatures[guard.tier].at(0);
int guardSize = rand.nextInt(guard.min, guard.max);
putStack(getFreeSlot(), new CStackInstance(guardID, guardSize));
}
}
void CGTownInstance::newTurn(vstd::RNG & rand) const
{
if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
{
if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
{
std::vector<SlotID> nativeCrits; //slots
for(const auto & elem : Slots())
{
if (elem.second->type->getFaction() == getFaction()) //native
{
nativeCrits.push_back(elem.first); //collect matching slots
}
}
if(!nativeCrits.empty())
{
SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand);
StackLocation sl(this, pos);
const CCreature *c = getCreature(pos);
if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available
{
cb->changeStackCount(sl, c->getGrowth());
}
else //upgrade
{
cb->changeStackType(sl, c->upgrades.begin()->toCreature());
}
}
if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack
{
int i = rand.nextInt(std::min((int)town->creatures.size(), cb->getDate(Date::MONTH) << 1) - 1);
if (!town->creatures[i].empty())
{
CreatureID c = town->creatures[i][0];
SlotID n;
TQuantity count = creatureGrowth(i);
if (!count) // no dwelling
count = VLC->creatures()->getById(c)->getGrowth();
{//no lower tiers or above current month
if ((n = getSlotFor(c)).validSlot())
{
StackLocation sl(this, n);
if (slotEmpty(n))
cb->insertNewStack(sl, c.toCreature(), count);
else //add to existing
cb->changeStackCount(sl, count);
}
}
}
}
}
}
for(const auto & building : rewardableBuildings)
building.second->newTurn(rand);
@ -545,12 +531,7 @@ void CGTownInstance::newTurn(vstd::RNG & rand) const
cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_SECOND, bonusValue.second - 500);
}
}
/*
int3 CGTownInstance::getSightCenter() const
{
return pos - int3(2,0,0);
}
*/
bool CGTownInstance::passableFor(PlayerColor color) const
{
if (!armedGarrison())//empty castle - anyone can visit

View File

@ -246,6 +246,7 @@ private:
int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<const CGObjectInstance* >& dwellings) const;
bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
void initializeConfigurableBuildings(vstd::RNG & rand);
void initializeNeutralTownGarrison(vstd::RNG & rand);
};
VCMI_LIB_NAMESPACE_END

View File

@ -27,6 +27,7 @@
#include "../../lib/mapObjects/IOwnableObject.h"
#include "../../lib/mapping/CMap.h"
#include "../../lib/networkPacks/PacksForClient.h"
#include "../../lib/networkPacks/StackLocation.h"
#include "../../lib/pathfinder/TurnInfo.h"
#include "../../lib/texts/CGeneralTextHandler.h"
@ -290,6 +291,110 @@ SetAvailableCreatures NewTurnProcessor::generateTownGrowth(const CGTownInstance
return sac;
}
void NewTurnProcessor::updateNeutralTownGarrison(const CGTownInstance * t, int currentWeek) const
{
assert(t);
assert(!t->getOwner().isValidPlayer());
constexpr int randomRollsCounts = 3; // H3 makes around 3 random rolls to make simple bell curve distribution
constexpr int upgradeChance = 5; // Chance for a unit to get an upgrade
constexpr int growthChanceFort = 80; // Chance for growth to occur in towns with fort built
constexpr int growthChanceVillage = 40; // Chance for growth to occur in towns without fort
const auto & takeFromAvailable = [this, t](CreatureID creatureID)
{
int tierToSubstract = -1;
for (int i = 0; i < t->getTown()->creatures.size(); ++i)
if (vstd::contains(t->getTown()->creatures[i], creatureID))
tierToSubstract = i;
if (tierToSubstract == -1)
return; // impossible?
int creaturesAvailable = t->creatures[tierToSubstract].first;
int creaturesRecruited = creatureID.toCreature()->getGrowth();
int creaturesLeft = std::max(0, creaturesAvailable - creaturesRecruited);
if (creaturesLeft != creaturesAvailable)
{
SetAvailableCreatures sac;
sac.tid = t->id;
sac.creatures = t->creatures;
sac.creatures[tierToSubstract].first = creaturesLeft;
gameHandler->sendAndApply(&sac);
}
};
int growthChance = t->hasFort() ? growthChanceFort : growthChanceVillage;
int growthRoll = gameHandler->getRandomGenerator().nextInt(0, 99);
if (growthRoll >= growthChance)
return;
int tierRoll = 0;
for(int i = 0; i < randomRollsCounts; ++i)
tierRoll += gameHandler->getRandomGenerator().nextInt(0, currentWeek);
// NOTE: determined by observing H3 games, might not match H3 100%
int tierToGrow = std::clamp(tierRoll / randomRollsCounts, 0, 6) + 1;
bool upgradeUnit = gameHandler->getRandomGenerator().nextInt(0, 99) < upgradeChance;
// Check if town garrison already has unit of specified tier
for(const auto & slot : t->Slots())
{
const auto * creature = slot.second->type;
if (creature->getFaction() != t->getFaction())
continue;
if (creature->getLevel() != tierToGrow)
continue;
StackLocation stackLocation(t, slot.first);
gameHandler->changeStackCount(stackLocation, creature->getGrowth(), false);
takeFromAvailable(creature->getGrowth());
if (upgradeUnit && !creature->upgrades.empty())
{
CreatureID upgraded = *RandomGeneratorUtil::nextItem(creature->upgrades, gameHandler->getRandomGenerator());
gameHandler->changeStackType(stackLocation, upgraded.toCreature());
}
else
gameHandler->changeStackType(stackLocation, creature);
return;
}
// No existing creatures in garrison, but we have a free slot we can use
SlotID freeSlotID = t->getFreeSlot();
if (freeSlotID.validSlot())
{
for (auto const & tierVector : t->getTown()->creatures)
{
CreatureID baseCreature = tierVector.at(0);
if (baseCreature.toEntity(VLC)->getLevel() != tierToGrow)
continue;
StackLocation stackLocation(t, freeSlotID);
if (upgradeUnit && !baseCreature.toCreature()->upgrades.empty())
{
CreatureID upgraded = *RandomGeneratorUtil::nextItem(baseCreature.toCreature()->upgrades, gameHandler->getRandomGenerator());
gameHandler->insertNewStack(stackLocation, upgraded.toCreature(), upgraded.toCreature()->getGrowth());
takeFromAvailable(upgraded.toCreature()->getGrowth());
}
else
{
gameHandler->insertNewStack(stackLocation, baseCreature.toCreature(), baseCreature.toCreature()->getGrowth());
takeFromAvailable(baseCreature.toCreature()->getGrowth());
}
return;
}
}
}
RumorState NewTurnProcessor::pickNewRumor()
{
RumorState newRumor;
@ -548,6 +653,7 @@ void NewTurnProcessor::onNewTurn()
{
NewTurn n = generateNewTurnPack();
bool firstTurn = !gameHandler->getDate(Date::DAY);
bool newWeek = gameHandler->getDate(Date::DAY_OF_WEEK) == 7; //day numbers are confusing, as day was not yet switched
bool newMonth = gameHandler->getDate(Date::DAY_OF_MONTH) == 28;
@ -560,6 +666,15 @@ void NewTurnProcessor::onNewTurn()
gameHandler->setPortalDwelling(t, true, (n.specialWeek == EWeekType::PLAGUE ? true : false)); //set creatures for Portal of Summoning
}
if (newWeek && !firstTurn)
{
for (CGTownInstance *t : gameHandler->gameState()->map->towns)
{
if (!t->getOwner().isValidPlayer())
updateNeutralTownGarrison(t, 1 + gameHandler->getDate(Date::DAY) / 7);
}
}
//spawn wandering monsters
if (newMonth && (n.specialWeek == EWeekType::DOUBLE_GROWTH || n.specialWeek == EWeekType::DEITYOFFIRE))
{

View File

@ -42,6 +42,8 @@ class NewTurnProcessor : boost::noncopyable
void handleTimeEvents(PlayerColor player);
void handleTownEvents(const CGTownInstance *town);
void updateNeutralTownGarrison(const CGTownInstance * t, int currentWeek) const;
public:
NewTurnProcessor(CGameHandler * gameHandler);