diff --git a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties index bd548b03d7..c2a76ad6a6 100644 --- a/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties +++ b/MekHQ/resources/mekhq/resources/CampaignOptionsDialog.properties @@ -568,11 +568,9 @@ chkEnableLocalAcademies.toolTip=Allows personnel to enroll in local, planet-side chkEnablePrestigiousAcademies.text=Enable Prestigious Academies chkEnablePrestigiousAcademies.toolTip=Allows personnel to enroll in named, canonical, academies. -xpAndSkillBonuses.title=Random XP and Skill Bonuses -chkEnableRandomXp.text=Random Monthly XP -chkEnableRandomXp.toolTip=Enables the per-month chance for students to gain additional xp (based on Faculty Skill). -lblRandomXpRate.text=Random XP Rate -lblRandomXpRate.toolTip=The amount of random xp gained per success. +xpAndSkillBonuses.title=Faculty XP and Skill Bonuses +lblFacultyXPMultiplier.text=Faculty XP Multiplier +lblFacultyXPMultiplier.toolTip=This value multiplies the number of XP gained when completing (or partially completing) a qualification. Setting this to 0 disabled faculty XP. chkEnableBonuses.text=Graduation Bonuses chkEnableBonuses.toolTip=Enables the chance of gaining a permanent skill bonus when graduating. diff --git a/MekHQ/resources/mekhq/resources/Education.properties b/MekHQ/resources/mekhq/resources/Education.properties index de9713e140..91a4cdf489 100644 --- a/MekHQ/resources/mekhq/resources/Education.properties +++ b/MekHQ/resources/mekhq/resources/Education.properties @@ -63,7 +63,7 @@ eventTrainingAccidentKilled.text=has suffered a serious training accident. Sadly dropOut.text=has dropped out of their education or training. Tomorrow they will begin their journey back to the unit. dropOutRejected.text=wanted to drop out of their education or training, but was convinced to stick it out. graduatedFailed.text=has failed to graduate. Tomorrow they will begin their journey back to the unit. -graduatedBarely.text=has barely graduated. Tomorrow they will begin their journey back to the unit. +graduatedBarely.text=has barely graduated from %s. Tomorrow they will begin their journey back to the unit. graduatedClassNeeded.text=still needs to complete one or more classes, and is unable to graduate. %s days have been added to their education. graduated.text=has attended their graduation ceremony%s. Tomorrow they will begin their journey back to the unit. graduatedHonors.text=has attended their graduation ceremony%s. They have graduated with honors. Tomorrow they will begin their journey back to the unit. diff --git a/MekHQ/src/mekhq/campaign/CampaignOptions.java b/MekHQ/src/mekhq/campaign/CampaignOptions.java index 47996edf3a..c7f28e3756 100644 --- a/MekHQ/src/mekhq/campaign/CampaignOptions.java +++ b/MekHQ/src/mekhq/campaign/CampaignOptions.java @@ -381,8 +381,7 @@ public static String getTransitUnitName(final int unit) { private boolean enablePrestigiousAcademies; private boolean enableOverrideRequirements; private boolean enableShowIneligibleAcademies; - private boolean enableRandomXp; - private Integer randomXpRate; + private Double facultyXpRate; private boolean enableBonuses; private Integer adultDropoutChance; private Integer childrenDropoutChance; @@ -888,8 +887,7 @@ public CampaignOptions() { setEnablePrestigiousAcademies(true); setEnableOverrideRequirements(false); setEnableShowIneligibleAcademies(true); - setEnableRandomXp(true); - setRandomXpRate(1); + setFacultyXpRate(1.00); setEnableBonuses(true); setAdultDropoutChance(1000); setChildrenDropoutChance(10000); @@ -2730,20 +2728,12 @@ public void setEnableShowIneligibleAcademies(boolean enableShowIneligibleAcademi this.enableShowIneligibleAcademies = enableShowIneligibleAcademies; } - public boolean isEnableRandomXp() { - return enableRandomXp; + public Double getFacultyXpRate() { + return facultyXpRate; } - public void setEnableRandomXp(boolean enableRandomXp) { - this.enableRandomXp = enableRandomXp; - } - - public Integer getRandomXpRate() { - return randomXpRate; - } - - public void setRandomXpRate(Integer randomXpRate) { - this.randomXpRate = randomXpRate; + public void setFacultyXpRate(Double facultyXpRate) { + this.facultyXpRate = facultyXpRate; } public boolean isEnableBonuses() { @@ -4696,8 +4686,7 @@ public void writeToXml(final PrintWriter pw, int indent) { MHQXMLUtility.writeSimpleXMLTag(pw, indent, "enablePrestigiousAcademies", isEnablePrestigiousAcademies()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "enableOverrideRequirements", isEnableOverrideRequirements()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "enableShowIneligibleAcademies", isEnableShowIneligibleAcademies()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "enableRandomXp", isEnableRandomXp()); - MHQXMLUtility.writeSimpleXMLTag(pw, indent, "randomXpRate", getRandomXpRate()); + MHQXMLUtility.writeSimpleXMLTag(pw, indent, "facultyXpRate", getFacultyXpRate()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "enableBonuses", isEnableBonuses()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "adultDropoutChance", getAdultDropoutChance()); MHQXMLUtility.writeSimpleXMLTag(pw, indent, "childrenDropoutChance", getChildrenDropoutChance()); @@ -5449,10 +5438,8 @@ public static CampaignOptions generateCampaignOptionsFromXml(Node wn, Version ve retVal.setEnableOverrideRequirements(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("enableShowIneligibleAcademies")) { retVal.setEnableShowIneligibleAcademies(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("enableRandomXp")) { - retVal.setEnableRandomXp(Boolean.parseBoolean(wn2.getTextContent().trim())); - } else if (wn2.getNodeName().equalsIgnoreCase("randomXpRate")) { - retVal.setRandomXpRate(Integer.parseInt(wn2.getTextContent().trim())); + } else if (wn2.getNodeName().equalsIgnoreCase("facultyXpRate")) { + retVal.setFacultyXpRate(Double.parseDouble(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("enableBonuses")) { retVal.setEnableBonuses(Boolean.parseBoolean(wn2.getTextContent().trim())); } else if (wn2.getNodeName().equalsIgnoreCase("adultDropoutChance")) { diff --git a/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java b/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java index 21764675c6..10810974e3 100644 --- a/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java +++ b/MekHQ/src/mekhq/campaign/personnel/education/EducationController.java @@ -32,7 +32,6 @@ import java.time.DayOfWeek; import java.util.*; -import java.util.stream.Collectors; /** * The EducationController class is responsible for managing the education process. @@ -175,22 +174,15 @@ public static String generateName(Academy academy, String campus) { * @throws IllegalStateException if an unexpected roll occurs */ private static String generateMilitaryPrefix(ResourceBundle resources) { - switch (Compute.d6(1)) { - case 1: - return resources.getString("prefixCombinedArms.text"); - case 2: - return resources.getString("prefixCombinedForces.text"); - case 3: - return resources.getString("prefixMilitary.text"); - case 4: - return resources.getString("prefixWar.text"); - case 5: - return resources.getString("prefixWarFighting.text"); - case 6: - return resources.getString("prefixCombat.text"); - default: - throw new IllegalStateException("Unexpected roll in generateMilitaryPrefix"); - } + return switch (Compute.d6(1)) { + case 1 -> resources.getString("prefixCombinedArms.text"); + case 2 -> resources.getString("prefixCombinedForces.text"); + case 3 -> resources.getString("prefixMilitary.text"); + case 4 -> resources.getString("prefixWar.text"); + case 5 -> resources.getString("prefixWarFighting.text"); + case 6 -> resources.getString("prefixCombat.text"); + default -> throw new IllegalStateException("Unexpected roll in generateMilitaryPrefix"); + }; } /** @@ -201,22 +193,15 @@ private static String generateMilitaryPrefix(ResourceBundle resources) { * @throws IllegalStateException if the random roll is unexpected. */ private static String generateSuffix(ResourceBundle resources) { - switch (Compute.d6(1)) { - case 1: - return resources.getString("suffixTechnology.text"); - case 2: - return resources.getString("suffixTechnologyAdvanced.text"); - case 3: - return resources.getString("suffixScience.text"); - case 4: - return resources.getString("suffixScienceAdvanced.text"); - case 5: - return resources.getString("suffixStudies.text"); - case 6: - return resources.getString("suffixHigherLearning.text"); - default: - throw new IllegalStateException("Unexpected roll in generateSuffix()"); - } + return switch (Compute.d6(1)) { + case 1 -> resources.getString("suffixTechnology.text"); + case 2 -> resources.getString("suffixTechnologyAdvanced.text"); + case 3 -> resources.getString("suffixScience.text"); + case 4 -> resources.getString("suffixScienceAdvanced.text"); + case 5 -> resources.getString("suffixStudies.text"); + case 6 -> resources.getString("suffixHigherLearning.text"); + default -> throw new IllegalStateException("Unexpected roll in generateSuffix()"); + }; } /** @@ -227,22 +212,15 @@ private static String generateSuffix(ResourceBundle resources) { * @throws IllegalStateException if the generated roll is unexpected */ private static String generateTypeAdult(ResourceBundle resources) { - switch (Compute.d6(1)) { - case 1: - return resources.getString("typeAcademy.text"); - case 2: - return resources.getString("typeCollege.text"); - case 3: - return resources.getString("typeInstitute.text"); - case 4: - return resources.getString("typeUniversity.text"); - case 5: - return resources.getString("typePolytechnic.text"); - case 6: - return resources.getString("typeSchool.text"); - default: - throw new IllegalStateException("Unexpected roll in generateTypeAdult"); - } + return switch (Compute.d6(1)) { + case 1 -> resources.getString("typeAcademy.text"); + case 2 -> resources.getString("typeCollege.text"); + case 3 -> resources.getString("typeInstitute.text"); + case 4 -> resources.getString("typeUniversity.text"); + case 5 -> resources.getString("typePolytechnic.text"); + case 6 -> resources.getString("typeSchool.text"); + default -> throw new IllegalStateException("Unexpected roll in generateTypeAdult"); + }; } @@ -254,22 +232,15 @@ private static String generateTypeAdult(ResourceBundle resources) { * @throws IllegalStateException if the generated roll is unexpected */ private static String generateTypeChild(ResourceBundle resources) { - switch (Compute.d6(1)) { - case 1: - return resources.getString("typeAcademy.text"); - case 2: - return resources.getString("typePreparatorySchool.text"); - case 3: - return resources.getString("typeInstitute.text"); - case 4: - return resources.getString("typeSchoolBoarding.text"); - case 5: - return resources.getString("typeFinishingSchool.text"); - case 6: - return resources.getString("typeSchool.text"); - default: - throw new IllegalStateException("Unexpected roll in generateTypeAdult"); - } + return switch (Compute.d6(1)) { + case 1 -> resources.getString("typeAcademy.text"); + case 2 -> resources.getString("typePreparatorySchool.text"); + case 3 -> resources.getString("typeInstitute.text"); + case 4 -> resources.getString("typeSchoolBoarding.text"); + case 5 -> resources.getString("typeFinishingSchool.text"); + case 6 -> resources.getString("typeSchool.text"); + default -> throw new IllegalStateException("Unexpected roll in generateTypeChild"); + }; } /** @@ -348,24 +319,16 @@ private static boolean ongoingEducation(Campaign campaign, Person person, Academ person.setEduEducationStage(EducationStage.GRADUATING); - if (campaign.getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY) { - processNewWeekChecks(campaign, academy, person, resources); - } - return true; } - if (campaign.getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY) { - processNewWeekChecks(campaign, academy, person, resources); - } + checkForEvents(campaign, person, academy, resources); return false; } else { person.setEduEducationTime(daysOfEducation - 1); - if (campaign.getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY) { - processNewWeekChecks(campaign, academy, person, resources); - } + checkForEvents(campaign, person, academy, resources); // we use 2 as that would be the value prior the day's decrement if (daysOfEducation < 2) { @@ -380,6 +343,24 @@ private static boolean ongoingEducation(Campaign campaign, Person person, Academ return false; } + /** + * Checks for any events based on the current date of the campaign. + * + * @param campaign the campaign to check events for + * @param person the person involved in the campaign + * @param academy the academy related to the campaign + * @param resources the resource bundle for localized messages + */ + private static void checkForEvents(Campaign campaign, Person person, Academy academy, ResourceBundle resources) { + if (campaign.getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY) { + processNewWeekChecks(campaign, academy, person, resources); + } + + if (campaign.getLocalDate().getDayOfYear() == 1) { + processNewYearChecks(campaign, academy, person, resources); + } + } + /** * Picks the appropriate graduation method based on the given campaign, person, academy, and resources. * @@ -466,46 +447,46 @@ public static Academy getAcademy(String academySetName, String academyNameInSet) * @param resources The resource bundle used for localized strings. */ private static void processNewWeekChecks(Campaign campaign, Academy academy, Person person, ResourceBundle resources) { - if ((campaign.getCampaignOptions().isEnableRandomXp()) && (campaign.getLocalDate().getDayOfMonth() == 1)) { - if (Compute.d6(2) >= academy.getFacultySkill()) { - person.awardXP(campaign, campaign.getCampaignOptions().getRandomXpRate()); + // has the system been depopulated? Nominally similar to destruction, but here we use actual system data, so it's more dynamic. + if (campaign.getSystemById(person.getEduAcademySystem()).getPopulation(campaign.getLocalDate()) == 0) { + if (checkForAcademyDestruction(campaign, academy, person, resources)) { + return; } } - if (!person.getEduEducationStage().isGraduating()) { - // It's unlikely we'll ever get canonical destruction or closure dates for all the academies, - // so no need to check these more than once a year - if (campaign.getLocalDate().getDayOfYear() == 1) { - // time to check whether the academy is still standing. - if (checkForAcademyDestruction(campaign, academy, person, resources)) { - return; - } - - // is the academy still open? - if (checkForAcademyClosure(campaign, academy, person, resources)) { - return; - } - } + // is the academy faction at war with person faction, or the campaign faction? + if (checkForAcademyFactionConflict(campaign, academy, person, resources)) { + return; + } - // has the system been depopulated? Nominally similar to the above, but here we use actual system data, so it's more dynamic. - if (campaign.getSystemById(person.getEduAcademySystem()).getPopulation(campaign.getLocalDate()) == 0) { - if (checkForAcademyDestruction(campaign, academy, person, resources)) { - return; - } - } + // does person want to drop out? + if (checkForDropout(campaign, academy, person, resources)) { + return; + } - // is the academy faction at war with person faction, or the campaign faction? - if (checkForAcademyFactionConflict(campaign, academy, person, resources)) { - return; - } + // was there a training accident? + checkForTrainingAccidents(campaign, academy, person, resources); + } - // does person want to drop out? - if (checkForDropout(campaign, academy, person, resources)) { + /** + * Processes the new year checks for a specific person in a campaign. + * + * @param campaign The campaign in which the person is participating. + * @param academy The academy where the person is receiving education. + * @param person The person whose new month checks need to be processed. + * @param resources The resource bundle used for localized strings. + */ + private static void processNewYearChecks(Campaign campaign, Academy academy, Person person, ResourceBundle resources) { + // It's unlikely we'll ever get canonical destruction or closure dates for all the academies, + // so no need to check these more than once a year + if (campaign.getLocalDate().getDayOfYear() == 1) { + // time to check whether the academy is still standing. + if (checkForAcademyDestruction(campaign, academy, person, resources)) { return; } - // was there a training accident? - checkForTrainingAccidents(campaign, academy, person, resources); + // is the academy still open? + checkForAcademyClosure(campaign, academy, person, resources); } } @@ -601,6 +582,7 @@ private static boolean checkForDropout(Campaign campaign, Academy academy, Perso campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("dropOut.text")); ServiceLogger.eduFailed(person, campaign.getLocalDate(), person.getEduAcademyName(), academy.getQualifications().get(person.getEduCourseIndex())); person.setEduEducationStage(EducationStage.DROPPING_OUT); + addFacultyXp(campaign, person, academy, 0); } } else { campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("dropOutRejected.text")); @@ -643,16 +625,12 @@ private static boolean checkForAcademyFactionConflict(Campaign campaign, Academy * @param academy the academy being checked for closure * @param person the person enrolled in the academy * @param resources the resource bundle for localization. - * @return true if the academy has been closed, false otherwise */ - private static boolean checkForAcademyClosure(Campaign campaign, Academy academy, Person person, ResourceBundle resources) { + private static void checkForAcademyClosure(Campaign campaign, Academy academy, Person person, ResourceBundle resources) { if (campaign.getLocalDate().getYear() >= academy.getClosureYear()) { campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("eventClosure.text")); person.setEduEducationStage(EducationStage.DROPPING_OUT); - - return true; } - return false; } /** @@ -705,6 +683,7 @@ private static boolean graduateAdult(Campaign campaign, Person person, Academy a ServiceLogger.eduFailed(person, campaign.getLocalDate(), person.getEduAcademyName(), academy.getQualifications().get(person.getEduCourseIndex())); improveSkills(campaign, person, academy, false); + addFacultyXp(campaign, person, academy, 0); person.setEduEducationStage(EducationStage.DROPPING_OUT); @@ -820,19 +799,12 @@ private static void reportMastersOrDoctorateGain(Campaign campaign, Person perso private static void graduateChild(Campaign campaign, Person person, Academy academy, ResourceBundle resources) { int graduationRoll = Compute.randomInt(100); - // qualification failed if (graduationRoll < 30) { - campaign.addReport(person.getHyperlinkedName() + ' ' + resources.getString("graduatedBarely.text")); - ServiceLogger.eduFailed(person, campaign.getLocalDate(), person.getEduAcademyName(), academy.getQualifications().get(person.getEduCourseIndex())); - - improveSkills(campaign, person, academy, false); - - return; + campaign.addReport(person.getHyperlinkedName() + ' ' + String.format(resources.getString("graduatedBarely.text"), person.getEduAcademyName())); + } else { + campaign.addReport(person.getHyperlinkedName() + ' ' + String.format(resources.getString("graduatedChild.text"), person.getEduAcademyName())); } - // default graduation - campaign.addReport(person.getHyperlinkedName() + ' ' + String.format(resources.getString("graduatedChild.text"), person.getEduAcademyName())); - ServiceLogger.eduGraduated(person, campaign.getLocalDate(), person.getEduAcademyName(), academy.getQualifications().get(person.getEduCourseIndex())); processGraduation(campaign, person, academy, 0, resources); @@ -867,6 +839,8 @@ private static void adjustLoyalty(Person person) { private static void processGraduation(Campaign campaign, Person person, Academy academy, Integer bonusCount, ResourceBundle resources) { improveSkills(campaign, person, academy, true); + addFacultyXp(campaign, person, academy, bonusCount); + if ((campaign.getCampaignOptions().isEnableBonuses()) && (bonusCount > 0)) { addBonus(campaign, person, academy, bonusCount, resources); } @@ -956,6 +930,38 @@ private static void improveSkills(Campaign campaign, Person person, Academy acad } } + /** + * Adds faculty XP to a person based on faculty skill and academy duration. + * + * @param campaign the campaign the person is participating in + * @param person the person receiving the bonus XP + * @param academy the academy attended by the person + * @param bonusCount the number of extra bonus XP to be added (based on graduation level) + */ + private static void addFacultyXp(Campaign campaign, Person person, Academy academy, Integer bonusCount) { + int academyDuration; + + if (academy.isPrepSchool()) { + academyDuration = (person.getAge(campaign.getLocalDate()) - academy.getAgeMin()) * 300; + } else { + academyDuration = academy.getDurationDays() - person.getEduEducationTime(); + } + + double bonusPercentage = (double) bonusCount / 5; + + if (EducationLevel.parseToInt(person.getEduHighestEducation()) < academy.getEducationLevel(person)) { + int xpRate = Math.max(1, (12 - academy.getFacultySkill()) * (academyDuration / 600)); + + xpRate *= campaign.getCampaignOptions().getFacultyXpRate(); + + int bonusAmount = (int) Math.max(bonusCount, xpRate * bonusPercentage); + person.awardXP(campaign, xpRate + bonusAmount); + } else { + int bonusAmount = (int) Math.max(bonusCount, 1 * bonusPercentage); + person.awardXP(campaign, 1 + bonusAmount); + } + } + /** * Adds bonus to a number of skills based on the course curriculum. * @@ -969,7 +975,7 @@ private static void addBonus(Campaign campaign, Person person, Academy academy, curriculum = curriculum.stream() .map(String::trim) - .collect(Collectors.toList()); + .toList(); for (int i = 0; i < bonusCount; i++) { int roll = Compute.randomInt(curriculum.size()); diff --git a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java index 1fa171b5ce..68001fb46d 100644 --- a/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java +++ b/MekHQ/src/mekhq/gui/panes/CampaignOptionsPane.java @@ -454,10 +454,9 @@ public class CampaignOptionsPane extends AbstractMHQTabbedPane { private JCheckBox chkEnablePrestigiousAcademies; private JCheckBox chkShowIneligibleAcademies; private JCheckBox chkEnableOverrideRequirements; - private JCheckBox chkEnableRandomXp; private JCheckBox chkEnableBonuses; - private JLabel lblRandomXpRate; - private JSpinner spnRandomXpRate; + private JLabel lblFacultyXpMultiplier; + private JSpinner spnFacultyXpMultiplier; private JLabel lblAdultDropoutChance; private JSpinner spnAdultDropoutChance; private JLabel lblChildrenDropoutChance; @@ -1082,9 +1081,7 @@ private JScrollPane createRepairAndMaintenanceTab() { gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; panSubMaintenance.add(chkUseRandomUnitQualities, gridBagConstraints); - reverseQualityNames.addActionListener(evt -> { - recreateFinancesPanel(reverseQualityNames.isSelected()); - }); + reverseQualityNames.addActionListener(evt -> recreateFinancesPanel(reverseQualityNames.isSelected())); useUnofficialMaintenance = new JCheckBox(resources.getString("useUnofficialMaintenance.text")); useUnofficialMaintenance.setToolTipText(resources.getString("useUnofficialMaintenance.toolTipText")); @@ -6107,9 +6104,8 @@ private JPanel createEducationPanel() { xpAndSkillBonusesPanel.setEnabled(isEnabled); chkEnableBonuses.setEnabled(isEnabled); - lblRandomXpRate.setEnabled(isEnabled); - spnRandomXpRate.setEnabled(isEnabled); - chkEnableRandomXp.setEnabled(isEnabled); + lblFacultyXpMultiplier.setEnabled(isEnabled); + spnFacultyXpMultiplier.setEnabled(isEnabled); dropoutChancePanel.setEnabled(isEnabled); lblAdultDropoutChance.setEnabled(isEnabled); @@ -6231,33 +6227,15 @@ private JPanel createXpAndSkillBonusesPanel() { chkEnableBonuses.setToolTipText(resources.getString("chkEnableBonuses.toolTip")); chkEnableBonuses.setName("chkEnableBonuses"); - lblRandomXpRate = new JLabel(resources.getString("lblRandomXpRate.text")); - lblRandomXpRate.setToolTipText(resources.getString("lblRandomXpRate.toolTip")); - lblRandomXpRate.setName("lblRandomXpRate"); - spnRandomXpRate = new JSpinner(new SpinnerNumberModel(1, 1, 10, 1)); - spnRandomXpRate.setToolTipText(resources.getString("lblRandomXpRate.toolTip")); - spnRandomXpRate.setName("spnRandomXpRate"); - - chkEnableRandomXp = new JCheckBox(resources.getString("chkEnableRandomXp.text")); - chkEnableRandomXp.setToolTipText(resources.getString("chkEnableRandomXp.toolTip")); - chkEnableRandomXp.setName("chkEnableRandomXp"); - chkEnableRandomXp.addActionListener(evt -> { - final boolean isEnabled = chkEnableRandomXp.isSelected(); + lblFacultyXpMultiplier = new JLabel(resources.getString("lblFacultyXPMultiplier.text")); + lblFacultyXpMultiplier.setToolTipText(resources.getString("lblFacultyXPMultiplier.toolTip")); + lblFacultyXpMultiplier.setName("lblFacultyXpMultiplier"); - lblRandomXpRate.setEnabled(isEnabled); - }); + spnFacultyXpMultiplier = new JSpinner(new SpinnerNumberModel(1.00, 0.00, 10.00, 0.01)); + spnFacultyXpMultiplier.setToolTipText(resources.getString("lblFacultyXPMultiplier.toolTip")); + spnFacultyXpMultiplier.setName("spnFacultyXpMultiplier"); - // These prevent a really annoying bug where disabled options don't stay disabled when - // reloading Campaign Options - if ((campaign.getCampaignOptions().isEnableRandomXp()) && (campaign.getCampaignOptions().isUseEducationModule())) { - lblRandomXpRate.setEnabled(true); - spnRandomXpRate.setEnabled(true); - } else { - lblRandomXpRate.setEnabled(false); - spnRandomXpRate.setEnabled(false); - } chkEnableBonuses.setEnabled(campaign.getCampaignOptions().isUseEducationModule()); - chkEnableRandomXp.setEnabled(campaign.getCampaignOptions().isUseEducationModule()); // creating the layout final JPanel panel = new JPanel(); @@ -6272,19 +6250,17 @@ private JPanel createXpAndSkillBonusesPanel() { layout.setVerticalGroup( layout.createSequentialGroup() .addComponent(chkEnableBonuses) - .addComponent(chkEnableRandomXp) .addGroup(layout.createParallelGroup(Alignment.BASELINE) - .addComponent(lblRandomXpRate) - .addComponent(spnRandomXpRate, Alignment.LEADING)) + .addComponent(lblFacultyXpMultiplier) + .addComponent(spnFacultyXpMultiplier, Alignment.LEADING)) ); layout.setHorizontalGroup( layout.createParallelGroup(Alignment.LEADING) .addComponent(chkEnableBonuses) - .addComponent(chkEnableRandomXp) .addGroup(layout.createSequentialGroup() - .addComponent(lblRandomXpRate) - .addComponent(spnRandomXpRate)) + .addComponent(lblFacultyXpMultiplier) + .addComponent(spnFacultyXpMultiplier)) ); return panel; @@ -8162,8 +8138,7 @@ public void setOptions(@Nullable CampaignOptions options, chkEnablePrestigiousAcademies.setSelected(options.isEnablePrestigiousAcademies()); chkEnableOverrideRequirements.setSelected(options.isEnableOverrideRequirements()); chkShowIneligibleAcademies.setSelected(options.isEnableShowIneligibleAcademies()); - chkEnableRandomXp.setSelected(options.isEnableRandomXp()); - spnRandomXpRate.setValue(options.getRandomXpRate()); + spnFacultyXpMultiplier.setValue(options.getFacultyXpRate()); chkEnableBonuses.setSelected(options.isEnableBonuses()); spnAdultDropoutChance.setValue(options.getAdultDropoutChance()); spnChildrenDropoutChance.setValue(options.getChildrenDropoutChance()); @@ -8824,8 +8799,7 @@ public void updateOptions() { options.setEnablePrestigiousAcademies(chkEnablePrestigiousAcademies.isSelected()); options.setEnableOverrideRequirements(chkEnableOverrideRequirements.isSelected()); options.setEnableShowIneligibleAcademies(chkShowIneligibleAcademies.isSelected()); - options.setEnableRandomXp(chkEnableRandomXp.isSelected()); - options.setRandomXpRate((Integer) spnRandomXpRate.getValue()); + options.setFacultyXpRate((Double) spnFacultyXpMultiplier.getValue()); options.setEnableBonuses(chkEnableBonuses.isSelected()); options.setAdultDropoutChance((Integer) spnAdultDropoutChance.getValue()); options.setChildrenDropoutChance((Integer) spnChildrenDropoutChance.getValue()); @@ -9397,8 +9371,7 @@ public void addNotify() { super.addNotify(); Component c = getParent(); // Keep scrolling of the row table in sync with the main table. - if (c instanceof JViewport) { - JViewport viewport = (JViewport) c; + if (c instanceof JViewport viewport) { viewport.addChangeListener(this); } }