function removeObsoleteResources(iRemovedResourcePlots: number[], aGeneratedResources: ResourceType[]): number {
console.log("Removing old resources");
let aTypesRemoved: number[] = [];
let aCutResources: ResourceType[] = [];
let resourcesAvailable = ResourceBuilder.getResourceCounts(-1);
let countOnMap = 0;
let countRemoved = 0;
for (let i = 0; i < resourcesAvailable.length; ++i) {
if (resourcesAvailable[i] > 0) {
countOnMap++;
}
}
let countToAdd = aGeneratedResources.length;
console.log("Adding new resources: " + countToAdd);
console.log("Resources already on map: " + countOnMap);
let totalResourceToCut = (countOnMap + countToAdd) - countOnMap;
if (totalResourceToCut < 0) {
totalResourceToCut = 0;
}
console.log("Number of resources to cut: " + totalResourceToCut);
let resourceToCut = ResourceBuilder.getBestMapResourceCuts(aGeneratedResources, totalResourceToCut);
for (let iI: number = 0; iI < resourceToCut.length; ++iI) {
var resourceInfo = GameInfo.Resources.lookup(resourceToCut[iI]);
if (resourceInfo) {
aCutResources.push(resourceInfo.$index);
}
}
// Traverse the map
let iWidth = GameplayMap.getGridWidth();
let iHeight = GameplayMap.getGridHeight();
for (let iY = 0; iY < iHeight; iY++) {
for (let iX = 0; iX < iWidth; iX++) {
let iIndex = (iY * iWidth) + iX;
// Resource here?
let resource = GameplayMap.getResourceType(iX, iY);
if (resource != ResourceTypes.NO_RESOURCE) {
// Remove it if wrong Age
let removeResource = false;
if (aCutResources.find(x => x == resource)) {
removeResource = true;
}
if (!removeResource && !ResourceBuilder.isResourceValidForAge(resource, g_incomingAge)) {
removeResource = true;
}
if (removeResource) {
var resourceInfo = GameInfo.Resources.lookup(resource);
if (resourceInfo) {
countRemoved++;
removeRuralDistrict(iX, iY);
ResourceBuilder.setResourceType(iX, iY, ResourceTypes.NO_RESOURCE);
console.log("Removed resource: " + Locale.compose(resourceInfo.Name) + " at (" + iX + ", " + iY + ")");
iRemovedResourcePlots.push(iIndex);
placeRuralDistrict(iX, iY);
let resourceType = resourceInfo.$index;
if (!aTypesRemoved.find(x => x == resourceType)) {
aTypesRemoved.push(resourceType);
}
}
}
}
}
}
console.log("Removed total resource locations: " + countRemoved);
return aTypesRemoved.length;
}
function addNewResources(iRemovedResourcePlots: number[], iNumberToPlacePerResource: number, aGeneratedResources: ResourceType[]) {
console.log("Adding new resources");
// Count all resources on map
let iResourceCounts: number[] = ResourceBuilder.getResourceCounts(-1);
// Look for resources included in this Age that aren't present
let aResourceTypes: number[] = [];
for (let ridx: number = 0; ridx < aGeneratedResources.length; ++ridx)
{
var resourceInfo = GameInfo.Resources.lookup(aGeneratedResources[ridx]);
if (resourceInfo && resourceInfo.Tradeable) {
if (iResourceCounts[resourceInfo.$index] == 0) {
aResourceTypes.push(resourceInfo.$index);
}
}
}
// Place using scatter algorithm
console.log("Number to place per resource:" + iNumberToPlacePerResource);
let iTotalToPlace = iNumberToPlacePerResource * aResourceTypes.length;
let iTotalPerHemisphere = iTotalToPlace / 2;
console.log("Total to place:" + iTotalToPlace);
// Get a list of plots from a new Poisson map with a different seed from the first time we placed resources
let aPlacementPlots: number[] = [];
let seed = GameplayMap.getRandomSeed() * (1 + g_incomingAge);
let avgDistanceBetweenPoints = 3;
let normalizedRangeSmoothing = 2;
let poisson = TerrainBuilder.generatePoissonMap(seed, avgDistanceBetweenPoints, normalizedRangeSmoothing);
let iWidth = GameplayMap.getGridWidth();
let iHeight = GameplayMap.getGridHeight();
for (let iY = 0; iY < iHeight; iY++) {
for (let iX = 0; iX < iWidth; iX++) {
let index = iY * iWidth + iX;
if (poisson[index] >= 1) {
// Don't put resources under existing districts
let districtID = MapCities.getDistrict(iX, iY);
if (districtID == null) {
aPlacementPlots.push(index);
}
}
}
}
// Add additional plots for each place a resource was removed
iRemovedResourcePlots.forEach(index => {
if (index) {
if (!aPlacementPlots.find(x => x == index)) {
aPlacementPlots.push(index);
}
}
});
// Randomize all these plots
shuffle(aPlacementPlots);
// Place the new resources using your weighting algorithm until we either run out of plots or hit the projected total
let resourceHemisphere: number[] = new Array(GameInfo.Resources.length);
//TODO: support multiple hemispheres for more than 1 continent of distance lands on larger maps
let resourceRegionalCount: number[] = new Array(2);
resourceRegionalCount[0] = 0;
resourceRegionalCount[1] = 0;
let resourceRegionalTotal: number = 0;
let resourceWeight: number[] = new Array(GameInfo.Resources.length);
let resourceRunningWeight: number[] = new Array(GameInfo.Resources.length);
let resourcesPlacedCount: number[] = new Array(GameInfo.Resources.length);
let importantResourceRegionalCountHome: number[] = new Array(GameInfo.Resources.length);
let importantResourceRegionalCountDistant: number[] = new Array(GameInfo.Resources.length);
//Initial Resource data
for (var resourceIdx = 0; resourceIdx < GameInfo.Resources.length; resourceIdx++) {
resourceHemisphere[resourceIdx] = 0;
resourceWeight[resourceIdx] = 0;
resourceRunningWeight[resourceIdx] = 0;
resourcesPlacedCount[resourceIdx] = 0;
importantResourceRegionalCountHome[resourceIdx] = 0;
importantResourceRegionalCountDistant[resourceIdx] = 0;
}
let maxPerHemisphere = 0;
let resourceDistribution = GameInfo.Resource_Distribution.lookup(g_incomingAge);
if (resourceDistribution) {
maxPerHemisphere = resourceDistribution.ResourceTypeMaxPerHemisphere;
}
// Set resource weights/hemispheres
aResourceTypes.forEach(resourceType => {
if (resourceType) {
var resourceInfo = GameInfo.Resources[resourceType];
if (resourceInfo) {
resourceWeight[resourceInfo.$index] = resourceInfo.Weight;
if (resourceInfo.Hemispheres == 1) {
let iRoll = TerrainBuilder.getRandomNumber(2, "Hemisphere Scatter");
if (iRoll >= 1 && resourceRegionalCount[1] <= resourceRegionalTotal / 2) {
resourceHemisphere[resourceInfo.$index] == 1;
resourceRegionalCount[1]++;
}
else {
resourceRegionalCount[0]++;
}
resourceRegionalTotal++;
}
else {
resourceHemisphere[resourceInfo.$index] = 2;
}
}
}
});
let iNumPlaced: number = 0;
aPlacementPlots.forEach(index => {
if (index && iNumPlaced <= iTotalToPlace) {
let kLocation: PlotCoord = GameplayMap.getLocationFromIndex(index);
let hemisphere = GameplayMap.getHemisphere(kLocation.x);
if (resourceRegionalCount[hemisphere] < iTotalPerHemisphere) {
//Generate a list of valid resources at this plot
let resources: number[] = [];
aResourceTypes.forEach(resourceIdx => {
let assignedHempisphere = ResourceBuilder.getResourceHemisphere(resourceIdx);
if (assignedHempisphere > -1) {
if (assignedHempisphere == 2 || assignedHempisphere == hemisphere) {
if (ResourceBuilder.canHaveResource(kLocation.x, kLocation.y, resourceIdx)) {
//Try not to place adjacent to other resources. The new Poission map causes the old one to be ignored, which causes clumping.
let canPlaceResource = true;
for (let iDirection: DirectionTypes = 0; iDirection < DirectionTypes.NUM_DIRECTION_TYPES; iDirection++) {
let iIndex: number = GameplayMap.getIndexFromXY(kLocation.x, kLocation.y);
let iLocation: PlotCoord = GameplayMap.getLocationFromIndex(iIndex);
let iAdjacentX: number = GameplayMap.getAdjacentPlotLocation(iLocation, iDirection).x;
let iAdjacentY: number = GameplayMap.getAdjacentPlotLocation(iLocation, iDirection).y;
if (GameplayMap.getResourceType(iAdjacentX, iAdjacentY) != ResourceTypes.NO_RESOURCE) {
canPlaceResource = TerrainBuilder.getRandomNumber(4, "Resource Scatter") == 0;
break;
}
}
if (canPlaceResource) {
resources.push(resourceIdx);
}
}
}
}
});
//Select the highest weighted (ties are a coin flip) resource
if (resources.length > 0) {
let resourceChosen: ResourceType = ResourceTypes.NO_RESOURCE;
let resourceChosenIndex: number = 0
for (let iI = 0; iI < resources.length; iI++) {
if (resourceChosen == ResourceTypes.NO_RESOURCE) {
resourceChosen = resources[iI];
resourceChosenIndex = resources[iI];
}
else {
if (resourceRunningWeight[resources[iI]] > resourceRunningWeight[resourceChosenIndex]) {
resourceChosen = resources[iI];
resourceChosenIndex = resources[iI];
}
else if (resourceRunningWeight[resources[iI]] == resourceRunningWeight[resourceChosenIndex]) {
let iRoll = TerrainBuilder.getRandomNumber(2, "Resource Scatter");
if (iRoll >= 1) {
resourceChosen = resources[iI];
resourceChosenIndex = resources[iI];
}
}
}
}
if (hemisphere == 0 && importantResourceRegionalCountHome[resourceChosenIndex] < maxPerHemisphere || hemisphere == 1 && importantResourceRegionalCountDistant[resourceChosenIndex] < maxPerHemisphere) {
//Place the selected resource
if (resourceChosen != ResourceTypes.NO_RESOURCE) {
ResourceBuilder.setResourceType(kLocation.x, kLocation.y, resourceChosen);
resourceRunningWeight[resourceChosenIndex] -= resourceWeight[resourceChosenIndex];
let name: any = GameInfo.Resources[resourceChosenIndex].Name;
console.log("Placed " + Locale.compose(name) + " at (" + kLocation.x + ", " + kLocation.y + ")");
iNumPlaced++;
resourceRegionalCount[hemisphere]++;
if (hemisphere == 0) {
importantResourceRegionalCountHome[resourceChosenIndex]++;
} else {
importantResourceRegionalCountDistant[resourceChosenIndex]++;
}
resourcesPlacedCount[resourceChosenIndex]++;
removeRuralDistrict(kLocation.x, kLocation.y);
placeRuralDistrict(kLocation.x, kLocation.y);
}
else {
console.log("Resource Type Failure");
}
}
}
}
}
});
//If we weren't able to place some resources, go through and force place the remaining ones, removing exisitng unimportant resources so the map isn't flooded
let checkHomeHemisphereLP = true;
let checkDistantHemisphereLP = true;
for (let iY = 0; iY < iHeight; iY++) {
for (let iX = 0; iX < iWidth; iX++) {
// Don't put resources under existing districts
let districtID = MapCities.getDistrict(iX, iY);
let hemisphere = GameplayMap.getHemisphere(iX);
if (districtID == null) {
for (let i = 0; i < resourcesPlacedCount.length; ++i) {
let resourceToPlace = GameInfo.Resources.lookup(i);
if (resourceToPlace) {
let assignedHempisphere = ResourceBuilder.getResourceHemisphere(i);
if (assignedHempisphere > -1) {
if (assignedHempisphere != 2 && assignedHempisphere != hemisphere) {
continue;
}
if (hemisphere == 0 && importantResourceRegionalCountHome[i] < resourceToPlace.MinimumPerHemisphere || hemisphere == 1 && importantResourceRegionalCountDistant[i] < resourceToPlace.MinimumPerHemisphere) {
//Once LP class checks are complete, no need to do them anymore. These checks are for the age specific resource class, once met they are no longer important and don't need to be forced.
if (checkHomeHemisphereLP == true && hemisphere == 0) {
checkHomeHemisphereLP = ResourceBuilder.isResourceClassRequiredForLegacyPath(i, hemisphere);
if (!checkHomeHemisphereLP) {
continue;
}
}
else if (checkDistantHemisphereLP == true && hemisphere == 1) {
checkDistantHemisphereLP = ResourceBuilder.isResourceClassRequiredForLegacyPath(i, hemisphere);
if (!checkDistantHemisphereLP) {
continue;
}
}
if ((resourcesPlacedCount[i] > 0) && ResourceBuilder.isResourceRequiredForAge(i, hemisphere)) {
if (ResourceBuilder.canHaveResource(iX, iY, i)) {
//Try not to place adjacent to other resources. The new Poission map causes the old one to be ignored, which causes clumping.
let hasAdjResource = false;
for (let iDirection: DirectionTypes = 0; iDirection < DirectionTypes.NUM_DIRECTION_TYPES; iDirection++) {
let iIndex: number = GameplayMap.getIndexFromXY(iX, iY);
let iLocation: PlotCoord = GameplayMap.getLocationFromIndex(iIndex);
let iAdjacentX: number = GameplayMap.getAdjacentPlotLocation(iLocation, iDirection).x;
let iAdjacentY: number = GameplayMap.getAdjacentPlotLocation(iLocation, iDirection).y;
if (GameplayMap.getResourceType(iAdjacentX, iAdjacentY) != ResourceTypes.NO_RESOURCE) {
hasAdjResource = true;
break;
}
}
if (!hasAdjResource) {
ResourceBuilder.setResourceType(iX, iY, i);
let name: any = GameInfo.Resources.lookup(i)?.Name;
console.log("Force Placed " + Locale.compose(name) + " at (" + iX + ", " + iY + ")");
hemisphere == 0 ? importantResourceRegionalCountHome[i]++ : importantResourceRegionalCountDistant[i]++;
removeRuralDistrict(iX, iY);
placeRuralDistrict(iX, iY);
break;
}
}
}
}
}
}
}
}
}
}
}