Skip to content

Commit

Permalink
Add support for SpawnsAdjacentResource (#11234)
Browse files Browse the repository at this point in the history
Includes AI support.

Resource is spawned in a random adjacent passable land tile which doesn't have an existing resource. The tile is claimed if it is unowned. Resource will only spawn within another civ's borders if there is no other viable plot.
  • Loading branch information
KungCheops authored Aug 25, 2024
1 parent adb8dc1 commit 80d157f
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,9 @@ ALTER TABLE Improvements ADD COLUMN 'CoastMakesValid' BOOLEAN DEFAULT 0;
-- Improvements can generate vision for builder x tiles away (radially)
ALTER TABLE Improvements ADD COLUMN 'GrantsVisionXTiles' INTEGER DEFAULT 0;

-- Improvement spawns a resource in an adjacent tile on completion
ALTER TABLE Improvements ADD COLUMN 'SpawnsAdjacentResource' TEXT DEFAULT NULL;

-- New Goody Hut Additions
ALTER TABLE GoodyHuts ADD COLUMN 'Production' INTEGER DEFAULT 0;
ALTER TABLE GoodyHuts ADD COLUMN 'GoldenAge' INTEGER DEFAULT 0;
Expand Down
104 changes: 104 additions & 0 deletions CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3615,9 +3615,113 @@ pair<int,int> CvBuilderTaskingAI::ScorePlotBuild(CvPlot* pPlot, ImprovementTypes
const int iImprovementMaintenanceTimes100 = pkImprovementInfo ? pkImprovementInfo->GetGoldMaintenance() * (100 + m_pPlayer->GetImprovementGoldMaintenanceMod()) : 0;
iSecondaryScore -= iImprovementMaintenanceTimes100;

ResourceTypes eSpawnedResource = pkImprovementInfo ? pkImprovementInfo->SpawnsAdjacentResource() : NO_RESOURCE;

if (eSpawnedResource != NO_RESOURCE)
{
int iTileClaimChance = 0;
int iTileWorkableChance = 0;
if (eBuild != NO_BUILD)
{
iTileWorkableChance = GetResourceSpawnWorkableChance(pPlot, iTileClaimChance);
}
else
{
CvPlot* pSpawnedPlot = pPlot->GetSpawnedResourcePlot();
if (pSpawnedPlot && pSpawnedPlot->isPlayerCityRadius(m_pPlayer->GetID()))
iTileWorkableChance = 100;
}

if (iTileClaimChance > 0)
{
iSecondaryScore += 3 * iTileClaimChance;
}

if (iTileWorkableChance > 0)
{
for (int iI = 0; iI < NUM_YIELD_TYPES; iI++)
{
YieldTypes eYield = (YieldTypes)iI;

if (eYield > YIELD_CULTURE_LOCAL)
break;

int iYieldChange = GC.getResourceInfo(eSpawnedResource)->getYieldChange(eYield);

if (iYieldChange != 0)
{
int iYieldModifier = GetYieldBaseModifierTimes100(eYield);
int iCityYieldModifier = pOwningCity ? GetYieldCityModifierTimes100(pOwningCity, m_pPlayer, eYield) : 100;
iYieldScore += (iYieldChange * iTileWorkableChance * iYieldModifier * iCityYieldModifier) / 10000;
}
}
}
}

return make_pair(iYieldScore + iSecondaryScore, iPotentialScore);
}

int CvBuilderTaskingAI::GetResourceSpawnWorkableChance(CvPlot* pPlot, int& iTileClaimChance)
{
vector<OptionWithScore<CvPlot*>> sortedPlots;
int iBestCost = INT_MAX;
for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
{
CvPlot* pAdjacentPlot = plotDirection(pPlot->getX(), pPlot->getY(), (DirectionTypes)iI);
if (!pAdjacentPlot)
continue;

if (pAdjacentPlot->isCity() || !pAdjacentPlot->isValidMovePlot(m_pPlayer->GetID()) || pAdjacentPlot->isWater()
|| pAdjacentPlot->IsNaturalWonder() || pAdjacentPlot->isMountain() || pAdjacentPlot->getResourceType(m_pPlayer->getTeam()) != NO_RESOURCE)
continue;

if (pAdjacentPlot->getOwner() != m_pPlayer->GetID() && pAdjacentPlot->getOwner() != NO_PLAYER)
continue;

int iCost = pAdjacentPlot->getOwner() != m_pPlayer->GetID() && pAdjacentPlot->getOwner() != NO_PLAYER ? 1 : 0;

sortedPlots.push_back(OptionWithScore<CvPlot*>(pAdjacentPlot, -iCost));
if (iCost < iBestCost)
iBestCost = iCost;
}

// if iBestCost is > 0, we will spawn the resource in enemy territory, or not spawn anything
if (iBestCost > 0)
{
iTileClaimChance = 0;
return 0;
}

std::stable_sort(sortedPlots.begin(), sortedPlots.end());

int iWorkableSpawnPlots = 0;
int iNonWorkableSpawnPlots = 0;
int iClaimTiles = 0;
int iNonClaimTiles = 0;

for (vector<OptionWithScore<CvPlot*>>::const_iterator it = sortedPlots.begin(); it != sortedPlots.end(); ++it)
{
int iCost = -it->score;
if (iCost != iBestCost)
continue;

CvPlot* pAdjacentPlot = it->option;

if (pAdjacentPlot->isPlayerCityRadius(m_pPlayer->GetID()) && (pAdjacentPlot->getOwner() == NO_PLAYER || pAdjacentPlot->getOwner() == m_pPlayer->GetID()))
iWorkableSpawnPlots++;
else
iNonWorkableSpawnPlots++;
if (pAdjacentPlot->getOwner() == NO_PLAYER)
iClaimTiles++;
else
iNonClaimTiles++;
}

iTileClaimChance = (100 * iClaimTiles) / (iNonClaimTiles + iClaimTiles);

return (100 * iWorkableSpawnPlots) / (iNonWorkableSpawnPlots + iWorkableSpawnPlots);
}

BuildTypes CvBuilderTaskingAI::GetRepairBuild(void)
{
for(int i = 0; i < GC.getNumBuildInfos(); i++)
Expand Down
2 changes: 2 additions & 0 deletions CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class CvBuilderTaskingAI
bool IsShortcutRoutePlot(const CvPlot* pPlot) const;
bool IsStrategicRoutePlot(const CvPlot* pPlot) const;

int GetResourceSpawnWorkableChance(CvPlot* pPlot, int& iTileClaimChance);

//---------------------------------------PROTECTED MEMBER VARIABLES---------------------------------
protected:

Expand Down
12 changes: 11 additions & 1 deletion CvGameCoreDLL_Expansion2/CvImprovementClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ CvImprovementEntry::CvImprovementEntry(void):
m_ppiTechNoFreshWaterYieldChanges(NULL),
m_ppiTechFreshWaterYieldChanges(NULL),
m_ppiRouteYieldChanges(NULL),
m_paImprovementResource(NULL)
m_paImprovementResource(NULL),
m_eSpawnsAdjacentResource(NO_RESOURCE)
{
}

Expand Down Expand Up @@ -386,6 +387,9 @@ bool CvImprovementEntry::CacheResults(Database::Results& kResults, CvDatabaseUti
const char* szImprovementUpgrade = kResults.GetText("ImprovementUpgrade");
m_iImprovementUpgrade = GC.getInfoTypeForString(szImprovementUpgrade, true);

const char* szSpawnsAdjacentResource = kResults.GetText("SpawnsAdjacentResource");
m_eSpawnsAdjacentResource = (ResourceTypes)GC.getInfoTypeForString(szSpawnsAdjacentResource, true);

//Arrays
const char* szImprovementType = GetType();
const size_t lenImprovementType = strlen(szImprovementType);
Expand Down Expand Up @@ -1626,6 +1630,12 @@ bool CvImprovementEntry::IsConnectsResource(int i) const
return ConnectsAllResources();
}

// Spawns a resource in one of the available adjacent tiles (if possible)
ResourceTypes CvImprovementEntry::SpawnsAdjacentResource() const
{
return m_eSpawnsAdjacentResource;
}

/// the chance of the specified Resource appearing randomly when the Improvement is present with no current Resource
int CvImprovementEntry::GetImprovementResourceDiscoverRand(int i) const
{
Expand Down
4 changes: 4 additions & 0 deletions CvGameCoreDLL_Expansion2/CvImprovementClasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ class CvImprovementEntry: public CvBaseInfo
bool IsImprovementResourceTrade(int i) const;
bool IsConnectsResource(int i) const;

ResourceTypes SpawnsAdjacentResource() const;

int GetImprovementResourceDiscoverRand(int i) const;
int GetFlavorValue(int i) const;

Expand Down Expand Up @@ -368,6 +370,8 @@ class CvImprovementEntry: public CvBaseInfo
int** m_ppiRouteYieldChanges;

CvImprovementResourceInfo* m_paImprovementResource;

ResourceTypes m_eSpawnsAdjacentResource;
};

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Expand Down
123 changes: 123 additions & 0 deletions CvGameCoreDLL_Expansion2/CvPlot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ void CvPlot::reset()
m_owningCity.reset();
m_owningCityOverride.reset();

m_sSpawnedResourceX = -1;
m_sSpawnedResourceY = -1;

m_vExtraYields.clear();
m_vRivers.clear();

Expand Down Expand Up @@ -8568,6 +8571,57 @@ void CvPlot::setImprovementType(ImprovementTypes eNewValue, PlayerTypes eBuilder
GET_PLAYER(eBuilder).doInstantYield(INSTANT_YIELD_TYPE_IMPROVEMENT_BUILD, false, NO_GREATPERSON, NO_BUILDING, 0, false, NO_PLAYER, NULL, false, pTargetCity);
}
}

ResourceTypes eSpawnedResource = newImprovementEntry.SpawnsAdjacentResource();
if (eSpawnedResource != NO_RESOURCE)
{
CvPlot* pSpawnPlot = GetAdjacentResourceSpawnPlot(eBuilder);
if (pSpawnPlot)
{
pSpawnPlot->setResourceType(eSpawnedResource, 1);
SetSpawnedResourcePlot(pSpawnPlot);

if (pSpawnPlot->getOwner() == NO_PLAYER)
{
int iBestCityID = -1;
int iBestCityDistance = -1;
int iDistance = 0;
CvCity* pLoopCity = NULL;
int iLoop = 0;
for (pLoopCity = GET_PLAYER(eBuilder).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(eBuilder).nextCity(&iLoop))
{
CvPlot* pPlot = pLoopCity->plot();
if (pPlot)
{
iDistance = plotDistance(getX(), getY(), pLoopCity->getX(), pLoopCity->getY());
if (iBestCityDistance == -1 || iDistance < iBestCityDistance)
{
iBestCityID = pLoopCity->GetID();
iBestCityDistance = iDistance;
}
}
}
pSpawnPlot->setOwner(eBuilder, iBestCityID);
}
}
}
}

