Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Contract Automation #5172

Merged
merged 7 commits into from
Nov 7, 2024
Merged
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions MekHQ/resources/mekhq/resources/ContractAutomation.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# General
generalTitle.text=++ INCOMING TRANSMISSION ++
generalFallbackAddress.text=Commander
generalConfirm.text=Accept
generalDecline.text=Decline
generalNonClan.text=The %s

# Messages
mothballDescription.text=%s, our employer has offered to assist our personnel in getting our\
\ equipment ready for transport.\
<br>\
<br>Prior to loading, all equipment in our TO&E will be <b>mothballed</b>. This will prevent us\
\ needing to maintain it while in transit and the units will be <b>unmothballed</b> prior to\
\ arrival. Equipment outside our TO&E will be unaffected.\
<br>\
<br>Will we be accepting their offer?\
<br>\
<br>If we decline, you will still be able to manually order mothballing of equipment, but this\
\ will need to be arranged by right-clicking the unit in the Hangar panel of your command console,\
\ and selecting 'mothball.' Our techs will then take care of it as time becomes available.

transitDescription.text=Our target system is <b>%s</b>. %s has already calculated the best route\
\ for us.\
<br>\
<br>This journey will take us <b>%s</b> days and cost <b>%s</b>. Would you like to accept this\
\ route and begin transit?\
<br>\
<br>If we decline, you will need to manually order route calculation from the Interstellar Map.\
\ This can be done by selecting the target system in your Briefing Room, followed by 'Calculate\
\ Jump Path' and 'Begin Transit.'
23 changes: 23 additions & 0 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
@@ -140,6 +140,7 @@
import java.util.Map.Entry;
import java.util.stream.Collectors;

import static mekhq.campaign.market.contractMarket.ContractAutomation.performAutomatedActivation;
import static mekhq.campaign.personnel.backgrounds.BackgroundsController.randomMercenaryCompanyNameGenerator;
import static mekhq.campaign.personnel.education.EducationController.getAcademy;
import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.Payout.isBreakingContract;
@@ -268,6 +269,7 @@ public class Campaign implements ITechManager {
private final Quartermaster quartermaster;
private StoryArc storyArc;
private FameAndInfamyController fameAndInfamy;
private List<Unit> automatedMothballUnits;

private final transient ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Campaign",
MekHQ.getMHQOptions().getLocale());
@@ -335,6 +337,7 @@ public Campaign() {
quartermaster = new Quartermaster(this);
fieldKitchenWithinCapacity = false;
fameAndInfamy = new FameAndInfamyController();
automatedMothballUnits = new ArrayList<>();
}

