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:
commit
71c7beb7a5
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user