if (eOldImprovement != NO_IMPROVEMENT)
{
ResourceTypes eSpawnedResource = GC.getImprovementInfo(eOldImprovement)->SpawnsAdjacentResource();
if (eSpawnedResource != NO_RESOURCE)
{
CvPlot* pSpawnedPlot = GetSpawnedResourcePlot();
if (pSpawnedPlot)
{
pSpawnedPlot->setResourceType(NO_RESOURCE, 0);
CvImprovementEntry* pkOldImprovementInfo = GC.getImprovementInfo(eOldImprovement);
if (pkOldImprovementInfo && pkOldImprovementInfo->IsImprovementResourceMakesValid(eSpawnedResource))
pSpawnedPlot->setImprovementType(NO_IMPROVEMENT);
SetSpawnedResourcePlot(NULL);
}
}
}

// If we're removing an Improvement that hooked up a resource then we need to take away the bonus
Expand Down Expand Up @@ -8883,6 +8937,72 @@ void CvPlot::setImprovementType(ImprovementTypes eNewValue, PlayerTypes eBuilder
}
}

CvPlot* CvPlot::GetAdjacentResourceSpawnPlot(PlayerTypes ePlayer) const
{
vector<OptionWithScore<CvPlot*>> sortedPlots;

int iBestCost = INT_MAX;
int iPossibleSpawnPlots = 0;

for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
{
CvPlot* pAdjacentPlot = plotDirection(getX(), getY(), (DirectionTypes)iI);
if (!pAdjacentPlot)
continue;

if (pAdjacentPlot->isCity() || !pAdjacentPlot->isValidMovePlot(ePlayer) || pAdjacentPlot->isWater()
|| pAdjacentPlot->IsNaturalWonder() || pAdjacentPlot->isMountain() || pAdjacentPlot->getResourceType() != NO_RESOURCE)
continue;

if (pAdjacentPlot->getOwner() != ePlayer && pAdjacentPlot->getOwner() != NO_PLAYER)
continue;

int iCost = pAdjacentPlot->getOwner() != ePlayer && pAdjacentPlot->getOwner() != NO_PLAYER ? 1 : 0;

sortedPlots.push_back(OptionWithScore<CvPlot*>(pAdjacentPlot, -iCost));
if (iCost < iBestCost)
{
iBestCost = iCost;
iPossibleSpawnPlots = 1;
}
else if (iCost == iBestCost)
{
iPossibleSpawnPlots++;
}
}

if (sortedPlots.empty())
return NULL;

std::stable_sort(sortedPlots.begin(), sortedPlots.end());

int iRandomIndex = GC.getGame().urandLimitExclusive(iPossibleSpawnPlots, GET_PLAYER(getOwner()).GetPseudoRandomSeed().mix(GetPseudoRandomSeed()));

return sortedPlots[iRandomIndex].option;
}

