From 53e09f620ab7b6fa033d95a49ce35827b62adafb Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 4 Oct 2024 02:27:57 -0500 Subject: [PATCH 1/2] Refactored Daily Personnel Processing Logic. Consolidated repeated code into private methods for better readability and maintainability. Updated `processNewDayPersonnel` to handle all personnel, avoiding potential `ConcurrentModificationExceptions`. --- MekHQ/src/mekhq/campaign/Campaign.java | 305 ++++++++++++++++--------- 1 file changed, 194 insertions(+), 111 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index 3a8831b2d6..fd949c8059 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3750,9 +3750,9 @@ private void processNewDayATB() { } /** - * Processes the new day for all active personnel. + * Processes the new day for all personnel present in the campaign. *

- * This method loops through all active personnel and performs the necessary actions + * This method loops through all personnel present and performs the necessary actions * for each person for a new day. *

* The following tasks are performed for each person: @@ -3778,9 +3778,15 @@ private void processNewDayATB() { * Note: This method uses several other methods to perform the specific actions for each task. */ public void processNewDayPersonnel() { - // This MUST use getActivePersonnel as we only want to process active personnel, and - // furthermore, this allows us to add and remove personnel without issue - for (Person person : getActivePersonnel()) { + List personnelForRelationshipProcessing = new ArrayList<>(); + + for (Person person : getPersonnel()) { + if (person.getStatus().isDepartedUnit()) { + continue; + } + + personnelForRelationshipProcessing.add(person); + // Death if (getDeath().processNewDay(this, getLocalDate(), person)) { // The person has died, so don't continue to process the dead @@ -3790,146 +3796,223 @@ public void processNewDayPersonnel() { person.resetMinutesLeft(); // Reset acquisitions made to 0 person.setAcquisition(0); - if (person.needsFixing() && !getCampaignOptions().isUseAdvancedMedical()) { - person.decrementDaysToWaitForHealing(); - Person doctor = getPerson(person.getDoctorId()); - if ((doctor != null) && doctor.isDoctor()) { - if (person.getDaysToWaitForHealing() <= 0) { - addReport(healPerson(person, doctor)); - } - } else if (person.checkNaturalHealing(15)) { - addReport(person.getHyperlinkedFullTitle() + " heals naturally!"); - Unit u = person.getUnit(); - if (u != null) { - u.resetPilotAndEntity(); - } - } - } - // TODO Advanced Medical needs to go away from here later on - if (getCampaignOptions().isUseAdvancedMedical()) { - InjuryUtil.resolveDailyHealing(this, person); - Unit u = person.getUnit(); - if (u != null) { - u.resetPilotAndEntity(); - } - } - // TODO : Reset this based on hasSupportRole(false) instead of checking for each type - // TODO : person.isEngineer will need to stay, however + processAdvancedMedicalEvents(person); + // Reset edge points to the purchased value each week. This should only // apply for support personnel - combat troops reset with each new mm game - if ((person.isAdministrator() || person.isDoctor() || person.isEngineer() || person.isTech()) - && (getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY)) { - person.resetCurrentEdge(); - } - - if ((getCampaignOptions().getIdleXP() > 0) && (getLocalDate().getDayOfMonth() == 1) - && !person.getPrisonerStatus().isCurrentPrisoner()) { // Prisoners can't gain XP, while Bondsmen can gain xp - person.setIdleMonths(person.getIdleMonths() + 1); - if (person.getIdleMonths() >= getCampaignOptions().getMonthsIdleXP()) { - if (Compute.d6(2) >= getCampaignOptions().getTargetIdleXP()) { - person.awardXP(this, getCampaignOptions().getIdleXP()); - addReport(person.getHyperlinkedFullTitle() + " has gained " - + getCampaignOptions().getIdleXP() + " XP"); - } - person.setIdleMonths(0); + processWeeklyEdgeResets(person); + + processMonthlyIdleXP(person); + + // Anniversaries + processAnniversaries(person); + + // autoAwards + processMonthlyAutoAwards(person); + } + + // Divorce, Marriage + // This has to be processed separately to avoid a ConcurrentModificationException + for (Person person : personnelForRelationshipProcessing) { + processWeeklyRelationshipEvents(person); + } + } + + /** + * Processes advanced medical events for a person. + * + * @param person the {@link Person} to process advanced medical events for + */ + private void processAdvancedMedicalEvents(Person person) { + if (person.needsFixing() && !getCampaignOptions().isUseAdvancedMedical()) { + person.decrementDaysToWaitForHealing(); + Person doctor = getPerson(person.getDoctorId()); + if ((doctor != null) && doctor.isDoctor()) { + if (person.getDaysToWaitForHealing() <= 0) { + addReport(healPerson(person, doctor)); + } + } else if (person.checkNaturalHealing(15)) { + addReport(person.getHyperlinkedFullTitle() + " heals naturally!"); + Unit unit = person.getUnit(); + if (unit != null) { + unit.resetPilotAndEntity(); } } + } + // TODO Advanced Medical needs to go away from here later on + if (getCampaignOptions().isUseAdvancedMedical()) { + InjuryUtil.resolveDailyHealing(this, person); + Unit unit = person.getUnit(); + if (unit != null) { + unit.resetPilotAndEntity(); + } + } + } - // Divorce, Marriage, & Procreation - if (getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY) { - getDivorce().processNewWeek(this, getLocalDate(), person, false); - getMarriage().processNewWeek(this, getLocalDate(), person); - getProcreation().processNewWeek(this, getLocalDate(), person); + /** + * Process weekly Edge resets for a given person. + * + * @param person the person for whom weekly Edge resets will be processed + */ + private void processWeeklyEdgeResets(Person person) { + if ((person.hasSupportRole(true) || person.isEngineer()) + && (getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY)) { + person.resetCurrentEdge(); + } + } - if (person.getGender().isFemale()) { - if (campaignOptions.isUseMaternityLeave()) { - if ((person.isPregnant()) - && (person.getStatus().isActive()) - && (person.getDueDate().minusWeeks(20).isEqual(getLocalDate()))) { + /** + * Process monthly idle XP for a given person. + * This method checks if the person is eligible to gain idle XP based on campaign options and current status. + * If the person meets the criteria, they may gain XP and an associated report will be added. + * + * @param person The person for whom to process monthly idle XP. + */ + private void processMonthlyIdleXP(Person person) { + if ((getCampaignOptions().getIdleXP() > 0) && (getLocalDate().getDayOfMonth() == 1) + && !person.getPrisonerStatus().isCurrentPrisoner()) { // Prisoners can't gain XP, while Bondsmen can gain xp + person.setIdleMonths(person.getIdleMonths() + 1); + if (person.getIdleMonths() >= getCampaignOptions().getMonthsIdleXP()) { + if (Compute.d6(2) >= getCampaignOptions().getTargetIdleXP()) { + person.awardXP(this, getCampaignOptions().getIdleXP()); + addReport(person.getHyperlinkedFullTitle() + " has gained " + + getCampaignOptions().getIdleXP() + " XP"); + } + person.setIdleMonths(0); + } + } + } - person.changeStatus(this, getLocalDate(), PersonnelStatus.ON_MATERNITY_LEAVE); - } + /** + * Process weekly relationship events for a given {@link Person} on Monday. + * This method triggers specific events related to divorce, marriage, procreation, and maternity leave. + * + * @param person The {@link Person} for which to process weekly relationship events + */ + private void processWeeklyRelationshipEvents(Person person) { + if (getLocalDate().getDayOfWeek() == DayOfWeek.MONDAY) { + getDivorce().processNewWeek(this, getLocalDate(), person, false); + getMarriage().processNewWeek(this, getLocalDate(), person); + getProcreation().processNewWeek(this, getLocalDate(), person); - List children = person.getGenealogy().getChildren(); + if (person.getGender().isFemale()) { + if (campaignOptions.isUseMaternityLeave()) { + if ((person.isPregnant()) + && (person.getStatus().isActive()) + && (person.getDueDate().minusWeeks(20).isEqual(getLocalDate()))) { - if ((person.getStatus().isOnMaternityLeave()) && (!children.isEmpty())) { + person.changeStatus(this, getLocalDate(), PersonnelStatus.ON_MATERNITY_LEAVE); + } - children.sort(Comparator.comparing(Person::getDateOfBirth).reversed()); + List children = person.getGenealogy().getChildren(); - if (getLocalDate().isAfter(children.get(0).getDateOfBirth().plusDays(41))) { - person.changeStatus(this, getLocalDate(), PersonnelStatus.ACTIVE); - } + if ((person.getStatus().isOnMaternityLeave()) && (!children.isEmpty())) { + LocalDate lastChildBirthDate = getYoungestChildDateOfBirth(children); + + if (currentDay.isAfter(lastChildBirthDate)) { + person.changeStatus(this, getLocalDate(), PersonnelStatus.ACTIVE); } } } } + } + } - // Anniversaries - if ((person.getRank().isOfficer()) || (!getCampaignOptions().isAnnounceOfficersOnly())) { - if ((person.getBirthday(getGameYear()).isEqual(getLocalDate())) - && (campaignOptions.isAnnounceBirthdays())) { - addReport(String.format(resources.getString("anniversaryBirthday.text"), + /** + * Process anniversaries for a given person, including birthdays and recruitment anniversaries. + * + * @param person The {@link Person} for whom the anniversaries will be processed + */ + private void processAnniversaries(Person person) { + if ((person.getRank().isOfficer()) || (!getCampaignOptions().isAnnounceOfficersOnly())) { + if ((person.getBirthday(getGameYear()).isEqual(getLocalDate())) + && (campaignOptions.isAnnounceBirthdays())) { + addReport(String.format(resources.getString("anniversaryBirthday.text"), + person.getHyperlinkedFullTitle(), + ReportingUtilities.spanOpeningWithCustomColor(MekHQ.getMHQOptions().getFontColorPositiveHexColor()), + person.getAge(getLocalDate()), + CLOSING_SPAN_TAG)); + } + + LocalDate recruitmentDate = person.getRecruitment(); + if (recruitmentDate != null) { + LocalDate recruitmentAnniversary = recruitmentDate.withYear(getGameYear()); + int yearsOfEmployment = (int) ChronoUnit.YEARS.between(recruitmentDate, currentDay); + + if ((recruitmentAnniversary.isEqual(getLocalDate())) + && (campaignOptions.isAnnounceRecruitmentAnniversaries())) { + addReport(String.format(resources.getString("anniversaryRecruitment.text"), person.getHyperlinkedFullTitle(), ReportingUtilities.spanOpeningWithCustomColor(MekHQ.getMHQOptions().getFontColorPositiveHexColor()), - person.getAge(getLocalDate()), - CLOSING_SPAN_TAG)); + yearsOfEmployment, CLOSING_SPAN_TAG, name)); } + } + } else if ((person.getAge(getLocalDate()) == 18) && (campaignOptions.isAnnounceChildBirthdays())) { + if (person.getBirthday(getGameYear()).isEqual(getLocalDate())) { + addReport(String.format(resources.getString("anniversaryBirthday.text"), + person.getHyperlinkedFullTitle(), + ReportingUtilities.spanOpeningWithCustomColor(MekHQ.getMHQOptions().getFontColorPositiveHexColor()), + person.getAge(getLocalDate()), + CLOSING_SPAN_TAG)); + } + } + } - LocalDate recruitmentDate = person.getRecruitment(); - if (recruitmentDate != null) { - LocalDate recruitmentAnniversary = recruitmentDate.withYear(getGameYear()); - int yearsOfEmployment = (int) ChronoUnit.YEARS.between(recruitmentDate, currentDay); + /** + * Process monthly auto awards for a given person based on their roles and experience level. + * + * @param person the person for whom the monthly auto awards are being processed + */ + private void processMonthlyAutoAwards(Person person) { + if (getLocalDate().getDayOfMonth() == 1) { + double multiplier = 0; - if ((recruitmentAnniversary.isEqual(getLocalDate())) - && (campaignOptions.isAnnounceRecruitmentAnniversaries())) { - addReport(String.format(resources.getString("anniversaryRecruitment.text"), - person.getHyperlinkedFullTitle(), - ReportingUtilities.spanOpeningWithCustomColor(MekHQ.getMHQOptions().getFontColorPositiveHexColor()), - yearsOfEmployment, CLOSING_SPAN_TAG, name)); - } - } - } else if ((person.getAge(getLocalDate()) == 18) && (campaignOptions.isAnnounceChildBirthdays())) { - if (person.getBirthday(getGameYear()).isEqual(getLocalDate())) { - addReport(String.format(resources.getString("anniversaryBirthday.text"), - person.getHyperlinkedFullTitle(), - ReportingUtilities.spanOpeningWithCustomColor(MekHQ.getMHQOptions().getFontColorPositiveHexColor()), - person.getAge(getLocalDate()), - CLOSING_SPAN_TAG)); - } - } + int score = 0; - // autoAwards - if (getLocalDate().getDayOfMonth() == 1) { - double multiplier = 0; + if (person.getPrimaryRole().isSupport(true)) { + int dice = person.getExperienceLevel(this, false); - int score = 0; + if (dice > 0) { + score = Compute.d6(dice); + } - if (person.getPrimaryRole().isSupport(true)) { - int dice = person.getExperienceLevel(this, false); + multiplier += 0.5; + } - if (dice > 0) { - score = Compute.d6(dice); - } + if (person.getSecondaryRole().isSupport(true)) { + int dice = person.getExperienceLevel(this, true); - multiplier += 0.5; + if (dice > 0) { + score += Compute.d6(dice); } - if (person.getSecondaryRole().isSupport(true)) { - int dice = person.getExperienceLevel(this, true); + multiplier += 0.5; + } else if (person.getSecondaryRole().isNone()) { + multiplier += 0.5; + } - if (dice > 0) { - score += Compute.d6(dice); - } + person.changeAutoAwardSupportPoints((int) (score * multiplier)); + } + } - multiplier += 0.5; - } else if (person.getSecondaryRole().isNone()) { - multiplier += 0.5; - } + /** + * Retrieves the date of birth of the youngest child among the provided list of children. + * + * @param children the list children + * @return the date of birth of the youngest child + */ + private LocalDate getYoungestChildDateOfBirth(List children) { + LocalDate youngestChildBirthDate = LocalDate.MIN; - person.changeAutoAwardSupportPoints((int) (score * multiplier)); + for (Person child : children) { + LocalDate dateOfBirth = child.getDateOfBirth(); + if (dateOfBirth.isAfter(youngestChildBirthDate)) { + youngestChildBirthDate = dateOfBirth; } } + + return youngestChildBirthDate; } public void processNewDayUnits() { From 64f815821ff261d33d12e9f8722a609b3c5876e0 Mon Sep 17 00:00:00 2001 From: IllianiCBT Date: Fri, 4 Oct 2024 02:30:44 -0500 Subject: [PATCH 2/2] Prevent idle XP accrual for inactive persons Added a check to return early in the processMonthlyIdleXP function if the person is not active. --- MekHQ/src/mekhq/campaign/Campaign.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index fd949c8059..16efae2c7d 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -3870,6 +3870,10 @@ private void processWeeklyEdgeResets(Person person) { * @param person The person for whom to process monthly idle XP. */ private void processMonthlyIdleXP(Person person) { + if (!person.getStatus().isActive()) { + return; + } + if ((getCampaignOptions().getIdleXP() > 0) && (getLocalDate().getDayOfMonth() == 1) && !person.getPrisonerStatus().isCurrentPrisoner()) { // Prisoners can't gain XP, while Bondsmen can gain xp person.setIdleMonths(person.getIdleMonths() + 1);