Skip to content

Commit 7bb3057

Browse files
authored
Merge pull request #6554 from IllianiCBT/improveAttributes
Added the Ability to Purchase Attribute Improvements; Added the GM Ability to Mass Set Attributes
2 parents 1490f3c + 147004e commit 7bb3057

File tree

5 files changed

+153
-16
lines changed

5 files changed

+153
-16
lines changed

MekHQ/resources/mekhq/resources/GUI.properties

+8
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ spendOnWealth.tooltip=Raising this trait to at least rank 7 will improve unit Re
157157
\ campaign commander.
158158
spendOnUnlucky.text=Unlucky -> %s (%s xp)
159159
spendOnUnlucky.tooltip=Decreases the character's available Edge by %s.
160+
spendOnAttributes.increase=Increase Attribute Scores
161+
spendOnAttributes.set=Set Attribute Score
162+
spendOnAttributes.format=%s %s -> %s (%s xp)
163+
spendOnAttributes.tooltip=Improves the chosen Attribute by 1 (to a maximum of 10). Attributes can affect skill target\
164+
\ numbers (see A Time of War).\
165+
<br>\
166+
<br>Attributes also improve skill checks for skills the character does not possess.
167+
spendOnAttributes.score=Attribute Score
160168
skillDesc.format=%s (%dXP)
161169
spendOnEdge.text=Edge Point (%dXP)
162170
setEdgeTriggers.text=Set Edge Triggers

MekHQ/src/mekhq/campaign/personnel/Person.java