/**
@@ -3661,6 +3664,12 @@ && getLocation().getJumpPath().getLastSystem().getId().equals(contract.getSystem
}
}

if (Objects.equals(location.getCurrentSystem(), contract.getSystem())) {
if (!automatedMothballUnits.isEmpty()) {
performAutomatedActivation(this);
}
}

for (final Scenario scenario : contract.getCurrentAtBScenarios()) {
if ((scenario.getDate() != null) && scenario.getDate().isBefore(getLocalDate())) {
if (getCampaignOptions().isUseStratCon() && (scenario instanceof AtBDynamicScenario)) {
@@ -5313,6 +5322,14 @@ public FameAndInfamyController getFameAndInfamy() {
return fameAndInfamy;
}

public List<Unit> getAutomatedMothballUnits() {
return automatedMothballUnits;
}

public void setAutomatedMothballUnits(List<Unit> automatedMothballUnits) {
this.automatedMothballUnits = automatedMothballUnits;
}

public void writeToXML(final PrintWriter pw) {
int indent = 0;

@@ -5495,6 +5512,12 @@ public void writeToXML(final PrintWriter pw) {

retirementDefectionTracker.writeToXML(pw, indent);

MHQXMLUtility.writeSimpleXMLOpenTag(pw, indent++, "automatedMothballUnits");
for (Unit unit : automatedMothballUnits) {
MHQXMLUtility.writeSimpleXMLTag(pw, indent, "mothballedUnit", unit.getId());
}
MHQXMLUtility.writeSimpleXMLCloseTag(pw, --indent, "automatedMothballUnits");

// Customised planetary events
MHQXMLUtility.writeSimpleXMLOpenTag(pw, indent++, "customPlanetaryEvents");
for (PlanetarySystem psystem : Systems.getInstance().getSystems().values()) {
39 changes: 37 additions & 2 deletions MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java
Original file line number Diff line number Diff line change
@@ -37,10 +37,10 @@
import mekhq.campaign.force.Force;
import mekhq.campaign.force.Lance;
import mekhq.campaign.icons.UnitIcon;
import mekhq.campaign.market.contractMarket.AbstractContractMarket;
import mekhq.campaign.market.contractMarket.AtbMonthlyContractMarket;
import mekhq.campaign.market.PersonnelMarket;
import mekhq.campaign.market.ShoppingList;
import mekhq.campaign.market.contractMarket.AbstractContractMarket;
import mekhq.campaign.market.contractMarket.AtbMonthlyContractMarket;
import mekhq.campaign.mission.AtBContract;
import mekhq.campaign.mission.Mission;
import mekhq.campaign.mission.Scenario;
@@ -284,6 +284,8 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException {
} else if (xn.equalsIgnoreCase("retirementDefectionTracker")) {
retVal.setRetirementDefectionTracker(
RetirementDefectionTracker.generateInstanceFromXML(wn, retVal));
} else if (xn.equalsIgnoreCase("automatedMothballUnits")) {
retVal.setAutomatedMothballUnits(processAutomatedMothballNodes(wn, retVal));
} else if (xn.equalsIgnoreCase("shipSearchStart")) {
retVal.setShipSearchStart(MHQXMLUtility.parseDate(wn.getTextContent().trim()));
} else if (xn.equalsIgnoreCase("shipSearchType")) {
@@ -937,6 +939,39 @@ private static void processFameAndInfamyNodes(Campaign relativeValue, Node worki
FameAndInfamyController.parseFromXML(workingNode.getChildNodes(), relativeValue);
}

private static List<Unit> processAutomatedMothballNodes(Node workingNode, Campaign campaign) {
logger.info("Loading Automated Mothball Nodes from XML...");

List<Unit> mothballedUnits = new ArrayList<>();

NodeList workingList = workingNode.getChildNodes();
for (int x = 0; x < workingList.getLength(); x++) {
Node childNode = workingList.item(x);

// If it's not an element node, we ignore it.
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}

if (!childNode.getNodeName().equalsIgnoreCase("mothballedUnit")) {
logger.error("Unknown node type not loaded in Automated Mothball nodes: "
+ childNode.getNodeName());
continue;
}

Unit unit = campaign.getUnit(UUID.fromString(childNode.getTextContent()));

if (unit == null) {
logger.error("Unknown UUID: " + childNode.getTextContent());
}

mothballedUnits.add(unit);
}

logger.info("Load Automated Mothball Nodes Complete!");
return mothballedUnits;
}

private static void processSpecialAbilityNodes(Campaign retVal, Node wn, Version version) {
logger.info("Loading Special Ability Nodes from XML...");

338 changes: 338 additions & 0 deletions MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
/*
* ContractAutomation.java
*
* Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
* MekHQ 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 3 of the License, or
* (at your option) any later version.
*
* MekHQ 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 MekHQ. If not, see <http://www.gnu.org/licenses/>.
*/
package mekhq.campaign.market.contractMarket;

import megamek.client.ui.swing.util.UIUtil;
import megamek.common.annotations.Nullable;
import mekhq.MekHQ;
import mekhq.campaign.Campaign;
import mekhq.campaign.JumpPath;
import mekhq.campaign.event.UnitChangedEvent;
import mekhq.campaign.finances.Money;
import mekhq.campaign.force.Force;
import mekhq.campaign.mission.Contract;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.unit.Unit;
import mekhq.campaign.unit.actions.ActivateUnitAction;
import mekhq.campaign.unit.actions.MothballUnitAction;
import mekhq.campaign.universe.Factions;

import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.*;

import static megamek.common.icons.AbstractIcon.DEFAULT_ICON_FILENAME;

