diff --git a/MekHQ/resources/mekhq/resources/Campaign.properties b/MekHQ/resources/mekhq/resources/Campaign.properties index ce735dc81a4..ac79026737a 100644 --- a/MekHQ/resources/mekhq/resources/Campaign.properties +++ b/MekHQ/resources/mekhq/resources/Campaign.properties @@ -82,7 +82,9 @@ turnoverPersonnelKilled.text=You have personnel who have left the unit or divorce.text=%s has divorced %s. #### Unsorted Campaign Resources -dependentLeavesForce.text=%s is no longer traveling with the force. +dependentLeavesForce.text=%s %s have departed the force. +dependentLeavesForce.dependent.singular=dependent +dependentLeavesForce.dependent.plural=dependents dependentJoinsForce.text=%s has started traveling with the force. relativeJoinsForce.text=%s has started traveling with the force. They are %s's %s. relativeJoinsForceSpouse.text=spouse diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index d9af7a7c8dc..d2a46ecdcb2 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -92,6 +92,7 @@ import mekhq.campaign.personnel.education.Academy; import mekhq.campaign.personnel.education.EducationController; import mekhq.campaign.personnel.enums.*; +import mekhq.campaign.personnel.familyTree.Genealogy; import mekhq.campaign.personnel.generator.*; import mekhq.campaign.personnel.marriage.AbstractMarriage; import mekhq.campaign.personnel.marriage.DisabledRandomMarriage; @@ -4852,15 +4853,13 @@ public boolean newDay() { // check for anything in finances finances.newDay(this, yesterday, getLocalDate()); - // process removal of old personnel data on the last day of each month - if ((campaignOptions.isUsePersonnelRemoval()) - && (currentDay.getMonth().length(false) == currentDay.getDayOfMonth())) { + // process removal of old personnel data on the first day of each month + if ((campaignOptions.isUsePersonnelRemoval()) && (currentDay.getDayOfMonth() == 1)) { processPersonnelRemoval(); } // this duplicates any turnover information so that it is still available on the - // new day. - // otherwise, it's only available if the user inspects history records + // new day. otherwise, it's only available if the user inspects history records if (!turnoverRetirementInformation.isEmpty()) { for (String entry : turnoverRetirementInformation) { addReport(entry); @@ -5009,6 +5008,8 @@ private void processRandomDependents() { * @return The updated number of dependents. */ int dependentsRollForRemoval(List dependents, int dependentCapacity) { + List dependentsToRemove = new ArrayList<>(); + if (getCampaignOptions().isUseRandomDependentRemoval()) { for (Person dependent : dependents) { if (!isRemovalEligible(dependent, currentDay)) { @@ -5024,12 +5025,34 @@ int dependentsRollForRemoval(List dependents, int dependentCapacity) { int targetNumber = 5 - getAtBUnitRatingMod(); if (roll <= targetNumber) { - addReport(String.format(resources.getString("dependentLeavesForce.text"), - dependent.getFullTitle())); + dependentsToRemove.add(dependent); + + Genealogy genealogy = dependent.getGenealogy(); + for (Person child : genealogy.getChildren()) { + if (child.isChild(currentDay)) { + dependentsToRemove.add(child); + } + } - removePerson(dependent, false); + Person spouse = genealogy.getSpouse(); + if (spouse.isDependent()) { + dependentsToRemove.add(spouse); + } } } + + if (!dependentsToRemove.isEmpty()) { + String pluralizer = dependentsToRemove.size() == 1 + ? resources.getString("dependentLeavesForce.dependent.singular") + : resources.getString("dependentLeavesForce.dependent.plural"); + + addReport(String.format(resources.getString("dependentLeavesForce.text"), + dependentsToRemove.size(), pluralizer)); + } + + for (Person dependent : dependentsToRemove) { + dependent.changeStatus(this, currentDay, PersonnelStatus.LEFT); + } } return dependents.size(); @@ -5103,7 +5126,7 @@ public void processPersonnelRemoval() { PersonnelStatus status = person.getStatus(); if (status.isDepartedUnit()) { - if (shouldRemovePerson(person, status)) { + if (shouldRemovePerson(person)) { personnelToRemove.add(person); } } @@ -5120,39 +5143,50 @@ public void processPersonnelRemoval() { /** * Determines whether a person's records should be removed from the campaign - * based on their status and retirement month. + * based on their retirement date, date of death, personnel status, and genealogy activity. + * + *