void CvPlot::SetSpawnedResourcePlot(const CvPlot* pPlot)
{
if (pPlot)
{
m_sSpawnedResourceX = pPlot->getX();
m_sSpawnedResourceY = pPlot->getY();
}
else
{
m_sSpawnedResourceX = -1;
m_sSpawnedResourceY = -1;
}
}

CvPlot* CvPlot::GetSpawnedResourcePlot() const
{
if (m_sSpawnedResourceX != -1 && m_sSpawnedResourceY != -1)
return GC.getMap().plot(m_sSpawnedResourceX, m_sSpawnedResourceY);

return NULL;
}

// --------------------------------------------------------------------------------
bool CvPlot::IsImprovementEmbassy() const
{
Expand Down Expand Up @@ -13479,6 +13599,9 @@ void CvPlot::Serialize(Plot& plot, Visitor& visitor)
visitor(plot.m_kArchaeologyData);
visitor(plot.m_bIsTradeUnitRoute);
visitor(plot.m_iLastTurnBuildChanged);

visitor(plot.m_sSpawnedResourceX);
visitor(plot.m_sSpawnedResourceY);
}

// --------------------------------------------------------------------------------
Expand Down
7 changes: 7 additions & 0 deletions CvGameCoreDLL_Expansion2/CvPlot.h
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,10 @@ class CvPlot
void updateRiverCrossing(DirectionTypes eIndex);
void updateRiverCrossing();

CvPlot* GetAdjacentResourceSpawnPlot(PlayerTypes ePlayer) const;
void SetSpawnedResourcePlot(const CvPlot* pPlot);
CvPlot* GetSpawnedResourcePlot() const;

bool isRevealed(TeamTypes eTeam, bool bDebug) const
{
if(bDebug && GC.getGame().isDebugMode())
Expand Down Expand Up @@ -914,6 +918,9 @@ class CvPlot
char /*TerrainTypes*/ m_eTerrainType;
bool m_bIsCity;

short m_sSpawnedResourceX;
short m_sSpawnedResourceY;

This comment has been minimized.

Copy link
@azum4roll

azum4roll Sep 4, 2024

Collaborator

short isn't guaranteed to be 2 bytes. We should just use int16 here.

But does this really save any memory over a 4-byte CvPlot*? I was thinking of using uint8 initially, but I don't know if maps can be more than 255 tiles wide.

PlotBoolField m_bfRevealed;

FFastSmallFixedList<IDInfo, 4, true, c_eCiv5GameplayDLL > m_units;
Expand Down

0 comments on commit 80d157f

Please sign in to comment.