/**
* The ContractAutomation class provides a suite of methods
* used in automating actions when a contract starts.
* This includes actions like mothballing of units,
* transit to mission location and the automated activation of units when arriving in system.
*/
public class ContractAutomation {
private final static ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.ContractAutomation");

/**
* Main function to initiate a sequence of automated tasks when a contract is started.
* The tasks include prompt and execution for unit mothballing, calculating and starting the
* journey to the target system.
*
* @param campaign The current campaign.
* @param contract Selected contract.
*/
public static void contractStartPrompt(Campaign campaign, Contract contract) {
// If we're already in the right system there is no need to automate these actions
if (Objects.equals(campaign.getLocation().getCurrentSystem(), contract.getSystem())) {
return;
}

// Initial setup
final Person speaker = getSpeaker(campaign);
final String speakerName = getSpeakerName(campaign, speaker);
final ImageIcon speakerIcon = getSpeakerIcon(campaign, speaker);

final String commanderAddress = getCommanderAddress(campaign);

// Mothballing
String message = String.format(resources.getString("mothballDescription.text"), commanderAddress);

if (createDialog(speakerName, speakerIcon, message)) {
campaign.setAutomatedMothballUnits(performAutomatedMothballing(campaign));
}

// Transit
String targetSystem = contract.getSystemName(campaign.getLocalDate());
String employerName = contract.getEmployer();

if (!employerName.contains("Clan")) {
employerName = String.format(resources.getString("generalNonClan.text"), employerName);
}

JumpPath jumpPath = contract.getJumpPath(campaign);
int travelDays = contract.getTravelDays(campaign);

Money costPerJump = campaign.calculateCostPerJump(true,
campaign.getCampaignOptions().isEquipmentContractBase());
String totalCost = costPerJump.multipliedBy(jumpPath.getJumps()).toAmountAndSymbolString();

message = String.format(resources.getString("transitDescription.text"),
targetSystem, employerName, travelDays, totalCost);
if (createDialog(speakerName, speakerIcon, message)) {
campaign.getLocation().setJumpPath(jumpPath);
campaign.getUnits().forEach(unit -> unit.setSite(Unit.SITE_FACILITY_BASIC));
campaign.getApp().getCampaigngui().refreshAllTabs();
campaign.getApp().getCampaigngui().refreshLocation();
}
}

/**
* @param campaign The current campaign
* @return The highest ranking Admin/Transport character. If none are found, returns {@code null}.
*/
private static @Nullable Person getSpeaker(Campaign campaign) {
List<Person> admins = campaign.getAdmins();

if (admins.isEmpty()) {
return null;
}

List<Person> transportAdmins = new ArrayList<>();

for (Person admin : admins) {
if (admin.getPrimaryRole().isAdministratorTransport()
|| admin.getSecondaryRole().isAdministratorTransport()) {
transportAdmins.add(admin);
}
}

if (transportAdmins.isEmpty()) {
return null;
}

Person speaker = transportAdmins.get(0);

for (Person admin : transportAdmins) {
if (admin.outRanksUsingSkillTiebreaker(campaign, speaker)) {
speaker = admin;
}
}

return speaker;
}

/**
* Gets the name of the individual to be displayed in the dialog.
* If the person is {@code null}, it uses the campaign's name.
*
* @param campaign The current campaign
* @param speaker The person who will be speaking, or {@code null}.
* @return The name to be displayed.
*/
private static String getSpeakerName(Campaign campaign, @Nullable Person speaker) {
if (speaker == null) {
return campaign.getName();
} else {
return speaker.getFullTitle();
}
}

/**
* Gets the icon representing the speaker.
* If the speaker is {@code null}, it defaults to displaying the campaign's icon, or the
* campaign's faction icon.
*
* @param campaign The current campaign
* @param speaker The person who is speaking, or {@code null}.
* @return The icon of the speaker, campaign, or faction.
*/
private static ImageIcon getSpeakerIcon(Campaign campaign, @Nullable Person speaker) {
ImageIcon icon;

if (speaker == null) {
String fallbackIconFilename = campaign.getUnitIcon().getFilename();

if (fallbackIconFilename == null || fallbackIconFilename.equals(DEFAULT_ICON_FILENAME)) {
icon = Factions.getFactionLogo(campaign, campaign.getFaction().getShortName(), true);
} else {
icon = campaign.getUnitIcon().getImageIcon();
}
} else {
icon = speaker.getPortrait().getImageIcon();
}

Image originalImage = icon.getImage();
Image scaledImage = originalImage.getScaledInstance(100, -1, Image.SCALE_SMOOTH);
return new ImageIcon(scaledImage);
}

/**
* Gets a string to use for addressing the commander.
* If no commander is flagged, returns a default address.
*
* @param campaign The current campaign
* @return The title of the commander, or a default string if no commander.
*/
private static String getCommanderAddress(Campaign campaign) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like this can't be the first time this is done (or it might not be the last)... same for the speaker icon. But I wouldnt know. There is this "this contract is too difficult for your wimpy forces" dialog. Might be something to unify in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, moved it to Campaign.java

Person commander = campaign.getFlaggedCommander();

if (commander == null) {
return resources.getString("generalFallbackAddress.text");
}

String commanderRank = commander.getRankName();

if (commanderRank.equalsIgnoreCase("None") || commanderRank.isBlank()) {
return commander.getFullName();
}

return commanderRank;
}

