diff --git a/MekHQ/resources/mekhq/resources/CampaignGUI.properties b/MekHQ/resources/mekhq/resources/CampaignGUI.properties index 6d8d3e01df..7264303d81 100644 --- a/MekHQ/resources/mekhq/resources/CampaignGUI.properties +++ b/MekHQ/resources/mekhq/resources/CampaignGUI.properties @@ -224,7 +224,10 @@ lblCargoSummary.text=Cargo Summary:; lblFacilityCapacities.text=Facility Capacities:; panLog.title=Daily Activity Log -dialogCheckDueScenarios.text=You must complete scenarios with a date of today or earlier before advancing the day. +dialogOverdueLoans.title=Overdue loans +dialogOverdueLoans.text=You must resolve overdue loans before advancing the day. + dialogCheckDueScenarios.title=Scenarios Must Be Completed +dialogCheckDueScenarios.text=You must complete scenarios with a date of today or earlier before advancing the day. spareBonusPartExchange.text=Bonus Part Exchange Program diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index 0ed21da257..41c3d1b061 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -306,15 +306,15 @@ UnitIconDialog.btnNone.toolTipText=The unit does not have a unit icon. This will #### Nag Dialogs ### InsufficientAstechsNagDialog Class InsufficientAstechsNagDialog.title=Astech Shortage -InsufficientAstechsNagDialog.text=You do not have enough astechs for your techs. You need %d more astech(s). Do you wish to proceed? +InsufficientAstechsNagDialog.text=Your Tech Teams require more Astechs. You need %d more Astech(s). Do you wish to proceed? ### InsufficientAstechTimeNagDialog Class -InsufficientAstechTimeNagDialog.title=Astech Shortage -InsufficientAstechTimeNagDialog.text=You do not have enough remaining astech time for maintenance. You need %d more astech(s). Do you wish to proceed? +InsufficientAstechTimeNagDialog.title=Astech Time Shortage +InsufficientAstechTimeNagDialog.text=You need more astech time for maintenance. You need %d more astech(s). Do you wish to proceed? ### InsufficientMedicsNagDialog Class InsufficientMedicsNagDialog.title=Medic Shortage -InsufficientMedicsNagDialog.text=You do not have enough medics for your doctors. You need %d more medic(s). Do you wish to proceed? +InsufficientMedicsNagDialog.text=Your Medical Teams require more Astechs. You need %d more medic(s). Do you wish to proceed? ### OutstandingScenariosNagDialog Class OutstandingScenariosNagDialog.title=Pending Battle @@ -358,12 +358,16 @@ InvalidFactionNagDialog.text=Your campaign faction is invalid. Either it has not ### UnableToAffordExpensesNagDialog Class UnableToAffordExpensesNagDialog.title=Unable to Afford Expenses -UnableToAffordExpensesNagDialog.text=Payday is due tomorrow, but you cannot afford to cover all expenses.\n\nAdvance day anyway? +UnableToAffordExpensesNagDialog.text=Payday is due tomorrow, but you cannot afford your monthly expenses of %s.\n\nAdvance day anyway? + +### UnableToAffordLoanPaymentNagDialog Class +UnableToAffordLoanPaymentNagDialog.title=Unable to Afford Loan Payment +UnableToAffordLoanPaymentNagDialog.text=One or more loans are due tomorrow, but you cannot afford to cover the %s payment.\n\nAdvance day anyway? ### UnableToAffordJumpNagDialog Class -UnableToAffordJumpNagDialog.title=Unable to Afford Jump -UnableToAffordJumpNagDialog.text=You are unable to afford your next jump.\n\nAdvance day anyway? +UnableToAffordJumpNagDialog.title=Unable to Afford the Next Jump +UnableToAffordJumpNagDialog.text=The next jump is %s which you cannot afford.\n\nAdvance day anyway? #### Report Dialogs ### CargoReportDialog Class @@ -753,7 +757,9 @@ optionOutstandingScenariosNag.toolTipText=This allows you to ignore the daily wa optionInvalidFactionNag.text=Hide Invalid Faction Nag optionInvalidFactionNag.toolTipText=This allows you to ignore the daily warning for having an invalid faction. optionUnableToAffordExpensesNag.text=Hide Unable to Afford Expenses Nag -optionUnableToAffordExpensesNag.toolTipText=This allows you to ignore the monthly warning for having insufficient funds to cover all expenses. +optionUnableToAffordExpensesNag.toolTipText=This allows you to ignore the monthly warning for having insufficient funds to cover monthly expenses. +optionUnableToAffordLoanPaymentNag.text=Hide Unable to Afford Loan Payments Nag +optionUnableToAffordLoanPaymentNag.toolTipText=This allows you to ignore the monthly warning for having insufficient funds to cover all loan payments due the next day. optionUnableToAffordJumpNag.text=Hide Unable to Afford Next Jump Nag optionUnableToAffordJumpNag.toolTipText=This allows you to ignore the daily warning when unable to afford a jump. ## Miscellaneous Tab diff --git a/MekHQ/src/mekhq/MHQConstants.java b/MekHQ/src/mekhq/MHQConstants.java index 8fc3dbf5c2..f049ccef03 100644 --- a/MekHQ/src/mekhq/MHQConstants.java +++ b/MekHQ/src/mekhq/MHQConstants.java @@ -178,6 +178,7 @@ public final class MHQConstants extends SuiteConstants { public static final String NAG_OUTSTANDING_SCENARIOS = "nagOutstandingScenarios"; public static final String NAG_INVALID_FACTION = "nagInvalidFaction"; public static final String NAG_UNABLE_TO_AFFORD_EXPENSES = "nagUnableToAffordExpenses"; + public static final String NAG_UNABLE_TO_AFFORD_LOAN_PAYMENT = "nagUnableToAffordLoanPayment"; public static final String NAG_UNABLE_TO_AFFORD_JUMP = "nagUnableToAffordJump"; // endregion Nag Tab diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 12116d680e..122a36bd34 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -21,23 +21,6 @@ */ package mekhq.campaign; -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; -import static mekhq.campaign.unit.Unit.SITE_FACILITY_MAINTENANCE; - -import java.io.PrintWriter; -import java.text.MessageFormat; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.Month; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; - -import javax.swing.JOptionPane; - import megamek.client.generator.RandomGenderGenerator; import megamek.client.generator.RandomNameGenerator; import megamek.client.generator.RandomUnitGenerator; @@ -54,11 +37,7 @@ import megamek.common.loaders.BLKFile; import megamek.common.loaders.EntityLoadingException; import megamek.common.loaders.EntitySavingException; -import megamek.common.options.GameOptions; -import megamek.common.options.IBasicOption; -import megamek.common.options.IOption; -import megamek.common.options.IOptionGroup; -import megamek.common.options.OptionsConstants; +import megamek.common.options.*; import megamek.common.util.BuildingBlock; import megamek.common.weapons.autocannons.ACWeapon; import megamek.common.weapons.flamers.FlamerWeapon; @@ -71,11 +50,7 @@ import mekhq.campaign.Quartermaster.PartAcquisitionResult; import mekhq.campaign.againstTheBot.AtBConfiguration; import mekhq.campaign.event.*; -import mekhq.campaign.finances.Accountant; -import mekhq.campaign.finances.CurrencyManager; -import mekhq.campaign.finances.Finances; -import mekhq.campaign.finances.Loan; -import mekhq.campaign.finances.Money; +import mekhq.campaign.finances.*; import mekhq.campaign.finances.enums.TransactionType; import mekhq.campaign.force.Force; import mekhq.campaign.force.Lance; @@ -90,12 +65,7 @@ import mekhq.campaign.market.ShoppingList; import mekhq.campaign.market.unitMarket.AbstractUnitMarket; import mekhq.campaign.market.unitMarket.DisabledUnitMarket; -import mekhq.campaign.mission.AtBContract; -import mekhq.campaign.mission.AtBDynamicScenario; -import mekhq.campaign.mission.AtBScenario; -import mekhq.campaign.mission.Contract; -import mekhq.campaign.mission.Mission; -import mekhq.campaign.mission.Scenario; +import mekhq.campaign.mission.*; import mekhq.campaign.mission.atb.AtBScenarioFactory; import mekhq.campaign.mission.enums.AtBLanceRole; import mekhq.campaign.mission.enums.MissionStatus; @@ -105,12 +75,7 @@ import mekhq.campaign.parts.equipment.AmmoBin; import mekhq.campaign.parts.equipment.EquipmentPart; import mekhq.campaign.parts.equipment.MissingEquipmentPart; -import mekhq.campaign.personnel.Bloodname; -import mekhq.campaign.personnel.Person; -import mekhq.campaign.personnel.PersonnelOptions; -import mekhq.campaign.personnel.Skill; -import mekhq.campaign.personnel.SkillType; -import mekhq.campaign.personnel.SpecialAbility; +import mekhq.campaign.personnel.*; import mekhq.campaign.personnel.autoAwards.AutoAwardsController; import mekhq.campaign.personnel.death.AbstractDeath; import mekhq.campaign.personnel.death.DisabledRandomDeath; @@ -118,12 +83,7 @@ import mekhq.campaign.personnel.divorce.DisabledRandomDivorce; import mekhq.campaign.personnel.education.Academy; import mekhq.campaign.personnel.education.EducationController; -import mekhq.campaign.personnel.enums.FamilialRelationshipType; -import mekhq.campaign.personnel.enums.PersonnelRole; -import mekhq.campaign.personnel.enums.PersonnelStatus; -import mekhq.campaign.personnel.enums.Phenotype; -import mekhq.campaign.personnel.enums.PrisonerStatus; -import mekhq.campaign.personnel.enums.SplittingSurnameStyle; +import mekhq.campaign.personnel.enums.*; import mekhq.campaign.personnel.generator.AbstractPersonnelGenerator; import mekhq.campaign.personnel.generator.DefaultPersonnelGenerator; import mekhq.campaign.personnel.generator.RandomPortraitGenerator; @@ -136,21 +96,16 @@ import mekhq.campaign.personnel.ranks.Ranks; import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; +import mekhq.campaign.rating.CamOpsReputation.ReputationController; import mekhq.campaign.rating.FieldManualMercRevDragoonsRating; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.rating.UnitRatingMethod; -import mekhq.campaign.rating.CamOpsReputation.ReputationController; import mekhq.campaign.storyarc.StoryArc; import mekhq.campaign.stratcon.StratconContractInitializer; import mekhq.campaign.stratcon.StratconRulesManager; import mekhq.campaign.stratcon.StratconTrackState; -import mekhq.campaign.unit.CargoStatistics; import mekhq.campaign.unit.CrewType; -import mekhq.campaign.unit.HangarStatistics; -import mekhq.campaign.unit.TestUnit; -import mekhq.campaign.unit.Unit; -import mekhq.campaign.unit.UnitOrder; -import mekhq.campaign.unit.UnitTechProgression; +import mekhq.campaign.unit.*; import mekhq.campaign.universe.*; import mekhq.campaign.universe.Planet.PlanetaryEvent; import mekhq.campaign.universe.PlanetarySystem.PlanetarySystemEvent; @@ -171,6 +126,22 @@ import mekhq.service.mrms.MRMSService; import mekhq.utilities.MHQXMLUtility; +import javax.swing.*; +import java.io.PrintWriter; +import java.text.MessageFormat; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.Month; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +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; +import static mekhq.campaign.unit.Unit.SITE_FACILITY_MAINTENANCE; + /** * The main campaign class, keeps track of teams and units * @@ -7749,6 +7720,11 @@ public int checkTurnoverPrompt() { options[0]); } + /** + * Checks if there are any scenarios that are due based on the current date. + * + * @return {@code true} if there are scenarios due, {@code false} otherwise + */ public boolean checkScenariosDue() { return getActiveMissions(true).stream() .flatMap(m -> m.getCurrentScenarios().stream()) diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index d63c171d36..5586421e09 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -18,11 +18,6 @@ */ package mekhq.campaign.stratcon; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.util.*; -import java.util.stream.Collectors; - import megamek.codeUtilities.ObjectUtility; import megamek.common.Compute; import megamek.common.Minefield; @@ -39,16 +34,10 @@ import mekhq.campaign.event.StratconDeploymentEvent; import mekhq.campaign.force.Force; import mekhq.campaign.force.Lance; -import mekhq.campaign.mission.AtBContract; -import mekhq.campaign.mission.AtBDynamicScenario; -import mekhq.campaign.mission.AtBDynamicScenarioFactory; -import mekhq.campaign.mission.AtBScenario; -import mekhq.campaign.mission.Scenario; -import mekhq.campaign.mission.ScenarioForceTemplate; +import mekhq.campaign.mission.*; import mekhq.campaign.mission.ScenarioForceTemplate.ForceAlignment; import mekhq.campaign.mission.ScenarioForceTemplate.ForceGenerationMethod; import mekhq.campaign.mission.ScenarioMapParameters.MapLocation; -import mekhq.campaign.mission.ScenarioTemplate; import mekhq.campaign.mission.atb.AtBScenarioModifier; import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; import mekhq.campaign.personnel.Person; @@ -58,6 +47,11 @@ import mekhq.campaign.stratcon.StratconScenario.ScenarioState; import mekhq.campaign.unit.Unit; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + /** * This class contains "rules" logic for the AtB-Stratcon state * @@ -771,36 +765,6 @@ private static Map> sortForcesByMapType(List return retVal; } - /** - * Determine whether the user should be nagged about unresolved scenarios on AtB - * Stratcon tracks. - * - * @param campaign Campaign to check. - * @return An informative string containing the reasons the user was nagged. - */ - public static String nagUnresolvedContacts(Campaign campaign) { - String sb = ""; - - // check every track attached to an active contract for unresolved scenarios - // to which the player must deploy forces today - for (AtBContract contract : campaign.getActiveAtBContracts()) { - if (contract.getStratconCampaignState() == null) { - continue; - } - - for (StratconTrackState track : contract.getStratconCampaignState().getTracks()) { - // "scenario name, track name" - sb = track.getScenarios().values().stream() - .filter(scenario -> (scenario.getCurrentState() == ScenarioState.UNRESOLVED) - && campaign.getLocalDate().equals(scenario.getDeploymentDate())) - .map(scenario -> String.format("%s, %s\n", scenario.getName(), track.getDisplayableName())) - .collect(Collectors.joining()); - } - } - - return sb; - } - /** * Worker function that generates stratcon scenario at the given coords, for the * given force, on the diff --git a/MekHQ/src/mekhq/gui/CampaignGUI.java b/MekHQ/src/mekhq/gui/CampaignGUI.java index f711257616..a7d9f3e0c6 100644 --- a/MekHQ/src/mekhq/gui/CampaignGUI.java +++ b/MekHQ/src/mekhq/gui/CampaignGUI.java @@ -21,44 +21,6 @@ */ package mekhq.gui; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAdjusters; -import java.util.*; -import java.util.stream.IntStream; -import java.util.zip.GZIPOutputStream; - -import javax.swing.*; -import javax.swing.UIManager.LookAndFeelInfo; -import javax.xml.parsers.DocumentBuilder; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import megamek.Version; import megamek.client.generator.RandomUnitGenerator; import megamek.client.ui.preferences.JWindowPreference; @@ -67,21 +29,12 @@ import megamek.client.ui.swing.UnitLoadingDialog; import megamek.client.ui.swing.dialog.AbstractUnitSelectorDialog; import megamek.client.ui.swing.util.UIUtil; -import megamek.common.Dropship; -import megamek.common.Entity; -import megamek.common.Jumpship; -import megamek.common.MULParser; -import megamek.common.MekSummaryCache; +import megamek.common.*; import megamek.common.annotations.Nullable; import megamek.common.event.Subscribe; import megamek.common.loaders.EntityLoadingException; import megamek.logging.MMLogger; -import mekhq.IconPackage; -import mekhq.MHQConstants; -import mekhq.MHQOptionsChangedEvent; -import mekhq.MHQStaticDirectoryManager; -import mekhq.MekHQ; -import mekhq.Utilities; +import mekhq.*; import mekhq.campaign.Campaign; import mekhq.campaign.CampaignController; import mekhq.campaign.CampaignOptions; @@ -102,11 +55,7 @@ import mekhq.campaign.personnel.death.ExponentialRandomDeath; import mekhq.campaign.personnel.death.PercentageRandomDeath; import mekhq.campaign.personnel.divorce.PercentageRandomDivorce; -import mekhq.campaign.personnel.enums.PersonnelRole; -import mekhq.campaign.personnel.enums.RandomDeathMethod; -import mekhq.campaign.personnel.enums.RandomDivorceMethod; -import mekhq.campaign.personnel.enums.RandomMarriageMethod; -import mekhq.campaign.personnel.enums.RandomProcreationMethod; +import mekhq.campaign.personnel.enums.*; import mekhq.campaign.personnel.marriage.PercentageRandomMarriage; import mekhq.campaign.personnel.procreation.AbstractProcreation; import mekhq.campaign.personnel.procreation.PercentageRandomProcreation; @@ -121,17 +70,29 @@ import mekhq.gui.dialog.*; import mekhq.gui.dialog.CampaignExportWizard.CampaignExportWizardState; import mekhq.gui.dialog.nagDialogs.*; -import mekhq.gui.dialog.reportDialogs.CargoReportDialog; -import mekhq.gui.dialog.reportDialogs.HangarReportDialog; -import mekhq.gui.dialog.reportDialogs.NewsReportDialog; -import mekhq.gui.dialog.reportDialogs.PersonnelReportDialog; -import mekhq.gui.dialog.reportDialogs.ReputationReportDialog; -import mekhq.gui.dialog.reportDialogs.TransportReportDialog; -import mekhq.gui.dialog.reportDialogs.UnitRatingReportDialog; +import mekhq.gui.dialog.reportDialogs.*; import mekhq.gui.enums.MHQTabType; import mekhq.gui.model.PartsTableModel; import mekhq.io.FileType; import mekhq.utilities.MHQXMLUtility; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import javax.swing.UIManager.LookAndFeelInfo; +import javax.xml.parsers.DocumentBuilder; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAdjusters; +import java.util.List; +import java.util.*; +import java.util.stream.IntStream; +import java.util.zip.GZIPOutputStream; /** * The application's main frame. @@ -2335,6 +2296,9 @@ public void refreshCalendar() { getFrame().setTitle(getCampaign().getTitle()); } + /** + * Refreshes the 'funds' display on the GUI. + */ private void refreshFunds() { Money funds = getCampaign().getFunds(); String inDebt = ""; @@ -2454,101 +2418,96 @@ public void undeployForce(Force f, boolean killSubs) { // region Subscriptions @Subscribe - public void handleDayEnding(DayEndingEvent evt) { - // first check for overdue loan payments - don't allow advancement until - // these are addressed - if (getCampaign().checkOverDueLoans()) { - refreshFunds(); - // FIXME : Localize - JOptionPane.showMessageDialog(null, "You must resolve overdue loans before advancing the day", - "Overdue loans", JOptionPane.WARNING_MESSAGE); - evt.cancel(); + public void handleDayEnding(DayEndingEvent dayEndingEvent) { + if (checkForOverdueLoans(dayEndingEvent)) { return; } - if (getCampaign().checkScenariosDue()) { - JOptionPane.showMessageDialog(null, getResourceMap().getString("dialogCheckDueScenarios.text"), - getResourceMap().getString("dialogCheckDueScenarios.title"), JOptionPane.WARNING_MESSAGE); - evt.cancel(); + if (checkForDueScenarios(dayEndingEvent)) { return; } if (new UnmaintainedUnitsNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new PregnantCombatantNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new PrisonersNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new UntreatedPersonnelNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new EndContractNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new NoCommanderNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new InvalidFactionNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new UnableToAffordJumpNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new InsufficientAstechsNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new InsufficientAstechTimeNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new InsufficientMedicsNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (getCampaign().getLocalDate() .equals(getCampaign().getLocalDate().with(TemporalAdjusters.lastDayOfMonth()))) { if (new UnableToAffordExpensesNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } } + if (new UnableToAffordLoanPaymentNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { + dayEndingEvent.cancel(); + return; + } + if (getCampaign().getCampaignOptions().isUseAtB()) { if (new ShortDeploymentNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new UnresolvedStratConContactsNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } if (new OutstandingScenariosNagDialog(getFrame(), getCampaign()).showDialog().isCancelled()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } } @@ -2563,7 +2522,7 @@ public void handleDayEnding(DayEndingEvent evt) { case 0: // the user launched the turnover dialog if (!showRetirementDefectionDialog()) { - evt.cancel(); + dayEndingEvent.cancel(); return; } case 1: @@ -2571,7 +2530,7 @@ public void handleDayEnding(DayEndingEvent evt) { break; case 2: // the user canceled - evt.cancel(); + dayEndingEvent.cancel(); return; default: throw new IllegalStateException( @@ -2580,6 +2539,54 @@ public void handleDayEnding(DayEndingEvent evt) { } } + /** + * Checks if there are any due instances of the {@link Scenario} class. + * If the {@code checkScenariosDue()} method of the {@link Campaign} associated with the given + * {@link DayEndingEvent} returns {@code true}, a dialo shows up informing the user of the due + * scenarios, and the {@link DayEndingEvent} is canceled. + * + * @param dayEndingEvent the {@link DayEndingEvent} being checked. + * @return {@code true} if there are due scenarios and {@code false} otherwise. + */ + private boolean checkForDueScenarios(DayEndingEvent dayEndingEvent) { + if (getCampaign().checkScenariosDue()) { + JOptionPane.showMessageDialog(null, + getResourceMap().getString("dialogCheckDueScenarios.text"), + getResourceMap().getString("dialogCheckDueScenarios.title"), + JOptionPane.WARNING_MESSAGE); + + dayEndingEvent.cancel(); + + return true; + } + return false; + } + + /** + * Checks if there are any overdue loans + * If the {@code checkOverDueLoans()} method of the {@link Campaign} associated with the given + * {@link DayEndingEvent} returns {@code true}, the funds get refreshed, a dialog shows up + * informing the user of the overdue loans, and the {@link DayEndingEvent} is canceled. + * + * @param dayEndingEvent the {@link DayEndingEvent} being checked. + * @return {@code true} if there are overdue loans and {@code false} otherwise. + */ + private boolean checkForOverdueLoans(DayEndingEvent dayEndingEvent) { + if (getCampaign().checkOverDueLoans()) { + refreshFunds(); + + JOptionPane.showMessageDialog(null, + getResourceMap().getString("dialogOverdueLoans.text"), + getResourceMap().getString("dialogOverdueLoans.title"), + JOptionPane.WARNING_MESSAGE); + + dayEndingEvent.cancel(); + + return true; + } + return false; + } + @Subscribe public void handleNewDay(NewDayEvent evt) { refreshCalendar(); diff --git a/MekHQ/src/mekhq/gui/dialog/MHQOptionsDialog.java b/MekHQ/src/mekhq/gui/dialog/MHQOptionsDialog.java index 67c792bfb7..b9bdbabd4d 100644 --- a/MekHQ/src/mekhq/gui/dialog/MHQOptionsDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/MHQOptionsDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2019-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,19 +18,6 @@ */ package mekhq.gui.dialog; -import java.awt.Component; -import java.awt.Container; -import java.awt.event.KeyEvent; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.Objects; - -import javax.swing.*; -import javax.swing.GroupLayout.Alignment; - import megamek.MMConstants; import megamek.client.ui.Messages; import megamek.client.ui.baseComponents.MMComboBox; @@ -49,6 +36,32 @@ import mekhq.gui.enums.ForceIconOperationalStatusStyle; import mekhq.gui.enums.PersonnelFilterStyle; +import javax.swing.*; +import javax.swing.GroupLayout.Alignment; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * MHQOptionsDialog is a dialog that allows the user to configure various options in MegaMekHQ. + * It extends the {@link AbstractMHQButtonDialog} class and inherits its common dialog features. + * The dialog allows configuration of options related to display, colors, fonts, autosave, + * startup behavior, notifications, and various other miscellaneous options. + *

