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

Updated Aging Effects to Include Clan Reputation Modifiers & Glass Jaw Gain #6575

Merged
merged 6 commits into from
Apr 12, 2025
Merged
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
20 changes: 19 additions & 1 deletion MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import static mekhq.campaign.personnel.lifeEvents.FreedomDayAnnouncement.isFreedomDay;
import static mekhq.campaign.personnel.lifeEvents.NewYearsDayAnnouncement.isNewYear;
import static mekhq.campaign.personnel.lifeEvents.WinterHolidayAnnouncement.isWinterHolidayMajorDay;
import static mekhq.campaign.personnel.skills.Aging.applyAgingSPA;
import static mekhq.campaign.personnel.skills.Aging.updateAllSkillAgeModifiers;
import static mekhq.campaign.personnel.turnoverAndRetention.Fatigue.areFieldKitchensWithinCapacity;
import static mekhq.campaign.personnel.turnoverAndRetention.Fatigue.checkFieldKitchenCapacity;
Expand Down Expand Up @@ -5096,6 +5097,7 @@ private void processWeeklyRelationshipEvents(Person person) {
private void processAnniversaries(Person person) {
LocalDate birthday = person.getBirthday(getGameYear());
boolean isBirthday = birthday != null && birthday.equals(currentDay);
int age = person.getAge(currentDay);

if ((person.getRank().isOfficer()) || (!campaignOptions.isAnnounceOfficersOnly())) {
if (isBirthday && campaignOptions.isAnnounceBirthdays()) {
Expand Down Expand Up @@ -5132,14 +5134,15 @@ private void processAnniversaries(Person person) {
}

if (campaignOptions.isShowLifeEventDialogComingOfAge()) {
if ((person.getAge(currentDay) == 16) && (isBirthday)) {
if ((age == 16) && (isBirthday)) {
new ComingOfAgeAnnouncement(this, person);
}
}

if (campaignOptions.isUseAgeEffects() && isBirthday) {
// This is where we update all the aging modifiers for the character.
updateAllSkillAgeModifiers(currentDay, person);
applyAgingSPA(age, person);
}

if (campaignOptions.isRewardComingOfAgeAbilities() && isBirthday && (person.getAge(currentDay) == 16)) {
Expand Down Expand Up @@ -6114,6 +6117,21 @@ public Faction getFaction() {
return faction;
}

/**
* Determines whether the current campaign is a clan campaign.
*
* <p>This method checks if the faction associated with the campaign is a clan, returning {@code true}
* if it is, and {@code false} otherwise.</p>
*
* @return {@code true} if the campaign belongs to a clan faction, {@code false} otherwise.
*
* @author Illiani
* @since 0.50.05
*/
public boolean isClanCampaign() {
return faction.isClan();
}

public void setFaction(final Faction faction) {
setFactionDirect(faction);
updateTechFactionCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,11 @@ private void calculateNegotiationSkill() {
Skill skill = negotiator.getSkill(SkillType.S_NEG);

if (skill != null) {
int skillLevel = skill.getFinalSkillValue(negotiator.getOptions(), negotiator.getReputation());
int reputation = negotiator.getAdjustedReputation(campaign.getCampaignOptions().isUseAgeEffects(),
campaign.isClanCampaign(),
campaign.getLocalDate(),
negotiator.getRankLevel());
int skillLevel = skill.getFinalSkillValue(negotiator.getOptions(), reputation);
negotiatorSkill = skill.getType().getExperienceLevel(skillLevel);
}
}
Expand Down
39 changes: 39 additions & 0 deletions MekHQ/src/mekhq/campaign/personnel/Person.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import static mekhq.campaign.personnel.enums.BloodGroup.getRandomBloodGroup;
import static mekhq.campaign.personnel.skills.Attributes.MAXIMUM_ATTRIBUTE_SCORE;
import static mekhq.campaign.personnel.skills.Attributes.MINIMUM_ATTRIBUTE_SCORE;
import static mekhq.campaign.personnel.skills.Aging.getReputationAgeModifier;
import static mekhq.campaign.personnel.skills.SkillType.S_ADMIN;

import java.io.PrintWriter;
Expand Down Expand Up @@ -5147,10 +5148,48 @@ public void setHasPerformedExtremeExpenditure(final boolean hasPerformedExtremeE
this.hasPerformedExtremeExpenditure = hasPerformedExtremeExpenditure;
}

/**
* Retrieves the raw reputation value of the character.
*
* <p>This method returns the unadjusted reputation value associated with the character.</p>
*
* <p><b>Usage:</b> If aging effects are enabled, you likely want to use
* {@link #getAdjustedReputation(boolean, boolean, LocalDate, int)} instead.</p>
*
* @return The raw reputation value.
*/
public int getReputation() {
return reputation;
}

/**
* Calculates the adjusted reputation value for the character based on aging effects, the current campaign type,
* date, and rank.
*
* <p>This method computes the character's reputation by applying age-based modifiers, which depend on factors such
* as whether aging effects are enabled, whether the campaign is clan-specific, the character's bloodname status,
* and their rank in the clan hierarchy. If aging effects are disabled, the reputation remains unchanged.</p>
*
* <p><b>Usage:</b> If aging effects are disabled, the result will be equivalent to the base reputation value
* provided by {@link #getReputation()}.</p>
*
* @param isUseAgingEffects Indicates whether aging effects should be applied to the reputation calculation.
* @param isClanCampaign Indicates whether the current campaign is specific to a clan.
* @param today The current date used to calculate the character's age.
* @param rankIndex The rank index of the character, which can adjust the reputation modifier in clan-based
* campaigns.
*
* @return The adjusted reputation value, accounting for factors like age, clan campaign status, bloodname
* possession, and rank. If aging effects are disabled, the base reputation value is returned.
*/
public int getAdjustedReputation(boolean isUseAgingEffects, boolean isClanCampaign, LocalDate today,
int rankIndex) {
return reputation +
(isUseAgingEffects ?
getReputationAgeModifier(getAge(today), isClanCampaign, !bloodname.isBlank(), rankIndex) :
0);
}

public void setReputation(final int reputation) {
this.reputation = clamp(reputation, MINIMUM_REPUTATION, MAXIMUM_REPUTATION);
}
Expand Down
115 changes: 114 additions & 1 deletion MekHQ/src/mekhq/campaign/personnel/skills/Aging.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,23 @@
*/
package mekhq.campaign.personnel.skills;

import static megamek.common.options.PilotOptions.LVL3_ADVANTAGES;
import static mekhq.campaign.personnel.PersonnelOptions.ATOW_FAST_LEARNER;
import static mekhq.campaign.personnel.PersonnelOptions.ATOW_TOUGHNESS;
import static mekhq.campaign.personnel.PersonnelOptions.FLAW_GLASS_JAW;
import static mekhq.campaign.personnel.PersonnelOptions.FLAW_SLOW_LEARNER;
import static mekhq.campaign.personnel.skills.enums.AgingMilestone.CLAN_REPUTATION_MULTIPLIER;
import static mekhq.campaign.personnel.skills.enums.AgingMilestone.NONE;
import static mekhq.campaign.personnel.skills.enums.AgingMilestone.STAR_CAPTAIN_RANK_INDEX;
import static mekhq.campaign.personnel.skills.enums.AgingMilestone.STAR_CAPTAIN_REPUTATION_MULTIPLIER;
import static mekhq.campaign.personnel.skills.enums.AgingMilestone.STAR_COLONEL_REPUTATION_MULTIPLIER;
import static mekhq.campaign.personnel.skills.enums.AgingMilestone.TWENTY_FIVE;
import static mekhq.campaign.personnel.skills.enums.SkillAttribute.NO_SKILL_ATTRIBUTE;

import java.time.LocalDate;

import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.PersonnelOptions;
import mekhq.campaign.personnel.skills.enums.AgingMilestone;
import mekhq.campaign.personnel.skills.enums.SkillAttribute;

Expand Down Expand Up @@ -89,6 +99,17 @@ public static void updateAllSkillAgeModifiers(LocalDate today, Person person) {
}
}

/**
* Resets all age-related modifiers for the skills of a given person to zero.
*
* <p>This method iterates through all skills in the {@code SkillType.skillList} and, for each skill that
* the person possesses, sets its aging modifier to zero. Skills that the person does not have are ignored.</p>
*
* @param person The person whose skill age modifiers will be cleared.
*
* @author Illiani
* @since 0.50.05
*/
public static void clearAllAgeModifiers(Person person) {
for (String skillName : SkillType.skillList) {
boolean hasSkill = person.hasSkill(skillName);
Expand Down Expand Up @@ -156,7 +177,7 @@ public static int getAgeModifier(int characterAge, SkillAttribute firstAttribute
* 0} if no valid combination or milestone exists
*/
public static int getAgeModifier(AgingMilestone milestone, SkillAttribute firstAttribute,
SkillAttribute secondAttribute) {
SkillAttribute secondAttribute) {
// If no milestone applies, return no modifier
if (milestone == NONE) {
return 0;
Expand All @@ -180,6 +201,98 @@ public static int getAgeModifier(AgingMilestone milestone, SkillAttribute firstA
return applyAgingModifier((firstModifier + secondModifier) / 2);
}

/**
* Calculates the reputation age modifier for a character based on their age, clan affiliation, bloodname status,
* and military rank.
*
* <p>This method determines a character's reputation age modifier by evaluating their age against a predefined
* aging milestone, their clan affiliation, their possession of a bloodname, and their rank in the clan hierarchy.
* If the character meets specific conditions, such as holding a high enough rank or possessing a bloodname, the
* reputation multiplier is adjusted. The final result is scaled by a clan-specific reputation multiplier.</p>
*
* @param characterAge The age of the character for which the reputation modifier is being calculated.
* @param isClan Indicates whether the character is part of a clan. If {@code false}, the method returns 0.
* @param hasBloodname Indicates whether the character possesses a bloodname, which can decrease the reputation
* multiplier under certain conditions.
* @param rankIndex The rank index of the character, used to determine if they meet rank-specific milestone
* conditions for reputation adjustment.
*
* @return The calculated reputation age modifier. Returns 0 if the character is not a clan member.
*
* @author Illiani
* @since 0.50.05
*/
public static int getReputationAgeModifier(int characterAge, boolean isClan, boolean hasBloodname, int rankIndex) {
if (!isClan) {
return 0;
}

AgingMilestone milestone = getMilestone(characterAge);

int reputationMultiplier = milestone.getReputation();
boolean hasHitRankTarget = false;

if (reputationMultiplier == STAR_CAPTAIN_REPUTATION_MULTIPLIER && rankIndex >= STAR_CAPTAIN_RANK_INDEX) {
hasHitRankTarget = true;
}

if (reputationMultiplier == STAR_COLONEL_REPUTATION_MULTIPLIER && rankIndex >= STAR_CAPTAIN_RANK_INDEX) {
hasHitRankTarget = true;
}

if (hasHitRankTarget || hasBloodname) {
reputationMultiplier--;
}

return reputationMultiplier * CLAN_REPUTATION_MULTIPLIER;
}

/**
* Applies age-related special abilities or flaws to a given person based on their age.
*
* <p>This method evaluates the character's age against predefined aging milestones, and if the age matches
* a milestone, specific effects such as applying flaws or adjusting abilities are triggered. For example, it may
* apply the "Glass Jaw" flaw or interact with existing abilities like "Toughness".</p>
*
* @param characterAge The age of the character, used to determine applicable aging milestones and effects.
* @param person The person to whom the aging-related effects will be applied.
*
* @author Illiani
* @since 0.50.05
*/
public static void applyAgingSPA(int characterAge, Person person) {
PersonnelOptions options = person.getOptions();
for (AgingMilestone milestone : AgingMilestone.values()) {
if (characterAge == milestone.getMilestone()) {
// Glass Jaw
if (milestone.isGlassJaw()) {
boolean hasGlassJaw = options.booleanOption(FLAW_GLASS_JAW);
boolean hasToughness = options.booleanOption(ATOW_TOUGHNESS);

if (hasToughness) {
person.getOptions().getOption(ATOW_TOUGHNESS).setValue(false);
} else if (!hasGlassJaw) {
options.acquireAbility(LVL3_ADVANTAGES, FLAW_GLASS_JAW, true);
}
}

// Slow Learner
if (milestone.isSlowLearner()) {
boolean hasSlowLearner = options.booleanOption(FLAW_SLOW_LEARNER);
boolean hasFastLearner = options.booleanOption(ATOW_FAST_LEARNER);

if (hasFastLearner) {
person.getOptions().getOption(ATOW_FAST_LEARNER).setValue(false);
} else if (!hasSlowLearner) {
options.acquireAbility(LVL3_ADVANTAGES, FLAW_SLOW_LEARNER, true);
}
}

break;
}
}
}

/**
* Determines the appropriate {@link AgingMilestone} for a given character's age.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,23 @@
public enum AgingMilestone {
NONE(0, 25, 0, 0, 0, 0, 0, 0, 0, 0, false, false),
TWENTY_FIVE(25, 31, 50, 50, 0, 50, 50, 50, 50, 0, false, false),
THIRTY_ONE(31, 14, 50, 50, 0, 50, 50, 50, 0, -150, false, false),
FORTY_ONE(41, 51, 0, 0, -50, 0, 0, 0, 0, 0, false, false),
FIFTY_ONE(51, 61, 0, -100, 0, -100, 0, 0, -50, -300, false, false),
SIXTY_ONE(61, 71, -100, -100, -100, 0, 50, 0, -50, 0, true, false),
SEVENTY_ONE(71, 81, -100, -125, 0, -100, 0, -50, -75, 0, true, true),
EIGHTY_ONE(81, 91, -150, -150, -100, -100, -100, -50, -100, 0, true, true),
NINETY_ONE(91, 101, -150, -175, -150, -125, -150, -100, -100, 0, true, true),
ONE_HUNDRED_ONE(101, MAX_VALUE, -200, -200, -200, -150, -200, -100, -1500, 0, true, true);
THIRTY_ONE(31, 14, 50, 50, 0, 50, 50, 50, 0, 1, false, false),
FORTY_ONE(41, 51, 0, 0, -50, 0, 0, 0, 0, 1, false, false),
FIFTY_ONE(51, 61, 0, -100, 0, -100, 0, 0, -50, 2, false, false),
SIXTY_ONE(61, 71, -100, -100, -100, 0, 50, 0, -50, 2, true, false),
SEVENTY_ONE(71, 81, -100, -125, 0, -100, 0, -50, -75, 2, true, true),
EIGHTY_ONE(81, 91, -150, -150, -100, -100, -100, -50, -100, 2, true, true),
NINETY_ONE(91, 101, -150, -175, -150, -125, -150, -100, -100, 2, true, true),
ONE_HUNDRED_ONE(101, MAX_VALUE, -200, -200, -200, -150, -200, -100, -1500, 2, true, true);

private static final MMLogger logger = MMLogger.create(AgingMilestone.class);

public static final int CLAN_REPUTATION_MULTIPLIER = 150;
public static final int STAR_CAPTAIN_RANK_INDEX = 34;
public static final int STAR_CAPTAIN_REPUTATION_MULTIPLIER = 1;
public static final int STAR_COLONEL_RANK_INDEX = 38;
public static final int STAR_COLONEL_REPUTATION_MULTIPLIER = 2;

// Attributes
private final int milestone;
private final int maximumAge;
Expand Down Expand Up @@ -100,7 +106,7 @@ public enum AgingMilestone {

// Constructor
AgingMilestone(int milestone, int maximumAge, int strength, int body, int dexterity, int reflexes, int intelligence,
int willpower, int charisma, int reputation, boolean slowLearner, boolean glassJaw) {
int willpower, int charisma, int reputation, boolean slowLearner, boolean glassJaw) {
this.milestone = milestone;
this.maximumAge = maximumAge;
this.strength = strength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static megamek.common.options.OptionsConstants.ATOW_COMBAT_SENSE;
import static mekhq.campaign.randomEvents.personalities.PersonalityController.getPersonalityValue;

import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -70,7 +71,11 @@ protected static Map<String, Integer> calculateCommanderRating(Campaign campaign
commandRating.put("strategy", getSkillValue(commander, SkillType.S_STRATEGY));
commandRating.put("negotiation", getSkillValue(commander, SkillType.S_NEG));

commandRating.put("traits", getATOWTraitValues(commander));
commandRating.put("traits",
getATOWTraitValues(commander,
campaign.getCampaignOptions().isUseAgeEffects(),
campaign.isClanCampaign(),
campaign.getLocalDate()));

int personalityValue = 0;
CampaignOptions campaignOptions = campaign.getCampaignOptions();
Expand Down Expand Up @@ -116,7 +121,8 @@ protected static Map<String, Integer> calculateCommanderRating(Campaign campaign
*
* @return The calculated trait score for the commander, with a minimum value of 1.
*/
private static int getATOWTraitValues(Person commander) {
private static int getATOWTraitValues(Person commander, boolean isUseAgingEffects, boolean isClanCampaign,
LocalDate today) {
if (commander == null) {
return 0;
}
Expand All @@ -131,7 +137,10 @@ private static int getATOWTraitValues(Person commander) {
traitScore += commander.getWealth() >= 7 ? 1 : 0;

// Reputation
int reputation = commander.getReputation();
int reputation = commander.getAdjustedReputation(isUseAgingEffects,
isClanCampaign,
today,
commander.getRankLevel());
if (reputation < 0) {
traitScore -= 1;
} else if (reputation > 0) {
Expand Down
9 changes: 5 additions & 4 deletions MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -601,16 +601,14 @@ public void actionPerformed(ActionEvent action) {
case CMD_IMPROVE: {
String type = data[1];
int cost = MathUtility.parseInt(data[2]);
int oldExpLevel = selectedPerson.getExperienceLevel(getCampaign(), false);
selectedPerson.improveSkill(type);
selectedPerson.spendXP(cost);

PersonalLogger.improvedSkill(getCampaign(),
selectedPerson,
getCampaign().getLocalDate(),
selectedPerson.getSkill(type).getType().getName(),
selectedPerson.getSkill(type)
.toString(selectedPerson.getOptions(), selectedPerson.getReputation()));
selectedPerson.getSkill(type).toString());
getCampaign().addReport(String.format(resources.getString("improved.format"),
selectedPerson.getHyperlinkedName(),
type));
Expand Down Expand Up @@ -2793,7 +2791,10 @@ protected Optional<JPopupMenu> createPopupMenu() {
traitsMenu.add(menuItem);

// Reputation
int reputation = person.getReputation();
int reputation = person.getAdjustedReputation(getCampaignOptions().isUseAgeEffects(),
getCampaign().isClanCampaign(),
getCampaign().getLocalDate(),
person.getRankLevel());
target = reputation + 1;
menuItem = new JMenuItem(String.format(resources.getString("spendOnReputation.text"), target, traitCost));
menuItem.setToolTipText(wordWrap(String.format(resources.getString("spendOnReputation.tooltip"),
Expand Down
2 changes: 1 addition & 1 deletion MekHQ/src/mekhq/gui/dialog/BatchXPDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ private void spendXP() {
person,
campaign.getLocalDate(),
person.getSkill(skillName).getType().getName(),
person.getSkill(skillName).toString(person.getOptions(), person.getReputation()));
person.getSkill(skillName).toString());
campaign.personUpdated(person);
}

Expand Down
Loading
Loading