/**
* Displays a dialog for user interaction.
* The dialog uses a custom formatted message and includes options for user to confirm or decline.
*
* @param speakerName The title of the speaker to be displayed.
* @param speakerIcon The {@link ImageIcon} of the person speaking.
* @param message The message to be displayed in the dialog.
* @return {@code true} if the user confirms, {@code false} otherwise.
*/
private static boolean createDialog(String speakerName, ImageIcon speakerIcon, String message) {
final int WIDTH = UIUtil.scaleForGUI(400);

// Custom button text
Object[] options = {
resources.getString("generalConfirm.text"),
resources.getString("generalDecline.text")
};

// Create a custom message with a border
String descriptionTitle = String.format("<html><b>%s</b></html>", speakerName);

// Create ImageIcon JLabel
JLabel iconLabel = new JLabel(speakerIcon);
iconLabel.setHorizontalAlignment(JLabel.CENTER);

// Create description JPanel
JPanel descriptionPanel = new JPanel();
descriptionPanel.setLayout(new BoxLayout(descriptionPanel, BoxLayout.PAGE_AXIS));
JLabel description = new JLabel(String.format("<html><div style='width: %s; text-align:justify;'>%s</div></html>",
WIDTH, message));
description.setBorder(BorderFactory.createTitledBorder(descriptionTitle));
descriptionPanel.add(description);

// Create main JPanel and add icon and description
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(iconLabel, BorderLayout.NORTH);
mainPanel.add(descriptionPanel, BorderLayout.CENTER);

// Create JOptionPane
JOptionPane optionPane = new JOptionPane(mainPanel,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are probably double-dialoging this because JOptionPane is not flexible, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the intent, but on a second viewing I don't think it's necessary and double-dialoging isn't doing anything beneficial here, just unnecessarily increasing complexity.

Removed.

JOptionPane.PLAIN_MESSAGE,
JOptionPane.YES_NO_OPTION,
null,
options,
options[0]);

// Create JDialog
JDialog dialog = new JDialog();
dialog.setTitle(resources.getString("generalTitle.text"));
dialog.setModal(true);
dialog.setContentPane(optionPane);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.pack();
dialog.setLocationRelativeTo(null);

optionPane.addPropertyChangeListener(evt -> {
if (JOptionPane.VALUE_PROPERTY.equals(evt.getPropertyName())) {
dialog.dispose();
}
});

dialog.setVisible(true);

int response = (Objects.equals(optionPane.getValue(), options[0]) ?
JOptionPane.YES_OPTION : JOptionPane.NO_OPTION);

return (response == JOptionPane.YES_OPTION);
}

