Skip to content

Commit

Permalink
Merge branch 'master' into resupply_dispatch
Browse files Browse the repository at this point in the history
# Conflicts:
#	MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/PerformResupply.java
  • Loading branch information
IllianiCBT committed Dec 20, 2024
2 parents e23c0c3 + 6758e40 commit 3d54a52
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 60 deletions.
4 changes: 1 addition & 3 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -4102,11 +4102,9 @@ private void processResupply(AtBContract contract) {
boolean isGuerrilla = contract.getContractType().isGuerrillaWarfare();

if (!isGuerrilla || Compute.d6(1) > 4) {
int dropCount = (int) round((double) contract.getRequiredLances() / 3);

ResupplyType resupplyType = isGuerrilla ? ResupplyType.RESUPPLY_SMUGGLER : ResupplyType.RESUPPLY_NORMAL;
Resupply resupply = new Resupply(this, contract, resupplyType);
performResupply(resupply, contract, dropCount);
performResupply(resupply, contract);
}
}

Expand Down
2 changes: 1 addition & 1 deletion MekHQ/src/mekhq/campaign/mission/AtBContract.java
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ public void checkEvents(Campaign campaign) {
if (doBonusRoll(campaign)) {
campaign.addReport("Bonus: Captured Supplies");
Resupply resupply = new Resupply(campaign, this, ResupplyType.RESUPPLY_LOOT);
performResupply(resupply, this, 1);
performResupply(resupply, this);
}

break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import megamek.codeUtilities.ObjectUtility;
import megamek.common.Compute;
import megamek.common.annotations.Nullable;
import megamek.logging.MMLogger;
import mekhq.campaign.Campaign;
import mekhq.campaign.finances.Money;
import mekhq.campaign.mission.AtBContract;
Expand All @@ -44,6 +45,8 @@
* item value constraints.
*/
public class GenerateResupplyContents {
private static final MMLogger logger = MMLogger.create(GenerateResupplyContents.class);

private static final Money HIGH_VALUE_ITEM = Money.of(250000);

/**
Expand All @@ -66,6 +69,10 @@ 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();
Expand All @@ -79,22 +86,25 @@ static void getResupplyContents(Resupply resupply, DropType dropType, boolean us

final int negotiatorSkill = resupply.getNegotiatorSkill();

List<Part> resupplyContents = resupply.getConvoyContents();

List<Part> droppedItems = new ArrayList<>();
double runningTotal = 0;

double targetValue = switch (dropType) {
double availableSpace = switch (dropType) {
case DROP_TYPE_PARTS -> targetCargoTonnage * resupply.getFocusParts();
case DROP_TYPE_ARMOR -> targetCargoTonnage * resupply.getFocusArmor();
case DROP_TYPE_AMMO -> targetCargoTonnage * resupply.getFocusAmmo();
};

if (targetValue == 0) {
if (availableSpace == 0) {
return;
}

while (runningTotal < targetValue) {
List<Part> relevantPartsPool = switch(dropType) {
case DROP_TYPE_PARTS -> partsPool;
case DROP_TYPE_ARMOR -> armorPool;
case DROP_TYPE_AMMO -> ammoBinPool;
};

while ((availableSpace > 0) && (!relevantPartsPool.isEmpty())) {
Part potentialPart = switch(dropType) {
case DROP_TYPE_PARTS -> getRandomDrop(partsPool, negotiatorSkill);
case DROP_TYPE_ARMOR -> getRandomDrop(armorPool, negotiatorSkill);
Expand All @@ -105,8 +115,9 @@ static void getResupplyContents(Resupply resupply, DropType dropType, boolean us
// Even if the pool isn't empty, it's highly unlikely we'll get a successful pull on
// future iterations, so we end generation early.
if (potentialPart == null) {
resupplyContents.addAll(droppedItems);
resupply.setConvoyContents(resupplyContents);
resupply.getConvoyContents().addAll(droppedItems);
calculateConvoyWorth(resupply);
logger.info("Encountered null part while getting resupply contents. Aborting early.");
return;
}

Expand Down Expand Up @@ -136,13 +147,15 @@ static void getResupplyContents(Resupply resupply, DropType dropType, boolean us
case DROP_TYPE_AMMO -> ammoBinPool.remove(potentialPart);
}

runningTotal += potentialPart.getTonnage();
droppedItems.add(potentialPart);
availableSpace -= potentialPart.getTonnage() * WEIGHT_MULTIPLIER;

if (availableSpace >= 0) {
droppedItems.add(potentialPart);
}
}
}

resupplyContents.addAll(droppedItems);
resupply.setConvoyContents(resupplyContents);
resupply.getConvoyContents().addAll(droppedItems);
calculateConvoyWorth(resupply);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ public class PerformResupply {

private static final MMLogger logger = MMLogger.create(PerformResupply.class);

/**
* Initiates the resupply process for a specified campaign and active contract.
*
* <p>This method provides a simplified entry point to the resupply workflow, using a default value
* of 1 for the supply drop count. It delegates to the overloaded method
* {@link #performResupply(Resupply, AtBContract, int)} for the main execution of the resupply
* process, encompassing supply generation, convoy interaction, and delivery confirmation.</p>
*
* <p>This entry point is typically used when the exact number of supply drops is not specified or
* defaults to a single drop per invocation.</p>
*
* @param resupply the {@link Resupply} instance containing information about the resupply operation,
* such as the supplies to be delivered, convoy setup, and context-specific rules.
* @param contract the {@link AtBContract} representing the current contract, which provides the
* operational context for the resupply, including permissions and restrictions.
*/
public static void performResupply(Resupply resupply, AtBContract contract) {
performResupply(resupply, contract, 1);
}

/**
* Executes the resupply process for a specified campaign, contract, and supply drop count.
* This method coordinates supply allocation, convoy interaction, potential for interception,
Expand Down Expand Up @@ -142,6 +162,16 @@ public static void performResupply(Resupply resupply, AtBContract contract, int
getResupplyContents(resupply, DROP_TYPE_PARTS, isUsePlayerConvoy);
}

resupply.setConvoyContents(resupply.getConvoyContents());

double totalTonnage = 0;
for (Part part : resupply.getConvoyContents()) {
totalTonnage += part.getTonnage() * (part instanceof Armor || part instanceof AmmoBin ? 5 : 1);
}

logger.info("totalTonnage: " + totalTonnage);


// This shouldn't occur, but we include it as insurance.
if (resupply.getConvoyContents().isEmpty()) {
campaign.addReport(String.format(resources.getString("convoyUnsuccessful.text"),
Expand Down Expand Up @@ -222,6 +252,9 @@ public static void makeSmugglerDelivery(Resupply resupply) {
* @param resupply the {@link Resupply} instance containing convoy and mission-specific data.
*/
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<Force, Double> playerConvoys = resupply.getPlayerConvoys();

Expand All @@ -235,10 +268,8 @@ public static void loadPlayerConvoys(Resupply resupply) {
sortedConvoys.add(entry.getKey());
}

// Sort the available parts according to weight
final List<Part> convoyContents = resupply.getConvoyContents();
convoyContents.sort((part1, part2) ->
Double.compare(part2.getTonnage(), part1.getTonnage()));
Collections.shuffle(convoyContents);

// Distribute parts across the convoys
for (Force convoy : sortedConvoys) {
Expand All @@ -249,43 +280,25 @@ public static void loadPlayerConvoys(Resupply resupply) {
Double cargoCapacity = playerConvoys.get(convoy);
List<Part> convoyItems = new ArrayList<>();

// It is technically possible to end up with one or more items that won't fit,
// we need an early break to avoid situations where we infinite loop due to a
// particularly large item.
while (!convoyContents.isEmpty() && cargoCapacity > 0) {
boolean partAdded = false; // Ensure at least one part is removed per iteration

// Iterate over parts to find what can fit
Iterator<Part> iterator = convoyContents.iterator();
while (iterator.hasNext()) {
Part part = iterator.next();
if (cargoCapacity - part.getTonnage() >= 0) {
convoyItems.add(part);
cargoCapacity -= part.getTonnage();
iterator.remove(); // Remove directly during iteration
partAdded = true;
}
for (Part part : convoyContents) {
double tonnage = part.getTonnage();

if (part instanceof AmmoBin || part instanceof Armor) {
tonnage *= WEIGHT_MULTIPLIER;
}

// Break the loop if no part was added in this iteration to avoid infinite looping
if (!partAdded) {
break;
if (cargoCapacity - tonnage >= 0) {
convoyItems.add(part);
cargoCapacity -= tonnage;
}
}

convoyContents.removeAll(convoyItems);

campaign.addReport(String.format(resources.getString("convoyDispatched.text"),
convoy.getName()));
processConvoy(resupply, convoyItems, convoy);
}

if (!convoyContents.isEmpty()) {
campaign.addReport(String.format(resources.getString("convoyInsufficientSize.text")));

for (Part part : convoyContents) {
campaign.addReport("- " + part.getName());
}
}
}

/**
Expand Down
13 changes: 12 additions & 1 deletion MekHQ/src/mekhq/campaign/mission/resupplyAndCaches/Resupply.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ public Resupply(Campaign campaign, AtBContract contract, ResupplyType resupplyTy
focusArmor = 0.25;
focusParts = 0.5;

buildPartsPools(collectParts());
calculateNegotiationSkill();
buildPartsPools(collectParts());
calculatePlayerConvoyValues();

convoyContents = new ArrayList<>();
Expand Down Expand Up @@ -566,6 +566,7 @@ private boolean isIneligiblePart(Part part, Unit unit) {
return checkExclusionList(part)
|| checkMekLocation(part, unit)
|| checkTankLocation(part)
|| checkMotiveSystem(part)
|| checkTransporter(part);
}

Expand All @@ -589,6 +590,16 @@ private boolean checkExclusionList(Part part) {
return false;
}

/**
* Checks if the given part is an instance of {@code MotiveSystem}.
*
* @param part the {@link Part} to be checked.
* @return {@code true} if the part is a {@link MotiveSystem}, {@code false} otherwise.
*/
private boolean checkMotiveSystem(Part part) {
return part instanceof MotiveSystem;
}

/**
* Checks if a part belonging to a 'Mek' unit is eligible for resupply, based on its location
* or whether the unit is considered extinct. For example, parts located in the center torso
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

import java.util.UUID;

import static java.lang.Math.round;
import static java.lang.Math.ceil;
import static mekhq.campaign.mission.resupplyAndCaches.Resupply.CARGO_MULTIPLIER;
import static mekhq.campaign.mission.resupplyAndCaches.Resupply.calculateTargetCargoTonnage;
import static mekhq.campaign.personnel.enums.PersonnelStatus.KIA;
Expand Down Expand Up @@ -128,22 +128,20 @@ private static void decideCrewMemberFate(Campaign campaign, Person person) {

/**
* Estimates the total cargo requirements for a resupply operation based on the campaign
* and the associated contract details.
* and the associated contract details. These cargo requirements are specifically modified for
* player-owned convoys.
*
* <p>This estimation is calculated as follows:
* <ul>
* <li>Determines the target cargo tonnage using the {@link Campaign} and {@link AtBContract} data.</li>
* <li>Applies a cargo multiplier defined in {@link Resupply#CARGO_MULTIPLIER}.</li>
* <li>Accounts for the required number of supply drops, assuming one drop per three lances.</li>
* </ul>
*
* @param campaign the {@link Campaign} instance to calculate cargo requirements for.
* @param contract the {@link AtBContract} defining the parameters of the mission.
* @return the estimated cargo requirement in tons.
*/
public static int estimateCargoRequirements(Campaign campaign, AtBContract contract) {
final double dropCount = (double) contract.getRequiredLances() / 3;

return (int) round(calculateTargetCargoTonnage(campaign, contract) * CARGO_MULTIPLIER * dropCount);
return (int) ceil(calculateTargetCargoTonnage(campaign, contract) * CARGO_MULTIPLIER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ public static void dialogInterception(Resupply resupply, @Nullable Force targetC

String speakerName;
if (speaker != null) {
speakerName = speaker.getFullTitle();
speakerName = speaker.getFullTitle() + " - " + targetConvoy.getName();
} else {
if (targetConvoy == null) {
speakerName = String.format(resources.getString("dialogBorderConvoySpeakerDefault.text"),
contract.getEmployerName(campaign.getGameYear()));
} else {
speakerName = campaign.getName();
speakerName = targetConvoy.getName();
}
}

Expand Down
10 changes: 10 additions & 0 deletions MekHQ/src/mekhq/gui/dialog/resupplyAndCaches/DialogItinerary.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import mekhq.campaign.mission.enums.AtBMoraleLevel;
import mekhq.campaign.mission.resupplyAndCaches.Resupply;
import mekhq.campaign.mission.resupplyAndCaches.Resupply.ResupplyType;
import mekhq.campaign.parts.Part;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.enums.PersonnelRole;

Expand Down Expand Up @@ -168,6 +169,15 @@ public static void itineraryDialog(Resupply resupply) {
} else {
if (resupply.getUsePlayerConvoy()) {
loadPlayerConvoys(resupply);

final List<Part> convoyContents = resupply.getConvoyContents();
if (!convoyContents.isEmpty()) {
campaign.addReport(String.format(resources.getString("convoyInsufficientSize.text")));

for (Part part : convoyContents) {
campaign.addReport("- " + part.getName());
}
}
} else {
processConvoy(resupply, resupply.getConvoyContents(), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public static void dialogConvoyRoleplayEvent(Campaign campaign, Force playerConv

String speakerName;
if (speaker != null) {
speakerName = speaker.getFullTitle();
speakerName = speaker.getFullTitle() + " - " + playerConvoy.getName();
} else {
speakerName = playerConvoy.getName();
}
Expand Down
4 changes: 2 additions & 2 deletions MekHQ/src/mekhq/gui/stratcon/CampaignManagementDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private void requestResupply(ActionEvent event) {
} else {
AtBContract contract = currentCampaignState.getContract();
Resupply resupply = new Resupply(campaign, contract, ResupplyType.RESUPPLY_NORMAL);
performResupply(resupply, contract, 1);
performResupply(resupply, contract);
}

btnRequestResupply.setEnabled(currentCampaignState.getSupportPoints() > 0);
Expand Down Expand Up @@ -207,7 +207,7 @@ public void supplyDropDialog() {

AtBContract contract = currentCampaignState.getContract();
Resupply resupply = new Resupply(campaign, contract, ResupplyType.RESUPPLY_NORMAL);
performResupply(resupply, contract, 1);
performResupply(resupply, contract);

currentCampaignState.useSupportPoints((int) numberModel.getValue());
});
Expand Down
2 changes: 0 additions & 2 deletions MekHQ/src/mekhq/gui/view/MissionViewPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,6 @@ public void mouseClicked(MouseEvent e) {
if (campaign.getCampaignOptions().isUseStratCon()) {
lblCargoRequirement.setName("lblCargoRequirement");
lblCargoRequirement.setText(resourceMap.getString("lblCargoRequirement.text"));
lblCargoRequirement.setToolTipText(wordWrap(contract.getMoraleLevel().getToolTipText()));
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = y;
Expand All @@ -996,7 +995,6 @@ public void mouseClicked(MouseEvent e) {

txtCargoRequirement.setName("txtCargoRequirement");
txtCargoRequirement.setText(estimateCargoRequirements(campaign, contract) + "t");
txtCargoRequirement.setToolTipText(wordWrap(contract.getMoraleLevel().getToolTipText()));
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = y++;
Expand Down

0 comments on commit 3d54a52

Please sign in to comment.