From 4c41623fa6655e0446b29abae540d7044b5dcd03 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 5 Nov 2024 12:53:12 -0600 Subject: [PATCH 1/7] Add contract automation for user prompts and mothballing Implemented automated prompts for contract initiation, including mothballing units, charting courses, and beginning transit. Added the ability to track and manage automatically mothballed units within the campaign. --- .../resources/ContractAutomation.properties | 36 +++ MekHQ/src/mekhq/campaign/Campaign.java | 10 + .../contractMarket/ContractAutomation.java | 209 ++++++++++++++++++ .../src/mekhq/campaign/mission/Contract.java | 21 +- .../gui/dialog/ContractMarketDialog.java | 3 + 5 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/ContractAutomation.properties create mode 100644 MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java diff --git a/MekHQ/resources/mekhq/resources/ContractAutomation.properties b/MekHQ/resources/mekhq/resources/ContractAutomation.properties new file mode 100644 index 0000000000..3357edfd11 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/ContractAutomation.properties @@ -0,0 +1,36 @@ +# General +generalTitle.text=++ INCOMING TRANSMISSION ++ +generalSpeakerNameFallback.text=%s Actual +generalFallbackAddress.text=Commander +generalConfirm.text=Accept +generalDecline.text=Decline +generalEmployerFallback=mercenary + +# Messages +mothballDescription.text=%s, our employer has offered to assist our personnel in getting our\ + \ equipment ready for transport.\ +
\ +
Prior to loading, all equipment in our TO&E will be mothballed. This will prevent us needing\ + \ to maintain it while in transit. Equipment outside our TO&E will be unaffected.\ +
\ +
Will we be accepting their offer?\ +
\ +
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. + +chartCourseDescription.text=Our target system is %s. Our %s contact has already calculated the best\ + \ route for us.\ +
\ +
This journey will take us %s days and cost %s. Would you like to accept this route?\ +
\ +
If we decline, you will need to manually order route calculation from the Interstellar Map\ + \ panel of your command console. Select the target system, and then 'Calculate Jump Path.'\ + \ Alternatively, if you click on the hyper-linked system name in your Briefing Room, that will\ + \ automatically select the appropriate system in the Interstellar Map panel. + +beginTransitDescription.text=%s, everything is good to go. Should I order our forces to mount up and\ + \ pass word to the DropShips to begin transit?\ +
\ +
If you decline, you will need to manually make this order by selecting 'Begin Transit' in the\ + \ Interstellar Map. diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 27e2f60414..cae576b99d 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -268,6 +268,7 @@ public class Campaign implements ITechManager { private final Quartermaster quartermaster; private StoryArc storyArc; private FameAndInfamyController fameAndInfamy; + private List automatedMothballUnits; private final transient ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Campaign", MekHQ.getMHQOptions().getLocale()); @@ -335,6 +336,7 @@ public Campaign() { quartermaster = new Quartermaster(this); fieldKitchenWithinCapacity = false; fameAndInfamy = new FameAndInfamyController(); + automatedMothballUnits = new ArrayList<>(); } /** @@ -5313,6 +5315,14 @@ public FameAndInfamyController getFameAndInfamy() { return fameAndInfamy; } + public List getAutomatedMothballUnits() { + return automatedMothballUnits; + } + + public void setAutomatedMothballUnits(List automatedMothballUnits) { + this.automatedMothballUnits = automatedMothballUnits; + } + public void writeToXML(final PrintWriter pw) { int indent = 0; diff --git a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java new file mode 100644 index 0000000000..5d338090f0 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java @@ -0,0 +1,209 @@ +package mekhq.campaign.market.contractMarket; + +import megamek.common.annotations.Nullable; +import mekhq.campaign.Campaign; +import mekhq.campaign.JumpPath; +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.universe.Faction; +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; + +public class ContractAutomation { + private final static ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.ContractAutomation"); + // Ask the user if they want to mothball their stuff. + // Store the units mothballed in this manner, so we can activate them. + // Ask the user if they want to chart a course to their destination. + // Make sure the user knows how much the journey will cost. + // Make sure the user knows how to set the journey if they refuse. + // Ask the user if they want to start their journey. + // Make sure the user knows how long the journey will take cost. + // Make sure the user knows how to begin the journey if they refuse. + + // When the user enters the target system automatically active the previously mothballed units. + + 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)); + } + + // Chart Course; + String targetSystem = contract.getSystemName(campaign.getLocalDate()); + + Faction employerFaction = Factions.getInstance().getFaction(contract.getEmployer()); + String employerName = resources.getString("generalEmployerFallback"); + if (employerFaction != null) { + employerName = employerFaction.getFullName(campaign.getGameYear()); + } + + 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("chartCourseDescription.text"), + targetSystem, employerName, travelDays, totalCost); + boolean calculateJumpPath = createDialog(speakerName, speakerIcon, message); + + if (calculateJumpPath) { + campaign.getLocation().setJumpPath(jumpPath); + } else { + return; + } + + // Begin Transit + message = String.format(resources.getString("beginTransitDescription.text"), + commanderAddress); + if (createDialog(speakerName, speakerIcon, message)) { + campaign.getLocation().setJumpPath(jumpPath); + campaign.getUnits().forEach(unit -> unit.setSite(Unit.SITE_FACILITY_BASIC)); + } + } + + private static @Nullable Person getSpeaker(Campaign campaign) { + List admins = campaign.getAdmins(); + + if (admins.isEmpty()) { + return null; + } + + List 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; + } + + private static String getSpeakerName(Campaign campaign, @Nullable Person speaker) { + if (speaker == null) { + return String.format(resources.getString("generalSpeakerNameFallback.text"), + campaign.getName()); + } else { + return speaker.getFullTitle(); + } + } + + 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 = new ImageIcon(fallbackIconFilename); + } + } else { + icon = speaker.getPortrait().getImageIcon(); + } + + Image originalImage = icon.getImage(); + Image scaledImage = originalImage.getScaledInstance(100, -1, Image.SCALE_SMOOTH); + return new ImageIcon(scaledImage); + } + + private static String getCommanderAddress(Campaign campaign) { + Person commander = campaign.getFlaggedCommander(); + + if (commander == null) { + return resources.getString("generalFallbackAddress.text"); + } + + String commanderRank = commander.getRankName(); + + if (commanderRank.equalsIgnoreCase("None")) { + return commander.getFullName(); + } + + return commanderRank; + } + + private static boolean createDialog(String speakerName, ImageIcon speakerIcon, String message) { + // Custom button text + Object[] options = { + resources.getString("generalConfirm.text"), + resources.getString("generalDecline.text") + }; + + // Create a custom message with a border + JPanel descriptionPanel = new JPanel(); + descriptionPanel.setLayout(new BoxLayout(descriptionPanel, BoxLayout.PAGE_AXIS)); + JLabel description = new JLabel(message); + description.setBorder(BorderFactory.createTitledBorder(speakerName)); + descriptionPanel.add(description); + + int response = JOptionPane.showOptionDialog(null, + descriptionPanel, // Description + resources.getString("generalTitle.text"), // Title + JOptionPane.YES_NO_OPTION, // Option type + JOptionPane.QUESTION_MESSAGE, // Message type + speakerIcon, // Icon + options, // Array of options + options[0]); // Default button title + + return (response == JOptionPane.YES_OPTION); + } + + private static List performAutomatedMothballing(Campaign campaign) { + List mothballedUnits = new ArrayList<>(); + + for (Force force : campaign.getAllForces()) { + for (UUID unitId : force.getUnits()) { + Unit unit = campaign.getUnit(unitId); + + if (unit != null) { + if (!unit.isMothballed()) { + unit.completeMothball(); + mothballedUnits.add(unit); + } + } + } + } + + return mothballedUnits; + } +} diff --git a/MekHQ/src/mekhq/campaign/mission/Contract.java b/MekHQ/src/mekhq/campaign/mission/Contract.java index ecaebf6af6..e36fd17a9b 100644 --- a/MekHQ/src/mekhq/campaign/mission/Contract.java +++ b/MekHQ/src/mekhq/campaign/mission/Contract.java @@ -20,14 +20,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 +30,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 +162,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 +441,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 +566,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) { diff --git a/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java b/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java index 29a9fba0c4..b1edfc19f1 100644 --- a/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java @@ -48,6 +48,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 +481,8 @@ private void acceptContract(ActionEvent evt) { } } + contractStartPrompt(campaign, selectedContract); + selectedContract.setName(contractView.getContractName()); campaign.getFinances().credit(TransactionType.CONTRACT_PAYMENT, campaign.getLocalDate(), selectedContract.getTotalAdvanceAmount(), From 84fa227b334678d7a903fb156cd098a3595e4cfa Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 5 Nov 2024 13:57:14 -0600 Subject: [PATCH 2/7] Remove "(GM)" from unit status reports and improve contract automation Removed the "(GM)" designation from mothball and activation reports to streamline notification messages. Enhanced contract automation with automated unit activation upon entering the target system and improved dialogue prompts for transit operations. --- .../resources/ContractAutomation.properties | 18 +- MekHQ/src/mekhq/campaign/Campaign.java | 7 + .../contractMarket/ContractAutomation.java | 177 ++++++++++++++---- MekHQ/src/mekhq/campaign/unit/Unit.java | 6 +- 4 files changed, 154 insertions(+), 54 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/ContractAutomation.properties b/MekHQ/resources/mekhq/resources/ContractAutomation.properties index 3357edfd11..23a5b64ea0 100644 --- a/MekHQ/resources/mekhq/resources/ContractAutomation.properties +++ b/MekHQ/resources/mekhq/resources/ContractAutomation.properties @@ -19,18 +19,12 @@ mothballDescription.text=%s, our employer has offered to assist our personnel in \ 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. -chartCourseDescription.text=Our target system is %s. Our %s contact has already calculated the best\ +transitDescription.text=Our target system is %s. Our %s contact has already calculated the best\ \ route for us.\
\ -
This journey will take us %s days and cost %s. Would you like to accept this route?\ +
This journey will take us %s days and cost %s. Would you like to accept this route and begin\ + \ transit?\
\ -
If we decline, you will need to manually order route calculation from the Interstellar Map\ - \ panel of your command console. Select the target system, and then 'Calculate Jump Path.'\ - \ Alternatively, if you click on the hyper-linked system name in your Briefing Room, that will\ - \ automatically select the appropriate system in the Interstellar Map panel. - -beginTransitDescription.text=%s, everything is good to go. Should I order our forces to mount up and\ - \ pass word to the DropShips to begin transit?\ -
\ -
If you decline, you will need to manually make this order by selecting 'Begin Transit' in the\ - \ Interstellar Map. +
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.' diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index cae576b99d..9da5e7a4b1 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -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; @@ -3663,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)) { diff --git a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java index 5d338090f0..2fca118cd9 100644 --- a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java +++ b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java @@ -1,14 +1,38 @@ +/* + * 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 . + */ 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.universe.Faction; +import mekhq.campaign.unit.actions.ActivateUnitAction; +import mekhq.campaign.unit.actions.MothballUnitAction; import mekhq.campaign.universe.Factions; import javax.swing.*; @@ -18,19 +42,23 @@ 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"); - // Ask the user if they want to mothball their stuff. - // Store the units mothballed in this manner, so we can activate them. - // Ask the user if they want to chart a course to their destination. - // Make sure the user knows how much the journey will cost. - // Make sure the user knows how to set the journey if they refuse. - // Ask the user if they want to start their journey. - // Make sure the user knows how long the journey will take cost. - // Make sure the user knows how to begin the journey if they refuse. - - // When the user enters the target system automatically active the previously mothballed units. + /** + * 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())) { @@ -51,14 +79,9 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { campaign.setAutomatedMothballUnits(performAutomatedMothballing(campaign)); } - // Chart Course; + // Transit; String targetSystem = contract.getSystemName(campaign.getLocalDate()); - - Faction employerFaction = Factions.getInstance().getFaction(contract.getEmployer()); - String employerName = resources.getString("generalEmployerFallback"); - if (employerFaction != null) { - employerName = employerFaction.getFullName(campaign.getGameYear()); - } + String employerName = contract.getEmployer(); JumpPath jumpPath = contract.getJumpPath(campaign); int travelDays = contract.getTravelDays(campaign); @@ -67,25 +90,22 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { campaign.getCampaignOptions().isEquipmentContractBase()); String totalCost = costPerJump.multipliedBy(jumpPath.getJumps()).toAmountAndSymbolString(); - message = String.format(resources.getString("chartCourseDescription.text"), + message = String.format(resources.getString("transitDescription.text"), targetSystem, employerName, travelDays, totalCost); boolean calculateJumpPath = createDialog(speakerName, speakerIcon, message); if (calculateJumpPath) { campaign.getLocation().setJumpPath(jumpPath); - } else { - return; - } - - // Begin Transit - message = String.format(resources.getString("beginTransitDescription.text"), - commanderAddress); - 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 admins = campaign.getAdmins(); @@ -117,6 +137,14 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { 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 String.format(resources.getString("generalSpeakerNameFallback.text"), @@ -126,6 +154,15 @@ private static String getSpeakerName(Campaign campaign, @Nullable Person speaker } } + /** + * 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; @@ -135,7 +172,7 @@ private static ImageIcon getSpeakerIcon(Campaign campaign, @Nullable Person spea if (fallbackIconFilename == null || fallbackIconFilename.equals(DEFAULT_ICON_FILENAME)) { icon = Factions.getFactionLogo(campaign, campaign.getFaction().getShortName(), true); } else { - icon = new ImageIcon(fallbackIconFilename); + icon = campaign.getUnitIcon().getImageIcon(); } } else { icon = speaker.getPortrait().getImageIcon(); @@ -146,6 +183,13 @@ private static ImageIcon getSpeakerIcon(Campaign campaign, @Nullable Person spea 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) { Person commander = campaign.getFlaggedCommander(); @@ -155,14 +199,25 @@ private static String getCommanderAddress(Campaign campaign) { String commanderRank = commander.getRankName(); - if (commanderRank.equalsIgnoreCase("None")) { + 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"), @@ -170,26 +225,38 @@ private static boolean createDialog(String speakerName, ImageIcon speakerIcon, S }; // Create a custom message with a border + String descriptionTitle = String.format("%s", speakerName); + JPanel descriptionPanel = new JPanel(); descriptionPanel.setLayout(new BoxLayout(descriptionPanel, BoxLayout.PAGE_AXIS)); - JLabel description = new JLabel(message); - description.setBorder(BorderFactory.createTitledBorder(speakerName)); + JLabel description = new JLabel(String.format("
%s
", + WIDTH, message)); + description.setBorder(BorderFactory.createTitledBorder(descriptionTitle)); descriptionPanel.add(description); int response = JOptionPane.showOptionDialog(null, - descriptionPanel, // Description - resources.getString("generalTitle.text"), // Title - JOptionPane.YES_NO_OPTION, // Option type - JOptionPane.QUESTION_MESSAGE, // Message type - speakerIcon, // Icon + descriptionPanel, + resources.getString("generalTitle.text"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + speakerIcon, options, // Array of options options[0]); // Default button title 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 performAutomatedMothballing(Campaign campaign) { + List mothballTargets = new ArrayList<>(); List mothballedUnits = new ArrayList<>(); + MothballUnitAction mothballUnitAction = new MothballUnitAction(null, true); for (Force force : campaign.getAllForces()) { for (UUID unitId : force.getUnits()) { @@ -197,13 +264,45 @@ private static List performAutomatedMothballing(Campaign campaign) { if (unit != null) { if (!unit.isMothballed()) { - unit.completeMothball(); - mothballedUnits.add(unit); + 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; } + + /** + * 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 units = campaign.getAutomatedMothballUnits(); + + if (units.isEmpty()) { + return; + } + + ActivateUnitAction activateUnitAction = new ActivateUnitAction(null, true); + + for (Unit unit : units) { + if (unit.isMothballed()) { + activateUnitAction.execute(campaign, unit); + MekHQ.triggerEvent(new UnitChangedEvent(unit)); + } + } + } } diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index a9c23b0381..495d7f47a1 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -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"); } } From 361393af1987952339fda35e3cf2cd792c77f3fb Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 5 Nov 2024 14:02:13 -0600 Subject: [PATCH 3/7] Update copyright information Added "The MegaMek Team" copyright for 2024 to Unit.java, Contract.java, and ContractMarketDialog.java. This update ensures proper attribution and legal compliance for the project's source files. --- MekHQ/src/mekhq/campaign/mission/Contract.java | 1 + MekHQ/src/mekhq/campaign/unit/Unit.java | 2 +- MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/mission/Contract.java b/MekHQ/src/mekhq/campaign/mission/Contract.java index e36fd17a9b..4718d3a327 100644 --- a/MekHQ/src/mekhq/campaign/mission/Contract.java +++ b/MekHQ/src/mekhq/campaign/mission/Contract.java @@ -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. * diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index 495d7f47a1..b3e24a4d49 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -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. * diff --git a/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java b/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java index b1edfc19f1..a9d580796f 100644 --- a/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/ContractMarketDialog.java @@ -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. * From a2c8ad585a2faf4d9e2ede26ecb9106044a28eff Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 5 Nov 2024 14:42:38 -0600 Subject: [PATCH 4/7] Implement automated mothball units and improve dialog interface Added functionality to handle automated mothballing of units in Campaign.java, including saving and loading from XML. Enhanced ContractAutomation dialog by refining employer name handling and transitioning to a JDialog. Verbose strings in properties files modified for better clarity. --- .../resources/ContractAutomation.properties | 16 +++--- MekHQ/src/mekhq/campaign/Campaign.java | 6 ++ .../mekhq/campaign/io/CampaignXmlParser.java | 42 +++++++++++++- .../contractMarket/ContractAutomation.java | 56 ++++++++++++++----- 4 files changed, 97 insertions(+), 23 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/ContractAutomation.properties b/MekHQ/resources/mekhq/resources/ContractAutomation.properties index 23a5b64ea0..1d25c52d75 100644 --- a/MekHQ/resources/mekhq/resources/ContractAutomation.properties +++ b/MekHQ/resources/mekhq/resources/ContractAutomation.properties @@ -1,17 +1,17 @@ # General generalTitle.text=++ INCOMING TRANSMISSION ++ -generalSpeakerNameFallback.text=%s Actual generalFallbackAddress.text=Commander generalConfirm.text=Accept generalDecline.text=Decline -generalEmployerFallback=mercenary +generalNonClan.text=The %s # Messages mothballDescription.text=%s, our employer has offered to assist our personnel in getting our\ \ equipment ready for transport.\
\ -
Prior to loading, all equipment in our TO&E will be mothballed. This will prevent us needing\ - \ to maintain it while in transit. Equipment outside our TO&E will be unaffected.\ +
Prior to loading, all equipment in our TO&E will be mothballed. This will prevent us\ + \ needing to maintain it while in transit and the units will be unmothballed prior to\ + \ arrival. Equipment outside our TO&E will be unaffected.\
\
Will we be accepting their offer?\
\ @@ -19,11 +19,11 @@ mothballDescription.text=%s, our employer has offered to assist our personnel in \ 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 %s. Our %s contact has already calculated the best\ - \ route for us.\ +transitDescription.text=Our target system is %s. %s has already calculated the best route\ + \ for us.\
\ -
This journey will take us %s days and cost %s. Would you like to accept this route and begin\ - \ transit?\ +
This journey will take us %s days and cost %s. Would you like to accept this\ + \ route and begin transit?\
\
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\ diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 9da5e7a4b1..3c434bfa8b 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -5512,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()) { diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index e9ddb64378..341f0b41e2 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -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,42 @@ private static void processFameAndInfamyNodes(Campaign relativeValue, Node worki FameAndInfamyController.parseFromXML(workingNode.getChildNodes(), relativeValue); } + private static List processAutomatedMothballNodes(Node workingNode, Campaign campaign) { + logger.info("Loading Automated Mothball Nodes from XML..."); + + // TODO: make SpecialAbility a Campaign instance + List 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")) { + // Error condition of sorts! + // Errr, what should we do here? + 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..."); diff --git a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java index 2fca118cd9..d411ad04c0 100644 --- a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java +++ b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java @@ -79,10 +79,14 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { campaign.setAutomatedMothballUnits(performAutomatedMothballing(campaign)); } - // Transit; + // 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); @@ -92,9 +96,7 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { message = String.format(resources.getString("transitDescription.text"), targetSystem, employerName, travelDays, totalCost); - boolean calculateJumpPath = createDialog(speakerName, speakerIcon, message); - - if (calculateJumpPath) { + if (createDialog(speakerName, speakerIcon, message)) { campaign.getLocation().setJumpPath(jumpPath); campaign.getUnits().forEach(unit -> unit.setSite(Unit.SITE_FACILITY_BASIC)); campaign.getApp().getCampaigngui().refreshAllTabs(); @@ -147,8 +149,7 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { */ private static String getSpeakerName(Campaign campaign, @Nullable Person speaker) { if (speaker == null) { - return String.format(resources.getString("generalSpeakerNameFallback.text"), - campaign.getName()); + return campaign.getName(); } else { return speaker.getFullTitle(); } @@ -227,6 +228,11 @@ private static boolean createDialog(String speakerName, ImageIcon speakerIcon, S // Create a custom message with a border String descriptionTitle = String.format("%s", 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("
%s
", @@ -234,14 +240,38 @@ private static boolean createDialog(String speakerName, ImageIcon speakerIcon, S description.setBorder(BorderFactory.createTitledBorder(descriptionTitle)); descriptionPanel.add(description); - int response = JOptionPane.showOptionDialog(null, - descriptionPanel, - resources.getString("generalTitle.text"), + // 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, + JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - speakerIcon, - options, // Array of options - options[0]); // Default button title + 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); } From a87ea8ed440defd02b1ca9204d355f8bbd5d8009 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 5 Nov 2024 14:56:08 -0600 Subject: [PATCH 5/7] Refactored unit mothball eligibility check. Replaced the check for unit mothball status with a more comprehensive condition that includes availability and repair status. This ensures only appropriate units are added to the mothball targets list. --- .../campaign/market/contractMarket/ContractAutomation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java index d411ad04c0..c4bd8c5c37 100644 --- a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java +++ b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java @@ -293,7 +293,7 @@ private static List performAutomatedMothballing(Campaign campaign) { Unit unit = campaign.getUnit(unitId); if (unit != null) { - if (!unit.isMothballed()) { + if (unit.isAvailable(false) && !unit.isUnderRepair()) { mothballTargets.add(unit); } } From 9f4df35174ab22b6a694fedfcaffc77f2468d021 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 5 Nov 2024 14:57:33 -0600 Subject: [PATCH 6/7] Remove obsolete comments in CampaignXmlParser The outdated comments regarding SpecialAbility and error handling in processAutomatedMothballNodes were removed. This cleanup improves code readability and maintains the focus on current logic. --- MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index 341f0b41e2..ec4b0c7806 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -942,7 +942,6 @@ private static void processFameAndInfamyNodes(Campaign relativeValue, Node worki private static List processAutomatedMothballNodes(Node workingNode, Campaign campaign) { logger.info("Loading Automated Mothball Nodes from XML..."); - // TODO: make SpecialAbility a Campaign instance List mothballedUnits = new ArrayList<>(); NodeList workingList = workingNode.getChildNodes(); @@ -955,8 +954,6 @@ private static List processAutomatedMothballNodes(Node workingNode, Campai } if (!childNode.getNodeName().equalsIgnoreCase("mothballedUnit")) { - // Error condition of sorts! - // Errr, what should we do here? logger.error("Unknown node type not loaded in Automated Mothball nodes: " + childNode.getNodeName()); continue; From dfef5aee9edaa21e5cb308e60462d28f954660a3 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 6 Nov 2024 12:43:29 -0600 Subject: [PATCH 7/7] Refactored commander address handling and improved dialogs Extracted commander address logic into `Campaign` class. Enhanced UI components for contract automation dialogs and added detailed mothballing and activation reporting. --- .../mekhq/resources/Campaign.properties | 1 + .../resources/ContractAutomation.properties | 10 ++- MekHQ/src/mekhq/campaign/Campaign.java | 23 +++++ .../contractMarket/ContractAutomation.java | 90 ++++++++----------- 4 files changed, 67 insertions(+), 57 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 85bf3cc4f7..f83243e46a 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -83,3 +83,4 @@ bonusPartLog.text=Bonus part used to acquire 1x newAtBScenario.format=New scenario "{0}" will occur on {1}. atbScenarioToday.format=Scenario "{0}" is today, deploy a force from your TOE! atbScenarioTodayWithForce.format=Scenario "{0}" is today, {1} has been deployed! +generalFallbackAddress.text=Commander diff --git a/MekHQ/resources/mekhq/resources/ContractAutomation.properties b/MekHQ/resources/mekhq/resources/ContractAutomation.properties index 1d25c52d75..e32466dd96 100644 --- a/MekHQ/resources/mekhq/resources/ContractAutomation.properties +++ b/MekHQ/resources/mekhq/resources/ContractAutomation.properties @@ -1,6 +1,5 @@ # General generalTitle.text=++ INCOMING TRANSMISSION ++ -generalFallbackAddress.text=Commander generalConfirm.text=Accept generalDecline.text=Decline generalNonClan.text=The %s @@ -22,9 +21,14 @@ mothballDescription.text=%s, our employer has offered to assist our personnel in transitDescription.text=Our target system is %s. %s has already calculated the best route\ \ for us.\
\ -
This journey will take us %s days and cost %s. Would you like to accept this\ - \ route and begin transit?\ +
This journey will take us approximately %s days and cost %s. Would you like to\ + \ accept this route and begin transit?\
\
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.' + +# Reports +mothballingFailed.text=%s was not eligible to be automatically mothballed. Units cannot be\ + \ destroyed, refitting, undergoing repairs, mid-mothball, or already mothballed. +activationFailed.text=%s could not be automatically un-mothballed. diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 3c434bfa8b..2d7aa84b72 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -8400,4 +8400,27 @@ public boolean useVariableTechLevel() { public boolean showExtinct() { return !campaignOptions.isDisallowExtinctStuff(); } + + /** + * Gets a string to use for addressing the commander. + * If no commander is flagged, returns a default address. + * + * @return The title of the commander, or a default string if no commander. + */ + public String getCommanderAddress() { + Person commander = getFlaggedCommander(); + + if (commander == null) { + return resources.getString("generalFallbackAddress.text"); + } + + String commanderRank = commander.getRankName(); + + if (commanderRank.equalsIgnoreCase("None") || commanderRank.isBlank()) { + return commander.getFullName(); + } + + return commanderRank; + } + } diff --git a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java index c4bd8c5c37..8a8e2facc9 100644 --- a/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java +++ b/MekHQ/src/mekhq/campaign/market/contractMarket/ContractAutomation.java @@ -70,7 +70,7 @@ public static void contractStartPrompt(Campaign campaign, Contract contract) { final String speakerName = getSpeakerName(campaign, speaker); final ImageIcon speakerIcon = getSpeakerIcon(campaign, speaker); - final String commanderAddress = getCommanderAddress(campaign); + final String commanderAddress = campaign.getCommanderAddress(); // Mothballing String message = String.format(resources.getString("mothballDescription.text"), commanderAddress); @@ -184,29 +184,6 @@ private static ImageIcon getSpeakerIcon(Campaign campaign, @Nullable Person spea 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) { - 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. @@ -220,10 +197,8 @@ private static boolean createDialog(String speakerName, ImageIcon speakerIcon, S final int WIDTH = UIUtil.scaleForGUI(400); // Custom button text - Object[] options = { - resources.getString("generalConfirm.text"), - resources.getString("generalDecline.text") - }; + JButton confirmButton = new JButton(resources.getString("generalConfirm.text")); + JButton declineButton = new JButton(resources.getString("generalDecline.text")); // Create a custom message with a border String descriptionTitle = String.format("%s", speakerName); @@ -235,45 +210,47 @@ private static boolean createDialog(String speakerName, ImageIcon speakerIcon, S // Create description JPanel JPanel descriptionPanel = new JPanel(); descriptionPanel.setLayout(new BoxLayout(descriptionPanel, BoxLayout.PAGE_AXIS)); - JLabel description = new JLabel(String.format("
%s
", + JLabel description = new JLabel( + String.format("
%s
", WIDTH, message)); description.setBorder(BorderFactory.createTitledBorder(descriptionTitle)); descriptionPanel.add(description); + // Create Buttons Panel + JPanel buttonsPanel = new JPanel(); + buttonsPanel.add(confirmButton); + buttonsPanel.add(declineButton); + // 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, - JOptionPane.PLAIN_MESSAGE, - JOptionPane.YES_NO_OPTION, - null, - options, - options[0]); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); // Create JDialog - JDialog dialog = new JDialog(); + final JDialog dialog = new JDialog(); dialog.setTitle(resources.getString("generalTitle.text")); dialog.setModal(true); - dialog.setContentPane(optionPane); + dialog.setContentPane(mainPanel); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.pack(); dialog.setLocationRelativeTo(null); - optionPane.addPropertyChangeListener(evt -> { - if (JOptionPane.VALUE_PROPERTY.equals(evt.getPropertyName())) { - dialog.dispose(); - } + boolean[] result = new boolean[1]; + + // Add functionality to buttons + confirmButton.addActionListener(e -> { + result[0] = true; + dialog.dispose(); + }); + declineButton.addActionListener(e -> { + result[0] = false; + dialog.dispose(); }); dialog.setVisible(true); - int response = (Objects.equals(optionPane.getValue(), options[0]) ? - JOptionPane.YES_OPTION : JOptionPane.NO_OPTION); - - return (response == JOptionPane.YES_OPTION); + return result[0]; } /** @@ -285,7 +262,6 @@ private static boolean createDialog(String speakerName, ImageIcon speakerIcon, S */ private static List performAutomatedMothballing(Campaign campaign) { List mothballTargets = new ArrayList<>(); - List mothballedUnits = new ArrayList<>(); MothballUnitAction mothballUnitAction = new MothballUnitAction(null, true); for (Force force : campaign.getAllForces()) { @@ -295,6 +271,9 @@ private static List performAutomatedMothballing(Campaign campaign) { if (unit != null) { if (unit.isAvailable(false) && !unit.isUnderRepair()) { mothballTargets.add(unit); + } else { + campaign.addReport(String.format(resources.getString("mothballingFailed.text"), + unit.getName())); } } } @@ -305,10 +284,9 @@ private static List performAutomatedMothballing(Campaign campaign) { for (Unit unit : mothballTargets) { mothballUnitAction.execute(campaign, unit); MekHQ.triggerEvent(new UnitChangedEvent(unit)); - mothballedUnits.add(unit); } - return mothballedUnits; + return mothballTargets; } /** @@ -322,17 +300,21 @@ private static List performAutomatedMothballing(Campaign campaign) { public static void performAutomatedActivation(Campaign campaign) { List units = campaign.getAutomatedMothballUnits(); - if (units.isEmpty()) { - return; - } - ActivateUnitAction activateUnitAction = new ActivateUnitAction(null, true); for (Unit unit : units) { if (unit.isMothballed()) { activateUnitAction.execute(campaign, unit); MekHQ.triggerEvent(new UnitChangedEvent(unit)); + + if (unit.isMothballed()) { + campaign.addReport(String.format(resources.getString("activationFailed.text"), + unit.getName())); + } } } + + // We still want to clear out any units + campaign.setAutomatedMothballUnits(new ArrayList<>()); } }