/**
* This method identifies all non-mothballed units within a campaign that are currently
* assigned to a {@code Force}. Those units are then GM Mothballed.
*
* @param campaign The current campaign.
* @return A list of all newly mothballed units.
*/
private static List<Unit> performAutomatedMothballing(Campaign campaign) {
List<Unit> mothballTargets = new ArrayList<>();
List<Unit> mothballedUnits = new ArrayList<>();
MothballUnitAction mothballUnitAction = new MothballUnitAction(null, true);

for (Force force : campaign.getAllForces()) {
for (UUID unitId : force.getUnits()) {
Unit unit = campaign.getUnit(unitId);

if (unit != null) {
if (unit.isAvailable(false) && !unit.isUnderRepair()) {
mothballTargets.add(unit);
}
}
}
}

// This needs to be a separate list as the act of mothballing the unit removes it from the
// list of units attached to the relevant force, resulting in a ConcurrentModificationException
for (Unit unit : mothballTargets) {
mothballUnitAction.execute(campaign, unit);
MekHQ.triggerEvent(new UnitChangedEvent(unit));
mothballedUnits.add(unit);
}

return mothballedUnits;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might simply return mothballTargets (?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point well made

}

/**
* Perform automated activation of units.
* Identifies all units that were mothballed previously and are now needing activation.
* The activation action is executed for each unit, and they are returned to their prior Force
* if it still exists.
*
* @param campaign The current campaign.
*/
public static void performAutomatedActivation(Campaign campaign) {
List<Unit> units = campaign.getAutomatedMothballUnits();

if (units.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it feels like this is unnecessary, the loop will simply skip if campaign.getAutomatedMothballUnits() is empty.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a relic from an earlier iteration and should have been removed. Thanks for pointing it out.

return;
}

ActivateUnitAction activateUnitAction = new ActivateUnitAction(null, true);

for (Unit unit : units) {
if (unit.isMothballed()) {
activateUnitAction.execute(campaign, unit);
MekHQ.triggerEvent(new UnitChangedEvent(unit));
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this method clean up the automothball list? If getAutomatedMothballUnits() is not a copy and is modifiable:
units.removeIf(unit -> !unit.isMothballed());

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for mentioning this, that was an oversight

}
}
22 changes: 11 additions & 11 deletions MekHQ/src/mekhq/campaign/mission/Contract.java
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
* Contract.java
*
* Copyright (c) 2011 Jay Lawson (jaylawson39 at yahoo.com). All rights reserved.
* Copyright (c) 2024 The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
@@ -20,14 +21,6 @@
*/
package mekhq.campaign.mission;

import java.io.PrintWriter;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import megamek.common.annotations.Nullable;
import megamek.logging.MMLogger;
import mekhq.campaign.Campaign;
@@ -38,6 +31,13 @@
import mekhq.campaign.rating.UnitRatingMethod;
import mekhq.campaign.unit.Unit;
import mekhq.utilities.MHQXMLUtility;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.PrintWriter;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

/**
* Contracts - we need to track static amounts here because changes in the
@@ -163,7 +163,7 @@ public void setEndDate(LocalDate endDate) {
* This sets the Start Date and End Date of the Contract based on the length of
* the contract and
* the starting date provided
*
*
* @param startDate the date the contract starts at
*/
public void setStartAndEndDate(LocalDate startDate) {
@@ -442,7 +442,7 @@ public Money getTotalMonthlyPayOut(Campaign c) {
.minus(getTotalEstimatedPayrollExpenses(c));
}

private int getTravelDays(Campaign c) {
public int getTravelDays(Campaign c) {
if (null != this.getSystem()) {
JumpPath jumpPath = getJumpPath(c);
double days = Math.round(jumpPath.getTotalTime(c.getLocalDate(), c.getLocation().getTransitTime()) * 100.0)
@@ -567,7 +567,7 @@ public void acceptContract(Campaign campaign) {
* Only do this at the time the contract is set up, otherwise amounts may change
* after
* the ink is signed, which is a no-no.
*
*
* @param c current campaign
*/
public void calculateContract(Campaign c) {
8 changes: 4 additions & 4 deletions MekHQ/src/mekhq/campaign/unit/Unit.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Unit.java
*
* Copyright (C) 2016-2024 - The MegaMek Team. All Rights Reserved.
* Copyright (c) 2009 Jay Lawson (jaylawson39 at yahoo.com). All rights reserved.
* Copyright (c) 2016-2024 The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
@@ -289,7 +289,7 @@ public String getTypeDisplayableNameWithOmni() {
StringBuilder toReturn = new StringBuilder();
toReturn.append("Omni");
if (!(type == UnitType.TANK || type == UnitType.MEK)) {
toReturn.append(" ");
toReturn.append(' ');
}
toReturn.append(UnitType.getTypeDisplayableName(type));
return toReturn.toString();
@@ -4817,7 +4817,7 @@ public void startMothballing(@Nullable Person mothballTech, boolean isGM) {
getCampaign().mothball(this);
} else {
completeMothball();
getCampaign().addReport(getHyperlinkedName() + " has been mothballed (GM)");
getCampaign().addReport(getHyperlinkedName() + " has been mothballed");
}
}

@@ -4866,7 +4866,7 @@ public void startActivating(@Nullable Person activationTech, boolean isGM) {
getCampaign().activate(this);
} else {
completeActivation();
getCampaign().addReport(getHyperlinkedName() + " has been activated (GM)");
getCampaign().addReport(getHyperlinkedName() + " has been activated");
}
}

4 changes: 4 additions & 0 deletions MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
* ContractMarketDialog.java
*
* Copyright (c) 2014-2024 Carl Spain. All rights reserved.
* Copyright (c) 2024 The MegaMek Team. All Rights Reserved.
*
* This file is part of MekHQ.
*
@@ -48,6 +49,7 @@
import java.util.List;
import java.util.*;

import static mekhq.campaign.market.contractMarket.ContractAutomation.contractStartPrompt;
import static mekhq.campaign.universe.Factions.getFactionLogo;

/**
@@ -480,6 +482,8 @@ private void acceptContract(ActionEvent evt) {
}
}

contractStartPrompt(campaign, selectedContract);

selectedContract.setName(contractView.getContractName());
campaign.getFinances().credit(TransactionType.CONTRACT_PAYMENT, campaign.getLocalDate(),
selectedContract.getTotalAdvanceAmount(),