mirror of https://github.com/FreeCol/freecol.git
Creates calculator classes replacing the functionality previously in ColonyTile and Building.
* Production output can now reliably be tested without having built a Colony. This will be used by the new AI Colony Management code. * Fixes several differences/bugs that occured because of code duplication.
This commit is contained in:
parent
f59cc15566
commit
3bdda582bd
|
@ -19,19 +19,26 @@
|
|||
|
||||
package net.sf.freecol.common.model;
|
||||
|
||||
import static net.sf.freecol.common.util.CollectionUtils.any;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.concat;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.sum;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.toList;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.transform;
|
||||
import static net.sf.freecol.common.util.StringUtils.lastPart;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
|
||||
import net.sf.freecol.common.io.FreeColXMLReader;
|
||||
import net.sf.freecol.common.io.FreeColXMLWriter;
|
||||
import net.sf.freecol.common.option.GameOptions;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.*;
|
||||
import static net.sf.freecol.common.util.StringUtils.*;
|
||||
import net.sf.freecol.common.model.production.BuildingProductionCalculator;
|
||||
import net.sf.freecol.common.model.production.WorkerAssignment;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -135,33 +142,6 @@ public class Building extends WorkLocation
|
|||
|
||||
return eject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the production modifiers for the given type of goods and
|
||||
* unit type.
|
||||
*
|
||||
* We use UnitType.getModifiers but modify this according to the
|
||||
* competence factor of this building type. Note that we do not modify
|
||||
* *multiplicative* modifiers, as this would capture the master blacksmith
|
||||
* doubling.
|
||||
*
|
||||
* @param id The String identifier
|
||||
* @param turn The turn number of type {@link Turn}
|
||||
* @param unitType The optional {@code UnitType} to produce them.
|
||||
* @return A stream of the applicable modifiers.
|
||||
*/
|
||||
public Stream<Modifier> getCompetenceModifiers(String id,
|
||||
UnitType unitType, Turn turn) {
|
||||
final float competence = getCompetenceFactor();
|
||||
return (competence == 1.0f) // Floating comparison OK!
|
||||
? unitType.getModifiers(id, getType(), turn)
|
||||
: map(unitType.getModifiers(id, getType(), turn),
|
||||
m -> {
|
||||
return (m.getType() == Modifier.ModifierType.ADDITIVE)
|
||||
? Modifier.makeModifier(m).setValue(m.getValue() * competence)
|
||||
: m;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this building have a higher level?
|
||||
|
@ -222,18 +202,6 @@ public class Building extends WorkLocation
|
|||
return canBeWorked() && getType().canAdd(unitType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to extract a goods amount from a list of
|
||||
* available goods.
|
||||
*
|
||||
* @param type The {@code GoodsType} to extract the amount for.
|
||||
* @param available The list of available goods to query.
|
||||
* @return The goods amount, or zero if none found.
|
||||
*/
|
||||
private int getAvailable(GoodsType type, List<AbstractGoods> available) {
|
||||
return AbstractGoods.getCount(type, available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the production information for this building taking account
|
||||
* of the available input and output goods.
|
||||
|
@ -244,136 +212,14 @@ public class Building extends WorkLocation
|
|||
* @return The production information.
|
||||
* @see ProductionCache#update
|
||||
*/
|
||||
public ProductionInfo getAdjustedProductionInfo(List<AbstractGoods> inputs,
|
||||
List<AbstractGoods> outputs) {
|
||||
ProductionInfo result = new ProductionInfo();
|
||||
if (!hasOutputs()) return result;
|
||||
final Specification spec = getSpecification();
|
||||
public ProductionInfo getAdjustedProductionInfo(List<AbstractGoods> inputs, List<AbstractGoods> outputs) {
|
||||
final BuildingProductionCalculator pc = new BuildingProductionCalculator(getOwner(), getColony().getFeatureContainer(), getColony().getProductionBonus());
|
||||
final List<WorkerAssignment> workerAssignments = getUnits()
|
||||
.map(u -> new WorkerAssignment(u.getType(), getProductionType()))
|
||||
.collect(Collectors.toList());
|
||||
final Turn turn = getGame().getTurn();
|
||||
final boolean avoidOverflow
|
||||
= hasAbility(Ability.AVOID_EXCESS_PRODUCTION);
|
||||
final int capacity = getColony().getWarehouseCapacity();
|
||||
// Calculate two production ratios, the minimum (and actual)
|
||||
// possible multiplier between the nominal input and output
|
||||
// goods and the amount actually consumed and produced, and
|
||||
// the maximum possible ratio that would apply but for
|
||||
// circumstances such as limited input availability.
|
||||
double maximumRatio = 0.0, minimumRatio = Double.MAX_VALUE;
|
||||
|
||||
// First, calculate the nominal production ratios.
|
||||
if (canAutoProduce()) {
|
||||
// Autoproducers are special
|
||||
for (AbstractGoods output : transform(getOutputs(),
|
||||
AbstractGoods::isPositive)) {
|
||||
final GoodsType goodsType = output.getType();
|
||||
int available = getColony().getGoodsCount(goodsType);
|
||||
if (available >= capacity) {
|
||||
minimumRatio = maximumRatio = 0.0;
|
||||
} else {
|
||||
int divisor = (int)getType().apply(0f, turn,
|
||||
Modifier.BREEDING_DIVISOR);
|
||||
int factor = (int)getType().apply(0f, turn,
|
||||
Modifier.BREEDING_FACTOR);
|
||||
int production = (available < goodsType.getBreedingNumber()
|
||||
|| divisor <= 0) ? 0
|
||||
// Deliberate use of integer division
|
||||
: ((available - 1) / divisor + 1) * factor;
|
||||
double newRatio = (double)production / output.getAmount();
|
||||
minimumRatio = Math.min(minimumRatio, newRatio);
|
||||
maximumRatio = Math.max(maximumRatio, newRatio);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (AbstractGoods output : iterable(getOutputs())) {
|
||||
final GoodsType goodsType = output.getType();
|
||||
float production = sum(getUnits(),
|
||||
u -> getUnitProduction(u, goodsType));
|
||||
// Unattended production always applies for buildings!
|
||||
production += getBaseProduction(null, goodsType, null);
|
||||
production = applyModifiers(production, turn,
|
||||
getProductionModifiers(goodsType, null));
|
||||
production = (int)Math.floor(production);
|
||||
// Beware! If we ever unify this code with ColonyTile,
|
||||
// ColonyTiles have outputs with zero amount.
|
||||
double newRatio = production / output.getAmount();
|
||||
minimumRatio = Math.min(minimumRatio, newRatio);
|
||||
maximumRatio = Math.max(maximumRatio, newRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// Then reduce the minimum ratio if some input is in short supply.
|
||||
for (AbstractGoods input : iterable(getInputs())) {
|
||||
long required = (long)Math.floor(input.getAmount() * minimumRatio);
|
||||
long available = getAvailable(input.getType(), inputs);
|
||||
// Do not allow auto-production to go negative.
|
||||
if (canAutoProduce()) available = Math.max(0, available);
|
||||
// Experts in factory level buildings may produce a
|
||||
// certain amount of goods even when no input is available.
|
||||
// Factories have the EXPERTS_USE_CONNECTIONS ability.
|
||||
long minimumGoodsInput;
|
||||
if (available < required
|
||||
&& hasAbility(Ability.EXPERTS_USE_CONNECTIONS)
|
||||
&& spec.getBoolean(GameOptions.EXPERTS_HAVE_CONNECTIONS)
|
||||
&& ((minimumGoodsInput = getType().getExpertConnectionProduction()
|
||||
* count(getUnits(),
|
||||
matchKey(getExpertUnitType(), Unit::getType)))
|
||||
> available)) {
|
||||
available = minimumGoodsInput;
|
||||
}
|
||||
// Scale production by limitations on availability.
|
||||
if (available < required) {
|
||||
minimumRatio *= (double)available / required;
|
||||
//maximumRatio = Math.max(maximumRatio, minimumRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether there is space enough to store the goods
|
||||
// produced in order to avoid excess production.
|
||||
if (avoidOverflow) {
|
||||
for (AbstractGoods output : iterable(getOutputs())) {
|
||||
double production = output.getAmount() * minimumRatio;
|
||||
if (production <= 0) continue;
|
||||
double headroom = (double)capacity
|
||||
- getAvailable(output.getType(), outputs);
|
||||
// Clamp production at warehouse capacity
|
||||
if (production > headroom) {
|
||||
minimumRatio = Math.min(minimumRatio,
|
||||
headroom / output.getAmount());
|
||||
}
|
||||
production = output.getAmount() * maximumRatio;
|
||||
if (production > headroom) {
|
||||
maximumRatio = Math.min(maximumRatio,
|
||||
headroom / output.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (AbstractGoods input : iterable(getInputs())) {
|
||||
GoodsType type = input.getType();
|
||||
// maximize consumption
|
||||
int consumption = (int)Math.floor(input.getAmount()
|
||||
* minimumRatio + EPSILON);
|
||||
int maximumConsumption = (int)Math.floor(input.getAmount()
|
||||
* maximumRatio);
|
||||
result.addConsumption(new AbstractGoods(type, consumption));
|
||||
if (consumption < maximumConsumption) {
|
||||
result.addMaximumConsumption(new AbstractGoods(type, maximumConsumption));
|
||||
}
|
||||
}
|
||||
for (AbstractGoods output : iterable(getOutputs())) {
|
||||
GoodsType type = output.getType();
|
||||
// minimize production, but add a magic little something
|
||||
// to counter rounding errors
|
||||
int production = (int)Math.floor(output.getAmount() * minimumRatio
|
||||
+ EPSILON);
|
||||
int maximumProduction = (int)Math.floor(output.getAmount()
|
||||
* maximumRatio);
|
||||
result.addProduction(new AbstractGoods(type, production));
|
||||
if (production < maximumProduction) {
|
||||
result.addMaximumProduction(new AbstractGoods(type, maximumProduction));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
final int warehouseCapacity = getColony().getWarehouseCapacity();
|
||||
return pc.getAdjustedProductionInfo(buildingType, turn, workerAssignments, inputs, outputs, warehouseCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -574,7 +420,7 @@ public class Building extends WorkLocation
|
|||
// With a unit, unit specific bonuses apply
|
||||
? concat(this.getModifiers(id, unitType, turn),
|
||||
colony.getProductionModifiers(goodsType, unitType, this),
|
||||
getCompetenceModifiers(id, unitType, turn),
|
||||
getType().getCompetenceModifiers(id, unitType, turn),
|
||||
owner.getModifiers(id, unitType, turn))
|
||||
// With no unit, only the building-specific bonuses
|
||||
: concat(colony.getModifiers(id, type, turn),
|
||||
|
|
|
@ -19,8 +19,18 @@
|
|||
|
||||
package net.sf.freecol.common.model;
|
||||
|
||||
import static net.sf.freecol.common.model.Constants.INFINITY;
|
||||
import static net.sf.freecol.common.model.Constants.UNDEFINED;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.concat;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.first;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.iterable;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.map;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.sum;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.transform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.swing.JList;
|
||||
import javax.swing.ListModel;
|
||||
|
@ -29,9 +39,7 @@ import javax.xml.stream.XMLStreamException;
|
|||
import net.sf.freecol.common.io.FreeColXMLReader;
|
||||
import net.sf.freecol.common.io.FreeColXMLWriter;
|
||||
import net.sf.freecol.common.model.Colony.NoBuildReason;
|
||||
import static net.sf.freecol.common.model.Constants.*;
|
||||
import net.sf.freecol.common.model.UnitLocation.NoAddReason;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.*;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -353,7 +361,7 @@ public final class BuildingType extends BuildableType
|
|||
amount = (int)apply(amount, null, goodsType.getId(), unitType);
|
||||
return (amount < 0) ? 0 : amount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -447,6 +455,32 @@ public final class BuildingType extends BuildableType
|
|||
return buildQueueLastPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the production modifiers for the given type of goods and
|
||||
* unit type.
|
||||
*
|
||||
* We use UnitType.getModifiers but modify this according to the
|
||||
* competence factor of this building type. Note that we do not modify
|
||||
* *multiplicative* modifiers, as this would capture the master blacksmith
|
||||
* doubling.
|
||||
*
|
||||
* @param id The String identifier
|
||||
* @param turn The turn number of type {@link Turn}
|
||||
* @param unitType The optional {@code UnitType} to produce them.
|
||||
* @return A stream of the applicable modifiers.
|
||||
*/
|
||||
public Stream<Modifier> getCompetenceModifiers(String id,
|
||||
UnitType unitType, Turn turn) {
|
||||
final float competence = getCompetenceFactor();
|
||||
return (competence == 1.0f) // Floating comparison OK!
|
||||
? unitType.getModifiers(id, getType(), turn)
|
||||
: map(unitType.getModifiers(id, getType(), turn),
|
||||
m -> {
|
||||
return (m.getType() == Modifier.ModifierType.ADDITIVE)
|
||||
? Modifier.makeModifier(m).setValue(m.getValue() * competence)
|
||||
: m;
|
||||
});
|
||||
}
|
||||
|
||||
// Override FreeColObject
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ import javax.xml.stream.XMLStreamException;
|
|||
import net.sf.freecol.common.io.FreeColXMLReader;
|
||||
import net.sf.freecol.common.io.FreeColXMLWriter;
|
||||
import net.sf.freecol.common.model.Player.NoClaimReason;
|
||||
import net.sf.freecol.common.model.production.TileProductionCalculator;
|
||||
import net.sf.freecol.common.model.production.WorkerAssignment;
|
||||
import net.sf.freecol.common.option.GameOptions;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.*;
|
||||
|
||||
|
@ -157,32 +159,17 @@ public class ColonyTile extends WorkLocation {
|
|||
* @see ProductionCache#update
|
||||
*/
|
||||
public ProductionInfo getBasicProductionInfo() {
|
||||
final Colony colony = getColony();
|
||||
ProductionInfo pi = new ProductionInfo();
|
||||
if (isColonyCenterTile()) {
|
||||
forEach(getOutputs(), output -> {
|
||||
boolean onlyNaturalImprovements = getSpecification()
|
||||
.getBoolean(GameOptions.ONLY_NATURAL_IMPROVEMENTS)
|
||||
&& !output.getType().isFoodType();
|
||||
int potential = output.getAmount();
|
||||
if (workTile.getTileItemContainer() != null) {
|
||||
potential = workTile.getTileItemContainer()
|
||||
.getTotalBonusPotential(output.getType(), null,
|
||||
potential, onlyNaturalImprovements);
|
||||
}
|
||||
potential += Math.max(0, colony.getProductionBonus());
|
||||
AbstractGoods production
|
||||
= new AbstractGoods(output.getType(), potential);
|
||||
pi.addProduction(production);
|
||||
});
|
||||
} else {
|
||||
forEach(map(getOutputs(), AbstractGoods::getType),
|
||||
gt -> {
|
||||
int n = sum(getUnits(), u -> getUnitProduction(u, gt));
|
||||
if (n > 0) pi.addProduction(new AbstractGoods(gt, n));
|
||||
});
|
||||
}
|
||||
return pi;
|
||||
final TileProductionCalculator tpc = new TileProductionCalculator(getOwner(),
|
||||
colony.getProductionBonus());
|
||||
|
||||
final UnitType workerUnitType = getUnits().findFirst()
|
||||
.map(Unit::getType)
|
||||
.orElse(null);
|
||||
|
||||
return tpc.getBasicProductionInfo(workTile,
|
||||
getGame().getTurn(),
|
||||
new WorkerAssignment(workerUnitType, getProductionType()),
|
||||
isColonyCenterTile());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -456,30 +443,8 @@ public class ColonyTile extends WorkLocation {
|
|||
@Override
|
||||
public Stream<Modifier> getProductionModifiers(GoodsType goodsType,
|
||||
UnitType unitType) {
|
||||
if (!canProduce(goodsType, unitType)) return Stream.<Modifier>empty();
|
||||
|
||||
final Tile workTile = getWorkTile();
|
||||
final TileType type = workTile.getType();
|
||||
final String id = goodsType.getId();
|
||||
final Colony colony = getColony();
|
||||
final Player owner = colony.getOwner();
|
||||
final Turn turn = getGame().getTurn();
|
||||
return (unitType != null)
|
||||
// Unit modifiers apply
|
||||
? concat(workTile.getProductionModifiers(goodsType, unitType),
|
||||
colony.getProductionModifiers(goodsType, unitType, this),
|
||||
unitType.getModifiers(id, type, turn),
|
||||
((owner == null) ? null
|
||||
: owner.getModifiers(id, unitType, turn)))
|
||||
// Unattended only possible in center, colony modifiers apply
|
||||
: (isColonyCenterTile())
|
||||
? concat(workTile.getProductionModifiers(goodsType, null),
|
||||
colony.getProductionModifiers(goodsType, null, this),
|
||||
colony.getModifiers(id, null, turn),
|
||||
((owner == null) ? null
|
||||
: owner.getModifiers(id, type, turn)))
|
||||
// Otherwise impossible
|
||||
: Stream.<Modifier>empty();
|
||||
return new TileProductionCalculator(getOwner(), getColony().getProductionBonus())
|
||||
.getProductionModifiers(getGame().getTurn(), workTile, goodsType, unitType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,25 @@
|
|||
|
||||
package net.sf.freecol.common.model;
|
||||
|
||||
import static net.sf.freecol.common.model.Constants.INFINITY;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.all;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.any;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.cacheInt;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.cachingDoubleComparator;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.concat;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.count;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.find;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.flatten;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.forEachMapEntry;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.isNotNull;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.matchKey;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.max;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.maximize;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.none;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.sum;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.transform;
|
||||
import static net.sf.freecol.common.util.RandomUtils.randomShuffle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
@ -39,11 +58,11 @@ import javax.xml.stream.XMLStreamException;
|
|||
|
||||
import net.sf.freecol.common.io.FreeColXMLReader;
|
||||
import net.sf.freecol.common.io.FreeColXMLWriter;
|
||||
import static net.sf.freecol.common.model.Constants.*;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.*;
|
||||
import net.sf.freecol.common.model.Constants.IntegrityType;
|
||||
import net.sf.freecol.common.model.production.TileProductionCalculator;
|
||||
import net.sf.freecol.common.model.production.WorkerAssignment;
|
||||
import net.sf.freecol.common.util.LogBuilder;
|
||||
import net.sf.freecol.common.util.RandomChoice;
|
||||
import static net.sf.freecol.common.util.RandomUtils.*;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1712,12 +1731,23 @@ public final class Tile extends UnitLocation implements Named, Ownable {
|
|||
*/
|
||||
public int getPotentialProduction(GoodsType goodsType,
|
||||
UnitType unitType) {
|
||||
if (!canProduce(goodsType, unitType)) return 0;
|
||||
|
||||
int amount = getBaseProduction(null, goodsType, unitType);
|
||||
amount = (int)applyModifiers(amount, getGame().getTurn(),
|
||||
getProductionModifiers(goodsType, unitType));
|
||||
return (amount < 0) ? 0 : amount;
|
||||
final TileProductionCalculator tpc = new TileProductionCalculator(null, 0);
|
||||
final ProductionType productionType = ProductionType.getBestProductionType(goodsType,
|
||||
getType().getAvailableProductionTypes(unitType == null));
|
||||
|
||||
final boolean colonyCenterTile = (unitType == null);
|
||||
|
||||
final ProductionInfo pi = tpc.getBasicProductionInfo(this,
|
||||
getGame().getTurn(),
|
||||
new WorkerAssignment(unitType, productionType),
|
||||
colonyCenterTile);
|
||||
|
||||
return pi.getProduction().stream()
|
||||
.filter(a -> a.getType().equals(goodsType))
|
||||
.map(AbstractGoods::getAmount)
|
||||
.findFirst()
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1871,11 +1901,24 @@ public final class Tile extends UnitLocation implements Named, Ownable {
|
|||
*/
|
||||
public AbstractGoods getBestFoodProduction() {
|
||||
final Comparator<AbstractGoods> goodsComp
|
||||
= Comparator.comparingInt(ag ->
|
||||
getPotentialProduction(ag.getType(), null));
|
||||
return maximize(flatten(getType().getAvailableProductionTypes(true),
|
||||
ProductionType::getOutputs),
|
||||
AbstractGoods::isFoodType, goodsComp);
|
||||
= Comparator.comparingInt(ag ->
|
||||
getPotentialProduction(ag.getType(), null));
|
||||
return maximize(flatten(getType().getAvailableProductionTypes(true),
|
||||
ProductionType::getOutputs),
|
||||
AbstractGoods::isFoodType, goodsComp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best food type to produce here with an appropriate expert.,
|
||||
*
|
||||
* @return The {@code AbstractGoods} to produce.
|
||||
*/
|
||||
public AbstractGoods getMaximumPotentialFoodProductionWithExpert() {
|
||||
return getSpecification().getFoodGoodsTypeList().stream()
|
||||
.map(goodsType -> new AbstractGoods(goodsType,
|
||||
getMaximumPotential(goodsType, getSpecification().getExpertForProducing(goodsType))))
|
||||
.max(Comparator.comparingInt(AbstractGoods::getAmount))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -383,21 +383,62 @@ public final class TileImprovementType extends FreeColSpecObjectType {
|
|||
* @return The increase in production
|
||||
*/
|
||||
public int getImprovementValue(Tile tile, GoodsType goodsType) {
|
||||
final UnitType colonistType
|
||||
= getSpecification().getDefaultUnitType();
|
||||
final UnitType colonistType = getSpecification().getDefaultUnitType();
|
||||
return getImprovementValue(tile, goodsType, colonistType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the increase in production of the given GoodsType
|
||||
* this tile improvement type would yield at a specified tile.
|
||||
*
|
||||
* @param tile The {@code Tile} to be considered.
|
||||
* @param goodsType An optional preferred {@code GoodsType}.
|
||||
* @param unitType The unit type working on the tile.
|
||||
* @return The increase in production
|
||||
*/
|
||||
public int getImprovementValue(Tile tile, GoodsType goodsType, UnitType unitType) {
|
||||
int value = 0;
|
||||
if (goodsType.isFarmed()) {
|
||||
final int oldProduction = tile.getType()
|
||||
.getPotentialProduction(goodsType, colonistType);
|
||||
.getPotentialProduction(goodsType, unitType);
|
||||
TileType tt = getChange(tile.getType());
|
||||
if (tt == null) { // simple bonus
|
||||
int production = tile.getPotentialProduction(goodsType, colonistType);
|
||||
int production = tile.getPotentialProduction(goodsType, unitType);
|
||||
if (production > 0) {
|
||||
float chg = apply(production, null, goodsType.getId());
|
||||
value = (int)(chg - production);
|
||||
}
|
||||
} else { // tile type change
|
||||
int chg = tt.getPotentialProduction(goodsType, colonistType)
|
||||
int chg = tt.getPotentialProduction(goodsType, unitType)
|
||||
- oldProduction;
|
||||
value = chg;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the increase in production of the given GoodsType
|
||||
* this tile improvement type would yield at a specified tile.
|
||||
*
|
||||
* @param tile The {@code Tile} to be considered.
|
||||
* @param goodsType An optional preferred {@code GoodsType}.
|
||||
* @return The increase in production
|
||||
*/
|
||||
public int getImprovementValue(TileType tileType, GoodsType goodsType, UnitType unitType) {
|
||||
int value = 0;
|
||||
if (goodsType.isFarmed()) {
|
||||
final int oldProduction = tileType
|
||||
.getPotentialProduction(goodsType, unitType);
|
||||
TileType tt = getChange(tileType);
|
||||
if (tt == null) { // simple bonus
|
||||
int production = tileType.getPotentialProduction(goodsType, unitType);
|
||||
if (production > 0) {
|
||||
float chg = apply(production, null, goodsType.getId());
|
||||
value = (int)(chg - production);
|
||||
}
|
||||
} else { // tile type change
|
||||
int chg = tt.getPotentialProduction(goodsType, unitType)
|
||||
- oldProduction;
|
||||
value = chg;
|
||||
}
|
||||
|
|
|
@ -426,7 +426,15 @@ public final class TileType extends FreeColSpecObjectType
|
|||
UnitType unitType) {
|
||||
if (goodsType == null) return 0;
|
||||
int amount = getBaseProduction(null, goodsType, unitType);
|
||||
amount = (int)apply(amount, null, goodsType.getId(), unitType);
|
||||
if (unitType != null) {
|
||||
amount = (int) unitType.apply(amount, null, goodsType.getId(), unitType);
|
||||
} else {
|
||||
/*
|
||||
* XXX: The feature container is always null for TileType. What was
|
||||
* the desired behaviour here?
|
||||
*/
|
||||
amount = (int)apply(amount, null, goodsType.getId(), unitType);
|
||||
}
|
||||
return (amount < 0) ? 0 : amount;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
/**
|
||||
* Copyright (C) 2002-2022 The FreeCol Team
|
||||
*
|
||||
* This file is part of FreeCol.
|
||||
*
|
||||
* FreeCol is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FreeCol is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.sf.freecol.common.model.production;
|
||||
|
||||
import static net.sf.freecol.common.util.CollectionUtils.concat;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.count;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.find;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.isNotNull;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.map;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.matchKey;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.sum;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.transform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.sf.freecol.common.model.Ability;
|
||||
import net.sf.freecol.common.model.AbstractGoods;
|
||||
import net.sf.freecol.common.model.BuildingType;
|
||||
import net.sf.freecol.common.model.FeatureContainer;
|
||||
import net.sf.freecol.common.model.GoodsType;
|
||||
import net.sf.freecol.common.model.Modifier;
|
||||
import net.sf.freecol.common.model.Player;
|
||||
import net.sf.freecol.common.model.ProductionCache;
|
||||
import net.sf.freecol.common.model.ProductionInfo;
|
||||
import net.sf.freecol.common.model.ProductionType;
|
||||
import net.sf.freecol.common.model.Specification;
|
||||
import net.sf.freecol.common.model.Turn;
|
||||
import net.sf.freecol.common.model.UnitType;
|
||||
import net.sf.freecol.common.option.GameOptions;
|
||||
|
||||
/**
|
||||
* Calculates the production for a building of a given type.
|
||||
*/
|
||||
public class BuildingProductionCalculator {
|
||||
private final static double EPSILON = 0.0001;
|
||||
|
||||
|
||||
private Player owner;
|
||||
private FeatureContainer colonyFeatureContainer;
|
||||
private int colonyProductionBonus;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a calculator for the given owner and colony data.
|
||||
*
|
||||
* @param owner The {@code Player} owning the building.
|
||||
* @param colonyFeatureContainer The {@code FeatureContainer} for the colony where the
|
||||
* building is located. This is used for applying bonus to the production.
|
||||
* @param colonyProductionBonus The production bonus for the colony where the building
|
||||
* is located.
|
||||
*/
|
||||
public BuildingProductionCalculator(Player owner, FeatureContainer colonyFeatureContainer, int colonyProductionBonus) {
|
||||
this.owner = owner;
|
||||
this.colonyFeatureContainer = colonyFeatureContainer;
|
||||
this.colonyProductionBonus = colonyProductionBonus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the production information for a building taking account
|
||||
* of the available input and output goods.
|
||||
*
|
||||
* @param buildingType The type of building.
|
||||
* @param turn The current game turn.
|
||||
* @param workerAssignments A list of workers assigned to the building.
|
||||
* @param inputs The input goods available.
|
||||
* @param outputs The output goods already available in the colony,
|
||||
* necessary in order to avoid excess production.
|
||||
* @param warehouseCapacity The storage capacity of the settlement
|
||||
* producing the goods.
|
||||
* @return The production information.
|
||||
* @see ProductionCache#update
|
||||
*/
|
||||
public ProductionInfo getAdjustedProductionInfo(
|
||||
BuildingType buildingType,
|
||||
Turn turn,
|
||||
List<WorkerAssignment> workerAssignments,
|
||||
List<AbstractGoods> inputs,
|
||||
List<AbstractGoods> outputs,
|
||||
int warehouseCapacity) {
|
||||
ProductionInfo result = new ProductionInfo();
|
||||
|
||||
final List<AbstractGoods> buildingOutputs = getOutputs(buildingType, workerAssignments);
|
||||
if (buildingOutputs.isEmpty()) return result;
|
||||
|
||||
final List<AbstractGoods> buildingInputs = getInputs(buildingType, workerAssignments);
|
||||
|
||||
final Specification spec = buildingType.getSpecification();
|
||||
final boolean avoidOverflow = buildingType.hasAbility(Ability.AVOID_EXCESS_PRODUCTION);
|
||||
// Calculate two production ratios, the minimum (and actual)
|
||||
// possible multiplier between the nominal input and output
|
||||
// goods and the amount actually consumed and produced, and
|
||||
// the maximum possible ratio that would apply but for
|
||||
// circumstances such as limited input availability.
|
||||
double maximumRatio = 0.0, minimumRatio = Double.MAX_VALUE;
|
||||
|
||||
// First, calculate the nominal production ratios.
|
||||
if (buildingType.hasAbility(Ability.AUTO_PRODUCTION)) {
|
||||
// Autoproducers are special
|
||||
for (AbstractGoods output : transform(buildingOutputs.stream(),
|
||||
AbstractGoods::isPositive)) {
|
||||
final GoodsType goodsType = output.getType();
|
||||
//int available = colony.getGoodsCount(goodsType);
|
||||
int available = outputs.stream().filter(gt -> gt.getType().equals(goodsType)).findAny().map(AbstractGoods::getAmount).orElse(0);
|
||||
if (available >= warehouseCapacity) {
|
||||
minimumRatio = maximumRatio = 0.0;
|
||||
} else {
|
||||
int divisor = (int) buildingType.apply(0f, turn, Modifier.BREEDING_DIVISOR);
|
||||
int factor = (int) buildingType.apply(0f, turn, Modifier.BREEDING_FACTOR);
|
||||
int production = (available < goodsType.getBreedingNumber()
|
||||
|| divisor <= 0) ? 0
|
||||
// Deliberate use of integer division
|
||||
: ((available - 1) / divisor + 1) * factor;
|
||||
double newRatio = (double)production / output.getAmount();
|
||||
minimumRatio = Math.min(minimumRatio, newRatio);
|
||||
maximumRatio = Math.max(maximumRatio, newRatio);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (AbstractGoods output : buildingOutputs) {
|
||||
final GoodsType goodsType = output.getType();
|
||||
float production = determineProduction(buildingType, workerAssignments, turn, goodsType);
|
||||
|
||||
// Beware! If we ever unify this code with ColonyTile,
|
||||
// ColonyTiles have outputs with zero amount.
|
||||
double newRatio = production / output.getAmount();
|
||||
minimumRatio = Math.min(minimumRatio, newRatio);
|
||||
maximumRatio = Math.max(maximumRatio, newRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// Then reduce the minimum ratio if some input is in short supply.
|
||||
for (AbstractGoods input : buildingInputs) {
|
||||
long required = (long)Math.floor(input.getAmount() * minimumRatio);
|
||||
long available = getAvailable(input.getType(), inputs);
|
||||
// Do not allow auto-production to go negative.
|
||||
if (buildingType.hasAbility(Ability.AUTO_PRODUCTION)) available = Math.max(0, available);
|
||||
// Experts in factory level buildings may produce a
|
||||
// certain amount of goods even when no input is available.
|
||||
// Factories have the EXPERTS_USE_CONNECTIONS ability.
|
||||
long minimumGoodsInput;
|
||||
if (available < required
|
||||
&& buildingType.hasAbility(Ability.EXPERTS_USE_CONNECTIONS)
|
||||
&& spec.getBoolean(GameOptions.EXPERTS_HAVE_CONNECTIONS)
|
||||
&& ((minimumGoodsInput = buildingType.getExpertConnectionProduction()
|
||||
* count(workerAssignments.stream().map(WorkerAssignment::getUnitType),
|
||||
matchKey(getExpertUnitType(buildingType))))
|
||||
> available)) {
|
||||
available = minimumGoodsInput;
|
||||
}
|
||||
// Scale production by limitations on availability.
|
||||
if (available < required) {
|
||||
minimumRatio *= (double)available / required;
|
||||
//maximumRatio = Math.max(maximumRatio, minimumRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether there is space enough to store the goods
|
||||
// produced in order to avoid excess production.
|
||||
if (avoidOverflow) {
|
||||
for (AbstractGoods output : buildingOutputs) {
|
||||
double production = output.getAmount() * minimumRatio;
|
||||
if (production <= 0) continue;
|
||||
double headroom = (double)warehouseCapacity
|
||||
- getAvailable(output.getType(), outputs);
|
||||
// Clamp production at warehouse capacity
|
||||
if (production > headroom) {
|
||||
minimumRatio = Math.min(minimumRatio,
|
||||
headroom / output.getAmount());
|
||||
}
|
||||
production = output.getAmount() * maximumRatio;
|
||||
if (production > headroom) {
|
||||
maximumRatio = Math.min(maximumRatio,
|
||||
headroom / output.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (AbstractGoods input : buildingInputs) {
|
||||
GoodsType type = input.getType();
|
||||
// maximize consumption
|
||||
int consumption = (int)Math.floor(input.getAmount()
|
||||
* minimumRatio + EPSILON);
|
||||
int maximumConsumption = (int)Math.floor(input.getAmount()
|
||||
* maximumRatio);
|
||||
result.addConsumption(new AbstractGoods(type, consumption));
|
||||
if (consumption < maximumConsumption) {
|
||||
result.addMaximumConsumption(new AbstractGoods(type, maximumConsumption));
|
||||
}
|
||||
}
|
||||
for (AbstractGoods output : buildingOutputs) {
|
||||
GoodsType type = output.getType();
|
||||
// minimize production, but add a magic little something
|
||||
// to counter rounding errors
|
||||
int production = (int)Math.floor(output.getAmount() * minimumRatio
|
||||
+ EPSILON);
|
||||
int maximumProduction = (int)Math.floor(output.getAmount()
|
||||
* maximumRatio);
|
||||
result.addProduction(new AbstractGoods(type, production));
|
||||
if (production < maximumProduction) {
|
||||
result.addMaximumProduction(new AbstractGoods(type, maximumProduction));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unit type that is the expert for this work location
|
||||
* using its first output for which an expert type can be found.
|
||||
*
|
||||
* @return The expert {@code UnitType} or null if none found.
|
||||
*/
|
||||
public UnitType getExpertUnitType(BuildingType buildingType) {
|
||||
final Specification spec = buildingType.getSpecification();
|
||||
ProductionType pt = getBestProductionType(buildingType);
|
||||
return (pt == null) ? null
|
||||
: find(map(pt.getOutputs(),
|
||||
ag -> spec.getExpertForProducing(ag.getType())),
|
||||
isNotNull());
|
||||
}
|
||||
|
||||
private ProductionType getBestProductionType(BuildingType buildingType) {
|
||||
return ProductionType.getBestProductionType(null, buildingType.getAvailableProductionTypes(false));
|
||||
}
|
||||
|
||||
private List<AbstractGoods> getOutputs(BuildingType buildingType, List<WorkerAssignment> workerAssignments) {
|
||||
final List<AbstractGoods> unattendedOutputs = buildingType.getAvailableProductionTypes(true).stream()
|
||||
.map(pt -> pt.getOutputList())
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<AbstractGoods> workerOutputs = workerAssignments.stream()
|
||||
//.map(wa -> wa.getProductionType().getOutputList())
|
||||
.map(WorkerAssignment::getProductionType)
|
||||
/*
|
||||
* XXX: This code is needed when a production type have yet to be
|
||||
* chosen for the worker. But why are we calling this method
|
||||
* in that case?
|
||||
*/
|
||||
.filter(Objects::nonNull)
|
||||
.map(pt -> pt.getOutputList())
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<AbstractGoods> allOutputs = new ArrayList<>(unattendedOutputs);
|
||||
allOutputs.addAll(workerOutputs);
|
||||
|
||||
final Map<GoodsType, Integer> amounts = new HashMap<>();
|
||||
for (AbstractGoods ag : allOutputs) {
|
||||
if (amounts.get(ag.getType()) == null) {
|
||||
amounts.put(ag.getType(), 0);
|
||||
}
|
||||
amounts.put(ag.getType(), amounts.get(ag.getType()) + ag.getAmount());
|
||||
}
|
||||
|
||||
return amounts.entrySet().stream().map(e -> new AbstractGoods(e.getKey(), e.getValue())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<AbstractGoods> getInputs(BuildingType buildingType, List<WorkerAssignment> workerAssignments) {
|
||||
final List<AbstractGoods> unattendedInputs = buildingType.getAvailableProductionTypes(true).stream()
|
||||
.map(pt -> pt.getInputList())
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
final List<AbstractGoods> workerInputs = workerAssignments.stream()
|
||||
//.map(wa -> wa.getProductionType().getInputList())
|
||||
.map(WorkerAssignment::getProductionType)
|
||||
/*
|
||||
* XXX: This code is needed when a production type have yet to be
|
||||
* chosen for the worker. But why are we calling this method
|
||||
* in that case?
|
||||
*/
|
||||
.filter(Objects::nonNull)
|
||||
.map(pt -> pt.getInputList())
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<AbstractGoods> allInputs = new ArrayList<>(unattendedInputs);
|
||||
allInputs.addAll(workerInputs);
|
||||
|
||||
final Map<GoodsType, Integer> amounts = new HashMap<>();
|
||||
for (AbstractGoods ag : allInputs) {
|
||||
if (amounts.get(ag.getType()) == null) {
|
||||
amounts.put(ag.getType(), 0);
|
||||
}
|
||||
amounts.put(ag.getType(), amounts.get(ag.getType()) + ag.getAmount());
|
||||
}
|
||||
|
||||
return amounts.entrySet().stream().map(e -> new AbstractGoods(e.getKey(), e.getValue())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int determineProduction(BuildingType buildingType, List<WorkerAssignment> workerAssignments, final Turn turn, final GoodsType goodsType) {
|
||||
float production = sum(workerAssignments,
|
||||
wa -> getUnitProduction(turn, buildingType, wa, goodsType));
|
||||
// Unattended production always applies for buildings!
|
||||
production += getBaseProduction(buildingType, null, goodsType, null);
|
||||
production = FeatureContainer.applyModifiers(production, turn,
|
||||
getProductionModifiers(turn, buildingType, goodsType, null));
|
||||
return (int) Math.floor(production);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to extract a goods amount from a list of
|
||||
* available goods.
|
||||
*
|
||||
* @param type The {@code GoodsType} to extract the amount for.
|
||||
* @param available The list of available goods to query.
|
||||
* @return The goods amount, or zero if none found.
|
||||
*/
|
||||
private int getAvailable(GoodsType type, List<AbstractGoods> available) {
|
||||
return AbstractGoods.getCount(type, available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the productivity of a unit working in this work location,
|
||||
* considering *only* the contribution of the unit, exclusive of
|
||||
* that of the work location.
|
||||
*
|
||||
* @param turn The current game turn.
|
||||
* @param buildingType The type of building.
|
||||
* @param workerAssignment The worker assigned to the building.
|
||||
* @param goodsType The {@code GoodsType} to check the production of.
|
||||
* @return The maximum return from this unit.
|
||||
*/
|
||||
private int getUnitProduction(Turn turn, BuildingType buildingType, WorkerAssignment workerAssignment, GoodsType goodsType) {
|
||||
if (workerAssignment == null || workerAssignment.getProductionType().getOutputs().noneMatch(g -> goodsType.equals(g.getType()))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0,
|
||||
(int) FeatureContainer.applyModifiers(getBaseProduction(buildingType, workerAssignment.getProductionType(), goodsType, workerAssignment.getUnitType()),
|
||||
turn,
|
||||
getProductionModifiers(turn, buildingType, goodsType, workerAssignment.getUnitType())));
|
||||
}
|
||||
|
||||
private int getBaseProduction(BuildingType buildingType, ProductionType productionType, GoodsType goodsType, UnitType unitType) {
|
||||
return (buildingType == null) ? 0 : buildingType.getBaseProduction(productionType, goodsType, unitType);
|
||||
}
|
||||
|
||||
private Stream<Modifier> getProductionModifiers(Turn turn, BuildingType buildingType, GoodsType goodsType,
|
||||
UnitType unitType) {
|
||||
final String id = (goodsType == null) ? null : goodsType.getId();
|
||||
|
||||
return (unitType != null)
|
||||
// With a unit, unit specific bonuses apply
|
||||
? concat(buildingType.getModifiers(id, unitType, turn),
|
||||
ProductionUtils.getRebelProductionModifiers(colonyProductionBonus, goodsType, buildingType),
|
||||
buildingType.getCompetenceModifiers(id, unitType, turn),
|
||||
owner.getModifiers(id, unitType, turn))
|
||||
// With no unit, only the building-specific bonuses
|
||||
: concat(colonyFeatureContainer.getModifiers(id, buildingType, turn), // XXX: Can we simplify this?
|
||||
owner.getModifiers(id, buildingType, turn));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package net.sf.freecol.common.model.production;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.sf.freecol.common.model.BuildingType;
|
||||
import net.sf.freecol.common.model.GoodsType;
|
||||
import net.sf.freecol.common.model.Modifier;
|
||||
import net.sf.freecol.common.model.Specification;
|
||||
|
||||
public final class ProductionUtils {
|
||||
|
||||
private ProductionUtils() {}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current production {@code Modifier}, which is
|
||||
* generated from the current production bonus.
|
||||
*
|
||||
* @param colonyProductionBonus The production bonus in the colony.
|
||||
* @param goodsType The {@code GoodsType} to produce.
|
||||
* @param buildingType A {@code BuildingType} for getting a rebel factor. Use {@code null}
|
||||
* for a tile.
|
||||
* @return A stream of suitable {@code Modifier}s.
|
||||
*/
|
||||
public static Stream<Modifier> getRebelProductionModifiers(int colonyProductionBonus,
|
||||
GoodsType goodsType, BuildingType buildingType) {
|
||||
final float rebelFactor = (buildingType != null) ? buildingType.getRebelFactor() : 1.0F;
|
||||
if (colonyProductionBonus == 0) return Stream.<Modifier>empty();
|
||||
int bonus = (int)Math.floor(colonyProductionBonus * rebelFactor);
|
||||
Modifier mod = new Modifier(goodsType.getId(), bonus,
|
||||
Modifier.ModifierType.ADDITIVE,
|
||||
Specification.SOL_MODIFIER_SOURCE);
|
||||
mod.setModifierIndex(Modifier.COLONY_PRODUCTION_INDEX);
|
||||
return Stream.of(mod);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
* Copyright (C) 2002-2022 The FreeCol Team
|
||||
*
|
||||
* This file is part of FreeCol.
|
||||
*
|
||||
* FreeCol is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FreeCol is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.sf.freecol.common.model.production;
|
||||
|
||||
import static net.sf.freecol.common.util.CollectionUtils.concat;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.forEach;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.map;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.sf.freecol.common.model.AbstractGoods;
|
||||
import net.sf.freecol.common.model.FeatureContainer;
|
||||
import net.sf.freecol.common.model.GoodsType;
|
||||
import net.sf.freecol.common.model.Modifier;
|
||||
import net.sf.freecol.common.model.Player;
|
||||
import net.sf.freecol.common.model.ProductionCache;
|
||||
import net.sf.freecol.common.model.ProductionInfo;
|
||||
import net.sf.freecol.common.model.ProductionType;
|
||||
import net.sf.freecol.common.model.Tile;
|
||||
import net.sf.freecol.common.model.Turn;
|
||||
import net.sf.freecol.common.model.UnitType;
|
||||
import net.sf.freecol.common.option.GameOptions;
|
||||
|
||||
/**
|
||||
* Calculates the production for a tile.
|
||||
*/
|
||||
public class TileProductionCalculator {
|
||||
|
||||
private Player owner;
|
||||
private int colonyProductionBonus;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a calculator for the given owner and colony data.
|
||||
*
|
||||
* @param owner The {@code Player} owning the building.
|
||||
* @param colonyProductionBonus The production bonus for the colony where the building
|
||||
* is located.
|
||||
*/
|
||||
public TileProductionCalculator(Player owner, int colonyProductionBonus) {
|
||||
this.owner = owner;
|
||||
this.colonyProductionBonus = colonyProductionBonus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the basic production information for the colony tile,
|
||||
* ignoring any colony limits (which for now, should be
|
||||
* irrelevant).
|
||||
*
|
||||
* In the original game, the following special rules apply to
|
||||
* colony center tiles:
|
||||
* - All tile improvements contribute to the production of food
|
||||
* - Only natural tile improvements, such as rivers, contribute
|
||||
* to the production of other types of goods.
|
||||
* - Artificial tile improvements, such as plowing, are ignored.
|
||||
*
|
||||
* @param tile The {@code Tile} where the production is happening.
|
||||
* @param turn The current game turn.
|
||||
* @param workerAssignment If any, the worker assign to this tile.
|
||||
* @param colonyCenterTile If true, then the tile will autoproduce.
|
||||
* @return The raw production of this colony tile.
|
||||
* @see ProductionCache#update
|
||||
*/
|
||||
public ProductionInfo getBasicProductionInfo(Tile tile,
|
||||
Turn turn,
|
||||
WorkerAssignment workerAssignment,
|
||||
boolean colonyCenterTile) {
|
||||
ProductionInfo pi = new ProductionInfo();
|
||||
|
||||
if (workerAssignment.getProductionType() == null) {
|
||||
/*
|
||||
* XXX: It's silly that the production is calculated
|
||||
* before the productionType is set.
|
||||
*/
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
if (colonyCenterTile) {
|
||||
forEach(workerAssignment.getProductionType().getOutputs(), output -> {
|
||||
boolean onlyNaturalImprovements = tile.getSpecification()
|
||||
.getBoolean(GameOptions.ONLY_NATURAL_IMPROVEMENTS)
|
||||
&& !output.getType().isFoodType();
|
||||
int potential = output.getAmount();
|
||||
if (tile.getTileItemContainer() != null) {
|
||||
potential = tile.getTileItemContainer()
|
||||
.getTotalBonusPotential(output.getType(), null,
|
||||
potential, onlyNaturalImprovements);
|
||||
}
|
||||
potential += Math.max(0, colonyProductionBonus);
|
||||
AbstractGoods production
|
||||
= new AbstractGoods(output.getType(), potential);
|
||||
pi.addProduction(production);
|
||||
});
|
||||
} else {
|
||||
forEach(map(workerAssignment.getProductionType().getOutputs(), AbstractGoods::getType),
|
||||
gt -> {
|
||||
int n = getUnitProduction(turn, tile, workerAssignment, gt);
|
||||
if (n > 0) pi.addProduction(new AbstractGoods(gt, n));
|
||||
});
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the productivity of a unit working in this work location,
|
||||
* considering *only* the contribution of the unit, exclusive of
|
||||
* that of the work location.
|
||||
*
|
||||
* Used below, only public for the test suite.
|
||||
*
|
||||
* @param turn The current game turn.
|
||||
* @param tile The tile where the production is happening.
|
||||
* @param workerAssignment If any, the worker assigned to the {@code Tile}.
|
||||
* @param goodsType The {@code GoodsType} to check the production of.
|
||||
* @return The maximum return from this unit.
|
||||
*/
|
||||
public int getUnitProduction(Turn turn, Tile tile, WorkerAssignment workerAssignment, GoodsType goodsType) {
|
||||
if (workerAssignment == null
|
||||
|| workerAssignment.getProductionType().getOutputs().noneMatch(g -> goodsType.equals(g.getType()))
|
||||
|| workerAssignment.getUnitType() == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, (int) FeatureContainer.applyModifiers(
|
||||
getBaseProduction(tile, workerAssignment.getProductionType(), goodsType, workerAssignment.getUnitType()),
|
||||
turn,
|
||||
getProductionModifiers(turn, tile, goodsType, workerAssignment.getUnitType())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base production exclusive of any bonuses.
|
||||
*
|
||||
* @param tile The tile where the production is happening.
|
||||
* @param productionType An optional {@code ProductionType} to use,
|
||||
* if null the best available one is used.
|
||||
* @param goodsType The {@code GoodsType} to produce.
|
||||
* @param unitType An optional {@code UnitType} to use.
|
||||
* @return The base production due to tile type and resources.
|
||||
*/
|
||||
private int getBaseProduction(Tile tile, ProductionType productionType,
|
||||
GoodsType goodsType, UnitType unitType) {
|
||||
if (tile == null || goodsType == null || !goodsType.isFarmed()) {
|
||||
return 0;
|
||||
}
|
||||
final int amount = tile.getBaseProduction(productionType, goodsType, unitType);
|
||||
return (amount < 0) ? 0 : amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the production modifiers for the given type of goods and
|
||||
* unit type.
|
||||
*
|
||||
* @param goodsType The {@code GoodsType} to produce.
|
||||
* @param unitType The optional {@code UnitType} to produce them.
|
||||
* @return A stream of the applicable modifiers.
|
||||
*/
|
||||
public Stream<Modifier> getProductionModifiers(Turn turn, Tile tile, GoodsType goodsType, UnitType unitType) {
|
||||
if (unitType == null || !tile.canProduce(goodsType, unitType)) {
|
||||
return Stream.<Modifier>empty();
|
||||
}
|
||||
|
||||
return concat(tile.getProductionModifiers(goodsType, unitType),
|
||||
ProductionUtils.getRebelProductionModifiers(colonyProductionBonus, goodsType, null),
|
||||
unitType.getModifiers(goodsType.getId(), tile.getType(), turn),
|
||||
((owner == null) ? null
|
||||
: owner.getModifiers(goodsType.getId(), unitType, turn)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the production modifiers for the given type of goods on
|
||||
* the colony center tile.
|
||||
*
|
||||
* @param goodsType The {@code GoodsType} to produce.
|
||||
* @param unitType The optional {@code UnitType} to produce them.
|
||||
* @return A stream of the applicable modifiers.
|
||||
*/
|
||||
public Stream<Modifier> getCenterTileProductionModifiers(Turn turn, Tile tile, GoodsType goodsType) {
|
||||
if (!tile.canProduce(goodsType, null)) {
|
||||
return Stream.<Modifier>empty();
|
||||
}
|
||||
return concat(tile.getProductionModifiers(goodsType, null),
|
||||
ProductionUtils.getRebelProductionModifiers(colonyProductionBonus, goodsType, null),
|
||||
// This does not seem to influence center tile production, but was present in the old code.
|
||||
//colony.getModifiers(id, null, turn),
|
||||
((owner == null) ? null
|
||||
: owner.getModifiers(goodsType.getId(), tile.getType(), turn)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright (C) 2002-2022 The FreeCol Team
|
||||
*
|
||||
* This file is part of FreeCol.
|
||||
*
|
||||
* FreeCol is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FreeCol is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.sf.freecol.common.model.production;
|
||||
|
||||
import net.sf.freecol.common.model.ProductionType;
|
||||
import net.sf.freecol.common.model.UnitType;
|
||||
|
||||
public class WorkerAssignment {
|
||||
|
||||
private final UnitType unitType;
|
||||
private final ProductionType productionType;
|
||||
|
||||
|
||||
public WorkerAssignment(UnitType unitType, ProductionType productionType) {
|
||||
this.unitType = unitType;
|
||||
this.productionType = productionType;
|
||||
}
|
||||
|
||||
public UnitType getUnitType() {
|
||||
return unitType;
|
||||
}
|
||||
|
||||
public ProductionType getProductionType() {
|
||||
return productionType;
|
||||
}
|
||||
}
|
|
@ -19,10 +19,13 @@
|
|||
|
||||
package net.sf.freecol.common.model;
|
||||
|
||||
import static net.sf.freecol.common.util.CollectionUtils.any;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.count;
|
||||
import static net.sf.freecol.common.util.CollectionUtils.matchKeyEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.sf.freecol.common.util.CollectionUtils.*;
|
||||
import net.sf.freecol.util.test.FreeColTestCase;
|
||||
import net.sf.freecol.util.test.FreeColTestUtils;
|
||||
|
||||
|
@ -323,14 +326,24 @@ public class TileTest extends FreeColTestCase {
|
|||
tile2.getPotentialProduction(grain, null));
|
||||
assertEquals("Plains/grain max", 6,
|
||||
tile2.getMaximumPotential(grain, null));
|
||||
assertEquals("Plains/grain/colonist", 5,
|
||||
tile2.getPotentialProduction(grain, colonistType));
|
||||
assertEquals("Plains/grain/colonist max", 6,
|
||||
tile2.getMaximumPotential(grain, colonistType));
|
||||
assertEquals("Plains/grain/expertFarmer", 8,
|
||||
tile2.getPotentialProduction(grain, expertFarmerType));
|
||||
tile2.addResource(new Resource(game, tile2, grainResource));
|
||||
assertEquals("Plains+Resource/grain", 7,
|
||||
tile2.getPotentialProduction(grain, null));
|
||||
assertEquals("Plains+Resource/grain max", 8,
|
||||
tile2.getMaximumPotential(grain, null));
|
||||
assertEquals("Plains+Resource/grain/expertFarmer", 9,
|
||||
assertEquals("Plains+Resource/grain/colonist", 7,
|
||||
tile2.getPotentialProduction(grain, colonistType));
|
||||
assertEquals("Plains+Resource/grain/colonist max", 8,
|
||||
tile2.getMaximumPotential(grain, colonistType));
|
||||
assertEquals("Plains+Resource/grain/expertFarmer", 12,
|
||||
tile2.getPotentialProduction(grain, expertFarmerType));
|
||||
assertEquals("Plains+Resource/grain/expertFarmer max", 10,
|
||||
assertEquals("Plains+Resource/grain/expertFarmer max", 13,
|
||||
tile2.getMaximumPotential(grain, expertFarmerType));
|
||||
|
||||
Tile tile3 = new Tile(game, plainsForest, 1, 1);
|
||||
|
|
Loading…
Reference in New Issue