diff --git a/MekHQ/resources/mekhq/resources/Resupply.properties b/MekHQ/resources/mekhq/resources/Resupply.properties
index 54119a3a15..16c58f407c 100644
--- a/MekHQ/resources/mekhq/resources/Resupply.properties
+++ b/MekHQ/resources/mekhq/resources/Resupply.properties
@@ -67,8 +67,8 @@ usePlayerConvoyOptional.text=%s, our employer has a delivery ready for us but is
\
This enhanced delivery requires an estimated %s tons of cargo space across all\
\ convoys. However, as this is an estimate final tonnage may vary. We currently have a total of\
- \ %s available space across %s convoy%s. Damaged or partially vehicles are not\
- \ considered available.\
+ \ %s available space across %s convoy%s. Damaged or partially crewed vehicles are\
+ \ not considered available.\
\
Be aware that this can be a risky job and if we fail to defend any intercepted convoys all\
\ units and personnel will be lost.\
@@ -81,7 +81,7 @@ usePlayerConvoyForced.text=%s, our employer has a delivery ready for us, but we
\
This delivery requires an estimated %s tons of cargo space across all convoys. However,\
\ as this is an estimate final tonnage may vary. We currently have a total of %s available\
- \ space across %s convoy%s. Damaged or partially vehicles are not considered available.\
+ \ space across %s convoy%s. Damaged or partially crewed vehicles are not considered available.\
\
Be aware that this can be a risky job and if we fail to defend any intercepted convoys all\
\ units and personnel will be lost.\
diff --git a/MekHQ/src/mekhq/campaign/market/procurement/Procurement.java b/MekHQ/src/mekhq/campaign/market/procurement/Procurement.java
index 92533651e7..de97c58c16 100644
--- a/MekHQ/src/mekhq/campaign/market/procurement/Procurement.java
+++ b/MekHQ/src/mekhq/campaign/market/procurement/Procurement.java
@@ -112,8 +112,6 @@ public List makeProcurementChecks(List parts, boolean useHardExtinct
}
}
- logger.info(successfulParts);
-
return successfulParts;
}
diff --git a/MekHQ/src/mekhq/campaign/mission/Loot.java b/MekHQ/src/mekhq/campaign/mission/Loot.java
index 242025bcd9..8dd7a6efe7 100644
--- a/MekHQ/src/mekhq/campaign/mission/Loot.java
+++ b/MekHQ/src/mekhq/campaign/mission/Loot.java
@@ -33,8 +33,10 @@
import mekhq.campaign.finances.Money;
import mekhq.campaign.finances.enums.TransactionType;
import mekhq.campaign.mission.enums.ScenarioType;
+import mekhq.campaign.parts.Armor;
import mekhq.campaign.parts.Part;
import mekhq.campaign.parts.enums.PartQuality;
+import mekhq.campaign.parts.equipment.AmmoBin;
import mekhq.campaign.rating.IUnitRating;
import mekhq.campaign.unit.Unit;
import mekhq.utilities.MHQXMLUtility;
@@ -45,6 +47,8 @@
import java.util.*;
import static mekhq.campaign.mission.resupplyAndCaches.GenerateResupplyContents.RESUPPLY_MINIMUM_PART_WEIGHT;
+import static mekhq.campaign.mission.resupplyAndCaches.Resupply.RESUPPLY_AMMO_TONNAGE;
+import static mekhq.campaign.mission.resupplyAndCaches.Resupply.RESUPPLY_ARMOR_TONNAGE;
import static mekhq.utilities.ReportingUtilities.CLOSING_SPAN_TAG;
import static mekhq.utilities.ReportingUtilities.spanOpeningWithCustomColor;
@@ -214,6 +218,12 @@ public void getLoot(Campaign campaign, Scenario scenario,
double partWeight = part.getTonnage();
partWeight = partWeight == 0 ? RESUPPLY_MINIMUM_PART_WEIGHT : partWeight;
+ if (part instanceof AmmoBin) {
+ partWeight = RESUPPLY_AMMO_TONNAGE;
+ } else if (part instanceof Armor) {
+ partWeight = RESUPPLY_ARMOR_TONNAGE;
+ }
+
if (isResupply) {
if (cargo - partWeight < 0) {
abandonedParts.add("
- " + part.getName() + " (" + partWeight + " tons)");
diff --git a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/GenerateResupplyContents.java b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/GenerateResupplyContents.java
index 26ba01902f..13b1cbab33 100644
--- a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/GenerateResupplyContents.java
+++ b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/GenerateResupplyContents.java
@@ -76,10 +76,6 @@ public enum DropType {
* @param usePlayerConvoys Indicates whether player convoy cargo capacity should be applied.
*/
static void getResupplyContents(Resupply resupply, DropType dropType, boolean usePlayerConvoys) {
- // Ammo and Armor are delivered in batches of 5, so we need to make sure to multiply their
- // weight by five when picking these items.
- final int WEIGHT_MULTIPLIER = dropType == DropType.DROP_TYPE_PARTS ? 1 : 5;
-
double targetCargoTonnage = resupply.getTargetCargoTonnage();
if (usePlayerConvoys) {
final int targetCargoTonnagePlayerConvoy = resupply.getTargetCargoTonnagePlayerConvoy();
@@ -112,7 +108,8 @@ static void getResupplyContents(Resupply resupply, DropType dropType, boolean us
case DROP_TYPE_AMMO -> ammoBinPool;
};
- while ((availableSpace > 0) && (!relevantPartsPool.isEmpty())) {
+ double currentLoad = 0;
+ while ((currentLoad < availableSpace) && (!relevantPartsPool.isEmpty())) {
Part potentialPart = switch(dropType) {
case DROP_TYPE_PARTS -> getRandomDrop(partsPool, negotiatorSkill);
case DROP_TYPE_ARMOR -> getRandomDrop(armorPool, negotiatorSkill);
@@ -155,10 +152,16 @@ static void getResupplyContents(Resupply resupply, DropType dropType, boolean us
case DROP_TYPE_AMMO -> ammoBinPool.remove(potentialPart);
}
- double partWeight = potentialPart.getTonnage();
- partWeight = partWeight == 0 ? RESUPPLY_MINIMUM_PART_WEIGHT : partWeight;
+ // Ammo and Armor are delivered in batches of 5t,
+ // so we need to make sure we're treating them as 5t no matter their actual weight.
+ double partWeight = 5;
+
+ if (dropType == DropType.DROP_TYPE_PARTS) {
+ partWeight = potentialPart.getTonnage();
+ partWeight = partWeight == 0 ? RESUPPLY_MINIMUM_PART_WEIGHT : partWeight;
+ }
- availableSpace -= partWeight * WEIGHT_MULTIPLIER;
+ currentLoad += partWeight;
droppedItems.add(potentialPart);
}
}
diff --git a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/PerformResupply.java b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/PerformResupply.java
index 2bcff6920f..6db3ff71f9 100644
--- a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/PerformResupply.java
+++ b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/PerformResupply.java
@@ -45,13 +45,14 @@
import static mekhq.campaign.mission.enums.AtBMoraleLevel.CRITICAL;
import static mekhq.campaign.mission.enums.AtBMoraleLevel.DOMINATING;
-import static mekhq.campaign.mission.enums.AtBMoraleLevel.ROUTED;
import static mekhq.campaign.mission.enums.AtBMoraleLevel.STALEMATE;
import static mekhq.campaign.mission.resupplyAndCaches.GenerateResupplyContents.DropType.DROP_TYPE_AMMO;
import static mekhq.campaign.mission.resupplyAndCaches.GenerateResupplyContents.DropType.DROP_TYPE_ARMOR;
import static mekhq.campaign.mission.resupplyAndCaches.GenerateResupplyContents.DropType.DROP_TYPE_PARTS;
import static mekhq.campaign.mission.resupplyAndCaches.GenerateResupplyContents.RESUPPLY_MINIMUM_PART_WEIGHT;
import static mekhq.campaign.mission.resupplyAndCaches.GenerateResupplyContents.getResupplyContents;
+import static mekhq.campaign.mission.resupplyAndCaches.Resupply.RESUPPLY_AMMO_TONNAGE;
+import static mekhq.campaign.mission.resupplyAndCaches.Resupply.RESUPPLY_ARMOR_TONNAGE;
import static mekhq.campaign.mission.resupplyAndCaches.Resupply.ResupplyType.RESUPPLY_CONTRACT_END;
import static mekhq.campaign.mission.resupplyAndCaches.Resupply.ResupplyType.RESUPPLY_LOOT;
import static mekhq.campaign.mission.resupplyAndCaches.ResupplyUtilities.forceContainsMajorityVTOLForces;
@@ -167,7 +168,13 @@ public static void performResupply(Resupply resupply, AtBContract contract, int
double totalTonnage = 0;
for (Part part : resupply.getConvoyContents()) {
- totalTonnage += part.getTonnage() * (part instanceof Armor || part instanceof AmmoBin ? 5 : 1);
+ if (part instanceof AmmoBin) {
+ totalTonnage += RESUPPLY_AMMO_TONNAGE;
+ } else if (part instanceof Armor) {
+ totalTonnage += RESUPPLY_ARMOR_TONNAGE;
+ } else {
+ totalTonnage += part.getTonnage();
+ }
}
logger.info("totalTonnage: " + totalTonnage);
@@ -207,9 +214,9 @@ public static void makeDelivery(Resupply resupply, @Nullable List contents
for (Part part : contents) {
if (part instanceof AmmoBin) {
campaign.getQuartermaster().addAmmo(((AmmoBin) part).getType(),
- ((AmmoBin) part).getFullShots() * 5);
+ ((AmmoBin) part).getFullShots() * RESUPPLY_AMMO_TONNAGE);
} else if (part instanceof Armor) {
- int quantity = (int) Math.ceil(((Armor) part).getArmorPointsPerTon() * 5);
+ int quantity = (int) Math.ceil(((Armor) part).getArmorPointsPerTon() * RESUPPLY_ARMOR_TONNAGE);
((Armor) part).setAmount(quantity);
campaign.getWarehouse().addPart(part, true);
} else {
@@ -255,7 +262,6 @@ public static void makeSmugglerDelivery(Resupply resupply) {
public static void loadPlayerConvoys(Resupply resupply) {
// Ammo and Armor are delivered in batches of 5, so we need to make sure to multiply their
// weight by five when picking these items.
- final int WEIGHT_MULTIPLIER = 5;
final Campaign campaign = resupply.getCampaign();
final Map playerConvoys = resupply.getPlayerConvoys();
@@ -285,8 +291,10 @@ public static void loadPlayerConvoys(Resupply resupply) {
double tonnage = part.getTonnage();
tonnage = tonnage == 0 ? RESUPPLY_MINIMUM_PART_WEIGHT : tonnage;
- if (part instanceof AmmoBin || part instanceof Armor) {
- tonnage *= WEIGHT_MULTIPLIER;
+ if (part instanceof AmmoBin) {
+ tonnage = RESUPPLY_AMMO_TONNAGE;
+ } else if (part instanceof Armor) {
+ tonnage = RESUPPLY_ARMOR_TONNAGE;
}
if (cargoCapacity - tonnage >= 0) {
@@ -326,18 +334,14 @@ public static void processConvoy(Resupply resupply, List convoyContents, @
// First, we need to identify whether the convoy has been intercepted.
AtBMoraleLevel morale = contract.getMoraleLevel();
+ // There isn't any chance of an interception if the enemy is Routed, so early-exit
if (morale.isRouted()) {
completeSuccessfulDelivery(resupply, convoyContents);
+ return;
}
int interceptionChance = morale.ordinal();
- // There isn't any chance of an interception if the enemy is Routed, so early-exit
- if (interceptionChance == ROUTED.ordinal()) {
- completeSuccessfulDelivery(resupply, convoyContents);
- return;
- }
-
// This chance is modified by convoy weight, for player convoys this is easy - we just
// calculate the weight of all units in the convoy. For NPC convoys, we need to get a bit
// creative, as we have no way to determine their size prior to any interception scenario.
diff --git a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/Resupply.java b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/Resupply.java
index 0ab0c1dcd1..4e98dab774 100644
--- a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/Resupply.java
+++ b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/Resupply.java
@@ -21,6 +21,7 @@
import java.math.BigInteger;
import java.util.*;
+import static java.lang.Math.floor;
import static java.lang.Math.min;
import static java.lang.Math.round;
import static megamek.common.MiscType.F_SPONSON_TURRET;
@@ -65,6 +66,8 @@ public class Resupply {
private Money convoyContentsValueCalculated;
public static final int CARGO_MULTIPLIER = 4;
+ public static final int RESUPPLY_AMMO_TONNAGE = 1;
+ public static final int RESUPPLY_ARMOR_TONNAGE = 5;
private static final MMLogger logger = MMLogger.create(Resupply.class);
@@ -545,9 +548,11 @@ private Map collectParts() {
}
int dropWeight = part instanceof MissingPart ? 10 : 1;
+ dropWeight = (int) floor(dropWeight * getPartMultiplier(part));
+
PartDetails partDetails = new PartDetails(part, dropWeight);
- processedParts.merge(part.toString(), partDetails, (oldValue, newValue) -> {
+ processedParts.merge(getPartKey(part), partDetails, (oldValue, newValue) -> {
oldValue.setWeight(oldValue.getWeight() + newValue.getWeight());
return oldValue;
});
@@ -563,6 +568,31 @@ private Map collectParts() {
return processedParts;
}
+ /**
+ * Generates a key for the given part based on its name and tonnage.
+ *
+ * The key is a combination of the part's name and its tonnage, separated by a colon.
+ * For specific part types such as {@link AmmoBin} and {@link Armor}, the tonnage is
+ * always set to a set value, regardless of the actual tonnage.
+ *
+ * @param part The {@link Part} for which the key is generated. Must not be {@code null}.
+ * @return A unique key in the format {@code "partName:partTonnage"}, where
+ * {@code partName} is the name of the part and {@code partTonnage} is the
+ * tonnage of the part or a fixed value for {@link AmmoBin} and {@link Armor}.
+ */
+ private static String getPartKey(Part part) {
+ String partName = part.getName();
+ double partTonnage = part.getTonnage();
+
+ if (part instanceof AmmoBin) {
+ partTonnage = RESUPPLY_AMMO_TONNAGE;
+ } else if (part instanceof Armor) {
+ partTonnage = RESUPPLY_ARMOR_TONNAGE;
+ }
+
+ return partName + ':' + partTonnage;
+ }
+
/**
* Checks if a part is ineligible for inclusion in the resupply process. Ineligibility is
* determined based on exclusion lists, unit structure compatibility, and transporter checks.
@@ -648,28 +678,60 @@ private boolean checkTransporter(Part part) {
}
/**
- * Applies warehouse weight modifiers to the collected parts list by comparing the
- * in-campaign warehouse spare parts with the current list. Removes parts from the pool
- * if the warehouse already contains enough resources to offset demand.
+ * Adjusts the provided parts list by applying warehouse weight modifiers.
+ *
+ * This method compares the in-campaign warehouse's spare parts inventory with the given
+ * parts list and reduces the weight (quantity) of parts in the list based on the warehouse
+ * stock. If the warehouse contains enough resources to fully satisfy the demand for a part,
+ * the part is removed from the parts list.
+ *
+ * The adjustments are performed as follows:
+ *
+ * - For each part in the warehouse:
+ *
+ * - The weight of the part in the part list is reduced by the quantity available
+ * in the warehouse.
+ * - If the weight becomes zero or negative, the part is flagged for removal.
+ *
+ *
+ * - All flagged parts are then removed from the part list.
+ *
+ *
*
- * @param partsList A map of part names and their respective part details to modify.
+ * @param partsList A map containing part identifiers (keys) and their corresponding {@link PartDetails}.
+ * The map will be modified to reflect the warehouse adjustments.
*/
private void applyWarehouseWeightModifiers(Map partsList) {
- // Because of how AmmoBins work, we're always considering the campaign to have 0 rounds
- // of ammo in storage, we could avoid this, but I don't think it's necessary.
+ // Adjust based on the quantity in the warehouse
for (Part part : campaign.getWarehouse().getSpareParts()) {
- PartDetails targetPart = partsList.get(part.toString());
- if (targetPart != null) {
- int spareCount = part.getQuantity();
- double multiplier = getPartMultiplier(part);
-
- double targetPartCount = targetPart.getWeight() * multiplier;
- if ((targetPartCount - spareCount) < 1) {
- partsList.remove(part.toString());
- } else {
- targetPart.setWeight(min(1, targetPartCount - spareCount));
- }
+ int weight = part.getQuantity();
+
+ // We don't want empty AmmoStorage to reduce Resupply weighting
+ if (part instanceof AmmoStorage && (((AmmoStorage) part).getShots() == 0)) {
+ continue;
}
+
+ PartDetails partDetails = new PartDetails(part, weight);
+
+ partsList.merge(getPartKey(part), partDetails, (oldValue, newValue) -> {
+ oldValue.setWeight(oldValue.getWeight() - newValue.getWeight());
+ return oldValue;
+ });
+ }
+
+ // Remove any items that now have 0 (or negative) tickets left in the pool
+ List removalList = new ArrayList<>();
+ for (PartDetails partDetails : partsList.values()) {
+ Part part = partDetails.getPart();
+ double weight = partDetails.getWeight();
+
+ if (weight <= 0) {
+ removalList.add(getPartKey(part));
+ }
+ }
+
+ for (String removalKey : removalList) {
+ partsList.remove(removalKey);
}
}
diff --git a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/ResupplyUtilities.java b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/ResupplyUtilities.java
index ca0150aede..121e06313a 100644
--- a/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/ResupplyUtilities.java
+++ b/MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/ResupplyUtilities.java
@@ -32,9 +32,11 @@
import java.util.UUID;
import java.util.Vector;
-import static java.lang.Math.ceil;
import static java.lang.Math.floor;
+import static java.lang.Math.max;
import static mekhq.campaign.mission.resupplyAndCaches.Resupply.CARGO_MULTIPLIER;
+import static mekhq.campaign.mission.resupplyAndCaches.Resupply.RESUPPLY_AMMO_TONNAGE;
+import static mekhq.campaign.mission.resupplyAndCaches.Resupply.RESUPPLY_ARMOR_TONNAGE;
import static mekhq.campaign.mission.resupplyAndCaches.Resupply.calculateTargetCargoTonnage;
import static mekhq.campaign.personnel.enums.PersonnelStatus.KIA;
import static mekhq.utilities.EntityUtilities.getEntityFromUnitId;
@@ -143,7 +145,10 @@ private static void decideCrewMemberFate(Campaign campaign, Person person) {
*/
public static int estimateCargoRequirements(Campaign campaign, AtBContract contract) {
double targetTonnage = calculateTargetCargoTonnage(campaign, contract) * CARGO_MULTIPLIER;
- return (int) Math.ceil(targetTonnage);
+
+ // Armor and ammo are always delivered in blocks, so cargo will never be less than the sum
+ // of those blocks
+ return max(RESUPPLY_AMMO_TONNAGE + RESUPPLY_ARMOR_TONNAGE, (int) Math.ceil(targetTonnage));
}
/**
diff --git a/MekHQ/src/mekhq/gui/view/MissionViewPanel.java b/MekHQ/src/mekhq/gui/view/MissionViewPanel.java
index 75178e3d97..07bbfaeef0 100644
--- a/MekHQ/src/mekhq/gui/view/MissionViewPanel.java
+++ b/MekHQ/src/mekhq/gui/view/MissionViewPanel.java
@@ -1000,7 +1000,7 @@ public void mouseClicked(MouseEvent e) {
pnlStats.add(lblCargoRequirement, gridBagConstraints);
txtCargoRequirement.setName("txtCargoRequirement");
- txtCargoRequirement.setText(estimateCargoRequirements(campaign, contract) + "t");
+ txtCargoRequirement.setText(estimateCargoRequirements(campaign, contract) + "t+");
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = y++;