From 6c66eaad29581a52d1998d1c51baf569609e4770 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 15 May 2024 18:48:24 -0500 Subject: [PATCH 001/101] Updated turnover settings and added turnover modifier options Expanded upon the turnover settings in Campaign Options. Multiple retirement related variables are introduced such as turnoverFixedTargetNumber, payoutRateOfficer, etc. Also, several turnover modifiers like useAgeModifiers, useUnitRatingModifiers and others have been introduced to provide more customization. --- .gitignore | 1 + .../CampaignOptionsDialog.properties | 54 ++-- MekHQ/src/mekhq/campaign/CampaignOptions.java | 136 +++++++++++ .../mekhq/gui/panes/CampaignOptionsPane.java | 231 ++++++++++++++++-- 4 files changed, 384 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index d00979dc92..993fd14f35 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ /settings_local.gradle /*.sav.gz /.DS_Store +/MekHQ/userdata/data/universe/ranks.xml diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 3404d4c728..e76b31b799 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -200,7 +200,7 @@ chkAtBPrisonerRansom.toolTipText=Prisoners can be ransomed back to their previou # Dependent dependentPanel.title=Dependents (Unofficial) dependentPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. -randomDependentPanel.title=Random Dependents (Currently AtB Only) +randomDependentPanel.title=Random Dependents (AtB Only) randomDependentPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. lblRandomDependentMethod.text=Random Dependents Method lblRandomDependentMethod.toolTipText=This is the method used to determine if a dependent is randomly added or removed from the force. @@ -234,23 +234,45 @@ chkUseDylansRandomXP.toolTipText=Use Dylan's optional random XP on creation of a # Employee Turnover retirementPanel.title=Employee Turnover (Unofficial) -retirementPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. -chkUseRetirementDateTracking.text=Track Retirement Date -chkUseRetirementDateTracking.toolTipText=Track the date of a person's retirement. -randomRetirementPanel.title=Random Employee Turnover (Currently AtB Only) -randomRetirementPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. +chkUseRetirementDateTracking.text=Track Turnover Date +chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure. +randomRetirementPanel.title=Random Employee Turnover (AtB Only) lblRandomRetirementMethod.text=Random Employee Turnover Method -lblRandomRetirementMethod.toolTipText=This is the method used to determine if a person will randomly retire at a given point. -chkUseYearEndRandomRetirement.text=Use Year End Random Employee Turnover Rolls -chkUseYearEndRandomRetirement.toolTipText=Make an Employee Turnover roll for each of the personnel in the unit at the end of every year. -chkUseContractCompletionRandomRetirement.text=Use Contract Completion Employee Turnover Rolls -chkUseContractCompletionRandomRetirement.toolTipText=Make an Employee Turnover roll for each of the personnel in the unit at the end of every contract. +lblRandomRetirementMethod.toolTipText=This is the method used to determine if a person will randomly leave the unit. +lblTurnoverFixedTargetNumber.text=Turnover Fixed Target Number +lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks. +chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls +chkUseYearEndRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of each year. +chkUseContractCompletionRandomRetirement.text=Use Contract Completion Turnover Rolls +chkUseContractCompletionRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of a contract. chkUseCustomRetirementModifiers.text=Customize Employee Turnover Rolls -chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Employee Turnover roll. -chkUseRandomFounderRetirement.text=Use Random Founder Retirement -chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly retire. +chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. +chkUseRandomFounderRetirement.text=Use Random Founder Turnover +chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly leave the unit. chkTrackUnitFatigue.text=Track Unit Fatigue -chkTrackUnitFatigue.toolTipText=Continuous deployments without a break increase the rate of retirements and defections. +chkTrackUnitFatigue.toolTipText=Continuous deployments without a break apply a penalty to Turnover checks. + +turnoverModifierPanel.title=Modifiers +chkUseAgeModifiers.text=Use Age Modifiers +chkUseAgeModifiers.toolTipText=If enabled, the age of personnel will influence their turnover target number. +chkUseUnitRatingModifiers.text=Use Unit Rating Modifiers +chkUseUnitRatingModifiers.toolTipText=If enabled, the rating of the unit will influence the turnover target number for all personnel. +chkUseFactionModifiers.text=Use Faction Modifiers +chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. +chkUseLeadershipModifiers.text=Use Leadership Modifiers +chkUseLeadershipModifiers.toolTipText=If enabled, the Commander's Leadership Skill will influence the turnover target number. + +turnoverPayoutPanel.title=Payout Rates +lblPayoutRateOfficer.text=Officer Payout Rate +lblPayoutRateOfficer.toolTipText=The number of months' pay officers get when resigning from the unit. +lblPayoutRateEnlisted.text=Enlisted Payout Rate +lblPayoutRateEnlisted.toolTipText=The number of months' pay enlisted personnel get when resigning from the unit. +lblPayoutRetirementMultiplier.text=Retirement Multiplier +lblPayoutRetirementMultiplier.toolTipText=The number of months to multiply payout rate, when personnel retire. +chkUsePayoutServiceBonus.text=Use Service Bonus +chkUsePayoutServiceBonus.toolTipText=If enabled, personnel receive a payout bonus based on service years when resigning or retiring. +lblPayoutServiceBonusRate.text=Service Bonus % +lblPayoutServiceBonusRate.toolTipText=If 'Use Service Bonus' is enabled, this sets the payout percentage increase per year of service, applied when personnel resign or retire. # Family familyPanel.title=Family (Unofficial) @@ -629,7 +651,7 @@ chkUnitMarketReportRefresh.text=Unit Market Refresh Report chkUnitMarketReportRefresh.toolTipText=Adds a report to the daily log when the unit market refreshes. # Contract Market -contractMarketPanel.title=Contract Market (Currently AtB Only) +contractMarketPanel.title=Contract Market (AtB Only) lblContractMarketMethod.text=Contract Market Method lblContractMarketMethod.toolTipText=This is the method of contract market used to generate new contracts to select. lblContractSearchRadius.text=Contract Search Radius (Light Years): diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index d382d41016..7826f4d9f8 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -249,12 +249,24 @@ public static String getTransitUnitName(final int unit) { // Retirement private boolean useRetirementDateTracking; private RandomRetirementMethod randomRetirementMethod; + private Integer turnoverFixedTargetNumber; private boolean useYearEndRandomRetirement; private boolean useContractCompletionRandomRetirement; private boolean useCustomRetirementModifiers; private boolean useRandomFounderRetirement; private boolean trackUnitFatigue; + private Integer payoutRateOfficer; + private Integer payoutRateEnlisted; + private Integer payoutRetirementMultiplier; + private boolean usePayoutServiceBonus; + private Integer payoutServiceBonusRate; + + private boolean useAgeModifiers; + private boolean useUnitRatingModifiers; + private boolean useFactionModifiers; + private boolean useLeadershipModifiers; + // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -713,12 +725,24 @@ public CampaignOptions() { // Retirement setUseRetirementDateTracking(false); setRandomRetirementMethod(RandomRetirementMethod.NONE); + setTurnoverFixedTargetNumber(5); setUseYearEndRandomRetirement(true); setUseContractCompletionRandomRetirement(true); setUseCustomRetirementModifiers(true); setUseRandomFounderRetirement(true); setTrackUnitFatigue(false); + setUseAgeModifiers(true); + setUseUnitRatingModifiers(true); + setUseFactionModifiers(true); + setUseLeadershipModifiers(true); + + setPayoutRateOfficer(3); + setPayoutRateEnlisted(3); + setPayoutRetirementMultiplier(24); + setUsePayoutServiceBonus(true); + setPayoutServiceBonusRate(10); + // Family setFamilyDisplayLevel(FamilialRelationshipDisplayLevel.SPOUSE); @@ -1609,6 +1633,86 @@ public boolean isTrackUnitFatigue() { public void setTrackUnitFatigue(final boolean trackUnitFatigue) { this.trackUnitFatigue = trackUnitFatigue; } + + public Integer getTurnoverFixedTargetNumber() { + return turnoverFixedTargetNumber; + } + + public void setTurnoverFixedTargetNumber(final Integer turnoverFixedTargetNumber) { + this.turnoverFixedTargetNumber = turnoverFixedTargetNumber; + } + + public Integer getPayoutRateOfficer() { + return payoutRateOfficer; + } + + public void setPayoutRateOfficer(final Integer payoutRateOfficer) { + this.payoutRateOfficer = payoutRateOfficer; + } + + public Integer getPayoutRateEnlisted() { + return payoutRateEnlisted; + } + + public void setPayoutRateEnlisted(final Integer payoutRateEnlisted) { + this.payoutRateEnlisted = payoutRateEnlisted; + } + + public Integer getPayoutRetirementMultiplier() { + return payoutRetirementMultiplier; + } + + public void setPayoutRetirementMultiplier(final Integer payoutRetirementMultiplier) { + this.payoutRetirementMultiplier = payoutRetirementMultiplier; + } + + public boolean isUsePayoutServiceBonus() { + return usePayoutServiceBonus; + } + + public void setUsePayoutServiceBonus(final boolean usePayoutServiceBonus) { + this.usePayoutServiceBonus = usePayoutServiceBonus; + } + + public Integer getPayoutServiceBonusRate() { + return payoutServiceBonusRate; + } + + public void setPayoutServiceBonusRate(final Integer payoutServiceBonusRate) { + this.payoutServiceBonusRate = payoutServiceBonusRate; + } + + public boolean isUseAgeModifiers() { + return useAgeModifiers; + } + + public void setUseAgeModifiers(final boolean useAgeModifiers) { + this.useAgeModifiers = useAgeModifiers; + } + + public boolean isUseUnitRatingModifiers() { + return useUnitRatingModifiers; + } + + public void setUseUnitRatingModifiers(final boolean useUnitRatingModifiers) { + this.useUnitRatingModifiers = useUnitRatingModifiers; + } + + public boolean isUseFactionModifiers() { + return useFactionModifiers; + } + + public void setUseFactionModifiers(final boolean useFactionModifiers) { + this.useFactionModifiers = useFactionModifiers; + } + + public boolean isUseLeadershipModifiers() { + return useLeadershipModifiers; + } + + public void setUseLeadershipModifiers(final boolean useLeadershipModifiers) { + this.useLeadershipModifiers = useLeadershipModifiers; + } //endregion Retirement //region Family @@ -3915,6 +4019,18 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomFounderRetirement", isUseRandomFounderRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAgeModifiers", isUseAgeModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useUnitRatingModifiers", isUseUnitRatingModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFactionModifiers", isUseFactionModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLeadershipModifiers", isUseLeadershipModifiers()); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverBaseTn", getTurnoverFixedTargetNumber()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateOfficer", getPayoutRateOfficer()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateEnlisted", getPayoutRateEnlisted()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRetirementMultiplier", getPayoutRetirementMultiplier()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "usePayoutServiceBonus", isUsePayoutServiceBonus()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutServiceBonusRate", getPayoutServiceBonusRate()); //endregion Retirement //region Family @@ -4546,6 +4662,26 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseRandomFounderRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useAgeModifiers")) { + retVal.setUseAgeModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useUnitRatingModifiers")) { + retVal.setUseUnitRatingModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useFactionModifiers")) { + retVal.setUseFactionModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useLeadershipModifiers")) { + retVal.setUseLeadershipModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("turnoverBaseTn")) { + retVal.setTurnoverFixedTargetNumber(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateOfficer")) { + retVal.setPayoutRateOfficer(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateEnlisted")) { + retVal.setPayoutRateEnlisted(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("payoutRetirementMultiplier")) { + retVal.setPayoutRetirementMultiplier(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("usePayoutServiceBonus")) { + retVal.setUsePayoutServiceBonus(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("payoutServiceBonusRate")) { + retVal.setPayoutServiceBonusRate(Integer.parseInt(wn2.getTextContent().trim())); //endregion Retirement //region Family diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 58efd23885..3b313564cd 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -254,11 +254,24 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseRetirementDateTracking; private JPanel randomRetirementPanel; private MMComboBox comboRandomRetirementMethod; + private JSpinner spnTurnoverFixedTargetNumber; private JCheckBox chkUseYearEndRandomRetirement; private JCheckBox chkUseContractCompletionRandomRetirement; private JCheckBox chkUseCustomRetirementModifiers; private JCheckBox chkUseRandomFounderRetirement; private JCheckBox chkTrackUnitFatigue; + private JPanel turnoverModifiersPanel; + private JCheckBox chkUseAgeModifiers; + private JCheckBox chkUseUnitRatingModifiers; + private JCheckBox chkUseFactionModifiers; + private JCheckBox chkUseLeadershipModifiers; + private JPanel turnoverPayoutPanel; + private JSpinner spnPayoutRateOfficer; + private JSpinner spnPayoutRateEnlisted; + private JSpinner spnPayoutRetirementMultiplier; + private JCheckBox chkUsePayoutServiceBonus; + private JLabel lblPayoutServiceBonusRate; + private JSpinner spnPayoutServiceBonusRate; // Marriage private JCheckBox chkUseManualMarriages; @@ -3603,7 +3616,6 @@ private JPanel createRetirementPanel() { // Layout the Panel final JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createTitledBorder(resources.getString("retirementPanel.title"))); - panel.setToolTipText(resources.getString("retirementPanel.toolTipText")); panel.setName("retirementPanel"); final GroupLayout layout = new GroupLayout(panel); @@ -3628,6 +3640,38 @@ private JPanel createRetirementPanel() { private void createRandomRetirementPanel() { // Create Panel Components + final JLabel lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); + lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); + lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); + + spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(5, 0, 12, 1)); + spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); + spnTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); + + chkUseYearEndRandomRetirement = new JCheckBox(resources.getString("chkUseYearEndRandomRetirement.text")); + chkUseYearEndRandomRetirement.setToolTipText(resources.getString("chkUseYearEndRandomRetirement.toolTipText")); + chkUseYearEndRandomRetirement.setName("chkUseYearEndRandomRetirement"); + + chkUseContractCompletionRandomRetirement = new JCheckBox(resources.getString("chkUseContractCompletionRandomRetirement.text")); + chkUseContractCompletionRandomRetirement.setToolTipText(resources.getString("chkUseContractCompletionRandomRetirement.toolTipText")); + chkUseContractCompletionRandomRetirement.setName("chkUseContractCompletionRandomRetirement"); + + chkUseCustomRetirementModifiers = new JCheckBox(resources.getString("chkUseCustomRetirementModifiers.text")); + chkUseCustomRetirementModifiers.setToolTipText(resources.getString("chkUseCustomRetirementModifiers.toolTipText")); + chkUseCustomRetirementModifiers.setName("chkUseCustomRetirementModifiers"); + + chkUseRandomFounderRetirement = new JCheckBox(resources.getString("chkUseRandomFounderRetirement.text")); + chkUseRandomFounderRetirement.setToolTipText(resources.getString("chkUseRandomFounderRetirement.toolTipText")); + chkUseRandomFounderRetirement.setName("chkUseRandomFounderRetirement"); + + chkTrackUnitFatigue = new JCheckBox(resources.getString("chkTrackUnitFatigue.text")); + chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); + chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); + + JPanel turnoverModifiersPanel = createTurnoverModifiersPanel(); + JPanel turnoverPayoutPanel = createTurnoverPayoutPanel(); + + // main controller option final JLabel lblRandomRetirementMethod = new JLabel(resources.getString("lblRandomRetirementMethod.text")); lblRandomRetirementMethod.setToolTipText(resources.getString("lblRandomRetirementMethod.toolTipText")); lblRandomRetirementMethod.setName("lblRandomRetirementMethod"); @@ -3652,40 +3696,23 @@ public Component getListCellRendererComponent(final JList list, final Object return; } final boolean enabled = randomRetirementPanel.isEnabled() && !method.isNone(); + lblTurnoverFixedTargetNumber.setEnabled(enabled); + spnTurnoverFixedTargetNumber.setEnabled(enabled); chkUseYearEndRandomRetirement.setEnabled(enabled); chkUseContractCompletionRandomRetirement.setEnabled(enabled); chkUseCustomRetirementModifiers.setEnabled(enabled); chkUseRandomFounderRetirement.setEnabled(enabled); chkTrackUnitFatigue.setEnabled(enabled); + turnoverModifiersPanel.setEnabled(enabled); + turnoverPayoutPanel.setEnabled(enabled); }); - chkUseYearEndRandomRetirement = new JCheckBox(resources.getString("chkUseYearEndRandomRetirement.text")); - chkUseYearEndRandomRetirement.setToolTipText(resources.getString("chkUseYearEndRandomRetirement.toolTipText")); - chkUseYearEndRandomRetirement.setName("chkUseYearEndRandomRetirement"); - - chkUseContractCompletionRandomRetirement = new JCheckBox(resources.getString("chkUseContractCompletionRandomRetirement.text")); - chkUseContractCompletionRandomRetirement.setToolTipText(resources.getString("chkUseContractCompletionRandomRetirement.toolTipText")); - chkUseContractCompletionRandomRetirement.setName("chkUseContractCompletionRandomRetirement"); - - chkUseCustomRetirementModifiers = new JCheckBox(resources.getString("chkUseCustomRetirementModifiers.text")); - chkUseCustomRetirementModifiers.setToolTipText(resources.getString("chkUseCustomRetirementModifiers.toolTipText")); - chkUseCustomRetirementModifiers.setName("chkUseCustomRetirementModifiers"); - - chkUseRandomFounderRetirement = new JCheckBox(resources.getString("chkUseRandomFounderRetirement.text")); - chkUseRandomFounderRetirement.setToolTipText(resources.getString("chkUseRandomFounderRetirement.toolTipText")); - chkUseRandomFounderRetirement.setName("chkUseRandomFounderRetirement"); - - chkTrackUnitFatigue = new JCheckBox(resources.getString("chkTrackUnitFatigue.text")); - chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); - chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); - // Programmatically Assign Accessibility Labels lblRandomRetirementMethod.setLabelFor(comboRandomRetirementMethod); // Layout the Panel randomRetirementPanel = new JDisableablePanel("randomRetirementPanel"); randomRetirementPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("randomRetirementPanel.title"))); - randomRetirementPanel.setToolTipText(resources.getString("randomRetirementPanel.toolTipText")); final GroupLayout layout = new GroupLayout(randomRetirementPanel); randomRetirementPanel.setLayout(layout); @@ -3697,11 +3724,16 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblRandomRetirementMethod) .addComponent(comboRandomRetirementMethod, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblTurnoverFixedTargetNumber) + .addComponent(spnTurnoverFixedTargetNumber, Alignment.LEADING)) .addComponent(chkUseYearEndRandomRetirement) .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) .addComponent(chkUseCustomRetirementModifiers) .addComponent(chkTrackUnitFatigue) + .addComponent(turnoverModifiersPanel) + .addComponent(turnoverPayoutPanel) ); layout.setHorizontalGroup( @@ -3709,12 +3741,143 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createSequentialGroup() .addComponent(lblRandomRetirementMethod) .addComponent(comboRandomRetirementMethod)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblTurnoverFixedTargetNumber) + .addComponent(spnTurnoverFixedTargetNumber)) .addComponent(chkUseYearEndRandomRetirement) .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) .addComponent(chkUseCustomRetirementModifiers) .addComponent(chkTrackUnitFatigue) + .addComponent(turnoverModifiersPanel) + .addComponent(turnoverPayoutPanel) + ); + } + + private JPanel createTurnoverModifiersPanel() { + chkUseAgeModifiers = new JCheckBox(resources.getString("chkUseAgeModifiers.text")); + chkUseAgeModifiers.setToolTipText(resources.getString("chkUseAgeModifiers.toolTipText")); + chkUseAgeModifiers.setName("chkUseAgeModifiers"); + + chkUseUnitRatingModifiers = new JCheckBox(resources.getString("chkUseUnitRatingModifiers.text")); + chkUseUnitRatingModifiers.setToolTipText(resources.getString("chkUseUnitRatingModifiers.toolTipText")); + chkUseUnitRatingModifiers.setName("chkUseUnitRatingModifiers"); + + chkUseFactionModifiers = new JCheckBox(resources.getString("chkUseFactionModifiers.text")); + chkUseFactionModifiers.setToolTipText(resources.getString("chkUseFactionModifiers.toolTipText")); + chkUseFactionModifiers.setName("chkUseFactionModifiers"); + + chkUseLeadershipModifiers = new JCheckBox(resources.getString("chkUseLeadershipModifiers.text")); + chkUseLeadershipModifiers.setToolTipText(resources.getString("chkUseLeadershipModifiers.toolTipText")); + chkUseLeadershipModifiers.setName("chkUseLeadershipModifiers"); + + turnoverModifiersPanel = new JDisableablePanel("turnoverModifierPanel"); + turnoverModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverModifierPanel.title"))); + + final GroupLayout layout = new GroupLayout(turnoverModifiersPanel); + turnoverModifiersPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseAgeModifiers) + .addComponent(chkUseUnitRatingModifiers) + .addComponent(chkUseFactionModifiers) + .addComponent(chkUseLeadershipModifiers) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseAgeModifiers) + .addComponent(chkUseUnitRatingModifiers) + .addComponent(chkUseFactionModifiers) + .addComponent(chkUseLeadershipModifiers) ); + + return turnoverModifiersPanel; + } + + private JPanel createTurnoverPayoutPanel() { + final JLabel lblPayoutRateOfficer = new JLabel(resources.getString("lblPayoutRateOfficer.text")); + lblPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); + lblPayoutRateOfficer.setName("lblPayoutRateOfficer"); + + spnPayoutRateOfficer = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1)); + spnPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); + spnPayoutRateOfficer.setName("lblPayoutRateOfficer"); + + final JLabel lblPayoutRateEnlisted = new JLabel(resources.getString("lblPayoutRateEnlisted.text")); + lblPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); + lblPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); + + spnPayoutRateEnlisted = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1)); + spnPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); + spnPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); + + final JLabel lblPayoutRetirementMultiplier = new JLabel(resources.getString("lblPayoutRetirementMultiplier.text")); + lblPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); + lblPayoutRetirementMultiplier.setName("lblPayoutRetirementMultiplier"); + + spnPayoutRetirementMultiplier = new JSpinner(new SpinnerNumberModel(24, 1, 100, 1)); + spnPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); + spnPayoutRetirementMultiplier.setName("lblPayoutRetirementMultiplier"); + + chkUsePayoutServiceBonus = new JCheckBox(resources.getString("chkUsePayoutServiceBonus.text")); + chkUsePayoutServiceBonus.setToolTipText(resources.getString("chkUsePayoutServiceBonus.toolTipText")); + chkUsePayoutServiceBonus.setName("chkUsePayoutServiceBonus"); + + lblPayoutServiceBonusRate = new JLabel(resources.getString("lblPayoutServiceBonusRate.text")); + lblPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); + lblPayoutServiceBonusRate.setName("lblPayoutServiceBonusRate"); + + spnPayoutServiceBonusRate = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); + spnPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); + spnPayoutServiceBonusRate.setName("lblPayoutServiceBonusRate"); + + turnoverPayoutPanel = new JDisableablePanel("turnoverPayoutPanel"); + turnoverPayoutPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverPayoutPanel.title"))); + + final GroupLayout layout = new GroupLayout(turnoverPayoutPanel); + turnoverPayoutPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblPayoutRateOfficer) + .addComponent(spnPayoutRateOfficer, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblPayoutRateEnlisted) + .addComponent(spnPayoutRateEnlisted, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblPayoutRetirementMultiplier) + .addComponent(spnPayoutRetirementMultiplier, Alignment.LEADING)) + .addComponent(chkUsePayoutServiceBonus) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblPayoutServiceBonusRate) + .addComponent(spnPayoutServiceBonusRate, Alignment.LEADING)) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblPayoutRateOfficer) + .addComponent(spnPayoutRateOfficer)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblPayoutRateEnlisted) + .addComponent(spnPayoutRateEnlisted)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblPayoutRetirementMultiplier) + .addComponent(spnPayoutRetirementMultiplier)) + .addComponent(chkUsePayoutServiceBonus) + .addGroup(layout.createSequentialGroup() + .addComponent(lblPayoutServiceBonusRate) + .addComponent(spnPayoutServiceBonusRate)) + ); + + return turnoverPayoutPanel; } private JPanel createFamilyPanel() { @@ -6733,12 +6896,24 @@ public void setOptions(@Nullable CampaignOptions options, // Retirement chkUseRetirementDateTracking.setSelected(options.isUseRetirementDateTracking()); comboRandomRetirementMethod.setSelectedItem(options.getRandomRetirementMethod()); + spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); chkUseYearEndRandomRetirement.setSelected(options.isUseYearEndRandomRetirement()); chkUseContractCompletionRandomRetirement.setSelected(options.isUseContractCompletionRandomRetirement()); chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); chkUseRandomFounderRetirement.setSelected(options.isUseRandomFounderRetirement()); chkTrackUnitFatigue.setSelected(options.isTrackUnitFatigue()); + chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); + chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); + chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); + chkUseLeadershipModifiers.setSelected(options.isUseLeadershipModifiers()); + + spnPayoutRateOfficer.setValue(options.getPayoutRateOfficer()); + spnPayoutRateEnlisted.setValue(options.getPayoutRateEnlisted()); + spnPayoutRetirementMultiplier.setValue(options.getPayoutRetirementMultiplier()); + chkUsePayoutServiceBonus.setSelected(options.isUsePayoutServiceBonus()); + spnPayoutServiceBonusRate.setValue(options.getPayoutServiceBonusRate()); + // Family comboFamilyDisplayLevel.setSelectedItem(options.getFamilyDisplayLevel()); @@ -7335,12 +7510,24 @@ public void updateOptions() { // Retirement options.setUseRetirementDateTracking(chkUseRetirementDateTracking.isSelected()); options.setRandomRetirementMethod(comboRandomRetirementMethod.getSelectedItem()); + options.setTurnoverFixedTargetNumber((Integer) spnTurnoverFixedTargetNumber.getValue()); options.setUseYearEndRandomRetirement(chkUseYearEndRandomRetirement.isSelected()); options.setUseContractCompletionRandomRetirement(chkUseContractCompletionRandomRetirement.isSelected()); options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); options.setUseRandomFounderRetirement(chkUseRandomFounderRetirement.isSelected()); options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); + options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); + options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); + options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); + options.setUseLeadershipModifiers(chkUseLeadershipModifiers.isSelected()); + + options.setPayoutRateOfficer((Integer) spnPayoutRateOfficer.getValue()); + options.setPayoutRateEnlisted((Integer) spnPayoutRateEnlisted.getValue()); + options.setPayoutRetirementMultiplier((Integer) spnPayoutRetirementMultiplier.getValue()); + options.setUsePayoutServiceBonus(chkUsePayoutServiceBonus.isSelected()); + options.setPayoutServiceBonusRate((Integer) spnPayoutServiceBonusRate.getValue()); + // Family options.setFamilyDisplayLevel(comboFamilyDisplayLevel.getSelectedItem()); From fc1fe81f97821ee5b31e50e97b588e597b1ca30b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 15 May 2024 19:30:48 -0500 Subject: [PATCH 002/101] Removed old leadership usage campaign option and rolled out new Turnover campaign options. --- .../CampaignOptionsDialog.properties | 4 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 13 -- .../src/mekhq/campaign/personnel/Person.java | 18 +++ .../personnel/RetirementDefectionTracker.java | 146 +++++++++++------- .../gui/dialog/RetirementDefectionDialog.java | 2 +- .../mekhq/gui/model/RetirementTableModel.java | 2 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 8 - 7 files changed, 107 insertions(+), 86 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index e76b31b799..a014b1b590 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -260,7 +260,7 @@ chkUseUnitRatingModifiers.toolTipText=If enabled, the rating of the unit will in chkUseFactionModifiers.text=Use Faction Modifiers chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. chkUseLeadershipModifiers.text=Use Leadership Modifiers -chkUseLeadershipModifiers.toolTipText=If enabled, the Commander's Leadership Skill will influence the turnover target number. +chkUseLeadershipModifiers.toolTipText=If enabled, the commander's Leadership skill limits the number of personnel that can be in the unit before incurring a penalty on Employee Turnover rolls. turnoverPayoutPanel.title=Payout Rates lblPayoutRateOfficer.text=Officer Payout Rate @@ -705,8 +705,6 @@ chkSharesForAll.text=All personnel have shares chkSharesForAll.toolTipText=All combat and support personnel have shares rather than just MechWarriors chkTrackOriginalUnit.text=Track original unit chkTrackOriginalUnit.toolTipText=MechWarriors who have their own unit when recruited will take the same unit when they go, if it is still available. -chkUseLeadership.text=Use leadership skill -chkUseLeadership.toolTipText=The commander's leadership skill limits the number of personnel that can be in the unit before incurring a penalty on Employee Turnover rolls. chkLimitLanceWeight.text=Limit lance deployment by weight chkLimitLanceWeight.toolTipText=Lances which exceed the maximum weight for an Assault lance (390 tons for IS) cannot be deployed. chkLimitLanceNumUnits.text=Limit lance deployment by size diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 7826f4d9f8..fc1d039a1e 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -477,7 +477,6 @@ public static String getTransitUnitName(final int unit) { private boolean sharesExcludeLargeCraft; private boolean sharesForAll; private boolean aeroRecruitsHaveUnits; - private boolean useLeadership; private boolean trackOriginalUnit; private boolean useAero; private boolean useVehicles; @@ -1016,7 +1015,6 @@ public CampaignOptions() { sharesExcludeLargeCraft = false; sharesForAll = false; aeroRecruitsHaveUnits = false; - useLeadership = true; trackOriginalUnit = false; useAero = false; useVehicles = true; @@ -3731,14 +3729,6 @@ public void setUsePlanetaryConditions(final boolean usePlanetaryConditions) { this.usePlanetaryConditions = usePlanetaryConditions; } - public boolean isUseLeadership() { - return useLeadership; - } - - public void setUseLeadership(final boolean useLeadership) { - this.useLeadership = useLeadership; - } - public boolean isUseStrategy() { return useStrategy; } @@ -4251,7 +4241,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useWeatherConditions", useWeatherConditions); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLightConditions", useLightConditions); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "usePlanetaryConditions", usePlanetaryConditions); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLeadership", useLeadership); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useStrategy", useStrategy); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "baseStrategyDeployment", baseStrategyDeployment); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "additionalStrategyDeployment", additionalStrategyDeployment); @@ -5141,8 +5130,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.useLightConditions = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("usePlanetaryConditions")) { retVal.usePlanetaryConditions = Boolean.parseBoolean(wn2.getTextContent().trim()); - } else if (wn2.getNodeName().equalsIgnoreCase("useLeadership")) { - retVal.useLeadership = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("useStrategy")) { retVal.useStrategy = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("baseStrategyDeployment")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index fa09780083..00d698cc06 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -1131,6 +1131,24 @@ public String getTimeInService(final Campaign campaign) { .getDisplayFormattedOutput(getRecruitment(), today); } + public Integer getYearsInService(final Campaign campaign) { + // Get time in service based on year + if (getRecruitment() == null) { + //use "" they haven't been recruited or are dependents + return 0; + } + + LocalDate today = campaign.getLocalDate(); + + // If the person is dead, we only care about how long they spent in service to the company + if (getDateOfDeath() != null) { + //use date of death instead of the current day + today = getDateOfDeath(); + } + + return Math.toIntExact(ChronoUnit.YEARS.between(getRecruitment(), today)); + } + public @Nullable LocalDate getLastRankChangeDate() { return lastRankChangeDate; } diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index e8516e11b1..59b035f2cc 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -24,7 +24,6 @@ import megamek.common.TargetRoll; import megamek.common.annotations.Nullable; import megamek.common.options.IOption; -import mekhq.utilities.MHQXMLUtility; import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.finances.FinancialReport; @@ -33,6 +32,7 @@ import mekhq.campaign.mission.Mission; import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Profession; +import mekhq.utilities.MHQXMLUtility; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.w3c.dom.Node; @@ -101,6 +101,7 @@ public static Money getShareValue(Campaign campaign) { */ private static int getAgeMod(int age) { int ageMod = 0; + if (age <= 20) { ageMod = -1; } else if ((age >= 50) && (age < 65)) { @@ -139,7 +140,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract rollRequired.add(contract.getId()); } - if (campaign.getCampaignOptions().isUseLeadership()) { + if (campaign.getCampaignOptions().isUseLeadershipModifiers()) { int combat = 0; int proto = 0; int support = 0; @@ -195,7 +196,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract continue; } - TargetRoll target = new TargetRoll(3, "Base"); + TargetRoll target = new TargetRoll(campaign.getCampaignOptions().getTurnoverFixedTargetNumber(), "Base"); // Skill Rating modifier int skillRating = p.getExperienceLevel(campaign, false); @@ -228,41 +229,36 @@ public Map calculateTargetNumbers(final @Nullable AtBContract target.addModifier(skillRating, skillRatingDescription); // Unit Rating modifier - int unitRating = 0; - - if (campaign.getUnitRatingMod() < 1) { - unitRating = 2; - } else if (campaign.getUnitRatingMod() == 1) { - unitRating = 1; - } else if (campaign.getUnitRatingMod() > 3) { - unitRating = -1; + if (campaign.getCampaignOptions().isUseUnitRatingModifiers()) { + int unitRatingModifier = getUnitRatingModifier(campaign); + target.addModifier(unitRatingModifier, "Unit Rating"); } - target.addModifier(unitRating, "Unit Rating"); - /* Retirement rolls are made before the contract status is set */ if ((contract != null) && (contract.getStatus().isFailed() || contract.getStatus().isBreach())) { target.addModifier(1, "Failed mission"); } // Fatigue Modifiers - if (campaign.getCampaignOptions().isTrackUnitFatigue() && (campaign.getFatigueLevel() >= 10)) { + if (campaign.getCampaignOptions().isTrackUnitFatigue()) { target.addModifier(campaign.getFatigueLevel() / 10, "Fatigue"); } // Faction Modifiers - if (campaign.getFaction().isPirate()) { - target.addModifier(1, "Pirate Company"); - } else if (p.getOriginFaction().isPirate()) { - target.addModifier(1,"Pirate"); - } + if (campaign.getCampaignOptions().isUseFactionModifiers()) { + if (campaign.getFaction().isPirate()) { + target.addModifier(1, "Pirate Company"); + } else if (p.getOriginFaction().isPirate()) { + target.addModifier(1, "Pirate"); + } - if (p.getOriginFaction().isMercenary()) { - target.addModifier(1, "Mercenary"); - } + if (p.getOriginFaction().isMercenary()) { + target.addModifier(1, "Mercenary"); + } - if (p.getOriginFaction().isClan()) { - target.addModifier(-2, "Clan"); + if (p.getOriginFaction().isClan()) { + target.addModifier(-2, "Clan"); + } } // Officer Modifiers @@ -280,12 +276,14 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } - // Old Age modifier - int age = p.getAge(campaign.getLocalDate()); - int ageMod = getAgeMod(age); + // Age modifiers + if (campaign.getCampaignOptions().isUseAgeModifiers()) { + int age = p.getAge(campaign.getLocalDate()); + int ageMod = getAgeMod(age); - if (ageMod > 0) { - target.addModifier(ageMod, "Age"); + if (ageMod != 0) { + target.addModifier(ageMod, "Age"); + } } // Shares Modifiers @@ -314,24 +312,23 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } // Injury Modifiers - int injuryMod = 0; - for (Injury i : p.getInjuries()) { - if (i.isPermanent()) { - injuryMod++; - } - } + int injuryMod = (int) p.getInjuries() + .stream() + .filter(Injury::isPermanent).count(); if (injuryMod > 0) { target.addModifier(injuryMod, "Permanent Injuries"); } // Leadership Modifiers - if ((combatLeadershipMod != 0) && p.getPrimaryRole().isCombat()) { - target.addModifier(combatLeadershipMod, "Leadership (Combatant)"); - } + if(campaign.getCampaignOptions().isUseLeadershipModifiers()) { + if ((combatLeadershipMod != 0) && p.getPrimaryRole().isCombat()) { + target.addModifier(combatLeadershipMod, "Leadership (Combatant)"); + } - if ((supportLeadershipMod != 0) && p.getPrimaryRole().isSupport()) { - target.addModifier(supportLeadershipMod, "Leadership (Support)"); + if ((supportLeadershipMod != 0) && p.getPrimaryRole().isSupport()) { + target.addModifier(supportLeadershipMod, "Leadership (Support)"); + } } targets.put(p.getId(), target); @@ -339,7 +336,28 @@ public Map calculateTargetNumbers(final @Nullable AtBContract return targets; } - /** + + + /** + * Returns the unit rating modifier for the campaign. + * + * @param campaign the campaign from which to derive the unit rating modifier + * @return the unit rating modifier + */ + private static int getUnitRatingModifier(Campaign campaign) { + int unitRating = 0; + + if (campaign.getUnitRatingMod() < 1) { + unitRating = 2; + } else if (campaign.getUnitRatingMod() == 1) { + unitRating = 1; + } else if (campaign.getUnitRatingMod() > 3) { + unitRating = -1; + } + return unitRating; + } + + /** * Makes rolls for Employee Turnover based on previously calculated target rolls, * and tracks all retirees in the unresolvedPersonnel hash in case the dialog * is closed before payments are resolved, to avoid re-rolling the results. @@ -347,7 +365,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract * @param mission Nullable mission value * @param targets The hash previously generated by calculateTargetNumbers. * @param shareValue The value of each share in the unit; if not using the share system, this is zero. - * @param campaign + * @param campaign the current campaign */ public void rollRetirement(final @Nullable Mission mission, final Map targets, final Money shareValue, final Campaign campaign) { @@ -385,25 +403,24 @@ public void setLastRetirementRoll(LocalDate lastRetirementRoll) { * * @param person The person to be removed from the campaign * @param killed True if killed in battle, false if sacked - * @param campaign + * @param campaign the ongoing campaign * @param contract If not null, the payout must be resolved before the contract can be resolved. - * @return true if the person is due a payout; otherwise false + * @return true, if the person is due a payout, otherwise false */ public boolean removeFromCampaign(Person person, boolean killed, Campaign campaign, AtBContract contract) { - /* Payouts to Infantry/Battle armor platoons/squads/points are - * handled as a unit in the AtB rules, so we're just going to ignore - * them here. - */ - if (person.getPrimaryRole().isSoldierOrBattleArmour() || !person.getPrisonerStatus().isFree()) { + if (!person.getPrisonerStatus().isFree()) { return false; } + payouts.put(person.getId(), new Payout(campaign, person, getShareValue(campaign), killed, campaign.getCampaignOptions().isSharesForAll())); + if (null != contract) { unresolvedPersonnel.computeIfAbsent(contract.getId(), k -> new HashSet<>()); unresolvedPersonnel.get(contract.getId()).add(person.getId()); } + return true; } @@ -438,8 +455,8 @@ public boolean isOutstanding(int id) { return unresolvedPersonnel.containsKey(id); } - /* Called by when all payouts have been resolved for the contract. - * If contract is null, the dialog has been invoked without a + /** Called by when all payouts have been resolved for the contract. + * If the contract is null, the dialog has been invoked without a * specific contract and all outstanding payouts have been resolved. */ public void resolveAllContracts() { @@ -483,8 +500,18 @@ public Payout getPayout(UUID id) { * @param person the person to get the bonus cost for * @return The amount in C-bills required to get a bonus to the Employee Turnover roll */ - public static Money getBonusCost(final Campaign campaign, Person person) { - return person.getSalary(campaign).multipliedBy(24); + public static Money getPayoutOrBonusValue(final Campaign campaign, Person person) { + int bonusMultiplier = campaign.getCampaignOptions().getPayoutRateEnlisted(); + + if (person.getRank().isOfficer()) { + bonusMultiplier = campaign.getCampaignOptions().getPayoutRateOfficer(); + } + + if (campaign.getCampaignOptions().isUsePayoutServiceBonus()) { + bonusMultiplier += person.getYearsInService(campaign) * (campaign.getCampaignOptions().getPayoutServiceBonusRate() / 100); + } + + return person.getSalary(campaign).multipliedBy(bonusMultiplier); } /** @@ -508,9 +535,11 @@ public Payout() { public Payout(final Campaign campaign, final Person person, final Money shareValue, final boolean killed, final boolean sharesForAll) { calculatePayout(campaign, person, killed, shareValue.isPositive()); + if (shareValue.isPositive()) { payoutAmount = payoutAmount.plus(shareValue.multipliedBy(person.getNumShares(campaign, sharesForAll))); } + if (killed) { switch (Compute.d6()) { case 2: @@ -535,6 +564,7 @@ public Payout(final Campaign campaign, final Person person, final Money shareVal private void calculatePayout(final Campaign campaign, final Person person, final boolean killed, final boolean shareSystem) { int roll; + if (killed) { roll = Utilities.dice(1, 5); } else { @@ -548,13 +578,9 @@ private void calculatePayout(final Campaign campaign, final Person person, stolenUnit = true; } else { final Profession profession = Profession.getProfessionFromPersonnelRole(person.getPrimaryRole()); - if (profession.isInfantry()) { - if (person.getUnit() != null) { - payoutAmount = Money.of(50000); - } - } else { - payoutAmount = getBonusCost(campaign, person); - } + + // TODO when we differentiate between types of retirement we'll need to edit this. + payoutAmount = getPayoutOrBonusValue(campaign, person).multipliedBy(campaign.getCampaignOptions().getPayoutRetirementMultiplier()); if (!shareSystem && (profession.isMechWarrior() || profession.isAerospace()) && (person.getOriginalUnitWeight() > 0)) { diff --git a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java index 55392cb849..63e36a3f2b 100644 --- a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java @@ -724,7 +724,7 @@ private Money getTotalBonus() { Money retVal = Money.zero(); for (UUID id : targetRolls.keySet()) { if (((RetirementTableModel) personnelTable.getModel()).getPayBonus(id)) { - retVal = retVal.plus(RetirementDefectionTracker.getBonusCost(hqView.getCampaign(), + retVal = retVal.plus(RetirementDefectionTracker.getPayoutOrBonusValue(hqView.getCampaign(), hqView.getCampaign().getPerson(id))); } } diff --git a/MekHQ/src/mekhq/gui/model/RetirementTableModel.java b/MekHQ/src/mekhq/gui/model/RetirementTableModel.java index 08a34bc382..6f695ab438 100644 --- a/MekHQ/src/mekhq/gui/model/RetirementTableModel.java +++ b/MekHQ/src/mekhq/gui/model/RetirementTableModel.java @@ -224,7 +224,7 @@ public Object getValueAt(int row, int col) { (payBonus.get(p.getId()) ? 2 : 0) + miscMods.get(p.getId()) + generalMod; case COL_BONUS_COST: - return RetirementDefectionTracker.getBonusCost(campaign, p).toAmountAndSymbolString(); + return RetirementDefectionTracker.getPayoutOrBonusValue(campaign, p).toAmountAndSymbolString(); case COL_PAY_BONUS: return payBonus.getOrDefault(p.getId(), false); case COL_MISC_MOD: diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 3b313564cd..891ac95448 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -533,7 +533,6 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkSharesExcludeLargeCraft; private JCheckBox chkSharesForAll; private JCheckBox chkAeroRecruitsHaveUnits; - private JCheckBox chkUseLeadership; private JCheckBox chkTrackOriginalUnit; private JCheckBox chkUseAero; private JCheckBox chkUseVehicles; @@ -2745,11 +2744,6 @@ private JScrollPane createAgainstTheBotTab() { gridBagConstraints.gridy++; panSubAtBAdmin.add(chkAeroRecruitsHaveUnits, gridBagConstraints); - chkUseLeadership = new JCheckBox(resources.getString("chkUseLeadership.text")); - chkUseLeadership.setToolTipText(resources.getString("chkUseLeadership.toolTipText")); - gridBagConstraints.gridy++; - panSubAtBAdmin.add(chkUseLeadership, gridBagConstraints); - chkTrackOriginalUnit = new JCheckBox(resources.getString("chkTrackOriginalUnit.text")); chkTrackOriginalUnit.setToolTipText(resources.getString("chkTrackOriginalUnit.toolTipText")); gridBagConstraints.gridy++; @@ -7232,7 +7226,6 @@ public void setOptions(@Nullable CampaignOptions options, chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); chkSharesForAll.setSelected(options.isSharesForAll()); chkAeroRecruitsHaveUnits.setSelected(options.isAeroRecruitsHaveUnits()); - chkUseLeadership.setSelected(options.isUseLeadership()); chkTrackOriginalUnit.setSelected(options.isTrackOriginalUnit()); chkUseAero.setSelected(options.isUseAero()); chkUseVehicles.setSelected(options.isUseVehicles()); @@ -7695,7 +7688,6 @@ public void updateOptions() { options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); options.setLimitLanceWeight(chkLimitLanceWeight.isSelected()); options.setLimitLanceNumUnits(chkLimitLanceNumUnits.isSelected()); - options.setUseLeadership(chkUseLeadership.isSelected()); options.setUseStrategy(chkUseStrategy.isSelected()); options.setBaseStrategyDeployment((Integer) spnBaseStrategyDeployment.getValue()); options.setAdditionalStrategyDeployment((Integer) spnAdditionalStrategyDeployment.getValue()); From 062abc20f3d00c1b9742c57d22b5994c2a16993d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 15 May 2024 19:35:50 -0500 Subject: [PATCH 003/101] Rolled back accidental commit of gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 993fd14f35..d00979dc92 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,3 @@ /settings_local.gradle /*.sav.gz /.DS_Store -/MekHQ/userdata/data/universe/ranks.xml From 6c2df5ca4dad3e8210fc635d3d6ba7dcc77368ba Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 15 May 2024 20:40:17 -0500 Subject: [PATCH 004/101] Added turnover target number method to campaign options The commit introduces a new method for determining the base target number for turnover checks in campaign options. --- .../CampaignOptionsDialog.properties | 2 + .../mekhq/resources/Personnel.properties | 12 +++- MekHQ/src/mekhq/campaign/CampaignOptions.java | 13 ++++ .../personnel/RetirementDefectionTracker.java | 35 ++++++++--- .../enums/TurnoverTargetNumberMethod.java | 60 +++++++++++++++++++ .../mekhq/gui/panes/CampaignOptionsPane.java | 48 ++++++++++++++- 6 files changed, 159 insertions(+), 11 deletions(-) create mode 100644 MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index a014b1b590..f7a2046a7b 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -239,6 +239,8 @@ chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure. randomRetirementPanel.title=Random Employee Turnover (AtB Only) lblRandomRetirementMethod.text=Random Employee Turnover Method lblRandomRetirementMethod.toolTipText=This is the method used to determine if a person will randomly leave the unit. +lblTurnoverTargetNumberMethod.text=Turnover Target Number Method +lblTurnoverTargetNumberMethod.toolTipText=This is the method used to determine the base target number for turnover checks. lblTurnoverFixedTargetNumber.text=Turnover Fixed Target Number lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks. chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index 23364d6180..70dbde7488 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -459,8 +459,16 @@ RandomProcreationMethod.PERCENTAGE.toolTipText=This checks if a random value is # RandomRetirementMethod Enum RandomRetirementMethod.NONE.text=Disabled RandomRetirementMethod.NONE.toolTipText=Random Employee Turnover is disabled. -RandomRetirementMethod.AGAINST_THE_BOT.text=Against the Bot -RandomRetirementMethod.AGAINST_THE_BOT.toolTipText=This follows the Employee Turnover rules as per the AtB rules in docs/AtB Stuff. +RandomRetirementMethod.AGAINST_THE_BOT.text=Enabled +RandomRetirementMethod.AGAINST_THE_BOT.toolTipText=This follows the Employee Turnover rules described in the Turnover Module documentation. + +# TurnoverTargetNumberMethod Enum +TurnoverTargetNumberMethod.FIXED.text=Fixed +TurnoverTargetNumberMethod.FIXED.toolTipText=Turnover checks use a fixed target number, set below. +RandomRetirementMethod.ADMINISTRATION.text=Administration +RandomRetirementMethod.ADMINISTRATION.toolTipText=The Target number is based on the Administration skill of the highest skilled Admin/HR personnel. +RandomRetirementMethod.NEGOTIATION.text=Negotiation +RandomRetirementMethod.NEGOTIATION.toolTipText=The Target number is based on the Negotiation skill of the highest skilled Admin/HR personnel. # RankSystemType Enum RankSystemType.DEFAULT.text=Default Rank System diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index fc1d039a1e..c030640766 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -249,6 +249,7 @@ public static String getTransitUnitName(final int unit) { // Retirement private boolean useRetirementDateTracking; private RandomRetirementMethod randomRetirementMethod; + private TurnoverTargetNumberMethod turnoverTargetNumberMethod; private Integer turnoverFixedTargetNumber; private boolean useYearEndRandomRetirement; private boolean useContractCompletionRandomRetirement; @@ -724,6 +725,7 @@ public CampaignOptions() { // Retirement setUseRetirementDateTracking(false); setRandomRetirementMethod(RandomRetirementMethod.NONE); + setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.NEGOTIATION); setTurnoverFixedTargetNumber(5); setUseYearEndRandomRetirement(true); setUseContractCompletionRandomRetirement(true); @@ -1592,6 +1594,14 @@ public void setRandomRetirementMethod(final RandomRetirementMethod randomRetirem this.randomRetirementMethod = randomRetirementMethod; } + public TurnoverTargetNumberMethod getTurnoverTargetNumberMethod() { + return turnoverTargetNumberMethod; + } + + public void setTurnoverTargetNumberMethod (final TurnoverTargetNumberMethod turnoverTargetNumberMethod) { + this.turnoverTargetNumberMethod = turnoverTargetNumberMethod; + } + public boolean isUseYearEndRandomRetirement() { return useYearEndRandomRetirement; } @@ -4004,6 +4014,7 @@ public void writeToXml(final PrintWriter pw, int indent) { //region Retirement MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRetirementDateTracking", isUseRetirementDateTracking()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "randomRetirementMethod", getRandomRetirementMethod().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverTargetNumberMethod", getTurnoverTargetNumberMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useYearEndRandomRetirement", isUseYearEndRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useContractCompletionRandomRetirement", isUseContractCompletionRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); @@ -4641,6 +4652,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseRetirementDateTracking(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("randomRetirementMethod")) { retVal.setRandomRetirementMethod(RandomRetirementMethod.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("turnoverTargetNumberMethod")) { + retVal.setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useYearEndRandomRetirement")) { retVal.setUseYearEndRandomRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useContractCompletionRandomRetirement")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 59b035f2cc..76702255f5 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -83,10 +83,10 @@ public static Money getShareValue(Campaign campaign) { netWorth = netWorth.minus(r.getLargeCraftValue()); } - int totalShares = 0; - for (Person p : campaign.getActivePersonnel()) { - totalShares += p.getNumShares(campaign, campaign.getCampaignOptions().isSharesForAll()); - } + int totalShares = campaign.getActivePersonnel() + .stream() + .mapToInt(p -> p.getNumShares(campaign, campaign.getCampaignOptions().isSharesForAll())) + .sum(); if (totalShares <= 0) { return Money.zero(); @@ -126,7 +126,7 @@ private static int getAgeMod(int age) { * all active personnel who are not dependents, prisoners, or bondsmen. * * @param contract The contract that is being resolved; if the retirement roll is not due to - * contract resolutions (e.g. > 12 months since last roll), this can be null. + * contract resolutions (e.g., > 12 months since last roll), this can be null. * @param campaign The campaign to calculate target numbers for * @return A map with person ids as key and calculated target roll as value. */ @@ -196,7 +196,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract continue; } - TargetRoll target = new TargetRoll(campaign.getCampaignOptions().getTurnoverFixedTargetNumber(), "Base"); + TargetRoll target = new TargetRoll(getBaseTargetNumber(campaign), "Base"); // Skill Rating modifier int skillRating = p.getExperienceLevel(campaign, false); @@ -223,7 +223,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract break; default: skillRatingDescription = "Error, please see log"; - LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning " + skillRating); + LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating); } target.addModifier(skillRating, skillRatingDescription); @@ -336,6 +336,27 @@ public Map calculateTargetNumbers(final @Nullable AtBContract return targets; } + private static Integer getBaseTargetNumber(Campaign campaign) { + if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { + try { + return campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN) + .getSkill(SkillType.S_ADMIN) + .getFinalSkillValue(); + } catch (Exception e) { + return 13; + } + } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { + try { + return campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_NEG) + .getSkill(SkillType.S_NEG) + .getFinalSkillValue(); + } catch (Exception e) { + return 13; + } + } else { + return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); + } + } /** diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java new file mode 100644 index 0000000000..b745643127 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021-2022 - 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.personnel.enums; + +import mekhq.MekHQ; + +import java.util.ResourceBundle; + +public enum TurnoverTargetNumberMethod { + FIXED("TurnoverTargetNumberMethod.FIXED.text", "TurnoverTargetNumberMethod.FIXED.toolTipText"), + ADMINISTRATION("RandomRetirementMethod.ADMINISTRATION.text", "RandomRetirementMethod.ADMINISTRATION.toolTipText"), + NEGOTIATION("RandomRetirementMethod.NEGOTIATION.text", "RandomRetirementMethod.NEGOTIATION.toolTipText"); + + private final String name; + private final String toolTipText; + + TurnoverTargetNumberMethod(final String name, final String toolTipText) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", + MekHQ.getMHQOptions().getLocale()); + this.name = resources.getString(name); + this.toolTipText = resources.getString(toolTipText); + } + + public String getToolTipText() { + return toolTipText; + } + + public boolean isFixed() { + return this == FIXED; + } + + public boolean isAdministration() { + return this == ADMINISTRATION; + } + + public boolean isNegotiation() { + return this == NEGOTIATION; + } + + @Override + public String toString() { + return name; + } +} diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 891ac95448..67ce0325f9 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -254,6 +254,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseRetirementDateTracking; private JPanel randomRetirementPanel; private MMComboBox comboRandomRetirementMethod; + private MMComboBox comboTurnoverTargetNumberMethod; private JSpinner spnTurnoverFixedTargetNumber; private JCheckBox chkUseYearEndRandomRetirement; private JCheckBox chkUseContractCompletionRandomRetirement; @@ -3637,10 +3638,42 @@ private void createRandomRetirementPanel() { final JLabel lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); + lblTurnoverFixedTargetNumber.setEnabled(false); spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(5, 0, 12, 1)); spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); spnTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); + lblTurnoverFixedTargetNumber.setEnabled(false); + + final JLabel lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); + lblTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); + lblTurnoverTargetNumberMethod.setName("lblTurnoverTargetNumberMethod"); + + comboTurnoverTargetNumberMethod = new MMComboBox<>("comboTurnoverTargetNumberMethod", TurnoverTargetNumberMethod.values()); + comboTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); + comboTurnoverTargetNumberMethod.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(final JList list, final Object value, + final int index, final boolean isSelected, + final boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof TurnoverTargetNumberMethod) { + list.setToolTipText(((TurnoverTargetNumberMethod) value).getToolTipText()); + } + return this; + } + }); + comboTurnoverTargetNumberMethod.addActionListener(evt -> { + final TurnoverTargetNumberMethod method = comboTurnoverTargetNumberMethod.getSelectedItem(); + + if (method == null) { + return; + } + + final boolean enabled = randomRetirementPanel.isEnabled() && method.isFixed(); + lblTurnoverFixedTargetNumber.setEnabled(enabled); + spnTurnoverFixedTargetNumber.setEnabled(enabled); + }); chkUseYearEndRandomRetirement = new JCheckBox(resources.getString("chkUseYearEndRandomRetirement.text")); chkUseYearEndRandomRetirement.setToolTipText(resources.getString("chkUseYearEndRandomRetirement.toolTipText")); @@ -3690,8 +3723,10 @@ public Component getListCellRendererComponent(final JList list, final Object return; } final boolean enabled = randomRetirementPanel.isEnabled() && !method.isNone(); - lblTurnoverFixedTargetNumber.setEnabled(enabled); - spnTurnoverFixedTargetNumber.setEnabled(enabled); + lblTurnoverTargetNumberMethod.setEnabled(enabled); + comboTurnoverTargetNumberMethod.setEnabled(enabled); + lblTurnoverFixedTargetNumber.setEnabled(enabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); + spnTurnoverFixedTargetNumber.setEnabled(enabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); chkUseYearEndRandomRetirement.setEnabled(enabled); chkUseContractCompletionRandomRetirement.setEnabled(enabled); chkUseCustomRetirementModifiers.setEnabled(enabled); @@ -3703,6 +3738,7 @@ public Component getListCellRendererComponent(final JList list, final Object // Programmatically Assign Accessibility Labels lblRandomRetirementMethod.setLabelFor(comboRandomRetirementMethod); + lblTurnoverTargetNumberMethod.setLabelFor(comboTurnoverTargetNumberMethod); // Layout the Panel randomRetirementPanel = new JDisableablePanel("randomRetirementPanel"); @@ -3718,6 +3754,9 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblRandomRetirementMethod) .addComponent(comboRandomRetirementMethod, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblTurnoverTargetNumberMethod) + .addComponent(comboTurnoverTargetNumberMethod, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblTurnoverFixedTargetNumber) .addComponent(spnTurnoverFixedTargetNumber, Alignment.LEADING)) @@ -3735,6 +3774,9 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createSequentialGroup() .addComponent(lblRandomRetirementMethod) .addComponent(comboRandomRetirementMethod)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblTurnoverTargetNumberMethod) + .addComponent(comboTurnoverTargetNumberMethod)) .addGroup(layout.createSequentialGroup() .addComponent(lblTurnoverFixedTargetNumber) .addComponent(spnTurnoverFixedTargetNumber)) @@ -6890,6 +6932,7 @@ public void setOptions(@Nullable CampaignOptions options, // Retirement chkUseRetirementDateTracking.setSelected(options.isUseRetirementDateTracking()); comboRandomRetirementMethod.setSelectedItem(options.getRandomRetirementMethod()); + comboTurnoverTargetNumberMethod.setSelectedItem(options.getTurnoverTargetNumberMethod()); spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); chkUseYearEndRandomRetirement.setSelected(options.isUseYearEndRandomRetirement()); chkUseContractCompletionRandomRetirement.setSelected(options.isUseContractCompletionRandomRetirement()); @@ -7503,6 +7546,7 @@ public void updateOptions() { // Retirement options.setUseRetirementDateTracking(chkUseRetirementDateTracking.isSelected()); options.setRandomRetirementMethod(comboRandomRetirementMethod.getSelectedItem()); + options.setTurnoverTargetNumberMethod(comboTurnoverTargetNumberMethod.getSelectedItem()); options.setTurnoverFixedTargetNumber((Integer) spnTurnoverFixedTargetNumber.getValue()); options.setUseYearEndRandomRetirement(chkUseYearEndRandomRetirement.isSelected()); options.setUseContractCompletionRandomRetirement(chkUseContractCompletionRandomRetirement.isSelected()); From 2e334a2f1437ba8b31ea8d1c0bfbec32851cfaac Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 15 May 2024 22:23:02 -0500 Subject: [PATCH 005/101] Updated Employee Turnover parameters and dialog This commit updates the Employee Turnover parameters in the CampaignOptions.java file, changes the dialog text in the CampaignOptionsDialog.properties file, and modifies the RetirementDefectionTracker.java. Changes include adjusting the turnover fixed target number and clarifying the tooltip text for improved user experience. Additional checks and modifiers for personnel departure were also implemented. --- .../CampaignOptionsDialog.properties | 12 ++-- MekHQ/src/mekhq/campaign/CampaignOptions.java | 2 +- .../personnel/RetirementDefectionTracker.java | 59 ++++++++++++------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index f7a2046a7b..d45b19dc79 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -234,15 +234,15 @@ chkUseDylansRandomXP.toolTipText=Use Dylan's optional random XP on creation of a # Employee Turnover retirementPanel.title=Employee Turnover (Unofficial) -chkUseRetirementDateTracking.text=Track Turnover Date -chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure. +chkUseRetirementDateTracking.text=Track Departure Date +chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure from the unit. randomRetirementPanel.title=Random Employee Turnover (AtB Only) -lblRandomRetirementMethod.text=Random Employee Turnover Method -lblRandomRetirementMethod.toolTipText=This is the method used to determine if a person will randomly leave the unit. +lblRandomRetirementMethod.text=Enable Employee Turnover +lblRandomRetirementMethod.toolTipText=Determines whether personnel will randomly leave the unit. lblTurnoverTargetNumberMethod.text=Turnover Target Number Method lblTurnoverTargetNumberMethod.toolTipText=This is the method used to determine the base target number for turnover checks. -lblTurnoverFixedTargetNumber.text=Turnover Fixed Target Number -lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks. +lblTurnoverFixedTargetNumber.text=Fixed Target Number +lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks, when using the Fixed method. chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls chkUseYearEndRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of each year. chkUseContractCompletionRandomRetirement.text=Use Contract Completion Turnover Rolls diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index c030640766..ebb748d6cd 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -726,7 +726,7 @@ public CampaignOptions() { setUseRetirementDateTracking(false); setRandomRetirementMethod(RandomRetirementMethod.NONE); setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.NEGOTIATION); - setTurnoverFixedTargetNumber(5); + setTurnoverFixedTargetNumber(3); setUseYearEndRandomRetirement(true); setUseContractCompletionRandomRetirement(true); setUseCustomRetirementModifiers(true); diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 76702255f5..7194aee85d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -32,6 +32,8 @@ import mekhq.campaign.mission.Mission; import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Profession; +import mekhq.campaign.universe.FactionHints; +import mekhq.campaign.universe.Factions; import mekhq.utilities.MHQXMLUtility; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -192,7 +194,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } /* Infantry units retire or defect by platoon */ - if ((null != p.getUnit()) && p.getUnit().usesSoldiers() && !p.getUnit().isCommander(p)) { + if ((p.getUnit() != null) && (p.getUnit().usesSoldiers()) && (!p.getUnit().isCommander(p))) { continue; } @@ -259,6 +261,10 @@ public Map calculateTargetNumbers(final @Nullable AtBContract if (p.getOriginFaction().isClan()) { target.addModifier(-2, "Clan"); } + + if (FactionHints.defaultFactionHints().isAtWarWith(campaign.getFaction(), p.getOriginFaction(), campaign.getLocalDate())) { + target.addModifier(1, "Enemy Faction"); + } } // Officer Modifiers @@ -302,15 +308,10 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } if (c != null) { - target.addModifier(-((c.getSharesPct() - 20) / 10), "Shares"); + target.addModifier(-((c.getSharesPct() - 10) / 10), "Shares"); } } - // Role Modifiers - if (p.getPrimaryRole().isSoldier()) { - target.addModifier(-1, p.getPrimaryRole().toString()); - } - // Injury Modifiers int injuryMod = (int) p.getInjuries() .stream() @@ -336,25 +337,28 @@ public Map calculateTargetNumbers(final @Nullable AtBContract return targets; } + /** + * This method calculates the base target number. + * + * @param campaign the campaign for which the base target number is calculated + * @return the base target number + */ private static Integer getBaseTargetNumber(Campaign campaign) { - if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { - try { + try { + if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { return campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN) .getSkill(SkillType.S_ADMIN) .getFinalSkillValue(); - } catch (Exception e) { - return 13; - } - } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { - try { + } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { return campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_NEG) .getSkill(SkillType.S_NEG) .getFinalSkillValue(); - } catch (Exception e) { - return 13; + } else { + return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); } - } else { - return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); + // this means there isn't someone in the campaign with the relevant skill + } catch (Exception e) { + return 13; } } @@ -399,8 +403,23 @@ public void rollRetirement(final @Nullable Mission mission, final Map Date: Wed, 15 May 2024 22:24:21 -0500 Subject: [PATCH 006/101] Remove unused import in RetirementDefectionTracker --- .../src/mekhq/campaign/personnel/RetirementDefectionTracker.java | 1 - 1 file changed, 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 7194aee85d..488a45adfc 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -33,7 +33,6 @@ import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Profession; import mekhq.campaign.universe.FactionHints; -import mekhq.campaign.universe.Factions; import mekhq.utilities.MHQXMLUtility; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; From 2baadc788698265dcba5877ed14b5d8171d8883a Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 12:28:28 -0500 Subject: [PATCH 007/101] Replaced RandomRetirementMethod enum with a boolean option --- .../CampaignOptionsDialog.properties | 6 +- .../mekhq/resources/Personnel.properties | 14 +- MekHQ/src/mekhq/campaign/Campaign.java | 2 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 25 ++-- .../enums/RandomRetirementMethod.java | 77 ----------- .../enums/TurnoverTargetNumberMethod.java | 4 +- MekHQ/src/mekhq/gui/BriefingTab.java | 2 +- MekHQ/src/mekhq/gui/CampaignGUI.java | 5 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 127 ++++++++---------- .../enums/RandomRetirementMethodTest.java | 79 ----------- .../enums/TurnoverTargetNumberMethodTest.java | 94 +++++++++++++ 11 files changed, 174 insertions(+), 261 deletions(-) delete mode 100644 MekHQ/src/mekhq/campaign/personnel/enums/RandomRetirementMethod.java delete mode 100644 MekHQ/unittests/mekhq/campaign/personnel/enums/RandomRetirementMethodTest.java create mode 100644 MekHQ/unittests/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethodTest.java diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index d45b19dc79..4aa317366a 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -233,12 +233,12 @@ chkUseDylansRandomXP.text=Use Dylan's Random XP (Unofficial) chkUseDylansRandomXP.toolTipText=Use Dylan's optional random XP on creation of a new person (20% chance each of 0, 1, 2, 3, and randomized between 1 and 8 XP) # Employee Turnover -retirementPanel.title=Employee Turnover (Unofficial) +retirementPanel.title=Employee Turnover chkUseRetirementDateTracking.text=Track Departure Date chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure from the unit. randomRetirementPanel.title=Random Employee Turnover (AtB Only) -lblRandomRetirementMethod.text=Enable Employee Turnover -lblRandomRetirementMethod.toolTipText=Determines whether personnel will randomly leave the unit. +chkUseRandomRetirement.text=Enable Employee Turnover +chkUseRandomRetirement.toolTipText=Determines whether personnel will randomly leave the unit. lblTurnoverTargetNumberMethod.text=Turnover Target Number Method lblTurnoverTargetNumberMethod.toolTipText=This is the method used to determine the base target number for turnover checks. lblTurnoverFixedTargetNumber.text=Fixed Target Number diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index 70dbde7488..b430df8b25 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -456,19 +456,13 @@ RandomProcreationMethod.NONE.toolTipText=Random procreation is disabled. RandomProcreationMethod.PERCENTAGE.text=Percentage RandomProcreationMethod.PERCENTAGE.toolTipText=This checks if a random value is lower than the percentage to determine if an eligible person procreates on a given day. -# RandomRetirementMethod Enum -RandomRetirementMethod.NONE.text=Disabled -RandomRetirementMethod.NONE.toolTipText=Random Employee Turnover is disabled. -RandomRetirementMethod.AGAINST_THE_BOT.text=Enabled -RandomRetirementMethod.AGAINST_THE_BOT.toolTipText=This follows the Employee Turnover rules described in the Turnover Module documentation. - # TurnoverTargetNumberMethod Enum TurnoverTargetNumberMethod.FIXED.text=Fixed TurnoverTargetNumberMethod.FIXED.toolTipText=Turnover checks use a fixed target number, set below. -RandomRetirementMethod.ADMINISTRATION.text=Administration -RandomRetirementMethod.ADMINISTRATION.toolTipText=The Target number is based on the Administration skill of the highest skilled Admin/HR personnel. -RandomRetirementMethod.NEGOTIATION.text=Negotiation -RandomRetirementMethod.NEGOTIATION.toolTipText=The Target number is based on the Negotiation skill of the highest skilled Admin/HR personnel. +TurnoverTargetNumberMethod.ADMINISTRATION.text=Administration +TurnoverTargetNumberMethod.ADMINISTRATION.toolTipText=The Target number is based on the Administration skill of the highest skilled Admin/HR personnel. +TurnoverTargetNumberMethod.NEGOTIATION.text=Negotiation +TurnoverTargetNumberMethod.NEGOTIATION.toolTipText=The Target number is based on the Negotiation skill of the highest skilled Admin/HR personnel. # RankSystemType Enum RankSystemType.DEFAULT.text=Default Rank System diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 68127426ed..6fa1385e30 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -6843,7 +6843,7 @@ public boolean checkRetirementDefections() { } public boolean checkYearlyRetirements() { - if (!getCampaignOptions().getRandomRetirementMethod().isNone() + if (getCampaignOptions().getUseRandomRetirement() && getCampaignOptions().isUseYearEndRandomRetirement() && (ChronoUnit.DAYS.between(getRetirementDefectionTracker().getLastRetirementRoll(), getLocalDate()) == getRetirementDefectionTracker().getLastRetirementRoll().lengthOfYear())) { diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index ebb748d6cd..6ee80fe14d 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -248,7 +248,7 @@ public static String getTransitUnitName(final int unit) { // Retirement private boolean useRetirementDateTracking; - private RandomRetirementMethod randomRetirementMethod; + private boolean useRandomRetirement; private TurnoverTargetNumberMethod turnoverTargetNumberMethod; private Integer turnoverFixedTargetNumber; private boolean useYearEndRandomRetirement; @@ -724,7 +724,7 @@ public CampaignOptions() { // Retirement setUseRetirementDateTracking(false); - setRandomRetirementMethod(RandomRetirementMethod.NONE); + setUseRandomRetirement(true); setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.NEGOTIATION); setTurnoverFixedTargetNumber(3); setUseYearEndRandomRetirement(true); @@ -1586,12 +1586,12 @@ public void setUseRetirementDateTracking(final boolean useRetirementDateTracking this.useRetirementDateTracking = useRetirementDateTracking; } - public RandomRetirementMethod getRandomRetirementMethod() { - return randomRetirementMethod; + public boolean getUseRandomRetirement() { + return useRandomRetirement; } - public void setRandomRetirementMethod(final RandomRetirementMethod randomRetirementMethod) { - this.randomRetirementMethod = randomRetirementMethod; + public void setUseRandomRetirement(final boolean useRandomRetirement) { + this.useRandomRetirement = useRandomRetirement; } public TurnoverTargetNumberMethod getTurnoverTargetNumberMethod() { @@ -4013,7 +4013,7 @@ public void writeToXml(final PrintWriter pw, int indent) { //region Retirement MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRetirementDateTracking", isUseRetirementDateTracking()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "randomRetirementMethod", getRandomRetirementMethod().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomRetirement", getUseRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverTargetNumberMethod", getTurnoverTargetNumberMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useYearEndRandomRetirement", isUseYearEndRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useContractCompletionRandomRetirement", isUseContractCompletionRandomRetirement()); @@ -4277,7 +4277,7 @@ public void writeToXml(final PrintWriter pw, int indent) { } public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version version) { - LogManager.getLogger().info("Loading Campaign Options from Version " + version + " XML..."); + LogManager.getLogger().info("Loading Campaign Options from Version {} XML...", version); wn.normalize(); CampaignOptions retVal = new CampaignOptions(); @@ -4650,8 +4650,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve //region Retirement } else if (wn2.getNodeName().equalsIgnoreCase("useRetirementDateTracking")) { retVal.setUseRetirementDateTracking(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("randomRetirementMethod")) { - retVal.setRandomRetirementMethod(RandomRetirementMethod.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useRandomRetirement")) { + retVal.setUseRandomRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("turnoverTargetNumberMethod")) { retVal.setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useYearEndRandomRetirement")) { @@ -5197,11 +5197,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.getRandomOriginOptions().setExtraRandomOrigin(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("originDistanceScale")) { // Legacy, 0.49.7 Removal retVal.getRandomOriginOptions().setOriginDistanceScale(Double.parseDouble(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("retirementRolls")) { // Legacy - 0.49.7 Removal - final boolean value = Boolean.parseBoolean(wn2.getTextContent().trim()); - retVal.setRandomRetirementMethod((value && retVal.isUseAtB()) ? RandomRetirementMethod.AGAINST_THE_BOT : RandomRetirementMethod.NONE); - retVal.setUseYearEndRandomRetirement(value); - retVal.setUseContractCompletionRandomRetirement(value); } else if (wn2.getNodeName().equalsIgnoreCase("customRetirementMods")) { // Legacy - 0.49.7 Removal retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("foundersNeverRetire")) { // Legacy - 0.49.7 Removal diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/RandomRetirementMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/RandomRetirementMethod.java deleted file mode 100644 index 2ad0182bf7..0000000000 --- a/MekHQ/src/mekhq/campaign/personnel/enums/RandomRetirementMethod.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2021-2022 - 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.personnel.enums; - -import mekhq.MekHQ; - -import java.util.ResourceBundle; - -public enum RandomRetirementMethod { - //region Enum Declarations - NONE("RandomRetirementMethod.NONE.text", "RandomRetirementMethod.NONE.toolTipText"), - AGAINST_THE_BOT("RandomRetirementMethod.AGAINST_THE_BOT.text", "RandomRetirementMethod.AGAINST_THE_BOT.toolTipText"); - //endregion Enum Declarations - - //region Variable Declarations - private final String name; - private final String toolTipText; - //endregion Variable Declarations - - //region Constructors - RandomRetirementMethod(final String name, final String toolTipText) { - final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", - MekHQ.getMHQOptions().getLocale()); - this.name = resources.getString(name); - this.toolTipText = resources.getString(toolTipText); - } - //endregion Constructors - - //region Getters - public String getToolTipText() { - return toolTipText; - } - //endregion Getters - - //region Boolean Comparison Methods - public boolean isNone() { - return this == NONE; - } - - public boolean isAgainstTheBot() { - return this == AGAINST_THE_BOT; - } - //endregion Boolean Comparison Methods - -/* - public AbstractRetirement getMethod(final CampaignOptions options) { - switch (this) { - case AGAINST_THE_BOT: - return new AtBRandomRetirement(options); - case NONE: - default: - return new DisabledRandomRetirement(options); - } - } -*/ - - @Override - public String toString() { - return name; - } -} diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java index b745643127..d0a50f6156 100644 --- a/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java +++ b/MekHQ/src/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethod.java @@ -24,8 +24,8 @@ public enum TurnoverTargetNumberMethod { FIXED("TurnoverTargetNumberMethod.FIXED.text", "TurnoverTargetNumberMethod.FIXED.toolTipText"), - ADMINISTRATION("RandomRetirementMethod.ADMINISTRATION.text", "RandomRetirementMethod.ADMINISTRATION.toolTipText"), - NEGOTIATION("RandomRetirementMethod.NEGOTIATION.text", "RandomRetirementMethod.NEGOTIATION.toolTipText"); + ADMINISTRATION("TurnoverTargetNumberMethod.ADMINISTRATION.text", "TurnoverTargetNumberMethod.ADMINISTRATION.toolTipText"), + NEGOTIATION("TurnoverTargetNumberMethod.NEGOTIATION.text", "TurnoverTargetNumberMethod.NEGOTIATION.toolTipText"); private final String name; private final String toolTipText; diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index ea1382dae5..d4ca229af1 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -357,7 +357,7 @@ private void completeMission() { return; } - if (getCampaign().getCampaignOptions().getRandomRetirementMethod().isAgainstTheBot() + if (getCampaign().getCampaignOptions().getUseRandomRetirement() && getCampaign().getCampaignOptions().isUseContractCompletionRandomRetirement()) { RetirementDefectionDialog rdd = new RetirementDefectionDialog(getCampaignGui(), (AtBContract) mission, true); diff --git a/MekHQ/src/mekhq/gui/CampaignGUI.java b/MekHQ/src/mekhq/gui/CampaignGUI.java index 9220d55c20..43b1f64c2d 100644 --- a/MekHQ/src/mekhq/gui/CampaignGUI.java +++ b/MekHQ/src/mekhq/gui/CampaignGUI.java @@ -30,7 +30,6 @@ import megamek.client.ui.swing.util.UIUtil; import megamek.common.*; import megamek.common.annotations.Nullable; -import megamek.common.enums.SkillLevel; import megamek.common.event.Subscribe; import megamek.common.loaders.EntityLoadingException; import mekhq.*; @@ -923,7 +922,7 @@ private void initMenu() { miRetirementDefectionDialog = new JMenuItem(resourceMap.getString("miRetirementDefectionDialog.text")); miRetirementDefectionDialog.setMnemonic(KeyEvent.VK_R); miRetirementDefectionDialog.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_DOWN_MASK)); - miRetirementDefectionDialog.setVisible(!getCampaign().getCampaignOptions().getRandomRetirementMethod().isNone()); + miRetirementDefectionDialog.setVisible(getCampaign().getCampaignOptions().getUseRandomRetirement()); miRetirementDefectionDialog.addActionListener(evt -> showRetirementDefectionDialog()); menuView.add(miRetirementDefectionDialog); @@ -2475,7 +2474,7 @@ public void handle(final OptionsChangedEvent evt) { fundsScheduler.schedule(); refreshPartsAvailability(); - miRetirementDefectionDialog.setVisible(!evt.getOptions().getRandomRetirementMethod().isNone()); + miRetirementDefectionDialog.setVisible(evt.getOptions().getUseRandomRetirement()); miUnitMarket.setVisible(!evt.getOptions().getUnitMarketMethod().isNone()); } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 67ce0325f9..37fbb3f449 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -253,8 +253,10 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { // Retirement private JCheckBox chkUseRetirementDateTracking; private JPanel randomRetirementPanel; - private MMComboBox comboRandomRetirementMethod; + private JCheckBox chkUseRandomRetirement; + private JLabel lblTurnoverTargetNumberMethod; private MMComboBox comboTurnoverTargetNumberMethod; + private JLabel lblTurnoverFixedTargetNumber; private JSpinner spnTurnoverFixedTargetNumber; private JCheckBox chkUseYearEndRandomRetirement; private JCheckBox chkUseContractCompletionRandomRetirement; @@ -267,8 +269,11 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseFactionModifiers; private JCheckBox chkUseLeadershipModifiers; private JPanel turnoverPayoutPanel; + private JLabel lblPayoutRateOfficer; private JSpinner spnPayoutRateOfficer; + private JLabel lblPayoutRateEnlisted; private JSpinner spnPayoutRateEnlisted; + private JLabel lblPayoutRetirementMultiplier; private JSpinner spnPayoutRetirementMultiplier; private JCheckBox chkUsePayoutServiceBonus; private JLabel lblPayoutServiceBonusRate; @@ -2658,7 +2663,7 @@ private JScrollPane createAgainstTheBotTab() { if (randomRetirementPanel.isEnabled() != enabled) { randomRetirementPanel.setEnabled(enabled); if (enabled) { - comboRandomRetirementMethod.setSelectedItem(comboRandomRetirementMethod.getSelectedItem()); + chkUseRandomRetirement.setSelected(true); } } @@ -3606,7 +3611,30 @@ private JPanel createRetirementPanel() { chkUseRetirementDateTracking.setToolTipText(resources.getString("chkUseRetirementDateTracking.toolTipText")); chkUseRetirementDateTracking.setName("chkUseRetirementDateTracking"); - createRandomRetirementPanel(); + JPanel randomRetirementPanel = createRandomRetirementPanel(); + + // global enable + chkUseRandomRetirement = new JCheckBox(resources.getString("chkUseRandomRetirement.text")); + chkUseRandomRetirement.setToolTipText(resources.getString("chkUseRandomRetirement.toolTipText")); + chkUseRandomRetirement.setName("chkUseRandomRetirement"); + chkUseRandomRetirement.addActionListener(evt -> { + final boolean isEnabled = chkUseRandomRetirement.isSelected(); + + // general handler + for (int index = 0; index < Arrays.stream(randomRetirementPanel.getComponents()).count(); index++) { + randomRetirementPanel.getComponent(index).setEnabled(isEnabled); + } + + // special handlers + randomRetirementPanel.setEnabled((isEnabled)); + lblTurnoverFixedTargetNumber.setEnabled(isEnabled + && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); + spnTurnoverFixedTargetNumber.setEnabled(isEnabled + && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); + }); + + // this prevents a really annoying bug where disabled options don't stay disabled when reloading Campaign Options + randomRetirementPanel.setEnabled(campaign.getCampaignOptions().getUseRandomRetirement()); // Layout the Panel final JPanel panel = new JPanel(); @@ -3621,21 +3649,23 @@ private JPanel createRetirementPanel() { layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkUseRetirementDateTracking) + .addComponent(chkUseRandomRetirement) .addComponent(randomRetirementPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseRetirementDateTracking) + .addComponent(chkUseRandomRetirement) .addComponent(randomRetirementPanel) ); return panel; } - private void createRandomRetirementPanel() { + private JPanel createRandomRetirementPanel() { // Create Panel Components - final JLabel lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); + lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); lblTurnoverFixedTargetNumber.setEnabled(false); @@ -3645,7 +3675,7 @@ private void createRandomRetirementPanel() { spnTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); lblTurnoverFixedTargetNumber.setEnabled(false); - final JLabel lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); + lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); lblTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); lblTurnoverTargetNumberMethod.setName("lblTurnoverTargetNumberMethod"); @@ -3683,61 +3713,14 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseContractCompletionRandomRetirement.setToolTipText(resources.getString("chkUseContractCompletionRandomRetirement.toolTipText")); chkUseContractCompletionRandomRetirement.setName("chkUseContractCompletionRandomRetirement"); - chkUseCustomRetirementModifiers = new JCheckBox(resources.getString("chkUseCustomRetirementModifiers.text")); - chkUseCustomRetirementModifiers.setToolTipText(resources.getString("chkUseCustomRetirementModifiers.toolTipText")); - chkUseCustomRetirementModifiers.setName("chkUseCustomRetirementModifiers"); - chkUseRandomFounderRetirement = new JCheckBox(resources.getString("chkUseRandomFounderRetirement.text")); chkUseRandomFounderRetirement.setToolTipText(resources.getString("chkUseRandomFounderRetirement.toolTipText")); chkUseRandomFounderRetirement.setName("chkUseRandomFounderRetirement"); - chkTrackUnitFatigue = new JCheckBox(resources.getString("chkTrackUnitFatigue.text")); - chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); - chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); - - JPanel turnoverModifiersPanel = createTurnoverModifiersPanel(); - JPanel turnoverPayoutPanel = createTurnoverPayoutPanel(); - - // main controller option - final JLabel lblRandomRetirementMethod = new JLabel(resources.getString("lblRandomRetirementMethod.text")); - lblRandomRetirementMethod.setToolTipText(resources.getString("lblRandomRetirementMethod.toolTipText")); - lblRandomRetirementMethod.setName("lblRandomRetirementMethod"); - - comboRandomRetirementMethod = new MMComboBox<>("comboRandomRetirementMethod", RandomRetirementMethod.values()); - comboRandomRetirementMethod.setToolTipText(resources.getString("lblRandomRetirementMethod.toolTipText")); - comboRandomRetirementMethod.setRenderer(new DefaultListCellRenderer() { - @Override - public Component getListCellRendererComponent(final JList list, final Object value, - final int index, final boolean isSelected, - final boolean cellHasFocus) { - super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (value instanceof RandomRetirementMethod) { - list.setToolTipText(((RandomRetirementMethod) value).getToolTipText()); - } - return this; - } - }); - comboRandomRetirementMethod.addActionListener(evt -> { - final RandomRetirementMethod method = comboRandomRetirementMethod.getSelectedItem(); - if (method == null) { - return; - } - final boolean enabled = randomRetirementPanel.isEnabled() && !method.isNone(); - lblTurnoverTargetNumberMethod.setEnabled(enabled); - comboTurnoverTargetNumberMethod.setEnabled(enabled); - lblTurnoverFixedTargetNumber.setEnabled(enabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); - spnTurnoverFixedTargetNumber.setEnabled(enabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); - chkUseYearEndRandomRetirement.setEnabled(enabled); - chkUseContractCompletionRandomRetirement.setEnabled(enabled); - chkUseCustomRetirementModifiers.setEnabled(enabled); - chkUseRandomFounderRetirement.setEnabled(enabled); - chkTrackUnitFatigue.setEnabled(enabled); - turnoverModifiersPanel.setEnabled(enabled); - turnoverPayoutPanel.setEnabled(enabled); - }); + turnoverModifiersPanel = createTurnoverModifiersPanel(); + turnoverPayoutPanel = createTurnoverPayoutPanel(); // Programmatically Assign Accessibility Labels - lblRandomRetirementMethod.setLabelFor(comboRandomRetirementMethod); lblTurnoverTargetNumberMethod.setLabelFor(comboTurnoverTargetNumberMethod); // Layout the Panel @@ -3751,9 +3734,6 @@ public Component getListCellRendererComponent(final JList list, final Object layout.setVerticalGroup( layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblRandomRetirementMethod) - .addComponent(comboRandomRetirementMethod, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblTurnoverTargetNumberMethod) .addComponent(comboTurnoverTargetNumberMethod, Alignment.LEADING)) @@ -3763,17 +3743,12 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseYearEndRandomRetirement) .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) - .addComponent(chkUseCustomRetirementModifiers) - .addComponent(chkTrackUnitFatigue) .addComponent(turnoverModifiersPanel) .addComponent(turnoverPayoutPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(lblRandomRetirementMethod) - .addComponent(comboRandomRetirementMethod)) .addGroup(layout.createSequentialGroup() .addComponent(lblTurnoverTargetNumberMethod) .addComponent(comboTurnoverTargetNumberMethod)) @@ -3783,14 +3758,18 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseYearEndRandomRetirement) .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) - .addComponent(chkUseCustomRetirementModifiers) - .addComponent(chkTrackUnitFatigue) .addComponent(turnoverModifiersPanel) .addComponent(turnoverPayoutPanel) ); + + return randomRetirementPanel; } private JPanel createTurnoverModifiersPanel() { + chkUseCustomRetirementModifiers = new JCheckBox(resources.getString("chkUseCustomRetirementModifiers.text")); + chkUseCustomRetirementModifiers.setToolTipText(resources.getString("chkUseCustomRetirementModifiers.toolTipText")); + chkUseCustomRetirementModifiers.setName("chkUseCustomRetirementModifiers"); + chkUseAgeModifiers = new JCheckBox(resources.getString("chkUseAgeModifiers.text")); chkUseAgeModifiers.setToolTipText(resources.getString("chkUseAgeModifiers.toolTipText")); chkUseAgeModifiers.setName("chkUseAgeModifiers"); @@ -3803,6 +3782,10 @@ private JPanel createTurnoverModifiersPanel() { chkUseFactionModifiers.setToolTipText(resources.getString("chkUseFactionModifiers.toolTipText")); chkUseFactionModifiers.setName("chkUseFactionModifiers"); + chkTrackUnitFatigue = new JCheckBox(resources.getString("chkTrackUnitFatigue.text")); + chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); + chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); + chkUseLeadershipModifiers = new JCheckBox(resources.getString("chkUseLeadershipModifiers.text")); chkUseLeadershipModifiers.setToolTipText(resources.getString("chkUseLeadershipModifiers.toolTipText")); chkUseLeadershipModifiers.setName("chkUseLeadershipModifiers"); @@ -3817,17 +3800,21 @@ private JPanel createTurnoverModifiersPanel() { layout.setVerticalGroup( layout.createSequentialGroup() + .addComponent(chkUseCustomRetirementModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) + .addComponent(chkTrackUnitFatigue) .addComponent(chkUseLeadershipModifiers) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseCustomRetirementModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) + .addComponent(chkTrackUnitFatigue) .addComponent(chkUseLeadershipModifiers) ); @@ -3835,7 +3822,7 @@ private JPanel createTurnoverModifiersPanel() { } private JPanel createTurnoverPayoutPanel() { - final JLabel lblPayoutRateOfficer = new JLabel(resources.getString("lblPayoutRateOfficer.text")); + lblPayoutRateOfficer = new JLabel(resources.getString("lblPayoutRateOfficer.text")); lblPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); lblPayoutRateOfficer.setName("lblPayoutRateOfficer"); @@ -3843,7 +3830,7 @@ private JPanel createTurnoverPayoutPanel() { spnPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); spnPayoutRateOfficer.setName("lblPayoutRateOfficer"); - final JLabel lblPayoutRateEnlisted = new JLabel(resources.getString("lblPayoutRateEnlisted.text")); + lblPayoutRateEnlisted = new JLabel(resources.getString("lblPayoutRateEnlisted.text")); lblPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); lblPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); @@ -3851,7 +3838,7 @@ private JPanel createTurnoverPayoutPanel() { spnPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); spnPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); - final JLabel lblPayoutRetirementMultiplier = new JLabel(resources.getString("lblPayoutRetirementMultiplier.text")); + lblPayoutRetirementMultiplier = new JLabel(resources.getString("lblPayoutRetirementMultiplier.text")); lblPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); lblPayoutRetirementMultiplier.setName("lblPayoutRetirementMultiplier"); @@ -6931,7 +6918,7 @@ public void setOptions(@Nullable CampaignOptions options, // Retirement chkUseRetirementDateTracking.setSelected(options.isUseRetirementDateTracking()); - comboRandomRetirementMethod.setSelectedItem(options.getRandomRetirementMethod()); + chkUseRandomRetirement.setSelected(options.getUseRandomRetirement()); comboTurnoverTargetNumberMethod.setSelectedItem(options.getTurnoverTargetNumberMethod()); spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); chkUseYearEndRandomRetirement.setSelected(options.isUseYearEndRandomRetirement()); @@ -7545,7 +7532,7 @@ public void updateOptions() { // Retirement options.setUseRetirementDateTracking(chkUseRetirementDateTracking.isSelected()); - options.setRandomRetirementMethod(comboRandomRetirementMethod.getSelectedItem()); + options.setUseRandomRetirement(chkUseRandomRetirement.isSelected()); options.setTurnoverTargetNumberMethod(comboTurnoverTargetNumberMethod.getSelectedItem()); options.setTurnoverFixedTargetNumber((Integer) spnTurnoverFixedTargetNumber.getValue()); options.setUseYearEndRandomRetirement(chkUseYearEndRandomRetirement.isSelected()); diff --git a/MekHQ/unittests/mekhq/campaign/personnel/enums/RandomRetirementMethodTest.java b/MekHQ/unittests/mekhq/campaign/personnel/enums/RandomRetirementMethodTest.java deleted file mode 100644 index 4f7acd6fa5..0000000000 --- a/MekHQ/unittests/mekhq/campaign/personnel/enums/RandomRetirementMethodTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2022 - 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.personnel.enums; - -import mekhq.MekHQ; -import org.junit.jupiter.api.Test; - -import java.util.ResourceBundle; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class RandomRetirementMethodTest { - //region Variable Declarations - private static final RandomRetirementMethod[] methods = RandomRetirementMethod.values(); - - private final transient ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", - MekHQ.getMHQOptions().getLocale()); - //endregion Variable Declarations - - //region Getters - @Test - public void testGetToolTipText() { - assertEquals(resources.getString("RandomRetirementMethod.NONE.toolTipText"), - RandomRetirementMethod.NONE.getToolTipText()); - assertEquals(resources.getString("RandomRetirementMethod.AGAINST_THE_BOT.toolTipText"), - RandomRetirementMethod.AGAINST_THE_BOT.getToolTipText()); - } - //endregion Getters - - //region Boolean Comparison Methods - @Test - public void testIsNone() { - for (final RandomRetirementMethod randomRetirementMethod : methods) { - if (randomRetirementMethod == RandomRetirementMethod.NONE) { - assertTrue(randomRetirementMethod.isNone()); - } else { - assertFalse(randomRetirementMethod.isNone()); - } - } - } - - @Test - public void testIsAgainstTheBot() { - for (final RandomRetirementMethod randomRetirementMethod : methods) { - if (randomRetirementMethod == RandomRetirementMethod.AGAINST_THE_BOT) { - assertTrue(randomRetirementMethod.isAgainstTheBot()); - } else { - assertFalse(randomRetirementMethod.isAgainstTheBot()); - } - } - } - //endregion Boolean Comparison Methods - - @Test - public void testToStringOverride() { - assertEquals(resources.getString("RandomRetirementMethod.NONE.text"), - RandomRetirementMethod.NONE.toString()); - assertEquals(resources.getString("RandomRetirementMethod.AGAINST_THE_BOT.text"), - RandomRetirementMethod.AGAINST_THE_BOT.toString()); - } -} diff --git a/MekHQ/unittests/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethodTest.java b/MekHQ/unittests/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethodTest.java new file mode 100644 index 0000000000..9622ca234b --- /dev/null +++ b/MekHQ/unittests/mekhq/campaign/personnel/enums/TurnoverTargetNumberMethodTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022 - 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.personnel.enums; + +import mekhq.MekHQ; +import org.junit.jupiter.api.Test; + +import java.util.ResourceBundle; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TurnoverTargetNumberMethodTest { + //region Variable Declarations + private static final TurnoverTargetNumberMethod[] methods = TurnoverTargetNumberMethod.values(); + + private final transient ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", + MekHQ.getMHQOptions().getLocale()); + //endregion Variable Declarations + + //region Getters + @Test + public void testGetToolTipText() { + assertEquals(resources.getString("TurnoverTargetNumberMethod.FIXED.toolTipText"), + TurnoverTargetNumberMethod.FIXED.getToolTipText()); + assertEquals(resources.getString("TurnoverTargetNumberMethod.ADMINISTRATION.toolTipText"), + TurnoverTargetNumberMethod.ADMINISTRATION.getToolTipText()); + assertEquals(resources.getString("TurnoverTargetNumberMethod.NEGOTIATION.toolTipText"), + TurnoverTargetNumberMethod.NEGOTIATION.getToolTipText()); + } + //endregion Getters + + //region Boolean Comparison Methods + @Test + public void testIsFixed() { + for (final TurnoverTargetNumberMethod TurnoverTargetNumberMethod : methods) { + if (TurnoverTargetNumberMethod == mekhq.campaign.personnel.enums.TurnoverTargetNumberMethod.FIXED) { + assertTrue(TurnoverTargetNumberMethod.isFixed()); + } else { + assertFalse(TurnoverTargetNumberMethod.isFixed()); + } + } + } + + @Test + public void testIsAdministration() { + for (final TurnoverTargetNumberMethod TurnoverTargetNumberMethod : methods) { + if (TurnoverTargetNumberMethod == mekhq.campaign.personnel.enums.TurnoverTargetNumberMethod.ADMINISTRATION) { + assertTrue(TurnoverTargetNumberMethod.isAdministration()); + } else { + assertFalse(TurnoverTargetNumberMethod.isAdministration()); + } + } + } + + @Test + public void testIsNegotiation() { + for (final TurnoverTargetNumberMethod TurnoverTargetNumberMethod : methods) { + if (TurnoverTargetNumberMethod == mekhq.campaign.personnel.enums.TurnoverTargetNumberMethod.NEGOTIATION) { + assertTrue(TurnoverTargetNumberMethod.isNegotiation()); + } else { + assertFalse(TurnoverTargetNumberMethod.isNegotiation()); + } + } + } + //endregion Boolean Comparison Methods + + @Test + public void testToStringOverride() { + assertEquals(resources.getString("TurnoverTargetNumberMethod.FIXED.text"), + TurnoverTargetNumberMethod.FIXED.toString()); + assertEquals(resources.getString("TurnoverTargetNumberMethod.ADMINISTRATION.text"), + TurnoverTargetNumberMethod.ADMINISTRATION.toString()); + assertEquals(resources.getString("TurnoverTargetNumberMethod.NEGOTIATION.text"), + TurnoverTargetNumberMethod.NEGOTIATION.toString()); + } +} From 38bf4c39ad1bab0ea877237a091c5a68a6745194 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 12:43:23 -0500 Subject: [PATCH 008/101] Refactored RetirementDefectionTracker.java by reorganizing code and improving readability Made a couple of changes, including reordering the checks for modifiers, making certain variables final, and optimizing the way HashSet is populated from an array using java Stream API. Ensured source code alignment with best practices and improved readability. --- .../personnel/RetirementDefectionTracker.java | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 488a45adfc..f5bd129f08 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -42,6 +42,7 @@ import java.io.PrintWriter; import java.time.LocalDate; import java.util.*; +import java.util.stream.Collectors; /** * @author Neoancient @@ -53,12 +54,12 @@ */ public class RetirementDefectionTracker { /* In case the dialog is closed after making the retirement rolls - * and determining payouts but before the retirees have been paid, + * and determining payouts, but before the retirees have been paid, * we store those results to avoid making the rolls again. */ - private Set rollRequired; - private Map> unresolvedPersonnel; - private Map payouts; + final private Set rollRequired; + final private Map> unresolvedPersonnel; + final private Map payouts; private LocalDate lastRetirementRoll; public RetirementDefectionTracker() { @@ -240,11 +241,6 @@ public Map calculateTargetNumbers(final @Nullable AtBContract target.addModifier(1, "Failed mission"); } - // Fatigue Modifiers - if (campaign.getCampaignOptions().isTrackUnitFatigue()) { - target.addModifier(campaign.getFatigueLevel() / 10, "Fatigue"); - } - // Faction Modifiers if (campaign.getCampaignOptions().isUseFactionModifiers()) { if (campaign.getFaction().isPirate()) { @@ -266,6 +262,25 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } + // Age modifiers + if (campaign.getCampaignOptions().isUseAgeModifiers()) { + int age = p.getAge(campaign.getLocalDate()); + int ageMod = getAgeMod(age); + + if (ageMod != 0) { + target.addModifier(ageMod, "Age"); + } + } + + // Injury Modifiers + int injuryMod = (int) p.getInjuries() + .stream() + .filter(Injury::isPermanent).count(); + + if (injuryMod > 0) { + target.addModifier(injuryMod, "Permanent Injuries"); + } + // Officer Modifiers if (p.getRank().isOfficer()) { target.addModifier(-1, "Officer"); @@ -281,14 +296,9 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } - // Age modifiers - if (campaign.getCampaignOptions().isUseAgeModifiers()) { - int age = p.getAge(campaign.getLocalDate()); - int ageMod = getAgeMod(age); - - if (ageMod != 0) { - target.addModifier(ageMod, "Age"); - } + // Founder Modifier + if (p.isFounder()) { + target.addModifier(2, "Founder"); } // Shares Modifiers @@ -311,15 +321,6 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } - // Injury Modifiers - int injuryMod = (int) p.getInjuries() - .stream() - .filter(Injury::isPermanent).count(); - - if (injuryMod > 0) { - target.addModifier(injuryMod, "Permanent Injuries"); - } - // Leadership Modifiers if(campaign.getCampaignOptions().isUseLeadershipModifiers()) { if ((combatLeadershipMod != 0) && p.getPrimaryRole().isCombat()) { @@ -331,6 +332,11 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } + // Fatigue Modifiers + if (campaign.getCampaignOptions().isTrackUnitFatigue()) { + target.addModifier(campaign.getFatigueLevel() / 10, "Fatigue"); + } + targets.put(p.getId(), target); } return targets; @@ -764,11 +770,11 @@ public static RetirementDefectionTracker generateInstanceFromXML(Node wn, Campai } if (wn3.getNodeName().equalsIgnoreCase("contract")) { int id = Integer.parseInt(wn3.getAttributes().getNamedItem("id").getTextContent()); - HashSet pids = new HashSet<>(); String [] ids = wn3.getTextContent().split(","); - for (String s : ids) { - pids.add(UUID.fromString(s)); - } + HashSet pids = Arrays + .stream(ids) + .map(UUID::fromString) + .collect(Collectors.toCollection(HashSet::new)); retVal.unresolvedPersonnel.put(id, pids); } } From 45dfd0b40f26940491c104e95e90291a213ae92a Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 13:09:00 -0500 Subject: [PATCH 009/101] Add functionality to track soldiers in a unit The commit introduces methods to retrieve all soldiers and uninjured soldiers in a given unit. The change also includes corrections to the RetirementDefectionTracker, whereby only soldiers in a unit commanded by the retiring person are now considered for payouts instead of the active crew. This ensures that only relevant personnel are handled during retirement and defection. --- .../personnel/RetirementDefectionTracker.java | 6 ++--- MekHQ/src/mekhq/campaign/unit/Unit.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index f5bd129f08..fd0a08ed74 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -414,10 +414,10 @@ public void rollRetirement(final @Nullable Mission mission, final Map getSoldiers() { + // soldiers appear in both the drivers and gunners list, + // so we use drivers + return drivers.stream() + .filter(person -> entity instanceof Infantry) + .collect(Collectors.toList()); + } + + /** + * Retrieves a list of uninjured soldiers in a unit. + * + * @return The list of uninjured soldiers. + */ + public List getActiveSoldiers() { + return getSoldiers().stream() + .filter(person -> person.getHits() == 0) + .collect(Collectors.toList()); + } + public List getActiveCrew() { List crew = new ArrayList<>(); for (Person p : drivers) { From 1e22ece3a072148a4ece286bed361278c1cb7331 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 13:12:48 -0500 Subject: [PATCH 010/101] Optimized imports in Unit.java --- MekHQ/src/mekhq/campaign/unit/Unit.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index 22f53c9eef..052bf06d69 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -36,7 +36,9 @@ import megamek.common.weapons.InfantryAttack; import megamek.common.weapons.bayweapons.BayWeapon; import megamek.common.weapons.infantry.InfantryWeapon; -import mekhq.*; +import mekhq.MHQStaticDirectoryManager; +import mekhq.MekHQ; +import mekhq.Utilities; import mekhq.campaign.Campaign; import mekhq.campaign.event.PersonCrewAssignmentEvent; import mekhq.campaign.event.PersonTechAssignmentEvent; @@ -62,6 +64,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.swing.*; import java.awt.*; import java.io.PrintWriter; import java.math.BigInteger; @@ -70,8 +73,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import javax.swing.UIManager; - /** * This is a wrapper class for entity, so that we can add some functionality to it * From a02790594ff7b085927b5c0cea9cf6f5f376412b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 13:30:53 -0500 Subject: [PATCH 011/101] Added option for soldiers to use commander's turnover roll A new option has been introduced to allow soldiers to use their commander's turnover roll. Corresponding changes have been made in the GUI and properties file. Furthermore, methods have been reordered in certain classes and a method name has been updated for clarity. --- .../CampaignOptionsDialog.properties | 2 ++ MekHQ/src/mekhq/campaign/CampaignOptions.java | 33 +++++++++++++------ .../personnel/RetirementDefectionTracker.java | 26 ++++++++------- .../mekhq/gui/panes/CampaignOptionsPane.java | 17 +++++++--- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 4aa317366a..81316bcc8e 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -251,6 +251,8 @@ chkUseCustomRetirementModifiers.text=Customize Employee Turnover Rolls chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. chkUseRandomFounderRetirement.text=Use Random Founder Turnover chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly leave the unit. +chkUseSubContractSoldiers.text=Soldiers Use Commander Turnover Roll +chkUseSubContractSoldiers.toolTipText=Commanders make Turnover rolls for all soldiers in their unit chkTrackUnitFatigue.text=Track Unit Fatigue chkTrackUnitFatigue.toolTipText=Continuous deployments without a break apply a penalty to Turnover checks. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 6ee80fe14d..4756200bdf 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -253,9 +253,8 @@ public static String getTransitUnitName(final int unit) { private Integer turnoverFixedTargetNumber; private boolean useYearEndRandomRetirement; private boolean useContractCompletionRandomRetirement; - private boolean useCustomRetirementModifiers; private boolean useRandomFounderRetirement; - private boolean trackUnitFatigue; + private boolean useSubContractSoldiers; private Integer payoutRateOfficer; private Integer payoutRateEnlisted; @@ -263,9 +262,11 @@ public static String getTransitUnitName(final int unit) { private boolean usePayoutServiceBonus; private Integer payoutServiceBonusRate; + private boolean useCustomRetirementModifiers; private boolean useAgeModifiers; private boolean useUnitRatingModifiers; private boolean useFactionModifiers; + private boolean trackUnitFatigue; private boolean useLeadershipModifiers; // Family @@ -731,6 +732,7 @@ public CampaignOptions() { setUseContractCompletionRandomRetirement(true); setUseCustomRetirementModifiers(true); setUseRandomFounderRetirement(true); + setUseSubContractSoldiers(false); setTrackUnitFatigue(false); setUseAgeModifiers(true); @@ -1586,7 +1588,7 @@ public void setUseRetirementDateTracking(final boolean useRetirementDateTracking this.useRetirementDateTracking = useRetirementDateTracking; } - public boolean getUseRandomRetirement() { + public boolean isUseRandomRetirement() { return useRandomRetirement; } @@ -1634,6 +1636,14 @@ public void setUseRandomFounderRetirement(final boolean useRandomFounderRetireme this.useRandomFounderRetirement = useRandomFounderRetirement; } + public boolean isUseSubContractSoldiers() { + return useSubContractSoldiers; + } + + public void setUseSubContractSoldiers(final boolean useSubContractSoldiers) { + this.useSubContractSoldiers = useSubContractSoldiers; + } + public boolean isTrackUnitFatigue() { return trackUnitFatigue; } @@ -4013,17 +4023,18 @@ public void writeToXml(final PrintWriter pw, int indent) { //region Retirement MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRetirementDateTracking", isUseRetirementDateTracking()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomRetirement", getUseRandomRetirement()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomRetirement", isUseRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverTargetNumberMethod", getTurnoverTargetNumberMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useYearEndRandomRetirement", isUseYearEndRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useContractCompletionRandomRetirement", isUseContractCompletionRandomRetirement()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomFounderRetirement", isUseRandomFounderRetirement()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSubContractSoldiers", isUseSubContractSoldiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAgeModifiers", isUseAgeModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useUnitRatingModifiers", isUseUnitRatingModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFactionModifiers", isUseFactionModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLeadershipModifiers", isUseLeadershipModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverBaseTn", getTurnoverFixedTargetNumber()); @@ -4658,18 +4669,20 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseYearEndRandomRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useContractCompletionRandomRetirement")) { retVal.setUseContractCompletionRandomRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useCustomRetirementModifiers")) { - retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useRandomFounderRetirement")) { retVal.setUseRandomFounderRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { - retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useSubContractSoldiers")) { + retVal.setUseSubContractSoldiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useCustomRetirementModifiers")) { + retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useAgeModifiers")) { retVal.setUseAgeModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useUnitRatingModifiers")) { retVal.setUseUnitRatingModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useFactionModifiers")) { retVal.setUseFactionModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { + retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useLeadershipModifiers")) { retVal.setUseLeadershipModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("turnoverBaseTn")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index fd0a08ed74..a910185c6e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -193,9 +193,10 @@ public Map calculateTargetNumbers(final @Nullable AtBContract continue; } - /* Infantry units retire or defect by platoon */ - if ((p.getUnit() != null) && (p.getUnit().usesSoldiers()) && (!p.getUnit().isCommander(p))) { - continue; + if (campaign.getCampaignOptions().isUseSubContractSoldiers()) { + if ((p.getUnit() != null) && (p.getUnit().usesSoldiers()) && (!p.getUnit().isCommander(p))) { + continue; + } } TargetRoll target = new TargetRoll(getBaseTargetNumber(campaign), "Base"); @@ -414,17 +415,20 @@ public void rollRetirement(final @Nullable Mission mission, final Map list, final Object chkUseRandomFounderRetirement.setToolTipText(resources.getString("chkUseRandomFounderRetirement.toolTipText")); chkUseRandomFounderRetirement.setName("chkUseRandomFounderRetirement"); + chkUseSubContractSoldiers = new JCheckBox(resources.getString("chkUseSubContractSoldiers.text")); + chkUseSubContractSoldiers.setToolTipText(resources.getString("chkUseSubContractSoldiers.toolTipText")); + chkUseSubContractSoldiers.setName("chkUseSubContractSoldiers"); + turnoverModifiersPanel = createTurnoverModifiersPanel(); turnoverPayoutPanel = createTurnoverPayoutPanel(); @@ -3743,6 +3748,7 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseYearEndRandomRetirement) .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) + .addComponent(chkUseSubContractSoldiers) .addComponent(turnoverModifiersPanel) .addComponent(turnoverPayoutPanel) ); @@ -3758,6 +3764,7 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseYearEndRandomRetirement) .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) + .addComponent(chkUseSubContractSoldiers) .addComponent(turnoverModifiersPanel) .addComponent(turnoverPayoutPanel) ); @@ -6918,13 +6925,14 @@ public void setOptions(@Nullable CampaignOptions options, // Retirement chkUseRetirementDateTracking.setSelected(options.isUseRetirementDateTracking()); - chkUseRandomRetirement.setSelected(options.getUseRandomRetirement()); + chkUseRandomRetirement.setSelected(options.isUseRandomRetirement()); comboTurnoverTargetNumberMethod.setSelectedItem(options.getTurnoverTargetNumberMethod()); spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); chkUseYearEndRandomRetirement.setSelected(options.isUseYearEndRandomRetirement()); chkUseContractCompletionRandomRetirement.setSelected(options.isUseContractCompletionRandomRetirement()); chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); chkUseRandomFounderRetirement.setSelected(options.isUseRandomFounderRetirement()); + chkUseSubContractSoldiers.setSelected(options.isUseSubContractSoldiers()); chkTrackUnitFatigue.setSelected(options.isTrackUnitFatigue()); chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); @@ -7539,6 +7547,7 @@ public void updateOptions() { options.setUseContractCompletionRandomRetirement(chkUseContractCompletionRandomRetirement.isSelected()); options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); options.setUseRandomFounderRetirement(chkUseRandomFounderRetirement.isSelected()); + options.setUseSubContractSoldiers(chkUseSubContractSoldiers.isSelected()); options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); From 2d32808503ee4f34a52eab66ca0f3ea815c33f28 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 13:38:17 -0500 Subject: [PATCH 012/101] Refactored method name from getUseRandomRetirement to isUseRandomRetirement The method name in various classes has been changed from getUseRandomRetirement to isUseRandomRetirement to better reflect its boolean nature. This approach provides a more intuitive understanding of the method's functionality, adhering to standard naming conventions for boolean methods. --- MekHQ/src/mekhq/campaign/Campaign.java | 2 +- MekHQ/src/mekhq/gui/BriefingTab.java | 2 +- MekHQ/src/mekhq/gui/CampaignGUI.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 6fa1385e30..beab4c17c6 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -6843,7 +6843,7 @@ public boolean checkRetirementDefections() { } public boolean checkYearlyRetirements() { - if (getCampaignOptions().getUseRandomRetirement() + if (getCampaignOptions().isUseRandomRetirement() && getCampaignOptions().isUseYearEndRandomRetirement() && (ChronoUnit.DAYS.between(getRetirementDefectionTracker().getLastRetirementRoll(), getLocalDate()) == getRetirementDefectionTracker().getLastRetirementRoll().lengthOfYear())) { diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index d4ca229af1..791a5e5d2c 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -357,7 +357,7 @@ private void completeMission() { return; } - if (getCampaign().getCampaignOptions().getUseRandomRetirement() + if (getCampaign().getCampaignOptions().isUseRandomRetirement() && getCampaign().getCampaignOptions().isUseContractCompletionRandomRetirement()) { RetirementDefectionDialog rdd = new RetirementDefectionDialog(getCampaignGui(), (AtBContract) mission, true); diff --git a/MekHQ/src/mekhq/gui/CampaignGUI.java b/MekHQ/src/mekhq/gui/CampaignGUI.java index 43b1f64c2d..51a9914da3 100644 --- a/MekHQ/src/mekhq/gui/CampaignGUI.java +++ b/MekHQ/src/mekhq/gui/CampaignGUI.java @@ -922,7 +922,7 @@ private void initMenu() { miRetirementDefectionDialog = new JMenuItem(resourceMap.getString("miRetirementDefectionDialog.text")); miRetirementDefectionDialog.setMnemonic(KeyEvent.VK_R); miRetirementDefectionDialog.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_DOWN_MASK)); - miRetirementDefectionDialog.setVisible(getCampaign().getCampaignOptions().getUseRandomRetirement()); + miRetirementDefectionDialog.setVisible(getCampaign().getCampaignOptions().isUseRandomRetirement()); miRetirementDefectionDialog.addActionListener(evt -> showRetirementDefectionDialog()); menuView.add(miRetirementDefectionDialog); @@ -2474,7 +2474,7 @@ public void handle(final OptionsChangedEvent evt) { fundsScheduler.schedule(); refreshPartsAvailability(); - miRetirementDefectionDialog.setVisible(evt.getOptions().getUseRandomRetirement()); + miRetirementDefectionDialog.setVisible(evt.getOptions().isUseRandomRetirement()); miUnitMarket.setVisible(!evt.getOptions().getUnitMarketMethod().isNone()); } From 3d566c385260971081fee61d68ffc1f836cf4ecd Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 15:50:48 -0500 Subject: [PATCH 013/101] Update Payout initialization in RetirementDefectionTracker The Payout initialization within the RetirementDefectionTracker class has been updated. Now, a person's ID is correctly passed to the getPerson method from the campaign, rectifying an issue where an incorrect ID may have been used previously. --- .../mekhq/campaign/personnel/RetirementDefectionTracker.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index a910185c6e..d8179d6311 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -419,7 +419,8 @@ public void rollRetirement(final @Nullable Mission mission, final Map Date: Thu, 16 May 2024 19:11:35 -0500 Subject: [PATCH 014/101] Add new retirement modifiers and updated shares system The Commit includes the addition of new retirement modifiers like "Skill" and "Mission Status". Moreover, the shares system has been temporarily moved to the Turnover panel. The code has also been cleaned up to fix any logic discrepancies. --- .../CampaignOptionsDialog.properties | 18 ++- MekHQ/src/mekhq/campaign/CampaignOptions.java | 59 ++++++--- .../personnel/RetirementDefectionTracker.java | 87 +++++++------ .../gui/dialog/RetirementDefectionDialog.java | 28 +++-- .../mekhq/gui/panes/CampaignOptionsPane.java | 114 +++++++++++++----- 5 files changed, 207 insertions(+), 99 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index b75e35df95..fe6c2bccba 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -263,12 +263,16 @@ chkTrackUnitFatigue.text=Track Unit Fatigue chkTrackUnitFatigue.toolTipText=Continuous deployments without a break apply a penalty to Turnover checks. turnoverModifierPanel.title=Modifiers +chkUseSkillModifiers.text=Use Skill Modifiers +chkUseSkillModifiers.toolTipText=If enabled, better skilled personnel have a higher turnover target number. chkUseAgeModifiers.text=Use Age Modifiers chkUseAgeModifiers.toolTipText=If enabled, the age of personnel will influence their turnover target number. chkUseUnitRatingModifiers.text=Use Unit Rating Modifiers chkUseUnitRatingModifiers.toolTipText=If enabled, the rating of the unit will influence the turnover target number for all personnel. chkUseFactionModifiers.text=Use Faction Modifiers chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. +chkUseMissionStatusModifiers.text=Use Mission Status Modifiers +chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. chkUseLeadershipModifiers.text=Use Leadership Modifiers chkUseLeadershipModifiers.toolTipText=If enabled, the commander's Leadership skill limits the number of personnel that can be in the unit before incurring a penalty on Employee Turnover rolls. @@ -284,6 +288,14 @@ chkUsePayoutServiceBonus.toolTipText=If enabled, personnel receive a payout bonu lblPayoutServiceBonusRate.text=Service Bonus % lblPayoutServiceBonusRate.toolTipText=If 'Use Service Bonus' is enabled, this sets the payout percentage increase per year of service, applied when personnel resign or retire. +sharesPanel.title=Shares System +chkUseShareSystem.text=Use Shares +chkUseShareSystem.toolTipText=Gives personnel a stake in the unit. This system lowers profits but can increase retention. +chkSharesExcludeLargeCraft.text=Large Craft Exemption +chkSharesExcludeLargeCraft.toolTipText=Exclude large craft from net worth when calculating share value. +chkSharesForAll.text=All Personnel Have Shares +chkSharesForAll.toolTipText=All combat and support personnel have shares rather than just MechWarriors + # Family familyPanel.title=Family (Unofficial) lblFamilyDisplayLevel.text=The Level of Relation to be Displayed in the Personnel Panel @@ -707,12 +719,6 @@ chkUseStratCon.text=Use StratCon campaign rules chkUseStratCon.toolTipText=An update of the AtB ruleset. Highly experimental. lblSkillLevel.text=Skill Level lblSkillLevel.toolTipText=This is the difficulty level for generated scenarios.
Values above Elite are not recommended. -chkUseShareSystem.text=Use share system -chkUseShareSystem.toolTipText=All personnel have a stake in the unit. This system lowers profits but can increase retention. -chkSharesExcludeLargeCraft.text=Exclude large craft from share value -chkSharesExcludeLargeCraft.toolTipText=When calculating payout to retirees, exclude DropShips and JumpShips from net worth when calculating share value. -chkSharesForAll.text=All personnel have shares -chkSharesForAll.toolTipText=All combat and support personnel have shares rather than just MechWarriors chkTrackOriginalUnit.text=Track original unit chkTrackOriginalUnit.toolTipText=MechWarriors who have their own unit when recruited will take the same unit when they go, if it is still available. chkLimitLanceWeight.text=Limit lance deployment by weight diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 336ceb3af5..0bbfa4a1f9 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -266,12 +266,18 @@ public static String getTransitUnitName(final int unit) { private Integer payoutServiceBonusRate; private boolean useCustomRetirementModifiers; + private boolean useSkillModifiers; private boolean useAgeModifiers; private boolean useUnitRatingModifiers; private boolean useFactionModifiers; + private boolean useMissionStatusModifiers; private boolean trackUnitFatigue; private boolean useLeadershipModifiers; + private boolean useShareSystem; + private boolean sharesExcludeLargeCraft; + private boolean sharesForAll; + // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -478,9 +484,6 @@ public static String getTransitUnitName(final int unit) { private SkillLevel skillLevel; // Unit Administration - private boolean useShareSystem; - private boolean sharesExcludeLargeCraft; - private boolean sharesForAll; private boolean aeroRecruitsHaveUnits; private boolean trackOriginalUnit; private boolean useAero; @@ -742,8 +745,10 @@ public CampaignOptions() { setTrackUnitFatigue(false); setUseAgeModifiers(true); + setUseSkillModifiers(true); setUseUnitRatingModifiers(true); setUseFactionModifiers(true); + setUseMissionStatusModifiers(true); setUseLeadershipModifiers(true); setPayoutRateOfficer(3); @@ -752,6 +757,10 @@ public CampaignOptions() { setUsePayoutServiceBonus(true); setPayoutServiceBonusRate(10); + setUseShareSystem(false); + setSharesExcludeLargeCraft(true); + setSharesForAll(false); + // Family setFamilyDisplayLevel(FamilialRelationshipDisplayLevel.SPOUSE); @@ -1021,9 +1030,6 @@ public CampaignOptions() { setSkillLevel(SkillLevel.REGULAR); // Unit Administration - useShareSystem = false; - sharesExcludeLargeCraft = false; - sharesForAll = false; aeroRecruitsHaveUnits = false; trackOriginalUnit = false; useAero = false; @@ -1730,6 +1736,14 @@ public void setPayoutServiceBonusRate(final Integer payoutServiceBonusRate) { this.payoutServiceBonusRate = payoutServiceBonusRate; } + public boolean isUseSkillModifiers() { + return useSkillModifiers; + } + + public void setUseSkillModifiers(final boolean useSkillModifiers) { + this.useSkillModifiers = useSkillModifiers; + } + public boolean isUseAgeModifiers() { return useAgeModifiers; } @@ -1754,6 +1768,14 @@ public void setUseFactionModifiers(final boolean useFactionModifiers) { this.useFactionModifiers = useFactionModifiers; } + public boolean isUseMissionStatusModifiers() { + return useMissionStatusModifiers; + } + + public void setUseMissionStatusModifiers(final boolean useMissionStatusModifiers) { + this.useMissionStatusModifiers = useMissionStatusModifiers; + } + public boolean isUseLeadershipModifiers() { return useLeadershipModifiers; } @@ -4064,9 +4086,11 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSubContractSoldiers", isUseSubContractSoldiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSkillModifiers", isUseSkillModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAgeModifiers", isUseAgeModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useUnitRatingModifiers", isUseUnitRatingModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFactionModifiers", isUseFactionModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMissionStatusModifiers", isUseMissionStatusModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLeadershipModifiers", isUseLeadershipModifiers()); @@ -4076,6 +4100,10 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRetirementMultiplier", getPayoutRetirementMultiplier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "usePayoutServiceBonus", isUsePayoutServiceBonus()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutServiceBonusRate", getPayoutServiceBonusRate()); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", isSharesExcludeLargeCraft()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesForAll", isSharesForAll()); //endregion Retirement //region Family @@ -4283,9 +4311,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "opForUsesVTOLs", isOpForUsesVTOLs()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useDropShips", useDropShips); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "aeroRecruitsHaveUnits", aeroRecruitsHaveUnits); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", useShareSystem); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", sharesExcludeLargeCraft); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesForAll", sharesForAll); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "mercSizeLimited", mercSizeLimited); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackOriginalUnit", trackOriginalUnit); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "regionalMechVariations", regionalMechVariations); @@ -4714,12 +4739,16 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseSubContractSoldiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useCustomRetirementModifiers")) { retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useSkillModifiers")) { + retVal.setUseSkillModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useAgeModifiers")) { retVal.setUseAgeModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useUnitRatingModifiers")) { retVal.setUseUnitRatingModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useFactionModifiers")) { retVal.setUseFactionModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMissionStatusModifiers")) { + retVal.setUseMissionStatusModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useLeadershipModifiers")) { @@ -4736,6 +4765,12 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUsePayoutServiceBonus(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutServiceBonusRate")) { retVal.setPayoutServiceBonusRate(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { + retVal.setUseShareSystem(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { + retVal.setSharesExcludeLargeCraft(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("sharesForAll")) { + retVal.setSharesForAll(Boolean.parseBoolean(wn2.getTextContent().trim())); //endregion Retirement //region Family @@ -5159,12 +5194,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.useDropShips = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("aeroRecruitsHaveUnits")) { retVal.aeroRecruitsHaveUnits = Boolean.parseBoolean(wn2.getTextContent().trim()); - } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { - retVal.useShareSystem = Boolean.parseBoolean(wn2.getTextContent().trim()); - } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { - retVal.sharesExcludeLargeCraft = Boolean.parseBoolean(wn2.getTextContent().trim()); - } else if (wn2.getNodeName().equalsIgnoreCase("sharesForAll")) { - retVal.sharesForAll = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("trackOriginalUnit")) { retVal.trackOriginalUnit = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("mercSizeLimited")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index d8179d6311..f54a2348db 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -189,7 +189,11 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } for (Person p : campaign.getActivePersonnel()) { - if (p.getPrimaryRole().isDependent() || !p.getPrisonerStatus().isFree() || p.isDeployed() || (p.isFounder() && !campaign.getCampaignOptions().isUseRandomFounderRetirement())) { + if (p.getPrimaryRole().isDependent() || !p.getPrisonerStatus().isFree() || p.isDeployed()) { + continue; + } + + if ((p.isFounder()) && (!campaign.getCampaignOptions().isUseRandomFounderRetirement())) { continue; } @@ -202,34 +206,36 @@ public Map calculateTargetNumbers(final @Nullable AtBContract TargetRoll target = new TargetRoll(getBaseTargetNumber(campaign), "Base"); // Skill Rating modifier - int skillRating = p.getExperienceLevel(campaign, false); - String skillRatingDescription; - - switch (skillRating) { - case -1: - skillRatingDescription = "Unskilled"; - break; - case 0: - skillRatingDescription = "Ultra-Green"; - break; - case 1: - skillRatingDescription = "Green"; - break; - case 2: - skillRatingDescription = "Regular"; - break; - case 3: - skillRatingDescription = "Veteran"; - break; - case 4: - skillRatingDescription = "Elite"; - break; - default: - skillRatingDescription = "Error, please see log"; - LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating); - } + if (campaign.getCampaignOptions().isUseSkillModifiers()) { + int skillRating = p.getExperienceLevel(campaign, false); + String skillRatingDescription; + + switch (skillRating) { + case -1: + skillRatingDescription = "Unskilled"; + break; + case 0: + skillRatingDescription = "Ultra-Green"; + break; + case 1: + skillRatingDescription = "Green"; + break; + case 2: + skillRatingDescription = "Regular"; + break; + case 3: + skillRatingDescription = "Veteran"; + break; + case 4: + skillRatingDescription = "Elite"; + break; + default: + skillRatingDescription = "Error, please see log"; + LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating); + } - target.addModifier(skillRating, skillRatingDescription); + target.addModifier(skillRating, skillRatingDescription); + } // Unit Rating modifier if (campaign.getCampaignOptions().isUseUnitRatingModifiers()) { @@ -237,9 +243,15 @@ public Map calculateTargetNumbers(final @Nullable AtBContract target.addModifier(unitRatingModifier, "Unit Rating"); } - /* Retirement rolls are made before the contract status is set */ - if ((contract != null) && (contract.getStatus().isFailed() || contract.getStatus().isBreach())) { - target.addModifier(1, "Failed mission"); + // Mission completion status modifiers + if ((contract != null) && (campaign.getCampaignOptions().isUseMissionStatusModifiers())) { + if (contract.getStatus().isSuccess()) { + target.addModifier(-1, "Recent Success"); + } else if (contract.getStatus().isFailed()) { + target.addModifier(1, "Recent Failure"); + } else if (contract.getStatus().isBreach()) { + target.addModifier(2, "Recent Contract Breach"); + } } // Faction Modifiers @@ -318,7 +330,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } if (c != null) { - target.addModifier(-((c.getSharesPct() - 10) / 10), "Shares"); + target.addModifier(- (c.getSharesPct() / 10), "Shares"); } } @@ -362,7 +374,7 @@ private static Integer getBaseTargetNumber(Campaign campaign) { } else { return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); } - // this means there isn't someone in the campaign with the relevant skill + // this means there isn't someone in the campaign with the relevant skill or role } catch (Exception e) { return 13; } @@ -582,14 +594,14 @@ public Payout() { } - public Payout(final Campaign campaign, final Person person, final Money shareValue, - final boolean killed, final boolean sharesForAll) { + public Payout(final Campaign campaign, final Person person, final Money shareValue, final boolean killed, final boolean sharesForAll) { calculatePayout(campaign, person, killed, shareValue.isPositive()); - if (shareValue.isPositive()) { + if ((shareValue.isPositive()) && (campaign.getCampaignOptions().isUseShareSystem())) { payoutAmount = payoutAmount.plus(shareValue.multipliedBy(person.getNumShares(campaign, sharesForAll))); } + // TODO investigate if these actually do anything if (killed) { switch (Compute.d6()) { case 2: @@ -611,8 +623,7 @@ public Payout(final Campaign campaign, final Person person, final Money shareVal } } - private void calculatePayout(final Campaign campaign, final Person person, - final boolean killed, final boolean shareSystem) { + private void calculatePayout(final Campaign campaign, final Person person, final boolean killed, final boolean shareSystem) { int roll; if (killed) { diff --git a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java index 63e36a3f2b..456df868c9 100644 --- a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java @@ -167,6 +167,7 @@ private void initComponents(boolean doRetirement) { for (PersonnelFilter filter : MekHQ.getMHQOptions().getPersonnelFilterStyle().getFilters(true)) { cbGroupOverview.addItem(filter); } + JPanel panTop = new JPanel(); panTop.setLayout(new BoxLayout(panTop, BoxLayout.X_AXIS)); panTop.add(cbGroupOverview); @@ -650,16 +651,21 @@ public Money totalPayout() { if (null == rdTracker.getRetirees(contract)) { return Money.zero(); } + Money retVal = Money.zero(); + for (UUID id : rdTracker.getRetirees(contract)) { - if (null == rdTracker.getPayout(id)) { + if (rdTracker.getPayout(id) == null) { continue; } + if (((RetirementTableModel) retireeTable.getModel()).getAltPayout().containsKey(id)) { retVal = retVal.plus(((RetirementTableModel) retireeTable.getModel()).getAltPayout().get(id)); continue; } + Money payout = rdTracker.getPayout(id).getPayoutAmount(); + /* If no unit is required as part of the payout, the unit is part or all of the * final payout. */ @@ -667,21 +673,23 @@ public Money totalPayout() { null != unitAssignments.get(id) && null != hqView.getCampaign().getUnit(unitAssignments.get(id)))) { payout = payout.minus(hqView.getCampaign().getUnit(unitAssignments.get(id)).getBuyCost()); - } else if ((hqView.getCampaign().getCampaignOptions().isUseShareSystem() && - hqView.getCampaign().getCampaignOptions().isTrackOriginalUnit() && - hqView.getCampaign().getPerson(id).getOriginalUnitId() == unitAssignments.get(id)) && - null != hqView.getCampaign().getUnit(unitAssignments.get(id))) { + } else if ((hqView.getCampaign().getCampaignOptions().isUseShareSystem() + && hqView.getCampaign().getCampaignOptions().isTrackOriginalUnit() + && Objects.equals(hqView.getCampaign().getPerson(id).getOriginalUnitId(), unitAssignments.get(id))) + && hqView.getCampaign().getUnit(unitAssignments.get(id)) != null) { payout = payout.minus(hqView.getCampaign().getUnit(unitAssignments.get(id)).getBuyCost()); } + /* If using the share system and tracking the original unit, * the payout is also reduced by the value of the unit. */ - if (hqView.getCampaign().getCampaignOptions().isUseShareSystem() && - hqView.getCampaign().getCampaignOptions().isTrackOriginalUnit() && - hqView.getCampaign().getPerson(id).getOriginalUnitId() == unitAssignments.get(id) && - null != hqView.getCampaign().getUnit(unitAssignments.get(id))) { + if (hqView.getCampaign().getCampaignOptions().isUseShareSystem() + && hqView.getCampaign().getCampaignOptions().isTrackOriginalUnit() + && Objects.equals(hqView.getCampaign().getPerson(id).getOriginalUnitId(), unitAssignments.get(id)) + && hqView.getCampaign().getUnit(unitAssignments.get(id)) != null) { payout = payout.minus(hqView.getCampaign().getUnit(unitAssignments.get(id)).getBuyCost()); } + /* If the unit given in payment is of lower quality than required, pay * an additional 3M C-bills per class. */ @@ -690,10 +698,12 @@ public Money totalPayout() { rdTracker.getPayout(id).getWeightClass(), RetirementDefectionDialog.weightClassIndex(hqView.getCampaign().getUnit(unitAssignments.get(id))))); } + /* If the pilot has stolen a unit, there is no payout */ if (rdTracker.getPayout(id).hasStolenUnit() && (null != unitAssignments.get(id))) { payout = Money.zero(); } + // If the payout is negative just set it to zero if (payout.isNegative()) { payout = Money.zero(); diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index f928d7240c..3d50821fbc 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -265,13 +265,17 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseContractCompletionRandomRetirement; private JCheckBox chkUseRandomFounderRetirement; private JCheckBox chkUseSubContractSoldiers; + private JPanel turnoverModifiersPanel; private JCheckBox chkUseCustomRetirementModifiers; + private JCheckBox chkUseSkillModifiers; private JCheckBox chkUseAgeModifiers; private JCheckBox chkUseUnitRatingModifiers; private JCheckBox chkUseFactionModifiers; + private JCheckBox chkUseMissionStatusModifiers; private JCheckBox chkTrackUnitFatigue; private JCheckBox chkUseLeadershipModifiers; + private JPanel turnoverPayoutPanel; private JLabel lblPayoutRateOfficer; private JSpinner spnPayoutRateOfficer; @@ -283,6 +287,11 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JLabel lblPayoutServiceBonusRate; private JSpinner spnPayoutServiceBonusRate; + private JPanel sharesPanel; + private JCheckBox chkUseShareSystem; + private JCheckBox chkSharesExcludeLargeCraft; + private JCheckBox chkSharesForAll; + // Marriage private JCheckBox chkUseManualMarriages; private JCheckBox chkUseClanPersonnelMarriages; @@ -539,9 +548,6 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private MMComboBox comboSkillLevel; // unit administration - private JCheckBox chkUseShareSystem; - private JCheckBox chkSharesExcludeLargeCraft; - private JCheckBox chkSharesForAll; private JCheckBox chkAeroRecruitsHaveUnits; private JCheckBox chkTrackOriginalUnit; private JCheckBox chkUseAero; @@ -2728,27 +2734,6 @@ private JScrollPane createAgainstTheBotTab() { panAtB.add(chkUseStratCon, gridBagConstraints); // AtB options: "Unit Administration" frame controls - chkUseShareSystem = new JCheckBox(resources.getString("chkUseShareSystem.text")); - chkUseShareSystem.setToolTipText(resources.getString("chkUseShareSystem.toolTipText")); - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.gridwidth = 1; - gridBagConstraints.fill = GridBagConstraints.BOTH; - gridBagConstraints.insets = new Insets(5, 5, 5, 5); - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - panSubAtBAdmin.add(chkUseShareSystem, gridBagConstraints); - - chkSharesExcludeLargeCraft = new JCheckBox(resources.getString("chkSharesExcludeLargeCraft.text")); - chkSharesExcludeLargeCraft.setToolTipText(resources.getString("chkSharesExcludeLargeCraft.toolTipText")); - gridBagConstraints.gridy++; - panSubAtBAdmin.add(chkSharesExcludeLargeCraft, gridBagConstraints); - - chkSharesForAll = new JCheckBox(resources.getString("chkSharesForAll.text")); - chkSharesForAll.setToolTipText(resources.getString("chkSharesForAll.toolTipText")); - gridBagConstraints.gridy++; - panSubAtBAdmin.add(chkSharesForAll, gridBagConstraints); - chkAeroRecruitsHaveUnits = new JCheckBox(resources.getString("chkAeroRecruitsHaveUnits.text")); chkAeroRecruitsHaveUnits.setToolTipText(resources.getString("chkAeroRecruitsHaveUnits.toolTipText")); gridBagConstraints.gridy++; @@ -3633,7 +3618,8 @@ private JPanel createRetirementPanel() { chkUseRetirementDateTracking.setToolTipText(resources.getString("chkUseRetirementDateTracking.toolTipText")); chkUseRetirementDateTracking.setName("chkUseRetirementDateTracking"); - JPanel randomRetirementPanel = createRandomRetirementPanel(); + randomRetirementPanel = createRandomRetirementPanel(); + sharesPanel = createSharesPanel(); // global enable chkUseRandomRetirement = new JCheckBox(resources.getString("chkUseRandomRetirement.text")); @@ -3673,6 +3659,7 @@ private JPanel createRetirementPanel() { .addComponent(chkUseRetirementDateTracking) .addComponent(chkUseRandomRetirement) .addComponent(randomRetirementPanel) + .addComponent(sharesPanel) ); layout.setHorizontalGroup( @@ -3680,6 +3667,7 @@ private JPanel createRetirementPanel() { .addComponent(chkUseRetirementDateTracking) .addComponent(chkUseRandomRetirement) .addComponent(randomRetirementPanel) + .addComponent(sharesPanel) ); return panel; @@ -3798,6 +3786,10 @@ private JPanel createTurnoverModifiersPanel() { chkUseCustomRetirementModifiers.setToolTipText(resources.getString("chkUseCustomRetirementModifiers.toolTipText")); chkUseCustomRetirementModifiers.setName("chkUseCustomRetirementModifiers"); + chkUseSkillModifiers = new JCheckBox(resources.getString("chkUseSkillModifiers.text")); + chkUseSkillModifiers.setToolTipText(resources.getString("chkUseSkillModifiers.toolTipText")); + chkUseSkillModifiers.setName("chkUseSkillModifiers"); + chkUseAgeModifiers = new JCheckBox(resources.getString("chkUseAgeModifiers.text")); chkUseAgeModifiers.setToolTipText(resources.getString("chkUseAgeModifiers.toolTipText")); chkUseAgeModifiers.setName("chkUseAgeModifiers"); @@ -3810,6 +3802,10 @@ private JPanel createTurnoverModifiersPanel() { chkUseFactionModifiers.setToolTipText(resources.getString("chkUseFactionModifiers.toolTipText")); chkUseFactionModifiers.setName("chkUseFactionModifiers"); + chkUseMissionStatusModifiers = new JCheckBox(resources.getString("chkUseMissionStatusModifiers.text")); + chkUseMissionStatusModifiers.setToolTipText(resources.getString("chkUseMissionStatusModifiers.toolTipText")); + chkUseMissionStatusModifiers.setName("chkUseMissionStatusModifiers"); + chkTrackUnitFatigue = new JCheckBox(resources.getString("chkTrackUnitFatigue.text")); chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); @@ -3829,9 +3825,11 @@ private JPanel createTurnoverModifiersPanel() { layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkUseCustomRetirementModifiers) + .addComponent(chkUseSkillModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) + .addComponent(chkUseMissionStatusModifiers) .addComponent(chkTrackUnitFatigue) .addComponent(chkUseLeadershipModifiers) ); @@ -3839,9 +3837,11 @@ private JPanel createTurnoverModifiersPanel() { layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseCustomRetirementModifiers) + .addComponent(chkUseSkillModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) + .addComponent(chkUseMissionStatusModifiers) .addComponent(chkTrackUnitFatigue) .addComponent(chkUseLeadershipModifiers) ); @@ -3931,6 +3931,52 @@ private JPanel createTurnoverPayoutPanel() { return turnoverPayoutPanel; } + private JPanel createSharesPanel() { + chkUseShareSystem = new JCheckBox(resources.getString("chkUseShareSystem.text")); + chkUseShareSystem.setToolTipText(resources.getString("chkUseShareSystem.toolTipText")); + chkUseShareSystem.setName("chkUseShareSystem"); + chkUseShareSystem.addActionListener(evt -> { + final boolean isEnabled = chkUseShareSystem.isSelected(); + + chkSharesExcludeLargeCraft.setEnabled(isEnabled); + chkSharesForAll.setEnabled(isEnabled); + }); + + chkSharesExcludeLargeCraft = new JCheckBox(resources.getString("chkSharesExcludeLargeCraft.text")); + chkSharesExcludeLargeCraft.setToolTipText(resources.getString("chkSharesExcludeLargeCraft.toolTipText")); + chkSharesExcludeLargeCraft.setName("chkSharesExcludeLargeCraft"); + chkSharesExcludeLargeCraft.setEnabled(chkUseShareSystem.isSelected()); + + chkSharesForAll = new JCheckBox(resources.getString("chkSharesForAll.text")); + chkSharesForAll.setToolTipText(resources.getString("chkSharesForAll.toolTipText")); + chkSharesForAll.setName("chkSharesForAll"); + chkSharesForAll.setEnabled(chkUseShareSystem.isSelected()); + + sharesPanel = new JDisableablePanel("sharesPanel"); + sharesPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("sharesPanel.title"))); + + final GroupLayout layout = new GroupLayout(sharesPanel); + sharesPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseShareSystem) + .addComponent(chkSharesExcludeLargeCraft) + .addComponent(chkSharesForAll) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseShareSystem) + .addComponent(chkSharesExcludeLargeCraft) + .addComponent(chkSharesForAll) + ); + + return sharesPanel; + } + private JPanel createFamilyPanel() { // Create Panel Components final JLabel lblFamilyDisplayLevel = new JLabel(resources.getString("lblFamilyDisplayLevel.text")); @@ -6960,8 +7006,10 @@ public void setOptions(@Nullable CampaignOptions options, chkTrackUnitFatigue.setSelected(options.isTrackUnitFatigue()); chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); + chkUseSkillModifiers.setSelected(options.isUseSkillModifiers()); chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); + chkUseMissionStatusModifiers.setSelected(options.isUseMissionStatusModifiers()); chkUseLeadershipModifiers.setSelected(options.isUseLeadershipModifiers()); spnPayoutRateOfficer.setValue(options.getPayoutRateOfficer()); @@ -6970,6 +7018,10 @@ public void setOptions(@Nullable CampaignOptions options, chkUsePayoutServiceBonus.setSelected(options.isUsePayoutServiceBonus()); spnPayoutServiceBonusRate.setValue(options.getPayoutServiceBonusRate()); + chkUseShareSystem.setSelected(options.isUseShareSystem()); + chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); + chkSharesForAll.setSelected(options.isSharesForAll()); + // Family comboFamilyDisplayLevel.setSelectedItem(options.getFamilyDisplayLevel()); @@ -7284,9 +7336,6 @@ public void setOptions(@Nullable CampaignOptions options, } chkUseStratCon.setSelected(options.isUseStratCon()); comboSkillLevel.setSelectedItem(options.getSkillLevel()); - chkUseShareSystem.setSelected(options.isUseShareSystem()); - chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); - chkSharesForAll.setSelected(options.isSharesForAll()); chkAeroRecruitsHaveUnits.setSelected(options.isAeroRecruitsHaveUnits()); chkTrackOriginalUnit.setSelected(options.isTrackOriginalUnit()); chkUseAero.setSelected(options.isUseAero()); @@ -7578,8 +7627,10 @@ public void updateOptions() { options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); + options.setUseSkillModifiers(chkUseSkillModifiers.isSelected()); options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); + options.setUseMissionStatusModifiers(chkUseMissionStatusModifiers.isSelected()); options.setUseLeadershipModifiers(chkUseLeadershipModifiers.isSelected()); options.setPayoutRateOfficer((Integer) spnPayoutRateOfficer.getValue()); @@ -7588,6 +7639,10 @@ public void updateOptions() { options.setUsePayoutServiceBonus(chkUsePayoutServiceBonus.isSelected()); options.setPayoutServiceBonusRate((Integer) spnPayoutServiceBonusRate.getValue()); + options.setUseShareSystem(chkUseShareSystem.isSelected()); + options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); + options.setSharesForAll(chkSharesForAll.isSelected()); + // Family options.setFamilyDisplayLevel(comboFamilyDisplayLevel.getSelectedItem()); @@ -7748,9 +7803,6 @@ public void updateOptions() { options.setUseAtB(chkUseAtB.isSelected()); options.setUseStratCon(chkUseStratCon.isSelected()); options.setSkillLevel(comboSkillLevel.getSelectedItem()); - options.setUseShareSystem(chkUseShareSystem.isSelected()); - options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); - options.setSharesForAll(chkSharesForAll.isSelected()); options.setTrackOriginalUnit(chkTrackOriginalUnit.isSelected()); options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); options.setLimitLanceWeight(chkLimitLanceWeight.isSelected()); From 59ec3308a33b72c74864a46a3c0524c6687ef479 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 19:15:10 -0500 Subject: [PATCH 015/101] Simplified RetirementDefectionDialog button logic --- .../mekhq/gui/dialog/RetirementDefectionDialog.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java index 456df868c9..d4fd7f99d5 100644 --- a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java @@ -789,13 +789,9 @@ private void enableAddRemoveButtons() { btnRemoveUnit.setEnabled(false); } else if (unitAssignments.containsKey(pid)) { btnAddUnit.setEnabled(false); - if ((hqView.getCampaign().getCampaignOptions().isTrackOriginalUnit() && - unitAssignments.get(pid).equals(hqView.getCampaign().getPerson(pid).getOriginalUnitId())) && - !btnEdit.isSelected()) { - btnRemoveUnit.setEnabled(false); - } else { - btnRemoveUnit.setEnabled(true); - } + btnRemoveUnit.setEnabled((!hqView.getCampaign().getCampaignOptions().isTrackOriginalUnit() + || !unitAssignments.get(pid).equals(hqView.getCampaign().getPerson(pid).getOriginalUnitId())) + || btnEdit.isSelected()); } else if (null != rdTracker.getPayout(pid) && rdTracker.getPayout(pid).getWeightClass() > 0) { if (unitAssignmentTable.getSelectedRow() < 0) { From 964d664654f002102fcfb9a4f6630456cb18003e Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 23:28:13 -0500 Subject: [PATCH 016/101] Added administrative strain tracking and removed leadership modifiers Implemented the addition of administrative strain tracking as part of the retirement and defection tracking system. This allows limiting the number of personnel that can be in the unit before incurring a turnover penalty based on admin/HR or admin/Command administration skill. At the same time, removed the usage of leadership modifiers. --- .../CampaignOptionsDialog.properties | 11 +- .../RetirementDefectionTracker.properties | 24 ++ MekHQ/src/mekhq/campaign/CampaignOptions.java | 47 +++- .../personnel/RetirementDefectionTracker.java | 213 ++++++++++-------- .../mekhq/gui/panes/CampaignOptionsPane.java | 169 +++++++++++--- 5 files changed, 329 insertions(+), 135 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index fe6c2bccba..15ca2ba41c 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -273,8 +273,15 @@ chkUseFactionModifiers.text=Use Faction Modifiers chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. chkUseMissionStatusModifiers.text=Use Mission Status Modifiers chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. -chkUseLeadershipModifiers.text=Use Leadership Modifiers -chkUseLeadershipModifiers.toolTipText=If enabled, the commander's Leadership skill limits the number of personnel that can be in the unit before incurring a penalty on Employee Turnover rolls. + +unitCohesionPanel.title=Unit Cohesion +administrativeStrainPanel.title=Administrative Strain +chkUseAdministrativeStrain.text=Enable Administrative Strain +chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty, based on Admin/HR Administration skill. +lblCombatantStrain.text=Combatant Strain (Admin/Command) +lblCombatantStrain.toolTipText=How many individual combatants can be supported per rank in Administration. +lblNonCombatantStrain.text=Non-Combatant Strain (Admin/HR) +lblNonCombatantStrain.toolTipText=How many individual non-combatants can be supported per rank in Administration. turnoverPayoutPanel.title=Payout Rates lblPayoutRateOfficer.text=Officer Payout Rate diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties new file mode 100644 index 0000000000..bdd8127378 --- /dev/null +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -0,0 +1,24 @@ +#### Target Number Labels +base.text=Base +skillUnskilled.text=Unskilled +skillUltraGreen.text=Ultra-Green +skillGreen.text=Green +skillRegular.text=Regular +skillVeteran.text=Veteran +skillElite.text=Elite +unitRating.text=Unit Rating +missionSuccess.text=Recent Successes +missionFailure.text=Recent Failures +missionBreach.text=Recent Contract Breach +factionPirateCompany.text=Pirate Company +factionPirate.text=Pirate +factionClan.text=Mercenary +factionEnemy.text=Warring Factions +age.text=Age +injuries.text=Permanent Injuries +officer.text=Officer +tacticalGenius.text=Non-Officer Tactical Genius +founder.text=Founder +shares.text=Shares +administrativeStrain.text=Admin Strain +fatigue.text=Fatigue diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 0bbfa4a1f9..b347e88ffc 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -272,7 +272,10 @@ public static String getTransitUnitName(final int unit) { private boolean useFactionModifiers; private boolean useMissionStatusModifiers; private boolean trackUnitFatigue; - private boolean useLeadershipModifiers; + + private boolean useAdministrativeStrain; + private Integer combatantStrain; + private Integer nonCombatantStrain; private boolean useShareSystem; private boolean sharesExcludeLargeCraft; @@ -749,7 +752,6 @@ public CampaignOptions() { setUseUnitRatingModifiers(true); setUseFactionModifiers(true); setUseMissionStatusModifiers(true); - setUseLeadershipModifiers(true); setPayoutRateOfficer(3); setPayoutRateEnlisted(3); @@ -757,6 +759,10 @@ public CampaignOptions() { setUsePayoutServiceBonus(true); setPayoutServiceBonusRate(10); + setUseAdministrativeStrain(true); + setCombatantStrain(12); + setNonCombatantStrain(24); + setUseShareSystem(false); setSharesExcludeLargeCraft(true); setSharesForAll(false); @@ -1776,12 +1782,28 @@ public void setUseMissionStatusModifiers(final boolean useMissionStatusModifiers this.useMissionStatusModifiers = useMissionStatusModifiers; } - public boolean isUseLeadershipModifiers() { - return useLeadershipModifiers; + public boolean isUseAdministrativeStrain() { + return useAdministrativeStrain; + } + + public void setUseAdministrativeStrain(final boolean useAdministrativeStrain) { + this.useAdministrativeStrain = useAdministrativeStrain; + } + + public Integer getCombatantStrain() { + return combatantStrain; } - public void setUseLeadershipModifiers(final boolean useLeadershipModifiers) { - this.useLeadershipModifiers = useLeadershipModifiers; + public void setCombatantStrain(final Integer combatantStrain) { + this.combatantStrain = combatantStrain; + } + + public Integer getNonCombatantStrain() { + return nonCombatantStrain; + } + + public void setNonCombatantStrain(final Integer nonCombatantStrain) { + this.nonCombatantStrain = nonCombatantStrain; } //endregion Retirement @@ -4092,7 +4114,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFactionModifiers", isUseFactionModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMissionStatusModifiers", isUseMissionStatusModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLeadershipModifiers", isUseLeadershipModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverBaseTn", getTurnoverFixedTargetNumber()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateOfficer", getPayoutRateOfficer()); @@ -4101,6 +4122,10 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "usePayoutServiceBonus", isUsePayoutServiceBonus()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutServiceBonusRate", getPayoutServiceBonusRate()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAdministrativeStrain", isUseAdministrativeStrain()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatantStrain", getCombatantStrain()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "nonCombatantStrain", getNonCombatantStrain()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", isSharesExcludeLargeCraft()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesForAll", isSharesForAll()); @@ -4751,8 +4776,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseMissionStatusModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useLeadershipModifiers")) { - retVal.setUseLeadershipModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("turnoverBaseTn")) { retVal.setTurnoverFixedTargetNumber(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateOfficer")) { @@ -4765,6 +4788,12 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUsePayoutServiceBonus(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutServiceBonusRate")) { retVal.setPayoutServiceBonusRate(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useAdministrativeStrain")) { + retVal.setUseAdministrativeStrain(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("combatantStrain")) { + retVal.setCombatantStrain(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("nonCombatantStrain")) { + retVal.setNonCombatantStrain(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { retVal.setUseShareSystem(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index f54a2348db..e829f9904a 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -62,6 +62,8 @@ public class RetirementDefectionTracker { final private Map payouts; private LocalDate lastRetirementRoll; + private final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.RetirementDefectionTracker"); + public RetirementDefectionTracker() { rollRequired = new HashSet<>(); unresolvedPersonnel = new HashMap<>(); @@ -132,177 +134,131 @@ private static int getAgeMod(int age) { * @param campaign The campaign to calculate target numbers for * @return A map with person ids as key and calculated target roll as value. */ - public Map calculateTargetNumbers(final @Nullable AtBContract contract, - final Campaign campaign) { + public Map calculateTargetNumbers(final @Nullable AtBContract contract, final Campaign campaign) { final Map targets = new HashMap<>(); - int combatLeadershipMod = 0; - int supportLeadershipMod = 0; if (null != contract) { rollRequired.add(contract.getId()); } - if (campaign.getCampaignOptions().isUseLeadershipModifiers()) { - int combat = 0; - int proto = 0; - int support = 0; - for (Person p : campaign.getActivePersonnel()) { - if (p.getPrimaryRole().isCivilian() || !p.getPrisonerStatus().isFree()) { - continue; - } - - if (p.getPrimaryRole().isSupport()) { - support++; - } else if ((null == p.getUnit()) || - ((null != p.getUnit()) && p.getUnit().isCommander(p))) { - /* - * The AtB rules do not state that crews count as a - * single person for leadership purposes, but to do otherwise - * would tax all but the most exceptional commanders of - * vehicle or infantry units. - */ - if (p.getPrimaryRole().isProtoMechPilot()) { - proto++; - } else { - combat++; - } - } - } - combat += proto / 5; - int max = 12; - if ((null != campaign.getFlaggedCommander()) && - (null != campaign.getFlaggedCommander().getSkill(SkillType.S_LEADER))) { - max += 6 * campaign.getFlaggedCommander().getSkill(SkillType.S_LEADER).getLevel(); - } - - if (combat > 2 * max) { - combatLeadershipMod = 2; - } else if (combat > max) { - combatLeadershipMod = 1; - } - - if (support > 2 * max) { - supportLeadershipMod = 2; - } else if (support > max) { - supportLeadershipMod = 1; - } - } - - for (Person p : campaign.getActivePersonnel()) { - if (p.getPrimaryRole().isDependent() || !p.getPrisonerStatus().isFree() || p.isDeployed()) { + for (Person person : campaign.getActivePersonnel()) { + if ((person.getPrimaryRole().isDependent()) || (!person.getPrisonerStatus().isFree()) || (person.isDeployed())) { continue; } - if ((p.isFounder()) && (!campaign.getCampaignOptions().isUseRandomFounderRetirement())) { + if ((person.isFounder()) && (!campaign.getCampaignOptions().isUseRandomFounderRetirement())) { continue; } if (campaign.getCampaignOptions().isUseSubContractSoldiers()) { - if ((p.getUnit() != null) && (p.getUnit().usesSoldiers()) && (!p.getUnit().isCommander(p))) { + if ((person.getUnit() != null) && (person.getUnit().usesSoldiers()) && (!person.getUnit().isCommander(person))) { continue; } } - TargetRoll target = new TargetRoll(getBaseTargetNumber(campaign), "Base"); + TargetRoll targetNumber = new TargetRoll(getBaseTargetNumber(campaign), resources.getString("base.text")); // Skill Rating modifier if (campaign.getCampaignOptions().isUseSkillModifiers()) { - int skillRating = p.getExperienceLevel(campaign, false); + int skillRating; String skillRatingDescription; + try { + skillRating = person.getExperienceLevel(campaign, true); + } catch (Exception e) { + skillRating = -1; + } + switch (skillRating) { case -1: - skillRatingDescription = "Unskilled"; + targetNumber.addModifier(0, resources.getString("skillUnskilled.text")); break; case 0: - skillRatingDescription = "Ultra-Green"; + targetNumber.addModifier(skillRating, resources.getString("skillUltraGreen.text")); break; case 1: - skillRatingDescription = "Green"; + targetNumber.addModifier(skillRating, resources.getString("skillGreen.text")); break; case 2: - skillRatingDescription = "Regular"; + targetNumber.addModifier(skillRating, resources.getString("skillRegular.text")); break; case 3: - skillRatingDescription = "Veteran"; + targetNumber.addModifier(skillRating, resources.getString("skillVeteran.text")); break; case 4: - skillRatingDescription = "Elite"; + targetNumber.addModifier(skillRating, resources.getString("skillElite.text")); break; default: - skillRatingDescription = "Error, please see log"; LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating); } - - target.addModifier(skillRating, skillRatingDescription); } // Unit Rating modifier if (campaign.getCampaignOptions().isUseUnitRatingModifiers()) { int unitRatingModifier = getUnitRatingModifier(campaign); - target.addModifier(unitRatingModifier, "Unit Rating"); + targetNumber.addModifier(unitRatingModifier, resources.getString("unitRating.text")); } // Mission completion status modifiers if ((contract != null) && (campaign.getCampaignOptions().isUseMissionStatusModifiers())) { if (contract.getStatus().isSuccess()) { - target.addModifier(-1, "Recent Success"); + targetNumber.addModifier(-1, resources.getString("missionSuccess.text")); } else if (contract.getStatus().isFailed()) { - target.addModifier(1, "Recent Failure"); + targetNumber.addModifier(1, resources.getString("missionFailure.text")); } else if (contract.getStatus().isBreach()) { - target.addModifier(2, "Recent Contract Breach"); + targetNumber.addModifier(2, resources.getString("missionBreach.text")); } } // Faction Modifiers if (campaign.getCampaignOptions().isUseFactionModifiers()) { if (campaign.getFaction().isPirate()) { - target.addModifier(1, "Pirate Company"); - } else if (p.getOriginFaction().isPirate()) { - target.addModifier(1, "Pirate"); + targetNumber.addModifier(1, resources.getString("factionPirateCompany.text")); + } else if (person.getOriginFaction().isPirate()) { + targetNumber.addModifier(1, resources.getString("factionPirate.text")); } - if (p.getOriginFaction().isMercenary()) { - target.addModifier(1, "Mercenary"); + if (person.getOriginFaction().isMercenary()) { + targetNumber.addModifier(1, resources.getString("factionMercenary.text")); } - if (p.getOriginFaction().isClan()) { - target.addModifier(-2, "Clan"); + if (person.getOriginFaction().isClan()) { + targetNumber.addModifier(-2, resources.getString("factionClan.text")); } - if (FactionHints.defaultFactionHints().isAtWarWith(campaign.getFaction(), p.getOriginFaction(), campaign.getLocalDate())) { - target.addModifier(1, "Enemy Faction"); + if (FactionHints.defaultFactionHints().isAtWarWith(campaign.getFaction(), person.getOriginFaction(), campaign.getLocalDate())) { + targetNumber.addModifier(1, resources.getString("factionEnemy.text")); } } // Age modifiers if (campaign.getCampaignOptions().isUseAgeModifiers()) { - int age = p.getAge(campaign.getLocalDate()); + int age = person.getAge(campaign.getLocalDate()); int ageMod = getAgeMod(age); if (ageMod != 0) { - target.addModifier(ageMod, "Age"); + targetNumber.addModifier(ageMod, resources.getString("age.text")); } } // Injury Modifiers - int injuryMod = (int) p.getInjuries() + int injuryMod = (int) person.getInjuries() .stream() .filter(Injury::isPermanent).count(); if (injuryMod > 0) { - target.addModifier(injuryMod, "Permanent Injuries"); + targetNumber.addModifier(injuryMod, resources.getString("injuries.text")); } // Officer Modifiers - if (p.getRank().isOfficer()) { - target.addModifier(-1, "Officer"); + if (person.getRank().isOfficer()) { + targetNumber.addModifier(-1, resources.getString("officer.text")); } else { - for (Enumeration i = p.getOptions(PersonnelOptions.LVL3_ADVANTAGES); i.hasMoreElements(); ) { + for (Enumeration i = person.getOptions(PersonnelOptions.LVL3_ADVANTAGES); i.hasMoreElements(); ) { IOption ability = i.nextElement(); if (ability.booleanValue()) { if (ability.getName().equals("tactical_genius")) { - target.addModifier(1, "Non-officer tactical genius"); + targetNumber.addModifier(1, resources.getString("tacticalGenius.text")); break; } } @@ -310,8 +266,8 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } // Founder Modifier - if (p.isFounder()) { - target.addModifier(2, "Founder"); + if (person.isFounder()) { + targetNumber.addModifier(2, resources.getString("founder.text")); } // Shares Modifiers @@ -330,31 +286,94 @@ public Map calculateTargetNumbers(final @Nullable AtBContract } } if (c != null) { - target.addModifier(- (c.getSharesPct() / 10), "Shares"); + targetNumber.addModifier(- (c.getSharesPct() / 10), resources.getString("shares.text")); } } - // Leadership Modifiers - if(campaign.getCampaignOptions().isUseLeadershipModifiers()) { - if ((combatLeadershipMod != 0) && p.getPrimaryRole().isCombat()) { - target.addModifier(combatLeadershipMod, "Leadership (Combatant)"); + // Administrative Strain Modifiers + if (campaign.getCampaignOptions().isUseAdministrativeStrain()) { + int nonCombatantStrainModifier = getNonCombatantStrainModifier(campaign); + int combatantStrainModifier = getCombatantStrainModifier(campaign); + + if ((nonCombatantStrainModifier > 0) && (person.getUnit() == null)) { + targetNumber.addModifier(nonCombatantStrainModifier, resources.getString("administrativeStrain.text")); } - if ((supportLeadershipMod != 0) && p.getPrimaryRole().isSupport()) { - target.addModifier(supportLeadershipMod, "Leadership (Support)"); + if ((combatantStrainModifier > 0) && (person.getUnit() != null)) { + targetNumber.addModifier(combatantStrainModifier, resources.getString("administrativeStrain.text")); } } // Fatigue Modifiers if (campaign.getCampaignOptions().isTrackUnitFatigue()) { - target.addModifier(campaign.getFatigueLevel() / 10, "Fatigue"); + targetNumber.addModifier(campaign.getFatigueLevel() / 10, resources.getString("fatigue.text")); } - targets.put(p.getId(), target); + targets.put(person.getId(), targetNumber); } return targets; } + /** + * This method calculates the combatant strain modifier based on the active personnel assigned to units. + * + * @param campaign the campaign for which to calculate the strain modifier + * @return the strain modifier + */ + private static int getCombatantStrainModifier(Campaign campaign) { + int combatants = 0; + int proto = 0; + + for (Person person : campaign.getActivePersonnel()) { + if (person.getPrimaryRole().isCivilian() || !person.getPrisonerStatus().isFree()) { + continue; + } + + // personnel without a unit are treated as non-combatants + if (person.getUnit() != null) { + // we treat multi-crewed units as one, for Administrative Strain, + // as otherwise users would be penalized for their use + if (person.getUnit().isCommander(person)) { + if (person.getUnit().getEntity().isProtoMek()) { + proto++; + } else { + combatants++; + } + } + } + } + + combatants += proto / 5; + + int maximumStrain = campaign.getCampaignOptions().getCombatantStrain(); + + int skillLevel = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_COMMAND, SkillType.S_ADMIN) + .getSkill(SkillType.S_ADMIN) + .getLevel(); + + return (maximumStrain * skillLevel) / combatants; + } + + /** + * This method calculates the non-combatant strain modifier based on the active personnel not assigned to units. + * + * @param campaign the campaign for which to calculate the strain modifier + * @return the strain modifier + */ + private int getNonCombatantStrainModifier(Campaign campaign) { + int nonCombatants = (int) campaign.getActivePersonnel().stream() + .filter(person -> !person.getPrimaryRole().isCivilian() && person.getPrisonerStatus().isFree()) + .filter(person -> person.getUnit() == null).count(); + + int maximumStrain = campaign.getCampaignOptions().getNonCombatantStrain(); + + int skillLevel = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN) + .getSkill(SkillType.S_ADMIN) + .getLevel(); + + return (maximumStrain * skillLevel) / nonCombatants; + } + /** * This method calculates the base target number. * diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 3d50821fbc..3b95290e71 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -274,7 +274,6 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseFactionModifiers; private JCheckBox chkUseMissionStatusModifiers; private JCheckBox chkTrackUnitFatigue; - private JCheckBox chkUseLeadershipModifiers; private JPanel turnoverPayoutPanel; private JLabel lblPayoutRateOfficer; @@ -287,6 +286,14 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JLabel lblPayoutServiceBonusRate; private JSpinner spnPayoutServiceBonusRate; + private JPanel unitCohesionPanel; + private JPanel administrativeStrainPanel; + private JCheckBox chkUseAdministrativeStrain; + private JLabel lblCombatantStrain; + private JSpinner spnCombatantStrain; + private JLabel lblNonCombatantStrain; + private JSpinner spnNonCombatantStrain; + private JPanel sharesPanel; private JCheckBox chkUseShareSystem; private JCheckBox chkSharesExcludeLargeCraft; @@ -3619,6 +3626,13 @@ private JPanel createRetirementPanel() { chkUseRetirementDateTracking.setName("chkUseRetirementDateTracking"); randomRetirementPanel = createRandomRetirementPanel(); + randomRetirementPanel.setEnabled(campaign.getCampaignOptions().isUseRandomRetirement()); + + unitCohesionPanel = createUnitCohesionPanel(); + unitCohesionPanel.setEnabled(campaign.getCampaignOptions().isUseRandomRetirement()); + administrativeStrainPanel.setEnabled(campaign.getCampaignOptions().isUseAdministrativeStrain() + && campaign.getCampaignOptions().isUseRandomRetirement()); + sharesPanel = createSharesPanel(); // global enable @@ -3628,21 +3642,31 @@ private JPanel createRetirementPanel() { chkUseRandomRetirement.addActionListener(evt -> { final boolean isEnabled = chkUseRandomRetirement.isSelected(); - // general handler + // general handlers for (int index = 0; index < Arrays.stream(randomRetirementPanel.getComponents()).count(); index++) { randomRetirementPanel.getComponent(index).setEnabled(isEnabled); } + for (int index = 0; index < Arrays.stream(unitCohesionPanel.getComponents()).count(); index++) { + unitCohesionPanel.getComponent(index).setEnabled(isEnabled); + } + + // border handlers + randomRetirementPanel.setEnabled(isEnabled); + unitCohesionPanel.setEnabled(isEnabled); + // special handlers - randomRetirementPanel.setEnabled((isEnabled)); lblTurnoverFixedTargetNumber.setEnabled(isEnabled && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); spnTurnoverFixedTargetNumber.setEnabled(isEnabled && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); - }); - // this prevents a really annoying bug where disabled options don't stay disabled when reloading Campaign Options - randomRetirementPanel.setEnabled(campaign.getCampaignOptions().isUseRandomRetirement()); + + lblCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + spnCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + lblNonCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + spnNonCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + }); // Layout the Panel final JPanel panel = new JPanel(); @@ -3659,6 +3683,7 @@ private JPanel createRetirementPanel() { .addComponent(chkUseRetirementDateTracking) .addComponent(chkUseRandomRetirement) .addComponent(randomRetirementPanel) + .addComponent(unitCohesionPanel) .addComponent(sharesPanel) ); @@ -3667,6 +3692,7 @@ private JPanel createRetirementPanel() { .addComponent(chkUseRetirementDateTracking) .addComponent(chkUseRandomRetirement) .addComponent(randomRetirementPanel) + .addComponent(unitCohesionPanel) .addComponent(sharesPanel) ); @@ -3682,8 +3708,8 @@ private JPanel createRandomRetirementPanel() { spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(5, 0, 12, 1)); spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); - spnTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); - lblTurnoverFixedTargetNumber.setEnabled(false); + spnTurnoverFixedTargetNumber.setName("spnTurnoverFixedTargetNumber"); + spnTurnoverFixedTargetNumber.setEnabled(false); lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); lblTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); @@ -3734,9 +3760,6 @@ public Component getListCellRendererComponent(final JList list, final Object turnoverModifiersPanel = createTurnoverModifiersPanel(); turnoverPayoutPanel = createTurnoverPayoutPanel(); - // Programmatically Assign Accessibility Labels - lblTurnoverTargetNumberMethod.setLabelFor(comboTurnoverTargetNumberMethod); - // Layout the Panel randomRetirementPanel = new JDisableablePanel("randomRetirementPanel"); randomRetirementPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("randomRetirementPanel.title"))); @@ -3810,10 +3833,6 @@ private JPanel createTurnoverModifiersPanel() { chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); - chkUseLeadershipModifiers = new JCheckBox(resources.getString("chkUseLeadershipModifiers.text")); - chkUseLeadershipModifiers.setToolTipText(resources.getString("chkUseLeadershipModifiers.toolTipText")); - chkUseLeadershipModifiers.setName("chkUseLeadershipModifiers"); - turnoverModifiersPanel = new JDisableablePanel("turnoverModifierPanel"); turnoverModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverModifierPanel.title"))); @@ -3831,7 +3850,6 @@ private JPanel createTurnoverModifiersPanel() { .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) .addComponent(chkTrackUnitFatigue) - .addComponent(chkUseLeadershipModifiers) ); layout.setHorizontalGroup( @@ -3843,7 +3861,6 @@ private JPanel createTurnoverModifiersPanel() { .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) .addComponent(chkTrackUnitFatigue) - .addComponent(chkUseLeadershipModifiers) ); return turnoverModifiersPanel; @@ -3856,7 +3873,7 @@ private JPanel createTurnoverPayoutPanel() { spnPayoutRateOfficer = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1)); spnPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); - spnPayoutRateOfficer.setName("lblPayoutRateOfficer"); + spnPayoutRateOfficer.setName("spnPayoutRateOfficer"); lblPayoutRateEnlisted = new JLabel(resources.getString("lblPayoutRateEnlisted.text")); lblPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); @@ -3864,7 +3881,7 @@ private JPanel createTurnoverPayoutPanel() { spnPayoutRateEnlisted = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1)); spnPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); - spnPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); + spnPayoutRateEnlisted.setName("spnPayoutRateEnlisted"); lblPayoutRetirementMultiplier = new JLabel(resources.getString("lblPayoutRetirementMultiplier.text")); lblPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); @@ -3872,7 +3889,7 @@ private JPanel createTurnoverPayoutPanel() { spnPayoutRetirementMultiplier = new JSpinner(new SpinnerNumberModel(24, 1, 100, 1)); spnPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); - spnPayoutRetirementMultiplier.setName("lblPayoutRetirementMultiplier"); + spnPayoutRetirementMultiplier.setName("spnPayoutRetirementMultiplier"); chkUsePayoutServiceBonus = new JCheckBox(resources.getString("chkUsePayoutServiceBonus.text")); chkUsePayoutServiceBonus.setToolTipText(resources.getString("chkUsePayoutServiceBonus.toolTipText")); @@ -3884,7 +3901,7 @@ private JPanel createTurnoverPayoutPanel() { spnPayoutServiceBonusRate = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); spnPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); - spnPayoutServiceBonusRate.setName("lblPayoutServiceBonusRate"); + spnPayoutServiceBonusRate.setName("spnPayoutServiceBonusRate"); turnoverPayoutPanel = new JDisableablePanel("turnoverPayoutPanel"); turnoverPayoutPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverPayoutPanel.title"))); @@ -3931,6 +3948,95 @@ private JPanel createTurnoverPayoutPanel() { return turnoverPayoutPanel; } + private JPanel createUnitCohesionPanel() { + chkUseAdministrativeStrain = new JCheckBox(resources.getString("chkUseAdministrativeStrain.text")); + chkUseAdministrativeStrain.setToolTipText(resources.getString("chkUseAdministrativeStrain.toolTipText")); + chkUseAdministrativeStrain.setName("chkUseAdministrativeStrain"); + chkUseAdministrativeStrain.addActionListener(evt -> { + final boolean isEnabled = chkUseAdministrativeStrain.isSelected(); + + // general handlers + for (int index = 0; index < Arrays.stream(administrativeStrainPanel.getComponents()).count(); index++) { + administrativeStrainPanel.getComponent(index).setEnabled(isEnabled); + } + + // border handlers + administrativeStrainPanel.setEnabled(isEnabled); + }); + + administrativeStrainPanel = createAdministrativeStrainPanel(); + + unitCohesionPanel = new JDisableablePanel("unitCohesionPanel"); + unitCohesionPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("unitCohesionPanel.title"))); + + final GroupLayout layout = new GroupLayout(unitCohesionPanel); + unitCohesionPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseAdministrativeStrain) + .addComponent(administrativeStrainPanel) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseAdministrativeStrain) + .addComponent(administrativeStrainPanel) + ); + + return unitCohesionPanel; + } + + private JPanel createAdministrativeStrainPanel() { + lblCombatantStrain = new JLabel(resources.getString("lblCombatantStrain.text")); + lblCombatantStrain.setToolTipText(resources.getString("lblCombatantStrain.toolTipText")); + lblCombatantStrain.setName("lblCombatantStrain"); + + spnCombatantStrain = new JSpinner(new SpinnerNumberModel(12, 1, 120, 1)); + spnCombatantStrain.setToolTipText(resources.getString("lblCombatantStrain.toolTipText")); + spnCombatantStrain.setName("spnCombatantStrain"); + + lblNonCombatantStrain = new JLabel(resources.getString("lblNonCombatantStrain.text")); + lblNonCombatantStrain.setToolTipText(resources.getString("lblNonCombatantStrain.toolTipText")); + lblNonCombatantStrain.setName("lblNonCombatantStrain"); + + spnNonCombatantStrain = new JSpinner(new SpinnerNumberModel(12, 1, 120, 1)); + spnNonCombatantStrain.setToolTipText(resources.getString("lblNonCombatantStrain.toolTipText")); + spnNonCombatantStrain.setName("spnNonCombatantStrain"); + + administrativeStrainPanel = new JDisableablePanel("administrativeStrainPanel"); + administrativeStrainPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("administrativeStrainPanel.title"))); + + final GroupLayout layout = new GroupLayout(administrativeStrainPanel); + administrativeStrainPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblCombatantStrain) + .addComponent(spnCombatantStrain, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblNonCombatantStrain) + .addComponent(spnNonCombatantStrain, Alignment.LEADING)) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblCombatantStrain) + .addComponent(spnCombatantStrain)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblNonCombatantStrain) + .addComponent(spnNonCombatantStrain)) + ); + + return administrativeStrainPanel; + } + private JPanel createSharesPanel() { chkUseShareSystem = new JCheckBox(resources.getString("chkUseShareSystem.text")); chkUseShareSystem.setToolTipText(resources.getString("chkUseShareSystem.toolTipText")); @@ -3938,19 +4044,22 @@ private JPanel createSharesPanel() { chkUseShareSystem.addActionListener(evt -> { final boolean isEnabled = chkUseShareSystem.isSelected(); - chkSharesExcludeLargeCraft.setEnabled(isEnabled); - chkSharesForAll.setEnabled(isEnabled); + // general handlers + for (int index = 0; index < Arrays.stream(sharesPanel.getComponents()).count(); index++) { + sharesPanel.getComponent(index).setEnabled(isEnabled); + } + + // border handlers + sharesPanel.setEnabled(isEnabled); }); chkSharesExcludeLargeCraft = new JCheckBox(resources.getString("chkSharesExcludeLargeCraft.text")); chkSharesExcludeLargeCraft.setToolTipText(resources.getString("chkSharesExcludeLargeCraft.toolTipText")); chkSharesExcludeLargeCraft.setName("chkSharesExcludeLargeCraft"); - chkSharesExcludeLargeCraft.setEnabled(chkUseShareSystem.isSelected()); chkSharesForAll = new JCheckBox(resources.getString("chkSharesForAll.text")); chkSharesForAll.setToolTipText(resources.getString("chkSharesForAll.toolTipText")); chkSharesForAll.setName("chkSharesForAll"); - chkSharesForAll.setEnabled(chkUseShareSystem.isSelected()); sharesPanel = new JDisableablePanel("sharesPanel"); sharesPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("sharesPanel.title"))); @@ -7010,7 +7119,6 @@ public void setOptions(@Nullable CampaignOptions options, chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); chkUseMissionStatusModifiers.setSelected(options.isUseMissionStatusModifiers()); - chkUseLeadershipModifiers.setSelected(options.isUseLeadershipModifiers()); spnPayoutRateOfficer.setValue(options.getPayoutRateOfficer()); spnPayoutRateEnlisted.setValue(options.getPayoutRateEnlisted()); @@ -7018,6 +7126,10 @@ public void setOptions(@Nullable CampaignOptions options, chkUsePayoutServiceBonus.setSelected(options.isUsePayoutServiceBonus()); spnPayoutServiceBonusRate.setValue(options.getPayoutServiceBonusRate()); + chkUseAdministrativeStrain.setSelected(options.isUseAdministrativeStrain()); + spnCombatantStrain.setValue(options.getCombatantStrain()); + spnNonCombatantStrain.setValue(options.getNonCombatantStrain()); + chkUseShareSystem.setSelected(options.isUseShareSystem()); chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); chkSharesForAll.setSelected(options.isSharesForAll()); @@ -7631,7 +7743,6 @@ public void updateOptions() { options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); options.setUseMissionStatusModifiers(chkUseMissionStatusModifiers.isSelected()); - options.setUseLeadershipModifiers(chkUseLeadershipModifiers.isSelected()); options.setPayoutRateOfficer((Integer) spnPayoutRateOfficer.getValue()); options.setPayoutRateEnlisted((Integer) spnPayoutRateEnlisted.getValue()); @@ -7639,6 +7750,10 @@ public void updateOptions() { options.setUsePayoutServiceBonus(chkUsePayoutServiceBonus.isSelected()); options.setPayoutServiceBonusRate((Integer) spnPayoutServiceBonusRate.getValue()); + options.setUseAdministrativeStrain(chkUseAdministrativeStrain.isSelected()); + options.setCombatantStrain((Integer) spnCombatantStrain.getValue()); + options.setNonCombatantStrain((Integer) spnNonCombatantStrain.getValue()); + options.setUseShareSystem(chkUseShareSystem.isSelected()); options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); options.setSharesForAll(chkSharesForAll.isSelected()); From 2325e55ea988ced89d44724c43b6883f9d2ae7df Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 23:30:34 -0500 Subject: [PATCH 017/101] Remove unused variable in RetirementDefectionTracker The 'skillRatingDescription' variable was defined but not used in the mekhq.campaign.personnel.RetirementDefectionTracker. Therefore, it has been removed to prevent confusion and make the code cleaner. --- .../src/mekhq/campaign/personnel/RetirementDefectionTracker.java | 1 - 1 file changed, 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index e829f9904a..ebb5b23a5d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -161,7 +161,6 @@ public Map calculateTargetNumbers(final @Nullable AtBContract // Skill Rating modifier if (campaign.getCampaignOptions().isUseSkillModifiers()) { int skillRating; - String skillRatingDescription; try { skillRating = person.getExperienceLevel(campaign, true); From 79049576b96f6d7c42c3dc12bf4ec15485f1ee89 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 16 May 2024 23:39:40 -0500 Subject: [PATCH 018/101] Refined filter for non-combatant strain calculation This commit adjusts the filter used to calculate the non-combatant strain in the RetirementDefectionTracker. This change ensures that only active personnel, who are not civilians, not prisoners, not assigned to a unit, and who are not Astechs or Medics, are considered in the strain calculation. --- .../mekhq/campaign/personnel/RetirementDefectionTracker.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index ebb5b23a5d..2a27fa5a68 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -361,8 +361,9 @@ private static int getCombatantStrainModifier(Campaign campaign) { */ private int getNonCombatantStrainModifier(Campaign campaign) { int nonCombatants = (int) campaign.getActivePersonnel().stream() - .filter(person -> !person.getPrimaryRole().isCivilian() && person.getPrisonerStatus().isFree()) - .filter(person -> person.getUnit() == null).count(); + .filter(person -> !person.getPrimaryRole().isCivilian() && person.getPrisonerStatus().isFree() && person.getUnit() == null) + .filter(person -> (!person.getPrimaryRole().isAstech()) && (!person.getPrimaryRole().isMedic())) + .count(); int maximumStrain = campaign.getCampaignOptions().getNonCombatantStrain(); From 15d9a623aaf289f0f2809e8ac4d4bc433abdda0f Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 17 May 2024 13:59:14 -0500 Subject: [PATCH 019/101] Updated turnover difficulty settings. Expanded Administrative Strain mechanics. The code updates provide additional difficulty settings for turnover checks, allowing tiered difficulty levels. It also includes settings for handling combatant/non-combatant strain and turnover for multi-crewed units. --- .../CampaignOptionsDialog.properties | 16 +- .../RetirementDefectionTracker.properties | 7 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 36 ++++- .../personnel/RetirementDefectionTracker.java | 152 ++++++++++++------ .../mekhq/gui/panes/CampaignOptionsPane.java | 103 ++++++++---- 5 files changed, 221 insertions(+), 93 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 15ca2ba41c..365a1d04e5 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -248,7 +248,9 @@ chkUseRandomRetirement.toolTipText=Determines whether personnel will randomly le lblTurnoverTargetNumberMethod.text=Turnover Target Number Method lblTurnoverTargetNumberMethod.toolTipText=This is the method used to determine the base target number for turnover checks. lblTurnoverFixedTargetNumber.text=Fixed Target Number -lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks, when using the Fixed method. +lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks. +lblTurnoverDifficulty.text=Turnover Difficulty +lblTurnoverDifficulty.toolTipText=Increases or decreases the difficulty of turnover target numbers. chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls chkUseYearEndRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of each year. chkUseContractCompletionRandomRetirement.text=Use Contract Completion Turnover Rolls @@ -277,11 +279,13 @@ chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and co unitCohesionPanel.title=Unit Cohesion administrativeStrainPanel.title=Administrative Strain chkUseAdministrativeStrain.text=Enable Administrative Strain -chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty, based on Admin/HR Administration skill. -lblCombatantStrain.text=Combatant Strain (Admin/Command) -lblCombatantStrain.toolTipText=How many individual combatants can be supported per rank in Administration. -lblNonCombatantStrain.text=Non-Combatant Strain (Admin/HR) -lblNonCombatantStrain.toolTipText=How many individual non-combatants can be supported per rank in Administration. +chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty (based on the combined ranks of all Admin/HR personnel). +lblCombatantStrain.text=Combatant Strain +lblCombatantStrain.toolTipText=How many personnel (assigned to a unit) can be supported per combined rank in Administration. +lblMultiCrewStrainDivider.text=Multi-Crew Strain Divider +lblMultiCrewStrainDivider.toolTipText=For multi-crew units, or ProtoMech points, divide crew size by this value. +lblNonCombatantStrain.text=Non-Combatant Strain +lblNonCombatantStrain.toolTipText=How many personnel (not assigned to a unit) can be supported per combined rank in Administration. turnoverPayoutPanel.title=Payout Rates lblPayoutRateOfficer.text=Officer Payout Rate diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties index bdd8127378..d346ba4281 100644 --- a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -1,11 +1,6 @@ #### Target Number Labels base.text=Base -skillUnskilled.text=Unskilled -skillUltraGreen.text=Ultra-Green -skillGreen.text=Green -skillRegular.text=Regular -skillVeteran.text=Veteran -skillElite.text=Elite +skillRatingUnskilled.text=Unskilled unitRating.text=Unit Rating missionSuccess.text=Recent Successes missionFailure.text=Recent Failures diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index b347e88ffc..e932f338a7 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -253,6 +253,7 @@ public static String getTransitUnitName(final int unit) { private boolean useRetirementDateTracking; private boolean useRandomRetirement; private TurnoverTargetNumberMethod turnoverTargetNumberMethod; + private SkillLevel turnoverDifficulty; private Integer turnoverFixedTargetNumber; private boolean useYearEndRandomRetirement; private boolean useContractCompletionRandomRetirement; @@ -275,6 +276,7 @@ public static String getTransitUnitName(final int unit) { private boolean useAdministrativeStrain; private Integer combatantStrain; + private Integer multiCrewStrainDivider; private Integer nonCombatantStrain; private boolean useShareSystem; @@ -739,6 +741,7 @@ public CampaignOptions() { setUseRetirementDateTracking(false); setUseRandomRetirement(true); setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.NEGOTIATION); + setTurnoverDifficulty(SkillLevel.REGULAR); setTurnoverFixedTargetNumber(3); setUseYearEndRandomRetirement(true); setUseContractCompletionRandomRetirement(true); @@ -760,8 +763,9 @@ public CampaignOptions() { setPayoutServiceBonusRate(10); setUseAdministrativeStrain(true); - setCombatantStrain(12); - setNonCombatantStrain(24); + setCombatantStrain(2); + setMultiCrewStrainDivider(5); + setNonCombatantStrain(3); setUseShareSystem(false); setSharesExcludeLargeCraft(true); @@ -1646,6 +1650,14 @@ public void setTurnoverTargetNumberMethod (final TurnoverTargetNumberMethod turn this.turnoverTargetNumberMethod = turnoverTargetNumberMethod; } + public SkillLevel getTurnoverDifficulty() { + return turnoverDifficulty; + } + + public void setTurnoverDifficulty (final SkillLevel turnoverDifficulty) { + this.turnoverDifficulty = turnoverDifficulty; + } + public boolean isUseYearEndRandomRetirement() { return useYearEndRandomRetirement; } @@ -1798,6 +1810,14 @@ public void setCombatantStrain(final Integer combatantStrain) { this.combatantStrain = combatantStrain; } + public Integer getMultiCrewStrainDivider() { + return multiCrewStrainDivider; + } + + public void setMultiCrewStrainDivider(final Integer multiCrewStrainDivider) { + this.multiCrewStrainDivider = multiCrewStrainDivider; + } + public Integer getNonCombatantStrain() { return nonCombatantStrain; } @@ -4102,6 +4122,8 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRetirementDateTracking", isUseRetirementDateTracking()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomRetirement", isUseRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverTargetNumberMethod", getTurnoverTargetNumberMethod().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverDifficulty", getTurnoverDifficulty().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverBaseTn", getTurnoverFixedTargetNumber()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useYearEndRandomRetirement", isUseYearEndRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useContractCompletionRandomRetirement", isUseContractCompletionRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomFounderRetirement", isUseRandomFounderRetirement()); @@ -4115,7 +4137,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMissionStatusModifiers", isUseMissionStatusModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "turnoverBaseTn", getTurnoverFixedTargetNumber()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateOfficer", getPayoutRateOfficer()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateEnlisted", getPayoutRateEnlisted()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRetirementMultiplier", getPayoutRetirementMultiplier()); @@ -4124,6 +4145,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAdministrativeStrain", isUseAdministrativeStrain()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatantStrain", getCombatantStrain()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "multiCrewStrainDivider", getMultiCrewStrainDivider()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "nonCombatantStrain", getNonCombatantStrain()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); @@ -4754,6 +4776,10 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseRandomRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("turnoverTargetNumberMethod")) { retVal.setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("turnoverDifficulty")) { + retVal.setTurnoverDifficulty(SkillLevel.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("turnoverBaseTn")) { + retVal.setTurnoverFixedTargetNumber(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useYearEndRandomRetirement")) { retVal.setUseYearEndRandomRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useContractCompletionRandomRetirement")) { @@ -4776,8 +4802,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseMissionStatusModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("turnoverBaseTn")) { - retVal.setTurnoverFixedTargetNumber(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateOfficer")) { retVal.setPayoutRateOfficer(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateEnlisted")) { @@ -4792,6 +4816,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseAdministrativeStrain(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("combatantStrain")) { retVal.setCombatantStrain(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("multiCrewStrainDivider")) { + retVal.setMultiCrewStrainDivider(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("nonCombatantStrain")) { retVal.setNonCombatantStrain(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 2a27fa5a68..3fc05ac29e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -23,6 +23,7 @@ import megamek.common.Compute; import megamek.common.TargetRoll; import megamek.common.annotations.Nullable; +import megamek.common.enums.SkillLevel; import megamek.common.options.IOption; import mekhq.Utilities; import mekhq.campaign.Campaign; @@ -160,35 +161,41 @@ public Map calculateTargetNumbers(final @Nullable AtBContract // Skill Rating modifier if (campaign.getCampaignOptions().isUseSkillModifiers()) { - int skillRating; + SkillLevel skillRating; try { - skillRating = person.getExperienceLevel(campaign, true); + skillRating = person.getSkillLevel(campaign, false); } catch (Exception e) { - skillRating = -1; + skillRating = SkillLevel.NONE; } switch (skillRating) { - case -1: - targetNumber.addModifier(0, resources.getString("skillUnskilled.text")); + case NONE: + targetNumber.addModifier(0, resources.getString("skillRatingUnskilled.text")); break; - case 0: - targetNumber.addModifier(skillRating, resources.getString("skillUltraGreen.text")); + case ULTRA_GREEN: + targetNumber.addModifier(0, skillRating.name()); break; - case 1: - targetNumber.addModifier(skillRating, resources.getString("skillGreen.text")); + case GREEN: + targetNumber.addModifier(1, skillRating.name()); break; - case 2: - targetNumber.addModifier(skillRating, resources.getString("skillRegular.text")); + case REGULAR: + targetNumber.addModifier(2, skillRating.name()); break; - case 3: - targetNumber.addModifier(skillRating, resources.getString("skillVeteran.text")); + case VETERAN: + targetNumber.addModifier(3, skillRating.name()); break; - case 4: - targetNumber.addModifier(skillRating, resources.getString("skillElite.text")); + case ELITE: + targetNumber.addModifier(4, skillRating.name()); + break; + case HEROIC: + targetNumber.addModifier(5, skillRating.name()); + break; + case LEGENDARY: + targetNumber.addModifier(6, skillRating.name()); break; default: - LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating); + LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating.toString()); } } @@ -291,16 +298,16 @@ public Map calculateTargetNumbers(final @Nullable AtBContract // Administrative Strain Modifiers if (campaign.getCampaignOptions().isUseAdministrativeStrain()) { - int nonCombatantStrainModifier = getNonCombatantStrainModifier(campaign); int combatantStrainModifier = getCombatantStrainModifier(campaign); - - if ((nonCombatantStrainModifier > 0) && (person.getUnit() == null)) { - targetNumber.addModifier(nonCombatantStrainModifier, resources.getString("administrativeStrain.text")); - } + int nonCombatantStrainModifier = getNonCombatantStrainModifier(campaign); if ((combatantStrainModifier > 0) && (person.getUnit() != null)) { targetNumber.addModifier(combatantStrainModifier, resources.getString("administrativeStrain.text")); } + + if ((nonCombatantStrainModifier > 0) && (person.getUnit() == null)) { + targetNumber.addModifier(nonCombatantStrainModifier, resources.getString("administrativeStrain.text")); + } } // Fatigue Modifiers @@ -324,33 +331,52 @@ private static int getCombatantStrainModifier(Campaign campaign) { int proto = 0; for (Person person : campaign.getActivePersonnel()) { - if (person.getPrimaryRole().isCivilian() || !person.getPrisonerStatus().isFree()) { + if ((person.getPrimaryRole().isCivilian()) || (person.getPrisonerStatus().isPrisoner()) || (person.getPrisonerStatus().isPrisonerDefector())) { continue; } // personnel without a unit are treated as non-combatants if (person.getUnit() != null) { - // we treat multi-crewed units as one, for Administrative Strain, - // as otherwise users would be penalized for their use if (person.getUnit().isCommander(person)) { if (person.getUnit().getEntity().isProtoMek()) { proto++; } else { - combatants++; + combatants += Math.max(1, person.getUnit().getCrew().size() / campaign.getCampaignOptions().getMultiCrewStrainDivider()); } } } } - combatants += proto / 5; + combatants += proto / campaign.getCampaignOptions().getMultiCrewStrainDivider(); - int maximumStrain = campaign.getCampaignOptions().getCombatantStrain(); + int maximumStrain = campaign.getCampaignOptions().getCombatantStrain() * getCombinedSkillValues(campaign); + + if (maximumStrain != 0) { + return combatants / maximumStrain; + } else { + return combatants; + } + } - int skillLevel = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_COMMAND, SkillType.S_ADMIN) - .getSkill(SkillType.S_ADMIN) - .getLevel(); + /** + * Calculates the combined skill values of active Admin/HR personnel. + * + * @param campaign the campaign for which to calculate the combined skill values + * @return the combined skill values of active Admin/HR personnel in the campaign + */ + private static int getCombinedSkillValues(Campaign campaign) { + int combinedSkillValues = 0; - return (maximumStrain * skillLevel) / combatants; + for (Person person : campaign.getActivePersonnel()) { + if ((!person.getPrisonerStatus().isPrisoner()) || (!person.getPrisonerStatus().isPrisonerDefector())) { + if (person.getPrimaryRole().isAdministratorHR()) { + combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); + } else if (person.getSecondaryRole().isAdministratorHR()) { + combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); + } + } + } + return combinedSkillValues; } /** @@ -361,17 +387,45 @@ private static int getCombatantStrainModifier(Campaign campaign) { */ private int getNonCombatantStrainModifier(Campaign campaign) { int nonCombatants = (int) campaign.getActivePersonnel().stream() - .filter(person -> !person.getPrimaryRole().isCivilian() && person.getPrisonerStatus().isFree() && person.getUnit() == null) - .filter(person -> (!person.getPrimaryRole().isAstech()) && (!person.getPrimaryRole().isMedic())) + .filter(person -> (!person.getPrimaryRole().isCivilian()) && (!person.getPrisonerStatus().isPrisoner()) && (!person.getPrisonerStatus().isPrisonerDefector())) + .filter(person -> (!person.getPrimaryRole().isMedic()) && (!person.getPrimaryRole().isAstech())) .count(); - int maximumStrain = campaign.getCampaignOptions().getNonCombatantStrain(); + int maximumStrain = campaign.getCampaignOptions().getNonCombatantStrain() * getCombinedSkillValues(campaign); - int skillLevel = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN) - .getSkill(SkillType.S_ADMIN) - .getLevel(); + if (maximumStrain != 0) { + return nonCombatants / maximumStrain; + } else { + return nonCombatants; + } + } - return (maximumStrain * skillLevel) / nonCombatants; + /** + * Returns a difficulty modifier based on the turnover difficulty campaign setting. + * + * @param campaign the current campaign + * @return the difficulty modifier as an integer value + */ + private static Integer getDifficultyModifier(Campaign campaign) { + switch (campaign.getCampaignOptions().getTurnoverDifficulty()) { + case NONE: + case ULTRA_GREEN: + return -2; + case GREEN: + return -1; + case REGULAR: + return 0; + case VETERAN: + return 1; + case ELITE: + return 2; + case HEROIC: + return 3; + case LEGENDARY: + return 4; + default: + return 0; + } } /** @@ -383,19 +437,27 @@ private int getNonCombatantStrainModifier(Campaign campaign) { private static Integer getBaseTargetNumber(Campaign campaign) { try { if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { - return campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN) - .getSkill(SkillType.S_ADMIN) - .getFinalSkillValue(); + Person bestInRole = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN); + + int skillRating = bestInRole.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); + int difficulty = getDifficultyModifier(campaign); + int targetNumber = bestInRole.getSkill(SkillType.S_ADMIN).getType().getTarget(); + + return targetNumber - skillRating - difficulty; } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { - return campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_NEG) - .getSkill(SkillType.S_NEG) - .getFinalSkillValue(); + Person bestInRole = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_NEG); + + int skillRating = bestInRole.getSkill(SkillType.S_NEG).getFinalSkillValue(); + int difficulty = getDifficultyModifier(campaign); + int targetNumber = bestInRole.getSkill(SkillType.S_NEG).getType().getTarget(); + + return targetNumber - skillRating - difficulty; } else { return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); } // this means there isn't someone in the campaign with the relevant skill or role } catch (Exception e) { - return 13; + return 12; } } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 3b95290e71..54c1864b92 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -259,6 +259,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseRandomRetirement; private JLabel lblTurnoverTargetNumberMethod; private MMComboBox comboTurnoverTargetNumberMethod; + private JLabel lblTurnoverDifficulty; + private MMComboBox comboTurnoverDifficulty; private JLabel lblTurnoverFixedTargetNumber; private JSpinner spnTurnoverFixedTargetNumber; private JCheckBox chkUseYearEndRandomRetirement; @@ -291,6 +293,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseAdministrativeStrain; private JLabel lblCombatantStrain; private JSpinner spnCombatantStrain; + private JLabel lblMultiCrewStrainDivider; + private JSpinner spnMultiCrewStrainDivider; private JLabel lblNonCombatantStrain; private JSpinner spnNonCombatantStrain; @@ -3620,22 +3624,14 @@ private JPanel createPersonnelRandomizationPanel() { } private JPanel createRetirementPanel() { - // Create Panel Components + // enablers + boolean isUseRandomTurnoverEnabled = campaign.getCampaignOptions().isUseRandomRetirement(); + + // initiate components chkUseRetirementDateTracking = new JCheckBox(resources.getString("chkUseRetirementDateTracking.text")); chkUseRetirementDateTracking.setToolTipText(resources.getString("chkUseRetirementDateTracking.toolTipText")); chkUseRetirementDateTracking.setName("chkUseRetirementDateTracking"); - randomRetirementPanel = createRandomRetirementPanel(); - randomRetirementPanel.setEnabled(campaign.getCampaignOptions().isUseRandomRetirement()); - - unitCohesionPanel = createUnitCohesionPanel(); - unitCohesionPanel.setEnabled(campaign.getCampaignOptions().isUseRandomRetirement()); - administrativeStrainPanel.setEnabled(campaign.getCampaignOptions().isUseAdministrativeStrain() - && campaign.getCampaignOptions().isUseRandomRetirement()); - - sharesPanel = createSharesPanel(); - - // global enable chkUseRandomRetirement = new JCheckBox(resources.getString("chkUseRandomRetirement.text")); chkUseRandomRetirement.setToolTipText(resources.getString("chkUseRandomRetirement.toolTipText")); chkUseRandomRetirement.setName("chkUseRandomRetirement"); @@ -3660,14 +3656,35 @@ private JPanel createRetirementPanel() { && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); spnTurnoverFixedTargetNumber.setEnabled(isEnabled && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); - + lblTurnoverDifficulty.setEnabled(isEnabled + && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); + comboTurnoverDifficulty.setEnabled(isEnabled + && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); lblCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); spnCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + lblMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + spnMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); lblNonCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); spnNonCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); }); + // Random Turnover Panel Handlers + randomRetirementPanel = createRandomRetirementPanel(); + randomRetirementPanel.setEnabled(isUseRandomTurnoverEnabled); + lblTurnoverDifficulty.setEnabled(isUseRandomTurnoverEnabled && !campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); + comboTurnoverDifficulty.setEnabled(isUseRandomTurnoverEnabled && !campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); + lblTurnoverFixedTargetNumber.setEnabled(isUseRandomTurnoverEnabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); + spnTurnoverFixedTargetNumber.setEnabled(isUseRandomTurnoverEnabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); + + // Unit Cohesion Panel Handlers + unitCohesionPanel = createUnitCohesionPanel(); + unitCohesionPanel.setEnabled(isUseRandomTurnoverEnabled); + administrativeStrainPanel.setEnabled(isUseRandomTurnoverEnabled && campaign.getCampaignOptions().isUseAdministrativeStrain()); + + // Shares Panel Handlers + sharesPanel = createSharesPanel(); + // Layout the Panel final JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createTitledBorder(resources.getString("retirementPanel.title"))); @@ -3700,17 +3717,6 @@ private JPanel createRetirementPanel() { } private JPanel createRandomRetirementPanel() { - // Create Panel Components - lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); - lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); - lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); - lblTurnoverFixedTargetNumber.setEnabled(false); - - spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(5, 0, 12, 1)); - spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); - spnTurnoverFixedTargetNumber.setName("spnTurnoverFixedTargetNumber"); - spnTurnoverFixedTargetNumber.setEnabled(false); - lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); lblTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); lblTurnoverTargetNumberMethod.setName("lblTurnoverTargetNumberMethod"); @@ -3730,17 +3736,30 @@ public Component getListCellRendererComponent(final JList list, final Object } }); comboTurnoverTargetNumberMethod.addActionListener(evt -> { - final TurnoverTargetNumberMethod method = comboTurnoverTargetNumberMethod.getSelectedItem(); + final boolean isEnabled = randomRetirementPanel.isEnabled() && comboTurnoverTargetNumberMethod.getSelectedItem().isFixed(); - if (method == null) { - return; - } + lblTurnoverDifficulty.setEnabled(!isEnabled); + comboTurnoverDifficulty.setEnabled(!isEnabled); - final boolean enabled = randomRetirementPanel.isEnabled() && method.isFixed(); - lblTurnoverFixedTargetNumber.setEnabled(enabled); - spnTurnoverFixedTargetNumber.setEnabled(enabled); + lblTurnoverFixedTargetNumber.setEnabled(isEnabled); + spnTurnoverFixedTargetNumber.setEnabled(isEnabled); }); + lblTurnoverDifficulty = new JLabel(resources.getString("lblTurnoverDifficulty.text")); + lblTurnoverDifficulty.setToolTipText(resources.getString("lblTurnoverDifficulty.toolTipText")); + lblTurnoverDifficulty.setName("lblTurnoverDifficulty"); + + comboTurnoverDifficulty = new MMComboBox<>("comboTurnoverDifficulty", SkillLevel.values()); + comboTurnoverDifficulty.setToolTipText(resources.getString("lblTurnoverDifficulty.toolTipText")); + + lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); + lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); + lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); + + spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(5, 0, 12, 1)); + spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); + spnTurnoverFixedTargetNumber.setName("spnTurnoverFixedTargetNumber"); + chkUseYearEndRandomRetirement = new JCheckBox(resources.getString("chkUseYearEndRandomRetirement.text")); chkUseYearEndRandomRetirement.setToolTipText(resources.getString("chkUseYearEndRandomRetirement.toolTipText")); chkUseYearEndRandomRetirement.setName("chkUseYearEndRandomRetirement"); @@ -3774,6 +3793,9 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblTurnoverTargetNumberMethod) .addComponent(comboTurnoverTargetNumberMethod, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblTurnoverDifficulty) + .addComponent(comboTurnoverDifficulty, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblTurnoverFixedTargetNumber) .addComponent(spnTurnoverFixedTargetNumber, Alignment.LEADING)) @@ -3790,6 +3812,9 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createSequentialGroup() .addComponent(lblTurnoverTargetNumberMethod) .addComponent(comboTurnoverTargetNumberMethod)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblTurnoverDifficulty) + .addComponent(comboTurnoverDifficulty)) .addGroup(layout.createSequentialGroup() .addComponent(lblTurnoverFixedTargetNumber) .addComponent(spnTurnoverFixedTargetNumber)) @@ -3998,6 +4023,14 @@ private JPanel createAdministrativeStrainPanel() { spnCombatantStrain.setToolTipText(resources.getString("lblCombatantStrain.toolTipText")); spnCombatantStrain.setName("spnCombatantStrain"); + lblMultiCrewStrainDivider = new JLabel(resources.getString("lblMultiCrewStrainDivider.text")); + lblMultiCrewStrainDivider.setToolTipText(resources.getString("lblMultiCrewStrainDivider.toolTipText")); + lblMultiCrewStrainDivider.setName("lblMultiCrewStrainDivider"); + + spnMultiCrewStrainDivider = new JSpinner(new SpinnerNumberModel(5, 1, 100, 1)); + spnMultiCrewStrainDivider.setToolTipText(resources.getString("lblMultiCrewStrainDivider.toolTipText")); + spnMultiCrewStrainDivider.setName("spnCombatantStrain"); + lblNonCombatantStrain = new JLabel(resources.getString("lblNonCombatantStrain.text")); lblNonCombatantStrain.setToolTipText(resources.getString("lblNonCombatantStrain.toolTipText")); lblNonCombatantStrain.setName("lblNonCombatantStrain"); @@ -4019,6 +4052,9 @@ private JPanel createAdministrativeStrainPanel() { .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblCombatantStrain) .addComponent(spnCombatantStrain, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblMultiCrewStrainDivider) + .addComponent(spnMultiCrewStrainDivider, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblNonCombatantStrain) .addComponent(spnNonCombatantStrain, Alignment.LEADING)) @@ -4029,6 +4065,9 @@ private JPanel createAdministrativeStrainPanel() { .addGroup(layout.createSequentialGroup() .addComponent(lblCombatantStrain) .addComponent(spnCombatantStrain)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblMultiCrewStrainDivider) + .addComponent(spnMultiCrewStrainDivider)) .addGroup(layout.createSequentialGroup() .addComponent(lblNonCombatantStrain) .addComponent(spnNonCombatantStrain)) @@ -7106,6 +7145,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseRetirementDateTracking.setSelected(options.isUseRetirementDateTracking()); chkUseRandomRetirement.setSelected(options.isUseRandomRetirement()); comboTurnoverTargetNumberMethod.setSelectedItem(options.getTurnoverTargetNumberMethod()); + comboTurnoverDifficulty.setSelectedItem(options.getTurnoverDifficulty()); spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); chkUseYearEndRandomRetirement.setSelected(options.isUseYearEndRandomRetirement()); chkUseContractCompletionRandomRetirement.setSelected(options.isUseContractCompletionRandomRetirement()); @@ -7730,6 +7770,7 @@ public void updateOptions() { options.setUseRetirementDateTracking(chkUseRetirementDateTracking.isSelected()); options.setUseRandomRetirement(chkUseRandomRetirement.isSelected()); options.setTurnoverTargetNumberMethod(comboTurnoverTargetNumberMethod.getSelectedItem()); + options.setTurnoverDifficulty(comboTurnoverDifficulty.getSelectedItem()); options.setTurnoverFixedTargetNumber((Integer) spnTurnoverFixedTargetNumber.getValue()); options.setUseYearEndRandomRetirement(chkUseYearEndRandomRetirement.isSelected()); options.setUseContractCompletionRandomRetirement(chkUseContractCompletionRandomRetirement.isSelected()); From df52215ba0436a754a8a83c6541f9dd28d542224 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 17 May 2024 14:19:41 -0500 Subject: [PATCH 020/101] Improved Turnover target number calculation. This change refactors the RetirementDefectionTracker class to improve the method of turnover target number calculation. Handler methods for skill rating are updated to use toString() instead of name(). Additionally, the system now calculates based on average skill value for HR roles, rather than solely on the best individual's skill. This should provide more balanced results when calculating turnover targets in campaigns. --- .../personnel/RetirementDefectionTracker.java | 79 ++++++++++++------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 3fc05ac29e..9d8da6f031 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -174,25 +174,25 @@ public Map calculateTargetNumbers(final @Nullable AtBContract targetNumber.addModifier(0, resources.getString("skillRatingUnskilled.text")); break; case ULTRA_GREEN: - targetNumber.addModifier(0, skillRating.name()); + targetNumber.addModifier(0, skillRating.toString()); break; case GREEN: - targetNumber.addModifier(1, skillRating.name()); + targetNumber.addModifier(1, skillRating.toString()); break; case REGULAR: - targetNumber.addModifier(2, skillRating.name()); + targetNumber.addModifier(2, skillRating.toString()); break; case VETERAN: - targetNumber.addModifier(3, skillRating.name()); + targetNumber.addModifier(3, skillRating.toString()); break; case ELITE: - targetNumber.addModifier(4, skillRating.name()); + targetNumber.addModifier(4, skillRating.toString()); break; case HEROIC: - targetNumber.addModifier(5, skillRating.name()); + targetNumber.addModifier(5, skillRating.toString()); break; case LEGENDARY: - targetNumber.addModifier(6, skillRating.name()); + targetNumber.addModifier(6, skillRating.toString()); break; default: LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating.toString()); @@ -413,8 +413,6 @@ private static Integer getDifficultyModifier(Campaign campaign) { return -2; case GREEN: return -1; - case REGULAR: - return 0; case VETERAN: return 1; case ELITE: @@ -435,29 +433,56 @@ private static Integer getDifficultyModifier(Campaign campaign) { * @return the base target number */ private static Integer getBaseTargetNumber(Campaign campaign) { - try { - if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { - Person bestInRole = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_ADMIN); + int personnelCount = 0; + int combinedSkill = 0; - int skillRating = bestInRole.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); - int difficulty = getDifficultyModifier(campaign); - int targetNumber = bestInRole.getSkill(SkillType.S_ADMIN).getType().getTarget(); - return targetNumber - skillRating - difficulty; - } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { - Person bestInRole = campaign.findBestInRole(PersonnelRole.ADMINISTRATOR_HR, SkillType.S_NEG); + if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { + for (Person person : campaign.getActivePersonnel()) { + if ((person.getPrisonerStatus().isPrisoner()) || (person.getPrisonerStatus().isPrisonerDefector())) { + continue; + } - int skillRating = bestInRole.getSkill(SkillType.S_NEG).getFinalSkillValue(); - int difficulty = getDifficultyModifier(campaign); - int targetNumber = bestInRole.getSkill(SkillType.S_NEG).getType().getTarget(); + if ((person.getPrimaryRole().isAdministratorHR()) || (person.getSecondaryRole().isAdministratorHR())) { + personnelCount++; + combinedSkill += person.getSkill(SkillType.S_NEG).getFinalSkillValue(); + } + } - return targetNumber - skillRating - difficulty; - } else { - return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); + if (personnelCount != 0) { + combinedSkill = combinedSkill / personnelCount; } - // this means there isn't someone in the campaign with the relevant skill or role - } catch (Exception e) { - return 12; + + int difficulty = getDifficultyModifier(campaign); + + Skills skill = new Skills(); + int targetNumber = skill.getSkill(SkillType.S_ADMIN).getType().getTarget(); + + return targetNumber - combinedSkill + difficulty; + } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { + for (Person person : campaign.getActivePersonnel()) { + if ((person.getPrisonerStatus().isPrisoner()) || (person.getPrisonerStatus().isPrisonerDefector())) { + continue; + } + + if ((person.getPrimaryRole().isAdministratorHR()) || (person.getSecondaryRole().isAdministratorHR())) { + personnelCount++; + combinedSkill += person.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); + } + } + + if (personnelCount != 0) { + combinedSkill = combinedSkill / personnelCount; + } + + int difficulty = getDifficultyModifier(campaign); + + Skills skill = new Skills(); + int targetNumber = skill.getSkill(SkillType.S_ADMIN).getType().getTarget(); + + return targetNumber - combinedSkill + difficulty; + } else { + return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); } } From 2ddca906a54d72eca4d97898f9492999811517b8 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 17 May 2024 14:21:22 -0500 Subject: [PATCH 021/101] Forgot to update the tooltips --- MekHQ/resources/mekhq/resources/Personnel.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index b430df8b25..4ef1f155b9 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -458,11 +458,11 @@ RandomProcreationMethod.PERCENTAGE.toolTipText=This checks if a random value is # TurnoverTargetNumberMethod Enum TurnoverTargetNumberMethod.FIXED.text=Fixed -TurnoverTargetNumberMethod.FIXED.toolTipText=Turnover checks use a fixed target number, set below. +TurnoverTargetNumberMethod.FIXED.toolTipText=Turnover checks use a fixed target number, assigned below. TurnoverTargetNumberMethod.ADMINISTRATION.text=Administration -TurnoverTargetNumberMethod.ADMINISTRATION.toolTipText=The Target number is based on the Administration skill of the highest skilled Admin/HR personnel. +TurnoverTargetNumberMethod.ADMINISTRATION.toolTipText=The Target number is based on the mean Administration skill across all Admin/HR personnel. TurnoverTargetNumberMethod.NEGOTIATION.text=Negotiation -TurnoverTargetNumberMethod.NEGOTIATION.toolTipText=The Target number is based on the Negotiation skill of the highest skilled Admin/HR personnel. +TurnoverTargetNumberMethod.NEGOTIATION.toolTipText=The Target number is based on the mean Negotiation skill across all Admin/HR personnel. # RankSystemType Enum RankSystemType.DEFAULT.text=Default Rank System From 7c23e229eddb8581fa4e0c5891dc1e944bdf612d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 17 May 2024 16:20:40 -0500 Subject: [PATCH 022/101] Added administrative capacity report to campaign summary Introduced new functionality for generating an administrative capacity report that displays strain per combined rank in the campaign summary. This will replace the combatant strain and non-combatant strain modifiers, providing a clearer representation of administrative capacity within the game settings. --- .../mekhq/resources/CampaignGUI.properties | 1 + .../CampaignOptionsDialog.properties | 8 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 31 ++----- MekHQ/src/mekhq/campaign/CampaignSummary.java | 34 +++++++ .../personnel/RetirementDefectionTracker.java | 88 +++++++++---------- MekHQ/src/mekhq/gui/CommandCenterTab.java | 21 ++++- .../mekhq/gui/panes/CampaignOptionsPane.java | 54 ++++-------- 7 files changed, 126 insertions(+), 111 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignGUI.properties b/MekHQ/resources/mekhq/resources/CampaignGUI.properties index a0e2824d6c..164a791020 100644 --- a/MekHQ/resources/mekhq/resources/CampaignGUI.properties +++ b/MekHQ/resources/mekhq/resources/CampaignGUI.properties @@ -219,6 +219,7 @@ lblComposition.text=Unit Composition:; lblRepairStatus.text=Unit Damage Status:; lblTransportCapacity.text=Transport Capacity:; lblCargoSummary.text=Cargo Summary:; +lblAdministrativeCapacity.text=Adminstrative Capacity:; panLog.title=Daily Activity Log dialogCheckDueScenarios.text=You must complete scenarios with a date of today or earlier before advancing the day. diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 365a1d04e5..11284ae8b5 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -280,12 +280,10 @@ unitCohesionPanel.title=Unit Cohesion administrativeStrainPanel.title=Administrative Strain chkUseAdministrativeStrain.text=Enable Administrative Strain chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty (based on the combined ranks of all Admin/HR personnel). -lblCombatantStrain.text=Combatant Strain -lblCombatantStrain.toolTipText=How many personnel (assigned to a unit) can be supported per combined rank in Administration. -lblMultiCrewStrainDivider.text=Multi-Crew Strain Divider +lblAdministrativeStrain.text=Administrative Capacity +lblAdministrativeStrain.toolTipText=How many personnel can be supported per combined rank in Administration. +lblMultiCrewStrainDivider.text=Multi-Crew Divider lblMultiCrewStrainDivider.toolTipText=For multi-crew units, or ProtoMech points, divide crew size by this value. -lblNonCombatantStrain.text=Non-Combatant Strain -lblNonCombatantStrain.toolTipText=How many personnel (not assigned to a unit) can be supported per combined rank in Administration. turnoverPayoutPanel.title=Payout Rates lblPayoutRateOfficer.text=Officer Payout Rate diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index e932f338a7..d8aa1b2fc0 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -275,9 +275,8 @@ public static String getTransitUnitName(final int unit) { private boolean trackUnitFatigue; private boolean useAdministrativeStrain; - private Integer combatantStrain; + private Integer administrativeStrain; private Integer multiCrewStrainDivider; - private Integer nonCombatantStrain; private boolean useShareSystem; private boolean sharesExcludeLargeCraft; @@ -763,9 +762,8 @@ public CampaignOptions() { setPayoutServiceBonusRate(10); setUseAdministrativeStrain(true); - setCombatantStrain(2); + setAdministrativeStrain(10); setMultiCrewStrainDivider(5); - setNonCombatantStrain(3); setUseShareSystem(false); setSharesExcludeLargeCraft(true); @@ -1802,12 +1800,12 @@ public void setUseAdministrativeStrain(final boolean useAdministrativeStrain) { this.useAdministrativeStrain = useAdministrativeStrain; } - public Integer getCombatantStrain() { - return combatantStrain; + public Integer getAdministrativeStrain() { + return administrativeStrain; } - public void setCombatantStrain(final Integer combatantStrain) { - this.combatantStrain = combatantStrain; + public void setAdministrativeStrain(final Integer administrativeStrain) { + this.administrativeStrain = administrativeStrain; } public Integer getMultiCrewStrainDivider() { @@ -1817,14 +1815,6 @@ public Integer getMultiCrewStrainDivider() { public void setMultiCrewStrainDivider(final Integer multiCrewStrainDivider) { this.multiCrewStrainDivider = multiCrewStrainDivider; } - - public Integer getNonCombatantStrain() { - return nonCombatantStrain; - } - - public void setNonCombatantStrain(final Integer nonCombatantStrain) { - this.nonCombatantStrain = nonCombatantStrain; - } //endregion Retirement //region Family @@ -4144,9 +4134,8 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutServiceBonusRate", getPayoutServiceBonusRate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAdministrativeStrain", isUseAdministrativeStrain()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatantStrain", getCombatantStrain()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "administrativeStrain", getAdministrativeStrain()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "multiCrewStrainDivider", getMultiCrewStrainDivider()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "nonCombatantStrain", getNonCombatantStrain()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", isSharesExcludeLargeCraft()); @@ -4814,12 +4803,10 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setPayoutServiceBonusRate(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useAdministrativeStrain")) { retVal.setUseAdministrativeStrain(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("combatantStrain")) { - retVal.setCombatantStrain(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("administrativeStrain")) { + retVal.setAdministrativeStrain(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("multiCrewStrainDivider")) { retVal.setMultiCrewStrainDivider(Integer.parseInt(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("nonCombatantStrain")) { - retVal.setNonCombatantStrain(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { retVal.setUseShareSystem(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 23aa90397a..0f386eb663 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -27,10 +27,16 @@ import mekhq.campaign.unit.CargoStatistics; import mekhq.campaign.unit.HangarStatistics; import mekhq.campaign.unit.Unit; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import static mekhq.campaign.personnel.RetirementDefectionTracker.getAdministrativeStrain; +import static mekhq.campaign.personnel.RetirementDefectionTracker.getAdministrativeStrainModifier; +import static mekhq.campaign.personnel.RetirementDefectionTracker.getCombinedSkillValues; + /** * calculates and stores summary information on a campaign for use in reporting, mostly for the command center */ @@ -276,4 +282,32 @@ public String getTransportCapacity() { return percentTransported + "% bay capacity" + dropshipAppend; } + + /** + * Generates an administrative capacity report for the Command Center. + * + * @param campaign the campaign for which the administrative capacity report is generated + * @return the administrative capacity report in HTML format + */ + public String getAdministrativeCapacityReport(Campaign campaign) { + int combinedSkillValues = getCombinedSkillValues(campaign); + + StringBuilder administrativeCapacityReport = new StringBuilder(" ") + .append(getAdministrativeStrain(campaign)).append(" / ") + .append(campaign.getCampaignOptions().getAdministrativeStrain() * combinedSkillValues) + .append(" personnel"); + + if (getAdministrativeStrainModifier(campaign) > 0) { + administrativeCapacityReport + .append(" (+") + .append(getAdministrativeStrainModifier(campaign)) + .append(')'); + } + + return administrativeCapacityReport.toString(); + } + + private String createCsv(Collection coll) { + return StringUtils.join(coll, ","); + } } diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 9d8da6f031..7e3a80cea4 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -298,15 +298,10 @@ public Map calculateTargetNumbers(final @Nullable AtBContract // Administrative Strain Modifiers if (campaign.getCampaignOptions().isUseAdministrativeStrain()) { - int combatantStrainModifier = getCombatantStrainModifier(campaign); - int nonCombatantStrainModifier = getNonCombatantStrainModifier(campaign); + int administrativeStrainModifier = getAdministrativeStrainModifier(campaign); - if ((combatantStrainModifier > 0) && (person.getUnit() != null)) { - targetNumber.addModifier(combatantStrainModifier, resources.getString("administrativeStrain.text")); - } - - if ((nonCombatantStrainModifier > 0) && (person.getUnit() == null)) { - targetNumber.addModifier(nonCombatantStrainModifier, resources.getString("administrativeStrain.text")); + if ((administrativeStrainModifier > 0) && (person.getUnit() != null)) { + targetNumber.addModifier(administrativeStrainModifier, resources.getString("administrativeStrain.text")); } } @@ -326,8 +321,26 @@ public Map calculateTargetNumbers(final @Nullable AtBContract * @param campaign the campaign for which to calculate the strain modifier * @return the strain modifier */ - private static int getCombatantStrainModifier(Campaign campaign) { - int combatants = 0; + public static int getAdministrativeStrainModifier(Campaign campaign) { + int personnel = getAdministrativeStrain(campaign); + + int maximumStrain = campaign.getCampaignOptions().getAdministrativeStrain() * getCombinedSkillValues(campaign); + + if (maximumStrain != 0) { + return personnel / maximumStrain; + } else { + return personnel; + } + } + + /** + * Calculates the administrative strain for a given campaign. + * + * @param campaign the campaign for which to calculate the administrative strain + * @return the total administrative strain of the campaign + */ + public static int getAdministrativeStrain(Campaign campaign) { + int personnel = 0; int proto = 0; for (Person person : campaign.getActivePersonnel()) { @@ -335,27 +348,31 @@ private static int getCombatantStrainModifier(Campaign campaign) { continue; } - // personnel without a unit are treated as non-combatants if (person.getUnit() != null) { if (person.getUnit().isCommander(person)) { if (person.getUnit().getEntity().isProtoMek()) { proto++; } else { - combatants += Math.max(1, person.getUnit().getCrew().size() / campaign.getCampaignOptions().getMultiCrewStrainDivider()); + personnel += Math.max(1, person.getUnit().getCrew().size() / campaign.getCampaignOptions().getMultiCrewStrainDivider()); } } + } else { + if ((person.getPrimaryRole().isAstech()) && person.getSecondaryRole().isNone()) { + continue; + } else if ((person.getPrimaryRole().isMedic()) && person.getSecondaryRole().isNone()) { + continue; + } else if ((person.getPrimaryRole().isMedic()) && person.getSecondaryRole().isAstech()) { + continue; + } else if ((person.getPrimaryRole().isAstech()) && person.getSecondaryRole().isMedic()) { + continue; + } + + personnel++; } } - combatants += proto / campaign.getCampaignOptions().getMultiCrewStrainDivider(); - - int maximumStrain = campaign.getCampaignOptions().getCombatantStrain() * getCombinedSkillValues(campaign); - - if (maximumStrain != 0) { - return combatants / maximumStrain; - } else { - return combatants; - } + personnel += proto / campaign.getCampaignOptions().getMultiCrewStrainDivider(); + return personnel; } /** @@ -364,42 +381,23 @@ private static int getCombatantStrainModifier(Campaign campaign) { * @param campaign the campaign for which to calculate the combined skill values * @return the combined skill values of active Admin/HR personnel in the campaign */ - private static int getCombinedSkillValues(Campaign campaign) { + public static int getCombinedSkillValues(Campaign campaign) { int combinedSkillValues = 0; for (Person person : campaign.getActivePersonnel()) { if ((!person.getPrisonerStatus().isPrisoner()) || (!person.getPrisonerStatus().isPrisonerDefector())) { if (person.getPrimaryRole().isAdministratorHR()) { - combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); + combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getLevel(); + combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getBonus(); } else if (person.getSecondaryRole().isAdministratorHR()) { - combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); + combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getLevel(); + combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getBonus(); } } } return combinedSkillValues; } - /** - * This method calculates the non-combatant strain modifier based on the active personnel not assigned to units. - * - * @param campaign the campaign for which to calculate the strain modifier - * @return the strain modifier - */ - private int getNonCombatantStrainModifier(Campaign campaign) { - int nonCombatants = (int) campaign.getActivePersonnel().stream() - .filter(person -> (!person.getPrimaryRole().isCivilian()) && (!person.getPrisonerStatus().isPrisoner()) && (!person.getPrisonerStatus().isPrisonerDefector())) - .filter(person -> (!person.getPrimaryRole().isMedic()) && (!person.getPrimaryRole().isAstech())) - .count(); - - int maximumStrain = campaign.getCampaignOptions().getNonCombatantStrain() * getCombinedSkillValues(campaign); - - if (maximumStrain != 0) { - return nonCombatants / maximumStrain; - } else { - return nonCombatants; - } - } - /** * Returns a difficulty modifier based on the turnover difficulty campaign setting. * diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index 7e97bf2cef..dbad5a7f7e 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -22,8 +22,8 @@ import megamek.client.ui.swing.dialog.AbstractUnitSelectorDialog; import megamek.common.MechSummaryCache; import megamek.common.event.Subscribe; -import mekhq.MekHQ; import mekhq.MHQOptionsChangedEvent; +import mekhq.MekHQ; import mekhq.campaign.event.*; import mekhq.campaign.report.CargoReport; import mekhq.campaign.report.HangarReport; @@ -46,7 +46,6 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; -import java.util.List; import java.util.ResourceBundle; /** @@ -66,6 +65,7 @@ public final class CommandCenterTab extends CampaignGuiTab { private JLabel lblRepairStatus; private JLabel lblTransportCapacity; private JLabel lblCargoSummary; + private JLabel lblAdminstrativeCapacity; // objectives panel private JPanel panObjectives; @@ -244,6 +244,22 @@ private void initInfoPanel() { gridBagConstraints.weightx = 1.0; panInfo.add(lblPersonnel, gridBagConstraints); + if (getCampaign().getCampaignOptions().isUseAdministrativeStrain()) { + JLabel lblAdministrativeCapacityHead = new JLabel(resourceMap.getString("lblAdministrativeCapacity.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y++; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new Insets(1, 5, 1, 5); + panInfo.add(lblAdministrativeCapacityHead, gridBagConstraints); + lblAdminstrativeCapacity = new JLabel(getCampaign().getCampaignSummary().getAdministrativeCapacityReport(getCampaign())); + lblAdministrativeCapacityHead.setLabelFor(lblAdminstrativeCapacity); + gridBagConstraints.gridx = 1; + gridBagConstraints.weightx = 1.0; + panInfo.add(lblAdminstrativeCapacity, gridBagConstraints); + } + JLabel lblCompositionHead = new JLabel(resourceMap.getString("lblComposition.text")); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -514,6 +530,7 @@ private void refreshBasicInfo() { lblCargoSummary.setText(getCampaign().getCampaignSummary().getCargoCapacityReport()); lblRepairStatus.setText(getCampaign().getCampaignSummary().getForceRepairReport()); lblTransportCapacity.setText(getCampaign().getCampaignSummary().getTransportCapacity()); + lblAdminstrativeCapacity.setText(getCampaign().getCampaignSummary().getAdministrativeCapacityReport(getCampaign())); } private void refreshObjectives() { diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 54c1864b92..617b0d4bc0 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -291,12 +291,10 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JPanel unitCohesionPanel; private JPanel administrativeStrainPanel; private JCheckBox chkUseAdministrativeStrain; - private JLabel lblCombatantStrain; - private JSpinner spnCombatantStrain; + private JLabel lblAdministrativeStrain; + private JSpinner spnAdministrativeStrain; private JLabel lblMultiCrewStrainDivider; private JSpinner spnMultiCrewStrainDivider; - private JLabel lblNonCombatantStrain; - private JSpinner spnNonCombatantStrain; private JPanel sharesPanel; private JCheckBox chkUseShareSystem; @@ -3661,12 +3659,10 @@ private JPanel createRetirementPanel() { comboTurnoverDifficulty.setEnabled(isEnabled && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); - lblCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - spnCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + lblAdministrativeStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + spnAdministrativeStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); lblMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); spnMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - lblNonCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - spnNonCombatantStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); }); // Random Turnover Panel Handlers @@ -4015,13 +4011,13 @@ private JPanel createUnitCohesionPanel() { } private JPanel createAdministrativeStrainPanel() { - lblCombatantStrain = new JLabel(resources.getString("lblCombatantStrain.text")); - lblCombatantStrain.setToolTipText(resources.getString("lblCombatantStrain.toolTipText")); - lblCombatantStrain.setName("lblCombatantStrain"); + lblAdministrativeStrain = new JLabel(resources.getString("lblAdministrativeStrain.text")); + lblAdministrativeStrain.setToolTipText(resources.getString("lblAdministrativeStrain.toolTipText")); + lblAdministrativeStrain.setName("lblAdministrativeStrain"); - spnCombatantStrain = new JSpinner(new SpinnerNumberModel(12, 1, 120, 1)); - spnCombatantStrain.setToolTipText(resources.getString("lblCombatantStrain.toolTipText")); - spnCombatantStrain.setName("spnCombatantStrain"); + spnAdministrativeStrain = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); + spnAdministrativeStrain.setToolTipText(resources.getString("lblAdministrativeStrain.toolTipText")); + spnAdministrativeStrain.setName("spnAdministrativeStrain"); lblMultiCrewStrainDivider = new JLabel(resources.getString("lblMultiCrewStrainDivider.text")); lblMultiCrewStrainDivider.setToolTipText(resources.getString("lblMultiCrewStrainDivider.toolTipText")); @@ -4029,15 +4025,7 @@ private JPanel createAdministrativeStrainPanel() { spnMultiCrewStrainDivider = new JSpinner(new SpinnerNumberModel(5, 1, 100, 1)); spnMultiCrewStrainDivider.setToolTipText(resources.getString("lblMultiCrewStrainDivider.toolTipText")); - spnMultiCrewStrainDivider.setName("spnCombatantStrain"); - - lblNonCombatantStrain = new JLabel(resources.getString("lblNonCombatantStrain.text")); - lblNonCombatantStrain.setToolTipText(resources.getString("lblNonCombatantStrain.toolTipText")); - lblNonCombatantStrain.setName("lblNonCombatantStrain"); - - spnNonCombatantStrain = new JSpinner(new SpinnerNumberModel(12, 1, 120, 1)); - spnNonCombatantStrain.setToolTipText(resources.getString("lblNonCombatantStrain.toolTipText")); - spnNonCombatantStrain.setName("spnNonCombatantStrain"); + spnMultiCrewStrainDivider.setName("spnMultiCrewStrainDivider"); administrativeStrainPanel = new JDisableablePanel("administrativeStrainPanel"); administrativeStrainPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("administrativeStrainPanel.title"))); @@ -4050,27 +4038,21 @@ private JPanel createAdministrativeStrainPanel() { layout.setVerticalGroup( layout.createSequentialGroup() .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblCombatantStrain) - .addComponent(spnCombatantStrain, Alignment.LEADING)) + .addComponent(lblAdministrativeStrain) + .addComponent(spnAdministrativeStrain, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblMultiCrewStrainDivider) .addComponent(spnMultiCrewStrainDivider, Alignment.LEADING)) - .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblNonCombatantStrain) - .addComponent(spnNonCombatantStrain, Alignment.LEADING)) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(lblCombatantStrain) - .addComponent(spnCombatantStrain)) + .addComponent(lblAdministrativeStrain) + .addComponent(spnAdministrativeStrain)) .addGroup(layout.createSequentialGroup() .addComponent(lblMultiCrewStrainDivider) .addComponent(spnMultiCrewStrainDivider)) - .addGroup(layout.createSequentialGroup() - .addComponent(lblNonCombatantStrain) - .addComponent(spnNonCombatantStrain)) ); return administrativeStrainPanel; @@ -7167,8 +7149,7 @@ public void setOptions(@Nullable CampaignOptions options, spnPayoutServiceBonusRate.setValue(options.getPayoutServiceBonusRate()); chkUseAdministrativeStrain.setSelected(options.isUseAdministrativeStrain()); - spnCombatantStrain.setValue(options.getCombatantStrain()); - spnNonCombatantStrain.setValue(options.getNonCombatantStrain()); + spnAdministrativeStrain.setValue(options.getAdministrativeStrain()); chkUseShareSystem.setSelected(options.isUseShareSystem()); chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); @@ -7792,8 +7773,7 @@ public void updateOptions() { options.setPayoutServiceBonusRate((Integer) spnPayoutServiceBonusRate.getValue()); options.setUseAdministrativeStrain(chkUseAdministrativeStrain.isSelected()); - options.setCombatantStrain((Integer) spnCombatantStrain.getValue()); - options.setNonCombatantStrain((Integer) spnNonCombatantStrain.getValue()); + options.setAdministrativeStrain((Integer) spnAdministrativeStrain.getValue()); options.setUseShareSystem(chkUseShareSystem.isSelected()); options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); From b06d37d453f76ca0ab9c5923ab038f733c84c82b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 17 May 2024 16:40:58 -0500 Subject: [PATCH 023/101] Improved GUI support for Unit Fatigue. A functionality has been added to retrieve the fatigue report for a campaign in the campaign summary. A corresponding label has been added to the GUI to display this information. Furthermore, unnecessary report log regarding fatigue level in the campaign process has been removed. --- .../mekhq/resources/CampaignGUI.properties | 3 ++- MekHQ/src/mekhq/campaign/Campaign.java | 1 - MekHQ/src/mekhq/campaign/CampaignSummary.java | 11 +++++++++- MekHQ/src/mekhq/gui/CommandCenterTab.java | 21 ++++++++++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignGUI.properties b/MekHQ/resources/mekhq/resources/CampaignGUI.properties index 164a791020..c0957a48e5 100644 --- a/MekHQ/resources/mekhq/resources/CampaignGUI.properties +++ b/MekHQ/resources/mekhq/resources/CampaignGUI.properties @@ -212,14 +212,15 @@ panReports.title=Available Reports panInfo.title=Basic Unit Information panObjectives.title=Current Objectives lblRating.text=Unit Rating:; +lblFatigue.text=Unit Fatigue:; lblPersonnel.text=Personnel:; +lblAdministrativeCapacity.text=Adminstrative Capacity:; lblMissionSuccess.text=Mission Success Rate:; lblExperience.text=Experience:; lblComposition.text=Unit Composition:; lblRepairStatus.text=Unit Damage Status:; lblTransportCapacity.text=Transport Capacity:; lblCargoSummary.text=Cargo Summary:; -lblAdministrativeCapacity.text=Adminstrative Capacity:; panLog.title=Daily Activity Log dialogCheckDueScenarios.text=You must complete scenarios with a date of today or earlier before advancing the day. diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index beab4c17c6..9ded4ddbe6 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3169,7 +3169,6 @@ private void processNewDayATBFatigue() { fatigueLevel -= 2; } fatigueLevel = Math.max(fatigueLevel, 0); - addReport("Your fatigue level is: " + fatigueLevel); } private void processNewDayATB() { diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 0f386eb663..4f24131491 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -239,6 +239,15 @@ public String getForceCompositionReport() { return String.join(", ", composition); } + /** + * Retrieves the fatigue report for the campaign. + * + * @return The fatigue report as a string. + */ + public String getFatigueReport() { + return campaign.getFatigueLevel() + " (+" + campaign.getFatigueLevel() / 10 + ')'; + } + /** * A report that gives the percentage of successful missions * @return a String of the report @@ -292,7 +301,7 @@ public String getTransportCapacity() { public String getAdministrativeCapacityReport(Campaign campaign) { int combinedSkillValues = getCombinedSkillValues(campaign); - StringBuilder administrativeCapacityReport = new StringBuilder(" ") + StringBuilder administrativeCapacityReport = new StringBuilder() .append(getAdministrativeStrain(campaign)).append(" / ") .append(campaign.getCampaignOptions().getAdministrativeStrain() * combinedSkillValues) .append(" personnel"); diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index dbad5a7f7e..34d1d49c14 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -60,12 +60,13 @@ public final class CommandCenterTab extends CampaignGuiTab { private JLabel lblRating; private JLabel lblExperience; private JLabel lblPersonnel; + private JLabel lblAdminstrativeCapacity; private JLabel lblMissionSuccess; + private JLabel lblFatigue; private JLabel lblComposition; private JLabel lblRepairStatus; private JLabel lblTransportCapacity; private JLabel lblCargoSummary; - private JLabel lblAdminstrativeCapacity; // objectives panel private JPanel panObjectives; @@ -202,6 +203,23 @@ private void initInfoPanel() { gridBagConstraints.gridx = 1; gridBagConstraints.weightx = 1.0; panInfo.add(lblRating, gridBagConstraints); + + if(getCampaign().getCampaignOptions().isTrackUnitFatigue()) { + JLabel lblFatigueHead = new JLabel(resourceMap.getString("lblFatigue.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y++; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new Insets(1, 5, 1, 5); + panInfo.add(lblFatigueHead, gridBagConstraints); + lblFatigue = new JLabel(getCampaign().getCampaignSummary().getFatigueReport()); + lblFatigueHead.setLabelFor(lblFatigue); + gridBagConstraints.gridx = 1; + gridBagConstraints.weightx = 1.0; + panInfo.add(lblFatigue, gridBagConstraints); + } + JLabel lblExperienceHead = new JLabel(resourceMap.getString("lblExperience.text")); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -523,6 +541,7 @@ private void refreshBasicInfo() { getCampaign().getUnitRating().reInitialize(); getCampaign().getCampaignSummary().updateInformation(); lblRating.setText(getCampaign().getUnitRatingText()); + lblFatigue.setText(getCampaign().getCampaignSummary().getFatigueReport()); lblPersonnel.setText(getCampaign().getCampaignSummary().getPersonnelReport()); lblMissionSuccess.setText(getCampaign().getCampaignSummary().getMissionSuccessReport()); lblExperience.setText(getCampaign().getUnitRating().getAverageExperience().toString()); From f1836ac19f2847d4ddfde152c9d0e1d020b6a07f Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 18 May 2024 18:42:17 -0500 Subject: [PATCH 024/101] Added an option to use management skill in turnover calculations. --- .../CampaignOptionsDialog.properties | 11 +- .../RetirementDefectionTracker.properties | 6 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 42 ++ MekHQ/src/mekhq/campaign/CampaignSummary.java | 3 +- .../src/mekhq/campaign/personnel/Person.java | 30 + .../personnel/RetirementDefectionTracker.java | 564 ++++++++++++------ .../gui/dialog/RetirementDefectionDialog.java | 2 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 83 ++- 8 files changed, 553 insertions(+), 188 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 11284ae8b5..10952bc7fa 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -277,14 +277,23 @@ chkUseMissionStatusModifiers.text=Use Mission Status Modifiers chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. unitCohesionPanel.title=Unit Cohesion -administrativeStrainPanel.title=Administrative Strain chkUseAdministrativeStrain.text=Enable Administrative Strain chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty (based on the combined ranks of all Admin/HR personnel). +chkUseManagementSkill.text=Enable Management Skill +chkUseManagementSkill.toolTipText=This option applies a modifier to turnover checks based on the Leadership of commanding personnel. + +administrativeStrainPanel.title=Administrative Strain lblAdministrativeStrain.text=Administrative Capacity lblAdministrativeStrain.toolTipText=How many personnel can be supported per combined rank in Administration. lblMultiCrewStrainDivider.text=Multi-Crew Divider lblMultiCrewStrainDivider.toolTipText=For multi-crew units, or ProtoMech points, divide crew size by this value. +managementSkillPanel.title=Management Skill +chkUseCommanderLeadershipOnly.text=Only Use Commander's Leadership +chkUseCommanderLeadershipOnly.toolTipText=Personnel only use the Leadership skill of whoever has the overall Commander flag. If disabled, personnel will use the Leadership of their profession group commander. +lblManagementSkillPenalty.text=Unskilled Penalty +lblManagementSkillPenalty.toolTipText=The unskilled modifier to turnover target numbers. Each rank in Leadership reduces this number by 1. + turnoverPayoutPanel.title=Payout Rates lblPayoutRateOfficer.text=Officer Payout Rate lblPayoutRateOfficer.toolTipText=The number of months' pay officers get when resigning from the unit. diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties index d346ba4281..6d6ea37516 100644 --- a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -1,13 +1,14 @@ #### Target Number Labels base.text=Base -skillRatingUnskilled.text=Unskilled +desirability.text=Desirability unitRating.text=Unit Rating missionSuccess.text=Recent Successes missionFailure.text=Recent Failures missionBreach.text=Recent Contract Breach factionPirateCompany.text=Pirate Company factionPirate.text=Pirate -factionClan.text=Mercenary +factionClan.text=Clan +factionMercenary.text=Mercenary factionEnemy.text=Warring Factions age.text=Age injuries.text=Permanent Injuries @@ -17,3 +18,4 @@ founder.text=Founder shares.text=Shares administrativeStrain.text=Admin Strain fatigue.text=Fatigue +managementSkill.text=Management Skill diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index d8aa1b2fc0..a00c4c86a5 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -278,6 +278,10 @@ public static String getTransitUnitName(final int unit) { private Integer administrativeStrain; private Integer multiCrewStrainDivider; + private boolean useManagementSkill; + private boolean useCommanderLeadershipOnly; + private Integer managementSkillPenalty; + private boolean useShareSystem; private boolean sharesExcludeLargeCraft; private boolean sharesForAll; @@ -765,6 +769,10 @@ public CampaignOptions() { setAdministrativeStrain(10); setMultiCrewStrainDivider(5); + setUseManagementSkill(true); + setUseCommanderLeadershipOnly(true); + setManagementSkillPenalty(-2); + setUseShareSystem(false); setSharesExcludeLargeCraft(true); setSharesForAll(false); @@ -1815,6 +1823,30 @@ public Integer getMultiCrewStrainDivider() { public void setMultiCrewStrainDivider(final Integer multiCrewStrainDivider) { this.multiCrewStrainDivider = multiCrewStrainDivider; } + + public boolean isUseManagementSkill() { + return useManagementSkill; + } + + public void setUseManagementSkill(final boolean useManagementSkill) { + this.useManagementSkill = useManagementSkill; + } + + public boolean isUseCommanderLeadershipOnly() { + return useCommanderLeadershipOnly; + } + + public void setUseCommanderLeadershipOnly(final boolean useCommanderLeadershipOnly) { + this.useCommanderLeadershipOnly = useCommanderLeadershipOnly; + } + + public Integer getManagementSkillPenalty() { + return managementSkillPenalty; + } + + public void setManagementSkillPenalty(final Integer managementSkillPenalty) { + this.managementSkillPenalty = managementSkillPenalty; + } //endregion Retirement //region Family @@ -4137,6 +4169,10 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "administrativeStrain", getAdministrativeStrain()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "multiCrewStrainDivider", getMultiCrewStrainDivider()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useManagementSkill", isUseManagementSkill()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCommanderLeadershipOnly", isUseCommanderLeadershipOnly()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "managementSkillPenalty", getManagementSkillPenalty()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", isSharesExcludeLargeCraft()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesForAll", isSharesForAll()); @@ -4807,6 +4843,12 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setAdministrativeStrain(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("multiCrewStrainDivider")) { retVal.setMultiCrewStrainDivider(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useManagementSkill")) { + retVal.setUseManagementSkill(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useCommanderLeadershipOnly")) { + retVal.setUseCommanderLeadershipOnly(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("managementSkillPenalty")) { + retVal.setManagementSkillPenalty(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { retVal.setUseShareSystem(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 4f24131491..4778ab91ee 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -24,6 +24,7 @@ import mekhq.campaign.mission.Mission; import mekhq.campaign.mission.enums.MissionStatus; import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.SkillType; import mekhq.campaign.unit.CargoStatistics; import mekhq.campaign.unit.HangarStatistics; import mekhq.campaign.unit.Unit; @@ -299,7 +300,7 @@ public String getTransportCapacity() { * @return the administrative capacity report in HTML format */ public String getAdministrativeCapacityReport(Campaign campaign) { - int combinedSkillValues = getCombinedSkillValues(campaign); + int combinedSkillValues = getCombinedSkillValues(campaign, SkillType.S_ADMIN); StringBuilder administrativeCapacityReport = new StringBuilder() .append(getAdministrativeStrain(campaign)).append(" / ") diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index 66caf646d6..353bba80ae 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -2388,6 +2388,36 @@ public boolean outRanks(final @Nullable Person other) { return getRankNumeric() > other.getRankNumeric(); } } + + /** + * Checks if the current person outranks another person using a skill tiebreaker. + * If the other person is null, it is considered that the current person outranks them. + * If both persons have the same rank numeric value, the rank level is compared. + * If both persons have the same rank numeric value and rank level, the experience levels are compared. + * + * @param campaign the campaign used to calculate the experience levels + * @param otherPerson the other person to compare ranks with + * @return true if the current person outranks the other person, false otherwise + */ + public boolean outRanksUsingSkillTiebreaker(Campaign campaign, @Nullable Person otherPerson) { + if (otherPerson == null) { + return true; + } else if (getRankNumeric() == otherPerson.getRankNumeric()) { + if (getRankLevel() > otherPerson.getRankLevel()) { + return true; + } else if (getRankLevel() < otherPerson.getRankLevel()) { + return false; + } else { + if (getExperienceLevel(campaign, false) == otherPerson.getExperienceLevel(campaign, false)) { + return getExperienceLevel(campaign, true) > otherPerson.getExperienceLevel(campaign, true); + } else { + return getExperienceLevel(campaign, false) > otherPerson.getExperienceLevel(campaign, false); + } + } + } else { + return getRankNumeric() > otherPerson.getRankNumeric(); + } + } //endregion Ranks @Override diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 7e3a80cea4..25383ac917 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -23,7 +23,6 @@ import megamek.common.Compute; import megamek.common.TargetRoll; import megamek.common.annotations.Nullable; -import megamek.common.enums.SkillLevel; import megamek.common.options.IOption; import mekhq.Utilities; import mekhq.campaign.Campaign; @@ -63,6 +62,26 @@ public class RetirementDefectionTracker { final private Map payouts; private LocalDate lastRetirementRoll; + private static Person asfCommander; + private static Integer asfCommanderModifier; + private static Person vehicleCrewCommander; + private static Integer vehicleCrewCommanderModifier; + private static Person infantryCommander; + private static Integer infantryCommanderModifier; + private static Person navalCommander; + private static Integer navalCommanderModifier; + private static Person techCommander; + private static Integer techCommanderModifier; + private static Person medicalCommander; + private static Integer medicalCommanderModifier; + private static Person administrationCommander; + private static Integer administrationCommanderModifier; + private static Person mechWarriorCommander; + private static Integer mechWarriorCommanderModifier; + + private Integer hrSkill; + private Integer difficulty; + private final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.RetirementDefectionTracker"); public RetirementDefectionTracker() { @@ -72,60 +91,6 @@ public RetirementDefectionTracker() { lastRetirementRoll = LocalDate.now(); } - /** - * @param campaign the campaign to get share values for - * @return The value of each share in C-bills - */ - public static Money getShareValue(Campaign campaign) { - if (!campaign.getCampaignOptions().isUseShareSystem()) { - return Money.zero(); - } - - FinancialReport r = FinancialReport.calculate(campaign); - - Money netWorth = r.getNetWorth(); - if (campaign.getCampaignOptions().isSharesExcludeLargeCraft()) { - netWorth = netWorth.minus(r.getLargeCraftValue()); - } - - int totalShares = campaign.getActivePersonnel() - .stream() - .mapToInt(p -> p.getNumShares(campaign, campaign.getCampaignOptions().isSharesForAll())) - .sum(); - - if (totalShares <= 0) { - return Money.zero(); - } - - return netWorth.dividedBy(totalShares); - } - - /** - * @param age the age of the employee - * @return the age-based modifier - */ - private static int getAgeMod(int age) { - int ageMod = 0; - - if (age <= 20) { - ageMod = -1; - } else if ((age >= 50) && (age < 65)) { - ageMod = 1; - } else if ((age >= 65) && (age < 75)) { - ageMod = 2; - } else if ((age >= 75) && (age < 85)) { - ageMod = 3; - } else if ((age >= 85) && (age < 95)) { - ageMod = 4; - } else if ((age >= 95) && (age < 105)) { - ageMod = 5; - } else if (age >= 105) { - ageMod = 6; - } - - return ageMod; - } - /** * Computes the target for retirement rolls for all eligible personnel; this includes * all active personnel who are not dependents, prisoners, or bondsmen. @@ -135,15 +100,24 @@ private static int getAgeMod(int age) { * @param campaign The campaign to calculate target numbers for * @return A map with person ids as key and calculated target roll as value. */ - public Map calculateTargetNumbers(final @Nullable AtBContract contract, final Campaign campaign) { + public Map getTargetNumbers(final @Nullable AtBContract contract, final Campaign campaign) { final Map targets = new HashMap<>(); if (null != contract) { rollRequired.add(contract.getId()); } + if (!campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()) { + setHrSkill(campaign); + getDifficultyModifier(campaign); + } + + if (campaign.getCampaignOptions().isUseManagementSkill()) { + getManagementSkillValues(campaign); + } + for (Person person : campaign.getActivePersonnel()) { - if ((person.getPrimaryRole().isDependent()) || (!person.getPrisonerStatus().isFree()) || (person.isDeployed())) { + if ((person.getPrimaryRole().isCivilian()) || (!person.getPrisonerStatus().isFree()) || (person.isDeployed())) { continue; } @@ -159,43 +133,41 @@ public Map calculateTargetNumbers(final @Nullable AtBContract TargetRoll targetNumber = new TargetRoll(getBaseTargetNumber(campaign), resources.getString("base.text")); - // Skill Rating modifier + // Desirability modifier if (campaign.getCampaignOptions().isUseSkillModifiers()) { - SkillLevel skillRating; + targetNumber.addModifier(person.getExperienceLevel(campaign, false), resources.getString("desirability.text")); + } + + // Administrative Strain Modifiers + if (campaign.getCampaignOptions().isUseAdministrativeStrain()) { + int administrativeStrainModifier = getAdministrativeStrainModifier(campaign); + + if (administrativeStrainModifier > 0) { + targetNumber.addModifier(administrativeStrainModifier, resources.getString("administrativeStrain.text")); + } + } + + // Management Skill Modifier + if (campaign.getCampaignOptions().isUseManagementSkill()) { + targetNumber.addModifier(getManagementSkillModifier(person), resources.getString("managementSkill.text")); + } - try { - skillRating = person.getSkillLevel(campaign, false); - } catch (Exception e) { - skillRating = SkillLevel.NONE; + // Shares Modifiers + if (campaign.getCampaignOptions().isUseShareSystem()) { + // If this retirement roll is not being made at the end of a contract (e.g. >12 months since last roll), + // the share percentage should still apply. + // In the case of multiple active contracts, pick the one with the best percentage. + AtBContract c = contract; + if (c == null) { + for (AtBContract atBContract : campaign.getActiveAtBContracts()) { + if ((c == null) || (c.getSharesPct() < atBContract.getSharesPct())) { + c = atBContract; + } + } } - switch (skillRating) { - case NONE: - targetNumber.addModifier(0, resources.getString("skillRatingUnskilled.text")); - break; - case ULTRA_GREEN: - targetNumber.addModifier(0, skillRating.toString()); - break; - case GREEN: - targetNumber.addModifier(1, skillRating.toString()); - break; - case REGULAR: - targetNumber.addModifier(2, skillRating.toString()); - break; - case VETERAN: - targetNumber.addModifier(3, skillRating.toString()); - break; - case ELITE: - targetNumber.addModifier(4, skillRating.toString()); - break; - case HEROIC: - targetNumber.addModifier(5, skillRating.toString()); - break; - case LEGENDARY: - targetNumber.addModifier(6, skillRating.toString()); - break; - default: - LogManager.getLogger().error("RetirementDefectionTracker: Unable to parse skillRating. Returning {}", skillRating.toString()); + if (c != null) { + targetNumber.addModifier(- (c.getSharesPct() / 10), resources.getString("shares.text")); } } @@ -205,6 +177,11 @@ public Map calculateTargetNumbers(final @Nullable AtBContract targetNumber.addModifier(unitRatingModifier, resources.getString("unitRating.text")); } + // Fatigue Modifiers + if (campaign.getCampaignOptions().isTrackUnitFatigue()) { + targetNumber.addModifier(campaign.getFatigueLevel() / 10, resources.getString("fatigue.text")); + } + // Mission completion status modifiers if ((contract != null) && (campaign.getCampaignOptions().isUseMissionStatusModifiers())) { if (contract.getStatus().isSuccess()) { @@ -276,43 +253,239 @@ public Map calculateTargetNumbers(final @Nullable AtBContract targetNumber.addModifier(2, resources.getString("founder.text")); } - // Shares Modifiers - if (campaign.getCampaignOptions().isUseShareSystem()) { - /* If this retirement roll is not being made at the end - * of a contract (e.g. >12 months since last roll), the - * share percentage should still apply. In the case of multiple - * active contracts, pick the one with the best percentage. - */ - AtBContract c = contract; - if (c == null) { - for (AtBContract atBContract : campaign.getActiveAtBContracts()) { - if ((c == null) || (c.getSharesPct() < atBContract.getSharesPct())) { - c = atBContract; - } - } - } - if (c != null) { - targetNumber.addModifier(- (c.getSharesPct() / 10), resources.getString("shares.text")); - } + targets.put(person.getId(), targetNumber); + } + return targets; + } + + /** + * Sets the HR skill averaged across all Admin/HR personnel. + * + * @param campaign the Campaign object to get personnel from. + */ + private void setHrSkill(Campaign campaign) { + int hrPersonnelCount = (int) campaign.getActivePersonnel().stream() + .filter(person -> (!person.getPrisonerStatus().isPrisoner()) && (!person.getPrisonerStatus().isPrisonerDefector())) + .filter(person -> (person.getPrimaryRole().isAdministratorHR()) || (person.getSecondaryRole().isAdministratorHR())) + .count(); + + if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { + hrSkill = getCombinedSkillValues(campaign, SkillType.S_NEG) / hrPersonnelCount; + } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { + hrSkill = getCombinedSkillValues(campaign, SkillType.S_ADMIN) / hrPersonnelCount; + } + } + + /** + * Calculates the management skill modifier for a person + * + * @param person the individual we're fetching the modifier for + * @return the management skill modifier + */ + private static int getManagementSkillModifier(Person person) { + if ((person.getPrimaryRole().isCivilian()) || (!person.getPrisonerStatus().isFree())) { + return 0; + } + + if (person.getSecondaryRole() == PersonnelRole.NONE) { + return -getCommanderManagementSkill(person.getPrimaryRole()); + } else { + return -((getCommanderManagementSkill(person.getPrimaryRole()) + getCommanderManagementSkill(person.getSecondaryRole())) / 2); + } + } + + /** + * Returns the management skill modifier for a commander based on the given personnel role. + * + * @param role the personnel role of the person we're fetching the modifier for + * @return the management skill modifier for the commander + */ + private static int getCommanderManagementSkill(PersonnelRole role) { + switch (Profession.getProfessionFromPersonnelRole(role)) { + case AEROSPACE: + return asfCommanderModifier; + case VEHICLE: + return vehicleCrewCommanderModifier; + case INFANTRY: + return infantryCommanderModifier; + case NAVAL: + return navalCommanderModifier; + case TECH: + return techCommanderModifier; + case MEDICAL: + return medicalCommanderModifier; + case ADMINISTRATOR: + return administrationCommanderModifier; + case MECHWARRIOR: + return mechWarriorCommanderModifier; + case CIVILIAN: + return 0; + } + return 0; + } + + /** + * This method calculates the management skill values for the different commanding officers. + * Each commander's management skill value is calculated based on their role and rank within the campaign. + * The management skill modifier is calculated by adding the base modifier + * (retrieved from campaign options) and the commander's individual leadership skill. + * If no suitable commander is found for a particular role, + * the management skill modifier for that role remains the same as the base modifier. + * + * @param campaign The Campaign object for which to calculate the management skill values. + */ + private void getManagementSkillValues(Campaign campaign) { + int baseModifier = campaign.getCampaignOptions().getManagementSkillPenalty(); + + if (campaign.getCampaignOptions().isUseCommanderLeadershipOnly()) { + Person commander = campaign.getFlaggedCommander(); + + if ((commander != null) && (commander.hasSkill(SkillType.S_LEADER))) { + int commanderSkill = baseModifier + commander.getSkill(SkillType.S_LEADER).getFinalSkillValue(); + + asfCommanderModifier = commanderSkill; + vehicleCrewCommanderModifier = commanderSkill; + infantryCommanderModifier = commanderSkill; + navalCommanderModifier = commanderSkill; + techCommanderModifier = commanderSkill; + medicalCommanderModifier = commanderSkill; + administrationCommanderModifier = commanderSkill; + mechWarriorCommanderModifier = commanderSkill; + } else { + asfCommanderModifier = baseModifier; + vehicleCrewCommanderModifier = baseModifier; + infantryCommanderModifier = baseModifier; + navalCommanderModifier = baseModifier; + techCommanderModifier = baseModifier; + medicalCommanderModifier = baseModifier; + administrationCommanderModifier = baseModifier; + mechWarriorCommanderModifier = baseModifier; } - // Administrative Strain Modifiers - if (campaign.getCampaignOptions().isUseAdministrativeStrain()) { - int administrativeStrainModifier = getAdministrativeStrainModifier(campaign); + return; + } - if ((administrativeStrainModifier > 0) && (person.getUnit() != null)) { - targetNumber.addModifier(administrativeStrainModifier, resources.getString("administrativeStrain.text")); - } + for (Person person : campaign.getActivePersonnel()) { + if ((person.getPrimaryRole().isCivilian()) + || (person.getPrisonerStatus().isPrisoner()) + || (person.getPrisonerStatus().isPrisonerDefector())) { + continue; } - // Fatigue Modifiers - if (campaign.getCampaignOptions().isTrackUnitFatigue()) { - targetNumber.addModifier(campaign.getFatigueLevel() / 10, resources.getString("fatigue.text")); + switch (Profession.getProfessionFromPersonnelRole(person.getPrimaryRole())) { + case AEROSPACE: + if (person.outRanksUsingSkillTiebreaker(campaign, asfCommander)) { + asfCommander = person; + asfCommanderModifier = baseModifier + getIndividualCommanderLeadership(asfCommander); + } + break; + case VEHICLE: + if (person.outRanksUsingSkillTiebreaker(campaign, vehicleCrewCommander)) { + vehicleCrewCommander = person; + vehicleCrewCommanderModifier = baseModifier + getIndividualCommanderLeadership(vehicleCrewCommander); + } + break; + case INFANTRY: + if (person.outRanksUsingSkillTiebreaker(campaign, infantryCommander)) { + infantryCommander = person; + infantryCommanderModifier = baseModifier + getIndividualCommanderLeadership(infantryCommander); + } + break; + case NAVAL: + if (person.outRanksUsingSkillTiebreaker(campaign, navalCommander)) { + navalCommander = person; + navalCommanderModifier = baseModifier + getIndividualCommanderLeadership(navalCommander); + } + break; + case TECH: + if (person.outRanksUsingSkillTiebreaker(campaign, techCommander)) { + techCommander = person; + techCommanderModifier = baseModifier + getIndividualCommanderLeadership(techCommander); + } + break; + case MEDICAL: + if (person.outRanksUsingSkillTiebreaker(campaign, medicalCommander)) { + medicalCommander = person; + medicalCommanderModifier = baseModifier + getIndividualCommanderLeadership(medicalCommander); + } + break; + case ADMINISTRATOR: + if (person.outRanksUsingSkillTiebreaker(campaign, administrationCommander)) { + administrationCommander = person; + administrationCommanderModifier = baseModifier + getIndividualCommanderLeadership(administrationCommander); + } + break; + case MECHWARRIOR: + if (person.outRanksUsingSkillTiebreaker(campaign, mechWarriorCommander)) { + mechWarriorCommander = person; + mechWarriorCommanderModifier = baseModifier + getIndividualCommanderLeadership(mechWarriorCommander); + } + break; + case CIVILIAN: + break; } + } - targets.put(person.getId(), targetNumber); + for (Profession profession : Profession.values()) { + switch (profession) { + case AEROSPACE: + if (asfCommander == null) { + asfCommanderModifier = baseModifier; + } + break; + case VEHICLE: + if (vehicleCrewCommander == null) { + vehicleCrewCommanderModifier = baseModifier; + } + break; + case INFANTRY: + if (infantryCommander == null) { + infantryCommanderModifier = baseModifier; + } + break; + case NAVAL: + if (navalCommander == null) { + navalCommanderModifier = baseModifier; + } + break; + case TECH: + if (techCommander == null) { + techCommanderModifier = baseModifier; + } + break; + case MEDICAL: + if (medicalCommander == null) { + medicalCommanderModifier = baseModifier; + } + break; + case ADMINISTRATOR: + if (administrationCommander == null) { + administrationCommanderModifier = baseModifier; + } + break; + case MECHWARRIOR: + if (mechWarriorCommander == null) { + mechWarriorCommanderModifier = baseModifier; + } + break; + case CIVILIAN: + break; + } + } + } + + /** + * Calculates the individual commander Leadership skill based on the provided commander. + * + * @param commander the commander for which the skill is being calculated + * @return the Leadership skill + */ + private static int getIndividualCommanderLeadership(Person commander) { + if (commander.hasSkill(SkillType.S_LEADER)) { + return commander.getSkill(SkillType.S_LEADER).getFinalSkillValue(); + } else { + return 0; } - return targets; } /** @@ -324,7 +497,7 @@ public Map calculateTargetNumbers(final @Nullable AtBContract public static int getAdministrativeStrainModifier(Campaign campaign) { int personnel = getAdministrativeStrain(campaign); - int maximumStrain = campaign.getCampaignOptions().getAdministrativeStrain() * getCombinedSkillValues(campaign); + int maximumStrain = campaign.getCampaignOptions().getAdministrativeStrain() * getCombinedSkillValues(campaign, SkillType.S_ADMIN); if (maximumStrain != 0) { return personnel / maximumStrain; @@ -381,17 +554,17 @@ public static int getAdministrativeStrain(Campaign campaign) { * @param campaign the campaign for which to calculate the combined skill values * @return the combined skill values of active Admin/HR personnel in the campaign */ - public static int getCombinedSkillValues(Campaign campaign) { + public static int getCombinedSkillValues(Campaign campaign, String skillType) { int combinedSkillValues = 0; for (Person person : campaign.getActivePersonnel()) { if ((!person.getPrisonerStatus().isPrisoner()) || (!person.getPrisonerStatus().isPrisonerDefector())) { if (person.getPrimaryRole().isAdministratorHR()) { - combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getLevel(); - combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getBonus(); + combinedSkillValues += person.getSkill(skillType).getLevel(); + combinedSkillValues += person.getSkill(skillType).getBonus(); } else if (person.getSecondaryRole().isAdministratorHR()) { - combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getLevel(); - combinedSkillValues += person.getSkill(SkillType.S_ADMIN).getBonus(); + combinedSkillValues += person.getSkill(skillType).getLevel(); + combinedSkillValues += person.getSkill(skillType).getBonus(); } } } @@ -402,25 +575,33 @@ public static int getCombinedSkillValues(Campaign campaign) { * Returns a difficulty modifier based on the turnover difficulty campaign setting. * * @param campaign the current campaign - * @return the difficulty modifier as an integer value */ - private static Integer getDifficultyModifier(Campaign campaign) { + private void getDifficultyModifier(Campaign campaign) { switch (campaign.getCampaignOptions().getTurnoverDifficulty()) { case NONE: + difficulty = -6; + break; case ULTRA_GREEN: - return -2; + difficulty = -5; + break; case GREEN: - return -1; + difficulty = -4; + break; + case REGULAR: + difficulty = -3; + break; case VETERAN: - return 1; + difficulty = -2; + break; case ELITE: - return 2; + difficulty = -1; + break; case HEROIC: - return 3; + difficulty = 0; + break; case LEGENDARY: - return 4; - default: - return 0; + difficulty = 1; + break; } } @@ -430,61 +611,26 @@ private static Integer getDifficultyModifier(Campaign campaign) { * @param campaign the campaign for which the base target number is calculated * @return the base target number */ - private static Integer getBaseTargetNumber(Campaign campaign) { - int personnelCount = 0; - int combinedSkill = 0; - - - if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { - for (Person person : campaign.getActivePersonnel()) { - if ((person.getPrisonerStatus().isPrisoner()) || (person.getPrisonerStatus().isPrisonerDefector())) { - continue; - } - - if ((person.getPrimaryRole().isAdministratorHR()) || (person.getSecondaryRole().isAdministratorHR())) { - personnelCount++; - combinedSkill += person.getSkill(SkillType.S_NEG).getFinalSkillValue(); - } - } + private int getBaseTargetNumber(Campaign campaign) { + if (!campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()) { + int targetNumber; - if (personnelCount != 0) { - combinedSkill = combinedSkill / personnelCount; - } - - int difficulty = getDifficultyModifier(campaign); + Person person = new Person(campaign); - Skills skill = new Skills(); - int targetNumber = skill.getSkill(SkillType.S_ADMIN).getType().getTarget(); - - return targetNumber - combinedSkill + difficulty; - } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { - for (Person person : campaign.getActivePersonnel()) { - if ((person.getPrisonerStatus().isPrisoner()) || (person.getPrisonerStatus().isPrisonerDefector())) { - continue; - } - - if ((person.getPrimaryRole().isAdministratorHR()) || (person.getSecondaryRole().isAdministratorHR())) { - personnelCount++; - combinedSkill += person.getSkill(SkillType.S_ADMIN).getFinalSkillValue(); - } - } - - if (personnelCount != 0) { - combinedSkill = combinedSkill / personnelCount; + if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { + person.addSkill(SkillType.S_NEG, 1, 0); + targetNumber = person.getSkills().getSkill(SkillType.S_NEG).getType().getTarget(); + } else { + person.addSkill(SkillType.S_ADMIN, 1, 0); + targetNumber = person.getSkills().getSkill(SkillType.S_ADMIN).getType().getTarget(); } - int difficulty = getDifficultyModifier(campaign); - - Skills skill = new Skills(); - int targetNumber = skill.getSkill(SkillType.S_ADMIN).getType().getTarget(); - - return targetNumber - combinedSkill + difficulty; + return targetNumber - hrSkill + difficulty; } else { return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); } } - /** * Returns the unit rating modifier for the campaign. * @@ -504,13 +650,67 @@ private static int getUnitRatingModifier(Campaign campaign) { return unitRating; } + /** + * @param campaign the campaign to get share values for + * @return The value of each share in C-bills + */ + public static Money getShareValue(Campaign campaign) { + if (!campaign.getCampaignOptions().isUseShareSystem()) { + return Money.zero(); + } + + FinancialReport r = FinancialReport.calculate(campaign); + + Money netWorth = r.getNetWorth(); + if (campaign.getCampaignOptions().isSharesExcludeLargeCraft()) { + netWorth = netWorth.minus(r.getLargeCraftValue()); + } + + int totalShares = campaign.getActivePersonnel() + .stream() + .mapToInt(p -> p.getNumShares(campaign, campaign.getCampaignOptions().isSharesForAll())) + .sum(); + + if (totalShares <= 0) { + return Money.zero(); + } + + return netWorth.dividedBy(totalShares); + } + + /** + * @param age the age of the employee + * @return the age-based modifier + */ + private static int getAgeMod(int age) { + int ageMod = 0; + + if (age <= 20) { + ageMod = -1; + } else if ((age >= 50) && (age < 65)) { + ageMod = 1; + } else if ((age >= 65) && (age < 75)) { + ageMod = 2; + } else if ((age >= 75) && (age < 85)) { + ageMod = 3; + } else if ((age >= 85) && (age < 95)) { + ageMod = 4; + } else if ((age >= 95) && (age < 105)) { + ageMod = 5; + } else if (age >= 105) { + ageMod = 6; + } + + return ageMod; + } + /** * Makes rolls for Employee Turnover based on previously calculated target rolls, * and tracks all retirees in the unresolvedPersonnel hash in case the dialog * is closed before payments are resolved, to avoid re-rolling the results. * * @param mission Nullable mission value - * @param targets The hash previously generated by calculateTargetNumbers. + * @param targets The hash previously generated by getTargetNumbers. * @param shareValue The value of each share in the unit; if not using the share system, this is zero. * @param campaign the current campaign */ diff --git a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java index d4fd7f99d5..18dddfc111 100644 --- a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java @@ -113,7 +113,7 @@ public RetirementDefectionDialog (CampaignGUI gui, AtBContract contract, boolean this.contract = contract; rdTracker = hqView.getCampaign().getRetirementDefectionTracker(); if (doRetirement) { - targetRolls = rdTracker.calculateTargetNumbers(contract, hqView.getCampaign()); + targetRolls = rdTracker.getTargetNumbers(contract, hqView.getCampaign()); } currentPanel = doRetirement?PAN_OVERVIEW:PAN_RESULTS; setSize(new Dimension(800, 600)); diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 617b0d4bc0..a250ffee4b 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -289,13 +289,20 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JSpinner spnPayoutServiceBonusRate; private JPanel unitCohesionPanel; - private JPanel administrativeStrainPanel; private JCheckBox chkUseAdministrativeStrain; + private JCheckBox chkUseManagementSkill; + + private JPanel administrativeStrainPanel; private JLabel lblAdministrativeStrain; private JSpinner spnAdministrativeStrain; private JLabel lblMultiCrewStrainDivider; private JSpinner spnMultiCrewStrainDivider; + private JPanel managementSkillPanel; + private JCheckBox chkUseCommanderLeadershipOnly; + private JLabel lblManagementSkillPenalty; + private JSpinner spnManagementSkillPenalty; + private JPanel sharesPanel; private JCheckBox chkUseShareSystem; private JCheckBox chkSharesExcludeLargeCraft; @@ -3663,6 +3670,10 @@ private JPanel createRetirementPanel() { spnAdministrativeStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); lblMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); spnMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); + + chkUseCommanderLeadershipOnly.setEnabled(isEnabled && chkUseManagementSkill.isSelected()); + lblManagementSkillPenalty.setEnabled(isEnabled && chkUseManagementSkill.isSelected()); + spnManagementSkillPenalty.setEnabled(isEnabled && chkUseManagementSkill.isSelected()); }); // Random Turnover Panel Handlers @@ -3985,7 +3996,23 @@ private JPanel createUnitCohesionPanel() { administrativeStrainPanel.setEnabled(isEnabled); }); + chkUseManagementSkill = new JCheckBox(resources.getString("chkUseManagementSkill.text")); + chkUseManagementSkill.setToolTipText(resources.getString("chkUseManagementSkill.toolTipText")); + chkUseManagementSkill.setName("chkUseManagementSkill"); + chkUseManagementSkill.addActionListener(evt -> { + final boolean isEnabled = chkUseManagementSkill.isSelected(); + + // general handlers + for (int index = 0; index < Arrays.stream(managementSkillPanel.getComponents()).count(); index++) { + managementSkillPanel.getComponent(index).setEnabled(isEnabled); + } + + // border handlers + managementSkillPanel.setEnabled(isEnabled); + }); + administrativeStrainPanel = createAdministrativeStrainPanel(); + managementSkillPanel = createManagementSkillPanel(); unitCohesionPanel = new JDisableablePanel("unitCohesionPanel"); unitCohesionPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("unitCohesionPanel.title"))); @@ -3999,12 +4026,16 @@ private JPanel createUnitCohesionPanel() { layout.createSequentialGroup() .addComponent(chkUseAdministrativeStrain) .addComponent(administrativeStrainPanel) + .addComponent(chkUseManagementSkill) + .addComponent(managementSkillPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseAdministrativeStrain) .addComponent(administrativeStrainPanel) + .addComponent(chkUseManagementSkill) + .addComponent(managementSkillPanel) ); return unitCohesionPanel; @@ -4058,6 +4089,46 @@ private JPanel createAdministrativeStrainPanel() { return administrativeStrainPanel; } + private JPanel createManagementSkillPanel() { + chkUseCommanderLeadershipOnly = new JCheckBox(resources.getString("chkUseCommanderLeadershipOnly.text")); + chkUseCommanderLeadershipOnly.setToolTipText(resources.getString("chkUseCommanderLeadershipOnly.toolTipText")); + chkUseCommanderLeadershipOnly.setName("chkUseCommanderLeadershipOnly"); + + lblManagementSkillPenalty = new JLabel(resources.getString("lblManagementSkillPenalty.text")); + lblManagementSkillPenalty.setToolTipText(resources.getString("lblManagementSkillPenalty.toolTipText")); + lblManagementSkillPenalty.setName("lblManagementSkillPenalty"); + + spnManagementSkillPenalty = new JSpinner(new SpinnerNumberModel(-2, -10, 0, 1)); + spnManagementSkillPenalty.setToolTipText(resources.getString("lblManagementSkillPenalty.toolTipText")); + spnManagementSkillPenalty.setName("spnManagementSkillPenalty"); + + managementSkillPanel = new JDisableablePanel("managementSkillPanel"); + managementSkillPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("managementSkillPanel.title"))); + + final GroupLayout layout = new GroupLayout(managementSkillPanel); + managementSkillPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseCommanderLeadershipOnly) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblManagementSkillPenalty) + .addComponent(spnManagementSkillPenalty, Alignment.LEADING)) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseCommanderLeadershipOnly) + .addGroup(layout.createSequentialGroup() + .addComponent(lblManagementSkillPenalty) + .addComponent(spnManagementSkillPenalty)) + ); + + return managementSkillPanel; + } + private JPanel createSharesPanel() { chkUseShareSystem = new JCheckBox(resources.getString("chkUseShareSystem.text")); chkUseShareSystem.setToolTipText(resources.getString("chkUseShareSystem.toolTipText")); @@ -7150,6 +7221,11 @@ public void setOptions(@Nullable CampaignOptions options, chkUseAdministrativeStrain.setSelected(options.isUseAdministrativeStrain()); spnAdministrativeStrain.setValue(options.getAdministrativeStrain()); + spnMultiCrewStrainDivider.setValue(options.getMultiCrewStrainDivider()); + + chkUseManagementSkill.setSelected(options.isUseManagementSkill()); + chkUseCommanderLeadershipOnly.setSelected(options.isUseCommanderLeadershipOnly()); + spnManagementSkillPenalty.setValue(options.getManagementSkillPenalty()); chkUseShareSystem.setSelected(options.isUseShareSystem()); chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); @@ -7774,6 +7850,11 @@ public void updateOptions() { options.setUseAdministrativeStrain(chkUseAdministrativeStrain.isSelected()); options.setAdministrativeStrain((Integer) spnAdministrativeStrain.getValue()); + options.setMultiCrewStrainDivider((Integer) spnMultiCrewStrainDivider.getValue()); + + options.setUseManagementSkill(chkUseManagementSkill.isSelected()); + options.setUseCommanderLeadershipOnly(chkUseCommanderLeadershipOnly.isSelected()); + options.setManagementSkillPenalty((Integer) spnManagementSkillPenalty.getValue()); options.setUseShareSystem(chkUseShareSystem.isSelected()); options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); From 8dfa3b414243aee051ac8915cc186d838b66e765 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 18 May 2024 21:17:18 -0500 Subject: [PATCH 025/101] Added loyalty tracking for personnel Implemented a new feature to track loyalty of personnel, which can affect their likelihood to stay or leave the unit. Loyalty is randomized for each personnel member, can be hidden or shown in the UI, and can be manually regenerated. --- .../CampaignOptionsDialog.properties | 24 ++++--- .../resources/mekhq/resources/GUI.properties | 1 + .../resources/PersonViewPanel.properties | 1 + .../RetirementDefectionTracker.properties | 1 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 35 +++++++++- .../src/mekhq/campaign/personnel/Person.java | 57 +++++++++++++--- .../personnel/RetirementDefectionTracker.java | 46 +++++++++---- .../generator/DefaultPersonnelGenerator.java | 2 + .../adapter/PersonnelTableMouseAdapter.java | 16 +++++ .../mekhq/gui/panes/CampaignOptionsPane.java | 66 +++++++++++++++++-- MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 31 +++++++++ 11 files changed, 242 insertions(+), 38 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 10952bc7fa..da02ff1cf2 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -255,26 +255,32 @@ chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls chkUseYearEndRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of each year. chkUseContractCompletionRandomRetirement.text=Use Contract Completion Turnover Rolls chkUseContractCompletionRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of a contract. -chkUseCustomRetirementModifiers.text=Customize Employee Turnover Rolls -chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. chkUseRandomFounderRetirement.text=Use Random Founder Turnover chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly leave the unit. chkUseSubContractSoldiers.text=Soldiers Use Commander Turnover Roll chkUseSubContractSoldiers.toolTipText=Commanders make Turnover rolls for all soldiers in their unit -chkTrackUnitFatigue.text=Track Unit Fatigue -chkTrackUnitFatigue.toolTipText=Continuous deployments without a break apply a penalty to Turnover checks. turnoverModifierPanel.title=Modifiers -chkUseSkillModifiers.text=Use Skill Modifiers +chkUseCustomRetirementModifiers.text=Custom +chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. +chkUseSkillModifiers.text=Desirability chkUseSkillModifiers.toolTipText=If enabled, better skilled personnel have a higher turnover target number. -chkUseAgeModifiers.text=Use Age Modifiers +chkUseAgeModifiers.text=Age chkUseAgeModifiers.toolTipText=If enabled, the age of personnel will influence their turnover target number. -chkUseUnitRatingModifiers.text=Use Unit Rating Modifiers +chkUseUnitRatingModifiers.text=Unit Rating chkUseUnitRatingModifiers.toolTipText=If enabled, the rating of the unit will influence the turnover target number for all personnel. -chkUseFactionModifiers.text=Use Faction Modifiers +chkUseFactionModifiers.text=Faction chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. -chkUseMissionStatusModifiers.text=Use Mission Status Modifiers +chkUseMissionStatusModifiers.text=Mission Status chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. +chkTrackUnitFatigue.text=Unit Fatigue +chkTrackUnitFatigue.toolTipText=Continuous deployments without a break apply a penalty to Turnover checks. + +loyaltyPanel.title=Loyalty +chkUseLoyaltyModifiers.text=Use Loyalty +chkUseLoyaltyModifiers.toolTipText=If enabled, personnel have a random loyalty rating ranging between -3 and 3. +chkUseHideLoyalty.text=Hide Loyalty +chkUseHideLoyalty.toolTipText=If enabled, loyalty modifiers will be hidden. unitCohesionPanel.title=Unit Cohesion chkUseAdministrativeStrain.text=Enable Administrative Strain diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index c6e4b3b4e4..377494155c 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -230,6 +230,7 @@ addPregnancy.text=Add Pregnancy addPregnancies.text=Add Pregnancies removePregnancy.text=Remove Pregnancy removePregnancies.text=Remove Pregnancies +regenerateLoyalty.text=Regenerate Loyalty #### ProcurementTableMouseAdapter Class miClearItems.text=Clear All Items diff --git a/MekHQ/resources/mekhq/resources/PersonViewPanel.properties b/MekHQ/resources/mekhq/resources/PersonViewPanel.properties index 2e59f81b96..082c8356c3 100644 --- a/MekHQ/resources/mekhq/resources/PersonViewPanel.properties +++ b/MekHQ/resources/mekhq/resources/PersonViewPanel.properties @@ -33,6 +33,7 @@ lblAdvancedMedical1.text=Injury Penalties:; lblInjuries.text=Injuries:; lblEdge1.text=Edge:; lblEdgeAvail1.text=Edge Available:; +lblLoyalty1.text=Loyalty:; lblSpouse1.text=Spouse:; lblFormerSpouses1.text=Former Spouse:; lblChildren1.text=Children:; diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties index 6d6ea37516..a6b0262c3c 100644 --- a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -5,6 +5,7 @@ unitRating.text=Unit Rating missionSuccess.text=Recent Successes missionFailure.text=Recent Failures missionBreach.text=Recent Contract Breach +loyalty.text=Loyalty factionPirateCompany.text=Pirate Company factionPirate.text=Pirate factionClan.text=Clan diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index a00c4c86a5..f353a2fbfb 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -274,6 +274,9 @@ public static String getTransitUnitName(final int unit) { private boolean useMissionStatusModifiers; private boolean trackUnitFatigue; + private boolean useLoyaltyModifiers; + private boolean useHideLoyalty; + private boolean useAdministrativeStrain; private Integer administrativeStrain; private Integer multiCrewStrainDivider; @@ -748,16 +751,19 @@ public CampaignOptions() { setTurnoverFixedTargetNumber(3); setUseYearEndRandomRetirement(true); setUseContractCompletionRandomRetirement(true); - setUseCustomRetirementModifiers(true); setUseRandomFounderRetirement(true); setUseSubContractSoldiers(false); - setTrackUnitFatigue(false); - setUseAgeModifiers(true); + setUseCustomRetirementModifiers(true); setUseSkillModifiers(true); + setUseAgeModifiers(true); setUseUnitRatingModifiers(true); setUseFactionModifiers(true); setUseMissionStatusModifiers(true); + setTrackUnitFatigue(false); + + setUseLoyaltyModifiers(true); + setUseHideLoyalty(true); setPayoutRateOfficer(3); setPayoutRateEnlisted(3); @@ -1688,6 +1694,22 @@ public void setUseCustomRetirementModifiers(final boolean useCustomRetirementMod this.useCustomRetirementModifiers = useCustomRetirementModifiers; } + public boolean isUseLoyaltyModifiers() { + return useLoyaltyModifiers; + } + + public void setUseLoyaltyModifiers(final boolean useLoyaltyModifiers) { + this.useLoyaltyModifiers = useLoyaltyModifiers; + } + + public boolean isUseHideLoyalty() { + return useHideLoyalty; + } + + public void setUseHideLoyalty(final boolean useHideLoyalty) { + this.useHideLoyalty = useHideLoyalty; + } + public boolean isUseRandomFounderRetirement() { return useRandomFounderRetirement; } @@ -4159,6 +4181,9 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMissionStatusModifiers", isUseMissionStatusModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLoyaltyModifiers", isUseLoyaltyModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useHideLoyalty", isUseHideLoyalty()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateOfficer", getPayoutRateOfficer()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRateEnlisted", getPayoutRateEnlisted()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutRetirementMultiplier", getPayoutRetirementMultiplier()); @@ -4827,6 +4852,10 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseMissionStatusModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useLoyaltyModifiers")) { + retVal.setUseLoyaltyModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useHideLoyalty")) { + retVal.setUseHideLoyalty(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateOfficer")) { retVal.setPayoutRateOfficer(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("payoutRateEnlisted")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index 353bba80ae..e53a8b8c9d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -119,11 +119,13 @@ public class Person { private LocalDate birthday; private LocalDate recruitment; private LocalDate lastRankChangeDate; - private LocalDate retirement; private LocalDate dateOfDeath; private List personnelLog; private List scenarioLog; + private LocalDate retirement; + private int loyalty; + private Skills skills; private PersonnelOptions options; private int toughness; @@ -326,6 +328,7 @@ public Person(final String preNominal, final String givenName, final String surn recruitment = null; lastRankChangeDate = null; retirement = null; + loyalty = 0; skills = new Skills(); options = new PersonnelOptions(); currentEdge = 0; @@ -1174,14 +1177,6 @@ public String getTimeInRank(final Campaign campaign) { .getDisplayFormattedOutput(getLastRankChangeDate(), today); } - public @Nullable LocalDate getRetirement() { - return retirement; - } - - public void setRetirement(final @Nullable LocalDate retirement) { - this.retirement = retirement; - } - public void setId(final UUID id) { this.id = id; } @@ -1198,6 +1193,24 @@ public Genealogy getGenealogy() { return genealogy; } + //region Turnover and Retention + public @Nullable LocalDate getRetirement() { + return retirement; + } + + public void setRetirement(final @Nullable LocalDate retirement) { + this.retirement = retirement; + } + + public Integer getLoyalty() { + return loyalty; + } + + public void setLoyalty(final Integer loyalty) { + this.loyalty = loyalty; + } + //region Turnover and Retention + //region Pregnancy public LocalDate getDueDate() { return dueDate; @@ -1599,6 +1612,7 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "recruitment", getRecruitment()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "lastRankChangeDate", getLastRankChangeDate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); } @@ -1904,6 +1918,8 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.lastRankChangeDate = MHQXMLUtility.parseDate(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("retirement")) { retVal.setRetirement(MHQXMLUtility.parseDate(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("loyalty")) { + retVal.setLoyalty(Integer.parseInt(wn2.getTextContent())); } else if (wn2.getNodeName().equalsIgnoreCase("advantages")) { advantages = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("edge")) { @@ -3596,4 +3612,27 @@ public void fixReferences(final Campaign campaign) { } } } + + /** + * Generates the loyalty value for a person. + */ + public void generateLoyalty() { + int roll = Compute.d6(3); + + if (roll == 3) { + setLoyalty(-3); + } else if (roll == 4) { + setLoyalty(-2); + } else if (roll < 7) { + setLoyalty(-1); + } else if (roll == 18) { + setLoyalty(3); + } else if (roll == 17) { + setLoyalty(2); + } else if (roll > 14) { + setLoyalty(1); + } else { + setLoyalty(0); + } + } } diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 25383ac917..798669cd76 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -131,7 +131,7 @@ public Map getTargetNumbers(final @Nullable AtBContract contra } } - TargetRoll targetNumber = new TargetRoll(getBaseTargetNumber(campaign), resources.getString("base.text")); + TargetRoll targetNumber = new TargetRoll(getBaseTargetNumber(campaign, person), resources.getString("base.text")); // Desirability modifier if (campaign.getCampaignOptions().isUseSkillModifiers()) { @@ -193,6 +193,13 @@ public Map getTargetNumbers(final @Nullable AtBContract contra } } + // Loyalty + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) + && (!campaign.getCampaignOptions().isUseHideLoyalty()) + && (person.getLoyalty() != 0)) { + targetNumber.addModifier(-person.getLoyalty(), resources.getString("loyalty.text")); + } + // Faction Modifiers if (campaign.getCampaignOptions().isUseFactionModifiers()) { if (campaign.getFaction().isPirate()) { @@ -269,10 +276,14 @@ private void setHrSkill(Campaign campaign) { .filter(person -> (person.getPrimaryRole().isAdministratorHR()) || (person.getSecondaryRole().isAdministratorHR())) .count(); - if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { - hrSkill = getCombinedSkillValues(campaign, SkillType.S_NEG) / hrPersonnelCount; - } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { - hrSkill = getCombinedSkillValues(campaign, SkillType.S_ADMIN) / hrPersonnelCount; + if (hrPersonnelCount != 0) { + if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { + hrSkill = getCombinedSkillValues(campaign, SkillType.S_NEG) / hrPersonnelCount; + } else if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isAdministration()) { + hrSkill = getCombinedSkillValues(campaign, SkillType.S_ADMIN) / hrPersonnelCount; + } + } else { + hrSkill = 0; } } @@ -611,23 +622,32 @@ private void getDifficultyModifier(Campaign campaign) { * @param campaign the campaign for which the base target number is calculated * @return the base target number */ - private int getBaseTargetNumber(Campaign campaign) { + private int getBaseTargetNumber(Campaign campaign, Person person) { if (!campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()) { int targetNumber; - Person person = new Person(campaign); + // we use 'shellPerson' as we have no way to ensure 'person' has the necessary skills, and we'll get an NPE if they don't + Person shellPerson = new Person(campaign); if (campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isNegotiation()) { - person.addSkill(SkillType.S_NEG, 1, 0); - targetNumber = person.getSkills().getSkill(SkillType.S_NEG).getType().getTarget(); + shellPerson.addSkill(SkillType.S_NEG, 1, 0); + targetNumber = shellPerson.getSkills().getSkill(SkillType.S_NEG).getType().getTarget(); } else { - person.addSkill(SkillType.S_ADMIN, 1, 0); - targetNumber = person.getSkills().getSkill(SkillType.S_ADMIN).getType().getTarget(); + shellPerson.addSkill(SkillType.S_ADMIN, 1, 0); + targetNumber = shellPerson.getSkills().getSkill(SkillType.S_ADMIN).getType().getTarget(); } - return targetNumber - hrSkill + difficulty; + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseHideLoyalty())) { + return targetNumber - hrSkill + difficulty - person.getLoyalty(); + } else { + return targetNumber - hrSkill + difficulty; + } } else { - return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseHideLoyalty())) { + return campaign.getCampaignOptions().getTurnoverFixedTargetNumber() - person.getLoyalty(); + } else { + return campaign.getCampaignOptions().getTurnoverFixedTargetNumber(); + } } } diff --git a/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java b/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java index 71376a4ee4..056542dd93 100644 --- a/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java +++ b/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java @@ -80,6 +80,8 @@ public Person generate(Campaign campaign, PersonnelRole primaryRole, PersonnelRo generateBirthday(campaign, person, expLvl, person.isClanPersonnel() && !person.getPhenotype().isNone()); + person.generateLoyalty(); + AbstractSkillGenerator skillGenerator = new DefaultSkillGenerator(getSkillPreferences()); skillGenerator.generateSkills(campaign, person, expLvl); diff --git a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java index 2127ec9ead..4c10a87bb1 100644 --- a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java @@ -41,6 +41,7 @@ import mekhq.campaign.personnel.education.AcademyFactory; import mekhq.campaign.personnel.education.EducationController; import mekhq.campaign.personnel.enums.*; +import mekhq.campaign.personnel.generator.AbstractPersonnelGenerator; import mekhq.campaign.personnel.generator.SingleSpecialAbilityGenerator; import mekhq.campaign.personnel.ranks.Rank; import mekhq.campaign.personnel.ranks.RankSystem; @@ -124,6 +125,7 @@ public class PersonnelTableMouseAdapter extends JPopupMenuAdapter { private static final String CMD_REMOVE_SPOUSE = "REMOVE_SPOUSE"; private static final String CMD_ADD_PREGNANCY = "ADD_PREGNANCY"; private static final String CMD_REMOVE_PREGNANCY = "PREGNANCY_SPOUSE"; + private static final String CMD_LOYALTY = "LOYALTY"; private static final String CMD_IMPRISON = "IMPRISON"; private static final String CMD_FREE = "FREE"; @@ -948,6 +950,13 @@ public void actionPerformed(ActionEvent action) { break; } + case CMD_LOYALTY: { + for (Person person : people) { + person.generateLoyalty(); + MekHQ.triggerEvent(new PersonChangedEvent(person)); + } + break; + } //region Randomization Menu case CMD_RANDOM_NAME: { @@ -2453,6 +2462,13 @@ protected Optional createPopupMenu() { } } + if (gui.getCampaign().getCampaignOptions().isUseLoyaltyModifiers()) { + menuItem = new JMenuItem(resources.getString("regenerateLoyalty.text")); + menuItem.setActionCommand(CMD_LOYALTY); + menuItem.addActionListener(this); + menu.add(menuItem); + } + JMenuHelpers.addMenuIfNonEmpty(popup, menu); } //endregion GM Menu diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index a250ffee4b..9eceaf60c4 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -255,6 +255,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { // Retirement private JCheckBox chkUseRetirementDateTracking; + private JPanel randomRetirementPanel; private JCheckBox chkUseRandomRetirement; private JLabel lblTurnoverTargetNumberMethod; @@ -277,6 +278,10 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseMissionStatusModifiers; private JCheckBox chkTrackUnitFatigue; + private JPanel loyaltyPanel; + private JCheckBox chkUseLoyaltyModifiers; + private JCheckBox chkUseHideLoyalty; + private JPanel turnoverPayoutPanel; private JLabel lblPayoutRateOfficer; private JSpinner spnPayoutRateOfficer; @@ -3865,6 +3870,23 @@ private JPanel createTurnoverModifiersPanel() { chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); + chkUseLoyaltyModifiers = new JCheckBox(resources.getString("chkUseLoyaltyModifiers.text")); + chkUseLoyaltyModifiers.setToolTipText(resources.getString("chkUseLoyaltyModifiers.toolTipText")); + chkUseLoyaltyModifiers.setName("chkUseLoyaltyModifiers"); + chkUseLoyaltyModifiers.addActionListener(evt -> { + final boolean isEnabled = randomRetirementPanel.isEnabled() && chkUseLoyaltyModifiers.isSelected(); + + // general handler + for (Component component : loyaltyPanel.getComponents()) { + component.setEnabled(isEnabled); + } + + // border handler + loyaltyPanel.setEnabled(isEnabled); + }); + + loyaltyPanel = createLoyaltyPanel(); + turnoverModifiersPanel = new JDisableablePanel("turnoverModifierPanel"); turnoverModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverModifierPanel.title"))); @@ -3882,6 +3904,8 @@ private JPanel createTurnoverModifiersPanel() { .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) .addComponent(chkTrackUnitFatigue) + .addComponent(chkUseLoyaltyModifiers) + .addComponent(loyaltyPanel) ); layout.setHorizontalGroup( @@ -3893,11 +3917,39 @@ private JPanel createTurnoverModifiersPanel() { .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) .addComponent(chkTrackUnitFatigue) + .addComponent(chkUseLoyaltyModifiers) + .addComponent(loyaltyPanel) ); return turnoverModifiersPanel; } + private JPanel createLoyaltyPanel() { + chkUseHideLoyalty = new JCheckBox(resources.getString("chkUseHideLoyalty.text")); + chkUseHideLoyalty.setToolTipText(resources.getString("chkUseHideLoyalty.toolTipText")); + chkUseHideLoyalty.setName("chkUseHideLoyalty"); + + loyaltyPanel = new JDisableablePanel("loyaltyPanel"); + loyaltyPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("loyaltyPanel.title"))); + + final GroupLayout layout = new GroupLayout(loyaltyPanel); + loyaltyPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseHideLoyalty) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseHideLoyalty) + ); + + return loyaltyPanel; + } + private JPanel createTurnoverPayoutPanel() { lblPayoutRateOfficer = new JLabel(resources.getString("lblPayoutRateOfficer.text")); lblPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); @@ -7202,16 +7254,19 @@ public void setOptions(@Nullable CampaignOptions options, spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); chkUseYearEndRandomRetirement.setSelected(options.isUseYearEndRandomRetirement()); chkUseContractCompletionRandomRetirement.setSelected(options.isUseContractCompletionRandomRetirement()); - chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); chkUseRandomFounderRetirement.setSelected(options.isUseRandomFounderRetirement()); chkUseSubContractSoldiers.setSelected(options.isUseSubContractSoldiers()); - chkTrackUnitFatigue.setSelected(options.isTrackUnitFatigue()); + chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); chkUseSkillModifiers.setSelected(options.isUseSkillModifiers()); chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); chkUseMissionStatusModifiers.setSelected(options.isUseMissionStatusModifiers()); + chkTrackUnitFatigue.setSelected(options.isTrackUnitFatigue()); + + chkUseLoyaltyModifiers.setSelected(options.isUseLoyaltyModifiers()); + chkUseHideLoyalty.setSelected(options.isUseHideLoyalty()); spnPayoutRateOfficer.setValue(options.getPayoutRateOfficer()); spnPayoutRateEnlisted.setValue(options.getPayoutRateEnlisted()); @@ -7831,16 +7886,19 @@ public void updateOptions() { options.setTurnoverFixedTargetNumber((Integer) spnTurnoverFixedTargetNumber.getValue()); options.setUseYearEndRandomRetirement(chkUseYearEndRandomRetirement.isSelected()); options.setUseContractCompletionRandomRetirement(chkUseContractCompletionRandomRetirement.isSelected()); - options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); options.setUseRandomFounderRetirement(chkUseRandomFounderRetirement.isSelected()); options.setUseSubContractSoldiers(chkUseSubContractSoldiers.isSelected()); - options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); + options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); options.setUseSkillModifiers(chkUseSkillModifiers.isSelected()); options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); options.setUseMissionStatusModifiers(chkUseMissionStatusModifiers.isSelected()); + options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); + + options.setUseLoyaltyModifiers(chkUseLoyaltyModifiers.isSelected()); + options.setUseHideLoyalty(chkUseHideLoyalty.isSelected()); options.setPayoutRateOfficer((Integer) spnPayoutRateOfficer.getValue()); options.setPayoutRateEnlisted((Integer) spnPayoutRateEnlisted.getValue()); diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index af4fd9ba7a..93f0edc752 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -1204,6 +1204,9 @@ private JPanel fillSkills() { JLabel lblEdgeAvail1 = new JLabel(); JLabel lblEdgeAvail2 = new JLabel(); + JLabel lblLoyalty1 = new JLabel(); + JLabel lblLoyalty2 = new JLabel(); + // education JLabel lblEducationLevel1 = new JLabel(); JLabel lblEducationLevel2 = new JLabel(); @@ -1389,6 +1392,34 @@ private JPanel fillSkills() { firsty++; } + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) + && (!campaign.getCampaignOptions().isUseHideLoyalty()) + && (person.getLoyalty() != 0)) { + lblLoyalty1.setName("lblLoyalty1"); + lblLoyalty1.setText(resourceMap.getString("lblLoyalty1.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = firsty; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + pnlSkills.add(lblLoyalty1, gridBagConstraints); + + lblLoyalty2.setName("lblLoyalty2"); + lblLoyalty2.setText(String.valueOf(person.getLoyalty())); + lblLoyalty2.setLabelFor(lblLoyalty2); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = firsty; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new Insets(0, 10, 0, 0); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + pnlSkills.add(lblLoyalty2, gridBagConstraints); + + firsty++; + } + if (campaign.getCampaignOptions().isUseEducationModule()) { lblEducationLevel1.setName("lblEducationLevel1"); lblEducationLevel1.setText(resourceMap.getString("lblEducationLevel1.text")); From 19db7ec4124a9c29ab8bb55a63e86a4557352d6b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 18 May 2024 21:30:45 -0500 Subject: [PATCH 026/101] There is always one missed important, isn't there? --- MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java index 4c10a87bb1..91b37027ae 100644 --- a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java @@ -41,7 +41,6 @@ import mekhq.campaign.personnel.education.AcademyFactory; import mekhq.campaign.personnel.education.EducationController; import mekhq.campaign.personnel.enums.*; -import mekhq.campaign.personnel.generator.AbstractPersonnelGenerator; import mekhq.campaign.personnel.generator.SingleSpecialAbilityGenerator; import mekhq.campaign.personnel.ranks.Rank; import mekhq.campaign.personnel.ranks.RankSystem; From e3f423ea5c11729b7744f639c12f9f57ddd5643e Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 18 May 2024 22:54:34 -0500 Subject: [PATCH 027/101] Added service contract options to campaign settings This update introduces new service contract options to the campaign settings. Now the minimum service contract duration and a contract modifier can be configured. The modifier adjusts the turnover target number while personnel are under contract. --- .../CampaignOptionsDialog.properties | 4 +++ .../RetirementDefectionTracker.properties | 1 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 26 ++++++++++++++ .../personnel/RetirementDefectionTracker.java | 18 +++++++--- .../mekhq/gui/panes/CampaignOptionsPane.java | 36 +++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index da02ff1cf2..b30f886de7 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -259,6 +259,10 @@ chkUseRandomFounderRetirement.text=Use Random Founder Turnover chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly leave the unit. chkUseSubContractSoldiers.text=Soldiers Use Commander Turnover Roll chkUseSubContractSoldiers.toolTipText=Commanders make Turnover rolls for all soldiers in their unit +lblServiceContractDuration.text=Service Contract Duration +lblServiceContractDuration.toolTipText=Once recruited, this is the minimum amount of months personnel will remain with the unit (set to 0 to disable service contracts). +lblServiceContractModifier.text=Service Contract Modifier +lblServiceContractModifier.toolTipText=While personnel are under contract, their Turnover target number is reduced by this value. turnoverModifierPanel.title=Modifiers chkUseCustomRetirementModifiers.text=Custom diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties index a6b0262c3c..464a14696f 100644 --- a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -1,5 +1,6 @@ #### Target Number Labels base.text=Base +contract.text=Under Contract desirability.text=Desirability unitRating.text=Unit Rating missionSuccess.text=Recent Successes diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index f353a2fbfb..659f3a31a6 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -259,6 +259,8 @@ public static String getTransitUnitName(final int unit) { private boolean useContractCompletionRandomRetirement; private boolean useRandomFounderRetirement; private boolean useSubContractSoldiers; + private Integer serviceContractDuration; + private Integer serviceContractModifier; private Integer payoutRateOfficer; private Integer payoutRateEnlisted; @@ -753,6 +755,8 @@ public CampaignOptions() { setUseContractCompletionRandomRetirement(true); setUseRandomFounderRetirement(true); setUseSubContractSoldiers(false); + setServiceContractDuration(36); + setServiceContractModifier(8); setUseCustomRetirementModifiers(true); setUseSkillModifiers(true); @@ -1869,6 +1873,22 @@ public Integer getManagementSkillPenalty() { public void setManagementSkillPenalty(final Integer managementSkillPenalty) { this.managementSkillPenalty = managementSkillPenalty; } + + public Integer getServiceContractDuration() { + return serviceContractDuration; + } + + public void setServiceContractDuration(final Integer serviceContractDuration) { + this.serviceContractDuration = serviceContractDuration; + } + + public Integer getServiceContractModifier() { + return serviceContractModifier; + } + + public void setServiceContractModifier(final Integer serviceContractModifier) { + this.serviceContractModifier = serviceContractModifier; + } //endregion Retirement //region Family @@ -4172,6 +4192,8 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useContractCompletionRandomRetirement", isUseContractCompletionRandomRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRandomFounderRetirement", isUseRandomFounderRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSubContractSoldiers", isUseSubContractSoldiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "serviceContractDuration", getServiceContractDuration()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "serviceContractModifier", getServiceContractModifier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSkillModifiers", isUseSkillModifiers()); @@ -4838,6 +4860,10 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseRandomFounderRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSubContractSoldiers")) { retVal.setUseSubContractSoldiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("serviceContractDuration")) { + retVal.setServiceContractDuration(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("serviceContractModifier")) { + retVal.setServiceContractModifier(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useCustomRetirementModifiers")) { retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSkillModifiers")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 798669cd76..24fc2fa7c9 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -41,6 +41,7 @@ import java.io.PrintWriter; import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; @@ -133,6 +134,11 @@ public Map getTargetNumbers(final @Nullable AtBContract contra TargetRoll targetNumber = new TargetRoll(getBaseTargetNumber(campaign, person), resources.getString("base.text")); + // Service Contract + if (ChronoUnit.MONTHS.between(person.getRecruitment(), campaign.getLocalDate()) < campaign.getCampaignOptions().getServiceContractDuration()) { + targetNumber.addModifier(campaign.getCampaignOptions().getServiceContractModifier(), resources.getString("contract.text")); + } + // Desirability modifier if (campaign.getCampaignOptions().isUseSkillModifiers()) { targetNumber.addModifier(person.getExperienceLevel(campaign, false), resources.getString("desirability.text")); @@ -571,11 +577,15 @@ public static int getCombinedSkillValues(Campaign campaign, String skillType) { for (Person person : campaign.getActivePersonnel()) { if ((!person.getPrisonerStatus().isPrisoner()) || (!person.getPrisonerStatus().isPrisonerDefector())) { if (person.getPrimaryRole().isAdministratorHR()) { - combinedSkillValues += person.getSkill(skillType).getLevel(); - combinedSkillValues += person.getSkill(skillType).getBonus(); + if (person.hasSkill(skillType)) { + combinedSkillValues += person.getSkill(skillType).getLevel(); + combinedSkillValues += person.getSkill(skillType).getBonus(); + } } else if (person.getSecondaryRole().isAdministratorHR()) { - combinedSkillValues += person.getSkill(skillType).getLevel(); - combinedSkillValues += person.getSkill(skillType).getBonus(); + if (person.hasSkill(skillType)) { + combinedSkillValues += person.getSkill(skillType).getLevel(); + combinedSkillValues += person.getSkill(skillType).getBonus(); + } } } } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 9eceaf60c4..eeaa11c232 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -268,6 +268,10 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseContractCompletionRandomRetirement; private JCheckBox chkUseRandomFounderRetirement; private JCheckBox chkUseSubContractSoldiers; + private JLabel lblServiceContractDuration; + private JSpinner spnServiceContractDuration; + private JLabel lblServiceContractModifier; + private JSpinner spnServiceContractModifier; private JPanel turnoverModifiersPanel; private JCheckBox chkUseCustomRetirementModifiers; @@ -3788,6 +3792,22 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseSubContractSoldiers.setToolTipText(resources.getString("chkUseSubContractSoldiers.toolTipText")); chkUseSubContractSoldiers.setName("chkUseSubContractSoldiers"); + lblServiceContractDuration = new JLabel(resources.getString("lblServiceContractDuration.text")); + lblServiceContractDuration.setToolTipText(resources.getString("lblServiceContractDuration.toolTipText")); + lblServiceContractDuration.setName("lblServiceContractDuration"); + + spnServiceContractDuration = new JSpinner(new SpinnerNumberModel(36, 0, 120, 1)); + spnServiceContractDuration.setToolTipText(resources.getString("lblServiceContractDuration.toolTipText")); + spnServiceContractDuration.setName("spnServiceContractDuration"); + + lblServiceContractModifier = new JLabel(resources.getString("lblServiceContractModifier.text")); + lblServiceContractModifier.setToolTipText(resources.getString("lblServiceContractModifier.toolTipText")); + lblServiceContractModifier.setName("lblServiceContractModifier"); + + spnServiceContractModifier = new JSpinner(new SpinnerNumberModel(36, 0, 120, 1)); + spnServiceContractModifier.setToolTipText(resources.getString("lblServiceContractModifier.toolTipText")); + spnServiceContractModifier.setName("spnServiceContractModifier"); + turnoverModifiersPanel = createTurnoverModifiersPanel(); turnoverPayoutPanel = createTurnoverPayoutPanel(); @@ -3815,6 +3835,12 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) .addComponent(chkUseSubContractSoldiers) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblServiceContractDuration) + .addComponent(spnServiceContractDuration, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblServiceContractModifier) + .addComponent(spnServiceContractModifier, Alignment.LEADING)) .addComponent(turnoverModifiersPanel) .addComponent(turnoverPayoutPanel) ); @@ -3834,6 +3860,12 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseContractCompletionRandomRetirement) .addComponent(chkUseRandomFounderRetirement) .addComponent(chkUseSubContractSoldiers) + .addGroup(layout.createSequentialGroup() + .addComponent(lblServiceContractDuration) + .addComponent(spnServiceContractDuration)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblServiceContractModifier) + .addComponent(spnServiceContractModifier)) .addComponent(turnoverModifiersPanel) .addComponent(turnoverPayoutPanel) ); @@ -7256,6 +7288,8 @@ public void setOptions(@Nullable CampaignOptions options, chkUseContractCompletionRandomRetirement.setSelected(options.isUseContractCompletionRandomRetirement()); chkUseRandomFounderRetirement.setSelected(options.isUseRandomFounderRetirement()); chkUseSubContractSoldiers.setSelected(options.isUseSubContractSoldiers()); + spnServiceContractDuration.setValue(options.getServiceContractDuration()); + spnServiceContractModifier.setValue(options.getServiceContractModifier()); chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); @@ -7888,6 +7922,8 @@ public void updateOptions() { options.setUseContractCompletionRandomRetirement(chkUseContractCompletionRandomRetirement.isSelected()); options.setUseRandomFounderRetirement(chkUseRandomFounderRetirement.isSelected()); options.setUseSubContractSoldiers(chkUseSubContractSoldiers.isSelected()); + options.setServiceContractDuration((Integer) spnServiceContractDuration.getValue()); + options.setServiceContractModifier((Integer) spnServiceContractModifier.getValue()); options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); From 14c50277e19f168e2de0c78ed7938074b918d3b9 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 19 May 2024 20:04:24 -0500 Subject: [PATCH 028/101] Implemented a Combat Fatigue system Added a Combat Fatigue system that affects personnel based on how often they have been deployed without a break. The fatigue can be impacted by various in-game facilities and personnel characteristics. This is designed to provide a more realistic experience of managing a military campaign. Additionally, removed the former fatigue tracking system. The Combat Fatigue system also includes related GUI updates. --- .../mekhq/resources/Campaign.properties | 6 +- .../mekhq/resources/CampaignGUI.properties | 2 +- .../CampaignOptionsDialog.properties | 13 +- .../resources/PersonViewPanel.properties | 1 + MekHQ/src/mekhq/campaign/Campaign.java | 165 ++++++++++++++---- MekHQ/src/mekhq/campaign/CampaignOptions.java | 76 ++++++-- MekHQ/src/mekhq/campaign/CampaignSummary.java | 34 +++- .../campaign/ResolveScenarioTracker.java | 11 ++ .../mekhq/campaign/io/CampaignXmlParser.java | 2 - .../mission/enums/AtBContractType.java | 22 --- .../src/mekhq/campaign/personnel/Person.java | 72 +++++++- .../personnel/RetirementDefectionTracker.java | 5 - .../stratcon/StratconRulesManager.java | 26 +++ MekHQ/src/mekhq/gui/CommandCenterTab.java | 48 ++--- .../mekhq/gui/panes/CampaignOptionsPane.java | 127 ++++++++++++-- MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 40 +++++ 16 files changed, 528 insertions(+), 122 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 9176293f15..903ba9235a 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -38,7 +38,11 @@ LayeredForceIconLayer.FRAME.toolTipText=This tab contains frames, which are the LayeredForceIconLayer.LOGO.text=Logos LayeredForceIconLayer.LOGO.toolTipText=This tab contains canon faction logos that can be added to the center of a force icon. - +#### Combat Fatigue Resources +combatFatigueVeryTired.text=is tired, their skills have been reduced by 1. +combatFatigueFatigued.text=is badly fatigued, their skills have been reduced by 2. +combatFatigueExhausted.text=is exhausted, their skills have been reduced by 3. +combatFatigueCritical.text=is critically fatigued, their skills have been reduced by %s. #### Unsorted Campaign Resources dependentLeavesForce.text=%s is no longer travelling with the force, and is thus no longer dependent on it. diff --git a/MekHQ/resources/mekhq/resources/CampaignGUI.properties b/MekHQ/resources/mekhq/resources/CampaignGUI.properties index c0957a48e5..5471a9d23a 100644 --- a/MekHQ/resources/mekhq/resources/CampaignGUI.properties +++ b/MekHQ/resources/mekhq/resources/CampaignGUI.properties @@ -212,7 +212,6 @@ panReports.title=Available Reports panInfo.title=Basic Unit Information panObjectives.title=Current Objectives lblRating.text=Unit Rating:; -lblFatigue.text=Unit Fatigue:; lblPersonnel.text=Personnel:; lblAdministrativeCapacity.text=Adminstrative Capacity:; lblMissionSuccess.text=Mission Success Rate:; @@ -221,6 +220,7 @@ lblComposition.text=Unit Composition:; lblRepairStatus.text=Unit Damage Status:; lblTransportCapacity.text=Transport Capacity:; 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. diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index b30f886de7..9011fed334 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -158,6 +158,17 @@ chkDisplayScenarioLog.toolTipText=If enabled, the scenario log will be expanded chkDisplayKillRecord.text=Expand Kill Record by Default chkDisplayKillRecord.toolTipText=If enabled, the kill record will be expanded by default +combatFatiguePanel.title=Combat Fatigue +lblCombatFatigueWarning.text=Client must be reloaded whenever enabling or disabling Combat Fatigue. +chkUseCombatFatigue.text=Enable Combat Fatigue +chkUseCombatFatigue.toolTipText=If enabled, combat personnel will gain Combat Fatigue whenever they are deployed to a Scenario +lblCombatFatigueThreshold.text=Threshold Value +lblCombatFatigueThreshold.toolTipText=The combat fatigue modifier is calculated by dividing Combat Fatigue by this number. +lblFieldKitchenCapacity.text=Field Kitchen Capacity +lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen equipped Unit? Reduces effective Combat Fatigue by 1. +lblMashCapacity.text=MASH Capacity +lblMashCapacity.toolTipText=How many personnel can be treated per MASH equipped Unit? This simulates minor day-to-day injuries not accounted for by the Hits system. Reduces effective Combat Fatigue by 1. + # Expanded Personnel Information expandedPersonnelInformationPanel.title=Expanded Personnel Information chkUseTimeInService.text=Track Time In Service @@ -277,8 +288,6 @@ chkUseFactionModifiers.text=Faction chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. chkUseMissionStatusModifiers.text=Mission Status chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. -chkTrackUnitFatigue.text=Unit Fatigue -chkTrackUnitFatigue.toolTipText=Continuous deployments without a break apply a penalty to Turnover checks. loyaltyPanel.title=Loyalty chkUseLoyaltyModifiers.text=Use Loyalty diff --git a/MekHQ/resources/mekhq/resources/PersonViewPanel.properties b/MekHQ/resources/mekhq/resources/PersonViewPanel.properties index 082c8356c3..87cf9c7259 100644 --- a/MekHQ/resources/mekhq/resources/PersonViewPanel.properties +++ b/MekHQ/resources/mekhq/resources/PersonViewPanel.properties @@ -34,6 +34,7 @@ lblInjuries.text=Injuries:; lblEdge1.text=Edge:; lblEdgeAvail1.text=Edge Available:; lblLoyalty1.text=Loyalty:; +lblCombatFatigue1.text=Combat Fatigue:; lblSpouse1.text=Spouse:; lblFormerSpouses1.text=Former Spouse:; lblChildren1.text=Children:; diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 9ded4ddbe6..4fcb36a0ab 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -31,6 +31,7 @@ import megamek.common.annotations.Nullable; import megamek.common.enums.Gender; import megamek.common.equipment.BombMounted; +import megamek.common.equipment.MiscMounted; import megamek.common.icons.Camouflage; import megamek.common.icons.Portrait; import megamek.common.loaders.BLKFile; @@ -185,6 +186,8 @@ public class Campaign implements ITechManager { private transient String currentReportHTML; private transient List newReports; + private ArrayList fieldFacilityCapacities; + // this is updated and used per gaming session, it is enabled/disabled via the Campaign options // we're re-using the LogEntry class that is used to store Personnel entries public LinkedList inMemoryLogHistory = new LinkedList<>(); @@ -223,7 +226,6 @@ public class Campaign implements ITechManager { private transient AbstractProcreation procreation; private RetirementDefectionTracker retirementDefectionTracker; // AtB - private int fatigueLevel; //AtB private AtBConfiguration atbConfig; //AtB private AtBEventProcessor atbEventProcessor; //AtB private LocalDate shipSearchStart; //AtB @@ -285,12 +287,12 @@ public Campaign() { setMarriage(new DisabledRandomMarriage(getCampaignOptions())); setProcreation(new DisabledRandomProcreation(getCampaignOptions())); retirementDefectionTracker = new RetirementDefectionTracker(); - fatigueLevel = 0; atbConfig = null; autosaveService = new AutosaveService(); hasActiveContract = false; campaignSummary = new CampaignSummary(this); quartermaster = new Quartermaster(this); + fieldFacilityCapacities = new ArrayList<>(Arrays.asList(false, false)); } /** @@ -479,14 +481,6 @@ public RetirementDefectionTracker getRetirementDefectionTracker() { return retirementDefectionTracker; } - public void setFatigueLevel(int fl) { - fatigueLevel = fl; - } - - public int getFatigueLevel() { - return fatigueLevel; - } - /** * Initializes the unit generator based on the method chosen in campaignOptions. * Called when the unit generator is first used or when the method has been @@ -1346,6 +1340,14 @@ public Person newPerson(final PersonnelRole primaryRole, final PersonnelRole sec return person; } + + public ArrayList getFieldFacilityCapacities() { + return fieldFacilityCapacities; + } + + public void setFieldFacilityCapacities(final ArrayList fieldFacilityCapacities) { + this.fieldFacilityCapacities = fieldFacilityCapacities; + } //endregion Person Creation //region Personnel Recruitment @@ -3158,19 +3160,6 @@ && getLocation().getJumpPath().getLastSystem().getId().equals(contract.getSystem } } - private void processNewDayATBFatigue() { - boolean inContract = false; - for (final AtBContract contract : getActiveAtBContracts()) { - fatigueLevel += contract.getContractType().getFatigue(); - inContract = true; - } - - if (!inContract && location.isOnPlanet()) { - fatigueLevel -= 2; - } - fatigueLevel = Math.max(fatigueLevel, 0); - } - private void processNewDayATB() { contractMarket.generateContractOffers(this); // TODO : AbstractContractMarket : Remove @@ -3234,7 +3223,7 @@ && getCampaignOptions().getRandomDependentMethod().isAgainstTheBot() if (getLocalDate().getDayOfMonth() == 1) { /* - * First of the month; roll morale, track unit fatigue. + * First of the month; roll morale. */ IUnitRating rating = getUnitRating(); rating.reInitialize(); @@ -3244,11 +3233,6 @@ && getCampaignOptions().getRandomDependentMethod().isAgainstTheBot() addReport("Enemy morale is now " + contract.getMoraleLevel() + " on contract " + contract.getName()); } - - // Account for fatigue - if (getCampaignOptions().isTrackUnitFatigue()) { - processNewDayATBFatigue(); - } } processNewDayATBScenarios(); @@ -3532,6 +3516,25 @@ public boolean newDay() { // check for anything in finances getFinances().newDay(this, yesterday, getLocalDate()); + // Combat Fatigue Region + if (getLocalDate().getDayOfMonth() == 1) { + // even if Combat Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state + processCombatFatigueRecovery(); + } + + if (campaignOptions.isUseCombatFatigue()) { + // we store these values, so this only needs to be checked once per day, + // otherwise we would need to check it once for each active person in the campaign + fieldFacilityCapacities = new ArrayList<>(checkFieldFacilityCapacities()); + } else { + fieldFacilityCapacities = new ArrayList<>(Arrays.asList(false, false)); + } + + // if Combat Fatigue is disabled, we reset everyone's fatigue modifier to 0, + // this means we don't have to check to see whether Combat Fatigue every time we make a roll affected by Combat Fatigue + setCombatFatigueModifiersForActivePersonnel(); + // End Combat Fatigue Region + MekHQ.triggerEvent(new NewDayEvent(this)); return true; } @@ -4183,7 +4186,6 @@ public void writeToXML(final PrintWriter pw) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "lastMissionId", lastMissionId); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "lastScenarioId", lastScenarioId); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "calendar", getLocalDate()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLevel", fatigueLevel); MHQXMLUtility.writeSimpleXMLOpenTag(pw, indent++, "nameGen"); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "faction", RandomNameGenerator.getInstance().getChosenFaction()); @@ -6981,4 +6983,107 @@ public boolean useVariableTechLevel() { public boolean showExtinct() { return !campaignOptions.isDisallowExtinctStuff(); } + + /** + * This method counts the number of field kitchens and Mash units available and compares them to the number of active personnel. + * + * @return A list of booleans indicating the status of the field facilities' capacities. + * True if the capacity of each field facility is not exceeded by the number of active personnel, + * false otherwise. + */ + public List checkFieldFacilityCapacities() { + List facilityCapacities = calculateFacilityCapacities(); + + return Arrays.asList(getActivePersonnel().size() <= facilityCapacities.get(0), + getActivePersonnel().size() <= facilityCapacities.get(1)); + } + + /** + * Calculates the facility capacities based on the units present. + * + * @return A list containing the calculated capacities for field kitchens + * and mash facilities. + */ + public List calculateFacilityCapacities() { + int fieldKitchenCount = 0; + int mashCount = 0; + + Collection allUnits = getUnits(); + + if (!allUnits.isEmpty()) { + for (Unit unit : getUnits()) { + if ((unit.isDeployed()) + || (unit.isDamaged()) + || (unit.getCrewState().isUncrewed()) + || (unit.getCrewState().isPartiallyCrewed()) + || (unit.isUnmaintained())) { + continue; + } + + List miscItems = unit.getEntity().getMisc(); + + if (!miscItems.isEmpty()) { + for (MiscMounted item : unit.getEntity().getMisc()) { + if (item.getType().hasFlag(MiscType.F_FIELD_KITCHEN)) { + fieldKitchenCount++; + } else if (item.getType().hasFlag(MiscType.F_MASH)) { + mashCount++; + } + } + } + } + } + + return Arrays.asList(fieldKitchenCount * campaignOptions.getFieldKitchenCapacity(), mashCount * campaignOptions.getMashCapacity()); + } + + + /** + * Sets the combat fatigue modifiers for all active personnel. + * If Combat Fatigue is disabled, all Fatigue Modifiers are set to 0. + */ + public void setCombatFatigueModifiersForActivePersonnel() { + for (Person person : getActivePersonnel()) { + if (campaignOptions.isUseCombatFatigue()) { + person.calculateCombatFatigueModifier(this); + } else { + person.setCombatFatigueModifier(0); + } + } + } + + /** + * Reports the combat fatigue of a person. + * + * @param person the person for which the combat fatigue needs to be reported + */ + public void reportCombatFatigue(Person person) { + switch (person.getCombatFatigue()) { + case 0: + break; + case 1: + addReport(person.getHyperlinkedFullTitle() + ' ' + + resources.getString("combatFatigueVeryTired.text")); + case 2: + addReport(person.getHyperlinkedFullTitle() + ' ' + + resources.getString("combatFatigueFatigued.text")); + case 3: + addReport(person.getHyperlinkedFullTitle() + ' ' + + resources.getString("combatFatigueExhausted.text")); + default: + addReport(person.getHyperlinkedFullTitle() + ' ' + + String.format(resources.getString("combatFatigueCritical.text"), person.getCombatFatigueModifier())); + } + } + + /** + * Decreases the combat fatigue of all active personnel by 1. + */ + public void processCombatFatigueRecovery() { + for (Person person : getActivePersonnel()) { + if (person.getCombatFatigue() > 0) { + person.setCombatFatigue(person.getCombatFatigue() - 1); + } + } + } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 659f3a31a6..214d0ce090 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -207,6 +207,10 @@ public static String getTransitUnitName(final int unit) { private boolean displayPersonnelLog; private boolean displayScenarioLog; private boolean displayKillRecord; + private boolean useCombatFatigue; + private Integer combatFatigueThreshold; + private Integer fieldKitchenCapacity; + private Integer mashCapacity; // Expanded Personnel Information private boolean useTimeInService; @@ -262,20 +266,18 @@ public static String getTransitUnitName(final int unit) { private Integer serviceContractDuration; private Integer serviceContractModifier; - private Integer payoutRateOfficer; - private Integer payoutRateEnlisted; - private Integer payoutRetirementMultiplier; - private boolean usePayoutServiceBonus; - private Integer payoutServiceBonusRate; - private boolean useCustomRetirementModifiers; private boolean useSkillModifiers; private boolean useAgeModifiers; private boolean useUnitRatingModifiers; private boolean useFactionModifiers; private boolean useMissionStatusModifiers; - private boolean trackUnitFatigue; + private Integer payoutRateOfficer; + private Integer payoutRateEnlisted; + private Integer payoutRetirementMultiplier; + private boolean usePayoutServiceBonus; + private Integer payoutServiceBonusRate; private boolean useLoyaltyModifiers; private boolean useHideLoyalty; @@ -666,6 +668,10 @@ public CampaignOptions() { setDisplayPersonnelLog(false); setDisplayScenarioLog(false); setDisplayKillRecord(false); + setUseCombatFatigue(true); + setCombatFatigueThreshold(5); + setFieldKitchenCapacity(150); + setMashCapacity(250); // Expanded Personnel Information setUseTimeInService(false); @@ -764,7 +770,6 @@ public CampaignOptions() { setUseUnitRatingModifiers(true); setUseFactionModifiers(true); setUseMissionStatusModifiers(true); - setTrackUnitFatigue(false); setUseLoyaltyModifiers(true); setUseHideLoyalty(true); @@ -1420,6 +1425,38 @@ public boolean isDisplayKillRecord() { public void setDisplayKillRecord(final boolean displayKillRecord) { this.displayKillRecord = displayKillRecord; } + + public boolean isUseCombatFatigue() { + return useCombatFatigue; + } + + public void setUseCombatFatigue(final boolean useCombatFatigue) { + this.useCombatFatigue = useCombatFatigue; + } + + public Integer getCombatFatigueThreshold() { + return combatFatigueThreshold; + } + + public void setCombatFatigueThreshold(final Integer combatFatigueThreshold) { + this.combatFatigueThreshold = combatFatigueThreshold; + } + + public Integer getFieldKitchenCapacity() { + return fieldKitchenCapacity; + } + + public void setFieldKitchenCapacity(final Integer fieldKitchenCapacity) { + this.fieldKitchenCapacity = fieldKitchenCapacity; + } + + public Integer getMashCapacity() { + return mashCapacity; + } + + public void setMashCapacity(final Integer mashCapacity) { + this.mashCapacity = mashCapacity; + } //endregion General Personnel //region Expanded Personnel Information @@ -1730,14 +1767,6 @@ public void setUseSubContractSoldiers(final boolean useSubContractSoldiers) { this.useSubContractSoldiers = useSubContractSoldiers; } - public boolean isTrackUnitFatigue() { - return trackUnitFatigue; - } - - public void setTrackUnitFatigue(final boolean trackUnitFatigue) { - this.trackUnitFatigue = trackUnitFatigue; - } - public Integer getTurnoverFixedTargetNumber() { return turnoverFixedTargetNumber; } @@ -4129,6 +4158,10 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayPersonnelLog", isDisplayPersonnelLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayScenarioLog", isDisplayScenarioLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayKillRecord", isDisplayKillRecord()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCombatFatigue", isUseCombatFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigueThreshold", getCombatFatigueThreshold()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "mashCapacity", getMashCapacity()); //endregion General Personnel //region Expanded Personnel Information @@ -4201,7 +4234,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useUnitRatingModifiers", isUseUnitRatingModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFactionModifiers", isUseFactionModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMissionStatusModifiers", isUseMissionStatusModifiers()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "trackUnitFatigue", isTrackUnitFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useLoyaltyModifiers", isUseLoyaltyModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useHideLoyalty", isUseHideLoyalty()); @@ -4728,6 +4760,14 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setDisplayScenarioLog(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("displayKillRecord")) { retVal.setDisplayKillRecord(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useCombatFatigue")) { + retVal.setUseCombatFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigueThreshold")) { + retVal.setCombatFatigueThreshold(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { + retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("mashCapacity")) { + retVal.setMashCapacity(Integer.parseInt(wn2.getTextContent().trim())); //endregion General Personnel //region Expanded Personnel Information @@ -4876,8 +4916,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseFactionModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMissionStatusModifiers")) { retVal.setUseMissionStatusModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("trackUnitFatigue")) { - retVal.setTrackUnitFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useLoyaltyModifiers")) { retVal.setUseLoyaltyModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useHideLoyalty")) { diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 4778ab91ee..e5c4d40ee2 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -240,15 +240,6 @@ public String getForceCompositionReport() { return String.join(", ", composition); } - /** - * Retrieves the fatigue report for the campaign. - * - * @return The fatigue report as a string. - */ - public String getFatigueReport() { - return campaign.getFatigueLevel() + " (+" + campaign.getFatigueLevel() / 10 + ')'; - } - /** * A report that gives the percentage of successful missions * @return a String of the report @@ -317,6 +308,31 @@ public String getAdministrativeCapacityReport(Campaign campaign) { return administrativeCapacityReport.toString(); } + /** + * Returns a summary of combat fatigue related facilities. + * + * @param campaign the campaign object for which to generate the summary + * @return a String representing the combat fatigue facility summary + */ + public String getCombatFatigueSummary() { + int personnelCount = campaign.getActivePersonnel().size(); + List facilityCapacities = campaign.calculateFacilityCapacities(); + + if ((facilityCapacities.get(0) > 0) && (facilityCapacities.get(1) > 0)) { + return String.format("Kitchens (%s/%s), Hospitals (%s/%s)", + personnelCount, campaign.calculateFacilityCapacities().get(0), + personnelCount, campaign.calculateFacilityCapacities().get(1)); + } else if (facilityCapacities.get(0) > 0) { + return String.format("Kitchens (%s/%s)", + personnelCount, campaign.calculateFacilityCapacities().get(0)); + } else if (facilityCapacities.get(1) > 0) { + return String.format("Hospitals (%s/%s)", + personnelCount, campaign.calculateFacilityCapacities().get(1)); + } else { + return ""; + } + } + private String createCsv(Collection coll) { return StringUtils.join(coll, ","); } diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index e2cd9a6e32..97341f9687 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -1423,6 +1423,17 @@ public void resolveScenario(ScenarioStatus resolution, String report) { } } + if (!status.isDead()) { + person.setCombatFatigue(person.getCombatFatigue() + 1); + + if (getCampaign().getCampaignOptions().isUseCombatFatigue()) { + person.calculateCombatFatigueModifier(campaign); + campaign.reportCombatFatigue(person); + } else { + person.setCombatFatigueModifier(0); + } + } + if (getCampaign().getCampaignOptions().isUseAdvancedMedical()) { person.diagnose(getCampaign(), status.getHits()); } diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index e69d42db9c..d4a1e665b9 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -720,8 +720,6 @@ private static void processInfoNode(Campaign retVal, Node wni, Version version) retVal.setAstechPoolOvertime(Integer.parseInt(wn.getTextContent().trim())); } else if (xn.equalsIgnoreCase("medicPool")) { retVal.setMedicPool(Integer.parseInt(wn.getTextContent().trim())); - } else if (xn.equalsIgnoreCase("fatigueLevel")) { - retVal.setFatigueLevel(Integer.parseInt(wn.getTextContent().trim())); } else if (xn.equalsIgnoreCase("id")) { retVal.setId(UUID.fromString(wn.getTextContent().trim())); } diff --git a/MekHQ/src/mekhq/campaign/mission/enums/AtBContractType.java b/MekHQ/src/mekhq/campaign/mission/enums/AtBContractType.java index bf8ed7e692..90b6cf5b34 100644 --- a/MekHQ/src/mekhq/campaign/mission/enums/AtBContractType.java +++ b/MekHQ/src/mekhq/campaign/mission/enums/AtBContractType.java @@ -211,28 +211,6 @@ public AtBLanceRole getRequiredLanceRole() { } } - public int getFatigue() { - switch (this) { - case GARRISON_DUTY: - case SECURITY_DUTY: - case CADRE_DUTY: - return -1; - case RIOT_DUTY: - case PIRATE_HUNTING: - return 1; - case DIVERSIONARY_RAID: - case EXTRACTION_RAID: - case RECON_RAID: - case RELIEF_DUTY: - case OBJECTIVE_RAID: - return 2; - case GUERRILLA_WARFARE: - case PLANETARY_ASSAULT: - return 3; - default: - return 0; - } - } public int generateEventType() { final int roll = Compute.randomInt(20) + 1; diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index e53a8b8c9d..1e68e30474 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -124,7 +124,9 @@ public class Person { private List scenarioLog; private LocalDate retirement; - private int loyalty; + private Integer loyalty; + private Integer combatFatigue; + private Integer combatFatigueModifier; private Skills skills; private PersonnelOptions options; @@ -329,6 +331,8 @@ public Person(final String preNominal, final String givenName, final String surn lastRankChangeDate = null; retirement = null; loyalty = 0; + combatFatigue = 0; + combatFatigueModifier = 0; skills = new Skills(); options = new PersonnelOptions(); currentEdge = 0; @@ -1209,6 +1213,22 @@ public Integer getLoyalty() { public void setLoyalty(final Integer loyalty) { this.loyalty = loyalty; } + + public Integer getCombatFatigue() { + return combatFatigue; + } + + public void setCombatFatigue(final Integer combatFatigue) { + this.combatFatigue = combatFatigue; + } + + public Integer getCombatFatigueModifier() { + return combatFatigueModifier; + } + + public void setCombatFatigueModifier(final Integer combatFatigueModifier) { + this.combatFatigueModifier = combatFatigueModifier; + } //region Turnover and Retention //region Pregnancy @@ -1613,6 +1633,7 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "lastRankChangeDate", getLastRankChangeDate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigue", getCombatFatigue()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); } @@ -1920,6 +1941,8 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.setRetirement(MHQXMLUtility.parseDate(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("loyalty")) { retVal.setLoyalty(Integer.parseInt(wn2.getTextContent())); + } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigue")) { + retVal.setCombatFatigue(Integer.parseInt(wn2.getTextContent())); } else if (wn2.getNodeName().equalsIgnoreCase("advantages")) { advantages = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("edge")) { @@ -3635,4 +3658,51 @@ public void generateLoyalty() { setLoyalty(0); } } + + /** + * Calculates a person's combat fatigue modifier. + * + * @param campaign the campaign + */ + public void calculateCombatFatigueModifier(Campaign campaign) { + combatFatigueModifier = getEffectiveCombatFatigue(campaign) / campaign.getCampaignOptions().getCombatFatigueThreshold(); + } + + /** + * Calculates the effective combat fatigue for a person. + * + * @param campaign the campaign for which to calculate the effective combat fatigue + * @return the effective combat fatigue value + */ + public int getEffectiveCombatFatigue(Campaign campaign) { + int effectiveCombatFatigue = combatFatigue; + + if (isClanPersonnel()) { + effectiveCombatFatigue -= 2; + } + + switch (getSkillLevel(campaign, false)) { + case NONE: + case ULTRA_GREEN: + case GREEN: + case REGULAR: + break; + case VETERAN: + effectiveCombatFatigue--; + break; + case ELITE: + case HEROIC: + case LEGENDARY: + effectiveCombatFatigue -= 2; + break; + } + + for (Boolean capacityStatus : campaign.getFieldFacilityCapacities()) { + if (capacityStatus) { + effectiveCombatFatigue--; + } + } + + return effectiveCombatFatigue; + } } diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 24fc2fa7c9..152d21006c 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -183,11 +183,6 @@ public Map getTargetNumbers(final @Nullable AtBContract contra targetNumber.addModifier(unitRatingModifier, resources.getString("unitRating.text")); } - // Fatigue Modifiers - if (campaign.getCampaignOptions().isTrackUnitFatigue()) { - targetNumber.addModifier(campaign.getFatigueLevel() / 10, resources.getString("fatigue.text")); - } - // Mission completion status modifiers if ((contract != null) && (campaign.getCampaignOptions().isUseMissionStatusModifiers())) { if (contract.getStatus().isSuccess()) { diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 214f025d6b..71c364cc09 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -34,6 +34,7 @@ import mekhq.campaign.mission.ScenarioMapParameters.MapLocation; import mekhq.campaign.mission.atb.AtBScenarioModifier; import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; +import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.stratcon.StratconContractDefinition.StrategicObjectiveType; import mekhq.campaign.stratcon.StratconScenario.ScenarioState; @@ -509,12 +510,37 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca } } + increaseCombatFatigue(forceID, campaign); + // the force may be located in other places on the track - clear it out track.unassignForce(forceID); track.assignForce(forceID, coords, campaign.getLocalDate(), sticky); MekHQ.triggerEvent(new StratconDeploymentEvent(campaign.getForce(forceID))); } + /** + * Increases the combat fatigue for all crew members per Unit in a force. + * + * @param forceID the ID of the force + * @param campaign the campaign + */ + private static void increaseCombatFatigue(int forceID, Campaign campaign) { + boolean isUseCombatFatigue = campaign.getCampaignOptions().isUseCombatFatigue(); + + for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { + for (Person person : campaign.getUnit(unit).getCrew()) { + person.setCombatFatigue(person.getCombatFatigue() + 1); + + if (isUseCombatFatigue) { + person.calculateCombatFatigueModifier(campaign); + campaign.reportCombatFatigue(person); + } else { + person.setCombatFatigueModifier(0); + } + } + } + } + /** * Worker function that processes the effects of deploying a reinforcement force to a scenario */ diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index 34d1d49c14..c55dd0b33d 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -62,11 +62,11 @@ public final class CommandCenterTab extends CampaignGuiTab { private JLabel lblPersonnel; private JLabel lblAdminstrativeCapacity; private JLabel lblMissionSuccess; - private JLabel lblFatigue; private JLabel lblComposition; private JLabel lblRepairStatus; private JLabel lblTransportCapacity; private JLabel lblCargoSummary; + private JLabel lblFacilityCapacities; // objectives panel private JPanel panObjectives; @@ -204,22 +204,6 @@ private void initInfoPanel() { gridBagConstraints.weightx = 1.0; panInfo.add(lblRating, gridBagConstraints); - if(getCampaign().getCampaignOptions().isTrackUnitFatigue()) { - JLabel lblFatigueHead = new JLabel(resourceMap.getString("lblFatigue.text")); - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = y++; - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new Insets(1, 5, 1, 5); - panInfo.add(lblFatigueHead, gridBagConstraints); - lblFatigue = new JLabel(getCampaign().getCampaignSummary().getFatigueReport()); - lblFatigueHead.setLabelFor(lblFatigue); - gridBagConstraints.gridx = 1; - gridBagConstraints.weightx = 1.0; - panInfo.add(lblFatigue, gridBagConstraints); - } - JLabel lblExperienceHead = new JLabel(resourceMap.getString("lblExperience.text")); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -326,7 +310,7 @@ private void initInfoPanel() { gridBagConstraints.gridy = y++; gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new Insets(1, 5, 5, 5); + gridBagConstraints.insets = new Insets(1, 5, 1, 5); panInfo.add(lblCargoSummaryHead, gridBagConstraints); lblCargoSummary = new JLabel(getCampaign().getCampaignSummary().getCargoCapacityReport()); lblCargoSummaryHead.setLabelFor(lblCargoSummary); @@ -334,6 +318,24 @@ private void initInfoPanel() { gridBagConstraints.weightx = 1.0; panInfo.add(lblCargoSummary, gridBagConstraints); + if (getCampaign().getCampaignOptions().isUseCombatFatigue()) { + if ((getCampaign().getCampaignOptions().getFieldKitchenCapacity() > 0) || (getCampaign().getCampaignOptions().getMashCapacity() > 0)) { + JLabel lblFacilityCapacitiesHead = new JLabel(resourceMap.getString("lblFacilityCapacities.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y++; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new Insets(1, 5, 1, 5); + panInfo.add(lblFacilityCapacitiesHead, gridBagConstraints); + lblFacilityCapacities = new JLabel(getCampaign().getCampaignSummary().getCombatFatigueSummary()); + lblFacilityCapacitiesHead.setLabelFor(lblFacilityCapacities); + gridBagConstraints.gridx = 1; + gridBagConstraints.weightx = 1.0; + panInfo.add(lblFacilityCapacities, gridBagConstraints); + } + } + panInfo.setBorder(BorderFactory.createTitledBorder(resourceMap.getString("panInfo.title"))); } @@ -541,7 +543,6 @@ private void refreshBasicInfo() { getCampaign().getUnitRating().reInitialize(); getCampaign().getCampaignSummary().updateInformation(); lblRating.setText(getCampaign().getUnitRatingText()); - lblFatigue.setText(getCampaign().getCampaignSummary().getFatigueReport()); lblPersonnel.setText(getCampaign().getCampaignSummary().getPersonnelReport()); lblMissionSuccess.setText(getCampaign().getCampaignSummary().getMissionSuccessReport()); lblExperience.setText(getCampaign().getUnitRating().getAverageExperience().toString()); @@ -549,7 +550,14 @@ private void refreshBasicInfo() { lblCargoSummary.setText(getCampaign().getCampaignSummary().getCargoCapacityReport()); lblRepairStatus.setText(getCampaign().getCampaignSummary().getForceRepairReport()); lblTransportCapacity.setText(getCampaign().getCampaignSummary().getTransportCapacity()); - lblAdminstrativeCapacity.setText(getCampaign().getCampaignSummary().getAdministrativeCapacityReport(getCampaign())); + + try { + lblAdminstrativeCapacity.setText(getCampaign().getCampaignSummary().getAdministrativeCapacityReport(getCampaign())); + } catch (Exception ignored) {} + + try { + lblFacilityCapacities.setText(getCampaign().getCampaignSummary().getCombatFatigueSummary()); + } catch (Exception ignored) {} } private void refreshObjectives() { diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index eeaa11c232..26a5ddb491 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -211,6 +211,16 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkDisplayScenarioLog; private JCheckBox chkDisplayKillRecord; + private JPanel combatFatiguePanel; + private JLabel lblCombatFatigueWarning; + private JCheckBox chkUseCombatFatigue; + private JLabel lblCombatFatigueThreshold; + private JSpinner spnCombatFatigueThreshold; + private JLabel lblFieldKitchenCapacity; + private JSpinner spnFieldKitchenCapacity; + private JLabel lblMashCapacity; + private JSpinner spnMashCapacity; + // Expanded Personnel private JCheckBox chkUseTimeInService; private MMComboBox comboTimeInServiceDisplayFormat; @@ -280,7 +290,6 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseUnitRatingModifiers; private JCheckBox chkUseFactionModifiers; private JCheckBox chkUseMissionStatusModifiers; - private JCheckBox chkTrackUnitFatigue; private JPanel loyaltyPanel; private JCheckBox chkUseLoyaltyModifiers; @@ -3274,6 +3283,23 @@ private JPanel createGeneralPersonnelPanel() { chkDisplayKillRecord.setToolTipText(resources.getString("chkDisplayKillRecord.toolTipText")); chkDisplayKillRecord.setName("chkDisplayKillRecord"); + chkUseCombatFatigue = new JCheckBox(resources.getString("chkUseCombatFatigue.text")); + chkUseCombatFatigue.setToolTipText(resources.getString("chkUseCombatFatigue.toolTipText")); + chkUseCombatFatigue.setName("chkUseCombatFatigue"); + chkUseCombatFatigue.addActionListener(evt -> { + final boolean isEnabled = chkUseCombatFatigue.isSelected(); + + // general handler + for (Component component : combatFatiguePanel.getComponents()) { + component.setEnabled(isEnabled); + } + + // border handler + combatFatiguePanel.setEnabled(isEnabled); + }); + + combatFatiguePanel = createCombatFatiguePanel(); + // Layout the Panel final JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createTitledBorder("")); @@ -3303,6 +3329,8 @@ private JPanel createGeneralPersonnelPanel() { .addComponent(chkDisplayPersonnelLog) .addComponent(chkDisplayScenarioLog) .addComponent(chkDisplayKillRecord) + .addComponent(chkUseCombatFatigue) + .addComponent(combatFatiguePanel) ); layout.setHorizontalGroup( @@ -3324,11 +3352,89 @@ private JPanel createGeneralPersonnelPanel() { .addComponent(chkDisplayPersonnelLog) .addComponent(chkDisplayScenarioLog) .addComponent(chkDisplayKillRecord) + .addComponent(chkUseCombatFatigue) + .addComponent(combatFatiguePanel) ); return panel; } + private JPanel createCombatFatiguePanel() { + lblCombatFatigueWarning = new JLabel(resources.getString("lblCombatFatigueWarning.text")); + lblCombatFatigueWarning.setName("lblCombatFatigueWarning"); + lblCombatFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + lblCombatFatigueThreshold = new JLabel(resources.getString("lblCombatFatigueThreshold.text")); + lblCombatFatigueThreshold.setToolTipText(resources.getString("lblCombatFatigueThreshold.toolTipText")); + lblCombatFatigueThreshold.setName("lblCombatFatigueThreshold"); + lblCombatFatigueThreshold.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + spnCombatFatigueThreshold = new JSpinner(new SpinnerNumberModel(5, 0, 15, 1)); + spnCombatFatigueThreshold.setToolTipText(resources.getString("lblCombatFatigueThreshold.toolTipText")); + spnCombatFatigueThreshold.setName("spnCombatFatigueThreshold"); + spnCombatFatigueThreshold.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + lblFieldKitchenCapacity = new JLabel(resources.getString("lblFieldKitchenCapacity.text")); + lblFieldKitchenCapacity.setToolTipText(resources.getString("lblFieldKitchenCapacity.toolTipText")); + lblFieldKitchenCapacity.setName("lblFieldKitchenCapacity"); + lblFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + spnFieldKitchenCapacity = new JSpinner(new SpinnerNumberModel(150, 0, 450, 1)); + spnFieldKitchenCapacity.setToolTipText(resources.getString("lblFieldKitchenCapacity.toolTipText")); + spnFieldKitchenCapacity.setName("spnFieldKitchenCapacity"); + spnFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + lblMashCapacity = new JLabel(resources.getString("lblMashCapacity.text")); + lblMashCapacity.setToolTipText(resources.getString("lblMashCapacity.toolTipText")); + lblMashCapacity.setName("lblMashCapacity"); + lblMashCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + spnMashCapacity = new JSpinner(new SpinnerNumberModel(250, 0, 750, 1)); + spnMashCapacity.setToolTipText(resources.getString("lblMashCapacity.toolTipText")); + spnMashCapacity.setName("spnMashCapacity"); + spnMashCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + combatFatiguePanel = new JPanel(); + combatFatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("combatFatiguePanel.title"))); + combatFatiguePanel.setName("combatFatiguePanel"); + combatFatiguePanel.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + + final GroupLayout layout = new GroupLayout(combatFatiguePanel); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + combatFatiguePanel.setLayout(layout); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(lblCombatFatigueWarning) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblCombatFatigueThreshold) + .addComponent(spnCombatFatigueThreshold, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblFieldKitchenCapacity) + .addComponent(spnFieldKitchenCapacity, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(spnMashCapacity) + .addComponent(lblMashCapacity, Alignment.LEADING)) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(lblCombatFatigueWarning) + .addGroup(layout.createSequentialGroup() + .addComponent(lblCombatFatigueThreshold) + .addComponent(spnCombatFatigueThreshold)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblFieldKitchenCapacity) + .addComponent(spnFieldKitchenCapacity)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblMashCapacity) + .addComponent(spnMashCapacity)) + ); + + return combatFatiguePanel; + } + private JPanel createExpandedPersonnelInformationPanel() { // Initialize Labels Used in ActionListeners final JLabel lblTimeInServiceDisplayFormat = new JLabel(); @@ -3898,10 +4004,6 @@ private JPanel createTurnoverModifiersPanel() { chkUseMissionStatusModifiers.setToolTipText(resources.getString("chkUseMissionStatusModifiers.toolTipText")); chkUseMissionStatusModifiers.setName("chkUseMissionStatusModifiers"); - chkTrackUnitFatigue = new JCheckBox(resources.getString("chkTrackUnitFatigue.text")); - chkTrackUnitFatigue.setToolTipText(resources.getString("chkTrackUnitFatigue.toolTipText")); - chkTrackUnitFatigue.setName("chkTrackUnitFatigue"); - chkUseLoyaltyModifiers = new JCheckBox(resources.getString("chkUseLoyaltyModifiers.text")); chkUseLoyaltyModifiers.setToolTipText(resources.getString("chkUseLoyaltyModifiers.toolTipText")); chkUseLoyaltyModifiers.setName("chkUseLoyaltyModifiers"); @@ -3935,7 +4037,6 @@ private JPanel createTurnoverModifiersPanel() { .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) - .addComponent(chkTrackUnitFatigue) .addComponent(chkUseLoyaltyModifiers) .addComponent(loyaltyPanel) ); @@ -3948,7 +4049,6 @@ private JPanel createTurnoverModifiersPanel() { .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) - .addComponent(chkTrackUnitFatigue) .addComponent(chkUseLoyaltyModifiers) .addComponent(loyaltyPanel) ); @@ -7228,6 +7328,11 @@ public void setOptions(@Nullable CampaignOptions options, chkDisplayScenarioLog.setSelected(options.isDisplayScenarioLog()); chkDisplayKillRecord.setSelected(options.isDisplayKillRecord()); + chkUseCombatFatigue.setSelected(options.isUseCombatFatigue()); + spnCombatFatigueThreshold.setValue(options.getCombatFatigueThreshold()); + spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); + spnMashCapacity.setValue(options.getMashCapacity()); + // Expanded Personnel Information if (chkUseTimeInService.isSelected() != options.isUseTimeInService()) { chkUseTimeInService.doClick(); @@ -7297,7 +7402,6 @@ public void setOptions(@Nullable CampaignOptions options, chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); chkUseMissionStatusModifiers.setSelected(options.isUseMissionStatusModifiers()); - chkTrackUnitFatigue.setSelected(options.isTrackUnitFatigue()); chkUseLoyaltyModifiers.setSelected(options.isUseLoyaltyModifiers()); chkUseHideLoyalty.setSelected(options.isUseHideLoyalty()); @@ -7864,6 +7968,11 @@ public void updateOptions() { options.setDisplayScenarioLog(chkDisplayScenarioLog.isSelected()); options.setDisplayKillRecord(chkDisplayKillRecord.isSelected()); + options.setUseCombatFatigue(chkUseCombatFatigue.isSelected()); + options.setCombatFatigueThreshold((Integer) spnCombatFatigueThreshold.getValue()); + options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); + options.setMashCapacity((Integer) spnMashCapacity.getValue()); + // Expanded Personnel Information options.setUseTimeInService(chkUseTimeInService.isSelected()); options.setTimeInServiceDisplayFormat(comboTimeInServiceDisplayFormat.getSelectedItem()); @@ -7931,7 +8040,6 @@ public void updateOptions() { options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); options.setUseMissionStatusModifiers(chkUseMissionStatusModifiers.isSelected()); - options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); options.setUseLoyaltyModifiers(chkUseLoyaltyModifiers.isSelected()); options.setUseHideLoyalty(chkUseHideLoyalty.isSelected()); @@ -8115,7 +8223,6 @@ public void updateOptions() { options.setUseStratCon(chkUseStratCon.isSelected()); options.setSkillLevel(comboSkillLevel.getSelectedItem()); options.setTrackOriginalUnit(chkTrackOriginalUnit.isSelected()); - options.setTrackUnitFatigue(chkTrackUnitFatigue.isSelected()); options.setLimitLanceWeight(chkLimitLanceWeight.isSelected()); options.setLimitLanceNumUnits(chkLimitLanceNumUnits.isSelected()); options.setUseStrategy(chkUseStrategy.isSelected()); diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index 93f0edc752..6d5c56c2e2 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -1207,6 +1207,9 @@ private JPanel fillSkills() { JLabel lblLoyalty1 = new JLabel(); JLabel lblLoyalty2 = new JLabel(); + JLabel lblCombatFatigue1 = new JLabel(); + JLabel lblCombatFatigue2 = new JLabel(); + // education JLabel lblEducationLevel1 = new JLabel(); JLabel lblEducationLevel2 = new JLabel(); @@ -1420,6 +1423,43 @@ private JPanel fillSkills() { firsty++; } + if ((campaign.getCampaignOptions().isUseCombatFatigue()) + && (person.getEffectiveCombatFatigue(campaign) > 0)) { + lblCombatFatigue1.setName("lblCombatFatigue1"); + lblCombatFatigue1.setText(resourceMap.getString("lblCombatFatigue1.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = firsty; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + pnlSkills.add(lblCombatFatigue1, gridBagConstraints); + + StringBuilder combatFatigueDisplay = new StringBuilder(person.getCombatFatigue()); + + if (person.getCombatFatigue() != person.getEffectiveCombatFatigue(campaign)) { + combatFatigueDisplay.append(" (").append(person.getEffectiveCombatFatigue(campaign)).append(')'); + } + + if (person.getCombatFatigueModifier() > 0) { + combatFatigueDisplay.append(" +").append(person.getCombatFatigueModifier()); + } + + lblCombatFatigue2.setName("lblCombatFatigue2"); + lblCombatFatigue2.setText(combatFatigueDisplay.toString()); + lblCombatFatigue2.setLabelFor(lblCombatFatigue2); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = firsty; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new Insets(0, 10, 0, 0); + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + pnlSkills.add(lblCombatFatigue2, gridBagConstraints); + + firsty++; + } + if (campaign.getCampaignOptions().isUseEducationModule()) { lblEducationLevel1.setName("lblEducationLevel1"); lblEducationLevel1.setText(resourceMap.getString("lblEducationLevel1.text")); From ebbf123a0ac8c7fdbd69f8a8069bc19f3433ea96 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 19 May 2024 20:21:59 -0500 Subject: [PATCH 029/101] Added report for when personnel are fully recovered from fatigue. Introduced a new field "isRecoveringFromFatigue" in the Person class to track recovery status from combat fatigue. When fatigue decreases, the calculation for combat fatigue modifier checks this status, and a recovery message is added to the report when the combat fatigue reaches zero. --- .../mekhq/resources/Campaign.properties | 1 + MekHQ/src/mekhq/campaign/Campaign.java | 8 +++++++ .../src/mekhq/campaign/personnel/Person.java | 23 +++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 903ba9235a..70cbf43287 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -43,6 +43,7 @@ combatFatigueVeryTired.text=is tired, their skills have been reduced by 1. combatFatigueFatigued.text=is badly fatigued, their skills have been reduced by 2. combatFatigueExhausted.text=is exhausted, their skills have been reduced by 3. combatFatigueCritical.text=is critically fatigued, their skills have been reduced by %s. +combatFatigueRecovered.text=is no longer fatigued. #### Unsorted Campaign Resources dependentLeavesForce.text=%s is no longer travelling with the force, and is thus no longer dependent on it. diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 4fcb36a0ab..2961a5be8f 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -7083,6 +7083,14 @@ public void processCombatFatigueRecovery() { for (Person person : getActivePersonnel()) { if (person.getCombatFatigue() > 0) { person.setCombatFatigue(person.getCombatFatigue() - 1); + person.calculateCombatFatigueModifier(this); + + if ((getCampaignOptions().isUseCombatFatigue()) && (person.getIsRecoveringFromFatigue())) { + if (person.getCombatFatigue() == 0) { + addReport(person.getHyperlinkedFullTitle() + ' ' + + resources.getString("combatFatigueRecovered.text")); + } + } } } } diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index 1e68e30474..b327782a06 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -127,6 +127,7 @@ public class Person { private Integer loyalty; private Integer combatFatigue; private Integer combatFatigueModifier; + private Boolean isRecoveringFromFatigue; private Skills skills; private PersonnelOptions options; @@ -333,6 +334,7 @@ public Person(final String preNominal, final String givenName, final String surn loyalty = 0; combatFatigue = 0; combatFatigueModifier = 0; + isRecoveringFromFatigue = false; skills = new Skills(); options = new PersonnelOptions(); currentEdge = 0; @@ -1229,6 +1231,14 @@ public Integer getCombatFatigueModifier() { public void setCombatFatigueModifier(final Integer combatFatigueModifier) { this.combatFatigueModifier = combatFatigueModifier; } + + public boolean getIsRecoveringFromFatigue() { + return isRecoveringFromFatigue; + } + + public void setIsRecoveringFromFatigue(final boolean isRecoveringFromFatigue) { + this.isRecoveringFromFatigue = isRecoveringFromFatigue; + } //region Turnover and Retention //region Pregnancy @@ -1634,6 +1644,8 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigue", getCombatFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigueModifier", getCombatFatigueModifier()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); } @@ -1940,9 +1952,13 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio } else if (wn2.getNodeName().equalsIgnoreCase("retirement")) { retVal.setRetirement(MHQXMLUtility.parseDate(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("loyalty")) { - retVal.setLoyalty(Integer.parseInt(wn2.getTextContent())); + retVal.loyalty = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigue")) { - retVal.setCombatFatigue(Integer.parseInt(wn2.getTextContent())); + retVal.combatFatigue = Integer.parseInt(wn2.getTextContent()); + } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigueModifier")) { + retVal.combatFatigueModifier = Integer.parseInt(wn2.getTextContent()); + } else if (wn2.getNodeName().equalsIgnoreCase("isRecoveringFromFatigue")) { + retVal.isRecoveringFromFatigue = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("advantages")) { advantages = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("edge")) { @@ -3666,6 +3682,9 @@ public void generateLoyalty() { */ public void calculateCombatFatigueModifier(Campaign campaign) { combatFatigueModifier = getEffectiveCombatFatigue(campaign) / campaign.getCampaignOptions().getCombatFatigueThreshold(); + if (combatFatigueModifier > 0) { + isRecoveringFromFatigue = true; + } } /** From aca384937af876648e807c7604b4d6a09b4bfec6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 19 May 2024 20:26:55 -0500 Subject: [PATCH 030/101] Added Combat Fatigue modifier to RetirementDefectionTracker A new modifier has been incorporated into the RetirementDefectionTracker which now considers the combat fatigue of personnel. --- .../mekhq/resources/RetirementDefectionTracker.properties | 1 + .../campaign/personnel/RetirementDefectionTracker.java | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties index 464a14696f..27ad03ea77 100644 --- a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -2,6 +2,7 @@ base.text=Base contract.text=Under Contract desirability.text=Desirability +combatFatigue.text=Combat Fatigue unitRating.text=Unit Rating missionSuccess.text=Recent Successes missionFailure.text=Recent Failures diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 152d21006c..6b9fdbcd68 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -144,6 +144,13 @@ public Map getTargetNumbers(final @Nullable AtBContract contra targetNumber.addModifier(person.getExperienceLevel(campaign, false), resources.getString("desirability.text")); } + // Combat Fatigue modifier + if (campaign.getCampaignOptions().isUseCombatFatigue()) { + if (person.getCombatFatigueModifier() > 1) { + targetNumber.addModifier(person.getCombatFatigueModifier() / 2, resources.getString("combatFatigue.text")); + } + } + // Administrative Strain Modifiers if (campaign.getCampaignOptions().isUseAdministrativeStrain()) { int administrativeStrainModifier = getAdministrativeStrainModifier(campaign); From 3e50a9dc1d92af1d581ba620fec15fcea78eb6bf Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 19 May 2024 21:34:31 -0500 Subject: [PATCH 031/101] Simplified and refactored Combat Fatigue system Combat Fatigue system has been optimized for clarity and stability. This update includes the removal of unnecessary checks and calculations related to combat fatigue from different parts of the system. Also, it modifies the way fatigue recovery is handled, aligning it with the beginning of the week rather than a fixed day of the month. --- .../mekhq/resources/Campaign.properties | 7 +- .../CampaignOptionsDialog.properties | 2 - MekHQ/src/mekhq/campaign/Campaign.java | 106 ++++++------------ MekHQ/src/mekhq/campaign/CampaignOptions.java | 26 ----- MekHQ/src/mekhq/campaign/CampaignSummary.java | 16 +-- .../campaign/ResolveScenarioTracker.java | 8 +- .../src/mekhq/campaign/personnel/Person.java | 31 +---- .../personnel/RetirementDefectionTracker.java | 6 +- .../stratcon/StratconRulesManager.java | 10 +- MekHQ/src/mekhq/gui/CommandCenterTab.java | 2 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 40 ------- MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 10 +- 12 files changed, 56 insertions(+), 208 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 70cbf43287..2b36a3545a 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -39,10 +39,9 @@ LayeredForceIconLayer.LOGO.text=Logos LayeredForceIconLayer.LOGO.toolTipText=This tab contains canon faction logos that can be added to the center of a force icon. #### Combat Fatigue Resources -combatFatigueVeryTired.text=is tired, their skills have been reduced by 1. -combatFatigueFatigued.text=is badly fatigued, their skills have been reduced by 2. -combatFatigueExhausted.text=is exhausted, their skills have been reduced by 3. -combatFatigueCritical.text=is critically fatigued, their skills have been reduced by %s. +combatFatigueTired.text=is tired and in need of rest. +combatFatigueFatigued.text=is badly fatigued and should be removed from active duty. +combatFatigueExhausted.text=is exhausted and in critical need of rest. combatFatigueRecovered.text=is no longer fatigued. #### Unsorted Campaign Resources diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 9011fed334..047ddd9de5 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -162,8 +162,6 @@ combatFatiguePanel.title=Combat Fatigue lblCombatFatigueWarning.text=Client must be reloaded whenever enabling or disabling Combat Fatigue. chkUseCombatFatigue.text=Enable Combat Fatigue chkUseCombatFatigue.toolTipText=If enabled, combat personnel will gain Combat Fatigue whenever they are deployed to a Scenario -lblCombatFatigueThreshold.text=Threshold Value -lblCombatFatigueThreshold.toolTipText=The combat fatigue modifier is calculated by dividing Combat Fatigue by this number. lblFieldKitchenCapacity.text=Field Kitchen Capacity lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen equipped Unit? Reduces effective Combat Fatigue by 1. lblMashCapacity.text=MASH Capacity diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 2961a5be8f..b8c6b986f5 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -186,7 +186,7 @@ public class Campaign implements ITechManager { private transient String currentReportHTML; private transient List newReports; - private ArrayList fieldFacilityCapacities; + private Boolean fieldKitchenWithinCapacity; // this is updated and used per gaming session, it is enabled/disabled via the Campaign options // we're re-using the LogEntry class that is used to store Personnel entries @@ -292,7 +292,7 @@ public Campaign() { hasActiveContract = false; campaignSummary = new CampaignSummary(this); quartermaster = new Quartermaster(this); - fieldFacilityCapacities = new ArrayList<>(Arrays.asList(false, false)); + fieldKitchenWithinCapacity = false; } /** @@ -1341,12 +1341,12 @@ public Person newPerson(final PersonnelRole primaryRole, final PersonnelRole sec return person; } - public ArrayList getFieldFacilityCapacities() { - return fieldFacilityCapacities; + public Boolean getFieldKitchenWithinCapacity() { + return fieldKitchenWithinCapacity; } - public void setFieldFacilityCapacities(final ArrayList fieldFacilityCapacities) { - this.fieldFacilityCapacities = fieldFacilityCapacities; + public void setFieldKitchenWithinCapacity(final Boolean fieldKitchenWithinCapacity) { + this.fieldKitchenWithinCapacity = fieldKitchenWithinCapacity; } //endregion Person Creation @@ -3517,7 +3517,7 @@ public boolean newDay() { getFinances().newDay(this, yesterday, getLocalDate()); // Combat Fatigue Region - if (getLocalDate().getDayOfMonth() == 1) { + if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { // even if Combat Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state processCombatFatigueRecovery(); } @@ -3525,14 +3525,10 @@ public boolean newDay() { if (campaignOptions.isUseCombatFatigue()) { // we store these values, so this only needs to be checked once per day, // otherwise we would need to check it once for each active person in the campaign - fieldFacilityCapacities = new ArrayList<>(checkFieldFacilityCapacities()); + fieldKitchenWithinCapacity = getActivePersonnel().size() <= checkFieldKitchenCapacity(); } else { - fieldFacilityCapacities = new ArrayList<>(Arrays.asList(false, false)); + fieldKitchenWithinCapacity = false; } - - // if Combat Fatigue is disabled, we reset everyone's fatigue modifier to 0, - // this means we don't have to check to see whether Combat Fatigue every time we make a roll affected by Combat Fatigue - setCombatFatigueModifiersForActivePersonnel(); // End Combat Fatigue Region MekHQ.triggerEvent(new NewDayEvent(this)); @@ -6985,28 +6981,12 @@ public boolean showExtinct() { } /** - * This method counts the number of field kitchens and Mash units available and compares them to the number of active personnel. - * - * @return A list of booleans indicating the status of the field facilities' capacities. - * True if the capacity of each field facility is not exceeded by the number of active personnel, - * false otherwise. - */ - public List checkFieldFacilityCapacities() { - List facilityCapacities = calculateFacilityCapacities(); - - return Arrays.asList(getActivePersonnel().size() <= facilityCapacities.get(0), - getActivePersonnel().size() <= facilityCapacities.get(1)); - } - - /** - * Calculates the facility capacities based on the units present. + * Calculates the total capacity of field kitchens based on the units present. * - * @return A list containing the calculated capacities for field kitchens - * and mash facilities. + * @return The total capacity of field kitchens. */ - public List calculateFacilityCapacities() { + public Integer checkFieldKitchenCapacity() { int fieldKitchenCount = 0; - int mashCount = 0; Collection allUnits = getUnits(); @@ -7023,56 +7003,39 @@ public List calculateFacilityCapacities() { List miscItems = unit.getEntity().getMisc(); if (!miscItems.isEmpty()) { - for (MiscMounted item : unit.getEntity().getMisc()) { - if (item.getType().hasFlag(MiscType.F_FIELD_KITCHEN)) { - fieldKitchenCount++; - } else if (item.getType().hasFlag(MiscType.F_MASH)) { - mashCount++; - } - } + fieldKitchenCount += (int) unit.getEntity().getMisc().stream() + .filter(item -> item.getType().hasFlag(MiscType.F_FIELD_KITCHEN)) + .count(); } } } - return Arrays.asList(fieldKitchenCount * campaignOptions.getFieldKitchenCapacity(), mashCount * campaignOptions.getMashCapacity()); - } - - - /** - * Sets the combat fatigue modifiers for all active personnel. - * If Combat Fatigue is disabled, all Fatigue Modifiers are set to 0. - */ - public void setCombatFatigueModifiersForActivePersonnel() { - for (Person person : getActivePersonnel()) { - if (campaignOptions.isUseCombatFatigue()) { - person.calculateCombatFatigueModifier(this); - } else { - person.setCombatFatigueModifier(0); - } - } + return fieldKitchenCount * campaignOptions.getFieldKitchenCapacity(); } /** - * Reports the combat fatigue of a person. + * Reports the combat fatigue of a person and, if appropriate, sets setIsRecoveringFromFatigue to true. * * @param person the person for which the combat fatigue needs to be reported */ public void reportCombatFatigue(Person person) { - switch (person.getCombatFatigue()) { - case 0: - break; - case 1: - addReport(person.getHyperlinkedFullTitle() + ' ' - + resources.getString("combatFatigueVeryTired.text")); - case 2: - addReport(person.getHyperlinkedFullTitle() + ' ' - + resources.getString("combatFatigueFatigued.text")); - case 3: - addReport(person.getHyperlinkedFullTitle() + ' ' - + resources.getString("combatFatigueExhausted.text")); - default: - addReport(person.getHyperlinkedFullTitle() + ' ' + - String.format(resources.getString("combatFatigueCritical.text"), person.getCombatFatigueModifier())); + int combatFatigue = person.getCombatFatigue(); + + if (getCampaignOptions().isUseCombatFatigue()) { + if ((combatFatigue >= 3) && (combatFatigue < 7)) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("combatFatigueTired.text")); + person.setIsRecoveringFromFatigue(true); + } else if ((combatFatigue >= 7) && (combatFatigue < 12)) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("combatFatigueFatigued.text")); + person.setIsRecoveringFromFatigue(true); + } else if (combatFatigue >= 12) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("combatFatigueExhausted.text")); + person.setIsRecoveringFromFatigue(true); + } + } else { + if (combatFatigue >=3) { + person.setIsRecoveringFromFatigue(true); + } } } @@ -7083,7 +7046,6 @@ public void processCombatFatigueRecovery() { for (Person person : getActivePersonnel()) { if (person.getCombatFatigue() > 0) { person.setCombatFatigue(person.getCombatFatigue() - 1); - person.calculateCombatFatigueModifier(this); if ((getCampaignOptions().isUseCombatFatigue()) && (person.getIsRecoveringFromFatigue())) { if (person.getCombatFatigue() == 0) { diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 214d0ce090..ac3412add1 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -208,9 +208,7 @@ public static String getTransitUnitName(final int unit) { private boolean displayScenarioLog; private boolean displayKillRecord; private boolean useCombatFatigue; - private Integer combatFatigueThreshold; private Integer fieldKitchenCapacity; - private Integer mashCapacity; // Expanded Personnel Information private boolean useTimeInService; @@ -669,9 +667,7 @@ public CampaignOptions() { setDisplayScenarioLog(false); setDisplayKillRecord(false); setUseCombatFatigue(true); - setCombatFatigueThreshold(5); setFieldKitchenCapacity(150); - setMashCapacity(250); // Expanded Personnel Information setUseTimeInService(false); @@ -1434,14 +1430,6 @@ public void setUseCombatFatigue(final boolean useCombatFatigue) { this.useCombatFatigue = useCombatFatigue; } - public Integer getCombatFatigueThreshold() { - return combatFatigueThreshold; - } - - public void setCombatFatigueThreshold(final Integer combatFatigueThreshold) { - this.combatFatigueThreshold = combatFatigueThreshold; - } - public Integer getFieldKitchenCapacity() { return fieldKitchenCapacity; } @@ -1449,14 +1437,6 @@ public Integer getFieldKitchenCapacity() { public void setFieldKitchenCapacity(final Integer fieldKitchenCapacity) { this.fieldKitchenCapacity = fieldKitchenCapacity; } - - public Integer getMashCapacity() { - return mashCapacity; - } - - public void setMashCapacity(final Integer mashCapacity) { - this.mashCapacity = mashCapacity; - } //endregion General Personnel //region Expanded Personnel Information @@ -4159,9 +4139,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayScenarioLog", isDisplayScenarioLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayKillRecord", isDisplayKillRecord()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCombatFatigue", isUseCombatFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigueThreshold", getCombatFatigueThreshold()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "mashCapacity", getMashCapacity()); //endregion General Personnel //region Expanded Personnel Information @@ -4762,12 +4740,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setDisplayKillRecord(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useCombatFatigue")) { retVal.setUseCombatFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigueThreshold")) { - retVal.setCombatFatigueThreshold(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("mashCapacity")) { - retVal.setMashCapacity(Integer.parseInt(wn2.getTextContent().trim())); //endregion General Personnel //region Expanded Personnel Information diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index e5c4d40ee2..9eb84fc16a 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -311,23 +311,15 @@ public String getAdministrativeCapacityReport(Campaign campaign) { /** * Returns a summary of combat fatigue related facilities. * - * @param campaign the campaign object for which to generate the summary - * @return a String representing the combat fatigue facility summary + * @return A summary of combat fatigue related facilities. */ public String getCombatFatigueSummary() { int personnelCount = campaign.getActivePersonnel().size(); - List facilityCapacities = campaign.calculateFacilityCapacities(); + int fieldKitchenCapacity = campaign.checkFieldKitchenCapacity(); - if ((facilityCapacities.get(0) > 0) && (facilityCapacities.get(1) > 0)) { - return String.format("Kitchens (%s/%s), Hospitals (%s/%s)", - personnelCount, campaign.calculateFacilityCapacities().get(0), - personnelCount, campaign.calculateFacilityCapacities().get(1)); - } else if (facilityCapacities.get(0) > 0) { + if (fieldKitchenCapacity > 0) { return String.format("Kitchens (%s/%s)", - personnelCount, campaign.calculateFacilityCapacities().get(0)); - } else if (facilityCapacities.get(1) > 0) { - return String.format("Hospitals (%s/%s)", - personnelCount, campaign.calculateFacilityCapacities().get(1)); + personnelCount, campaign.checkFieldKitchenCapacity()); } else { return ""; } diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index 97341f9687..41b4e31dc1 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -1425,13 +1425,7 @@ public void resolveScenario(ScenarioStatus resolution, String report) { if (!status.isDead()) { person.setCombatFatigue(person.getCombatFatigue() + 1); - - if (getCampaign().getCampaignOptions().isUseCombatFatigue()) { - person.calculateCombatFatigueModifier(campaign); - campaign.reportCombatFatigue(person); - } else { - person.setCombatFatigueModifier(0); - } + campaign.reportCombatFatigue(person); } if (getCampaign().getCampaignOptions().isUseAdvancedMedical()) { diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index b327782a06..e92813bd03 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -126,7 +126,6 @@ public class Person { private LocalDate retirement; private Integer loyalty; private Integer combatFatigue; - private Integer combatFatigueModifier; private Boolean isRecoveringFromFatigue; private Skills skills; @@ -333,7 +332,6 @@ public Person(final String preNominal, final String givenName, final String surn retirement = null; loyalty = 0; combatFatigue = 0; - combatFatigueModifier = 0; isRecoveringFromFatigue = false; skills = new Skills(); options = new PersonnelOptions(); @@ -1224,14 +1222,6 @@ public void setCombatFatigue(final Integer combatFatigue) { this.combatFatigue = combatFatigue; } - public Integer getCombatFatigueModifier() { - return combatFatigueModifier; - } - - public void setCombatFatigueModifier(final Integer combatFatigueModifier) { - this.combatFatigueModifier = combatFatigueModifier; - } - public boolean getIsRecoveringFromFatigue() { return isRecoveringFromFatigue; } @@ -1644,7 +1634,6 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigue", getCombatFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigueModifier", getCombatFatigueModifier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); @@ -1955,8 +1944,6 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.loyalty = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigue")) { retVal.combatFatigue = Integer.parseInt(wn2.getTextContent()); - } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigueModifier")) { - retVal.combatFatigueModifier = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("isRecoveringFromFatigue")) { retVal.isRecoveringFromFatigue = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("advantages")) { @@ -3675,18 +3662,6 @@ public void generateLoyalty() { } } - /** - * Calculates a person's combat fatigue modifier. - * - * @param campaign the campaign - */ - public void calculateCombatFatigueModifier(Campaign campaign) { - combatFatigueModifier = getEffectiveCombatFatigue(campaign) / campaign.getCampaignOptions().getCombatFatigueThreshold(); - if (combatFatigueModifier > 0) { - isRecoveringFromFatigue = true; - } - } - /** * Calculates the effective combat fatigue for a person. * @@ -3716,10 +3691,8 @@ public int getEffectiveCombatFatigue(Campaign campaign) { break; } - for (Boolean capacityStatus : campaign.getFieldFacilityCapacities()) { - if (capacityStatus) { - effectiveCombatFatigue--; - } + if (campaign.getFieldKitchenWithinCapacity()) { + effectiveCombatFatigue--; } return effectiveCombatFatigue; diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 6b9fdbcd68..1cdf7a4d80 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -146,8 +146,10 @@ public Map getTargetNumbers(final @Nullable AtBContract contra // Combat Fatigue modifier if (campaign.getCampaignOptions().isUseCombatFatigue()) { - if (person.getCombatFatigueModifier() > 1) { - targetNumber.addModifier(person.getCombatFatigueModifier() / 2, resources.getString("combatFatigue.text")); + int combatFatigueModifier = person.getCombatFatigue() / 5; + + if (combatFatigueModifier > 0) { + targetNumber.addModifier(combatFatigueModifier, resources.getString("combatFatigue.text")); } } diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 71c364cc09..c1f243dbc7 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -525,18 +525,10 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca * @param campaign the campaign */ private static void increaseCombatFatigue(int forceID, Campaign campaign) { - boolean isUseCombatFatigue = campaign.getCampaignOptions().isUseCombatFatigue(); - for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { for (Person person : campaign.getUnit(unit).getCrew()) { person.setCombatFatigue(person.getCombatFatigue() + 1); - - if (isUseCombatFatigue) { - person.calculateCombatFatigueModifier(campaign); - campaign.reportCombatFatigue(person); - } else { - person.setCombatFatigueModifier(0); - } + campaign.reportCombatFatigue(person); } } } diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index c55dd0b33d..04ca5e85b6 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -319,7 +319,7 @@ private void initInfoPanel() { panInfo.add(lblCargoSummary, gridBagConstraints); if (getCampaign().getCampaignOptions().isUseCombatFatigue()) { - if ((getCampaign().getCampaignOptions().getFieldKitchenCapacity() > 0) || (getCampaign().getCampaignOptions().getMashCapacity() > 0)) { + if (getCampaign().getCampaignOptions().getFieldKitchenCapacity() > 0) { JLabel lblFacilityCapacitiesHead = new JLabel(resourceMap.getString("lblFacilityCapacities.text")); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 26a5ddb491..19a79c32a5 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -214,12 +214,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JPanel combatFatiguePanel; private JLabel lblCombatFatigueWarning; private JCheckBox chkUseCombatFatigue; - private JLabel lblCombatFatigueThreshold; - private JSpinner spnCombatFatigueThreshold; private JLabel lblFieldKitchenCapacity; private JSpinner spnFieldKitchenCapacity; - private JLabel lblMashCapacity; - private JSpinner spnMashCapacity; // Expanded Personnel private JCheckBox chkUseTimeInService; @@ -3364,16 +3360,6 @@ private JPanel createCombatFatiguePanel() { lblCombatFatigueWarning.setName("lblCombatFatigueWarning"); lblCombatFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); - lblCombatFatigueThreshold = new JLabel(resources.getString("lblCombatFatigueThreshold.text")); - lblCombatFatigueThreshold.setToolTipText(resources.getString("lblCombatFatigueThreshold.toolTipText")); - lblCombatFatigueThreshold.setName("lblCombatFatigueThreshold"); - lblCombatFatigueThreshold.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); - - spnCombatFatigueThreshold = new JSpinner(new SpinnerNumberModel(5, 0, 15, 1)); - spnCombatFatigueThreshold.setToolTipText(resources.getString("lblCombatFatigueThreshold.toolTipText")); - spnCombatFatigueThreshold.setName("spnCombatFatigueThreshold"); - spnCombatFatigueThreshold.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); - lblFieldKitchenCapacity = new JLabel(resources.getString("lblFieldKitchenCapacity.text")); lblFieldKitchenCapacity.setToolTipText(resources.getString("lblFieldKitchenCapacity.toolTipText")); lblFieldKitchenCapacity.setName("lblFieldKitchenCapacity"); @@ -3384,16 +3370,6 @@ private JPanel createCombatFatiguePanel() { spnFieldKitchenCapacity.setName("spnFieldKitchenCapacity"); spnFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); - lblMashCapacity = new JLabel(resources.getString("lblMashCapacity.text")); - lblMashCapacity.setToolTipText(resources.getString("lblMashCapacity.toolTipText")); - lblMashCapacity.setName("lblMashCapacity"); - lblMashCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); - - spnMashCapacity = new JSpinner(new SpinnerNumberModel(250, 0, 750, 1)); - spnMashCapacity.setToolTipText(resources.getString("lblMashCapacity.toolTipText")); - spnMashCapacity.setName("spnMashCapacity"); - spnMashCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); - combatFatiguePanel = new JPanel(); combatFatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("combatFatiguePanel.title"))); combatFatiguePanel.setName("combatFatiguePanel"); @@ -3407,29 +3383,17 @@ private JPanel createCombatFatiguePanel() { layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(lblCombatFatigueWarning) - .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblCombatFatigueThreshold) - .addComponent(spnCombatFatigueThreshold, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity, Alignment.LEADING)) - .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(spnMashCapacity) - .addComponent(lblMashCapacity, Alignment.LEADING)) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(lblCombatFatigueWarning) - .addGroup(layout.createSequentialGroup() - .addComponent(lblCombatFatigueThreshold) - .addComponent(spnCombatFatigueThreshold)) .addGroup(layout.createSequentialGroup() .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity)) - .addGroup(layout.createSequentialGroup() - .addComponent(lblMashCapacity) - .addComponent(spnMashCapacity)) ); return combatFatiguePanel; @@ -7329,9 +7293,7 @@ public void setOptions(@Nullable CampaignOptions options, chkDisplayKillRecord.setSelected(options.isDisplayKillRecord()); chkUseCombatFatigue.setSelected(options.isUseCombatFatigue()); - spnCombatFatigueThreshold.setValue(options.getCombatFatigueThreshold()); spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); - spnMashCapacity.setValue(options.getMashCapacity()); // Expanded Personnel Information if (chkUseTimeInService.isSelected() != options.isUseTimeInService()) { @@ -7969,9 +7931,7 @@ public void updateOptions() { options.setDisplayKillRecord(chkDisplayKillRecord.isSelected()); options.setUseCombatFatigue(chkUseCombatFatigue.isSelected()); - options.setCombatFatigueThreshold((Integer) spnCombatFatigueThreshold.getValue()); options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); - options.setMashCapacity((Integer) spnMashCapacity.getValue()); // Expanded Personnel Information options.setUseTimeInService(chkUseTimeInService.isSelected()); diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index 6d5c56c2e2..0e6f69e5f6 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -1435,13 +1435,15 @@ private JPanel fillSkills() { pnlSkills.add(lblCombatFatigue1, gridBagConstraints); StringBuilder combatFatigueDisplay = new StringBuilder(person.getCombatFatigue()); + int effectiveCombatFatigue = person.getEffectiveCombatFatigue(campaign); + int combatFatigueTurnoverModifier = effectiveCombatFatigue / 5; - if (person.getCombatFatigue() != person.getEffectiveCombatFatigue(campaign)) { - combatFatigueDisplay.append(" (").append(person.getEffectiveCombatFatigue(campaign)).append(')'); + if (person.getCombatFatigue() != effectiveCombatFatigue) { + combatFatigueDisplay.append(" (").append(effectiveCombatFatigue).append(')'); } - if (person.getCombatFatigueModifier() > 0) { - combatFatigueDisplay.append(" +").append(person.getCombatFatigueModifier()); + if (combatFatigueTurnoverModifier > 0) { + combatFatigueDisplay.append(" -").append(combatFatigueTurnoverModifier); } lblCombatFatigue2.setName("lblCombatFatigue2"); From 593cf3ed9d7549d0fb1aa6930e745ed3a9e70f4e Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 00:14:05 -0500 Subject: [PATCH 032/101] Added CamOps fatigue tracking to megamek Updated Crew.java to include fatigue field and methods to access or modify the new field. Modified user interfaces to reflect and interact with these changes. --- .../mekhq/resources/Campaign.properties | 11 ++-- .../CampaignOptionsDialog.properties | 12 ++-- .../CreateCharacterDialog.properties | 1 + .../resources/mekhq/resources/GUI.properties | 1 + .../resources/PersonViewPanel.properties | 2 +- .../RetirementDefectionTracker.properties | 3 +- MekHQ/src/mekhq/campaign/Campaign.java | 51 ++++++++------- MekHQ/src/mekhq/campaign/CampaignOptions.java | 18 +++--- MekHQ/src/mekhq/campaign/CampaignSummary.java | 6 +- .../campaign/ResolveScenarioTracker.java | 4 +- .../src/mekhq/campaign/personnel/Person.java | 38 +++++------ .../personnel/RetirementDefectionTracker.java | 11 ++-- .../stratcon/StratconRulesManager.java | 15 +++-- MekHQ/src/mekhq/campaign/unit/Unit.java | 2 + MekHQ/src/mekhq/gui/CommandCenterTab.java | 6 +- .../gui/dialog/CustomizePersonDialog.java | 31 +++++++++ .../gui/enums/PersonnelTableModelColumn.java | 8 +++ .../mekhq/gui/panes/CampaignOptionsPane.java | 64 +++++++++---------- MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 36 +++++------ MekHQ/src/mekhq/utilities/MHQXMLUtility.java | 5 ++ 20 files changed, 189 insertions(+), 136 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 2b36a3545a..55f2f043ba 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -38,11 +38,12 @@ LayeredForceIconLayer.FRAME.toolTipText=This tab contains frames, which are the LayeredForceIconLayer.LOGO.text=Logos LayeredForceIconLayer.LOGO.toolTipText=This tab contains canon faction logos that can be added to the center of a force icon. -#### Combat Fatigue Resources -combatFatigueTired.text=is tired and in need of rest. -combatFatigueFatigued.text=is badly fatigued and should be removed from active duty. -combatFatigueExhausted.text=is exhausted and in critical need of rest. -combatFatigueRecovered.text=is no longer fatigued. +#### Fatigue Resources +fatigueTired.text=is tired and in need of rest. +fatigueFatigued.text=is badly fatigued and should be removed from active duty. +fatigueExhausted.text=is exhausted and in desperate need of rest. +fatigueCritical.text=is critically fatigued and in critical need of rest. +fatigueRecovered.text=is no longer fatigued. #### Unsorted Campaign Resources dependentLeavesForce.text=%s is no longer travelling with the force, and is thus no longer dependent on it. diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 047ddd9de5..cef7d09da3 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -158,14 +158,12 @@ chkDisplayScenarioLog.toolTipText=If enabled, the scenario log will be expanded chkDisplayKillRecord.text=Expand Kill Record by Default chkDisplayKillRecord.toolTipText=If enabled, the kill record will be expanded by default -combatFatiguePanel.title=Combat Fatigue -lblCombatFatigueWarning.text=Client must be reloaded whenever enabling or disabling Combat Fatigue. -chkUseCombatFatigue.text=Enable Combat Fatigue -chkUseCombatFatigue.toolTipText=If enabled, combat personnel will gain Combat Fatigue whenever they are deployed to a Scenario +fatiguePanel.title=Fatigue (CamOps) +lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
This option requires Fatigue to be enabled in MegaMek.
Interacts with Turnover and Retention. +chkUseFatigue.text=Enable Fatigue +chkUseFatigue.toolTipText=If enabled, combat personnel will gain Fatigue whenever they are deployed to a Scenario or entering an unexplored StratCon hex. lblFieldKitchenCapacity.text=Field Kitchen Capacity -lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen equipped Unit? Reduces effective Combat Fatigue by 1. -lblMashCapacity.text=MASH Capacity -lblMashCapacity.toolTipText=How many personnel can be treated per MASH equipped Unit? This simulates minor day-to-day injuries not accounted for by the Hits system. Reduces effective Combat Fatigue by 1. +lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen? Reduces effective Fatigue by 1. # Expanded Personnel Information expandedPersonnelInformationPanel.title=Expanded Personnel Information diff --git a/MekHQ/resources/mekhq/resources/CreateCharacterDialog.properties b/MekHQ/resources/mekhq/resources/CreateCharacterDialog.properties index 10fd939f4d..b144b3e57d 100644 --- a/MekHQ/resources/mekhq/resources/CreateCharacterDialog.properties +++ b/MekHQ/resources/mekhq/resources/CreateCharacterDialog.properties @@ -9,6 +9,7 @@ lblGunnery.text=Gunnery: lblInitB.text=Initiative Bonus: lblCommandB.text=Tactics: lblToughness.text=Toughness: +lblFatigue.text=Fatigue: lblArtillery.text=Artillery: lblBday.text=Birthdate: lblRecruitment.text=Recruited: diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index 377494155c..d41ecde289 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -906,6 +906,7 @@ PersonnelTableModelColumn.DIVORCEABLE.text=Divorceable PersonnelTableModelColumn.TRYING_TO_CONCEIVE.text=Trying to Conceive PersonnelTableModelColumn.IMMORTAL.text=Immortal PersonnelTableModelColumn.TOUGHNESS.text=Toughness +PersonnelTableModelColumn.FATIGUE.text=Fatigue PersonnelTableModelColumn.EDGE.text=Edge PersonnelTableModelColumn.SPA_COUNT.text=SPA Count PersonnelTableModelColumn.IMPLANT_COUNT.text=Implant Count diff --git a/MekHQ/resources/mekhq/resources/PersonViewPanel.properties b/MekHQ/resources/mekhq/resources/PersonViewPanel.properties index 87cf9c7259..944e01ebc3 100644 --- a/MekHQ/resources/mekhq/resources/PersonViewPanel.properties +++ b/MekHQ/resources/mekhq/resources/PersonViewPanel.properties @@ -34,7 +34,7 @@ lblInjuries.text=Injuries:; lblEdge1.text=Edge:; lblEdgeAvail1.text=Edge Available:; lblLoyalty1.text=Loyalty:; -lblCombatFatigue1.text=Combat Fatigue:; +lblFatigue1.text=Fatigue:; lblSpouse1.text=Spouse:; lblFormerSpouses1.text=Former Spouse:; lblChildren1.text=Children:; diff --git a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties index 27ad03ea77..ce95bdd06f 100644 --- a/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties +++ b/MekHQ/resources/mekhq/resources/RetirementDefectionTracker.properties @@ -2,7 +2,7 @@ base.text=Base contract.text=Under Contract desirability.text=Desirability -combatFatigue.text=Combat Fatigue +fatigue.text=Fatigue unitRating.text=Unit Rating missionSuccess.text=Recent Successes missionFailure.text=Recent Failures @@ -20,5 +20,4 @@ tacticalGenius.text=Non-Officer Tactical Genius founder.text=Founder shares.text=Shares administrativeStrain.text=Admin Strain -fatigue.text=Fatigue managementSkill.text=Management Skill diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index b8c6b986f5..fa73d8d556 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3516,20 +3516,20 @@ public boolean newDay() { // check for anything in finances getFinances().newDay(this, yesterday, getLocalDate()); - // Combat Fatigue Region + // Fatigue Region if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { - // even if Combat Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state - processCombatFatigueRecovery(); + // even if Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state + processFatigueRecovery(); } - if (campaignOptions.isUseCombatFatigue()) { + if (campaignOptions.isUseFatigue()) { // we store these values, so this only needs to be checked once per day, // otherwise we would need to check it once for each active person in the campaign fieldKitchenWithinCapacity = getActivePersonnel().size() <= checkFieldKitchenCapacity(); } else { fieldKitchenWithinCapacity = false; } - // End Combat Fatigue Region + // End Fatigue Region MekHQ.triggerEvent(new NewDayEvent(this)); return true; @@ -7014,43 +7014,46 @@ public Integer checkFieldKitchenCapacity() { } /** - * Reports the combat fatigue of a person and, if appropriate, sets setIsRecoveringFromFatigue to true. + * Reports the fatigue of a person and, if appropriate, sets setIsRecoveringFromFatigue to true. * - * @param person the person for which the combat fatigue needs to be reported + * @param person the person for which the fatigue needs to be reported */ - public void reportCombatFatigue(Person person) { - int combatFatigue = person.getCombatFatigue(); + public void reportFatigue(Person person) { + int fatigue = MathUtility.clamp((person.getFatigue() - 1) / 4, 0, 4); - if (getCampaignOptions().isUseCombatFatigue()) { - if ((combatFatigue >= 3) && (combatFatigue < 7)) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("combatFatigueTired.text")); + if (getCampaignOptions().isUseFatigue()) { + if ((fatigue >= 5) && (fatigue < 9)) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueTired.text")); person.setIsRecoveringFromFatigue(true); - } else if ((combatFatigue >= 7) && (combatFatigue < 12)) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("combatFatigueFatigued.text")); + } else if ((fatigue >= 9) && (fatigue < 12)) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueFatigued.text")); person.setIsRecoveringFromFatigue(true); - } else if (combatFatigue >= 12) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("combatFatigueExhausted.text")); + } else if ((fatigue >= 12) && (fatigue < 16)) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueExhausted.text")); + person.setIsRecoveringFromFatigue(true); + } else if (fatigue >= 17) { + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueCritical.text")); person.setIsRecoveringFromFatigue(true); } } else { - if (combatFatigue >=3) { + if (fatigue >=3) { person.setIsRecoveringFromFatigue(true); } } } /** - * Decreases the combat fatigue of all active personnel by 1. + * Decreases the fatigue of all active personnel by 1. */ - public void processCombatFatigueRecovery() { + public void processFatigueRecovery() { for (Person person : getActivePersonnel()) { - if (person.getCombatFatigue() > 0) { - person.setCombatFatigue(person.getCombatFatigue() - 1); + if (person.getFatigue() > 0) { + person.setFatigue(person.getFatigue() - 1); - if ((getCampaignOptions().isUseCombatFatigue()) && (person.getIsRecoveringFromFatigue())) { - if (person.getCombatFatigue() == 0) { + if ((getCampaignOptions().isUseFatigue()) && (person.getIsRecoveringFromFatigue())) { + if (person.getFatigue() == 0) { addReport(person.getHyperlinkedFullTitle() + ' ' - + resources.getString("combatFatigueRecovered.text")); + + resources.getString("fatigueRecovered.text")); } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index ac3412add1..8fd165111d 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -207,7 +207,7 @@ public static String getTransitUnitName(final int unit) { private boolean displayPersonnelLog; private boolean displayScenarioLog; private boolean displayKillRecord; - private boolean useCombatFatigue; + private boolean useFatigue; private Integer fieldKitchenCapacity; // Expanded Personnel Information @@ -666,7 +666,7 @@ public CampaignOptions() { setDisplayPersonnelLog(false); setDisplayScenarioLog(false); setDisplayKillRecord(false); - setUseCombatFatigue(true); + setUseFatigue(true); setFieldKitchenCapacity(150); // Expanded Personnel Information @@ -1422,12 +1422,12 @@ public void setDisplayKillRecord(final boolean displayKillRecord) { this.displayKillRecord = displayKillRecord; } - public boolean isUseCombatFatigue() { - return useCombatFatigue; + public boolean isUseFatigue() { + return useFatigue; } - public void setUseCombatFatigue(final boolean useCombatFatigue) { - this.useCombatFatigue = useCombatFatigue; + public void setUseFatigue(final boolean useFatigue) { + this.useFatigue = useFatigue; } public Integer getFieldKitchenCapacity() { @@ -4138,7 +4138,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayPersonnelLog", isDisplayPersonnelLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayScenarioLog", isDisplayScenarioLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayKillRecord", isDisplayKillRecord()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCombatFatigue", isUseCombatFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFatigue", isUseFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); //endregion General Personnel @@ -4738,8 +4738,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setDisplayScenarioLog(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("displayKillRecord")) { retVal.setDisplayKillRecord(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useCombatFatigue")) { - retVal.setUseCombatFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useFatigue")) { + retVal.setUseFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); //endregion General Personnel diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 9eb84fc16a..00a0e2f046 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -309,11 +309,11 @@ public String getAdministrativeCapacityReport(Campaign campaign) { } /** - * Returns a summary of combat fatigue related facilities. + * Returns a summary of fatigue related facilities. * - * @return A summary of combat fatigue related facilities. + * @return A summary of fatigue related facilities. */ - public String getCombatFatigueSummary() { + public String getFatigueSummary() { int personnelCount = campaign.getActivePersonnel().size(); int fieldKitchenCapacity = campaign.checkFieldKitchenCapacity(); diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index 41b4e31dc1..ff2f0fb230 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -1424,8 +1424,8 @@ public void resolveScenario(ScenarioStatus resolution, String report) { } if (!status.isDead()) { - person.setCombatFatigue(person.getCombatFatigue() + 1); - campaign.reportCombatFatigue(person); + person.setFatigue(person.getFatigue() + 1); + campaign.reportFatigue(person); } if (getCampaign().getCampaignOptions().isUseAdvancedMedical()) { diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index e92813bd03..277e3a9f13 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -125,7 +125,7 @@ public class Person { private LocalDate retirement; private Integer loyalty; - private Integer combatFatigue; + private Integer fatigue; private Boolean isRecoveringFromFatigue; private Skills skills; @@ -331,7 +331,7 @@ public Person(final String preNominal, final String givenName, final String surn lastRankChangeDate = null; retirement = null; loyalty = 0; - combatFatigue = 0; + fatigue = 0; isRecoveringFromFatigue = false; skills = new Skills(); options = new PersonnelOptions(); @@ -1214,12 +1214,12 @@ public void setLoyalty(final Integer loyalty) { this.loyalty = loyalty; } - public Integer getCombatFatigue() { - return combatFatigue; + public Integer getFatigue() { + return fatigue; } - public void setCombatFatigue(final Integer combatFatigue) { - this.combatFatigue = combatFatigue; + public void setFatigue(final Integer fatigue) { + this.fatigue = fatigue; } public boolean getIsRecoveringFromFatigue() { @@ -1633,7 +1633,7 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "lastRankChangeDate", getLastRankChangeDate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "combatFatigue", getCombatFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigue", getFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); @@ -1942,8 +1942,8 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.setRetirement(MHQXMLUtility.parseDate(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("loyalty")) { retVal.loyalty = Integer.parseInt(wn2.getTextContent()); - } else if (wn2.getNodeName().equalsIgnoreCase("combatFatigue")) { - retVal.combatFatigue = Integer.parseInt(wn2.getTextContent()); + } else if (wn2.getNodeName().equalsIgnoreCase("fatigue")) { + retVal.fatigue = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("isRecoveringFromFatigue")) { retVal.isRecoveringFromFatigue = Boolean.parseBoolean(wn2.getTextContent().trim()); } else if (wn2.getNodeName().equalsIgnoreCase("advantages")) { @@ -3663,16 +3663,16 @@ public void generateLoyalty() { } /** - * Calculates the effective combat fatigue for a person. + * Calculates the effective fatigue for a person. * - * @param campaign the campaign for which to calculate the effective combat fatigue - * @return the effective combat fatigue value + * @param campaign the campaign for which to calculate the effective fatigue + * @return the effective fatigue value */ - public int getEffectiveCombatFatigue(Campaign campaign) { - int effectiveCombatFatigue = combatFatigue; + public int getEffectiveFatigue(Campaign campaign) { + int effectiveFatigue = fatigue; if (isClanPersonnel()) { - effectiveCombatFatigue -= 2; + effectiveFatigue -= 2; } switch (getSkillLevel(campaign, false)) { @@ -3682,19 +3682,19 @@ public int getEffectiveCombatFatigue(Campaign campaign) { case REGULAR: break; case VETERAN: - effectiveCombatFatigue--; + effectiveFatigue--; break; case ELITE: case HEROIC: case LEGENDARY: - effectiveCombatFatigue -= 2; + effectiveFatigue -= 2; break; } if (campaign.getFieldKitchenWithinCapacity()) { - effectiveCombatFatigue--; + effectiveFatigue--; } - return effectiveCombatFatigue; + return effectiveFatigue; } } diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 1cdf7a4d80..c09bf64fac 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -20,6 +20,7 @@ */ package mekhq.campaign.personnel; +import megamek.codeUtilities.MathUtility; import megamek.common.Compute; import megamek.common.TargetRoll; import megamek.common.annotations.Nullable; @@ -144,12 +145,12 @@ public Map getTargetNumbers(final @Nullable AtBContract contra targetNumber.addModifier(person.getExperienceLevel(campaign, false), resources.getString("desirability.text")); } - // Combat Fatigue modifier - if (campaign.getCampaignOptions().isUseCombatFatigue()) { - int combatFatigueModifier = person.getCombatFatigue() / 5; + // Fatigue modifier + if (campaign.getCampaignOptions().isUseFatigue()) { + int fatigueModifier = MathUtility.clamp(((person.getFatigue() - 1) / 4) - 1, 0, 3); - if (combatFatigueModifier > 0) { - targetNumber.addModifier(combatFatigueModifier, resources.getString("combatFatigue.text")); + if (fatigueModifier > 0) { + targetNumber.addModifier(fatigueModifier, resources.getString("fatigue.text")); } } diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index c1f243dbc7..737192b2f1 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -468,10 +468,15 @@ private static void processFacilityEffects(StratconTrackState track, public static void processForceDeployment(StratconCoords coords, int forceID, Campaign campaign, StratconTrackState track, boolean sticky) { // plan of action: + // increase fatigue if the coordinates are not currently unrevealed // reveal deployed coordinates // reveal facility in deployed coordinates (and all adjacent coordinates for scout lances) // reveal scenario in deployed coordinates (and all adjacent coordinates for scout lances) + if (!track.getRevealedCoords().contains(coords)) { + increaseFatigue(forceID, campaign); + } + track.getRevealedCoords().add(coords); StratconFacility facility = track.getFacility(coords); @@ -510,8 +515,6 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca } } - increaseCombatFatigue(forceID, campaign); - // the force may be located in other places on the track - clear it out track.unassignForce(forceID); track.assignForce(forceID, coords, campaign.getLocalDate(), sticky); @@ -519,16 +522,16 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca } /** - * Increases the combat fatigue for all crew members per Unit in a force. + * Increases the fatigue for all crew members per Unit in a force. * * @param forceID the ID of the force * @param campaign the campaign */ - private static void increaseCombatFatigue(int forceID, Campaign campaign) { + private static void increaseFatigue(int forceID, Campaign campaign) { for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { for (Person person : campaign.getUnit(unit).getCrew()) { - person.setCombatFatigue(person.getCombatFatigue() + 1); - campaign.reportCombatFatigue(person); + person.setFatigue(person.getFatigue() + 1); + campaign.reportFatigue(person); } } } diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index 052bf06d69..e2736366c5 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -3573,6 +3573,7 @@ public void resetPilotAndEntity() { entity.getCrew().setPortrait(commander.getPortrait().clone(), 0); entity.getCrew().setExternalIdAsString(commander.getId().toString(), 0); entity.getCrew().setToughness(commander.getToughness(), 0); + entity.getCrew().setFatigue(commander.getFatigue(), 0); if (entity instanceof Tank) { ((Tank) entity).setCommanderHit(commander.getHits() > 0); @@ -4004,6 +4005,7 @@ private void assignToCrewSlot(Person p, int slot, String gunType, String driveTy entity.getCrew().setGunneryB(Math.min(Math.max(gunnery, 0), 7), slot); entity.getCrew().setArtillery(Math.min(Math.max(artillery, 0), 7), slot); entity.getCrew().setToughness(p.getToughness(), slot); + entity.getCrew().setFatigue(p.getFatigue(), slot); entity.getCrew().setExternalIdAsString(p.getId().toString(), slot); entity.getCrew().setMissing(false, slot); } diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index 04ca5e85b6..7719dee73b 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -318,7 +318,7 @@ private void initInfoPanel() { gridBagConstraints.weightx = 1.0; panInfo.add(lblCargoSummary, gridBagConstraints); - if (getCampaign().getCampaignOptions().isUseCombatFatigue()) { + if (getCampaign().getCampaignOptions().isUseFatigue()) { if (getCampaign().getCampaignOptions().getFieldKitchenCapacity() > 0) { JLabel lblFacilityCapacitiesHead = new JLabel(resourceMap.getString("lblFacilityCapacities.text")); gridBagConstraints = new GridBagConstraints(); @@ -328,7 +328,7 @@ private void initInfoPanel() { gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new Insets(1, 5, 1, 5); panInfo.add(lblFacilityCapacitiesHead, gridBagConstraints); - lblFacilityCapacities = new JLabel(getCampaign().getCampaignSummary().getCombatFatigueSummary()); + lblFacilityCapacities = new JLabel(getCampaign().getCampaignSummary().getFatigueSummary()); lblFacilityCapacitiesHead.setLabelFor(lblFacilityCapacities); gridBagConstraints.gridx = 1; gridBagConstraints.weightx = 1.0; @@ -556,7 +556,7 @@ private void refreshBasicInfo() { } catch (Exception ignored) {} try { - lblFacilityCapacities.setText(getCampaign().getCampaignSummary().getCombatFatigueSummary()); + lblFacilityCapacities.setText(getCampaign().getCampaignSummary().getFatigueSummary()); } catch (Exception ignored) {} } diff --git a/MekHQ/src/mekhq/gui/dialog/CustomizePersonDialog.java b/MekHQ/src/mekhq/gui/dialog/CustomizePersonDialog.java index 01bcf73ec5..45ec4bdf90 100644 --- a/MekHQ/src/mekhq/gui/dialog/CustomizePersonDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/CustomizePersonDialog.java @@ -86,6 +86,7 @@ public class CustomizePersonDialog extends JDialog implements DialogOptionListen private AbstractMHQScrollablePanel skillsPanel; private AbstractMHQScrollablePanel optionsPanel; private JTextField textToughness; + private JTextField textFatigue; private JTextField textPreNominal; private JTextField textGivenName; private JTextField textSurname; @@ -160,7 +161,9 @@ private void initComponents() { textNickname = new JTextField(); textBloodname = new JTextField(); textToughness = new JTextField(); + textFatigue = new JTextField(); JLabel lblToughness = new JLabel(); + JLabel lblFatigue = new JLabel(); JScrollPane scrOptions = new JScrollPane(); JScrollPane scrSkills = new JScrollPane(); JPanel panButtons = new JPanel(); @@ -637,6 +640,31 @@ public Component getListCellRendererComponent(final JList list, gridBagConstraints.anchor = GridBagConstraints.WEST; gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; panDemog.add(textToughness, gridBagConstraints); + + y++; + } + + lblFatigue.setText(resourceMap.getString("lblFatigue.text")); + lblFatigue.setName("lblFatigue"); + + textFatigue.setText(Integer.toString(person.getFatigue())); + textFatigue.setName("textFatigue"); + + if (campaign.getCampaignOptions().isUseFatigue()) { + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y; + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.insets = new Insets(0, 5, 0, 0); + panDemog.add(lblFatigue, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = y; + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + panDemog.add(textFatigue, gridBagConstraints); + + y++; } JLabel lblUnit = new JLabel(); @@ -967,6 +995,9 @@ private void btnOkActionPerformed(ActionEvent evt) { try { person.setToughness(Integer.parseInt(textToughness.getText())); } catch (NumberFormatException ignored) { } + try { + person.setFatigue(Integer.parseInt(textFatigue.getText())); + } catch (NumberFormatException ignored) { } if (null == choiceOriginalUnit.getSelectedItem()) { person.setOriginalUnitWeight(choiceUnitWeight.getSelectedIndex()); person.setOriginalUnitTech(choiceUnitTech.getSelectedIndex()); diff --git a/MekHQ/src/mekhq/gui/enums/PersonnelTableModelColumn.java b/MekHQ/src/mekhq/gui/enums/PersonnelTableModelColumn.java index e34134e220..5459181e5b 100644 --- a/MekHQ/src/mekhq/gui/enums/PersonnelTableModelColumn.java +++ b/MekHQ/src/mekhq/gui/enums/PersonnelTableModelColumn.java @@ -105,6 +105,7 @@ public enum PersonnelTableModelColumn { TRYING_TO_CONCEIVE("PersonnelTableModelColumn.TRYING_TO_CONCEIVE.text"), IMMORTAL("PersonnelTableModelColumn.IMMORTAL.text"), TOUGHNESS("PersonnelTableModelColumn.TOUGHNESS.text"), + FATIGUE("PersonnelTableModelColumn.FATIGUE.text"), EDGE("PersonnelTableModelColumn.EDGE.text"), SPA_COUNT("PersonnelTableModelColumn.SPA_COUNT.text"), IMPLANT_COUNT("PersonnelTableModelColumn.IMPLANT_COUNT.text"), @@ -368,6 +369,10 @@ public boolean isToughness() { return this == TOUGHNESS; } + public boolean isFatigue() { + return this == FATIGUE; + } + public boolean isEdge() { return this == EDGE; } @@ -658,6 +663,8 @@ public String getCellValue(final Campaign campaign, final PersonnelMarket person return resources.getString(person.getStatus().isDead() ? "NA.text" : (person.isImmortal() ? "Yes.text" : "No.text")); case TOUGHNESS: return Integer.toString(person.getToughness()); + case FATIGUE: + return Integer.toString(person.getFatigue()); case EDGE: return Integer.toString(person.getEdge()); case SPA_COUNT: @@ -943,6 +950,7 @@ public boolean isVisible(final Campaign campaign, final PersonnelTabView view, case FIRST_NAME: case LAST_NAME: case TOUGHNESS: + case FATIGUE: case EDGE: case SPA_COUNT: case IMPLANT_COUNT: diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 19a79c32a5..d98c12f4ab 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -211,9 +211,9 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkDisplayScenarioLog; private JCheckBox chkDisplayKillRecord; - private JPanel combatFatiguePanel; - private JLabel lblCombatFatigueWarning; - private JCheckBox chkUseCombatFatigue; + private JPanel fatiguePanel; + private JLabel lblFatigueWarning; + private JCheckBox chkUseFatigue; private JLabel lblFieldKitchenCapacity; private JSpinner spnFieldKitchenCapacity; @@ -3279,22 +3279,22 @@ private JPanel createGeneralPersonnelPanel() { chkDisplayKillRecord.setToolTipText(resources.getString("chkDisplayKillRecord.toolTipText")); chkDisplayKillRecord.setName("chkDisplayKillRecord"); - chkUseCombatFatigue = new JCheckBox(resources.getString("chkUseCombatFatigue.text")); - chkUseCombatFatigue.setToolTipText(resources.getString("chkUseCombatFatigue.toolTipText")); - chkUseCombatFatigue.setName("chkUseCombatFatigue"); - chkUseCombatFatigue.addActionListener(evt -> { - final boolean isEnabled = chkUseCombatFatigue.isSelected(); + chkUseFatigue = new JCheckBox(resources.getString("chkUseFatigue.text")); + chkUseFatigue.setToolTipText(resources.getString("chkUseFatigue.toolTipText")); + chkUseFatigue.setName("chkUseFatigue"); + chkUseFatigue.addActionListener(evt -> { + final boolean isEnabled = chkUseFatigue.isSelected(); // general handler - for (Component component : combatFatiguePanel.getComponents()) { + for (Component component : fatiguePanel.getComponents()) { component.setEnabled(isEnabled); } // border handler - combatFatiguePanel.setEnabled(isEnabled); + fatiguePanel.setEnabled(isEnabled); }); - combatFatiguePanel = createCombatFatiguePanel(); + fatiguePanel = createFatiguePanel(); // Layout the Panel final JPanel panel = new JPanel(); @@ -3325,8 +3325,8 @@ private JPanel createGeneralPersonnelPanel() { .addComponent(chkDisplayPersonnelLog) .addComponent(chkDisplayScenarioLog) .addComponent(chkDisplayKillRecord) - .addComponent(chkUseCombatFatigue) - .addComponent(combatFatiguePanel) + .addComponent(chkUseFatigue) + .addComponent(fatiguePanel) ); layout.setHorizontalGroup( @@ -3348,41 +3348,41 @@ private JPanel createGeneralPersonnelPanel() { .addComponent(chkDisplayPersonnelLog) .addComponent(chkDisplayScenarioLog) .addComponent(chkDisplayKillRecord) - .addComponent(chkUseCombatFatigue) - .addComponent(combatFatiguePanel) + .addComponent(chkUseFatigue) + .addComponent(fatiguePanel) ); return panel; } - private JPanel createCombatFatiguePanel() { - lblCombatFatigueWarning = new JLabel(resources.getString("lblCombatFatigueWarning.text")); - lblCombatFatigueWarning.setName("lblCombatFatigueWarning"); - lblCombatFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + private JPanel createFatiguePanel() { + lblFatigueWarning = new JLabel(resources.getString("lblFatigueWarning.text")); + lblFatigueWarning.setName("lblFatigueWarning"); + lblFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseFatigue()); lblFieldKitchenCapacity = new JLabel(resources.getString("lblFieldKitchenCapacity.text")); lblFieldKitchenCapacity.setToolTipText(resources.getString("lblFieldKitchenCapacity.toolTipText")); lblFieldKitchenCapacity.setName("lblFieldKitchenCapacity"); - lblFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + lblFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseFatigue()); spnFieldKitchenCapacity = new JSpinner(new SpinnerNumberModel(150, 0, 450, 1)); spnFieldKitchenCapacity.setToolTipText(resources.getString("lblFieldKitchenCapacity.toolTipText")); spnFieldKitchenCapacity.setName("spnFieldKitchenCapacity"); - spnFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + spnFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseFatigue()); - combatFatiguePanel = new JPanel(); - combatFatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("combatFatiguePanel.title"))); - combatFatiguePanel.setName("combatFatiguePanel"); - combatFatiguePanel.setEnabled(campaign.getCampaignOptions().isUseCombatFatigue()); + fatiguePanel = new JPanel(); + fatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); + fatiguePanel.setName("fatiguePanel"); + fatiguePanel.setEnabled(campaign.getCampaignOptions().isUseFatigue()); - final GroupLayout layout = new GroupLayout(combatFatiguePanel); + final GroupLayout layout = new GroupLayout(fatiguePanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); - combatFatiguePanel.setLayout(layout); + fatiguePanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() - .addComponent(lblCombatFatigueWarning) + .addComponent(lblFatigueWarning) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity, Alignment.LEADING)) @@ -3390,13 +3390,13 @@ private JPanel createCombatFatiguePanel() { layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) - .addComponent(lblCombatFatigueWarning) + .addComponent(lblFatigueWarning) .addGroup(layout.createSequentialGroup() .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity)) ); - return combatFatiguePanel; + return fatiguePanel; } private JPanel createExpandedPersonnelInformationPanel() { @@ -7292,7 +7292,7 @@ public void setOptions(@Nullable CampaignOptions options, chkDisplayScenarioLog.setSelected(options.isDisplayScenarioLog()); chkDisplayKillRecord.setSelected(options.isDisplayKillRecord()); - chkUseCombatFatigue.setSelected(options.isUseCombatFatigue()); + chkUseFatigue.setSelected(options.isUseFatigue()); spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); // Expanded Personnel Information @@ -7930,7 +7930,7 @@ public void updateOptions() { options.setDisplayScenarioLog(chkDisplayScenarioLog.isSelected()); options.setDisplayKillRecord(chkDisplayKillRecord.isSelected()); - options.setUseCombatFatigue(chkUseCombatFatigue.isSelected()); + options.setUseFatigue(chkUseFatigue.isSelected()); options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); // Expanded Personnel Information diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index 0e6f69e5f6..c8b90cb1ae 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -1207,8 +1207,8 @@ private JPanel fillSkills() { JLabel lblLoyalty1 = new JLabel(); JLabel lblLoyalty2 = new JLabel(); - JLabel lblCombatFatigue1 = new JLabel(); - JLabel lblCombatFatigue2 = new JLabel(); + JLabel lblFatigue1 = new JLabel(); + JLabel lblFatigue2 = new JLabel(); // education JLabel lblEducationLevel1 = new JLabel(); @@ -1423,32 +1423,32 @@ private JPanel fillSkills() { firsty++; } - if ((campaign.getCampaignOptions().isUseCombatFatigue()) - && (person.getEffectiveCombatFatigue(campaign) > 0)) { - lblCombatFatigue1.setName("lblCombatFatigue1"); - lblCombatFatigue1.setText(resourceMap.getString("lblCombatFatigue1.text")); + if ((campaign.getCampaignOptions().isUseFatigue()) + && (person.getEffectiveFatigue(campaign) > 0)) { + lblFatigue1.setName("lblFatigue1"); + lblFatigue1.setText(resourceMap.getString("lblFatigue1.text")); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = firsty; gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - pnlSkills.add(lblCombatFatigue1, gridBagConstraints); + pnlSkills.add(lblFatigue1, gridBagConstraints); - StringBuilder combatFatigueDisplay = new StringBuilder(person.getCombatFatigue()); - int effectiveCombatFatigue = person.getEffectiveCombatFatigue(campaign); - int combatFatigueTurnoverModifier = effectiveCombatFatigue / 5; + StringBuilder fatigueDisplay = new StringBuilder(person.getFatigue()); + int effectiveFatigue = person.getEffectiveFatigue(campaign); + int fatigueTurnoverModifier = effectiveFatigue / 5; - if (person.getCombatFatigue() != effectiveCombatFatigue) { - combatFatigueDisplay.append(" (").append(effectiveCombatFatigue).append(')'); + if (person.getFatigue() != effectiveFatigue) { + fatigueDisplay.append(" (").append(effectiveFatigue).append(')'); } - if (combatFatigueTurnoverModifier > 0) { - combatFatigueDisplay.append(" -").append(combatFatigueTurnoverModifier); + if (fatigueTurnoverModifier > 0) { + fatigueDisplay.append(" -").append(fatigueTurnoverModifier); } - lblCombatFatigue2.setName("lblCombatFatigue2"); - lblCombatFatigue2.setText(combatFatigueDisplay.toString()); - lblCombatFatigue2.setLabelFor(lblCombatFatigue2); + lblFatigue2.setName("lblFatigue2"); + lblFatigue2.setText(fatigueDisplay.toString()); + lblFatigue2.setLabelFor(lblFatigue2); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = firsty; @@ -1457,7 +1457,7 @@ private JPanel fillSkills() { gridBagConstraints.insets = new Insets(0, 10, 0, 0); gridBagConstraints.fill = GridBagConstraints.NONE; gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - pnlSkills.add(lblCombatFatigue2, gridBagConstraints); + pnlSkills.add(lblFatigue2, gridBagConstraints); firsty++; } diff --git a/MekHQ/src/mekhq/utilities/MHQXMLUtility.java b/MekHQ/src/mekhq/utilities/MHQXMLUtility.java index 6f90ed364c..3183da1610 100644 --- a/MekHQ/src/mekhq/utilities/MHQXMLUtility.java +++ b/MekHQ/src/mekhq/utilities/MHQXMLUtility.java @@ -488,6 +488,11 @@ public static void writeEntityWithCrewToXML(PrintWriter pw, int indentLvl, Entit if (tgtEnt.getCrew().getToughness(pos) != 0) { crew.append("\" " + MULParser.ATTR_TOUGH + "=\"").append(tgtEnt.getCrew().getToughness(pos)); } + + if (tgtEnt.getCrew().getFatigue(pos) != 0) { + crew.append("\" " + MULParser.ATTR_FATIGUE + "=\"").append(tgtEnt.getCrew().getFatigue(pos)); + } + if (tgtEnt.getCrew().isDead(pos) || tgtEnt.getCrew().getHits(pos) >= Crew.DEATH) { crew.append("\" " + MULParser.ATTR_HITS + "=\"" + MULParser.VALUE_DEAD + ""); } else if (tgtEnt.getCrew().getHits(pos) > 0) { From d52a8859020f4465b26fa5f2f3c5ce2917f32334 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 11:24:39 -0500 Subject: [PATCH 033/101] Updated fatigue handling and display The code has been updated to improve the naming and display of fatigue related features. The attribute "Fatigue Points" has been modified to "Fatigue". --- .../mekhq/resources/CustomizePersonDialog.properties | 1 + MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CustomizePersonDialog.properties b/MekHQ/resources/mekhq/resources/CustomizePersonDialog.properties index 5923bf4234..024b3dec2f 100644 --- a/MekHQ/resources/mekhq/resources/CustomizePersonDialog.properties +++ b/MekHQ/resources/mekhq/resources/CustomizePersonDialog.properties @@ -9,6 +9,7 @@ lblGunnery.text=Gunnery: lblInitB.text=Initiative Bonus: lblCommandB.text=Tactics: lblToughness.text=Toughness: +lblFatigue.text=Fatigue: lblArtillery.text=Artillery: lblBday.text=Birthdate: lblRecruitment.text=Recruited: diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index c8b90cb1ae..b99a2d31ae 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -1434,16 +1434,18 @@ private JPanel fillSkills() { gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; pnlSkills.add(lblFatigue1, gridBagConstraints); - StringBuilder fatigueDisplay = new StringBuilder(person.getFatigue()); + StringBuilder fatigueDisplay = new StringBuilder(); int effectiveFatigue = person.getEffectiveFatigue(campaign); int fatigueTurnoverModifier = effectiveFatigue / 5; + fatigueDisplay.append(person.getFatigue()); + if (person.getFatigue() != effectiveFatigue) { - fatigueDisplay.append(" (").append(effectiveFatigue).append(')'); + fatigueDisplay.append(" / ").append(effectiveFatigue); } if (fatigueTurnoverModifier > 0) { - fatigueDisplay.append(" -").append(fatigueTurnoverModifier); + fatigueDisplay.append(" (-").append(fatigueTurnoverModifier).append(')'); } lblFatigue2.setName("lblFatigue2"); From 9fd621bbe053dcbc880dd91e4bfdfd510c89e3fa Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 11:49:24 -0500 Subject: [PATCH 034/101] Implemented fatigue rate control and prevented duplicate fatigue increments in StratconRulesManager Improved the fatigue system by adding a fatigue rate feature to regulate the rate at which fatigue points are gained. Adjusted the StratCon rules manager logic to prevent duplicate increases of fatigue in the same action. --- .../CampaignOptionsDialog.properties | 4 +++- MekHQ/src/mekhq/campaign/CampaignOptions.java | 13 ++++++++++++ .../campaign/ResolveScenarioTracker.java | 2 +- .../stratcon/StratconRulesManager.java | 11 +++++++++- MekHQ/src/mekhq/campaign/unit/Unit.java | 8 +++++++- .../mekhq/gui/panes/CampaignOptionsPane.java | 20 +++++++++++++++++++ 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index cef7d09da3..ffb7badacd 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -159,9 +159,11 @@ chkDisplayKillRecord.text=Expand Kill Record by Default chkDisplayKillRecord.toolTipText=If enabled, the kill record will be expanded by default fatiguePanel.title=Fatigue (CamOps) -lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
This option requires Fatigue to be enabled in MegaMek.
Interacts with Turnover and Retention. +lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
This option requires Fatigue to be enabled in MegaMek.
Interacts with optional Turnover and Retention system. chkUseFatigue.text=Enable Fatigue chkUseFatigue.toolTipText=If enabled, combat personnel will gain Fatigue whenever they are deployed to a Scenario or entering an unexplored StratCon hex. +lblFatigueRate.text=Fatigue Rate +lblFatigueRate.toolTipText=How many fatigue points should be gained per Scenario or unexplored StratCon hex? lblFieldKitchenCapacity.text=Field Kitchen Capacity lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen? Reduces effective Fatigue by 1. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 8fd165111d..47bf7e4cde 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -208,6 +208,7 @@ public static String getTransitUnitName(final int unit) { private boolean displayScenarioLog; private boolean displayKillRecord; private boolean useFatigue; + private Integer fatigueRate; private Integer fieldKitchenCapacity; // Expanded Personnel Information @@ -667,6 +668,7 @@ public CampaignOptions() { setDisplayScenarioLog(false); setDisplayKillRecord(false); setUseFatigue(true); + setFatigueRate(1); setFieldKitchenCapacity(150); // Expanded Personnel Information @@ -1430,6 +1432,14 @@ public void setUseFatigue(final boolean useFatigue) { this.useFatigue = useFatigue; } + public Integer getFatigueRate() { + return fatigueRate; + } + + public void setFatigueRate(final Integer fatigueRate) { + this.fatigueRate = fatigueRate; + } + public Integer getFieldKitchenCapacity() { return fieldKitchenCapacity; } @@ -4139,6 +4149,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayScenarioLog", isDisplayScenarioLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayKillRecord", isDisplayKillRecord()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFatigue", isUseFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueRate", getFatigueRate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); //endregion General Personnel @@ -4740,6 +4751,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setDisplayKillRecord(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useFatigue")) { retVal.setUseFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("fatigueRate")) { + retVal.setFatigueRate(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); //endregion General Personnel diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index ff2f0fb230..b3ff4f27d1 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -1424,7 +1424,7 @@ public void resolveScenario(ScenarioStatus resolution, String report) { } if (!status.isDead()) { - person.setFatigue(person.getFatigue() + 1); + person.setFatigue(person.getFatigue() + campaign.getCampaignOptions().getFatigueRate()); campaign.reportFatigue(person); } diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 737192b2f1..6d5a15399c 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -473,8 +473,12 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca // reveal facility in deployed coordinates (and all adjacent coordinates for scout lances) // reveal scenario in deployed coordinates (and all adjacent coordinates for scout lances) + // we want to ensure we only increase Fatigue once + boolean hasFatigueIncreased = false; + if (!track.getRevealedCoords().contains(coords)) { increaseFatigue(forceID, campaign); + hasFatigueIncreased = true; } track.getRevealedCoords().add(coords); @@ -511,6 +515,11 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca MekHQ.triggerEvent(new ScenarioChangedEvent(scenario.getBackingScenario())); } + if ((!track.getRevealedCoords().contains(checkCoords)) && (!hasFatigueIncreased)) { + increaseFatigue(forceID, campaign); + hasFatigueIncreased = true; + } + track.getRevealedCoords().add(coords.translate(direction)); } } @@ -530,7 +539,7 @@ public static void processForceDeployment(StratconCoords coords, int forceID, Ca private static void increaseFatigue(int forceID, Campaign campaign) { for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { for (Person person : campaign.getUnit(unit).getCrew()) { - person.setFatigue(person.getFatigue() + 1); + person.setFatigue(person.getFatigue() + campaign.getCampaignOptions().getFatigueRate()); campaign.reportFatigue(person); } } diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index e2736366c5..bcef8fa889 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -4005,7 +4005,13 @@ private void assignToCrewSlot(Person p, int slot, String gunType, String driveTy entity.getCrew().setGunneryB(Math.min(Math.max(gunnery, 0), 7), slot); entity.getCrew().setArtillery(Math.min(Math.max(artillery, 0), 7), slot); entity.getCrew().setToughness(p.getToughness(), slot); - entity.getCrew().setFatigue(p.getFatigue(), slot); + + if (campaign.getCampaignOptions().isUseFatigue()) { + entity.getCrew().setFatigue(p.getFatigue(), slot); + } else { + entity.getCrew().setFatigue(0, slot); + } + entity.getCrew().setExternalIdAsString(p.getId().toString(), slot); entity.getCrew().setMissing(false, slot); } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index d98c12f4ab..f3d1a12a87 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -214,6 +214,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JPanel fatiguePanel; private JLabel lblFatigueWarning; private JCheckBox chkUseFatigue; + private JLabel lblFatigueRate; + private JSpinner spnFatigueRate; private JLabel lblFieldKitchenCapacity; private JSpinner spnFieldKitchenCapacity; @@ -3360,6 +3362,16 @@ private JPanel createFatiguePanel() { lblFatigueWarning.setName("lblFatigueWarning"); lblFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + lblFatigueRate = new JLabel(resources.getString("lblFatigueRate.text")); + lblFatigueRate.setToolTipText(resources.getString("lblFatigueRate.toolTipText")); + lblFatigueRate.setName("lblFatigueRate"); + lblFatigueRate.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + + spnFatigueRate = new JSpinner(new SpinnerNumberModel(150, 0, 450, 1)); + spnFatigueRate.setToolTipText(resources.getString("lblFatigueRate.toolTipText")); + spnFatigueRate.setName("spnFatigueRate"); + spnFatigueRate.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + lblFieldKitchenCapacity = new JLabel(resources.getString("lblFieldKitchenCapacity.text")); lblFieldKitchenCapacity.setToolTipText(resources.getString("lblFieldKitchenCapacity.toolTipText")); lblFieldKitchenCapacity.setName("lblFieldKitchenCapacity"); @@ -3383,6 +3395,9 @@ private JPanel createFatiguePanel() { layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(lblFatigueWarning) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblFatigueRate) + .addComponent(spnFatigueRate, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity, Alignment.LEADING)) @@ -3391,6 +3406,9 @@ private JPanel createFatiguePanel() { layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(lblFatigueWarning) + .addGroup(layout.createSequentialGroup() + .addComponent(lblFatigueRate) + .addComponent(spnFatigueRate)) .addGroup(layout.createSequentialGroup() .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity)) @@ -7293,6 +7311,7 @@ public void setOptions(@Nullable CampaignOptions options, chkDisplayKillRecord.setSelected(options.isDisplayKillRecord()); chkUseFatigue.setSelected(options.isUseFatigue()); + spnFatigueRate.setValue(options.getFatigueRate()); spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); // Expanded Personnel Information @@ -7932,6 +7951,7 @@ public void updateOptions() { options.setUseFatigue(chkUseFatigue.isSelected()); options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); + options.setFatigueRate((Integer) spnFatigueRate.getValue()); // Expanded Personnel Information options.setUseTimeInService(chkUseTimeInService.isSelected()); From 73e3a2c1c988e8a14736900b2910ec0a2f0ee5b6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 13:17:48 -0500 Subject: [PATCH 035/101] Updated fatigue management and recovery process This commit refactors the fatigue management to process fatigue actions based on its level and condition. It also adds a fatigue leave threshold to automatically assign personnel to leave status once fatigue reaches a specified value. --- .../mekhq/resources/Campaign.properties | 6 +- .../CampaignOptionsDialog.properties | 2 + MekHQ/src/mekhq/campaign/Campaign.java | 87 ++++++++++++++----- MekHQ/src/mekhq/campaign/CampaignOptions.java | 13 +++ .../campaign/ResolveScenarioTracker.java | 2 +- .../personnel/enums/PersonnelStatus.java | 7 ++ .../stratcon/StratconRulesManager.java | 2 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 22 ++++- 8 files changed, 112 insertions(+), 29 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 55f2f043ba..7184ff3dd4 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -39,10 +39,10 @@ LayeredForceIconLayer.LOGO.text=Logos LayeredForceIconLayer.LOGO.toolTipText=This tab contains canon faction logos that can be added to the center of a force icon. #### Fatigue Resources -fatigueTired.text=is tired and in need of rest. -fatigueFatigued.text=is badly fatigued and should be removed from active duty. +fatigueTired.text=is tired and in need of a break. +fatigueFatigued.text=is badly fatigued and in need of rest. fatigueExhausted.text=is exhausted and in desperate need of rest. -fatigueCritical.text=is critically fatigued and in critical need of rest. +fatigueCritical.text=is critically fatigued and should be immediately removed from active duty. fatigueRecovered.text=is no longer fatigued. #### Unsorted Campaign Resources diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index ffb7badacd..e2dfc84f50 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -166,6 +166,8 @@ lblFatigueRate.text=Fatigue Rate lblFatigueRate.toolTipText=How many fatigue points should be gained per Scenario or unexplored StratCon hex? lblFieldKitchenCapacity.text=Field Kitchen Capacity lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen? Reduces effective Fatigue by 1. +lblFatigueLeaveThreshold.text=Automatic Leave Threshold (unofficial) +lblFatigueLeaveThreshold.toolTipText=Automatically assign personnel to Leave status once fatigue (including modifiers) has reached this value.
Personnel heal fatigue twice as fast while on leave and will automatically return to active duty once their fatigue has returned to 0.
Set to 0 to disable. # Expanded Personnel Information expandedPersonnelInformationPanel.title=Expanded Personnel Information diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index fa73d8d556..81158d1113 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3517,10 +3517,8 @@ public boolean newDay() { getFinances().newDay(this, yesterday, getLocalDate()); // Fatigue Region - if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { - // even if Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state - processFatigueRecovery(); - } + // even if Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state + processFatigueRecovery(); if (campaignOptions.isUseFatigue()) { // we store these values, so this only needs to be checked once per day, @@ -7013,47 +7011,90 @@ public Integer checkFieldKitchenCapacity() { return fieldKitchenCount * campaignOptions.getFieldKitchenCapacity(); } + /** - * Reports the fatigue of a person and, if appropriate, sets setIsRecoveringFromFatigue to true. + * Reports the fatigue level of a person and perform actions based on the fatigue level. * - * @param person the person for which the fatigue needs to be reported + * @param person The person for which the fatigue level is reported. */ - public void reportFatigue(Person person) { - int fatigue = MathUtility.clamp((person.getFatigue() - 1) / 4, 0, 4); + public void processFatigueActions(Person person) { + int effectiveFatigue = person.getEffectiveFatigue(this); if (getCampaignOptions().isUseFatigue()) { - if ((fatigue >= 5) && (fatigue < 9)) { + if ((effectiveFatigue >= 5) && (effectiveFatigue < 9)) { addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueTired.text")); person.setIsRecoveringFromFatigue(true); - } else if ((fatigue >= 9) && (fatigue < 12)) { + } else if ((effectiveFatigue >= 9) && (effectiveFatigue < 12)) { addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueFatigued.text")); person.setIsRecoveringFromFatigue(true); - } else if ((fatigue >= 12) && (fatigue < 16)) { + } else if ((effectiveFatigue >= 12) && (effectiveFatigue < 16)) { addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueExhausted.text")); person.setIsRecoveringFromFatigue(true); - } else if (fatigue >= 17) { + } else if (effectiveFatigue >= 17) { addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueCritical.text")); person.setIsRecoveringFromFatigue(true); } - } else { - if (fatigue >=3) { - person.setIsRecoveringFromFatigue(true); - } + } + + if ((campaignOptions.getFatigueLeaveThreshold() != 0) + && (effectiveFatigue >= campaignOptions.getFatigueLeaveThreshold())) { + person.changeStatus(this, getLocalDate(), PersonnelStatus.ON_LEAVE); } } /** - * Decreases the fatigue of all active personnel by 1. + * Decreases the fatigue of all active personnel. + * Fatigue recovery is determined based on the following criteria: + * - Fatigue is adjusted based on various conditions: + * - If it is Monday + * - Fatigue is decreased by an 1 + * - If 'person' is on leave, decreased fatigue by an additional 1 + * - If there are no active contracts, decreased fatigue by an additional 1 + * - If campaign options include fatigue usage and 'person' is recovering from fatigue: + * - If fatigue reaches 0, trigger a report indicating fatigue recovery + * - If fatigue leave threshold is not 0, and 'person' is on leave, change status to active */ public void processFatigueRecovery() { - for (Person person : getActivePersonnel()) { - if (person.getFatigue() > 0) { - person.setFatigue(person.getFatigue() - 1); + List filteredPersonnel = getPersonnel().stream() + .filter(person -> !person.getStatus().isDepartedUnit()) + .collect(Collectors.toList()); + + + for (Person person : filteredPersonnel) { + if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { + if (person.getFatigue() > 0) { + int fatigueAdjustment = 1; + + if (person.getStatus().isOnLeave()) { + fatigueAdjustment++; + } - if ((getCampaignOptions().isUseFatigue()) && (person.getIsRecoveringFromFatigue())) { + if (getActiveContracts().isEmpty()) { + fatigueAdjustment++; + } + + person.setFatigue(person.getFatigue() - fatigueAdjustment); + + if (person.getFatigue() < 0) { + person.setFatigue(0); + } + } + } + + if (getCampaignOptions().isUseFatigue()) { + if ((!person.getStatus().isOnLeave()) && (!person.getIsRecoveringFromFatigue())) { + processFatigueActions(person); + } + + if (person.getIsRecoveringFromFatigue()) { if (person.getFatigue() == 0) { - addReport(person.getHyperlinkedFullTitle() + ' ' - + resources.getString("fatigueRecovered.text")); + addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueRecovered.text")); + + person.setIsRecoveringFromFatigue(false); + + if ((getCampaignOptions().getFatigueLeaveThreshold() != 0) && (person.getStatus().isOnLeave())) { + person.changeStatus(this, getLocalDate(), PersonnelStatus.ACTIVE); + } } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 47bf7e4cde..c7813682dc 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -210,6 +210,7 @@ public static String getTransitUnitName(final int unit) { private boolean useFatigue; private Integer fatigueRate; private Integer fieldKitchenCapacity; + private Integer fatigueLeaveThreshold; // Expanded Personnel Information private boolean useTimeInService; @@ -670,6 +671,7 @@ public CampaignOptions() { setUseFatigue(true); setFatigueRate(1); setFieldKitchenCapacity(150); + setFatigueLeaveThreshold(13); // Expanded Personnel Information setUseTimeInService(false); @@ -1447,6 +1449,14 @@ public Integer getFieldKitchenCapacity() { public void setFieldKitchenCapacity(final Integer fieldKitchenCapacity) { this.fieldKitchenCapacity = fieldKitchenCapacity; } + + public Integer getFatigueLeaveThreshold() { + return fatigueLeaveThreshold; + } + + public void setFatigueLeaveThreshold(final Integer fatigueLeaveThreshold) { + this.fatigueLeaveThreshold = fatigueLeaveThreshold; + } //endregion General Personnel //region Expanded Personnel Information @@ -4151,6 +4161,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFatigue", isUseFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueRate", getFatigueRate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLeaveThreshold", getFatigueLeaveThreshold()); //endregion General Personnel //region Expanded Personnel Information @@ -4755,6 +4766,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setFatigueRate(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("fatigueLeaveThreshold")) { + retVal.setFatigueLeaveThreshold(Integer.parseInt(wn2.getTextContent().trim())); //endregion General Personnel //region Expanded Personnel Information diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index b3ff4f27d1..2d08d967d3 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -1425,7 +1425,7 @@ public void resolveScenario(ScenarioStatus resolution, String report) { if (!status.isDead()) { person.setFatigue(person.getFatigue() + campaign.getCampaignOptions().getFatigueRate()); - campaign.reportFatigue(person); + campaign.processFatigueActions(person); } if (getCampaign().getCampaignOptions().isUseAdvancedMedical()) { diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/PersonnelStatus.java b/MekHQ/src/mekhq/campaign/personnel/enums/PersonnelStatus.java index a2855f6a83..e18c96c4f1 100644 --- a/MekHQ/src/mekhq/campaign/personnel/enums/PersonnelStatus.java +++ b/MekHQ/src/mekhq/campaign/personnel/enums/PersonnelStatus.java @@ -174,6 +174,13 @@ public boolean isAbsent() { return isMIA() || isPoW() || isOnLeave() || isAWOL() || isStudent() || isMissing(); } + /** + * @return true if a person has left the unit, otherwise false + */ + public boolean isDepartedUnit() { + return isDead() || isAWOL() || isRetired() || isDeserted() || isMissing(); + } + /** * @return true if a person is dead, otherwise false */ diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 6d5a15399c..8e9bc5bbd4 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -540,7 +540,7 @@ private static void increaseFatigue(int forceID, Campaign campaign) { for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { for (Person person : campaign.getUnit(unit).getCrew()) { person.setFatigue(person.getFatigue() + campaign.getCampaignOptions().getFatigueRate()); - campaign.reportFatigue(person); + campaign.processFatigueActions(person); } } } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index f3d1a12a87..e495e1195e 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -218,6 +218,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JSpinner spnFatigueRate; private JLabel lblFieldKitchenCapacity; private JSpinner spnFieldKitchenCapacity; + private JLabel lblFatigueLeaveThreshold; + private JSpinner spnFatigueLeaveThreshold; // Expanded Personnel private JCheckBox chkUseTimeInService; @@ -3382,6 +3384,16 @@ private JPanel createFatiguePanel() { spnFieldKitchenCapacity.setName("spnFieldKitchenCapacity"); spnFieldKitchenCapacity.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + lblFatigueLeaveThreshold = new JLabel(resources.getString("lblFatigueLeaveThreshold.text")); + lblFatigueLeaveThreshold.setToolTipText(resources.getString("lblFatigueLeaveThreshold.toolTipText")); + lblFatigueLeaveThreshold.setName("lblFatigueLeaveThreshold"); + lblFatigueLeaveThreshold.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + + spnFatigueLeaveThreshold = new JSpinner(new SpinnerNumberModel(13, 0, 17, 1)); + spnFatigueLeaveThreshold.setToolTipText(resources.getString("lblFatigueLeaveThreshold.toolTipText")); + spnFatigueLeaveThreshold.setName("spnFatigueLeaveThreshold"); + spnFatigueLeaveThreshold.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + fatiguePanel = new JPanel(); fatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); fatiguePanel.setName("fatiguePanel"); @@ -3401,6 +3413,9 @@ private JPanel createFatiguePanel() { .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblFatigueLeaveThreshold) + .addComponent(spnFatigueLeaveThreshold, Alignment.LEADING)) ); layout.setHorizontalGroup( @@ -3412,6 +3427,9 @@ private JPanel createFatiguePanel() { .addGroup(layout.createSequentialGroup() .addComponent(lblFieldKitchenCapacity) .addComponent(spnFieldKitchenCapacity)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblFatigueLeaveThreshold) + .addComponent(spnFatigueLeaveThreshold)) ); return fatiguePanel; @@ -7313,6 +7331,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseFatigue.setSelected(options.isUseFatigue()); spnFatigueRate.setValue(options.getFatigueRate()); spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); + spnFatigueLeaveThreshold.setValue(options.getFatigueLeaveThreshold()); // Expanded Personnel Information if (chkUseTimeInService.isSelected() != options.isUseTimeInService()) { @@ -7950,8 +7969,9 @@ public void updateOptions() { options.setDisplayKillRecord(chkDisplayKillRecord.isSelected()); options.setUseFatigue(chkUseFatigue.isSelected()); - options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); options.setFatigueRate((Integer) spnFatigueRate.getValue()); + options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); + options.setFatigueLeaveThreshold((Integer) spnFatigueLeaveThreshold.getValue()); // Expanded Personnel Information options.setUseTimeInService(chkUseTimeInService.isSelected()); From 697e9b9b5e5aa669b8985642607fb06df97c876c Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 13:56:31 -0500 Subject: [PATCH 036/101] Refactor Fatigue-related code and GUI structure This commit regroups Fatigue-related code in both the resources and the campaign options. It also modifies the GUI to have the activation checkbox for Fatigue features outside of the panel containing its options. --- .../CampaignOptionsDialog.properties | 23 +++--- MekHQ/src/mekhq/campaign/CampaignOptions.java | 27 +++++-- .../mekhq/gui/panes/CampaignOptionsPane.java | 79 ++++++++++++------- 3 files changed, 81 insertions(+), 48 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index e2dfc84f50..327839b415 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -158,17 +158,6 @@ chkDisplayScenarioLog.toolTipText=If enabled, the scenario log will be expanded chkDisplayKillRecord.text=Expand Kill Record by Default chkDisplayKillRecord.toolTipText=If enabled, the kill record will be expanded by default -fatiguePanel.title=Fatigue (CamOps) -lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
This option requires Fatigue to be enabled in MegaMek.
Interacts with optional Turnover and Retention system. -chkUseFatigue.text=Enable Fatigue -chkUseFatigue.toolTipText=If enabled, combat personnel will gain Fatigue whenever they are deployed to a Scenario or entering an unexplored StratCon hex. -lblFatigueRate.text=Fatigue Rate -lblFatigueRate.toolTipText=How many fatigue points should be gained per Scenario or unexplored StratCon hex? -lblFieldKitchenCapacity.text=Field Kitchen Capacity -lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen? Reduces effective Fatigue by 1. -lblFatigueLeaveThreshold.text=Automatic Leave Threshold (unofficial) -lblFatigueLeaveThreshold.toolTipText=Automatically assign personnel to Leave status once fatigue (including modifiers) has reached this value.
Personnel heal fatigue twice as fast while on leave and will automatically return to active duty once their fatigue has returned to 0.
Set to 0 to disable. - # Expanded Personnel Information expandedPersonnelInformationPanel.title=Expanded Personnel Information chkUseTimeInService.text=Track Time In Service @@ -214,6 +203,18 @@ chkAtBPrisonerDefection.toolTipText=This adds a random roll to determine i chkAtBPrisonerRansom.text=Enable AtB Prisoner Ransom chkAtBPrisonerRansom.toolTipText=Prisoners can be ransomed back to their previous faction as per the AtB ruleset. +# Fatigue +fatiguePanel.title=Fatigue (CamOps) +lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
This option requires Fatigue to be enabled in MegaMek. +chkUseFatigue.text=Enable Fatigue +chkUseFatigue.toolTipText=If enabled, combat personnel will gain Fatigue whenever they are deployed to a Scenario or entering an unexplored StratCon hex. +lblFatigueRate.text=Fatigue Rate +lblFatigueRate.toolTipText=How many fatigue points should be gained per Scenario or unexplored StratCon hex? +lblFieldKitchenCapacity.text=Field Kitchen Capacity +lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen? Reduces effective Fatigue by 1. +lblFatigueLeaveThreshold.text=Automatic Leave Threshold (unofficial) +lblFatigueLeaveThreshold.toolTipText=Automatically assign personnel to Leave status once fatigue (including modifiers) has reached this value.
Personnel heal fatigue twice as fast while on leave and will automatically return to active duty once their fatigue has returned to 0.
Set to 0 to disable. + # Dependent dependentPanel.title=Dependents (Unofficial) dependentPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index c7813682dc..6e34fbbf38 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -207,10 +207,6 @@ public static String getTransitUnitName(final int unit) { private boolean displayPersonnelLog; private boolean displayScenarioLog; private boolean displayKillRecord; - private boolean useFatigue; - private Integer fatigueRate; - private Integer fieldKitchenCapacity; - private Integer fatigueLeaveThreshold; // Expanded Personnel Information private boolean useTimeInService; @@ -236,6 +232,12 @@ public static String getTransitUnitName(final int unit) { private boolean useAtBPrisonerDefection; private boolean useAtBPrisonerRansom; + // Fatigue + private boolean useFatigue; + private Integer fatigueRate; + private Integer fieldKitchenCapacity; + private Integer fatigueLeaveThreshold; + // Dependent private RandomDependentMethod randomDependentMethod; private boolean useRandomDependentAddition; @@ -267,6 +269,7 @@ public static String getTransitUnitName(final int unit) { private Integer serviceContractModifier; private boolean useCustomRetirementModifiers; + private boolean useFatigueModifiers; private boolean useSkillModifiers; private boolean useAgeModifiers; private boolean useUnitRatingModifiers; @@ -765,6 +768,7 @@ public CampaignOptions() { setServiceContractModifier(8); setUseCustomRetirementModifiers(true); + setUseFatigueModifiers(true); setUseSkillModifiers(true); setUseAgeModifiers(true); setUseUnitRatingModifiers(true); @@ -1735,6 +1739,14 @@ public void setUseCustomRetirementModifiers(final boolean useCustomRetirementMod this.useCustomRetirementModifiers = useCustomRetirementModifiers; } + public boolean isUseFatigueModifiers() { + return useFatigueModifiers; + } + + public void setUseFatigueModifiers(final boolean useFatigueModifiers) { + this.useFatigueModifiers = useFatigueModifiers; + } + public boolean isUseLoyaltyModifiers() { return useLoyaltyModifiers; } @@ -4229,6 +4241,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "serviceContractModifier", getServiceContractModifier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCustomRetirementModifiers", isUseCustomRetirementModifiers()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFatigueModifiers", isUseFatigueModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSkillModifiers", isUseSkillModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAgeModifiers", isUseAgeModifiers()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useUnitRatingModifiers", isUseUnitRatingModifiers()); @@ -4906,6 +4919,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setServiceContractModifier(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useCustomRetirementModifiers")) { retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useFatigueModifiers")) { + retVal.setUseFatigueModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSkillModifiers")) { retVal.setUseSkillModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useAgeModifiers")) { @@ -5455,10 +5470,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.getRandomOriginOptions().setExtraRandomOrigin(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("originDistanceScale")) { // Legacy, 0.49.7 Removal retVal.getRandomOriginOptions().setOriginDistanceScale(Double.parseDouble(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("customRetirementMods")) { // Legacy - 0.49.7 Removal - retVal.setUseCustomRetirementModifiers(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("foundersNeverRetire")) { // Legacy - 0.49.7 Removal - retVal.setUseRandomFounderRetirement(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("atbAddDependents")) { // Legacy - 0.49.7 Removal final boolean value = Boolean.parseBoolean(wn2.getTextContent().trim()); retVal.setRandomDependentMethod((value && retVal.isUseAtB()) ? RandomDependentMethod.AGAINST_THE_BOT : RandomDependentMethod.NONE); diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index e495e1195e..71bf52563d 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -212,6 +212,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkDisplayKillRecord; private JPanel fatiguePanel; + private JPanel fatiguePanelElements; private JLabel lblFatigueWarning; private JCheckBox chkUseFatigue; private JLabel lblFatigueRate; @@ -3152,7 +3153,11 @@ private JScrollPane createPersonnelTab() { gbc.gridx++; personnelPanel.add(createPrisonerPanel(), gbc); + gbc.gridx = 0; gbc.gridy++; + personnelPanel.add(createFatiguePanel(), gbc); + + gbc.gridx++; personnelPanel.add(createDependentPanel(), gbc); gbc.gridx = 0; @@ -3283,23 +3288,6 @@ private JPanel createGeneralPersonnelPanel() { chkDisplayKillRecord.setToolTipText(resources.getString("chkDisplayKillRecord.toolTipText")); chkDisplayKillRecord.setName("chkDisplayKillRecord"); - chkUseFatigue = new JCheckBox(resources.getString("chkUseFatigue.text")); - chkUseFatigue.setToolTipText(resources.getString("chkUseFatigue.toolTipText")); - chkUseFatigue.setName("chkUseFatigue"); - chkUseFatigue.addActionListener(evt -> { - final boolean isEnabled = chkUseFatigue.isSelected(); - - // general handler - for (Component component : fatiguePanel.getComponents()) { - component.setEnabled(isEnabled); - } - - // border handler - fatiguePanel.setEnabled(isEnabled); - }); - - fatiguePanel = createFatiguePanel(); - // Layout the Panel final JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createTitledBorder("")); @@ -3329,8 +3317,6 @@ private JPanel createGeneralPersonnelPanel() { .addComponent(chkDisplayPersonnelLog) .addComponent(chkDisplayScenarioLog) .addComponent(chkDisplayKillRecord) - .addComponent(chkUseFatigue) - .addComponent(fatiguePanel) ); layout.setHorizontalGroup( @@ -3352,14 +3338,12 @@ private JPanel createGeneralPersonnelPanel() { .addComponent(chkDisplayPersonnelLog) .addComponent(chkDisplayScenarioLog) .addComponent(chkDisplayKillRecord) - .addComponent(chkUseFatigue) - .addComponent(fatiguePanel) ); return panel; } - private JPanel createFatiguePanel() { + private JPanel createFatiguePanelElements() { lblFatigueWarning = new JLabel(resources.getString("lblFatigueWarning.text")); lblFatigueWarning.setName("lblFatigueWarning"); lblFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseFatigue()); @@ -3394,15 +3378,14 @@ private JPanel createFatiguePanel() { spnFatigueLeaveThreshold.setName("spnFatigueLeaveThreshold"); spnFatigueLeaveThreshold.setEnabled(campaign.getCampaignOptions().isUseFatigue()); - fatiguePanel = new JPanel(); - fatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); - fatiguePanel.setName("fatiguePanel"); - fatiguePanel.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + fatiguePanelElements = new JPanel(); + fatiguePanelElements.setName("fatiguePanelElements"); + fatiguePanelElements.setEnabled(campaign.getCampaignOptions().isUseFatigue()); - final GroupLayout layout = new GroupLayout(fatiguePanel); + final GroupLayout layout = new GroupLayout(fatiguePanelElements); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); - fatiguePanel.setLayout(layout); + fatiguePanelElements.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() @@ -3432,7 +3415,7 @@ private JPanel createFatiguePanel() { .addComponent(spnFatigueLeaveThreshold)) ); - return fatiguePanel; + return fatiguePanelElements; } private JPanel createExpandedPersonnelInformationPanel() { @@ -4430,6 +4413,44 @@ private JPanel createDependentPanel() { return panel; } + private JPanel createFatiguePanel() { + chkUseFatigue = new JCheckBox(resources.getString("chkUseFatigue.text")); + chkUseFatigue.setToolTipText(resources.getString("chkUseFatigue.toolTipText")); + chkUseFatigue.setName("chkUseFatigue"); + chkUseFatigue.addActionListener(evt -> { + final boolean isEnabled = chkUseFatigue.isSelected(); + + for (Component component : fatiguePanel.getComponents()) { + component.setEnabled(isEnabled); + } + }); + + fatiguePanel = createFatiguePanelElements(); + + final JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); + panel.setName("fatiguePanel"); + + final GroupLayout layout = new GroupLayout(panel); + panel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseFatigue) + .addComponent(fatiguePanel) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseFatigue) + .addComponent(fatiguePanel) + ); + + return panel; + } + private void createRandomDependentPanel() { // Create Panel Components final JLabel lblRandomDependentMethod = new JLabel(resources.getString("lblRandomDependentMethod.text")); From a03d9a6dc9168aeeb8acff1a1659b2a063c4e241 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 14:05:15 -0500 Subject: [PATCH 037/101] Update fatigue calculation in PersonViewPanel Updated the fatigue turnover modifier calculation in PersonViewPanel java class. --- MekHQ/src/mekhq/gui/view/PersonViewPanel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java index b99a2d31ae..06be45a7e5 100644 --- a/MekHQ/src/mekhq/gui/view/PersonViewPanel.java +++ b/MekHQ/src/mekhq/gui/view/PersonViewPanel.java @@ -18,6 +18,7 @@ */ package mekhq.gui.view; +import megamek.codeUtilities.MathUtility; import megamek.common.options.IOption; import mekhq.MHQStaticDirectoryManager; import mekhq.MekHQ; @@ -1436,7 +1437,7 @@ private JPanel fillSkills() { StringBuilder fatigueDisplay = new StringBuilder(); int effectiveFatigue = person.getEffectiveFatigue(campaign); - int fatigueTurnoverModifier = effectiveFatigue / 5; + int fatigueTurnoverModifier = MathUtility.clamp(((person.getFatigue() - 1) / 4) - 1, 0, 3); fatigueDisplay.append(person.getFatigue()); From f863642566c85c314a8a9913fbd6947a3ca3d0c6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 14:12:56 -0500 Subject: [PATCH 038/101] Added fatigue modifier boolean to campaign options A new checkbox option, "chkUseFatigueModifiers", has been added to the campaign options. Now users have the choice to enable fatigue as a modifier to Turnover checks. --- .../mekhq/resources/CampaignOptionsDialog.properties | 2 ++ .../campaign/personnel/RetirementDefectionTracker.java | 2 +- MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 327839b415..0ee9a2f99f 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -281,6 +281,8 @@ chkUseCustomRetirementModifiers.text=Custom chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. chkUseSkillModifiers.text=Desirability chkUseSkillModifiers.toolTipText=If enabled, better skilled personnel have a higher turnover target number. +chkUseFatigueModifiers.text=Fatigue +chkUseFatigueModifiers.toolTipText=If enabled, fatigued personnel have a higher turnover target number. chkUseAgeModifiers.text=Age chkUseAgeModifiers.toolTipText=If enabled, the age of personnel will influence their turnover target number. chkUseUnitRatingModifiers.text=Unit Rating diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index c09bf64fac..c08d6c664e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -146,7 +146,7 @@ public Map getTargetNumbers(final @Nullable AtBContract contra } // Fatigue modifier - if (campaign.getCampaignOptions().isUseFatigue()) { + if ((campaign.getCampaignOptions().isUseFatigue()) && (campaign.getCampaignOptions().isUseFatigueModifiers())) { int fatigueModifier = MathUtility.clamp(((person.getFatigue() - 1) / 4) - 1, 0, 3); if (fatigueModifier > 0) { diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 71bf52563d..6001f4f1a9 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -287,6 +287,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JPanel turnoverModifiersPanel; private JCheckBox chkUseCustomRetirementModifiers; private JCheckBox chkUseSkillModifiers; + private JCheckBox chkUseFatigueModifiers; private JCheckBox chkUseAgeModifiers; private JCheckBox chkUseUnitRatingModifiers; private JCheckBox chkUseFactionModifiers; @@ -3971,6 +3972,10 @@ private JPanel createTurnoverModifiersPanel() { chkUseSkillModifiers.setToolTipText(resources.getString("chkUseSkillModifiers.toolTipText")); chkUseSkillModifiers.setName("chkUseSkillModifiers"); + chkUseFatigueModifiers = new JCheckBox(resources.getString("chkUseFatigueModifiers.text")); + chkUseFatigueModifiers.setToolTipText(resources.getString("chkUseFatigueModifiers.toolTipText")); + chkUseFatigueModifiers.setName("chkUseFatigueModifiers"); + chkUseAgeModifiers = new JCheckBox(resources.getString("chkUseAgeModifiers.text")); chkUseAgeModifiers.setToolTipText(resources.getString("chkUseAgeModifiers.toolTipText")); chkUseAgeModifiers.setName("chkUseAgeModifiers"); @@ -4016,6 +4021,7 @@ private JPanel createTurnoverModifiersPanel() { layout.createSequentialGroup() .addComponent(chkUseCustomRetirementModifiers) .addComponent(chkUseSkillModifiers) + .addComponent(chkUseFatigueModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) @@ -4028,6 +4034,7 @@ private JPanel createTurnoverModifiersPanel() { layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseCustomRetirementModifiers) .addComponent(chkUseSkillModifiers) + .addComponent(chkUseFatigueModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) @@ -7420,6 +7427,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); chkUseSkillModifiers.setSelected(options.isUseSkillModifiers()); + chkUseFatigueModifiers.setSelected(options.isUseFatigueModifiers()); chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); chkUseMissionStatusModifiers.setSelected(options.isUseMissionStatusModifiers()); @@ -8058,6 +8066,7 @@ public void updateOptions() { options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); options.setUseSkillModifiers(chkUseSkillModifiers.isSelected()); + options.setUseFatigueModifiers(chkUseFatigueModifiers.isSelected()); options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); options.setUseMissionStatusModifiers(chkUseMissionStatusModifiers.isSelected()); From 0f0ebe031da5888693e8ce88d1152135deec90cf Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 15:28:46 -0500 Subject: [PATCH 039/101] Refactor retirement and defection dialog to accept any mission type The RetirementDefectionDialog now takes any Mission object instead of an AtBContract one, enabling compatibility with a greater variety of campaigns. Relevant parts of the codebase have been adjusted accordingly, including adjustments in processing retirement conditions and computing targets for retirement rolls. --- MekHQ/src/mekhq/MekHQ.java | 16 +++-- MekHQ/src/mekhq/campaign/CampaignOptions.java | 4 +- .../personnel/RetirementDefectionTracker.java | 44 ++++++++----- MekHQ/src/mekhq/gui/BriefingTab.java | 61 ++++++++++--------- MekHQ/src/mekhq/gui/CampaignGUI.java | 1 + .../gui/dialog/RetirementDefectionDialog.java | 10 +-- .../mekhq/gui/panes/CampaignOptionsPane.java | 8 --- 7 files changed, 74 insertions(+), 70 deletions(-) diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index bfd07f52ad..91d3dea956 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -478,15 +478,13 @@ public void gameVictory(GameVictoryEvent gve) { ResolveScenarioWizardDialog resolveDialog = new ResolveScenarioWizardDialog(campaignGUI.getFrame(), true, tracker); resolveDialog.setVisible(true); - if (campaignGUI.getCampaign().getCampaignOptions().isUseAtB() - && (campaignGUI.getCampaign().getMission(currentScenario.getMissionId()) instanceof AtBContract) - && !campaignGUI.getCampaign().getRetirementDefectionTracker().getRetirees().isEmpty()) { - RetirementDefectionDialog rdd = new RetirementDefectionDialog(campaignGUI, - (AtBContract) campaignGUI.getCampaign().getMission(currentScenario.getMissionId()), false); - rdd.setVisible(true); - if (!rdd.wasAborted()) { - getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments()); - } + + RetirementDefectionDialog rdd = new RetirementDefectionDialog(campaignGUI, campaignGUI.getCampaign().getMission(currentScenario.getMissionId()), false); + + rdd.setVisible(true); + + if (!rdd.wasAborted()) { + getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments()); } // we need to trigger ScenarioResolvedEvent before stopping the thread or currentScenario may become null MekHQ.triggerEvent(new ScenarioResolvedEvent(currentScenario)); diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 6e34fbbf38..da26f5e22b 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -765,7 +765,7 @@ public CampaignOptions() { setUseRandomFounderRetirement(true); setUseSubContractSoldiers(false); setServiceContractDuration(36); - setServiceContractModifier(8); + setServiceContractModifier(5); setUseCustomRetirementModifiers(true); setUseFatigueModifiers(true); @@ -780,7 +780,7 @@ public CampaignOptions() { setPayoutRateOfficer(3); setPayoutRateEnlisted(3); - setPayoutRetirementMultiplier(24); + setPayoutRetirementMultiplier(12); setUsePayoutServiceBonus(true); setPayoutServiceBonusRate(10); diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index c08d6c664e..2649f49363 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -97,16 +97,16 @@ public RetirementDefectionTracker() { * Computes the target for retirement rolls for all eligible personnel; this includes * all active personnel who are not dependents, prisoners, or bondsmen. * - * @param contract The contract that is being resolved; if the retirement roll is not due to + * @param mission The contract that is being resolved; if the retirement roll is not due to * contract resolutions (e.g., > 12 months since last roll), this can be null. * @param campaign The campaign to calculate target numbers for * @return A map with person ids as key and calculated target roll as value. */ - public Map getTargetNumbers(final @Nullable AtBContract contract, final Campaign campaign) { + public Map getTargetNumbers(final @Nullable Mission mission, final Campaign campaign) { final Map targets = new HashMap<>(); - if (null != contract) { - rollRequired.add(contract.getId()); + if (null != mission) { + rollRequired.add(mission.getId()); } if (!campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()) { @@ -137,7 +137,7 @@ public Map getTargetNumbers(final @Nullable AtBContract contra // Service Contract if (ChronoUnit.MONTHS.between(person.getRecruitment(), campaign.getLocalDate()) < campaign.getCampaignOptions().getServiceContractDuration()) { - targetNumber.addModifier(campaign.getCampaignOptions().getServiceContractModifier(), resources.getString("contract.text")); + targetNumber.addModifier(-campaign.getCampaignOptions().getServiceContractModifier(), resources.getString("contract.text")); } // Desirability modifier @@ -173,17 +173,29 @@ public Map getTargetNumbers(final @Nullable AtBContract contra // If this retirement roll is not being made at the end of a contract (e.g. >12 months since last roll), // the share percentage should still apply. // In the case of multiple active contracts, pick the one with the best percentage. - AtBContract c = contract; - if (c == null) { - for (AtBContract atBContract : campaign.getActiveAtBContracts()) { - if ((c == null) || (c.getSharesPct() < atBContract.getSharesPct())) { - c = atBContract; + + AtBContract contract; + + try { + contract = (AtBContract) mission; + } catch (Exception e) { + contract = null; + } + + if (contract == null) { + List atbContracts = campaign.getActiveAtBContracts(); + + if (!atbContracts.isEmpty()) { + for (AtBContract atbContract : atbContracts) { + if ((contract == null) || (contract.getSharesPct() < atbContract.getSharesPct())) { + contract = atbContract; + } } } } - if (c != null) { - targetNumber.addModifier(- (c.getSharesPct() / 10), resources.getString("shares.text")); + if (contract != null) { + targetNumber.addModifier(- (contract.getSharesPct() / 10), resources.getString("shares.text")); } } @@ -194,12 +206,12 @@ public Map getTargetNumbers(final @Nullable AtBContract contra } // Mission completion status modifiers - if ((contract != null) && (campaign.getCampaignOptions().isUseMissionStatusModifiers())) { - if (contract.getStatus().isSuccess()) { + if ((mission != null) && (campaign.getCampaignOptions().isUseMissionStatusModifiers())) { + if (mission.getStatus().isSuccess()) { targetNumber.addModifier(-1, resources.getString("missionSuccess.text")); - } else if (contract.getStatus().isFailed()) { + } else if (mission.getStatus().isFailed()) { targetNumber.addModifier(1, resources.getString("missionFailure.text")); - } else if (contract.getStatus().isBreach()) { + } else if (mission.getStatus().isBreach()) { targetNumber.addModifier(2, resources.getString("missionBreach.text")); } } diff --git a/MekHQ/src/mekhq/gui/BriefingTab.java b/MekHQ/src/mekhq/gui/BriefingTab.java index 791a5e5d2c..1c46a97959 100644 --- a/MekHQ/src/mekhq/gui/BriefingTab.java +++ b/MekHQ/src/mekhq/gui/BriefingTab.java @@ -356,36 +356,36 @@ private void completeMission() { if (((AtBContract) mission).contractExtended(getCampaign())) { return; } + } - if (getCampaign().getCampaignOptions().isUseRandomRetirement() - && getCampaign().getCampaignOptions().isUseContractCompletionRandomRetirement()) { - RetirementDefectionDialog rdd = new RetirementDefectionDialog(getCampaignGui(), - (AtBContract) mission, true); - rdd.setVisible(true); - if (rdd.wasAborted()) { - /* - * Once the retirement rolls have been made, the outstanding payouts can be resolved - * without reference to the contract and the dialog can be accessed through the menu - * provided they aren't still assigned to the mission in question. - */ - if (!getCampaign().getRetirementDefectionTracker().isOutstanding(mission.getId())) { - return; - } - } else { - if ((getCampaign().getRetirementDefectionTracker().getRetirees(mission) != null) - && getCampaign().getFinances().getBalance().isGreaterOrEqualThan(rdd.totalPayout())) { - for (PersonnelRole role : PersonnelRole.getAdministratorRoles()) { - Person admin = getCampaign().findBestInRole(role, SkillType.S_ADMIN); - if (admin != null) { - admin.awardXP(getCampaign(), 1); - getCampaign().addReport(admin.getHyperlinkedName() + " has gained 1 XP."); - } + if (getCampaign().getCampaignOptions().isUseRandomRetirement() + && getCampaign().getCampaignOptions().isUseContractCompletionRandomRetirement()) { + RetirementDefectionDialog rdd = new RetirementDefectionDialog(getCampaignGui(), mission, true); + rdd.setVisible(true); + + if (rdd.wasAborted()) { + /* + * Once the retirement rolls have been made, the outstanding payouts can be resolved + * without a reference to the contract and the dialog can be accessed through the menu + * provided they aren't still assigned to the mission in question. + */ + if (!getCampaign().getRetirementDefectionTracker().isOutstanding(mission.getId())) { + return; + } + } else { + if ((getCampaign().getRetirementDefectionTracker().getRetirees(mission) != null) + && getCampaign().getFinances().getBalance().isGreaterOrEqualThan(rdd.totalPayout())) { + for (PersonnelRole role : PersonnelRole.getAdministratorRoles()) { + Person admin = getCampaign().findBestInRole(role, SkillType.S_ADMIN); + if (admin != null) { + admin.awardXP(getCampaign(), 1); + getCampaign().addReport(admin.getHyperlinkedName() + " has gained 1 XP."); } } + } - if (!getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments())) { - return; - } + if (!getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments())) { + return; } } } @@ -478,12 +478,13 @@ private void resolveScenario() { // tracker.postProcessEntities(control); ResolveScenarioWizardDialog resolveDialog = new ResolveScenarioWizardDialog(getFrame(), true, tracker); resolveDialog.setVisible(true); - if (getCampaign().getCampaignOptions().isUseAtB() - && getCampaign().getMission(scenario.getMissionId()) instanceof AtBContract - && !getCampaign().getRetirementDefectionTracker().getRetirees().isEmpty()) { + + if (!getCampaign().getRetirementDefectionTracker().getRetirees().isEmpty()) { RetirementDefectionDialog rdd = new RetirementDefectionDialog(getCampaignGui(), - (AtBContract) getCampaign().getMission(scenario.getMissionId()), false); + getCampaign().getMission(scenario.getMissionId()), false); + rdd.setVisible(true); + if (!rdd.wasAborted()) { getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments()); } diff --git a/MekHQ/src/mekhq/gui/CampaignGUI.java b/MekHQ/src/mekhq/gui/CampaignGUI.java index 51a9914da3..65573d0e83 100644 --- a/MekHQ/src/mekhq/gui/CampaignGUI.java +++ b/MekHQ/src/mekhq/gui/CampaignGUI.java @@ -1200,6 +1200,7 @@ public void showRetirementDefectionDialog() { RetirementDefectionDialog rdd = new RetirementDefectionDialog(this, null, getCampaign().getRetirementDefectionTracker().getRetirees().isEmpty()); rdd.setVisible(true); + if (!rdd.wasAborted()) { getCampaign().applyRetirement(rdd.totalPayout(), rdd.getUnitAssignments()); } diff --git a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java index 18dddfc111..302afe8d4f 100644 --- a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java @@ -30,7 +30,7 @@ import mekhq.MekHQ; import mekhq.campaign.finances.Money; import mekhq.campaign.finances.enums.TransactionType; -import mekhq.campaign.mission.AtBContract; +import mekhq.campaign.mission.Mission; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.RetirementDefectionTracker; import mekhq.campaign.personnel.enums.PersonnelRole; @@ -65,7 +65,7 @@ public class RetirementDefectionDialog extends JDialog { private String currentPanel; private CampaignGUI hqView; - private AtBContract contract; + private Mission contract; private RetirementDefectionTracker rdTracker; private Map targetRolls; @@ -106,14 +106,14 @@ public class RetirementDefectionDialog extends JDialog { private final ResourceBundle resourceMap = ResourceBundle.getBundle("mekhq.resources.RetirementDefectionDialog", MekHQ.getMHQOptions().getLocale()); - public RetirementDefectionDialog (CampaignGUI gui, AtBContract contract, boolean doRetirement) { + public RetirementDefectionDialog (CampaignGUI gui, Mission mission, boolean doRetirement) { super(gui.getFrame(), true); hqView = gui; unitAssignments = new HashMap<>(); - this.contract = contract; + this.contract = mission; rdTracker = hqView.getCampaign().getRetirementDefectionTracker(); if (doRetirement) { - targetRolls = rdTracker.getTargetNumbers(contract, hqView.getCampaign()); + targetRolls = rdTracker.getTargetNumbers(mission, hqView.getCampaign()); } currentPanel = doRetirement?PAN_OVERVIEW:PAN_RESULTS; setSize(new Dimension(800, 600)); diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 6001f4f1a9..ad68380115 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -2705,14 +2705,6 @@ private JScrollPane createAgainstTheBotTab() { final boolean enabled = chkUseAtB.isSelected(); enableAtBComponents(panAtB, enabled); - // This is necessary to prevent issues where disabled options become visible - if (randomRetirementPanel.isEnabled() != enabled) { - randomRetirementPanel.setEnabled(enabled); - if (enabled) { - chkUseRandomRetirement.setSelected(true); - } - } - if (randomDependentPanel.isEnabled() != enabled) { randomDependentPanel.setEnabled(enabled); if (enabled) { From ef08f9c76c6ba8946bc79b44a13cf1be5ea7547f Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 20 May 2024 15:31:29 -0500 Subject: [PATCH 040/101] Remove unused import in MekHQ.java The unused `AtBContract` import from the `mekhq.campaign.mission` package in the `MekHQ.java` file was removed. --- MekHQ/src/mekhq/MekHQ.java | 1 - 1 file changed, 1 deletion(-) diff --git a/MekHQ/src/mekhq/MekHQ.java b/MekHQ/src/mekhq/MekHQ.java index 91d3dea956..b9ea03cd49 100644 --- a/MekHQ/src/mekhq/MekHQ.java +++ b/MekHQ/src/mekhq/MekHQ.java @@ -39,7 +39,6 @@ import mekhq.campaign.ResolveScenarioTracker; import mekhq.campaign.event.ScenarioResolvedEvent; import mekhq.campaign.handler.XPHandler; -import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.mission.Scenario; import mekhq.campaign.stratcon.StratconRulesManager; From 67f60a19a4fc909e74429bfcc4b80f66da4f7e28 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 21 May 2024 00:31:43 -0500 Subject: [PATCH 041/101] Refactored employee turnover settings in CampaignOptionsDialog The code has been updated to improve the arrangement of employee turnover settings in the CampaignOptionsDialog. --- .../CampaignOptionsDialog.properties | 186 +++-- MekHQ/src/mekhq/campaign/CampaignOptions.java | 115 +-- MekHQ/src/mekhq/campaign/CampaignSummary.java | 2 +- .../personnel/RetirementDefectionTracker.java | 2 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 755 +++++++++++------- 5 files changed, 619 insertions(+), 441 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 0ee9a2f99f..5f9b88ac3e 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -122,6 +122,97 @@ lblTechLevel.text=Maximum Tech Level: ## Personnel Tab personnelPanel.title=Personnel +## Turnover and Retention Tab +turnoverAndRetentionPanel.title=Turnover and Retention + +## Header +chkUseRetirementDateTracking.text=Track Departure Date +chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure from the unit. +chkUseRandomRetirement.text=Enable Employee Turnover +chkUseRandomRetirement.toolTipText=Determines whether personnel will randomly leave the unit. + +## Settings +turnoverAndRetentionSettingsPanel.title=Settings +lblTurnoverTargetNumberMethod.text=Turnover Target Number Method +lblTurnoverTargetNumberMethod.toolTipText=This is the method used to determine the base target number for turnover checks. +lblTurnoverFixedTargetNumber.text=Fixed Target Number +lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks. +lblTurnoverDifficulty.text=Turnover Difficulty +lblTurnoverDifficulty.toolTipText=Increases or decreases the difficulty of turnover target numbers. +chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls +chkUseYearEndRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of each year. +chkUseContractCompletionRandomRetirement.text=Use Contract Completion Turnover Rolls +chkUseContractCompletionRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of a contract. +chkUseRandomFounderRetirement.text=Use Random Founder Turnover +chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly leave the unit. +chkUseSubContractSoldiers.text=Soldiers Use Commander's Turnover Roll +chkUseSubContractSoldiers.toolTipText=Infantry commanders make Turnover rolls for all soldiers in their unit. +lblServiceContractDuration.text=Service Contract Duration +lblServiceContractDuration.toolTipText=Once recruited, this is the minimum amount of months personnel will remain with the unit (set to 0 to disable service contracts). +lblServiceContractModifier.text=Service Contract Modifier +lblServiceContractModifier.toolTipText=While personnel are under contract, their Turnover target number is reduced by this value. + +## Modifiers +turnoverAndRetentionModifiersPanel.title=Modifiers +chkUseCustomRetirementModifiers.text=Custom +chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. +chkUseSkillModifiers.text=Desirability +chkUseSkillModifiers.toolTipText=If enabled, better skilled personnel have a higher turnover target number. +chkUseFatigueModifiers.text=Fatigue +chkUseFatigueModifiers.toolTipText=If enabled, fatigued personnel have a higher turnover target number. +chkUseAgeModifiers.text=Age +chkUseAgeModifiers.toolTipText=If enabled, the age of personnel will influence their turnover target number. +chkUseUnitRatingModifiers.text=Unit Rating +chkUseUnitRatingModifiers.toolTipText=If enabled, the rating of the unit will influence the turnover target number for all personnel. +chkUseFactionModifiers.text=Faction +chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. +chkUseMissionStatusModifiers.text=Mission Status +chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. +chkUseLoyaltyModifiers.text=Use Loyalty +chkUseLoyaltyModifiers.toolTipText=If enabled, personnel have a random loyalty rating ranging between -3 and 3. + +loyaltySubPanel.title=Loyalty Options +chkUseHideLoyalty.text=Hide Loyalty +chkUseHideLoyalty.toolTipText=If enabled, loyalty modifiers will be hidden. + +## Payouts +turnoverAndRetentionPayoutPanel.title=Payouts +lblPayoutRateOfficer.text=Officer Payout Rate +lblPayoutRateOfficer.toolTipText=The number of months' pay officers get when resigning from the unit. +lblPayoutRateEnlisted.text=Enlisted Payout Rate +lblPayoutRateEnlisted.toolTipText=The number of months' pay enlisted personnel get when resigning from the unit. +lblPayoutRetirementMultiplier.text=Retirement Multiplier +lblPayoutRetirementMultiplier.toolTipText=The number of months to multiply payout rate, when personnel retire. +chkUsePayoutServiceBonus.text=Use Service Bonus +chkUsePayoutServiceBonus.toolTipText=If enabled, personnel increase their payouts based on years of service. + +payoutServiceBonusSubPanel.title=Service Bonus Options +lblPayoutServiceBonusRate.text=Bonus % +lblPayoutServiceBonusRate.toolTipText=This sets the payout percentage increase per year of service, applied when personnel resign or retire. + +## Unit Cohesion +turnoverAndRetentionUnitCohesionPanel.title=Unit Cohesion +chkUseAdministrativeStrain.text=Enable Administrative Strain +chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty (based on the combined ranks of all Admin/HR personnel). +chkUseManagementSkill.text=Enable Management Skill +chkUseManagementSkill.toolTipText=This option applies a modifier to turnover checks based on the Leadership of commanding personnel. + +### Administrative Strain +administrativeStrainSubPanel.title=Administrative Strain +lblAdministrativeCapacity.text=Administrative Capacity +lblAdministrativeCapacity.toolTipText=How many personnel can be supported per combined rank in Administration. +lblMultiCrewStrainDivider.text=Multi-Crew Divider +lblMultiCrewStrainDivider.toolTipText=For multi-crew units, or ProtoMech points, divide crew size by this value. + +### Management Skill +managementSkillSubPanel.title=Management Skill +chkUseCommanderLeadershipOnly.text=Only Use Commander's Leadership +chkUseCommanderLeadershipOnly.toolTipText=Personnel only use the Leadership skill of whoever has the overall Commander flag. If disabled, personnel will use the Leadership of their profession group commander. +lblManagementSkillPenalty.text=Unskilled Penalty +lblManagementSkillPenalty.toolTipText=The unskilled modifier to turnover target numbers. Each rank in Leadership reduces this number by 1. + + + # General Personnel chkUseTactics.text=Use Tactics Skill as Commander Initiative Bonus chkUseTactics.toolTipText=Give each pilot a tactics skill that can be added to initiative using the commander initiative option in MegaMek @@ -250,92 +341,6 @@ personnelRandomizationPanel.title=Personnel Randomization chkUseDylansRandomXP.text=Use Dylan's Random XP (Unofficial) chkUseDylansRandomXP.toolTipText=Use Dylan's optional random XP on creation of a new person (20% chance each of 0, 1, 2, 3, and randomized between 1 and 8 XP) -# Employee Turnover -retirementPanel.title=Employee Turnover -chkUseRetirementDateTracking.text=Track Departure Date -chkUseRetirementDateTracking.toolTipText=Track the date of a person's departure from the unit. -randomRetirementPanel.title=Random Employee Turnover (AtB Only) -chkUseRandomRetirement.text=Enable Employee Turnover -chkUseRandomRetirement.toolTipText=Determines whether personnel will randomly leave the unit. -lblTurnoverTargetNumberMethod.text=Turnover Target Number Method -lblTurnoverTargetNumberMethod.toolTipText=This is the method used to determine the base target number for turnover checks. -lblTurnoverFixedTargetNumber.text=Fixed Target Number -lblTurnoverFixedTargetNumber.toolTipText=The base target number for turnover checks. -lblTurnoverDifficulty.text=Turnover Difficulty -lblTurnoverDifficulty.toolTipText=Increases or decreases the difficulty of turnover target numbers. -chkUseYearEndRandomRetirement.text=Use Year End Turnover Rolls -chkUseYearEndRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of each year. -chkUseContractCompletionRandomRetirement.text=Use Contract Completion Turnover Rolls -chkUseContractCompletionRandomRetirement.toolTipText=Make a Turnover check for all active personnel at the end of a contract. -chkUseRandomFounderRetirement.text=Use Random Founder Turnover -chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly leave the unit. -chkUseSubContractSoldiers.text=Soldiers Use Commander Turnover Roll -chkUseSubContractSoldiers.toolTipText=Commanders make Turnover rolls for all soldiers in their unit -lblServiceContractDuration.text=Service Contract Duration -lblServiceContractDuration.toolTipText=Once recruited, this is the minimum amount of months personnel will remain with the unit (set to 0 to disable service contracts). -lblServiceContractModifier.text=Service Contract Modifier -lblServiceContractModifier.toolTipText=While personnel are under contract, their Turnover target number is reduced by this value. - -turnoverModifierPanel.title=Modifiers -chkUseCustomRetirementModifiers.text=Custom -chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Turnover check. -chkUseSkillModifiers.text=Desirability -chkUseSkillModifiers.toolTipText=If enabled, better skilled personnel have a higher turnover target number. -chkUseFatigueModifiers.text=Fatigue -chkUseFatigueModifiers.toolTipText=If enabled, fatigued personnel have a higher turnover target number. -chkUseAgeModifiers.text=Age -chkUseAgeModifiers.toolTipText=If enabled, the age of personnel will influence their turnover target number. -chkUseUnitRatingModifiers.text=Unit Rating -chkUseUnitRatingModifiers.toolTipText=If enabled, the rating of the unit will influence the turnover target number for all personnel. -chkUseFactionModifiers.text=Faction -chkUseFactionModifiers.toolTipText=If enabled, campaign and personnel factions may influence the turnover target number. -chkUseMissionStatusModifiers.text=Mission Status -chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and contract breaches to influence the turnover target number. - -loyaltyPanel.title=Loyalty -chkUseLoyaltyModifiers.text=Use Loyalty -chkUseLoyaltyModifiers.toolTipText=If enabled, personnel have a random loyalty rating ranging between -3 and 3. -chkUseHideLoyalty.text=Hide Loyalty -chkUseHideLoyalty.toolTipText=If enabled, loyalty modifiers will be hidden. - -unitCohesionPanel.title=Unit Cohesion -chkUseAdministrativeStrain.text=Enable Administrative Strain -chkUseAdministrativeStrain.toolTipText=This option limits the number of personnel that can be in the unit before incurring a Turnover penalty (based on the combined ranks of all Admin/HR personnel). -chkUseManagementSkill.text=Enable Management Skill -chkUseManagementSkill.toolTipText=This option applies a modifier to turnover checks based on the Leadership of commanding personnel. - -administrativeStrainPanel.title=Administrative Strain -lblAdministrativeStrain.text=Administrative Capacity -lblAdministrativeStrain.toolTipText=How many personnel can be supported per combined rank in Administration. -lblMultiCrewStrainDivider.text=Multi-Crew Divider -lblMultiCrewStrainDivider.toolTipText=For multi-crew units, or ProtoMech points, divide crew size by this value. - -managementSkillPanel.title=Management Skill -chkUseCommanderLeadershipOnly.text=Only Use Commander's Leadership -chkUseCommanderLeadershipOnly.toolTipText=Personnel only use the Leadership skill of whoever has the overall Commander flag. If disabled, personnel will use the Leadership of their profession group commander. -lblManagementSkillPenalty.text=Unskilled Penalty -lblManagementSkillPenalty.toolTipText=The unskilled modifier to turnover target numbers. Each rank in Leadership reduces this number by 1. - -turnoverPayoutPanel.title=Payout Rates -lblPayoutRateOfficer.text=Officer Payout Rate -lblPayoutRateOfficer.toolTipText=The number of months' pay officers get when resigning from the unit. -lblPayoutRateEnlisted.text=Enlisted Payout Rate -lblPayoutRateEnlisted.toolTipText=The number of months' pay enlisted personnel get when resigning from the unit. -lblPayoutRetirementMultiplier.text=Retirement Multiplier -lblPayoutRetirementMultiplier.toolTipText=The number of months to multiply payout rate, when personnel retire. -chkUsePayoutServiceBonus.text=Use Service Bonus -chkUsePayoutServiceBonus.toolTipText=If enabled, personnel receive a payout bonus based on service years when resigning or retiring. -lblPayoutServiceBonusRate.text=Service Bonus % -lblPayoutServiceBonusRate.toolTipText=If 'Use Service Bonus' is enabled, this sets the payout percentage increase per year of service, applied when personnel resign or retire. - -sharesPanel.title=Shares System -chkUseShareSystem.text=Use Shares -chkUseShareSystem.toolTipText=Gives personnel a stake in the unit. This system lowers profits but can increase retention. -chkSharesExcludeLargeCraft.text=Large Craft Exemption -chkSharesExcludeLargeCraft.toolTipText=Exclude large craft from net worth when calculating share value. -chkSharesForAll.text=All Personnel Have Shares -chkSharesForAll.toolTipText=All combat and support personnel have shares rather than just MechWarriors - # Family familyPanel.title=Family (Unofficial) lblFamilyDisplayLevel.text=The Level of Relation to be Displayed in the Personnel Panel @@ -596,6 +601,15 @@ lblUnrepairablePartsValueMultiplier.text=Unrepairable Damaged Parts Value Multip lblUnrepairablePartsValueMultiplier.toolTipText=Multiplies the value and thus the sell price of unrepairable damaged parts by the specified number. lblCancelledOrderRefundMultiplier.text=Cancelled Order Refund Percentage lblCancelledOrderRefundMultiplier.toolTipText=The decimal percentage of the original purchase price that is refunded when an order is cancelled. + +# Shares +sharesPanel.title=Shares System +chkUseShareSystem.text=Use Shares +chkUseShareSystem.toolTipText=Gives personnel a stake in the unit. This system lowers profits but can increase retention. +chkSharesExcludeLargeCraft.text=Large Craft Exemption +chkSharesExcludeLargeCraft.toolTipText=Exclude large craft from net worth when calculating share value. +chkSharesForAll.text=All Personnel Have Shares +chkSharesForAll.toolTipText=All combat and support personnel have shares rather than just MechWarriors ##end Finances Tab ## Mercenary Tab diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index da26f5e22b..d7bce2e864 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -258,6 +258,7 @@ public static String getTransitUnitName(final int unit) { // Retirement private boolean useRetirementDateTracking; private boolean useRandomRetirement; + private TurnoverTargetNumberMethod turnoverTargetNumberMethod; private SkillLevel turnoverDifficulty; private Integer turnoverFixedTargetNumber; @@ -275,27 +276,23 @@ public static String getTransitUnitName(final int unit) { private boolean useUnitRatingModifiers; private boolean useFactionModifiers; private boolean useMissionStatusModifiers; + private boolean useLoyaltyModifiers; + private boolean useHideLoyalty; private Integer payoutRateOfficer; private Integer payoutRateEnlisted; private Integer payoutRetirementMultiplier; private boolean usePayoutServiceBonus; private Integer payoutServiceBonusRate; - private boolean useLoyaltyModifiers; - private boolean useHideLoyalty; private boolean useAdministrativeStrain; - private Integer administrativeStrain; + private Integer administrativeCapacity; private Integer multiCrewStrainDivider; private boolean useManagementSkill; private boolean useCommanderLeadershipOnly; private Integer managementSkillPenalty; - private boolean useShareSystem; - private boolean sharesExcludeLargeCraft; - private boolean sharesForAll; - // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -420,6 +417,11 @@ public static String getTransitUnitName(final int unit) { private double damagedPartsValueMultiplier; private double unrepairablePartsValueMultiplier; private double cancelledOrderRefundMultiplier; + + // Shares + private boolean useShareSystem; + private boolean sharesExcludeLargeCraft; + private boolean sharesForAll; //endregion Finance Tab //region Mercenary Tab @@ -754,48 +756,6 @@ public CampaignOptions() { setUseDylansRandomXP(false); setRandomOriginOptions(new RandomOriginOptions(true)); - // Retirement - setUseRetirementDateTracking(false); - setUseRandomRetirement(true); - setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.NEGOTIATION); - setTurnoverDifficulty(SkillLevel.REGULAR); - setTurnoverFixedTargetNumber(3); - setUseYearEndRandomRetirement(true); - setUseContractCompletionRandomRetirement(true); - setUseRandomFounderRetirement(true); - setUseSubContractSoldiers(false); - setServiceContractDuration(36); - setServiceContractModifier(5); - - setUseCustomRetirementModifiers(true); - setUseFatigueModifiers(true); - setUseSkillModifiers(true); - setUseAgeModifiers(true); - setUseUnitRatingModifiers(true); - setUseFactionModifiers(true); - setUseMissionStatusModifiers(true); - - setUseLoyaltyModifiers(true); - setUseHideLoyalty(true); - - setPayoutRateOfficer(3); - setPayoutRateEnlisted(3); - setPayoutRetirementMultiplier(12); - setUsePayoutServiceBonus(true); - setPayoutServiceBonusRate(10); - - setUseAdministrativeStrain(true); - setAdministrativeStrain(10); - setMultiCrewStrainDivider(5); - - setUseManagementSkill(true); - setUseCommanderLeadershipOnly(true); - setManagementSkillPenalty(-2); - - setUseShareSystem(false); - setSharesExcludeLargeCraft(true); - setSharesForAll(false); - // Family setFamilyDisplayLevel(FamilialRelationshipDisplayLevel.SPOUSE); @@ -936,6 +896,46 @@ public CampaignOptions() { getAgeRangeRandomDeathFemaleValues().put(TenYearAgeRange.EIGHTY_FIVE_OR_OLDER, 12870.0); //endregion Life Paths Tab + //region Turnover and Retention + // Retirement + setUseRetirementDateTracking(false); + setUseRandomRetirement(true); + setTurnoverTargetNumberMethod(TurnoverTargetNumberMethod.NEGOTIATION); + setTurnoverDifficulty(SkillLevel.REGULAR); + setTurnoverFixedTargetNumber(3); + setUseYearEndRandomRetirement(true); + setUseContractCompletionRandomRetirement(true); + setUseRandomFounderRetirement(true); + setUseSubContractSoldiers(false); + setServiceContractDuration(36); + setServiceContractModifier(5); + + setUseCustomRetirementModifiers(true); + setUseFatigueModifiers(true); + setUseSkillModifiers(true); + setUseAgeModifiers(true); + setUseUnitRatingModifiers(true); + setUseFactionModifiers(true); + setUseMissionStatusModifiers(true); + + setUseLoyaltyModifiers(true); + setUseHideLoyalty(true); + + setPayoutRateOfficer(3); + setPayoutRateEnlisted(3); + setPayoutRetirementMultiplier(12); + setUsePayoutServiceBonus(true); + setPayoutServiceBonusRate(10); + + setUseAdministrativeStrain(true); + setAdministrativeCapacity(10); + setMultiCrewStrainDivider(5); + + setUseManagementSkill(true); + setUseCommanderLeadershipOnly(false); + setManagementSkillPenalty(-2); + //endregion Turnover and Retention + //region Finances Tab payForParts = false; payForRepairs = false; @@ -967,6 +967,11 @@ public CampaignOptions() { setDamagedPartsValueMultiplier(0.33); setUnrepairablePartsValueMultiplier(0.1); setCancelledOrderRefundMultiplier(0.5); + + // Shares + setUseShareSystem(true); + setSharesExcludeLargeCraft(true); + setSharesForAll(true); //endregion Finances Tab //region Mercenary Tab @@ -1875,12 +1880,12 @@ public void setUseAdministrativeStrain(final boolean useAdministrativeStrain) { this.useAdministrativeStrain = useAdministrativeStrain; } - public Integer getAdministrativeStrain() { - return administrativeStrain; + public Integer getAdministrativeCapacity() { + return administrativeCapacity; } - public void setAdministrativeStrain(final Integer administrativeStrain) { - this.administrativeStrain = administrativeStrain; + public void setAdministrativeCapacity(final Integer administrativeCapacity) { + this.administrativeCapacity = administrativeCapacity; } public Integer getMultiCrewStrainDivider() { @@ -4258,7 +4263,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "payoutServiceBonusRate", getPayoutServiceBonusRate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useAdministrativeStrain", isUseAdministrativeStrain()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "administrativeStrain", getAdministrativeStrain()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "administrativeStrain", getAdministrativeCapacity()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "multiCrewStrainDivider", getMultiCrewStrainDivider()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useManagementSkill", isUseManagementSkill()); @@ -4948,7 +4953,7 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve } else if (wn2.getNodeName().equalsIgnoreCase("useAdministrativeStrain")) { retVal.setUseAdministrativeStrain(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("administrativeStrain")) { - retVal.setAdministrativeStrain(Integer.parseInt(wn2.getTextContent().trim())); + retVal.setAdministrativeCapacity(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("multiCrewStrainDivider")) { retVal.setMultiCrewStrainDivider(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useManagementSkill")) { diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index 00a0e2f046..fe1aa07edd 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -295,7 +295,7 @@ public String getAdministrativeCapacityReport(Campaign campaign) { StringBuilder administrativeCapacityReport = new StringBuilder() .append(getAdministrativeStrain(campaign)).append(" / ") - .append(campaign.getCampaignOptions().getAdministrativeStrain() * combinedSkillValues) + .append(campaign.getCampaignOptions().getAdministrativeCapacity() * combinedSkillValues) .append(" personnel"); if (getAdministrativeStrainModifier(campaign) > 0) { diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java index 2649f49363..775aebb872 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java @@ -531,7 +531,7 @@ private static int getIndividualCommanderLeadership(Person commander) { public static int getAdministrativeStrainModifier(Campaign campaign) { int personnel = getAdministrativeStrain(campaign); - int maximumStrain = campaign.getCampaignOptions().getAdministrativeStrain() * getCombinedSkillValues(campaign, SkillType.S_ADMIN); + int maximumStrain = campaign.getCampaignOptions().getAdministrativeCapacity() * getCombinedSkillValues(campaign, SkillType.S_ADMIN); if (maximumStrain != 0) { return personnel / maximumStrain; diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index ad68380115..203e2a888c 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -259,16 +259,13 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JSpinner[] spnBaseSalary; //endregion Personnel Tab - //region Life Paths Ta - // Personnel Randomization - private JCheckBox chkUseDylansRandomXP; - private RandomOriginOptionsPanel randomOriginOptionsPanel; - - // Retirement + //region Turnover and Retention Tab + // Header Options private JCheckBox chkUseRetirementDateTracking; - - private JPanel randomRetirementPanel; private JCheckBox chkUseRandomRetirement; + + // Settings + private JPanel turnoverAndRetentionSettingsPanel = new JPanel(); private JLabel lblTurnoverTargetNumberMethod; private MMComboBox comboTurnoverTargetNumberMethod; private JLabel lblTurnoverDifficulty; @@ -284,20 +281,22 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JLabel lblServiceContractModifier; private JSpinner spnServiceContractModifier; - private JPanel turnoverModifiersPanel; + // Modifiers + private JPanel turnoverAndRetentionModifiersPanel = new JPanel(); private JCheckBox chkUseCustomRetirementModifiers; - private JCheckBox chkUseSkillModifiers; private JCheckBox chkUseFatigueModifiers; + private JCheckBox chkUseSkillModifiers; private JCheckBox chkUseAgeModifiers; private JCheckBox chkUseUnitRatingModifiers; private JCheckBox chkUseFactionModifiers; private JCheckBox chkUseMissionStatusModifiers; - - private JPanel loyaltyPanel; private JCheckBox chkUseLoyaltyModifiers; + + private JPanel loyaltySubPanel = new JPanel(); private JCheckBox chkUseHideLoyalty; - private JPanel turnoverPayoutPanel; + // Payout + private JPanel turnoverAndRetentionPayoutPanel = new JPanel(); private JLabel lblPayoutRateOfficer; private JSpinner spnPayoutRateOfficer; private JLabel lblPayoutRateEnlisted; @@ -305,28 +304,32 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JLabel lblPayoutRetirementMultiplier; private JSpinner spnPayoutRetirementMultiplier; private JCheckBox chkUsePayoutServiceBonus; + + private JPanel payoutServiceBonusSubPanel = new JPanel(); private JLabel lblPayoutServiceBonusRate; private JSpinner spnPayoutServiceBonusRate; - private JPanel unitCohesionPanel; + // Unit Cohesion + private JPanel turnoverAndRetentionUnitCohesionPanel = new JPanel(); private JCheckBox chkUseAdministrativeStrain; private JCheckBox chkUseManagementSkill; - private JPanel administrativeStrainPanel; - private JLabel lblAdministrativeStrain; - private JSpinner spnAdministrativeStrain; + private JPanel administrativeStrainSubPanel = new JPanel(); + private JLabel lblAdministrativeCapacity; + private JSpinner spnAdministrativeCapacity; private JLabel lblMultiCrewStrainDivider; private JSpinner spnMultiCrewStrainDivider; - private JPanel managementSkillPanel; + private JPanel managementSkillSubPanel = new JPanel(); private JCheckBox chkUseCommanderLeadershipOnly; private JLabel lblManagementSkillPenalty; private JSpinner spnManagementSkillPenalty; + //endregion Turnover and Retention Tab - private JPanel sharesPanel; - private JCheckBox chkUseShareSystem; - private JCheckBox chkSharesExcludeLargeCraft; - private JCheckBox chkSharesForAll; + //region Life Paths Tab + // Personnel Randomization + private JCheckBox chkUseDylansRandomXP; + private RandomOriginOptionsPanel randomOriginOptionsPanel; // Marriage private JCheckBox chkUseManualMarriages; @@ -469,6 +472,12 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JSpinner spnDamagedPartsValueMultiplier; private JSpinner spnUnrepairablePartsValueMultiplier; private JSpinner spnCancelledOrderRefundMultiplier; + + private JPanel sharesPanel; + private JCheckBox chkUseShareSystem; + private JPanel sharesSubPanel; + private JCheckBox chkSharesExcludeLargeCraft; + private JCheckBox chkSharesForAll; //endregion Finances Tab //region Mercenary Tab @@ -670,6 +679,7 @@ protected void initialize() { addTab(resources.getString("techLimitsPanel.title"), createTechLimitsTab()); addTab(resources.getString("personnelPanel.title"), createPersonnelTab()); addTab(resources.getString("lifePathsPanel.title"), createLifePathsPanel()); + addTab(resources.getString("turnoverAndRetentionPanel.title"), createTurnoverAndRetentionTab()); addTab(resources.getString("financesPanel.title"), createFinancesTab(campaign.getCampaignOptions().isReverseQualityNames())); addTab(resources.getString("mercenaryPanel.title"), createMercenaryTab()); addTab(resources.getString("experiencePanel.title"), createExperienceTab()); @@ -1743,6 +1753,11 @@ public Component getListCellRendererComponent(JList list, Object value, int i gridBagConstraints.gridheight = 20; panFinances.add(createPriceModifiersPanel(reverseQualities), gridBagConstraints); + gridBagConstraints.gridx = 4; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 20; + panFinances.add(createSharesPanel(), gridBagConstraints); + return new JScrollPane(panFinances); } @@ -3164,7 +3179,39 @@ private JScrollPane createPersonnelTab() { return scrollPersonnel; } - //region Personnel Tab + //region Turnover and Retention Tab + private JScrollPane createTurnoverAndRetentionTab() { + final AbstractMHQScrollablePanel turnoverAndRetentionPanel = new DefaultMHQScrollablePanel(getFrame(), + "turnoverAndRetentionPanel", new GridBagLayout()); + turnoverAndRetentionPanel.setTracksViewportWidth(false); + + final GridBagConstraints gbc = new GridBagConstraints(); + + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.HORIZONTAL; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionHeaderPanel(), gbc); + + gbc.gridy++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionSettingsPanel(), gbc); + + gbc.gridx++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionModifiersPanel(), gbc); + + gbc.gridx++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionPayoutPanel(), gbc); + + gbc.gridx++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionUnitCohesionPanel(), gbc); + + final JScrollPane scrollPersonnel = new JScrollPane(turnoverAndRetentionPanel); + scrollPersonnel.setPreferredSize(new Dimension(500, 400)); + + return scrollPersonnel; + } + + //region Life Paths Tab private JScrollPane createLifePathsPanel() { final AbstractMHQScrollablePanel lifePathsPanel = new DefaultMHQScrollablePanel(getFrame(), "lifePathsPanel", new GridBagLayout()); @@ -3178,9 +3225,6 @@ private JScrollPane createLifePathsPanel() { lifePathsPanel.add(createPersonnelRandomizationPanel(), gbc); - gbc.gridx++; - lifePathsPanel.add(createRetirementPanel(), gbc); - gbc.gridx = 0; gbc.gridy++; gbc.gridwidth = 2; @@ -3686,44 +3730,7 @@ public Component getListCellRendererComponent(final JList list, final Object return panel; } - private JPanel createPersonnelRandomizationPanel() { - // Create Panel Components - chkUseDylansRandomXP = new JCheckBox(resources.getString("chkUseDylansRandomXP.text")); - chkUseDylansRandomXP.setToolTipText(resources.getString("chkUseDylansRandomXP.toolTipText")); - chkUseDylansRandomXP.setName("chkUseDylansRandomXP"); - - randomOriginOptionsPanel = new RandomOriginOptionsPanel(getFrame(), campaign, comboFaction); - - // Layout the Panel - final JPanel panel = new JPanel(); - panel.setBorder(BorderFactory.createTitledBorder(resources.getString("personnelRandomizationPanel.title"))); - panel.setName("personnelRandomizationPanel"); - - final GroupLayout layout = new GroupLayout(panel); - layout.setAutoCreateGaps(true); - layout.setAutoCreateContainerGaps(true); - panel.setLayout(layout); - - layout.setVerticalGroup( - layout.createSequentialGroup() - .addComponent(chkUseDylansRandomXP) - .addComponent(randomOriginOptionsPanel) - ); - - layout.setHorizontalGroup( - layout.createParallelGroup(Alignment.LEADING) - .addComponent(chkUseDylansRandomXP) - .addComponent(randomOriginOptionsPanel) - ); - - return panel; - } - - private JPanel createRetirementPanel() { - // enablers - boolean isUseRandomTurnoverEnabled = campaign.getCampaignOptions().isUseRandomRetirement(); - - // initiate components + private JPanel createTurnoverAndRetentionHeaderPanel() { chkUseRetirementDateTracking = new JCheckBox(resources.getString("chkUseRetirementDateTracking.text")); chkUseRetirementDateTracking.setToolTipText(resources.getString("chkUseRetirementDateTracking.toolTipText")); chkUseRetirementDateTracking.setName("chkUseRetirementDateTracking"); @@ -3732,95 +3739,96 @@ private JPanel createRetirementPanel() { chkUseRandomRetirement.setToolTipText(resources.getString("chkUseRandomRetirement.toolTipText")); chkUseRandomRetirement.setName("chkUseRandomRetirement"); chkUseRandomRetirement.addActionListener(evt -> { - final boolean isEnabled = chkUseRandomRetirement.isSelected(); + boolean isEnabled = chkUseRandomRetirement.isSelected(); - // general handlers - for (int index = 0; index < Arrays.stream(randomRetirementPanel.getComponents()).count(); index++) { - randomRetirementPanel.getComponent(index).setEnabled(isEnabled); + // General Handlers + for (Component component : turnoverAndRetentionSettingsPanel.getComponents()) { + component.setEnabled(isEnabled); } - for (int index = 0; index < Arrays.stream(unitCohesionPanel.getComponents()).count(); index++) { - unitCohesionPanel.getComponent(index).setEnabled(isEnabled); + for (Component component : turnoverAndRetentionModifiersPanel.getComponents()) { + component.setEnabled(isEnabled); } - // border handlers - randomRetirementPanel.setEnabled(isEnabled); - unitCohesionPanel.setEnabled(isEnabled); - - // special handlers - lblTurnoverFixedTargetNumber.setEnabled(isEnabled - && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); - spnTurnoverFixedTargetNumber.setEnabled(isEnabled - && Objects.requireNonNull(comboTurnoverTargetNumberMethod.getSelectedItem()).isFixed()); - lblTurnoverDifficulty.setEnabled(isEnabled - && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); - comboTurnoverDifficulty.setEnabled(isEnabled - && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); - - lblAdministrativeStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - spnAdministrativeStrain.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - lblMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - spnMultiCrewStrainDivider.setEnabled(isEnabled && chkUseAdministrativeStrain.isSelected()); - - chkUseCommanderLeadershipOnly.setEnabled(isEnabled && chkUseManagementSkill.isSelected()); - lblManagementSkillPenalty.setEnabled(isEnabled && chkUseManagementSkill.isSelected()); - spnManagementSkillPenalty.setEnabled(isEnabled && chkUseManagementSkill.isSelected()); - }); - - // Random Turnover Panel Handlers - randomRetirementPanel = createRandomRetirementPanel(); - randomRetirementPanel.setEnabled(isUseRandomTurnoverEnabled); - lblTurnoverDifficulty.setEnabled(isUseRandomTurnoverEnabled && !campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); - comboTurnoverDifficulty.setEnabled(isUseRandomTurnoverEnabled && !campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); - lblTurnoverFixedTargetNumber.setEnabled(isUseRandomTurnoverEnabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); - spnTurnoverFixedTargetNumber.setEnabled(isUseRandomTurnoverEnabled && campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed()); + for (Component component : turnoverAndRetentionPayoutPanel.getComponents()) { + component.setEnabled(isEnabled); + } - // Unit Cohesion Panel Handlers - unitCohesionPanel = createUnitCohesionPanel(); - unitCohesionPanel.setEnabled(isUseRandomTurnoverEnabled); - administrativeStrainPanel.setEnabled(isUseRandomTurnoverEnabled && campaign.getCampaignOptions().isUseAdministrativeStrain()); + for (Component component : turnoverAndRetentionUnitCohesionPanel.getComponents()) { + component.setEnabled(isEnabled); + } - // Shares Panel Handlers - sharesPanel = createSharesPanel(); + // Border Handlers + turnoverAndRetentionSettingsPanel.setEnabled(isEnabled); + turnoverAndRetentionModifiersPanel.setEnabled(isEnabled); + turnoverAndRetentionPayoutPanel.setEnabled(isEnabled); + turnoverAndRetentionUnitCohesionPanel.setEnabled(isEnabled); + + // Special Case Handlers + // These are elements whose status is influenced by other campaign options. + final TurnoverTargetNumberMethod turnoverTargetNumberMethod = comboTurnoverTargetNumberMethod.getSelectedItem(); + lblTurnoverDifficulty.setEnabled((isEnabled) && (!Objects.requireNonNull(turnoverTargetNumberMethod).isFixed())); + comboTurnoverDifficulty.setEnabled((isEnabled) && (!Objects.requireNonNull(turnoverTargetNumberMethod).isFixed())); + lblTurnoverFixedTargetNumber.setEnabled((isEnabled) && (Objects.requireNonNull(turnoverTargetNumberMethod).isFixed())); + spnTurnoverFixedTargetNumber.setEnabled((isEnabled) && (Objects.requireNonNull(turnoverTargetNumberMethod).isFixed())); + + boolean isUseLoyaltyModifiers = chkUseLoyaltyModifiers.isSelected(); + loyaltySubPanel.setEnabled((isEnabled) && (isUseLoyaltyModifiers)); + chkUseHideLoyalty.setEnabled((isEnabled) && (isUseLoyaltyModifiers)); + + boolean isUseServiceBonus = chkUsePayoutServiceBonus.isSelected(); + payoutServiceBonusSubPanel.setEnabled((isEnabled) && (isUseServiceBonus)); + lblPayoutServiceBonusRate.setEnabled((isEnabled) && (isUseServiceBonus)); + spnPayoutServiceBonusRate.setEnabled((isEnabled) && (isUseServiceBonus)); + + boolean isUseAdministrativeStrain = chkUseAdministrativeStrain.isSelected(); + administrativeStrainSubPanel.setEnabled((isEnabled) && (isUseAdministrativeStrain)); + lblAdministrativeCapacity.setEnabled((isEnabled) && (isUseAdministrativeStrain)); + spnAdministrativeCapacity.setEnabled((isEnabled) && (isUseAdministrativeStrain)); + lblMultiCrewStrainDivider.setEnabled((isEnabled) && (isUseAdministrativeStrain)); + spnMultiCrewStrainDivider.setEnabled((isEnabled) && (isUseAdministrativeStrain)); + + boolean isUseManagementSkill = chkUseManagementSkill.isSelected(); + managementSkillSubPanel.setEnabled((isEnabled) && (isUseManagementSkill)); + chkUseCommanderLeadershipOnly.setEnabled((isEnabled) && (isUseManagementSkill)); + lblManagementSkillPenalty.setEnabled((isEnabled) && (isUseManagementSkill)); + spnManagementSkillPenalty.setEnabled((isEnabled) && (isUseManagementSkill)); + }); - // Layout the Panel - final JPanel panel = new JPanel(); - panel.setBorder(BorderFactory.createTitledBorder(resources.getString("retirementPanel.title"))); - panel.setName("retirementPanel"); + final JPanel turnoverAndRetentionHeaderPanel = new JPanel(); + turnoverAndRetentionHeaderPanel.setName("turnoverAndRetentionHeaderPanel"); - final GroupLayout layout = new GroupLayout(panel); - panel.setLayout(layout); + final GroupLayout layout = new GroupLayout(turnoverAndRetentionHeaderPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + turnoverAndRetentionHeaderPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkUseRetirementDateTracking) .addComponent(chkUseRandomRetirement) - .addComponent(randomRetirementPanel) - .addComponent(unitCohesionPanel) - .addComponent(sharesPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseRetirementDateTracking) .addComponent(chkUseRandomRetirement) - .addComponent(randomRetirementPanel) - .addComponent(unitCohesionPanel) - .addComponent(sharesPanel) ); - return panel; + return turnoverAndRetentionHeaderPanel; } - private JPanel createRandomRetirementPanel() { + private JPanel createTurnoverAndRetentionSettingsPanel() { + boolean isUseTurnover = campaign.getCampaignOptions().isUseRandomRetirement(); + lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); lblTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); lblTurnoverTargetNumberMethod.setName("lblTurnoverTargetNumberMethod"); + lblTurnoverTargetNumberMethod.setEnabled(isUseTurnover); comboTurnoverTargetNumberMethod = new MMComboBox<>("comboTurnoverTargetNumberMethod", TurnoverTargetNumberMethod.values()); comboTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); + comboTurnoverTargetNumberMethod.setEnabled(isUseTurnover); comboTurnoverTargetNumberMethod.setRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(final JList list, final Object value, @@ -3834,73 +3842,88 @@ public Component getListCellRendererComponent(final JList list, final Object } }); comboTurnoverTargetNumberMethod.addActionListener(evt -> { - final boolean isEnabled = randomRetirementPanel.isEnabled() && comboTurnoverTargetNumberMethod.getSelectedItem().isFixed(); + final TurnoverTargetNumberMethod method = comboTurnoverTargetNumberMethod.getSelectedItem(); + + if (method == null) { + return; + } - lblTurnoverDifficulty.setEnabled(!isEnabled); - comboTurnoverDifficulty.setEnabled(!isEnabled); + lblTurnoverDifficulty.setEnabled(!method.isFixed()); + comboTurnoverDifficulty.setEnabled(!method.isFixed()); - lblTurnoverFixedTargetNumber.setEnabled(isEnabled); - spnTurnoverFixedTargetNumber.setEnabled(isEnabled); + lblTurnoverFixedTargetNumber.setEnabled(method.isFixed()); + spnTurnoverFixedTargetNumber.setEnabled(method.isFixed()); }); lblTurnoverDifficulty = new JLabel(resources.getString("lblTurnoverDifficulty.text")); lblTurnoverDifficulty.setToolTipText(resources.getString("lblTurnoverDifficulty.toolTipText")); lblTurnoverDifficulty.setName("lblTurnoverDifficulty"); + lblTurnoverDifficulty.setEnabled(isUseTurnover && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); - comboTurnoverDifficulty = new MMComboBox<>("comboTurnoverDifficulty", SkillLevel.values()); + final DefaultComboBoxModel skillLevelModel = new DefaultComboBoxModel<>(Skills.SKILL_LEVELS); + skillLevelModel.removeElement(SkillLevel.NONE); + comboTurnoverDifficulty = new MMComboBox<>("comboTurnoverDifficulty", skillLevelModel); comboTurnoverDifficulty.setToolTipText(resources.getString("lblTurnoverDifficulty.toolTipText")); + comboTurnoverDifficulty.setEnabled(isUseTurnover && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); + lblTurnoverFixedTargetNumber.setEnabled(isUseTurnover && comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); - spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(5, 0, 12, 1)); + spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(3, 0, 10, 1)); spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); spnTurnoverFixedTargetNumber.setName("spnTurnoverFixedTargetNumber"); + spnTurnoverFixedTargetNumber.setEnabled(isUseTurnover && comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); chkUseYearEndRandomRetirement = new JCheckBox(resources.getString("chkUseYearEndRandomRetirement.text")); chkUseYearEndRandomRetirement.setToolTipText(resources.getString("chkUseYearEndRandomRetirement.toolTipText")); chkUseYearEndRandomRetirement.setName("chkUseYearEndRandomRetirement"); + chkUseYearEndRandomRetirement.setEnabled(isUseTurnover); chkUseContractCompletionRandomRetirement = new JCheckBox(resources.getString("chkUseContractCompletionRandomRetirement.text")); chkUseContractCompletionRandomRetirement.setToolTipText(resources.getString("chkUseContractCompletionRandomRetirement.toolTipText")); chkUseContractCompletionRandomRetirement.setName("chkUseContractCompletionRandomRetirement"); + chkUseContractCompletionRandomRetirement.setEnabled(isUseTurnover); chkUseRandomFounderRetirement = new JCheckBox(resources.getString("chkUseRandomFounderRetirement.text")); chkUseRandomFounderRetirement.setToolTipText(resources.getString("chkUseRandomFounderRetirement.toolTipText")); chkUseRandomFounderRetirement.setName("chkUseRandomFounderRetirement"); + chkUseRandomFounderRetirement.setEnabled(isUseTurnover); chkUseSubContractSoldiers = new JCheckBox(resources.getString("chkUseSubContractSoldiers.text")); chkUseSubContractSoldiers.setToolTipText(resources.getString("chkUseSubContractSoldiers.toolTipText")); chkUseSubContractSoldiers.setName("chkUseSubContractSoldiers"); + chkUseSubContractSoldiers.setEnabled(isUseTurnover); lblServiceContractDuration = new JLabel(resources.getString("lblServiceContractDuration.text")); lblServiceContractDuration.setToolTipText(resources.getString("lblServiceContractDuration.toolTipText")); lblServiceContractDuration.setName("lblServiceContractDuration"); + lblServiceContractDuration.setEnabled(isUseTurnover); spnServiceContractDuration = new JSpinner(new SpinnerNumberModel(36, 0, 120, 1)); spnServiceContractDuration.setToolTipText(resources.getString("lblServiceContractDuration.toolTipText")); spnServiceContractDuration.setName("spnServiceContractDuration"); + spnServiceContractDuration.setEnabled(isUseTurnover); lblServiceContractModifier = new JLabel(resources.getString("lblServiceContractModifier.text")); lblServiceContractModifier.setToolTipText(resources.getString("lblServiceContractModifier.toolTipText")); lblServiceContractModifier.setName("lblServiceContractModifier"); + lblServiceContractModifier.setEnabled(isUseTurnover); - spnServiceContractModifier = new JSpinner(new SpinnerNumberModel(36, 0, 120, 1)); + spnServiceContractModifier = new JSpinner(new SpinnerNumberModel(5, 0, 10, 1)); spnServiceContractModifier.setToolTipText(resources.getString("lblServiceContractModifier.toolTipText")); spnServiceContractModifier.setName("spnServiceContractModifier"); + spnServiceContractModifier.setEnabled(isUseTurnover); - turnoverModifiersPanel = createTurnoverModifiersPanel(); - turnoverPayoutPanel = createTurnoverPayoutPanel(); - - // Layout the Panel - randomRetirementPanel = new JDisableablePanel("randomRetirementPanel"); - randomRetirementPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("randomRetirementPanel.title"))); + turnoverAndRetentionSettingsPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionSettingsPanel.title"))); + turnoverAndRetentionSettingsPanel.setName("turnoverAndRetentionSettingsPanel"); + turnoverAndRetentionSettingsPanel.setEnabled(isUseTurnover); - final GroupLayout layout = new GroupLayout(randomRetirementPanel); - randomRetirementPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(turnoverAndRetentionSettingsPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + turnoverAndRetentionSettingsPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() @@ -3923,8 +3946,6 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblServiceContractModifier) .addComponent(spnServiceContractModifier, Alignment.LEADING)) - .addComponent(turnoverModifiersPanel) - .addComponent(turnoverPayoutPanel) ); layout.setHorizontalGroup( @@ -3948,108 +3969,118 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createSequentialGroup() .addComponent(lblServiceContractModifier) .addComponent(spnServiceContractModifier)) - .addComponent(turnoverModifiersPanel) - .addComponent(turnoverPayoutPanel) ); - return randomRetirementPanel; + return turnoverAndRetentionSettingsPanel; } - private JPanel createTurnoverModifiersPanel() { + private JPanel createTurnoverAndRetentionModifiersPanel() { + boolean isUseTurnover = campaign.getCampaignOptions().isUseRandomRetirement(); + chkUseCustomRetirementModifiers = new JCheckBox(resources.getString("chkUseCustomRetirementModifiers.text")); chkUseCustomRetirementModifiers.setToolTipText(resources.getString("chkUseCustomRetirementModifiers.toolTipText")); chkUseCustomRetirementModifiers.setName("chkUseCustomRetirementModifiers"); - - chkUseSkillModifiers = new JCheckBox(resources.getString("chkUseSkillModifiers.text")); - chkUseSkillModifiers.setToolTipText(resources.getString("chkUseSkillModifiers.toolTipText")); - chkUseSkillModifiers.setName("chkUseSkillModifiers"); + chkUseCustomRetirementModifiers.setEnabled(campaign.getCampaignOptions().isUseRandomRetirement()); chkUseFatigueModifiers = new JCheckBox(resources.getString("chkUseFatigueModifiers.text")); chkUseFatigueModifiers.setToolTipText(resources.getString("chkUseFatigueModifiers.toolTipText")); chkUseFatigueModifiers.setName("chkUseFatigueModifiers"); + chkUseFatigueModifiers.setEnabled(isUseTurnover); + + chkUseSkillModifiers = new JCheckBox(resources.getString("chkUseSkillModifiers.text")); + chkUseSkillModifiers.setToolTipText(resources.getString("chkUseSkillModifiers.toolTipText")); + chkUseSkillModifiers.setName("chkUseSkillModifiers"); + chkUseSkillModifiers.setEnabled(isUseTurnover); chkUseAgeModifiers = new JCheckBox(resources.getString("chkUseAgeModifiers.text")); chkUseAgeModifiers.setToolTipText(resources.getString("chkUseAgeModifiers.toolTipText")); chkUseAgeModifiers.setName("chkUseAgeModifiers"); + chkUseAgeModifiers.setEnabled(isUseTurnover); chkUseUnitRatingModifiers = new JCheckBox(resources.getString("chkUseUnitRatingModifiers.text")); chkUseUnitRatingModifiers.setToolTipText(resources.getString("chkUseUnitRatingModifiers.toolTipText")); chkUseUnitRatingModifiers.setName("chkUseUnitRatingModifiers"); + chkUseUnitRatingModifiers.setEnabled(isUseTurnover); chkUseFactionModifiers = new JCheckBox(resources.getString("chkUseFactionModifiers.text")); chkUseFactionModifiers.setToolTipText(resources.getString("chkUseFactionModifiers.toolTipText")); chkUseFactionModifiers.setName("chkUseFactionModifiers"); + chkUseFactionModifiers.setEnabled(isUseTurnover); chkUseMissionStatusModifiers = new JCheckBox(resources.getString("chkUseMissionStatusModifiers.text")); chkUseMissionStatusModifiers.setToolTipText(resources.getString("chkUseMissionStatusModifiers.toolTipText")); chkUseMissionStatusModifiers.setName("chkUseMissionStatusModifiers"); + chkUseMissionStatusModifiers.setEnabled(isUseTurnover); chkUseLoyaltyModifiers = new JCheckBox(resources.getString("chkUseLoyaltyModifiers.text")); chkUseLoyaltyModifiers.setToolTipText(resources.getString("chkUseLoyaltyModifiers.toolTipText")); chkUseLoyaltyModifiers.setName("chkUseLoyaltyModifiers"); + chkUseLoyaltyModifiers.setEnabled(isUseTurnover); chkUseLoyaltyModifiers.addActionListener(evt -> { - final boolean isEnabled = randomRetirementPanel.isEnabled() && chkUseLoyaltyModifiers.isSelected(); + final boolean isEnabled = chkUseLoyaltyModifiers.isSelected(); - // general handler - for (Component component : loyaltyPanel.getComponents()) { + for (Component component : loyaltySubPanel.getComponents()) { component.setEnabled(isEnabled); } - // border handler - loyaltyPanel.setEnabled(isEnabled); + loyaltySubPanel.setEnabled(isEnabled); }); - loyaltyPanel = createLoyaltyPanel(); + createLoyaltySubPanel(isUseTurnover); - turnoverModifiersPanel = new JDisableablePanel("turnoverModifierPanel"); - turnoverModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverModifierPanel.title"))); + turnoverAndRetentionModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionModifiersPanel.title"))); + turnoverAndRetentionModifiersPanel.setName("turnoverAndRetentionModifiersPanel"); + chkUseMissionStatusModifiers.setEnabled(isUseTurnover); - final GroupLayout layout = new GroupLayout(turnoverModifiersPanel); - turnoverModifiersPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(turnoverAndRetentionModifiersPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + turnoverAndRetentionModifiersPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkUseCustomRetirementModifiers) - .addComponent(chkUseSkillModifiers) .addComponent(chkUseFatigueModifiers) + .addComponent(chkUseSkillModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) + .addGap(15) .addComponent(chkUseLoyaltyModifiers) - .addComponent(loyaltyPanel) + .addComponent(loyaltySubPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseCustomRetirementModifiers) - .addComponent(chkUseSkillModifiers) .addComponent(chkUseFatigueModifiers) + .addComponent(chkUseSkillModifiers) .addComponent(chkUseAgeModifiers) .addComponent(chkUseUnitRatingModifiers) .addComponent(chkUseFactionModifiers) .addComponent(chkUseMissionStatusModifiers) .addComponent(chkUseLoyaltyModifiers) - .addComponent(loyaltyPanel) + .addComponent(loyaltySubPanel) ); - return turnoverModifiersPanel; + return turnoverAndRetentionModifiersPanel; } - private JPanel createLoyaltyPanel() { + private void createLoyaltySubPanel(boolean isUseTurnover) { chkUseHideLoyalty = new JCheckBox(resources.getString("chkUseHideLoyalty.text")); chkUseHideLoyalty.setToolTipText(resources.getString("chkUseHideLoyalty.toolTipText")); chkUseHideLoyalty.setName("chkUseHideLoyalty"); + chkUseHideLoyalty.setEnabled(isUseTurnover && campaign.getCampaignOptions().isUseLoyaltyModifiers()); - loyaltyPanel = new JDisableablePanel("loyaltyPanel"); - loyaltyPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("loyaltyPanel.title"))); + loyaltySubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("loyaltySubPanel.title"))); + loyaltySubPanel.setName("loyaltySubPanel"); + loyaltySubPanel.setEnabled(isUseTurnover && campaign.getCampaignOptions().isUseLoyaltyModifiers()); - final GroupLayout layout = new GroupLayout(loyaltyPanel); - loyaltyPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(loyaltySubPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + loyaltySubPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() @@ -4060,54 +4091,65 @@ private JPanel createLoyaltyPanel() { layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseHideLoyalty) ); - - return loyaltyPanel; } - private JPanel createTurnoverPayoutPanel() { + private JPanel createTurnoverAndRetentionPayoutPanel() { + boolean isUseTurnover = campaign.getCampaignOptions().isUseRandomRetirement(); + lblPayoutRateOfficer = new JLabel(resources.getString("lblPayoutRateOfficer.text")); lblPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); lblPayoutRateOfficer.setName("lblPayoutRateOfficer"); + lblPayoutRateOfficer.setEnabled(isUseTurnover); - spnPayoutRateOfficer = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1)); + spnPayoutRateOfficer = new JSpinner(new SpinnerNumberModel(3, 0, 12, 1)); spnPayoutRateOfficer.setToolTipText(resources.getString("lblPayoutRateOfficer.toolTipText")); spnPayoutRateOfficer.setName("spnPayoutRateOfficer"); + spnPayoutRateOfficer.setEnabled(isUseTurnover); lblPayoutRateEnlisted = new JLabel(resources.getString("lblPayoutRateEnlisted.text")); lblPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); lblPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); + lblPayoutRateEnlisted.setEnabled(isUseTurnover); - spnPayoutRateEnlisted = new JSpinner(new SpinnerNumberModel(3, 0, 100, 1)); + spnPayoutRateEnlisted = new JSpinner(new SpinnerNumberModel(3, 0, 12, 1)); spnPayoutRateEnlisted.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); - spnPayoutRateEnlisted.setName("spnPayoutRateEnlisted"); + spnPayoutRateEnlisted.setName("lblPayoutRateEnlisted"); + spnPayoutRateEnlisted.setEnabled(isUseTurnover); lblPayoutRetirementMultiplier = new JLabel(resources.getString("lblPayoutRetirementMultiplier.text")); lblPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); lblPayoutRetirementMultiplier.setName("lblPayoutRetirementMultiplier"); + lblPayoutRetirementMultiplier.setEnabled(isUseTurnover); - spnPayoutRetirementMultiplier = new JSpinner(new SpinnerNumberModel(24, 1, 100, 1)); - spnPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRetirementMultiplier.toolTipText")); + spnPayoutRetirementMultiplier = new JSpinner(new SpinnerNumberModel(24, 1, 120, 1)); + spnPayoutRetirementMultiplier.setToolTipText(resources.getString("lblPayoutRateEnlisted.toolTipText")); spnPayoutRetirementMultiplier.setName("spnPayoutRetirementMultiplier"); + spnPayoutRetirementMultiplier.setEnabled(isUseTurnover); chkUsePayoutServiceBonus = new JCheckBox(resources.getString("chkUsePayoutServiceBonus.text")); chkUsePayoutServiceBonus.setToolTipText(resources.getString("chkUsePayoutServiceBonus.toolTipText")); chkUsePayoutServiceBonus.setName("chkUsePayoutServiceBonus"); + chkUsePayoutServiceBonus.setEnabled(isUseTurnover); + chkUsePayoutServiceBonus.addActionListener(evt -> { + final boolean isEnabled = chkUsePayoutServiceBonus.isSelected(); - lblPayoutServiceBonusRate = new JLabel(resources.getString("lblPayoutServiceBonusRate.text")); - lblPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); - lblPayoutServiceBonusRate.setName("lblPayoutServiceBonusRate"); + for (Component component : payoutServiceBonusSubPanel.getComponents()) { + component.setEnabled(isEnabled); + } - spnPayoutServiceBonusRate = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); - spnPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); - spnPayoutServiceBonusRate.setName("spnPayoutServiceBonusRate"); + payoutServiceBonusSubPanel.setEnabled(isEnabled); + }); + + createPayoutServiceBonusSubPanel(isUseTurnover); - turnoverPayoutPanel = new JDisableablePanel("turnoverPayoutPanel"); - turnoverPayoutPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverPayoutPanel.title"))); + turnoverAndRetentionPayoutPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionPayoutPanel.title"))); + turnoverAndRetentionPayoutPanel.setName("turnoverAndRetentionPayoutPanel"); + turnoverAndRetentionPayoutPanel.setEnabled(isUseTurnover); - final GroupLayout layout = new GroupLayout(turnoverPayoutPanel); - turnoverPayoutPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(turnoverAndRetentionPayoutPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + turnoverAndRetentionPayoutPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() @@ -4120,10 +4162,9 @@ private JPanel createTurnoverPayoutPanel() { .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblPayoutRetirementMultiplier) .addComponent(spnPayoutRetirementMultiplier, Alignment.LEADING)) + .addGap(15) .addComponent(chkUsePayoutServiceBonus) - .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblPayoutServiceBonusRate) - .addComponent(spnPayoutServiceBonusRate, Alignment.LEADING)) + .addComponent(payoutServiceBonusSubPanel) ); layout.setHorizontalGroup( @@ -4138,105 +4179,150 @@ private JPanel createTurnoverPayoutPanel() { .addComponent(lblPayoutRetirementMultiplier) .addComponent(spnPayoutRetirementMultiplier)) .addComponent(chkUsePayoutServiceBonus) + .addComponent(payoutServiceBonusSubPanel) + ); + + return turnoverAndRetentionPayoutPanel; + } + + private void createPayoutServiceBonusSubPanel(boolean isUseTurnover) { + boolean isUseServiceBonus = campaign.getCampaignOptions().isUsePayoutServiceBonus(); + + lblPayoutServiceBonusRate = new JLabel(resources.getString("lblPayoutServiceBonusRate.text")); + lblPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); + lblPayoutServiceBonusRate.setName("lblPayoutServiceBonusRate"); + lblPayoutServiceBonusRate.setEnabled((isUseTurnover) && (isUseServiceBonus)); + + spnPayoutServiceBonusRate = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); + spnPayoutServiceBonusRate.setToolTipText(resources.getString("lblPayoutServiceBonusRate.toolTipText")); + spnPayoutServiceBonusRate.setName("spnPayoutServiceBonusRate"); + spnPayoutServiceBonusRate.setEnabled((isUseTurnover) && (isUseServiceBonus)); + + payoutServiceBonusSubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("payoutServiceBonusSubPanel.title"))); + payoutServiceBonusSubPanel.setName("payoutServiceBonusSubPanel"); + payoutServiceBonusSubPanel.setEnabled((isUseTurnover) && (isUseServiceBonus)); + + final GroupLayout layout = new GroupLayout(payoutServiceBonusSubPanel); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + payoutServiceBonusSubPanel.setLayout(layout); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblPayoutServiceBonusRate) + .addComponent(spnPayoutServiceBonusRate, Alignment.LEADING)) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(lblPayoutServiceBonusRate) .addComponent(spnPayoutServiceBonusRate)) ); - - return turnoverPayoutPanel; } - private JPanel createUnitCohesionPanel() { + private JPanel createTurnoverAndRetentionUnitCohesionPanel() { + boolean isUseTurnover = campaign.getCampaignOptions().isUseRandomRetirement(); + chkUseAdministrativeStrain = new JCheckBox(resources.getString("chkUseAdministrativeStrain.text")); chkUseAdministrativeStrain.setToolTipText(resources.getString("chkUseAdministrativeStrain.toolTipText")); chkUseAdministrativeStrain.setName("chkUseAdministrativeStrain"); + chkUseAdministrativeStrain.setEnabled(isUseTurnover); chkUseAdministrativeStrain.addActionListener(evt -> { final boolean isEnabled = chkUseAdministrativeStrain.isSelected(); - // general handlers - for (int index = 0; index < Arrays.stream(administrativeStrainPanel.getComponents()).count(); index++) { - administrativeStrainPanel.getComponent(index).setEnabled(isEnabled); + for (Component component : administrativeStrainSubPanel.getComponents()) { + component.setEnabled(isEnabled); } - // border handlers - administrativeStrainPanel.setEnabled(isEnabled); + administrativeStrainSubPanel.setEnabled(isEnabled); }); + createAdministrativeStrainSubPanel(isUseTurnover); + chkUseManagementSkill = new JCheckBox(resources.getString("chkUseManagementSkill.text")); chkUseManagementSkill.setToolTipText(resources.getString("chkUseManagementSkill.toolTipText")); chkUseManagementSkill.setName("chkUseManagementSkill"); + chkUseManagementSkill.setEnabled(isUseTurnover); chkUseManagementSkill.addActionListener(evt -> { final boolean isEnabled = chkUseManagementSkill.isSelected(); - // general handlers - for (int index = 0; index < Arrays.stream(managementSkillPanel.getComponents()).count(); index++) { - managementSkillPanel.getComponent(index).setEnabled(isEnabled); + for (Component component : managementSkillSubPanel.getComponents()) { + component.setEnabled(isEnabled); } - // border handlers - managementSkillPanel.setEnabled(isEnabled); + managementSkillSubPanel.setEnabled(isEnabled); }); - administrativeStrainPanel = createAdministrativeStrainPanel(); - managementSkillPanel = createManagementSkillPanel(); + createManagementSkillSubPanel(isUseTurnover); - unitCohesionPanel = new JDisableablePanel("unitCohesionPanel"); - unitCohesionPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("unitCohesionPanel.title"))); + turnoverAndRetentionUnitCohesionPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionUnitCohesionPanel.title"))); + turnoverAndRetentionUnitCohesionPanel.setName("turnoverAndRetentionUnitCohesionPanel"); + turnoverAndRetentionUnitCohesionPanel.setEnabled(isUseTurnover); - final GroupLayout layout = new GroupLayout(unitCohesionPanel); - unitCohesionPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(turnoverAndRetentionUnitCohesionPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + turnoverAndRetentionUnitCohesionPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkUseAdministrativeStrain) - .addComponent(administrativeStrainPanel) + .addComponent(administrativeStrainSubPanel) + .addGap(15) .addComponent(chkUseManagementSkill) - .addComponent(managementSkillPanel) + .addComponent(managementSkillSubPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseAdministrativeStrain) - .addComponent(administrativeStrainPanel) + .addComponent(administrativeStrainSubPanel) .addComponent(chkUseManagementSkill) - .addComponent(managementSkillPanel) + .addComponent(managementSkillSubPanel) ); - return unitCohesionPanel; + return turnoverAndRetentionUnitCohesionPanel; } - private JPanel createAdministrativeStrainPanel() { - lblAdministrativeStrain = new JLabel(resources.getString("lblAdministrativeStrain.text")); - lblAdministrativeStrain.setToolTipText(resources.getString("lblAdministrativeStrain.toolTipText")); - lblAdministrativeStrain.setName("lblAdministrativeStrain"); + private void createAdministrativeStrainSubPanel(boolean isUseTurnover) { + boolean isUseAdministrativeStrain = campaign.getCampaignOptions().isUseAdministrativeStrain(); - spnAdministrativeStrain = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); - spnAdministrativeStrain.setToolTipText(resources.getString("lblAdministrativeStrain.toolTipText")); - spnAdministrativeStrain.setName("spnAdministrativeStrain"); + lblAdministrativeCapacity = new JLabel(resources.getString("lblAdministrativeCapacity.text")); + lblAdministrativeCapacity.setToolTipText(resources.getString("lblAdministrativeCapacity.toolTipText")); + lblAdministrativeCapacity.setName("lblAdministrativeCapacity"); + lblAdministrativeCapacity.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); + + spnAdministrativeCapacity = new JSpinner(new SpinnerNumberModel(10, 1, 30, 1)); + spnAdministrativeCapacity.setToolTipText(resources.getString("lblAdministrativeCapacity.toolTipText")); + spnAdministrativeCapacity.setName("spnAdministrativeCapacity"); + spnAdministrativeCapacity.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); lblMultiCrewStrainDivider = new JLabel(resources.getString("lblMultiCrewStrainDivider.text")); lblMultiCrewStrainDivider.setToolTipText(resources.getString("lblMultiCrewStrainDivider.toolTipText")); lblMultiCrewStrainDivider.setName("lblMultiCrewStrainDivider"); + lblMultiCrewStrainDivider.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); - spnMultiCrewStrainDivider = new JSpinner(new SpinnerNumberModel(5, 1, 100, 1)); + spnMultiCrewStrainDivider = new JSpinner(new SpinnerNumberModel(5, 1, 25, 1)); spnMultiCrewStrainDivider.setToolTipText(resources.getString("lblMultiCrewStrainDivider.toolTipText")); spnMultiCrewStrainDivider.setName("spnMultiCrewStrainDivider"); + spnMultiCrewStrainDivider.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); - administrativeStrainPanel = new JDisableablePanel("administrativeStrainPanel"); - administrativeStrainPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("administrativeStrainPanel.title"))); + administrativeStrainSubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("administrativeStrainSubPanel.title"))); + administrativeStrainSubPanel.setName("administrativeStrainSubPanel"); + administrativeStrainSubPanel.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); - final GroupLayout layout = new GroupLayout(administrativeStrainPanel); - administrativeStrainPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(administrativeStrainSubPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + administrativeStrainSubPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblAdministrativeStrain) - .addComponent(spnAdministrativeStrain, Alignment.LEADING)) + .addComponent(lblAdministrativeCapacity) + .addComponent(spnAdministrativeCapacity, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblMultiCrewStrainDivider) .addComponent(spnMultiCrewStrainDivider, Alignment.LEADING)) @@ -4245,36 +4331,40 @@ private JPanel createAdministrativeStrainPanel() { layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(lblAdministrativeStrain) - .addComponent(spnAdministrativeStrain)) + .addComponent(lblAdministrativeCapacity) + .addComponent(spnAdministrativeCapacity)) .addGroup(layout.createSequentialGroup() .addComponent(lblMultiCrewStrainDivider) .addComponent(spnMultiCrewStrainDivider)) ); - - return administrativeStrainPanel; } - private JPanel createManagementSkillPanel() { + private void createManagementSkillSubPanel(boolean isUseTurnover) { + boolean isUseManagementSkill = campaign.getCampaignOptions().isUseManagementSkill(); + chkUseCommanderLeadershipOnly = new JCheckBox(resources.getString("chkUseCommanderLeadershipOnly.text")); chkUseCommanderLeadershipOnly.setToolTipText(resources.getString("chkUseCommanderLeadershipOnly.toolTipText")); chkUseCommanderLeadershipOnly.setName("chkUseCommanderLeadershipOnly"); + chkUseCommanderLeadershipOnly.setEnabled((isUseTurnover) && (isUseManagementSkill)); lblManagementSkillPenalty = new JLabel(resources.getString("lblManagementSkillPenalty.text")); lblManagementSkillPenalty.setToolTipText(resources.getString("lblManagementSkillPenalty.toolTipText")); lblManagementSkillPenalty.setName("lblManagementSkillPenalty"); + lblManagementSkillPenalty.setEnabled((isUseTurnover) && (isUseManagementSkill)); spnManagementSkillPenalty = new JSpinner(new SpinnerNumberModel(-2, -10, 0, 1)); spnManagementSkillPenalty.setToolTipText(resources.getString("lblManagementSkillPenalty.toolTipText")); spnManagementSkillPenalty.setName("spnManagementSkillPenalty"); + spnManagementSkillPenalty.setEnabled((isUseTurnover) && (isUseManagementSkill)); - managementSkillPanel = new JDisableablePanel("managementSkillPanel"); - managementSkillPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("managementSkillPanel.title"))); + managementSkillSubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("managementSkillSubPanel.title"))); + managementSkillSubPanel.setName("managementSkillSubPanel"); + managementSkillSubPanel.setEnabled((isUseTurnover) && (isUseManagementSkill)); - final GroupLayout layout = new GroupLayout(managementSkillPanel); - managementSkillPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(managementSkillSubPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + managementSkillSubPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() @@ -4291,57 +4381,39 @@ private JPanel createManagementSkillPanel() { .addComponent(lblManagementSkillPenalty) .addComponent(spnManagementSkillPenalty)) ); - - return managementSkillPanel; } - private JPanel createSharesPanel() { - chkUseShareSystem = new JCheckBox(resources.getString("chkUseShareSystem.text")); - chkUseShareSystem.setToolTipText(resources.getString("chkUseShareSystem.toolTipText")); - chkUseShareSystem.setName("chkUseShareSystem"); - chkUseShareSystem.addActionListener(evt -> { - final boolean isEnabled = chkUseShareSystem.isSelected(); - - // general handlers - for (int index = 0; index < Arrays.stream(sharesPanel.getComponents()).count(); index++) { - sharesPanel.getComponent(index).setEnabled(isEnabled); - } - - // border handlers - sharesPanel.setEnabled(isEnabled); - }); - - chkSharesExcludeLargeCraft = new JCheckBox(resources.getString("chkSharesExcludeLargeCraft.text")); - chkSharesExcludeLargeCraft.setToolTipText(resources.getString("chkSharesExcludeLargeCraft.toolTipText")); - chkSharesExcludeLargeCraft.setName("chkSharesExcludeLargeCraft"); + private JPanel createPersonnelRandomizationPanel() { + // Create Panel Components + chkUseDylansRandomXP = new JCheckBox(resources.getString("chkUseDylansRandomXP.text")); + chkUseDylansRandomXP.setToolTipText(resources.getString("chkUseDylansRandomXP.toolTipText")); + chkUseDylansRandomXP.setName("chkUseDylansRandomXP"); - chkSharesForAll = new JCheckBox(resources.getString("chkSharesForAll.text")); - chkSharesForAll.setToolTipText(resources.getString("chkSharesForAll.toolTipText")); - chkSharesForAll.setName("chkSharesForAll"); + randomOriginOptionsPanel = new RandomOriginOptionsPanel(getFrame(), campaign, comboFaction); - sharesPanel = new JDisableablePanel("sharesPanel"); - sharesPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("sharesPanel.title"))); + // Layout the Panel + final JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createTitledBorder(resources.getString("personnelRandomizationPanel.title"))); + panel.setName("personnelRandomizationPanel"); - final GroupLayout layout = new GroupLayout(sharesPanel); - sharesPanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(panel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); + panel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() - .addComponent(chkUseShareSystem) - .addComponent(chkSharesExcludeLargeCraft) - .addComponent(chkSharesForAll) + .addComponent(chkUseDylansRandomXP) + .addComponent(randomOriginOptionsPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) - .addComponent(chkUseShareSystem) - .addComponent(chkSharesExcludeLargeCraft) - .addComponent(chkSharesForAll) + .addComponent(chkUseDylansRandomXP) + .addComponent(randomOriginOptionsPanel) ); - return sharesPanel; + return panel; } private JPanel createFamilyPanel() { @@ -6714,6 +6786,77 @@ private JPanel createUsedPartsValueMultipliersPanel(boolean reverseQualities) { return panel; } + + private JPanel createSharesPanel() { + chkUseShareSystem = new JCheckBox(resources.getString("chkUseShareSystem.text")); + chkUseShareSystem.setToolTipText(resources.getString("chkUseShareSystem.toolTipText")); + chkUseShareSystem.setName("chkUseShareSystem"); + chkUseShareSystem.addActionListener(evt -> { + final boolean isEnabled = chkUseShareSystem.isSelected(); + + // general handlers + for (Component component : sharesSubPanel.getComponents()) { + component.setEnabled(isEnabled); + } + }); + + sharesSubPanel = createSharesSubPanel(); + for (Component component : sharesSubPanel.getComponents()) { + component.setEnabled(campaign.getCampaignOptions().isUseShareSystem()); + } + + sharesPanel = new JDisableablePanel("sharesPanel"); + sharesPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("sharesPanel.title"))); + sharesPanel.setEnabled(campaign.getCampaignOptions().isUseShareSystem()); + + final GroupLayout layout = new GroupLayout(sharesPanel); + sharesPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseShareSystem) + .addComponent(sharesSubPanel) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseShareSystem) + .addComponent(sharesSubPanel) + ); + + return sharesPanel; + } + + private JPanel createSharesSubPanel() { + chkSharesExcludeLargeCraft = new JCheckBox(resources.getString("chkSharesExcludeLargeCraft.text")); + chkSharesExcludeLargeCraft.setToolTipText(resources.getString("chkSharesExcludeLargeCraft.toolTipText")); + chkSharesExcludeLargeCraft.setName("chkSharesExcludeLargeCraft"); + + chkSharesForAll = new JCheckBox(resources.getString("chkSharesForAll.text")); + chkSharesForAll.setToolTipText(resources.getString("chkSharesForAll.toolTipText")); + chkSharesForAll.setName("chkSharesForAll"); + + sharesSubPanel = new JDisableablePanel("sharesSubPanel"); + + final GroupLayout layout = new GroupLayout(sharesSubPanel); + sharesSubPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup(layout + .createSequentialGroup() + .addComponent(chkSharesExcludeLargeCraft) + .addComponent(chkSharesForAll)); + + layout.setHorizontalGroup(layout + .createParallelGroup(Alignment.LEADING) + .addComponent(chkSharesExcludeLargeCraft) + .addComponent(chkSharesForAll)); + + return sharesSubPanel; + } //endregion Finances Tab //region Rank Systems Tab @@ -7398,14 +7541,12 @@ public void setOptions(@Nullable CampaignOptions options, } //endregion Personnel Tab - //region Life Paths Tab - // Personnel Randomization - chkUseDylansRandomXP.setSelected(options.isUseDylansRandomXP()); - randomOriginOptionsPanel.setOptions(options.getRandomOriginOptions()); - - // Retirement + //region Turnover and Retention Tab + // Header chkUseRetirementDateTracking.setSelected(options.isUseRetirementDateTracking()); chkUseRandomRetirement.setSelected(options.isUseRandomRetirement()); + + // Settings comboTurnoverTargetNumberMethod.setSelectedItem(options.getTurnoverTargetNumberMethod()); comboTurnoverDifficulty.setSelectedItem(options.getTurnoverDifficulty()); spnTurnoverFixedTargetNumber.setValue(options.getTurnoverFixedTargetNumber()); @@ -7416,34 +7557,39 @@ public void setOptions(@Nullable CampaignOptions options, spnServiceContractDuration.setValue(options.getServiceContractDuration()); spnServiceContractModifier.setValue(options.getServiceContractModifier()); + // Modifiers chkUseCustomRetirementModifiers.setSelected(options.isUseCustomRetirementModifiers()); - chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); - chkUseSkillModifiers.setSelected(options.isUseSkillModifiers()); chkUseFatigueModifiers.setSelected(options.isUseFatigueModifiers()); + chkUseSkillModifiers.setSelected(options.isUseSkillModifiers()); + chkUseAgeModifiers.setSelected(options.isUseAgeModifiers()); chkUseUnitRatingModifiers.setSelected(options.isUseUnitRatingModifiers()); chkUseFactionModifiers.setSelected(options.isUseFactionModifiers()); chkUseMissionStatusModifiers.setSelected(options.isUseMissionStatusModifiers()); - chkUseLoyaltyModifiers.setSelected(options.isUseLoyaltyModifiers()); chkUseHideLoyalty.setSelected(options.isUseHideLoyalty()); + // Payouts spnPayoutRateOfficer.setValue(options.getPayoutRateOfficer()); spnPayoutRateEnlisted.setValue(options.getPayoutRateEnlisted()); spnPayoutRetirementMultiplier.setValue(options.getPayoutRetirementMultiplier()); chkUsePayoutServiceBonus.setSelected(options.isUsePayoutServiceBonus()); spnPayoutServiceBonusRate.setValue(options.getPayoutServiceBonusRate()); + // Unit Cohesion chkUseAdministrativeStrain.setSelected(options.isUseAdministrativeStrain()); - spnAdministrativeStrain.setValue(options.getAdministrativeStrain()); + chkUseManagementSkill.setSelected(options.isUseManagementSkill()); + + spnAdministrativeCapacity.setValue(options.getAdministrativeCapacity()); spnMultiCrewStrainDivider.setValue(options.getMultiCrewStrainDivider()); - chkUseManagementSkill.setSelected(options.isUseManagementSkill()); chkUseCommanderLeadershipOnly.setSelected(options.isUseCommanderLeadershipOnly()); spnManagementSkillPenalty.setValue(options.getManagementSkillPenalty()); + //endregion Turnover and Retention Tab - chkUseShareSystem.setSelected(options.isUseShareSystem()); - chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); - chkSharesForAll.setSelected(options.isSharesForAll()); + //region Life Paths Tab + // Personnel Randomization + chkUseDylansRandomXP.setSelected(options.isUseDylansRandomXP()); + randomOriginOptionsPanel.setOptions(options.getRandomOriginOptions()); // Family comboFamilyDisplayLevel.setSelectedItem(options.getFamilyDisplayLevel()); @@ -7608,6 +7754,11 @@ public void setOptions(@Nullable CampaignOptions options, spnDamagedPartsValueMultiplier.setValue(options.getDamagedPartsValueMultiplier()); spnUnrepairablePartsValueMultiplier.setValue(options.getUnrepairablePartsValueMultiplier()); spnCancelledOrderRefundMultiplier.setValue(options.getCancelledOrderRefundMultiplier()); + + // Shares + chkUseShareSystem.setSelected(options.isUseShareSystem()); + chkSharesExcludeLargeCraft.setSelected(options.isSharesExcludeLargeCraft()); + chkSharesForAll.setSelected(options.isSharesForAll()); //endregion Finances Tab //region Mercenary Tab @@ -8037,14 +8188,12 @@ public void updateOptions() { } //endregion Personnel Tab - //region Life Paths Tab - // Personnel Randomization - options.setUseDylansRandomXP(chkUseDylansRandomXP.isSelected()); - options.setRandomOriginOptions(randomOriginOptionsPanel.createOptionsFromPanel()); - - // Retirement + //region Turnover and Retention + // Header options.setUseRetirementDateTracking(chkUseRetirementDateTracking.isSelected()); options.setUseRandomRetirement(chkUseRandomRetirement.isSelected()); + + // Settings options.setTurnoverTargetNumberMethod(comboTurnoverTargetNumberMethod.getSelectedItem()); options.setTurnoverDifficulty(comboTurnoverDifficulty.getSelectedItem()); options.setTurnoverFixedTargetNumber((Integer) spnTurnoverFixedTargetNumber.getValue()); @@ -8055,34 +8204,40 @@ public void updateOptions() { options.setServiceContractDuration((Integer) spnServiceContractDuration.getValue()); options.setServiceContractModifier((Integer) spnServiceContractModifier.getValue()); + // Modifiers options.setUseCustomRetirementModifiers(chkUseCustomRetirementModifiers.isSelected()); - options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); - options.setUseSkillModifiers(chkUseSkillModifiers.isSelected()); options.setUseFatigueModifiers(chkUseFatigueModifiers.isSelected()); + options.setUseSkillModifiers(chkUseSkillModifiers.isSelected()); + options.setUseAgeModifiers(chkUseAgeModifiers.isSelected()); options.setUseUnitRatingModifiers(chkUseUnitRatingModifiers.isSelected()); options.setUseFactionModifiers(chkUseFactionModifiers.isSelected()); options.setUseMissionStatusModifiers(chkUseMissionStatusModifiers.isSelected()); - options.setUseLoyaltyModifiers(chkUseLoyaltyModifiers.isSelected()); options.setUseHideLoyalty(chkUseHideLoyalty.isSelected()); + // Payouts options.setPayoutRateOfficer((Integer) spnPayoutRateOfficer.getValue()); options.setPayoutRateEnlisted((Integer) spnPayoutRateEnlisted.getValue()); options.setPayoutRetirementMultiplier((Integer) spnPayoutRetirementMultiplier.getValue()); options.setUsePayoutServiceBonus(chkUsePayoutServiceBonus.isSelected()); options.setPayoutServiceBonusRate((Integer) spnPayoutServiceBonusRate.getValue()); + // Unit Cohesion options.setUseAdministrativeStrain(chkUseAdministrativeStrain.isSelected()); - options.setAdministrativeStrain((Integer) spnAdministrativeStrain.getValue()); + options.setUseManagementSkill(chkUseManagementSkill.isSelected()); + + options.setAdministrativeCapacity((Integer) spnAdministrativeCapacity.getValue()); options.setMultiCrewStrainDivider((Integer) spnMultiCrewStrainDivider.getValue()); - options.setUseManagementSkill(chkUseManagementSkill.isSelected()); options.setUseCommanderLeadershipOnly(chkUseCommanderLeadershipOnly.isSelected()); options.setManagementSkillPenalty((Integer) spnManagementSkillPenalty.getValue()); - options.setUseShareSystem(chkUseShareSystem.isSelected()); - options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); - options.setSharesForAll(chkSharesForAll.isSelected()); + //endregion Turnover and Retention + + //region Life Paths Tab + // Personnel Randomization + options.setUseDylansRandomXP(chkUseDylansRandomXP.isSelected()); + options.setRandomOriginOptions(randomOriginOptionsPanel.createOptionsFromPanel()); // Family options.setFamilyDisplayLevel(comboFamilyDisplayLevel.getSelectedItem()); @@ -8202,6 +8357,10 @@ public void updateOptions() { options.setDamagedPartsValueMultiplier((Double) spnDamagedPartsValueMultiplier.getValue()); options.setUnrepairablePartsValueMultiplier((Double) spnUnrepairablePartsValueMultiplier.getValue()); options.setCancelledOrderRefundMultiplier((Double) spnCancelledOrderRefundMultiplier.getValue()); + + options.setUseShareSystem(chkUseShareSystem.isSelected()); + options.setSharesExcludeLargeCraft(chkSharesExcludeLargeCraft.isSelected()); + options.setSharesForAll(chkSharesForAll.isSelected()); //endregion Finances Tab //start SPA From d117c26bd9a44f8071976ad73969e05e7adf756b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 21 May 2024 11:56:48 -0500 Subject: [PATCH 042/101] Refactored fatigue system settings in Campaign Options This commit reorganizes the layout of the turn-over and retention tab in campaign options, moving the fatigue settings into the Turnover tab. --- .../CampaignOptionsDialog.properties | 12 +-- MekHQ/src/mekhq/campaign/CampaignOptions.java | 66 ++++++++-------- .../mekhq/gui/panes/CampaignOptionsPane.java | 79 ++++++++++--------- 3 files changed, 80 insertions(+), 77 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 5f9b88ac3e..7783580f25 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -171,7 +171,6 @@ chkUseMissionStatusModifiers.toolTipText=Allows mission failure; success; and co chkUseLoyaltyModifiers.text=Use Loyalty chkUseLoyaltyModifiers.toolTipText=If enabled, personnel have a random loyalty rating ranging between -3 and 3. -loyaltySubPanel.title=Loyalty Options chkUseHideLoyalty.text=Hide Loyalty chkUseHideLoyalty.toolTipText=If enabled, loyalty modifiers will be hidden. @@ -186,7 +185,6 @@ lblPayoutRetirementMultiplier.toolTipText=The number of months to multiply payou chkUsePayoutServiceBonus.text=Use Service Bonus chkUsePayoutServiceBonus.toolTipText=If enabled, personnel increase their payouts based on years of service. -payoutServiceBonusSubPanel.title=Service Bonus Options lblPayoutServiceBonusRate.text=Bonus % lblPayoutServiceBonusRate.toolTipText=This sets the payout percentage increase per year of service, applied when personnel resign or retire. @@ -198,14 +196,12 @@ chkUseManagementSkill.text=Enable Management Skill chkUseManagementSkill.toolTipText=This option applies a modifier to turnover checks based on the Leadership of commanding personnel. ### Administrative Strain -administrativeStrainSubPanel.title=Administrative Strain lblAdministrativeCapacity.text=Administrative Capacity lblAdministrativeCapacity.toolTipText=How many personnel can be supported per combined rank in Administration. lblMultiCrewStrainDivider.text=Multi-Crew Divider lblMultiCrewStrainDivider.toolTipText=For multi-crew units, or ProtoMech points, divide crew size by this value. ### Management Skill -managementSkillSubPanel.title=Management Skill chkUseCommanderLeadershipOnly.text=Only Use Commander's Leadership chkUseCommanderLeadershipOnly.toolTipText=Personnel only use the Leadership skill of whoever has the overall Commander flag. If disabled, personnel will use the Leadership of their profession group commander. lblManagementSkillPenalty.text=Unskilled Penalty @@ -295,12 +291,12 @@ chkAtBPrisonerRansom.text=Enable AtB Prisoner Ransom chkAtBPrisonerRansom.toolTipText=Prisoners can be ransomed back to their previous faction as per the AtB ruleset. # Fatigue -fatiguePanel.title=Fatigue (CamOps) -lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
This option requires Fatigue to be enabled in MegaMek. -chkUseFatigue.text=Enable Fatigue +fatiguePanel.title=CampOps Fatigue +lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
CamOps Fatigue has added functionality if Fatigue is enabled in the MegaMek client settings.
Non-combat modifiers are not implemented. +chkUseFatigue.text=Enable chkUseFatigue.toolTipText=If enabled, combat personnel will gain Fatigue whenever they are deployed to a Scenario or entering an unexplored StratCon hex. lblFatigueRate.text=Fatigue Rate -lblFatigueRate.toolTipText=How many fatigue points should be gained per Scenario or unexplored StratCon hex? +lblFatigueRate.toolTipText=How many fatigue points should be gained per Scenario or unexplored StratCon hex?
Forces with the Scout role only count the first unexplored hex. lblFieldKitchenCapacity.text=Field Kitchen Capacity lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field Kitchen? Reduces effective Fatigue by 1. lblFatigueLeaveThreshold.text=Automatic Leave Threshold (unofficial) diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index d7bce2e864..7901a74a65 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -232,12 +232,6 @@ public static String getTransitUnitName(final int unit) { private boolean useAtBPrisonerDefection; private boolean useAtBPrisonerRansom; - // Fatigue - private boolean useFatigue; - private Integer fatigueRate; - private Integer fieldKitchenCapacity; - private Integer fatigueLeaveThreshold; - // Dependent private RandomDependentMethod randomDependentMethod; private boolean useRandomDependentAddition; @@ -293,6 +287,11 @@ public static String getTransitUnitName(final int unit) { private boolean useCommanderLeadershipOnly; private Integer managementSkillPenalty; + private boolean useFatigue; + private Integer fatigueRate; + private Integer fieldKitchenCapacity; + private Integer fatigueLeaveThreshold; + // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -673,10 +672,6 @@ public CampaignOptions() { setDisplayPersonnelLog(false); setDisplayScenarioLog(false); setDisplayKillRecord(false); - setUseFatigue(true); - setFatigueRate(1); - setFieldKitchenCapacity(150); - setFatigueLeaveThreshold(13); // Expanded Personnel Information setUseTimeInService(false); @@ -934,6 +929,11 @@ public CampaignOptions() { setUseManagementSkill(true); setUseCommanderLeadershipOnly(false); setManagementSkillPenalty(-2); + + setUseFatigue(true); + setFatigueRate(1); + setFieldKitchenCapacity(150); + setFatigueLeaveThreshold(13); //endregion Turnover and Retention //region Finances Tab @@ -4175,10 +4175,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayPersonnelLog", isDisplayPersonnelLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayScenarioLog", isDisplayScenarioLog()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "displayKillRecord", isDisplayKillRecord()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFatigue", isUseFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueRate", getFatigueRate()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLeaveThreshold", getFatigueLeaveThreshold()); //endregion General Personnel //region Expanded Personnel Information @@ -4270,9 +4266,10 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useCommanderLeadershipOnly", isUseCommanderLeadershipOnly()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "managementSkillPenalty", getManagementSkillPenalty()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", isSharesExcludeLargeCraft()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesForAll", isSharesForAll()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useFatigue", isUseFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueRate", getFatigueRate()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLeaveThreshold", getFatigueLeaveThreshold()); //endregion Retirement //region Family @@ -4425,6 +4422,11 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "damagedPartsValueMultiplier", getDamagedPartsValueMultiplier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "unrepairablePartsValueMultiplier", getUnrepairablePartsValueMultiplier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "cancelledOrderRefundMultiplier", getCancelledOrderRefundMultiplier()); + + // Shares + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useShareSystem", isUseShareSystem()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesExcludeLargeCraft", isSharesExcludeLargeCraft()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "sharesForAll", isSharesForAll()); //endregion Price Multipliers //endregion Finances Tab @@ -4778,14 +4780,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setDisplayScenarioLog(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("displayKillRecord")) { retVal.setDisplayKillRecord(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useFatigue")) { - retVal.setUseFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("fatigueRate")) { - retVal.setFatigueRate(Integer.parseInt(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { - retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("fatigueLeaveThreshold")) { - retVal.setFatigueLeaveThreshold(Integer.parseInt(wn2.getTextContent().trim())); //endregion General Personnel //region Expanded Personnel Information @@ -4962,12 +4956,14 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseCommanderLeadershipOnly(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("managementSkillPenalty")) { retVal.setManagementSkillPenalty(Integer.parseInt(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { - retVal.setUseShareSystem(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { - retVal.setSharesExcludeLargeCraft(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("sharesForAll")) { - retVal.setSharesForAll(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useFatigue")) { + retVal.setUseFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("fatigueRate")) { + retVal.setFatigueRate(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("fieldKitchenCapacity")) { + retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("fatigueLeaveThreshold")) { + retVal.setFatigueLeaveThreshold(Integer.parseInt(wn2.getTextContent().trim())); //endregion Retirement //region Family @@ -5291,6 +5287,14 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUnrepairablePartsValueMultiplier(Double.parseDouble(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("cancelledOrderRefundMultiplier")) { retVal.setCancelledOrderRefundMultiplier(Double.parseDouble(wn2.getTextContent().trim())); + + // Shares + } else if (wn2.getNodeName().equalsIgnoreCase("useShareSystem")) { + retVal.setUseShareSystem(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("sharesExcludeLargeCraft")) { + retVal.setSharesExcludeLargeCraft(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("sharesForAll")) { + retVal.setSharesForAll(Boolean.parseBoolean(wn2.getTextContent().trim())); //endregion Price Multipliers //endregion Finances Tab diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 203e2a888c..880456aeb0 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -211,17 +211,6 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkDisplayScenarioLog; private JCheckBox chkDisplayKillRecord; - private JPanel fatiguePanel; - private JPanel fatiguePanelElements; - private JLabel lblFatigueWarning; - private JCheckBox chkUseFatigue; - private JLabel lblFatigueRate; - private JSpinner spnFatigueRate; - private JLabel lblFieldKitchenCapacity; - private JSpinner spnFieldKitchenCapacity; - private JLabel lblFatigueLeaveThreshold; - private JSpinner spnFatigueLeaveThreshold; - // Expanded Personnel private JCheckBox chkUseTimeInService; private MMComboBox comboTimeInServiceDisplayFormat; @@ -324,6 +313,19 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseCommanderLeadershipOnly; private JLabel lblManagementSkillPenalty; private JSpinner spnManagementSkillPenalty; + + // Fatigue + private JPanel fatiguePanel = new JPanel(); + private JCheckBox chkUseFatigue; + + private JPanel fatigueSubPanel = new JPanel(); + private JLabel lblFatigueWarning; + private JLabel lblFatigueRate; + private JSpinner spnFatigueRate; + private JLabel lblFieldKitchenCapacity; + private JSpinner spnFieldKitchenCapacity; + private JLabel lblFatigueLeaveThreshold; + private JSpinner spnFatigueLeaveThreshold; //endregion Turnover and Retention Tab //region Life Paths Tab @@ -3161,10 +3163,6 @@ private JScrollPane createPersonnelTab() { gbc.gridx++; personnelPanel.add(createPrisonerPanel(), gbc); - gbc.gridx = 0; - gbc.gridy++; - personnelPanel.add(createFatiguePanel(), gbc); - gbc.gridx++; personnelPanel.add(createDependentPanel(), gbc); @@ -3205,6 +3203,10 @@ private JScrollPane createTurnoverAndRetentionTab() { gbc.gridx++; turnoverAndRetentionPanel.add(createTurnoverAndRetentionUnitCohesionPanel(), gbc); + gbc.gridx = 0; + gbc.gridy++; + turnoverAndRetentionPanel.add(createFatiguePanel(), gbc); + final JScrollPane scrollPersonnel = new JScrollPane(turnoverAndRetentionPanel); scrollPersonnel.setPreferredSize(new Dimension(500, 400)); @@ -3380,7 +3382,7 @@ private JPanel createGeneralPersonnelPanel() { return panel; } - private JPanel createFatiguePanelElements() { + private void createFatigueSubPanel() { lblFatigueWarning = new JLabel(resources.getString("lblFatigueWarning.text")); lblFatigueWarning.setName("lblFatigueWarning"); lblFatigueWarning.setEnabled(campaign.getCampaignOptions().isUseFatigue()); @@ -3415,14 +3417,14 @@ private JPanel createFatiguePanelElements() { spnFatigueLeaveThreshold.setName("spnFatigueLeaveThreshold"); spnFatigueLeaveThreshold.setEnabled(campaign.getCampaignOptions().isUseFatigue()); - fatiguePanelElements = new JPanel(); - fatiguePanelElements.setName("fatiguePanelElements"); - fatiguePanelElements.setEnabled(campaign.getCampaignOptions().isUseFatigue()); + fatigueSubPanel.setBorder(BorderFactory.createTitledBorder("")); + fatigueSubPanel.setName("fatigueSubPanel"); + fatigueSubPanel.setEnabled(campaign.getCampaignOptions().isUseFatigue()); - final GroupLayout layout = new GroupLayout(fatiguePanelElements); + final GroupLayout layout = new GroupLayout(fatigueSubPanel); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); - fatiguePanelElements.setLayout(layout); + fatigueSubPanel.setLayout(layout); layout.setVerticalGroup( layout.createSequentialGroup() @@ -3451,8 +3453,6 @@ private JPanel createFatiguePanelElements() { .addComponent(lblFatigueLeaveThreshold) .addComponent(spnFatigueLeaveThreshold)) ); - - return fatiguePanelElements; } private JPanel createExpandedPersonnelInformationPanel() { @@ -3985,7 +3985,7 @@ private JPanel createTurnoverAndRetentionModifiersPanel() { chkUseFatigueModifiers = new JCheckBox(resources.getString("chkUseFatigueModifiers.text")); chkUseFatigueModifiers.setToolTipText(resources.getString("chkUseFatigueModifiers.toolTipText")); chkUseFatigueModifiers.setName("chkUseFatigueModifiers"); - chkUseFatigueModifiers.setEnabled(isUseTurnover); + chkUseFatigueModifiers.setEnabled((isUseTurnover) && (campaign.getCampaignOptions().isUseFatigue())); chkUseSkillModifiers = new JCheckBox(resources.getString("chkUseSkillModifiers.text")); chkUseSkillModifiers.setToolTipText(resources.getString("chkUseSkillModifiers.toolTipText")); @@ -4073,7 +4073,7 @@ private void createLoyaltySubPanel(boolean isUseTurnover) { chkUseHideLoyalty.setName("chkUseHideLoyalty"); chkUseHideLoyalty.setEnabled(isUseTurnover && campaign.getCampaignOptions().isUseLoyaltyModifiers()); - loyaltySubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("loyaltySubPanel.title"))); + loyaltySubPanel.setBorder(BorderFactory.createTitledBorder("")); loyaltySubPanel.setName("loyaltySubPanel"); loyaltySubPanel.setEnabled(isUseTurnover && campaign.getCampaignOptions().isUseLoyaltyModifiers()); @@ -4198,7 +4198,7 @@ private void createPayoutServiceBonusSubPanel(boolean isUseTurnover) { spnPayoutServiceBonusRate.setName("spnPayoutServiceBonusRate"); spnPayoutServiceBonusRate.setEnabled((isUseTurnover) && (isUseServiceBonus)); - payoutServiceBonusSubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("payoutServiceBonusSubPanel.title"))); + payoutServiceBonusSubPanel.setBorder(BorderFactory.createTitledBorder("")); payoutServiceBonusSubPanel.setName("payoutServiceBonusSubPanel"); payoutServiceBonusSubPanel.setEnabled((isUseTurnover) && (isUseServiceBonus)); @@ -4309,7 +4309,7 @@ private void createAdministrativeStrainSubPanel(boolean isUseTurnover) { spnMultiCrewStrainDivider.setName("spnMultiCrewStrainDivider"); spnMultiCrewStrainDivider.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); - administrativeStrainSubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("administrativeStrainSubPanel.title"))); + administrativeStrainSubPanel.setBorder(BorderFactory.createTitledBorder("")); administrativeStrainSubPanel.setName("administrativeStrainSubPanel"); administrativeStrainSubPanel.setEnabled((isUseTurnover) && (isUseAdministrativeStrain)); @@ -4357,7 +4357,7 @@ private void createManagementSkillSubPanel(boolean isUseTurnover) { spnManagementSkillPenalty.setName("spnManagementSkillPenalty"); spnManagementSkillPenalty.setEnabled((isUseTurnover) && (isUseManagementSkill)); - managementSkillSubPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("managementSkillSubPanel.title"))); + managementSkillSubPanel.setBorder(BorderFactory.createTitledBorder("")); managementSkillSubPanel.setName("managementSkillSubPanel"); managementSkillSubPanel.setEnabled((isUseTurnover) && (isUseManagementSkill)); @@ -4491,35 +4491,38 @@ private JPanel createFatiguePanel() { chkUseFatigue.addActionListener(evt -> { final boolean isEnabled = chkUseFatigue.isSelected(); - for (Component component : fatiguePanel.getComponents()) { + for (Component component : fatigueSubPanel.getComponents()) { component.setEnabled(isEnabled); } + + fatigueSubPanel.setEnabled(isEnabled); + + chkUseFactionModifiers.setEnabled(isEnabled); }); - fatiguePanel = createFatiguePanelElements(); + createFatigueSubPanel(); - final JPanel panel = new JPanel(); - panel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); - panel.setName("fatiguePanel"); + fatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); + fatiguePanel.setName("fatiguePanel"); - final GroupLayout layout = new GroupLayout(panel); - panel.setLayout(layout); + final GroupLayout layout = new GroupLayout(fatiguePanel); + fatiguePanel.setLayout(layout); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkUseFatigue) - .addComponent(fatiguePanel) + .addComponent(fatigueSubPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkUseFatigue) - .addComponent(fatiguePanel) + .addComponent(fatigueSubPanel) ); - return panel; + return fatiguePanel; } private void createRandomDependentPanel() { From 4899df4530889ffabf5b0da70fd7fb4a158801a2 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 21 May 2024 19:06:11 -0500 Subject: [PATCH 043/101] Implemented morale system Campaign Options with GUI integration --- .../CampaignOptionsDialog.properties | 51 +- .../mekhq/resources/Personnel.properties | 16 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 267 +++++++++++ .../enums/ForceReliabilityMethod.java | 80 ++++ .../mekhq/gui/panes/CampaignOptionsPane.java | 441 +++++++++++++++++- 5 files changed, 831 insertions(+), 24 deletions(-) create mode 100644 MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 7783580f25..518b16a325 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -291,7 +291,7 @@ chkAtBPrisonerRansom.text=Enable AtB Prisoner Ransom chkAtBPrisonerRansom.toolTipText=Prisoners can be ransomed back to their previous faction as per the AtB ruleset. # Fatigue -fatiguePanel.title=CampOps Fatigue +turnoverAndRetentionFatiguePanel.title=CampOps Fatigue lblFatigueWarning.text=Client must be reloaded whenever enabling or disabling Fatigue.
CamOps Fatigue has added functionality if Fatigue is enabled in the MegaMek client settings.
Non-combat modifiers are not implemented. chkUseFatigue.text=Enable chkUseFatigue.toolTipText=If enabled, combat personnel will gain Fatigue whenever they are deployed to a Scenario or entering an unexplored StratCon hex. @@ -302,6 +302,55 @@ lblFieldKitchenCapacity.toolTipText=How many personnel can be served per Field K lblFatigueLeaveThreshold.text=Automatic Leave Threshold (unofficial) lblFatigueLeaveThreshold.toolTipText=Automatically assign personnel to Leave status once fatigue (including modifiers) has reached this value.
Personnel heal fatigue twice as fast while on leave and will automatically return to active duty once their fatigue has returned to 0.
Set to 0 to disable. +# Morale +turnoverAndRetentionMoralePanel.title=Morale +chkUseMorale.text=Enable Morale +chkUseMorale.toolTipText=Enables the morale system. This system is based on the CamOps Morale rules on p217.
Full documentation is available in MekHQ/docs/Turnover and Retention Module.pdf + +lblForceReliabilityMethod.text=Force Reliability Method +lblForceReliabilityMethod.toolTipText=How should Force Reliability be calculated (referred to as 'Loyalty' on CamOps p217)? +chkUseDesertions.text=Enable Desertions +chkUseDesertions.toolTipText=Low morale may cause personnel to randomly desert the company. +chkUseEmergencyBonuses.text=Issue Emergency Bonuses +chkUseEmergencyBonuses.toolTipText=Attempt to quell desertion with an emergency bonus payout to the deserter (equal to their monthly salary).
Provides a +1 bonus on the desertion roll. This does not affect mutinies. +chkUseSabotage.text=Enable Sabotage +chkUseSabotage.toolTipText=Low morale influences maintenance checks. +chkUseMutinies.text=Enable Mutinies +chkUseMutinies.toolTipText=Low morale may cause the unit to mutiny. + +turnoverAndRetentionMoraleModifiersPanel.title=Morale Modifiers +lblCustomMoraleModifier.text=Custom Modifier +lblCustomMoraleModifier.toolTipText=This is a direct modifier applied to both mutiny and desertion checks. +chkUseMoraleModifierFatigue.text=Fatigue +chkUseMoraleModifierFatigue.toolTipText=Personal fatigue influences both desertion and mutiny checks. +chkUseMoraleModifierProfession.text=Profession +chkUseMoraleModifierProfession.toolTipText=Professions influence desertion and mutiny checks. Personnel with multiple professions use the average of both modifiers. +chkUseMoraleModifierForceReliability.text=Force Reliability +chkUseMoraleModifierForceReliability.toolTipText=Force reliability influences both desertion and mutiny checks. +chkUseMoraleModifierManagementSkill.text=Management Skill +chkUseMoraleModifierManagementSkill.toolTipText=Having a positive management skill modifier improves both desertion and mutiny checks. +chkUseRuleWithIronFist.text=Rule With an Iron Fist +chkUseRuleWithIronFist.toolTipText=Where there is a whip, there is a way. Desertion and Mutiny checks have a +1 modifier, but mutinies are more destructive. + +turnoverAndRetentionMoraleChangePanel.title=Morale Change Triggers +chkUseMoraleModifierFieldControl.text=Field Control +chkUseMoraleModifierFieldControl.toolTipText=Failing to control the field at the end of a scenario decreases morale. +chkUseMoraleModifierMissionStatus.text=Mission Status +chkUseMoraleModifierMissionStatus.toolTipText=Succeeding or failing, a Mission affects morale. +chkUseMoraleModifierLeaderLoss.text=Leader Loss +chkUseMoraleModifierLeaderLoss.toolTipText=The loss of an officer (or the unit Commander) decreases morale. +chkUseMoraleModifierCombatLoss.text=Combat Losses +chkUseMoraleModifierCombatLoss.toolTipText=Significant losses during a scenario decreases morale. +chkUseMoraleModifierDesertion.text=Desertion +chkUseMoraleModifierDesertion.toolTipText=Suffering desertion decreases morale. +chkUseMoraleModifierMutiny.text=Mutiny +chkUseMoraleModifierMutiny.toolTipText=Suffering a mutiny substantially decreases morale. +chkUseMoraleModifierRest.text=Rest +chkUseMoraleModifierRest.toolTipText=Morale improves during periods spent planet-side without an active contract. + +chkUseMoraleModifierMissedPayDay.text=Missed Payment +chkUseMoraleModifierMissedPayDay.toolTipText=Missing a payday substantially decreases morale. + # Dependent dependentPanel.title=Dependents (Unofficial) dependentPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index 4ef1f155b9..059c4b8613 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -464,6 +464,22 @@ TurnoverTargetNumberMethod.ADMINISTRATION.toolTipText=The Target number is based TurnoverTargetNumberMethod.NEGOTIATION.text=Negotiation TurnoverTargetNumberMethod.NEGOTIATION.toolTipText=The Target number is based on the mean Negotiation skill across all Admin/HR personnel. +# ForceReliabilityMethod Enum +ForceReliabilityMethod.EQUIPMENT.text=Dynamic: Equipment Rating +ForceReliabilityMethod.EQUIPMENT.toolTipText=Desertion and Mutiny checks are modified by average equipment rating (CamOps p217) +ForceReliabilityMethod.LOYALTY.text=Dynamic: Loyalty +ForceReliabilityMethod.LOYALTY.toolTipText=Desertion and Mutiny checks are modified by average personnel loyalty.
If loyalty is disabled, this setting is identical to 'Dynamic: Equipment Rating.' +ForceReliabilityMethod.OVERRIDE_A.text=Fixed: A (Fanatical / Clan Front Line) +ForceReliabilityMethod.OVERRIDE_A.toolTipText=Desertion and Mutiny checks have a +1 modifier. +ForceReliabilityMethod.OVERRIDE_B.text=Fixed: B (Fanatical-Reliable / Clan Second Line) +ForceReliabilityMethod.OVERRIDE_B.toolTipText=Desertion checks have a +1 modifier. Mutiny checks have no modifier +ForceReliabilityMethod.OVERRIDE_C.text=Fixed: C (Reliable) +ForceReliabilityMethod.OVERRIDE_C.toolTipText=Desertion and Mutiny checks have no modifier. +ForceReliabilityMethod.OVERRIDE_D.text=Fixed: D (Reliable-Questionable / Clan Garrison or Solahma) +ForceReliabilityMethod.OVERRIDE_D.toolTipText=Desertion checks have no modifier. Mutiny checks have a -1 modifier +ForceReliabilityMethod.OVERRIDE_F.text=Fixed: F (Questionable) +ForceReliabilityMethod.OVERRIDE_F.toolTipText=Desertion and Mutiny checks have a -1 modifier. + # RankSystemType Enum RankSystemType.DEFAULT.text=Default Rank System RankSystemType.DEFAULT.toolTipText=This rank system is a default MekHQ rank system and is stored within the standard data path. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 7901a74a65..45c329e6fd 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -292,6 +292,28 @@ public static String getTransitUnitName(final int unit) { private Integer fieldKitchenCapacity; private Integer fatigueLeaveThreshold; + private boolean useMorale; + private ForceReliabilityMethod forceReliabilityMethod; + private boolean useDesertions; + private boolean useEmergencyBonuses; + private boolean useSabotage; + private boolean useMutinies; + private boolean useRuleWithIronFist; + + private Integer customMoraleModifier; + private boolean useMoraleModifierFieldControl; + private boolean useMoraleModifierMissionStatus; + private boolean useMoraleModifierLeaderLoss; + private boolean useMoraleModifierCombatLoss; + private boolean useMoraleModifierDesertion; + private boolean useMoraleModifierMutiny; + private boolean useMoraleModifierRest; + private boolean useMoraleModifierFatigue; + private boolean useMoraleModifierProfession; + private boolean useMoraleModifierForceReliability; + private boolean useMoraleModifierManagementSkill; + private boolean useMoraleModifierMissedPayDay; + // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -934,6 +956,28 @@ public CampaignOptions() { setFatigueRate(1); setFieldKitchenCapacity(150); setFatigueLeaveThreshold(13); + + setUseMorale(true); + setForceReliabilityMethod(ForceReliabilityMethod.LOYALTY); + setUseDesertions(true); + setUseEmergencyBonuses(true); + setUseSabotage(true); + setUseMutinies(true); + setUseRuleWithIronFist(false); + + setCustomMoraleModifier(-2); + setUseMoraleModifierFieldControl(true); + setUseMoraleModifierMissionStatus(true); + setUseMoraleModifierLeaderLoss(true); + setUseMoraleModifierCombatLoss(true); + setUseMoraleModifierDesertion(true); + setUseMoraleModifierMutiny(true); + setUseMoraleModifierRest(true); + setUseMoraleModifierFatigue(true); + setUseMoraleModifierProfession(true); + setUseMoraleModifierForceReliability(true); + setUseMoraleModifierManagementSkill(true); + setUseMoraleModifierMissedPayDay(true); //endregion Turnover and Retention //region Finances Tab @@ -1466,6 +1510,167 @@ public Integer getFatigueLeaveThreshold() { public void setFatigueLeaveThreshold(final Integer fatigueLeaveThreshold) { this.fatigueLeaveThreshold = fatigueLeaveThreshold; } + + public boolean isUseMorale() { + return useMorale; + } + + public void setUseMorale(final boolean useMorale) { + this.useMorale = useMorale; + } + + public ForceReliabilityMethod getForceReliabilityMethod() { + return forceReliabilityMethod; + } + + public void setForceReliabilityMethod(final ForceReliabilityMethod forceReliabilityMethod) { + this.forceReliabilityMethod = forceReliabilityMethod; + } + + public boolean isUseDesertions() { + return useDesertions; + } + + public void setUseDesertions(final boolean useDesertions) { + this.useDesertions = useDesertions; + } + + public boolean isUseEmergencyBonuses() { + return useEmergencyBonuses; + } + + public void setUseEmergencyBonuses(final boolean useEmergencyBonuses) { + this.useEmergencyBonuses = useEmergencyBonuses; + } + + public boolean isUseSabotage() { + return useSabotage; + } + + public void setUseSabotage(final boolean useSabotage) { + this.useSabotage = useSabotage; + } + + public boolean isUseMutinies() { + return useMutinies; + } + + public void setUseMutinies(final boolean useMutinies) { + this.useMutinies = useMutinies; + } + + public boolean isUseRuleWithIronFist() { + return useRuleWithIronFist; + } + + public void setUseRuleWithIronFist(final boolean useRuleWithIronFist) { + this.useRuleWithIronFist = useRuleWithIronFist; + } + + public Integer getCustomMoraleModifier() { + return customMoraleModifier; + } + + public void setCustomMoraleModifier(final Integer customMoraleModifier) { + this.customMoraleModifier = customMoraleModifier; + } + + public boolean isUseMoraleModifierProfession() { + return useMoraleModifierProfession; + } + + public void setUseMoraleModifierProfession(final boolean useMoraleModifierProfession) { + this.useMoraleModifierProfession = useMoraleModifierProfession; + } + + public boolean isUseMoraleModifierForceReliability() { + return useMoraleModifierForceReliability; + } + + public void setUseMoraleModifierForceReliability(final boolean useMoraleModifierForceReliability) { + this.useMoraleModifierForceReliability = useMoraleModifierForceReliability; + } + + public boolean isUseMoraleModifierManagementSkill() { + return useMoraleModifierManagementSkill; + } + + public void setUseMoraleModifierManagementSkill(final boolean useMoraleModifierManagementSkill) { + this.useMoraleModifierManagementSkill = useMoraleModifierManagementSkill; + } + + public boolean isUseMoraleModifierMissedPayDay() { + return useMoraleModifierMissedPayDay; + } + + public void setUseMoraleModifierMissedPayDay(final boolean useMoraleModifierMissedPayDay) { + this.useMoraleModifierMissedPayDay = useMoraleModifierMissedPayDay; + } + + public boolean isUseMoraleModifierFatigue() { + return useMoraleModifierFatigue; + } + + public void setUseMoraleModifierFatigue(final boolean useMoraleModifierFatigue) { + this.useMoraleModifierFatigue = useMoraleModifierFatigue; + } + + public boolean isUseMoraleModifierFieldControl() { + return useMoraleModifierFieldControl; + } + + public void setUseMoraleModifierFieldControl(final boolean useMoraleModifierFieldControl) { + this.useMoraleModifierFieldControl = useMoraleModifierFieldControl; + } + + public boolean isUseMoraleModifierMissionStatus() { + return useMoraleModifierMissionStatus; + } + + public void setUseMoraleModifierMissionStatus(final boolean useMoraleModifierMissionStatus) { + this.useMoraleModifierMissionStatus = useMoraleModifierMissionStatus; + } + + public boolean isUseMoraleModifierLeaderLoss() { + return useMoraleModifierLeaderLoss; + } + + public void setUseMoraleModifierLeaderLoss(final boolean useMoraleModifierLeaderLoss) { + this.useMoraleModifierLeaderLoss = useMoraleModifierLeaderLoss; + } + + public boolean isUseMoraleModifierCombatLoss() { + return useMoraleModifierCombatLoss; + } + + public void setUseMoraleModifierCombatLoss(final boolean useMoraleModifierCombatLoss) { + this.useMoraleModifierCombatLoss = useMoraleModifierCombatLoss; + } + + public boolean isUseMoraleModifierDesertion() { + return useMoraleModifierDesertion; + } + + public void setUseMoraleModifierDesertion(final boolean useMoraleModifierDesertion) { + this.useMoraleModifierDesertion = useMoraleModifierDesertion; + } + + public boolean isUseMoraleModifierMutiny() { + return useMoraleModifierMutiny; + } + + public void setUseMoraleModifierMutiny(final boolean useMoraleModifierMutiny) { + this.useMoraleModifierMutiny = useMoraleModifierMutiny; + } + + public boolean isUseMoraleModifierRest() { + return useMoraleModifierRest; + } + + public void setUseMoraleModifierRest(final boolean useMoraleModifierRest) { + this.useMoraleModifierRest = useMoraleModifierRest; + } + //endregion General Personnel //region Expanded Personnel Information @@ -4270,6 +4475,28 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueRate", getFatigueRate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fieldKitchenCapacity", getFieldKitchenCapacity()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLeaveThreshold", getFatigueLeaveThreshold()); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMorale", isUseMorale()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "forceReliabilityMethod", getForceReliabilityMethod().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useDesertions", isUseDesertions()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useEmergencyBonuses", isUseEmergencyBonuses()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMutinies", isUseMutinies()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRuleWithIronFist", isUseRuleWithIronFist()); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "customMoraleModifier", getCustomMoraleModifier()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierFieldControl", isUseMoraleModifierFieldControl()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierMissionStatus", isUseMoraleModifierMissionStatus()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierLeaderLoss", isUseMoraleModifierLeaderLoss()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCombatLoss", isUseMoraleModifierCombatLoss()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierDesertion", isUseMoraleModifierDesertion()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierMutiny", isUseMoraleModifierMutiny()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierRest", isUseMoraleModifierRest()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierFatigue", isUseMoraleModifierFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierProfession", isUseMoraleModifierProfession()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierForceReliability", isUseMoraleModifierForceReliability()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierManagementSkill", isUseMoraleModifierManagementSkill()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "chkUseMoraleModifierMissedPayDay", isUseMoraleModifierMissedPayDay()); //endregion Retirement //region Family @@ -4964,6 +5191,46 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setFieldKitchenCapacity(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("fatigueLeaveThreshold")) { retVal.setFatigueLeaveThreshold(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMorale")) { + retVal.setUseMorale(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("forceReliabilityMethod")) { + retVal.setForceReliabilityMethod(ForceReliabilityMethod.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useDesertions")) { + retVal.setUseDesertions(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useEmergencyBonuses")) { + retVal.setUseEmergencyBonuses(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useSabotage")) { + retVal.setUseSabotage(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMutinies")) { + retVal.setUseMutinies(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useRuleWithIronFist")) { + retVal.setUseRuleWithIronFist(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("customMoraleModifier")) { + retVal.setCustomMoraleModifier(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierScenarioStatus")) { + retVal.setUseMoraleModifierFieldControl(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMissionStatus")) { + retVal.setUseMoraleModifierMissionStatus(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierLeaderLoss")) { + retVal.setUseMoraleModifierLeaderLoss(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierCombatLoss")) { + retVal.setUseMoraleModifierCombatLoss(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierDesertion")) { + retVal.setUseMoraleModifierDesertion(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMutiny")) { + retVal.setUseMoraleModifierMutiny(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierInactivity")) { + retVal.setUseMoraleModifierRest(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierFatigue")) { + retVal.setUseMoraleModifierFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierRole")) { + retVal.setUseMoraleModifierProfession(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierForceReliability")) { + retVal.setUseMoraleModifierForceReliability(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierManagementSkill")) { + retVal.setUseMoraleModifierManagementSkill(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMissedPayment")) { + retVal.setUseMoraleModifierMissedPayDay(Boolean.parseBoolean(wn2.getTextContent().trim())); //endregion Retirement //region Family diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java new file mode 100644 index 0000000000..e76f90d05a --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021-2022 - 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.personnel.enums; + +import mekhq.MekHQ; + +import java.util.ResourceBundle; + +public enum ForceReliabilityMethod { + EQUIPMENT("ForceReliabilityMethod.EQUIPMENT.text", "ForceReliabilityMethod.EQUIPMENT.toolTipText"), + LOYALTY("ForceReliabilityMethod.LOYALTY.text", "ForceReliabilityMethod.LOYALTY.toolTipText"), + OVERRIDE_A("ForceReliabilityMethod.OVERRIDE_A.text", "ForceReliabilityMethod.OVERRIDE_A.toolTipText"), + OVERRIDE_B("ForceReliabilityMethod.OVERRIDE_B.text", "ForceReliabilityMethod.OVERRIDE_B.toolTipText"), + OVERRIDE_C("ForceReliabilityMethod.OVERRIDE_C.text", "ForceReliabilityMethod.OVERRIDE_C.toolTipText"), + OVERRIDE_D("ForceReliabilityMethod.OVERRIDE_D.text", "ForceReliabilityMethod.OVERRIDE_D.toolTipText"), + OVERRIDE_F("ForceReliabilityMethod.OVERRIDE_F.text", "ForceReliabilityMethod.OVERRIDE_F.toolTipText"); + + private final String name; + private final String toolTipText; + + ForceReliabilityMethod(final String name, final String toolTipText) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", + MekHQ.getMHQOptions().getLocale()); + this.name = resources.getString(name); + this.toolTipText = resources.getString(toolTipText); + } + + public String getToolTipText() { + return toolTipText; + } + + public boolean isEquipment() { + return this == EQUIPMENT; + } + + public boolean isLoyalty() { + return this == LOYALTY; + } + + public boolean isOverrideA() { + return this == OVERRIDE_A; + } + + public boolean isOverrideB() { + return this == OVERRIDE_B; + } + + public boolean isOverrideC() { + return this == OVERRIDE_C; + } + + public boolean isOverrideD() { + return this == OVERRIDE_D; + } + + public boolean isOverrideF() { + return this == OVERRIDE_F; + } + + @Override + public String toString() { + return name; + } +} diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 880456aeb0..f650a870c1 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -315,7 +315,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JSpinner spnManagementSkillPenalty; // Fatigue - private JPanel fatiguePanel = new JPanel(); + private JPanel turnoverAndRetentionFatiguePanel = new JPanel(); private JCheckBox chkUseFatigue; private JPanel fatigueSubPanel = new JPanel(); @@ -326,6 +326,37 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JSpinner spnFieldKitchenCapacity; private JLabel lblFatigueLeaveThreshold; private JSpinner spnFatigueLeaveThreshold; + + // Morale + private JPanel turnoverAndRetentionMoralePanel = new JPanel(); + private JCheckBox chkUseMorale; + + private JPanel moraleSubPanel = new JPanel(); + private JLabel lblForceReliabilityMethod; + private MMComboBox comboForceReliabilityMethod; + private JCheckBox chkUseDesertions; + private JCheckBox chkUseEmergencyBonuses; + private JCheckBox chkUseSabotage; + private JCheckBox chkUseMutinies; + + private JPanel turnoverAndRetentionMoraleModifiersPanel = new JPanel(); + private JLabel lblCustomMoraleModifier; + private JSpinner spnCustomMoraleModifier; + private JCheckBox chkUseMoraleModifierFatigue; + private JCheckBox chkUseMoraleModifierProfession; + private JCheckBox chkUseMoraleModifierForceReliability; + private JCheckBox chkUseMoraleModifierManagementSkill; + private JCheckBox chkUseMoraleModifierMissedPayDay; + private JCheckBox chkUseRuleWithIronFist; + + private JPanel turnoverAndRetentionMoraleChangePanel = new JPanel(); + private JCheckBox chkUseMoraleModifierFieldControl; + private JCheckBox chkUseMoraleModifierMissionStatus; + private JCheckBox chkUseMoraleModifierLeaderLoss; + private JCheckBox chkUseMoraleModifierCombatLoss; + private JCheckBox chkUseMoraleModifierDesertion; + private JCheckBox chkUseMoraleModifierMutiny; + private JCheckBox chkUseMoraleModifierRest; //endregion Turnover and Retention Tab //region Life Paths Tab @@ -3200,12 +3231,22 @@ private JScrollPane createTurnoverAndRetentionTab() { gbc.gridx++; turnoverAndRetentionPanel.add(createTurnoverAndRetentionPayoutPanel(), gbc); + gbc.gridx = 0; + gbc.gridy++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionFatiguePanel(), gbc); + gbc.gridx++; turnoverAndRetentionPanel.add(createTurnoverAndRetentionUnitCohesionPanel(), gbc); gbc.gridx = 0; gbc.gridy++; - turnoverAndRetentionPanel.add(createFatiguePanel(), gbc); + turnoverAndRetentionPanel.add(createTurnoverAndRetentionMoralePanel(), gbc); + + gbc.gridx++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionMoraleModifiersPanel(), gbc); + + gbc.gridx++; + turnoverAndRetentionPanel.add(createTurnoverAndRetentionMoraleChangePanel(), gbc); final JScrollPane scrollPersonnel = new JScrollPane(turnoverAndRetentionPanel); scrollPersonnel.setPreferredSize(new Dimension(500, 400)); @@ -3820,6 +3861,7 @@ private JPanel createTurnoverAndRetentionHeaderPanel() { private JPanel createTurnoverAndRetentionSettingsPanel() { boolean isUseTurnover = campaign.getCampaignOptions().isUseRandomRetirement(); + boolean isUseFixedTargetNumber = campaign.getCampaignOptions().getTurnoverTargetNumberMethod().isFixed(); lblTurnoverTargetNumberMethod = new JLabel(resources.getString("lblTurnoverTargetNumberMethod.text")); lblTurnoverTargetNumberMethod.setToolTipText(resources.getString("lblTurnoverTargetNumberMethod.toolTipText")); @@ -3858,23 +3900,23 @@ public Component getListCellRendererComponent(final JList list, final Object lblTurnoverDifficulty = new JLabel(resources.getString("lblTurnoverDifficulty.text")); lblTurnoverDifficulty.setToolTipText(resources.getString("lblTurnoverDifficulty.toolTipText")); lblTurnoverDifficulty.setName("lblTurnoverDifficulty"); - lblTurnoverDifficulty.setEnabled(isUseTurnover && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); + lblTurnoverDifficulty.setEnabled((isUseTurnover) && (!isUseFixedTargetNumber)); final DefaultComboBoxModel skillLevelModel = new DefaultComboBoxModel<>(Skills.SKILL_LEVELS); skillLevelModel.removeElement(SkillLevel.NONE); comboTurnoverDifficulty = new MMComboBox<>("comboTurnoverDifficulty", skillLevelModel); comboTurnoverDifficulty.setToolTipText(resources.getString("lblTurnoverDifficulty.toolTipText")); - comboTurnoverDifficulty.setEnabled(isUseTurnover && !comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); + comboTurnoverDifficulty.setEnabled((isUseTurnover) && (!isUseFixedTargetNumber)); lblTurnoverFixedTargetNumber = new JLabel(resources.getString("lblTurnoverFixedTargetNumber.text")); lblTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); lblTurnoverFixedTargetNumber.setName("lblTurnoverFixedTargetNumber"); - lblTurnoverFixedTargetNumber.setEnabled(isUseTurnover && comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); + lblTurnoverFixedTargetNumber.setEnabled((isUseTurnover) && (isUseFixedTargetNumber)); spnTurnoverFixedTargetNumber = new JSpinner(new SpinnerNumberModel(3, 0, 10, 1)); spnTurnoverFixedTargetNumber.setToolTipText(resources.getString("lblTurnoverFixedTargetNumber.toolTipText")); spnTurnoverFixedTargetNumber.setName("spnTurnoverFixedTargetNumber"); - spnTurnoverFixedTargetNumber.setEnabled(isUseTurnover && comboTurnoverTargetNumberMethod.getSelectedItem().isFixed()); + spnTurnoverFixedTargetNumber.setEnabled((isUseTurnover) && (isUseFixedTargetNumber)); chkUseYearEndRandomRetirement = new JCheckBox(resources.getString("chkUseYearEndRandomRetirement.text")); chkUseYearEndRandomRetirement.setToolTipText(resources.getString("chkUseYearEndRandomRetirement.toolTipText")); @@ -4237,6 +4279,8 @@ private JPanel createTurnoverAndRetentionUnitCohesionPanel() { } administrativeStrainSubPanel.setEnabled(isEnabled); + + chkUseMoraleModifierManagementSkill.setEnabled((isEnabled) && (chkUseMorale.isSelected())); }); createAdministrativeStrainSubPanel(isUseTurnover); @@ -4253,6 +4297,8 @@ private JPanel createTurnoverAndRetentionUnitCohesionPanel() { } managementSkillSubPanel.setEnabled(isEnabled); + + chkUseMoraleModifierManagementSkill.setEnabled((isEnabled) && (chkUseMorale.isSelected())); }); createManagementSkillSubPanel(isUseTurnover); @@ -4484,7 +4530,7 @@ private JPanel createDependentPanel() { return panel; } - private JPanel createFatiguePanel() { + private JPanel createTurnoverAndRetentionFatiguePanel() { chkUseFatigue = new JCheckBox(resources.getString("chkUseFatigue.text")); chkUseFatigue.setToolTipText(resources.getString("chkUseFatigue.toolTipText")); chkUseFatigue.setName("chkUseFatigue"); @@ -4497,16 +4543,17 @@ private JPanel createFatiguePanel() { fatigueSubPanel.setEnabled(isEnabled); - chkUseFactionModifiers.setEnabled(isEnabled); + chkUseFatigueModifiers.setEnabled(isEnabled); + chkUseMoraleModifierFatigue.setEnabled((isEnabled) && (chkUseMorale.isSelected())); }); createFatigueSubPanel(); - fatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("fatiguePanel.title"))); - fatiguePanel.setName("fatiguePanel"); + turnoverAndRetentionFatiguePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionFatiguePanel.title"))); + turnoverAndRetentionFatiguePanel.setName("turnoverAndRetentionFatiguePanel"); - final GroupLayout layout = new GroupLayout(fatiguePanel); - fatiguePanel.setLayout(layout); + final GroupLayout layout = new GroupLayout(turnoverAndRetentionFatiguePanel); + turnoverAndRetentionFatiguePanel.setLayout(layout); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); @@ -4522,7 +4569,304 @@ private JPanel createFatiguePanel() { .addComponent(fatigueSubPanel) ); - return fatiguePanel; + return turnoverAndRetentionFatiguePanel; + } + + private JPanel createTurnoverAndRetentionMoralePanel() { + chkUseMorale = new JCheckBox(resources.getString("chkUseMorale.text")); + chkUseMorale.setToolTipText(resources.getString("chkUseMorale.toolTipText")); + chkUseMorale.setName("chkUseMorale"); + chkUseMorale.addActionListener(evt -> { + final boolean isEnabled = chkUseMorale.isSelected(); + + // General Handlers + for (Component component : moraleSubPanel.getComponents()) { + component.setEnabled(isEnabled); + } + + for (Component component : turnoverAndRetentionMoraleModifiersPanel.getComponents()) { + component.setEnabled(isEnabled); + } + + for (Component component : turnoverAndRetentionMoraleChangePanel.getComponents()) { + component.setEnabled(isEnabled); + } + + // Border Handlers + moraleSubPanel.setEnabled(isEnabled); + turnoverAndRetentionMoraleModifiersPanel.setEnabled(isEnabled); + turnoverAndRetentionMoraleChangePanel.setEnabled(isEnabled); + + // Special Case Handlers + chkUseMoraleModifierDesertion.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); + chkUseMoraleModifierManagementSkill.setEnabled((isEnabled) && (chkUseManagementSkill.isSelected())); + + chkUseMoraleModifierMutiny.setEnabled((isEnabled) && (chkUseMutinies.isSelected())); + chkUseMoraleModifierFatigue.setEnabled((isEnabled) && (chkUseFatigue.isSelected())); + }); + + createMoraleSubPanel(); + + turnoverAndRetentionMoralePanel = new JDisableablePanel("turnoverAndRetentionMoralePanel"); + turnoverAndRetentionMoralePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionMoralePanel.title"))); + + final GroupLayout layout = new GroupLayout(turnoverAndRetentionMoralePanel); + turnoverAndRetentionMoralePanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseMorale) + .addComponent(moraleSubPanel) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseMorale) + .addComponent(moraleSubPanel) + ); + + return turnoverAndRetentionMoralePanel; + } + + private void createMoraleSubPanel() { + boolean isUseMorale = campaign.getCampaignOptions().isUseMorale(); + + lblForceReliabilityMethod = new JLabel(resources.getString("lblForceReliabilityMethod.text")); + lblForceReliabilityMethod.setToolTipText(resources.getString("lblForceReliabilityMethod.toolTipText")); + lblForceReliabilityMethod.setName("lblForceReliabilityMethod"); + lblForceReliabilityMethod.setEnabled(isUseMorale); + + comboForceReliabilityMethod = new MMComboBox<>("comboForceReliabilityMethod", ForceReliabilityMethod.values()); + comboForceReliabilityMethod.setToolTipText(resources.getString("lblForceReliabilityMethod.toolTipText")); + comboForceReliabilityMethod.setEnabled(isUseMorale); + comboForceReliabilityMethod.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(final JList list, final Object value, + final int index, final boolean isSelected, + final boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof ForceReliabilityMethod) { + list.setToolTipText(((ForceReliabilityMethod) value).getToolTipText()); + } + return this; + } + }); + + chkUseDesertions = new JCheckBox(resources.getString("chkUseDesertions.text")); + chkUseDesertions.setToolTipText(resources.getString("chkUseDesertions.toolTipText")); + chkUseDesertions.setName("chkUseDesertions"); + chkUseDesertions.setEnabled(isUseMorale); + chkUseDesertions.addActionListener(evt -> { + final boolean isEnabled = chkUseDesertions.isSelected(); + + chkUseMoraleModifierDesertion.setEnabled(isEnabled); + }); + + chkUseEmergencyBonuses = new JCheckBox(resources.getString("chkUseEmergencyBonuses.text")); + chkUseEmergencyBonuses.setToolTipText(resources.getString("chkUseEmergencyBonuses.toolTipText")); + chkUseEmergencyBonuses.setName("chkUseEmergencyBonuses"); + chkUseEmergencyBonuses.setEnabled(isUseMorale); + + chkUseSabotage = new JCheckBox(resources.getString("chkUseSabotage.text")); + chkUseSabotage.setToolTipText(resources.getString("chkUseSabotage.toolTipText")); + chkUseSabotage.setName("chkUseSabotage"); + chkUseSabotage.setEnabled(isUseMorale); + + chkUseMutinies = new JCheckBox(resources.getString("chkUseMutinies.text")); + chkUseMutinies.setToolTipText(resources.getString("chkUseMutinies.toolTipText")); + chkUseMutinies.setName("chkUseMutinies"); + chkUseMutinies.setEnabled(isUseMorale); + chkUseMutinies.addActionListener(evt -> { + final boolean isEnabled = chkUseMutinies.isSelected(); + + chkUseMoraleModifierMutiny.setEnabled(isEnabled); + }); + + moraleSubPanel.setName("moraleSubPanel"); + moraleSubPanel.setBorder(BorderFactory.createTitledBorder("")); + moraleSubPanel.setEnabled(isUseMorale); + + final GroupLayout layout = new GroupLayout(moraleSubPanel); + moraleSubPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblForceReliabilityMethod) + .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) + .addComponent(chkUseDesertions) + .addComponent(chkUseEmergencyBonuses) + .addComponent(chkUseSabotage) + .addComponent(chkUseMutinies) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblForceReliabilityMethod) + .addComponent(comboForceReliabilityMethod)) + .addComponent(chkUseDesertions) + .addComponent(chkUseEmergencyBonuses) + .addComponent(chkUseSabotage) + .addComponent(chkUseMutinies) + ); + } + + private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { + boolean isUseMorale = campaign.getCampaignOptions().isUseMorale(); + + lblCustomMoraleModifier = new JLabel(resources.getString("lblCustomMoraleModifier.text")); + lblCustomMoraleModifier.setToolTipText(resources.getString("lblCustomMoraleModifier.toolTipText")); + lblCustomMoraleModifier.setName("lblCustomMoraleModifier"); + lblCustomMoraleModifier.setEnabled(isUseMorale); + + spnCustomMoraleModifier = new JSpinner(new SpinnerNumberModel(-2, -5, 5, 1)); + spnCustomMoraleModifier.setToolTipText(resources.getString("lblCustomMoraleModifier.toolTipText")); + spnCustomMoraleModifier.setName("spnCustomMoraleModifier"); + spnCustomMoraleModifier.setEnabled(isUseMorale); + + chkUseMoraleModifierFatigue = new JCheckBox(resources.getString("chkUseMoraleModifierFatigue.text")); + chkUseMoraleModifierFatigue.setToolTipText(resources.getString("chkUseMoraleModifierFatigue.toolTipText")); + chkUseMoraleModifierFatigue.setName("chkUseMoraleModifierFatigue"); + chkUseMoraleModifierFatigue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseFatigue())); + + chkUseMoraleModifierProfession = new JCheckBox(resources.getString("chkUseMoraleModifierProfession.text")); + chkUseMoraleModifierProfession.setToolTipText(resources.getString("chkUseMoraleModifierProfession.toolTipText")); + chkUseMoraleModifierProfession.setName("chkUseMoraleModifierProfession"); + chkUseMoraleModifierProfession.setEnabled(isUseMorale); + + chkUseMoraleModifierForceReliability = new JCheckBox(resources.getString("chkUseMoraleModifierForceReliability.text")); + chkUseMoraleModifierForceReliability.setToolTipText(resources.getString("chkUseMoraleModifierForceReliability.toolTipText")); + chkUseMoraleModifierForceReliability.setName("chkUseMoraleModifierForceReliability"); + chkUseMoraleModifierForceReliability.setEnabled(isUseMorale); + + chkUseMoraleModifierManagementSkill = new JCheckBox(resources.getString("chkUseMoraleModifierManagementSkill.text")); + chkUseMoraleModifierManagementSkill.setToolTipText(resources.getString("chkUseMoraleModifierManagementSkill.toolTipText")); + chkUseMoraleModifierManagementSkill.setName("chkUseMoraleModifierManagementSkill"); + chkUseMoraleModifierManagementSkill.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); + + chkUseRuleWithIronFist = new JCheckBox(resources.getString("chkUseRuleWithIronFist.text")); + chkUseRuleWithIronFist.setToolTipText(resources.getString("chkUseRuleWithIronFist.toolTipText")); + chkUseRuleWithIronFist.setName("chkUseRuleWithIronFist"); + chkUseRuleWithIronFist.setEnabled(isUseMorale); + + turnoverAndRetentionMoraleModifiersPanel.setName("turnoverAndRetentionMoraleModifiersPanel"); + turnoverAndRetentionMoraleModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionMoraleModifiersPanel.title"))); + turnoverAndRetentionMoraleModifiersPanel.setEnabled(isUseMorale); + + final GroupLayout layout = new GroupLayout(turnoverAndRetentionMoraleModifiersPanel); + turnoverAndRetentionMoraleModifiersPanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblCustomMoraleModifier) + .addComponent(spnCustomMoraleModifier, Alignment.LEADING)) + .addComponent(chkUseMoraleModifierFatigue) + .addComponent(chkUseMoraleModifierProfession) + .addComponent(chkUseMoraleModifierForceReliability) + .addComponent(chkUseMoraleModifierManagementSkill) + .addComponent(chkUseRuleWithIronFist) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblCustomMoraleModifier) + .addComponent(spnCustomMoraleModifier)) + .addComponent(chkUseMoraleModifierFatigue) + .addComponent(chkUseMoraleModifierProfession) + .addComponent(chkUseMoraleModifierForceReliability) + .addComponent(chkUseMoraleModifierManagementSkill) + .addComponent(chkUseRuleWithIronFist) + ); + + return turnoverAndRetentionMoraleModifiersPanel; + } + + private JPanel createTurnoverAndRetentionMoraleChangePanel() { + boolean isUseMorale = campaign.getCampaignOptions().isUseMorale(); + + chkUseMoraleModifierFieldControl = new JCheckBox(resources.getString("chkUseMoraleModifierFieldControl.text")); + chkUseMoraleModifierFieldControl.setToolTipText(resources.getString("chkUseMoraleModifierFieldControl.toolTipText")); + chkUseMoraleModifierFieldControl.setName("chkUseMoraleModifierFieldControl"); + chkUseMoraleModifierFieldControl.setEnabled(isUseMorale); + + chkUseMoraleModifierMissionStatus = new JCheckBox(resources.getString("chkUseMoraleModifierMissionStatus.text")); + chkUseMoraleModifierMissionStatus.setToolTipText(resources.getString("chkUseMoraleModifierMissionStatus.toolTipText")); + chkUseMoraleModifierMissionStatus.setName("chkUseMoraleModifierMissionStatus"); + chkUseMoraleModifierMissionStatus.setEnabled(isUseMorale); + + chkUseMoraleModifierLeaderLoss = new JCheckBox(resources.getString("chkUseMoraleModifierLeaderLoss.text")); + chkUseMoraleModifierLeaderLoss.setToolTipText(resources.getString("chkUseMoraleModifierLeaderLoss.toolTipText")); + chkUseMoraleModifierLeaderLoss.setName("chkUseMoraleModifierLeaderLoss"); + chkUseMoraleModifierLeaderLoss.setEnabled(isUseMorale); + + chkUseMoraleModifierCombatLoss = new JCheckBox(resources.getString("chkUseMoraleModifierCombatLoss.text")); + chkUseMoraleModifierCombatLoss.setToolTipText(resources.getString("chkUseMoraleModifierCombatLoss.toolTipText")); + chkUseMoraleModifierCombatLoss.setName("chkUseMoraleModifierCombatLoss"); + chkUseMoraleModifierCombatLoss.setEnabled(isUseMorale); + + chkUseMoraleModifierDesertion = new JCheckBox(resources.getString("chkUseMoraleModifierDesertion.text")); + chkUseMoraleModifierDesertion.setToolTipText(resources.getString("chkUseMoraleModifierDesertion.toolTipText")); + chkUseMoraleModifierDesertion.setName("chkUseMoraleModifierDesertion"); + chkUseMoraleModifierDesertion.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + + chkUseMoraleModifierMutiny = new JCheckBox(resources.getString("chkUseMoraleModifierMutiny.text")); + chkUseMoraleModifierMutiny.setToolTipText(resources.getString("chkUseMoraleModifierMutiny.toolTipText")); + chkUseMoraleModifierMutiny.setName("chkUseMoraleModifierMutiny"); + chkUseMoraleModifierMutiny.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseMutinies())); + + chkUseMoraleModifierRest = new JCheckBox(resources.getString("chkUseMoraleModifierRest.text")); + chkUseMoraleModifierRest.setToolTipText(resources.getString("chkUseMoraleModifierRest.toolTipText")); + chkUseMoraleModifierRest.setName("chkUseMoraleModifierRest"); + chkUseMoraleModifierRest.setEnabled(isUseMorale); + + chkUseMoraleModifierMissedPayDay = new JCheckBox(resources.getString("chkUseMoraleModifierMissedPayDay.text")); + chkUseMoraleModifierMissedPayDay.setToolTipText(resources.getString("chkUseMoraleModifierMissedPayDay.toolTipText")); + chkUseMoraleModifierMissedPayDay.setName("chkUseMoraleModifierMissedPayDay"); + chkUseMoraleModifierMissedPayDay.setEnabled(isUseMorale); + + turnoverAndRetentionMoraleChangePanel.setName("turnoverAndRetentionMoraleChangePanel"); + turnoverAndRetentionMoraleChangePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionMoraleChangePanel.title"))); + turnoverAndRetentionMoraleChangePanel.setEnabled(isUseMorale); + + final GroupLayout layout = new GroupLayout(turnoverAndRetentionMoraleChangePanel); + turnoverAndRetentionMoraleChangePanel.setLayout(layout); + layout.setAutoCreateGaps(true); + layout.setAutoCreateContainerGaps(true); + + layout.setVerticalGroup( + layout.createSequentialGroup() + .addComponent(chkUseMoraleModifierFieldControl) + .addComponent(chkUseMoraleModifierMissionStatus) + .addComponent(chkUseMoraleModifierLeaderLoss) + .addComponent(chkUseMoraleModifierCombatLoss) + .addComponent(chkUseMoraleModifierDesertion) + .addComponent(chkUseMoraleModifierMutiny) + .addComponent(chkUseMoraleModifierRest) + .addComponent(chkUseMoraleModifierMissedPayDay) + ); + + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseMoraleModifierFieldControl) + .addComponent(chkUseMoraleModifierMissionStatus) + .addComponent(chkUseMoraleModifierLeaderLoss) + .addComponent(chkUseMoraleModifierCombatLoss) + .addComponent(chkUseMoraleModifierDesertion) + .addComponent(chkUseMoraleModifierMutiny) + .addComponent(chkUseMoraleModifierRest) + .addComponent(chkUseMoraleModifierMissedPayDay) + ); + + return turnoverAndRetentionMoraleChangePanel; } private void createRandomDependentPanel() { @@ -7494,11 +7838,6 @@ public void setOptions(@Nullable CampaignOptions options, chkDisplayScenarioLog.setSelected(options.isDisplayScenarioLog()); chkDisplayKillRecord.setSelected(options.isDisplayKillRecord()); - chkUseFatigue.setSelected(options.isUseFatigue()); - spnFatigueRate.setValue(options.getFatigueRate()); - spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); - spnFatigueLeaveThreshold.setValue(options.getFatigueLeaveThreshold()); - // Expanded Personnel Information if (chkUseTimeInService.isSelected() != options.isUseTimeInService()) { chkUseTimeInService.doClick(); @@ -7587,6 +7926,37 @@ public void setOptions(@Nullable CampaignOptions options, chkUseCommanderLeadershipOnly.setSelected(options.isUseCommanderLeadershipOnly()); spnManagementSkillPenalty.setValue(options.getManagementSkillPenalty()); + + // Fatigue + chkUseFatigue.setSelected(options.isUseFatigue()); + spnFatigueRate.setValue(options.getFatigueRate()); + spnFieldKitchenCapacity.setValue(options.getFieldKitchenCapacity()); + spnFatigueLeaveThreshold.setValue(options.getFatigueLeaveThreshold()); + + // Morale + chkUseMorale.setSelected(options.isUseMorale()); + + comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); + chkUseDesertions.setSelected(options.isUseDesertions()); + chkUseEmergencyBonuses.setSelected(options.isUseEmergencyBonuses()); + chkUseSabotage.setSelected(options.isUseSabotage()); + chkUseMutinies.setSelected(options.isUseMutinies()); + + // Morale Modifiers + spnCustomMoraleModifier.setValue(options.getCustomMoraleModifier()); + chkUseMoraleModifierFieldControl.setSelected(options.isUseMoraleModifierFieldControl()); + chkUseMoraleModifierMissionStatus.setSelected(options.isUseMoraleModifierMissionStatus()); + chkUseMoraleModifierLeaderLoss.setSelected(options.isUseMoraleModifierLeaderLoss()); + chkUseMoraleModifierCombatLoss.setSelected(options.isUseMoraleModifierCombatLoss()); + chkUseMoraleModifierDesertion.setSelected(options.isUseMoraleModifierDesertion()); + chkUseMoraleModifierMutiny.setSelected(options.isUseMoraleModifierMutiny()); + chkUseMoraleModifierRest.setSelected(options.isUseMoraleModifierRest()); + chkUseMoraleModifierFatigue.setSelected(options.isUseMoraleModifierFatigue()); + chkUseMoraleModifierProfession.setSelected(options.isUseMoraleModifierProfession()); + chkUseMoraleModifierForceReliability.setSelected(options.isUseMoraleModifierForceReliability()); + chkUseMoraleModifierManagementSkill.setSelected(options.isUseMoraleModifierManagementSkill()); + chkUseMoraleModifierMissedPayDay.setSelected(options.isUseMoraleModifierMissedPayDay()); + chkUseRuleWithIronFist.setSelected(options.isUseRuleWithIronFist()); //endregion Turnover and Retention Tab //region Life Paths Tab @@ -8143,11 +8513,6 @@ public void updateOptions() { options.setDisplayScenarioLog(chkDisplayScenarioLog.isSelected()); options.setDisplayKillRecord(chkDisplayKillRecord.isSelected()); - options.setUseFatigue(chkUseFatigue.isSelected()); - options.setFatigueRate((Integer) spnFatigueRate.getValue()); - options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); - options.setFatigueLeaveThreshold((Integer) spnFatigueLeaveThreshold.getValue()); - // Expanded Personnel Information options.setUseTimeInService(chkUseTimeInService.isSelected()); options.setTimeInServiceDisplayFormat(comboTimeInServiceDisplayFormat.getSelectedItem()); @@ -8235,6 +8600,36 @@ public void updateOptions() { options.setUseCommanderLeadershipOnly(chkUseCommanderLeadershipOnly.isSelected()); options.setManagementSkillPenalty((Integer) spnManagementSkillPenalty.getValue()); + // Fatigue + options.setUseFatigue(chkUseFatigue.isSelected()); + options.setFatigueRate((Integer) spnFatigueRate.getValue()); + options.setFieldKitchenCapacity((Integer) spnFieldKitchenCapacity.getValue()); + options.setFatigueLeaveThreshold((Integer) spnFatigueLeaveThreshold.getValue()); + + // Morale + options.setUseMorale(chkUseMorale.isSelected()); + + options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); + options.setUseDesertions(chkUseDesertions.isSelected()); + options.setUseEmergencyBonuses(chkUseEmergencyBonuses.isSelected()); + options.setUseSabotage(chkUseSabotage.isSelected()); + options.setUseMutinies(chkUseMutinies.isSelected()); + + // Morale Modifiers + options.setCustomMoraleModifier((Integer) spnCustomMoraleModifier.getValue()); + options.setUseMoraleModifierFieldControl(chkUseMoraleModifierFieldControl.isSelected()); + options.setUseMoraleModifierMissionStatus(chkUseMoraleModifierMissionStatus.isSelected()); + options.setUseMoraleModifierLeaderLoss(chkUseMoraleModifierLeaderLoss.isSelected()); + options.setUseMoraleModifierCombatLoss(chkUseMoraleModifierCombatLoss.isSelected()); + options.setUseMoraleModifierDesertion(chkUseMoraleModifierDesertion.isSelected()); + options.setUseMoraleModifierMutiny(chkUseMoraleModifierMutiny.isSelected()); + options.setUseMoraleModifierRest(chkUseMoraleModifierRest.isSelected()); + options.setUseMoraleModifierFatigue(chkUseMoraleModifierFatigue.isSelected()); + options.setUseMoraleModifierProfession(chkUseMoraleModifierProfession.isSelected()); + options.setUseMoraleModifierForceReliability(chkUseMoraleModifierForceReliability.isSelected()); + options.setUseMoraleModifierManagementSkill(chkUseMoraleModifierManagementSkill.isSelected()); + options.setUseMoraleModifierMissedPayDay(chkUseMoraleModifierMissedPayDay.isSelected()); + options.setUseRuleWithIronFist(chkUseRuleWithIronFist.isSelected()); //endregion Turnover and Retention //region Life Paths Tab From db20e0f64a713d05048197dac17643b936741c15 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 21 May 2024 20:47:15 -0500 Subject: [PATCH 044/101] Refactored fatigue and morale handling The fatigue and retirement/defection systems have been extracted into separate classes to improve code organization and readability. Exceptions to handle invalid morale values have also been included to solidify the error handling within the game. Additional GUI updates for morale display have also been made. --- .../mekhq/resources/Campaign.properties | 7 - .../mekhq/resources/CampaignGUI.properties | 1 + .../CampaignOptionsDialog.properties | 2 +- .../mekhq/resources/Fatigue.properties | 5 + .../mekhq/resources/Morale.properties | 12 ++ MekHQ/src/mekhq/campaign/Campaign.java | 187 ++++-------------- MekHQ/src/mekhq/campaign/CampaignSummary.java | 16 +- .../campaign/ResolveScenarioTracker.java | 3 +- .../mekhq/campaign/io/CampaignXmlParser.java | 8 +- .../turnoverAndRetention/Fatigue.java | 149 ++++++++++++++ .../turnoverAndRetention/Morale.java | 113 +++++++++++ .../RetirementDefectionTracker.java | 6 +- .../stratcon/StratconRulesManager.java | 3 +- MekHQ/src/mekhq/gui/CommandCenterTab.java | 68 +++++-- .../gui/dialog/RetirementDefectionDialog.java | 2 +- .../mekhq/gui/model/RetirementTableModel.java | 2 +- 16 files changed, 393 insertions(+), 191 deletions(-) create mode 100644 MekHQ/resources/mekhq/resources/Fatigue.properties create mode 100644 MekHQ/resources/mekhq/resources/Morale.properties create mode 100644 MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Fatigue.java create mode 100644 MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java rename MekHQ/src/mekhq/campaign/personnel/{ => turnoverAndRetention}/RetirementDefectionTracker.java (99%) diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index 7184ff3dd4..444ff10368 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -38,13 +38,6 @@ LayeredForceIconLayer.FRAME.toolTipText=This tab contains frames, which are the LayeredForceIconLayer.LOGO.text=Logos LayeredForceIconLayer.LOGO.toolTipText=This tab contains canon faction logos that can be added to the center of a force icon. -#### Fatigue Resources -fatigueTired.text=is tired and in need of a break. -fatigueFatigued.text=is badly fatigued and in need of rest. -fatigueExhausted.text=is exhausted and in desperate need of rest. -fatigueCritical.text=is critically fatigued and should be immediately removed from active duty. -fatigueRecovered.text=is no longer fatigued. - #### Unsorted Campaign Resources dependentLeavesForce.text=%s is no longer travelling with the force, and is thus no longer dependent on it. dependentJoinsForce.text=%s has started travelling with the force, and is now dependent on it. diff --git a/MekHQ/resources/mekhq/resources/CampaignGUI.properties b/MekHQ/resources/mekhq/resources/CampaignGUI.properties index 5471a9d23a..a03a682cad 100644 --- a/MekHQ/resources/mekhq/resources/CampaignGUI.properties +++ b/MekHQ/resources/mekhq/resources/CampaignGUI.properties @@ -213,6 +213,7 @@ panInfo.title=Basic Unit Information panObjectives.title=Current Objectives lblRating.text=Unit Rating:; lblPersonnel.text=Personnel:; +lblMorale.text=Morale:; lblAdministrativeCapacity.text=Adminstrative Capacity:; lblMissionSuccess.text=Mission Success Rate:; lblExperience.text=Experience:; diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 518b16a325..6f999d25d5 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -329,7 +329,7 @@ chkUseMoraleModifierForceReliability.text=Force Reliability chkUseMoraleModifierForceReliability.toolTipText=Force reliability influences both desertion and mutiny checks. chkUseMoraleModifierManagementSkill.text=Management Skill chkUseMoraleModifierManagementSkill.toolTipText=Having a positive management skill modifier improves both desertion and mutiny checks. -chkUseRuleWithIronFist.text=Rule With an Iron Fist +chkUseRuleWithIronFist.text=Rule with an Iron Fist chkUseRuleWithIronFist.toolTipText=Where there is a whip, there is a way. Desertion and Mutiny checks have a +1 modifier, but mutinies are more destructive. turnoverAndRetentionMoraleChangePanel.title=Morale Change Triggers diff --git a/MekHQ/resources/mekhq/resources/Fatigue.properties b/MekHQ/resources/mekhq/resources/Fatigue.properties new file mode 100644 index 0000000000..d7b98fd6ed --- /dev/null +++ b/MekHQ/resources/mekhq/resources/Fatigue.properties @@ -0,0 +1,5 @@ +fatigueTired.text=is tired and in need of a break. +fatigueFatigued.text=is badly fatigued and in need of rest. +fatigueExhausted.text=is exhausted and in desperate need of rest. +fatigueCritical.text=is critically fatigued and should be immediately removed from active duty. +fatigueRecovered.text=is no longer fatigued. \ No newline at end of file diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties new file mode 100644 index 0000000000..00651cdced --- /dev/null +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -0,0 +1,12 @@ +moraleLevelUnbreakable.text=Unbreakable +moraleLevelVeryHigh.text=Very High +moraleLevelHigh.text=High +moraleLevelNormal.text=Normal +moraleLevelLow.text=Low +moraleLevelVeryLow.text=Very Low +moraleLevelBroken.text=Broken + +moraleReport.text=Morale is now %s. +moraleReportLow.text=Caution, morale is now %s. +moraleReportMutiny.text=Your personnel are actively planning a coup. +moraleReportRecovered.text=Your personnel are placated, for now. \ No newline at end of file diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 81158d1113..38653426d5 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -31,13 +31,16 @@ import megamek.common.annotations.Nullable; import megamek.common.enums.Gender; import megamek.common.equipment.BombMounted; -import megamek.common.equipment.MiscMounted; import megamek.common.icons.Camouflage; import megamek.common.icons.Portrait; import megamek.common.loaders.BLKFile; import megamek.common.loaders.EntityLoadingException; import megamek.common.options.*; import megamek.common.util.BuildingBlock; +import megamek.common.weapons.autocannons.ACWeapon; +import megamek.common.weapons.flamers.FlamerWeapon; +import megamek.common.weapons.gaussrifles.GaussWeapon; +import megamek.common.weapons.lasers.EnergyWeapon; import mekhq.MHQConstants; import mekhq.MekHQ; import mekhq.Utilities; @@ -88,6 +91,8 @@ import mekhq.campaign.personnel.ranks.RankSystem; import mekhq.campaign.personnel.ranks.RankValidator; import mekhq.campaign.personnel.ranks.Ranks; +import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; +import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; import mekhq.campaign.rating.CampaignOpsReputation; import mekhq.campaign.rating.FieldManualMercRevDragoonsRating; import mekhq.campaign.rating.IUnitRating; @@ -225,7 +230,9 @@ public class Campaign implements ITechManager { private transient AbstractMarriage marriage; private transient AbstractProcreation procreation; - private RetirementDefectionTracker retirementDefectionTracker; // AtB + private RetirementDefectionTracker retirementDefectionTracker; + private int morale; + private AtBConfiguration atbConfig; //AtB private AtBEventProcessor atbEventProcessor; //AtB private LocalDate shipSearchStart; //AtB @@ -287,6 +294,7 @@ public Campaign() { setMarriage(new DisabledRandomMarriage(getCampaignOptions())); setProcreation(new DisabledRandomProcreation(getCampaignOptions())); retirementDefectionTracker = new RetirementDefectionTracker(); + morale = 4; atbConfig = null; autosaveService = new AutosaveService(); hasActiveContract = false; @@ -471,6 +479,14 @@ public AbstractProcreation getProcreation() { public void setProcreation(final AbstractProcreation procreation) { this.procreation = procreation; } + + public Integer getMorale() { + return morale; + } + + public void setMorale(final Integer morale) { + this.morale = morale; + } //endregion Personnel Modules public void setRetirementDefectionTracker(RetirementDefectionTracker rdt) { @@ -1550,7 +1566,7 @@ public void checkBloodnameAdd(Person person, boolean ignoreDice) { if (getCampaignOptions().getUnitRatingMethod().isEnabled()) { IUnitRating rating = getUnitRating(); bloodnameTarget += IUnitRating.DRAGOON_C - (getCampaignOptions().getUnitRatingMethod().equals( - mekhq.campaign.rating.UnitRatingMethod.FLD_MAN_MERCS_REV) + UnitRatingMethod.FLD_MAN_MERCS_REV) ? rating.getUnitRatingAsInteger() : rating.getModifier()); } @@ -3223,14 +3239,14 @@ && getCampaignOptions().getRandomDependentMethod().isAgainstTheBot() if (getLocalDate().getDayOfMonth() == 1) { /* - * First of the month; roll morale. + * First of the month; roll Morale. */ IUnitRating rating = getUnitRating(); rating.reInitialize(); for (AtBContract contract : getActiveAtBContracts()) { contract.checkMorale(getLocalDate(), getUnitRatingMod()); - addReport("Enemy morale is now " + contract.getMoraleLevel() + addReport("Enemy Morale is now " + contract.getMoraleLevel() + " on contract " + contract.getName()); } } @@ -3518,12 +3534,12 @@ public boolean newDay() { // Fatigue Region // even if Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state - processFatigueRecovery(); + Fatigue.processFatigueRecovery(this); if (campaignOptions.isUseFatigue()) { // we store these values, so this only needs to be checked once per day, // otherwise we would need to check it once for each active person in the campaign - fieldKitchenWithinCapacity = getActivePersonnel().size() <= checkFieldKitchenCapacity(); + fieldKitchenWithinCapacity = getActivePersonnel().size() <= Fatigue.checkFieldKitchenCapacity(this); } else { fieldKitchenWithinCapacity = false; } @@ -4194,6 +4210,8 @@ public void writeToXML(final PrintWriter pw) { MHQXMLUtility.writeSimpleXMLCloseTag(pw, --indent, "currentReport"); MHQXMLUtility.writeSimpleXMLCloseTag(pw, --indent, "info"); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "Morale", morale); //endregion Basic Campaign Info //region Campaign Options @@ -5150,34 +5168,34 @@ public TargetRoll getTargetForAcquisition(final IAcquisitionWork acquisition, * a minimum for non-flamer energy weapons, which was the reason this rule was * included in AtB to begin with. */ - if (et instanceof megamek.common.weapons.lasers.EnergyWeapon - && !(et instanceof megamek.common.weapons.flamers.FlamerWeapon) + if (et instanceof EnergyWeapon + && !(et instanceof FlamerWeapon) && partAvailability < EquipmentType.RATING_C) { partAvailability = EquipmentType.RATING_C; partAvailabilityLog.append(";(non-flamer lasers)"); } - if (et instanceof megamek.common.weapons.autocannons.ACWeapon) { + if (et instanceof ACWeapon) { partAvailability -= 2; partAvailabilityLog.append(";(autocannon): -2"); } - if (et instanceof megamek.common.weapons.gaussrifles.GaussWeapon - || et instanceof megamek.common.weapons.flamers.FlamerWeapon) { + if (et instanceof GaussWeapon + || et instanceof FlamerWeapon) { partAvailability--; partAvailabilityLog.append(";(gauss rifle or flamer): -1"); } - if (et instanceof megamek.common.AmmoType) { - switch (((megamek.common.AmmoType) et).getAmmoType()) { - case megamek.common.AmmoType.T_AC: + if (et instanceof AmmoType) { + switch (((AmmoType) et).getAmmoType()) { + case AmmoType.T_AC: partAvailability -= 2; partAvailabilityLog.append(";(autocannon ammo): -2"); break; - case megamek.common.AmmoType.T_GAUSS: + case AmmoType.T_GAUSS: partAvailability -= 1; partAvailabilityLog.append(";(gauss ammo): -1"); break; } if (EnumSet.of(AmmoType.Munitions.M_STANDARD).containsAll( - ((megamek.common.AmmoType) et).getMunitionType())){ + ((AmmoType) et).getMunitionType())){ partAvailability--; partAvailabilityLog.append(";(standard ammo): -1"); } @@ -5186,10 +5204,10 @@ public TargetRoll getTargetForAcquisition(final IAcquisitionWork acquisition, if (((getGameYear() < 2950) || (getGameYear() > 3040)) && (acquisition instanceof Armor || acquisition instanceof MissingMekActuator - || acquisition instanceof mekhq.campaign.parts.MissingMekCockpit - || acquisition instanceof mekhq.campaign.parts.MissingMekLifeSupport - || acquisition instanceof mekhq.campaign.parts.MissingMekLocation - || acquisition instanceof mekhq.campaign.parts.MissingMekSensor)) { + || acquisition instanceof MissingMekCockpit + || acquisition instanceof MissingMekLifeSupport + || acquisition instanceof MissingMekLocation + || acquisition instanceof MissingMekSensor)) { partAvailability--; partAvailabilityLog.append("(Mek part prior to 2950 or after 3040): - 1"); } @@ -6295,7 +6313,7 @@ public int calculatePartTransitTime(int mos) { * @param part A part to lookup its current inventory. * @return A PartInventory object detailing the current counts of * the part on hand, in transit, and ordered. - * @see mekhq.campaign.parts.PartInventory + * @see PartInventory */ public PartInventory getPartInventory(Part part) { PartInventory inventory = new PartInventory(); @@ -6771,7 +6789,7 @@ public void initAtB(boolean newCampaign) { String mech = e.getDesc().substring(12); MechSummary ms = MechSummaryCache.getInstance().getMech(mech); if (null != ms && (p.isFounder() - || ms.getWeightClass() < megamek.common.EntityWeightClass.WEIGHT_ASSAULT)) { + || ms.getWeightClass() < EntityWeightClass.WEIGHT_ASSAULT)) { p.setOriginalUnitWeight(ms.getWeightClass()); if (ms.isClan()) { p.setOriginalUnitTech(Person.TECH_CLAN); @@ -6977,127 +6995,4 @@ public boolean useVariableTechLevel() { public boolean showExtinct() { return !campaignOptions.isDisallowExtinctStuff(); } - - /** - * Calculates the total capacity of field kitchens based on the units present. - * - * @return The total capacity of field kitchens. - */ - public Integer checkFieldKitchenCapacity() { - int fieldKitchenCount = 0; - - Collection allUnits = getUnits(); - - if (!allUnits.isEmpty()) { - for (Unit unit : getUnits()) { - if ((unit.isDeployed()) - || (unit.isDamaged()) - || (unit.getCrewState().isUncrewed()) - || (unit.getCrewState().isPartiallyCrewed()) - || (unit.isUnmaintained())) { - continue; - } - - List miscItems = unit.getEntity().getMisc(); - - if (!miscItems.isEmpty()) { - fieldKitchenCount += (int) unit.getEntity().getMisc().stream() - .filter(item -> item.getType().hasFlag(MiscType.F_FIELD_KITCHEN)) - .count(); - } - } - } - - return fieldKitchenCount * campaignOptions.getFieldKitchenCapacity(); - } - - - /** - * Reports the fatigue level of a person and perform actions based on the fatigue level. - * - * @param person The person for which the fatigue level is reported. - */ - public void processFatigueActions(Person person) { - int effectiveFatigue = person.getEffectiveFatigue(this); - - if (getCampaignOptions().isUseFatigue()) { - if ((effectiveFatigue >= 5) && (effectiveFatigue < 9)) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueTired.text")); - person.setIsRecoveringFromFatigue(true); - } else if ((effectiveFatigue >= 9) && (effectiveFatigue < 12)) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueFatigued.text")); - person.setIsRecoveringFromFatigue(true); - } else if ((effectiveFatigue >= 12) && (effectiveFatigue < 16)) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueExhausted.text")); - person.setIsRecoveringFromFatigue(true); - } else if (effectiveFatigue >= 17) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueCritical.text")); - person.setIsRecoveringFromFatigue(true); - } - } - - if ((campaignOptions.getFatigueLeaveThreshold() != 0) - && (effectiveFatigue >= campaignOptions.getFatigueLeaveThreshold())) { - person.changeStatus(this, getLocalDate(), PersonnelStatus.ON_LEAVE); - } - } - - /** - * Decreases the fatigue of all active personnel. - * Fatigue recovery is determined based on the following criteria: - * - Fatigue is adjusted based on various conditions: - * - If it is Monday - * - Fatigue is decreased by an 1 - * - If 'person' is on leave, decreased fatigue by an additional 1 - * - If there are no active contracts, decreased fatigue by an additional 1 - * - If campaign options include fatigue usage and 'person' is recovering from fatigue: - * - If fatigue reaches 0, trigger a report indicating fatigue recovery - * - If fatigue leave threshold is not 0, and 'person' is on leave, change status to active - */ - public void processFatigueRecovery() { - List filteredPersonnel = getPersonnel().stream() - .filter(person -> !person.getStatus().isDepartedUnit()) - .collect(Collectors.toList()); - - - for (Person person : filteredPersonnel) { - if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { - if (person.getFatigue() > 0) { - int fatigueAdjustment = 1; - - if (person.getStatus().isOnLeave()) { - fatigueAdjustment++; - } - - if (getActiveContracts().isEmpty()) { - fatigueAdjustment++; - } - - person.setFatigue(person.getFatigue() - fatigueAdjustment); - - if (person.getFatigue() < 0) { - person.setFatigue(0); - } - } - } - - if (getCampaignOptions().isUseFatigue()) { - if ((!person.getStatus().isOnLeave()) && (!person.getIsRecoveringFromFatigue())) { - processFatigueActions(person); - } - - if (person.getIsRecoveringFromFatigue()) { - if (person.getFatigue() == 0) { - addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueRecovered.text")); - - person.setIsRecoveringFromFatigue(false); - - if ((getCampaignOptions().getFatigueLeaveThreshold() != 0) && (person.getStatus().isOnLeave())) { - person.changeStatus(this, getLocalDate(), PersonnelStatus.ACTIVE); - } - } - } - } - } - } } diff --git a/MekHQ/src/mekhq/campaign/CampaignSummary.java b/MekHQ/src/mekhq/campaign/CampaignSummary.java index fe1aa07edd..cdc66911bc 100644 --- a/MekHQ/src/mekhq/campaign/CampaignSummary.java +++ b/MekHQ/src/mekhq/campaign/CampaignSummary.java @@ -25,6 +25,7 @@ import mekhq.campaign.mission.enums.MissionStatus; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; import mekhq.campaign.unit.CargoStatistics; import mekhq.campaign.unit.HangarStatistics; import mekhq.campaign.unit.Unit; @@ -34,9 +35,9 @@ import java.util.Collection; import java.util.List; -import static mekhq.campaign.personnel.RetirementDefectionTracker.getAdministrativeStrain; -import static mekhq.campaign.personnel.RetirementDefectionTracker.getAdministrativeStrainModifier; -import static mekhq.campaign.personnel.RetirementDefectionTracker.getCombinedSkillValues; +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getAdministrativeStrain; +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getAdministrativeStrainModifier; +import static mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker.getCombinedSkillValues; /** * calculates and stores summary information on a campaign for use in reporting, mostly for the command center @@ -315,14 +316,9 @@ public String getAdministrativeCapacityReport(Campaign campaign) { */ public String getFatigueSummary() { int personnelCount = campaign.getActivePersonnel().size(); - int fieldKitchenCapacity = campaign.checkFieldKitchenCapacity(); - if (fieldKitchenCapacity > 0) { - return String.format("Kitchens (%s/%s)", - personnelCount, campaign.checkFieldKitchenCapacity()); - } else { - return ""; - } + return String.format("Kitchens (%s/%s)", + personnelCount, Fatigue.checkFieldKitchenCapacity(campaign)); } private String createCsv(Collection coll) { diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index 2d08d967d3..5849d482eb 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -39,6 +39,7 @@ import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.enums.PersonnelStatus; import mekhq.campaign.personnel.enums.PrisonerStatus; +import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; import mekhq.campaign.unit.TestUnit; import mekhq.campaign.unit.Unit; import mekhq.campaign.unit.actions.AdjustLargeCraftAmmoAction; @@ -1425,7 +1426,7 @@ public void resolveScenario(ScenarioStatus resolution, String report) { if (!status.isDead()) { person.setFatigue(person.getFatigue() + campaign.getCampaignOptions().getFatigueRate()); - campaign.processFatigueActions(person); + Fatigue.processFatigueActions(campaign, person); } if (getCampaign().getCampaignOptions().isUseAdvancedMedical()) { diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index d4a1e665b9..6458039f2b 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -47,10 +47,14 @@ import mekhq.campaign.mod.am.InjuryTypes; import mekhq.campaign.parts.*; import mekhq.campaign.parts.equipment.*; -import mekhq.campaign.personnel.*; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.PersonnelOptions; +import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.SpecialAbility; import mekhq.campaign.personnel.enums.FamilialRelationshipType; import mekhq.campaign.personnel.ranks.RankSystem; import mekhq.campaign.personnel.ranks.RankValidator; +import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; import mekhq.campaign.storyarc.StoryArc; import mekhq.campaign.unit.Unit; import mekhq.campaign.unit.cleanup.EquipmentUnscrambler; @@ -271,6 +275,8 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException { processLanceNodes(retVal, wn); } else if (xn.equalsIgnoreCase("retirementDefectionTracker")) { retVal.setRetirementDefectionTracker(RetirementDefectionTracker.generateInstanceFromXML(wn, retVal)); + } else if (xn.equalsIgnoreCase("Morale")) { + retVal.setMorale(Integer.parseInt(wn.getTextContent())); } else if (xn.equalsIgnoreCase("shipSearchStart")) { retVal.setShipSearchStart(MHQXMLUtility.parseDate(wn.getTextContent().trim())); } else if (xn.equalsIgnoreCase("shipSearchType")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Fatigue.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Fatigue.java new file mode 100644 index 0000000000..b6b30f9803 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Fatigue.java @@ -0,0 +1,149 @@ +package mekhq.campaign.personnel.turnoverAndRetention; + +import megamek.common.MiscType; +import megamek.common.equipment.MiscMounted; +import mekhq.MekHQ; +import mekhq.campaign.Campaign; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.unit.Unit; + +import java.time.DayOfWeek; +import java.util.Collection; +import java.util.List; +import java.util.ResourceBundle; +import java.util.stream.Collectors; + +/** + * The Fatigue class contains static methods for calculating and processing fatigue levels and actions. + */ +public class Fatigue { + /** + * Calculates the total capacity of field kitchens based on the units present. + * + * @return The total capacity of field kitchens. + */ + public static Integer checkFieldKitchenCapacity(Campaign campaign) { + int fieldKitchenCount = 0; + + Collection allUnits = campaign.getUnits(); + + if (!allUnits.isEmpty()) { + for (Unit unit : campaign.getUnits()) { + if ((unit.isDeployed()) + || (unit.isDamaged()) + || (unit.getCrewState().isUncrewed()) + || (unit.getCrewState().isPartiallyCrewed()) + || (unit.isUnmaintained())) { + continue; + } + + List miscItems = unit.getEntity().getMisc(); + + if (!miscItems.isEmpty()) { + fieldKitchenCount += (int) unit.getEntity().getMisc().stream() + .filter(item -> item.getType().hasFlag(MiscType.F_FIELD_KITCHEN)) + .count(); + } + } + } + + return fieldKitchenCount * campaign.getCampaignOptions().getFieldKitchenCapacity(); + } + + + /** + * Reports the fatigue level of a person and perform actions based on the fatigue level. + * + * @param person The person for which the fatigue level is reported. + */ + public static void processFatigueActions(Campaign campaign, Person person) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Fatigue", + MekHQ.getMHQOptions().getLocale()); + + int effectiveFatigue = person.getEffectiveFatigue(campaign); + + if (campaign.getCampaignOptions().isUseFatigue()) { + if ((effectiveFatigue >= 5) && (effectiveFatigue < 9)) { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueTired.text")); + person.setIsRecoveringFromFatigue(true); + } else if ((effectiveFatigue >= 9) && (effectiveFatigue < 12)) { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueFatigued.text")); + person.setIsRecoveringFromFatigue(true); + } else if ((effectiveFatigue >= 12) && (effectiveFatigue < 16)) { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueExhausted.text")); + person.setIsRecoveringFromFatigue(true); + } else if (effectiveFatigue >= 17) { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueCritical.text")); + person.setIsRecoveringFromFatigue(true); + } + } + + if ((campaign.getCampaignOptions().getFatigueLeaveThreshold() != 0) + && (effectiveFatigue >= campaign.getCampaignOptions().getFatigueLeaveThreshold())) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.ON_LEAVE); + } + } + + /** + * Decreases the fatigue of all active personnel. + * Fatigue recovery is determined based on the following criteria: + * - Fatigue is adjusted based on various conditions: + * - If it is Monday + * - Fatigue is decreased by an 1 + * - If 'person' is on leave, decreased fatigue by an additional 1 + * - If there are no active contracts, decreased fatigue by an additional 1 + * - If campaign options include fatigue usage and 'person' is recovering from fatigue: + * - If fatigue reaches 0, trigger a report indicating fatigue recovery + * - If fatigue leave threshold is not 0, and 'person' is on leave, change status to active + */ + public static void processFatigueRecovery(Campaign campaign) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Fatigue", + MekHQ.getMHQOptions().getLocale()); + + List filteredPersonnel = campaign.getPersonnel().stream() + .filter(person -> !person.getStatus().isDepartedUnit()) + .collect(Collectors.toList()); + + + for (Person person : filteredPersonnel) { + if (campaign.getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { + if (person.getFatigue() > 0) { + int fatigueAdjustment = 1; + + if (person.getStatus().isOnLeave()) { + fatigueAdjustment++; + } + + if (campaign.getActiveContracts().isEmpty()) { + fatigueAdjustment++; + } + + person.setFatigue(person.getFatigue() - fatigueAdjustment); + + if (person.getFatigue() < 0) { + person.setFatigue(0); + } + } + } + + if (campaign.getCampaignOptions().isUseFatigue()) { + if ((!person.getStatus().isOnLeave()) && (!person.getIsRecoveringFromFatigue())) { + processFatigueActions(campaign, person); + } + + if (person.getIsRecoveringFromFatigue()) { + if (person.getFatigue() == 0) { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("fatigueRecovered.text")); + + person.setIsRecoveringFromFatigue(false); + + if ((campaign.getCampaignOptions().getFatigueLeaveThreshold() != 0) && (person.getStatus().isOnLeave())) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.ACTIVE); + } + } + } + } + } + } +} diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java new file mode 100644 index 0000000000..b21f2e9e77 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -0,0 +1,113 @@ +package mekhq.campaign.personnel.turnoverAndRetention; + +import mekhq.MekHQ; +import mekhq.campaign.Campaign; + +import java.util.ResourceBundle; + +public class Morale { + + /** + * This method returns the Morale level as a string based on the value of the 'Morale' variable. + * + * @return The Morale level as a string. + * @throws IllegalStateException if the value of 'Morale' is unexpected. + */ + public static String getMoraleLevel(Campaign campaign) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + switch(campaign.getMorale()) { + case 1: + return resources.getString("moraleLevelUnbreakable.text"); + case 2: + return resources.getString("moraleLevelVeryHigh.text"); + case 3: + return resources.getString("moraleLevelHigh.text"); + case 4: + return resources.getString("moraleLevelNormal.text"); + case 5: + return resources.getString("moraleLevelLow.text"); + case 6: + return resources.getString("moraleLevelVeryLow.text"); + case 7: + return resources.getString("moraleLevelBroken.text"); + default: + throw new IllegalStateException("Unexpected value in getMoraleLevel(): " + campaign.getMorale()); + } + } + + /** + * Calculates the base target number for desertion based on the morale of the campaign. + * + * @param campaign the campaign for which to calculate the base target number for desertion + * @return the base target number for desertion + * @throws IllegalStateException if the morale value of the campaign is unexpected + */ + public static Integer getDesertionBaseTargetNumber(Campaign campaign) { + switch(campaign.getMorale()) { + case 1: + case 2: + case 3: + return 0; + case 4: + return 2; + case 5: + case 6: + return 5; + case 7: + return 8; + default: + throw new IllegalStateException("Unexpected value in getDesertionTargetNumber: " + campaign.getMorale()); + } + } + + /** + * Calculates the base target number for mutiny based on the morale of the campaign. + * + * @param campaign the campaign for which to calculate the base target number for mutiny + * @return the base target number for mutiny + * @throws IllegalStateException if the morale value of the campaign is unexpected + */ + public static Integer getMutinyBaseTargetNumber(Campaign campaign) { + switch(campaign.getMorale()) { + case 1: + case 2: + case 3: + case 4: + return 0; + case 5: + case 6: + return 4; + case 7: + return 7; + default: + throw new IllegalStateException("Unexpected value in getDesertionTargetNumber: " + campaign.getMorale()); + } + } + + /** + * Returns a morale report for the given Campaign. + * + * @param campaign the Campaign for which to generate the report + * @return the morale report as a String + */ + public static String getMoraleReport(Campaign campaign) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + StringBuilder moraleReport = new StringBuilder(); + + if ((getDesertionBaseTargetNumber(campaign) - campaign.getCampaignOptions().getCustomMoraleModifier()) >= 2) { + moraleReport.append(String.format(resources.getString("moraleReportLow.text="), getMoraleReport(campaign))); + } else { + moraleReport.append(String.format(resources.getString("moraleReport.text="), getMoraleReport(campaign))); + } + + if ((getMutinyBaseTargetNumber(campaign) - campaign.getCampaignOptions().getCustomMoraleModifier()) >= 2) { + moraleReport.append(' ').append(resources.getString("moraleReportMutiny.text=")); + } + + return moraleReport.toString(); + } +} diff --git a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/RetirementDefectionTracker.java similarity index 99% rename from MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java rename to MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/RetirementDefectionTracker.java index 775aebb872..32cddecd99 100644 --- a/MekHQ/src/mekhq/campaign/personnel/RetirementDefectionTracker.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/RetirementDefectionTracker.java @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with MekHQ. If not, see . */ -package mekhq.campaign.personnel; +package mekhq.campaign.personnel.turnoverAndRetention; import megamek.codeUtilities.MathUtility; import megamek.common.Compute; @@ -31,6 +31,10 @@ import mekhq.campaign.finances.Money; import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.Mission; +import mekhq.campaign.personnel.Injury; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.PersonnelOptions; +import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.Profession; import mekhq.campaign.universe.FactionHints; diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 8e9bc5bbd4..2ad6485f3b 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -36,6 +36,7 @@ import mekhq.campaign.mission.atb.AtBScenarioModifier.EventTiming; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; import mekhq.campaign.stratcon.StratconContractDefinition.StrategicObjectiveType; import mekhq.campaign.stratcon.StratconScenario.ScenarioState; import mekhq.campaign.unit.Unit; @@ -540,7 +541,7 @@ private static void increaseFatigue(int forceID, Campaign campaign) { for (UUID unit : campaign.getForce(forceID).getAllUnits(false)) { for (Person person : campaign.getUnit(unit).getCrew()) { person.setFatigue(person.getFatigue() + campaign.getCampaignOptions().getFatigueRate()); - campaign.processFatigueActions(person); + Fatigue.processFatigueActions(campaign, person); } } } diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index 7719dee73b..8e1ee0b67f 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -25,6 +25,7 @@ import mekhq.MHQOptionsChangedEvent; import mekhq.MekHQ; import mekhq.campaign.event.*; +import mekhq.campaign.personnel.turnoverAndRetention.Morale; import mekhq.campaign.report.CargoReport; import mekhq.campaign.report.HangarReport; import mekhq.campaign.report.PersonnelReport; @@ -60,6 +61,7 @@ public final class CommandCenterTab extends CampaignGuiTab { private JLabel lblRating; private JLabel lblExperience; private JLabel lblPersonnel; + private JLabel lblMorale; private JLabel lblAdminstrativeCapacity; private JLabel lblMissionSuccess; private JLabel lblComposition; @@ -262,6 +264,22 @@ private void initInfoPanel() { panInfo.add(lblAdminstrativeCapacity, gridBagConstraints); } + if (getCampaign().getCampaignOptions().isUseMorale()) { + JLabel lblMoraleHead = new JLabel(resourceMap.getString("lblMorale.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y++; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new Insets(1, 5, 1, 5); + panInfo.add(lblMoraleHead, gridBagConstraints); + lblMorale = new JLabel(getCampaign().getMorale() + " (" + Morale.getMoraleLevel(getCampaign()) + ')'); + lblMoraleHead.setLabelFor(lblMorale); + gridBagConstraints.gridx = 1; + gridBagConstraints.weightx = 1.0; + panInfo.add(lblMorale, gridBagConstraints); + } + JLabel lblCompositionHead = new JLabel(resourceMap.getString("lblComposition.text")); gridBagConstraints = new GridBagConstraints(); gridBagConstraints.gridx = 0; @@ -319,21 +337,19 @@ private void initInfoPanel() { panInfo.add(lblCargoSummary, gridBagConstraints); if (getCampaign().getCampaignOptions().isUseFatigue()) { - if (getCampaign().getCampaignOptions().getFieldKitchenCapacity() > 0) { - JLabel lblFacilityCapacitiesHead = new JLabel(resourceMap.getString("lblFacilityCapacities.text")); - gridBagConstraints = new GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = y++; - gridBagConstraints.fill = GridBagConstraints.NONE; - gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new Insets(1, 5, 1, 5); - panInfo.add(lblFacilityCapacitiesHead, gridBagConstraints); - lblFacilityCapacities = new JLabel(getCampaign().getCampaignSummary().getFatigueSummary()); - lblFacilityCapacitiesHead.setLabelFor(lblFacilityCapacities); - gridBagConstraints.gridx = 1; - gridBagConstraints.weightx = 1.0; - panInfo.add(lblFacilityCapacities, gridBagConstraints); - } + JLabel lblFacilityCapacitiesHead = new JLabel(resourceMap.getString("lblFacilityCapacities.text")); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = y++; + gridBagConstraints.fill = GridBagConstraints.NONE; + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new Insets(1, 5, 1, 5); + panInfo.add(lblFacilityCapacitiesHead, gridBagConstraints); + lblFacilityCapacities = new JLabel(getCampaign().getCampaignSummary().getFatigueSummary()); + lblFacilityCapacitiesHead.setLabelFor(lblFacilityCapacities); + gridBagConstraints.gridx = 1; + gridBagConstraints.weightx = 1.0; + panInfo.add(lblFacilityCapacities, gridBagConstraints); } panInfo.setBorder(BorderFactory.createTitledBorder(resourceMap.getString("panInfo.title"))); @@ -551,13 +567,23 @@ private void refreshBasicInfo() { lblRepairStatus.setText(getCampaign().getCampaignSummary().getForceRepairReport()); lblTransportCapacity.setText(getCampaign().getCampaignSummary().getTransportCapacity()); - try { - lblAdminstrativeCapacity.setText(getCampaign().getCampaignSummary().getAdministrativeCapacityReport(getCampaign())); - } catch (Exception ignored) {} + if (getCampaign().getCampaignOptions().isUseMorale()) { + try { + lblMorale.setText(getCampaign().getMorale() + " (" + Morale.getMoraleLevel(getCampaign()) + ')'); + } catch (Exception ignored) {} + } + + if (getCampaign().getCampaignOptions().isUseAdministrativeStrain()) { + try { + lblAdminstrativeCapacity.setText(getCampaign().getCampaignSummary().getAdministrativeCapacityReport(getCampaign())); + } catch (Exception ignored) {} + } - try { - lblFacilityCapacities.setText(getCampaign().getCampaignSummary().getFatigueSummary()); - } catch (Exception ignored) {} + if (getCampaign().getCampaignOptions().isUseFatigue()) { + try { + lblFacilityCapacities.setText(getCampaign().getCampaignSummary().getFatigueSummary()); + } catch (Exception ignored) {} + } } private void refreshObjectives() { diff --git a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java index 302afe8d4f..728de7e253 100644 --- a/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/RetirementDefectionDialog.java @@ -32,8 +32,8 @@ import mekhq.campaign.finances.enums.TransactionType; import mekhq.campaign.mission.Mission; import mekhq.campaign.personnel.Person; -import mekhq.campaign.personnel.RetirementDefectionTracker; import mekhq.campaign.personnel.enums.PersonnelRole; +import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; import mekhq.campaign.unit.Unit; import mekhq.gui.CampaignGUI; import mekhq.gui.enums.PersonnelFilter; diff --git a/MekHQ/src/mekhq/gui/model/RetirementTableModel.java b/MekHQ/src/mekhq/gui/model/RetirementTableModel.java index 6f695ab438..bbf7065565 100644 --- a/MekHQ/src/mekhq/gui/model/RetirementTableModel.java +++ b/MekHQ/src/mekhq/gui/model/RetirementTableModel.java @@ -7,8 +7,8 @@ import mekhq.campaign.finances.Money; import mekhq.campaign.force.Force; import mekhq.campaign.personnel.Person; -import mekhq.campaign.personnel.RetirementDefectionTracker; import mekhq.campaign.personnel.enums.PersonnelRole; +import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; import mekhq.campaign.unit.Unit; import mekhq.gui.BasicInfo; import mekhq.gui.dialog.RetirementDefectionDialog; From 1b5ab6d4a07f8b172ef45f434c49ed61c1cf8368 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 02:39:59 -0500 Subject: [PATCH 045/101] Began implementing the morale system and refactoring of Turnover code Made extensive changes to the organization and implementation of the morale and turnover rules, including the addition of a step size option and a theft function. Also renamed and updated various methods and properties for improved code clarity and readability. --- .../CampaignOptionsDialog.properties | 57 +- .../mekhq/resources/Finances.properties | 2 + .../mekhq/resources/Morale.properties | 5 +- .../mekhq/resources/Personnel.properties | 6 +- MekHQ/src/mekhq/campaign/Campaign.java | 62 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 243 +++++--- .../finances/enums/TransactionType.java | 7 + .../mekhq/campaign/io/CampaignXmlParser.java | 2 +- .../src/mekhq/campaign/personnel/Person.java | 15 +- .../enums/ForceReliabilityMethod.java | 6 +- .../turnoverAndRetention/Morale.java | 544 ++++++++++++++++-- .../mekhq/gui/panes/CampaignOptionsPane.java | 323 ++++++----- .../finances/enums/TransactionTypeTest.java | 3 +- 13 files changed, 954 insertions(+), 321 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 6f999d25d5..d08ae6a690 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -307,49 +307,56 @@ turnoverAndRetentionMoralePanel.title=Morale chkUseMorale.text=Enable Morale chkUseMorale.toolTipText=Enables the morale system. This system is based on the CamOps Morale rules on p217.
Full documentation is available in MekHQ/docs/Turnover and Retention Module.pdf +lblStepSize.text=Step Size +lblStepSize.toolTipText=How large should each morale adjustment be? CamOps uses step sizes of 1.0. lblForceReliabilityMethod.text=Force Reliability Method lblForceReliabilityMethod.toolTipText=How should Force Reliability be calculated (referred to as 'Loyalty' on CamOps p217)? chkUseDesertions.text=Enable Desertions chkUseDesertions.toolTipText=Low morale may cause personnel to randomly desert the company. +chkUseMutinies.text=Enable Mutinies +chkUseMutinies.toolTipText=Low morale may cause the unit to mutiny. chkUseEmergencyBonuses.text=Issue Emergency Bonuses -chkUseEmergencyBonuses.toolTipText=Attempt to quell desertion with an emergency bonus payout to the deserter (equal to their monthly salary).
Provides a +1 bonus on the desertion roll. This does not affect mutinies. +chkUseEmergencyBonuses.toolTipText=Time for a pizza party! Attempt to quell desertion with an emergency bonus payout to the deserter (equal to their monthly salary).
Provides a re-roll on the desertion roll. This does not affect mutinies. chkUseSabotage.text=Enable Sabotage chkUseSabotage.toolTipText=Low morale influences maintenance checks. -chkUseMutinies.text=Enable Mutinies -chkUseMutinies.toolTipText=Low morale may cause the unit to mutiny. +chkUseTheft.text=Enable Theft +chkUseTheft.toolTipText=Deserting personnel may steal units or funds on their way out. turnoverAndRetentionMoraleModifiersPanel.title=Morale Modifiers lblCustomMoraleModifier.text=Custom Modifier lblCustomMoraleModifier.toolTipText=This is a direct modifier applied to both mutiny and desertion checks. -chkUseMoraleModifierFatigue.text=Fatigue -chkUseMoraleModifierFatigue.toolTipText=Personal fatigue influences both desertion and mutiny checks. +chkUseMoraleModifierExperienceLevel.text=Experience Level +chkUseMoraleModifierExperienceLevel.toolTipText=Experience level (green, regular, etc.) influences both mutiny and desertion checks. +chkUseMoraleModifierFaction.text=Faction +chkUseMoraleModifierFaction.toolTipText=Clan or Mercenary origin factions influence both mutiny and desertion checks. chkUseMoraleModifierProfession.text=Profession chkUseMoraleModifierProfession.toolTipText=Professions influence desertion and mutiny checks. Personnel with multiple professions use the average of both modifiers. chkUseMoraleModifierForceReliability.text=Force Reliability chkUseMoraleModifierForceReliability.toolTipText=Force reliability influences both desertion and mutiny checks. -chkUseMoraleModifierManagementSkill.text=Management Skill -chkUseMoraleModifierManagementSkill.toolTipText=Having a positive management skill modifier improves both desertion and mutiny checks. +chkUseMoraleModifierCommanderSkill.text=Commander's Management Skill +chkUseMoraleModifierCommanderSkill.toolTipText=The commander having a positive Management Skill modifier improves both desertion and mutiny checks. +chkUseMoraleModifierLoyalty.text=Personal Loyalty +chkUseMoraleModifierLoyalty.toolTipText=Personal loyalty adjusts both desertion and mutiny checks. Stacks with the Loyalty force reliability method. chkUseRuleWithIronFist.text=Rule with an Iron Fist chkUseRuleWithIronFist.toolTipText=Where there is a whip, there is a way. Desertion and Mutiny checks have a +1 modifier, but mutinies are more destructive. -turnoverAndRetentionMoraleChangePanel.title=Morale Change Triggers -chkUseMoraleModifierFieldControl.text=Field Control -chkUseMoraleModifierFieldControl.toolTipText=Failing to control the field at the end of a scenario decreases morale. -chkUseMoraleModifierMissionStatus.text=Mission Status -chkUseMoraleModifierMissionStatus.toolTipText=Succeeding or failing, a Mission affects morale. -chkUseMoraleModifierLeaderLoss.text=Leader Loss -chkUseMoraleModifierLeaderLoss.toolTipText=The loss of an officer (or the unit Commander) decreases morale. -chkUseMoraleModifierCombatLoss.text=Combat Losses -chkUseMoraleModifierCombatLoss.toolTipText=Significant losses during a scenario decreases morale. -chkUseMoraleModifierDesertion.text=Desertion -chkUseMoraleModifierDesertion.toolTipText=Suffering desertion decreases morale. -chkUseMoraleModifierMutiny.text=Mutiny -chkUseMoraleModifierMutiny.toolTipText=Suffering a mutiny substantially decreases morale. -chkUseMoraleModifierRest.text=Rest -chkUseMoraleModifierRest.toolTipText=Morale improves during periods spent planet-side without an active contract. - -chkUseMoraleModifierMissedPayDay.text=Missed Payment -chkUseMoraleModifierMissedPayDay.toolTipText=Missing a payday substantially decreases morale. +turnoverAndRetentionMoraleTriggersPanel.title=Morale Change Triggers +chkUseMoraleTriggerFieldControl.text=Field Control +chkUseMoraleTriggerFieldControl.toolTipText=Failing to control the field at the end of a scenario decreases morale. +chkUseMoraleTriggerMissionStatus.text=Mission Status +chkUseMoraleTriggerMissionStatus.toolTipText=Succeeding or failing, a Mission affects morale. +chkUseMoraleTriggerLeaderLoss.text=Leader Loss +chkUseMoraleTriggerLeaderLoss.toolTipText=The loss of an officer (or the unit Commander) decreases morale. +chkUseMoraleTriggerCombatLoss.text=Combat Losses +chkUseMoraleTriggerCombatLoss.toolTipText=Significant losses during a scenario decreases morale. +chkUseMoraleTriggerDesertion.text=Desertion +chkUseMoraleTriggerDesertion.toolTipText=Suffering desertion decreases morale. +chkUseMoraleTriggerMutiny.text=Mutiny +chkUseMoraleTriggerMutiny.toolTipText=Suffering a mutiny substantially decreases morale. +chkUseMoraleTriggerMissedPayDay.text=Missed Payment +chkUseMoraleTriggerMissedPayDay.toolTipText=Missing a payday substantially decreases morale. +chkUseMoraleTriggerFatigue.text=Fatigue +chkUseMoraleTriggerFatigue.toolTipText=Personal fatigue may cause morale to decrease. # Dependent dependentPanel.title=Dependents (Unofficial) diff --git a/MekHQ/resources/mekhq/resources/Finances.properties b/MekHQ/resources/mekhq/resources/Finances.properties index bf465b8c7f..2efb23d4e0 100644 --- a/MekHQ/resources/mekhq/resources/Finances.properties +++ b/MekHQ/resources/mekhq/resources/Finances.properties @@ -78,6 +78,8 @@ TransactionType.STARTING_CAPITAL.text=Starting Capital TransactionType.STARTING_CAPITAL.toolTipText=A financial transaction where the fund are supplied to start a force. TransactionType.TAXES.text=Taxes TransactionType.TAXES.toolTipText=A financial transaction where the force pays or is paid taxes. +TransactionType.THEFT.text=Theft +TransactionType.THEFT.toolTipText=A financial transaction where funds are sourced illegally. TransactionType.TRANSPORTATION.text=Transportation TransactionType.TRANSPORTATION.toolTipText=A financial transaction where force transportation is paid for. TransactionType.UNIT_PURCHASE.text=Unit Purchase(s) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 00651cdced..dbd3c2f325 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -9,4 +9,7 @@ moraleLevelBroken.text=Broken moraleReport.text=Morale is now %s. moraleReportLow.text=Caution, morale is now %s. moraleReportMutiny.text=Your personnel are actively planning a coup. -moraleReportRecovered.text=Your personnel are placated, for now. \ No newline at end of file +moraleReportRecovered.text=Your personnel are placated, for now. + +desertionStolen.text=%s has been stolen. +desertionHeist.text=Screw you, Commander diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index 059c4b8613..07c4936339 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -298,7 +298,7 @@ PersonnelStatus.ON_LEAVE.toolTipText=They are currently on leave from the force. PersonnelStatus.ON_LEAVE.reportText=%s has gone on leave from the force PersonnelStatus.ON_LEAVE.logText=Went on leave PersonnelStatus.AWOL.text=AWOL -PersonnelStatus.AWOL.toolTipText=They are currently away from the force without leave. +PersonnelStatus.AWOL.toolTipText=They are currently away from the force without permission. PersonnelStatus.AWOL.reportText=%s has gone on leave from the force without permission PersonnelStatus.AWOL.logText=Went AWOL PersonnelStatus.RETIRED.text=Retired @@ -465,8 +465,8 @@ TurnoverTargetNumberMethod.NEGOTIATION.text=Negotiation TurnoverTargetNumberMethod.NEGOTIATION.toolTipText=The Target number is based on the mean Negotiation skill across all Admin/HR personnel. # ForceReliabilityMethod Enum -ForceReliabilityMethod.EQUIPMENT.text=Dynamic: Equipment Rating -ForceReliabilityMethod.EQUIPMENT.toolTipText=Desertion and Mutiny checks are modified by average equipment rating (CamOps p217) +ForceReliabilityMethod.UNIT_RATING.text=Dynamic: Unit Rating +ForceReliabilityMethod.UNIT_RATING.toolTipText=Unit Rating modifies desertion and Mutiny checks ForceReliabilityMethod.LOYALTY.text=Dynamic: Loyalty ForceReliabilityMethod.LOYALTY.toolTipText=Desertion and Mutiny checks are modified by average personnel loyalty.
If loyalty is disabled, this setting is identical to 'Dynamic: Equipment Rating.' ForceReliabilityMethod.OVERRIDE_A.text=Fixed: A (Fanatical / Clan Front Line) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 38653426d5..d435fab063 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -92,6 +92,7 @@ import mekhq.campaign.personnel.ranks.RankValidator; import mekhq.campaign.personnel.ranks.Ranks; import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; +import mekhq.campaign.personnel.turnoverAndRetention.Morale; import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; import mekhq.campaign.rating.CampaignOpsReputation; import mekhq.campaign.rating.FieldManualMercRevDragoonsRating; @@ -231,7 +232,7 @@ public class Campaign implements ITechManager { private transient AbstractProcreation procreation; private RetirementDefectionTracker retirementDefectionTracker; - private int morale; + private double morale; private AtBConfiguration atbConfig; //AtB private AtBEventProcessor atbEventProcessor; //AtB @@ -294,7 +295,7 @@ public Campaign() { setMarriage(new DisabledRandomMarriage(getCampaignOptions())); setProcreation(new DisabledRandomProcreation(getCampaignOptions())); retirementDefectionTracker = new RetirementDefectionTracker(); - morale = 4; + morale = 4.0; atbConfig = null; autosaveService = new AutosaveService(); hasActiveContract = false; @@ -480,11 +481,11 @@ public void setProcreation(final AbstractProcreation procreation) { this.procreation = procreation; } - public Integer getMorale() { + public Double getMorale() { return morale; } - public void setMorale(final Integer morale) { + public void setMorale(final Double morale) { this.morale = morale; } //endregion Personnel Modules @@ -1634,6 +1635,16 @@ public List getActivePersonnel() { .collect(Collectors.toList()); } + /** + * Provides a filtered list of personnel including only Persons with the AWOL status. + * @return a {@link Person} List containing all active personnel + */ + public List getAwolPersonnel() { + return getPersonnel().stream() + .filter(p -> p.getStatus().isAWOL()) + .collect(Collectors.toList()); + } + /** * Provides a filtered list of personnel including only Persons with the Student status. * @return a {@link Person} List containing all active personnel @@ -3517,6 +3528,10 @@ public boolean newDay() { processNewDayPersonnel(); + processFatigueNewDay(); + + processMoraleNewDay(); + if (campaignOptions.isUseEducationModule()) { EducationController.processNewDay(this); } @@ -3532,7 +3547,40 @@ public boolean newDay() { // check for anything in finances getFinances().newDay(this, yesterday, getLocalDate()); - // Fatigue Region + MekHQ.triggerEvent(new NewDayEvent(this)); + return true; + } + + private void processMoraleNewDay() { + // we still process awol personnel, even if Morale is disabled, to ensure they're not frozen in this state. + for (Person person : getAwolPersonnel()) { + if (person.getAwolDays() != -1) { + Morale.processAwolDays(this, person); + } + } + + if (!campaignOptions.isUseMorale()) { + return; + } + + if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { + Morale.makeMoraleChecks(this, true); + Morale.makeMoraleChecks(this, false); + + if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { + Morale.processMoraleRecovery(this, 1); + } + + + } + } + + /** + * This method is responsible for handling fatigue recovery and checking if the field kitchen is within capacity. + * Even if fatigue is disabled, fatigue recovery is still processed to ensure that fatigued personnel are not + * frozen in that state. + */ + private void processFatigueNewDay() { // even if Fatigue is disabled, we still want to process recovery so fatigued personnel aren't frozen in that state Fatigue.processFatigueRecovery(this); @@ -3543,10 +3591,6 @@ public boolean newDay() { } else { fieldKitchenWithinCapacity = false; } - // End Fatigue Region - - MekHQ.triggerEvent(new NewDayEvent(this)); - return true; } public @Nullable Person getFlaggedCommander() { diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 45c329e6fd..2673973e40 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -293,26 +293,31 @@ public static String getTransitUnitName(final int unit) { private Integer fatigueLeaveThreshold; private boolean useMorale; - private ForceReliabilityMethod forceReliabilityMethod; + private Double stepSize; private boolean useDesertions; + private boolean useMutinies; + private ForceReliabilityMethod forceReliabilityMethod; private boolean useEmergencyBonuses; private boolean useSabotage; - private boolean useMutinies; - private boolean useRuleWithIronFist; + private boolean useTheft; + + private boolean useMoraleTriggerFieldControl; + private boolean useMoraleTriggerMissionStatus; + private boolean useMoraleTriggerModifierLeaderLoss; + private boolean useMoraleTriggerCombatLoss; + private boolean useMoraleTriggerDesertion; + private boolean useMoraleTriggerMutiny; + private boolean useMoraleTriggerFatigue; + private boolean useMoraleModifierMissedPayDay; private Integer customMoraleModifier; - private boolean useMoraleModifierFieldControl; - private boolean useMoraleModifierMissionStatus; - private boolean useMoraleModifierLeaderLoss; - private boolean useMoraleModifierCombatLoss; - private boolean useMoraleModifierDesertion; - private boolean useMoraleModifierMutiny; - private boolean useMoraleModifierRest; - private boolean useMoraleModifierFatigue; + private boolean useMoraleModifierExperienceLevel; + private boolean useMoraleModifierFaction; private boolean useMoraleModifierProfession; private boolean useMoraleModifierForceReliability; - private boolean useMoraleModifierManagementSkill; - private boolean useMoraleModifierMissedPayDay; + private boolean useMoraleModifierCommanderLeadership; + private boolean useMoraleModifierLoyalty; + private boolean useRuleWithIronFist; // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -958,26 +963,31 @@ public CampaignOptions() { setFatigueLeaveThreshold(13); setUseMorale(true); + setStepSize(0.1); setForceReliabilityMethod(ForceReliabilityMethod.LOYALTY); setUseDesertions(true); setUseEmergencyBonuses(true); setUseSabotage(true); setUseMutinies(true); - setUseRuleWithIronFist(false); - - setCustomMoraleModifier(-2); - setUseMoraleModifierFieldControl(true); - setUseMoraleModifierMissionStatus(true); - setUseMoraleModifierLeaderLoss(true); - setUseMoraleModifierCombatLoss(true); - setUseMoraleModifierDesertion(true); - setUseMoraleModifierMutiny(true); - setUseMoraleModifierRest(true); - setUseMoraleModifierFatigue(true); + setUseTheft(true); + + setCustomMoraleModifier(2); + setUseMoraleTriggerFieldControl(true); + setUseMoraleTriggerMissionStatus(true); + setUseMoraleTriggerModifierLeaderLoss(true); + setUseMoraleTriggerCombatLoss(true); + setUseMoraleTriggerDesertion(true); + setUseMoraleTriggerMutiny(true); + setUseMoraleTriggerFatigue(true); + + setUseMoraleModifierExperienceLevel(true); + setUseMoraleModifierFaction(true); setUseMoraleModifierProfession(true); setUseMoraleModifierForceReliability(true); - setUseMoraleModifierManagementSkill(true); + setUseMoraleModifierCommanderLeadership(true); + setUseMoraleModifierLoyalty(true); setUseMoraleModifierMissedPayDay(true); + setUseRuleWithIronFist(false); //endregion Turnover and Retention //region Finances Tab @@ -1519,6 +1529,14 @@ public void setUseMorale(final boolean useMorale) { this.useMorale = useMorale; } + public double getStepSize() { + return stepSize; + } + + public void setStepSize(final double stepSize) { + this.stepSize = stepSize; + } + public ForceReliabilityMethod getForceReliabilityMethod() { return forceReliabilityMethod; } @@ -1551,6 +1569,14 @@ public void setUseSabotage(final boolean useSabotage) { this.useSabotage = useSabotage; } + public boolean isUseTheft() { + return useTheft; + } + + public void setUseTheft(final boolean useTheft) { + this.useTheft = useTheft; + } + public boolean isUseMutinies() { return useMutinies; } @@ -1591,12 +1617,20 @@ public void setUseMoraleModifierForceReliability(final boolean useMoraleModifier this.useMoraleModifierForceReliability = useMoraleModifierForceReliability; } - public boolean isUseMoraleModifierManagementSkill() { - return useMoraleModifierManagementSkill; + public boolean isUseMoraleModifierCommanderLeadership() { + return useMoraleModifierCommanderLeadership; + } + + public void setUseMoraleModifierCommanderLeadership(final boolean useMoraleModifierCommanderLeadership) { + this.useMoraleModifierCommanderLeadership = useMoraleModifierCommanderLeadership; } - public void setUseMoraleModifierManagementSkill(final boolean useMoraleModifierManagementSkill) { - this.useMoraleModifierManagementSkill = useMoraleModifierManagementSkill; + public boolean isUseMoraleModifierLoyalty() { + return useMoraleModifierLoyalty; + } + + public void setUseMoraleModifierLoyalty(final boolean useMoraleModifierLoyalty) { + this.useMoraleModifierLoyalty = useMoraleModifierLoyalty; } public boolean isUseMoraleModifierMissedPayDay() { @@ -1607,68 +1641,76 @@ public void setUseMoraleModifierMissedPayDay(final boolean useMoraleModifierMiss this.useMoraleModifierMissedPayDay = useMoraleModifierMissedPayDay; } - public boolean isUseMoraleModifierFatigue() { - return useMoraleModifierFatigue; + public boolean isUseMoraleTriggerFatigue() { + return useMoraleTriggerFatigue; + } + + public void setUseMoraleTriggerFatigue(final boolean useMoraleTriggerFatigue) { + this.useMoraleTriggerFatigue = useMoraleTriggerFatigue; } - public void setUseMoraleModifierFatigue(final boolean useMoraleModifierFatigue) { - this.useMoraleModifierFatigue = useMoraleModifierFatigue; + public boolean isUseMoraleModifierExperienceLevel() { + return useMoraleModifierExperienceLevel; } - public boolean isUseMoraleModifierFieldControl() { - return useMoraleModifierFieldControl; + public void setUseMoraleModifierExperienceLevel(final boolean useMoraleModifierExperienceLevel) { + this.useMoraleModifierExperienceLevel = useMoraleModifierExperienceLevel; } - public void setUseMoraleModifierFieldControl(final boolean useMoraleModifierFieldControl) { - this.useMoraleModifierFieldControl = useMoraleModifierFieldControl; + public boolean isUseMoraleModifierFaction() { + return useMoraleModifierFaction; } - public boolean isUseMoraleModifierMissionStatus() { - return useMoraleModifierMissionStatus; + public void setUseMoraleModifierFaction(final boolean useMoraleModifierFaction) { + this.useMoraleModifierFaction = useMoraleModifierFaction; } - public void setUseMoraleModifierMissionStatus(final boolean useMoraleModifierMissionStatus) { - this.useMoraleModifierMissionStatus = useMoraleModifierMissionStatus; + public boolean isUseMoraleTriggerFieldControl() { + return useMoraleTriggerFieldControl; } - public boolean isUseMoraleModifierLeaderLoss() { - return useMoraleModifierLeaderLoss; + public void setUseMoraleTriggerFieldControl(final boolean useMoraleTriggerFieldControl) { + this.useMoraleTriggerFieldControl = useMoraleTriggerFieldControl; } - public void setUseMoraleModifierLeaderLoss(final boolean useMoraleModifierLeaderLoss) { - this.useMoraleModifierLeaderLoss = useMoraleModifierLeaderLoss; + public boolean isUseMoraleTriggerMissionStatus() { + return useMoraleTriggerMissionStatus; } - public boolean isUseMoraleModifierCombatLoss() { - return useMoraleModifierCombatLoss; + public void setUseMoraleTriggerMissionStatus(final boolean useMoraleTriggerMissionStatus) { + this.useMoraleTriggerMissionStatus = useMoraleTriggerMissionStatus; } - public void setUseMoraleModifierCombatLoss(final boolean useMoraleModifierCombatLoss) { - this.useMoraleModifierCombatLoss = useMoraleModifierCombatLoss; + public boolean isUseMoraleTriggerModifierLeaderLoss() { + return useMoraleTriggerModifierLeaderLoss; } - public boolean isUseMoraleModifierDesertion() { - return useMoraleModifierDesertion; + public void setUseMoraleTriggerModifierLeaderLoss(final boolean useMoraleTriggerModifierLeaderLoss) { + this.useMoraleTriggerModifierLeaderLoss = useMoraleTriggerModifierLeaderLoss; } - public void setUseMoraleModifierDesertion(final boolean useMoraleModifierDesertion) { - this.useMoraleModifierDesertion = useMoraleModifierDesertion; + public boolean isUseMoraleTriggerCombatLoss() { + return useMoraleTriggerCombatLoss; } - public boolean isUseMoraleModifierMutiny() { - return useMoraleModifierMutiny; + public void setUseMoraleTriggerCombatLoss(final boolean useMoraleTriggerCombatLoss) { + this.useMoraleTriggerCombatLoss = useMoraleTriggerCombatLoss; } - public void setUseMoraleModifierMutiny(final boolean useMoraleModifierMutiny) { - this.useMoraleModifierMutiny = useMoraleModifierMutiny; + public boolean isUseMoraleTriggerDesertion() { + return useMoraleTriggerDesertion; } - public boolean isUseMoraleModifierRest() { - return useMoraleModifierRest; + public void setUseMoraleTriggerDesertion(final boolean useMoraleTriggerDesertion) { + this.useMoraleTriggerDesertion = useMoraleTriggerDesertion; } - public void setUseMoraleModifierRest(final boolean useMoraleModifierRest) { - this.useMoraleModifierRest = useMoraleModifierRest; + public boolean isUseMoraleTriggerMutiny() { + return useMoraleTriggerMutiny; + } + + public void setUseMoraleTriggerMutiny(final boolean useMoraleTriggerMutiny) { + this.useMoraleTriggerMutiny = useMoraleTriggerMutiny; } //endregion General Personnel @@ -4477,26 +4519,31 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLeaveThreshold", getFatigueLeaveThreshold()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMorale", isUseMorale()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "stepSize", getStepSize()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "forceReliabilityMethod", getForceReliabilityMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useDesertions", isUseDesertions()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useEmergencyBonuses", isUseEmergencyBonuses()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMutinies", isUseMutinies()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRuleWithIronFist", isUseRuleWithIronFist()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheft", isUseTheft()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "customMoraleModifier", getCustomMoraleModifier()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierFieldControl", isUseMoraleModifierFieldControl()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierMissionStatus", isUseMoraleModifierMissionStatus()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierLeaderLoss", isUseMoraleModifierLeaderLoss()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCombatLoss", isUseMoraleModifierCombatLoss()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierDesertion", isUseMoraleModifierDesertion()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierMutiny", isUseMoraleModifierMutiny()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierRest", isUseMoraleModifierRest()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierFatigue", isUseMoraleModifierFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerFieldControl", isUseMoraleTriggerFieldControl()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerMissionStatus", isUseMoraleTriggerMissionStatus()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerLeaderLoss", isUseMoraleTriggerModifierLeaderLoss()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerCombatLoss", isUseMoraleTriggerCombatLoss()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerDesertion", isUseMoraleTriggerDesertion()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerMutiny", isUseMoraleTriggerMutiny()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerFatigue", isUseMoraleTriggerFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "chkUseMoraleTriggerMissedPayDay", isUseMoraleModifierMissedPayDay()); + + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierExperienceLevel", isUseMoraleModifierExperienceLevel()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierFaction", isUseMoraleModifierFaction()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierProfession", isUseMoraleModifierProfession()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierForceReliability", isUseMoraleModifierForceReliability()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierManagementSkill", isUseMoraleModifierManagementSkill()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "chkUseMoraleModifierMissedPayDay", isUseMoraleModifierMissedPayDay()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCommanderLeadership", isUseMoraleModifierCommanderLeadership()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierLoyalty", isUseMoraleModifierLoyalty()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRuleWithIronFist", isUseRuleWithIronFist()); //endregion Retirement //region Family @@ -5193,6 +5240,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setFatigueLeaveThreshold(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMorale")) { retVal.setUseMorale(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("stepSize")) { + retVal.setStepSize(Double.parseDouble(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("forceReliabilityMethod")) { retVal.setForceReliabilityMethod(ForceReliabilityMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useDesertions")) { @@ -5201,36 +5250,42 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseEmergencyBonuses(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSabotage")) { retVal.setUseSabotage(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useTheft")) { + retVal.setUseTheft(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMutinies")) { retVal.setUseMutinies(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useRuleWithIronFist")) { - retVal.setUseRuleWithIronFist(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerFieldControl")) { + retVal.setUseMoraleTriggerFieldControl(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerMissionStatus")) { + retVal.setUseMoraleTriggerMissionStatus(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerLeaderLoss")) { + retVal.setUseMoraleTriggerModifierLeaderLoss(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerCombatLoss")) { + retVal.setUseMoraleTriggerCombatLoss(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerDesertion")) { + retVal.setUseMoraleTriggerDesertion(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerMutiny")) { + retVal.setUseMoraleTriggerMutiny(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerFatigue")) { + retVal.setUseMoraleTriggerFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("customMoraleModifier")) { retVal.setCustomMoraleModifier(Integer.parseInt(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierScenarioStatus")) { - retVal.setUseMoraleModifierFieldControl(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMissionStatus")) { - retVal.setUseMoraleModifierMissionStatus(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierLeaderLoss")) { - retVal.setUseMoraleModifierLeaderLoss(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierCombatLoss")) { - retVal.setUseMoraleModifierCombatLoss(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierDesertion")) { - retVal.setUseMoraleModifierDesertion(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMutiny")) { - retVal.setUseMoraleModifierMutiny(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierInactivity")) { - retVal.setUseMoraleModifierRest(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierFatigue")) { - retVal.setUseMoraleModifierFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierRole")) { + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierExperienceLevel")) { + retVal.setUseMoraleModifierExperienceLevel(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierFaction")) { + retVal.setUseMoraleModifierFaction(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierProfession")) { retVal.setUseMoraleModifierProfession(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierForceReliability")) { retVal.setUseMoraleModifierForceReliability(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierManagementSkill")) { - retVal.setUseMoraleModifierManagementSkill(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMissedPayment")) { + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierCommanderLeadership")) { + retVal.setUseMoraleModifierCommanderLeadership(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierLoyalty")) { + retVal.setUseMoraleModifierLoyalty(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerMissedPayDay")) { retVal.setUseMoraleModifierMissedPayDay(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useRuleWithIronFist")) { + retVal.setUseRuleWithIronFist(Boolean.parseBoolean(wn2.getTextContent().trim())); //endregion Retirement //region Family diff --git a/MekHQ/src/mekhq/campaign/finances/enums/TransactionType.java b/MekHQ/src/mekhq/campaign/finances/enums/TransactionType.java index 3e69a3e3e8..d071d41c3a 100644 --- a/MekHQ/src/mekhq/campaign/finances/enums/TransactionType.java +++ b/MekHQ/src/mekhq/campaign/finances/enums/TransactionType.java @@ -49,6 +49,7 @@ public enum TransactionType { SALVAGE_EXCHANGE("TransactionType.SALVAGE_EXCHANGE.text", "TransactionType.SALVAGE_EXCHANGE.toolTipText"), STARTING_CAPITAL("TransactionType.STARTING_CAPITAL.text", "TransactionType.STARTING_CAPITAL.toolTipText"), TAXES("TransactionType.TAXES.text", "TransactionType.TAXES.toolTipText"), + THEFT("TransactionType.THEFT.text", "TransactionType.THEFT.toolTipText"), TRANSPORTATION("TransactionType.TRANSPORTATION.text", "TransactionType.TRANSPORTATION.toolTipText"), UNIT_PURCHASE("TransactionType.UNIT_PURCHASE.text", "TransactionType.UNIT_PURCHASE.toolTipText"), UNIT_SALE("TransactionType.UNIT_SALE.text", "TransactionType.UNIT_SALE.toolTipText"); @@ -171,6 +172,10 @@ public boolean isTaxes() { return this == TAXES; } + public boolean isTheft() { + return this == THEFT; + } + public boolean isTransportation() { return this == TRANSPORTATION; } @@ -235,6 +240,8 @@ public static TransactionType parseFromString(final String text) { return RANSOM; case 17: return EDUCATION; + case 18: + return THEFT; default: break; } diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index 6458039f2b..b2c65b57b1 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -276,7 +276,7 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException { } else if (xn.equalsIgnoreCase("retirementDefectionTracker")) { retVal.setRetirementDefectionTracker(RetirementDefectionTracker.generateInstanceFromXML(wn, retVal)); } else if (xn.equalsIgnoreCase("Morale")) { - retVal.setMorale(Integer.parseInt(wn.getTextContent())); + retVal.setMorale(Double.parseDouble(wn.getTextContent())); } else if (xn.equalsIgnoreCase("shipSearchStart")) { retVal.setShipSearchStart(MHQXMLUtility.parseDate(wn.getTextContent().trim())); } else if (xn.equalsIgnoreCase("shipSearchType")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index 277e3a9f13..d6ac6fdca0 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -127,6 +127,7 @@ public class Person { private Integer loyalty; private Integer fatigue; private Boolean isRecoveringFromFatigue; + private Integer awolDays; private Skills skills; private PersonnelOptions options; @@ -333,6 +334,7 @@ public Person(final String preNominal, final String givenName, final String surn loyalty = 0; fatigue = 0; isRecoveringFromFatigue = false; + awolDays = -1; skills = new Skills(); options = new PersonnelOptions(); currentEdge = 0; @@ -1222,6 +1224,14 @@ public void setFatigue(final Integer fatigue) { this.fatigue = fatigue; } + public Integer getAwolDays() { + return awolDays; + } + + public void setAwolDays(final Integer awolDays) { + this.awolDays = awolDays; + } + public boolean getIsRecoveringFromFatigue() { return isRecoveringFromFatigue; } @@ -1634,7 +1644,8 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigue", getFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue());; + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "awolDays", getAwolDays()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); } @@ -1946,6 +1957,8 @@ public static Person generateInstanceFromXML(Node wn, Campaign c, Version versio retVal.fatigue = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("isRecoveringFromFatigue")) { retVal.isRecoveringFromFatigue = Boolean.parseBoolean(wn2.getTextContent().trim()); + } else if (wn2.getNodeName().equalsIgnoreCase("awolDays")) { + retVal.awolDays = Integer.parseInt(wn2.getTextContent()); } else if (wn2.getNodeName().equalsIgnoreCase("advantages")) { advantages = wn2.getTextContent(); } else if (wn2.getNodeName().equalsIgnoreCase("edge")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java index e76f90d05a..0c21da1d0d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java +++ b/MekHQ/src/mekhq/campaign/personnel/enums/ForceReliabilityMethod.java @@ -23,7 +23,7 @@ import java.util.ResourceBundle; public enum ForceReliabilityMethod { - EQUIPMENT("ForceReliabilityMethod.EQUIPMENT.text", "ForceReliabilityMethod.EQUIPMENT.toolTipText"), + UNIT_RATING("ForceReliabilityMethod.UNIT_RATING.text", "ForceReliabilityMethod.UNIT_RATING.toolTipText"), LOYALTY("ForceReliabilityMethod.LOYALTY.text", "ForceReliabilityMethod.LOYALTY.toolTipText"), OVERRIDE_A("ForceReliabilityMethod.OVERRIDE_A.text", "ForceReliabilityMethod.OVERRIDE_A.toolTipText"), OVERRIDE_B("ForceReliabilityMethod.OVERRIDE_B.text", "ForceReliabilityMethod.OVERRIDE_B.toolTipText"), @@ -45,8 +45,8 @@ public String getToolTipText() { return toolTipText; } - public boolean isEquipment() { - return this == EQUIPMENT; + public boolean isUnitRating() { + return this == UNIT_RATING; } public boolean isLoyalty() { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index b21f2e9e77..088d8071ec 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -1,9 +1,20 @@ package mekhq.campaign.personnel.turnoverAndRetention; +import megamek.codeUtilities.MathUtility; +import megamek.common.Compute; +import megamek.common.Entity; import mekhq.MekHQ; import mekhq.campaign.Campaign; +import mekhq.campaign.finances.Money; +import mekhq.campaign.finances.enums.TransactionType; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.enums.ForceReliabilityMethod; +import mekhq.campaign.personnel.enums.PersonnelRole; +import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.unit.Unit; -import java.util.ResourceBundle; +import java.util.*; public class Morale { @@ -17,97 +28,532 @@ public static String getMoraleLevel(Campaign campaign) { final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); - switch(campaign.getMorale()) { + double morale = campaign.getMorale(); + + if ((morale >= 1) && (morale < 2)) { + return resources.getString("moraleLevelUnbreakable.text"); + } else if (morale < 3) { + return resources.getString("moraleLevelVeryHigh.text"); + } else if (morale < 4) { + return resources.getString("moraleLevelHigh.text"); + } else if (morale < 5) { + return resources.getString("moraleLevelNormal.text"); + } else if (morale < 6) { + return resources.getString("moraleLevelLow.text"); + } else if (morale < 7) { + return resources.getString("moraleLevelVeryLow.text"); + } else if (morale == 7) { + return resources.getString("moraleLevelBroken.text"); + } + + throw new IllegalStateException("Unexpected value in getMoraleLevel(): " + campaign.getMorale()); + } + + /** + * Returns the target number for a morale check based on the campaign morale level and desertion flag. + * + * @param campaign the current campaign + * @param isDesertion whether the target number is for a desertion check + * @return the base target number for morale + * @throws IllegalStateException if the campaign morale level is unexpected + */ + private static Double getTargetNumber(Campaign campaign, boolean isDesertion) { + double morale = campaign.getMorale(); + + if ((morale >= 1) && (morale < 4)) { + return 0.0; + } else if (morale < 5) { + if (isDesertion) { + return 2.0; + } else { + return 0.0; + } + } else if (morale < 7) { + if (isDesertion) { + return 5.0; + } else { + return 4.0; + } + } else if (morale == 7) { + if (isDesertion) { + return 8.0; + } else { + return 7.0; + } + } + + throw new IllegalStateException("Unexpected value in getTargetNumber: " + campaign.getMorale()); + } + + /** + * Returns the final morale modifier for a person. + * + * @param campaign the ongoing campaign + * @param person the person the modifier is being calculated for + * @param isDesertion whether the target number is for a desertion check + * @param meanLoyalty the mean loyalty value + * @return the morale check modifiers + */ + private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, boolean isDesertion, Integer meanLoyalty) { + int modifier = 0; + + // Custom Modifier + modifier += campaign.getCampaignOptions().getCustomMoraleModifier(); + + // Experience Level Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierExperienceLevel()) { + modifier += getExperienceModifier(campaign, person); + } + + // Faction Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierFaction()) { + if (person.getOriginFaction().isClan()) { + modifier++; + } else if (person.getOriginFaction().isMercenary()) { + modifier--; + } + } + + // Profession Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierProfession()) { + modifier += getProfessionModifier(person.getPrimaryRole(), person); + + if (person.getSecondaryRole() != null) { + modifier += (modifier + getProfessionModifier(person.getSecondaryRole(), person)) / 2; + } + } + + // Force Reliability Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierForceReliability()) { + modifier += getForceReliabilityModifier(campaign, isDesertion, meanLoyalty); + } + + // Management Skill Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierCommanderLeadership()) { + Person commander = campaign.getFlaggedCommander(); + + if ((commander != null) && (campaign.getCampaignOptions().isUseManagementSkill())) { + if (commander.hasSkill(SkillType.S_LEADER)) { + if ((commander.getSkill(SkillType.S_LEADER).getLevel() + campaign.getCampaignOptions().getManagementSkillPenalty()) > 0) { + modifier++; + } + } + } + } + + // Iron Fist Modifier + if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { + modifier++; + } + + return modifier; + } + + /** + * Calculates the experience modifier based on the person's experience level. + * + * @param campaign the campaign that the person is participating in + * @param person the person whose experience level is being evaluated + * @return the experience modifier based on the person's experience level + * @throws IllegalStateException if the person's experience level is an unexpected value + */ + private static int getExperienceModifier(Campaign campaign, Person person) { + switch (person.getExperienceLevel(campaign, false)) { + case -1: + return -2; + case 0: case 1: - return resources.getString("moraleLevelUnbreakable.text"); + return -1; case 2: - return resources.getString("moraleLevelVeryHigh.text"); + return 0; case 3: - return resources.getString("moraleLevelHigh.text"); + return 1; case 4: - return resources.getString("moraleLevelNormal.text"); - case 5: - return resources.getString("moraleLevelLow.text"); - case 6: - return resources.getString("moraleLevelVeryLow.text"); - case 7: - return resources.getString("moraleLevelBroken.text"); + return 2; default: - throw new IllegalStateException("Unexpected value in getMoraleLevel(): " + campaign.getMorale()); + throw new IllegalStateException("Unexpected value in getExperienceModifier: " + person.getExperienceLevel(campaign, false)); + } + } + + /** + * Calculates the force reliability modifier based on the desertion flag, and chosen reliability method. + * + * @param campaign the ongoing campaign + * @param isDesertion whether the target number is for a desertion check + * @param meanLoyalty the mean loyalty value + * @return the force reliability modifier as an integer value + */ + private static int getForceReliabilityModifier(Campaign campaign, boolean isDesertion, Integer meanLoyalty) { + ForceReliabilityMethod reliabilityMethod = campaign.getCampaignOptions().getForceReliabilityMethod(); + + switch (reliabilityMethod) { + case UNIT_RATING: + return getUnitRatingModifier(campaign.getUnitRatingMod(), isDesertion); + case LOYALTY: + if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { + return getLoyaltyModifier(isDesertion, meanLoyalty); + } else { + return getUnitRatingModifier(campaign.getUnitRatingMod(), isDesertion); + } + case OVERRIDE_C: + return 0; + case OVERRIDE_A: + return 1; + case OVERRIDE_B: + if (isDesertion) { + return 1; + } else { + return 0; + } + case OVERRIDE_D: + if (!isDesertion) { + return -1; + } else { + return 0; + } + case OVERRIDE_F: + return -1; } + return 0; } /** - * Calculates the base target number for desertion based on the morale of the campaign. + * Computes the loyalty morale modifier. * - * @param campaign the campaign for which to calculate the base target number for desertion - * @return the base target number for desertion - * @throws IllegalStateException if the morale value of the campaign is unexpected + * @param isDesertion whether the target number is for a desertion check + * @param meanLoyalty the mean loyalty value used to calculate the loyalty modifier + * @return the loyalty modifier calculated based on the given parameters + * @throws IllegalStateException if the meanLoyalty value is unexpected */ - public static Integer getDesertionBaseTargetNumber(Campaign campaign) { - switch(campaign.getMorale()) { + private static int getLoyaltyModifier(boolean isDesertion, Integer meanLoyalty) { + switch (meanLoyalty) { + case -3: + return -1; + case -2: + case -1: + if (!isDesertion) { + return -1; + } else { + return 0; + } + case 0: case 1: + if (isDesertion) { + return 1; + } else { + return 0; + } case 2: case 3: - return 0; - case 4: - return 2; - case 5: - case 6: - return 5; - case 7: - return 8; + return 1; default: - throw new IllegalStateException("Unexpected value in getDesertionTargetNumber: " + campaign.getMorale()); + throw new IllegalStateException("Unexpected value in getLoyaltyModifier: " + meanLoyalty); } } /** - * Calculates the base target number for mutiny based on the morale of the campaign. + * Computes the unit rating morale modifier. * - * @param campaign the campaign for which to calculate the base target number for mutiny - * @return the base target number for mutiny - * @throws IllegalStateException if the morale value of the campaign is unexpected + * @param unitRatingMod the unit rating modifier value + * @param isDesertion whether the target number is for a desertion check + * @return the unit rating morale modifier + * @throws IllegalStateException if the campaign has an unexpected value for unit rating */ - public static Integer getMutinyBaseTargetNumber(Campaign campaign) { - switch(campaign.getMorale()) { + private static int getUnitRatingModifier(Integer unitRatingMod, boolean isDesertion) { + switch(unitRatingMod) { + case 0: + return -1; case 1: + if (!isDesertion) { + return -1; + } else { + return 0; + } case 2: case 3: - case 4: return 0; + case 4: + if (isDesertion) { + return 1; + } else { + return 0; + } case 5: - case 6: - return 4; - case 7: - return 7; + return 1; default: - throw new IllegalStateException("Unexpected value in getDesertionTargetNumber: " + campaign.getMorale()); + throw new IllegalStateException("Unexpected value in getUnitRatingModifier: " + unitRatingMod); } } /** - * Returns a morale report for the given Campaign. + * Returns the profession modifier based on the role and person. + * + * @param role the personnel role of the person + * @param person the person for whom the profession modifier is being calculated + * @return the profession modifier as an Integer + */ + private static Integer getProfessionModifier(PersonnelRole role, Person person) { + if (role.isVesselCrew()) { + if (person.getUnit() == null) { + return -1; + } else { + Entity entity = person.getUnit().getEntity(); + + if ((entity.isSmallCraft()) || (entity.isJumpShip())) { + return -1; + } else if (entity.isDropShip()) { + return 0; + } else if (entity.isWarShip()) { + return 2; + } + } + } else if ((role.isMechWarrior()) || (role.isProtoMechPilot()) || (role.isAerospaceGrouping()) || (role.isMedicalStaff())) { + return 1; + } else if ((role.isSoldier()) || (role.isTech())) { + return -1; + } else if (role.isAdministrator()) { + return -2; + } else if (role.isVehicleCrew()) { + if (person.getUnit() == null) { + return 0; + } else if (person.getUnit().getEntity().isSupportVehicle()) { + return -2; + } + } + + return 0; + } + + /** + * Adds a morale report to the given Campaign. * * @param campaign the Campaign for which to generate the report - * @return the morale report as a String */ - public static String getMoraleReport(Campaign campaign) { + public static void getMoraleReport(Campaign campaign) { final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); StringBuilder moraleReport = new StringBuilder(); - if ((getDesertionBaseTargetNumber(campaign) - campaign.getCampaignOptions().getCustomMoraleModifier()) >= 2) { - moraleReport.append(String.format(resources.getString("moraleReportLow.text="), getMoraleReport(campaign))); + if (getTargetNumber(campaign, true) >= 2) { + moraleReport.append(String.format(resources.getString("moraleReportLow.text"), getMoraleLevel(campaign))); + } else { + moraleReport.append(String.format(resources.getString("moraleReport.text"), getMoraleLevel(campaign))); + } + + if (getTargetNumber(campaign, false) >= 2) { + moraleReport.append(' ').append(resources.getString("moraleReportMutiny.text")); + } + + campaign.addReport(moraleReport.toString()); + } + + public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { + if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { + return; + } else if ((isDesertion) && (!campaign.getLocation().isOnPlanet())) { + return; + } else if ((!isDesertion) && (!campaign.getCampaignOptions().isUseMutinies())) { + return; + } + + double targetNumber = getTargetNumber(campaign, isDesertion); + + if (targetNumber == 0) { + return; + } + + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + List filteredPersonnel = new ArrayList<>(); + int loyalty = 0; + + for (Person person : campaign.getActivePersonnel()) { + if ((person.getPrisonerStatus().isFree()) || (!isDesertion)) { + filteredPersonnel.add(person); + loyalty += person.getLoyalty(); + } + } + + if (campaign.getCampaignOptions().getForceReliabilityMethod().isLoyalty()) { + loyalty = MathUtility.clamp(loyalty / filteredPersonnel.size(), -3, 3); + } else { + loyalty = 0; + } + + double morale = campaign.getMorale(); + + ArrayList unitList = new ArrayList<>(); + + if (isDesertion) { + unitList = (ArrayList) campaign.getUnits(); + } + + boolean someoneHasDeserted = false; + boolean someoneHasMutinied = false; + + List loyalists = new ArrayList<>(); + List rebels = new ArrayList<>(); + + for (Person person : filteredPersonnel) { + int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, loyalty); + int roll = Compute.d6(2) + modifier; + + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseMoraleModifierLoyalty())) { + roll += getLoyaltyModifier(isDesertion, person.getLoyalty()); + } + + if (roll <= targetNumber) { + if (isDesertion) { + // TODO emergency bonuses give reroll + + if ((processDisertion(campaign, person, roll, targetNumber, morale, unitList, resources)) && (!someoneHasDeserted)) { + someoneHasDeserted = true; + } + } else { + rebels.add(person); + someoneHasMutinied = true; + } + } else { + loyalists.add(person); + } + } + + if (someoneHasMutinied) { + processMoraleLoss(campaign, -2); + processMutiny(campaign, loyalists, rebels); + } else if (someoneHasDeserted) { + processMoraleLoss(campaign, -1); + } + } + + private static boolean processDisertion(Campaign campaign, Person person, int roll, double targetNumber, double morale, ArrayList unitList, ResourceBundle resources) { + if (roll <= (targetNumber - 2)) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); + + if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheft())) { + processTheft(campaign, unitList, resources); + } + + return true; + } else { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); + person.setAwolDays(Compute.d6(2)); + + return false; + } + } + + /** + * Process theft of units and money. + * + * @param campaign the campaign from which units or funds are stolen + * @param unitList the list of available units + * @param resources the resource bundle for retrieving localized strings + */ + private static void processTheft(Campaign campaign, ArrayList unitList, ResourceBundle resources) { + boolean committingTheft = true; + int attempts = 3; + + while (committingTheft) { + Unit desiredUnit = unitList.get(new Random().nextInt(unitList.size())); + + if ((desiredUnit.getEntity().isLargeCraft()) || (desiredUnit.getEntity().isWarShip()) || (desiredUnit.isDeployed())) { + attempts--; + + if (attempts == 0) { + Money stolenMoney = campaign.getFinances().getBalance().multipliedBy(0.1); + + campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), stolenMoney, resources.getString("desertionHeist.text")); + committingTheft = false; + } + } else { + StringBuilder unitName = new StringBuilder(desiredUnit.getName()); + + if (!Objects.equals(desiredUnit.getFluffName(), "")) { + unitName.append(' ').append(desiredUnit.getFluffName()); + } + + campaign.addReport(String.format(resources.getString("desertionStolen.text"), unitName)); + + campaign.removeUnit(desiredUnit.getId()); + unitList.remove(desiredUnit); + + committingTheft = false; + } + } + } + + /** + * Processes the number of AWOL (Absent Without Leave) days for a person. + * If the person has no AWOL days remaining, it randomly determines whether to add another d6 AWOL days + * or change the person's status to ACTIVE. + * Otherwise, it subtracts one AWOL day from the person's total. + * + * @param campaign the campaign in which the person belongs + * @param person the person for whom AWOL days are being processed + */ + public static void processAwolDays(Campaign campaign, Person person) { + int awolDays = person.getAwolDays(); + + if (awolDays == 0) { + if (Compute.d6(1) <= 2) { + person.setAwolDays(awolDays + Compute.d6(1)); + } else { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.ACTIVE); + } } else { - moraleReport.append(String.format(resources.getString("moraleReport.text="), getMoraleReport(campaign))); + person.setAwolDays(awolDays - 1); } + } + + /** + * Process morale recovery for a campaign by decreasing the morale value based on the given number of steps. + * If the current morale value is not equal to 1, the morale value will be updated by subtracting the 'steps' value. + * The updated morale value will be clamped within the range of 1 to 7. + * After the morale value is updated, the morale report will be generated. + * + * @param campaign the Campaign object representing the campaign to process morale loss for + * @param steps the Integer value representing the number of steps to decrease morale by + */ + public static void processMoraleRecovery(Campaign campaign, Integer steps) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + double morale = campaign.getMorale(); + + if (morale != 1) { + campaign.setMorale(MathUtility.clamp(morale - (steps * campaign.getCampaignOptions().getStepSize()), 1.0, 7.0)); + getMoraleReport(campaign); - if ((getMutinyBaseTargetNumber(campaign) - campaign.getCampaignOptions().getCustomMoraleModifier()) >= 2) { - moraleReport.append(' ').append(resources.getString("moraleReportMutiny.text=")); + if ((morale >= 5) && ((morale + steps) < 5)) { + campaign.addReport(resources.getString("moraleReportRecovered.text")); + } } + } + + /** + * Process morale loss for a campaign by increasing the morale value based on the given number of steps. + * If the current morale value is not equal to 7, the morale value will be updated by adding the 'steps' value. + * The updated morale value will be clamped within the range of 1 to 7. + * After the morale value is updated, the morale report will be generated. + * + * @param campaign the Campaign object representing the campaign to process morale loss for + * @param steps the Integer value representing the number of steps to increase morale by + */ + public static void processMoraleLoss(Campaign campaign, Integer steps) { + double morale = campaign.getMorale(); + + if (morale != 7) { + campaign.setMorale(MathUtility.clamp(morale + (steps * campaign.getCampaignOptions().getStepSize()), 1.0, 7.0)); + getMoraleReport(campaign); + } + } - return moraleReport.toString(); + private static void processMutiny(Campaign campaign, List loyalists, List rebels) { + // TODO process mutinies + // There should be three possible events: violent transfer of power, demand leader step down, or failed to garner enough support + // Player should have the option to join the rebels or stay with the loyalists } } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index f650a870c1..7d4084ec2a 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -332,31 +332,36 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseMorale; private JPanel moraleSubPanel = new JPanel(); - private JLabel lblForceReliabilityMethod; - private MMComboBox comboForceReliabilityMethod; + private JLabel lblStepSize; + private JSpinner spnStepSize; private JCheckBox chkUseDesertions; private JCheckBox chkUseEmergencyBonuses; + private JLabel lblForceReliabilityMethod; + private MMComboBox comboForceReliabilityMethod; private JCheckBox chkUseSabotage; private JCheckBox chkUseMutinies; + private JCheckBox chkUseTheft; private JPanel turnoverAndRetentionMoraleModifiersPanel = new JPanel(); private JLabel lblCustomMoraleModifier; private JSpinner spnCustomMoraleModifier; - private JCheckBox chkUseMoraleModifierFatigue; + private JCheckBox chkUseMoraleModifierExperienceLevel; + private JCheckBox chkUseMoraleModifierFaction; private JCheckBox chkUseMoraleModifierProfession; private JCheckBox chkUseMoraleModifierForceReliability; - private JCheckBox chkUseMoraleModifierManagementSkill; - private JCheckBox chkUseMoraleModifierMissedPayDay; + private JCheckBox chkUseMoraleModifierCommanderSkill; + private JCheckBox chkUseMoraleModifierLoyalty; private JCheckBox chkUseRuleWithIronFist; - private JPanel turnoverAndRetentionMoraleChangePanel = new JPanel(); - private JCheckBox chkUseMoraleModifierFieldControl; - private JCheckBox chkUseMoraleModifierMissionStatus; - private JCheckBox chkUseMoraleModifierLeaderLoss; - private JCheckBox chkUseMoraleModifierCombatLoss; - private JCheckBox chkUseMoraleModifierDesertion; - private JCheckBox chkUseMoraleModifierMutiny; - private JCheckBox chkUseMoraleModifierRest; + private JPanel turnoverAndRetentionMoraleTriggersPanel = new JPanel(); + private JCheckBox chkUseMoraleTriggerFieldControl; + private JCheckBox chkUseMoraleTriggerMissionStatus; + private JCheckBox chkUseMoraleTriggerLeaderLoss; + private JCheckBox chkUseMoraleTriggerCombatLoss; + private JCheckBox chkUseMoraleTriggerDesertion; + private JCheckBox chkUseMoraleTriggerMutiny; + private JCheckBox chkUseMoraleTriggerMissedPayDay; + private JCheckBox chkUseMoraleTriggerFatigue; //endregion Turnover and Retention Tab //region Life Paths Tab @@ -3246,7 +3251,7 @@ private JScrollPane createTurnoverAndRetentionTab() { turnoverAndRetentionPanel.add(createTurnoverAndRetentionMoraleModifiersPanel(), gbc); gbc.gridx++; - turnoverAndRetentionPanel.add(createTurnoverAndRetentionMoraleChangePanel(), gbc); + turnoverAndRetentionPanel.add(createTurnoverAndRetentionMoraleTriggersPanel(), gbc); final JScrollPane scrollPersonnel = new JScrollPane(turnoverAndRetentionPanel); scrollPersonnel.setPreferredSize(new Dimension(500, 400)); @@ -4280,7 +4285,7 @@ private JPanel createTurnoverAndRetentionUnitCohesionPanel() { administrativeStrainSubPanel.setEnabled(isEnabled); - chkUseMoraleModifierManagementSkill.setEnabled((isEnabled) && (chkUseMorale.isSelected())); + chkUseMoraleModifierCommanderSkill.setEnabled((isEnabled) && (chkUseMorale.isSelected())); }); createAdministrativeStrainSubPanel(isUseTurnover); @@ -4298,7 +4303,7 @@ private JPanel createTurnoverAndRetentionUnitCohesionPanel() { managementSkillSubPanel.setEnabled(isEnabled); - chkUseMoraleModifierManagementSkill.setEnabled((isEnabled) && (chkUseMorale.isSelected())); + chkUseMoraleModifierCommanderSkill.setEnabled((isEnabled) && (chkUseMorale.isSelected())); }); createManagementSkillSubPanel(isUseTurnover); @@ -4544,7 +4549,7 @@ private JPanel createTurnoverAndRetentionFatiguePanel() { fatigueSubPanel.setEnabled(isEnabled); chkUseFatigueModifiers.setEnabled(isEnabled); - chkUseMoraleModifierFatigue.setEnabled((isEnabled) && (chkUseMorale.isSelected())); + chkUseMoraleTriggerFatigue.setEnabled((isEnabled) && (chkUseMorale.isSelected())); }); createFatigueSubPanel(); @@ -4588,21 +4593,23 @@ private JPanel createTurnoverAndRetentionMoralePanel() { component.setEnabled(isEnabled); } - for (Component component : turnoverAndRetentionMoraleChangePanel.getComponents()) { + for (Component component : turnoverAndRetentionMoraleTriggersPanel.getComponents()) { component.setEnabled(isEnabled); } // Border Handlers moraleSubPanel.setEnabled(isEnabled); turnoverAndRetentionMoraleModifiersPanel.setEnabled(isEnabled); - turnoverAndRetentionMoraleChangePanel.setEnabled(isEnabled); + turnoverAndRetentionMoraleTriggersPanel.setEnabled(isEnabled); // Special Case Handlers - chkUseMoraleModifierDesertion.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); - chkUseMoraleModifierManagementSkill.setEnabled((isEnabled) && (chkUseManagementSkill.isSelected())); + chkUseMoraleTriggerDesertion.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); + chkUseMoraleModifierCommanderSkill.setEnabled((isEnabled) && (chkUseManagementSkill.isSelected())); + + chkUseMoraleTriggerMutiny.setEnabled((isEnabled) && (chkUseMutinies.isSelected())); + chkUseMoraleTriggerFatigue.setEnabled((isEnabled) && (chkUseFatigue.isSelected())); - chkUseMoraleModifierMutiny.setEnabled((isEnabled) && (chkUseMutinies.isSelected())); - chkUseMoraleModifierFatigue.setEnabled((isEnabled) && (chkUseFatigue.isSelected())); + chkUseTheft.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); }); createMoraleSubPanel(); @@ -4633,6 +4640,16 @@ private JPanel createTurnoverAndRetentionMoralePanel() { private void createMoraleSubPanel() { boolean isUseMorale = campaign.getCampaignOptions().isUseMorale(); + lblStepSize = new JLabel(resources.getString("lblStepSize.text")); + lblStepSize.setToolTipText(resources.getString("lblStepSize.toolTipText")); + lblStepSize.setName("lblStepSize"); + lblStepSize.setEnabled(isUseMorale); + + spnStepSize = new JSpinner(new SpinnerNumberModel(0.1, 0.1, 2, 0.1)); + spnStepSize.setToolTipText(resources.getString("lblStepSize.toolTipText")); + spnStepSize.setName("spnStepSize"); + spnStepSize.setEnabled(isUseMorale); + lblForceReliabilityMethod = new JLabel(resources.getString("lblForceReliabilityMethod.text")); lblForceReliabilityMethod.setToolTipText(resources.getString("lblForceReliabilityMethod.toolTipText")); lblForceReliabilityMethod.setName("lblForceReliabilityMethod"); @@ -4661,7 +4678,18 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseDesertions.addActionListener(evt -> { final boolean isEnabled = chkUseDesertions.isSelected(); - chkUseMoraleModifierDesertion.setEnabled(isEnabled); + chkUseMoraleTriggerDesertion.setEnabled(isEnabled); + chkUseTheft.setEnabled(isEnabled); + }); + + chkUseMutinies = new JCheckBox(resources.getString("chkUseMutinies.text")); + chkUseMutinies.setToolTipText(resources.getString("chkUseMutinies.toolTipText")); + chkUseMutinies.setName("chkUseMutinies"); + chkUseMutinies.setEnabled(isUseMorale); + chkUseMutinies.addActionListener(evt -> { + final boolean isEnabled = chkUseMutinies.isSelected(); + + chkUseMoraleTriggerMutiny.setEnabled(isEnabled); }); chkUseEmergencyBonuses = new JCheckBox(resources.getString("chkUseEmergencyBonuses.text")); @@ -4674,15 +4702,10 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseSabotage.setName("chkUseSabotage"); chkUseSabotage.setEnabled(isUseMorale); - chkUseMutinies = new JCheckBox(resources.getString("chkUseMutinies.text")); - chkUseMutinies.setToolTipText(resources.getString("chkUseMutinies.toolTipText")); - chkUseMutinies.setName("chkUseMutinies"); - chkUseMutinies.setEnabled(isUseMorale); - chkUseMutinies.addActionListener(evt -> { - final boolean isEnabled = chkUseMutinies.isSelected(); - - chkUseMoraleModifierMutiny.setEnabled(isEnabled); - }); + chkUseTheft = new JCheckBox(resources.getString("chkUseTheft.text")); + chkUseTheft.setToolTipText(resources.getString("chkUseTheft.toolTipText")); + chkUseTheft.setName("chkUseTheft"); + chkUseTheft.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); moraleSubPanel.setName("moraleSubPanel"); moraleSubPanel.setBorder(BorderFactory.createTitledBorder("")); @@ -4695,24 +4718,32 @@ public Component getListCellRendererComponent(final JList list, final Object layout.setVerticalGroup( layout.createSequentialGroup() + .addComponent(chkUseDesertions) + .addComponent(chkUseMutinies) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblStepSize) + .addComponent(spnStepSize, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) - .addComponent(chkUseDesertions) .addComponent(chkUseEmergencyBonuses) .addComponent(chkUseSabotage) - .addComponent(chkUseMutinies) + .addComponent(chkUseTheft) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) + .addComponent(chkUseDesertions) + .addComponent(chkUseMutinies) + .addGroup(layout.createSequentialGroup() + .addComponent(lblStepSize) + .addComponent(spnStepSize)) .addGroup(layout.createSequentialGroup() .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod)) - .addComponent(chkUseDesertions) .addComponent(chkUseEmergencyBonuses) .addComponent(chkUseSabotage) - .addComponent(chkUseMutinies) + .addComponent(chkUseTheft) ); } @@ -4724,15 +4755,20 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { lblCustomMoraleModifier.setName("lblCustomMoraleModifier"); lblCustomMoraleModifier.setEnabled(isUseMorale); - spnCustomMoraleModifier = new JSpinner(new SpinnerNumberModel(-2, -5, 5, 1)); + spnCustomMoraleModifier = new JSpinner(new SpinnerNumberModel(2, -5, 5, 1)); spnCustomMoraleModifier.setToolTipText(resources.getString("lblCustomMoraleModifier.toolTipText")); spnCustomMoraleModifier.setName("spnCustomMoraleModifier"); spnCustomMoraleModifier.setEnabled(isUseMorale); - chkUseMoraleModifierFatigue = new JCheckBox(resources.getString("chkUseMoraleModifierFatigue.text")); - chkUseMoraleModifierFatigue.setToolTipText(resources.getString("chkUseMoraleModifierFatigue.toolTipText")); - chkUseMoraleModifierFatigue.setName("chkUseMoraleModifierFatigue"); - chkUseMoraleModifierFatigue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseFatigue())); + chkUseMoraleModifierExperienceLevel = new JCheckBox(resources.getString("chkUseMoraleModifierExperienceLevel.text")); + chkUseMoraleModifierExperienceLevel.setToolTipText(resources.getString("chkUseMoraleModifierExperienceLevel.toolTipText")); + chkUseMoraleModifierExperienceLevel.setName("chkUseMoraleModifierExperienceLevel"); + chkUseMoraleModifierExperienceLevel.setEnabled(isUseMorale); + + chkUseMoraleModifierFaction = new JCheckBox(resources.getString("chkUseMoraleModifierFaction.text")); + chkUseMoraleModifierFaction.setToolTipText(resources.getString("chkUseMoraleModifierFaction.toolTipText")); + chkUseMoraleModifierFaction.setName("chkUseMoraleModifierFaction"); + chkUseMoraleModifierFaction.setEnabled(isUseMorale); chkUseMoraleModifierProfession = new JCheckBox(resources.getString("chkUseMoraleModifierProfession.text")); chkUseMoraleModifierProfession.setToolTipText(resources.getString("chkUseMoraleModifierProfession.toolTipText")); @@ -4744,10 +4780,15 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { chkUseMoraleModifierForceReliability.setName("chkUseMoraleModifierForceReliability"); chkUseMoraleModifierForceReliability.setEnabled(isUseMorale); - chkUseMoraleModifierManagementSkill = new JCheckBox(resources.getString("chkUseMoraleModifierManagementSkill.text")); - chkUseMoraleModifierManagementSkill.setToolTipText(resources.getString("chkUseMoraleModifierManagementSkill.toolTipText")); - chkUseMoraleModifierManagementSkill.setName("chkUseMoraleModifierManagementSkill"); - chkUseMoraleModifierManagementSkill.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); + chkUseMoraleModifierCommanderSkill = new JCheckBox(resources.getString("chkUseMoraleModifierCommanderSkill.text")); + chkUseMoraleModifierCommanderSkill.setToolTipText(resources.getString("chkUseMoraleModifierCommanderSkill.toolTipText")); + chkUseMoraleModifierCommanderSkill.setName("chkUseMoraleModifierCommanderSkill"); + chkUseMoraleModifierCommanderSkill.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); + + chkUseMoraleModifierLoyalty = new JCheckBox(resources.getString("chkUseMoraleModifierLoyalty.text")); + chkUseMoraleModifierLoyalty.setToolTipText(resources.getString("chkUseMoraleModifierLoyalty.toolTipText")); + chkUseMoraleModifierLoyalty.setName("chkUseMoraleModifierLoyalty"); + chkUseMoraleModifierLoyalty.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); chkUseRuleWithIronFist = new JCheckBox(resources.getString("chkUseRuleWithIronFist.text")); chkUseRuleWithIronFist.setToolTipText(resources.getString("chkUseRuleWithIronFist.toolTipText")); @@ -4768,10 +4809,12 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblCustomMoraleModifier) .addComponent(spnCustomMoraleModifier, Alignment.LEADING)) - .addComponent(chkUseMoraleModifierFatigue) + .addComponent(chkUseMoraleModifierExperienceLevel) + .addComponent(chkUseMoraleModifierFaction) .addComponent(chkUseMoraleModifierProfession) .addComponent(chkUseMoraleModifierForceReliability) - .addComponent(chkUseMoraleModifierManagementSkill) + .addComponent(chkUseMoraleModifierCommanderSkill) + .addComponent(chkUseMoraleModifierLoyalty) .addComponent(chkUseRuleWithIronFist) ); @@ -4780,93 +4823,95 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { .addGroup(layout.createSequentialGroup() .addComponent(lblCustomMoraleModifier) .addComponent(spnCustomMoraleModifier)) - .addComponent(chkUseMoraleModifierFatigue) + .addComponent(chkUseMoraleModifierExperienceLevel) + .addComponent(chkUseMoraleModifierFaction) .addComponent(chkUseMoraleModifierProfession) .addComponent(chkUseMoraleModifierForceReliability) - .addComponent(chkUseMoraleModifierManagementSkill) + .addComponent(chkUseMoraleModifierCommanderSkill) + .addComponent(chkUseMoraleModifierLoyalty) .addComponent(chkUseRuleWithIronFist) ); return turnoverAndRetentionMoraleModifiersPanel; } - private JPanel createTurnoverAndRetentionMoraleChangePanel() { + private JPanel createTurnoverAndRetentionMoraleTriggersPanel() { boolean isUseMorale = campaign.getCampaignOptions().isUseMorale(); - chkUseMoraleModifierFieldControl = new JCheckBox(resources.getString("chkUseMoraleModifierFieldControl.text")); - chkUseMoraleModifierFieldControl.setToolTipText(resources.getString("chkUseMoraleModifierFieldControl.toolTipText")); - chkUseMoraleModifierFieldControl.setName("chkUseMoraleModifierFieldControl"); - chkUseMoraleModifierFieldControl.setEnabled(isUseMorale); - - chkUseMoraleModifierMissionStatus = new JCheckBox(resources.getString("chkUseMoraleModifierMissionStatus.text")); - chkUseMoraleModifierMissionStatus.setToolTipText(resources.getString("chkUseMoraleModifierMissionStatus.toolTipText")); - chkUseMoraleModifierMissionStatus.setName("chkUseMoraleModifierMissionStatus"); - chkUseMoraleModifierMissionStatus.setEnabled(isUseMorale); - - chkUseMoraleModifierLeaderLoss = new JCheckBox(resources.getString("chkUseMoraleModifierLeaderLoss.text")); - chkUseMoraleModifierLeaderLoss.setToolTipText(resources.getString("chkUseMoraleModifierLeaderLoss.toolTipText")); - chkUseMoraleModifierLeaderLoss.setName("chkUseMoraleModifierLeaderLoss"); - chkUseMoraleModifierLeaderLoss.setEnabled(isUseMorale); - - chkUseMoraleModifierCombatLoss = new JCheckBox(resources.getString("chkUseMoraleModifierCombatLoss.text")); - chkUseMoraleModifierCombatLoss.setToolTipText(resources.getString("chkUseMoraleModifierCombatLoss.toolTipText")); - chkUseMoraleModifierCombatLoss.setName("chkUseMoraleModifierCombatLoss"); - chkUseMoraleModifierCombatLoss.setEnabled(isUseMorale); - - chkUseMoraleModifierDesertion = new JCheckBox(resources.getString("chkUseMoraleModifierDesertion.text")); - chkUseMoraleModifierDesertion.setToolTipText(resources.getString("chkUseMoraleModifierDesertion.toolTipText")); - chkUseMoraleModifierDesertion.setName("chkUseMoraleModifierDesertion"); - chkUseMoraleModifierDesertion.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); - - chkUseMoraleModifierMutiny = new JCheckBox(resources.getString("chkUseMoraleModifierMutiny.text")); - chkUseMoraleModifierMutiny.setToolTipText(resources.getString("chkUseMoraleModifierMutiny.toolTipText")); - chkUseMoraleModifierMutiny.setName("chkUseMoraleModifierMutiny"); - chkUseMoraleModifierMutiny.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseMutinies())); - - chkUseMoraleModifierRest = new JCheckBox(resources.getString("chkUseMoraleModifierRest.text")); - chkUseMoraleModifierRest.setToolTipText(resources.getString("chkUseMoraleModifierRest.toolTipText")); - chkUseMoraleModifierRest.setName("chkUseMoraleModifierRest"); - chkUseMoraleModifierRest.setEnabled(isUseMorale); - - chkUseMoraleModifierMissedPayDay = new JCheckBox(resources.getString("chkUseMoraleModifierMissedPayDay.text")); - chkUseMoraleModifierMissedPayDay.setToolTipText(resources.getString("chkUseMoraleModifierMissedPayDay.toolTipText")); - chkUseMoraleModifierMissedPayDay.setName("chkUseMoraleModifierMissedPayDay"); - chkUseMoraleModifierMissedPayDay.setEnabled(isUseMorale); - - turnoverAndRetentionMoraleChangePanel.setName("turnoverAndRetentionMoraleChangePanel"); - turnoverAndRetentionMoraleChangePanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionMoraleChangePanel.title"))); - turnoverAndRetentionMoraleChangePanel.setEnabled(isUseMorale); - - final GroupLayout layout = new GroupLayout(turnoverAndRetentionMoraleChangePanel); - turnoverAndRetentionMoraleChangePanel.setLayout(layout); + chkUseMoraleTriggerFieldControl = new JCheckBox(resources.getString("chkUseMoraleTriggerFieldControl.text")); + chkUseMoraleTriggerFieldControl.setToolTipText(resources.getString("chkUseMoraleTriggerFieldControl.toolTipText")); + chkUseMoraleTriggerFieldControl.setName("chkUseMoraleTriggerFieldControl"); + chkUseMoraleTriggerFieldControl.setEnabled(isUseMorale); + + chkUseMoraleTriggerMissionStatus = new JCheckBox(resources.getString("chkUseMoraleTriggerMissionStatus.text")); + chkUseMoraleTriggerMissionStatus.setToolTipText(resources.getString("chkUseMoraleTriggerMissionStatus.toolTipText")); + chkUseMoraleTriggerMissionStatus.setName("chkUseMoraleTriggerMissionStatus"); + chkUseMoraleTriggerMissionStatus.setEnabled(isUseMorale); + + chkUseMoraleTriggerLeaderLoss = new JCheckBox(resources.getString("chkUseMoraleTriggerLeaderLoss.text")); + chkUseMoraleTriggerLeaderLoss.setToolTipText(resources.getString("chkUseMoraleTriggerLeaderLoss.toolTipText")); + chkUseMoraleTriggerLeaderLoss.setName("chkUseMoraleTriggerLeaderLoss"); + chkUseMoraleTriggerLeaderLoss.setEnabled(isUseMorale); + + chkUseMoraleTriggerCombatLoss = new JCheckBox(resources.getString("chkUseMoraleTriggerCombatLoss.text")); + chkUseMoraleTriggerCombatLoss.setToolTipText(resources.getString("chkUseMoraleTriggerCombatLoss.toolTipText")); + chkUseMoraleTriggerCombatLoss.setName("chkUseMoraleTriggerCombatLoss"); + chkUseMoraleTriggerCombatLoss.setEnabled(isUseMorale); + + chkUseMoraleTriggerDesertion = new JCheckBox(resources.getString("chkUseMoraleTriggerDesertion.text")); + chkUseMoraleTriggerDesertion.setToolTipText(resources.getString("chkUseMoraleTriggerDesertion.toolTipText")); + chkUseMoraleTriggerDesertion.setName("chkUseMoraleTriggerDesertion"); + chkUseMoraleTriggerDesertion.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + + chkUseMoraleTriggerMutiny = new JCheckBox(resources.getString("chkUseMoraleTriggerMutiny.text")); + chkUseMoraleTriggerMutiny.setToolTipText(resources.getString("chkUseMoraleTriggerMutiny.toolTipText")); + chkUseMoraleTriggerMutiny.setName("chkUseMoraleTriggerMutiny"); + chkUseMoraleTriggerMutiny.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseMutinies())); + + chkUseMoraleTriggerMissedPayDay = new JCheckBox(resources.getString("chkUseMoraleTriggerMissedPayDay.text")); + chkUseMoraleTriggerMissedPayDay.setToolTipText(resources.getString("chkUseMoraleTriggerMissedPayDay.toolTipText")); + chkUseMoraleTriggerMissedPayDay.setName("chkUseMoraleTriggerMissedPayDay"); + chkUseMoraleTriggerMissedPayDay.setEnabled(isUseMorale); + + chkUseMoraleTriggerFatigue = new JCheckBox(resources.getString("chkUseMoraleTriggerFatigue.text")); + chkUseMoraleTriggerFatigue.setToolTipText(resources.getString("chkUseMoraleTriggerFatigue.toolTipText")); + chkUseMoraleTriggerFatigue.setName("chkUseMoraleTriggerFatigue"); + chkUseMoraleTriggerFatigue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseFatigue())); + + turnoverAndRetentionMoraleTriggersPanel.setName("turnoverAndRetentionMoraleTriggersPanel"); + turnoverAndRetentionMoraleTriggersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionMoraleTriggersPanel.title"))); + turnoverAndRetentionMoraleTriggersPanel.setEnabled(isUseMorale); + + final GroupLayout layout = new GroupLayout(turnoverAndRetentionMoraleTriggersPanel); + turnoverAndRetentionMoraleTriggersPanel.setLayout(layout); layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true); layout.setVerticalGroup( layout.createSequentialGroup() - .addComponent(chkUseMoraleModifierFieldControl) - .addComponent(chkUseMoraleModifierMissionStatus) - .addComponent(chkUseMoraleModifierLeaderLoss) - .addComponent(chkUseMoraleModifierCombatLoss) - .addComponent(chkUseMoraleModifierDesertion) - .addComponent(chkUseMoraleModifierMutiny) - .addComponent(chkUseMoraleModifierRest) - .addComponent(chkUseMoraleModifierMissedPayDay) + .addComponent(chkUseMoraleTriggerFatigue) + .addComponent(chkUseMoraleTriggerFieldControl) + .addComponent(chkUseMoraleTriggerMissionStatus) + .addComponent(chkUseMoraleTriggerLeaderLoss) + .addComponent(chkUseMoraleTriggerCombatLoss) + .addComponent(chkUseMoraleTriggerDesertion) + .addComponent(chkUseMoraleTriggerMutiny) + .addComponent(chkUseMoraleTriggerMissedPayDay) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) - .addComponent(chkUseMoraleModifierFieldControl) - .addComponent(chkUseMoraleModifierMissionStatus) - .addComponent(chkUseMoraleModifierLeaderLoss) - .addComponent(chkUseMoraleModifierCombatLoss) - .addComponent(chkUseMoraleModifierDesertion) - .addComponent(chkUseMoraleModifierMutiny) - .addComponent(chkUseMoraleModifierRest) - .addComponent(chkUseMoraleModifierMissedPayDay) + .addComponent(chkUseMoraleTriggerFatigue) + .addComponent(chkUseMoraleTriggerFieldControl) + .addComponent(chkUseMoraleTriggerMissionStatus) + .addComponent(chkUseMoraleTriggerLeaderLoss) + .addComponent(chkUseMoraleTriggerCombatLoss) + .addComponent(chkUseMoraleTriggerDesertion) + .addComponent(chkUseMoraleTriggerMutiny) + .addComponent(chkUseMoraleTriggerMissedPayDay) ); - return turnoverAndRetentionMoraleChangePanel; + return turnoverAndRetentionMoraleTriggersPanel; } private void createRandomDependentPanel() { @@ -7936,26 +7981,31 @@ public void setOptions(@Nullable CampaignOptions options, // Morale chkUseMorale.setSelected(options.isUseMorale()); + spnStepSize.setValue(options.getStepSize()); comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); chkUseDesertions.setSelected(options.isUseDesertions()); chkUseEmergencyBonuses.setSelected(options.isUseEmergencyBonuses()); chkUseSabotage.setSelected(options.isUseSabotage()); + chkUseTheft.setSelected(options.isUseTheft()); chkUseMutinies.setSelected(options.isUseMutinies()); // Morale Modifiers spnCustomMoraleModifier.setValue(options.getCustomMoraleModifier()); - chkUseMoraleModifierFieldControl.setSelected(options.isUseMoraleModifierFieldControl()); - chkUseMoraleModifierMissionStatus.setSelected(options.isUseMoraleModifierMissionStatus()); - chkUseMoraleModifierLeaderLoss.setSelected(options.isUseMoraleModifierLeaderLoss()); - chkUseMoraleModifierCombatLoss.setSelected(options.isUseMoraleModifierCombatLoss()); - chkUseMoraleModifierDesertion.setSelected(options.isUseMoraleModifierDesertion()); - chkUseMoraleModifierMutiny.setSelected(options.isUseMoraleModifierMutiny()); - chkUseMoraleModifierRest.setSelected(options.isUseMoraleModifierRest()); - chkUseMoraleModifierFatigue.setSelected(options.isUseMoraleModifierFatigue()); + chkUseMoraleTriggerFieldControl.setSelected(options.isUseMoraleTriggerFieldControl()); + chkUseMoraleTriggerMissionStatus.setSelected(options.isUseMoraleTriggerMissionStatus()); + chkUseMoraleTriggerLeaderLoss.setSelected(options.isUseMoraleTriggerModifierLeaderLoss()); + chkUseMoraleTriggerCombatLoss.setSelected(options.isUseMoraleTriggerCombatLoss()); + chkUseMoraleTriggerDesertion.setSelected(options.isUseMoraleTriggerDesertion()); + chkUseMoraleTriggerMutiny.setSelected(options.isUseMoraleTriggerMutiny()); + chkUseMoraleTriggerFatigue.setSelected(options.isUseMoraleTriggerFatigue()); + chkUseMoraleTriggerMissedPayDay.setSelected(options.isUseMoraleModifierMissedPayDay()); + + chkUseMoraleModifierExperienceLevel.setSelected(options.isUseMoraleModifierExperienceLevel()); + chkUseMoraleModifierFaction.setSelected(options.isUseMoraleModifierFaction()); chkUseMoraleModifierProfession.setSelected(options.isUseMoraleModifierProfession()); chkUseMoraleModifierForceReliability.setSelected(options.isUseMoraleModifierForceReliability()); - chkUseMoraleModifierManagementSkill.setSelected(options.isUseMoraleModifierManagementSkill()); - chkUseMoraleModifierMissedPayDay.setSelected(options.isUseMoraleModifierMissedPayDay()); + chkUseMoraleModifierCommanderSkill.setSelected(options.isUseMoraleModifierCommanderLeadership()); + chkUseMoraleModifierLoyalty.setSelected(options.isUseMoraleModifierLoyalty()); chkUseRuleWithIronFist.setSelected(options.isUseRuleWithIronFist()); //endregion Turnover and Retention Tab @@ -8609,26 +8659,31 @@ public void updateOptions() { // Morale options.setUseMorale(chkUseMorale.isSelected()); + options.setStepSize((Double) spnStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); options.setUseDesertions(chkUseDesertions.isSelected()); options.setUseEmergencyBonuses(chkUseEmergencyBonuses.isSelected()); options.setUseSabotage(chkUseSabotage.isSelected()); + options.setUseTheft(chkUseTheft.isSelected()); options.setUseMutinies(chkUseMutinies.isSelected()); // Morale Modifiers + options.setUseMoraleTriggerFieldControl(chkUseMoraleTriggerFieldControl.isSelected()); + options.setUseMoraleTriggerMissionStatus(chkUseMoraleTriggerMissionStatus.isSelected()); + options.setUseMoraleTriggerModifierLeaderLoss(chkUseMoraleTriggerLeaderLoss.isSelected()); + options.setUseMoraleTriggerCombatLoss(chkUseMoraleTriggerCombatLoss.isSelected()); + options.setUseMoraleTriggerDesertion(chkUseMoraleTriggerDesertion.isSelected()); + options.setUseMoraleTriggerMutiny(chkUseMoraleTriggerMutiny.isSelected()); + options.setUseMoraleTriggerFatigue(chkUseMoraleTriggerFatigue.isSelected()); + options.setUseMoraleModifierMissedPayDay(chkUseMoraleTriggerMissedPayDay.isSelected()); + options.setCustomMoraleModifier((Integer) spnCustomMoraleModifier.getValue()); - options.setUseMoraleModifierFieldControl(chkUseMoraleModifierFieldControl.isSelected()); - options.setUseMoraleModifierMissionStatus(chkUseMoraleModifierMissionStatus.isSelected()); - options.setUseMoraleModifierLeaderLoss(chkUseMoraleModifierLeaderLoss.isSelected()); - options.setUseMoraleModifierCombatLoss(chkUseMoraleModifierCombatLoss.isSelected()); - options.setUseMoraleModifierDesertion(chkUseMoraleModifierDesertion.isSelected()); - options.setUseMoraleModifierMutiny(chkUseMoraleModifierMutiny.isSelected()); - options.setUseMoraleModifierRest(chkUseMoraleModifierRest.isSelected()); - options.setUseMoraleModifierFatigue(chkUseMoraleModifierFatigue.isSelected()); + options.setUseMoraleModifierExperienceLevel(chkUseMoraleModifierExperienceLevel.isSelected()); + options.setUseMoraleModifierFaction(chkUseMoraleModifierFaction.isSelected()); options.setUseMoraleModifierProfession(chkUseMoraleModifierProfession.isSelected()); options.setUseMoraleModifierForceReliability(chkUseMoraleModifierForceReliability.isSelected()); - options.setUseMoraleModifierManagementSkill(chkUseMoraleModifierManagementSkill.isSelected()); - options.setUseMoraleModifierMissedPayDay(chkUseMoraleModifierMissedPayDay.isSelected()); + options.setUseMoraleModifierCommanderLeadership(chkUseMoraleModifierCommanderSkill.isSelected()); + options.setUseMoraleModifierLoyalty(chkUseMoraleModifierLoyalty.isSelected()); options.setUseRuleWithIronFist(chkUseRuleWithIronFist.isSelected()); //endregion Turnover and Retention diff --git a/MekHQ/unittests/mekhq/campaign/finances/enums/TransactionTypeTest.java b/MekHQ/unittests/mekhq/campaign/finances/enums/TransactionTypeTest.java index 2e985d3c4e..ef0f96048e 100644 --- a/MekHQ/unittests/mekhq/campaign/finances/enums/TransactionTypeTest.java +++ b/MekHQ/unittests/mekhq/campaign/finances/enums/TransactionTypeTest.java @@ -372,7 +372,8 @@ public void testParseFromString() { assertEquals(TransactionType.REPAIRS, TransactionType.parseFromString("15")); assertEquals(TransactionType.RANSOM, TransactionType.parseFromString("16")); assertEquals(TransactionType.EDUCATION, TransactionType.parseFromString("17")); - assertEquals(TransactionType.MISCELLANEOUS, TransactionType.parseFromString("18")); + assertEquals(TransactionType.THEFT, TransactionType.parseFromString("18")); + assertEquals(TransactionType.MISCELLANEOUS, TransactionType.parseFromString("19")); // Failure Testing assertEquals(TransactionType.MISCELLANEOUS, TransactionType.parseFromString("failureFailsFake")); From e90505442255e0a7f9409072d3acd88e2981590c Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 12:15:52 -0500 Subject: [PATCH 046/101] Fixed error resulting from merge --- MekHQ/src/mekhq/campaign/CampaignOptions.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 00a4002602..5f261a2c54 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -242,7 +242,7 @@ public static String getTransitUnitName(final int unit) { private double salarySpecialistInfantryMultiplier; private Map salaryXPMultipliers; private Money[] roleBaseSalaries; - + // Awards private AwardBonus awardBonusStyle; private boolean enableAutoAwards; @@ -790,7 +790,7 @@ public CampaignOptions() { setRoleBaseSalary(PersonnelRole.ADMINISTRATOR_HR, 500); setRoleBaseSalary(PersonnelRole.DEPENDENT, 0); setRoleBaseSalary(PersonnelRole.NONE, 0); - + // Awards setAwardBonusStyle(AwardBonus.BOTH); setEnableAutoAwards(true); @@ -4643,7 +4643,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLCloseTag(pw, --indent, "salaryXPMultipliers"); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "salaryTypeBase", Utilities.printMoneyArray(getRoleBaseSalaries())); //endregion Salary - + //region Awards MHQXMLUtility.writeSimpleXMLTag(pw, indent, "awardBonusStyle", getAwardBonusStyle().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "enableAutoAwards", isEnableAutoAwards()); @@ -5346,7 +5346,7 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setRoleBaseSalaries(Utilities.readMoneyArray(wn2, retVal.getRoleBaseSalaries().length)); } //endregion Salary - + //region Awards } else if (wn2.getNodeName().equalsIgnoreCase("awardBonusStyle")) { retVal.setAwardBonusStyle(AwardBonus.valueOf(wn2.getTextContent().trim())); @@ -5652,10 +5652,9 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve } } - } //endregion Death //endregion Life Paths Tab - + //region Turnover and Retention } else if (wn2.getNodeName().equalsIgnoreCase("useRetirementDateTracking")) { retVal.setUseRetirementDateTracking(Boolean.parseBoolean(wn2.getTextContent().trim())); From 950e51b11320e6083246142c0ca1c1e6ff44e3cb Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 12:47:33 -0500 Subject: [PATCH 047/101] Update loyalty generation for personnel This commit modifies the loyalty generation code across various files. The `generateLoyalty` method has been updated to take a roll value as an argument, which influences the loyalty that is generated. Changes have also been made to the loyalty modifier calculation in the morale checks. This makes loyalty generation more consistent and flexible. --- MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java | 2 ++ MekHQ/src/mekhq/campaign/personnel/Person.java | 11 ++++++----- .../generator/DefaultPersonnelGenerator.java | 3 ++- .../personnel/turnoverAndRetention/Morale.java | 9 +++++---- .../mekhq/gui/adapter/PersonnelTableMouseAdapter.java | 5 +++-- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java index 3b0631781f..9d9d0f3a2e 100644 --- a/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java +++ b/MekHQ/src/mekhq/campaign/ResolveScenarioTracker.java @@ -1484,6 +1484,8 @@ public void resolveScenario(ScenarioStatus resolution, String report) { getCampaign().addReport(String.format("You have convinced %s to defect.", person.getHyperlinkedName())); } + + person.generateLoyalty(Compute.d6(2)); } else { continue; } diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index d6ac6fdca0..d1aa06e5c5 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -3653,11 +3653,12 @@ public void fixReferences(final Campaign campaign) { } /** - * Generates the loyalty value for a person. + * Generates the loyalty value for a person based on the given roll value. + * Roll should be set to 3 for normal loyalty, 2 for low loyalty, or 4 for high loyalty. + * + * @param roll the roll value used to determine loyalty */ - public void generateLoyalty() { - int roll = Compute.d6(3); - + public void generateLoyalty(int roll) { if (roll == 3) { setLoyalty(-3); } else if (roll == 4) { @@ -3666,7 +3667,7 @@ public void generateLoyalty() { setLoyalty(-1); } else if (roll == 18) { setLoyalty(3); - } else if (roll == 17) { + } else if (roll >= 17) { setLoyalty(2); } else if (roll > 14) { setLoyalty(1); diff --git a/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java b/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java index 056542dd93..455cf02b81 100644 --- a/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java +++ b/MekHQ/src/mekhq/campaign/personnel/generator/DefaultPersonnelGenerator.java @@ -18,6 +18,7 @@ */ package mekhq.campaign.personnel.generator; +import megamek.common.Compute; import megamek.common.enums.Gender; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; @@ -80,7 +81,7 @@ public Person generate(Campaign campaign, PersonnelRole primaryRole, PersonnelRo generateBirthday(campaign, person, expLvl, person.isClanPersonnel() && !person.getPhenotype().isNone()); - person.generateLoyalty(); + person.generateLoyalty(Compute.d6(3)); AbstractSkillGenerator skillGenerator = new DefaultSkillGenerator(getSkillPreferences()); skillGenerator.generateSkills(campaign, person, expLvl); diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 088d8071ec..06cb7d1f2d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -141,6 +141,11 @@ private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, } } + // Loyalty Modifier + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseMoraleModifierLoyalty())) { + modifier += getLoyaltyModifier(isDesertion, person.getLoyalty()); + } + // Iron Fist Modifier if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { modifier++; @@ -400,10 +405,6 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, loyalty); int roll = Compute.d6(2) + modifier; - if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseMoraleModifierLoyalty())) { - roll += getLoyaltyModifier(isDesertion, person.getLoyalty()); - } - if (roll <= targetNumber) { if (isDesertion) { // TODO emergency bonuses give reroll diff --git a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java index 59dc2e8310..85b595d0da 100644 --- a/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java @@ -21,6 +21,7 @@ import megamek.client.generator.RandomCallsignGenerator; import megamek.client.generator.RandomNameGenerator; import megamek.client.ui.dialogs.PortraitChooserDialog; +import megamek.common.Compute; import megamek.common.Crew; import megamek.common.Mounted; import megamek.common.options.IOption; @@ -951,7 +952,7 @@ public void actionPerformed(ActionEvent action) { } case CMD_LOYALTY: { for (Person person : people) { - person.generateLoyalty(); + person.generateLoyalty(Compute.d6(3)); MekHQ.triggerEvent(new PersonChangedEvent(person)); } break; @@ -2691,7 +2692,7 @@ private void buildEducationSubMenus(Campaign campaign, Academy academy, Person p } } } - + /** * Returns a JMenuItem for a given Award. * From d9c759988716b7505dc12d18efe76f3bcdb75119 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 14:55:21 -0500 Subject: [PATCH 048/101] Refactored theft functionality and added petty theft Refactoring was done in the campaign options to divide theft into two categories: theft of units and theft of money. An integer value for average heist percentage was also added, along with the corresponding user interface elements. The handling of petty theft was added with a comprehensive list of possible stolen items. --- .../CampaignOptionsDialog.properties | 8 +- .../mekhq/resources/Morale.properties | 106 ++++++++++ MekHQ/src/mekhq/campaign/CampaignOptions.java | 44 ++++- .../turnoverAndRetention/Morale.java | 185 +++++++++++++++++- .../mekhq/gui/panes/CampaignOptionsPane.java | 70 +++++-- 5 files changed, 376 insertions(+), 37 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 3ebd31ded6..260d785839 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -319,8 +319,12 @@ chkUseEmergencyBonuses.text=Issue Emergency Bonuses chkUseEmergencyBonuses.toolTipText=Time for a pizza party! Attempt to quell desertion with an emergency bonus payout to the deserter (equal to their monthly salary).
Provides a re-roll on the desertion roll. This does not affect mutinies. chkUseSabotage.text=Enable Sabotage chkUseSabotage.toolTipText=Low morale influences maintenance checks. -chkUseTheft.text=Enable Theft -chkUseTheft.toolTipText=Deserting personnel may steal units or funds on their way out. +chkUseTheftUnit.text=Enable Theft of Units +chkUseTheftUnit.toolTipText=Deserting personnel may steal units on their way out. +chkUseTheftMoney.text=Enable Theft of Money +chkUseTheftMoney.toolTipText=Deserting personnel may steal money on their way out. +lblTheftValue.text=Average Heist Percentage +lblTheftValue.toolTipText=The average percentage of unit funds stolen during a heist. turnoverAndRetentionMoraleModifiersPanel.title=Morale Modifiers lblCustomMoraleModifier.text=Custom Modifier diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index dbd3c2f325..af843733be 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -12,4 +12,110 @@ moraleReportMutiny.text=Your personnel are actively planning a coup. moraleReportRecovered.text=Your personnel are placated, for now. desertionStolen.text=%s has been stolen. +desertionHeistReport=The company accountants report an unusual transaction of %s c-bills. desertionHeist.text=Screw you, Commander + +officeSupplies.text=A number of random office supplies +mascot.text=A company mascot +phones.text=The office phones +tablets.text=A number of tablets +hardDrives.text=Some important hard drives +flashDrive.text=An incriminating flash drive +companyCreditCard.text=The company credit card +officePet.text=An office pet +confidentialReports.text=A number of confidential reports +clientLists.text=Historic client lists +unitSchematics.text=Unit schematics +businessPlans.text=Business plans +marketingMaterials.text=Marketing materials +trainingPresentations.text=Training presentations +softwareLicenses.text=Some expensive software licenses +employeeBelongings.text=Some employee belongings +financialRecords.text=Financial records +employeeRecords.text=Employee records +proprietarySoftware.text=Propriety accounting software +networkAccessCredentials.text=Network access credentials +companyUniforms.text=Some company uniforms +desks.text=Some desks +monitors.text=Office computer monitors +printers.text=Office printers +projectors.text=A briefing projector +carKeys.text=The commander's car keys +dartboard.text=The dartboard +securityBadges.text=Some security badges +officeKeys.text=The office keys +pettyCashBox.text=The petty cash box +cheques.text=Some pre-signed cheques +diary.text=The commander's diary +giftCards.text=Some employee incentive gift cards +coupons.text=Some discount coupons +personalDataOfCoworkers.text=All personnel logs +battlePlans.text=Battle plans +legalDocuments.text=Legal documents +signedContracts.text=Signed contracts +clientFeedbackForms.text=Client feedback forms +trainingManuals.text=Training manuals +marketResearch.text=Market research +businessContacts.text=A list of business contacts +meetingNotes.text=A folder of meeting notes +contractLeads.text=A list of contract leads +urbanMechPlushie.text=Someone's UrbanMech Plushie +brandedMugs.text=A box of branded mugs +companyPhoneDirectories.text=The company phone directories +logbooks.text=The operational logbooks +inventoryLists.text=The inventory lists +confidentialHpgMessages.text=Some confidential HPG messages +strategyDocuments.text=Strategy documents +passwordLists.text=Password lists +internalMemos.text=Embarrassing internal memos +surveillanceCameraRecordings.text=Surveillance camera recordings +brandedPens.text=A box of branded pens +engineeringBlueprints.text=Engineering blueprints +codeRepositories.text=Administrative code repositories +internalNewsletters.text=Copies of the internal newsletter +hrPolicies.text=A copy of HR's internal policies +companyHandbooks.text=A number of company handbooks +procedureManuals.text=A number of procedure manuals +securityPolicies.text=A copy of the company security policies +simulationData.text=A copy of simulation data +businessCards.text=A box of business cards +ndaAgreements.text=A file containing NDA agreements +nonCompeteAgreements.text=A file containing non-compete agreements +softwareCode.text=Proprietary software code +technicalSpecifications.text=Technical specifications +securitySchedules.text=Security schedules +underWear.text=The commander's underwear +marketAnalysis.text=Market analysis data +salesContracts.text=Copies of sales contracts +expenseReports.text=Copies of the company's expense reports +reimbursementReceipts.text=The box of outstanding reimbursement receipts +invoices.text=A pile of invoices +employeeBenefitsInformation.text=A copy of the employee benefits information +insuranceDocuments.text=A hard drive containing insurance documents +lightBulbs.text=The spare light bulbs +strategicAlliancesInformation.text=Strategic alliance information +computers.text=Some office computers +boots.text=The commander's boots +employeeDiscountStructures.text=A copy of the company's employee discount structures +meetingMinutes.text=A copy of company meeting minutes +itInfrastructureDetails.text=Details about the company's IT infrastructure +serverAccessCodes.text=Server access codes +backupDrives.text=Some backup drives +missionData.text=Mission data +executiveMeetingNotes.text=A copy of executive meeting notes +toe.text=A copy of the company's TOE +clientComplaints.text=A file containing client complaints +inventoryControlSystems.text=A copy of the company's inventory control system +chairs.text=A number of chairs +shippingLogs.text=The company's shipping logs +printerPaper.text=Multiple boxes of printer paper +internalAuditReports.text=Copies of incriminating internal audit reports +corruption.text=Incriminating evidence of internal corruption +officePlants.text=The office plants +battlefieldPerformanceReports.text=Battlefield performance reports +companyStandard.text=The company standard +analyticsReports.text=Analytics reports +fridge.text=The contents of the communal fridge +coffeeMachine.text=The coffee machine +mug.text=Commander's favorite mug +toiletSeats.text=All the toilet seats diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 5f261a2c54..38dc662c89 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -318,7 +318,9 @@ public static String getTransitUnitName(final int unit) { private ForceReliabilityMethod forceReliabilityMethod; private boolean useEmergencyBonuses; private boolean useSabotage; - private boolean useTheft; + private boolean useTheftUnit; + private boolean useTheftMoney; + private Integer theftValue; private boolean useMoraleTriggerFieldControl; private boolean useMoraleTriggerMissionStatus; @@ -1007,7 +1009,9 @@ public CampaignOptions() { setUseEmergencyBonuses(true); setUseSabotage(true); setUseMutinies(true); - setUseTheft(true); + setUseTheftUnit(true); + setUseTheftMoney(true); + setUseTheftValue(10); setCustomMoraleModifier(2); setUseMoraleTriggerFieldControl(true); @@ -1607,12 +1611,28 @@ public void setUseSabotage(final boolean useSabotage) { this.useSabotage = useSabotage; } - public boolean isUseTheft() { - return useTheft; + public boolean isUseTheftUnit() { + return useTheftUnit; } - public void setUseTheft(final boolean useTheft) { - this.useTheft = useTheft; + public void setUseTheftUnit(final boolean useTheftUnit) { + this.useTheftUnit = useTheftUnit; + } + + public boolean isUseTheftMoney() { + return useTheftMoney; + } + + public void setUseTheftMoney(final boolean useTheftMoney) { + this.useTheftMoney = useTheftMoney; + } + + public Integer getTheftValue() { + return theftValue; + } + + public void setTheftValue(final Integer theftValue) { + this.theftValue = theftValue; } public boolean isUseMutinies() { @@ -4721,7 +4741,9 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useEmergencyBonuses", isUseEmergencyBonuses()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMutinies", isUseMutinies()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheft", isUseTheft()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheftUnit", isUseTheftUnit()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheftMoney", isUseTheftMoney()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "theftValue", getTheftValue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "customMoraleModifier", getCustomMoraleModifier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerFieldControl", isUseMoraleTriggerFieldControl()); @@ -5738,8 +5760,12 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseEmergencyBonuses(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSabotage")) { retVal.setUseSabotage(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useTheft")) { - retVal.setUseTheft(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useTheftUnit")) { + retVal.setUseTheftUnit(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useTheftMoney")) { + retVal.setUseTheftMoney(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("theftValue")) { + retVal.setTheftValue(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMutinies")) { retVal.setUseMutinies(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerFieldControl")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 06cb7d1f2d..0937b5b3dd 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -409,7 +409,7 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (isDesertion) { // TODO emergency bonuses give reroll - if ((processDisertion(campaign, person, roll, targetNumber, morale, unitList, resources)) && (!someoneHasDeserted)) { + if ((processDesertion(campaign, person, roll, targetNumber, morale, unitList, resources)) && (!someoneHasDeserted)) { someoneHasDeserted = true; } } else { @@ -429,12 +429,16 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { } } - private static boolean processDisertion(Campaign campaign, Person person, int roll, double targetNumber, double morale, ArrayList unitList, ResourceBundle resources) { + private static boolean processDesertion(Campaign campaign, Person person, int roll, double targetNumber, double morale, ArrayList unitList, ResourceBundle resources) { if (roll <= (targetNumber - 2)) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); - if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheft())) { - processTheft(campaign, unitList, resources); + if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheftUnit())) { + processUnitTheft(campaign, unitList, resources); + } else if ((roll <= (morale - 3)) && (campaign.getCampaignOptions().isUseTheftMoney())) { + processMoneyTheft(campaign, resources); + } else if (roll <= (morale - 2)) { + processPettyTheft(campaign, resources); } return true; @@ -447,13 +451,13 @@ private static boolean processDisertion(Campaign campaign, Person person, int ro } /** - * Process theft of units and money. + * Process theft of units. * * @param campaign the campaign from which units or funds are stolen * @param unitList the list of available units * @param resources the resource bundle for retrieving localized strings */ - private static void processTheft(Campaign campaign, ArrayList unitList, ResourceBundle resources) { + private static void processUnitTheft(Campaign campaign, ArrayList unitList, ResourceBundle resources) { boolean committingTheft = true; int attempts = 3; @@ -464,9 +468,11 @@ private static void processTheft(Campaign campaign, ArrayList unitList, Re attempts--; if (attempts == 0) { - Money stolenMoney = campaign.getFinances().getBalance().multipliedBy(0.1); - - campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), stolenMoney, resources.getString("desertionHeist.text")); + if (campaign.getCampaignOptions().isUseTheftMoney()) { + processMoneyTheft(campaign, resources); + } else { + processPettyTheft(campaign, resources); + } committingTheft = false; } } else { @@ -486,6 +492,167 @@ private static void processTheft(Campaign campaign, ArrayList unitList, Re } } + /** + * Processes money theft. + * + * @param campaign the campaign for which the theft is being processed + * @param resources the ResourceBundle containing the necessary resources + */ + private static void processMoneyTheft(Campaign campaign, ResourceBundle resources) { + int theftPercentage = campaign.getCampaignOptions().getTheftValue(); + + switch(Compute.d6(2)) { + case 2: + theftPercentage += 3; + break; + case 3: + theftPercentage += 2; + break; + case 4: + case 5: + theftPercentage++; + break; + case 9: + theftPercentage--; + break; + case 10: + case 11: + theftPercentage -= 2; + break; + case 12: + theftPercentage -= 3; + break; + default: + break; + } + + Money theft = campaign.getFunds().multipliedBy(MathUtility.clamp(theftPercentage, 1, 100) / 100).round(); + + if (theft.isPositive()) { + campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, resources.getString("desertionHeist.text")); + + campaign.addReport(String.format(resources.getString("desertionHeistReport.text"), theft.getAmount())); + } else { + processPettyTheft(campaign, resources); + } + } + + /** + * This method is used to process a petty theft incident in a company. + * It randomly selects an item from a list of stolen items and adds the item to the campaign report. + * + * @param campaign The campaign object to add the report to. + * @param resources The ResourceBundle object to retrieve localized strings. + */ + private static void processPettyTheft(Campaign campaign, ResourceBundle resources) { + List items = List.of("officeSupplies.text", + "mascot.text", + "phones.text", + "tablets.text", + "hardDrives.text", + "flashDrive.text", + "companyCreditCard.text", + "officePet.text", + "confidentialReports.text", + "clientLists.text", + "unitSchematics.text", + "businessPlans.text", + "marketingMaterials.text", + "trainingPresentations.text", + "softwareLicenses.text", + "employeeBelongings.text", + "financialRecords.text", + "employeeRecords.text", + "proprietarySoftware.text", + "networkAccessCredentials.text", + "companyUniforms.text", + "desks.text", + "monitors.text", + "printers.text", + "projectors.text", + "carKeys.text", + "dartboard.text", + "securityBadges.text", + "officeKeys.text", + "pettyCashBox.text", + "cheques.text", + "diary.text", + "giftCards.text", + "coupons.text", + "personalDataOfCoworkers.text", + "battlePlans.text", + "legalDocuments.text", + "signedContracts.text", + "clientFeedbackForms.text", + "trainingManuals.text", + "marketResearch.text", + "businessContacts.text", + "meetingNotes.text", + "contractLeads.text", + "urbanMechPlushie.text", + "brandedMugs.text", + "companyPhoneDirectories.text", + "logbooks.text", + "inventoryLists.text", + "confidentialHpgMessages.text", + "strategyDocuments.text", + "passwordLists.text", + "internalMemos.text", + "surveillanceCameraRecordings.text", + "brandedPens.text", + "engineeringBlueprints.text", + "codeRepositories.text", + "internalNewsletters.text", + "hrPolicies.text", + "companyHandbooks.text", + "procedureManuals.text", + "securityPolicies.text", + "simulationData.text", + "businessCards.text", + "ndaAgreements.text", + "nonCompeteAgreements.text", + "softwareCode.text", + "technicalSpecifications.text", + "securitySchedules.text", + "underWear.text", + "marketAnalysis.text", + "salesContracts.text", + "expenseReports.text", + "reimbursementReceipts.text", + "invoices.text", + "employeeBenefitsInformation.text", + "insuranceDocuments.text", + "lightBulbs.text", + "strategicAlliancesInformation.text", + "computers.text", + "boots.text", + "employeeDiscountStructures.text", + "meetingMinutes.text", + "itInfrastructureDetails.text", + "serverAccessCodes.text", + "backupDrives.text", + "missionData.text", + "executiveMeetingNotes.text", + "toe.text", + "clientComplaints.text", + "inventoryControlSystems.text", + "chairs.text", + "shippingLogs.text", + "printerPaper.text", + "internalAuditReports.text", + "corruption.text", + "officePlants.text", + "battlefieldPerformanceReports.text", + "companyStandard.text", + "analyticsReports.text", + "fridge.text", + "coffeeMachine.text", + "mug.text", + "toiletSeats.text"); + + campaign.addReport(String.format(resources.getString("desertionStolen.text"), new Random().nextInt(items.size()))); + } + /** * Processes the number of AWOL (Absent Without Leave) days for a person. * If the person has no AWOL days remaining, it randomly determines whether to add another d6 AWOL days diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index d547010aed..85a3bf59ae 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -340,7 +340,10 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private MMComboBox comboForceReliabilityMethod; private JCheckBox chkUseSabotage; private JCheckBox chkUseMutinies; - private JCheckBox chkUseTheft; + private JCheckBox chkUseTheftUnit; + private JCheckBox chkUseTheftMoney; + private JLabel lblTheftValue; + private JSpinner spnTheftValue; private JPanel turnoverAndRetentionMoraleModifiersPanel = new JPanel(); private JLabel lblCustomMoraleModifier; @@ -4856,7 +4859,10 @@ private JPanel createTurnoverAndRetentionMoralePanel() { chkUseMoraleTriggerMutiny.setEnabled((isEnabled) && (chkUseMutinies.isSelected())); chkUseMoraleTriggerFatigue.setEnabled((isEnabled) && (chkUseFatigue.isSelected())); - chkUseTheft.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); + chkUseTheftUnit.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); + chkUseTheftMoney.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); + lblTheftValue.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); + spnTheftValue.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); }); createMoraleSubPanel(); @@ -4926,7 +4932,10 @@ public Component getListCellRendererComponent(final JList list, final Object final boolean isEnabled = chkUseDesertions.isSelected(); chkUseMoraleTriggerDesertion.setEnabled(isEnabled); - chkUseTheft.setEnabled(isEnabled); + chkUseTheftUnit.setEnabled(isEnabled); + chkUseTheftMoney.setEnabled(isEnabled); + lblTheftValue.setEnabled(isEnabled); + spnTheftValue.setEnabled(isEnabled); }); chkUseMutinies = new JCheckBox(resources.getString("chkUseMutinies.text")); @@ -4949,10 +4958,25 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseSabotage.setName("chkUseSabotage"); chkUseSabotage.setEnabled(isUseMorale); - chkUseTheft = new JCheckBox(resources.getString("chkUseTheft.text")); - chkUseTheft.setToolTipText(resources.getString("chkUseTheft.toolTipText")); - chkUseTheft.setName("chkUseTheft"); - chkUseTheft.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + chkUseTheftUnit = new JCheckBox(resources.getString("chkUseTheftUnit.text")); + chkUseTheftUnit.setToolTipText(resources.getString("chkUseTheftUnit.toolTipText")); + chkUseTheftUnit.setName("chkUseTheftUnit"); + chkUseTheftUnit.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + + chkUseTheftMoney = new JCheckBox(resources.getString("chkUseTheftMoney.text")); + chkUseTheftMoney.setToolTipText(resources.getString("chkUseTheftMoney.toolTipText")); + chkUseTheftMoney.setName("chkUseTheftMoney"); + chkUseTheftMoney.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + + lblTheftValue = new JLabel(resources.getString("lblTheftValue.text")); + lblTheftValue.setToolTipText(resources.getString("lblTheftValue.toolTipText")); + lblTheftValue.setName("lblTheftValue"); + lblTheftValue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + + spnTheftValue = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); + spnTheftValue.setToolTipText(resources.getString("lblTheftValue.toolTipText")); + spnTheftValue.setName("spnStepSize"); + spnTheftValue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); moraleSubPanel.setName("moraleSubPanel"); moraleSubPanel.setBorder(BorderFactory.createTitledBorder("")); @@ -4975,7 +4999,11 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) .addComponent(chkUseEmergencyBonuses) .addComponent(chkUseSabotage) - .addComponent(chkUseTheft) + .addComponent(chkUseTheftUnit) + .addComponent(chkUseTheftMoney) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblTheftValue) + .addComponent(spnTheftValue, Alignment.LEADING)) ); layout.setHorizontalGroup( @@ -4990,7 +5018,11 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(comboForceReliabilityMethod)) .addComponent(chkUseEmergencyBonuses) .addComponent(chkUseSabotage) - .addComponent(chkUseTheft) + .addComponent(chkUseTheftUnit) + .addComponent(chkUseTheftMoney) + .addGroup(layout.createSequentialGroup() + .addComponent(lblTheftValue) + .addComponent(spnTheftValue)) ); } @@ -8173,7 +8205,7 @@ public void setOptions(@Nullable CampaignOptions options, for (int i = 0; i < spnBaseSalary.length; i++) { spnBaseSalary[i].setValue(options.getRoleBaseSalaries()[i].getAmount().doubleValue()); } - + // Awards comboAwardBonusStyle.setSelectedItem(options.getAwardBonusStyle()); chkEnableAutoAwards.setSelected(options.isEnableAutoAwards()); @@ -8247,13 +8279,15 @@ public void setOptions(@Nullable CampaignOptions options, // Morale chkUseMorale.setSelected(options.isUseMorale()); + chkUseDesertions.setSelected(options.isUseDesertions()); + chkUseMutinies.setSelected(options.isUseMutinies()); spnStepSize.setValue(options.getStepSize()); comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); - chkUseDesertions.setSelected(options.isUseDesertions()); chkUseEmergencyBonuses.setSelected(options.isUseEmergencyBonuses()); chkUseSabotage.setSelected(options.isUseSabotage()); - chkUseTheft.setSelected(options.isUseTheft()); - chkUseMutinies.setSelected(options.isUseMutinies()); + chkUseTheftUnit.setSelected(options.isUseTheftUnit()); + chkUseTheftMoney.setSelected(options.isUseTheftUnit()); + spnTheftValue.setValue(options.getTheftValue()); // Morale Modifiers spnCustomMoraleModifier.setValue(options.getCustomMoraleModifier()); @@ -8867,7 +8901,7 @@ public void updateOptions() { for (final PersonnelRole personnelRole : PersonnelRole.values()) { options.setRoleBaseSalary(personnelRole, (double) spnBaseSalary[personnelRole.ordinal()].getValue()); } - + // Awards options.setEnableAutoAwards(chkEnableAutoAwards.isSelected()); options.setAwardBonusStyle(comboAwardBonusStyle.getSelectedItem()); @@ -8941,13 +8975,15 @@ public void updateOptions() { // Morale options.setUseMorale(chkUseMorale.isSelected()); + options.setUseDesertions(chkUseDesertions.isSelected()); + options.setUseMutinies(chkUseMutinies.isSelected()); options.setStepSize((Double) spnStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); - options.setUseDesertions(chkUseDesertions.isSelected()); options.setUseEmergencyBonuses(chkUseEmergencyBonuses.isSelected()); options.setUseSabotage(chkUseSabotage.isSelected()); - options.setUseTheft(chkUseTheft.isSelected()); - options.setUseMutinies(chkUseMutinies.isSelected()); + options.setUseTheftUnit(chkUseTheftUnit.isSelected()); + options.setUseTheftUnit(chkUseTheftMoney.isSelected()); + options.setTheftValue((Integer) spnTheftValue.getValue()); // Morale Modifiers options.setUseMoraleTriggerFieldControl(chkUseMoraleTriggerFieldControl.isSelected()); From dcea719ce99e13cdac27058d2461de28b3f16f9d Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 15:11:30 -0500 Subject: [PATCH 049/101] Removed emergency bonuses functionality Removed the functionality and UI related to issuing emergency bonuses to prevent desertion. The update involved changes in several files. This decision was taken to streamline the morale management system of the campaign. --- .../CampaignOptionsDialog.properties | 2 -- .../mekhq/resources/Morale.properties | 6 ++--- MekHQ/src/mekhq/campaign/CampaignOptions.java | 17 ++------------ .../turnoverAndRetention/Morale.java | 23 ++++++++++++++----- .../mekhq/gui/panes/CampaignOptionsPane.java | 10 -------- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 260d785839..eb449ef144 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -315,8 +315,6 @@ chkUseDesertions.text=Enable Desertions chkUseDesertions.toolTipText=Low morale may cause personnel to randomly desert the company. chkUseMutinies.text=Enable Mutinies chkUseMutinies.toolTipText=Low morale may cause the unit to mutiny. -chkUseEmergencyBonuses.text=Issue Emergency Bonuses -chkUseEmergencyBonuses.toolTipText=Time for a pizza party! Attempt to quell desertion with an emergency bonus payout to the deserter (equal to their monthly salary).
Provides a re-roll on the desertion roll. This does not affect mutinies. chkUseSabotage.text=Enable Sabotage chkUseSabotage.toolTipText=Low morale influences maintenance checks. chkUseTheftUnit.text=Enable Theft of Units diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index af843733be..03b85b4b9d 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -11,9 +11,9 @@ moraleReportLow.text=Caution, morale is now %s. moraleReportMutiny.text=Your personnel are actively planning a coup. moraleReportRecovered.text=Your personnel are placated, for now. -desertionStolen.text=%s has been stolen. -desertionHeistReport=The company accountants report an unusual transaction of %s c-bills. -desertionHeist.text=Screw you, Commander +desertionTheftUnit.text=%s has been stolen. +desertionTheftMoney.text=The company accountants report an unusual transaction of %s c-bills. +desertionTheftTransactionReport.text=Screw you, Commander officeSupplies.text=A number of random office supplies mascot.text=A company mascot diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 38dc662c89..0d96bde450 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -316,7 +316,6 @@ public static String getTransitUnitName(final int unit) { private boolean useDesertions; private boolean useMutinies; private ForceReliabilityMethod forceReliabilityMethod; - private boolean useEmergencyBonuses; private boolean useSabotage; private boolean useTheftUnit; private boolean useTheftMoney; @@ -1006,12 +1005,11 @@ public CampaignOptions() { setStepSize(0.1); setForceReliabilityMethod(ForceReliabilityMethod.LOYALTY); setUseDesertions(true); - setUseEmergencyBonuses(true); setUseSabotage(true); setUseMutinies(true); setUseTheftUnit(true); setUseTheftMoney(true); - setUseTheftValue(10); + setTheftValue(10); setCustomMoraleModifier(2); setUseMoraleTriggerFieldControl(true); @@ -1595,14 +1593,6 @@ public void setUseDesertions(final boolean useDesertions) { this.useDesertions = useDesertions; } - public boolean isUseEmergencyBonuses() { - return useEmergencyBonuses; - } - - public void setUseEmergencyBonuses(final boolean useEmergencyBonuses) { - this.useEmergencyBonuses = useEmergencyBonuses; - } - public boolean isUseSabotage() { return useSabotage; } @@ -4738,7 +4728,6 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "stepSize", getStepSize()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "forceReliabilityMethod", getForceReliabilityMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useDesertions", isUseDesertions()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useEmergencyBonuses", isUseEmergencyBonuses()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMutinies", isUseMutinies()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheftUnit", isUseTheftUnit()); @@ -5756,8 +5745,6 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setForceReliabilityMethod(ForceReliabilityMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useDesertions")) { retVal.setUseDesertions(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useEmergencyBonuses")) { - retVal.setUseEmergencyBonuses(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSabotage")) { retVal.setUseSabotage(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useTheftUnit")) { @@ -5765,7 +5752,7 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve } else if (wn2.getNodeName().equalsIgnoreCase("useTheftMoney")) { retVal.setUseTheftMoney(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("theftValue")) { - retVal.setTheftValue(Boolean.parseBoolean(wn2.getTextContent().trim())); + retVal.setTheftValue(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMutinies")) { retVal.setUseMutinies(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerFieldControl")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 0937b5b3dd..7b7b8cdcb8 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -407,8 +407,6 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (roll <= targetNumber) { if (isDesertion) { - // TODO emergency bonuses give reroll - if ((processDesertion(campaign, person, roll, targetNumber, morale, unitList, resources)) && (!someoneHasDeserted)) { someoneHasDeserted = true; } @@ -429,7 +427,20 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { } } - private static boolean processDesertion(Campaign campaign, Person person, int roll, double targetNumber, double morale, ArrayList unitList, ResourceBundle resources) { + /** + * Processes desertion for a person. + * + * @param campaign the current campaign + * @param person the potential deserter + * @param roll the desertion roll result + * @param targetNumber the target number for desertion + * @param morale the morale value + * @param unitList the list of units + * @param resources the resource bundle for localized messages + * @return true if desertion occurred, false otherwise + */ + private static boolean processDesertion(Campaign campaign, Person person, int roll, double targetNumber, + double morale, ArrayList unitList, ResourceBundle resources) { if (roll <= (targetNumber - 2)) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); @@ -482,7 +493,7 @@ private static void processUnitTheft(Campaign campaign, ArrayList unitList unitName.append(' ').append(desiredUnit.getFluffName()); } - campaign.addReport(String.format(resources.getString("desertionStolen.text"), unitName)); + campaign.addReport(String.format(resources.getString("desertionTheftUnit.text"), unitName)); campaign.removeUnit(desiredUnit.getId()); unitList.remove(desiredUnit); @@ -529,9 +540,9 @@ private static void processMoneyTheft(Campaign campaign, ResourceBundle resource Money theft = campaign.getFunds().multipliedBy(MathUtility.clamp(theftPercentage, 1, 100) / 100).round(); if (theft.isPositive()) { - campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, resources.getString("desertionHeist.text")); + campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, resources.getString("desertionTheftTransactionReport.text")); - campaign.addReport(String.format(resources.getString("desertionHeistReport.text"), theft.getAmount())); + campaign.addReport(String.format(resources.getString("desertionTheftMoney.text"), theft.getAmount())); } else { processPettyTheft(campaign, resources); } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 85a3bf59ae..92c0d44f8f 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -335,7 +335,6 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JLabel lblStepSize; private JSpinner spnStepSize; private JCheckBox chkUseDesertions; - private JCheckBox chkUseEmergencyBonuses; private JLabel lblForceReliabilityMethod; private MMComboBox comboForceReliabilityMethod; private JCheckBox chkUseSabotage; @@ -4948,11 +4947,6 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseMoraleTriggerMutiny.setEnabled(isEnabled); }); - chkUseEmergencyBonuses = new JCheckBox(resources.getString("chkUseEmergencyBonuses.text")); - chkUseEmergencyBonuses.setToolTipText(resources.getString("chkUseEmergencyBonuses.toolTipText")); - chkUseEmergencyBonuses.setName("chkUseEmergencyBonuses"); - chkUseEmergencyBonuses.setEnabled(isUseMorale); - chkUseSabotage = new JCheckBox(resources.getString("chkUseSabotage.text")); chkUseSabotage.setToolTipText(resources.getString("chkUseSabotage.toolTipText")); chkUseSabotage.setName("chkUseSabotage"); @@ -4997,7 +4991,6 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) - .addComponent(chkUseEmergencyBonuses) .addComponent(chkUseSabotage) .addComponent(chkUseTheftUnit) .addComponent(chkUseTheftMoney) @@ -5016,7 +5009,6 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createSequentialGroup() .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod)) - .addComponent(chkUseEmergencyBonuses) .addComponent(chkUseSabotage) .addComponent(chkUseTheftUnit) .addComponent(chkUseTheftMoney) @@ -8283,7 +8275,6 @@ public void setOptions(@Nullable CampaignOptions options, chkUseMutinies.setSelected(options.isUseMutinies()); spnStepSize.setValue(options.getStepSize()); comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); - chkUseEmergencyBonuses.setSelected(options.isUseEmergencyBonuses()); chkUseSabotage.setSelected(options.isUseSabotage()); chkUseTheftUnit.setSelected(options.isUseTheftUnit()); chkUseTheftMoney.setSelected(options.isUseTheftUnit()); @@ -8979,7 +8970,6 @@ public void updateOptions() { options.setUseMutinies(chkUseMutinies.isSelected()); options.setStepSize((Double) spnStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); - options.setUseEmergencyBonuses(chkUseEmergencyBonuses.isSelected()); options.setUseSabotage(chkUseSabotage.isSelected()); options.setUseTheftUnit(chkUseTheftUnit.isSelected()); options.setUseTheftUnit(chkUseTheftMoney.isSelected()); From dc49f2072eb3080fd949f7777487118d4c8212b1 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 15:49:27 -0500 Subject: [PATCH 050/101] Refactor desertion and theft process in Morale class The desertion and theft process is significantly refactored in Morale class by adding more specific filtering for theft targets. A deserting personnel's chance to steal a unit is updated: units will only be stolen if they are undamaged, undeployed, not a large craft, or a warship. Implementation of generating black market offers from stolen units was also included in this refactor. Furthermore, access modifier of the 'addOffers' method in the AbstractUnitMarket class and its respective children classes has been changed from protected to public. --- .../mekhq/resources/Morale.properties | 3 +- .../market/unitMarket/AbstractUnitMarket.java | 5 +- .../unitMarket/AtBMonthlyUnitMarket.java | 2 +- .../market/unitMarket/DisabledUnitMarket.java | 2 +- .../turnoverAndRetention/Morale.java | 56 +++++++++---------- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 03b85b4b9d..5e98d82859 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -11,7 +11,8 @@ moraleReportLow.text=Caution, morale is now %s. moraleReportMutiny.text=Your personnel are actively planning a coup. moraleReportRecovered.text=Your personnel are placated, for now. -desertionTheftUnit.text=%s has been stolen. +desertionTheftUnit.text=has stolen %s. +desertionTheftUnitBlackMarket.text=has stolen %s and is trying to sell it on the Black Market. desertionTheftMoney.text=The company accountants report an unusual transaction of %s c-bills. desertionTheftTransactionReport.text=Screw you, Commander diff --git a/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java b/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java index ea23edad2b..73e2507f93 100644 --- a/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java +++ b/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java @@ -97,10 +97,7 @@ public void setOffers(final List offers) { * @param quality the quality of the unit to generate * @param priceTarget the target number used to determine the percent */ - protected abstract void addOffers(final Campaign campaign, final int number, - UnitMarketType market, final int unitType, - @Nullable Faction faction, final int quality, - final int priceTarget); + public abstract void addOffers(final Campaign campaign, final int number, UnitMarketType market, final int unitType, @Nullable Faction faction, final int quality, final int priceTarget); /** * @param campaign the campaign to use to generate the unit diff --git a/MekHQ/src/mekhq/campaign/market/unitMarket/AtBMonthlyUnitMarket.java b/MekHQ/src/mekhq/campaign/market/unitMarket/AtBMonthlyUnitMarket.java index 337679dae1..d19b9cafc9 100644 --- a/MekHQ/src/mekhq/campaign/market/unitMarket/AtBMonthlyUnitMarket.java +++ b/MekHQ/src/mekhq/campaign/market/unitMarket/AtBMonthlyUnitMarket.java @@ -133,7 +133,7 @@ public void generateUnitOffers(final Campaign campaign) { } @Override - protected void addOffers(final Campaign campaign, final int num, UnitMarketType market, + public void addOffers(final Campaign campaign, final int num, UnitMarketType market, final int unitType, @Nullable Faction faction, final int quality, final int priceTarget) { if (faction == null) { diff --git a/MekHQ/src/mekhq/campaign/market/unitMarket/DisabledUnitMarket.java b/MekHQ/src/mekhq/campaign/market/unitMarket/DisabledUnitMarket.java index dfa3207ce7..f0e475892d 100644 --- a/MekHQ/src/mekhq/campaign/market/unitMarket/DisabledUnitMarket.java +++ b/MekHQ/src/mekhq/campaign/market/unitMarket/DisabledUnitMarket.java @@ -68,7 +68,7 @@ public void generateUnitOffers(final Campaign campaign) { } @Override - protected void addOffers(final Campaign campaign, final int number, final UnitMarketType market, + public void addOffers(final Campaign campaign, final int number, final UnitMarketType market, final int unitType, final Faction faction, final int quality, final int priceTarget) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 7b7b8cdcb8..f55adfcf5d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -3,18 +3,22 @@ import megamek.codeUtilities.MathUtility; import megamek.common.Compute; import megamek.common.Entity; +import megamek.common.UnitType; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; import mekhq.campaign.finances.enums.TransactionType; +import mekhq.campaign.market.enums.UnitMarketType; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.ForceReliabilityMethod; import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.unit.Unit; import java.util.*; +import java.util.stream.Collectors; public class Morale { @@ -389,10 +393,12 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { double morale = campaign.getMorale(); - ArrayList unitList = new ArrayList<>(); + ArrayList theftTargets = new ArrayList<>(); if (isDesertion) { - unitList = (ArrayList) campaign.getUnits(); + theftTargets = campaign.getHangar().getUnits().stream() + .filter(unit -> (!unit.isDamaged()) || (!unit.isDeployed()) || (!unit.getEntity().isLargeCraft()) || (!unit.getEntity().isWarShip())) + .collect(Collectors.toCollection(ArrayList::new)); } boolean someoneHasDeserted = false; @@ -407,7 +413,8 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (roll <= targetNumber) { if (isDesertion) { - if ((processDesertion(campaign, person, roll, targetNumber, morale, unitList, resources)) && (!someoneHasDeserted)) { + if ((processDesertion(campaign, person, roll, targetNumber, morale, theftTargets, resources)) + && (!someoneHasDeserted)) { someoneHasDeserted = true; } } else { @@ -444,7 +451,7 @@ private static boolean processDesertion(Campaign campaign, Person person, int ro if (roll <= (targetNumber - 2)) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); - if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheftUnit())) { + if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!unitList.isEmpty())) { processUnitTheft(campaign, unitList, resources); } else if ((roll <= (morale - 3)) && (campaign.getCampaignOptions().isUseTheftMoney())) { processMoneyTheft(campaign, resources); @@ -469,38 +476,25 @@ private static boolean processDesertion(Campaign campaign, Person person, int ro * @param resources the resource bundle for retrieving localized strings */ private static void processUnitTheft(Campaign campaign, ArrayList unitList, ResourceBundle resources) { - boolean committingTheft = true; - int attempts = 3; + Unit desiredUnit = unitList.get(new Random().nextInt(unitList.size())); - while (committingTheft) { - Unit desiredUnit = unitList.get(new Random().nextInt(unitList.size())); + StringBuilder unitName = new StringBuilder(desiredUnit.getName()); - if ((desiredUnit.getEntity().isLargeCraft()) || (desiredUnit.getEntity().isWarShip()) || (desiredUnit.isDeployed())) { - attempts--; - - if (attempts == 0) { - if (campaign.getCampaignOptions().isUseTheftMoney()) { - processMoneyTheft(campaign, resources); - } else { - processPettyTheft(campaign, resources); - } - committingTheft = false; - } - } else { - StringBuilder unitName = new StringBuilder(desiredUnit.getName()); - - if (!Objects.equals(desiredUnit.getFluffName(), "")) { - unitName.append(' ').append(desiredUnit.getFluffName()); - } - - campaign.addReport(String.format(resources.getString("desertionTheftUnit.text"), unitName)); + if (!Objects.equals(desiredUnit.getFluffName(), "")) { + unitName.append(' ').append(desiredUnit.getFluffName()); + } - campaign.removeUnit(desiredUnit.getId()); - unitList.remove(desiredUnit); + if ((!campaign.getFaction().isClan()) && (Compute.d6(1) >= 3)) { + campaign.getUnitMarket().addOffers(campaign, 1, UnitMarketType.BLACK_MARKET, desiredUnit.getEntity().getUnitType(), + campaign.getFaction(), desiredUnit.getQuality(), 6); - committingTheft = false; - } + campaign.addReport(String.format(resources.getString("desertionTheftUnitBlackMarket.text"), unitName)); + } else { + campaign.addReport(String.format(resources.getString("desertionTheftUnit.text"), unitName)); } + + campaign.removeUnit(desiredUnit.getId()); + unitList.remove(desiredUnit); } /** From b31d49a284423ffa36e06d9cec2e65a4303efb5e Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 22 May 2024 21:44:32 -0500 Subject: [PATCH 051/101] Added mutiny handling mechanics and related dialog A mechanic to handle mutiny scenarios among personnel in the MekHQ campaign has been implemented. This includes the addition of a new dialog box providing the user with the options to either support the loyalists or the rebels during such an event. Updated the method to process desertions and added operation for civil war loyalty checks. The Morale.properties file is also modified to include appropriate text messages for the mutiny situations. --- .../mekhq/resources/Morale.properties | 25 ++- .../turnoverAndRetention/Morale.java | 191 +++++++++++++++--- .../mekhq/gui/dialog/MutinySupportDialog.java | 160 +++++++++++++++ 3 files changed, 352 insertions(+), 24 deletions(-) create mode 100644 MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 5e98d82859..29870bcb2b 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -1,3 +1,4 @@ +# Morale Tiers moraleLevelUnbreakable.text=Unbreakable moraleLevelVeryHigh.text=Very High moraleLevelHigh.text=High @@ -6,16 +7,38 @@ moraleLevelLow.text=Low moraleLevelVeryLow.text=Very Low moraleLevelBroken.text=Broken +# Morale Report moraleReport.text=Morale is now %s. moraleReportLow.text=Caution, morale is now %s. moraleReportMutiny.text=Your personnel are actively planning a coup. moraleReportRecovered.text=Your personnel are placated, for now. +# Theft desertionTheftUnit.text=has stolen %s. desertionTheftUnitBlackMarket.text=has stolen %s and is trying to sell it on the Black Market. desertionTheftMoney.text=The company accountants report an unusual transaction of %s c-bills. desertionTheftTransactionReport.text=Screw you, Commander +# Mutiny +mutinyThwartedSingular.text=An attempted coup has been thwarted. The would-be mutineer has fled the company. +mutinyThwartedPlural.text=An attempted coup has been thwarted. %s would-be mutineers have fled the company. + +# Mutiny Dialog +dialogSupportLoyalists.text=Support Loyalists +dialogSupportRebels.text=Support Rebels +dialogTitle.text=Betrayal! + +dialogDescriptionIntroduction.text=
Your Commander's poor leadership has given your personnel no other choice. +dialogDescriptionViolentTakeover.text=They have drawn arms against them and are demanding control of the company.

+dialogDescriptionRegimeChange.text=They are demanding the Commander be removed from power and will fight to ensure this happens.

+dialogDescriptionLoyalist.text=Loyalist +dialogDescriptionRebels.text=Rebel +dialogDescriptionForces.text=The %s faction is supported by %s personnel, %s Meks, %s Fighters, %s ProtoMeks, %s units of Battle Armor, %s DropShips,\ + \ %s units of Infantry, %s Vehicles, and %s other units (estimated %s BV).

+dialogDescriptionDecision.text=You must choose whether to support the Loyalists or the Rebels.\ + \ WARNING: once you have made your decision, there is no turning back.
+ +# Petty Theft Table officeSupplies.text=A number of random office supplies mascot.text=A company mascot phones.text=The office phones @@ -118,5 +141,5 @@ companyStandard.text=The company standard analyticsReports.text=Analytics reports fridge.text=The contents of the communal fridge coffeeMachine.text=The coffee machine -mug.text=Commander's favorite mug +mug.text=The Commander's favorite mug toiletSeats.text=All the toilet seats diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index f55adfcf5d..ffa6a19375 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -3,7 +3,6 @@ import megamek.codeUtilities.MathUtility; import megamek.common.Compute; import megamek.common.Entity; -import megamek.common.UnitType; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; @@ -14,8 +13,8 @@ import mekhq.campaign.personnel.enums.ForceReliabilityMethod; import mekhq.campaign.personnel.enums.PersonnelRole; import mekhq.campaign.personnel.enums.PersonnelStatus; -import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.unit.Unit; +import mekhq.gui.dialog.MutinySupportDialog; import java.util.*; import java.util.stream.Collectors; @@ -61,28 +60,28 @@ public static String getMoraleLevel(Campaign campaign) { * @return the base target number for morale * @throws IllegalStateException if the campaign morale level is unexpected */ - private static Double getTargetNumber(Campaign campaign, boolean isDesertion) { + private static Integer getTargetNumber(Campaign campaign, boolean isDesertion) { double morale = campaign.getMorale(); if ((morale >= 1) && (morale < 4)) { - return 0.0; + return 0; } else if (morale < 5) { if (isDesertion) { - return 2.0; + return 2; } else { - return 0.0; + return 0; } } else if (morale < 7) { if (isDesertion) { - return 5.0; + return 5; } else { - return 4.0; + return 4; } } else if (morale == 7) { if (isDesertion) { - return 8.0; + return 8; } else { - return 7.0; + return 7; } } @@ -397,7 +396,7 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (isDesertion) { theftTargets = campaign.getHangar().getUnits().stream() - .filter(unit -> (!unit.isDamaged()) || (!unit.isDeployed()) || (!unit.getEntity().isLargeCraft()) || (!unit.getEntity().isWarShip())) + .filter(unit -> (!unit.isDamaged()) && (!unit.isDeployed()) && (!unit.getEntity().isLargeCraft()) && (!unit.getEntity().isWarShip())) .collect(Collectors.toCollection(ArrayList::new)); } @@ -405,20 +404,26 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { boolean someoneHasMutinied = false; List loyalists = new ArrayList<>(); - List rebels = new ArrayList<>(); + HashMap rebels = new HashMap<>(); for (Person person : filteredPersonnel) { int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, loyalty); - int roll = Compute.d6(2) + modifier; - if (roll <= targetNumber) { + int firstRoll = Compute.d6(2) + modifier; + int secondRoll = 0; + + if (isDesertion) { + secondRoll = Compute.d6(2) + modifier; + } + + if ((firstRoll <= targetNumber) && (secondRoll <= targetNumber)) { if (isDesertion) { - if ((processDesertion(campaign, person, roll, targetNumber, morale, theftTargets, resources)) + if ((processDesertion(campaign, person, secondRoll, targetNumber, morale, theftTargets, resources)) && (!someoneHasDeserted)) { someoneHasDeserted = true; } } else { - rebels.add(person); + rebels.put(person, firstRoll); someoneHasMutinied = true; } } else { @@ -428,7 +433,7 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (someoneHasMutinied) { processMoraleLoss(campaign, -2); - processMutiny(campaign, loyalists, rebels); + processMutiny(campaign, loyalists, rebels, theftTargets, resources); } else if (someoneHasDeserted) { processMoraleLoss(campaign, -1); } @@ -448,6 +453,10 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { */ private static boolean processDesertion(Campaign campaign, Person person, int roll, double targetNumber, double morale, ArrayList unitList, ResourceBundle resources) { + if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { + morale -= 1; + } + if (roll <= (targetNumber - 2)) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); @@ -457,12 +466,14 @@ private static boolean processDesertion(Campaign campaign, Person person, int ro processMoneyTheft(campaign, resources); } else if (roll <= (morale - 2)) { processPettyTheft(campaign, resources); + } else { + + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); + person.setAwolDays(Compute.d6(2)); } return true; } else { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); - person.setAwolDays(Compute.d6(2)); return false; } @@ -724,9 +735,143 @@ public static void processMoraleLoss(Campaign campaign, Integer steps) { } } - private static void processMutiny(Campaign campaign, List loyalists, List rebels) { - // TODO process mutinies - // There should be three possible events: violent transfer of power, demand leader step down, or failed to garner enough support - // Player should have the option to join the rebels or stay with the loyalists + private static void processMutiny(Campaign campaign, List loyalists, HashMap rebels, ArrayList unitList, ResourceBundle resources) { + // This prevents us from needing to do the full process for tiny mutinies that have no chance of success + if ((loyalists.size() / 10) > rebels.size()) { + if (rebels.size() > 1) { + campaign.addReport(String.format(resources.getString("mutinyThwartedPlural.text"), rebels.size())); + } else { + campaign.addReport(String.format(resources.getString("mutinyThwartedSingular.text"), rebels.size())); + } + + for (Person person : rebels.keySet()) { + processDesertion(campaign, person, rebels.get(person), getTargetNumber(campaign, true), campaign.getMorale(), unitList, resources); + } + + return; + } + + // A civil war breaks out. + // Everyone is forced to pick sides. + + // The rebels have already picked their side, so we only need to process the loyalists + for (Person person : loyalists) { + if (Compute.d6(1) < getCivilWarTargetNumber(campaign, person)) { + loyalists.remove(person); + rebels.put(person, 0); + } + } + + // with the line drawn in the sand, we need to assess the forces available to each side + HashMap rebelUnits = getUnits(new ArrayList<>(rebels.keySet()), false); + int rebelBv = rebelUnits.keySet().stream() + .mapToInt(unit -> unit.getEntity() + .calculateBattleValue(true, false)).sum(); + + HashMap loyalUnits = getUnits(loyalists, true); + int loyalistBv = loyalUnits.keySet().stream() + .mapToInt(unit -> unit.getEntity() + .calculateBattleValue(true, false)).sum(); + + // we now need to present the player with a choice: join the rebels, or support the loyalists + int supportDecision = -1; + + while (supportDecision == -1) { + supportDecision = MutinySupportDialog.SupportDialog( + resources, false, + loyalists.size(), new ArrayList<>(loyalUnits.keySet()), loyalistBv, + rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv + ); + } + } + + /** + * Calculates the target number for civil war loyalty checks + * + * @param campaign the ongoing campaign + * @param person the person for which loyalty is being tested + * @return the target number for a civil war loyalty check + * @throws IllegalStateException if the loyalty value is unexpected + */ + private static int getCivilWarTargetNumber(Campaign campaign, Person person) { + int modifier = 0; + + if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { + modifier++; + } + + if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { + switch (person.getLoyalty()) { + case -3: + return 6 + modifier; + case -2: + return 5 + modifier; + case -1: + case 0: + return 4 + modifier; + case 1: + return 3 + modifier; + case 2: + return 2 + modifier; + case 3: + return 1 + modifier; + default: + throw new IllegalStateException("Unexpected value in getCivilWarTargetNumber: " + person.getLoyalty()); + } + } + return 4 + modifier; + } + + + /** + * Retrieves the units that are eligible to participate in the civil war based on the provided personnel. + * Multi-crewed units perform a vote to determine which side they join. + * + * @param personnel A list of personnel (should all belong to the same mutiny faction. + * @param isLoyalists A boolean value indicating whether to retrieve units for loyalists or rebels. + * @return A HashMap of units and their corresponding battle values. + */ + private static HashMap getUnits(List personnel, boolean isLoyalists) { + HashMap forces = new HashMap<>(); + + for (Person person: personnel) { + if (person.getUnit() != null) { + Unit unit = person.getUnit(); + + if ((unit.getEntity().isJumpShip()) || (unit.getEntity().isWarShip()) || (unit.getEntity().isSupportVehicle())) { + continue; + } + + // We only care about the commander, as this allows us to ensure each Unit is only counted once. + // We also check to ensure the unit isn't already deployed, or too damaged to fight. + if ((unit.isCommander(person)) && (!unit.isDeployed()) && (!unit.getEntity().isCrippled()) && (!unit.getEntity().isDmgHeavy())) { + int loyalVoteCount = 0; + int rebelVoteCount = 0; + + for (Person crew : unit.getCrew()) { + if (personnel.contains(crew)) { + if (isLoyalists) { + loyalVoteCount++; + } else { + rebelVoteCount++; + } + } + } + + // if the votes are equal, the unit abstains from the conflict + if (loyalVoteCount > rebelVoteCount) { + if (isLoyalists) { + forces.put(unit, unit.getEntity().calculateBattleValue(true, false)); + } + } else if (loyalVoteCount < rebelVoteCount) { + if (!isLoyalists) { + forces.put(unit, unit.getEntity().calculateBattleValue(true, false)); + } + } + } + } + } + + return forces; } } diff --git a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java new file mode 100644 index 0000000000..10bbfd5da9 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java @@ -0,0 +1,160 @@ +package mekhq.gui.dialog; + +import megamek.common.Entity; +import mekhq.campaign.unit.Unit; + +import javax.swing.*; +import java.util.HashMap; +import java.util.List; +import java.util.ResourceBundle; + +public class MutinySupportDialog { + /** + * Displays a dialog with options to either support loyalists or rebels. + * + * @param resources the resource bundle containing the dialog messages + * @param isViolentRebellion a boolean indicating whether the rebellion is violent or not + * @param loyalistForces a list of loyalist units + * @param rebelForces a list of rebel units + * @return an integer representing the user's choice: + * - 1 if the user chooses to support the loyalists, + * - 0 if the user chooses to support the rebels, + * - -1 if the user cancels the dialog + */ + public static int SupportDialog(ResourceBundle resources, boolean isViolentRebellion, + Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, + Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { + + Object[] options = { resources.getString("dialogSupportLoyalists.text"), resources.getString("dialogSupportRebels.text") }; + + int choice = JOptionPane.showOptionDialog(null, + buildSituationDescription( + resources, isViolentRebellion, + loyalistPersonnelCount, loyalistForces, loyalistBv, + rebelPersonnelCount, rebelForces, rebelBv + ), + resources.getString("dialogTitle.text"), + + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, + + null, + + options, options[1]); + + if (choice == JOptionPane.YES_OPTION) { + return 1; + } else if (choice == JOptionPane.NO_OPTION) { + return 0; + } else { + return -1; + } + } + + private static String buildSituationDescription(ResourceBundle resources, boolean isViolentRebellion, + Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, + Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { + + StringBuilder situationDescription = new StringBuilder(resources.getString("dialogDescriptionIntroduction.text")); + + if (isViolentRebellion) { + situationDescription.append(' ').append(resources.getString("dialogDescriptionViolentTakeover.text")); + } else { + situationDescription.append(' ').append(resources.getString("dialogDescriptionRegimeChange.text")); + } + + HashMap unitMap = mapUnitCounts(loyalistForces); + situationDescription.append(getForceSummaryString(resources, unitMap, true, loyalistBv, loyalistPersonnelCount)); + + unitMap = mapUnitCounts(rebelForces); + situationDescription.append(getForceSummaryString(resources, unitMap, false, rebelBv, rebelPersonnelCount)); + + situationDescription.append(resources.getString("dialogDescriptionDecision.text")); + + return situationDescription.toString(); + } + + /** + * Maps the counts of different units in the given list of units. + * + * @param units the list of units + * @return a HashMap containing the counts of each unit type, where the key is the unit type and the value is the count + */ + private static HashMap mapUnitCounts(List units) { + HashMap unitCounts = new HashMap<>(); + + int mekCount = 0; + int fighterCount = 0; + int protoMekCount = 0; + int baCount = 0; + int dropShipCount = 0; + int infantryCount = 0; + int vehicleCount = 0; + int otherCount = 0; + + for (Unit unit : units) { + Entity entity = unit.getEntity(); + + if (entity.isMek()) { + mekCount++; + } else if (entity.isFighter()) { + fighterCount++; + } else if (entity.isProtoMek()) { + protoMekCount++; + } else if (entity.isBattleArmor()) { + baCount++; + } else if (entity.isDropShip()) { + dropShipCount++; + } else if (entity.isInfantry()) { + infantryCount++; + } else if (entity.isVehicle()) { + vehicleCount++; + } else { + otherCount++; + } + } + + unitCounts.put("mek", mekCount); + unitCounts.put("fighter", fighterCount); + unitCounts.put("protoMek", protoMekCount); + unitCounts.put("battleArmor", baCount); + unitCounts.put("dropShip", dropShipCount); + unitCounts.put("infantry", infantryCount); + unitCounts.put("vehicle", vehicleCount); + unitCounts.put("other", otherCount); + + return unitCounts; + } + + /** + * Retrieves the summary string for the forces based on the given resources, unit map, and loyalty faction. + * + * @param resources the ResourceBundle containing the necessary strings for formatting the summary string + * @param unitMap the HashMap containing the count of each unit type + * @param isLoyalist the flag indicating if the forces are loyalist or rebel + * @param bv the force's estimated Battle Value + * @param personnelCount the number of personnel supporting the faction + * @return the formatted summary string + */ + private static String getForceSummaryString(ResourceBundle resources, HashMap unitMap, boolean isLoyalist, Integer bv, Integer personnelCount) { + String faction; + + if (isLoyalist) { + faction = resources.getString("dialogDescriptionLoyalist.text"); + } else { + faction = resources.getString("dialogDescriptionRebels.text"); + } + + return String.format(resources.getString("dialogDescriptionForces.text"), + faction, + unitMap.get("mek"), + unitMap.get("fighter"), + unitMap.get("protoMek"), + unitMap.get("battleArmor"), + unitMap.get("dropShip"), + unitMap.get("infantry"), + unitMap.get("vehicle"), + unitMap.get("other"), + bv, + personnelCount); + } +} \ No newline at end of file From 1e9d20084ab9985838392df0bc40df34cafc302b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 23 May 2024 00:29:14 -0500 Subject: [PATCH 052/101] Added abstract Mutiny Battle feature in Morale module Implemented logic to handle abstract mutiny battles in the morale module. The functionality now can assess and calculate combat statistics, perform rounds of combat, and process attrition, ammo usage, and force damage. Modified the Morale.properties file to add a new message for unit destruction during mutiny battles. --- .../mekhq/resources/Morale.properties | 3 + .../turnoverAndRetention/Morale.java | 228 ++++++++++++++++++ 2 files changed, 231 insertions(+) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 29870bcb2b..eda59e5ca3 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -38,6 +38,9 @@ dialogDescriptionForces.text=The %s faction is supported by %s personnel, %s Mek dialogDescriptionDecision.text=You must choose whether to support the Loyalists or the Rebels.\ \ WARNING: once you have made your decision, there is no turning back. +# Mutiny Battle +battleUnitDestroyed.text=was completely destroyed, all personnel were killed outright. + # Petty Theft Table officeSupplies.text=A number of random office supplies mascot.text=A company mascot diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index ffa6a19375..d16e54899f 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -3,6 +3,7 @@ import megamek.codeUtilities.MathUtility; import megamek.common.Compute; import megamek.common.Entity; +import megamek.common.Mounted; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; @@ -785,6 +786,233 @@ private static void processMutiny(Campaign campaign, List loyalists, Has } } + private static void processMutinyBattle(Campaign campaign, + List loyalists, List loyalistForces, Integer loyalistBv, + List rebels, List rebelForces, Integer rebelBv, + ResourceBundle resources) { + + // start by calculating combat statistics + HashMap abstractBattleStatistics = getAbstractBattleStatistics(loyalists, loyalistForces.size(), loyalistBv); + int loyalistAttackDice = abstractBattleStatistics.get("attackDice"); + int loyalistDefenceDice = abstractBattleStatistics.get("defenceDice"); + + abstractBattleStatistics = getAbstractBattleStatistics(rebels, rebelForces.size(), rebelBv); + int rebelAttackDice = abstractBattleStatistics.get("attackDice"); + int rebelDefenceDice = abstractBattleStatistics.get("defenceDice"); + + // next we perform three-four rounds of combat: the opening engagement, the brawl, the bitter end. + int attrition = 0; + int loyalistVictoryPoints = 0; + int rebelVictoryPoints = 0; + + // the opening engagement + HashMap engagement = processEngagement(loyalistAttackDice, loyalistDefenceDice, rebelAttackDice, rebelDefenceDice); + attrition += engagement.get("attrition"); + + if (engagement.get("loyalistVictory") == 1) { + loyalistVictoryPoints++; + } else { + rebelVictoryPoints++; + } + + // the brawl + engagement = processEngagement(loyalistAttackDice, loyalistDefenceDice, rebelAttackDice, rebelDefenceDice); + attrition += engagement.get("attrition"); + + if (engagement.get("loyalistVictory") == 1) { + loyalistVictoryPoints++; + } else { + rebelVictoryPoints++; + } + + // the bitter end + engagement = processEngagement(loyalistAttackDice, loyalistDefenceDice, rebelAttackDice, rebelDefenceDice); + attrition += engagement.get("attrition"); + + if (engagement.get("loyalistVictory") == 1) { + loyalistVictoryPoints++; + } else { + rebelVictoryPoints++; + } + + // results + HashMap loyalistForceDamage = mapForceDamage(loyalistForces.size(), attrition, rebelAttackDice, loyalistDefenceDice); + + for (int damagedUnit = 0; damagedUnit < loyalistForceDamage.get("damagedLight"); damagedUnit++) {} + + // process attrition (units outright destroyed) + for (int destroyedUnit = 0; destroyedUnit < loyalistForceDamage.get("attrition"); destroyedUnit++) { + Unit unit = loyalistForces.get(new Random().nextInt(loyalistForces.size())); + + campaign.addReport(unit.getName() + ' ' + String.format(resources.getString("battleUnitDestroyed.text"))); + + for (Person person : unit.getCrew()) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); + loyalists.remove(person); + } + + campaign.removeUnit(unit.getId()); + loyalistForces.remove(unit); + } + + // process ammo usage + getAmmoUsage(loyalistForces, attrition); + getAmmoUsage(rebelForces, attrition); + + // TODO damage + } + + /** + * Updates the ammo usage of each unit in the force, taking into account attrition. + * + * @param force the list of units in the force + * @param attrition the attrition value to apply to the ammo usage calculation + */ + private static void getAmmoUsage(List force, int attrition) { + for (Unit unit : force) { + for (Mounted bin : unit.getEntity().getAmmo()) { + int ammo = bin.getUsableShotsLeft(); + int roll = Compute.randomInt((int) ((ammo * 0.33) + attrition)); + + bin.setShotsLeft(Math.max(0, ammo - roll)); + } + } + } + + /** + * Maps force damage based on the given parameters. + * + * @param forceSize the size of the force + * @param attrition the attrition value + * @param enemyAttackDice the number of dice used for enemy attacks + * @param friendlyDefenceDice the number of dice used for friendly defenses + * @return a HashMap containing the force damage: + * - "attrition": the attrition value after mapping + * - "damagedLight": the number of units damaged lightly + * - "damagedModerate": the number of units damaged moderately + * - "damagedBadly": the number of units damaged badly + */ + private static HashMap mapForceDamage(int forceSize, int attrition, int enemyAttackDice, int friendlyDefenceDice) { + HashMap forceDamage = new HashMap<>(); + + attrition = attrition * (forceSize / 12); + + int damageDice = Math.max(0, enemyAttackDice - friendlyDefenceDice); + + int damagedBadly = 0; + int damagedModerate = 0; + int damagedLight = 0; + + for (int rollNumber = 0; rollNumber < damageDice; rollNumber++) { + switch (Compute.d6(1)) { + case 1: + damagedBadly++; + break; + case 2: + case 3: + damagedModerate++; + break; + case 4: + case 5: + case 6: + default: + damagedLight++; + break; + } + } + + while ((attrition + damagedBadly + damagedModerate + damagedLight) > forceSize) { + if (damagedLight > 0) { + damagedLight--; + } else if (damagedModerate > 0) { + damagedModerate--; + } else if (damagedBadly > 0) { + damagedBadly--; + } else { + attrition--; + } + } + + forceDamage.put("attrition", attrition); + forceDamage.put("damagedLight", damagedLight); + forceDamage.put("damagedModerate", damagedModerate); + forceDamage.put("damagedBadly", damagedBadly); + + return forceDamage; + } + + private static HashMap processEngagement(int loyalistAttackDice, int loyalistDefenceDice, int rebelAttackDice, int rebelDefenceDice) { + HashMap combatResults = new HashMap<>(); + + boolean concludeEngagement = false; + int attrition = 0; + int loyalistVictory = 0; + int rebelVictory = 0; + + while (!concludeEngagement) { + int loyalistAttack = Compute.d6(loyalistAttackDice); + int loyalistDefence = Compute.d6(loyalistDefenceDice); + int rebelAttack = Compute.d6(rebelAttackDice); + int rebelDefence = Compute.d6(rebelDefenceDice); + + loyalistVictory = 0; + rebelVictory = 0; + + if (loyalistAttack > rebelDefence) { + if (loyalistAttack < (rebelDefence * 1.25)) { + attrition++; + } + + loyalistVictory = 1; + } + + if (rebelAttack > loyalistDefence) { + if (rebelAttack < (loyalistDefence * 1.25)) { + attrition++; + } + + rebelVictory = 1; + } + + if (loyalistVictory == rebelVictory) { + attrition++; + } else { + concludeEngagement = true; + } + } + + combatResults.put("attrition", attrition); + combatResults.put("loyalistVictory", loyalistVictory); + + return combatResults; + } + + /** + * Calculates a faction's abstract battle statistics based on the given combatants, force size, and battle value. + * + * @param combatants the list of combatants participating in the battle + * @param forceSize the size of the force + * @param battleValue the value of the battle + * @return a HashMap containing the statistics of the battle + */ + private static HashMap getAbstractBattleStatistics(List combatants, Integer forceSize, Integer battleValue) { + HashMap statistics = new HashMap<>(); + + // TODO make the dividers Campaign Options + int loyalistLeadership = (int) combatants.stream().filter(person -> person.getRank().isOfficer()).count() / (forceSize / 12); + int loyalistMedical = (int) combatants.stream().filter(person -> (person.getPrimaryRole().isDoctor())).count() / (forceSize / 12); + int loyalistAdministration = (int) combatants.stream().filter(person -> (person.getPrimaryRole().isAdministrator())).count() / (forceSize / 3); + int loyalistTech = (int) combatants.stream().filter(person -> (person.getPrimaryRole().isTech())).count() / (forceSize / 6); + + int attackDice = (battleValue / 250) + loyalistLeadership + loyalistAdministration; + int defenceDice = (battleValue / 250) + loyalistTech + loyalistMedical; + + statistics.put("attackDice", attackDice); + statistics.put("defenceDice", defenceDice); + + return statistics; + } + /** * Calculates the target number for civil war loyalty checks * From d201b6813ff98b15d720ab7a75e36dd75bf05d56 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 23 May 2024 10:39:28 -0500 Subject: [PATCH 053/101] Add morale manipulation tools to GMToolsDialog A General Tools Panel is added to the GMToolsDialog for manipulating morale values. Morale is now adjustable via a JSpinner. This change includes the addition of a tooltip that informs users that changes may not be reflected until they Advance Day or reload the client. The Campaign.java class is also updated to handle morale as a double instead of Double. --- .../CampaignOptionsDialog.properties | 4 +- .../resources/mekhq/resources/GUI.properties | 6 +++ MekHQ/src/mekhq/campaign/Campaign.java | 15 +++--- MekHQ/src/mekhq/campaign/CampaignOptions.java | 18 +++---- MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java | 49 +++++++++++++++++++ .../mekhq/gui/panes/CampaignOptionsPane.java | 34 ++++++------- 6 files changed, 92 insertions(+), 34 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index eb449ef144..342a2ca296 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -307,8 +307,8 @@ turnoverAndRetentionMoralePanel.title=Morale chkUseMorale.text=Enable Morale chkUseMorale.toolTipText=Enables the morale system. This system is based on the CamOps Morale rules on p217.
Full documentation is available in MekHQ/docs/Turnover and Retention Module.pdf -lblStepSize.text=Step Size -lblStepSize.toolTipText=How large should each morale adjustment be? CamOps uses step sizes of 1.0. +lblMoraleStepSize.text=Step Size +lblMoraleStepSize.toolTipText=How large should each morale adjustment be? CamOps uses step sizes of 1.0. lblForceReliabilityMethod.text=Force Reliability Method lblForceReliabilityMethod.toolTipText=How should Force Reliability be calculated (referred to as 'Loyalty' on CamOps p217)? chkUseDesertions.text=Enable Desertions diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index d41ecde289..5455f47549 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -490,6 +490,11 @@ DataLoadingDialog.ExecutionException.text=The campaign file could not be loaded. GMToolsDialog.title=GM Tools ## General Tab generalTab.title=General +# General Tools Panel +generalTools.title=General Tools +lblMorale.text=Morale: +btnMorale.text=Set +btnMorale.toolTipText=Adjust morale to the chosen value. This change may not be reflected in the command center until you Advance Day or reload the client. # Dice Panel dicePanel.title=Dice Roller lblRolls.text=rolls of @@ -499,6 +504,7 @@ lblTotalDiceResult.text=%5d btnDiceRoll.text=Roll btnDiceRoll.toolTipText=Perform the specified dice roll. lblIndividualDiceResults.text=Individual Results: + # RAT Panel ratPanel.title=RAT Roller lblQuality.text=Quality diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index d435fab063..3de0bf5d7a 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -481,11 +481,11 @@ public void setProcreation(final AbstractProcreation procreation) { this.procreation = procreation; } - public Double getMorale() { + public double getMorale() { return morale; } - public void setMorale(final Double morale) { + public void setMorale(final double morale) { this.morale = morale; } //endregion Personnel Modules @@ -3564,14 +3564,17 @@ private void processMoraleNewDay() { } if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { - Morale.makeMoraleChecks(this, true); - Morale.makeMoraleChecks(this, false); + if ((campaignOptions.isUseDesertions()) && (getLocation().isOnPlanet())) { + Morale.makeMoraleChecks(this, true); + } + + if (campaignOptions.isUseMutinies()) { + Morale.makeMoraleChecks(this, false); + } if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { Morale.processMoraleRecovery(this, 1); } - - } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 0d96bde450..5f2f429dab 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -312,7 +312,7 @@ public static String getTransitUnitName(final int unit) { private Integer fatigueLeaveThreshold; private boolean useMorale; - private Double stepSize; + private Double MoraleStepSize; private boolean useDesertions; private boolean useMutinies; private ForceReliabilityMethod forceReliabilityMethod; @@ -1002,7 +1002,7 @@ public CampaignOptions() { setFatigueLeaveThreshold(13); setUseMorale(true); - setStepSize(0.1); + setMoraleStepSize(0.1); setForceReliabilityMethod(ForceReliabilityMethod.LOYALTY); setUseDesertions(true); setUseSabotage(true); @@ -1569,12 +1569,12 @@ public void setUseMorale(final boolean useMorale) { this.useMorale = useMorale; } - public double getStepSize() { - return stepSize; + public double getMoraleStepSize() { + return MoraleStepSize; } - public void setStepSize(final double stepSize) { - this.stepSize = stepSize; + public void setMoraleStepSize(final double moraleStepSize) { + this.MoraleStepSize = moraleStepSize; } public ForceReliabilityMethod getForceReliabilityMethod() { @@ -4725,7 +4725,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigueLeaveThreshold", getFatigueLeaveThreshold()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMorale", isUseMorale()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "stepSize", getStepSize()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "moraleStepSize", getMoraleStepSize()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "forceReliabilityMethod", getForceReliabilityMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useDesertions", isUseDesertions()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); @@ -5739,8 +5739,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setFatigueLeaveThreshold(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMorale")) { retVal.setUseMorale(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("stepSize")) { - retVal.setStepSize(Double.parseDouble(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("moraleStepSize")) { + retVal.setMoraleStepSize(Double.parseDouble(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("forceReliabilityMethod")) { retVal.setForceReliabilityMethod(ForceReliabilityMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useDesertions")) { diff --git a/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java b/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java index bca9b91e87..525a5d2aaa 100644 --- a/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java @@ -67,6 +67,9 @@ public class GMToolsDialog extends AbstractMHQDialog { private JTabbedPane tabbedPane; //region General Tab + // General Tools Panel + private JSpinner spnMorale; + // Dice Panel private JSpinner spnDiceCount; private JSpinner spnDiceNumber; @@ -185,6 +188,14 @@ public void setSpnDiceSides(final JSpinner spnDiceSides) { this.spnDiceSides = spnDiceSides; } + public JSpinner getSpnMorale() { + return spnMorale; + } + + public void setSpnMorale(final JSpinner spnMorale) { + this.spnMorale = spnMorale; + } + public JLabel getLblTotalDiceResult() { return lblTotalDiceResult; } @@ -487,6 +498,8 @@ protected Container createCenterPane() { //region General Tab private JScrollPane createGeneralTab() { // Create Panel Components + final JPanel generalToolsPanel = createGeneralToolsPanel(); + final JPanel dicePanel = createDicePanel(); final JPanel ratPanel = createRATPanel(); @@ -501,12 +514,14 @@ private JScrollPane createGeneralTab() { layout.setVerticalGroup( layout.createSequentialGroup() + .addComponent(generalToolsPanel) .addComponent(dicePanel) .addComponent(ratPanel) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) + .addComponent(generalToolsPanel) .addComponent(dicePanel) .addComponent(ratPanel) ); @@ -706,6 +721,40 @@ public void mouseClicked(final MouseEvent evt) { return panel; } + + private JPanel createGeneralToolsPanel() { + // Create the Panel + final JPanel panel = new JPanel(new GridBagLayout()); + panel.setBorder(BorderFactory.createTitledBorder("generalTools.title")); + panel.setName("generalToolsPanel"); + + // Create the Constraints + final GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(0, 3, 0, 3); + + final int maxGridX; + + // Create the Components and Layout + final JLabel lblMorale = new JLabel(resources.getString("lblMorale.text")); + lblMorale.setName("lblMorale"); + panel.add(lblMorale, gbc); + + setSpnMorale(new JSpinner(new SpinnerNumberModel(gui.getCampaign().getMorale(), 1.0, 7.0, gui.getCampaign().getCampaignOptions().getMoraleStepSize()))); + gbc.gridx++; + getSpnMorale().setName("spnMorale"); + panel.add(getSpnMorale(), gbc); + + final JButton btnMorale = new MMButton("btnMorale", resources, "btnMorale.text", + "btnMorale.toolTipText", evt -> gui.getCampaign().setMorale((double) spnMorale.getValue())); + gbc.gridx++; + panel.add(btnMorale, gbc); + + return panel; + } //endregion General Tab //region Names Tab diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 92c0d44f8f..68380b9ecd 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -332,8 +332,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseMorale; private JPanel moraleSubPanel = new JPanel(); - private JLabel lblStepSize; - private JSpinner spnStepSize; + private JLabel lblMoraleStepSize; + private JSpinner spnMoraleStepSize; private JCheckBox chkUseDesertions; private JLabel lblForceReliabilityMethod; private MMComboBox comboForceReliabilityMethod; @@ -4892,15 +4892,15 @@ private JPanel createTurnoverAndRetentionMoralePanel() { private void createMoraleSubPanel() { boolean isUseMorale = campaign.getCampaignOptions().isUseMorale(); - lblStepSize = new JLabel(resources.getString("lblStepSize.text")); - lblStepSize.setToolTipText(resources.getString("lblStepSize.toolTipText")); - lblStepSize.setName("lblStepSize"); - lblStepSize.setEnabled(isUseMorale); + lblMoraleStepSize = new JLabel(resources.getString("lblMoraleStepSize.text")); + lblMoraleStepSize.setToolTipText(resources.getString("lblMoraleStepSize.toolTipText")); + lblMoraleStepSize.setName("lblMoraleStepSize"); + lblMoraleStepSize.setEnabled(isUseMorale); - spnStepSize = new JSpinner(new SpinnerNumberModel(0.1, 0.1, 2, 0.1)); - spnStepSize.setToolTipText(resources.getString("lblStepSize.toolTipText")); - spnStepSize.setName("spnStepSize"); - spnStepSize.setEnabled(isUseMorale); + spnMoraleStepSize = new JSpinner(new SpinnerNumberModel(0.1, 0.1, 2, 0.1)); + spnMoraleStepSize.setToolTipText(resources.getString("lblMoraleStepSize.toolTipText")); + spnMoraleStepSize.setName("spnMoraleStepSize"); + spnMoraleStepSize.setEnabled(isUseMorale); lblForceReliabilityMethod = new JLabel(resources.getString("lblForceReliabilityMethod.text")); lblForceReliabilityMethod.setToolTipText(resources.getString("lblForceReliabilityMethod.toolTipText")); @@ -4969,7 +4969,7 @@ public Component getListCellRendererComponent(final JList list, final Object spnTheftValue = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); spnTheftValue.setToolTipText(resources.getString("lblTheftValue.toolTipText")); - spnTheftValue.setName("spnStepSize"); + spnTheftValue.setName("spnTheftValue"); spnTheftValue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); moraleSubPanel.setName("moraleSubPanel"); @@ -4986,8 +4986,8 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseDesertions) .addComponent(chkUseMutinies) .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblStepSize) - .addComponent(spnStepSize, Alignment.LEADING)) + .addComponent(lblMoraleStepSize) + .addComponent(spnMoraleStepSize, Alignment.LEADING)) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) @@ -5004,8 +5004,8 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseDesertions) .addComponent(chkUseMutinies) .addGroup(layout.createSequentialGroup() - .addComponent(lblStepSize) - .addComponent(spnStepSize)) + .addComponent(lblMoraleStepSize) + .addComponent(spnMoraleStepSize)) .addGroup(layout.createSequentialGroup() .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod)) @@ -8273,7 +8273,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseDesertions.setSelected(options.isUseDesertions()); chkUseMutinies.setSelected(options.isUseMutinies()); - spnStepSize.setValue(options.getStepSize()); + spnMoraleStepSize.setValue(options.getMoraleStepSize()); comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); chkUseSabotage.setSelected(options.isUseSabotage()); chkUseTheftUnit.setSelected(options.isUseTheftUnit()); @@ -8968,7 +8968,7 @@ public void updateOptions() { options.setUseDesertions(chkUseDesertions.isSelected()); options.setUseMutinies(chkUseMutinies.isSelected()); - options.setStepSize((Double) spnStepSize.getValue()); + options.setMoraleStepSize((Double) spnMoraleStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); options.setUseSabotage(chkUseSabotage.isSelected()); options.setUseTheftUnit(chkUseTheftUnit.isSelected()); From 2047a95d6eaccfcbb36597c4304b2440062d0214 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Thu, 23 May 2024 15:28:41 -0500 Subject: [PATCH 054/101] Refactored morale checks and Mutiny system in the campaign This commit introduces significant changes to morale checks and Mutiny system in the campaign, enhancing the behavior of these features. The morale checks have been refactored to ensure accurate processing, and the improved mutiny system now includes a rebel leader determining logic, alongside enabling theft checks for mutinies. This update also amends some related GUI behavior and text descriptions for more accurate notifications and user prompts. --- .../mekhq/resources/Morale.properties | 20 +- .../mekhq/resources/Personnel.properties | 4 +- MekHQ/src/mekhq/campaign/Campaign.java | 56 ++- .../turnoverAndRetention/Morale.java | 324 +++++++++++------- .../mekhq/gui/dialog/MutinySupportDialog.java | 106 ++++-- .../mekhq/gui/panes/CampaignOptionsPane.java | 20 +- 6 files changed, 336 insertions(+), 194 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index eda59e5ca3..7b33f666ce 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -8,14 +8,17 @@ moraleLevelVeryLow.text=Very Low moraleLevelBroken.text=Broken # Morale Report -moraleReport.text=Morale is now %s. -moraleReportLow.text=Caution, morale is now %s. +moraleReport.text=Morale is %s. +moraleReportLow.text=Caution, morale is %s. moraleReportMutiny.text=Your personnel are actively planning a coup. moraleReportRecovered.text=Your personnel are placated, for now. +# Desertion +desertionAwolExtended.text=has extended their unauthorized leave. + # Theft -desertionTheftUnit.text=has stolen %s. -desertionTheftUnitBlackMarket.text=has stolen %s and is trying to sell it on the Black Market. +desertionTheft.text=has stolen %s. +desertionTheftBlackMarket.text=has stolen %s and is trying to sell it on the Black Market. desertionTheftMoney.text=The company accountants report an unusual transaction of %s c-bills. desertionTheftTransactionReport.text=Screw you, Commander @@ -28,14 +31,15 @@ dialogSupportLoyalists.text=Support Loyalists dialogSupportRebels.text=Support Rebels dialogTitle.text=Betrayal! -dialogDescriptionIntroduction.text=
Your Commander's poor leadership has given your personnel no other choice. -dialogDescriptionViolentTakeover.text=They have drawn arms against them and are demanding control of the company.

-dialogDescriptionRegimeChange.text=They are demanding the Commander be removed from power and will fight to ensure this happens.

+dialogDescriptionIntroduction.text=
%s's poor leadership has given the personnel of %s no other choice.

+dialogDescriptionViolentTakeover.text=Led by %s, they have drawn arms against %s and are demanding control of the company.

+dialogDescriptionRegimeChange.text=Led by %s, the rebels are demanding the commander be removed from power and will fight to ensure this happens.

dialogDescriptionLoyalist.text=Loyalist dialogDescriptionRebels.text=Rebel dialogDescriptionForces.text=The %s faction is supported by %s personnel, %s Meks, %s Fighters, %s ProtoMeks, %s units of Battle Armor, %s DropShips,\ \ %s units of Infantry, %s Vehicles, and %s other units (estimated %s BV).

-dialogDescriptionDecision.text=You must choose whether to support the Loyalists or the Rebels.\ +dialogDescriptionBystanders.text=%s personnel have abstained from the conflict.

+dialogDescriptionDecision.text=You must choose whether to support the Loyalists or the Rebels.

\ \ WARNING: once you have made your decision, there is no turning back.
# Mutiny Battle diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index 7ad0f58bfb..a94d260937 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -308,8 +308,8 @@ PersonnelStatus.ON_LEAVE.toolTipText=They are currently on leave from the force. PersonnelStatus.ON_LEAVE.reportText=%s has gone on leave from the force PersonnelStatus.ON_LEAVE.logText=Went on leave PersonnelStatus.AWOL.text=AWOL -PersonnelStatus.AWOL.toolTipText=They are currently away from the force without permission. -PersonnelStatus.AWOL.reportText=%s has gone on leave from the force without permission +PersonnelStatus.AWOL.toolTipText=They have abandoned their post. +PersonnelStatus.AWOL.reportText=%s has abandoned their post. PersonnelStatus.AWOL.logText=Went AWOL PersonnelStatus.RETIRED.text=Retired PersonnelStatus.RETIRED.toolTipText=They have retired from the force. diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 3de0bf5d7a..dbd7491173 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3564,16 +3564,37 @@ private void processMoraleNewDay() { } if (getLocalDate().getDayOfWeek().equals(DayOfWeek.MONDAY)) { + boolean desertionEvent = false; + boolean mutinyEvent = false; + + // these are the morale checks and dictate whether personnel mutiny or desert if ((campaignOptions.isUseDesertions()) && (getLocation().isOnPlanet())) { - Morale.makeMoraleChecks(this, true); + desertionEvent = Morale.makeMoraleChecks(this, true); } if (campaignOptions.isUseMutinies()) { - Morale.makeMoraleChecks(this, false); + mutinyEvent = Morale.makeMoraleChecks(this, false); } - if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleRecovery(this, 1); + // this processes morale recovery based on campaign location and the results of the prior checks + if (desertionEvent && mutinyEvent) { + if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { + Morale.processMoraleChange(this, -2.0); + } else { + Morale.processMoraleChange(this, -3.0); + } + } else if (desertionEvent) { + if ((!getActiveContracts().isEmpty()) || (!getLocation().isOnPlanet())) { + Morale.processMoraleChange(this, -1.0); + } + } else if (mutinyEvent) { + if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { + Morale.processMoraleChange(this, -1.0); + } else { + Morale.processMoraleChange(this, -2.0); + } + } else { + Morale.processMoraleChange(this, 1.0); } } } @@ -4506,6 +4527,33 @@ public void setRankSystem(final @Nullable RankSystem rankSystem) { public void setRankSystemDirect(final RankSystem rankSystem) { this.rankSystem = rankSystem; } + + /** + * Returns the highest ranked person from the given list of personnel. + * + * @param personnel the list of personnel from which to find the highest ranked person + * @param useSkillTiebreaker determines whether to use a experience level tiebreaker when comparing ranks + * (true - use skill tiebreaker, false - do not use skill tiebreaker) + * @return the highest ranked person from the list, or null if the provided personnel list is empty + */ + public Person getHighestRankedPerson(List personnel, boolean useSkillTiebreaker) { + Person highestRankedPerson = null; + + if (!personnel.isEmpty()) { + for (Person person : personnel) { + if (useSkillTiebreaker) { + if (person.outRanksUsingSkillTiebreaker(this, highestRankedPerson)) { + highestRankedPerson = person; + } + } else { + if (person.outRanks(highestRankedPerson)) { + highestRankedPerson = person; + } + } + } + } + return highestRankedPerson; + } //endregion Ranks public void setFinances(Finances f) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index d16e54899f..974172aacd 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -20,6 +20,8 @@ import java.util.*; import java.util.stream.Collectors; +import static megamek.common.EntityWeightClass.WEIGHT_LARGE_WAR; + public class Morale { /** @@ -357,19 +359,19 @@ public static void getMoraleReport(Campaign campaign) { campaign.addReport(moraleReport.toString()); } - public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { + public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { - return; + return isDesertion; } else if ((isDesertion) && (!campaign.getLocation().isOnPlanet())) { - return; + return isDesertion; } else if ((!isDesertion) && (!campaign.getCampaignOptions().isUseMutinies())) { - return; + return isDesertion; } double targetNumber = getTargetNumber(campaign, isDesertion); if (targetNumber == 0) { - return; + return isDesertion; } final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", @@ -405,9 +407,23 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { boolean someoneHasMutinied = false; List loyalists = new ArrayList<>(); + Person loyalistLeader = campaign.getFlaggedCommander(); + + // if there is no Commander, we assume the highest ranked person is the loyalist leader + if (loyalistLeader == null) { + campaign.getHighestRankedPerson(filteredPersonnel, true); + } + + Person rebelLeader; HashMap rebels = new HashMap<>(); for (Person person : filteredPersonnel) { + // the loyalistLeader can't rebel against themselves + if (Objects.equals(person, loyalistLeader)) { + loyalists.add(person); + continue; + } + int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, loyalty); int firstRoll = Compute.d6(2) + modifier; @@ -419,7 +435,7 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { if ((firstRoll <= targetNumber) && (secondRoll <= targetNumber)) { if (isDesertion) { - if ((processDesertion(campaign, person, secondRoll, targetNumber, morale, theftTargets, resources)) + if ((processDesertion(campaign, person, secondRoll, targetNumber, theftTargets, resources)) && (!someoneHasDeserted)) { someoneHasDeserted = true; } @@ -433,10 +449,10 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { } if (someoneHasMutinied) { - processMoraleLoss(campaign, -2); - processMutiny(campaign, loyalists, rebels, theftTargets, resources); - } else if (someoneHasDeserted) { - processMoraleLoss(campaign, -1); + processMutiny(campaign, loyalistLeader, loyalists, rebels, theftTargets, resources); + return true; + } else { + return someoneHasDeserted; } } @@ -446,37 +462,73 @@ public static void makeMoraleChecks(Campaign campaign, boolean isDesertion) { * @param campaign the current campaign * @param person the potential deserter * @param roll the desertion roll result - * @param targetNumber the target number for desertion - * @param morale the morale value + * @param targetNumber the target number for desertion= * @param unitList the list of units * @param resources the resource bundle for localized messages * @return true if desertion occurred, false otherwise */ private static boolean processDesertion(Campaign campaign, Person person, int roll, double targetNumber, - double morale, ArrayList unitList, ResourceBundle resources) { - if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { - morale -= 1; - } + ArrayList unitList, ResourceBundle resources) { + double morale = campaign.getMorale(); if (roll <= (targetNumber - 2)) { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); - - if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!unitList.isEmpty())) { - processUnitTheft(campaign, unitList, resources); - } else if ((roll <= (morale - 3)) && (campaign.getCampaignOptions().isUseTheftMoney())) { - processMoneyTheft(campaign, resources); - } else if (roll <= (morale - 2)) { - processPettyTheft(campaign, resources); - } else { + if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { + morale -= 1; + } + + if (roll <= (morale - 2)) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); + + // reclaim original unit (if available) + if (person.getOriginalUnitId() != null) { + morale = reclaimOriginalUnit(campaign, person); + } + + // check for theft + if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!unitList.isEmpty())) { + processUnitTheft(campaign, unitList, resources); + } else if ((roll <= (morale - 3)) && (campaign.getCampaignOptions().isUseTheftMoney())) { + processMoneyTheft(campaign, resources); + } else if (roll <= (morale - 2)) { + processPettyTheft(campaign, resources); + } - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); - person.setAwolDays(Compute.d6(2)); + return true; } + } - return true; - } else { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); + person.setAwolDays(Compute.d6(2)); + + return false; + } + + /** + * Reclaims the original unit for a person in a campaign. + * If the unit is no longer available, return an integer used to reduce effective morale. + * + * @param campaign The campaign the person is participating in + * @param person The person whose original unit is to be reclaimed + * @return 0 if the original unit is removed successfully, or an integer if removal fails + */ + private static double reclaimOriginalUnit(Campaign campaign, Person person) { + UUID originalUnitId = person.getOriginalUnitId(); + int originalUnitWeight = person.getOriginalUnitWeight(); + + // this stops support vehicles being over-valued + if (originalUnitWeight > WEIGHT_LARGE_WAR) { + originalUnitWeight -= WEIGHT_LARGE_WAR; + } - return false; + if (!campaign.getUnit(originalUnitId).isDeployed()) { + try { + campaign.getHangar().removeUnit(person.getOriginalUnitId()); + return 0; + } catch (Exception e) { + return originalUnitWeight; + } + } else { + return originalUnitWeight; } } @@ -500,9 +552,9 @@ private static void processUnitTheft(Campaign campaign, ArrayList unitList campaign.getUnitMarket().addOffers(campaign, 1, UnitMarketType.BLACK_MARKET, desiredUnit.getEntity().getUnitType(), campaign.getFaction(), desiredUnit.getQuality(), 6); - campaign.addReport(String.format(resources.getString("desertionTheftUnitBlackMarket.text"), unitName)); + campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), unitName)); } else { - campaign.addReport(String.format(resources.getString("desertionTheftUnit.text"), unitName)); + campaign.addReport(String.format(resources.getString("desertionTheft.text"), unitName)); } campaign.removeUnit(desiredUnit.getId()); @@ -667,7 +719,7 @@ private static void processPettyTheft(Campaign campaign, ResourceBundle resource "mug.text", "toiletSeats.text"); - campaign.addReport(String.format(resources.getString("desertionStolen.text"), new Random().nextInt(items.size()))); + campaign.addReport(String.format(resources.getString("desertionTheft.text"), new Random().nextInt(items.size()))); } /** @@ -680,63 +732,57 @@ private static void processPettyTheft(Campaign campaign, ResourceBundle resource * @param person the person for whom AWOL days are being processed */ public static void processAwolDays(Campaign campaign, Person person) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + int awolDays = person.getAwolDays(); if (awolDays == 0) { if (Compute.d6(1) <= 2) { person.setAwolDays(awolDays + Compute.d6(1)); + campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("desertionAwolExtended.text")); } else { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.ACTIVE); } + } else if (awolDays > 0) { + person.setAwolDays(awolDays - 1); } else { person.setAwolDays(awolDays - 1); } } /** - * Process morale recovery for a campaign by decreasing the morale value based on the given number of steps. - * If the current morale value is not equal to 1, the morale value will be updated by subtracting the 'steps' value. - * The updated morale value will be clamped within the range of 1 to 7. - * After the morale value is updated, the morale report will be generated. + * The method processes the morale change in a campaign. * - * @param campaign the Campaign object representing the campaign to process morale loss for - * @param steps the Integer value representing the number of steps to decrease morale by + * @param campaign the campaign to process the morale change for + * @param steps the number of steps to change the morale by */ - public static void processMoraleRecovery(Campaign campaign, Integer steps) { + public static void processMoraleChange(Campaign campaign, double steps) { final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); double morale = campaign.getMorale(); + double change = campaign.getCampaignOptions().getMoraleStepSize() * steps; - if (morale != 1) { - campaign.setMorale(MathUtility.clamp(morale - (steps * campaign.getCampaignOptions().getStepSize()), 1.0, 7.0)); + if ((change > 0) && (morale != 1)) { + campaign.setMorale(MathUtility.clamp(morale - (steps * campaign.getCampaignOptions().getMoraleStepSize()), 1.0, 7.0)); getMoraleReport(campaign); if ((morale >= 5) && ((morale + steps) < 5)) { campaign.addReport(resources.getString("moraleReportRecovered.text")); } - } - } - - /** - * Process morale loss for a campaign by increasing the morale value based on the given number of steps. - * If the current morale value is not equal to 7, the morale value will be updated by adding the 'steps' value. - * The updated morale value will be clamped within the range of 1 to 7. - * After the morale value is updated, the morale report will be generated. - * - * @param campaign the Campaign object representing the campaign to process morale loss for - * @param steps the Integer value representing the number of steps to increase morale by - */ - public static void processMoraleLoss(Campaign campaign, Integer steps) { - double morale = campaign.getMorale(); - - if (morale != 7) { - campaign.setMorale(MathUtility.clamp(morale + (steps * campaign.getCampaignOptions().getStepSize()), 1.0, 7.0)); + } else if ((change < 0) && (morale != 7)) { + campaign.setMorale(MathUtility.clamp(morale + change, 1.0, 7.0)); getMoraleReport(campaign); } } - private static void processMutiny(Campaign campaign, List loyalists, HashMap rebels, ArrayList unitList, ResourceBundle resources) { + private static void processMutiny(Campaign campaign, + Person loyalistLeader, List loyalists, + HashMap rebels, + ArrayList unitList, + ResourceBundle resources) { + // This prevents us from needing to do the full process for tiny mutinies that have no chance of success if ((loyalists.size() / 10) > rebels.size()) { if (rebels.size() > 1) { @@ -746,23 +792,42 @@ private static void processMutiny(Campaign campaign, List loyalists, Has } for (Person person : rebels.keySet()) { - processDesertion(campaign, person, rebels.get(person), getTargetNumber(campaign, true), campaign.getMorale(), unitList, resources); + processDesertion(campaign, person, rebels.get(person), getTargetNumber(campaign, true), unitList, resources); } return; } // A civil war breaks out. - // Everyone is forced to pick sides. + List bystanders = new ArrayList<>(); // The rebels have already picked their side, so we only need to process the loyalists - for (Person person : loyalists) { - if (Compute.d6(1) < getCivilWarTargetNumber(campaign, person)) { - loyalists.remove(person); + Iterator iterator = loyalists.iterator(); + + while (iterator.hasNext()) { + Person person = iterator.next(); + + int roll = Compute.d6(1); + int civilWarTargetNumber = getCivilWarTargetNumber(campaign, person); + + if ((roll >= (civilWarTargetNumber - 1)) && + (roll <= (civilWarTargetNumber + 1))) { + + iterator.remove(); + bystanders.add(person); + } + else if (roll < (civilWarTargetNumber - 1)) { + + iterator.remove(); rebels.put(person, 0); } } + // We now need to determine the leader of the rebels. + // This might not always be someone who initially joined the mutiny, + // in which case we can assume they were persuaded into the role by the original mutineers + Person rebelLeader = getRebelLeader(campaign, new ArrayList<>(rebels.keySet())); + // with the line drawn in the sand, we need to assess the forces available to each side HashMap rebelUnits = getUnits(new ArrayList<>(rebels.keySet()), false); int rebelBv = rebelUnits.keySet().stream() @@ -778,88 +843,81 @@ private static void processMutiny(Campaign campaign, List loyalists, Has int supportDecision = -1; while (supportDecision == -1) { - supportDecision = MutinySupportDialog.SupportDialog( - resources, false, - loyalists.size(), new ArrayList<>(loyalUnits.keySet()), loyalistBv, - rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv + supportDecision = MutinySupportDialog.supportDialog( + campaign, resources, false, + bystanders.size(), + loyalistLeader, loyalists.size(), new ArrayList<>(loyalUnits.keySet()), loyalistBv, + rebelLeader, rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv ); } } - private static void processMutinyBattle(Campaign campaign, - List loyalists, List loyalistForces, Integer loyalistBv, - List rebels, List rebelForces, Integer rebelBv, - ResourceBundle resources) { - - // start by calculating combat statistics - HashMap abstractBattleStatistics = getAbstractBattleStatistics(loyalists, loyalistForces.size(), loyalistBv); - int loyalistAttackDice = abstractBattleStatistics.get("attackDice"); - int loyalistDefenceDice = abstractBattleStatistics.get("defenceDice"); - - abstractBattleStatistics = getAbstractBattleStatistics(rebels, rebelForces.size(), rebelBv); - int rebelAttackDice = abstractBattleStatistics.get("attackDice"); - int rebelDefenceDice = abstractBattleStatistics.get("defenceDice"); - - // next we perform three-four rounds of combat: the opening engagement, the brawl, the bitter end. - int attrition = 0; - int loyalistVictoryPoints = 0; - int rebelVictoryPoints = 0; - - // the opening engagement - HashMap engagement = processEngagement(loyalistAttackDice, loyalistDefenceDice, rebelAttackDice, rebelDefenceDice); - attrition += engagement.get("attrition"); - - if (engagement.get("loyalistVictory") == 1) { - loyalistVictoryPoints++; - } else { - rebelVictoryPoints++; - } - - // the brawl - engagement = processEngagement(loyalistAttackDice, loyalistDefenceDice, rebelAttackDice, rebelDefenceDice); - attrition += engagement.get("attrition"); - - if (engagement.get("loyalistVictory") == 1) { - loyalistVictoryPoints++; - } else { - rebelVictoryPoints++; - } + /** + * This method is used to determine the rebel leader based on the given campaign and rebel information. + * + * @param campaign The current campaign. + * @param rebels The list of rebels. + * @return The person object representing the rebel leader. + */ + private static Person getRebelLeader(Campaign campaign, List rebels) { + Person rebelLeader = null; - // the bitter end - engagement = processEngagement(loyalistAttackDice, loyalistDefenceDice, rebelAttackDice, rebelDefenceDice); - attrition += engagement.get("attrition"); + for (Person person : rebels) { + if (rebelLeader == null) { + rebelLeader = person; + continue; + } - if (engagement.get("loyalistVictory") == 1) { - loyalistVictoryPoints++; - } else { - rebelVictoryPoints++; + int oldRankNumeric = getAdjustedRankNumeric(campaign, rebelLeader); + int newRankNumeric = getAdjustedRankNumeric(campaign, person); + + if (newRankNumeric == oldRankNumeric) { + // in the case of a tie, we use the negotiation skill + if ((person.hasSkill(SkillType.S_NEG)) && (Objects.requireNonNull(rebelLeader).hasSkill(SkillType.S_NEG))) { + if (person.getSkillLevel(SkillType.S_NEG) > rebelLeader.getSkillLevel(SkillType.S_NEG)) { + rebelLeader = person; + } else if (person.getSkillLevel(SkillType.S_NEG) == rebelLeader.getSkillLevel(SkillType.S_NEG)) { + // if we still have a tie, we use overall experience level. + // if this fails to break the tie, we give up and just use the new person + if (person.getExperienceLevel(campaign, false) > rebelLeader.getExperienceLevel(campaign, false)) { + rebelLeader = person; + } else if (person.getExperienceLevel(campaign, false) == rebelLeader.getExperienceLevel(campaign, false)) { + rebelLeader = person; + } + } + } else if (person.hasSkill(SkillType.S_NEG)) { + rebelLeader = person; + } + } else if (newRankNumeric > oldRankNumeric) { + rebelLeader = person; + } } + return rebelLeader; + } - // results - HashMap loyalistForceDamage = mapForceDamage(loyalistForces.size(), attrition, rebelAttackDice, loyalistDefenceDice); - - for (int damagedUnit = 0; damagedUnit < loyalistForceDamage.get("damagedLight"); damagedUnit++) {} - - // process attrition (units outright destroyed) - for (int destroyedUnit = 0; destroyedUnit < loyalistForceDamage.get("attrition"); destroyedUnit++) { - Unit unit = loyalistForces.get(new Random().nextInt(loyalistForces.size())); + /** + * Returns the adjusted rank numeric for a person. + * + * @param campaign the current campaign + * @param person the person whose rank numeric is being calculated + * @return the adjusted numeric rank of the person + */ + private static Integer getAdjustedRankNumeric(Campaign campaign, Person person) { + int rankNumeric = person.getRankNumeric(); - campaign.addReport(unit.getName() + ' ' + String.format(resources.getString("battleUnitDestroyed.text"))); + if (person.hasSkill(SkillType.S_LEADER)) { + rankNumeric = person.getSkillLevel(SkillType.S_LEADER); - for (Person person : unit.getCrew()) { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); - loyalists.remove(person); + if (campaign.getCampaignOptions().isUseManagementSkill()) { + rankNumeric += campaign.getCampaignOptions().getManagementSkillPenalty(); } - - campaign.removeUnit(unit.getId()); - loyalistForces.remove(unit); } - // process ammo usage - getAmmoUsage(loyalistForces, attrition); - getAmmoUsage(rebelForces, attrition); + if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { + rankNumeric += person.getLoyalty(); + } - // TODO damage + return rankNumeric; } /** diff --git a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java index 10bbfd5da9..f2c1084ef9 100644 --- a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java @@ -1,12 +1,15 @@ package mekhq.gui.dialog; import megamek.common.Entity; +import mekhq.campaign.Campaign; +import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.Unit; import javax.swing.*; -import java.util.HashMap; +import java.awt.*; +import java.util.*; import java.util.List; -import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; public class MutinySupportDialog { /** @@ -21,45 +24,75 @@ public class MutinySupportDialog { * - 0 if the user chooses to support the rebels, * - -1 if the user cancels the dialog */ - public static int SupportDialog(ResourceBundle resources, boolean isViolentRebellion, - Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, - Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { - - Object[] options = { resources.getString("dialogSupportLoyalists.text"), resources.getString("dialogSupportRebels.text") }; - - int choice = JOptionPane.showOptionDialog(null, - buildSituationDescription( - resources, isViolentRebellion, - loyalistPersonnelCount, loyalistForces, loyalistBv, - rebelPersonnelCount, rebelForces, rebelBv - ), - resources.getString("dialogTitle.text"), - - JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, + public static int supportDialog(Campaign campaign, ResourceBundle resources, boolean isViolentRebellion, + Integer bystanderPersonnelCount, + Person loyalistLeader, Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, + Person rebelLeader, Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { + // Initialize a JOptionPane + AtomicInteger choice = new AtomicInteger(-1); + JOptionPane pane = new JOptionPane(buildMutinyDescription( + campaign, resources, isViolentRebellion, + bystanderPersonnelCount, + loyalistLeader, loyalistPersonnelCount, loyalistForces, loyalistBv, + rebelLeader, rebelPersonnelCount, rebelForces, rebelBv + ), + JOptionPane.QUESTION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}, null); + + Object[] options = { + resources.getString("dialogSupportLoyalists.text"), + resources.getString("dialogSupportRebels.text") + }; + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + // buttonPanel.add(Box.createHorizontalGlue()); + + for (Object option : options){ + JButton button = new JButton(option.toString()); + button.addActionListener(e -> { + choice.set(Arrays.asList(options).indexOf(option)); + + Window window = SwingUtilities.getWindowAncestor(button); + + if (window != null) { + window.setVisible(false); + } + }); + + buttonPanel.add(button); + buttonPanel.add(Box.createHorizontalStrut(15)); + } - null, + pane.setOptions(new Object[]{buttonPanel}); - options, options[1]); + JDialog dialog = pane.createDialog(null, resources.getString("dialogTitle.text")); + dialog.setVisible(true); - if (choice == JOptionPane.YES_OPTION) { - return 1; - } else if (choice == JOptionPane.NO_OPTION) { - return 0; - } else { - return -1; - } + return choice.get(); } - private static String buildSituationDescription(ResourceBundle resources, boolean isViolentRebellion, - Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, - Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { + private static String buildMutinyDescription(Campaign campaign, ResourceBundle resources, boolean isViolentRebellion, + Integer bystanderPersonnelCount, + Person loyalistLeader, Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, + Person rebelLeader, Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { - StringBuilder situationDescription = new StringBuilder(resources.getString("dialogDescriptionIntroduction.text")); + StringBuilder situationDescription = new StringBuilder(String.format( + resources.getString("dialogDescriptionIntroduction.text"), + loyalistLeader.getFullTitle(), + campaign.getName()) + ); if (isViolentRebellion) { - situationDescription.append(' ').append(resources.getString("dialogDescriptionViolentTakeover.text")); + situationDescription.append(' ').append(String.format( + resources.getString("dialogDescriptionViolentTakeover.text"), + rebelLeader.getFullTitle() + )); } else { - situationDescription.append(' ').append(resources.getString("dialogDescriptionRegimeChange.text")); + situationDescription.append(' ').append(String.format( + resources.getString("dialogDescriptionRegimeChange.text"), + rebelLeader.getFullTitle()) + ); } HashMap unitMap = mapUnitCounts(loyalistForces); @@ -68,6 +101,8 @@ private static String buildSituationDescription(ResourceBundle resources, boolea unitMap = mapUnitCounts(rebelForces); situationDescription.append(getForceSummaryString(resources, unitMap, false, rebelBv, rebelPersonnelCount)); + situationDescription.append(String.format(resources.getString("dialogDescriptionBystanders.text"), bystanderPersonnelCount)); + situationDescription.append(resources.getString("dialogDescriptionDecision.text")); return situationDescription.toString(); @@ -122,6 +157,7 @@ private static HashMap mapUnitCounts(List units) { unitCounts.put("vehicle", vehicleCount); unitCounts.put("other", otherCount); + return unitCounts; } @@ -135,7 +171,7 @@ private static HashMap mapUnitCounts(List units) { * @param personnelCount the number of personnel supporting the faction * @return the formatted summary string */ - private static String getForceSummaryString(ResourceBundle resources, HashMap unitMap, boolean isLoyalist, Integer bv, Integer personnelCount) { + private static String getForceSummaryString(ResourceBundle resources, HashMap unitMap, boolean isLoyalist, Integer battleValue, Integer personnelCount) { String faction; if (isLoyalist) { @@ -154,7 +190,7 @@ private static String getForceSummaryString(ResourceBundle resources, HashMap list, final Object final boolean isEnabled = chkUseDesertions.isSelected(); chkUseMoraleTriggerDesertion.setEnabled(isEnabled); - chkUseTheftUnit.setEnabled(isEnabled); - chkUseTheftMoney.setEnabled(isEnabled); - lblTheftValue.setEnabled(isEnabled); - spnTheftValue.setEnabled(isEnabled); }); chkUseMutinies = new JCheckBox(resources.getString("chkUseMutinies.text")); @@ -4955,22 +4951,22 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseTheftUnit = new JCheckBox(resources.getString("chkUseTheftUnit.text")); chkUseTheftUnit.setToolTipText(resources.getString("chkUseTheftUnit.toolTipText")); chkUseTheftUnit.setName("chkUseTheftUnit"); - chkUseTheftUnit.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + chkUseTheftUnit.setEnabled(isUseMorale); chkUseTheftMoney = new JCheckBox(resources.getString("chkUseTheftMoney.text")); chkUseTheftMoney.setToolTipText(resources.getString("chkUseTheftMoney.toolTipText")); chkUseTheftMoney.setName("chkUseTheftMoney"); - chkUseTheftMoney.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + chkUseTheftMoney.setEnabled(isUseMorale); lblTheftValue = new JLabel(resources.getString("lblTheftValue.text")); lblTheftValue.setToolTipText(resources.getString("lblTheftValue.toolTipText")); lblTheftValue.setName("lblTheftValue"); - lblTheftValue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + lblTheftValue.setEnabled(isUseMorale); spnTheftValue = new JSpinner(new SpinnerNumberModel(10, 1, 100, 1)); spnTheftValue.setToolTipText(resources.getString("lblTheftValue.toolTipText")); spnTheftValue.setName("spnTheftValue"); - spnTheftValue.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseDesertions())); + spnTheftValue.setEnabled(isUseMorale); moraleSubPanel.setName("moraleSubPanel"); moraleSubPanel.setBorder(BorderFactory.createTitledBorder("")); From 1285d6f02b0a30b7707d9cf4790fa42dd6aa21bb Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 24 May 2024 01:00:22 -0500 Subject: [PATCH 055/101] Updated the morale system and improved theft processing The morale system has been updated to better reflect ranges in the relevant documentation. It now ranges from 10 to 70, rather than 1 to 7. The theft processing now provides more detailed feedback, including the person committing the theft, and expanded random items for petty thefts. Minor code improvements are also included. --- .../CampaignOptionsDialog.properties | 4 +- .../mekhq/resources/Morale.properties | 226 +++++++++--------- .../mekhq/resources/Personnel.properties | 2 +- MekHQ/src/mekhq/campaign/Campaign.java | 20 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 40 ++-- .../mekhq/campaign/io/CampaignXmlParser.java | 2 +- .../turnoverAndRetention/Morale.java | 100 ++++---- MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java | 4 +- .../mekhq/gui/dialog/MutinySupportDialog.java | 93 +++++-- .../mekhq/gui/panes/CampaignOptionsPane.java | 8 +- 10 files changed, 289 insertions(+), 210 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 342a2ca296..502cd2e99c 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -308,9 +308,9 @@ chkUseMorale.text=Enable Morale chkUseMorale.toolTipText=Enables the morale system. This system is based on the CamOps Morale rules on p217.
Full documentation is available in MekHQ/docs/Turnover and Retention Module.pdf lblMoraleStepSize.text=Step Size -lblMoraleStepSize.toolTipText=How large should each morale adjustment be? CamOps uses step sizes of 1.0. +lblMoraleStepSize.toolTipText=How large should each morale adjustment be? CamOps uses step sizes of 10. lblForceReliabilityMethod.text=Force Reliability Method -lblForceReliabilityMethod.toolTipText=How should Force Reliability be calculated (referred to as 'Loyalty' on CamOps p217)? +lblForceReliabilityMethod.toolTipText=How should Force Reliability be calculated (referred to as 'Loyalty' on CamOps page 217)? chkUseDesertions.text=Enable Desertions chkUseDesertions.toolTipText=Low morale may cause personnel to randomly desert the company. chkUseMutinies.text=Enable Mutinies diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 7b33f666ce..c9ebf73661 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -19,8 +19,8 @@ desertionAwolExtended.text=has extended their unauthorized leave. # Theft desertionTheft.text=has stolen %s. desertionTheftBlackMarket.text=has stolen %s and is trying to sell it on the Black Market. -desertionTheftMoney.text=The company accountants report an unusual transaction of %s c-bills. -desertionTheftTransactionReport.text=Screw you, Commander +desertionTheftMoney.text=The company accountants report an unusual payment of %s c-bills to an off-planet account. +desertionTheftTransactionReport.text=Suspicious transfer of funds to %s # Mutiny mutinyThwartedSingular.text=An attempted coup has been thwarted. The would-be mutineer has fled the company. @@ -36,8 +36,17 @@ dialogDescriptionViolentTakeover.text=Led by %s, they have drawn arms aga dialogDescriptionRegimeChange.text=Led by %s, the rebels are demanding the commander be removed from power and will fight to ensure this happens.

dialogDescriptionLoyalist.text=Loyalist dialogDescriptionRebels.text=Rebel -dialogDescriptionForces.text=The %s faction is supported by %s personnel, %s Meks, %s Fighters, %s ProtoMeks, %s units of Battle Armor, %s DropShips,\ - \ %s units of Infantry, %s Vehicles, and %s other units (estimated %s BV).

+dialogDescriptionPluralizer.text=s +dialogDescriptionForces.text=The %s faction is supported by %s personnel +dialogDescriptionForcesMeks.text=, %s Mek%s +dialogDescriptionForcesFighters.text=, %s Fighter%s +dialogDescriptionForcesProtoMechs.text=, %s ProtoMek%s +dialogDescriptionForcesBattleArmor.text=, %s unit%s of Battle Armor +dialogDescriptionForcesVehicles.text=, %s Vehicle%s +dialogDescriptionForcesInfantry.text=, %s unit%s of Infantry +dialogDescriptionForcesDropShips.text=, %s DropShip%s +dialogDescriptionForcesOther.text=, %s other unit%s +dialogDescriptionForcesBv.text=(estimated %s BV).

dialogDescriptionBystanders.text=%s personnel have abstained from the conflict.

dialogDescriptionDecision.text=You must choose whether to support the Loyalists or the Rebels.

\ \ WARNING: once you have made your decision, there is no turning back.
@@ -46,107 +55,108 @@ dialogDescriptionDecision.text=You must choose whether to support the Loyalists battleUnitDestroyed.text=was completely destroyed, all personnel were killed outright. # Petty Theft Table -officeSupplies.text=A number of random office supplies -mascot.text=A company mascot -phones.text=The office phones -tablets.text=A number of tablets -hardDrives.text=Some important hard drives -flashDrive.text=An incriminating flash drive -companyCreditCard.text=The company credit card -officePet.text=An office pet -confidentialReports.text=A number of confidential reports -clientLists.text=Historic client lists -unitSchematics.text=Unit schematics -businessPlans.text=Business plans -marketingMaterials.text=Marketing materials -trainingPresentations.text=Training presentations -softwareLicenses.text=Some expensive software licenses -employeeBelongings.text=Some employee belongings -financialRecords.text=Financial records -employeeRecords.text=Employee records -proprietarySoftware.text=Propriety accounting software -networkAccessCredentials.text=Network access credentials -companyUniforms.text=Some company uniforms -desks.text=Some desks -monitors.text=Office computer monitors -printers.text=Office printers -projectors.text=A briefing projector -carKeys.text=The commander's car keys -dartboard.text=The dartboard -securityBadges.text=Some security badges -officeKeys.text=The office keys -pettyCashBox.text=The petty cash box -cheques.text=Some pre-signed cheques -diary.text=The commander's diary -giftCards.text=Some employee incentive gift cards -coupons.text=Some discount coupons -personalDataOfCoworkers.text=All personnel logs -battlePlans.text=Battle plans -legalDocuments.text=Legal documents -signedContracts.text=Signed contracts -clientFeedbackForms.text=Client feedback forms -trainingManuals.text=Training manuals -marketResearch.text=Market research -businessContacts.text=A list of business contacts -meetingNotes.text=A folder of meeting notes -contractLeads.text=A list of contract leads -urbanMechPlushie.text=Someone's UrbanMech Plushie -brandedMugs.text=A box of branded mugs -companyPhoneDirectories.text=The company phone directories -logbooks.text=The operational logbooks -inventoryLists.text=The inventory lists -confidentialHpgMessages.text=Some confidential HPG messages -strategyDocuments.text=Strategy documents -passwordLists.text=Password lists -internalMemos.text=Embarrassing internal memos -surveillanceCameraRecordings.text=Surveillance camera recordings -brandedPens.text=A box of branded pens -engineeringBlueprints.text=Engineering blueprints -codeRepositories.text=Administrative code repositories -internalNewsletters.text=Copies of the internal newsletter -hrPolicies.text=A copy of HR's internal policies -companyHandbooks.text=A number of company handbooks -procedureManuals.text=A number of procedure manuals -securityPolicies.text=A copy of the company security policies -simulationData.text=A copy of simulation data -businessCards.text=A box of business cards -ndaAgreements.text=A file containing NDA agreements -nonCompeteAgreements.text=A file containing non-compete agreements -softwareCode.text=Proprietary software code -technicalSpecifications.text=Technical specifications -securitySchedules.text=Security schedules -underWear.text=The commander's underwear -marketAnalysis.text=Market analysis data -salesContracts.text=Copies of sales contracts -expenseReports.text=Copies of the company's expense reports -reimbursementReceipts.text=The box of outstanding reimbursement receipts -invoices.text=A pile of invoices -employeeBenefitsInformation.text=A copy of the employee benefits information -insuranceDocuments.text=A hard drive containing insurance documents -lightBulbs.text=The spare light bulbs -strategicAlliancesInformation.text=Strategic alliance information -computers.text=Some office computers -boots.text=The commander's boots -employeeDiscountStructures.text=A copy of the company's employee discount structures -meetingMinutes.text=A copy of company meeting minutes -itInfrastructureDetails.text=Details about the company's IT infrastructure -serverAccessCodes.text=Server access codes -backupDrives.text=Some backup drives -missionData.text=Mission data -executiveMeetingNotes.text=A copy of executive meeting notes -toe.text=A copy of the company's TOE -clientComplaints.text=A file containing client complaints -inventoryControlSystems.text=A copy of the company's inventory control system -chairs.text=A number of chairs -shippingLogs.text=The company's shipping logs -printerPaper.text=Multiple boxes of printer paper -internalAuditReports.text=Copies of incriminating internal audit reports -corruption.text=Incriminating evidence of internal corruption -officePlants.text=The office plants -battlefieldPerformanceReports.text=Battlefield performance reports -companyStandard.text=The company standard -analyticsReports.text=Analytics reports -fridge.text=The contents of the communal fridge -coffeeMachine.text=The coffee machine -mug.text=The Commander's favorite mug -toiletSeats.text=All the toilet seats +stapler.text=the office stapler +mascot.text=a company mascot +phones.text=the office phones +tablets.text=a number of tablets +hardDrives.text=some important hard drives +flashDrive.text=an incriminating flash drive +companyCreditCard.text=the company credit card +officePet.text=an office pet +confidentialReports.text=a number of confidential reports +clientLists.text=historic client lists +unitSchematics.text=unit schematics +businessPlans.text=business plans +marketingMaterials.text=marketing materials +trainingPresentations.text=training presentations +softwareLicenses.text=some expensive software licenses +employeeBelongings.text=some employee belongings +financialRecords.text=financial records +employeeRecords.text=employee records +proprietarySoftware.text=propriety accounting software +networkAccessCredentials.text=network access credentials +companyUniforms.text=some company uniforms +desks.text=some desks +monitors.text=office computer monitors +printers.text=office printers +projectors.text=a briefing projector +carKeys.text=the commander's car keys +dartboard.text=the dartboard +securityBadges.text=some security badges +officeKeys.text=the office keys +pettyCashBox.text=the petty cash box +cheques.text=some pre-signed cheques +diary.text=the commander's diary +giftCards.text=some employee incentive gift cards +coupons.text=some discount coupons +personalDataOfCoworkers.text=a number of personnel logs +battlePlans.text=battle plans +legalDocuments.text=legal documents +signedContracts.text=signed contracts +clientFeedbackForms.text=client feedback forms +trainingManuals.text=training manuals +marketResearch.text=market research +businessContacts.text=a list of business contacts +meetingNotes.text=a folder of meeting notes +contractLeads.text=a list of contract leads +urbanMechPlushie.text=someone's UrbanMech Plushie +brandedMugs.text=a box of branded mugs +companyPhoneDirectories.text=the company phone directories +logbooks.text=the operational logbooks +inventoryLists.text=the inventory lists +confidentialHpgMessages.text=some confidential HPG messages +strategyDocuments.text=strategy documents +passwordLists.text=password lists +internalMemos.text=embarrassing internal memos +surveillanceCameraRecordings.text=surveillance camera recordings +brandedPens.text=a box of branded pens +engineeringBlueprints.text=engineering blueprints +codeRepositories.text=administrative code repositories +internalNewsletters.text=copies of the internal newsletter +hrPolicies.text=a copy of HR's internal policies +companyHandbooks.text=a number of company handbooks +procedureManuals.text=a number of procedure manuals +securityPolicies.text=a copy of the company security policies +simulationData.text=a copy of simulation data +businessCards.text=a box of business cards +ndaAgreements.text=a file containing NDA agreements +nonCompeteAgreements.text=a file containing non-compete agreements +softwareCode.text=proprietary software code +technicalSpecifications.text=technical specifications +securitySchedules.text=security schedules +underWear.text=the commander's underwear +marketAnalysis.text=market analysis data +salesContracts.text=copies of sales contracts +expenseReports.text=copies of the company's expense reports +reimbursementReceipts.text=the box of outstanding reimbursement receipts +invoices.text=a pile of invoices +employeeBenefitsInformation.text=a copy of the employee benefits information +insuranceDocuments.text=a hard drive containing insurance documents +lightBulbs.text=the spare light bulbs +strategicAlliancesInformation.text=strategic alliance information +computers.text=some office computers +boots.text=the commander's boots +employeeDiscountStructures.text=a copy of the company's employee discount structures +meetingMinutes.text=a copy of company meeting minutes +itInfrastructureDetails.text=details about the company's IT infrastructure +serverAccessCodes.text=server access codes +backupDrives.text=some backup drives +missionData.text=sensitive mission data +executiveMeetingNotes.text=a copy of executive meeting notes +toe.text=a copy of the company's TOE +clientComplaints.text=a file containing client complaints +inventoryControlSystems.text=a copy of the company's inventory control system +chairs.text=a number of chairs +shippingLogs.text=the company's shipping logs +printerPaper.text=multiple boxes of printer paper +internalAuditReports.text=copies of incriminating internal audit reports +corruption.text=incriminating evidence of internal corruption +officePlants.text=the office plants +battlefieldPerformanceReports.text=battlefield performance reports +companyStandard.text=the company standard +analyticsReports.text=analytics reports +fridge.text=the contents of the communal fridge +coffeeMachine.text=the coffee machine +mug.text=the Commander's favorite mug +toiletSeats.text=all the toilet seats +miniatures.text=the company's collection of hand-painted miniature BattleMechs diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index a94d260937..f9a5f1f679 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -478,7 +478,7 @@ TurnoverTargetNumberMethod.NEGOTIATION.toolTipText=The Target number is based on ForceReliabilityMethod.UNIT_RATING.text=Dynamic: Unit Rating ForceReliabilityMethod.UNIT_RATING.toolTipText=Unit Rating modifies desertion and Mutiny checks ForceReliabilityMethod.LOYALTY.text=Dynamic: Loyalty -ForceReliabilityMethod.LOYALTY.toolTipText=Desertion and Mutiny checks are modified by average personnel loyalty.
If loyalty is disabled, this setting is identical to 'Dynamic: Equipment Rating.' +ForceReliabilityMethod.LOYALTY.toolTipText=Desertion and Mutiny checks are modified by average personnel loyalty.
If loyalty is disabled, this setting is identical to 'Dynamic: Unit Rating.' ForceReliabilityMethod.OVERRIDE_A.text=Fixed: A (Fanatical / Clan Front Line) ForceReliabilityMethod.OVERRIDE_A.toolTipText=Desertion and Mutiny checks have a +1 modifier. ForceReliabilityMethod.OVERRIDE_B.text=Fixed: B (Fanatical-Reliable / Clan Second Line) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index dbd7491173..8151af8c43 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -232,7 +232,7 @@ public class Campaign implements ITechManager { private transient AbstractProcreation procreation; private RetirementDefectionTracker retirementDefectionTracker; - private double morale; + private int morale; private AtBConfiguration atbConfig; //AtB private AtBEventProcessor atbEventProcessor; //AtB @@ -295,7 +295,7 @@ public Campaign() { setMarriage(new DisabledRandomMarriage(getCampaignOptions())); setProcreation(new DisabledRandomProcreation(getCampaignOptions())); retirementDefectionTracker = new RetirementDefectionTracker(); - morale = 4.0; + morale = 40; atbConfig = null; autosaveService = new AutosaveService(); hasActiveContract = false; @@ -481,11 +481,11 @@ public void setProcreation(final AbstractProcreation procreation) { this.procreation = procreation; } - public double getMorale() { + public int getMorale() { return morale; } - public void setMorale(final double morale) { + public void setMorale(final int morale) { this.morale = morale; } //endregion Personnel Modules @@ -3579,22 +3579,22 @@ private void processMoraleNewDay() { // this processes morale recovery based on campaign location and the results of the prior checks if (desertionEvent && mutinyEvent) { if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, -2.0); + Morale.processMoraleChange(this, -2); } else { - Morale.processMoraleChange(this, -3.0); + Morale.processMoraleChange(this, -3); } } else if (desertionEvent) { if ((!getActiveContracts().isEmpty()) || (!getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, -1.0); + Morale.processMoraleChange(this, -1); } } else if (mutinyEvent) { if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, -1.0); + Morale.processMoraleChange(this, -1); } else { - Morale.processMoraleChange(this, -2.0); + Morale.processMoraleChange(this, -2); } } else { - Morale.processMoraleChange(this, 1.0); + Morale.processMoraleChange(this, 1); } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 5f2f429dab..e10cb163fe 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -274,13 +274,13 @@ public static String getTransitUnitName(final int unit) { private TurnoverTargetNumberMethod turnoverTargetNumberMethod; private SkillLevel turnoverDifficulty; - private Integer turnoverFixedTargetNumber; + private int turnoverFixedTargetNumber; private boolean useYearEndRandomRetirement; private boolean useContractCompletionRandomRetirement; private boolean useRandomFounderRetirement; private boolean useSubContractSoldiers; - private Integer serviceContractDuration; - private Integer serviceContractModifier; + private int serviceContractDuration; + private int serviceContractModifier; private boolean useCustomRetirementModifiers; private boolean useFatigueModifiers; @@ -292,34 +292,34 @@ public static String getTransitUnitName(final int unit) { private boolean useLoyaltyModifiers; private boolean useHideLoyalty; - private Integer payoutRateOfficer; - private Integer payoutRateEnlisted; - private Integer payoutRetirementMultiplier; + private int payoutRateOfficer; + private int payoutRateEnlisted; + private int payoutRetirementMultiplier; private boolean usePayoutServiceBonus; - private Integer payoutServiceBonusRate; + private int payoutServiceBonusRate; private boolean useAdministrativeStrain; - private Integer administrativeCapacity; - private Integer multiCrewStrainDivider; + private int administrativeCapacity; + private int multiCrewStrainDivider; private boolean useManagementSkill; private boolean useCommanderLeadershipOnly; - private Integer managementSkillPenalty; + private int managementSkillPenalty; private boolean useFatigue; - private Integer fatigueRate; - private Integer fieldKitchenCapacity; - private Integer fatigueLeaveThreshold; + private int fatigueRate; + private int fieldKitchenCapacity; + private int fatigueLeaveThreshold; private boolean useMorale; - private Double MoraleStepSize; + private int MoraleStepSize; private boolean useDesertions; private boolean useMutinies; private ForceReliabilityMethod forceReliabilityMethod; private boolean useSabotage; private boolean useTheftUnit; private boolean useTheftMoney; - private Integer theftValue; + private int theftValue; private boolean useMoraleTriggerFieldControl; private boolean useMoraleTriggerMissionStatus; @@ -330,7 +330,7 @@ public static String getTransitUnitName(final int unit) { private boolean useMoraleTriggerFatigue; private boolean useMoraleModifierMissedPayDay; - private Integer customMoraleModifier; + private int customMoraleModifier; private boolean useMoraleModifierExperienceLevel; private boolean useMoraleModifierFaction; private boolean useMoraleModifierProfession; @@ -1002,7 +1002,7 @@ public CampaignOptions() { setFatigueLeaveThreshold(13); setUseMorale(true); - setMoraleStepSize(0.1); + setMoraleStepSize(1); setForceReliabilityMethod(ForceReliabilityMethod.LOYALTY); setUseDesertions(true); setUseSabotage(true); @@ -1569,11 +1569,11 @@ public void setUseMorale(final boolean useMorale) { this.useMorale = useMorale; } - public double getMoraleStepSize() { + public int getMoraleStepSize() { return MoraleStepSize; } - public void setMoraleStepSize(final double moraleStepSize) { + public void setMoraleStepSize(final int moraleStepSize) { this.MoraleStepSize = moraleStepSize; } @@ -5740,7 +5740,7 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve } else if (wn2.getNodeName().equalsIgnoreCase("useMorale")) { retVal.setUseMorale(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("moraleStepSize")) { - retVal.setMoraleStepSize(Double.parseDouble(wn2.getTextContent().trim())); + retVal.setMoraleStepSize(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("forceReliabilityMethod")) { retVal.setForceReliabilityMethod(ForceReliabilityMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useDesertions")) { diff --git a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java index b2c65b57b1..6458039f2b 100644 --- a/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java +++ b/MekHQ/src/mekhq/campaign/io/CampaignXmlParser.java @@ -276,7 +276,7 @@ public Campaign parse() throws CampaignXmlParseException, NullEntityException { } else if (xn.equalsIgnoreCase("retirementDefectionTracker")) { retVal.setRetirementDefectionTracker(RetirementDefectionTracker.generateInstanceFromXML(wn, retVal)); } else if (xn.equalsIgnoreCase("Morale")) { - retVal.setMorale(Double.parseDouble(wn.getTextContent())); + retVal.setMorale(Integer.parseInt(wn.getTextContent())); } else if (xn.equalsIgnoreCase("shipSearchStart")) { retVal.setShipSearchStart(MHQXMLUtility.parseDate(wn.getTextContent().trim())); } else if (xn.equalsIgnoreCase("shipSearchType")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 974172aacd..1db754b88d 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -8,6 +8,7 @@ import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; import mekhq.campaign.finances.enums.TransactionType; +import mekhq.campaign.finances.financialInstitutions.FinancialInstitutions; import mekhq.campaign.market.enums.UnitMarketType; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; @@ -16,6 +17,7 @@ import mekhq.campaign.personnel.enums.PersonnelStatus; import mekhq.campaign.unit.Unit; import mekhq.gui.dialog.MutinySupportDialog; +import org.apache.logging.log4j.LogManager; import java.util.*; import java.util.stream.Collectors; @@ -23,7 +25,6 @@ import static megamek.common.EntityWeightClass.WEIGHT_LARGE_WAR; public class Morale { - /** * This method returns the Morale level as a string based on the value of the 'Morale' variable. * @@ -36,19 +37,19 @@ public static String getMoraleLevel(Campaign campaign) { double morale = campaign.getMorale(); - if ((morale >= 1) && (morale < 2)) { + if ((morale >= 10) && (morale < 20)) { return resources.getString("moraleLevelUnbreakable.text"); - } else if (morale < 3) { + } else if (morale < 30) { return resources.getString("moraleLevelVeryHigh.text"); - } else if (morale < 4) { + } else if (morale < 40) { return resources.getString("moraleLevelHigh.text"); - } else if (morale < 5) { + } else if (morale < 50) { return resources.getString("moraleLevelNormal.text"); - } else if (morale < 6) { + } else if (morale < 60) { return resources.getString("moraleLevelLow.text"); - } else if (morale < 7) { + } else if (morale < 70) { return resources.getString("moraleLevelVeryLow.text"); - } else if (morale == 7) { + } else if (morale == 70) { return resources.getString("moraleLevelBroken.text"); } @@ -66,21 +67,21 @@ public static String getMoraleLevel(Campaign campaign) { private static Integer getTargetNumber(Campaign campaign, boolean isDesertion) { double morale = campaign.getMorale(); - if ((morale >= 1) && (morale < 4)) { + if ((morale >= 10) && (morale < 40)) { return 0; - } else if (morale < 5) { + } else if (morale < 50) { if (isDesertion) { return 2; } else { return 0; } - } else if (morale < 7) { + } else if (morale < 70) { if (isDesertion) { return 5; } else { return 4; } - } else if (morale == 7) { + } else if (morale == 70) { if (isDesertion) { return 8; } else { @@ -361,11 +362,11 @@ public static void getMoraleReport(Campaign campaign) { public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { - return isDesertion; + return false; } else if ((isDesertion) && (!campaign.getLocation().isOnPlanet())) { - return isDesertion; + return false; } else if ((!isDesertion) && (!campaign.getCampaignOptions().isUseMutinies())) { - return isDesertion; + return false; } double targetNumber = getTargetNumber(campaign, isDesertion); @@ -387,14 +388,12 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { } } - if (campaign.getCampaignOptions().getForceReliabilityMethod().isLoyalty()) { + if ((!filteredPersonnel.isEmpty()) && (campaign.getCampaignOptions().getForceReliabilityMethod().isLoyalty())) { loyalty = MathUtility.clamp(loyalty / filteredPersonnel.size(), -3, 3); } else { loyalty = 0; } - double morale = campaign.getMorale(); - ArrayList theftTargets = new ArrayList<>(); if (isDesertion) { @@ -485,12 +484,13 @@ private static boolean processDesertion(Campaign campaign, Person person, int ro } // check for theft - if ((roll <= (morale - 4)) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!unitList.isEmpty())) { - processUnitTheft(campaign, unitList, resources); - } else if ((roll <= (morale - 3)) && (campaign.getCampaignOptions().isUseTheftMoney())) { + LogManager.getLogger().info(((roll + 3) < morale) && (campaign.getCampaignOptions().isUseTheftMoney())); + if (((roll + 5) < morale) && (campaign.getCampaignOptions().isUseTheftUnit())) { + processUnitTheft(campaign, person, unitList, resources); + } else if (((roll + 4) < morale) && (campaign.getCampaignOptions().isUseTheftMoney())) { processMoneyTheft(campaign, resources); - } else if (roll <= (morale - 2)) { - processPettyTheft(campaign, resources); + } else if ((roll + 3) < morale) { + processPettyTheft(campaign, person, resources); } return true; @@ -533,13 +533,14 @@ private static double reclaimOriginalUnit(Campaign campaign, Person person) { } /** - * Process theft of units. + * Processes unit theft. * - * @param campaign the campaign from which units or funds are stolen - * @param unitList the list of available units - * @param resources the resource bundle for retrieving localized strings + * @param campaign the current campaign + * @param person the person committing the theft + * @param unitList the list of stealable units + * @param resources the resource bundle for localization */ - private static void processUnitTheft(Campaign campaign, ArrayList unitList, ResourceBundle resources) { + private static void processUnitTheft(Campaign campaign, Person person, ArrayList unitList, ResourceBundle resources) { Unit desiredUnit = unitList.get(new Random().nextInt(unitList.size())); StringBuilder unitName = new StringBuilder(desiredUnit.getName()); @@ -552,9 +553,9 @@ private static void processUnitTheft(Campaign campaign, ArrayList unitList campaign.getUnitMarket().addOffers(campaign, 1, UnitMarketType.BLACK_MARKET, desiredUnit.getEntity().getUnitType(), campaign.getFaction(), desiredUnit.getQuality(), 6); - campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), unitName)); + campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheftBlackMarket.text"), unitName)); } else { - campaign.addReport(String.format(resources.getString("desertionTheft.text"), unitName)); + campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheft.text"), unitName)); } campaign.removeUnit(desiredUnit.getId()); @@ -564,8 +565,8 @@ private static void processUnitTheft(Campaign campaign, ArrayList unitList /** * Processes money theft. * - * @param campaign the campaign for which the theft is being processed - * @param resources the ResourceBundle containing the necessary resources + * @param campaign the campaign for which the theft is being processed + * @param resources the ResourceBundle containing the necessary resources */ private static void processMoneyTheft(Campaign campaign, ResourceBundle resources) { int theftPercentage = campaign.getCampaignOptions().getTheftValue(); @@ -595,14 +596,17 @@ private static void processMoneyTheft(Campaign campaign, ResourceBundle resource break; } - Money theft = campaign.getFunds().multipliedBy(MathUtility.clamp(theftPercentage, 1, 100) / 100).round(); + Money theft = campaign.getFunds() + .multipliedBy(theftPercentage) + .dividedBy(100) + .round(); if (theft.isPositive()) { - campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, resources.getString("desertionTheftTransactionReport.text")); + campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, + String.format(resources.getString("desertionTheftTransactionReport.text"), + FinancialInstitutions.randomFinancialInstitution(campaign.getLocalDate()).toString())); - campaign.addReport(String.format(resources.getString("desertionTheftMoney.text"), theft.getAmount())); - } else { - processPettyTheft(campaign, resources); + campaign.addReport(String.format(String.format(resources.getString("desertionTheftMoney.text"), theft.getAmount()))); } } @@ -611,10 +615,12 @@ private static void processMoneyTheft(Campaign campaign, ResourceBundle resource * It randomly selects an item from a list of stolen items and adds the item to the campaign report. * * @param campaign The campaign object to add the report to. + * @param person The person committing the theft * @param resources The ResourceBundle object to retrieve localized strings. */ - private static void processPettyTheft(Campaign campaign, ResourceBundle resources) { - List items = List.of("officeSupplies.text", + private static void processPettyTheft(Campaign campaign, Person person, ResourceBundle resources) { + List items = List.of( + "stapler.text", "mascot.text", "phones.text", "tablets.text", @@ -717,9 +723,11 @@ private static void processPettyTheft(Campaign campaign, ResourceBundle resource "fridge.text", "coffeeMachine.text", "mug.text", - "toiletSeats.text"); + "toiletSeats.text", + "miniatures.text"); - campaign.addReport(String.format(resources.getString("desertionTheft.text"), new Random().nextInt(items.size()))); + campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheft.text"), + resources.getString(items.get(new Random().nextInt(items.size()))))); } /** @@ -757,22 +765,22 @@ public static void processAwolDays(Campaign campaign, Person person) { * @param campaign the campaign to process the morale change for * @param steps the number of steps to change the morale by */ - public static void processMoraleChange(Campaign campaign, double steps) { + public static void processMoraleChange(Campaign campaign, int steps) { final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); - double morale = campaign.getMorale(); - double change = campaign.getCampaignOptions().getMoraleStepSize() * steps; + int morale = campaign.getMorale(); + int change = campaign.getCampaignOptions().getMoraleStepSize() * steps; if ((change > 0) && (morale != 1)) { - campaign.setMorale(MathUtility.clamp(morale - (steps * campaign.getCampaignOptions().getMoraleStepSize()), 1.0, 7.0)); + campaign.setMorale(MathUtility.clamp(morale - (steps * campaign.getCampaignOptions().getMoraleStepSize()), 1, 7)); getMoraleReport(campaign); if ((morale >= 5) && ((morale + steps) < 5)) { campaign.addReport(resources.getString("moraleReportRecovered.text")); } } else if ((change < 0) && (morale != 7)) { - campaign.setMorale(MathUtility.clamp(morale + change, 1.0, 7.0)); + campaign.setMorale(MathUtility.clamp(morale + change, 1, 7)); getMoraleReport(campaign); } } diff --git a/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java b/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java index 525a5d2aaa..24b0baf3d8 100644 --- a/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/GMToolsDialog.java @@ -743,13 +743,13 @@ private JPanel createGeneralToolsPanel() { lblMorale.setName("lblMorale"); panel.add(lblMorale, gbc); - setSpnMorale(new JSpinner(new SpinnerNumberModel(gui.getCampaign().getMorale(), 1.0, 7.0, gui.getCampaign().getCampaignOptions().getMoraleStepSize()))); + setSpnMorale(new JSpinner(new SpinnerNumberModel(gui.getCampaign().getMorale(), 10, 70, gui.getCampaign().getCampaignOptions().getMoraleStepSize()))); gbc.gridx++; getSpnMorale().setName("spnMorale"); panel.add(getSpnMorale(), gbc); final JButton btnMorale = new MMButton("btnMorale", resources, "btnMorale.text", - "btnMorale.toolTipText", evt -> gui.getCampaign().setMorale((double) spnMorale.getValue())); + "btnMorale.toolTipText", evt -> gui.getCampaign().setMorale((int) spnMorale.getValue())); gbc.gridx++; panel.add(btnMorale, gbc); diff --git a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java index f2c1084ef9..d10570e1c6 100644 --- a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java @@ -2,8 +2,10 @@ import megamek.common.Entity; import mekhq.campaign.Campaign; +import mekhq.campaign.parts.Armor; import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.Unit; +import org.apache.logging.log4j.LogManager; import javax.swing.*; import java.awt.*; @@ -28,7 +30,7 @@ public static int supportDialog(Campaign campaign, ResourceBundle resources, boo Integer bystanderPersonnelCount, Person loyalistLeader, Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, Person rebelLeader, Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { - // Initialize a JOptionPane + AtomicInteger choice = new AtomicInteger(-1); JOptionPane pane = new JOptionPane(buildMutinyDescription( campaign, resources, isViolentRebellion, @@ -46,7 +48,6 @@ public static int supportDialog(Campaign campaign, ResourceBundle resources, boo JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - // buttonPanel.add(Box.createHorizontalGlue()); for (Object option : options){ JButton button = new JButton(option.toString()); @@ -157,7 +158,6 @@ private static HashMap mapUnitCounts(List units) { unitCounts.put("vehicle", vehicleCount); unitCounts.put("other", otherCount); - return unitCounts; } @@ -167,11 +167,12 @@ private static HashMap mapUnitCounts(List units) { * @param resources the ResourceBundle containing the necessary strings for formatting the summary string * @param unitMap the HashMap containing the count of each unit type * @param isLoyalist the flag indicating if the forces are loyalist or rebel - * @param bv the force's estimated Battle Value + * @param battleValue the force's estimated Battle Value * @param personnelCount the number of personnel supporting the faction * @return the formatted summary string */ - private static String getForceSummaryString(ResourceBundle resources, HashMap unitMap, boolean isLoyalist, Integer battleValue, Integer personnelCount) { + private static String getForceSummaryString(ResourceBundle resources, HashMap unitMap, boolean isLoyalist, int battleValue, int personnelCount) { + StringBuilder forceSummaryString = new StringBuilder(); String faction; if (isLoyalist) { @@ -180,17 +181,77 @@ private static String getForceSummaryString(ResourceBundle resources, HashMap 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesMeks.text"), + unitMap.get("mek"), + pluralizer(resources, unitMap.get("mek")))); + } + + if (unitMap.get("fighter") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesFighters.text"), + unitMap.get("fighter"), + pluralizer(resources, unitMap.get("fighter")))); + } + + if (unitMap.get("protoMek") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesProtoMechs.text"), + unitMap.get("protoMek"), + pluralizer(resources, unitMap.get("protoMek")))); + } + + if (unitMap.get("battleArmor") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesBattleArmor.text"), + pluralizer(resources, unitMap.get("battleArmor")), + unitMap.get("battleArmor"))); + } + + if (unitMap.get("vehicle") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesVehicles.text"), + unitMap.get("vehicle"), + pluralizer(resources, unitMap.get("vehicle")))); + } + + if (unitMap.get("infantry") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesInfantry.text"), + pluralizer(resources, unitMap.get("infantry")), + unitMap.get("infantry"))); + } + + if (unitMap.get("dropShip") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesDropShips.text"), + unitMap.get("dropShip"), + pluralizer(resources, unitMap.get("dropShip")))); + } + + if (unitMap.get("other") > 0) { + forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesOther.text"), + unitMap.get("other"), + pluralizer(resources, unitMap.get("other")))); + } + + forceSummaryString.append(' ').append(String.format(resources.getString("dialogDescriptionForcesBv.text"), battleValue)); + + return forceSummaryString.toString(); + } + + /** + * This method is used to determine whether a string should be plural based on the given unit count. + * + * @param resources The ResourceBundle object containing the string resource for pluralizer text. + * @param unitCount The number of units. + * @return The pluralizer text based on the unit count. Returns an empty string if the unit count is less than or equal to 1. + */ + private static String pluralizer(ResourceBundle resources, int unitCount) { + String pluralizer = ""; + + if ((unitCount > 1) || (unitCount == 0)) { + pluralizer = resources.getString("dialogDescriptionPluralizer.text"); + } + + return pluralizer; } } \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 1f3051ae6a..b9ded3212c 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -4897,7 +4897,7 @@ private void createMoraleSubPanel() { lblMoraleStepSize.setName("lblMoraleStepSize"); lblMoraleStepSize.setEnabled(isUseMorale); - spnMoraleStepSize = new JSpinner(new SpinnerNumberModel(0.1, 0.1, 2, 0.1)); + spnMoraleStepSize = new JSpinner(new SpinnerNumberModel(1, 1, 20, 1)); spnMoraleStepSize.setToolTipText(resources.getString("lblMoraleStepSize.toolTipText")); spnMoraleStepSize.setName("spnMoraleStepSize"); spnMoraleStepSize.setEnabled(isUseMorale); @@ -8273,7 +8273,7 @@ public void setOptions(@Nullable CampaignOptions options, comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); chkUseSabotage.setSelected(options.isUseSabotage()); chkUseTheftUnit.setSelected(options.isUseTheftUnit()); - chkUseTheftMoney.setSelected(options.isUseTheftUnit()); + chkUseTheftMoney.setSelected(options.isUseTheftMoney()); spnTheftValue.setValue(options.getTheftValue()); // Morale Modifiers @@ -8964,11 +8964,11 @@ public void updateOptions() { options.setUseDesertions(chkUseDesertions.isSelected()); options.setUseMutinies(chkUseMutinies.isSelected()); - options.setMoraleStepSize((Double) spnMoraleStepSize.getValue()); + options.setMoraleStepSize((int) spnMoraleStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); options.setUseSabotage(chkUseSabotage.isSelected()); options.setUseTheftUnit(chkUseTheftUnit.isSelected()); - options.setUseTheftUnit(chkUseTheftMoney.isSelected()); + options.setUseTheftMoney(chkUseTheftMoney.isSelected()); options.setTheftValue((Integer) spnTheftValue.getValue()); // Morale Modifiers From a2235ee5ac3c7c5813e58b23cbdecff9d2398629 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 24 May 2024 02:14:52 -0500 Subject: [PATCH 056/101] Fixed unit theft not transferring stolen units to the black market --- .../market/unitMarket/AbstractUnitMarket.java | 4 ++-- .../personnel/turnoverAndRetention/Morale.java | 11 +++++++---- MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java b/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java index 73e2507f93..f68d62c38f 100644 --- a/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java +++ b/MekHQ/src/mekhq/campaign/market/unitMarket/AbstractUnitMarket.java @@ -25,11 +25,11 @@ import megamek.common.MechSummary; import megamek.common.annotations.Nullable; import mekhq.MekHQ; -import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.Campaign; import mekhq.campaign.market.enums.UnitMarketMethod; import mekhq.campaign.market.enums.UnitMarketType; import mekhq.campaign.universe.Faction; +import mekhq.utilities.MHQXMLUtility; import org.apache.logging.log4j.LogManager; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -170,7 +170,7 @@ public void setOffers(final List offers) { * @param percent the percentage of the original unit cost the unit will be offered at * @return the name of the unit that has been added to the market */ - protected String addSingleUnit(final Campaign campaign, final UnitMarketType market, + public String addSingleUnit(final Campaign campaign, final UnitMarketType market, final int unitType, final MechSummary mechSummary, final int percent) { getOffers().add(new UnitMarketOffer(market, unitType, mechSummary, percent, diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 1db754b88d..b425d8bb23 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -3,6 +3,7 @@ import megamek.codeUtilities.MathUtility; import megamek.common.Compute; import megamek.common.Entity; +import megamek.common.MechSummaryCache; import megamek.common.Mounted; import mekhq.MekHQ; import mekhq.campaign.Campaign; @@ -413,7 +414,6 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { campaign.getHighestRankedPerson(filteredPersonnel, true); } - Person rebelLeader; HashMap rebels = new HashMap<>(); for (Person person : filteredPersonnel) { @@ -549,9 +549,12 @@ private static void processUnitTheft(Campaign campaign, Person person, ArrayList unitName.append(' ').append(desiredUnit.getFluffName()); } - if ((!campaign.getFaction().isClan()) && (Compute.d6(1) >= 3)) { - campaign.getUnitMarket().addOffers(campaign, 1, UnitMarketType.BLACK_MARKET, desiredUnit.getEntity().getUnitType(), - campaign.getFaction(), desiredUnit.getQuality(), 6); + if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 3)) { + campaign.getUnitMarket().addSingleUnit(campaign, + UnitMarketType.BLACK_MARKET, + desiredUnit.getEntity().getUnitType(), + MechSummaryCache.getInstance().getMech(desiredUnit.getEntity().getShortNameRaw()), + 50); campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheftBlackMarket.text"), unitName)); } else { diff --git a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java index d10570e1c6..87cf3a68ae 100644 --- a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java @@ -2,15 +2,15 @@ import megamek.common.Entity; import mekhq.campaign.Campaign; -import mekhq.campaign.parts.Armor; import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.Unit; -import org.apache.logging.log4j.LogManager; import javax.swing.*; import java.awt.*; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicInteger; public class MutinySupportDialog { From 1f1c9ac6711f708cc964ca5b73c8ad06e8543d53 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 24 May 2024 19:20:09 -0500 Subject: [PATCH 057/101] Refactored morale system and improved code readability The morale system has been refactored to increase efficiency and improve code readability. Modifications include changing the way morale and checks are calculated and stored, updating internal comments for clarity, and revising variable and function names for better understanding. The morale enums have also been updated with improved naming conventions. --- .../resources/AtBScenarioBuiltIn.properties | 4 +- .../CampaignOptionsDialog.properties | 16 +- .../mekhq/resources/Mission.properties | 8 +- .../mekhq/resources/Morale.properties | 215 ++++----- .../mekhq/campaign/mission/AtBContract.java | 2 +- .../mission/atb/AtBScenarioFactory.java | 14 +- .../mission/enums/AtBMoraleLevel.java | 14 +- .../turnoverAndRetention/Morale.java | 432 +++++++++++------- .../stratcon/StratconRulesManager.java | 9 +- 9 files changed, 401 insertions(+), 313 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/AtBScenarioBuiltIn.properties b/MekHQ/resources/mekhq/resources/AtBScenarioBuiltIn.properties index 1d881d48ca..91a14fc420 100644 --- a/MekHQ/resources/mekhq/resources/AtBScenarioBuiltIn.properties +++ b/MekHQ/resources/mekhq/resources/AtBScenarioBuiltIn.properties @@ -1,5 +1,5 @@ battleDetails.baseAttack.attacker.victory=Destroy all base forces and 50% of the other enemy units.\nKeep more than 50% of your+ally's units operational. -battleDetails.baseAttack.attacker.observations=Winner controls the battlefield after the battle. If player wins the battle and the contract required lance type is fight or scout, it ends with an early victory. Otherwise the enemy morale level becomes rout. +battleDetails.baseAttack.attacker.observations=Winner controls the battlefield after the battle. If player wins the battle and the contract required lance type is fight or scout, it ends with an early victory. Otherwise the enemy morale level becomes broken. battleDetails.baseAttack.defender.victory=Must keep at least three base units operational and destroy 50% of enemy forces.\nKeep more than 50% of your+ally's units operational. battleDetails.baseAttack.defender.observations=If player loses, the contract ends early with a contract defeat. @@ -91,7 +91,7 @@ battleDetails.reconRaid.instructions.returnEdge=Return to %s map edge. battleDetails.reconRaid.instructions.reward=1d6-2 bonus rolls if victorious. battleDetails.baseAttack.attacker.details.winnerFightScout=Completing this objective will end the contract with an early victory. -battleDetails.baseAttack.attacker.details.winnerDefendTraining=Completing this objective will set the enemy morale to "Rout". +battleDetails.baseAttack.attacker.details.winnerDefendTraining=Completing this objective will set the enemy morale to "Broken". battleDetails.baseAttack.attacker.details.loser=Losing this battle will end the contract with an early defeat. battleDetails.starLeagueCache.Mek=Star League Mek diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 502cd2e99c..4d2382c3a5 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -326,21 +326,21 @@ lblTheftValue.toolTipText=The average percentage of unit funds stolen during a h turnoverAndRetentionMoraleModifiersPanel.title=Morale Modifiers lblCustomMoraleModifier.text=Custom Modifier -lblCustomMoraleModifier.toolTipText=This is a direct modifier applied to both mutiny and desertion checks. +lblCustomMoraleModifier.toolTipText=This is a direct modifier applied to both mutiny and desertion rolls. chkUseMoraleModifierExperienceLevel.text=Experience Level -chkUseMoraleModifierExperienceLevel.toolTipText=Experience level (green, regular, etc.) influences both mutiny and desertion checks. +chkUseMoraleModifierExperienceLevel.toolTipText=Experience level (green, regular, etc.) influences both mutiny and desertion rolls. chkUseMoraleModifierFaction.text=Faction -chkUseMoraleModifierFaction.toolTipText=Clan or Mercenary origin factions influence both mutiny and desertion checks. +chkUseMoraleModifierFaction.toolTipText=Clan or Mercenary origin factions influence both mutiny and desertion rolls. chkUseMoraleModifierProfession.text=Profession -chkUseMoraleModifierProfession.toolTipText=Professions influence desertion and mutiny checks. Personnel with multiple professions use the average of both modifiers. +chkUseMoraleModifierProfession.toolTipText=Professions influence desertion and mutiny rolls. Personnel with multiple professions use the average of both modifiers. chkUseMoraleModifierForceReliability.text=Force Reliability -chkUseMoraleModifierForceReliability.toolTipText=Force reliability influences both desertion and mutiny checks. +chkUseMoraleModifierForceReliability.toolTipText=Force reliability influences both desertion and mutiny rolls. chkUseMoraleModifierCommanderSkill.text=Commander's Management Skill -chkUseMoraleModifierCommanderSkill.toolTipText=The commander having a positive Management Skill modifier improves both desertion and mutiny checks. +chkUseMoraleModifierCommanderSkill.toolTipText=The commander having a positive Management Skill modifier improves both desertion and mutiny rolls. chkUseMoraleModifierLoyalty.text=Personal Loyalty -chkUseMoraleModifierLoyalty.toolTipText=Personal loyalty adjusts both desertion and mutiny checks. Stacks with the Loyalty force reliability method. +chkUseMoraleModifierLoyalty.toolTipText=Personal loyalty adjusts both desertion and mutiny rolls. Stacks with the Loyalty force reliability method. chkUseRuleWithIronFist.text=Rule with an Iron Fist -chkUseRuleWithIronFist.toolTipText=Where there is a whip, there is a way. Desertion and Mutiny checks have a +1 modifier, but mutinies are more destructive. +chkUseRuleWithIronFist.toolTipText=Where there is a whip, there is a way. Desertion and Mutiny rolls have a +1 modifier, but desertions and mutinies are more destructive. turnoverAndRetentionMoraleTriggersPanel.title=Morale Change Triggers chkUseMoraleTriggerFieldControl.text=Field Control diff --git a/MekHQ/resources/mekhq/resources/Mission.properties b/MekHQ/resources/mekhq/resources/Mission.properties index 0caa1d5eb2..5b4b50f815 100644 --- a/MekHQ/resources/mekhq/resources/Mission.properties +++ b/MekHQ/resources/mekhq/resources/Mission.properties @@ -42,8 +42,8 @@ AtBLanceRole.UNASSIGNED.text=Unassigned AtBLanceRole.UNASSIGNED.toolTipText=The lance is not currently assigned to combat duties. # AtBMoraleLevel Enum -AtBMoraleLevel.ROUT.text=Rout -AtBMoraleLevel.ROUT.toolTipText=The unit's morale has broken, and it's men are in full retreat. +AtBMoraleLevel.BROKEN.text=Broken +AtBMoraleLevel.BROKEN.toolTipText=The unit's morale has broken, and its men are in full retreat. AtBMoraleLevel.VERY_LOW.text=Very Low AtBMoraleLevel.VERY_LOW.toolTipText=The unit is on precipice of breaking, their leadership barely holding the unit together. AtBMoraleLevel.LOW.text=Low @@ -54,8 +54,8 @@ AtBMoraleLevel.HIGH.text=High AtBMoraleLevel.HIGH.toolTipText=The unit is ready and glad to fight. AtBMoraleLevel.VERY_HIGH.text=Very High AtBMoraleLevel.VERY_HIGH.toolTipText=The unit is dedicated to their cause. -AtBMoraleLevel.INVINCIBLE.text=Invincible -AtBMoraleLevel.INVINCIBLE.toolTipText=The unit's trust in their leadership and dedication to their cause is nigh-on unbreakable, and they look forward to fighting their enemies. +AtBMoraleLevel.UNBREAKABLE.text=Unbreakable +AtBMoraleLevel.UNBREAKABLE.toolTipText=The unit's trust in their leadership and dedication to their cause is nigh-on unbreakable, and they look forward to fighting their enemies. # ContractCommandRights Enum ContractCommandRights.INTEGRATED.text=Integrated diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index c9ebf73661..0aeae0681b 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -17,8 +17,8 @@ moraleReportRecovered.text=Your personnel are placated, for now. desertionAwolExtended.text=has extended their unauthorized leave. # Theft -desertionTheft.text=has stolen %s. -desertionTheftBlackMarket.text=has stolen %s and is trying to sell it on the Black Market. +desertionTheft.text=%s has gone missing. +desertionTheftBlackMarket.text=Someone is trying to sell one of our missing units on the Black Market: %s. desertionTheftMoney.text=The company accountants report an unusual payment of %s c-bills to an off-planet account. desertionTheftTransactionReport.text=Suspicious transfer of funds to %s @@ -55,108 +55,109 @@ dialogDescriptionDecision.text=You must choose whether to support the Loyalists battleUnitDestroyed.text=was completely destroyed, all personnel were killed outright. # Petty Theft Table -stapler.text=the office stapler -mascot.text=a company mascot -phones.text=the office phones -tablets.text=a number of tablets -hardDrives.text=some important hard drives -flashDrive.text=an incriminating flash drive -companyCreditCard.text=the company credit card -officePet.text=an office pet -confidentialReports.text=a number of confidential reports -clientLists.text=historic client lists -unitSchematics.text=unit schematics -businessPlans.text=business plans -marketingMaterials.text=marketing materials -trainingPresentations.text=training presentations -softwareLicenses.text=some expensive software licenses -employeeBelongings.text=some employee belongings -financialRecords.text=financial records -employeeRecords.text=employee records -proprietarySoftware.text=propriety accounting software -networkAccessCredentials.text=network access credentials -companyUniforms.text=some company uniforms -desks.text=some desks -monitors.text=office computer monitors -printers.text=office printers -projectors.text=a briefing projector -carKeys.text=the commander's car keys -dartboard.text=the dartboard -securityBadges.text=some security badges -officeKeys.text=the office keys -pettyCashBox.text=the petty cash box -cheques.text=some pre-signed cheques -diary.text=the commander's diary -giftCards.text=some employee incentive gift cards -coupons.text=some discount coupons -personalDataOfCoworkers.text=a number of personnel logs -battlePlans.text=battle plans -legalDocuments.text=legal documents -signedContracts.text=signed contracts -clientFeedbackForms.text=client feedback forms -trainingManuals.text=training manuals -marketResearch.text=market research -businessContacts.text=a list of business contacts -meetingNotes.text=a folder of meeting notes -contractLeads.text=a list of contract leads -urbanMechPlushie.text=someone's UrbanMech Plushie -brandedMugs.text=a box of branded mugs -companyPhoneDirectories.text=the company phone directories -logbooks.text=the operational logbooks -inventoryLists.text=the inventory lists -confidentialHpgMessages.text=some confidential HPG messages -strategyDocuments.text=strategy documents -passwordLists.text=password lists -internalMemos.text=embarrassing internal memos -surveillanceCameraRecordings.text=surveillance camera recordings -brandedPens.text=a box of branded pens -engineeringBlueprints.text=engineering blueprints -codeRepositories.text=administrative code repositories -internalNewsletters.text=copies of the internal newsletter -hrPolicies.text=a copy of HR's internal policies -companyHandbooks.text=a number of company handbooks -procedureManuals.text=a number of procedure manuals -securityPolicies.text=a copy of the company security policies -simulationData.text=a copy of simulation data -businessCards.text=a box of business cards -ndaAgreements.text=a file containing NDA agreements -nonCompeteAgreements.text=a file containing non-compete agreements -softwareCode.text=proprietary software code -technicalSpecifications.text=technical specifications -securitySchedules.text=security schedules -underWear.text=the commander's underwear -marketAnalysis.text=market analysis data -salesContracts.text=copies of sales contracts -expenseReports.text=copies of the company's expense reports -reimbursementReceipts.text=the box of outstanding reimbursement receipts -invoices.text=a pile of invoices -employeeBenefitsInformation.text=a copy of the employee benefits information -insuranceDocuments.text=a hard drive containing insurance documents -lightBulbs.text=the spare light bulbs -strategicAlliancesInformation.text=strategic alliance information -computers.text=some office computers -boots.text=the commander's boots -employeeDiscountStructures.text=a copy of the company's employee discount structures -meetingMinutes.text=a copy of company meeting minutes -itInfrastructureDetails.text=details about the company's IT infrastructure -serverAccessCodes.text=server access codes -backupDrives.text=some backup drives -missionData.text=sensitive mission data -executiveMeetingNotes.text=a copy of executive meeting notes -toe.text=a copy of the company's TOE -clientComplaints.text=a file containing client complaints -inventoryControlSystems.text=a copy of the company's inventory control system -chairs.text=a number of chairs -shippingLogs.text=the company's shipping logs -printerPaper.text=multiple boxes of printer paper -internalAuditReports.text=copies of incriminating internal audit reports -corruption.text=incriminating evidence of internal corruption -officePlants.text=the office plants -battlefieldPerformanceReports.text=battlefield performance reports -companyStandard.text=the company standard -analyticsReports.text=analytics reports -fridge.text=the contents of the communal fridge -coffeeMachine.text=the coffee machine -mug.text=the Commander's favorite mug -toiletSeats.text=all the toilet seats -miniatures.text=the company's collection of hand-painted miniature BattleMechs +stapler.text=The office stapler +mascot.text=A company mascot +phones.text=The office phones +tablets.text=A number of tablets +hardDrives.text=An important hard drive +flashDrive.text=An incriminating flash drive +companyCreditCard.text=The company credit card +officePet.text=An office pet +confidentialReports.text=A number of confidential reports +clientLists.text=A historic client list +unitSchematics.text=A sensitive unit schematic +businessPlans.text=A number of important business plans +marketingMaterials.text=Vital marketing materials +trainingPresentations.text=A critical training presentation +softwareLicenses.text=An expensive software license +rifle.text=A rifle +financialRecords.text=This quarters' financial records +employeeRecords.text=Some employee records +proprietarySoftware.text=Our propriety accounting software +networkAccessCredentials.text=A network access credential +companyUniforms.text=A company uniform +desks.text=An entire desk +monitors.text=An office computer monitor +printers.text=The office printer +projectors.text=A briefing projector +carKeys.text=A set of car keys +dartboard.text=The communal dartboard +securityBadges.text=A security badge +officeKeys.text=A set of office keys +pettyCashBox.text=The contents of the petty cash box +cheques.text=A book of pre-signed checks +diary.text=A combat journal +giftCards.text=An employee incentive gift card +coupons.text=A valuable discount coupon +personalDataOfCoworkers.text=A copy of our personnel logs +battlePlans.text=A battle plan +legalDocuments.text=A legal document +signedContracts.text=A copy of our previously signed contracts +clientFeedbackForms.text=A client feedback form +trainingManuals.text=A training manual +marketResearch.text=A copy of our market research +businessContacts.text=A list of business contacts +meetingNotes.text=A folder of meeting notes +contractLeads.text=A list of contract leads +urbanMechPlushie.text=An UrbanMech plushie +brandedMugs.text=A box of branded mugs +companyPhoneDirectories.text=A company phone directory +logbooks.text=An operational logbook +inventoryLists.text=An inventory list +confidentialHpgMessages.text=A confidential HPG message +strategyDocuments.text=A copy of our strategy documents +passwordLists.text=A post-it note of computer passwords +internalMemos.text=An embarrassing internal memo +surveillanceCameraRecordings.text=A surveillance camera recording +brandedPens.text=A box of branded pens +engineeringBlueprints.text=An engineering blueprint +codeRepositories.text=An administrative code repository +internalNewsletters.text=A saved copy of the internal newsletter +hrPolicies.text=A copy of HR's internal policies +companyHandbooks.text=A company handbook +procedureManuals.text=A NeuroHelmet repair manual +securityPolicies.text=A copy of the company security policies +simulationData.text=A copy of battle simulation data +businessCards.text=A box of business cards +ndaAgreements.text=A file containing NDA agreements +nonCompeteAgreements.text=A file containing non-compete agreements +softwareCode.text=Some proprietary software code +technicalSpecifications.text=A technical specifications manual +securitySchedules.text=A copy of our security schedules +underWear.text=Some underwear +marketAnalysis.text=A copy of our market analysis data +salesContracts.text=A copy of our sales contracts +expenseReports.text=A copy of our expense reports +reimbursementReceipts.text=The box of outstanding reimbursement receipts +invoices.text=A pile of invoices +employeeBenefitsInformation.text=A copy of our employee benefits information +insuranceDocuments.text=A hard drive containing insurance documents +lightBulbs.text=A box of spare light bulbs +strategicAlliancesInformation.text=A copy of our strategic alliance information +computers.text=A office computer +boots.text=A pair of boots +employeeDiscountStructures.text=A copy of the company's employee discount structures +meetingMinutes.text=A copy of company meeting minutes +itInfrastructureDetails.text=A file relating to our critical IT infrastructure +serverAccessCodes.text=A server-room access key +backupDrives.text=A backup drive +missionData.text=A log of sensitive mission data +executiveMeetingNotes.text=A copy of our executive meeting notes +toe.text=A copy of the company's TOE +clientComplaints.text=A file containing client complaints +inventoryControlSystems.text=A copy of the company's inventory control system +chairs.text=An office chair +shippingLogs.text=A copy of our shipping logs +printerPaper.text=A box of premium printer paper +internalAuditReports.text=A copy of an incriminating internal audit report +corruption.text=Some incriminating evidence of internal corruption +officePlants.text=A potted plant +battlefieldPerformanceReports.text=A copy of our battlefield performance reports +companyStandard.text=The company standard +analyticsReports.text=A copy of our combat analytics reports +fridge.text=The communal mini-fridge +coffeeMachine.text=The coffee machine +mug.text=The Commander's favorite mug +toiletSeats.text=A toilet seat +miniatures.text=An antique collection of BattleMech miniatures +dropShip.text=A ornamental DropShip in a bottle diff --git a/MekHQ/src/mekhq/campaign/mission/AtBContract.java b/MekHQ/src/mekhq/campaign/mission/AtBContract.java index f47fe80f6c..b80d1b66fc 100644 --- a/MekHQ/src/mekhq/campaign/mission/AtBContract.java +++ b/MekHQ/src/mekhq/campaign/mission/AtBContract.java @@ -288,7 +288,7 @@ public void checkMorale(LocalDate today, int dragoonRating) { routEnd = null; updateEnemy(today); // mix it up a little } else { - setMoraleLevel(AtBMoraleLevel.ROUT); + setMoraleLevel(AtBMoraleLevel.BROKEN); } return; } diff --git a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioFactory.java b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioFactory.java index adf8cf53c8..05b626b345 100644 --- a/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioFactory.java +++ b/MekHQ/src/mekhq/campaign/mission/atb/AtBScenarioFactory.java @@ -109,7 +109,7 @@ public static void registerScenario(IAtBScenario scenario) { /** * Iterate through the list of lances and make a scenario roll for each, * then sort them by date before adding them to the campaign. - * Contracts with enemy morale level of invincible have a base attack + * Contracts with enemy morale level of unbreakable have a base attack * (defender) scenario each week. If there is a base attack (attacker) * scenario, that is the only one for the week on that contracts. * @@ -191,7 +191,7 @@ public static void createScenariosForNewWeek(Campaign c) { assignedLances.add(lance.getForceId()); // We care if the scenario is a Base Attack, as one must be generated if the - // current contract's morale is Invincible + // current contract's morale is Unbreakable if (scenario.getScenarioType() == AtBScenario.BASEATTACK) { hasBaseAttack = true; @@ -208,9 +208,9 @@ public static void createScenariosForNewWeek(Campaign c) { } //endregion Generate Scenarios - //region Invincible Morale Missions - // Make sure invincible morale missions have a base attack scenario generated - if (!hasBaseAttack && contract.getMoraleLevel().isInvincible()) { + //region Unbreakable Morale Missions + // Make sure Unbreakable morale missions have a base attack scenario generated + if (!hasBaseAttack && contract.getMoraleLevel().isUnbreakable()) { /* find a lance to act as defender, giving preference * first to those assigned to the same contract, * then to those assigned to defense roles @@ -272,10 +272,10 @@ public static void createScenariosForNewWeek(Campaign c) { } } else { LogManager.getLogger().warn("No lances assigned to mission " + contract.getName() - + ". Can't generate an Invincible Morale base defence mission for this force."); + + ". Can't generate an Unbreakable Morale base defence mission for this force."); } } - //endregion Invincible Morale Missions + //endregion Unbreakable Morale Missions //region Base Attack (Attacker) Generated // If there is a base attack (attacker), it is the only one for this contract until it happens. diff --git a/MekHQ/src/mekhq/campaign/mission/enums/AtBMoraleLevel.java b/MekHQ/src/mekhq/campaign/mission/enums/AtBMoraleLevel.java index 0dbb941719..93ad70e4f5 100644 --- a/MekHQ/src/mekhq/campaign/mission/enums/AtBMoraleLevel.java +++ b/MekHQ/src/mekhq/campaign/mission/enums/AtBMoraleLevel.java @@ -25,13 +25,13 @@ public enum AtBMoraleLevel { //region Enum Declarations - ROUT("AtBMoraleLevel.ROUT.text", "AtBMoraleLevel.ROUT.toolTipText"), + BROKEN("AtBMoraleLevel.BROKEN.text", "AtBMoraleLevel.BROKEN.toolTipText"), VERY_LOW("AtBMoraleLevel.VERY_LOW.text", "AtBMoraleLevel.VERY_LOW.toolTipText"), LOW("AtBMoraleLevel.LOW.text", "AtBMoraleLevel.LOW.toolTipText"), NORMAL("AtBMoraleLevel.NORMAL.text", "AtBMoraleLevel.NORMAL.toolTipText"), HIGH("AtBMoraleLevel.HIGH.text", "AtBMoraleLevel.HIGH.toolTipText"), VERY_HIGH("AtBMoraleLevel.VERY_HIGH.text", "AtBMoraleLevel.VERY_HIGH.toolTipText"), - INVINCIBLE("AtBMoraleLevel.INVINCIBLE.text", "AtBMoraleLevel.INVINCIBLE.toolTipText"); + UNBREAKABLE("AtBMoraleLevel.UNBREAKABLE.text", "AtBMoraleLevel.UNBREAKABLE.toolTipText"); //endregion Enum Declarations //region Variable Declarations @@ -56,7 +56,7 @@ public String getToolTipText() { //region Boolean Comparison Methods public boolean isRout() { - return this == ROUT; + return this == BROKEN; } public boolean isVeryLow() { @@ -79,8 +79,8 @@ public boolean isVeryHigh() { return this == VERY_HIGH; } - public boolean isInvincible() { - return this == INVINCIBLE; + public boolean isUnbreakable() { + return this == UNBREAKABLE; } //endregion Boolean Comparison Methods @@ -99,7 +99,7 @@ public static AtBMoraleLevel parseFromString(final String text) { try { switch (Integer.parseInt(text)) { case 0: - return ROUT; + return BROKEN; case 1: return VERY_LOW; case 2: @@ -111,7 +111,7 @@ public static AtBMoraleLevel parseFromString(final String text) { case 5: return VERY_HIGH; case 6: - return INVINCIBLE; + return UNBREAKABLE; default: break; } diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index b425d8bb23..96ec439550 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -1,10 +1,7 @@ package mekhq.campaign.personnel.turnoverAndRetention; import megamek.codeUtilities.MathUtility; -import megamek.common.Compute; -import megamek.common.Entity; -import megamek.common.MechSummaryCache; -import megamek.common.Mounted; +import megamek.common.*; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; @@ -18,7 +15,6 @@ import mekhq.campaign.personnel.enums.PersonnelStatus; import mekhq.campaign.unit.Unit; import mekhq.gui.dialog.MutinySupportDialog; -import org.apache.logging.log4j.LogManager; import java.util.*; import java.util.stream.Collectors; @@ -27,7 +23,7 @@ public class Morale { /** - * This method returns the Morale level as a string based on the value of the 'Morale' variable. + * This method returns the Morale level as a string based on the campaign's current morale. * * @return The Morale level as a string. * @throws IllegalStateException if the value of 'Morale' is unexpected. @@ -36,61 +32,85 @@ public static String getMoraleLevel(Campaign campaign) { final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); - double morale = campaign.getMorale(); - - if ((morale >= 10) && (morale < 20)) { - return resources.getString("moraleLevelUnbreakable.text"); - } else if (morale < 30) { - return resources.getString("moraleLevelVeryHigh.text"); - } else if (morale < 40) { - return resources.getString("moraleLevelHigh.text"); - } else if (morale < 50) { - return resources.getString("moraleLevelNormal.text"); - } else if (morale < 60) { - return resources.getString("moraleLevelLow.text"); - } else if (morale < 70) { - return resources.getString("moraleLevelVeryLow.text"); - } else if (morale == 70) { - return resources.getString("moraleLevelBroken.text"); - } + int morale = campaign.getMorale() / 10; - throw new IllegalStateException("Unexpected value in getMoraleLevel(): " + campaign.getMorale()); + switch (morale) { + case 1: + return resources.getString("moraleLevelUnbreakable.text"); + case 2: + return resources.getString("moraleLevelVeryHigh.text"); + case 3: + return resources.getString("moraleLevelHigh.text"); + case 4: + return resources.getString("moraleLevelNormal.text"); + case 5: + return resources.getString("moraleLevelLow.text"); + case 6: + return resources.getString("moraleLevelVeryLow.text"); + case 7: + return resources.getString("moraleLevelBroken.text"); + default: + throw new IllegalStateException("Unexpected value in getMoraleLevel: " + morale); + } } /** - * Returns the target number for a morale check based on the campaign morale level and desertion flag. + * Calculates the morale check target number based on the campaign's morale and the desertion flag. * - * @param campaign the current campaign - * @param isDesertion whether the target number is for a desertion check - * @return the base target number for morale - * @throws IllegalStateException if the campaign morale level is unexpected + * @param campaign the current campaign + * @param isDesertion a flag indicating whether the target number is for desertion or not + * @return the calculated target number + * @throws IllegalStateException if the morale value is unexpected */ - private static Integer getTargetNumber(Campaign campaign, boolean isDesertion) { - double morale = campaign.getMorale(); + private static int getTargetNumber(Campaign campaign, boolean isDesertion) { + int morale = campaign.getMorale() / 10; - if ((morale >= 10) && (morale < 40)) { - return 0; - } else if (morale < 50) { - if (isDesertion) { - return 2; - } else { - return 0; - } - } else if (morale < 70) { - if (isDesertion) { - return 5; - } else { - return 4; - } - } else if (morale == 70) { - if (isDesertion) { - return 8; - } else { - return 7; - } + switch (morale) { + case 1: + if (isDesertion) { + return 0; + } else { + return -3; + } + case 2: + if (isDesertion) { + return 1; + } else { + return -2; + } + case 3: + if (isDesertion) { + return 1; + } else { + return -1; + } + case 4: + if (isDesertion) { + return 2; + } else { + return 0; + } + case 5: + if (isDesertion) { + return 4; + } else { + return 2; + } + case 6: + if (isDesertion) { + return 5; + } else { + return 4; + } + case 7: + if (isDesertion) { + return 8; + } else { + return 7; + } + default: + throw new IllegalStateException("Unexpected value in getTargetNumber: " + morale); } - - throw new IllegalStateException("Unexpected value in getTargetNumber: " + campaign.getMorale()); } /** @@ -361,7 +381,15 @@ public static void getMoraleReport(Campaign campaign) { campaign.addReport(moraleReport.toString()); } + /** + * Makes morale checks for personnel in a given campaign. + * + * @param campaign the campaign in which to make the morale checks + * @param isDesertion a boolean indicating if the checks are for desertion (true) or mutiny (false) + * @return true if someone has mutinied or deserted, false otherwise + */ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { + // we start with cases that cause not check to be needed if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { return false; } else if ((isDesertion) && (!campaign.getLocation().isOnPlanet())) { @@ -370,88 +398,122 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { return false; } - double targetNumber = getTargetNumber(campaign, isDesertion); - - if (targetNumber == 0) { - return isDesertion; - } + // Next, we gather essential information, such as the target number, + // personnel list, unit list (if unit theft is enabled, and mean loyalty score + int targetNumber = getTargetNumber(campaign, isDesertion); final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); - List filteredPersonnel = new ArrayList<>(); - int loyalty = 0; - - for (Person person : campaign.getActivePersonnel()) { - if ((person.getPrisonerStatus().isFree()) || (!isDesertion)) { - filteredPersonnel.add(person); - loyalty += person.getLoyalty(); - } - } + List filteredPersonnel = campaign.getActivePersonnel().stream() + .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> !person.isChild(campaign.getLocalDate())) + .collect(Collectors.toList()); - if ((!filteredPersonnel.isEmpty()) && (campaign.getCampaignOptions().getForceReliabilityMethod().isLoyalty())) { - loyalty = MathUtility.clamp(loyalty / filteredPersonnel.size(), -3, 3); - } else { - loyalty = 0; - } + int meanLoyalty = getMeanLoyalty(campaign, isDesertion); - ArrayList theftTargets = new ArrayList<>(); + List possibleTheftTargets = new ArrayList<>(); - if (isDesertion) { - theftTargets = campaign.getHangar().getUnits().stream() - .filter(unit -> (!unit.isDamaged()) && (!unit.isDeployed()) && (!unit.getEntity().isLargeCraft()) && (!unit.getEntity().isWarShip())) + if (campaign.getCampaignOptions().isUseTheftUnit()) { + possibleTheftTargets = campaign.getHangar().getUnits().stream() + .filter(unit -> + (!unit.isDamaged()) + && (!unit.isDeployed()) + && (!unit.getEntity().isLargeCraft()) + && (!unit.getEntity().isWarShip()) + ) .collect(Collectors.toCollection(ArrayList::new)); } - boolean someoneHasDeserted = false; - boolean someoneHasMutinied = false; - - List loyalists = new ArrayList<>(); + // Here we identify who the loyalist commander is Person loyalistLeader = campaign.getFlaggedCommander(); // if there is no Commander, we assume the highest ranked person is the loyalist leader if (loyalistLeader == null) { - campaign.getHighestRankedPerson(filteredPersonnel, true); + loyalistLeader = campaign.getHighestRankedPerson(filteredPersonnel, true); } + // Next we perform the actual checks, building a list of loyalists and rebels (if relevant) + List loyalists = new ArrayList<>(); HashMap rebels = new HashMap<>(); - for (Person person : filteredPersonnel) { - // the loyalistLeader can't rebel against themselves - if (Objects.equals(person, loyalistLeader)) { - loyalists.add(person); - continue; - } + boolean someoneHasDeserted = false; + boolean someoneHasMutinied = false; - int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, loyalty); + for (Person person : filteredPersonnel) { + int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, meanLoyalty); int firstRoll = Compute.d6(2) + modifier; - int secondRoll = 0; + int secondRoll = Compute.d6(2) + modifier; if (isDesertion) { - secondRoll = Compute.d6(2) + modifier; - } - - if ((firstRoll <= targetNumber) && (secondRoll <= targetNumber)) { - if (isDesertion) { - if ((processDesertion(campaign, person, secondRoll, targetNumber, theftTargets, resources)) - && (!someoneHasDeserted)) { - someoneHasDeserted = true; + if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { + possibleTheftTargets = processDesertion(campaign, person, secondRoll, targetNumber, possibleTheftTargets, resources); + someoneHasDeserted = true; + } + } else { + if (firstRoll < targetNumber) { + if (person.getUnit() != null) { + if ((person.getUnit().getCrew().contains(loyalistLeader)) && (person.isCommander())) { + loyalists.addAll(person.getUnit().getCrew()); + } else if (person.isCommander()) { + for (Person crew : person.getUnit().getCrew()) { + if (person.isCommander()) { + rebels.put(person, firstRoll); + } else { + rebels.put(crew, targetNumber - Compute.d6(1)); + } + } + } + } else { + rebels.put(person, firstRoll); } - } else { - rebels.put(person, firstRoll); + someoneHasMutinied = true; + } else { + if ((person.getUnit() != null) && (person.isCommander())) { + loyalists.addAll(person.getUnit().getCrew()); + } else if (person.getUnit() == null) { + loyalists.add(person); + } } - } else { - loyalists.add(person); } } - if (someoneHasMutinied) { - processMutiny(campaign, loyalistLeader, loyalists, rebels, theftTargets, resources); + // the rolls made, we check whether a mutiny or desertion has occurred and if so, process it + if (someoneHasDeserted) { + return true; + } else if (someoneHasMutinied) { + processMutiny(campaign, loyalistLeader, loyalists, rebels, possibleTheftTargets, resources); return true; + } + + return false; + } + + /** + * Calculates the mean loyalty of active personnel in a campaign. + * + * @param campaign the active personnel + * @param isDesertion true if desertions should be included in the calculation, false otherwise + * @return the mean loyalty of the active personnel in the campaign + */ + private static int getMeanLoyalty(Campaign campaign, boolean isDesertion) { + int loyalty = campaign.getActivePersonnel().stream() + .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> person.isChild(campaign.getLocalDate())) + .mapToInt(Person::getLoyalty) + .sum(); + + long personnel = campaign.getActivePersonnel().stream() + .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> person.isChild(campaign.getLocalDate())) + .count(); + + if (personnel == 0) { + return 0; } else { - return someoneHasDeserted; + return (int) (loyalty / personnel); } } @@ -462,13 +524,12 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { * @param person the potential deserter * @param roll the desertion roll result * @param targetNumber the target number for desertion= - * @param unitList the list of units + * @param possibleTheftTargets the list of units * @param resources the resource bundle for localized messages - * @return true if desertion occurred, false otherwise */ - private static boolean processDesertion(Campaign campaign, Person person, int roll, double targetNumber, - ArrayList unitList, ResourceBundle resources) { - double morale = campaign.getMorale(); + private static List processDesertion(Campaign campaign, Person person, int roll, int targetNumber, + List possibleTheftTargets, ResourceBundle resources) { + int morale = campaign.getMorale(); if (roll <= (targetNumber - 2)) { if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { @@ -484,23 +545,20 @@ private static boolean processDesertion(Campaign campaign, Person person, int ro } // check for theft - LogManager.getLogger().info(((roll + 3) < morale) && (campaign.getCampaignOptions().isUseTheftMoney())); - if (((roll + 5) < morale) && (campaign.getCampaignOptions().isUseTheftUnit())) { - processUnitTheft(campaign, person, unitList, resources); + if (((roll + 5) < morale) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!possibleTheftTargets.isEmpty())) { + possibleTheftTargets.remove(processUnitThefts(campaign, possibleTheftTargets, resources)); } else if (((roll + 4) < morale) && (campaign.getCampaignOptions().isUseTheftMoney())) { processMoneyTheft(campaign, resources); } else if ((roll + 3) < morale) { - processPettyTheft(campaign, person, resources); + processPettyTheft(campaign, resources); } - - return true; } } person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); person.setAwolDays(Compute.d6(2)); - return false; + return possibleTheftTargets; } /** @@ -511,7 +569,7 @@ private static boolean processDesertion(Campaign campaign, Person person, int ro * @param person The person whose original unit is to be reclaimed * @return 0 if the original unit is removed successfully, or an integer if removal fails */ - private static double reclaimOriginalUnit(Campaign campaign, Person person) { + private static int reclaimOriginalUnit(Campaign campaign, Person person) { UUID originalUnitId = person.getOriginalUnitId(); int originalUnitWeight = person.getOriginalUnitWeight(); @@ -522,7 +580,7 @@ private static double reclaimOriginalUnit(Campaign campaign, Person person) { if (!campaign.getUnit(originalUnitId).isDeployed()) { try { - campaign.getHangar().removeUnit(person.getOriginalUnitId()); + campaign.removeUnit(person.getOriginalUnitId()); return 0; } catch (Exception e) { return originalUnitWeight; @@ -533,36 +591,44 @@ private static double reclaimOriginalUnit(Campaign campaign, Person person) { } /** - * Processes unit theft. + * Processes unit thefts during a campaign. * - * @param campaign the current campaign - * @param person the person committing the theft - * @param unitList the list of stealable units - * @param resources the resource bundle for localization + * @param campaign The campaign in which the unit thefts occur. + * @param possibleTheftTargets The list of units that can be stolen. + * @param resources The resource bundle for internationalization. + * @return A list of stolen units. */ - private static void processUnitTheft(Campaign campaign, Person person, ArrayList unitList, ResourceBundle resources) { - Unit desiredUnit = unitList.get(new Random().nextInt(unitList.size())); - - StringBuilder unitName = new StringBuilder(desiredUnit.getName()); + private static Unit processUnitThefts(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { + // if there is nothing left to steal, downgrade the theft + if (possibleTheftTargets.isEmpty()) { + if (campaign.getCampaignOptions().isUseTheftMoney()) { + processMoneyTheft(campaign, resources); + } else { + processPettyTheft(campaign, resources); + } - if (!Objects.equals(desiredUnit.getFluffName(), "")) { - unitName.append(' ').append(desiredUnit.getFluffName()); + return null; } - if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 3)) { + // process the theft + Unit stolenUnit = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); + + if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 1)) { + int stolenUnitType = stolenUnit.getEntity().getUnitType(); + String stolenUnitShortNameRaw = stolenUnit.getEntity().getShortNameRaw(); + campaign.getUnitMarket().addSingleUnit(campaign, UnitMarketType.BLACK_MARKET, - desiredUnit.getEntity().getUnitType(), - MechSummaryCache.getInstance().getMech(desiredUnit.getEntity().getShortNameRaw()), + stolenUnitType, + MechSummaryCache.getInstance().getMech(stolenUnitShortNameRaw), 50); - campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheftBlackMarket.text"), unitName)); + campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), stolenUnit)); } else { - campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheft.text"), unitName)); + campaign.addReport(String.format(resources.getString("desertionTheft.text"), stolenUnit)); } - campaign.removeUnit(desiredUnit.getId()); - unitList.remove(desiredUnit); + return stolenUnit; } /** @@ -574,30 +640,7 @@ private static void processUnitTheft(Campaign campaign, Person person, ArrayList private static void processMoneyTheft(Campaign campaign, ResourceBundle resources) { int theftPercentage = campaign.getCampaignOptions().getTheftValue(); - switch(Compute.d6(2)) { - case 2: - theftPercentage += 3; - break; - case 3: - theftPercentage += 2; - break; - case 4: - case 5: - theftPercentage++; - break; - case 9: - theftPercentage--; - break; - case 10: - case 11: - theftPercentage -= 2; - break; - case 12: - theftPercentage -= 3; - break; - default: - break; - } + theftPercentage += getPercentageModifier(); Money theft = campaign.getFunds() .multipliedBy(theftPercentage) @@ -613,15 +656,54 @@ private static void processMoneyTheft(Campaign campaign, ResourceBundle resource } } + /** + * Gets the percentage modifier based on a roll of two six-sided dice. + * + * @return the percentage modifier based on the dice roll: + * - 3 for a roll of 2 + * - 2 for a roll of 3 + * - 1 for a roll of 4 or 5 + * - 0 for a roll of 6, 7, or 8 + * - -1 for a roll of 9 + * - -2 for a roll of 10 or 11 + * - -3 for a roll of 12 + * @throws IllegalStateException if the roll is unexpected + */ + private static int getPercentageModifier() { + int roll = Compute.d6(2); + + switch(roll) { + case 2: + return 3; + case 3: + return 2; + case 4: + case 5: + return 1; + case 6: + case 7: + case 8: + return 0; + case 9: + return -1; + case 10: + case 11: + return -2; + case 12: + return -3; + default: + throw new IllegalStateException("Unexpected value in getPercentageModifier: " + roll); + } + } + /** * This method is used to process a petty theft incident in a company. * It randomly selects an item from a list of stolen items and adds the item to the campaign report. * * @param campaign The campaign object to add the report to. - * @param person The person committing the theft * @param resources The ResourceBundle object to retrieve localized strings. */ - private static void processPettyTheft(Campaign campaign, Person person, ResourceBundle resources) { + private static void processPettyTheft(Campaign campaign, ResourceBundle resources) { List items = List.of( "stapler.text", "mascot.text", @@ -638,7 +720,7 @@ private static void processPettyTheft(Campaign campaign, Person person, Resource "marketingMaterials.text", "trainingPresentations.text", "softwareLicenses.text", - "employeeBelongings.text", + "rifle.text", "financialRecords.text", "employeeRecords.text", "proprietarySoftware.text", @@ -727,9 +809,10 @@ private static void processPettyTheft(Campaign campaign, Person person, Resource "coffeeMachine.text", "mug.text", "toiletSeats.text", - "miniatures.text"); + "miniatures.text", + "dropShip.text"); - campaign.addReport(person.getFullName() + ' ' + String.format(resources.getString("desertionTheft.text"), + campaign.addReport(String.format(resources.getString("desertionTheft.text"), resources.getString(items.get(new Random().nextInt(items.size()))))); } @@ -791,11 +874,10 @@ public static void processMoraleChange(Campaign campaign, int steps) { private static void processMutiny(Campaign campaign, Person loyalistLeader, List loyalists, HashMap rebels, - ArrayList unitList, + List unitList, ResourceBundle resources) { - // This prevents us from needing to do the full process for tiny mutinies that have no chance of success - if ((loyalists.size() / 10) > rebels.size()) { + if ((loyalists.size() / 2) > rebels.size()) { if (rebels.size() > 1) { campaign.addReport(String.format(resources.getString("mutinyThwartedPlural.text"), rebels.size())); } else { @@ -813,11 +895,19 @@ private static void processMutiny(Campaign campaign, List bystanders = new ArrayList<>(); // The rebels have already picked their side, so we only need to process the loyalists + // This represents the mutineers gathering support Iterator iterator = loyalists.iterator(); while (iterator.hasNext()) { Person person = iterator.next(); + // The loyalist leader isn't allowed to rebel against themselves + if (person.equals(loyalistLeader)) { + continue; + } + + + // we then process everyone else int roll = Compute.d6(1); int civilWarTargetNumber = getCivilWarTargetNumber(campaign, person); @@ -826,9 +916,7 @@ private static void processMutiny(Campaign campaign, iterator.remove(); bystanders.add(person); - } - else if (roll < (civilWarTargetNumber - 1)) { - + } else if (roll < (civilWarTargetNumber - 1)) { iterator.remove(); rebels.put(person, 0); } @@ -839,7 +927,6 @@ else if (roll < (civilWarTargetNumber - 1)) { // in which case we can assume they were persuaded into the role by the original mutineers Person rebelLeader = getRebelLeader(campaign, new ArrayList<>(rebels.keySet())); - // with the line drawn in the sand, we need to assess the forces available to each side HashMap rebelUnits = getUnits(new ArrayList<>(rebels.keySet()), false); int rebelBv = rebelUnits.keySet().stream() .mapToInt(unit -> unit.getEntity() @@ -1119,7 +1206,6 @@ private static int getCivilWarTargetNumber(Campaign campaign, Person person) { return 4 + modifier; } - /** * Retrieves the units that are eligible to participate in the civil war based on the provided personnel. * Multi-crewed units perform a vote to determine which side they join. diff --git a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java index 2ad6485f3b..46fdc8ca44 100644 --- a/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java +++ b/MekHQ/src/mekhq/campaign/stratcon/StratconRulesManager.java @@ -1364,15 +1364,16 @@ public static boolean canManuallyDeployAnyForce(StratconCoords coords, public static int calculateScenarioOdds(StratconTrackState track, AtBContract contract, boolean playerDeployingForce) { // rules: - // rout morale: 0% + // broken morale: 0% // very low morale: -10% when deploying forces to track, 0% attack // low morale: -5% // high morale: +5% - // invincible: special case, let's do +10% for now + // very high morale: +10% + // unbreakable: special case, let's do +15% for now int moraleModifier = 0; switch (contract.getMoraleLevel()) { - case ROUT: + case BROKEN: return 0; case VERY_LOW: if (playerDeployingForce) { @@ -1390,7 +1391,7 @@ public static int calculateScenarioOdds(StratconTrackState track, AtBContract co case VERY_HIGH: moraleModifier = 10; break; - case INVINCIBLE: + case UNBREAKABLE: moraleModifier = 15; break; default: From 8608593c7c587a038c4d0ef8284e2eaa0cbd8263 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 24 May 2024 23:20:45 -0500 Subject: [PATCH 058/101] Refactored morale system and added part theft functionality The morale system was refactored for improved functionality and clarity. Major adjustments include the addition of a part theft feature when personnel desert. These changes aimed at enhancing the game by increasing the implications of morale changes. --- .../CampaignOptionsDialog.properties | 6 + .../mekhq/resources/Morale.properties | 3 +- MekHQ/src/mekhq/campaign/Campaign.java | 12 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 43 +++- .../turnoverAndRetention/Morale.java | 190 +++++++++++++++--- .../mekhq/gui/panes/CampaignOptionsPane.java | 63 +++++- 6 files changed, 278 insertions(+), 39 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 4d2382c3a5..06027cd72b 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -319,10 +319,16 @@ chkUseSabotage.text=Enable Sabotage chkUseSabotage.toolTipText=Low morale influences maintenance checks. chkUseTheftUnit.text=Enable Theft of Units chkUseTheftUnit.toolTipText=Deserting personnel may steal units on their way out. +lblTheftResellValue.text=Average Stolen Unit Resell Value +lblTheftResellValue.toolTipText=If using AtB, stolen units may appear on the Black Market. This is the average percentage the unit will be sold at. chkUseTheftMoney.text=Enable Theft of Money chkUseTheftMoney.toolTipText=Deserting personnel may steal money on their way out. lblTheftValue.text=Average Heist Percentage lblTheftValue.toolTipText=The average percentage of unit funds stolen during a heist. +chkUseTheftParts.text=Enable Theft of Parts +chkUseTheftParts.toolTipText=Deserting personnel may steal parts on their way out. +lblTheftPartsDiceCount.text=Part Theft Dice Count +lblTheftPartsDiceCount.toolTipText=How many d6 (or batches of 3d6 armor or ammo) should be rolled to determine the number of parts stolen.
Set to 0 to only have one item (or 3d6 units of ammunition/armor) stolen per theft. turnoverAndRetentionMoraleModifiersPanel.title=Morale Modifiers lblCustomMoraleModifier.text=Custom Modifier diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 0aeae0681b..5e0e9ac79d 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -20,6 +20,7 @@ desertionAwolExtended.text=has extended their unauthorized leave. desertionTheft.text=%s has gone missing. desertionTheftBlackMarket.text=Someone is trying to sell one of our missing units on the Black Market: %s. desertionTheftMoney.text=The company accountants report an unusual payment of %s c-bills to an off-planet account. +desertionTheftParts.text=Our logistics software is reporting a number of missing items: desertionTheftTransactionReport.text=Suspicious transfer of funds to %s # Mutiny @@ -64,7 +65,7 @@ flashDrive.text=An incriminating flash drive companyCreditCard.text=The company credit card officePet.text=An office pet confidentialReports.text=A number of confidential reports -clientLists.text=A historic client list +clientLists.text=A list of prospective clients unitSchematics.text=A sensitive unit schematic businessPlans.text=A number of important business plans marketingMaterials.text=Vital marketing materials diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 8151af8c43..0537e6a781 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3579,22 +3579,22 @@ private void processMoraleNewDay() { // this processes morale recovery based on campaign location and the results of the prior checks if (desertionEvent && mutinyEvent) { if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, -2); + Morale.processMoraleChange(this, 2); } else { - Morale.processMoraleChange(this, -3); + Morale.processMoraleChange(this, 3); } } else if (desertionEvent) { if ((!getActiveContracts().isEmpty()) || (!getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, -1); + Morale.processMoraleChange(this, 1); } } else if (mutinyEvent) { if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, -1); + Morale.processMoraleChange(this, 1); } else { - Morale.processMoraleChange(this, -2); + Morale.processMoraleChange(this, 2); } } else { - Morale.processMoraleChange(this, 1); + Morale.processMoraleChange(this, -1); } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index e10cb163fe..8876885982 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -318,8 +318,11 @@ public static String getTransitUnitName(final int unit) { private ForceReliabilityMethod forceReliabilityMethod; private boolean useSabotage; private boolean useTheftUnit; + private int theftResellValue; private boolean useTheftMoney; private int theftValue; + private boolean useTheftParts; + private int theftPartsDiceCount; private boolean useMoraleTriggerFieldControl; private boolean useMoraleTriggerMissionStatus; @@ -1008,10 +1011,13 @@ public CampaignOptions() { setUseSabotage(true); setUseMutinies(true); setUseTheftUnit(true); + setTheftResellValue(50); setUseTheftMoney(true); setTheftValue(10); + setUseTheftParts(true); + setTheftPartsDiceCount(1); - setCustomMoraleModifier(2); + setCustomMoraleModifier(0); setUseMoraleTriggerFieldControl(true); setUseMoraleTriggerMissionStatus(true); setUseMoraleTriggerModifierLeaderLoss(true); @@ -1609,6 +1615,14 @@ public void setUseTheftUnit(final boolean useTheftUnit) { this.useTheftUnit = useTheftUnit; } + public int getTheftResellValue() { + return theftResellValue; + } + + public void setTheftResellValue(final int theftResellValue) { + this.theftResellValue = theftResellValue; + } + public boolean isUseTheftMoney() { return useTheftMoney; } @@ -1617,7 +1631,7 @@ public void setUseTheftMoney(final boolean useTheftMoney) { this.useTheftMoney = useTheftMoney; } - public Integer getTheftValue() { + public int getTheftValue() { return theftValue; } @@ -1625,6 +1639,22 @@ public void setTheftValue(final Integer theftValue) { this.theftValue = theftValue; } + public boolean isUseTheftParts() { + return useTheftParts; + } + + public void setUseTheftParts(final boolean useTheftParts) { + this.useTheftParts = useTheftParts; + } + + public int getTheftPartsDiceCount() { + return theftPartsDiceCount; + } + + public void setTheftPartsDiceCount(final int theftPartsDiceCount) { + this.theftPartsDiceCount = theftPartsDiceCount; + } + public boolean isUseMutinies() { return useMutinies; } @@ -4731,8 +4761,11 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMutinies", isUseMutinies()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheftUnit", isUseTheftUnit()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "theftResellValue", getTheftResellValue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheftMoney", isUseTheftMoney()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "theftValue", getTheftValue()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useTheftParts", isUseTheftParts()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "theftPartsDiceCount", getTheftPartsDiceCount()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "customMoraleModifier", getCustomMoraleModifier()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerFieldControl", isUseMoraleTriggerFieldControl()); @@ -5749,10 +5782,16 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseSabotage(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useTheftUnit")) { retVal.setUseTheftUnit(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("theftResellValue")) { + retVal.setTheftResellValue(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useTheftMoney")) { retVal.setUseTheftMoney(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("theftValue")) { retVal.setTheftValue(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useTheftParts")) { + retVal.setUseTheftParts(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useThetheftPartsDiceCountftParts")) { + retVal.setTheftPartsDiceCount(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMutinies")) { retVal.setUseMutinies(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerFieldControl")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 96ec439550..70d0ead12e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -8,6 +8,9 @@ import mekhq.campaign.finances.enums.TransactionType; import mekhq.campaign.finances.financialInstitutions.FinancialInstitutions; import mekhq.campaign.market.enums.UnitMarketType; +import mekhq.campaign.parts.AmmoStorage; +import mekhq.campaign.parts.Armor; +import mekhq.campaign.parts.Part; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.ForceReliabilityMethod; @@ -15,6 +18,7 @@ import mekhq.campaign.personnel.enums.PersonnelStatus; import mekhq.campaign.unit.Unit; import mekhq.gui.dialog.MutinySupportDialog; +import org.apache.logging.log4j.LogManager; import java.util.*; import java.util.stream.Collectors; @@ -66,6 +70,9 @@ private static int getTargetNumber(Campaign campaign, boolean isDesertion) { int morale = campaign.getMorale() / 10; switch (morale) { + case 0: + LogManager.getLogger().error("IMPORTANT: Morale has weirdly reset"); + return 0; case 1: if (isDesertion) { return 0; @@ -368,13 +375,13 @@ public static void getMoraleReport(Campaign campaign) { StringBuilder moraleReport = new StringBuilder(); - if (getTargetNumber(campaign, true) >= 2) { + if (getTargetNumber(campaign, true) > 2) { moraleReport.append(String.format(resources.getString("moraleReportLow.text"), getMoraleLevel(campaign))); } else { moraleReport.append(String.format(resources.getString("moraleReport.text"), getMoraleLevel(campaign))); } - if (getTargetNumber(campaign, false) >= 2) { + if (getTargetNumber(campaign, false) > 2) { moraleReport.append(' ').append(resources.getString("moraleReportMutiny.text")); } @@ -448,7 +455,8 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (isDesertion) { if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { - possibleTheftTargets = processDesertion(campaign, person, secondRoll, targetNumber, possibleTheftTargets, resources); + campaign.addReport(person.getFullName() + " failed their morale check [TN" + targetNumber + "] [rA" + firstRoll + "][rB" + secondRoll + ']'); + possibleTheftTargets.remove(processDesertion(campaign, person, secondRoll, targetNumber, possibleTheftTargets, resources)); someoneHasDeserted = true; } } else { @@ -527,7 +535,7 @@ private static int getMeanLoyalty(Campaign campaign, boolean isDesertion) { * @param possibleTheftTargets the list of units * @param resources the resource bundle for localized messages */ - private static List processDesertion(Campaign campaign, Person person, int roll, int targetNumber, + private static Unit processDesertion(Campaign campaign, Person person, int roll, int targetNumber, List possibleTheftTargets, ResourceBundle resources) { int morale = campaign.getMorale(); @@ -545,20 +553,25 @@ private static List processDesertion(Campaign campaign, Person person, int } // check for theft - if (((roll + 5) < morale) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!possibleTheftTargets.isEmpty())) { - possibleTheftTargets.remove(processUnitThefts(campaign, possibleTheftTargets, resources)); - } else if (((roll + 4) < morale) && (campaign.getCampaignOptions().isUseTheftMoney())) { + if (((roll + 6) < (morale / 10)) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!possibleTheftTargets.isEmpty())) { + return processUnitTheft(campaign, possibleTheftTargets, resources); + } else if (((roll + 5) < (morale / 10)) && (campaign.getCampaignOptions().isUseTheftMoney())) { processMoneyTheft(campaign, resources); - } else if ((roll + 3) < morale) { + return null; + } else if (((roll + 4) < (morale / 10)) && (campaign.getCampaignOptions().isUseTheftParts())) { + processPartTheft(campaign, resources); + return null; + } else if ((roll + 3) < (morale / 10)) { processPettyTheft(campaign, resources); + return null; } } + } else { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); + person.setAwolDays(Compute.d6(2)); } - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); - person.setAwolDays(Compute.d6(2)); - - return possibleTheftTargets; + return null; } /** @@ -598,11 +611,13 @@ private static int reclaimOriginalUnit(Campaign campaign, Person person) { * @param resources The resource bundle for internationalization. * @return A list of stolen units. */ - private static Unit processUnitThefts(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { + private static Unit processUnitTheft(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { // if there is nothing left to steal, downgrade the theft if (possibleTheftTargets.isEmpty()) { if (campaign.getCampaignOptions().isUseTheftMoney()) { processMoneyTheft(campaign, resources); + } else if (campaign.getCampaignOptions().isUseTheftParts()) { + processPartTheft(campaign, resources); } else { processPettyTheft(campaign, resources); } @@ -612,8 +627,13 @@ private static Unit processUnitThefts(Campaign campaign, List possibleThef // process the theft Unit stolenUnit = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); + String theftString = stolenUnit.getName(); - if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 1)) { + if (!Objects.equals(stolenUnit.getFluffName(), "")) { + theftString += ' ' + stolenUnit.getFluffName(); + } + + if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 5)) { int stolenUnitType = stolenUnit.getEntity().getUnitType(); String stolenUnitShortNameRaw = stolenUnit.getEntity().getShortNameRaw(); @@ -621,11 +641,12 @@ private static Unit processUnitThefts(Campaign campaign, List possibleThef UnitMarketType.BLACK_MARKET, stolenUnitType, MechSummaryCache.getInstance().getMech(stolenUnitShortNameRaw), - 50); + campaign.getCampaignOptions().getTheftResellValue() + getPercentageModifier()); - campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), stolenUnit)); + campaign.addReport(String.format(resources.getString("desertionTheft.text"), theftString)); + campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), theftString)); } else { - campaign.addReport(String.format(resources.getString("desertionTheft.text"), stolenUnit)); + campaign.addReport(String.format(resources.getString("desertionTheft.text"), theftString)); } return stolenUnit; @@ -653,6 +674,10 @@ private static void processMoneyTheft(Campaign campaign, ResourceBundle resource FinancialInstitutions.randomFinancialInstitution(campaign.getLocalDate()).toString())); campaign.addReport(String.format(String.format(resources.getString("desertionTheftMoney.text"), theft.getAmount()))); + } else if (campaign.getCampaignOptions().isUseTheftParts()) { + processPartTheft(campaign, resources); + } else { + processPettyTheft(campaign, resources); } } @@ -696,6 +721,116 @@ private static int getPercentageModifier() { } } + private static void processPartTheft(Campaign campaign, ResourceBundle resources) { + List possibleTheftTargets = campaign.getWarehouse().getSpareParts(); + + // if there are no parts to steal, commit petty theft instead + if (possibleTheftTargets.isEmpty()) { + processPettyTheft(campaign, resources); + + return; + } + + // how many thefts should be rolled? + int originalTheftCount = 1; + + if (campaign.getCampaignOptions().getTheftPartsDiceCount() != 0) { + originalTheftCount = Compute.d6(campaign.getCampaignOptions().getTheftPartsDiceCount()); + } + + boolean committingTheft = true; + int theftCount = originalTheftCount; + HashMap stolenItems = new HashMap<>(); + + while (committingTheft) { + if (possibleTheftTargets.isEmpty()) { + if (stolenItems.isEmpty()) { + processPettyTheft(campaign, resources); + + return; + } + committingTheft = false; + continue; + } + + Part desiredPart = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); + + boolean partStolen = false; + + while (!partStolen) { + boolean hasParent = true; + + while (hasParent) { + if (desiredPart.getParentPart() != null) { + possibleTheftTargets.remove(desiredPart); + desiredPart = desiredPart.getParentPart(); + } else { + hasParent = false; + } + } + + // if the part is in transit, + // we don't want to steal it, so we pick another item + if (desiredPart.getDaysToArrival() > 0) { + possibleTheftTargets.remove(desiredPart); + partStolen = true; + continue; + } else if (desiredPart.getDaysToWait() > 0) { + possibleTheftTargets.remove(desiredPart); + partStolen = true; + continue; + } + + // if the part is being actively worked on, + // we don't want to steal it, so we pick another item + if (desiredPart.isBeingWorkedOn()) { + possibleTheftTargets.remove(desiredPart); + partStolen = true; + continue; + } + + // this is where we try to steal an item + if ((desiredPart instanceof AmmoStorage) || (desiredPart instanceof Armor)) { + int roll = Compute.d6(3); + + if (campaign.getWarehouse().removePart(desiredPart, roll)) { + theftCount--; + possibleTheftTargets.remove(desiredPart); + stolenItems.put(desiredPart.getName(), roll); + } else { + LogManager.getLogger().info("Part theft failed to steal ammo/armor ({})", desiredPart); + partStolen = true; + possibleTheftTargets.remove(desiredPart); + continue; + } + } else { + if (campaign.getWarehouse().removePart(desiredPart, 1)) { + theftCount--; + possibleTheftTargets.remove(desiredPart); + stolenItems.put(desiredPart.getName(), 1); + } else { + LogManager.getLogger().info("Part theft failed to steal part ({})", desiredPart); + partStolen = true; + possibleTheftTargets.remove(desiredPart); + continue; + } + } + + partStolen = true; + + if (theftCount == 0) { + committingTheft = false; + + campaign.addReport(stolenItems.keySet().stream() + .map(entry -> " [" + stolenItems.get(entry) + "x " + entry + ']') + .collect(Collectors.joining("" + , resources.getString("desertionTheftParts.text") + , ""))); + } + } + } + } + /** * This method is used to process a petty theft incident in a company. * It randomly selects an item from a list of stolen items and adds the item to the campaign report. @@ -855,26 +990,25 @@ public static void processMoraleChange(Campaign campaign, int steps) { final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", MekHQ.getMHQOptions().getLocale()); - int morale = campaign.getMorale(); int change = campaign.getCampaignOptions().getMoraleStepSize() * steps; + int oldMorale = campaign.getMorale(); + int newMorale = MathUtility.clamp(campaign.getMorale() + change, 10, 70); + + campaign.setMorale(newMorale); - if ((change > 0) && (morale != 1)) { - campaign.setMorale(MathUtility.clamp(morale - (steps * campaign.getCampaignOptions().getMoraleStepSize()), 1, 7)); + if ((oldMorale / 10) != (newMorale / 10)) { getMoraleReport(campaign); - if ((morale >= 5) && ((morale + steps) < 5)) { + if ((oldMorale >= 50) && (newMorale < 50)) { campaign.addReport(resources.getString("moraleReportRecovered.text")); } - } else if ((change < 0) && (morale != 7)) { - campaign.setMorale(MathUtility.clamp(morale + change, 1, 7)); - getMoraleReport(campaign); } } private static void processMutiny(Campaign campaign, Person loyalistLeader, List loyalists, HashMap rebels, - List unitList, + List possibleTheftTargets, ResourceBundle resources) { // This prevents us from needing to do the full process for tiny mutinies that have no chance of success if ((loyalists.size() / 2) > rebels.size()) { @@ -885,7 +1019,11 @@ private static void processMutiny(Campaign campaign, } for (Person person : rebels.keySet()) { - processDesertion(campaign, person, rebels.get(person), getTargetNumber(campaign, true), unitList, resources); + possibleTheftTargets.remove(processDesertion(campaign, + person, + rebels.get(person), + getTargetNumber(campaign, true), + possibleTheftTargets, resources)); } return; diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index b9ded3212c..b8fc43ecb0 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -340,9 +340,14 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseSabotage; private JCheckBox chkUseMutinies; private JCheckBox chkUseTheftUnit; + private JLabel lblTheftResellValue; + private JSpinner spnTheftResellValue; private JCheckBox chkUseTheftMoney; private JLabel lblTheftValue; private JSpinner spnTheftValue; + private JCheckBox chkUseTheftParts; + private JLabel lblTheftPartsDiceCount; + private JSpinner spnTheftPartsDiceCount; private JPanel turnoverAndRetentionMoraleModifiersPanel = new JPanel(); private JLabel lblCustomMoraleModifier; @@ -4859,9 +4864,14 @@ private JPanel createTurnoverAndRetentionMoralePanel() { chkUseMoraleTriggerFatigue.setEnabled((isEnabled) && (chkUseFatigue.isSelected())); chkUseTheftUnit.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); + lblTheftResellValue.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); + spnTheftResellValue.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); chkUseTheftMoney.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); lblTheftValue.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); spnTheftValue.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); + chkUseTheftParts.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); + lblTheftPartsDiceCount.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); + spnTheftPartsDiceCount.setEnabled((isEnabled) && ((chkUseDesertions.isSelected()) || (chkUseMutinies.isSelected()))); }); createMoraleSubPanel(); @@ -4953,6 +4963,16 @@ public Component getListCellRendererComponent(final JList list, final Object chkUseTheftUnit.setName("chkUseTheftUnit"); chkUseTheftUnit.setEnabled(isUseMorale); + lblTheftResellValue = new JLabel(resources.getString("lblTheftResellValue.text")); + lblTheftResellValue.setToolTipText(resources.getString("lblTheftResellValue.toolTipText")); + lblTheftResellValue.setName("lblTheftResellValue"); + lblTheftResellValue.setEnabled(isUseMorale); + + spnTheftResellValue = new JSpinner(new SpinnerNumberModel(50, 1, 100, 1)); + spnTheftResellValue.setToolTipText(resources.getString("lblTheftResellValue.toolTipText")); + spnTheftResellValue.setName("spnTheftResellValue"); + spnTheftResellValue.setEnabled(isUseMorale); + chkUseTheftMoney = new JCheckBox(resources.getString("chkUseTheftMoney.text")); chkUseTheftMoney.setToolTipText(resources.getString("chkUseTheftMoney.toolTipText")); chkUseTheftMoney.setName("chkUseTheftMoney"); @@ -4968,6 +4988,21 @@ public Component getListCellRendererComponent(final JList list, final Object spnTheftValue.setName("spnTheftValue"); spnTheftValue.setEnabled(isUseMorale); + chkUseTheftParts = new JCheckBox(resources.getString("chkUseTheftParts.text")); + chkUseTheftParts.setToolTipText(resources.getString("chkUseTheftParts.toolTipText")); + chkUseTheftParts.setName("chkUseTheftParts"); + chkUseTheftParts.setEnabled(isUseMorale); + + lblTheftPartsDiceCount = new JLabel(resources.getString("lblTheftPartsDiceCount.text")); + lblTheftPartsDiceCount.setToolTipText(resources.getString("lblTheftPartsDiceCount.toolTipText")); + lblTheftPartsDiceCount.setName("lblTheftPartsDiceCount"); + lblTheftPartsDiceCount.setEnabled(isUseMorale); + + spnTheftPartsDiceCount = new JSpinner(new SpinnerNumberModel(1, 0, 3, 1)); + spnTheftPartsDiceCount.setToolTipText(resources.getString("lblTheftPartsDiceCount.toolTipText")); + spnTheftPartsDiceCount.setName("spnTheftPartsDiceCount"); + spnTheftPartsDiceCount.setEnabled(isUseMorale); + moraleSubPanel.setName("moraleSubPanel"); moraleSubPanel.setBorder(BorderFactory.createTitledBorder("")); moraleSubPanel.setEnabled(isUseMorale); @@ -4989,10 +5024,17 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) .addComponent(chkUseSabotage) .addComponent(chkUseTheftUnit) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblTheftResellValue) + .addComponent(spnTheftResellValue, Alignment.LEADING)) .addComponent(chkUseTheftMoney) .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblTheftValue) - .addComponent(spnTheftValue, Alignment.LEADING)) + .addComponent(spnTheftValue) + .addComponent(lblTheftValue, Alignment.LEADING)) + .addComponent(chkUseTheftParts) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblTheftPartsDiceCount) + .addComponent(spnTheftPartsDiceCount, Alignment.LEADING)) ); layout.setHorizontalGroup( @@ -5006,11 +5048,18 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod)) .addComponent(chkUseSabotage) + .addGroup(layout.createSequentialGroup() + .addComponent(lblTheftResellValue) + .addComponent(spnTheftResellValue)) .addComponent(chkUseTheftUnit) .addComponent(chkUseTheftMoney) .addGroup(layout.createSequentialGroup() .addComponent(lblTheftValue) .addComponent(spnTheftValue)) + .addComponent(chkUseTheftParts) + .addGroup(layout.createSequentialGroup() + .addComponent(lblTheftPartsDiceCount) + .addComponent(spnTheftPartsDiceCount)) ); } @@ -5022,7 +5071,7 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { lblCustomMoraleModifier.setName("lblCustomMoraleModifier"); lblCustomMoraleModifier.setEnabled(isUseMorale); - spnCustomMoraleModifier = new JSpinner(new SpinnerNumberModel(2, -5, 5, 1)); + spnCustomMoraleModifier = new JSpinner(new SpinnerNumberModel(0, -10, 10, 1)); spnCustomMoraleModifier.setToolTipText(resources.getString("lblCustomMoraleModifier.toolTipText")); spnCustomMoraleModifier.setName("spnCustomMoraleModifier"); spnCustomMoraleModifier.setEnabled(isUseMorale); @@ -8273,8 +8322,11 @@ public void setOptions(@Nullable CampaignOptions options, comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); chkUseSabotage.setSelected(options.isUseSabotage()); chkUseTheftUnit.setSelected(options.isUseTheftUnit()); + spnTheftResellValue.setValue(options.getTheftResellValue()); chkUseTheftMoney.setSelected(options.isUseTheftMoney()); spnTheftValue.setValue(options.getTheftValue()); + chkUseTheftParts.setSelected(options.isUseTheftParts()); + spnTheftPartsDiceCount.setValue(options.getTheftPartsDiceCount()); // Morale Modifiers spnCustomMoraleModifier.setValue(options.getCustomMoraleModifier()); @@ -8964,12 +9016,15 @@ public void updateOptions() { options.setUseDesertions(chkUseDesertions.isSelected()); options.setUseMutinies(chkUseMutinies.isSelected()); - options.setMoraleStepSize((int) spnMoraleStepSize.getValue()); + options.setMoraleStepSize((Integer) spnMoraleStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); options.setUseSabotage(chkUseSabotage.isSelected()); options.setUseTheftUnit(chkUseTheftUnit.isSelected()); + options.setTheftResellValue((Integer) spnTheftResellValue.getValue()); options.setUseTheftMoney(chkUseTheftMoney.isSelected()); options.setTheftValue((Integer) spnTheftValue.getValue()); + options.setUseTheftParts(chkUseTheftParts.isSelected()); + options.setTheftPartsDiceCount((Integer) spnTheftPartsDiceCount.getValue()); // Morale Modifiers options.setUseMoraleTriggerFieldControl(chkUseMoraleTriggerFieldControl.isSelected()); From d050920654238978c00e162a6aa63f3aee84ab22 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sat, 25 May 2024 01:38:52 -0500 Subject: [PATCH 059/101] Added leadership styles in morale calculation This update introduces different styles of leadership that can be selected for the campaign. These styles apply modifiers to desertion and mutiny rolls based on the leadership style chosen. The 'Iron Fist' rule has been incorporated as one of these leadership styles. Other styles introduced include 'Mercenary', 'Family', 'Elite', and 'Green'. --- .../CampaignOptionsDialog.properties | 6 +- .../mekhq/resources/Personnel.properties | 12 ++++ MekHQ/src/mekhq/campaign/CampaignOptions.java | 56 ++++++++++------ .../personnel/enums/LeadershipMethod.java | 66 +++++++++++++++++++ .../turnoverAndRetention/Morale.java | 36 +++++++--- .../mekhq/gui/panes/CampaignOptionsPane.java | 53 +++++++++++---- 6 files changed, 187 insertions(+), 42 deletions(-) create mode 100644 MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 06027cd72b..ed72d4fa09 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -345,8 +345,10 @@ chkUseMoraleModifierCommanderSkill.text=Commander's Management Skill chkUseMoraleModifierCommanderSkill.toolTipText=The commander having a positive Management Skill modifier improves both desertion and mutiny rolls. chkUseMoraleModifierLoyalty.text=Personal Loyalty chkUseMoraleModifierLoyalty.toolTipText=Personal loyalty adjusts both desertion and mutiny rolls. Stacks with the Loyalty force reliability method. -chkUseRuleWithIronFist.text=Rule with an Iron Fist -chkUseRuleWithIronFist.toolTipText=Where there is a whip, there is a way. Desertion and Mutiny rolls have a +1 modifier, but desertions and mutinies are more destructive. +chkUseMoraleModifierCabinFever.text=Cabin Fever +chkUseMoraleModifierCabinFever.toolTipText=Desertions are disabled while in transit. To compensate, this options adds a -1 modifier to Mutiny rolls. +lblMoraleModifierLeadershipMethod.text=Leadership Style +lblMoraleModifierLeadershipMethod.toolTipText=What style of leadership best describes the unit? turnoverAndRetentionMoraleTriggersPanel.title=Morale Change Triggers chkUseMoraleTriggerFieldControl.text=Field Control diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index f9a5f1f679..dde755efb6 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -490,6 +490,18 @@ ForceReliabilityMethod.OVERRIDE_D.toolTipText=Desertion checks have no modifier. ForceReliabilityMethod.OVERRIDE_F.text=Fixed: F (Questionable) ForceReliabilityMethod.OVERRIDE_F.toolTipText=Desertion and Mutiny checks have a -1 modifier. +# LeadershipMethod Enum +LeadershipMethod.MERCENARY.text=Mercenary +LeadershipMethod.MERCENARY.toolTipText=The average mercenary company. Applies no additional modifiers. +LeadershipMethod.FAMILY.text=Family +LeadershipMethod.FAMILY.toolTipText=Family treats everyone equally, but the unit lacks discipline. Applies a -1 modifier to Desertion rolls, but a +1 modifier to Mutiny rolls. +LeadershipMethod.ELITE.text=Elite +LeadershipMethod.ELITE.toolTipText=An elite, disciplined and professional fighting force. Applies a +1 modifier to both Desertion and Mutiny rolls. +LeadershipMethod.GREEN.text=Green +LeadershipMethod.GREEN.toolTipText=An inexperienced commander does not inspire confidence or respect. Applies a -1 modifier to Desertion rolls. +LeadershipMethod.IRON_FIST.text=Iron Fist +LeadershipMethod.IRON_FIST.toolTipText=Where there's a whip, there's a way. Applies a +1 modifier to Desertion and Mutiny rolls, but both desertions and mutinies are more impactful. + # RankSystemType Enum RankSystemType.DEFAULT.text=Default Rank System RankSystemType.DEFAULT.toolTipText=This rank system is a default MekHQ rank system and is stored within the standard data path. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 8876885982..09633f040d 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -331,7 +331,7 @@ public static String getTransitUnitName(final int unit) { private boolean useMoraleTriggerDesertion; private boolean useMoraleTriggerMutiny; private boolean useMoraleTriggerFatigue; - private boolean useMoraleModifierMissedPayDay; + private boolean useMoraleTriggerMissedPayDay; private int customMoraleModifier; private boolean useMoraleModifierExperienceLevel; @@ -340,7 +340,8 @@ public static String getTransitUnitName(final int unit) { private boolean useMoraleModifierForceReliability; private boolean useMoraleModifierCommanderLeadership; private boolean useMoraleModifierLoyalty; - private boolean useRuleWithIronFist; + private boolean useMoraleModifierCabinFever; + private LeadershipMethod moraleModifierLeadershipMethod; // Family private FamilialRelationshipDisplayLevel familyDisplayLevel; @@ -1025,6 +1026,7 @@ public CampaignOptions() { setUseMoraleTriggerDesertion(true); setUseMoraleTriggerMutiny(true); setUseMoraleTriggerFatigue(true); + setUseMoraleTriggerMissedPayDay(true); setUseMoraleModifierExperienceLevel(true); setUseMoraleModifierFaction(true); @@ -1032,8 +1034,8 @@ public CampaignOptions() { setUseMoraleModifierForceReliability(true); setUseMoraleModifierCommanderLeadership(true); setUseMoraleModifierLoyalty(true); - setUseMoraleModifierMissedPayDay(true); - setUseRuleWithIronFist(false); + setUseMoraleModifierCabinFever(true); + setMoraleModifierLeadershipMethod(LeadershipMethod.MERCENARY); //endregion Turnover and Retention //region Finances Tab @@ -1663,14 +1665,6 @@ public void setUseMutinies(final boolean useMutinies) { this.useMutinies = useMutinies; } - public boolean isUseRuleWithIronFist() { - return useRuleWithIronFist; - } - - public void setUseRuleWithIronFist(final boolean useRuleWithIronFist) { - this.useRuleWithIronFist = useRuleWithIronFist; - } - public Integer getCustomMoraleModifier() { return customMoraleModifier; } @@ -1711,12 +1705,29 @@ public void setUseMoraleModifierLoyalty(final boolean useMoraleModifierLoyalty) this.useMoraleModifierLoyalty = useMoraleModifierLoyalty; } - public boolean isUseMoraleModifierMissedPayDay() { - return useMoraleModifierMissedPayDay; + public boolean isUseMoraleModifierCabinFever() { + return useMoraleModifierCabinFever; + } + + public void setUseMoraleModifierCabinFever(final boolean useMoraleModifierCabinFever) { + this.useMoraleModifierCabinFever = useMoraleModifierCabinFever; + } + + public LeadershipMethod getMoraleModifierLeadershipMethod() { + return moraleModifierLeadershipMethod; + } + + public void setMoraleModifierLeadershipMethod(final LeadershipMethod moraleModifierLeadershipMethod) { + this.moraleModifierLeadershipMethod = moraleModifierLeadershipMethod; + } + + + public boolean isUseMoraleTriggerMissedPayDay() { + return useMoraleTriggerMissedPayDay; } - public void setUseMoraleModifierMissedPayDay(final boolean useMoraleModifierMissedPayDay) { - this.useMoraleModifierMissedPayDay = useMoraleModifierMissedPayDay; + public void setUseMoraleTriggerMissedPayDay(final boolean useMoraleTriggerMissedPayDay) { + this.useMoraleTriggerMissedPayDay = useMoraleTriggerMissedPayDay; } public boolean isUseMoraleTriggerFatigue() { @@ -4775,7 +4786,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerDesertion", isUseMoraleTriggerDesertion()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerMutiny", isUseMoraleTriggerMutiny()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleTriggerFatigue", isUseMoraleTriggerFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "chkUseMoraleTriggerMissedPayDay", isUseMoraleModifierMissedPayDay()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "chkUseMoraleTriggerMissedPayDay", isUseMoraleTriggerMissedPayDay()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierExperienceLevel", isUseMoraleModifierExperienceLevel()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierFaction", isUseMoraleModifierFaction()); @@ -4783,7 +4794,8 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierForceReliability", isUseMoraleModifierForceReliability()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCommanderLeadership", isUseMoraleModifierCommanderLeadership()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierLoyalty", isUseMoraleModifierLoyalty()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useRuleWithIronFist", isUseRuleWithIronFist()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCabinFever", isUseMoraleModifierCabinFever()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "moraleModifierLeadershipMethod", getMoraleModifierLeadershipMethod().name()); //endregion Retirement //region Family @@ -5822,10 +5834,12 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseMoraleModifierCommanderLeadership(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierLoyalty")) { retVal.setUseMoraleModifierLoyalty(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierCabinFever")) { + retVal.setUseMoraleModifierCabinFever(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerMissedPayDay")) { - retVal.setUseMoraleModifierMissedPayDay(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useRuleWithIronFist")) { - retVal.setUseRuleWithIronFist(Boolean.parseBoolean(wn2.getTextContent().trim())); + retVal.setUseMoraleTriggerMissedPayDay(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("moraleModifierLeadershipMethod")) { + retVal.setMoraleModifierLeadershipMethod(LeadershipMethod.valueOf(wn2.getTextContent().trim())); //endregion Turnover and Retention //region Finances Tab diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java new file mode 100644 index 0000000000..1954d0525f --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021-2022 - 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.personnel.enums; + +import mekhq.MekHQ; + +import java.util.ResourceBundle; + +public enum LeadershipMethod { + MERCENARY("LeadershipMethod.MERCENARY.text", "LeadershipMethod.MERCENARY.toolTipText"), + FAMILY("LeadershipMethod.FAMILY.text", "LeadershipMethod.FAMILY.toolTipText"), + ELITE("LeadershipMethod.ELITE.text", "LeadershipMethod.ELITE.toolTipText"), + GREEN("LeadershipMethod.GREEN.text", "LeadershipMethod.GREEN.toolTipText"), + IRON_FIST("LeadershipMethod.IRON_FIST.text", "LeadershipMethod.IRON_FIST.toolTipText"); + + private final String name; + private final String toolTipText; + + LeadershipMethod(final String name, final String toolTipText) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", + MekHQ.getMHQOptions().getLocale()); + this.name = resources.getString(name); + this.toolTipText = resources.getString(toolTipText); + } + + public String getToolTipText() { + return toolTipText; + } + + public boolean isMercenary() { + return this == MERCENARY; + } + + public boolean isElite() { + return this == ELITE; + } + + public boolean isFamily() { + return this == FAMILY; + } + + public boolean isIronFist() { + return this == IRON_FIST; + } + + @Override + public String toString() { + return name; + } +} diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 70d0ead12e..8a20d36048 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -1,7 +1,10 @@ package mekhq.campaign.personnel.turnoverAndRetention; import megamek.codeUtilities.MathUtility; -import megamek.common.*; +import megamek.common.Compute; +import megamek.common.Entity; +import megamek.common.MechSummaryCache; +import megamek.common.Mounted; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; @@ -181,9 +184,26 @@ private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, modifier += getLoyaltyModifier(isDesertion, person.getLoyalty()); } - // Iron Fist Modifier - if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { - modifier++; + // Leadership Method Modifier + switch (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod()) { + case MERCENARY: + break; + case FAMILY: + if (isDesertion) { + modifier--; + } else { + modifier++; + } + break; + case GREEN: + if (isDesertion) { + modifier--; + } + break; + case ELITE: + case IRON_FIST: + modifier++; + break; } return modifier; @@ -540,8 +560,8 @@ private static Unit processDesertion(Campaign campaign, Person person, int roll, int morale = campaign.getMorale(); if (roll <= (targetNumber - 2)) { - if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { - morale -= 1; + if (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod().isIronFist()) { + morale -= 2; } if (roll <= (morale - 2)) { @@ -1318,8 +1338,8 @@ private static HashMap getAbstractBattleStatistics(List private static int getCivilWarTargetNumber(Campaign campaign, Person person) { int modifier = 0; - if (campaign.getCampaignOptions().isUseRuleWithIronFist()) { - modifier++; + if (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod().isIronFist()) { + modifier += 2; } if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index b8fc43ecb0..be7e20f8e9 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -358,7 +358,9 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseMoraleModifierForceReliability; private JCheckBox chkUseMoraleModifierCommanderSkill; private JCheckBox chkUseMoraleModifierLoyalty; - private JCheckBox chkUseRuleWithIronFist; + private JCheckBox chkUseMoraleModifierCabinFever; + private JLabel lblMoraleModifierLeadershipMethod; + private MMComboBox comboMoraleModifierLeadershipMethod; private JPanel turnoverAndRetentionMoraleTriggersPanel = new JPanel(); private JCheckBox chkUseMoraleTriggerFieldControl; @@ -5106,10 +5108,31 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { chkUseMoraleModifierLoyalty.setName("chkUseMoraleModifierLoyalty"); chkUseMoraleModifierLoyalty.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); - chkUseRuleWithIronFist = new JCheckBox(resources.getString("chkUseRuleWithIronFist.text")); - chkUseRuleWithIronFist.setToolTipText(resources.getString("chkUseRuleWithIronFist.toolTipText")); - chkUseRuleWithIronFist.setName("chkUseRuleWithIronFist"); - chkUseRuleWithIronFist.setEnabled(isUseMorale); + chkUseMoraleModifierCabinFever = new JCheckBox(resources.getString("chkUseMoraleModifierCabinFever.text")); + chkUseMoraleModifierCabinFever.setToolTipText(resources.getString("chkUseMoraleModifierCabinFever.toolTipText")); + chkUseMoraleModifierCabinFever.setName("chkUseMoraleModifierCabinFever"); + chkUseMoraleModifierCabinFever.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); + + lblMoraleModifierLeadershipMethod = new JLabel(resources.getString("lblMoraleModifierLeadershipMethod.text")); + lblMoraleModifierLeadershipMethod.setToolTipText(resources.getString("lblMoraleModifierLeadershipMethod.toolTipText")); + lblMoraleModifierLeadershipMethod.setName("lblMoraleModifierLeadershipMethod"); + lblMoraleModifierLeadershipMethod.setEnabled(isUseMorale); + + comboMoraleModifierLeadershipMethod = new MMComboBox<>("comboMoraleModifierLeadershipMethod", LeadershipMethod.values()); + comboMoraleModifierLeadershipMethod.setToolTipText(resources.getString("lblMoraleModifierLeadershipMethod.toolTipText")); + comboMoraleModifierLeadershipMethod.setEnabled(isUseMorale); + comboMoraleModifierLeadershipMethod.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(final JList list, final Object value, + final int index, final boolean isSelected, + final boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof LeadershipMethod) { + list.setToolTipText(((LeadershipMethod) value).getToolTipText()); + } + return this; + } + }); turnoverAndRetentionMoraleModifiersPanel.setName("turnoverAndRetentionMoraleModifiersPanel"); turnoverAndRetentionMoraleModifiersPanel.setBorder(BorderFactory.createTitledBorder(resources.getString("turnoverAndRetentionMoraleModifiersPanel.title"))); @@ -5131,7 +5154,10 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { .addComponent(chkUseMoraleModifierForceReliability) .addComponent(chkUseMoraleModifierCommanderSkill) .addComponent(chkUseMoraleModifierLoyalty) - .addComponent(chkUseRuleWithIronFist) + .addComponent(chkUseMoraleModifierCabinFever) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblMoraleModifierLeadershipMethod) + .addComponent(comboMoraleModifierLeadershipMethod, Alignment.LEADING)) ); layout.setHorizontalGroup( @@ -5145,7 +5171,10 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { .addComponent(chkUseMoraleModifierForceReliability) .addComponent(chkUseMoraleModifierCommanderSkill) .addComponent(chkUseMoraleModifierLoyalty) - .addComponent(chkUseRuleWithIronFist) + .addComponent(chkUseMoraleModifierCabinFever) + .addGroup(layout.createSequentialGroup() + .addComponent(lblMoraleModifierLeadershipMethod) + .addComponent(comboMoraleModifierLeadershipMethod)) ); return turnoverAndRetentionMoraleModifiersPanel; @@ -8337,7 +8366,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseMoraleTriggerDesertion.setSelected(options.isUseMoraleTriggerDesertion()); chkUseMoraleTriggerMutiny.setSelected(options.isUseMoraleTriggerMutiny()); chkUseMoraleTriggerFatigue.setSelected(options.isUseMoraleTriggerFatigue()); - chkUseMoraleTriggerMissedPayDay.setSelected(options.isUseMoraleModifierMissedPayDay()); + chkUseMoraleTriggerMissedPayDay.setSelected(options.isUseMoraleTriggerMissedPayDay()); chkUseMoraleModifierExperienceLevel.setSelected(options.isUseMoraleModifierExperienceLevel()); chkUseMoraleModifierFaction.setSelected(options.isUseMoraleModifierFaction()); @@ -8345,7 +8374,8 @@ public void setOptions(@Nullable CampaignOptions options, chkUseMoraleModifierForceReliability.setSelected(options.isUseMoraleModifierForceReliability()); chkUseMoraleModifierCommanderSkill.setSelected(options.isUseMoraleModifierCommanderLeadership()); chkUseMoraleModifierLoyalty.setSelected(options.isUseMoraleModifierLoyalty()); - chkUseRuleWithIronFist.setSelected(options.isUseRuleWithIronFist()); + chkUseMoraleModifierCabinFever.setSelected(options.isUseMoraleModifierCabinFever()); + comboMoraleModifierLeadershipMethod.setSelectedItem(options.getCustomMoraleModifier()); //endregion Turnover and Retention Tab //region Life Paths Tab @@ -9034,7 +9064,7 @@ public void updateOptions() { options.setUseMoraleTriggerDesertion(chkUseMoraleTriggerDesertion.isSelected()); options.setUseMoraleTriggerMutiny(chkUseMoraleTriggerMutiny.isSelected()); options.setUseMoraleTriggerFatigue(chkUseMoraleTriggerFatigue.isSelected()); - options.setUseMoraleModifierMissedPayDay(chkUseMoraleTriggerMissedPayDay.isSelected()); + options.setUseMoraleTriggerMissedPayDay(chkUseMoraleTriggerMissedPayDay.isSelected()); options.setCustomMoraleModifier((Integer) spnCustomMoraleModifier.getValue()); options.setUseMoraleModifierExperienceLevel(chkUseMoraleModifierExperienceLevel.isSelected()); @@ -9043,7 +9073,8 @@ public void updateOptions() { options.setUseMoraleModifierForceReliability(chkUseMoraleModifierForceReliability.isSelected()); options.setUseMoraleModifierCommanderLeadership(chkUseMoraleModifierCommanderSkill.isSelected()); options.setUseMoraleModifierLoyalty(chkUseMoraleModifierLoyalty.isSelected()); - options.setUseRuleWithIronFist(chkUseRuleWithIronFist.isSelected()); + options.setUseMoraleModifierCabinFever(chkUseMoraleModifierCabinFever.isSelected()); + options.setMoraleModifierLeadershipMethod(comboMoraleModifierLeadershipMethod.getSelectedItem()); //endregion Turnover and Retention //region Life Paths Tab From e9f34ecc1735662b760c96f4cab218d6b3e81d14 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 26 May 2024 20:42:02 -0500 Subject: [PATCH 060/101] Fixed travel time reporting formatting and removed whitespace in washout report --- .../campaign/personnel/education/EducationController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java b/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java index 72492e47c8..1938806f41 100644 --- a/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java +++ b/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java @@ -501,7 +501,7 @@ private static Integer beginJourneyHome(Campaign campaign, Person person, Academ campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("creche.text")); } else { campaign.addReport(person.getHyperlinkedName() + ' ' - + String.format(resources.getString("returningFromSchool.text"), travelTime + + String.format(resources.getString("returningFromSchool.text"), travelTime)); } return null; @@ -843,7 +843,7 @@ private static void processClanWashout(Campaign campaign, Person person, Integer campaign.addReport(person.getHyperlinkedName() + ' ' + String.format(resources.getString("washout.text"), resources.getString("graduatedScientist.text") + resources.getString("graduatedWarriorLabor.text"))); - + person.setEduCourseIndex(10); person.setEduAcademyName(generateClanEducationCode(campaign, person, 10, resources)); @@ -853,7 +853,7 @@ private static void processClanWashout(Campaign campaign, Person person, Integer campaign.addReport(person.getHyperlinkedName() + ' ' + String.format(resources.getString("washout.text"), resources.getString("graduatedMerchant.text") + resources.getString("graduatedWarriorLabor.text"))); - + person.setEduCourseIndex(10); person.setEduAcademyName(generateClanEducationCode(campaign, person, 10, resources)); From 17ab9e01d393e013cd8b177e7417b72d0f9ad859 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 26 May 2024 20:52:48 -0500 Subject: [PATCH 061/101] Changed LeadershipMethod from MERCENARY to REGULAR - Replaced MERCENARY with REGULAR in CampaignOptions.java, LeadershipMethod.java, Morale.java, and Personnel.properties - Adjusted corresponding methods and tool tip texts --- MekHQ/resources/mekhq/resources/Personnel.properties | 12 ++++++------ MekHQ/src/mekhq/campaign/CampaignOptions.java | 2 +- .../campaign/personnel/enums/LeadershipMethod.java | 10 +++++----- .../personnel/turnoverAndRetention/Morale.java | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index dde755efb6..cc3d3c066a 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -491,14 +491,14 @@ ForceReliabilityMethod.OVERRIDE_F.text=Fixed: F (Questionable) ForceReliabilityMethod.OVERRIDE_F.toolTipText=Desertion and Mutiny checks have a -1 modifier. # LeadershipMethod Enum -LeadershipMethod.MERCENARY.text=Mercenary -LeadershipMethod.MERCENARY.toolTipText=The average mercenary company. Applies no additional modifiers. -LeadershipMethod.FAMILY.text=Family -LeadershipMethod.FAMILY.toolTipText=Family treats everyone equally, but the unit lacks discipline. Applies a -1 modifier to Desertion rolls, but a +1 modifier to Mutiny rolls. -LeadershipMethod.ELITE.text=Elite -LeadershipMethod.ELITE.toolTipText=An elite, disciplined and professional fighting force. Applies a +1 modifier to both Desertion and Mutiny rolls. +LeadershipMethod.REGULAR.text=Regular +LeadershipMethod.REGULAR.toolTipText=The average mercenary company. Applies no additional modifiers. LeadershipMethod.GREEN.text=Green LeadershipMethod.GREEN.toolTipText=An inexperienced commander does not inspire confidence or respect. Applies a -1 modifier to Desertion rolls. +LeadershipMethod.ELITE.text=Elite +LeadershipMethod.ELITE.toolTipText=An elite, disciplined and professional fighting force. Applies a +1 modifier to both Desertion and Mutiny rolls. +LeadershipMethod.FAMILY.text=Family +LeadershipMethod.FAMILY.toolTipText=Family treats everyone equally, but the unit lacks discipline. Applies a -1 modifier to Desertion rolls, but a +1 modifier to Mutiny rolls. LeadershipMethod.IRON_FIST.text=Iron Fist LeadershipMethod.IRON_FIST.toolTipText=Where there's a whip, there's a way. Applies a +1 modifier to Desertion and Mutiny rolls, but both desertions and mutinies are more impactful. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 215fdac2c7..56a7d09094 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -1047,7 +1047,7 @@ public CampaignOptions() { setUseMoraleModifierCommanderLeadership(true); setUseMoraleModifierLoyalty(true); setUseMoraleModifierCabinFever(true); - setMoraleModifierLeadershipMethod(LeadershipMethod.MERCENARY); + setMoraleModifierLeadershipMethod(LeadershipMethod.REGULAR); //endregion Turnover and Retention //region Finances Tab diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java index 1954d0525f..e6d3bb537e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java +++ b/MekHQ/src/mekhq/campaign/personnel/enums/LeadershipMethod.java @@ -23,10 +23,10 @@ import java.util.ResourceBundle; public enum LeadershipMethod { - MERCENARY("LeadershipMethod.MERCENARY.text", "LeadershipMethod.MERCENARY.toolTipText"), - FAMILY("LeadershipMethod.FAMILY.text", "LeadershipMethod.FAMILY.toolTipText"), - ELITE("LeadershipMethod.ELITE.text", "LeadershipMethod.ELITE.toolTipText"), + REGULAR("LeadershipMethod.REGULAR.text", "LeadershipMethod.REGULAR.toolTipText"), GREEN("LeadershipMethod.GREEN.text", "LeadershipMethod.GREEN.toolTipText"), + ELITE("LeadershipMethod.ELITE.text", "LeadershipMethod.ELITE.toolTipText"), + FAMILY("LeadershipMethod.FAMILY.text", "LeadershipMethod.FAMILY.toolTipText"), IRON_FIST("LeadershipMethod.IRON_FIST.text", "LeadershipMethod.IRON_FIST.toolTipText"); private final String name; @@ -43,8 +43,8 @@ public String getToolTipText() { return toolTipText; } - public boolean isMercenary() { - return this == MERCENARY; + public boolean isRegular() { + return this == REGULAR; } public boolean isElite() { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index 8a20d36048..e15d8730f4 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -186,7 +186,7 @@ private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, // Leadership Method Modifier switch (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod()) { - case MERCENARY: + case REGULAR: break; case FAMILY: if (isDesertion) { From b74217f16729ecdaa97dab7543e22181fe150cca Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 26 May 2024 21:22:45 -0500 Subject: [PATCH 062/101] refactor: changed Integer to int in Person class properties and method signatures --- .../src/mekhq/campaign/personnel/Person.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index b29387cac8..bfb36998b1 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -124,10 +124,10 @@ public class Person { private List scenarioLog; private LocalDate retirement; - private Integer loyalty; - private Integer fatigue; + private int loyalty; + private int fatigue; private Boolean isRecoveringFromFatigue; - private Integer awolDays; + private int awolDays; private Skills skills; private PersonnelOptions options; @@ -1208,27 +1208,27 @@ public void setRetirement(final @Nullable LocalDate retirement) { this.retirement = retirement; } - public Integer getLoyalty() { + public int getLoyalty() { return loyalty; } - public void setLoyalty(final Integer loyalty) { + public void setLoyalty(final int loyalty) { this.loyalty = loyalty; } - public Integer getFatigue() { + public int getFatigue() { return fatigue; } - public void setFatigue(final Integer fatigue) { + public void setFatigue(final int fatigue) { this.fatigue = fatigue; } - public Integer getAwolDays() { + public int getAwolDays() { return awolDays; } - public void setAwolDays(final Integer awolDays) { + public void setAwolDays(final int awolDays) { this.awolDays = awolDays; } @@ -1644,7 +1644,7 @@ public void writeToXML(final PrintWriter pw, int indent, final Campaign campaign MHQXMLUtility.writeSimpleXMLTag(pw, indent, "retirement", getRetirement()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "loyalty", getLoyalty()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "fatigue", getFatigue()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue());; + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "isRecoveringFromFatigue", getIsRecoveringFromFatigue()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "awolDays", getAwolDays()); for (Skill skill : skills.getSkills()) { skill.writeToXML(pw, indent); From e472e1b199fd0b13efc757b22f3646bca8cf134c Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 26 May 2024 21:39:51 -0500 Subject: [PATCH 063/101] refactor: renamed `setFatigue` and `getFatigue` methods to `setCrewFatigue` and `getCrewFatigue` in multiple classes --- MekHQ/src/mekhq/campaign/unit/Unit.java | 6 +++--- MekHQ/src/mekhq/utilities/MHQXMLUtility.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index bcef8fa889..e17a1e8644 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -3573,7 +3573,7 @@ public void resetPilotAndEntity() { entity.getCrew().setPortrait(commander.getPortrait().clone(), 0); entity.getCrew().setExternalIdAsString(commander.getId().toString(), 0); entity.getCrew().setToughness(commander.getToughness(), 0); - entity.getCrew().setFatigue(commander.getFatigue(), 0); + entity.getCrew().setCrewFatigue(commander.getFatigue(), 0); if (entity instanceof Tank) { ((Tank) entity).setCommanderHit(commander.getHits() > 0); @@ -4007,9 +4007,9 @@ private void assignToCrewSlot(Person p, int slot, String gunType, String driveTy entity.getCrew().setToughness(p.getToughness(), slot); if (campaign.getCampaignOptions().isUseFatigue()) { - entity.getCrew().setFatigue(p.getFatigue(), slot); + entity.getCrew().setCrewFatigue(p.getFatigue(), slot); } else { - entity.getCrew().setFatigue(0, slot); + entity.getCrew().setCrewFatigue(0, slot); } entity.getCrew().setExternalIdAsString(p.getId().toString(), slot); diff --git a/MekHQ/src/mekhq/utilities/MHQXMLUtility.java b/MekHQ/src/mekhq/utilities/MHQXMLUtility.java index 3183da1610..c61b05417d 100644 --- a/MekHQ/src/mekhq/utilities/MHQXMLUtility.java +++ b/MekHQ/src/mekhq/utilities/MHQXMLUtility.java @@ -489,8 +489,8 @@ public static void writeEntityWithCrewToXML(PrintWriter pw, int indentLvl, Entit crew.append("\" " + MULParser.ATTR_TOUGH + "=\"").append(tgtEnt.getCrew().getToughness(pos)); } - if (tgtEnt.getCrew().getFatigue(pos) != 0) { - crew.append("\" " + MULParser.ATTR_FATIGUE + "=\"").append(tgtEnt.getCrew().getFatigue(pos)); + if (tgtEnt.getCrew().getCrewFatigue(pos) != 0) { + crew.append("\" " + MULParser.ATTR_FATIGUE + "=\"").append(tgtEnt.getCrew().getCrewFatigue(pos)); } if (tgtEnt.getCrew().isDead(pos) || tgtEnt.getCrew().getHits(pos) >= Crew.DEATH) { From 06b64cdb0b1eac47b7deb8e0d7c4965f17ca1f40 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 26 May 2024 22:08:27 -0500 Subject: [PATCH 064/101] Updated mutiny logic and refactor fatigue handling - Enhanced mutiny logic to consider vote on peaceful resolution vs violent rebellion - Removed unused methods related to ammo usage and battle statistics - Refactored fatigue handling across `Crew.java`, `CustomPilotView.java`, `CustomMechDialog.java` - Minor documentation fixes --- .../turnoverAndRetention/Morale.java | 181 +++--------------- 1 file changed, 27 insertions(+), 154 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index e15d8730f4..c97e90b024 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -4,7 +4,6 @@ import megamek.common.Compute; import megamek.common.Entity; import megamek.common.MechSummaryCache; -import megamek.common.Mounted; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Money; @@ -1031,6 +1030,7 @@ private static void processMutiny(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { // This prevents us from needing to do the full process for tiny mutinies that have no chance of success + // In these cases we alert the player that a mutiny was thwarted and then treat it as a Desertion if ((loyalists.size() / 2) > rebels.size()) { if (rebels.size() > 1) { campaign.addReport(String.format(resources.getString("mutinyThwartedPlural.text"), rebels.size())); @@ -1095,12 +1095,36 @@ private static void processMutiny(Campaign campaign, .mapToInt(unit -> unit.getEntity() .calculateBattleValue(true, false)).sum(); + // The rebels now decide whether they want to overthrow the Loyalist leader, or just depose them + int deposeVoteCount = 0; + int voteModifier = 0; + + // The larger the rebel force, the more confident they feel and the less prone to drastic measures + if (rebels.keySet().size() > loyalists.size()) { + voteModifier--; + } else if (rebels.keySet().size() < loyalists.size()) { + voteModifier++; + } + + // the more loyal the rebel leader, the more they call for a peaceful resolution + if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { + voteModifier -= rebelLeader.getLoyalty(); + } + + for (Person ignored : rebels.keySet()) { + if ((Compute.d6(1) + voteModifier) >= 4) { + deposeVoteCount++; + } + } + + boolean isViolentRebellion = deposeVoteCount > (rebels.keySet().size() / 2); + // we now need to present the player with a choice: join the rebels, or support the loyalists int supportDecision = -1; while (supportDecision == -1) { supportDecision = MutinySupportDialog.supportDialog( - campaign, resources, false, + campaign, resources, isViolentRebellion, bystanders.size(), loyalistLeader, loyalists.size(), new ArrayList<>(loyalUnits.keySet()), loyalistBv, rebelLeader, rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv @@ -1176,157 +1200,6 @@ private static Integer getAdjustedRankNumeric(Campaign campaign, Person person) return rankNumeric; } - /** - * Updates the ammo usage of each unit in the force, taking into account attrition. - * - * @param force the list of units in the force - * @param attrition the attrition value to apply to the ammo usage calculation - */ - private static void getAmmoUsage(List force, int attrition) { - for (Unit unit : force) { - for (Mounted bin : unit.getEntity().getAmmo()) { - int ammo = bin.getUsableShotsLeft(); - int roll = Compute.randomInt((int) ((ammo * 0.33) + attrition)); - - bin.setShotsLeft(Math.max(0, ammo - roll)); - } - } - } - - /** - * Maps force damage based on the given parameters. - * - * @param forceSize the size of the force - * @param attrition the attrition value - * @param enemyAttackDice the number of dice used for enemy attacks - * @param friendlyDefenceDice the number of dice used for friendly defenses - * @return a HashMap containing the force damage: - * - "attrition": the attrition value after mapping - * - "damagedLight": the number of units damaged lightly - * - "damagedModerate": the number of units damaged moderately - * - "damagedBadly": the number of units damaged badly - */ - private static HashMap mapForceDamage(int forceSize, int attrition, int enemyAttackDice, int friendlyDefenceDice) { - HashMap forceDamage = new HashMap<>(); - - attrition = attrition * (forceSize / 12); - - int damageDice = Math.max(0, enemyAttackDice - friendlyDefenceDice); - - int damagedBadly = 0; - int damagedModerate = 0; - int damagedLight = 0; - - for (int rollNumber = 0; rollNumber < damageDice; rollNumber++) { - switch (Compute.d6(1)) { - case 1: - damagedBadly++; - break; - case 2: - case 3: - damagedModerate++; - break; - case 4: - case 5: - case 6: - default: - damagedLight++; - break; - } - } - - while ((attrition + damagedBadly + damagedModerate + damagedLight) > forceSize) { - if (damagedLight > 0) { - damagedLight--; - } else if (damagedModerate > 0) { - damagedModerate--; - } else if (damagedBadly > 0) { - damagedBadly--; - } else { - attrition--; - } - } - - forceDamage.put("attrition", attrition); - forceDamage.put("damagedLight", damagedLight); - forceDamage.put("damagedModerate", damagedModerate); - forceDamage.put("damagedBadly", damagedBadly); - - return forceDamage; - } - - private static HashMap processEngagement(int loyalistAttackDice, int loyalistDefenceDice, int rebelAttackDice, int rebelDefenceDice) { - HashMap combatResults = new HashMap<>(); - - boolean concludeEngagement = false; - int attrition = 0; - int loyalistVictory = 0; - int rebelVictory = 0; - - while (!concludeEngagement) { - int loyalistAttack = Compute.d6(loyalistAttackDice); - int loyalistDefence = Compute.d6(loyalistDefenceDice); - int rebelAttack = Compute.d6(rebelAttackDice); - int rebelDefence = Compute.d6(rebelDefenceDice); - - loyalistVictory = 0; - rebelVictory = 0; - - if (loyalistAttack > rebelDefence) { - if (loyalistAttack < (rebelDefence * 1.25)) { - attrition++; - } - - loyalistVictory = 1; - } - - if (rebelAttack > loyalistDefence) { - if (rebelAttack < (loyalistDefence * 1.25)) { - attrition++; - } - - rebelVictory = 1; - } - - if (loyalistVictory == rebelVictory) { - attrition++; - } else { - concludeEngagement = true; - } - } - - combatResults.put("attrition", attrition); - combatResults.put("loyalistVictory", loyalistVictory); - - return combatResults; - } - - /** - * Calculates a faction's abstract battle statistics based on the given combatants, force size, and battle value. - * - * @param combatants the list of combatants participating in the battle - * @param forceSize the size of the force - * @param battleValue the value of the battle - * @return a HashMap containing the statistics of the battle - */ - private static HashMap getAbstractBattleStatistics(List combatants, Integer forceSize, Integer battleValue) { - HashMap statistics = new HashMap<>(); - - // TODO make the dividers Campaign Options - int loyalistLeadership = (int) combatants.stream().filter(person -> person.getRank().isOfficer()).count() / (forceSize / 12); - int loyalistMedical = (int) combatants.stream().filter(person -> (person.getPrimaryRole().isDoctor())).count() / (forceSize / 12); - int loyalistAdministration = (int) combatants.stream().filter(person -> (person.getPrimaryRole().isAdministrator())).count() / (forceSize / 3); - int loyalistTech = (int) combatants.stream().filter(person -> (person.getPrimaryRole().isTech())).count() / (forceSize / 6); - - int attackDice = (battleValue / 250) + loyalistLeadership + loyalistAdministration; - int defenceDice = (battleValue / 250) + loyalistTech + loyalistMedical; - - statistics.put("attackDice", attackDice); - statistics.put("defenceDice", defenceDice); - - return statistics; - } - /** * Calculates the target number for civil war loyalty checks * @@ -1368,7 +1241,7 @@ private static int getCivilWarTargetNumber(Campaign campaign, Person person) { * Retrieves the units that are eligible to participate in the civil war based on the provided personnel. * Multi-crewed units perform a vote to determine which side they join. * - * @param personnel A list of personnel (should all belong to the same mutiny faction. + * @param personnel A list of personnel (should all belong to the same mutiny faction). * @param isLoyalists A boolean value indicating whether to retrieve units for loyalists or rebels. * @return A HashMap of units and their corresponding battle values. */ From 333b1e50cf7614e4c6a29452cbbef65174718ec8 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Sun, 26 May 2024 23:07:34 -0500 Subject: [PATCH 065/101] refactor: removed crew fatigue functionality from Unit and MHQXMLUtility --- MekHQ/src/mekhq/campaign/unit/Unit.java | 8 -------- MekHQ/src/mekhq/utilities/MHQXMLUtility.java | 4 ---- 2 files changed, 12 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index e17a1e8644..052bf06d69 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -3573,7 +3573,6 @@ public void resetPilotAndEntity() { entity.getCrew().setPortrait(commander.getPortrait().clone(), 0); entity.getCrew().setExternalIdAsString(commander.getId().toString(), 0); entity.getCrew().setToughness(commander.getToughness(), 0); - entity.getCrew().setCrewFatigue(commander.getFatigue(), 0); if (entity instanceof Tank) { ((Tank) entity).setCommanderHit(commander.getHits() > 0); @@ -4005,13 +4004,6 @@ private void assignToCrewSlot(Person p, int slot, String gunType, String driveTy entity.getCrew().setGunneryB(Math.min(Math.max(gunnery, 0), 7), slot); entity.getCrew().setArtillery(Math.min(Math.max(artillery, 0), 7), slot); entity.getCrew().setToughness(p.getToughness(), slot); - - if (campaign.getCampaignOptions().isUseFatigue()) { - entity.getCrew().setCrewFatigue(p.getFatigue(), slot); - } else { - entity.getCrew().setCrewFatigue(0, slot); - } - entity.getCrew().setExternalIdAsString(p.getId().toString(), slot); entity.getCrew().setMissing(false, slot); } diff --git a/MekHQ/src/mekhq/utilities/MHQXMLUtility.java b/MekHQ/src/mekhq/utilities/MHQXMLUtility.java index c61b05417d..bc14346fc3 100644 --- a/MekHQ/src/mekhq/utilities/MHQXMLUtility.java +++ b/MekHQ/src/mekhq/utilities/MHQXMLUtility.java @@ -489,10 +489,6 @@ public static void writeEntityWithCrewToXML(PrintWriter pw, int indentLvl, Entit crew.append("\" " + MULParser.ATTR_TOUGH + "=\"").append(tgtEnt.getCrew().getToughness(pos)); } - if (tgtEnt.getCrew().getCrewFatigue(pos) != 0) { - crew.append("\" " + MULParser.ATTR_FATIGUE + "=\"").append(tgtEnt.getCrew().getCrewFatigue(pos)); - } - if (tgtEnt.getCrew().isDead(pos) || tgtEnt.getCrew().getHits(pos) >= Crew.DEATH) { crew.append("\" " + MULParser.ATTR_HITS + "=\"" + MULParser.VALUE_DEAD + ""); } else if (tgtEnt.getCrew().getHits(pos) > 0) { From d7307b6c4f130814de84d2409fceff2d456bc025 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Mon, 27 May 2024 12:31:14 -0500 Subject: [PATCH 066/101] refactor(MutinySupportDialog): updated logic and messages - Redefined logic for assessing mutiny scenarios - Replaced 'dialogDescriptionViolentTakeover.text' with 'dialogDescriptionViolentUprising.text' - Adjusted rank calculation and unit assessment in mutinies - Revised text descriptions in 'Morale.properties' files for more clarity. --- .../mekhq/resources/Morale.properties | 7 +- .../turnoverAndRetention/Morale.java | 201 +++++++++--------- .../mekhq/gui/dialog/MutinySupportDialog.java | 10 +- 3 files changed, 111 insertions(+), 107 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 5e0e9ac79d..f3986bbdbd 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -30,11 +30,12 @@ mutinyThwartedPlural.text=An attempted coup has been thwarted. %s would-be mutin # Mutiny Dialog dialogSupportLoyalists.text=Support Loyalists dialogSupportRebels.text=Support Rebels -dialogTitle.text=Betrayal! +dialogTitleViolentUprising.text=Violent Uprising +dialogTitleRegimeChange.text=Regime Change dialogDescriptionIntroduction.text=
%s's poor leadership has given the personnel of %s no other choice.

-dialogDescriptionViolentTakeover.text=Led by %s, they have drawn arms against %s and are demanding control of the company.

-dialogDescriptionRegimeChange.text=Led by %s, the rebels are demanding the commander be removed from power and will fight to ensure this happens.

+dialogDescriptionViolentUprising.text=Led by %s, they have drawn arms against the commander and are demanding control of the unit.

+dialogDescriptionRegimeChange.text=Led by %s, the rebels are demanding the commander be removed from command and will fight to ensure this happens.

dialogDescriptionLoyalist.text=Loyalist dialogDescriptionRebels.text=Rebel dialogDescriptionPluralizer.text=s diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java index c97e90b024..5dad550a89 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java @@ -474,29 +474,52 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { if (isDesertion) { if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { - campaign.addReport(person.getFullName() + " failed their morale check [TN" + targetNumber + "] [rA" + firstRoll + "][rB" + secondRoll + ']'); possibleTheftTargets.remove(processDesertion(campaign, person, secondRoll, targetNumber, possibleTheftTargets, resources)); someoneHasDeserted = true; } } else { + if (person.equals(loyalistLeader)) { + loyalists.add(person); + } + if (firstRoll < targetNumber) { - if (person.getUnit() != null) { - if ((person.getUnit().getCrew().contains(loyalistLeader)) && (person.isCommander())) { + if (person.getUnit() == null) { + rebels.put(person, firstRoll); + + someoneHasMutinied = true; + + continue; + } + + if (person.getUnit().getCrew().size() == 1) { + rebels.put(person, firstRoll); + + someoneHasMutinied = true; + + continue; + } + + if (person.getUnit().getCrew().contains(loyalistLeader)) { + if (person.getUnit().getEntity().isCommander()) { loyalists.addAll(person.getUnit().getCrew()); - } else if (person.isCommander()) { - for (Person crew : person.getUnit().getCrew()) { - if (person.isCommander()) { - rebels.put(person, firstRoll); - } else { - rebels.put(crew, targetNumber - Compute.d6(1)); - } + } + } + + if (person.getUnit().getEntity().isCommander()) { + for (Person crew : person.getUnit().getCrew()) { + if (!person.getUnit().getEntity().isCommander()) { + rebels.put(crew, targetNumber - 2); + } else { + rebels.put(person, firstRoll); } } - } else { - rebels.put(person, firstRoll); } - someoneHasMutinied = true; + LogManager.getLogger().info(rebels.toString()); + + if (!rebels.isEmpty()) { + someoneHasMutinied = true; + } } else { if ((person.getUnit() != null) && (person.isCommander())) { loyalists.addAll(person.getUnit().getCrew()); @@ -1038,11 +1061,13 @@ private static void processMutiny(Campaign campaign, campaign.addReport(String.format(resources.getString("mutinyThwartedSingular.text"), rebels.size())); } + int targetNumber = getTargetNumber(campaign, true); + for (Person person : rebels.keySet()) { possibleTheftTargets.remove(processDesertion(campaign, person, - rebels.get(person), - getTargetNumber(campaign, true), + Math.min(rebels.get(person), targetNumber - 2), + targetNumber, possibleTheftTargets, resources)); } @@ -1054,8 +1079,8 @@ private static void processMutiny(Campaign campaign, // The rebels have already picked their side, so we only need to process the loyalists // This represents the mutineers gathering support - Iterator iterator = loyalists.iterator(); + Iterator iterator = loyalists.iterator(); while (iterator.hasNext()) { Person person = iterator.next(); @@ -1064,14 +1089,11 @@ private static void processMutiny(Campaign campaign, continue; } - // we then process everyone else int roll = Compute.d6(1); int civilWarTargetNumber = getCivilWarTargetNumber(campaign, person); - if ((roll >= (civilWarTargetNumber - 1)) && - (roll <= (civilWarTargetNumber + 1))) { - + if ((roll >= (civilWarTargetNumber - 1)) && (roll <= (civilWarTargetNumber + 1))) { iterator.remove(); bystanders.add(person); } else if (roll < (civilWarTargetNumber - 1)) { @@ -1085,91 +1107,88 @@ private static void processMutiny(Campaign campaign, // in which case we can assume they were persuaded into the role by the original mutineers Person rebelLeader = getRebelLeader(campaign, new ArrayList<>(rebels.keySet())); - HashMap rebelUnits = getUnits(new ArrayList<>(rebels.keySet()), false); + HashMap rebelUnits = getUnits(new ArrayList<>(rebels.keySet())); int rebelBv = rebelUnits.keySet().stream() .mapToInt(unit -> unit.getEntity() .calculateBattleValue(true, false)).sum(); - HashMap loyalUnits = getUnits(loyalists, true); - int loyalistBv = loyalUnits.keySet().stream() + HashMap loyalistUnits = getUnits(loyalists); + int loyalistBv = loyalistUnits.keySet().stream() .mapToInt(unit -> unit.getEntity() .calculateBattleValue(true, false)).sum(); + // we now need to present the player with a choice: join the rebels, or support the loyalists + int supportDecision = -1; + + while (supportDecision == -1) { + supportDecision = MutinySupportDialog.supportDialog( + campaign, resources, getDeposeVoteCount(campaign, loyalists.size(), rebels, rebelLeader), + bystanders.size(), + loyalistLeader, loyalists.size(), new ArrayList<>(loyalistUnits.keySet()), loyalistBv, + rebelLeader, rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv + ); + } + } + + /** + * Calculates the vote count for deposing the Loyalist leader in a campaign. + * + * @param campaign the campaign object + * @param loyalistsCount the number of loyalists + * @param rebels a map containing rebels and their votes + * @param rebelLeader the leader of the rebels + * @return true if the vote count for deposing the Loyalist leader is higher than the vote count for an uprising, false otherwise. + */ + private static boolean getDeposeVoteCount(Campaign campaign, int loyalistsCount, HashMap rebels, Person rebelLeader) { // The rebels now decide whether they want to overthrow the Loyalist leader, or just depose them int deposeVoteCount = 0; + int uprisingVoteCount = 0; int voteModifier = 0; // The larger the rebel force, the more confident they feel and the less prone to drastic measures - if (rebels.keySet().size() > loyalists.size()) { - voteModifier--; - } else if (rebels.keySet().size() < loyalists.size()) { + if (rebels.keySet().size() > (loyalistsCount * 1.25)) { voteModifier++; + } else if (rebels.keySet().size() < (loyalistsCount * 0.75)) { + voteModifier--; } // the more loyal the rebel leader, the more they call for a peaceful resolution if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { - voteModifier -= rebelLeader.getLoyalty(); + voteModifier += rebelLeader.getLoyalty(); } for (Person ignored : rebels.keySet()) { if ((Compute.d6(1) + voteModifier) >= 4) { deposeVoteCount++; + } else { + uprisingVoteCount++; } } - boolean isViolentRebellion = deposeVoteCount > (rebels.keySet().size() / 2); - - // we now need to present the player with a choice: join the rebels, or support the loyalists - int supportDecision = -1; - - while (supportDecision == -1) { - supportDecision = MutinySupportDialog.supportDialog( - campaign, resources, isViolentRebellion, - bystanders.size(), - loyalistLeader, loyalists.size(), new ArrayList<>(loyalUnits.keySet()), loyalistBv, - rebelLeader, rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv - ); - } + return deposeVoteCount > uprisingVoteCount; } + /** - * This method is used to determine the rebel leader based on the given campaign and rebel information. + * Returns the rebel leader from the given list of rebels based on their rank. * - * @param campaign The current campaign. - * @param rebels The list of rebels. - * @return The person object representing the rebel leader. + * @param campaign The campaign containing the rank data. + * @param rebels The list of rebels to choose the leader from. + * @return The rebel leader with the highest rank in the campaign. */ private static Person getRebelLeader(Campaign campaign, List rebels) { - Person rebelLeader = null; + Person rebelLeader = rebels.get(1); + + int incumbantRankNumeric = getAdjustedRankNumeric(campaign, rebelLeader); + int newRankNumeric; for (Person person : rebels) { - if (rebelLeader == null) { - rebelLeader = person; - continue; - } + newRankNumeric = getAdjustedRankNumeric(campaign, person); - int oldRankNumeric = getAdjustedRankNumeric(campaign, rebelLeader); - int newRankNumeric = getAdjustedRankNumeric(campaign, person); - - if (newRankNumeric == oldRankNumeric) { - // in the case of a tie, we use the negotiation skill - if ((person.hasSkill(SkillType.S_NEG)) && (Objects.requireNonNull(rebelLeader).hasSkill(SkillType.S_NEG))) { - if (person.getSkillLevel(SkillType.S_NEG) > rebelLeader.getSkillLevel(SkillType.S_NEG)) { - rebelLeader = person; - } else if (person.getSkillLevel(SkillType.S_NEG) == rebelLeader.getSkillLevel(SkillType.S_NEG)) { - // if we still have a tie, we use overall experience level. - // if this fails to break the tie, we give up and just use the new person - if (person.getExperienceLevel(campaign, false) > rebelLeader.getExperienceLevel(campaign, false)) { - rebelLeader = person; - } else if (person.getExperienceLevel(campaign, false) == rebelLeader.getExperienceLevel(campaign, false)) { - rebelLeader = person; - } - } - } else if (person.hasSkill(SkillType.S_NEG)) { - rebelLeader = person; - } - } else if (newRankNumeric > oldRankNumeric) { + // The incumbent wins, in the event of a draw + if (incumbantRankNumeric < newRankNumeric) { rebelLeader = person; + incumbantRankNumeric = newRankNumeric; } } return rebelLeader; @@ -1185,16 +1204,18 @@ private static Person getRebelLeader(Campaign campaign, List rebels) { private static Integer getAdjustedRankNumeric(Campaign campaign, Person person) { int rankNumeric = person.getRankNumeric(); - if (person.hasSkill(SkillType.S_LEADER)) { - rankNumeric = person.getSkillLevel(SkillType.S_LEADER); - - if (campaign.getCampaignOptions().isUseManagementSkill()) { + // if management skill is enabled, it influences adjusted rank numeric + if (campaign.getCampaignOptions().isUseManagementSkill()) { + if (person.hasSkill(SkillType.S_LEADER)) { + rankNumeric += person.getSkillLevel(SkillType.S_LEADER) + campaign.getCampaignOptions().getManagementSkillPenalty(); + } else { rankNumeric += campaign.getCampaignOptions().getManagementSkillPenalty(); } } + // We use inverse loyalty here, with the less loyal pushing themselves into positions of power within the rebellion if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { - rankNumeric += person.getLoyalty(); + rankNumeric -= person.getLoyalty(); } return rankNumeric; @@ -1239,13 +1260,12 @@ private static int getCivilWarTargetNumber(Campaign campaign, Person person) { /** * Retrieves the units that are eligible to participate in the civil war based on the provided personnel. - * Multi-crewed units perform a vote to determine which side they join. + * Multi-crewed units follow their commander. * * @param personnel A list of personnel (should all belong to the same mutiny faction). - * @param isLoyalists A boolean value indicating whether to retrieve units for loyalists or rebels. * @return A HashMap of units and their corresponding battle values. */ - private static HashMap getUnits(List personnel, boolean isLoyalists) { + private static HashMap getUnits(List personnel) { HashMap forces = new HashMap<>(); for (Person person: personnel) { @@ -1259,33 +1279,10 @@ private static HashMap getUnits(List personnel, boolean i // We only care about the commander, as this allows us to ensure each Unit is only counted once. // We also check to ensure the unit isn't already deployed, or too damaged to fight. if ((unit.isCommander(person)) && (!unit.isDeployed()) && (!unit.getEntity().isCrippled()) && (!unit.getEntity().isDmgHeavy())) { - int loyalVoteCount = 0; - int rebelVoteCount = 0; - - for (Person crew : unit.getCrew()) { - if (personnel.contains(crew)) { - if (isLoyalists) { - loyalVoteCount++; - } else { - rebelVoteCount++; - } - } - } - - // if the votes are equal, the unit abstains from the conflict - if (loyalVoteCount > rebelVoteCount) { - if (isLoyalists) { - forces.put(unit, unit.getEntity().calculateBattleValue(true, false)); - } - } else if (loyalVoteCount < rebelVoteCount) { - if (!isLoyalists) { - forces.put(unit, unit.getEntity().calculateBattleValue(true, false)); - } - } + forces.put(unit, unit.getEntity().calculateBattleValue(true, false)); } } } - return forces; } } diff --git a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java index 87cf3a68ae..dabc68a511 100644 --- a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java @@ -66,8 +66,14 @@ public static int supportDialog(Campaign campaign, ResourceBundle resources, boo } pane.setOptions(new Object[]{buttonPanel}); + JDialog dialog; + + if (isViolentRebellion) { + dialog = pane.createDialog(null, resources.getString("dialogTitleViolentUprising.text")); + } else { + dialog = pane.createDialog(null, resources.getString("dialogTitleRegimeChange.text")); + } - JDialog dialog = pane.createDialog(null, resources.getString("dialogTitle.text")); dialog.setVisible(true); return choice.get(); @@ -86,7 +92,7 @@ private static String buildMutinyDescription(Campaign campaign, ResourceBundle if (isViolentRebellion) { situationDescription.append(' ').append(String.format( - resources.getString("dialogDescriptionViolentTakeover.text"), + resources.getString("dialogDescriptionViolentUprising.text"), rebelLeader.getFullTitle() )); } else { From 6ad9a490f85565b61026c73eaa148c40d8b745b5 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 28 May 2024 15:13:17 -0500 Subject: [PATCH 067/101] feat: Updated Desertion and Morale Behavior - Added Marriage Morale Modifier - Adjusted guidelines for item theft on desertion - Simplified code for identifying spare parts - Improved handling of Unit theft - Modified the user interface for implementing changes in morale rules. --- .../CampaignOptionsDialog.properties | 4 +- .../mekhq/resources/Morale.properties | 4 + MekHQ/src/mekhq/campaign/Campaign.java | 24 +- MekHQ/src/mekhq/campaign/CampaignOptions.java | 16 +- MekHQ/src/mekhq/campaign/Warehouse.java | 13 +- .../turnoverAndRetention/Morale.java | 1288 ----------------- .../Morale/Desertion.java | 415 ++++++ .../Morale/MoraleController.java | 604 ++++++++ .../turnoverAndRetention/Morale/Mutiny.java | 4 + MekHQ/src/mekhq/gui/CommandCenterTab.java | 6 +- .../mekhq/gui/panes/CampaignOptionsPane.java | 10 + 11 files changed, 1074 insertions(+), 1314 deletions(-) delete mode 100644 MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java create mode 100644 MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java create mode 100644 MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java create mode 100644 MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 7e0c154707..ec30f0bc70 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -340,7 +340,7 @@ lblTheftValue.toolTipText=The average percentage of unit funds stolen during a h chkUseTheftParts.text=Enable Theft of Parts chkUseTheftParts.toolTipText=Deserting personnel may steal parts on their way out. lblTheftPartsDiceCount.text=Part Theft Dice Count -lblTheftPartsDiceCount.toolTipText=How many d6 (or batches of 3d6 armor or ammo) should be rolled to determine the number of parts stolen.
Set to 0 to only have one item (or 3d6 units of ammunition/armor) stolen per theft. +lblTheftPartsDiceCount.toolTipText=How many d3 items (or batches of 2d6 armor plates or ammo rounds) should be stolen per theft.
Set to 0 to only have one item (or 2d6 units of ammunition/armor) stolen per theft. turnoverAndRetentionMoraleModifiersPanel.title=Morale Modifiers lblCustomMoraleModifier.text=Custom Modifier @@ -359,6 +359,8 @@ chkUseMoraleModifierLoyalty.text=Personal Loyalty chkUseMoraleModifierLoyalty.toolTipText=Personal loyalty adjusts both desertion and mutiny rolls. Stacks with the Loyalty force reliability method. chkUseMoraleModifierCabinFever.text=Cabin Fever chkUseMoraleModifierCabinFever.toolTipText=Desertions are disabled while in transit. To compensate, this options adds a -1 modifier to Mutiny rolls. +chkUseMoraleModifierMarriage.text=Marriage +chkUseMoraleModifierMarriage.toolTipText=Married personal apply a +1 bonus to desertion rolls, but will desert as a couple. Mutinies are unaffected. lblMoraleModifierLeadershipMethod.text=Leadership Style lblMoraleModifierLeadershipMethod.toolTipText=What style of leadership best describes the unit? diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index f3986bbdbd..f08d865322 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -13,6 +13,10 @@ moraleReportLow.text=Caution, morale is %s. moraleReportMutiny.text=Your personnel are actively planning a coup. moraleReportRecovered.text=Your personnel are placated, for now. +# Reclaim Original Unit +reclaimSuccessful.text=has taken back %s +reclaimFailed.text=was unable to reclaim their original unit. + # Desertion desertionAwolExtended.text=has extended their unauthorized leave. diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index a92a21dacf..749be479d4 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -92,7 +92,7 @@ import mekhq.campaign.personnel.ranks.RankValidator; import mekhq.campaign.personnel.ranks.Ranks; import mekhq.campaign.personnel.turnoverAndRetention.Fatigue; -import mekhq.campaign.personnel.turnoverAndRetention.Morale; +import mekhq.campaign.personnel.turnoverAndRetention.Morale.MoraleController; import mekhq.campaign.personnel.turnoverAndRetention.RetirementDefectionTracker; import mekhq.campaign.rating.CampaignOpsReputation; import mekhq.campaign.rating.FieldManualMercRevDragoonsRating; @@ -3527,11 +3527,11 @@ public boolean newDay() { } processNewDayPersonnel(); - + processFatigueNewDay(); processMoraleNewDay(); - + if (campaignOptions.isUseEducationModule()) { EducationController.processNewDay(this); } @@ -3555,7 +3555,7 @@ private void processMoraleNewDay() { // we still process awol personnel, even if Morale is disabled, to ensure they're not frozen in this state. for (Person person : getAwolPersonnel()) { if (person.getAwolDays() != -1) { - Morale.processAwolDays(this, person); + MoraleController.processAwolDays(this, person); } } @@ -3569,32 +3569,32 @@ private void processMoraleNewDay() { // these are the morale checks and dictate whether personnel mutiny or desert if ((campaignOptions.isUseDesertions()) && (getLocation().isOnPlanet())) { - desertionEvent = Morale.makeMoraleChecks(this, true); + desertionEvent = MoraleController.makeMoraleChecks(this, true); } if (campaignOptions.isUseMutinies()) { - mutinyEvent = Morale.makeMoraleChecks(this, false); + mutinyEvent = MoraleController.makeMoraleChecks(this, false); } // this processes morale recovery based on campaign location and the results of the prior checks if (desertionEvent && mutinyEvent) { if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, 2); + MoraleController.processMoraleChange(this, 2); } else { - Morale.processMoraleChange(this, 3); + MoraleController.processMoraleChange(this, 3); } } else if (desertionEvent) { if ((!getActiveContracts().isEmpty()) || (!getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, 1); + MoraleController.processMoraleChange(this, 1); } } else if (mutinyEvent) { if ((getActiveContracts().isEmpty()) && (getLocation().isOnPlanet())) { - Morale.processMoraleChange(this, 1); + MoraleController.processMoraleChange(this, 1); } else { - Morale.processMoraleChange(this, 2); + MoraleController.processMoraleChange(this, 2); } } else { - Morale.processMoraleChange(this, -1); + MoraleController.processMoraleChange(this, -1); } } } diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 56a7d09094..0d37a63c2b 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -347,6 +347,7 @@ public static String getTransitUnitName(final int unit) { private boolean useMoraleModifierCommanderLeadership; private boolean useMoraleModifierLoyalty; private boolean useMoraleModifierCabinFever; + private boolean useMoraleModifierMarriage; private LeadershipMethod moraleModifierLeadershipMethod; // Family @@ -1047,6 +1048,7 @@ public CampaignOptions() { setUseMoraleModifierCommanderLeadership(true); setUseMoraleModifierLoyalty(true); setUseMoraleModifierCabinFever(true); + setUseMoraleModifierMarriage(true); setMoraleModifierLeadershipMethod(LeadershipMethod.REGULAR); //endregion Turnover and Retention @@ -1725,6 +1727,13 @@ public void setUseMoraleModifierCabinFever(final boolean useMoraleModifierCabinF this.useMoraleModifierCabinFever = useMoraleModifierCabinFever; } + public boolean isUseMoraleModifierMarriage() { + return useMoraleModifierMarriage; + } + + public void setUseMoraleModifierMarriage(final boolean useMoraleModifierMarriage) { + this.useMoraleModifierMarriage = useMoraleModifierMarriage; + } public LeadershipMethod getMoraleModifierLeadershipMethod() { return moraleModifierLeadershipMethod; } @@ -4846,6 +4855,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCommanderLeadership", isUseMoraleModifierCommanderLeadership()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierLoyalty", isUseMoraleModifierLoyalty()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierCabinFever", isUseMoraleModifierCabinFever()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMoraleModifierMarriage", isUseMoraleModifierMarriage()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "moraleModifierLeadershipMethod", getMoraleModifierLeadershipMethod().name()); //endregion Retirement @@ -5879,6 +5889,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseMoraleTriggerMutiny(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerFatigue")) { retVal.setUseMoraleTriggerFatigue(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerMissedPayDay")) { + retVal.setUseMoraleTriggerMissedPayDay(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("customMoraleModifier")) { retVal.setCustomMoraleModifier(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierExperienceLevel")) { @@ -5895,8 +5907,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setUseMoraleModifierLoyalty(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierCabinFever")) { retVal.setUseMoraleModifierCabinFever(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleTriggerMissedPayDay")) { - retVal.setUseMoraleTriggerMissedPayDay(Boolean.parseBoolean(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("useMoraleModifierMarriage")) { + retVal.setUseMoraleModifierMarriage(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("moraleModifierLeadershipMethod")) { retVal.setMoraleModifierLeadershipMethod(LeadershipMethod.valueOf(wn2.getTextContent().trim())); //endregion Turnover and Retention diff --git a/MekHQ/src/mekhq/campaign/Warehouse.java b/MekHQ/src/mekhq/campaign/Warehouse.java index 409c670169..d9ee9f5c80 100644 --- a/MekHQ/src/mekhq/campaign/Warehouse.java +++ b/MekHQ/src/mekhq/campaign/Warehouse.java @@ -20,18 +20,19 @@ import megamek.common.annotations.Nullable; import mekhq.MekHQ; -import mekhq.utilities.MHQXMLUtility; import mekhq.campaign.event.PartChangedEvent; import mekhq.campaign.event.PartNewEvent; import mekhq.campaign.event.PartRemovedEvent; import mekhq.campaign.parts.AmmoStorage; import mekhq.campaign.parts.Armor; import mekhq.campaign.parts.Part; +import mekhq.utilities.MHQXMLUtility; import java.io.PrintWriter; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -247,13 +248,9 @@ private Part mergePartWithExisting(Part part) { * @return A list of spare parts in the warehouse. */ public List getSpareParts() { - List spares = new ArrayList<>(); - for (Part part : getParts()) { - if (part.isSpare()) { - spares.add(part); - } - } - return spares; + return getParts().stream() + .filter(Part::isSpare) + .collect(Collectors.toList()); } /** diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java deleted file mode 100644 index 5dad550a89..0000000000 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale.java +++ /dev/null @@ -1,1288 +0,0 @@ -package mekhq.campaign.personnel.turnoverAndRetention; - -import megamek.codeUtilities.MathUtility; -import megamek.common.Compute; -import megamek.common.Entity; -import megamek.common.MechSummaryCache; -import mekhq.MekHQ; -import mekhq.campaign.Campaign; -import mekhq.campaign.finances.Money; -import mekhq.campaign.finances.enums.TransactionType; -import mekhq.campaign.finances.financialInstitutions.FinancialInstitutions; -import mekhq.campaign.market.enums.UnitMarketType; -import mekhq.campaign.parts.AmmoStorage; -import mekhq.campaign.parts.Armor; -import mekhq.campaign.parts.Part; -import mekhq.campaign.personnel.Person; -import mekhq.campaign.personnel.SkillType; -import mekhq.campaign.personnel.enums.ForceReliabilityMethod; -import mekhq.campaign.personnel.enums.PersonnelRole; -import mekhq.campaign.personnel.enums.PersonnelStatus; -import mekhq.campaign.unit.Unit; -import mekhq.gui.dialog.MutinySupportDialog; -import org.apache.logging.log4j.LogManager; - -import java.util.*; -import java.util.stream.Collectors; - -import static megamek.common.EntityWeightClass.WEIGHT_LARGE_WAR; - -public class Morale { - /** - * This method returns the Morale level as a string based on the campaign's current morale. - * - * @return The Morale level as a string. - * @throws IllegalStateException if the value of 'Morale' is unexpected. - */ - public static String getMoraleLevel(Campaign campaign) { - final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", - MekHQ.getMHQOptions().getLocale()); - - int morale = campaign.getMorale() / 10; - - switch (morale) { - case 1: - return resources.getString("moraleLevelUnbreakable.text"); - case 2: - return resources.getString("moraleLevelVeryHigh.text"); - case 3: - return resources.getString("moraleLevelHigh.text"); - case 4: - return resources.getString("moraleLevelNormal.text"); - case 5: - return resources.getString("moraleLevelLow.text"); - case 6: - return resources.getString("moraleLevelVeryLow.text"); - case 7: - return resources.getString("moraleLevelBroken.text"); - default: - throw new IllegalStateException("Unexpected value in getMoraleLevel: " + morale); - } - } - - /** - * Calculates the morale check target number based on the campaign's morale and the desertion flag. - * - * @param campaign the current campaign - * @param isDesertion a flag indicating whether the target number is for desertion or not - * @return the calculated target number - * @throws IllegalStateException if the morale value is unexpected - */ - private static int getTargetNumber(Campaign campaign, boolean isDesertion) { - int morale = campaign.getMorale() / 10; - - switch (morale) { - case 0: - LogManager.getLogger().error("IMPORTANT: Morale has weirdly reset"); - return 0; - case 1: - if (isDesertion) { - return 0; - } else { - return -3; - } - case 2: - if (isDesertion) { - return 1; - } else { - return -2; - } - case 3: - if (isDesertion) { - return 1; - } else { - return -1; - } - case 4: - if (isDesertion) { - return 2; - } else { - return 0; - } - case 5: - if (isDesertion) { - return 4; - } else { - return 2; - } - case 6: - if (isDesertion) { - return 5; - } else { - return 4; - } - case 7: - if (isDesertion) { - return 8; - } else { - return 7; - } - default: - throw new IllegalStateException("Unexpected value in getTargetNumber: " + morale); - } - } - - /** - * Returns the final morale modifier for a person. - * - * @param campaign the ongoing campaign - * @param person the person the modifier is being calculated for - * @param isDesertion whether the target number is for a desertion check - * @param meanLoyalty the mean loyalty value - * @return the morale check modifiers - */ - private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, boolean isDesertion, Integer meanLoyalty) { - int modifier = 0; - - // Custom Modifier - modifier += campaign.getCampaignOptions().getCustomMoraleModifier(); - - // Experience Level Modifier - if (campaign.getCampaignOptions().isUseMoraleModifierExperienceLevel()) { - modifier += getExperienceModifier(campaign, person); - } - - // Faction Modifier - if (campaign.getCampaignOptions().isUseMoraleModifierFaction()) { - if (person.getOriginFaction().isClan()) { - modifier++; - } else if (person.getOriginFaction().isMercenary()) { - modifier--; - } - } - - // Profession Modifier - if (campaign.getCampaignOptions().isUseMoraleModifierProfession()) { - modifier += getProfessionModifier(person.getPrimaryRole(), person); - - if (person.getSecondaryRole() != null) { - modifier += (modifier + getProfessionModifier(person.getSecondaryRole(), person)) / 2; - } - } - - // Force Reliability Modifier - if (campaign.getCampaignOptions().isUseMoraleModifierForceReliability()) { - modifier += getForceReliabilityModifier(campaign, isDesertion, meanLoyalty); - } - - // Management Skill Modifier - if (campaign.getCampaignOptions().isUseMoraleModifierCommanderLeadership()) { - Person commander = campaign.getFlaggedCommander(); - - if ((commander != null) && (campaign.getCampaignOptions().isUseManagementSkill())) { - if (commander.hasSkill(SkillType.S_LEADER)) { - if ((commander.getSkill(SkillType.S_LEADER).getLevel() + campaign.getCampaignOptions().getManagementSkillPenalty()) > 0) { - modifier++; - } - } - } - } - - // Loyalty Modifier - if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseMoraleModifierLoyalty())) { - modifier += getLoyaltyModifier(isDesertion, person.getLoyalty()); - } - - // Leadership Method Modifier - switch (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod()) { - case REGULAR: - break; - case FAMILY: - if (isDesertion) { - modifier--; - } else { - modifier++; - } - break; - case GREEN: - if (isDesertion) { - modifier--; - } - break; - case ELITE: - case IRON_FIST: - modifier++; - break; - } - - return modifier; - } - - /** - * Calculates the experience modifier based on the person's experience level. - * - * @param campaign the campaign that the person is participating in - * @param person the person whose experience level is being evaluated - * @return the experience modifier based on the person's experience level - * @throws IllegalStateException if the person's experience level is an unexpected value - */ - private static int getExperienceModifier(Campaign campaign, Person person) { - switch (person.getExperienceLevel(campaign, false)) { - case -1: - return -2; - case 0: - case 1: - return -1; - case 2: - return 0; - case 3: - return 1; - case 4: - return 2; - default: - throw new IllegalStateException("Unexpected value in getExperienceModifier: " + person.getExperienceLevel(campaign, false)); - } - } - - /** - * Calculates the force reliability modifier based on the desertion flag, and chosen reliability method. - * - * @param campaign the ongoing campaign - * @param isDesertion whether the target number is for a desertion check - * @param meanLoyalty the mean loyalty value - * @return the force reliability modifier as an integer value - */ - private static int getForceReliabilityModifier(Campaign campaign, boolean isDesertion, Integer meanLoyalty) { - ForceReliabilityMethod reliabilityMethod = campaign.getCampaignOptions().getForceReliabilityMethod(); - - switch (reliabilityMethod) { - case UNIT_RATING: - return getUnitRatingModifier(campaign.getUnitRatingMod(), isDesertion); - case LOYALTY: - if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { - return getLoyaltyModifier(isDesertion, meanLoyalty); - } else { - return getUnitRatingModifier(campaign.getUnitRatingMod(), isDesertion); - } - case OVERRIDE_C: - return 0; - case OVERRIDE_A: - return 1; - case OVERRIDE_B: - if (isDesertion) { - return 1; - } else { - return 0; - } - case OVERRIDE_D: - if (!isDesertion) { - return -1; - } else { - return 0; - } - case OVERRIDE_F: - return -1; - } - return 0; - } - - /** - * Computes the loyalty morale modifier. - * - * @param isDesertion whether the target number is for a desertion check - * @param meanLoyalty the mean loyalty value used to calculate the loyalty modifier - * @return the loyalty modifier calculated based on the given parameters - * @throws IllegalStateException if the meanLoyalty value is unexpected - */ - private static int getLoyaltyModifier(boolean isDesertion, Integer meanLoyalty) { - switch (meanLoyalty) { - case -3: - return -1; - case -2: - case -1: - if (!isDesertion) { - return -1; - } else { - return 0; - } - case 0: - case 1: - if (isDesertion) { - return 1; - } else { - return 0; - } - case 2: - case 3: - return 1; - default: - throw new IllegalStateException("Unexpected value in getLoyaltyModifier: " + meanLoyalty); - } - } - - /** - * Computes the unit rating morale modifier. - * - * @param unitRatingMod the unit rating modifier value - * @param isDesertion whether the target number is for a desertion check - * @return the unit rating morale modifier - * @throws IllegalStateException if the campaign has an unexpected value for unit rating - */ - private static int getUnitRatingModifier(Integer unitRatingMod, boolean isDesertion) { - switch(unitRatingMod) { - case 0: - return -1; - case 1: - if (!isDesertion) { - return -1; - } else { - return 0; - } - case 2: - case 3: - return 0; - case 4: - if (isDesertion) { - return 1; - } else { - return 0; - } - case 5: - return 1; - default: - throw new IllegalStateException("Unexpected value in getUnitRatingModifier: " + unitRatingMod); - } - } - - /** - * Returns the profession modifier based on the role and person. - * - * @param role the personnel role of the person - * @param person the person for whom the profession modifier is being calculated - * @return the profession modifier as an Integer - */ - private static Integer getProfessionModifier(PersonnelRole role, Person person) { - if (role.isVesselCrew()) { - if (person.getUnit() == null) { - return -1; - } else { - Entity entity = person.getUnit().getEntity(); - - if ((entity.isSmallCraft()) || (entity.isJumpShip())) { - return -1; - } else if (entity.isDropShip()) { - return 0; - } else if (entity.isWarShip()) { - return 2; - } - } - } else if ((role.isMechWarrior()) || (role.isProtoMechPilot()) || (role.isAerospaceGrouping()) || (role.isMedicalStaff())) { - return 1; - } else if ((role.isSoldier()) || (role.isTech())) { - return -1; - } else if (role.isAdministrator()) { - return -2; - } else if (role.isVehicleCrew()) { - if (person.getUnit() == null) { - return 0; - } else if (person.getUnit().getEntity().isSupportVehicle()) { - return -2; - } - } - - return 0; - } - - /** - * Adds a morale report to the given Campaign. - * - * @param campaign the Campaign for which to generate the report - */ - public static void getMoraleReport(Campaign campaign) { - final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", - MekHQ.getMHQOptions().getLocale()); - - StringBuilder moraleReport = new StringBuilder(); - - if (getTargetNumber(campaign, true) > 2) { - moraleReport.append(String.format(resources.getString("moraleReportLow.text"), getMoraleLevel(campaign))); - } else { - moraleReport.append(String.format(resources.getString("moraleReport.text"), getMoraleLevel(campaign))); - } - - if (getTargetNumber(campaign, false) > 2) { - moraleReport.append(' ').append(resources.getString("moraleReportMutiny.text")); - } - - campaign.addReport(moraleReport.toString()); - } - - /** - * Makes morale checks for personnel in a given campaign. - * - * @param campaign the campaign in which to make the morale checks - * @param isDesertion a boolean indicating if the checks are for desertion (true) or mutiny (false) - * @return true if someone has mutinied or deserted, false otherwise - */ - public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { - // we start with cases that cause not check to be needed - if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { - return false; - } else if ((isDesertion) && (!campaign.getLocation().isOnPlanet())) { - return false; - } else if ((!isDesertion) && (!campaign.getCampaignOptions().isUseMutinies())) { - return false; - } - - // Next, we gather essential information, such as the target number, - // personnel list, unit list (if unit theft is enabled, and mean loyalty score - int targetNumber = getTargetNumber(campaign, isDesertion); - - final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", - MekHQ.getMHQOptions().getLocale()); - - List filteredPersonnel = campaign.getActivePersonnel().stream() - .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) - .filter(person -> !person.isChild(campaign.getLocalDate())) - .collect(Collectors.toList()); - - int meanLoyalty = getMeanLoyalty(campaign, isDesertion); - - List possibleTheftTargets = new ArrayList<>(); - - if (campaign.getCampaignOptions().isUseTheftUnit()) { - possibleTheftTargets = campaign.getHangar().getUnits().stream() - .filter(unit -> - (!unit.isDamaged()) - && (!unit.isDeployed()) - && (!unit.getEntity().isLargeCraft()) - && (!unit.getEntity().isWarShip()) - ) - .collect(Collectors.toCollection(ArrayList::new)); - } - - // Here we identify who the loyalist commander is - Person loyalistLeader = campaign.getFlaggedCommander(); - - // if there is no Commander, we assume the highest ranked person is the loyalist leader - if (loyalistLeader == null) { - loyalistLeader = campaign.getHighestRankedPerson(filteredPersonnel, true); - } - - // Next we perform the actual checks, building a list of loyalists and rebels (if relevant) - List loyalists = new ArrayList<>(); - HashMap rebels = new HashMap<>(); - - boolean someoneHasDeserted = false; - boolean someoneHasMutinied = false; - - for (Person person : filteredPersonnel) { - int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, meanLoyalty); - - int firstRoll = Compute.d6(2) + modifier; - int secondRoll = Compute.d6(2) + modifier; - - if (isDesertion) { - if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { - possibleTheftTargets.remove(processDesertion(campaign, person, secondRoll, targetNumber, possibleTheftTargets, resources)); - someoneHasDeserted = true; - } - } else { - if (person.equals(loyalistLeader)) { - loyalists.add(person); - } - - if (firstRoll < targetNumber) { - if (person.getUnit() == null) { - rebels.put(person, firstRoll); - - someoneHasMutinied = true; - - continue; - } - - if (person.getUnit().getCrew().size() == 1) { - rebels.put(person, firstRoll); - - someoneHasMutinied = true; - - continue; - } - - if (person.getUnit().getCrew().contains(loyalistLeader)) { - if (person.getUnit().getEntity().isCommander()) { - loyalists.addAll(person.getUnit().getCrew()); - } - } - - if (person.getUnit().getEntity().isCommander()) { - for (Person crew : person.getUnit().getCrew()) { - if (!person.getUnit().getEntity().isCommander()) { - rebels.put(crew, targetNumber - 2); - } else { - rebels.put(person, firstRoll); - } - } - } - - LogManager.getLogger().info(rebels.toString()); - - if (!rebels.isEmpty()) { - someoneHasMutinied = true; - } - } else { - if ((person.getUnit() != null) && (person.isCommander())) { - loyalists.addAll(person.getUnit().getCrew()); - } else if (person.getUnit() == null) { - loyalists.add(person); - } - } - } - } - - // the rolls made, we check whether a mutiny or desertion has occurred and if so, process it - if (someoneHasDeserted) { - return true; - } else if (someoneHasMutinied) { - processMutiny(campaign, loyalistLeader, loyalists, rebels, possibleTheftTargets, resources); - return true; - } - - return false; - } - - /** - * Calculates the mean loyalty of active personnel in a campaign. - * - * @param campaign the active personnel - * @param isDesertion true if desertions should be included in the calculation, false otherwise - * @return the mean loyalty of the active personnel in the campaign - */ - private static int getMeanLoyalty(Campaign campaign, boolean isDesertion) { - int loyalty = campaign.getActivePersonnel().stream() - .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) - .filter(person -> person.isChild(campaign.getLocalDate())) - .mapToInt(Person::getLoyalty) - .sum(); - - long personnel = campaign.getActivePersonnel().stream() - .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) - .filter(person -> person.isChild(campaign.getLocalDate())) - .count(); - - if (personnel == 0) { - return 0; - } else { - return (int) (loyalty / personnel); - } - } - - /** - * Processes desertion for a person. - * - * @param campaign the current campaign - * @param person the potential deserter - * @param roll the desertion roll result - * @param targetNumber the target number for desertion= - * @param possibleTheftTargets the list of units - * @param resources the resource bundle for localized messages - */ - private static Unit processDesertion(Campaign campaign, Person person, int roll, int targetNumber, - List possibleTheftTargets, ResourceBundle resources) { - int morale = campaign.getMorale(); - - if (roll <= (targetNumber - 2)) { - if (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod().isIronFist()) { - morale -= 2; - } - - if (roll <= (morale - 2)) { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.DESERTED); - - // reclaim original unit (if available) - if (person.getOriginalUnitId() != null) { - morale = reclaimOriginalUnit(campaign, person); - } - - // check for theft - if (((roll + 6) < (morale / 10)) && (campaign.getCampaignOptions().isUseTheftUnit()) && (!possibleTheftTargets.isEmpty())) { - return processUnitTheft(campaign, possibleTheftTargets, resources); - } else if (((roll + 5) < (morale / 10)) && (campaign.getCampaignOptions().isUseTheftMoney())) { - processMoneyTheft(campaign, resources); - return null; - } else if (((roll + 4) < (morale / 10)) && (campaign.getCampaignOptions().isUseTheftParts())) { - processPartTheft(campaign, resources); - return null; - } else if ((roll + 3) < (morale / 10)) { - processPettyTheft(campaign, resources); - return null; - } - } - } else { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); - person.setAwolDays(Compute.d6(2)); - } - - return null; - } - - /** - * Reclaims the original unit for a person in a campaign. - * If the unit is no longer available, return an integer used to reduce effective morale. - * - * @param campaign The campaign the person is participating in - * @param person The person whose original unit is to be reclaimed - * @return 0 if the original unit is removed successfully, or an integer if removal fails - */ - private static int reclaimOriginalUnit(Campaign campaign, Person person) { - UUID originalUnitId = person.getOriginalUnitId(); - int originalUnitWeight = person.getOriginalUnitWeight(); - - // this stops support vehicles being over-valued - if (originalUnitWeight > WEIGHT_LARGE_WAR) { - originalUnitWeight -= WEIGHT_LARGE_WAR; - } - - if (!campaign.getUnit(originalUnitId).isDeployed()) { - try { - campaign.removeUnit(person.getOriginalUnitId()); - return 0; - } catch (Exception e) { - return originalUnitWeight; - } - } else { - return originalUnitWeight; - } - } - - /** - * Processes unit thefts during a campaign. - * - * @param campaign The campaign in which the unit thefts occur. - * @param possibleTheftTargets The list of units that can be stolen. - * @param resources The resource bundle for internationalization. - * @return A list of stolen units. - */ - private static Unit processUnitTheft(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { - // if there is nothing left to steal, downgrade the theft - if (possibleTheftTargets.isEmpty()) { - if (campaign.getCampaignOptions().isUseTheftMoney()) { - processMoneyTheft(campaign, resources); - } else if (campaign.getCampaignOptions().isUseTheftParts()) { - processPartTheft(campaign, resources); - } else { - processPettyTheft(campaign, resources); - } - - return null; - } - - // process the theft - Unit stolenUnit = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); - String theftString = stolenUnit.getName(); - - if (!Objects.equals(stolenUnit.getFluffName(), "")) { - theftString += ' ' + stolenUnit.getFluffName(); - } - - if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 5)) { - int stolenUnitType = stolenUnit.getEntity().getUnitType(); - String stolenUnitShortNameRaw = stolenUnit.getEntity().getShortNameRaw(); - - campaign.getUnitMarket().addSingleUnit(campaign, - UnitMarketType.BLACK_MARKET, - stolenUnitType, - MechSummaryCache.getInstance().getMech(stolenUnitShortNameRaw), - campaign.getCampaignOptions().getTheftResellValue() + getPercentageModifier()); - - campaign.addReport(String.format(resources.getString("desertionTheft.text"), theftString)); - campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), theftString)); - } else { - campaign.addReport(String.format(resources.getString("desertionTheft.text"), theftString)); - } - - return stolenUnit; - } - - /** - * Processes money theft. - * - * @param campaign the campaign for which the theft is being processed - * @param resources the ResourceBundle containing the necessary resources - */ - private static void processMoneyTheft(Campaign campaign, ResourceBundle resources) { - int theftPercentage = campaign.getCampaignOptions().getTheftValue(); - - theftPercentage += getPercentageModifier(); - - Money theft = campaign.getFunds() - .multipliedBy(theftPercentage) - .dividedBy(100) - .round(); - - if (theft.isPositive()) { - campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, - String.format(resources.getString("desertionTheftTransactionReport.text"), - FinancialInstitutions.randomFinancialInstitution(campaign.getLocalDate()).toString())); - - campaign.addReport(String.format(String.format(resources.getString("desertionTheftMoney.text"), theft.getAmount()))); - } else if (campaign.getCampaignOptions().isUseTheftParts()) { - processPartTheft(campaign, resources); - } else { - processPettyTheft(campaign, resources); - } - } - - /** - * Gets the percentage modifier based on a roll of two six-sided dice. - * - * @return the percentage modifier based on the dice roll: - * - 3 for a roll of 2 - * - 2 for a roll of 3 - * - 1 for a roll of 4 or 5 - * - 0 for a roll of 6, 7, or 8 - * - -1 for a roll of 9 - * - -2 for a roll of 10 or 11 - * - -3 for a roll of 12 - * @throws IllegalStateException if the roll is unexpected - */ - private static int getPercentageModifier() { - int roll = Compute.d6(2); - - switch(roll) { - case 2: - return 3; - case 3: - return 2; - case 4: - case 5: - return 1; - case 6: - case 7: - case 8: - return 0; - case 9: - return -1; - case 10: - case 11: - return -2; - case 12: - return -3; - default: - throw new IllegalStateException("Unexpected value in getPercentageModifier: " + roll); - } - } - - private static void processPartTheft(Campaign campaign, ResourceBundle resources) { - List possibleTheftTargets = campaign.getWarehouse().getSpareParts(); - - // if there are no parts to steal, commit petty theft instead - if (possibleTheftTargets.isEmpty()) { - processPettyTheft(campaign, resources); - - return; - } - - // how many thefts should be rolled? - int originalTheftCount = 1; - - if (campaign.getCampaignOptions().getTheftPartsDiceCount() != 0) { - originalTheftCount = Compute.d6(campaign.getCampaignOptions().getTheftPartsDiceCount()); - } - - boolean committingTheft = true; - int theftCount = originalTheftCount; - HashMap stolenItems = new HashMap<>(); - - while (committingTheft) { - if (possibleTheftTargets.isEmpty()) { - if (stolenItems.isEmpty()) { - processPettyTheft(campaign, resources); - - return; - } - committingTheft = false; - continue; - } - - Part desiredPart = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); - - boolean partStolen = false; - - while (!partStolen) { - boolean hasParent = true; - - while (hasParent) { - if (desiredPart.getParentPart() != null) { - possibleTheftTargets.remove(desiredPart); - desiredPart = desiredPart.getParentPart(); - } else { - hasParent = false; - } - } - - // if the part is in transit, - // we don't want to steal it, so we pick another item - if (desiredPart.getDaysToArrival() > 0) { - possibleTheftTargets.remove(desiredPart); - partStolen = true; - continue; - } else if (desiredPart.getDaysToWait() > 0) { - possibleTheftTargets.remove(desiredPart); - partStolen = true; - continue; - } - - // if the part is being actively worked on, - // we don't want to steal it, so we pick another item - if (desiredPart.isBeingWorkedOn()) { - possibleTheftTargets.remove(desiredPart); - partStolen = true; - continue; - } - - // this is where we try to steal an item - if ((desiredPart instanceof AmmoStorage) || (desiredPart instanceof Armor)) { - int roll = Compute.d6(3); - - if (campaign.getWarehouse().removePart(desiredPart, roll)) { - theftCount--; - possibleTheftTargets.remove(desiredPart); - stolenItems.put(desiredPart.getName(), roll); - } else { - LogManager.getLogger().info("Part theft failed to steal ammo/armor ({})", desiredPart); - partStolen = true; - possibleTheftTargets.remove(desiredPart); - continue; - } - } else { - if (campaign.getWarehouse().removePart(desiredPart, 1)) { - theftCount--; - possibleTheftTargets.remove(desiredPart); - stolenItems.put(desiredPart.getName(), 1); - } else { - LogManager.getLogger().info("Part theft failed to steal part ({})", desiredPart); - partStolen = true; - possibleTheftTargets.remove(desiredPart); - continue; - } - } - - partStolen = true; - - if (theftCount == 0) { - committingTheft = false; - - campaign.addReport(stolenItems.keySet().stream() - .map(entry -> " [" + stolenItems.get(entry) + "x " + entry + ']') - .collect(Collectors.joining("" - , resources.getString("desertionTheftParts.text") - , ""))); - } - } - } - } - - /** - * This method is used to process a petty theft incident in a company. - * It randomly selects an item from a list of stolen items and adds the item to the campaign report. - * - * @param campaign The campaign object to add the report to. - * @param resources The ResourceBundle object to retrieve localized strings. - */ - private static void processPettyTheft(Campaign campaign, ResourceBundle resources) { - List items = List.of( - "stapler.text", - "mascot.text", - "phones.text", - "tablets.text", - "hardDrives.text", - "flashDrive.text", - "companyCreditCard.text", - "officePet.text", - "confidentialReports.text", - "clientLists.text", - "unitSchematics.text", - "businessPlans.text", - "marketingMaterials.text", - "trainingPresentations.text", - "softwareLicenses.text", - "rifle.text", - "financialRecords.text", - "employeeRecords.text", - "proprietarySoftware.text", - "networkAccessCredentials.text", - "companyUniforms.text", - "desks.text", - "monitors.text", - "printers.text", - "projectors.text", - "carKeys.text", - "dartboard.text", - "securityBadges.text", - "officeKeys.text", - "pettyCashBox.text", - "cheques.text", - "diary.text", - "giftCards.text", - "coupons.text", - "personalDataOfCoworkers.text", - "battlePlans.text", - "legalDocuments.text", - "signedContracts.text", - "clientFeedbackForms.text", - "trainingManuals.text", - "marketResearch.text", - "businessContacts.text", - "meetingNotes.text", - "contractLeads.text", - "urbanMechPlushie.text", - "brandedMugs.text", - "companyPhoneDirectories.text", - "logbooks.text", - "inventoryLists.text", - "confidentialHpgMessages.text", - "strategyDocuments.text", - "passwordLists.text", - "internalMemos.text", - "surveillanceCameraRecordings.text", - "brandedPens.text", - "engineeringBlueprints.text", - "codeRepositories.text", - "internalNewsletters.text", - "hrPolicies.text", - "companyHandbooks.text", - "procedureManuals.text", - "securityPolicies.text", - "simulationData.text", - "businessCards.text", - "ndaAgreements.text", - "nonCompeteAgreements.text", - "softwareCode.text", - "technicalSpecifications.text", - "securitySchedules.text", - "underWear.text", - "marketAnalysis.text", - "salesContracts.text", - "expenseReports.text", - "reimbursementReceipts.text", - "invoices.text", - "employeeBenefitsInformation.text", - "insuranceDocuments.text", - "lightBulbs.text", - "strategicAlliancesInformation.text", - "computers.text", - "boots.text", - "employeeDiscountStructures.text", - "meetingMinutes.text", - "itInfrastructureDetails.text", - "serverAccessCodes.text", - "backupDrives.text", - "missionData.text", - "executiveMeetingNotes.text", - "toe.text", - "clientComplaints.text", - "inventoryControlSystems.text", - "chairs.text", - "shippingLogs.text", - "printerPaper.text", - "internalAuditReports.text", - "corruption.text", - "officePlants.text", - "battlefieldPerformanceReports.text", - "companyStandard.text", - "analyticsReports.text", - "fridge.text", - "coffeeMachine.text", - "mug.text", - "toiletSeats.text", - "miniatures.text", - "dropShip.text"); - - campaign.addReport(String.format(resources.getString("desertionTheft.text"), - resources.getString(items.get(new Random().nextInt(items.size()))))); - } - - /** - * Processes the number of AWOL (Absent Without Leave) days for a person. - * If the person has no AWOL days remaining, it randomly determines whether to add another d6 AWOL days - * or change the person's status to ACTIVE. - * Otherwise, it subtracts one AWOL day from the person's total. - * - * @param campaign the campaign in which the person belongs - * @param person the person for whom AWOL days are being processed - */ - public static void processAwolDays(Campaign campaign, Person person) { - final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", - MekHQ.getMHQOptions().getLocale()); - - int awolDays = person.getAwolDays(); - - if (awolDays == 0) { - if (Compute.d6(1) <= 2) { - person.setAwolDays(awolDays + Compute.d6(1)); - campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("desertionAwolExtended.text")); - } else { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.ACTIVE); - } - } else if (awolDays > 0) { - person.setAwolDays(awolDays - 1); - } else { - person.setAwolDays(awolDays - 1); - } - } - - /** - * The method processes the morale change in a campaign. - * - * @param campaign the campaign to process the morale change for - * @param steps the number of steps to change the morale by - */ - public static void processMoraleChange(Campaign campaign, int steps) { - final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", - MekHQ.getMHQOptions().getLocale()); - - int change = campaign.getCampaignOptions().getMoraleStepSize() * steps; - int oldMorale = campaign.getMorale(); - int newMorale = MathUtility.clamp(campaign.getMorale() + change, 10, 70); - - campaign.setMorale(newMorale); - - if ((oldMorale / 10) != (newMorale / 10)) { - getMoraleReport(campaign); - - if ((oldMorale >= 50) && (newMorale < 50)) { - campaign.addReport(resources.getString("moraleReportRecovered.text")); - } - } - } - - private static void processMutiny(Campaign campaign, - Person loyalistLeader, List loyalists, - HashMap rebels, - List possibleTheftTargets, - ResourceBundle resources) { - // This prevents us from needing to do the full process for tiny mutinies that have no chance of success - // In these cases we alert the player that a mutiny was thwarted and then treat it as a Desertion - if ((loyalists.size() / 2) > rebels.size()) { - if (rebels.size() > 1) { - campaign.addReport(String.format(resources.getString("mutinyThwartedPlural.text"), rebels.size())); - } else { - campaign.addReport(String.format(resources.getString("mutinyThwartedSingular.text"), rebels.size())); - } - - int targetNumber = getTargetNumber(campaign, true); - - for (Person person : rebels.keySet()) { - possibleTheftTargets.remove(processDesertion(campaign, - person, - Math.min(rebels.get(person), targetNumber - 2), - targetNumber, - possibleTheftTargets, resources)); - } - - return; - } - - // A civil war breaks out. - List bystanders = new ArrayList<>(); - - // The rebels have already picked their side, so we only need to process the loyalists - // This represents the mutineers gathering support - - Iterator iterator = loyalists.iterator(); - while (iterator.hasNext()) { - Person person = iterator.next(); - - // The loyalist leader isn't allowed to rebel against themselves - if (person.equals(loyalistLeader)) { - continue; - } - - // we then process everyone else - int roll = Compute.d6(1); - int civilWarTargetNumber = getCivilWarTargetNumber(campaign, person); - - if ((roll >= (civilWarTargetNumber - 1)) && (roll <= (civilWarTargetNumber + 1))) { - iterator.remove(); - bystanders.add(person); - } else if (roll < (civilWarTargetNumber - 1)) { - iterator.remove(); - rebels.put(person, 0); - } - } - - // We now need to determine the leader of the rebels. - // This might not always be someone who initially joined the mutiny, - // in which case we can assume they were persuaded into the role by the original mutineers - Person rebelLeader = getRebelLeader(campaign, new ArrayList<>(rebels.keySet())); - - HashMap rebelUnits = getUnits(new ArrayList<>(rebels.keySet())); - int rebelBv = rebelUnits.keySet().stream() - .mapToInt(unit -> unit.getEntity() - .calculateBattleValue(true, false)).sum(); - - HashMap loyalistUnits = getUnits(loyalists); - int loyalistBv = loyalistUnits.keySet().stream() - .mapToInt(unit -> unit.getEntity() - .calculateBattleValue(true, false)).sum(); - - // we now need to present the player with a choice: join the rebels, or support the loyalists - int supportDecision = -1; - - while (supportDecision == -1) { - supportDecision = MutinySupportDialog.supportDialog( - campaign, resources, getDeposeVoteCount(campaign, loyalists.size(), rebels, rebelLeader), - bystanders.size(), - loyalistLeader, loyalists.size(), new ArrayList<>(loyalistUnits.keySet()), loyalistBv, - rebelLeader, rebels.size(), new ArrayList<>(rebelUnits.keySet()), rebelBv - ); - } - } - - /** - * Calculates the vote count for deposing the Loyalist leader in a campaign. - * - * @param campaign the campaign object - * @param loyalistsCount the number of loyalists - * @param rebels a map containing rebels and their votes - * @param rebelLeader the leader of the rebels - * @return true if the vote count for deposing the Loyalist leader is higher than the vote count for an uprising, false otherwise. - */ - private static boolean getDeposeVoteCount(Campaign campaign, int loyalistsCount, HashMap rebels, Person rebelLeader) { - // The rebels now decide whether they want to overthrow the Loyalist leader, or just depose them - int deposeVoteCount = 0; - int uprisingVoteCount = 0; - int voteModifier = 0; - - // The larger the rebel force, the more confident they feel and the less prone to drastic measures - if (rebels.keySet().size() > (loyalistsCount * 1.25)) { - voteModifier++; - } else if (rebels.keySet().size() < (loyalistsCount * 0.75)) { - voteModifier--; - } - - // the more loyal the rebel leader, the more they call for a peaceful resolution - if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { - voteModifier += rebelLeader.getLoyalty(); - } - - for (Person ignored : rebels.keySet()) { - if ((Compute.d6(1) + voteModifier) >= 4) { - deposeVoteCount++; - } else { - uprisingVoteCount++; - } - } - - return deposeVoteCount > uprisingVoteCount; - } - - - /** - * Returns the rebel leader from the given list of rebels based on their rank. - * - * @param campaign The campaign containing the rank data. - * @param rebels The list of rebels to choose the leader from. - * @return The rebel leader with the highest rank in the campaign. - */ - private static Person getRebelLeader(Campaign campaign, List rebels) { - Person rebelLeader = rebels.get(1); - - int incumbantRankNumeric = getAdjustedRankNumeric(campaign, rebelLeader); - int newRankNumeric; - - for (Person person : rebels) { - newRankNumeric = getAdjustedRankNumeric(campaign, person); - - // The incumbent wins, in the event of a draw - if (incumbantRankNumeric < newRankNumeric) { - rebelLeader = person; - incumbantRankNumeric = newRankNumeric; - } - } - return rebelLeader; - } - - /** - * Returns the adjusted rank numeric for a person. - * - * @param campaign the current campaign - * @param person the person whose rank numeric is being calculated - * @return the adjusted numeric rank of the person - */ - private static Integer getAdjustedRankNumeric(Campaign campaign, Person person) { - int rankNumeric = person.getRankNumeric(); - - // if management skill is enabled, it influences adjusted rank numeric - if (campaign.getCampaignOptions().isUseManagementSkill()) { - if (person.hasSkill(SkillType.S_LEADER)) { - rankNumeric += person.getSkillLevel(SkillType.S_LEADER) + campaign.getCampaignOptions().getManagementSkillPenalty(); - } else { - rankNumeric += campaign.getCampaignOptions().getManagementSkillPenalty(); - } - } - - // We use inverse loyalty here, with the less loyal pushing themselves into positions of power within the rebellion - if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { - rankNumeric -= person.getLoyalty(); - } - - return rankNumeric; - } - - /** - * Calculates the target number for civil war loyalty checks - * - * @param campaign the ongoing campaign - * @param person the person for which loyalty is being tested - * @return the target number for a civil war loyalty check - * @throws IllegalStateException if the loyalty value is unexpected - */ - private static int getCivilWarTargetNumber(Campaign campaign, Person person) { - int modifier = 0; - - if (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod().isIronFist()) { - modifier += 2; - } - - if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { - switch (person.getLoyalty()) { - case -3: - return 6 + modifier; - case -2: - return 5 + modifier; - case -1: - case 0: - return 4 + modifier; - case 1: - return 3 + modifier; - case 2: - return 2 + modifier; - case 3: - return 1 + modifier; - default: - throw new IllegalStateException("Unexpected value in getCivilWarTargetNumber: " + person.getLoyalty()); - } - } - return 4 + modifier; - } - - /** - * Retrieves the units that are eligible to participate in the civil war based on the provided personnel. - * Multi-crewed units follow their commander. - * - * @param personnel A list of personnel (should all belong to the same mutiny faction). - * @return A HashMap of units and their corresponding battle values. - */ - private static HashMap getUnits(List personnel) { - HashMap forces = new HashMap<>(); - - for (Person person: personnel) { - if (person.getUnit() != null) { - Unit unit = person.getUnit(); - - if ((unit.getEntity().isJumpShip()) || (unit.getEntity().isWarShip()) || (unit.getEntity().isSupportVehicle())) { - continue; - } - - // We only care about the commander, as this allows us to ensure each Unit is only counted once. - // We also check to ensure the unit isn't already deployed, or too damaged to fight. - if ((unit.isCommander(person)) && (!unit.isDeployed()) && (!unit.getEntity().isCrippled()) && (!unit.getEntity().isDmgHeavy())) { - forces.put(unit, unit.getEntity().calculateBattleValue(true, false)); - } - } - } - return forces; - } -} diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java new file mode 100644 index 0000000000..b6ea606936 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java @@ -0,0 +1,415 @@ +package mekhq.campaign.personnel.turnoverAndRetention.Morale; + +import megamek.common.Compute; +import megamek.common.MechSummaryCache; +import mekhq.campaign.Campaign; +import mekhq.campaign.finances.Money; +import mekhq.campaign.finances.enums.TransactionType; +import mekhq.campaign.finances.financialInstitutions.FinancialInstitutions; +import mekhq.campaign.market.enums.UnitMarketType; +import mekhq.campaign.parts.AmmoStorage; +import mekhq.campaign.parts.Armor; +import mekhq.campaign.parts.Part; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.unit.Unit; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static mekhq.campaign.personnel.turnoverAndRetention.Morale.MoraleController.getPercentageModifier; +import static mekhq.campaign.personnel.turnoverAndRetention.Morale.MoraleController.reclaimOriginalUnit; + +public class Desertion { + /** + * Processes desertion for a person. + * + * @param campaign the current campaign + * @param deserters the potential deserters and their desertion rolls + * @param targetNumber the target number for desertion + * @param resources the resource bundle for localized messages + */ + private static void processDesertion(Campaign campaign, HashMap deserters, int targetNumber, + List possibleTheftTargets, ResourceBundle resources) { + // morale is used to determine whether a theft occurs + int morale = campaign.getMorale() / 10; + + // if a theft occurs, it will take an item from one of these lists + + // we assume units with a crew size > 5 cannot be stolen by one person, + // neither can units that are currently unavailable + ArrayList theftTargetsUnits = campaign.getUnits().stream() + .filter(unit -> (!unit.isAvailable()) && (unit.getFullCrewSize() < 6)) + .collect(Collectors.toCollection(ArrayList::new)); + + + // here we collect a list of parts that are available to be stolen + ArrayList theftTargetsParts = campaign.getWarehouse().getSpareParts().stream() + .filter(part -> part.getDaysToArrival() == 0) + .filter(part -> part.getDaysToWait() == 0) + .filter(part -> !part.isBeingWorkedOn()) + .filter(part -> !part.needsFixing()) + .filter(part -> !part.isOmniPodded()) + .collect(Collectors.toCollection(ArrayList::new)); + + // we make a list of thieves, so they can be processed at the same time + List thieves = new ArrayList<>(); + + // next we check what type of desertion is occurring: going AWOL, deserting, or deserting with theft + for (Person person : deserters.keySet()) { + int roll = deserters.get(person); + + // the Iron Fist leadership style makes desertions worse + if (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod().isIronFist()) { + roll -= 1; + } + + // if margin of failure is 1-2 person goes AWOL instead of deserting + if ((targetNumber - roll) >= 2) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); + person.setAwolDays(Compute.d6(2)); + + return; + } + + // otherwise, reclaim original unit + if (person.getOriginalUnitId() != null) { + if (reclaimOriginalUnit(campaign, person)) { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + + String.format(resources.getString("reclaimSuccessful.text"), + campaign.getUnit(person.getOriginalUnitId()))); + } else { + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + + resources.getString("reclaimFailed.text")); + + // if we're unable to reclaim original unit, roll is set to 0 to force a theft + roll = 0; + } + } + + // if margin of failure is 4+ a theft occurs + if ((morale - roll) >= 4) { + thieves.add(person); + } + } + + if (!thieves.isEmpty()) { + theftController(campaign, thieves, theftTargetsParts, theftTargetsUnits, resources); + } + } + + /** + * Processes the theft of petty objects, units, money, or spare parts + * + * @param campaign The current campaign + * @param thieves The list of thieves involved in the theft + * @param theftTargetsParts The list of parts targeted for theft + * @param theftTargetsUnits The list of units targeted for theft + * @param resources The ResourceBundle containing game resources + */ + static void theftController(Campaign campaign, List thieves, List theftTargetsParts, ArrayList theftTargetsUnits, ResourceBundle resources) { + for (Person ignored : thieves) { + boolean theftComplete = false; + + // we want to keep rolling for a theft until a successful one occurs. + while (theftComplete) { + int roll = Compute.randomInt(15); + + switch (roll) { + case 0: + // unit theft + if ((campaign.getCampaignOptions().isUseTheftUnit()) && (!theftTargetsUnits.isEmpty())) { + theftTargetsUnits.remove(processUnitTheft(campaign, theftTargetsUnits, resources)); + theftComplete = true; + } + break; + case 1: + case 2: + // money theft + if (campaign.getCampaignOptions().isUseTheftMoney()) { + theftComplete = processMoneyTheft(campaign, resources); + } + break; + case 3: + case 4: + case 5: + case 6: + // part theft + if ((campaign.getCampaignOptions().isUseTheftParts()) && (!theftTargetsParts.isEmpty())) { + theftTargetsParts = processPartTheft(campaign, theftTargetsParts, resources); + theftComplete = true; + } + break; + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + // petty theft + processPettyTheft(campaign, resources); + theftComplete = true; + break; + default: + throw new IllegalStateException("Unexpected value in theftController: " + roll); + } + } + } + } + + /** + * Processes unit thefts during a campaign. + * + * @param campaign The campaign in which the unit thefts occur. + * @param possibleTheftTargets The list of units that can be stolen. + * @param resources The resource bundle for internationalization. + * @return A list of stolen units. + */ + static Unit processUnitTheft(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { + Unit stolenUnit = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); + String theftString = stolenUnit.getName(); + + if (!Objects.equals(stolenUnit.getFluffName(), "")) { + theftString += ' ' + stolenUnit.getFluffName(); + } + + if ((campaign.getCampaignOptions().isUseAtB()) && (!campaign.getFaction().isClan()) && (Compute.d6(1) >= 5)) { + int stolenUnitType = stolenUnit.getEntity().getUnitType(); + String stolenUnitShortNameRaw = stolenUnit.getEntity().getShortNameRaw(); + + campaign.getUnitMarket().addSingleUnit(campaign, + UnitMarketType.BLACK_MARKET, + stolenUnitType, + MechSummaryCache.getInstance().getMech(stolenUnitShortNameRaw), + campaign.getCampaignOptions().getTheftResellValue() + getPercentageModifier()); + + campaign.addReport(String.format(resources.getString("desertionTheft.text"), theftString)); + campaign.addReport(String.format(resources.getString("desertionTheftBlackMarket.text"), theftString)); + } else { + campaign.addReport(String.format(resources.getString("desertionTheft.text"), theftString)); + } + + return stolenUnit; + } + + /** + * Processes money theft. + * + * @param campaign the campaign for which the theft is being processed + * @param resources the ResourceBundle containing the necessary resources + * @return true if money theft was successfully processed, false otherwise + */ + static boolean processMoneyTheft(Campaign campaign, ResourceBundle resources) { + int theftPercentage = campaign.getCampaignOptions().getTheftValue() + getPercentageModifier(); + + Money theft = campaign.getFunds() + .multipliedBy(theftPercentage) + .dividedBy(100) + .round(); + + if (theft.isPositive()) { + campaign.getFinances().debit(TransactionType.THEFT, campaign.getLocalDate(), theft, + String.format(resources.getString("desertionTheftTransactionReport.text"), + FinancialInstitutions.randomFinancialInstitution(campaign.getLocalDate()).toString())); + + campaign.addReport(String.format(String.format(resources.getString("desertionTheftMoney.text"), theft.getAmount()))); + + return true; + } else { + return false; + } + } + + /** + * Processes the theft of parts. + * + * @param campaign the current campaign + * @param theftTargetsParts the list of parts targeted for theft + * @param resources the ResourceBundle containing game resources + * @return a list of parts that have been stolen + */ + static List processPartTheft(Campaign campaign, List theftTargetsParts, ResourceBundle resources) { + // how many parts should be stolen? + int theftCount = 1; + + if (campaign.getCampaignOptions().getTheftPartsDiceCount() != 0) { + theftCount += IntStream + .range(0, campaign.getCampaignOptions() + .getTheftPartsDiceCount()).map(roll -> Compute.randomInt(3) + 1) + .sum(); + } + + HashMap stolenItems = new HashMap<>(); + + for (int theft = 0; theft < theftCount; theft++) { + int itemCount; + + // if everything has been stolen the would-be thief will leave empty-handed + if (!theftTargetsParts.isEmpty()) { + // pick the part to be stolen + Part stolenPart = theftTargetsParts.get(new Random().nextInt(theftTargetsParts.size())); + + // how many parts should be stolen? + if ((stolenPart instanceof AmmoStorage) || (stolenPart instanceof Armor)) { + itemCount = Compute.d6(2); + + if (itemCount > stolenPart.getQuantity()) { + itemCount = stolenPart.getQuantity(); + } + } else { + itemCount = 1; + } + + // steal the part + if (stolenPart instanceof AmmoStorage) { + campaign.getWarehouse().removeAmmo((AmmoStorage) stolenPart, itemCount); + } else if (stolenPart instanceof Armor) { + campaign.getWarehouse().removeArmor((Armor) stolenPart, itemCount); + } else { + campaign.getWarehouse().getPart(stolenPart.getId()); + } + + // add the item and count to our list of stolen items + stolenItems.put(stolenPart, itemCount); + + // after each theft we need to rebuild our list of spare parts + theftTargetsParts = campaign.getWarehouse().getSpareParts().stream() + .filter(part -> part.getDaysToArrival() == 0) + .filter(part -> part.getDaysToWait() == 0) + .filter(part -> !part.isBeingWorkedOn()) + .filter(part -> !part.needsFixing()) + .filter(part -> !part.isOmniPodded()) + .collect(Collectors.toCollection(ArrayList::new)); + } + } + + campaign.addReport(stolenItems.keySet().stream() + .map(part -> ' ' + part.getName() + 'x' + stolenItems.get(part)) + .collect(Collectors.joining("", resources.getString("desertionTheftParts.text"), ""))); + + return theftTargetsParts; + } + + /** + * This method is used to process a petty theft incident in a company. + * It randomly selects an item from a list of stolen items and adds the item to the campaign report. + * + * @param campaign The campaign object to add the report to. + * @param resources The ResourceBundle object to retrieve localized strings. + */ + static void processPettyTheft(Campaign campaign, ResourceBundle resources) { + List items = List.of( + "stapler.text", + "mascot.text", + "phones.text", + "tablets.text", + "hardDrives.text", + "flashDrive.text", + "companyCreditCard.text", + "officePet.text", + "confidentialReports.text", + "clientLists.text", + "unitSchematics.text", + "businessPlans.text", + "marketingMaterials.text", + "trainingPresentations.text", + "softwareLicenses.text", + "rifle.text", + "financialRecords.text", + "employeeRecords.text", + "proprietarySoftware.text", + "networkAccessCredentials.text", + "companyUniforms.text", + "desks.text", + "monitors.text", + "printers.text", + "projectors.text", + "carKeys.text", + "dartboard.text", + "securityBadges.text", + "officeKeys.text", + "pettyCashBox.text", + "cheques.text", + "diary.text", + "giftCards.text", + "coupons.text", + "personalDataOfCoworkers.text", + "battlePlans.text", + "legalDocuments.text", + "signedContracts.text", + "clientFeedbackForms.text", + "trainingManuals.text", + "marketResearch.text", + "businessContacts.text", + "meetingNotes.text", + "contractLeads.text", + "urbanMechPlushie.text", + "brandedMugs.text", + "companyPhoneDirectories.text", + "logbooks.text", + "inventoryLists.text", + "confidentialHpgMessages.text", + "strategyDocuments.text", + "passwordLists.text", + "internalMemos.text", + "surveillanceCameraRecordings.text", + "brandedPens.text", + "engineeringBlueprints.text", + "codeRepositories.text", + "internalNewsletters.text", + "hrPolicies.text", + "companyHandbooks.text", + "procedureManuals.text", + "securityPolicies.text", + "simulationData.text", + "businessCards.text", + "ndaAgreements.text", + "nonCompeteAgreements.text", + "softwareCode.text", + "technicalSpecifications.text", + "securitySchedules.text", + "underWear.text", + "marketAnalysis.text", + "salesContracts.text", + "expenseReports.text", + "reimbursementReceipts.text", + "invoices.text", + "employeeBenefitsInformation.text", + "insuranceDocuments.text", + "lightBulbs.text", + "strategicAlliancesInformation.text", + "computers.text", + "boots.text", + "employeeDiscountStructures.text", + "meetingMinutes.text", + "itInfrastructureDetails.text", + "serverAccessCodes.text", + "backupDrives.text", + "missionData.text", + "executiveMeetingNotes.text", + "toe.text", + "clientComplaints.text", + "inventoryControlSystems.text", + "chairs.text", + "shippingLogs.text", + "printerPaper.text", + "internalAuditReports.text", + "corruption.text", + "officePlants.text", + "battlefieldPerformanceReports.text", + "companyStandard.text", + "analyticsReports.text", + "fridge.text", + "coffeeMachine.text", + "mug.text", + "toiletSeats.text", + "miniatures.text", + "dropShip.text"); + + campaign.addReport(String.format(resources.getString("desertionTheft.text"), + resources.getString(items.get(new Random().nextInt(items.size()))))); + } +} diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java new file mode 100644 index 0000000000..7945dcd3dc --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java @@ -0,0 +1,604 @@ +package mekhq.campaign.personnel.turnoverAndRetention.Morale; + +import megamek.codeUtilities.MathUtility; +import megamek.common.Compute; +import megamek.common.Entity; +import mekhq.MekHQ; +import mekhq.campaign.Campaign; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.enums.ForceReliabilityMethod; +import mekhq.campaign.personnel.enums.PersonnelRole; +import mekhq.campaign.personnel.enums.PersonnelStatus; + +import java.util.HashMap; +import java.util.List; +import java.util.ResourceBundle; +import java.util.UUID; +import java.util.stream.Collectors; + +public class MoraleController { + /** + * This method returns the Morale level as a string based on the campaign's current morale. + * + * @return The Morale level as a string. + * @throws IllegalStateException if the value of 'Morale' is unexpected. + */ + public static String getMoraleLevel(Campaign campaign) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + int morale = campaign.getMorale() / 10; + + switch (morale) { + case 1: + return resources.getString("moraleLevelUnbreakable.text"); + case 2: + return resources.getString("moraleLevelVeryHigh.text"); + case 3: + return resources.getString("moraleLevelHigh.text"); + case 4: + return resources.getString("moraleLevelNormal.text"); + case 5: + return resources.getString("moraleLevelLow.text"); + case 6: + return resources.getString("moraleLevelVeryLow.text"); + case 7: + return resources.getString("moraleLevelBroken.text"); + default: + throw new IllegalStateException("Unexpected value in getMoraleLevel: " + morale); + } + } + + /** + * Calculates the morale check target number based on the campaign's morale and the desertion flag. + * + * @param campaign the current campaign + * @param isDesertion a flag indicating whether the target number is for desertion or not + * @return the calculated target number + * @throws IllegalStateException if the morale value is unexpected + */ + private static int getTargetNumber(Campaign campaign, boolean isDesertion) { + int morale = campaign.getMorale() / 10; + + switch (morale) { + case 1: + if (isDesertion) { + return 0; + } else { + return -3; + } + case 2: + if (isDesertion) { + return 1; + } else { + return -2; + } + case 3: + if (isDesertion) { + return 1; + } else { + return -1; + } + case 4: + if (isDesertion) { + return 2; + } else { + return 0; + } + case 5: + if (isDesertion) { + return 4; + } else { + return 2; + } + case 6: + if (isDesertion) { + return 5; + } else { + return 4; + } + case 7: + if (isDesertion) { + return 8; + } else { + return 7; + } + default: + throw new IllegalStateException("Unexpected value in getTargetNumber: " + morale); + } + } + + /** + * Returns the final morale modifier for a person. + * + * @param campaign the ongoing campaign + * @param person the person the modifier is being calculated for + * @param isDesertion whether the target number is for a desertion check + * @param meanLoyalty the mean loyalty value + * @return the morale check modifiers + */ + private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, boolean isDesertion, Integer meanLoyalty) { + int modifier = 0; + + // Custom Modifier + modifier += campaign.getCampaignOptions().getCustomMoraleModifier(); + + // Experience Level Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierExperienceLevel()) { + modifier += getExperienceModifier(campaign, person); + } + + // Faction Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierFaction()) { + if (person.getOriginFaction().isClan()) { + modifier++; + } else if (person.getOriginFaction().isMercenary()) { + modifier--; + } + } + + // Profession Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierProfession()) { + modifier += getProfessionModifier(person.getPrimaryRole(), person); + + if (person.getSecondaryRole() != null) { + modifier += (modifier + getProfessionModifier(person.getSecondaryRole(), person)) / 2; + } + } + + // Force Reliability Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierForceReliability()) { + modifier += getForceReliabilityModifier(campaign, isDesertion, meanLoyalty); + } + + // Cabin Fever + if (campaign.getCampaignOptions().isUseMoraleModifierCabinFever()) { + modifier--; + } + + // Marriage + if ((campaign.getCampaignOptions().isUseMoraleModifierMarriage()) && (isDesertion)) { + modifier--; + } + + // Management Skill Modifier + if (campaign.getCampaignOptions().isUseMoraleModifierCommanderLeadership()) { + Person commander = campaign.getFlaggedCommander(); + + if ((commander != null) && (campaign.getCampaignOptions().isUseManagementSkill())) { + if (commander.hasSkill(SkillType.S_LEADER)) { + if ((commander.getSkill(SkillType.S_LEADER).getLevel() + campaign.getCampaignOptions().getManagementSkillPenalty()) > 0) { + modifier++; + } + } + } + } + + // Loyalty Modifier + if ((campaign.getCampaignOptions().isUseLoyaltyModifiers()) && (campaign.getCampaignOptions().isUseMoraleModifierLoyalty())) { + modifier += getLoyaltyModifier(isDesertion, person.getLoyalty()); + } + + // Leadership Method Modifier + switch (campaign.getCampaignOptions().getMoraleModifierLeadershipMethod()) { + case REGULAR: + break; + case FAMILY: + if (isDesertion) { + modifier--; + } else { + modifier++; + } + break; + case GREEN: + if (isDesertion) { + modifier--; + } + break; + case ELITE: + case IRON_FIST: + modifier++; + break; + } + + return modifier; + } + + /** + * Calculates the experience modifier based on the person's experience level. + * + * @param campaign the campaign that the person is participating in + * @param person the person whose experience level is being evaluated + * @return the experience modifier based on the person's experience level + * @throws IllegalStateException if the person's experience level is an unexpected value + */ + private static int getExperienceModifier(Campaign campaign, Person person) { + switch (person.getExperienceLevel(campaign, false)) { + case -1: + return -2; + case 0: + case 1: + return -1; + case 2: + return 0; + case 3: + return 1; + case 4: + return 2; + default: + throw new IllegalStateException("Unexpected value in getExperienceModifier: " + person.getExperienceLevel(campaign, false)); + } + } + + /** + * Calculates the force reliability modifier based on the desertion flag, and chosen reliability method. + * + * @param campaign the ongoing campaign + * @param isDesertion whether the target number is for a desertion check + * @param meanLoyalty the mean loyalty value + * @return the force reliability modifier as an integer value + */ + private static int getForceReliabilityModifier(Campaign campaign, boolean isDesertion, Integer meanLoyalty) { + ForceReliabilityMethod reliabilityMethod = campaign.getCampaignOptions().getForceReliabilityMethod(); + + switch (reliabilityMethod) { + case UNIT_RATING: + return getUnitRatingModifier(campaign.getUnitRatingMod(), isDesertion); + case LOYALTY: + if (campaign.getCampaignOptions().isUseLoyaltyModifiers()) { + return getLoyaltyModifier(isDesertion, meanLoyalty); + } else { + return getUnitRatingModifier(campaign.getUnitRatingMod(), isDesertion); + } + case OVERRIDE_C: + return 0; + case OVERRIDE_A: + return 1; + case OVERRIDE_B: + if (isDesertion) { + return 1; + } else { + return 0; + } + case OVERRIDE_D: + if (!isDesertion) { + return -1; + } else { + return 0; + } + case OVERRIDE_F: + return -1; + } + return 0; + } + + /** + * Computes the loyalty morale modifier. + * + * @param isDesertion whether the target number is for a desertion check + * @param meanLoyalty the mean loyalty value used to calculate the loyalty modifier + * @return the loyalty modifier calculated based on the given parameters + * @throws IllegalStateException if the meanLoyalty value is unexpected + */ + private static int getLoyaltyModifier(boolean isDesertion, Integer meanLoyalty) { + switch (meanLoyalty) { + case -3: + return -1; + case -2: + case -1: + if (!isDesertion) { + return -1; + } else { + return 0; + } + case 0: + case 1: + if (isDesertion) { + return 1; + } else { + return 0; + } + case 2: + case 3: + return 1; + default: + throw new IllegalStateException("Unexpected value in getLoyaltyModifier: " + meanLoyalty); + } + } + + /** + * Computes the unit rating morale modifier. + * + * @param unitRatingMod the unit rating modifier value + * @param isDesertion whether the target number is for a desertion check + * @return the unit rating morale modifier + * @throws IllegalStateException if the campaign has an unexpected value for unit rating + */ + private static int getUnitRatingModifier(Integer unitRatingMod, boolean isDesertion) { + switch(unitRatingMod) { + case 0: + return -1; + case 1: + if (!isDesertion) { + return -1; + } else { + return 0; + } + case 2: + case 3: + return 0; + case 4: + if (isDesertion) { + return 1; + } else { + return 0; + } + case 5: + return 1; + default: + throw new IllegalStateException("Unexpected value in getUnitRatingModifier: " + unitRatingMod); + } + } + + /** + * Returns the profession modifier based on the role and person. + * + * @param role the personnel role of the person + * @param person the person for whom the profession modifier is being calculated + * @return the profession modifier as an Integer + */ + private static Integer getProfessionModifier(PersonnelRole role, Person person) { + if (role.isVesselCrew()) { + if (person.getUnit() == null) { + return -1; + } else { + Entity entity = person.getUnit().getEntity(); + + if ((entity.isSmallCraft()) || (entity.isJumpShip())) { + return -1; + } else if (entity.isDropShip()) { + return 0; + } else if (entity.isWarShip()) { + return 2; + } + } + } else if ((role.isMechWarrior()) || (role.isProtoMechPilot()) || (role.isAerospaceGrouping()) || (role.isMedicalStaff())) { + return 1; + } else if ((role.isSoldier()) || (role.isTech())) { + return -1; + } else if (role.isAdministrator()) { + return -2; + } else if (role.isVehicleCrew()) { + if (person.getUnit() == null) { + return 0; + } else if (person.getUnit().getEntity().isSupportVehicle()) { + return -2; + } + } + + return 0; + } + + /** + * Adds a morale report to the given Campaign. + * + * @param campaign the Campaign for which to generate the report + */ + private static void getMoraleReport(Campaign campaign, ResourceBundle resources) { + StringBuilder moraleReport = new StringBuilder(); + + if (getTargetNumber(campaign, true) > 2) { + moraleReport.append(String.format(resources.getString("moraleReportLow.text"), getMoraleLevel(campaign))); + } else { + moraleReport.append(String.format(resources.getString("moraleReport.text"), getMoraleLevel(campaign))); + } + + if (getTargetNumber(campaign, false) > 2) { + moraleReport.append(' ').append(resources.getString("moraleReportMutiny.text")); + } + + campaign.addReport(moraleReport.toString()); + } + + /** + * Makes morale checks for personnel in a given campaign. + * + * @param campaign the campaign in which to make the morale checks + * @param isDesertion a boolean indicating if the checks are for desertion (true) or mutiny (false) + * @return true if someone has mutinied or deserted, false otherwise + */ + public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { + // we start with cases that cause not check to be needed + if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { + return false; + } else if ((isDesertion) && (!campaign.getLocation().isOnPlanet())) { + return false; + } else if ((!isDesertion) && (!campaign.getCampaignOptions().isUseMutinies())) { + return false; + } + + // Next, we gather essential information, such as the target number, + // personnel list, unit list (if unit theft is enabled, and mean loyalty score + int targetNumber = getTargetNumber(campaign, isDesertion); + + List filteredPersonnel = campaign.getActivePersonnel().stream() + .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> !person.isChild(campaign.getLocalDate())) + .collect(Collectors.toList()); + + int meanLoyalty = getMeanLoyalty(campaign, isDesertion); + + // Next we perform the actual checks, building a list of deserters and mutineers + HashMap deserters = new HashMap<>(); + HashMap mutineers = new HashMap<>(); + + for (Person person : filteredPersonnel) { + int modifier = getMoraleCheckModifiers(campaign, person, isDesertion, meanLoyalty); + + int firstRoll = Compute.d6(2) + modifier; + int secondRoll = Compute.d6(2) + modifier; + + if (isDesertion) { + if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { + deserters.put(person, secondRoll); + } + } else { + if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { + mutineers.put(person, secondRoll); + // TODO add mutiny call here + } + } + } + + // the rolls made, we check whether a mutiny or desertion has occurred and if so, process it + if (isDesertion) { + return !deserters.isEmpty(); + } else { + return !mutineers.isEmpty(); + } + } + + /** + * Calculates the mean loyalty of active personnel in a campaign. + * + * @param campaign the active personnel + * @param isDesertion true if desertions should be included in the calculation, false otherwise + * @return the mean loyalty of the active personnel in the campaign + */ + private static int getMeanLoyalty(Campaign campaign, boolean isDesertion) { + int loyalty = campaign.getActivePersonnel().stream() + .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> person.isChild(campaign.getLocalDate())) + .mapToInt(Person::getLoyalty) + .sum(); + + long personnel = campaign.getActivePersonnel().stream() + .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> person.isChild(campaign.getLocalDate())) + .count(); + + if (personnel == 0) { + return 0; + } else { + return (int) (loyalty / personnel); + } + } + + /** + * Reclaims the original unit for a person in a campaign. + * If the unit is no longer available, return an integer used to reduce effective morale. + * + * @param campaign The campaign the person is participating in + * @param person The person whose original unit is to be reclaimed + * @return {@code true} if the original unit is reclaimed successfully, {@code false} otherwise + */ + static boolean reclaimOriginalUnit(Campaign campaign, Person person) { + UUID originalUnitId = person.getOriginalUnitId(); + + if (!campaign.getUnit(originalUnitId).isDeployed()) { + try { + campaign.removeUnit(person.getOriginalUnitId()); + return true; + } catch (Exception e) { + return false; + } + } else { + return false; + } + } + + /** + * Gets the percentage modifier based on a roll of two six-sided dice. + * + * @return the percentage modifier based on the dice roll: + * - 3 for a roll of 2 + * - 2 for a roll of 3 + * - 1 for a roll of 4 or 5 + * - 0 for a roll of 6, 7, or 8 + * - -1 for a roll of 9 + * - -2 for a roll of 10 or 11 + * - -3 for a roll of 12 + * @throws IllegalStateException if the roll is unexpected + */ + static int getPercentageModifier() { + int roll = Compute.d6(2); + + switch(roll) { + case 2: + return 3; + case 3: + return 2; + case 4: + case 5: + return 1; + case 6: + case 7: + case 8: + return 0; + case 9: + return -1; + case 10: + case 11: + return -2; + case 12: + return -3; + default: + throw new IllegalStateException("Unexpected value in getPercentageModifier: " + roll); + } + } + + /** + * Processes the number of AWOL (Absent Without Leave) days for a person. + * If the person has no AWOL days remaining, it randomly determines whether to add another d6 AWOL days + * or change the person's status to ACTIVE. + * Otherwise, it subtracts one AWOL day from the person's total. + * + * @param campaign the campaign in which the person belongs + * @param person the person for whom AWOL days are being processed + */ + public static void processAwolDays(Campaign campaign, Person person) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + int awolDays = person.getAwolDays(); + + if (awolDays == 0) { + if (Compute.d6(1) <= 2) { + person.setAwolDays(awolDays + Compute.d6(1)); + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("desertionAwolExtended.text")); + } else { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.ACTIVE); + } + } else if (awolDays > 0) { + person.setAwolDays(awolDays - 1); + } else { + person.setAwolDays(awolDays - 1); + } + } + + /** + * The method processes the morale change in a campaign. + * + * @param campaign the campaign to process the morale change for + * @param steps the number of steps to change the morale by + */ + public static void processMoraleChange(Campaign campaign, int steps) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + + int change = campaign.getCampaignOptions().getMoraleStepSize() * steps; + int oldMorale = campaign.getMorale(); + int newMorale = MathUtility.clamp(campaign.getMorale() + change, 10, 70); + + campaign.setMorale(newMorale); + + if ((oldMorale / 10) != (newMorale / 10)) { + getMoraleReport(campaign, resources); + + if ((oldMorale >= 50) && (newMorale < 50)) { + campaign.addReport(resources.getString("moraleReportRecovered.text")); + } + } + } +} diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java new file mode 100644 index 0000000000..5c24960fac --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java @@ -0,0 +1,4 @@ +package mekhq.campaign.personnel.turnoverAndRetention.Morale; + +public class Mutiny { +} diff --git a/MekHQ/src/mekhq/gui/CommandCenterTab.java b/MekHQ/src/mekhq/gui/CommandCenterTab.java index 8e1ee0b67f..d57589b5fe 100644 --- a/MekHQ/src/mekhq/gui/CommandCenterTab.java +++ b/MekHQ/src/mekhq/gui/CommandCenterTab.java @@ -25,7 +25,7 @@ import mekhq.MHQOptionsChangedEvent; import mekhq.MekHQ; import mekhq.campaign.event.*; -import mekhq.campaign.personnel.turnoverAndRetention.Morale; +import mekhq.campaign.personnel.turnoverAndRetention.Morale.MoraleController; import mekhq.campaign.report.CargoReport; import mekhq.campaign.report.HangarReport; import mekhq.campaign.report.PersonnelReport; @@ -273,7 +273,7 @@ private void initInfoPanel() { gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new Insets(1, 5, 1, 5); panInfo.add(lblMoraleHead, gridBagConstraints); - lblMorale = new JLabel(getCampaign().getMorale() + " (" + Morale.getMoraleLevel(getCampaign()) + ')'); + lblMorale = new JLabel(getCampaign().getMorale() + " (" + MoraleController.getMoraleLevel(getCampaign()) + ')'); lblMoraleHead.setLabelFor(lblMorale); gridBagConstraints.gridx = 1; gridBagConstraints.weightx = 1.0; @@ -569,7 +569,7 @@ private void refreshBasicInfo() { if (getCampaign().getCampaignOptions().isUseMorale()) { try { - lblMorale.setText(getCampaign().getMorale() + " (" + Morale.getMoraleLevel(getCampaign()) + ')'); + lblMorale.setText(getCampaign().getMorale() + " (" + MoraleController.getMoraleLevel(getCampaign()) + ')'); } catch (Exception ignored) {} } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index a44778d3e3..5654a8894f 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -366,6 +366,7 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseMoraleModifierCommanderSkill; private JCheckBox chkUseMoraleModifierLoyalty; private JCheckBox chkUseMoraleModifierCabinFever; + private JCheckBox chkUseMoraleModifierMarriage; private JLabel lblMoraleModifierLeadershipMethod; private MMComboBox comboMoraleModifierLeadershipMethod; @@ -5168,6 +5169,11 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { chkUseMoraleModifierCabinFever.setName("chkUseMoraleModifierCabinFever"); chkUseMoraleModifierCabinFever.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); + chkUseMoraleModifierMarriage = new JCheckBox(resources.getString("chkUseMoraleModifierMarriage .text")); + chkUseMoraleModifierMarriage.setToolTipText(resources.getString("chkUseMoraleModifierMarriage.toolTipText")); + chkUseMoraleModifierMarriage.setName("chkUseMoraleModifierMarriage"); + chkUseMoraleModifierMarriage.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); + lblMoraleModifierLeadershipMethod = new JLabel(resources.getString("lblMoraleModifierLeadershipMethod.text")); lblMoraleModifierLeadershipMethod.setToolTipText(resources.getString("lblMoraleModifierLeadershipMethod.toolTipText")); lblMoraleModifierLeadershipMethod.setName("lblMoraleModifierLeadershipMethod"); @@ -5210,6 +5216,7 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseMoraleModifierCommanderSkill) .addComponent(chkUseMoraleModifierLoyalty) .addComponent(chkUseMoraleModifierCabinFever) + .addComponent(chkUseMoraleModifierMarriage) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblMoraleModifierLeadershipMethod) .addComponent(comboMoraleModifierLeadershipMethod, Alignment.LEADING)) @@ -5227,6 +5234,7 @@ public Component getListCellRendererComponent(final JList list, final Object .addComponent(chkUseMoraleModifierCommanderSkill) .addComponent(chkUseMoraleModifierLoyalty) .addComponent(chkUseMoraleModifierCabinFever) + .addComponent(chkUseMoraleModifierMarriage) .addGroup(layout.createSequentialGroup() .addComponent(lblMoraleModifierLeadershipMethod) .addComponent(comboMoraleModifierLeadershipMethod)) @@ -8436,6 +8444,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseMoraleModifierCommanderSkill.setSelected(options.isUseMoraleModifierCommanderLeadership()); chkUseMoraleModifierLoyalty.setSelected(options.isUseMoraleModifierLoyalty()); chkUseMoraleModifierCabinFever.setSelected(options.isUseMoraleModifierCabinFever()); + chkUseMoraleModifierMarriage.setSelected(options.isUseMoraleModifierMarriage()); comboMoraleModifierLeadershipMethod.setSelectedItem(options.getCustomMoraleModifier()); //endregion Turnover and Retention Tab @@ -9141,6 +9150,7 @@ public void updateOptions() { options.setUseMoraleModifierCommanderLeadership(chkUseMoraleModifierCommanderSkill.isSelected()); options.setUseMoraleModifierLoyalty(chkUseMoraleModifierLoyalty.isSelected()); options.setUseMoraleModifierCabinFever(chkUseMoraleModifierCabinFever.isSelected()); + options.setUseMoraleModifierMarriage(chkUseMoraleModifierMarriage.isSelected()); options.setMoraleModifierLeadershipMethod(comboMoraleModifierLeadershipMethod.getSelectedItem()); //endregion Turnover and Retention From d8e550ab966679229ddc0d70abfe84bcbce26f3a Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 28 May 2024 16:58:41 -0500 Subject: [PATCH 068/101] feat: Enhanced marriage morale modifier and introduced child desertion logic - Increased bonus applied to marriage morale modifier to +2 - Implemented child desertion logic: Children may follow a deserting parent --- .../CampaignOptionsDialog.properties | 100 +------------ .../mekhq/resources/Morale.properties | 2 + .../Morale/Desertion.java | 133 +++++++++++++++--- .../Morale/MoraleController.java | 18 ++- .../mekhq/gui/panes/CampaignOptionsPane.java | 2 +- 5 files changed, 130 insertions(+), 125 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index ec30f0bc70..42b64cd859 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -360,7 +360,7 @@ chkUseMoraleModifierLoyalty.toolTipText=Personal loyalty adjusts both desertion chkUseMoraleModifierCabinFever.text=Cabin Fever chkUseMoraleModifierCabinFever.toolTipText=Desertions are disabled while in transit. To compensate, this options adds a -1 modifier to Mutiny rolls. chkUseMoraleModifierMarriage.text=Marriage -chkUseMoraleModifierMarriage.toolTipText=Married personal apply a +1 bonus to desertion rolls, but will desert as a couple. Mutinies are unaffected. +chkUseMoraleModifierMarriage.toolTipText=Married personnel apply a +2 bonus to desertion rolls, but will desert as a couple. Mutinies are unaffected. lblMoraleModifierLeadershipMethod.text=Leadership Style lblMoraleModifierLeadershipMethod.toolTipText=What style of leadership best describes the unit? @@ -460,104 +460,6 @@ familyPanel.title=Family (Unofficial) lblFamilyDisplayLevel.text=The Level of Relation to be Displayed in the Personnel Panel lblFamilyDisplayLevel.toolTipText=This setting is the relation to the selected person that MekHQ will display up to, including the previous levels
(higher levels require more processing when loading a person in the personnel table) -# Dependent -dependentPanel.title=Dependents (Unofficial) -dependentPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. -randomDependentPanel.title=Random Dependents (Currently AtB Only) -randomDependentPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. -lblRandomDependentMethod.text=Random Dependents Method -lblRandomDependentMethod.toolTipText=This is the method used to determine if a dependent is randomly added or removed from the force. -chkUseRandomDependentAddition.text=Use Random Dependent Addition -chkUseRandomDependentAddition.toolTipText=This enables the random additions of dependents to the Force. -chkUseRandomDependentRemoval.text=Use Random Dependent Removal -chkUseRandomDependentRemoval.toolTipText=This enables the random removal of dependents from the Force. - -# Salary -salaryPanel.title=Salary -salaryMultiplierPanel.title=Multipliers -salaryMultiplierPanel.toolTipText=These multipliers are multiplicatively applied to the base salary as part of determining the final salary. -lblAntiMekSalary.text=Anti-Mek -lblAntiMekSalary.toolTipText=Anti-Mek trained soldiers and battle armour/elemental personnel have their salaries multiplied by this. -lblSpecialistInfantrySalary.text=Specialist Infantry -lblSpecialistInfantrySalary.toolTipText=Soldiers assigned to specialist infantry units have their salaries multiplied by this. -salaryExperienceMultiplierPanel.title=Experience Multipliers -salaryExperienceMultiplierPanel.toolTipText=The experience multiplier is multiplicatively applied to the base salary as part of determining the final salary. -lblSalaryExperienceMultiplier.toolTipText=%s personnel have their base pay multiplied by this. -baseSalaryPanel.title=Base Salaries -lblBaseSalary.toolTipText=This is the base salary paid to %ss. - -# Awards -awardsPanel.title=Awards -lblAwardBonusStyle.text=Award Bonuses -lblAwardBonusStyle.toolTipText=Toggle XP and/or Edge bonuses from awards. -chkEnableAutoAwards.text=Track Award Eligibility -chkEnableAutoAwards.toolTipText=Enable the automatic tracking of award eligibility. -chkIssuePosthumousAwards.text=Issue Posthumous Awards -chkIssuePosthumousAwards.toolTipText=Enabling this setting will qualify personnel for awards even if they are dead (excludes formation-based kill awards). -chkIssueBestAwardOnly.text=Only Issue Best Award -chkIssueBestAwardOnly.toolTipText=Activating this setting will disqualify personnel from receiving an award if they qualify for a higher-tier award of the same type. -chkIgnoreStandardSet.text=Ignore Standard Set -chkIgnoreStandardSet.toolTipText=Ignore the default set of awards -lblAwardTierSize.text=Award Tier Size -lblAwardTierSize.toolTipText=How many times must an award be issued before using the image of the next tier -autoAwardsPanel.title=Award Tracking -chkEnableContractAwards.text=Contract -chkEnableContractAwards.toolTipText=Awards issued for completing Contracts. -chkEnableFactionHunterAwards.text=Faction Hunter -chkEnableFactionHunterAwards.toolTipText=Awards issued for completing AtB Contracts against specific Factions. -chkEnableInjuryAwards.text=Injury -chkEnableInjuryAwards.toolTipText=Awards issued for suffering Hits during a Scenario. -chkEnableIndividualKillAwards.text=Kill (Individual) -chkEnableIndividualKillAwards.toolTipText=Awards issued for pilot kills in Scenarios or Missions. -chkEnableFormationKillAwards.text=Kill (Formation) -chkEnableFormationKillAwards.toolTipText=Awards issued for formation kills (Lance, Company, etc.) in Missions. -chkEnableRankAwards.text=Rank -chkEnableRankAwards.toolTipText=Awards issued for achieving specific Ranks. -chkEnableScenarioAwards.text=Scenario -chkEnableScenarioAwards.toolTipText=Awards issued for completing a certain number of Scenarios. -chkEnableSkillAwards.text=Skill -chkEnableSkillAwards.toolTipText=Awards issued for reaching certain levels in specific Skills. -chkEnableTheatreOfWarAwards.text=Theatre of War -chkEnableTheatreOfWarAwards.toolTipText=Awards issued for partaking in galactic conflicts. -chkEnableTimeAwards.text=Time -chkEnableTimeAwards.toolTipText=Awards for remaining in the unit for a certain duration. -chkEnableMiscAwards.text=Misc -chkEnableMiscAwards.toolTipText=Miscellaneous awards (see documentation). -##end Personnel Tab - -## Life Paths Tab -lifePathsPanel.title=Life Paths - -# Personnel Randomization -personnelRandomizationPanel.title=Personnel Randomization -chkUseDylansRandomXP.text=Use Dylan's Random XP (Unofficial) -chkUseDylansRandomXP.toolTipText=Use Dylan's optional random XP on creation of a new person (20% chance each of 0, 1, 2, 3, and randomized between 1 and 8 XP) - -# Employee Turnover -retirementPanel.title=Employee Turnover (Unofficial) -retirementPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. -chkUseRetirementDateTracking.text=Track Retirement Date -chkUseRetirementDateTracking.toolTipText=Track the date of a person's retirement. -randomRetirementPanel.title=Random Employee Turnover (Currently AtB Only) -randomRetirementPanel.toolTipText=Warning: This is a temporary setup, and is very much subject to changes including removing functionality if deemed necessary. -lblRandomRetirementMethod.text=Random Employee Turnover Method -lblRandomRetirementMethod.toolTipText=This is the method used to determine if a person will randomly retire at a given point. -chkUseYearEndRandomRetirement.text=Use Year End Random Employee Turnover Rolls -chkUseYearEndRandomRetirement.toolTipText=Make an Employee Turnover roll for each of the personnel in the unit at the end of every year. -chkUseContractCompletionRandomRetirement.text=Use Contract Completion Employee Turnover Rolls -chkUseContractCompletionRandomRetirement.toolTipText=Make an Employee Turnover roll for each of the personnel in the unit at the end of every contract. -chkUseCustomRetirementModifiers.text=Customize Employee Turnover Rolls -chkUseCustomRetirementModifiers.toolTipText=Allows you to manually provide additional modifiers to the Employee Turnover roll. -chkUseRandomFounderRetirement.text=Use Random Founder Retirement -chkUseRandomFounderRetirement.toolTipText=Allow Founders to randomly retire. -chkTrackUnitFatigue.text=Track Unit Fatigue -chkTrackUnitFatigue.toolTipText=Continuous deployments without a break increase the rate of retirements and defections. - -# Family -familyPanel.title=Family (Unofficial) -lblFamilyDisplayLevel.text=The Level of Relation to be Displayed in the Personnel Panel -lblFamilyDisplayLevel.toolTipText=This setting is the relation to the selected person that MekHQ will display up to, including the previous levels
(higher levels require more processing when loading a person in the personnel table) - # Marriage marriagePanel.title=Marriage chkUseManualMarriages.text=Use Manual Marriages diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index f08d865322..6ce2dc13bb 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -19,6 +19,8 @@ reclaimFailed.text=was unable to reclaim their original unit. # Desertion desertionAwolExtended.text=has extended their unauthorized leave. +desertionSpouse.text=has followed their spouse into desertion. +desertionChild.text=has followed a parent into desertion. # Theft desertionTheft.text=%s has gone missing. diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java index b6ea606936..c2a1ca1b7f 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java @@ -18,23 +18,24 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static mekhq.campaign.personnel.enums.PersonnelStatus.DESERTED; import static mekhq.campaign.personnel.turnoverAndRetention.Morale.MoraleController.getPercentageModifier; import static mekhq.campaign.personnel.turnoverAndRetention.Morale.MoraleController.reclaimOriginalUnit; public class Desertion { /** - * Processes desertion for a person. + * Processes a desertion event in a campaign. * - * @param campaign the current campaign - * @param deserters the potential deserters and their desertion rolls - * @param targetNumber the target number for desertion - * @param resources the resource bundle for localized messages + * @param campaign The current campaign. + * @param deserters A map of deserters and their corresponding desertion rolls. + * @param targetNumber The target number for determining the severity of the desertion. + * @param resources The ResourceBundle containing game resources. */ - private static void processDesertion(Campaign campaign, HashMap deserters, int targetNumber, - List possibleTheftTargets, ResourceBundle resources) { + static void processDesertion(Campaign campaign, HashMap deserters, int targetNumber, ResourceBundle resources) { // morale is used to determine whether a theft occurs int morale = campaign.getMorale() / 10; + // we start by building our theft target lists. // if a theft occurs, it will take an item from one of these lists // we assume units with a crew size > 5 cannot be stolen by one person, @@ -43,8 +44,7 @@ private static void processDesertion(Campaign campaign, HashMap .filter(unit -> (!unit.isAvailable()) && (unit.getFullCrewSize() < 6)) .collect(Collectors.toCollection(ArrayList::new)); - - // here we collect a list of parts that are available to be stolen + // here we collect a list of parts that can be stolen ArrayList theftTargetsParts = campaign.getWarehouse().getSpareParts().stream() .filter(part -> part.getDaysToArrival() == 0) .filter(part -> part.getDaysToWait() == 0) @@ -53,11 +53,16 @@ private static void processDesertion(Campaign campaign, HashMap .filter(part -> !part.isOmniPodded()) .collect(Collectors.toCollection(ArrayList::new)); - // we make a list of thieves, so they can be processed at the same time + // we're going to make a list of thieves, so they can be processed at the same time List thieves = new ArrayList<>(); - // next we check what type of desertion is occurring: going AWOL, deserting, or deserting with theft + // next, we check what type of desertion is occurring: going AWOL, deserting, or deserting with theft for (Person person : deserters.keySet()) { + // this allows us to avoid double-handling spouses + if (person.getStatus() == DESERTED) { + continue; + } + int roll = deserters.get(person); // the Iron Fist leadership style makes desertions worse @@ -65,33 +70,46 @@ private static void processDesertion(Campaign campaign, HashMap roll -= 1; } - // if margin of failure is 1-2 person goes AWOL instead of deserting + // if the margin of failure is 1-2 person goes AWOL instead of deserting if ((targetNumber - roll) >= 2) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); person.setAwolDays(Compute.d6(2)); - return; + continue; } - // otherwise, reclaim original unit + // At this point we know the person is going to desert, so we run with that assumption + + // reclaim the original unit if (person.getOriginalUnitId() != null) { if (reclaimOriginalUnit(campaign, person)) { - campaign.addReport(person.getHyperlinkedFullTitle() + ' ' - + String.format(resources.getString("reclaimSuccessful.text"), - campaign.getUnit(person.getOriginalUnitId()))); + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + String.format(resources.getString("reclaimSuccessful.text"), campaign.getUnit(person.getOriginalUnitId()))); } else { - campaign.addReport(person.getHyperlinkedFullTitle() + ' ' - + resources.getString("reclaimFailed.text")); + campaign.addReport(person.getHyperlinkedFullTitle() + ' ' + resources.getString("reclaimFailed.text")); - // if we're unable to reclaim original unit, roll is set to 0 to force a theft + // if we're unable to reclaim the original unit, roll is set to 0 to force a theft roll = 0; } } - // if margin of failure is 4+ a theft occurs + // if the margin of failure is a 4+, theft occurs if ((morale - roll) >= 4) { thieves.add(person); } + + person.changeStatus(campaign, campaign.getLocalDate(), DESERTED); + + if (person.getGenealogy().getChildren() != null) { + processChildDesertion(campaign, person, resources); + } + + if ((person.getGenealogy().hasSpouse()) && (campaign.getCampaignOptions().isUseMoraleModifierMarriage())) { + Person spouse = person.getGenealogy().getSpouse(); + + campaign.addReport(spouse.getHyperlinkedFullTitle() + ' ' + resources.getString("desertionSpouse.text")); + + spouse.changeStatus(campaign, campaign.getLocalDate(), DESERTED); + } } if (!thieves.isEmpty()) { @@ -99,6 +117,77 @@ private static void processDesertion(Campaign campaign, HashMap } } + /** + * Processes child desertion for a given person in the campaign. + * + * @param campaign the current campaign + * @param person the person whose children are being processed for desertion + * @param resources the ResourceBundle containing string resources + */ + private static void processChildDesertion(Campaign campaign, Person person, ResourceBundle resources) { + for (Person child : person.getGenealogy().getChildren()) { + if (!child.isChild(campaign.getLocalDate())) { + continue; + } else if (isAbsentChild(child)) { + continue; + } + + List parents = child.getGenealogy().getParents(); + + boolean singleAbsentParent = false; + boolean bothAbsentParents = false; + + // orphans do not desert + if (parents.size() == 1) { + child.changeStatus(campaign, campaign.getLocalDate(), DESERTED); + } else if (parents.size() == 2) { + for (Person parent : parents) { + if (isAbsentAdult(parent)) { + if (singleAbsentParent) { + bothAbsentParents = true; + } else { + singleAbsentParent = true; + } + } + } + } + + if (bothAbsentParents) { + child.changeStatus(campaign, campaign.getLocalDate(), DESERTED); + campaign.addReport(child.getHyperlinkedFullTitle() + ' ' + resources.getString("desertionChild.text")); + } else if (singleAbsentParent) { + if (Compute.d6(1) >= 4) { + child.changeStatus(campaign, campaign.getLocalDate(), DESERTED); + campaign.addReport(child.getHyperlinkedFullTitle() + ' ' + resources.getString("desertionChild.text")); + } + } + } + } + + /** + * Determines if an adult person is absent based on their status. + * + * @param adult The adult person to check for absence. + * @return True if the adult is absent, otherwise false. + */ + public static boolean isAbsentAdult(Person adult) { + PersonnelStatus status = adult.getStatus(); + + return status.isMIA() || status.isPoW() || status.isOnLeave() || status.isAWOL() || status.isStudent() || status.isMissing() || status.isDead() || status.isRetired() || status.isDeserted(); + } + + /** + * Checks if the child person is considered an absent child. + * + * @param child The child to check for absence. + * @return True if the child is considered absent, otherwise false. + */ + public static boolean isAbsentChild(Person child) { + PersonnelStatus status = child.getStatus(); + + return status.isMIA() || status.isPoW() || status.isMissing() || status.isDead() || status.isRetired() || status.isDeserted(); + } + /** * Processes the theft of petty objects, units, money, or spare parts * @@ -113,7 +202,7 @@ static void theftController(Campaign campaign, List thieves, List boolean theftComplete = false; // we want to keep rolling for a theft until a successful one occurs. - while (theftComplete) { + while (!theftComplete) { int roll = Compute.randomInt(15); switch (roll) { diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java index 7945dcd3dc..6a6e534a9e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java @@ -17,6 +17,8 @@ import java.util.UUID; import java.util.stream.Collectors; +import static mekhq.campaign.personnel.turnoverAndRetention.Morale.Desertion.processDesertion; + public class MoraleController { /** * This method returns the Morale level as a string based on the campaign's current morale. @@ -159,7 +161,7 @@ private static Integer getMoraleCheckModifiers(Campaign campaign, Person person, // Marriage if ((campaign.getCampaignOptions().isUseMoraleModifierMarriage()) && (isDesertion)) { - modifier--; + modifier += 2; } // Management Skill Modifier @@ -409,6 +411,9 @@ private static void getMoraleReport(Campaign campaign, ResourceBundle resources) * @return true if someone has mutinied or deserted, false otherwise */ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Morale", + MekHQ.getMHQOptions().getLocale()); + // we start with cases that cause not check to be needed if ((isDesertion) && (!campaign.getCampaignOptions().isUseDesertions())) { return false; @@ -429,7 +434,7 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { int meanLoyalty = getMeanLoyalty(campaign, isDesertion); - // Next we perform the actual checks, building a list of deserters and mutineers + // finally, we perform the actual checks, building a list of deserters and mutineers HashMap deserters = new HashMap<>(); HashMap mutineers = new HashMap<>(); @@ -452,8 +457,15 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { } // the rolls made, we check whether a mutiny or desertion has occurred and if so, process it + if (isDesertion) { - return !deserters.isEmpty(); + if (!deserters.isEmpty()) { + processDesertion(campaign, deserters, targetNumber, resources); + + return true; + } else { + return false; + } } else { return !mutineers.isEmpty(); } diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 5654a8894f..5a54372def 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -5169,7 +5169,7 @@ private JPanel createTurnoverAndRetentionMoraleModifiersPanel() { chkUseMoraleModifierCabinFever.setName("chkUseMoraleModifierCabinFever"); chkUseMoraleModifierCabinFever.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); - chkUseMoraleModifierMarriage = new JCheckBox(resources.getString("chkUseMoraleModifierMarriage .text")); + chkUseMoraleModifierMarriage = new JCheckBox(resources.getString("chkUseMoraleModifierMarriage.text")); chkUseMoraleModifierMarriage.setToolTipText(resources.getString("chkUseMoraleModifierMarriage.toolTipText")); chkUseMoraleModifierMarriage.setName("chkUseMoraleModifierMarriage"); chkUseMoraleModifierMarriage.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseManagementSkill())); From a273de4c2c4c222d0cee1af85732d35b8eeef587 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Tue, 28 May 2024 18:48:18 -0500 Subject: [PATCH 069/101] feat: updated theft processing in Desertion class - Revised the way theft of parts and units are processed in the Desertion class - Updated text descriptions in Morale.properties file - Added a control for theft percentage to avoid less than 1% scenarios - Implemented the use of a random number generator object for better randomness control - Modified logic for processing petty theft incidents - Renamed "tablets.text" to "interface.text" in items list --- .../mekhq/resources/Morale.properties | 4 +- .../Morale/Desertion.java | 104 +++++++++++------- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 6ce2dc13bb..c80c4f4622 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -26,7 +26,7 @@ desertionChild.text=has followed a parent into desertion. desertionTheft.text=%s has gone missing. desertionTheftBlackMarket.text=Someone is trying to sell one of our missing units on the Black Market: %s. desertionTheftMoney.text=The company accountants report an unusual payment of %s c-bills to an off-planet account. -desertionTheftParts.text=Our logistics software is reporting a number of missing items: +desertionTheftParts.text=Our logistics software is reporting missing items:
    desertionTheftTransactionReport.text=Suspicious transfer of funds to %s # Mutiny @@ -66,7 +66,7 @@ battleUnitDestroyed.text=was completely destroyed, all personnel were killed out stapler.text=The office stapler mascot.text=A company mascot phones.text=The office phones -tablets.text=A number of tablets +interface.text=A digital interface hardDrives.text=An important hard drive flashDrive.text=An incriminating flash drive companyCreditCard.text=The company credit card diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java index c2a1ca1b7f..59f7ce5eba 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Desertion.java @@ -13,6 +13,7 @@ import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.enums.PersonnelStatus; import mekhq.campaign.unit.Unit; +import org.apache.logging.log4j.LogManager; import java.util.*; import java.util.stream.Collectors; @@ -71,7 +72,7 @@ static void processDesertion(Campaign campaign, HashMap deserte } // if the margin of failure is 1-2 person goes AWOL instead of deserting - if ((targetNumber - roll) >= 2) { + if ((targetNumber - roll) <= 2) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.AWOL); person.setAwolDays(Compute.d6(2)); @@ -198,6 +199,8 @@ public static boolean isAbsentChild(Person child) { * @param resources The ResourceBundle containing game resources */ static void theftController(Campaign campaign, List thieves, List theftTargetsParts, ArrayList theftTargetsUnits, ResourceBundle resources) { + Random random = new Random(); + for (Person ignored : thieves) { boolean theftComplete = false; @@ -209,7 +212,7 @@ static void theftController(Campaign campaign, List thieves, List case 0: // unit theft if ((campaign.getCampaignOptions().isUseTheftUnit()) && (!theftTargetsUnits.isEmpty())) { - theftTargetsUnits.remove(processUnitTheft(campaign, theftTargetsUnits, resources)); + theftTargetsUnits.remove(processUnitTheft(campaign, theftTargetsUnits, random, resources)); theftComplete = true; } break; @@ -226,7 +229,7 @@ static void theftController(Campaign campaign, List thieves, List case 6: // part theft if ((campaign.getCampaignOptions().isUseTheftParts()) && (!theftTargetsParts.isEmpty())) { - theftTargetsParts = processPartTheft(campaign, theftTargetsParts, resources); + theftTargetsParts = processPartTheft(campaign, theftTargetsParts, random, resources); theftComplete = true; } break; @@ -239,7 +242,7 @@ static void theftController(Campaign campaign, List thieves, List case 13: case 14: // petty theft - processPettyTheft(campaign, resources); + processPettyTheft(campaign, random, resources); theftComplete = true; break; default: @@ -250,15 +253,21 @@ static void theftController(Campaign campaign, List thieves, List } /** - * Processes unit thefts during a campaign. + * Processes the theft of a unit in a campaign. + * This method selects a random unit from a list of possible theft targets + * and performs the theft operation. + * If the campaign options allow, and the faction is not a clan, the stolen unit may be + * resold on the black market with a specified resale value. + * Otherwise, the theft is reported in the campaign report. * - * @param campaign The campaign in which the unit thefts occur. - * @param possibleTheftTargets The list of units that can be stolen. - * @param resources The resource bundle for internationalization. - * @return A list of stolen units. + * @param campaign The current campaign. + * @param possibleTheftTargets The list of possible units that can be stolen. + * @param random The random number generator. + * @param resources The ResourceBundle containing game resources. + * @return The stolen unit. */ - static Unit processUnitTheft(Campaign campaign, List possibleTheftTargets, ResourceBundle resources) { - Unit stolenUnit = possibleTheftTargets.get(new Random().nextInt(possibleTheftTargets.size())); + static Unit processUnitTheft(Campaign campaign, List possibleTheftTargets, Random random, ResourceBundle resources) { + Unit stolenUnit = possibleTheftTargets.get(random.nextInt(possibleTheftTargets.size())); String theftString = stolenUnit.getName(); if (!Objects.equals(stolenUnit.getFluffName(), "")) { @@ -294,6 +303,10 @@ static Unit processUnitTheft(Campaign campaign, List possibleTheftTargets, static boolean processMoneyTheft(Campaign campaign, ResourceBundle resources) { int theftPercentage = campaign.getCampaignOptions().getTheftValue() + getPercentageModifier(); + if (theftPercentage < 1) { + theftPercentage = 1; + } + Money theft = campaign.getFunds() .multipliedBy(theftPercentage) .dividedBy(100) @@ -315,12 +328,13 @@ static boolean processMoneyTheft(Campaign campaign, ResourceBundle resources) { /** * Processes the theft of parts. * - * @param campaign the current campaign - * @param theftTargetsParts the list of parts targeted for theft - * @param resources the ResourceBundle containing game resources - * @return a list of parts that have been stolen + * @param campaign The current campaign. + * @param theftTargetsParts The list of parts targeted for theft. + * @param random The random number generator. + * @param resources The ResourceBundle containing game resources. + * @return The updated list of parts after the theft. */ - static List processPartTheft(Campaign campaign, List theftTargetsParts, ResourceBundle resources) { + static List processPartTheft(Campaign campaign, List theftTargetsParts, Random random, ResourceBundle resources) { // how many parts should be stolen? int theftCount = 1; @@ -331,40 +345,46 @@ static List processPartTheft(Campaign campaign, List theftTargetsPar .sum(); } - HashMap stolenItems = new HashMap<>(); + LogManager.getLogger().info(theftCount); + + HashMap stolenItems = new HashMap<>(); for (int theft = 0; theft < theftCount; theft++) { int itemCount; - // if everything has been stolen the would-be thief will leave empty-handed + // if everything has been stolen, the would-be thief will leave empty-handed if (!theftTargetsParts.isEmpty()) { // pick the part to be stolen - Part stolenPart = theftTargetsParts.get(new Random().nextInt(theftTargetsParts.size())); + Part stolenPart = theftTargetsParts.get(random.nextInt(theftTargetsParts.size())); // how many parts should be stolen? - if ((stolenPart instanceof AmmoStorage) || (stolenPart instanceof Armor)) { + if (stolenPart instanceof AmmoStorage) { itemCount = Compute.d6(2); - if (itemCount > stolenPart.getQuantity()) { + if (itemCount > (stolenPart.getQuantity())) { itemCount = stolenPart.getQuantity(); } + } else if (stolenPart instanceof Armor) { + itemCount = Compute.d6(2); + + if (itemCount > ((Armor) stolenPart).getAmount()) { + itemCount = ((Armor) stolenPart).getAmount(); + } } else { itemCount = 1; } - // steal the part - if (stolenPart instanceof AmmoStorage) { - campaign.getWarehouse().removeAmmo((AmmoStorage) stolenPart, itemCount); - } else if (stolenPart instanceof Armor) { - campaign.getWarehouse().removeArmor((Armor) stolenPart, itemCount); - } else { - campaign.getWarehouse().getPart(stolenPart.getId()); - } + // steal the part/s + campaign.getWarehouse().removePart(stolenPart, itemCount); // add the item and count to our list of stolen items - stolenItems.put(stolenPart, itemCount); + if (stolenItems.containsKey(stolenPart.getName())) { + stolenItems.put(stolenPart.getName(), stolenItems.get(stolenPart.getName()) + itemCount); + } else { + stolenItems.put(stolenPart.getName(), itemCount); + } - // after each theft we need to rebuild our list of spare parts + // after each theft, we need to rebuild our list of spare parts theftTargetsParts = campaign.getWarehouse().getSpareParts().stream() .filter(part -> part.getDaysToArrival() == 0) .filter(part -> part.getDaysToWait() == 0) @@ -375,26 +395,28 @@ static List processPartTheft(Campaign campaign, List theftTargetsPar } } - campaign.addReport(stolenItems.keySet().stream() - .map(part -> ' ' + part.getName() + 'x' + stolenItems.get(part)) - .collect(Collectors.joining("", resources.getString("desertionTheftParts.text"), ""))); + String theftReport = stolenItems.keySet().stream() + .map(stolenItem -> "
  • " + stolenItem + " x" + stolenItems.get(stolenItem) + "
  • ") + .collect(Collectors.joining("", resources.getString("desertionTheftParts.text"), "
")); + + campaign.addReport(theftReport); return theftTargetsParts; } /** - * This method is used to process a petty theft incident in a company. - * It randomly selects an item from a list of stolen items and adds the item to the campaign report. + * Processes the theft of petty objects during a campaign. * - * @param campaign The campaign object to add the report to. - * @param resources The ResourceBundle object to retrieve localized strings. + * @param campaign The current campaign. + * @param random The random number generator. + * @param resources The ResourceBundle containing game resources. */ - static void processPettyTheft(Campaign campaign, ResourceBundle resources) { + static void processPettyTheft(Campaign campaign, Random random, ResourceBundle resources) { List items = List.of( "stapler.text", "mascot.text", "phones.text", - "tablets.text", + "interface.text", "hardDrives.text", "flashDrive.text", "companyCreditCard.text", @@ -499,6 +521,6 @@ static void processPettyTheft(Campaign campaign, ResourceBundle resources) { "dropShip.text"); campaign.addReport(String.format(resources.getString("desertionTheft.text"), - resources.getString(items.get(new Random().nextInt(items.size()))))); + resources.getString(items.get(random.nextInt(items.size()))))); } } From acce8b38c7379ffb5c2feaa2b4c1d47e16b0042b Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 29 May 2024 00:59:07 -0500 Subject: [PATCH 070/101] feat: Added Advanced Mutinies functionality with new classes and updated existing ones: - Deleted 'MutinySupportDialog.java' file - Added new battle conditions for mutinies in 'Morale.properties' - Created new classes 'TransitMutinyToe.java' and 'TransitMutinyBattleDialog.java' for managing mutinies in transit - Updated 'CampaignOptions.java' and 'CampaignOptionsDialog.properties' to include mutiny methods - Updated 'Personnel.properties' to include new MutinyMethod enums - Added 'isFreeOrBondsman()' method to 'PrisonerStatus' enum --- .../CampaignOptionsDialog.properties | 2 + .../mekhq/resources/Morale.properties | 87 ++++-- .../mekhq/resources/Personnel.properties | 6 + MekHQ/src/mekhq/campaign/CampaignOptions.java | 13 + .../personnel/enums/MutinyMethod.java | 55 ++++ .../personnel/enums/PrisonerStatus.java | 3 + .../Morale/MoraleController.java | 24 +- .../turnoverAndRetention/Morale/Mutiny.java | 243 ++++++++++++++++ .../mekhq/gui/dialog/MutinySupportDialog.java | 263 ------------------ .../TransitMutinyBattleDialog.java | 130 +++++++++ .../moraleDialogs/TransitMutinyDialog.java | 62 +++++ .../moraleDialogs/TransitMutinyToe.java | 92 ++++++ .../mekhq/gui/panes/CampaignOptionsPane.java | 36 +++ 13 files changed, 722 insertions(+), 294 deletions(-) create mode 100644 MekHQ/src/mekhq/campaign/personnel/enums/MutinyMethod.java delete mode 100644 MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java create mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyBattleDialog.java create mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java create mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index 42b64cd859..4586345430 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -323,6 +323,8 @@ lblMoraleStepSize.text=Step Size lblMoraleStepSize.toolTipText=How large should each morale adjustment be? CamOps uses step sizes of 10. lblForceReliabilityMethod.text=Force Reliability Method lblForceReliabilityMethod.toolTipText=How should Force Reliability be calculated (referred to as 'Loyalty' on CamOps page 217)? +lblMutinyMethod.text=Mutiny Method +lblMutinyMethod.toolTipText=Which ruleset should mutinies use? chkUseDesertions.text=Enable Desertions chkUseDesertions.toolTipText=Low morale may cause personnel to randomly desert the company. chkUseMutinies.text=Enable Mutinies diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index c80c4f4622..558cfe63cf 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -30,37 +30,70 @@ desertionTheftParts.text=Our logistics software is reporting missing items desertionTheftTransactionReport.text=Suspicious transfer of funds to %s # Mutiny -mutinyThwartedSingular.text=An attempted coup has been thwarted. The would-be mutineer has fled the company. -mutinyThwartedPlural.text=An attempted coup has been thwarted. %s would-be mutineers have fled the company. +mutinyThwartedSingular.text=A mutiny was building, but failed to gain enough support. The would-be mutineer has deserted the company. +mutinyThwartedPlural.text=A mutiny was building, but failed to gain enough support. %s would-be mutineers have deserted the company. # Mutiny Dialog -dialogSupportLoyalists.text=Support Loyalists -dialogSupportRebels.text=Support Rebels -dialogTitleViolentUprising.text=Violent Uprising -dialogTitleRegimeChange.text=Regime Change +abstractMutiny.title=Mutiny! +abstractMutinyButtonConfirm.text=Confirm +abstractBattleFactionLoyalists.text=Loyalists +abstractBattleFactionMutineers.text=Mutineers +abstractBattleFactionSupportLoyalists.text=Support the Loyalists +abstractBattleFactionSupportMutineers.text=Support the Mutineers +abstractBattleFactionSupportVictor.text=Support the Victor -dialogDescriptionIntroduction.text=
%s's poor leadership has given the personnel of %s no other choice.

-dialogDescriptionViolentUprising.text=Led by %s, they have drawn arms against the commander and are demanding control of the unit.

-dialogDescriptionRegimeChange.text=Led by %s, the rebels are demanding the commander be removed from command and will fight to ensure this happens.

-dialogDescriptionLoyalist.text=Loyalist -dialogDescriptionRebels.text=Rebel -dialogDescriptionPluralizer.text=s -dialogDescriptionForces.text=The %s faction is supported by %s personnel -dialogDescriptionForcesMeks.text=, %s Mek%s -dialogDescriptionForcesFighters.text=, %s Fighter%s -dialogDescriptionForcesProtoMechs.text=, %s ProtoMek%s -dialogDescriptionForcesBattleArmor.text=, %s unit%s of Battle Armor -dialogDescriptionForcesVehicles.text=, %s Vehicle%s -dialogDescriptionForcesInfantry.text=, %s unit%s of Infantry -dialogDescriptionForcesDropShips.text=, %s DropShip%s -dialogDescriptionForcesOther.text=, %s other unit%s -dialogDescriptionForcesBv.text=(estimated %s BV).

-dialogDescriptionBystanders.text=%s personnel have abstained from the conflict.

-dialogDescriptionDecision.text=You must choose whether to support the Loyalists or the Rebels.

\ - \ WARNING: once you have made your decision, there is no turning back.
+# Onset Dialog +abstractMutinyOnsetDescription.text=A mutiny has broken out among the DropShips transporting the unit!\ +
\ +
Prompted by harsh conditions, tyrannical leadership, or dire shortages, the mutiny began with disgruntled support personnel and embittered warriors conspiring to overthrow their commanding officers.\ +
\ +
As chaos erupts, loyalists and mutineers begin to clash in fierce skirmishes through narrow corridors and vital control rooms, with the mutineers aiming to seize the bridge and engineering sections.\ +
\ +
Both sides are deploying small arms, makeshift weapons, and cunning tactics with the confined spaces amplify the danger. A single misstep could lead to catastrophic damage or destruction of the DropShip.\ +
\ +
The mutiny's resolution will leave indelible scars on the unit's cohesion, morale, and operational capability.\ +
\ +
Betrayal casts a long shadow.\ +
\ +
+ +# TOE dialog +abstractBattleToe.text=Led by %s, the %s are supported by %s personnel.\ +
  • Offense: %s\ +
  • Defense: %s
+ +# Combat Dialog +abstractBattleDescription01.text=The %s have barricaded themselves in the bridge, while %s attempt to breach their defenses. +abstractBattleDescription02.text=The %s are attempting to sabotage critical systems to hinder the %s' progress. This has led to a dangerous game of cat and mouse in the engineering section. +abstractBattleDescription03.text=The %s are attempting to gain control of the DropShip's life support systems, and threaten to vent the atmosphere unless the %s surrender. +abstractBattleDescription04.text=The %s, facing overwhelming odds, consider the possibility of surrendering to the %s, grappling with the moral implications of such a decision amidst the chaos of the mutiny. +abstractBattleDescription05.text=The %s, driven to desperation by the relentless assault of the %s, initiate a daring escape plan. Risking everything, they try to flee the besieged DropShip, hoping to find refuge and reinforcements aboard the nearby JumpShip. +abstractBattleDescription06.text=The %s' medics are struggling to treat wounded crew members amidst the chaos, facing shortages of medical supplies as these keep getting seized by the %s. +abstractBattleDescription07.text=The %s hijack the DropShip's communications array from the %s, broadcasting propaganda messages to nearby vessels and spreading dissent among potential allies. +abstractBattleDescription08.text=The %s attempt to send a distress signal to the JumpShip, hoping for reinforcements to quell the %s. +abstractBattleDescription09.text=The %s started a fire in a room held by the %s. It is now threatening to engulf the entire ship in flames and compromise its structural integrity. +abstractBattleDescription10.text=The %s cut off access to the DropShip's armory, forcing %s to scavenge for makeshift weapons to defend themselves. +abstractBattleDescription11.text=The ship's command staff, torn between loyalty to %s and sympathy for the %s, struggle to determine their allegiances. +abstractBattleDescription12.text=The %s leaders attempt to negotiate a ceasefire with the %s, offering concessions in exchange for the safe release of hostages. +abstractBattleDescription13.text=A group of %s with technical expertise are attempting to hack into the DropShip's control systems, seeking to gain full control of the vessel's functions. The %s have launched an attempt to thwart them. +abstractBattleDescription14.text=The %s launched a daring raid to reclaim control of the DropShip's bridge from the %s, risking their lives to eliminate key enemy leaders. +abstractBattleDescription15.text=The %s block access to the DropShip's escape pods, preventing %s from fleeing the ship. +abstractBattleDescription16.text=A power struggle erupts among the crew's personnel, as some align themselves with the %s while others turn to the %s. +abstractBattleDescription17.text=The %s sabotage critical systems to hinder the %s' progress, leading to a race against time to repair the damage before it's too late. +abstractBattleDescription18.text=A %s officer manages to establish contact with nearby authorities, pleading for assistance in defeating the %s and restoring order. +abstractBattleDescription19.text=The %s attempt to breach the DropShip's command center using explosives, risking catastrophic damage to the ship's structure in a desperate bid to defeat the %s. +abstractBattleDescription20.text=With resources dwindling and morale at an all-time low, both %s and %s must contend with the harsh realities of survival in a mutiny, facing starvation, exhaustion, and the constant threat of further violence as they struggle to gain the upper hand in the ongoing conflict. +abstractBattleDescription21.text=As tensions escalate, %s and %s alike struggle with their own moral dilemmas, torn between loyalty to their comrades and the desire to survive at any cost. +abstractBattleDescription22.text=The %s organize a counterattack, using guerrilla tactics to strike at strongholds held by the %s and disrupt their plans. +abstractBattleDescription23.text=A group of noncombatants, caught in the middle of the conflict, band together to form a neutral faction, seeking to broker peace between the %s and %s. +abstractBattleDescription24.text=The %s attempt to disable the DropShip's navigation systems, threatening to strand the vessel. The %s are fighting to prevent this, but losing ground. +abstractBattleDescription25.text=The %s initiate a lockdown of critical ship systems, sealing off access points and restricting movement to contain the %s and prevent further bloodshed. +abstractBattleDescription26.text=The %s attempt to rally support by staging a daring rescue mission to liberate hostages held by the %s. +abstractBattleDescription27.text=The %s attempt to seize control of the DropShip's medical bay, using wounded crew members and hostages as leverage to ward off the %s. +abstractBattleDescription28.text=The %s deploy booby traps and improvised explosive devices throughout the ship, turning every corridor and compartment into a potential death trap for the %s. +abstractBattleDescription29.text=The %s struggle to maintain discipline as fear and paranoia spread, leading to accusations and suspicions that even their most loyal members have turned to the %s. +abstractBattleDescription30.text=The %s, facing dwindling supplies and escalating tensions, resort to extreme measures such as rationing food and water, further intensifying the conflict against the %s. -# Mutiny Battle -battleUnitDestroyed.text=was completely destroyed, all personnel were killed outright. # Petty Theft Table stapler.text=The office stapler diff --git a/MekHQ/resources/mekhq/resources/Personnel.properties b/MekHQ/resources/mekhq/resources/Personnel.properties index cc3d3c066a..79c6f1a2e6 100644 --- a/MekHQ/resources/mekhq/resources/Personnel.properties +++ b/MekHQ/resources/mekhq/resources/Personnel.properties @@ -490,6 +490,12 @@ ForceReliabilityMethod.OVERRIDE_D.toolTipText=Desertion checks have no modifier. ForceReliabilityMethod.OVERRIDE_F.text=Fixed: F (Questionable) ForceReliabilityMethod.OVERRIDE_F.toolTipText=Desertion and Mutiny checks have a -1 modifier. +# MutinyMethod Enum +MutinyMethod.CAMPAIGN_OPERATIONS.text=Campaign Operations +MutinyMethod.CAMPAIGN_OPERATIONS.toolTipText=Where possible, this option follows the rules outlined in Campaign Operations. +MutinyMethod.ADVANCED_MUTINIES.text=Advanced Mutinies +MutinyMethod.ADVANCED_MUTINIES.toolTipText=This option expands on Campaign Operations, attempting to provide a more nuanced and interactive experience. + # LeadershipMethod Enum LeadershipMethod.REGULAR.text=Regular LeadershipMethod.REGULAR.toolTipText=The average mercenary company. Applies no additional modifiers. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 0d37a63c2b..e87bc657dd 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -322,6 +322,7 @@ public static String getTransitUnitName(final int unit) { private boolean useDesertions; private boolean useMutinies; private ForceReliabilityMethod forceReliabilityMethod; + private MutinyMethod mutinyMethod; private boolean useSabotage; private boolean useTheftUnit; private int theftResellValue; @@ -1021,6 +1022,7 @@ public CampaignOptions() { setUseMorale(true); setMoraleStepSize(1); setForceReliabilityMethod(ForceReliabilityMethod.LOYALTY); + setMutinyMethod(MutinyMethod.ADVANCED_MUTINIES); setUseDesertions(true); setUseSabotage(true); setUseMutinies(true); @@ -1607,6 +1609,14 @@ public void setForceReliabilityMethod(final ForceReliabilityMethod forceReliabil this.forceReliabilityMethod = forceReliabilityMethod; } + public MutinyMethod getMutinyMethod() { + return mutinyMethod; + } + + public void setMutinyMethod(final MutinyMethod mutinyMethod) { + this.mutinyMethod = mutinyMethod; + } + public boolean isUseDesertions() { return useDesertions; } @@ -4828,6 +4838,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMorale", isUseMorale()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "moraleStepSize", getMoraleStepSize()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "forceReliabilityMethod", getForceReliabilityMethod().name()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "mutinyMethod", getMutinyMethod().name()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useDesertions", isUseDesertions()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useSabotage", isUseSabotage()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "useMutinies", isUseMutinies()); @@ -5857,6 +5868,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setMoraleStepSize(Integer.parseInt(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("forceReliabilityMethod")) { retVal.setForceReliabilityMethod(ForceReliabilityMethod.valueOf(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("mutinyMethod")) { + retVal.setMutinyMethod(MutinyMethod.valueOf(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useDesertions")) { retVal.setUseDesertions(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("useSabotage")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/MutinyMethod.java b/MekHQ/src/mekhq/campaign/personnel/enums/MutinyMethod.java new file mode 100644 index 0000000000..b1283411d4 --- /dev/null +++ b/MekHQ/src/mekhq/campaign/personnel/enums/MutinyMethod.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021-2022 - 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.personnel.enums; + +import mekhq.MekHQ; + +import java.util.ResourceBundle; + +public enum MutinyMethod { + CAMPAIGN_OPERATIONS("MutinyMethod.CAMPAIGN_OPERATIONS.text", "MutinyMethod.CAMPAIGN_OPERATIONS.toolTipText"), + ADVANCED_MUTINIES("MutinyMethod.ADVANCED_MUTINIES.text", "MutinyMethod.ADVANCED_MUTINIES.toolTipText"); + + private final String name; + private final String toolTipText; + + MutinyMethod(final String name, final String toolTipText) { + final ResourceBundle resources = ResourceBundle.getBundle("mekhq.resources.Personnel", + MekHQ.getMHQOptions().getLocale()); + this.name = resources.getString(name); + this.toolTipText = resources.getString(toolTipText); + } + + public String getToolTipText() { + return toolTipText; + } + + public boolean isCampaignOperations() { + return this == CAMPAIGN_OPERATIONS; + } + + public boolean isAdvancedMutinies() { + return this == ADVANCED_MUTINIES; + } + + @Override + public String toString() { + return name; + } +} diff --git a/MekHQ/src/mekhq/campaign/personnel/enums/PrisonerStatus.java b/MekHQ/src/mekhq/campaign/personnel/enums/PrisonerStatus.java index 904646d2f2..dfb5455548 100644 --- a/MekHQ/src/mekhq/campaign/personnel/enums/PrisonerStatus.java +++ b/MekHQ/src/mekhq/campaign/personnel/enums/PrisonerStatus.java @@ -67,6 +67,9 @@ public String getTitleExtension() { public boolean isFree() { return this == FREE; } + public boolean isFreeOrBondsman() { + return isFree() || isBondsman(); + } public boolean isPrisoner() { return this == PRISONER; diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java index 6a6e534a9e..243f728f2b 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/MoraleController.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import static mekhq.campaign.personnel.turnoverAndRetention.Morale.Desertion.processDesertion; +import static mekhq.campaign.personnel.turnoverAndRetention.Morale.Mutiny.processMutiny; public class MoraleController { /** @@ -428,7 +429,7 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { int targetNumber = getTargetNumber(campaign, isDesertion); List filteredPersonnel = campaign.getActivePersonnel().stream() - .filter(person -> (person.getPrisonerStatus().isFree()) || (!isDesertion)) + .filter(person -> (person.getPrisonerStatus().isFreeOrBondsman()) || (!isDesertion)) .filter(person -> !person.isChild(campaign.getLocalDate())) .collect(Collectors.toList()); @@ -445,13 +446,18 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { int secondRoll = Compute.d6(2) + modifier; if (isDesertion) { + // Bondsmen can mutiny, but they cannot desert + if (person.getPrisonerStatus().isBondsman()) { + continue; + } + if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { deserters.put(person, secondRoll); } } else { - if ((firstRoll < targetNumber) && (secondRoll < targetNumber)) { + if (firstRoll < targetNumber) { + // we record secondRoll in case the mutiny turns into desertion mutineers.put(person, secondRoll); - // TODO add mutiny call here } } } @@ -467,7 +473,17 @@ public static boolean makeMoraleChecks(Campaign campaign, boolean isDesertion) { return false; } } else { - return !mutineers.isEmpty(); + if (!mutineers.isEmpty()) { + List loyalists = filteredPersonnel.stream() + .filter(person -> !mutineers.containsKey(person)) + .collect(Collectors.toList()); + + processMutiny(campaign, loyalists, mutineers, targetNumber, resources); + + return true; + } else { + return false; + } } } diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java index 5c24960fac..441f276698 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java @@ -1,4 +1,247 @@ package mekhq.campaign.personnel.turnoverAndRetention.Morale; +import megamek.codeUtilities.MathUtility; +import megamek.common.Compute; +import mekhq.campaign.Campaign; +import mekhq.campaign.mod.am.InjuryUtil; +import mekhq.campaign.personnel.Person; +import mekhq.campaign.personnel.SkillType; +import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.gui.dialog.moraleDialogs.TransitMutinyDialog; +import mekhq.gui.dialog.moraleDialogs.TransitMutinyToe; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static mekhq.campaign.personnel.turnoverAndRetention.Morale.Desertion.processDesertion; +import static mekhq.gui.dialog.moraleDialogs.TransitMutinyBattleDialog.transitMutinyBattleConditionDialog; + public class Mutiny { + static void processMutiny(Campaign campaign, List loyalists, HashMap mutineers, int targetNumber, ResourceBundle resources) { + Random random = new Random(); + + if (campaign.getCampaignOptions().getMutinyMethod().isCampaignOperations()) { + processCamOpsMutiny(campaign, random, loyalists, mutineers, targetNumber, resources); + } else { + // TODO this is where Advanced Mutinies call goes + return; + } + } + + static void processCamOpsMutiny(Campaign campaign, Random random, List loyalists, HashMap mutineers, int targetNumber, ResourceBundle resources) { + // if the mutineers are out-numbered 4:1 (or worse), they're treated as if having deserted + if ((loyalists.size() / 4) >= mutineers.keySet().size()) { + if (mutineers.keySet().size() == 1) { + campaign.addReport(resources.getString("mutinyThwartedSingular.text")); + } else { + campaign.addReport(String.format(resources.getString("mutinyThwartedPlural.text"), mutineers.keySet().size())); + } + + processDesertion(campaign, mutineers, targetNumber, resources); + + return; + } + + // next we pick the mutiny & loyalist leaders. + // CamOps doesn't have any special handling for unit leader, so we don't either + Person mutineerLeader = campaign.getHighestRankedPerson(new ArrayList<>(mutineers.keySet()), true); + Person loyalistLeader = campaign.getHighestRankedPerson(loyalists, true); + + // this dialog alerts the player to the mutiny + TransitMutinyDialog.transitMutinyOnsetDialog(resources); + + // this dialog tells the player about the array of forces and gives them the option to support the mutineers or loyalists + + if (campaign.getLocation().isOnPlanet()) { + processAbstractBattle(campaign, random, resources, loyalistLeader, loyalists, mutineerLeader, mutineers); + } + + + } + + private static void processAbstractBattle(Campaign campaign, Random random, ResourceBundle resources, + Person loyalistLeader, List loyalists, + Person mutineerLeader, HashMap mutineers) { + HashMap loyalistBattlePower = getAbstractBattlePower(loyalistLeader, loyalists); + HashMap mutinyBattlePower = getAbstractBattlePower(mutineerLeader, new ArrayList<>(mutineers.keySet())); + + + int support = TransitMutinyToe.transitMutinyToeDialog(resources, + loyalistLeader, loyalists.size(), loyalistBattlePower.get("attack"), loyalistBattlePower.get("defense"), + mutineerLeader, mutineers.keySet().size(), mutinyBattlePower.get("attack"), mutinyBattlePower.get("defense")); + + int victor = processAbstractBattleRound(campaign, random, loyalists, loyalistBattlePower, new ArrayList<>(mutineers.keySet()), mutinyBattlePower); + + transitMutinyBattleConditionDialog(resources, random); + } + + private static HashMap getAbstractBattlePower(Person loyalistLeader, List loyalists) { + HashMap battlePower = new HashMap<>(); + + int attackPower = 0; + int defensePower = 0; + + for (Person person : loyalists) { + if (person.hasSkill(SkillType.S_SMALL_ARMS)) { + attackPower += person.getSkill(SkillType.S_SMALL_ARMS).getExperienceLevel() / 2; + defensePower += person.getSkill(SkillType.S_SMALL_ARMS).getExperienceLevel() / 2; + } + + if (person.hasSkill(SkillType.S_ADMIN)) { + defensePower += person.getSkill(SkillType.S_ADMIN).getExperienceLevel() / 2; + } + + if (person.hasSkill(SkillType.S_DOCTOR)) { + defensePower += person.getSkill(SkillType.S_DOCTOR).getExperienceLevel() / 2; + } + + if (person.hasSkill(SkillType.S_TACTICS)) { + attackPower += person.getSkill(SkillType.S_TACTICS).getExperienceLevel() / 2; + } + + if (person.hasSkill(SkillType.S_STRATEGY)) { + attackPower += person.getSkill(SkillType.S_STRATEGY).getExperienceLevel() / 2; + } + } + + if (loyalistLeader.hasSkill(SkillType.S_LEADER)) { + attackPower += loyalistLeader.getSkill(SkillType.S_LEADER).getExperienceLevel() / 2; + defensePower += loyalistLeader.getSkill(SkillType.S_LEADER).getExperienceLevel() / 2; + } + + battlePower.put("attack", attackPower); + battlePower.put("defense", defensePower); + + return battlePower; + } + + public static int processAbstractBattleRound(Campaign campaign, Random random, + List loyalists, HashMap loyalistBattlePower, + List mutineers, HashMap mutinyBattlePower) { + + // the loyalist side of the battle (ten rounds of combat) + int hits = 0; + int combatRound = 0; + + while (combatRound < 10) { + hits += combatRound(loyalistBattlePower.get("attack"), mutinyBattlePower.get("defense")); + combatRound++; + } + + distributeHits(campaign, mutineers, hits, random); + + List mutineerGraveyard = fillGraveyard(campaign, mutineers); + + for (Person person : mutineerGraveyard) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); + } + + mutineers.removeAll(mutineerGraveyard); + + // the mutineer side of the battle (ten rounds of combat) + hits = 0; + combatRound = 0; + + while (combatRound < 10) { + hits += combatRound(mutinyBattlePower.get("attack"), loyalistBattlePower.get("defense")); + combatRound++; + } + + distributeHits(campaign, loyalists, hits, random); + + List loyalistsGraveyard = fillGraveyard(campaign, loyalists); + + for (Person person : loyalistsGraveyard) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); + } + + loyalists.removeAll(loyalistsGraveyard); + + // calculate results + if ((mutineers.isEmpty()) && (loyalists.isEmpty())) { + // they wiped each other out + return -1; + } + + if (mutineerGraveyard.size() == loyalistsGraveyard.size()) { + // if the battle is drawn, we open another instance of the fight + return processAbstractBattleRound(campaign, random, + loyalists, loyalistBattlePower, + mutineers, mutinyBattlePower); + } else if (loyalistsGraveyard.size() > mutineerGraveyard.size()) { + // loyalist victory + return 0; + } else { + // mutineer victory + return 1; + } + } + + private static List fillGraveyard(Campaign campaign, List personnel) { + List graveyard; + + if (campaign.getCampaignOptions().isUseAdvancedMedical()) { + graveyard = personnel.stream() + .filter(person -> person.getInjuries().size() > 5) + .collect(Collectors.toList()); + } else { + graveyard = personnel.stream() + .filter(person -> person.getHits() > 5) + .collect(Collectors.toList()); + } + return graveyard; + } + + private static void distributeHits(Campaign campaign, List potentialVictims, int hits, Random random) { + for (int hit = 0; hit < hits; hit++) { + boolean victimFound = false; + int maxAttempts = 3; + + Person victim = null; + + while ((!victimFound) || (maxAttempts == 0)) { + victim = potentialVictims.get(random.nextInt(potentialVictims.size())); + + if ((campaign.getCampaignOptions().isUseAdvancedMedical()) && (victim.getInjuries().size() < 6)) { + victimFound = true; + } else if (victim.getHits() < 6) { + victimFound = true; + } + + // this prevents an infinite loop from occurring when all combatants are dead + maxAttempts--; + } + + int injuryCount = MathUtility.clamp(Compute.randomInt(4) + Compute.randomInt(4) + 2, 2, 5); + + if (campaign.getCampaignOptions().isUseAdvancedMedical()) { + InjuryUtil.resolveCombatDamage(campaign, victim, injuryCount); + victim.setHits(victim.getHits() + injuryCount); + } else { + victim.setHits(victim.getHits() + injuryCount); + } + } + } + + private static int combatRound(int attackerAttackPower, int defenderDefensePower) { + int attackerHits = 0; + int attackerCriticalHits = 0; + + for (int attackRoll = 0; attackRoll < attackerAttackPower; attackRoll++) { + int roll = Compute.d6(1); + + if (roll == 6) { + attackerCriticalHits++; + } else if (roll >= 4) { + attackerHits++; + } + } + + int defenderDefense = (int) IntStream.range(0, defenderDefensePower) + .filter(defenseRoll -> Compute.d6(1) == 6) + .count(); + + return Math.max(0, (attackerHits - defenderDefense)) + attackerCriticalHits; + } } diff --git a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java b/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java deleted file mode 100644 index dabc68a511..0000000000 --- a/MekHQ/src/mekhq/gui/dialog/MutinySupportDialog.java +++ /dev/null @@ -1,263 +0,0 @@ -package mekhq.gui.dialog; - -import megamek.common.Entity; -import mekhq.campaign.Campaign; -import mekhq.campaign.personnel.Person; -import mekhq.campaign.unit.Unit; - -import javax.swing.*; -import java.awt.*; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.ResourceBundle; -import java.util.concurrent.atomic.AtomicInteger; - -public class MutinySupportDialog { - /** - * Displays a dialog with options to either support loyalists or rebels. - * - * @param resources the resource bundle containing the dialog messages - * @param isViolentRebellion a boolean indicating whether the rebellion is violent or not - * @param loyalistForces a list of loyalist units - * @param rebelForces a list of rebel units - * @return an integer representing the user's choice: - * - 1 if the user chooses to support the loyalists, - * - 0 if the user chooses to support the rebels, - * - -1 if the user cancels the dialog - */ - public static int supportDialog(Campaign campaign, ResourceBundle resources, boolean isViolentRebellion, - Integer bystanderPersonnelCount, - Person loyalistLeader, Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, - Person rebelLeader, Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { - - AtomicInteger choice = new AtomicInteger(-1); - JOptionPane pane = new JOptionPane(buildMutinyDescription( - campaign, resources, isViolentRebellion, - bystanderPersonnelCount, - loyalistLeader, loyalistPersonnelCount, loyalistForces, loyalistBv, - rebelLeader, rebelPersonnelCount, rebelForces, rebelBv - ), - JOptionPane.QUESTION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}, null); - - Object[] options = { - resources.getString("dialogSupportLoyalists.text"), - resources.getString("dialogSupportRebels.text") - }; - - JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); - buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - - for (Object option : options){ - JButton button = new JButton(option.toString()); - button.addActionListener(e -> { - choice.set(Arrays.asList(options).indexOf(option)); - - Window window = SwingUtilities.getWindowAncestor(button); - - if (window != null) { - window.setVisible(false); - } - }); - - buttonPanel.add(button); - buttonPanel.add(Box.createHorizontalStrut(15)); - } - - pane.setOptions(new Object[]{buttonPanel}); - JDialog dialog; - - if (isViolentRebellion) { - dialog = pane.createDialog(null, resources.getString("dialogTitleViolentUprising.text")); - } else { - dialog = pane.createDialog(null, resources.getString("dialogTitleRegimeChange.text")); - } - - dialog.setVisible(true); - - return choice.get(); - } - - private static String buildMutinyDescription(Campaign campaign, ResourceBundle resources, boolean isViolentRebellion, - Integer bystanderPersonnelCount, - Person loyalistLeader, Integer loyalistPersonnelCount, List loyalistForces, Integer loyalistBv, - Person rebelLeader, Integer rebelPersonnelCount, List rebelForces, Integer rebelBv) { - - StringBuilder situationDescription = new StringBuilder(String.format( - resources.getString("dialogDescriptionIntroduction.text"), - loyalistLeader.getFullTitle(), - campaign.getName()) - ); - - if (isViolentRebellion) { - situationDescription.append(' ').append(String.format( - resources.getString("dialogDescriptionViolentUprising.text"), - rebelLeader.getFullTitle() - )); - } else { - situationDescription.append(' ').append(String.format( - resources.getString("dialogDescriptionRegimeChange.text"), - rebelLeader.getFullTitle()) - ); - } - - HashMap unitMap = mapUnitCounts(loyalistForces); - situationDescription.append(getForceSummaryString(resources, unitMap, true, loyalistBv, loyalistPersonnelCount)); - - unitMap = mapUnitCounts(rebelForces); - situationDescription.append(getForceSummaryString(resources, unitMap, false, rebelBv, rebelPersonnelCount)); - - situationDescription.append(String.format(resources.getString("dialogDescriptionBystanders.text"), bystanderPersonnelCount)); - - situationDescription.append(resources.getString("dialogDescriptionDecision.text")); - - return situationDescription.toString(); - } - - /** - * Maps the counts of different units in the given list of units. - * - * @param units the list of units - * @return a HashMap containing the counts of each unit type, where the key is the unit type and the value is the count - */ - private static HashMap mapUnitCounts(List units) { - HashMap unitCounts = new HashMap<>(); - - int mekCount = 0; - int fighterCount = 0; - int protoMekCount = 0; - int baCount = 0; - int dropShipCount = 0; - int infantryCount = 0; - int vehicleCount = 0; - int otherCount = 0; - - for (Unit unit : units) { - Entity entity = unit.getEntity(); - - if (entity.isMek()) { - mekCount++; - } else if (entity.isFighter()) { - fighterCount++; - } else if (entity.isProtoMek()) { - protoMekCount++; - } else if (entity.isBattleArmor()) { - baCount++; - } else if (entity.isDropShip()) { - dropShipCount++; - } else if (entity.isInfantry()) { - infantryCount++; - } else if (entity.isVehicle()) { - vehicleCount++; - } else { - otherCount++; - } - } - - unitCounts.put("mek", mekCount); - unitCounts.put("fighter", fighterCount); - unitCounts.put("protoMek", protoMekCount); - unitCounts.put("battleArmor", baCount); - unitCounts.put("dropShip", dropShipCount); - unitCounts.put("infantry", infantryCount); - unitCounts.put("vehicle", vehicleCount); - unitCounts.put("other", otherCount); - - return unitCounts; - } - - /** - * Retrieves the summary string for the forces based on the given resources, unit map, and loyalty faction. - * - * @param resources the ResourceBundle containing the necessary strings for formatting the summary string - * @param unitMap the HashMap containing the count of each unit type - * @param isLoyalist the flag indicating if the forces are loyalist or rebel - * @param battleValue the force's estimated Battle Value - * @param personnelCount the number of personnel supporting the faction - * @return the formatted summary string - */ - private static String getForceSummaryString(ResourceBundle resources, HashMap unitMap, boolean isLoyalist, int battleValue, int personnelCount) { - StringBuilder forceSummaryString = new StringBuilder(); - String faction; - - if (isLoyalist) { - faction = resources.getString("dialogDescriptionLoyalist.text"); - } else { - faction = resources.getString("dialogDescriptionRebels.text"); - } - - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForces.text"), - faction, - personnelCount)); - - if (unitMap.get("mek") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesMeks.text"), - unitMap.get("mek"), - pluralizer(resources, unitMap.get("mek")))); - } - - if (unitMap.get("fighter") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesFighters.text"), - unitMap.get("fighter"), - pluralizer(resources, unitMap.get("fighter")))); - } - - if (unitMap.get("protoMek") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesProtoMechs.text"), - unitMap.get("protoMek"), - pluralizer(resources, unitMap.get("protoMek")))); - } - - if (unitMap.get("battleArmor") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesBattleArmor.text"), - pluralizer(resources, unitMap.get("battleArmor")), - unitMap.get("battleArmor"))); - } - - if (unitMap.get("vehicle") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesVehicles.text"), - unitMap.get("vehicle"), - pluralizer(resources, unitMap.get("vehicle")))); - } - - if (unitMap.get("infantry") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesInfantry.text"), - pluralizer(resources, unitMap.get("infantry")), - unitMap.get("infantry"))); - } - - if (unitMap.get("dropShip") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesDropShips.text"), - unitMap.get("dropShip"), - pluralizer(resources, unitMap.get("dropShip")))); - } - - if (unitMap.get("other") > 0) { - forceSummaryString.append(String.format(resources.getString("dialogDescriptionForcesOther.text"), - unitMap.get("other"), - pluralizer(resources, unitMap.get("other")))); - } - - forceSummaryString.append(' ').append(String.format(resources.getString("dialogDescriptionForcesBv.text"), battleValue)); - - return forceSummaryString.toString(); - } - - /** - * This method is used to determine whether a string should be plural based on the given unit count. - * - * @param resources The ResourceBundle object containing the string resource for pluralizer text. - * @param unitCount The number of units. - * @return The pluralizer text based on the unit count. Returns an empty string if the unit count is less than or equal to 1. - */ - private static String pluralizer(ResourceBundle resources, int unitCount) { - String pluralizer = ""; - - if ((unitCount > 1) || (unitCount == 0)) { - pluralizer = resources.getString("dialogDescriptionPluralizer.text"); - } - - return pluralizer; - } -} \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyBattleDialog.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyBattleDialog.java new file mode 100644 index 0000000000..92e0834f80 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyBattleDialog.java @@ -0,0 +1,130 @@ +package mekhq.gui.dialog.moraleDialogs; + +import megamek.common.Compute; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.ResourceBundle; + +public class TransitMutinyBattleDialog extends JDialog { + /** + * Displays a dialog for the onset of a mutiny while in transit. + * + * @param resources the resource bundle containing the dialog text and options + */ + public static void transitMutinyBattleConditionDialog(ResourceBundle resources, Random random) { + JOptionPane pane = new JOptionPane( + randomBattleCondition(resources, random), + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.YES_NO_OPTION, + null, + new Object[]{}, + null + ); + + Object[] options = { + resources.getString("abstractMutinyButtonConfirm.text") + }; + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + for (Object option : options){ + JButton button = new JButton(option.toString()); + button.addActionListener(e -> { + Window window = SwingUtilities.getWindowAncestor(button); + if (window != null) { + window.setVisible(false); + } + }); + + buttonPanel.add(button); + buttonPanel.add(Box.createHorizontalStrut(15)); + } + + pane.setOptions(new Object[]{buttonPanel}); + + JDialog dialog = pane.createDialog(null, resources.getString("abstractMutiny.title")); + + dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + dialog.setResizable(false); + + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + // Do nothing + } + }); + + dialog.setVisible(true); + } + + /** + * Generates a random battle condition using the provided resources and random number generator. + * + * @param resources the ResourceBundle containing the battle condition texts + * @param random the Random object used to generate random numbers + * @return a randomly selected battle condition as a formatted String + * @throws IllegalStateException if the generated random number is out of range + */ + private static String randomBattleCondition(ResourceBundle resources, Random random) { + String attacker; + String defender; + + int roll = Compute.randomInt(2); + + switch (roll) { + case 0: + attacker = resources.getString("abstractBattleFactionLoyalists.text"); + defender = resources.getString("abstractBattleFactionMutineers.text"); + break; + case 1: + attacker = resources.getString("abstractBattleFactionMutineers.text"); + defender = resources.getString("abstractBattleFactionLoyalists.text"); + break; + default: + throw new IllegalStateException("Unexpected value in randomBattleCondition " + roll); + } + + List conditions = Arrays.asList( + String.format(resources.getString("abstractBattleDescription01.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription02.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription03.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription04.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription05.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription06.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription07.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription08.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription09.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription10.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription11.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription12.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription13.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription14.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription15.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription16.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription17.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription18.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription19.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription20.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription21.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription22.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription23.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription24.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription25.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription26.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription27.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription28.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription29.text"), attacker, defender), + String.format(resources.getString("abstractBattleDescription30.text"), attacker, defender) + ); + + return conditions.get(random.nextInt(conditions.size())); + } +} \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java new file mode 100644 index 0000000000..3d23e1c8e4 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java @@ -0,0 +1,62 @@ +package mekhq.gui.dialog.moraleDialogs; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ResourceBundle; + +public class TransitMutinyDialog extends JDialog { + /** + * Displays a dialog for the onset of a mutiny while in transit. + * + * @param resources the resource bundle containing the dialog text and options + */ + public static void transitMutinyOnsetDialog(ResourceBundle resources) { + JOptionPane pane = new JOptionPane( + resources.getString("abstractMutinyOnsetDescription.text"), + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.YES_NO_OPTION, + null, + new Object[]{}, + null + ); + + Object[] options = { + resources.getString("abstractMutinyButtonConfirm.text") + }; + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + for (Object option : options){ + JButton button = new JButton(option.toString()); + button.addActionListener(e -> { + Window window = SwingUtilities.getWindowAncestor(button); + if (window != null) { + window.setVisible(false); + } + }); + + buttonPanel.add(button); + buttonPanel.add(Box.createHorizontalStrut(15)); + } + + pane.setOptions(new Object[]{buttonPanel}); + + JDialog dialog = pane.createDialog(null, resources.getString("abstractMutiny.title")); + + dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + dialog.setResizable(false); + + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + // Do nothing + } + }); + + dialog.setVisible(true); + } +} \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java new file mode 100644 index 0000000000..7fb0d02742 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java @@ -0,0 +1,92 @@ +package mekhq.gui.dialog.moraleDialogs; + +import mekhq.campaign.personnel.Person; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; + +public class TransitMutinyToe extends JDialog { + /** + * Displays a dialog for the onset of a mutiny while in transit. + * + * @param resources the resource bundle containing the dialog text and options + */ + public static int transitMutinyToeDialog(ResourceBundle resources, + Person loyalistLeader, int loyalistCount, int loyalistAttackPower, int loyalistDefensePower, + Person mutineerLeader, int mutineerCount, int mutineerAttackPower, int mutineerDefensePower) { + // this builds the text for the dialog + StringBuilder toeDescription = new StringBuilder(""); + + toeDescription.append(String.format(resources.getString("abstractBattleToe.text"), + loyalistLeader.getFullTitle(), + resources.getString("abstractBattleFactionLoyalists.text"), + loyalistCount, + loyalistAttackPower, + loyalistDefensePower)); + + toeDescription.append(String.format(resources.getString("abstractBattleToe.text"), + mutineerLeader.getFullTitle(), + resources.getString("abstractBattleFactionMutineers.text"), + mutineerCount, + mutineerAttackPower, + mutineerDefensePower)); + + toeDescription.append(""); + + // this builds the dialog + AtomicInteger choice = new AtomicInteger(-1); + JOptionPane pane = new JOptionPane( + toeDescription, + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.YES_NO_OPTION, + null, + new Object[]{}, + null + ); + + Object[] options = { + resources.getString("abstractBattleFactionSupportLoyalists.text"), + resources.getString("abstractBattleFactionSupportMutineers.text"), + resources.getString("abstractBattleFactionSupportVictor.text"), + }; + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + for (Object option : options){ + JButton button = new JButton(option.toString()); + button.addActionListener(e -> { + Window window = SwingUtilities.getWindowAncestor(button); + if (window != null) { + window.setVisible(false); + } + }); + + buttonPanel.add(button); + buttonPanel.add(Box.createHorizontalStrut(15)); + } + + pane.setOptions(new Object[]{buttonPanel}); + + JDialog dialog = pane.createDialog(null, resources.getString("abstractMutiny.title")); + + dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + dialog.setResizable(false); + + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + // Do nothing + } + }); + + dialog.setVisible(true); + + return choice.get(); + } +} \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 5a54372def..498324c66c 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -344,6 +344,8 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkUseDesertions; private JLabel lblForceReliabilityMethod; private MMComboBox comboForceReliabilityMethod; + private JLabel lblMutinyMethod; + private MMComboBox comboMutinyMethod; private JCheckBox chkUseSabotage; private JCheckBox chkUseMutinies; private JCheckBox chkUseTheftUnit; @@ -4915,6 +4917,9 @@ private JPanel createTurnoverAndRetentionMoralePanel() { turnoverAndRetentionMoraleTriggersPanel.setEnabled(isEnabled); // Special Case Handlers + lblMutinyMethod.setEnabled((isEnabled) && (campaign.getCampaignOptions().isUseMutinies())); + comboMutinyMethod.setEnabled((isEnabled) && (campaign.getCampaignOptions().isUseMutinies())); + chkUseMoraleTriggerDesertion.setEnabled((isEnabled) && (chkUseDesertions.isSelected())); chkUseMoraleModifierCommanderSkill.setEnabled((isEnabled) && (chkUseManagementSkill.isSelected())); @@ -4991,6 +4996,27 @@ public Component getListCellRendererComponent(final JList list, final Object } }); + lblMutinyMethod = new JLabel(resources.getString("lblMutinyMethod.text")); + lblMutinyMethod.setToolTipText(resources.getString("lblMutinyMethod.toolTipText")); + lblMutinyMethod.setName("lblMutinyMethod"); + lblMutinyMethod.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseMutinies())); + + comboMutinyMethod = new MMComboBox<>("comboMutinyMethod", MutinyMethod.values()); + comboMutinyMethod.setToolTipText(resources.getString("lblMutinyMethod.toolTipText")); + comboMutinyMethod.setEnabled((isUseMorale) && (campaign.getCampaignOptions().isUseMutinies())); + comboMutinyMethod.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(final JList list, final Object value, + final int index, final boolean isSelected, + final boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof MutinyMethod) { + list.setToolTipText(((MutinyMethod) value).getToolTipText()); + } + return this; + } + }); + chkUseDesertions = new JCheckBox(resources.getString("chkUseDesertions.text")); chkUseDesertions.setToolTipText(resources.getString("chkUseDesertions.toolTipText")); chkUseDesertions.setName("chkUseDesertions"); @@ -5009,6 +5035,8 @@ public Component getListCellRendererComponent(final JList list, final Object final boolean isEnabled = chkUseMutinies.isSelected(); chkUseMoraleTriggerMutiny.setEnabled(isEnabled); + lblMutinyMethod.setEnabled(isEnabled); + comboMutinyMethod.setEnabled(isEnabled); }); chkUseSabotage = new JCheckBox(resources.getString("chkUseSabotage.text")); @@ -5080,6 +5108,9 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod, Alignment.LEADING)) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(lblMutinyMethod) + .addComponent(comboMutinyMethod, Alignment.LEADING)) .addComponent(chkUseSabotage) .addComponent(chkUseTheftUnit) .addGroup(layout.createParallelGroup(Alignment.BASELINE) @@ -5105,6 +5136,9 @@ public Component getListCellRendererComponent(final JList list, final Object .addGroup(layout.createSequentialGroup() .addComponent(lblForceReliabilityMethod) .addComponent(comboForceReliabilityMethod)) + .addGroup(layout.createSequentialGroup() + .addComponent(lblMutinyMethod) + .addComponent(comboMutinyMethod)) .addComponent(chkUseSabotage) .addGroup(layout.createSequentialGroup() .addComponent(lblTheftResellValue) @@ -8418,6 +8452,7 @@ public void setOptions(@Nullable CampaignOptions options, chkUseMutinies.setSelected(options.isUseMutinies()); spnMoraleStepSize.setValue(options.getMoraleStepSize()); comboForceReliabilityMethod.setSelectedItem(options.getForceReliabilityMethod()); + comboMutinyMethod.setSelectedItem(options.getMutinyMethod()); chkUseSabotage.setSelected(options.isUseSabotage()); chkUseTheftUnit.setSelected(options.isUseTheftUnit()); spnTheftResellValue.setValue(options.getTheftResellValue()); @@ -9124,6 +9159,7 @@ public void updateOptions() { options.setUseMutinies(chkUseMutinies.isSelected()); options.setMoraleStepSize((Integer) spnMoraleStepSize.getValue()); options.setForceReliabilityMethod(comboForceReliabilityMethod.getSelectedItem()); + options.setMutinyMethod(comboMutinyMethod.getSelectedItem()); options.setUseSabotage(chkUseSabotage.isSelected()); options.setUseTheftUnit(chkUseTheftUnit.isSelected()); options.setTheftResellValue((Integer) spnTheftResellValue.getValue()); From 6ffbe5041b52f27c43b5be2e8c15d690fc722ed6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 29 May 2024 03:07:17 -0500 Subject: [PATCH 071/101] feat: Added mutiny conclusion and campaign end dialog+ messages for MekHQ --- .../mekhq/resources/Morale.properties | 41 +++++++++ .../turnoverAndRetention/Morale/Mutiny.java | 14 ++- .../TransitMutinyCampaignOverDialog.java | 33 +++++++ .../TransitMutinyConclusionDialog.java | 85 +++++++++++++++++++ ...log.java => TransitMutinyOnsetDialog.java} | 2 +- 5 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyCampaignOverDialog.java create mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyConclusionDialog.java rename MekHQ/src/mekhq/gui/dialog/moraleDialogs/{TransitMutinyDialog.java => TransitMutinyOnsetDialog.java} (97%) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 558cfe63cf..fa9a9f5230 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -62,6 +62,47 @@ abstractBattleToe.text=Led by %s, the %s are supported by %s personnel.\
  • Offense: %s\
  • Defense: %s
+# Conclusion Dialog +abstractMutinyConclusionNoVictor.title=Mutual Destruction +abstractMutinyConclusionLoyalistVictory.title=Loyalist Victory +abstractMutinyConclusionMutineerVictory.title=Mutineer Victory + +abstractMutinyConclusionNoVictorDescription.text=The end of the mutiny is marked by the grim and harrowing silence that follows the chaos. As the final shots are fired and the last cries of conflict fade, both loyalists and mutineers lay scattered across the blood-streaked corridors and shattered control rooms. The air is thick with the acrid smell of spent ammunition and the faint, metallic tang of blood.\ +
\ +
The bridge, once a hub of coordinated effort and command, now stands eerily silent. Consoles flicker with sporadic life, displaying warnings and error messages, unattended and ignored. The captain's chair, a symbol of authority and leadership, is slumped with the lifeless body of its last occupant, a silent testament to the brutality that transpired. Engineering sections, critical for the DropShip's operation, are littered with debris and the remains of desperate combatants who fought fiercely to control the heart of the vessel.\ +
\ +
With no survivors to tend to the wounded or repair the damage, the DropShip is silent, a ghost ship. Its engines, either critically damaged or left unattended, sputter intermittently, sending the ship into a slow, aimless spin. Systems that once maintained life support, navigation, and communication flicker uncertainly, threatening to fail completely and plunge the vessel into darkness and silence.\ +
\ +
The catastrophic conclusion of the mutiny not only claims the lives of every crew member but also condemns the DropShip itself to a slow, inevitable demise. Without a guiding hand, the ship becomes a drifting tomb, a chilling reminder of the destructive power of internal conflict. Units, supplies, and the dreams of those who once served aboard are now lost to the void, their mission abandoned and their fate sealed by the ultimate cost of their rebellion.\ +
\ +
In the aftermath, the once-cohesive unit is no more, and the echoes of their struggle may only be discovered by a passing salvage crew or another vessel, drawn by the silent distress beacon that still pulses, a lonely signal of life extinguished by the very hands meant to protect it.\ + +abstractMutinyConclusionLoyalistVictory.text=The end of the mutiny sees the loyalists victorious but at great cost. The last gunfire and shouts fade into silence as the loyal crew stands amidst the aftermath. Bodies of mutineers and loyalists lie scattered through narrow corridors and control rooms, a stark testament to the brutal conflict.\ +
\ +
The bridge, now under loyalist control, is a wreckage of broken consoles and bloodied bodies. The commander, battered but resolute, surveys casualty reports and system alerts that flicker on the screens. In engineering, loyalist engineers work tirelessly to stabilize critical systems and prevent further catastrophe.\ +
\ +
Throughout the ship, loyalists secure vital areas and tend to the wounded in a makeshift medbay. Exhaustion and grief mark their faces, reflecting the heavy toll of victory. The unit's cohesion and morale are deeply scarred, and trust among the crew is fragile. + +abstractMutinyConclusionMutineerVictory.text=The end of the mutiny is marked by tense silence as the last echoes of gunfire fade. The mutineers, weary but determined, stand amidst the wreckage of fierce battles in narrow corridors and crucial control rooms. Loyalist crew members lie scattered, their resistance crushed by the mutineers.\ +
\ +
The bridge, now under mutineer control, is a scene of broken consoles and bloodied bodies. The leader of the mutineers takes the commander's chair, surveying the aftermath with grim satisfaction. Displays flicker with damage reports and system alerts, evidence of the intense struggle to seize command.\ +
\ +
In engineering, mutineers work frantically to stabilize critical systems, their efforts a desperate attempt to prevent catastrophic failure. The hum of machinery and clatter of tools create a frantic symphony as they patch up the worst damage.\ +
\ +
Throughout the ship, mutineers secure vital areas and tend to their wounded in a makeshift medbay. Exhaustion and grief mark their faces, reflecting the heavy toll of victory. The unit's cohesion and morale are shattered, trust is fragile, and the shadow of betrayal looms large.\ +
\ +
As they set a new course, haunted by the memory of their conflict and the high price of rebellion, the future remains uncertain. + +abstractMutingCampaignEndTitle.title=Game Over +abstractMutinyCampaignEnd.text=Following the mutiny and its grim resolution, your campaign has reached its abrupt and somber end.\ +
\ +
%s is now a scarred monument to internal conflict, its personnel decimated. The survivors left standing among the wreckage.\ +
\ +
The once-vibrant corridors of the DropShip are eerily silent, with only the hum of damaged systems breaking the quiet. The victory, whether by loyalists or mutineers, is pyrrhic. The survivors are weary, their faces etched with the toll of battle and the weight of their decisions. However, what happens next is a story told without you.\ +
\ +
Your campaign is now over. + + # Combat Dialog abstractBattleDescription01.text=The %s have barricaded themselves in the bridge, while %s attempt to breach their defenses. abstractBattleDescription02.text=The %s are attempting to sabotage critical systems to hinder the %s' progress. This has led to a dangerous game of cat and mouse in the engineering section. diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java index 441f276698..fb49fc62e6 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java @@ -7,7 +7,7 @@ import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.PersonnelStatus; -import mekhq.gui.dialog.moraleDialogs.TransitMutinyDialog; +import mekhq.gui.dialog.moraleDialogs.TransitMutinyOnsetDialog; import mekhq.gui.dialog.moraleDialogs.TransitMutinyToe; import java.util.*; @@ -16,6 +16,8 @@ import static mekhq.campaign.personnel.turnoverAndRetention.Morale.Desertion.processDesertion; import static mekhq.gui.dialog.moraleDialogs.TransitMutinyBattleDialog.transitMutinyBattleConditionDialog; +import static mekhq.gui.dialog.moraleDialogs.TransitMutinyCampaignOverDialog.transitMutinyCampaignOverDialog; +import static mekhq.gui.dialog.moraleDialogs.TransitMutinyConclusionDialog.transitMutinyConclusionDialog; public class Mutiny { static void processMutiny(Campaign campaign, List loyalists, HashMap mutineers, int targetNumber, ResourceBundle resources) { @@ -49,7 +51,7 @@ static void processCamOpsMutiny(Campaign campaign, Random random, List l Person loyalistLeader = campaign.getHighestRankedPerson(loyalists, true); // this dialog alerts the player to the mutiny - TransitMutinyDialog.transitMutinyOnsetDialog(resources); + TransitMutinyOnsetDialog.transitMutinyOnsetDialog(resources); // this dialog tells the player about the array of forces and gives them the option to support the mutineers or loyalists @@ -71,9 +73,15 @@ private static void processAbstractBattle(Campaign campaign, Random random, Reso loyalistLeader, loyalists.size(), loyalistBattlePower.get("attack"), loyalistBattlePower.get("defense"), mutineerLeader, mutineers.keySet().size(), mutinyBattlePower.get("attack"), mutinyBattlePower.get("defense")); + transitMutinyBattleConditionDialog(resources, random); + int victor = processAbstractBattleRound(campaign, random, loyalists, loyalistBattlePower, new ArrayList<>(mutineers.keySet()), mutinyBattlePower); - transitMutinyBattleConditionDialog(resources, random); + transitMutinyConclusionDialog(resources, victor); + + if (victor != support) { + transitMutinyCampaignOverDialog(campaign, resources); + } } private static HashMap getAbstractBattlePower(Person loyalistLeader, List loyalists) { diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyCampaignOverDialog.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyCampaignOverDialog.java new file mode 100644 index 0000000000..398db0fc0c --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyCampaignOverDialog.java @@ -0,0 +1,33 @@ +package mekhq.gui.dialog.moraleDialogs; + +import mekhq.campaign.Campaign; + +import javax.swing.*; +import java.util.ResourceBundle; + +public class TransitMutinyCampaignOverDialog extends JDialog { + /** + * Displays a dialog for the onset of a mutiny while in transit. + * + * @param resources the resource bundle containing the dialog text and options + */ + public static void transitMutinyCampaignOverDialog(Campaign campaign, ResourceBundle resources) { + JOptionPane pane = new JOptionPane( + String.format(resources.getString("abstractMutinyCampaignEnd.text"), campaign.getName()), + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.YES_NO_OPTION, + null, + new Object[]{}, + null + ); + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + JDialog dialog = pane.createDialog(null, resources.getString("abstractMutingCampaignEndTitle.title")); + + dialog.setResizable(false); + dialog.setVisible(true); + } +} \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyConclusionDialog.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyConclusionDialog.java new file mode 100644 index 0000000000..ce8661bf35 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyConclusionDialog.java @@ -0,0 +1,85 @@ +package mekhq.gui.dialog.moraleDialogs; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ResourceBundle; + +public class TransitMutinyConclusionDialog extends JDialog { + /** + * Displays a dialog for the onset of a mutiny while in transit. + * + * @param resources the resource bundle containing the dialog text and options + */ + public static void transitMutinyConclusionDialog(ResourceBundle resources, int victor) { + String title; + String description; + + switch (victor) { + case -1: + // mutual destruction + title = resources.getString("abstractMutinyConclusionNoVictor.title"); + description = resources.getString("abstractMutinyConclusionNoVictorDescription.text"); + break; + case 0: + // loyalist + title = resources.getString("abstractMutinyConclusionLoyalistVictory.title"); + description = resources.getString("abstractMutinyConclusionLoyalistVictory.text"); + break; + case 1: + // mutineer + title = resources.getString("abstractMutinyConclusionMutineerVictory.title"); + description = resources.getString("abstractMutinyConclusionMutineerVictory.text"); + break; + default: + throw new IllegalStateException("Unexpected value in transitMutinyConclusionDialog: " + victor); + } + + JOptionPane pane = new JOptionPane( + description, + JOptionPane.INFORMATION_MESSAGE, + JOptionPane.YES_NO_OPTION, + null, + new Object[]{}, + null + ); + + Object[] options = { + resources.getString("abstractMutinyButtonConfirm.text") + }; + + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + for (Object option : options){ + JButton button = new JButton(option.toString()); + button.addActionListener(e -> { + Window window = SwingUtilities.getWindowAncestor(button); + if (window != null) { + window.setVisible(false); + } + }); + + buttonPanel.add(button); + buttonPanel.add(Box.createHorizontalStrut(15)); + } + + pane.setOptions(new Object[]{buttonPanel}); + + JDialog dialog = pane.createDialog(null, title); + + dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + dialog.setResizable(false); + + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + // Do nothing + } + }); + + dialog.setVisible(true); + } +} \ No newline at end of file diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyOnsetDialog.java similarity index 97% rename from MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java rename to MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyOnsetDialog.java index 3d23e1c8e4..684a1590f5 100644 --- a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyDialog.java +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyOnsetDialog.java @@ -6,7 +6,7 @@ import java.awt.event.WindowEvent; import java.util.ResourceBundle; -public class TransitMutinyDialog extends JDialog { +public class TransitMutinyOnsetDialog extends JDialog { /** * Displays a dialog for the onset of a mutiny while in transit. * From 32f7bc9e911271b3278be01f1b73f00772476bc6 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 29 May 2024 18:07:40 -0500 Subject: [PATCH 072/101] feat: Updated mutiny event handling in MekHQ - Expanded mutiny event implementation to include battle sequences between loyalists and mutineers - Included a campaign fail state when supporting the losing side - Provided mechanisms to set prisoner status based on the victor - Included ship damage and financial implications from damage to hired transports - Corrected armour setting methods in Entity class - Updated applyAeroCritical method to public in GameManager class - Added new function getLargeCraftAndWarShips in Campaign class - Included new dialogues and tooltips for improved user interaction - Various bug fixes and improvements in Morale properties --- .../mekhq/resources/Morale.properties | 9 +- MekHQ/src/mekhq/campaign/Campaign.java | 6 + .../turnoverAndRetention/Morale/Mutiny.java | 103 ++++++++++++++++-- .../moraleDialogs/TransitMutinyToe.java | 20 +++- 4 files changed, 122 insertions(+), 16 deletions(-) diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index fa9a9f5230..2ae87c19da 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -32,6 +32,9 @@ desertionTheftTransactionReport.text=Suspicious transfer of funds to %s # Mutiny mutinyThwartedSingular.text=A mutiny was building, but failed to gain enough support. The would-be mutineer has deserted the company. mutinyThwartedPlural.text=A mutiny was building, but failed to gain enough support. %s would-be mutineers have deserted the company. +mutinyUnitDestroyed.text=%s was destroyed in the fighting. +mutinyDropShipDamage.text=We have been issued with a %s c-bill fine for damage to hired transports. +mutinyDropShipDamageFine.text=Damage to hired transports. # Mutiny Dialog abstractMutiny.title=Mutiny! @@ -39,8 +42,11 @@ abstractMutinyButtonConfirm.text=Confirm abstractBattleFactionLoyalists.text=Loyalists abstractBattleFactionMutineers.text=Mutineers abstractBattleFactionSupportLoyalists.text=Support the Loyalists +abstractBattleFactionSupportLoyalists.toolTip=If the loyalists are defeated, your campaign will be over. abstractBattleFactionSupportMutineers.text=Support the Mutineers +abstractBattleFactionSupportMutineers.toolTip=If the mutineers are defeated, your campaign will be over. abstractBattleFactionSupportVictor.text=Support the Victor +abstractBattleFactionSupportVictor.toolTip=Join whichever side prevails. # Onset Dialog abstractMutinyOnsetDescription.text=A mutiny has broken out among the DropShips transporting the unit!\ @@ -60,7 +66,8 @@ abstractMutinyOnsetDescription.text=A mutiny h # TOE dialog abstractBattleToe.text=Led by %s, the %s are supported by %s personnel.\
  • Offense: %s\ -
  • Defense: %s
+
  • Defense: %s\ +
  • Effective Offense: %s # Conclusion Dialog abstractMutinyConclusionNoVictor.title=Mutual Destruction diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 749be479d4..0e3562b814 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -1237,6 +1237,12 @@ public Collection getUnits() { return getHangar().getUnits(); } + public Collection getLargeCraftAndWarShips() { + return getHangar().getUnits().stream() + .filter(unit -> (unit.getEntity().isLargeCraft()) || (unit.getEntity().isWarShip())) + .collect(Collectors.toList()); + } + public List getEntities() { return getUnits().stream() .map(Unit::getEntity) diff --git a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java index fb49fc62e6..e32e55183f 100644 --- a/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java +++ b/MekHQ/src/mekhq/campaign/personnel/turnoverAndRetention/Morale/Mutiny.java @@ -2,13 +2,22 @@ import megamek.codeUtilities.MathUtility; import megamek.common.Compute; +import megamek.common.Entity; +import megamek.common.HitData; +import megamek.common.ToHitData; import mekhq.campaign.Campaign; +import mekhq.campaign.finances.Money; +import mekhq.campaign.finances.enums.TransactionType; import mekhq.campaign.mod.am.InjuryUtil; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.personnel.enums.PersonnelStatus; +import mekhq.campaign.personnel.enums.PrisonerStatus; +import mekhq.campaign.rating.FieldManualMercRevDragoonsRating; +import mekhq.campaign.unit.Unit; import mekhq.gui.dialog.moraleDialogs.TransitMutinyOnsetDialog; import mekhq.gui.dialog.moraleDialogs.TransitMutinyToe; +import org.apache.logging.log4j.LogManager; import java.util.*; import java.util.stream.Collectors; @@ -65,22 +74,95 @@ static void processCamOpsMutiny(Campaign campaign, Random random, List l private static void processAbstractBattle(Campaign campaign, Random random, ResourceBundle resources, Person loyalistLeader, List loyalists, Person mutineerLeader, HashMap mutineers) { + // Start by gathering the battle powers for each side HashMap loyalistBattlePower = getAbstractBattlePower(loyalistLeader, loyalists); HashMap mutinyBattlePower = getAbstractBattlePower(mutineerLeader, new ArrayList<>(mutineers.keySet())); - + // inform the player of the measure of power among each side int support = TransitMutinyToe.transitMutinyToeDialog(resources, loyalistLeader, loyalists.size(), loyalistBattlePower.get("attack"), loyalistBattlePower.get("defense"), mutineerLeader, mutineers.keySet().size(), mutinyBattlePower.get("attack"), mutinyBattlePower.get("defense")); + // show some flavor text, to make the battle seem more dramatic transitMutinyBattleConditionDialog(resources, random); + // process the battle int victor = processAbstractBattleRound(campaign, random, loyalists, loyalistBattlePower, new ArrayList<>(mutineers.keySet()), mutinyBattlePower); + // broadcast the conclusion transitMutinyConclusionDialog(resources, victor); - if (victor != support) { + // if the player supported the wrong side (and didn't pick 'victor') end their campaign + if ((victor != support) && (support != 2)) { transitMutinyCampaignOverDialog(campaign, resources); + + campaign.getWarehouse().getParts().clear(); + campaign.getPersonnel().clear(); + campaign.getUnits().clear(); + campaign.getFinances().getTransactions().clear(); + } + + if (victor == 0) { + for (Person person : mutineers.keySet()) { + person.setPrisonerStatus(campaign, PrisonerStatus.PRISONER, true); + } + } else { + for (Person person : loyalists) { + person.setPrisonerStatus(campaign, PrisonerStatus.PRISONER, true); + } + } + + // damage any vessels the unit was traveling in + if (!campaign.getLargeCraftAndWarShips().isEmpty()) { + damageShips(campaign, resources); + } + + // issue a fine based on damage issued to any hired transports + if (new FieldManualMercRevDragoonsRating(campaign).getTransportPercent().doubleValue() != 100) { + LogManager.getLogger().info(new FieldManualMercRevDragoonsRating(campaign).getTransportPercent().doubleValue()); + abstractDamageShips(campaign, resources); + } + } + + private static void abstractDamageShips(Campaign campaign, ResourceBundle resources) { + int clusters = Compute.d6(10); + + int damage = IntStream.range(0, clusters) + .map(clusterCount -> Compute.d6(2)) + .sum(); + + int fine = (damage / 80) * 100000; + campaign.getFinances().debit(TransactionType.FINE, campaign.getLocalDate(), Money.of(fine), resources.getString("mutinyDropShipDamageFine.text")); + campaign.addReport(String.format(resources.getString("mutinyDropShipDamage.text"), fine)); + } + + + private static void damageShips(Campaign campaign, ResourceBundle resources) { + // this reflects internal damage sustained during the mutiny + for (Unit unit : campaign.getLargeCraftAndWarShips()) { + Entity entity = unit.getEntity(); + boolean isDestroyed = false; + + int numClusters = Compute.d6(10); + + for (int clusterCount = 0; clusterCount < numClusters; clusterCount++) { + int location = Compute.randomInt(4); + + HitData HitData = entity.rollHitLocation(ToHitData.HIT_NORMAL, location); + int resultingArmor = Math.max(0, entity.getArmor(HitData) - Compute.d6(2)); + entity.setArmor(resultingArmor, location); + + if (entity.getArmor(location) == 0) { + isDestroyed = true; + } + } + + if (isDestroyed) { + campaign.getHangar().removeUnit(unit.getId()); + campaign.addReport(String.format(resources.getString("mutinyUnitDestroyed.text"), unit.getName())); + } else { + unit.runDiagnostic(true); + } } } @@ -139,14 +221,6 @@ public static int processAbstractBattleRound(Campaign campaign, Random random, distributeHits(campaign, mutineers, hits, random); - List mutineerGraveyard = fillGraveyard(campaign, mutineers); - - for (Person person : mutineerGraveyard) { - person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); - } - - mutineers.removeAll(mutineerGraveyard); - // the mutineer side of the battle (ten rounds of combat) hits = 0; combatRound = 0; @@ -158,12 +232,19 @@ public static int processAbstractBattleRound(Campaign campaign, Random random, distributeHits(campaign, loyalists, hits, random); + // post-battle clean up + List mutineerGraveyard = fillGraveyard(campaign, mutineers); List loyalistsGraveyard = fillGraveyard(campaign, loyalists); + for (Person person : mutineerGraveyard) { + person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); + } + for (Person person : loyalistsGraveyard) { person.changeStatus(campaign, campaign.getLocalDate(), PersonnelStatus.KIA); } + mutineers.removeAll(mutineerGraveyard); loyalists.removeAll(loyalistsGraveyard); // calculate results @@ -177,7 +258,7 @@ public static int processAbstractBattleRound(Campaign campaign, Random random, return processAbstractBattleRound(campaign, random, loyalists, loyalistBattlePower, mutineers, mutinyBattlePower); - } else if (loyalistsGraveyard.size() > mutineerGraveyard.size()) { + } else if (mutineerGraveyard.size() > loyalistsGraveyard.size()) { // loyalist victory return 0; } else { diff --git a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java index 7fb0d02742..796d8728b6 100644 --- a/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java +++ b/MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyToe.java @@ -26,14 +26,16 @@ public static int transitMutinyToeDialog(ResourceBundle resources, resources.getString("abstractBattleFactionLoyalists.text"), loyalistCount, loyalistAttackPower, - loyalistDefensePower)); + loyalistDefensePower, + loyalistAttackPower - (mutineerDefensePower / 6))); toeDescription.append(String.format(resources.getString("abstractBattleToe.text"), mutineerLeader.getFullTitle(), resources.getString("abstractBattleFactionMutineers.text"), mutineerCount, mutineerAttackPower, - mutineerDefensePower)); + mutineerDefensePower, + mutineerAttackPower - (loyalistDefensePower / 6))); toeDescription.append(""); @@ -54,13 +56,23 @@ public static int transitMutinyToeDialog(ResourceBundle resources, resources.getString("abstractBattleFactionSupportVictor.text"), }; + String[] tooltips = { + resources.getString("abstractBattleFactionSupportLoyalists.toolTip"), + resources.getString("abstractBattleFactionSupportMutineers.toolTip"), + resources.getString("abstractBattleFactionSupportVictor.toolTip"), + }; + JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - for (Object option : options){ - JButton button = new JButton(option.toString()); + for (int index = 0; index < options.length; index++) { + JButton button = new JButton(options[index].toString()); + button.setToolTipText(tooltips[index]); + int finalI = index; button.addActionListener(e -> { + choice.set(finalI); + Window window = SwingUtilities.getWindowAncestor(button); if (window != null) { window.setVisible(false); From 9a5e8142c346029b687ae777f2d3c91333897b61 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 29 May 2024 18:45:01 -0500 Subject: [PATCH 073/101] fix(EducationController): removed duplicate text concatenation --- .../mekhq/campaign/personnel/education/EducationController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java b/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java index 187c2e301d..d45f70c19e 100644 --- a/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java +++ b/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java @@ -812,7 +812,6 @@ private static void processClanWashout(Campaign campaign, Person person, Integer ServiceLogger.eduClanWashout(person, campaign.getLocalDate(), resources.getString("graduatedScientist.text")); campaign.addReport(person.getHyperlinkedName() + ' ' + String.format(resources.getString("washout.text"), - resources.getString("graduatedScientist.text") + resources.getString("graduatedWarriorLabor.text"))); resources.getString("graduatedScientist.text"), resources.getString("graduatedWarriorLabor.text"))); person.setEduCourseIndex(10); From 6af38f8d3c46a1b19cd6cc8a5b994f78fce1baec Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Wed, 29 May 2024 19:10:35 -0500 Subject: [PATCH 074/101] feat: Added casualty tracking and revised drawing conditions in transit mutiny battle round, restructured morale properties text content, and updated relevant methods accordingly --- .../mekhq/resources/Morale.properties | 63 ++++---- .../turnoverAndRetention/Morale/Mutiny.java | 47 +++--- .../TransitMutinyBattleConditionDialog.java | 136 ++++++++++++++++++ .../TransitMutinyBattleDialog.java | 130 ----------------- .../TransitMutinyCampaignOverDialog.java | 37 ++++- 5 files changed, 232 insertions(+), 181 deletions(-) create mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyBattleConditionDialog.java delete mode 100644 MekHQ/src/mekhq/gui/dialog/moraleDialogs/TransitMutinyBattleDialog.java diff --git a/MekHQ/resources/mekhq/resources/Morale.properties b/MekHQ/resources/mekhq/resources/Morale.properties index 2ae87c19da..7a460eb107 100644 --- a/MekHQ/resources/mekhq/resources/Morale.properties +++ b/MekHQ/resources/mekhq/resources/Morale.properties @@ -110,37 +110,38 @@ abstractMutinyCampaignEnd.text=Following the m
    Your campaign is now over. -# Combat Dialog -abstractBattleDescription01.text=The %s have barricaded themselves in the bridge, while %s attempt to breach their defenses. -abstractBattleDescription02.text=The %s are attempting to sabotage critical systems to hinder the %s' progress. This has led to a dangerous game of cat and mouse in the engineering section. -abstractBattleDescription03.text=The %s are attempting to gain control of the DropShip's life support systems, and threaten to vent the atmosphere unless the %s surrender. -abstractBattleDescription04.text=The %s, facing overwhelming odds, consider the possibility of surrendering to the %s, grappling with the moral implications of such a decision amidst the chaos of the mutiny. -abstractBattleDescription05.text=The %s, driven to desperation by the relentless assault of the %s, initiate a daring escape plan. Risking everything, they try to flee the besieged DropShip, hoping to find refuge and reinforcements aboard the nearby JumpShip. -abstractBattleDescription06.text=The %s' medics are struggling to treat wounded crew members amidst the chaos, facing shortages of medical supplies as these keep getting seized by the %s. -abstractBattleDescription07.text=The %s hijack the DropShip's communications array from the %s, broadcasting propaganda messages to nearby vessels and spreading dissent among potential allies. -abstractBattleDescription08.text=The %s attempt to send a distress signal to the JumpShip, hoping for reinforcements to quell the %s. -abstractBattleDescription09.text=The %s started a fire in a room held by the %s. It is now threatening to engulf the entire ship in flames and compromise its structural integrity. -abstractBattleDescription10.text=The %s cut off access to the DropShip's armory, forcing %s to scavenge for makeshift weapons to defend themselves. -abstractBattleDescription11.text=The ship's command staff, torn between loyalty to %s and sympathy for the %s, struggle to determine their allegiances. -abstractBattleDescription12.text=The %s leaders attempt to negotiate a ceasefire with the %s, offering concessions in exchange for the safe release of hostages. -abstractBattleDescription13.text=A group of %s with technical expertise are attempting to hack into the DropShip's control systems, seeking to gain full control of the vessel's functions. The %s have launched an attempt to thwart them. -abstractBattleDescription14.text=The %s launched a daring raid to reclaim control of the DropShip's bridge from the %s, risking their lives to eliminate key enemy leaders. -abstractBattleDescription15.text=The %s block access to the DropShip's escape pods, preventing %s from fleeing the ship. -abstractBattleDescription16.text=A power struggle erupts among the crew's personnel, as some align themselves with the %s while others turn to the %s. -abstractBattleDescription17.text=The %s sabotage critical systems to hinder the %s' progress, leading to a race against time to repair the damage before it's too late. -abstractBattleDescription18.text=A %s officer manages to establish contact with nearby authorities, pleading for assistance in defeating the %s and restoring order. -abstractBattleDescription19.text=The %s attempt to breach the DropShip's command center using explosives, risking catastrophic damage to the ship's structure in a desperate bid to defeat the %s. -abstractBattleDescription20.text=With resources dwindling and morale at an all-time low, both %s and %s must contend with the harsh realities of survival in a mutiny, facing starvation, exhaustion, and the constant threat of further violence as they struggle to gain the upper hand in the ongoing conflict. -abstractBattleDescription21.text=As tensions escalate, %s and %s alike struggle with their own moral dilemmas, torn between loyalty to their comrades and the desire to survive at any cost. -abstractBattleDescription22.text=The %s organize a counterattack, using guerrilla tactics to strike at strongholds held by the %s and disrupt their plans. -abstractBattleDescription23.text=A group of noncombatants, caught in the middle of the conflict, band together to form a neutral faction, seeking to broker peace between the %s and %s. -abstractBattleDescription24.text=The %s attempt to disable the DropShip's navigation systems, threatening to strand the vessel. The %s are fighting to prevent this, but losing ground. -abstractBattleDescription25.text=The %s initiate a lockdown of critical ship systems, sealing off access points and restricting movement to contain the %s and prevent further bloodshed. -abstractBattleDescription26.text=The %s attempt to rally support by staging a daring rescue mission to liberate hostages held by the %s. -abstractBattleDescription27.text=The %s attempt to seize control of the DropShip's medical bay, using wounded crew members and hostages as leverage to ward off the %s. -abstractBattleDescription28.text=The %s deploy booby traps and improvised explosive devices throughout the ship, turning every corridor and compartment into a potential death trap for the %s. -abstractBattleDescription29.text=The %s struggle to maintain discipline as fear and paranoia spread, leading to accusations and suspicions that even their most loyal members have turned to the %s. -abstractBattleDescription30.text=The %s, facing dwindling supplies and escalating tensions, resort to extreme measures such as rationing food and water, further intensifying the conflict against the %s. +# Battle Condition Dialog +abstractBattleDescriptionCasualties.text=