Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored Personnel Cleanup and Random Dependent Removal #5871

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion MekHQ/resources/mekhq/resources/Campaign.properties
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ turnoverPersonnelKilled.text=<html>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
Expand Down
102 changes: 68 additions & 34 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -5009,6 +5008,8 @@ private void processRandomDependents() {
* @return The updated number of dependents.
*/
int dependentsRollForRemoval(List<Person> dependents, int dependentCapacity) {
List<Person> dependentsToRemove = new ArrayList<>();

if (getCampaignOptions().isUseRandomDependentRemoval()) {
for (Person dependent : dependents) {
if (!isRemovalEligible(dependent, currentDay)) {
Expand All @@ -5024,12 +5025,34 @@ int dependentsRollForRemoval(List<Person> 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();
Expand Down Expand Up @@ -5103,7 +5126,7 @@ public void processPersonnelRemoval() {
PersonnelStatus status = person.getStatus();

if (status.isDepartedUnit()) {
if (shouldRemovePerson(person, status)) {
if (shouldRemovePerson(person)) {
personnelToRemove.add(person);
}
}
Expand All @@ -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.
*
* <p>The method evaluates the following conditions in order:
* <ul>
* <li>If the person has a retirement date and retirees are exempt from removal as per
* campaign options, the method returns {@code false}.</li>
* <li>If the person has a date of death, and cemeteries are exempt from removal as per
* campaign options, the method returns {@code false}.</li>
* <li>If the person has an active genealogy, the method returns {@code false}.</li>
* <li>If the person's retirement date is more than one month ago, the method returns {@code true}.</li>
* <li>If the person's date of death is more than one month ago, the method returns {@code true}.</li>
* </ul>
*
* <p>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<FamilialRelationshipType, List<Person>> 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);
}

/**
Expand Down Expand Up @@ -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() {
Expand All @@ -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;
}
Expand Down
23 changes: 23 additions & 0 deletions MekHQ/src/mekhq/campaign/personnel/familyTree/Genealogy.java
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,29 @@ public void clearGenealogyLinks() {
}
}

/**
* Checks if there is at least one person in the family who is not marked as "departed".
*
* <p>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<Person> 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
Expand Down