+ * To create an instance of MHQOptionsDialog, invoke one of its constructors with a frame as a parameter. + *

+ * Example Usage: + * JFrame frame = new JFrame("Main Frame"); + * MHQOptionsDialog dialog = new MHQOptionsDialog(frame); + * dialog.setVisible(true); + *

+ * This dialog uses the following Mnemonics: C, D, M, M, S, U, W, Y + */ public class MHQOptionsDialog extends AbstractMHQButtonDialog { private static final MMLogger logger = MMLogger.create(MHQOptionsDialog.class); @@ -162,6 +175,7 @@ public class MHQOptionsDialog extends AbstractMHQButtonDialog { private JCheckBox optionOutstandingScenariosNag; private JCheckBox optionInvalidFactionNag; private JCheckBox optionUnableToAffordExpensesNag; + private JCheckBox optionUnableToAffordLoanPaymentNag; private JCheckBox optionUnableToAffordJumpNag; // endregion Nag Tab @@ -1008,6 +1022,12 @@ private JPanel createNagTab() { .setToolTipText(resources.getString("optionUnableToAffordExpensesNag.toolTipText")); optionUnableToAffordExpensesNag.setName("optionUnableToAffordExpensesNag"); + optionUnableToAffordLoanPaymentNag = new JCheckBox( + resources.getString("optionUnableToAffordLoanPaymentNag.text")); + optionUnableToAffordLoanPaymentNag + .setToolTipText(resources.getString("optionUnableToAffordLoanPaymentNag.toolTipText")); + optionUnableToAffordLoanPaymentNag.setName("optionUnableToAffordLoanPaymentNag"); + optionUnableToAffordJumpNag = new JCheckBox(resources.getString("optionUnableToAffordJumpNag.text")); optionUnableToAffordJumpNag .setToolTipText(resources.getString("optionUnableToAffordJumpNag.toolTipText")); @@ -1037,6 +1057,7 @@ private JPanel createNagTab() { .addComponent(optionOutstandingScenariosNag) .addComponent(optionInvalidFactionNag) .addComponent(optionUnableToAffordExpensesNag) + .addComponent(optionUnableToAffordLoanPaymentNag) .addComponent(optionUnableToAffordJumpNag)); layout.setHorizontalGroup( @@ -1055,6 +1076,7 @@ private JPanel createNagTab() { .addComponent(optionOutstandingScenariosNag) .addComponent(optionInvalidFactionNag) .addComponent(optionUnableToAffordExpensesNag) + .addComponent(optionUnableToAffordLoanPaymentNag) .addComponent(optionUnableToAffordJumpNag)); return panel; @@ -1359,6 +1381,8 @@ protected void okAction() { optionInvalidFactionNag.isSelected()); MekHQ.getMHQOptions().setNagDialogIgnore(MHQConstants.NAG_UNABLE_TO_AFFORD_EXPENSES, optionUnableToAffordExpensesNag.isSelected()); + MekHQ.getMHQOptions().setNagDialogIgnore(MHQConstants.NAG_UNABLE_TO_AFFORD_LOAN_PAYMENT, + optionUnableToAffordLoanPaymentNag.isSelected()); MekHQ.getMHQOptions().setNagDialogIgnore(MHQConstants.NAG_UNABLE_TO_AFFORD_JUMP, optionUnableToAffordJumpNag.isSelected()); @@ -1505,6 +1529,8 @@ private void setInitialState() { MekHQ.getMHQOptions().getNagDialogIgnore(MHQConstants.NAG_INVALID_FACTION)); optionUnableToAffordExpensesNag.setSelected( MekHQ.getMHQOptions().getNagDialogIgnore(MHQConstants.NAG_UNABLE_TO_AFFORD_EXPENSES)); + optionUnableToAffordLoanPaymentNag.setSelected( + MekHQ.getMHQOptions().getNagDialogIgnore(MHQConstants.NAG_UNABLE_TO_AFFORD_LOAN_PAYMENT)); optionUnableToAffordJumpNag.setSelected( MekHQ.getMHQOptions().getNagDialogIgnore(MHQConstants.NAG_UNABLE_TO_AFFORD_JUMP)); diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/EndContractNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/EndContractNagDialog.java index 89948d1c68..9654f48da5 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/EndContractNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/EndContractNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - 2024 The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -21,24 +21,59 @@ import mekhq.MHQConstants; import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.mission.Contract; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; -import java.time.temporal.ChronoUnit; +import java.time.LocalDate; +/** + * This class represents a nag dialog displayed on the day a contract ends + * It extends the {@link AbstractMHQNagDialog} class. + */ public class EndContractNagDialog extends AbstractMHQNagDialog { - private static boolean isContractEnded (Campaign campaign) { - // we can use 'is date y after x', as once the end date has been passed, + private static String DIALOG_NAME = "EndContractNagDialog"; + private static String DIALOG_TITLE = "EndContractNagDialog.title"; + private static String DIALOG_BODY = "EndContractNagDialog.text"; + + /** + * Checks if a contract within a campaign has ended on the current date. + * + * @param campaign the {@link Campaign} containing the contracts to be checked + * @return {@code true} if a contract within the campaign has ended on the current date, + * {@code false} otherwise + */ + static boolean isContractEnded(Campaign campaign) { + LocalDate today = campaign.getLocalDate(); + + // we can't use 'is date y after x', as once the end date has been passed, // the contract is removed from the list of active contracts - return campaign.getActiveContracts().stream() - .anyMatch(contract -> ChronoUnit.DAYS.between(campaign.getLocalDate(), contract.getEndingDate()) == 0); + + // there is no reason to use a stream here, as there won't be enough iterations to warrant it + for (Contract contract : campaign.getActiveContracts()) { + if (contract.getEndingDate().equals(today)) { + return true; + } + } + + return false; } + /** + * Creates a new instance of the {@link EndContractNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public EndContractNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "EndContractNagDialog", "EndContractNagDialog.title", - "EndContractNagDialog.text", campaign, MHQConstants.NAG_CONTRACT_ENDED); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_CONTRACT_ENDED); } + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialog.java index 28ba8da0e6..488c37001a 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,47 +18,85 @@ */ package mekhq.gui.dialog.nagDialogs; -import mekhq.MekHQ; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; - +/** + * This class represents a nag dialog displayed when a campaign's Astech time deficit is greater than 0. + * It extends the {@link AbstractMHQNagDialog} class. + */ public class InsufficientAstechTimeNagDialog extends AbstractMHQNagDialog { - //region Constructors - public InsufficientAstechTimeNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "InsufficientAstechTimeNagDialog", "InsufficientAstechTimeNagDialog.title", - "", campaign, MHQConstants.NAG_INSUFFICIENT_ASTECH_TIME); - } - //endregion Constructors + private static String DIALOG_NAME = "InsufficientAstechTimeNagDialog"; + private static String DIALOG_TITLE = "InsufficientAstechTimeNagDialog.title"; + private static String DIALOG_BODY = "InsufficientAstechTimeNagDialog.text"; - @Override - protected boolean checkNag() { - if (MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - || !getCampaign().getCampaignOptions().isCheckMaintenance()) { - return false; - } + /** + * Checks if the given campaign has a deficit in Astech time. + * + * @param campaign the {@link Campaign} to check for an Astech time deficit + * @return {@code true} if the campaign has a deficit in Astech time, {@code false} otherwise + */ + static boolean checkAstechTimeDeficit(Campaign campaign) { + return getAstechTimeDeficit(campaign) > 0; + } + /** + * Calculates the time deficit of Astechs needed for a given campaign. + *

+ * The method calculates the Astech time deficit by determining the number of Astechs + * required to support maintenance for all valid units in the hangar of the campaign. + * + * @param campaign the {@link Campaign} for which to calculate the Astech time deficit + * @return the Astech time deficit, rounded up to the nearest whole number + */ + static int getAstechTimeDeficit(Campaign campaign) { // Units are only valid if they are maintained, present, and not self crewed (as the crew - // maintain it in that case). For each unit this is valid for, we need six astechs to assist - // the tech for the maintenance. - final int need = getCampaign().getHangar().getUnitsStream() + // maintain it in that case). + // For each unit, this is valid for; we need six astechs to help the tech for the maintenance. + final int need = campaign.getHangar().getUnitsStream() .filter(unit -> !unit.isUnmaintained() && unit.isPresent() && !unit.isSelfCrewed()) .mapToInt(unit -> unit.getMaintenanceTime() * 6).sum(); - int available = getCampaign().getPossibleAstechPoolMinutes(); - if (getCampaign().isOvertimeAllowed()) { - available += getCampaign().getPossibleAstechPoolOvertime(); + int available = campaign.getPossibleAstechPoolMinutes(); + if (campaign.isOvertimeAllowed()) { + available += campaign.getPossibleAstechPoolOvertime(); } - if (available < need) { - final int astechsNeeded = (int) Math.ceil((need - available) / (double) Person.PRIMARY_ROLE_SUPPORT_TIME); - setDescription(String.format(resources.getString("InsufficientAstechTimeNagDialog.text"), astechsNeeded)); + return (int) Math.ceil((need - available) / (double) Person.PRIMARY_ROLE_SUPPORT_TIME); + } + + //region Constructors + /** + * Creates a new instance of the {@link InsufficientAstechTimeNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ + public InsufficientAstechTimeNagDialog(final JFrame frame, final Campaign campaign) { + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_INSUFFICIENT_ASTECH_TIME); + } + //endregion Constructors + + /** + * Checks if the Astech time deficit is greater than zero. + * If the count is greater than zero and the Nag dialog for the current key is not ignored, + * it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ + @Override + protected boolean checkNag() { + if (!MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && checkAstechTimeDeficit(getCampaign())) { + setDescription(String.format( + resources.getString(DIALOG_BODY), + getAstechTimeDeficit(getCampaign()))); return true; - } else { - return false; } + + return false; } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialog.java index 12f688591b..9e50a061a2 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,33 +18,60 @@ */ package mekhq.gui.dialog.nagDialogs; -import mekhq.MekHQ; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +/** + * This class represents a nag dialog displayed when a campaign's Astech deficit is greater than 0. + * It extends the {@link AbstractMHQNagDialog} class. + */ public class InsufficientAstechsNagDialog extends AbstractMHQNagDialog { + private static String DIALOG_NAME = "InsufficientAstechsNagDialog"; + private static String DIALOG_TITLE = "InsufficientAstechsNagDialog.title"; + private static String DIALOG_BODY = "InsufficientAstechsNagDialog.text"; + + /** + * Checks if the count of Astechs needed is greater than zero. + * If so, it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ + static boolean checkAstechsNeededCount(Campaign campaign) { + final int need = campaign.getAstechNeed(); + return need > 0; + } + //region Constructors + /** + * Creates a new instance of the {@link InsufficientAstechsNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public InsufficientAstechsNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "InsufficientAstechsNagDialog", "InsufficientAstechsNagDialog.title", - "", campaign, MHQConstants.NAG_INSUFFICIENT_ASTECHS); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_INSUFFICIENT_ASTECHS); } //endregion Constructors + /** + * Checks if the count of Astechs needed is greater than zero. + * If the count is greater than zero and the Nag dialog for the current key is not ignored, + * it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ @Override protected boolean checkNag() { - if (MekHQ.getMHQOptions().getNagDialogIgnore(getKey())) { - return false; - } - - final int need = getCampaign().getAstechNeed(); - if (need > 0) { - setDescription(String.format(resources.getString("InsufficientAstechsNagDialog.text"), need)); + if (!MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && checkAstechsNeededCount(getCampaign())) { + setDescription(String.format( + resources.getString(DIALOG_BODY), + getCampaign().getAstechNeed())); return true; - } else { - return false; } + + return false; } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialog.java index 2f7a71f7d5..8e2836d6ed 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,33 +18,59 @@ */ package mekhq.gui.dialog.nagDialogs; -import mekhq.MekHQ; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +/** + * This class represents a nag dialog displayed when a campaign's Medic deficit is greater than 0. + * It extends the {@link AbstractMHQNagDialog} class. + */ public class InsufficientMedicsNagDialog extends AbstractMHQNagDialog { + private static String DIALOG_NAME = "InsufficientMedicsNagDialog"; + private static String DIALOG_TITLE = "InsufficientMedicsNagDialog.title"; + private static String DIALOG_BODY = "InsufficientMedicsNagDialog.text"; + + /** + * Checks if the count of Medics needed is greater than zero. + * If so, it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ + static boolean checkMedicsNeededCount(Campaign campaign) { + return campaign.getMedicsNeed() > 0; + } + //region Constructors + /** + * Creates a new instance of the {@link InsufficientAstechsNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public InsufficientMedicsNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "InsufficientMedicsNagDialog", "InsufficientMedicsNagDialog.title", - "", campaign, MHQConstants.NAG_INSUFFICIENT_MEDICS); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_INSUFFICIENT_MEDICS); } //endregion Constructors + /** + * Checks if the count of Medics needed is greater than zero. + * If the count is greater than zero and the Nag dialog for the current key is not ignored, + * it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ @Override protected boolean checkNag() { - if (MekHQ.getMHQOptions().getNagDialogIgnore(getKey())) { - return false; - } - - final int need = getCampaign().getMedicsNeed(); - if (need > 0) { - setDescription(String.format(resources.getString("InsufficientMedicsNagDialog.text"), need)); + if (!MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && checkMedicsNeededCount(getCampaign())) { + setDescription(String.format( + resources.getString(DIALOG_BODY), + getCampaign().getAstechNeed())); return true; - } else { - return false; } + + return false; } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialog.java index d3e501c089..b9a853c89b 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -28,8 +28,22 @@ import java.time.LocalDate; import java.util.Objects; +/** + * This class represents a nag dialog displayed when a campaign's faction has become extinct. + * It extends the {@link AbstractMHQNagDialog} class. + */ public class InvalidFactionNagDialog extends AbstractMHQNagDialog { - private static boolean isFactionInvalid (Campaign campaign) { + private static String DIALOG_NAME = "InvalidFactionNagDialog"; + private static String DIALOG_TITLE = "InvalidFactionNagDialog.title"; + private static String DIALOG_BODY = "InvalidFactionNagDialog.text"; + + /** + * Checks if the given campaign's faction is valid. + * + * @param campaign the campaign to check + * @return {@code true} if the campaign's faction is invalid, {@code false} otherwise + */ + static boolean isFactionInvalid(Campaign campaign) { Faction campaignFaction = campaign.getFaction(); if (!campaign.getFaction().validIn(campaign.getLocalDate())) { @@ -40,27 +54,61 @@ private static boolean isFactionInvalid (Campaign campaign) { // FS and LA campaigns won't trigger the above conditional, because those factions aren't technically ended when the FedSuns forms // they just become dormant. if (Objects.equals(campaignFaction.getShortName(), "LA")) { - // the dates picked are chosen as these are when mhq does the bulk of the faction ownership transfers - return ((campaign.getLocalDate().isAfter(LocalDate.of(3040, 1, 18))) - && (campaign.getLocalDate().isBefore(LocalDate.of(3067, 4, 20)))); + return lyranAllianceSpecialHandler(campaign); } // this is another special handler for FedSuns as they're the main culprit behind the issue of users having invalid factions. if (Objects.equals(campaignFaction.getShortName(), "FS")) { - return ((campaign.getLocalDate().isAfter(LocalDate.of(3040, 1, 18))) - && (campaign.getLocalDate().isBefore(LocalDate.of(3057, 9, 18)))); + return federatedSunsSpecialHandler(campaign); } return false; } + /** + * Checks if the given campaign falls within the inactive date range of the Federated Suns. + * + * @param campaign The current campaign. + * @return Returns {@code true} if the campaign falls within the active date range, otherwise {@code false}. + */ + static boolean federatedSunsSpecialHandler(Campaign campaign) { + boolean isAfterActiveDate = campaign.getLocalDate().isAfter(LocalDate.of(3040, 1, 18)); + boolean isBeforeInactiveDate = campaign.getLocalDate().isBefore(LocalDate.of(3057, 9, 18)); + + return isAfterActiveDate && isBeforeInactiveDate; + } + + /** + * Checks if the given campaign falls within the inactive date range of the Lyran Alliance. + * + * @param campaign The current campaign. + * @return Returns {@code true} if the campaign falls within the active date range, otherwise {@code false}. + */ + static boolean lyranAllianceSpecialHandler(Campaign campaign) { + // the dates picked are chosen as these are when mhq does the bulk of the faction ownership transfers + boolean isAfterActiveDate = campaign.getLocalDate().isAfter(LocalDate.of(3040, 1, 18)); + boolean isBeforeInactiveDate = campaign.getLocalDate().isBefore(LocalDate.of(3067, 4, 20)); + + return isAfterActiveDate && isBeforeInactiveDate; + } + //region Constructors + /** + * Creates a new instance of the {@link InvalidFactionNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public InvalidFactionNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "InvalidFactionNagDialog", "InvalidFactionNagDialog.title", - "InvalidFactionNagDialog.text", campaign, MHQConstants.NAG_INVALID_FACTION); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_INVALID_FACTION); } - //endregion Constructors + + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) && (isFactionInvalid(getCampaign())); diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialog.java index 9e24e4c555..61d8458bb4 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - 2024 The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -25,16 +25,41 @@ import javax.swing.*; +/** + * This class represents a nag dialog displayed when the campaign has no assigned commander + * It extends the {@link AbstractMHQNagDialog} class. + */ public class NoCommanderNagDialog extends AbstractMHQNagDialog { - private static boolean isCommanderMissing (Campaign campaign) { + private static String DIALOG_NAME = "NoCommanderNagDialog"; + private static String DIALOG_TITLE = "NoCommanderNagDialog.title"; + private static String DIALOG_BODY = "NoCommanderNagDialog.text"; + + + /** + * Checks if the given {@link Campaign} is missing a commander. + * + * @param campaign the campaign to check for a missing commander + * @return {@code true} if the campaign is missing a commander, otherwise {@code false} + */ + static boolean isCommanderMissing (Campaign campaign) { return (campaign.getFlaggedCommander() == null); } + /** + * Creates a new instance of the {@link EndContractNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public NoCommanderNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "NoCommanderNagDialog", "NoCommanderNagDialog.title", - "NoCommanderNagDialog.text", campaign, MHQConstants.NAG_NO_COMMANDER); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_NO_COMMANDER); } + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialog.java index 3513d2b07b..69a4f9deea 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,30 +18,71 @@ */ package mekhq.gui.dialog.nagDialogs; -import mekhq.MekHQ; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.mission.AtBContract; +import mekhq.campaign.mission.AtBScenario; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +import java.time.LocalDate; +import java.util.List; +/** + * This class represents a nag dialog displayed when the campaign one or more unresolved scenarios. + * It extends the {@link AbstractMHQNagDialog} class. + */ public class OutstandingScenariosNagDialog extends AbstractMHQNagDialog { + private static String DIALOG_NAME = "OutstandingScenariosNagDialog"; + private static String DIALOG_TITLE = "OutstandingScenariosNagDialog.title"; + private static String DIALOG_BODY = "OutstandingScenariosNagDialog.text"; + + /** + * Checks if there are any outstanding scenarios in the given campaign. + * An outstanding scenario is defined as a scenario whose date is the same as the current date. + * + * @param campaign the campaign to check for outstanding scenarios + * @return {@code true} if there are outstanding scenarios, {@code false} otherwise + */ + static boolean checkForOutstandingScenarios(Campaign campaign) { + List activeContracts = campaign.getActiveAtBContracts(true); + + LocalDate today = campaign.getLocalDate(); + + for (AtBContract contract : activeContracts) { + for (AtBScenario scenario : contract.getCurrentAtBScenarios()) { + LocalDate scenarioDate = scenario.getDate(); + + if (scenarioDate.equals(today)) { + return true; + } + } + } + + return false; + } + //region Constructors + /** + * Creates a new instance of the {@link OutstandingScenariosNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public OutstandingScenariosNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "OutstandingScenariosNagDialog", "OutstandingScenariosNagDialog.title", - "OutstandingScenariosNagDialog.text", campaign, - MHQConstants.NAG_OUTSTANDING_SCENARIOS); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_OUTSTANDING_SCENARIOS); } //endregion Constructors + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { - // If this isn't ignored, check all active AtB contracts for current AtB scenarios whose - // date is today return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - && getCampaign().getActiveAtBContracts(true).stream() - .anyMatch(contract -> contract.getCurrentAtBScenarios().stream() - .anyMatch(scenario -> (scenario.getDate() != null) - && scenario.getDate().isEqual(getCampaign().getLocalDate()))); + && checkForOutstandingScenarios(getCampaign()); } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialog.java index 11302d3cc2..89d7bfbffe 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -21,29 +21,69 @@ import mekhq.MHQConstants; import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.force.Force; +import mekhq.campaign.mission.Mission; import mekhq.campaign.personnel.Person; +import mekhq.campaign.unit.Unit; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +/** + * This class represents a nag dialog displayed when the campaign has an active mission and has one + * or more pregnant personnel assigned to the TOE + * It extends the {@link AbstractMHQNagDialog} class. + */ public class PregnantCombatantNagDialog extends AbstractMHQNagDialog { - private static boolean isPregnantCombatant(Campaign campaign) { + private static String DIALOG_NAME = "PregnantCombatantNagDialog"; + private static String DIALOG_TITLE = "PregnantCombatantNagDialog.title"; + private static String DIALOG_BODY = "PregnantCombatantNagDialog.text"; + + /** + * Checks if there is a pregnant combatant in the provided campaign. Combatants are defined as + * personnel assigned to a {@link Unit} in the TO&E during an active {@link Mission} + * + * @param campaign the campaign to check + * @return {@code true} if there is a pregnant combatant, {@code false} otherwise + */ + static boolean isPregnantCombatant(Campaign campaign) { if (campaign.getActiveMissions(false).isEmpty()) { return false; } - return campaign.getActivePersonnel().stream() - .filter(Person::isPregnant) - .anyMatch(p -> ((p.getUnit() != null) && (p.getUnit().getForceId() != -1))); + // there is no reason to use a stream here, as there won't be enough iterations to warrant it + for (Person person : campaign.getActivePersonnel()) { + if (person.isPregnant()) { + Unit unit = person.getUnit(); + + if (unit != null) { + if (unit.getForceId() != Force.FORCE_NONE) { + return true; + } + } + } + } + + return false; } //region Constructors + /** + * Creates a new instance of the {@link PregnantCombatantNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public PregnantCombatantNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "PregnantCombatantNagDialog", "PregnantCombatantNagDialog.title", - "PregnantCombatantNagDialog.text", campaign, MHQConstants.NAG_PREGNANT_COMBATANT); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_PREGNANT_COMBATANT); } //endregion Constructors + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/PrisonersNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/PrisonersNagDialog.java index 096d2ec598..32616835b5 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/PrisonersNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/PrisonersNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -21,24 +21,52 @@ import mekhq.MHQConstants; import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.mission.Mission; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +/** + * This class represents a nag dialog displayed when the campaign has retained prisoners of war + * outside an active {@link Mission} + * It extends the {@link AbstractMHQNagDialog} class. + */ public class PrisonersNagDialog extends AbstractMHQNagDialog { - private static boolean hasPrisoners (Campaign campaign) { + private static String DIALOG_NAME = "PrisonersNagDialog"; + private static String DIALOG_TITLE = "PrisonersNagDialog.title"; + private static String DIALOG_BODY = "PrisonersNagDialog.text"; + + /** + * Checks if the given campaign has any prisoners. + * + * @param campaign the campaign to check for prisoners + * @return {@code true} if the campaign has prisoners, {@code false} otherwise + */ + static boolean hasPrisoners (Campaign campaign) { if (!campaign.hasActiveContract()) { return !campaign.getCurrentPrisoners().isEmpty(); } + return false; } + //region Constructors + /** + * Creates a new instance of the {@link PrisonersNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public PrisonersNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "PrisonersNagDialog", "PrisonersNagDialog.title", - "PrisonersNagDialog.text", campaign, MHQConstants.NAG_PRISONERS); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_PRISONERS); } - //endregion Constructors + + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) && hasPrisoners(getCampaign()); diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialog.java index f558f1f64b..a008f9c424 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,31 +18,71 @@ */ package mekhq.gui.dialog.nagDialogs; -import mekhq.MekHQ; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.mission.AtBContract; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; import java.time.DayOfWeek; +/** + * This class represents a nag dialog displayed when the campaign does not meet the deployment + * levels required by their active {@link AtBContract} + * It extends the {@link AbstractMHQNagDialog} class. + */ public class ShortDeploymentNagDialog extends AbstractMHQNagDialog { + private static String DIALOG_NAME = "ShortDeploymentNagDialog"; + private static String DIALOG_TITLE = "ShortDeploymentNagDialog.title"; + private static String DIALOG_BODY = "ShortDeploymentNagDialog.text"; + + /** + * Checks if the deployment requirements are met for a given campaign. + * + * @param campaign the campaign to check the deployment requirements for + * @return {@code true} if the deployment requirements are met, {@code false} otherwise + */ + static boolean checkDeploymentRequirementsMet(Campaign campaign) { + if (!campaign.getLocation().isOnPlanet()) { + return false; + } + + // this prevents the nag from spamming daily + if (campaign.getLocalDate().getDayOfWeek() != DayOfWeek.SUNDAY) { + return false; + } + + // There is no need to use a stream here, as the number of iterations doesn't warrant it. + for (AtBContract contract : campaign.getActiveAtBContracts()) { + if (campaign.getDeploymentDeficit(contract) > 0) { + return true; + } + } + + return false; + } + //region Constructors + /** + * Creates a new instance of the {@link ShortDeploymentNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public ShortDeploymentNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "ShortDeploymentNagDialog", "ShortDeploymentNagDialog.title", - "ShortDeploymentNagDialog.text", campaign, MHQConstants.NAG_SHORT_DEPLOYMENT); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_SHORT_DEPLOYMENT); } //endregion Constructors + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { - if (MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - || !getCampaign().getLocation().isOnPlanet() - || (getCampaign().getLocalDate().getDayOfWeek() != DayOfWeek.SUNDAY)) { - return false; - } - - return getCampaign().getActiveAtBContracts().stream() - .anyMatch(contract -> getCampaign().getDeploymentDeficit(contract) > 0); + return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && checkDeploymentRequirementsMet(getCampaign()); } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialog.java index 1d468df5d5..bf62fb31db 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -27,25 +27,71 @@ import javax.swing.*; +/** + * This class represents a nag dialog displayed when the campaign does not have enough funds to + * cover monthly expenses + * It extends the {@link AbstractMHQNagDialog} class. + */ public class UnableToAffordExpensesNagDialog extends AbstractMHQNagDialog { - private static boolean isUnableToAffordExpenses (Campaign campaign) { - FinancialReport financialReport = FinancialReport.calculate(campaign); + private static String DIALOG_NAME = "UnableToAffordExpensesNagDialog"; + private static String DIALOG_TITLE = "UnableToAffordExpensesNagDialog.title"; + private static String DIALOG_BODY = "UnableToAffordExpensesNagDialog.text"; - Money deficit = financialReport.getTotalLiabilities().plus(financialReport.getMonthlyExpenses()); + /** + * Determines whether the given campaign is unable to afford its monthly expenses. + * + * @param campaign the ongoing campaign + * @return {@code true} if the campaign's funds are less than the total deficit, {@code false} otherwise + */ + static boolean isUnableToAffordExpenses (Campaign campaign) { + Money deficit = getMonthlyExpenses(campaign); + // check if the campaign's funds are less than the total deficit return campaign.getFunds().isLessThan(deficit); } + /** + * Calculates and returns the monthly expenses of a given campaign. + * + * @param campaign the campaign for which to calculate the monthly expenses + * @return the monthly expenses as a {@link Money} object + */ + static Money getMonthlyExpenses(Campaign campaign) { + // calculate a financial report which includes the monthly expenses + FinancialReport financialReport = FinancialReport.calculate(campaign); + + // get the total monthly expenses + return financialReport.getMonthlyExpenses(); + } + //region Constructors + /** + * Creates a new instance of the {@link ShortDeploymentNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public UnableToAffordExpensesNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "UnableToAffordExpensesNagDialog", "UnableToAffordExpensesNagDialog.title", - "UnableToAffordExpensesNagDialog.text", campaign, MHQConstants.NAG_UNABLE_TO_AFFORD_EXPENSES); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_UNABLE_TO_AFFORD_EXPENSES); } //endregion Constructors + /** + * Checks if the campaign is able to afford its monthly expenses. + * If the campaign is unable to afford monthly expenses and the Nag dialog for the current key + * is not ignored, it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ @Override protected boolean checkNag() { - return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - && isUnableToAffordExpenses(getCampaign()); + if (!MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && isUnableToAffordExpenses(getCampaign())) { + setDescription(String.format( + resources.getString(DIALOG_BODY), + getMonthlyExpenses(getCampaign()).toAmountAndSymbolString())); + return true; + } + + return false; } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialog.java index dc04fd84d1..df9648df95 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -21,25 +21,77 @@ import mekhq.MHQConstants; import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.finances.Money; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +/** + * This class represents a nag dialog displayed when the campaign can't afford its next jump + * It extends the {@link AbstractMHQNagDialog} class. + */ public class UnableToAffordJumpNagDialog extends AbstractMHQNagDialog { - private static boolean isUnableToAffordNextJump (Campaign campaign) { - return campaign.getFunds().isLessThan(campaign.calculateCostPerJump(true, campaign.getCampaignOptions().isEquipmentContractBase())); + private static String DIALOG_NAME = "UnableToAffordJumpNagDialog"; + private static String DIALOG_TITLE = "UnableToAffordJumpNagDialog.title"; + private static String DIALOG_BODY = "UnableToAffordJumpNagDialog.text"; + + /** + * Checks if the campaign is unable to afford the cost of the next jump. + * + * @param campaign The campaign for which to check the affordability of the next jump. + * @return {@code true} if the campaign is unable to afford the cost of the next jump, {@code false} otherwise. + */ + static boolean isUnableToAffordNextJump (Campaign campaign) { + Money currentFunds = campaign.getFunds(); + Money nextJumpCost = getNextJumpCost(campaign); + + return currentFunds.isLessThan(nextJumpCost); + } + + /** + * Calculates the cost of the next jump for a given campaign. + *

+ * This method determines the cost of the next jump by using the campaign's + * options and calculates the cost per jump based on whether the contract pays + * based on the value of the units in the campaign's TO&E. + * + * @param campaign the campaign for which to calculate the next jump cost + * @return the cost of the next jump for the campaign + */ + static Money getNextJumpCost(Campaign campaign) { + boolean isContractPayBasedOnToeUnitsValue = campaign.getCampaignOptions().isEquipmentContractBase(); + + return campaign.calculateCostPerJump(true, isContractPayBasedOnToeUnitsValue); } //region Constructors + /** + * Creates a new instance of the {@link UnableToAffordJumpNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public UnableToAffordJumpNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "UnableToAffordJumpNagDialog", "UnableToAffordJumpNagDialog.title", - "UnableToAffordJumpNagDialog.text", campaign, MHQConstants.NAG_UNABLE_TO_AFFORD_JUMP); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_UNABLE_TO_AFFORD_JUMP); } //endregion Constructors + /** + * Checks if the campaign is able to afford its next jump. + * If the campaign is unable to afford its next jump and the Nag dialog for the current key is + * not ignored, it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ @Override protected boolean checkNag() { - return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - && isUnableToAffordNextJump(getCampaign()); + if (!MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && isUnableToAffordNextJump(getCampaign())) { + setDescription(String.format( + resources.getString(DIALOG_BODY), + getNextJumpCost(getCampaign()).toAmountAndSymbolString())); + return true; + } + + return false; } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordLoanPaymentNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordLoanPaymentNagDialog.java new file mode 100644 index 0000000000..0b86ce9ae7 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnableToAffordLoanPaymentNagDialog.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021-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.gui.dialog.nagDialogs; + +import mekhq.MHQConstants; +import mekhq.MekHQ; +import mekhq.campaign.Campaign; +import mekhq.campaign.finances.Loan; +import mekhq.campaign.finances.Money; +import mekhq.gui.baseComponents.AbstractMHQNagDialog; + +import javax.swing.*; +import java.time.LocalDate; +import java.util.List; + +/** + * This class represents a nag dialog displayed when the campaign cannot afford its next loan payment + * It extends the {@link AbstractMHQNagDialog} class. + */ +public class UnableToAffordLoanPaymentNagDialog extends AbstractMHQNagDialog { + private static String DIALOG_NAME = "UnableToAffordLoanPaymentNagDialog"; + private static String DIALOG_TITLE = "UnableToAffordLoanPaymentNagDialog.title"; + private static String DIALOG_BODY = "UnableToAffordLoanPaymentNagDialog.text"; + + /** + * Determines if the campaign is unable to afford its due loan payments. + * + * @param campaign the campaign for which the loan payment affordability needs to be checked + * @return {@code true} if the campaign is unable to afford the loan payment, {@code false} otherwise + */ + static boolean isUnableToAffordLoanPayment(Campaign campaign) { + Money totalPaymentsDue = getTotalPaymentsDue(campaign); + + // check if the campaign's funds are less than the total payments due tomorrow + return campaign.getFunds().isLessThan(totalPaymentsDue); + } + + /** + * Calculates the total payments due tomorrow, across all current loans + * + * @param campaign the campaign for which to calculate the total payments due + * @return the total payments due as a {@link Money} object + */ + static Money getTotalPaymentsDue(Campaign campaign) { + // gets the list of the campaign's current loans + List loans = campaign.getFinances().getLoans(); + + // gets tomorrow's date + LocalDate tomorrow = campaign.getLocalDate().plusDays(1); + + // initialize the total loan payment due tomorrow as zero + Money totalPaymentsDue = Money.zero(); + + // iterate over all loans + for (Loan loan : loans) { + // if a loan payment is due tomorrow, add its payment amount to the total payments due + if (loan.getNextPayment().equals(tomorrow)) { + totalPaymentsDue = totalPaymentsDue.plus(loan.getPaymentAmount()); + } + } + return totalPaymentsDue; + } + + //region Constructors + /** + * Creates a new instance of the {@link UnableToAffordLoanPaymentNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ + public UnableToAffordLoanPaymentNagDialog(final JFrame frame, final Campaign campaign) { + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_UNABLE_TO_AFFORD_LOAN_PAYMENT); + } + //endregion Constructors + + /** + * Checks if the campaign is able to afford its next loan payment. + * If the campaign is unable to afford its next loan payment and the Nag dialog for the current + * key is not ignored, it sets the description using the specified format and returns {@code true}. + * Otherwise, it returns {@code false}. + */ + @Override + protected boolean checkNag() { + if (!MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && isUnableToAffordLoanPayment(getCampaign())) { + setDescription(String.format( + resources.getString(DIALOG_BODY), + getTotalPaymentsDue(getCampaign()).toAmountAndSymbolString())); + return true; + } + + return false; + } +} diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialog.java index a80792d50b..6ef957e934 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -26,9 +26,23 @@ import javax.swing.*; +/** + * A dialog that displays a nag message if there are unmaintained units in the campaign's hangar. + * It extends the {@link AbstractMHQNagDialog} class. + */ public class UnmaintainedUnitsNagDialog extends AbstractMHQNagDialog { - private boolean checkHanger() { - for (Unit u : getCampaign().getHangar().getUnits()) { + private static String DIALOG_NAME = "UnmaintainedUnitsNagDialog"; + private static String DIALOG_TITLE = "UnmaintainedUnitsNagDialog.title"; + private static String DIALOG_BODY = "UnmaintainedUnitsNagDialog.text"; + + /** + * Checks if there are any unmaintained units in the given campaign's hangar. + * + * @param campaign the {@link Campaign} containing the hangar to check + * @return {@code true} if there are unmaintained units in the hangar, {@code false} otherwise + */ + static boolean checkHanger(Campaign campaign) { + for (Unit u : campaign.getHangar().getUnits()) { if ((u.isUnmaintained()) && (!u.isSalvage())) { return true; } @@ -36,16 +50,26 @@ private boolean checkHanger() { return false; } + /** + * Creates a new instance of the {@link UnmaintainedUnitsNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ //region Constructors public UnmaintainedUnitsNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "UnmaintainedUnitsNagDialog", "UnmaintainedUnitsNagDialog.title", - "UnmaintainedUnitsNagDialog.text", campaign, MHQConstants.NAG_UNMAINTAINED_UNITS); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_UNMAINTAINED_UNITS); } //endregion Constructors + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - && checkHanger(); + && checkHanger(getCampaign()); } } diff --git a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialog.java b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialog.java index 2d017eee0c..b1483beb1a 100644 --- a/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved. * * This file is part of MekHQ. * @@ -18,36 +18,102 @@ */ package mekhq.gui.dialog.nagDialogs; -import mekhq.MekHQ; import mekhq.MHQConstants; +import mekhq.MekHQ; import mekhq.campaign.Campaign; -import mekhq.campaign.stratcon.StratconRulesManager; +import mekhq.campaign.mission.AtBContract; +import mekhq.campaign.stratcon.StratconScenario; +import mekhq.campaign.stratcon.StratconScenario.ScenarioState; +import mekhq.campaign.stratcon.StratconTrackState; import mekhq.gui.baseComponents.AbstractMHQNagDialog; import javax.swing.*; +/** + * This class represents a nag dialog displayed when the campaign has outstanding StratCon contacts + * It extends the {@link AbstractMHQNagDialog} class. + */ public class UnresolvedStratConContactsNagDialog extends AbstractMHQNagDialog { + private static String DIALOG_NAME = "UnresolvedStratConContactsNagDialog"; + private static String DIALOG_TITLE = "UnresolvedStratConContactsNagDialog.title"; + private static String DIALOG_BODY = "UnresolvedStratConContactsNagDialog.text"; + + /** + * Checks if the given campaign has unresolved contact nags. + * + * @param campaign the campaign to check for unresolved contacts + * @return a string indicating whether the campaign has unresolved contacts or not + */ + boolean hasUnresolvedContacts(Campaign campaign) { + String unresolvedContacts = nagUnresolvedContacts(campaign); + + if (unresolvedContacts.isEmpty()) { + return false; + } else { + setDescription(String.format(resources.getString(DIALOG_BODY), unresolvedContacts)); + return true; + } + } + + /** + * Determine whether the user should be nagged about unresolved scenarios on AtB + * StratCon tracks. + * + * @param campaign Campaign to check. + * @return An informative string containing the reasons the user was nagged. + */ + static String nagUnresolvedContacts(Campaign campaign) { + if (!campaign.getCampaignOptions().isUseStratCon()) { + return ""; + } + + StringBuilder unresolvedContacts = new StringBuilder(); + + // check every track attached to an active contract for unresolved scenarios + // to which the player must deploy forces today + for (AtBContract contract : campaign.getActiveAtBContracts()) { + if (contract.getStratconCampaignState() == null) { + continue; + } + + for (StratconTrackState track : contract.getStratconCampaignState().getTracks()) { + // "scenario name, track name" + for (StratconScenario scenario : track.getScenarios().values()) { + if ((scenario.getCurrentState() == ScenarioState.UNRESOLVED) + && (campaign.getLocalDate().equals(scenario.getDeploymentDate()))) { + String resolvedScenario = String.format("%s, %s\n", + scenario.getName(), + track.getDisplayableName()); + + unresolvedContacts.append(resolvedScenario); + } + } + } + } + + return unresolvedContacts.toString(); + } + //region Constructors + /** + * Creates a new instance of the {@link UnresolvedStratConContactsNagDialog} class. + * + * @param frame the parent JFrame for the dialog + * @param campaign the {@link Campaign} associated with the dialog + */ public UnresolvedStratConContactsNagDialog(final JFrame frame, final Campaign campaign) { - super(frame, "UnresolvedStratConContactsNagDialog", "UnresolvedStratConContactsNagDialog.title", - "", campaign, MHQConstants.NAG_UNRESOLVED_STRATCON_CONTACTS); + super(frame, DIALOG_NAME, DIALOG_TITLE, DIALOG_BODY, campaign, MHQConstants.NAG_UNRESOLVED_STRATCON_CONTACTS); } //endregion Constructors + /** + * Checks if there is a nag message to display. + * + * @return {@code true} if there is a nag message to display, {@code false} otherwise + */ @Override protected boolean checkNag() { - if (MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) - || !getCampaign().getCampaignOptions().isUseStratCon()) { - return false; - } - - final String text = StratconRulesManager.nagUnresolvedContacts(getCampaign()); - - if (text.isEmpty()) { - return false; - } else { - setDescription(String.format(resources.getString("UnresolvedStratConContactsNagDialog.text"), text)); - return true; - } + return !MekHQ.getMHQOptions().getNagDialogIgnore(getKey()) + && hasUnresolvedContacts(getCampaign()); } } diff --git a/MekHQ/unittests/mekhq/campaign/event/DayEndingEventTest.java b/MekHQ/unittests/mekhq/campaign/event/DayEndingEventTest.java new file mode 100644 index 0000000000..39e8c78e79 --- /dev/null +++ b/MekHQ/unittests/mekhq/campaign/event/DayEndingEventTest.java @@ -0,0 +1,46 @@ +/* + * 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.event; + +import mekhq.campaign.Campaign; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * This class is responsible for unit testing the cancellation of {@link DayEndingEvent}. + */ +class DayEndingEventTest { + /** + * Unit test to verify if a {@link DayEndingEvent} can be canceled. + */ + @Test + void checkDayEndingEventCancellable() { + // Creates a mock instance of the Campaign class. + Campaign mockCampaign = mock(Campaign.class); + + // Creates a new DayEndingEvent associated with the mock Campaign. + DayEndingEvent dayEndingEvent = new DayEndingEvent(mockCampaign); + + // Asserts that the isCancellable() method of the created DayEndingEvent returns true. + // If it does not, this test will fail. + assertTrue(dayEndingEvent.isCancellable()); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/EndContractNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/EndContractNagDialogTest.java new file mode 100644 index 0000000000..8993646200 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/EndContractNagDialogTest.java @@ -0,0 +1,99 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.mission.Contract; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.EndContractNagDialog.isContractEnded; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link EndContractNagDialog} class. + * It contains test methods for various scenarios related to contract expiration. + */ +public class EndContractNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private LocalDate today; + private Contract contract1, contract2; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + today = LocalDate.now(); + contract1 = mock(Contract.class); + contract2 = mock(Contract.class); + + // When the Campaign mock calls 'getLocalDate()' return today's date + when(campaign.getLocalDate()).thenReturn(today); + } + + // In the following tests the isContractEnded() method is called, and its response is + // checked against expected behavior + + @Test + void noActiveContracts() { + when(campaign.getActiveContracts()).thenReturn(new ArrayList<>()); + assertFalse(isContractEnded(campaign)); + } + + @Test + void oneActiveContractEndsTomorrow() { + when(campaign.getActiveContracts()).thenReturn(List.of(contract1)); + when(contract1.getEndingDate()).thenReturn(today.plusDays(1)); + assertFalse(isContractEnded(campaign)); + } + + @Test + void oneActiveContractEndsToday() { + when(campaign.getActiveContracts()).thenReturn(List.of(contract1)); + when(contract1.getEndingDate()).thenReturn(today); + assertTrue(isContractEnded(campaign)); + } + + @Test + void twoActiveContractsOneEndsTomorrowOneEndsToday() { + when(campaign.getActiveContracts()).thenReturn(List.of(contract1, contract2)); + when(contract1.getEndingDate()).thenReturn(today.plusDays(1)); + when(contract2.getEndingDate()).thenReturn(today); + assertTrue(isContractEnded(campaign)); + } + + @Test + void twoActiveContractsBothEndToday() { + when(campaign.getActiveContracts()).thenReturn(List.of(contract1, contract2)); + when(contract1.getEndingDate()).thenReturn(today); + assertTrue(isContractEnded(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialogTest.java new file mode 100644 index 0000000000..084ceb73e0 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientAstechTimeNagDialogTest.java @@ -0,0 +1,291 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.Hangar; +import mekhq.campaign.unit.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.stream.Stream; + +import static mekhq.gui.dialog.nagDialogs.InsufficientAstechTimeNagDialog.checkAstechTimeDeficit; +import static mekhq.gui.dialog.nagDialogs.InsufficientAstechTimeNagDialog.getAstechTimeDeficit; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class contains test cases for the {@link InsufficientAstechTimeNagDialog} class. + * It tests the different combinations of Astech requirements and verifies the behavior of the + * {@code getAstechTimeDeficit()} and {@code checkAstechTimeDeficit()} methods. + */ +class InsufficientAstechTimeNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private Hangar hangar; + private Unit unit1, unit2; + private int possibleAstechMinutes; + private int possibleAstechOvertimeMinutes; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + hangar = mock(Hangar.class); + unit1 = mock(Unit.class); + unit2 = mock(Unit.class); + possibleAstechMinutes = 2880; // This is the equivalent of one full team + possibleAstechOvertimeMinutes = 1440; // As is this + + // Stub the getHangar() method to return the hangar mock + when(campaign.getHangar()).thenReturn(hangar); + } + + /** + * Initializes a unit with the given parameters. + * + * @param unit the unit to be initialized + * @param isUnmaintained a boolean indicating if the unit is unmaintained + * @param isPresent a boolean indicating if the unit is present + * @param isSelfCrewed a boolean indicating if the unit is self-crewed + * @param maintenanceTime the maintenance time for the unit + */ + private void initiateUnit(Unit unit, boolean isUnmaintained, boolean isPresent, boolean isSelfCrewed, int maintenanceTime) { + when(unit.isUnmaintained()).thenReturn(isUnmaintained); + when(unit.isPresent()).thenReturn(isPresent); + when(unit.isSelfCrewed()).thenReturn(isSelfCrewed); + when(unit.getMaintenanceTime()).thenReturn(maintenanceTime); + } + + /** + * Processes units and Astech time based on the given parameters. + * + * @param isOvertimeAllowed {@code true} if overtime is allowed, {@code false} otherwise + */ + private void processUnitsAndAstechTime(boolean isOvertimeAllowed) { + // Prepare a stream of the unit mocks + Stream unitStream = Stream.of(unit1, unit2); + + // Stub the getUnitsStream() method to return the stream of unit mocks + when(hangar.getUnitsStream()).thenReturn(unitStream); + + // Calculate possible Astech Minutes + when(campaign.getPossibleAstechPoolMinutes()).thenReturn(possibleAstechMinutes); + + // Calculate overtime minutes + when(campaign.isOvertimeAllowed()).thenReturn(isOvertimeAllowed); + when(campaign.getPossibleAstechPoolOvertime()).thenReturn(possibleAstechOvertimeMinutes); + } + + // In the following tests the getAstechTimeDeficit() method is called, and its response is + // checked against expected behavior + + @Test + void testAstechTimeDeficitCalculationNoOvertime() { + // Initiate Units + initiateUnit(unit1, false, true, false, 60); + initiateUnit(unit2, false, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-4, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationWithOvertime() { + // Initiate Units + initiateUnit(unit1, false, true, false, 60); + initiateUnit(unit2, false, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(true); + + // Assert results equals expected value + assertEquals(-7, getAstechTimeDeficit(campaign)); + } + + @Test + void testInsufficientAstechTimeDeficitCalculationNoOvertime() { + // Initiate Units + initiateUnit(unit1, false, true, false, 6000); + initiateUnit(unit2, false, true, false, 6000); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(144, getAstechTimeDeficit(campaign)); + } + + @Test + void testInsufficientAstechTimeDeficitCalculationWithInsufficientOvertime() { + // Initiate Units + initiateUnit(unit1, false, true, false, 6000); + initiateUnit(unit2, false, true, false, 6000); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(true); + + // Assert results equals expected value + assertEquals(141, getAstechTimeDeficit(campaign)); + } + + @Test + void testInsufficientAstechTimeDeficitCalculationWithSufficientOvertime() { + // Initiate Units + initiateUnit(unit1, false, true, false, 300); + initiateUnit(unit2, false, true, false, 300); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(true); + + // Assert results equals expected value + assertEquals(-1, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationOneUnitUnmaintained() { + // Initiate Units + initiateUnit(unit1, true, true, false, 60); + initiateUnit(unit2, false, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-5, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationTwoUnitsUnmaintained() { + // Initiate Units + initiateUnit(unit1, true, true, false, 60); + initiateUnit(unit2, true, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-6, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationOneUnitAbsent() { + // Initiate Units + initiateUnit(unit1, false, false, false, 60); + initiateUnit(unit2, false, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-5, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationTwoUnitsAbsent() { + // Initiate Units + initiateUnit(unit1, false, false, false, 60); + initiateUnit(unit2, false, false, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-6, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationOneUnitSelfCrewed() { + // Initiate Units + initiateUnit(unit1, false, true, true, 60); + initiateUnit(unit2, false, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-5, getAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCalculationTwoUnitsSelfCrewed() { + // Initiate Units + initiateUnit(unit1, false, true, true, 60); + initiateUnit(unit2, false, true, true, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertEquals(-6, getAstechTimeDeficit(campaign)); + } + + // In the following tests the checkAstechTimeDeficit() method is called, and its response is + // checked against expected behavior + + @Test + void testAstechTimeDeficitCheckNegativeDeficit() { + // Initiate Units + initiateUnit(unit1, false, true, false, 60); + initiateUnit(unit2, false, true, false, 60); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertFalse(checkAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCheckPositiveDeficit() { + // Initiate Units + initiateUnit(unit1, false, true, false, 6000); + initiateUnit(unit2, false, true, false, 6000); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertTrue(checkAstechTimeDeficit(campaign)); + } + + @Test + void testAstechTimeDeficitCheckZeroDeficit() { + // Initiate Units + initiateUnit(unit1, false, true, false, 240); + initiateUnit(unit2, false, true, false, 240); + + // Stream Units and process Astech Time + processUnitsAndAstechTime(false); + + // Assert results equals expected value + assertFalse(checkAstechTimeDeficit(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialogTest.java new file mode 100644 index 0000000000..8664a38a28 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientAstechsNagDialogTest.java @@ -0,0 +1,69 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mekhq.gui.dialog.nagDialogs.InsufficientAstechsNagDialog.checkAstechsNeededCount; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class contains test cases for the {@link InsufficientAstechsNagDialog} class. + * It tests the different combinations of Astech requirements and verifies the behavior of the + * {@code checkAstechsNeededCount()} method. + */ +class InsufficientAstechsNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + campaign = mock(Campaign.class); + } + + // In the following tests the checkAstechsNeededCount() method is called, and its response is + // checked against expected behavior + + @Test + void noAstechsNeeded() { + when(campaign.getAstechNeed()).thenReturn(0); + assertFalse(checkAstechsNeededCount(campaign)); + } + + @Test + void oneAstechNeeded() { + when(campaign.getAstechNeed()).thenReturn(1); + assertTrue(checkAstechsNeededCount(campaign)); + } + + @Test + void negativeAstechsNeeded() { + when(campaign.getAstechNeed()).thenReturn(-1); + assertFalse(checkAstechsNeededCount(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialogTest.java new file mode 100644 index 0000000000..00a3a9fdbd --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InsufficientMedicsNagDialogTest.java @@ -0,0 +1,69 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mekhq.gui.dialog.nagDialogs.InsufficientMedicsNagDialog.checkMedicsNeededCount; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class contains test cases for the {@link InsufficientMedicsNagDialogTest} class. + * It tests the different combinations of Medic requirements and verifies the behavior of the + * {@code checkMedicsNeededCount()} method. + */ +class InsufficientMedicsNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + campaign = mock(Campaign.class); + } + + // In the following tests the checkMedicsNeededCount() method is called, and its response is + // checked against expected behavior + + @Test + void noMedicsNeeded() { + when(campaign.getMedicsNeed()).thenReturn(0); + assertFalse(checkMedicsNeededCount(campaign)); + } + + @Test + void oneMedicNeeded() { + when(campaign.getMedicsNeed()).thenReturn(1); + assertTrue(checkMedicsNeededCount(campaign)); + } + + @Test + void negativeMedicsNeeded() { + when(campaign.getMedicsNeed()).thenReturn(-1); + assertFalse(checkMedicsNeededCount(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialogTest.java new file mode 100644 index 0000000000..5316f020ed --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/InvalidFactionNagDialogTest.java @@ -0,0 +1,130 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.universe.Faction; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static mekhq.gui.dialog.nagDialogs.InvalidFactionNagDialog.federatedSunsSpecialHandler; +import static mekhq.gui.dialog.nagDialogs.InvalidFactionNagDialog.isFactionInvalid; +import static mekhq.gui.dialog.nagDialogs.InvalidFactionNagDialog.lyranAllianceSpecialHandler; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link InvalidFactionNagDialog} class. + *

+ * It tests the different combinations of unit states and verifies the behavior of the + * {@code isFactionInvalid()}, {@code lyranAllianceSpecialHandler()}, and + * {@code federatedSunsSpecialHandler()} methods. + */ +class InvalidFactionNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private Faction faction; + private LocalDate dateValid; + private LocalDate dateInvalid; + + /** + * Sets up the necessary dependencies and configurations before running the test methods. + * Runs once before all tests + */ + @BeforeEach + public void setup() { + // Initialize the mock objects + campaign = mock(Campaign.class); + faction = mock(Faction.class); + + dateValid = LocalDate.of(3151, 1, 1); + dateInvalid = LocalDate.of(1936, 1, 1); + + // When the Campaign mock calls 'getFaction()' return the mocked faction + when(campaign.getFaction()).thenReturn(faction); + } + + // In the following tests, the beginning of the isFactionInvalid() method is called, and its + // response is checked against expected behavior + + @Test + public void validDate() { + when(faction.validIn(dateValid)).thenReturn(true); + when(campaign.getLocalDate()).thenReturn(dateValid); + + assertFalse(isFactionInvalid(campaign)); + } + + @Test + public void invalidDate() { + when(faction.validIn(dateInvalid)).thenReturn(false); + when(campaign.getLocalDate()).thenReturn(dateInvalid); + + assertTrue(isFactionInvalid(campaign)); + } + + // In the following tests, the lyranAllianceSpecialHandler() method is called, and its response + // is checked against expected behavior + + @Test + public void lyranAllianceSpecialHandlerInvalidDate() { + when(campaign.getLocalDate()).thenReturn(LocalDate.of(3067, 4, 19)); + assertTrue(lyranAllianceSpecialHandler(campaign)); + } + + @Test + public void lyranAllianceSpecialHandlerBeforeActiveDate() { + when(campaign.getLocalDate()).thenReturn(LocalDate.of(3040, 1, 17)); + assertFalse(lyranAllianceSpecialHandler(campaign)); + } + + @Test + public void lyranAllianceSpecialHandlerAfterInactiveDate() { + when(campaign.getLocalDate()).thenReturn(LocalDate.of(3067, 4, 21)); + assertFalse(lyranAllianceSpecialHandler(campaign)); + } + + // In the following tests, the federatedSunsSpecialHandler() method is called, and its response + // is checked against expected behavior + + @Test + public void federatedSunsSpecialHandlerInvalidDate() { + when(campaign.getLocalDate()).thenReturn(LocalDate.of(3057, 9, 17)); + + assertTrue(federatedSunsSpecialHandler(campaign)); + } + + @Test + public void federatedSunsSpecialHandlerBeforeActiveDate() { + when(campaign.getLocalDate()).thenReturn(LocalDate.of(3040, 1, 17)); + + assertFalse(federatedSunsSpecialHandler(campaign)); + } + + @Test + public void federatedSunsSpecialHandlerAfterInactiveDate() { + when(campaign.getLocalDate()).thenReturn(LocalDate.of(3057, 9, 19)); + + assertFalse(federatedSunsSpecialHandler(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialogTest.java new file mode 100644 index 0000000000..37f725ede8 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/NoCommanderNagDialogTest.java @@ -0,0 +1,68 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.personnel.Person; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mekhq.gui.dialog.nagDialogs.NoCommanderNagDialog.isCommanderMissing; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link NoCommanderNagDialog} class. + * It contains test methods for various scenarios related to commander assignment. + */ +class NoCommanderNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private Person commander; + private Person commanderNull; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + commander = mock(Person.class); + commanderNull = null; + } + + // In the following tests the isCommanderMissing() method is called, and its response is checked + // against expected behavior + + @Test + void commanderPresent() { + when(campaign.getFlaggedCommander()).thenReturn(commander); + assertFalse(isCommanderMissing(campaign)); + } + + @Test + void commanderMissing() { + when(campaign.getFlaggedCommander()).thenReturn(commanderNull); + assertTrue(isCommanderMissing(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialogTest.java new file mode 100644 index 0000000000..a7a5f8e6b5 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/OutstandingScenariosNagDialogTest.java @@ -0,0 +1,130 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.mission.AtBContract; +import mekhq.campaign.mission.AtBScenario; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.OutstandingScenariosNagDialog.checkForOutstandingScenarios; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link OutstandingScenariosNagDialog} class. + * It contains tests for various scenarios related to the {@code checkForOutstandingScenarios} method + */ +class OutstandingScenariosNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private AtBContract contract; + private AtBScenario scenario1, scenario2; + private LocalDate today; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + contract = mock(AtBContract.class); + scenario1 = mock(AtBScenario.class); + scenario2 = mock(AtBScenario.class); + today = LocalDate.now(); + + // When the Campaign mock calls 'getLocalDate()' return today's date + when(campaign.getLocalDate()).thenReturn(today); + } + + /** + * Initializes an {@link AtBContract} containing two instances of {@link AtBScenario}. + */ + private void initializeContractWithTwoScenarios() { + when(campaign.getActiveAtBContracts(true)).thenReturn(List.of(contract)); + when(contract.getCurrentAtBScenarios()).thenReturn(List.of(scenario1, scenario2)); + } + + // In the following tests the checkForOutstandingScenarios() method is called, and its response + // is checked against expected behavior + + @Test + void noContracts() { + when(campaign.getActiveAtBContracts(true)).thenReturn(new ArrayList<>()); + + assertFalse(checkForOutstandingScenarios(campaign)); + } + + @Test + void noScenarios() { + when(campaign.getActiveAtBContracts(true)).thenReturn(List.of(contract)); + when(contract.getCurrentAtBScenarios()).thenReturn(new ArrayList<>()); + + assertFalse(checkForOutstandingScenarios(campaign)); + } + + @Test + void noOutstandingScenarios() { + initializeContractWithTwoScenarios(); + + when(scenario1.getDate()).thenReturn(today.plusDays(1)); + when(scenario2.getDate()).thenReturn(today.plusDays(1)); + + assertFalse(checkForOutstandingScenarios(campaign)); + } + + @Test + void oneOutstandingScenarioFirst() { + initializeContractWithTwoScenarios(); + + when(scenario1.getDate()).thenReturn(today); + when(scenario2.getDate()).thenReturn(today.plusDays(1)); + + assertTrue(checkForOutstandingScenarios(campaign)); + } + + @Test + void oneOutstandingScenarioSecond() { + initializeContractWithTwoScenarios(); + + when(scenario1.getDate()).thenReturn(today.plusDays(1)); + when(scenario2.getDate()).thenReturn(today); + + assertTrue(checkForOutstandingScenarios(campaign)); + } + + @Test + void twoOutstandingScenarios() { + initializeContractWithTwoScenarios(); + + when(scenario1.getDate()).thenReturn(today); + when(scenario2.getDate()).thenReturn(today); + + assertTrue(checkForOutstandingScenarios(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialogTest.java new file mode 100644 index 0000000000..343aa7987d --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/PregnantCombatantNagDialogTest.java @@ -0,0 +1,116 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.force.Force; +import mekhq.campaign.mission.Mission; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.unit.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.PregnantCombatantNagDialog.isPregnantCombatant; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link PregnantCombatantNagDialog} class. + * It contains tests for various scenarios related to the {@code isPregnantCombatant} method + */ +class PregnantCombatantNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private Mission mission; + private Person personNotPregnant; + private Person personPregnant; + private Unit unit; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + mission = mock(Mission.class); + personNotPregnant = mock(Person.class); + personPregnant = mock(Person.class); + unit = mock(Unit.class); + + // Stubs + when(personNotPregnant.isPregnant()).thenReturn(false); + when(personPregnant.isPregnant()).thenReturn(true); + } + + // In the following tests the isPregnantCombatant() method is called, and its response is + // checked against expected behavior + + @Test + void noActiveMission() { + when(campaign.getActiveMissions(false)).thenReturn(new ArrayList<>()); + assertFalse(isPregnantCombatant(campaign)); + } + + @Test + void activeMissionsNoPregnancy() { + when(campaign.getActiveMissions(false)).thenReturn(List.of(mission)); + when(campaign.getActivePersonnel()).thenReturn(List.of(personNotPregnant)); + + assertFalse(isPregnantCombatant(campaign)); + } + + @Test + void activeMissionsPregnancyNoUnit() { + when(campaign.getActiveMissions(false)).thenReturn(List.of(mission)); + when(campaign.getActivePersonnel()).thenReturn(List.of(personNotPregnant, personPregnant)); + + when(personPregnant.getUnit()).thenReturn(null); + + assertFalse(isPregnantCombatant(campaign)); + } + + @Test + void activeMissionsPregnancyNoForce() { + when(campaign.getActiveMissions(false)).thenReturn(List.of(mission)); + when(campaign.getActivePersonnel()).thenReturn(List.of(personNotPregnant, personPregnant)); + + when(personPregnant.getUnit()).thenReturn(unit); + when(unit.getForceId()).thenReturn(Force.FORCE_NONE); + + assertFalse(isPregnantCombatant(campaign)); + } + + @Test + void activeMissionsPregnancyYesUnitYesForce() { + when(campaign.getActiveMissions(false)).thenReturn(List.of(mission)); + when(campaign.getActivePersonnel()).thenReturn(List.of(personNotPregnant, personPregnant)); + + when(personPregnant.getUnit()).thenReturn(unit); + when(unit.getForceId()).thenReturn(1); + + assertTrue(isPregnantCombatant(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/PrisonersNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/PrisonersNagDialogTest.java new file mode 100644 index 0000000000..7e8af2e374 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/PrisonersNagDialogTest.java @@ -0,0 +1,80 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.personnel.Person; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.PrisonersNagDialog.hasPrisoners; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link PrisonersNagDialog} class. + * It contains tests for various scenarios related to the {@code hasPrisoners} method + */ +class PrisonersNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + } + + // In the following tests the hasPrisoners() method is called, and its response is checked + // against expected behavior + + @Test + void activeContract() { + when(campaign.hasActiveContract()).thenReturn(true); + + assertFalse(hasPrisoners(campaign)); + } + + @Test + void noActiveContractNoPrisoners() { + when(campaign.hasActiveContract()).thenReturn(false); + when(campaign.getCurrentPrisoners()).thenReturn(new ArrayList<>()); + + assertFalse(hasPrisoners(campaign)); + } + + @Test + void noActiveContractPrisoners() { + Person prisoner = mock(Person.class); + + when(campaign.hasActiveContract()).thenReturn(false); + when(campaign.getCurrentPrisoners()).thenReturn(List.of(prisoner)); + + assertTrue(hasPrisoners(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialogTest.java new file mode 100644 index 0000000000..25ab03cd9b --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/ShortDeploymentNagDialogTest.java @@ -0,0 +1,131 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.CurrentLocation; +import mekhq.campaign.mission.AtBContract; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.ShortDeploymentNagDialog.checkDeploymentRequirementsMet; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link ShortDeploymentNagDialog} class. + * It contains tests for various scenarios related to the {@code checkDeploymentRequirementsMet} + * method + */ +public class ShortDeploymentNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + // I know 'location' can be converted to a local variable, but it makes sense to keep all the + // mock objects in one place + private CurrentLocation location; + private LocalDate monday, sunday; + private AtBContract contract; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + + location = mock(CurrentLocation.class); + + monday = LocalDate.of(2024, 10, 7); + sunday = LocalDate.of(2024, 10, 6); + + contract = mock(AtBContract.class); + + // Stubs + when(campaign.getLocation()).thenReturn(location); + } + + // In the following tests the checkDeploymentRequirementsMet() method is called, and its + // response is checked against expected behavior + + @Test + void notOnPlanet() { + when(campaign.getLocation().isOnPlanet()).thenReturn(false); + + assertFalse(checkDeploymentRequirementsMet(campaign)); + } + + @Test + void notSunday() { + when(campaign.getLocation().isOnPlanet()).thenReturn(true); + when(campaign.getLocalDate()).thenReturn(monday); + + assertFalse(checkDeploymentRequirementsMet(campaign)); + } + + @Test + void noContract() { + when(campaign.getLocation().isOnPlanet()).thenReturn(true); + when(campaign.getLocalDate()).thenReturn(sunday); + + when(campaign.getActiveAtBContracts()).thenReturn(new ArrayList<>()); + + assertFalse(checkDeploymentRequirementsMet(campaign)); + } + + @Test + void noDeploymentDeficit() { + when(campaign.getLocation().isOnPlanet()).thenReturn(true); + when(campaign.getLocalDate()).thenReturn(sunday); + + when(campaign.getActiveAtBContracts()).thenReturn(List.of(contract)); + when(campaign.getDeploymentDeficit(contract)).thenReturn(0); + + assertFalse(checkDeploymentRequirementsMet(campaign)); + } + + @Test + void negativeDeploymentDeficit() { + when(campaign.getLocation().isOnPlanet()).thenReturn(true); + when(campaign.getLocalDate()).thenReturn(sunday); + + when(campaign.getActiveAtBContracts()).thenReturn(List.of(contract)); + when(campaign.getDeploymentDeficit(contract)).thenReturn(-3); + + assertFalse(checkDeploymentRequirementsMet(campaign)); + } + + @Test + void positiveDeploymentDeficit() { + when(campaign.getLocation().isOnPlanet()).thenReturn(true); + when(campaign.getLocalDate()).thenReturn(sunday); + + when(campaign.getActiveAtBContracts()).thenReturn(List.of(contract)); + when(campaign.getDeploymentDeficit(contract)).thenReturn(1); + + assertTrue(checkDeploymentRequirementsMet(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialogTest.java new file mode 100644 index 0000000000..f74dce5be0 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordExpensesNagDialogTest.java @@ -0,0 +1,98 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.CampaignOptions; +import mekhq.campaign.Hangar; +import mekhq.campaign.Warehouse; +import mekhq.campaign.finances.Finances; +import mekhq.campaign.finances.FinancialReport; +import mekhq.campaign.finances.Money; +import mekhq.campaign.unit.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mekhq.gui.dialog.nagDialogs.UnableToAffordExpensesNagDialog.isUnableToAffordExpenses; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link UnableToAffordExpensesNagDialog} class. + * It contains tests for various scenarios related to the {@code isUnableToAffordExpenses} method + */ +class UnableToAffordExpensesNagDialogTest { + // Mock objects for the tests + // I know some of these can be converted to a local variable, but it makes sense to keep all the + // mock objects in one place + private Campaign campaign; + private CampaignOptions campaignOptions; + private Finances finances; + private Unit unit; + private Hangar hangar; + private Warehouse warehouse; + private FinancialReport report; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + campaignOptions = mock(CampaignOptions.class); + + finances = mock(Finances.class); + + unit = mock(Unit.class); + hangar = mock(Hangar.class); + hangar.addUnit(unit); + + warehouse = mock(Warehouse.class); + + report = mock(FinancialReport.class); + + // Stubs + when(campaign.getFinances()).thenReturn(finances); + when(campaign.getHangar()).thenReturn(hangar); + when(campaign.getWarehouse()).thenReturn(warehouse); + when(campaign.getCampaignOptions()).thenReturn(campaignOptions); + } + + // In the following tests the isUnableToAffordExpenses() method is called, and its response is + // checked against expected behavior + + @Test + void canAffordExpenses() { + when(campaign.getFunds()).thenReturn(Money.of(2)); + when(report.getMonthlyExpenses()).thenReturn(Money.of(1)); + + assertFalse(isUnableToAffordExpenses(campaign)); + } + + @Test + void cannotAffordExpenses() { + when(campaign.getFunds()).thenReturn(Money.of(1)); + when(report.getMonthlyExpenses()).thenReturn(Money.of(2)); + + assertFalse(isUnableToAffordExpenses(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialogTest.java new file mode 100644 index 0000000000..70cbe2cb4f --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordJumpNagDialogTest.java @@ -0,0 +1,75 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.CampaignOptions; +import mekhq.campaign.finances.Money; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mekhq.gui.dialog.nagDialogs.UnableToAffordJumpNagDialog.getNextJumpCost; +import static mekhq.gui.dialog.nagDialogs.UnableToAffordJumpNagDialog.isUnableToAffordNextJump; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link UnableToAffordJumpNagDialog} class. + * It contains tests for various scenarios related to the {@code isUnableToAffordNextJump} method + */ +class UnableToAffordJumpNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private CampaignOptions options; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + options = mock(CampaignOptions.class); + + // Stubs + when(campaign.getCampaignOptions()).thenReturn(options); + } + + // In the following tests the canAffordNextJump() method is called, and its response is checked + // against expected behavior + + @Test + void canAffordNextJump() { + when(campaign.getFunds()).thenReturn(Money.of(5)); + when(getNextJumpCost(campaign)).thenReturn(Money.of(1)); + + assertFalse(isUnableToAffordNextJump(campaign)); + } + + @Test + void cannotAffordNextJump() { + when(campaign.getFunds()).thenReturn(Money.of(1)); + when(getNextJumpCost(campaign)).thenReturn(Money.of(5)); + + assertTrue(isUnableToAffordNextJump(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordLoanPaymentNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordLoanPaymentNagDialogTest.java new file mode 100644 index 0000000000..0bea2932fa --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnableToAffordLoanPaymentNagDialogTest.java @@ -0,0 +1,137 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.finances.Finances; +import mekhq.campaign.finances.Loan; +import mekhq.campaign.finances.Money; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.UnableToAffordLoanPaymentNagDialog.getTotalPaymentsDue; +import static mekhq.gui.dialog.nagDialogs.UnableToAffordLoanPaymentNagDialog.isUnableToAffordLoanPayment; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link UnableToAffordLoanPaymentNagDialog} class. + * It contains tests for various scenarios related to the {@code getTotalPaymentsDue} and + * {@code isUnableToAffordLoanPayment} methods + */ +class UnableToAffordLoanPaymentNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private LocalDate today; + private Finances finances; + private Loan firstLoan, secondLoan; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + today = LocalDate.now(); + finances = mock(Finances.class); + firstLoan = mock(Loan.class); + secondLoan = mock(Loan.class); + + // Stubs + when(campaign.getFinances()).thenReturn(finances); + when(campaign.getLocalDate()).thenReturn(today); + + when(firstLoan.getPaymentAmount()).thenReturn(Money.of(5)); + when(secondLoan.getPaymentAmount()).thenReturn(Money.of(5)); + } + + /** + * Initializes the loans with the specified number of days till the next payment. + * + * @param daysTillFirstLoan The number of days till the next payment for the first loan. + * @param daysTillSecondLoan The number of days till the next payment for the second loan. + */ + private void initializeLoans(int daysTillFirstLoan, int daysTillSecondLoan) { + when(finances.getLoans()).thenReturn(List.of(firstLoan, secondLoan)); + + when(firstLoan.getNextPayment()).thenReturn(today.plusDays(daysTillFirstLoan)); + when(secondLoan.getNextPayment()).thenReturn(today.plusDays(daysTillSecondLoan)); + } + + // In the following tests the getTotalPaymentsDue() method is called, and its response + // is checked against expected behavior + + @Test + void noLoans() { + when(finances.getLoans()).thenReturn(new ArrayList<>()); + + assertEquals(Money.zero(), getTotalPaymentsDue(campaign)); + } + + @Test + void noLoanDueTomorrow() { + initializeLoans(2, 2); + + assertEquals(Money.zero(), getTotalPaymentsDue(campaign)); + } + + @Test + void oneLoanDueTomorrow() { + initializeLoans(2, 1); + + assertEquals(Money.of(5), getTotalPaymentsDue(campaign)); + } + + @Test + void twoLoansDueTomorrow() { + initializeLoans(1, 1); + + assertEquals(Money.of(10), getTotalPaymentsDue(campaign)); + } + + // In the following tests the canAffordLoans() method is called, and its response is checked + // against expected behavior + + @Test + void canAffordLoans() { + initializeLoans(2, 1); + + when(campaign.getFunds()).thenReturn(Money.of(10)); + + assertFalse(isUnableToAffordLoanPayment(campaign)); + } + + @Test + void cannotAffordLoans() { + initializeLoans(1, 1); + + when(campaign.getFunds()).thenReturn(Money.of(5)); + + assertTrue(isUnableToAffordLoanPayment(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialogTest.java new file mode 100644 index 0000000000..63b2911e6e --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnmaintainedUnitsNagDialogTest.java @@ -0,0 +1,142 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.Hangar; +import mekhq.campaign.unit.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static mekhq.gui.dialog.nagDialogs.UnmaintainedUnitsNagDialog.checkHanger; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link UnmaintainedUnitsNagDialog} class. + * It tests the different combinations of unit states and verifies the behavior of the {@code checkHanger()} method. + */ +class UnmaintainedUnitsNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private Hangar hangar; + private Unit mockUnit1, mockUnit2; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + hangar = mock(Hangar.class); + mockUnit1 = mock(Unit.class); + mockUnit2 = mock(Unit.class); + + // When the Campaign mock calls 'getHangar()' return the 'hangar' mock + when(campaign.getHangar()).thenReturn(hangar); + } + + /** + * Initializes the units by setting their maintenance status and salvage status. + * + * @param unit1Unmaintained A boolean indicating whether the first unit is unmaintained. + * @param unit1Salvage A boolean indicating whether the first unit is salvage. + * @param unit2Unmaintained A boolean indicating whether the second unit is unmaintained. + * @param unit2Salvage A boolean indicating whether the second unit is salvage. + */ + private void initializeUnits(boolean unit1Unmaintained, boolean unit1Salvage, boolean unit2Unmaintained, boolean unit2Salvage) { + when(mockUnit1.isUnmaintained()).thenReturn(unit1Unmaintained); + when(mockUnit1.isSalvage()).thenReturn(unit1Salvage); + + when(mockUnit2.isUnmaintained()).thenReturn(unit2Unmaintained); + when(mockUnit2.isSalvage()).thenReturn(unit2Salvage); + + List units = List.of(mockUnit1, mockUnit2); + when(hangar.getUnits()).thenReturn(units); + } + + // In the following tests the checkHanger() method is called, and its response is checked + // against expected behavior + + @Test + void unmaintainedUnitExistsUnit1() { + initializeUnits(true, false, false, false); + assertTrue(checkHanger(campaign)); + } + + @Test + void unmaintainedUnitExistsUnit2() { + initializeUnits(false, false, true, false); + assertTrue(checkHanger(campaign)); + } + + @Test + void unmaintainedUnitExistsButSalvageUnit1() { + initializeUnits(true, true, true, false); + assertTrue(checkHanger(campaign)); + } + + @Test + void unmaintainedUnitExistsButSalvageUnit2() { + initializeUnits(true, false, true, true); + assertTrue(checkHanger(campaign)); + } + + @Test + void unmaintainedUnitExistsButSalvageMixed() { + initializeUnits(false, true, true, false); + assertTrue(checkHanger(campaign)); + } + + @Test + void noUnmaintainedUnitExistsNoSalvage() { + initializeUnits(false, false, false, false); + assertFalse(checkHanger(campaign)); + } + + @Test + void noUnmaintainedUnitExistsAllSalvage() { + initializeUnits(false, true, false, true); + assertFalse(checkHanger(campaign)); + } + + @Test + void noUnmaintainedUnitExistsButSalvageUnit1() { + initializeUnits(false, true, false, false); + assertFalse(checkHanger(campaign)); + } + + @Test + void noUnmaintainedUnitExistsButSalvageUnit2() { + initializeUnits(false, false, false, true); + assertFalse(checkHanger(campaign)); + } + + @Test + void noUnmaintainedUnitExistsButSalvageMixed() { + initializeUnits(false, true, false, false); + assertFalse(checkHanger(campaign)); + } +} diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialogTest.java new file mode 100644 index 0000000000..b62254caf8 --- /dev/null +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UnresolvedStratConContactsNagDialogTest.java @@ -0,0 +1,150 @@ +/* + * 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.gui.dialog.nagDialogs; + +import mekhq.campaign.Campaign; +import mekhq.campaign.CampaignOptions; +import mekhq.campaign.mission.AtBContract; +import mekhq.campaign.stratcon.StratconCampaignState; +import mekhq.campaign.stratcon.StratconCoords; +import mekhq.campaign.stratcon.StratconScenario; +import mekhq.campaign.stratcon.StratconScenario.ScenarioState; +import mekhq.campaign.stratcon.StratconTrackState; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.*; + +import static mekhq.gui.dialog.nagDialogs.UnresolvedStratConContactsNagDialog.nagUnresolvedContacts; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class is a test class for the {@link UnresolvedStratConContactsNagDialog} class. + * It contains tests for various scenarios related to the {@code nagUnresolvedContacts} method + */ +public class UnresolvedStratConContactsNagDialogTest { + // Mock objects for the tests + private Campaign campaign; + private LocalDate today; + private CampaignOptions options; + private AtBContract contract; + private StratconCampaignState stratconCampaignState; + private StratconTrackState track; + + private StratconCoords coordinates; + private StratconScenario scenarioNotDue, scenarioDue; + + /** + * Test setup for each test, runs before each test. + * Initializes the mock objects and sets up the necessary mock behaviors. + */ + @BeforeEach + void init() { + // Initialize the mock objects + campaign = mock(Campaign.class); + options = mock(CampaignOptions.class); + today = LocalDate.now(); + + contract = mock(AtBContract.class); + + stratconCampaignState = mock(StratconCampaignState.class); + track = mock(StratconTrackState.class); + + coordinates = mock(StratconCoords.class); + + scenarioNotDue = mock(StratconScenario.class); + scenarioDue = mock(StratconScenario.class); + + // Stubs + when(campaign.getCampaignOptions()).thenReturn(options); + when(campaign.getLocalDate()).thenReturn(today); + + when(contract.getStratconCampaignState()).thenReturn(stratconCampaignState); + when(stratconCampaignState.getTracks()).thenReturn(Collections.singletonList(track)); + + when(options.isUseStratCon()).thenReturn(true); + when(campaign.getActiveAtBContracts()).thenReturn(List.of(contract)); + + when(scenarioDue.getCurrentState()).thenReturn(ScenarioState.UNRESOLVED); + when(scenarioDue.getDeploymentDate()).thenReturn(today); + when(scenarioDue.getName()).thenReturn("Scenario Due"); + + when(scenarioNotDue.getCurrentState()).thenReturn(ScenarioState.UNRESOLVED); + when(scenarioNotDue.getDeploymentDate()).thenReturn(today.plusDays(1)); + when(scenarioNotDue.getName()).thenReturn("Scenario Not Due"); + } + + @Test + void stratConDisabled() { + when(options.isUseStratCon()).thenReturn(false); + + assertEquals("", nagUnresolvedContacts(campaign)); + } + + @Test + void noActiveContract() { + when(campaign.getActiveAtBContracts()).thenReturn(new ArrayList<>()); + + assertEquals("", nagUnresolvedContacts(campaign)); + } + + @Test + void nullStratConState() { + when(contract.getStratconCampaignState()).thenReturn(null); + + assertEquals("", nagUnresolvedContacts(campaign)); + } + + @Test + void noScenarios() { + when(track.getScenarios()).thenReturn(new HashMap<>()); + + assertEquals("", nagUnresolvedContacts(campaign)); + } + + @Test + void noUnresolvedScenarios() { + Map mockMap = new HashMap<>(); + mockMap.put(coordinates, scenarioNotDue); + + when(track.getScenarios()).thenReturn(mockMap); + when(scenarioNotDue.getCurrentState()).thenReturn(ScenarioState.COMPLETED); + + assertEquals("", nagUnresolvedContacts(campaign)); + } + + @Test + void noScenariosDue() { + when(track.getScenarios()).thenReturn(Collections.singletonMap(coordinates, scenarioNotDue)); + when(track.getDisplayableName()).thenReturn("Test Track"); + + assertEquals("", nagUnresolvedContacts(campaign)); + } + + @Test + void scenarioDue() { + when(track.getScenarios()).thenReturn(Collections.singletonMap(coordinates, scenarioDue)); + when(track.getDisplayableName()).thenReturn("Test Track"); + + assertEquals("Scenario Due, Test Track\n", nagUnresolvedContacts(campaign)); + } +} \ No newline at end of file diff --git a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UntreatedPersonnelNagDialogTest.java b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UntreatedPersonnelNagDialogTest.java index 6d53db7cb9..f27513b2f1 100644 --- a/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UntreatedPersonnelNagDialogTest.java +++ b/MekHQ/unittests/mekhq/gui/dialog/nagDialogs/UntreatedPersonnelNagDialogTest.java @@ -1,6 +1,25 @@ +/* + * 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.gui.dialog.nagDialogs; import megamek.common.EquipmentType; +import megamek.logging.MMLogger; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; @@ -9,7 +28,6 @@ import mekhq.campaign.personnel.enums.PrisonerStatus; import mekhq.campaign.personnel.ranks.Ranks; import mekhq.campaign.universe.Systems; -import org.apache.logging.log4j.LogManager; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,10 +36,18 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +/** + * This class contains test methods for the {@link UntreatedPersonnelNagDialog} class. + * It tests the different combinations of untreated personnel and verifies the behavior of the + * {@code isUntreatedInjury()} method. + */ class UntreatedPersonnelNagDialogTest { Campaign campaign; Person person; + /** + * Sets up the necessary dependencies and configurations before running the test methods. + */ @BeforeAll public static void setup() { EquipmentType.initializeTypes(); @@ -30,10 +56,14 @@ public static void setup() { try { Systems.setInstance(Systems.loadDefault()); } catch (Exception ex) { - LogManager.getLogger().error("", ex); + MMLogger.create(UntreatedPersonnelNagDialogTest.class).error("", ex); } } + /** + * Initializes the campaign and creates a new person with the specified personnel role. + * This person is assigned one hit. + */ @BeforeEach public void init() { campaign = new Campaign(); @@ -41,6 +71,9 @@ public void init() { person.setHits(1); } + // In the following tests the isUntreatedInjury() method is called, and its response is checked + // against expected behavior + @Test public void isUntreatedInjuryIncludesNonPrisonersTest() { person.setPrisonerStatus(campaign, PrisonerStatus.FREE, false); diff --git a/MekHQ/userdata/data/universe/ranks.xml b/MekHQ/userdata/data/universe/ranks.xml index a84279a626..9222a5a93e 100644 --- a/MekHQ/userdata/data/universe/ranks.xml +++ b/MekHQ/userdata/data/universe/ranks.xml @@ -1,3 +1,3 @@ - +