AI: Allows better use of the tranport's capacity by filling it with all possible resources that should be going to the same destination. This was a capability that was present for years in FreeCol, but was lost in a refacturing someone did in 2008.

This commit is contained in:
Stian Grenborgen 2023-05-02 19:29:43 +02:00
parent 20b08818e9
commit 1315bdb5f3
1 changed files with 70 additions and 14 deletions

View File

@ -19,33 +19,41 @@
package net.sf.freecol.server.ai.mission;
import static net.sf.freecol.common.model.Constants.INFINITY;
import static net.sf.freecol.common.util.CollectionUtils.find;
import static net.sf.freecol.common.util.CollectionUtils.getPermutations;
import static net.sf.freecol.common.util.CollectionUtils.removeInPlace;
import static net.sf.freecol.common.util.CollectionUtils.transform;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.xml.stream.XMLStreamException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.sf.freecol.common.FreeColException;
import net.sf.freecol.common.io.FreeColXMLReader;
import net.sf.freecol.common.io.FreeColXMLWriter;
import net.sf.freecol.common.model.AbstractGoods;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.CombatModel;
import static net.sf.freecol.common.model.Constants.*;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Locatable;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.PathNode;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.pathfinding.CostDecider;
import net.sf.freecol.common.model.pathfinding.CostDeciders;
import net.sf.freecol.common.util.LogBuilder;
import static net.sf.freecol.common.util.CollectionUtils.*;
import net.sf.freecol.server.ai.AIColony;
import net.sf.freecol.server.ai.AIGoods;
import net.sf.freecol.server.ai.AIMain;
@ -55,8 +63,6 @@ import net.sf.freecol.server.ai.Cargo;
import net.sf.freecol.server.ai.EuropeanAIPlayer;
import net.sf.freecol.server.ai.TransportableAIObject;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Mission for transporting units and goods on a carrier.
@ -1062,6 +1068,8 @@ public final class TransportMission extends Mission {
optimizeCargoes(lb);
}
queueEasilyTransportedCargo(unit);
// Replenish cargoes up to available destination capacity
// and 50% above maximum cargoes (FIXME: longer?)
final EuropeanAIPlayer euaip = getEuropeanAIPlayer();
@ -1072,6 +1080,8 @@ public final class TransportMission extends Mission {
if (!queueCargo(cargo, false, lb)) break;
euaip.claimTransportable(cargo.getTransportable());
}
queueEasilyTransportedCargo(unit);
}
/**
@ -1165,8 +1175,7 @@ public final class TransportMission extends Mission {
final EuropeanAIPlayer euaip = getEuropeanAIPlayer();
Cargo bestDirect = null, bestFallback = null;
float bestDirectValue = 0.0f, bestFallbackValue = 0.0f;
final List<Cargo> ts = tCopy();
for (TransportableAIObject t : euaip.getUrgentTransportables()) {
for (TransportableAIObject t : euaip.getTransportables()) {
if (t.isDisposed() || !t.carriableBy(carrier)) continue;
Cargo cargo;
try {
@ -1177,13 +1186,6 @@ public final class TransportMission extends Mission {
if (cargo == null) continue;
float value = t.getTransportPriority() / (cargo.getTurns() + 1.0f);
if (t.getTransportDestination() != null
&& t.getTransportDestination().equals(target)
&& cargo.canQueueAt(carrier, 0, ts)) {
// Prioritize transports with the same destination.
value *= 10000;
}
if (cargo.isFallback()) {
if (bestFallbackValue < value) {
bestFallbackValue = value;
@ -1200,6 +1202,60 @@ public final class TransportMission extends Mission {
: (bestFallback != null) ? bestFallback
: null;
}
/**
* Queues extra transportables at the carrier's current location
* that shares a destination with cargo already on the transport list.
*
* These transportables can be added to the transport list without
* adding extra stops.
*
* @param carrier The carrier.
*/
private void queueEasilyTransportedCargo(Unit carrier) {
final EuropeanAIPlayer euaip = getEuropeanAIPlayer();
final List<Cargo> ts = tCopy();
final Set<Location> existingDestinations = ts.stream()
.map(c -> c.getCarrierTarget())
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (TransportableAIObject t : euaip.getTransportables()) {
if (t.isDisposed() || !t.carriableBy(carrier)) {
continue;
}
if (t.getTransportSource() != carrier.getLocation()) {
continue;
}
if (!existingDestinations.contains(t.getTransportDestination())) {
continue;
}
Cargo cargo;
try {
cargo = Cargo.newCargo(t, carrier);
} catch (FreeColException fce) {
cargo = null;
}
if (cargo == null) {
continue;
}
final int spaceAvailable = carrier.getCargoCapacity() - carrier.getCargoSpaceTaken();
if (spaceAvailable < cargo.getNewSpace()) {
continue;
}
if (!t.joinTransport(carrier, null)) {
logger.warning("Failed to add cargo there should be room for.");
}
cargo.update();
if (!addCargo(cargo, 0, new LogBuilder(0))) {
logger.warning("Failed to add cargo already on the transport.");
}
euaip.claimTransportable(t);
}
}
/**
* Why would an TransportMission be invalid with the given unit?