+34-2
Original file line numberDiff line numberDiff line change
@@ -2969,8 +2969,8 @@ public static Person generateInstanceFromXML(Node wn, Campaign campaign, Version
29692969
} else if ((nodeName.equalsIgnoreCase("reasoning")) || (nodeName.equalsIgnoreCase("intelligence"))) {
29702970
person.reasoning = Reasoning.fromString(wn2.getTextContent());
29712971
// 'intelligenceDescriptionIndex' is a <50.05 compatibility handler
2972-
} else if ((nodeName.equalsIgnoreCase("reasoningDescriptionIndex")) || (nodeName.equalsIgnoreCase(
2973-
"intelligenceDescriptionIndex"))) {
2972+
} else if ((nodeName.equalsIgnoreCase("reasoningDescriptionIndex")) ||
2973+
(nodeName.equalsIgnoreCase("intelligenceDescriptionIndex"))) {
29742974
person.reasoningDescriptionIndex = MathUtility.parseInt(wn2.getTextContent());
29752975
} else if (nodeName.equalsIgnoreCase("personalityDescription")) {
29762976
person.personalityDescription = wn2.getTextContent();
@@ -4957,6 +4957,38 @@ public Attributes getATOWAttributes() {
49574957
return atowAttributes;
49584958
}
49594959

4960+
/**
4961+
* Updates the score of a specific attribute to a new value.
4962+
*
4963+
* <p>This method modifies the score of an attribute specified by the {@link SkillAttribute} parameter. If the
4964+
* provided attribute is {@code NONE}, no changes will be made. If the attribute is {@code null}, the method logs an
4965+
* error and exits without making changes.</p>
4966+
*
4967+
* @param attribute the {@link SkillAttribute} to update. Must not be {@code null}.
4968+
* @param newScore the new score to set for the specified attribute.
4969+
*
4970+
* @since 0.50.5
4971+
*/
4972+
public void setAttributeScore(final SkillAttribute attribute, final int newScore) {
4973+
if (attribute == null) {
4974+
logger.error("(setAttributeScore) SkillAttribute is null.");
4975+
return;
4976+
}
4977+
4978+
switch (attribute) {
4979+
case NONE -> {
4980+
}
4981+
case STRENGTH -> atowAttributes.setStrength(newScore);
4982+
case BODY -> atowAttributes.setBody(newScore);
4983+
case REFLEXES -> atowAttributes.setReflexes(newScore);
4984+
case DEXTERITY -> atowAttributes.setDexterity(newScore);
4985+
case INTELLIGENCE -> atowAttributes.setIntelligence(newScore);
4986+
case WILLPOWER -> atowAttributes.setWillpower(newScore);
4987+
case CHARISMA -> atowAttributes.setCharisma(newScore);
4988+
}
4989+
;
4990+
}
4991+
49604992
/**
49614993
* Retrieves the score of a specified attribute.
49624994
*

MekHQ/src/mekhq/campaign/personnel/skills/Attributes.java

+5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ public class Attributes {
9494
*/
9595
public static int MAXIMUM_ATTRIBUTE_SCORE = 10;
9696

97+
/**
98+
* Represents the cost required to improve an attribute. This is taken from ATOW pg 333.
99+
*/
100+
public static int ATTRIBUTE_IMPROVEMENT_COST = 100;
101+
97102
// Constructor
98103

99104
/**

MekHQ/src/mekhq/campaign/personnel/skills/enums/SkillAttribute.java

+8
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ public enum SkillAttribute {
8383

8484
final private static String RESOURCE_BUNDLE = "mekhq.resources." + SkillAttribute.class.getSimpleName();
8585

86+
/**
87+
* Checks if the current instance is {@link #NONE}.
88+
*
89+
* @return {@code true} if the current instance is {@code NONE}, {@code false} otherwise.
90+
*/
91+
public boolean isNone() {
92+
return this == NONE;
93+
}
8694

8795
/**
8896
* Retrieves the label associated with this {@link SkillAttribute}.

MekHQ/src/mekhq/gui/adapter/PersonnelTableMouseAdapter.java

+98-14
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import static mekhq.campaign.personnel.education.EducationController.makeEnrollmentCheck;
4545
import static mekhq.campaign.personnel.enums.PersonnelStatus.statusValidator;
4646
import static mekhq.campaign.personnel.enums.education.EducationLevel.DOCTORATE;
47+
import static mekhq.campaign.personnel.skills.Attributes.ATTRIBUTE_IMPROVEMENT_COST;
48+
import static mekhq.campaign.personnel.skills.Attributes.MAXIMUM_ATTRIBUTE_SCORE;
4749
import static mekhq.campaign.personnel.skills.SkillType.S_DOCTOR;
4850
import static mekhq.campaign.randomEvents.personalities.PersonalityController.writePersonalityDescription;
4951
import static mekhq.campaign.randomEvents.prisoners.PrisonerEventManager.processAdHocExecution;
@@ -121,6 +123,7 @@
121123
import mekhq.campaign.personnel.ranks.Ranks;
122124
import mekhq.campaign.personnel.skills.Skill;
123125
import mekhq.campaign.personnel.skills.SkillType;
126+
import mekhq.campaign.personnel.skills.enums.SkillAttribute;
124127
import mekhq.campaign.randomEvents.personalities.PersonalityController;
125128
import mekhq.campaign.randomEvents.prisoners.enums.PrisonerStatus;
126129
import mekhq.campaign.unit.Unit;
@@ -179,6 +182,7 @@ public class PersonnelTableMouseAdapter extends JPopupMenuAdapter {
179182
* @deprecated use {@code CMD_ADD_XP} instead
180183
*/
181184
@Deprecated(since = "0.50.05", forRemoval = true)
185+
private static final String CMD_ADD_1_XP = "XP_ADD_1";
182186
private static final String CMD_ADD_XP = "XP_ADD";
183187
private static final String CMD_EDIT_BIOGRAPHY = "BIOGRAPHY";
184188
private static final String CMD_EDIT_PORTRAIT = "PORTRAIT";
@@ -199,6 +203,8 @@ public class PersonnelTableMouseAdapter extends JPopupMenuAdapter {
199203
private static final String CMD_ACQUIRE_CUSTOM_CHOICE = "CUSTOM_CHOICE";
200204
private static final String CMD_IMPROVE = "IMPROVE";
201205
private static final String CMD_BUY_TRAIT = "BUY_TRAIT";
206+
private static final String CMD_CHANGE_ATTRIBUTE = "CHANGE_ATTRIBUTE";
207+
private static final String CMD_SET_ATTRIBUTE = "SET_ATTRIBUTE";
202208
private static final String CMD_ADD_SPOUSE = "SPOUSE";
203209
private static final String CMD_REMOVE_SPOUSE = "REMOVE_SPOUSE";
204210
private static final String CMD_ADD_PREGNANCY = "ADD_PREGNANCY";
@@ -330,8 +336,8 @@ public void actionPerformed(ActionEvent action) {
330336
case CMD_RANK: {
331337
List<Person> promotedPersonnel = new ArrayList<>();
332338
try {
333-
final int rank = Integer.parseInt(data[1]);
334-
final int level = (data.length > 2) ? Integer.parseInt(data[2]) : 0;
339+
final int rank = MathUtility.parseInt(data[1]);
340+
final int level = (data.length > 2) ? MathUtility.parseInt(data[2]) : 0;
335341
for (final Person person : people) {
336342
person.changeRank(getCampaign(), rank, level, true);
337343

@@ -580,7 +586,7 @@ public void actionPerformed(ActionEvent action) {
580586
}
581587
case CMD_IMPROVE: {
582588
String type = data[1];
583-
int cost = Integer.parseInt(data[2]);
589+
int cost = MathUtility.parseInt(data[2]);
584590
int oldExpLevel = selectedPerson.getExperienceLevel(getCampaign(), false);
585591
selectedPerson.improveSkill(type);
586592
selectedPerson.spendXP(cost);
@@ -600,8 +606,8 @@ public void actionPerformed(ActionEvent action) {
600606
}
601607
case CMD_BUY_TRAIT: {
602608
String type = data[1];
603-
int cost = Integer.parseInt(data[2]);
604-
int target = Integer.parseInt(data[3]);
609+
int cost = MathUtility.parseInt(data[2]);
610+
int target = MathUtility.parseInt(data[3]);
605611

606612
switch (type) {
607613
case CONNECTIONS_LABEL -> selectedPerson.setConnections(target);
@@ -616,9 +622,44 @@ public void actionPerformed(ActionEvent action) {
616622
getCampaign().personUpdated(selectedPerson);
617623
break;
618624
}
625+
case CMD_CHANGE_ATTRIBUTE: {
626+
SkillAttribute attribute = SkillAttribute.fromString(data[1]);
627+
628+
selectedPerson.changeAttributeScore(attribute, 1);
629+
630+
int cost = MathUtility.parseInt(data[2]);
631+
selectedPerson.spendXP(cost);
632+
633+
getCampaign().personUpdated(selectedPerson);
634+
break;
635+
}
636+
case CMD_SET_ATTRIBUTE: {
637+
SkillAttribute attribute = SkillAttribute.fromString(data[1]);
638+
639+
PopupValueChoiceDialog choiceDialog = new PopupValueChoiceDialog(getFrame(),
640+
true,
641+
resources.getString("spendOnAttributes.score"),
642+
selectedPerson.getAttributeScore(attribute),
643+
0);
644+
choiceDialog.setVisible(true);
645+
646+
int choice = choiceDialog.getValue();
647+
if (choice <= 0) {
648+
// <0 indicates Cancellation
649+
return;
650+
}
651+
652+
for (Person person : people) {
653+
person.setAttributeScore(attribute, choice);
654+
MekHQ.triggerEvent(new PersonChangedEvent(person));
655+
getCampaign().personUpdated(person);
656+
}
657+
658+
break;
659+
}
619660
case CMD_ACQUIRE_ABILITY: {
620661
String selected = data[1];
621-
int cost = Integer.parseInt(data[2]);
662+
int cost = MathUtility.parseInt(data[2]);
622663
selectedPerson.getOptions().acquireAbility(PersonnelOptions.LVL3_ADVANTAGES, selected, true);
623664
selectedPerson.spendXP(cost);
624665
final String displayName = SpecialAbility.getDisplayName(selected);
@@ -631,7 +672,7 @@ public void actionPerformed(ActionEvent action) {
631672
}
632673
case CMD_ACQUIRE_WEAPON_SPECIALIST: {
633674
String selected = data[1];
634-
int cost = Integer.parseInt(data[2]);
675+
int cost = MathUtility.parseInt(data[2]);
635676
selectedPerson.getOptions()
636677
.acquireAbility(PersonnelOptions.LVL3_ADVANTAGES,
637678
OptionsConstants.GUNNERY_WEAPON_SPECIALIST,
@@ -649,7 +690,7 @@ public void actionPerformed(ActionEvent action) {
649690
}
650691
case CMD_ACQUIRE_SANDBLASTER: {
651692
String selected = data[1];
652-
int cost = Integer.parseInt(data[2]);
693+
int cost = MathUtility.parseInt(data[2]);
653694
selectedPerson.getOptions()
654695
.acquireAbility(PersonnelOptions.LVL3_ADVANTAGES, OptionsConstants.GUNNERY_SANDBLASTER, selected);
655696
selectedPerson.spendXP(cost);
@@ -665,7 +706,7 @@ public void actionPerformed(ActionEvent action) {
665706
}
666707
case CMD_ACQUIRE_SPECIALIST: {
667708
String selected = data[1];
668-
int cost = Integer.parseInt(data[2]);
709+
int cost = MathUtility.parseInt(data[2]);
669710
selectedPerson.getOptions()
670711
.acquireAbility(PersonnelOptions.LVL3_ADVANTAGES, OptionsConstants.GUNNERY_SPECIALIST, selected);
671712
selectedPerson.spendXP(cost);
@@ -681,7 +722,7 @@ public void actionPerformed(ActionEvent action) {
681722
}
682723
case CMD_ACQUIRE_RANGEMASTER: {
683724
String selected = data[1];
684-
int cost = Integer.parseInt(data[2]);
725+
int cost = MathUtility.parseInt(data[2]);
685726
selectedPerson.getOptions()
686727
.acquireAbility(PersonnelOptions.LVL3_ADVANTAGES,
687728
OptionsConstants.GUNNERY_RANGE_MASTER,
@@ -699,7 +740,7 @@ public void actionPerformed(ActionEvent action) {
699740
}
700741
case CMD_ACQUIRE_ENVSPEC: {
701742
String selected = data[1];
702-
int cost = Integer.parseInt(data[2]);
743+
int cost = MathUtility.parseInt(data[2]);
703744
selectedPerson.getOptions()
704745
.acquireAbility(PersonnelOptions.LVL3_ADVANTAGES, OptionsConstants.MISC_ENV_SPECIALIST, selected);
705746
selectedPerson.spendXP(cost);
@@ -715,7 +756,7 @@ public void actionPerformed(ActionEvent action) {
715756
}
716757
case CMD_ACQUIRE_HUMANTRO: {
717758
String selected = data[1];
718-
int cost = Integer.parseInt(data[2]);
759+
int cost = MathUtility.parseInt(data[2]);
719760
selectedPerson.getOptions()
720761
.acquireAbility(PersonnelOptions.LVL3_ADVANTAGES, OptionsConstants.MISC_HUMAN_TRO, selected);
721762
selectedPerson.spendXP(cost);
@@ -731,7 +772,7 @@ public void actionPerformed(ActionEvent action) {
731772
}
732773
case CMD_ACQUIRE_CUSTOM_CHOICE: {
733774
String selected = data[1];
734-
int cost = Integer.parseInt(data[2]);
775+
int cost = MathUtility.parseInt(data[2]);
735776
String ability = data[3];
736777
selectedPerson.getOptions().acquireAbility(PersonnelOptions.LVL3_ADVANTAGES, ability, selected);
737778
selectedPerson.spendXP(cost);
@@ -1515,7 +1556,7 @@ private void processApplication(Person[] people, String[] data, boolean isReEnro
15151556
person,
15161557
data[1],
15171558
data[2],
1518-
Integer.parseInt(data[3]),
1559+
MathUtility.parseInt(data[3]),
15191560
data[4],
15201561
data[5],
15211562
isReEnrollment);
@@ -2766,6 +2807,32 @@ protected Optional<JPopupMenu> createPopupMenu() {
27662807
traitsMenu.add(menuItem);
27672808
menu.add(traitsMenu);
27682809

2810+
JMenu attributesMenuIncrease = new JMenu(resources.getString("spendOnAttributes.increase"));
2811+
int attributeCost = (int) round(ATTRIBUTE_IMPROVEMENT_COST * costMultiplier);
2812+
2813+
for (SkillAttribute attribute : SkillAttribute.values()) {
2814+
if (attribute.isNone()) {
2815+
continue;
2816+
}
2817+
2818+
int current = person.getAttributeScore(attribute);
2819+
// Improve
2820+
target = current + 1;
2821+
menuItem = new JMenuItem(String.format(resources.getString("spendOnAttributes.format"),
2822+
attribute.getLabel(),
2823+
current,
2824+
target,
2825+
traitCost));
2826+
menuItem.setToolTipText(wordWrap(String.format(resources.getString("spendOnAttributes.tooltip"))));
2827+
menuItem.setActionCommand(makeCommand(CMD_CHANGE_ATTRIBUTE,
2828+
String.valueOf(attribute),
2829+
String.valueOf(attributeCost)));
2830+
menuItem.addActionListener(this);
2831+
menuItem.setEnabled(target <= MAXIMUM_ATTRIBUTE_SCORE && person.getXP() >= attributeCost);
2832+
attributesMenuIncrease.add(menuItem);
2833+
}
2834+
menu.add(attributesMenuIncrease);
2835+
27692836
// Edge Purchasing
27702837
if (getCampaignOptions().isUseEdge()) {
27712838
JMenu edgeMenu = new JMenu(resources.getString("edge.text"));
@@ -3564,6 +3631,23 @@ protected Optional<JPopupMenu> createPopupMenu() {
35643631
menuItem.addActionListener(this);
35653632
menu.add(menuItem);
35663633

3634+
JMenu attributesMenu = new JMenu(resources.getString("spendOnAttributes.set"));
3635+
3636+
for (SkillAttribute attribute : SkillAttribute.values()) {
3637+
if (attribute.isNone()) {
3638+
continue;
3639+
}
3640+
3641+
// Set
3642+
menuItem = new JMenuItem(attribute.getLabel());
3643+
menuItem.setToolTipText(wordWrap(String.format(resources.getString("spendOnAttributes.tooltip"))));
3644+
menuItem.setActionCommand(makeCommand(CMD_SET_ATTRIBUTE, String.valueOf(attribute)));
3645+
menuItem.addActionListener(this);
3646+
menuItem.setEnabled(getCampaign().isGM());
3647+
attributesMenu.add(menuItem);
3648+
}
3649+
menu.add(attributesMenu);
3650+
35673651
JMenuHelpers.addMenuIfNonEmpty(popup, menu);
35683652
}
35693653
// endregion GM Menu

0 commit comments

Comments
 (0)