The method evaluates the following conditions in order: + *

    + *
  • If the person has a retirement date and retirees are exempt from removal as per + * campaign options, the method returns {@code false}.
  • + *
  • If the person has a date of death, and cemeteries are exempt from removal as per + * campaign options, the method returns {@code false}.
  • + *
  • If the person has an active genealogy, the method returns {@code false}.
  • + *
  • If the person's retirement date is more than one month ago, the method returns {@code true}.
  • + *
  • If the person's date of death is more than one month ago, the method returns {@code true}.
  • + *
+ * + *

If none of the above conditions are met, the method returns {@code false}. * * @param person The individual being checked. - * @param status The personnel status of the individual. - * @return true if the person should be removed, false otherwise. + * @return {@code true} if the person should be removed, {@code false} otherwise. */ - private boolean shouldRemovePerson(Person person, PersonnelStatus status) { - // don't remove a character if they are related to a genealogy - // with at least one member still present in the campaign - Map> family = person.getGenealogy().getFamily(); - if (family.keySet().stream() - .flatMap(relationshipType -> family.get(relationshipType).stream()) - .anyMatch(relation -> relation.getStatus().isDepartedUnit())) { + private boolean shouldRemovePerson(Person person) { + // We do these checks first, as they're cheaper than parsing the entire genealogy + LocalDate retirementDate = person.getRetirement(); + if (retirementDate != null && campaignOptions.isUseRemovalExemptRetirees()) { return false; } - int retirementMonthValue; + LocalDate deathDate = person.getDateOfDeath(); + if (deathDate != null && campaignOptions.isUseRemovalExemptCemetery()) { + return false; + } - if (person.getRetirement() != null) { - retirementMonthValue = person.getRetirement().getMonthValue(); - } else { - person.setRetirement(getLocalDate()); + // Do not remove if the character has an active genealogy + Genealogy genealogy = person.getGenealogy(); + + if (genealogy.isActive()) { return false; } - // return true if the individual has left the campaign for over a month - // *AND* - // is dead (and we're not exempting the cemetery) *OR* is retired (and we're not - // exempting retirees) - return (retirementMonthValue < (getLocalDate().getMonthValue() + 1)) && - (((status.isDead()) && (!campaignOptions.isUseRemovalExemptCemetery())) - || ((status.isRetired()) && (!campaignOptions.isUseRemovalExemptRetirees()))); + // Did the departure occur more than a month ago? + LocalDate aMonthAgo = currentDay.minusMonths(1); + if (retirementDate != null && retirementDate.isBefore(aMonthAgo)) { + return true; + } + return deathDate != null && deathDate.isBefore(aMonthAgo); } /** @@ -9160,7 +9194,7 @@ public void writePartInUseMapToXML(final PrintWriter pw, int indent) { } } - /** + /** * Wipes the Parts in use map for the purpose of resetting all values to their default */ public void wipePartsInUseMap() { @@ -9186,13 +9220,13 @@ public ImageIcon getCampaignFactionIcon() { } return icon; } - + /** * Checks if another active scenario has this scenarioID as it's linkedScenarioID and returns true if it finds one. */ public boolean checkLinkedScenario(int scenarioID) { for (Scenario scenario : getScenarios()) { - if ((scenario.getLinkedScenario() == scenarioID) + if ((scenario.getLinkedScenario() == scenarioID) && (getScenario(scenario.getId()).getStatus().isCurrent())) { return true; } diff --git a/MekHQ/src/mekhq/campaign/personnel/familyTree/Genealogy.java b/MekHQ/src/mekhq/campaign/personnel/familyTree/Genealogy.java index 09151f7cca0..4722b6e85f6 100644 --- a/MekHQ/src/mekhq/campaign/personnel/familyTree/Genealogy.java +++ b/MekHQ/src/mekhq/campaign/personnel/familyTree/Genealogy.java @@ -433,6 +433,29 @@ public void clearGenealogyLinks() { } } + /** + * Checks if there is at least one person in the family who is not marked as "departed". + * + *

The method iterates through all relationship groups in the {@code family} map and checks + * the status of each person. If any person is found whose status is not marked as "departed", + * the method returns {@code true}. Otherwise, it returns {@code false} once all groups have + * been checked. + * + * @return {@code true} if at least one person in the family is active (not "departed"), + * {@code false} otherwise. + */ + public boolean isActive() { + for (List relationshipGroup : family.values()) { + for (Person relation : relationshipGroup) { + if (!relation.getStatus().isDepartedUnit()) { + return true; + } + } + } + + return false; + } + // region File I/O /** * @param pw the PrintWriter to write to