From e9da4f6ec406fd61baead559e160054add23103c Mon Sep 17 00:00:00 2001 From: Scoppio Date: Mon, 25 Nov 2024 22:24:00 -0300 Subject: [PATCH] fix: allows to select a previous engineer or new crew member to activate large vessel --- MekHQ/src/mekhq/campaign/Campaign.java | 16 +++++-- .../src/mekhq/campaign/personnel/Person.java | 44 +++++++++++++++---- .../src/mekhq/campaign/unit/MothballInfo.java | 16 +++++-- MekHQ/src/mekhq/campaign/unit/Unit.java | 42 +++++++++++++++++- MekHQ/src/mekhq/gui/CampaignGUI.java | 4 +- .../gui/adapter/UnitTableMouseAdapter.java | 8 +++- .../mekhq/campaign/CampaignTest.java | 1 + 7 files changed, 111 insertions(+), 20 deletions(-) diff --git a/MekHQ/src/mekhq/campaign/Campaign.java b/MekHQ/src/mekhq/campaign/Campaign.java index f08daaaa43..9972682ee4 100644 --- a/MekHQ/src/mekhq/campaign/Campaign.java +++ b/MekHQ/src/mekhq/campaign/Campaign.java @@ -2447,6 +2447,14 @@ public List getTechs(final boolean noZeroMinute) { return getTechs(noZeroMinute, false); } + public List getTechsExpanded() { + return getTechsExpanded(false, false, true); + } + + public List getTechs(final boolean noZeroMinute, final boolean eliteFirst) { + return getTechsExpanded(noZeroMinute, eliteFirst, false); + } + /** * Returns a list of active technicians. * @@ -2454,12 +2462,14 @@ public List getTechs(final boolean noZeroMinute) { * excluded from the list. * @param eliteFirst If TRUE and sorted also TRUE, then return the list sorted * from best to worst + * @param expanded If TRUE, then include techs with expanded roles (e.g. Tech/Vessel skill) * @return The list of active {@link Person}s who qualify as technicians - * ({@link Person#isTech()}). + * ({@link Person#isTech()}), or who qualify as expanded technicians ({@link Person#isTechExpanded()}). */ - public List getTechs(final boolean noZeroMinute, final boolean eliteFirst) { + public List getTechsExpanded(final boolean noZeroMinute, final boolean eliteFirst, final boolean expanded) { final List techs = getActivePersonnel().stream() - .filter(person -> person.isTech() && (!noZeroMinute || (person.getMinutesLeft() > 0))) + .filter(person -> (expanded ? person.isTechExpanded() : person.isTech()) + && (!noZeroMinute || (person.getMinutesLeft() > 0))) .collect(Collectors.toList()); // also need to loop through and collect engineers on self-crewed vessels diff --git a/MekHQ/src/mekhq/campaign/personnel/Person.java b/MekHQ/src/mekhq/campaign/personnel/Person.java index b6842d72f6..c9285cfaa4 100644 --- a/MekHQ/src/mekhq/campaign/personnel/Person.java +++ b/MekHQ/src/mekhq/campaign/personnel/Person.java @@ -3753,8 +3753,13 @@ public boolean canGun(final Entity entity) { } public boolean canTech(final Entity entity) { + if (entity == null) { + return false; + } if ((entity instanceof Mek) || (entity instanceof ProtoMek)) { return hasSkill(SkillType.S_TECH_MEK); + } else if (entity instanceof Dropship || entity instanceof Jumpship) { + return hasSkill(SkillType.S_TECH_VESSEL); } else if (entity instanceof Aero) { return hasSkill(SkillType.S_TECH_AERO); } else if (entity instanceof BattleArmor) { @@ -3845,13 +3850,23 @@ public int getMinutesLeft() { public void setMinutesLeft(final int minutesLeft) { this.minutesLeft = minutesLeft; if (engineer && (getUnit() != null)) { - // set minutes for all crewmembers - for (Person p : getUnit().getActiveCrew()) { - p.setMinutesLeft(minutesLeft); - } + // set minutes for all crew members, except the engineer to not cause infinite recursion. + getUnit().getActiveCrew() + .stream() + .filter(this::isNotSelf) + .forEach(p -> p.setMinutesLeft(minutesLeft)); } } + /** + * Checks if the other person is not the same person as this person, easy right? + * @param p Person to check against + * @return true if the person is not the same person as this person + */ + private boolean isNotSelf(Person p) { + return !this.equals(p); + } + public int getOvertimeLeft() { return overtimeLeft; } @@ -3859,10 +3874,10 @@ public int getOvertimeLeft() { public void setOvertimeLeft(final int overtimeLeft) { this.overtimeLeft = overtimeLeft; if (engineer && (getUnit() != null)) { - // set minutes for all crewmembers - for (Person p : getUnit().getActiveCrew()) { - p.setMinutesLeft(overtimeLeft); - } + getUnit().getActiveCrew() + .stream() + .filter(this::isNotSelf) + .forEach(p -> p.setOvertimeLeft(overtimeLeft)); } } @@ -3901,6 +3916,19 @@ public boolean isTech() { return isTechMek() || isTechAero() || isTechMechanic() || isTechBA(); } + /** + * Checks if the person is a tech, includes mektek, mechanic, aerotek, BAtek and the non-cannon "large vessel tek" + * @return true if the person is a tech + */ + public boolean isTechExpanded() { + return isTechMek() || isTechAero() || isTechMechanic() || isTechBA() || isTechLargeVessel(); + } + + public boolean isTechLargeVessel() { + boolean hasSkill = hasSkill(SkillType.S_TECH_VESSEL); + return hasSkill && (getPrimaryRole().isVesselCrew() || getSecondaryRole().isVesselCrew()); + } + public boolean isTechMek() { boolean hasSkill = hasSkill(SkillType.S_TECH_MEK); return hasSkill && (getPrimaryRole().isMekTech() || getSecondaryRole().isMekTech()); diff --git a/MekHQ/src/mekhq/campaign/unit/MothballInfo.java b/MekHQ/src/mekhq/campaign/unit/MothballInfo.java index 5b51fbd69e..469d753500 100644 --- a/MekHQ/src/mekhq/campaign/unit/MothballInfo.java +++ b/MekHQ/src/mekhq/campaign/unit/MothballInfo.java @@ -38,7 +38,7 @@ * This class is used to store information about a particular unit that is * lost when a unit is mothballed, so that it may be restored to as close to * its prior state as possible when the unit is reactivated. - * + * * @author NickAragua */ public class MothballInfo { @@ -61,9 +61,17 @@ private MothballInfo() { vesselCrew = new ArrayList<>(); } + /** + * Who was the original tech of this vessel? + * @return The original tech + */ + public Person getTech() { + return tech; + } + /** * Creates a set of mothball info for a given unit - * + * * @param unit The unit to work with */ public MothballInfo(Unit unit) { @@ -78,7 +86,7 @@ public MothballInfo(Unit unit) { /** * Restore a unit's pilot, assigned tech and force, to the best of our ability - * + * * @param unit The unit to restore * @param campaign The campaign in which this is happening */ @@ -158,7 +166,7 @@ public void writeToXML(final PrintWriter pw, int indent) { /** * Deserializer method implemented in standard MekHQ pattern. - * + * * @return Instance of MothballInfo */ public static MothballInfo generateInstanceFromXML(Node wn, Version version) { diff --git a/MekHQ/src/mekhq/campaign/unit/Unit.java b/MekHQ/src/mekhq/campaign/unit/Unit.java index b3e24a4d49..2eb4e33569 100644 --- a/MekHQ/src/mekhq/campaign/unit/Unit.java +++ b/MekHQ/src/mekhq/campaign/unit/Unit.java @@ -4821,6 +4821,29 @@ public void startMothballing(@Nullable Person mothballTech, boolean isGM) { } } + /** + * Returns the engineer responsible for the mothballing or activation of this unit. + * @return Person the previous engineer that worked on this vessel, or an empty object. + */ + public Optional engineerResponsible() { + // if it is NOT self crewed or it is NOT mothballed, just get the tech + if (!isMothballed() || !isSelfCrewed()) { + return Optional.ofNullable(getTech()); + } else { + // if it is self crewed AND is mothballed and has a mothball info, get the tech + if (isSelfCrewed() && isMothballed() && (this.mothballInfo != null)) { + var previousTech = this.mothballInfo.getTech(); + var previousTechExists = previousTech != null; + var previousTechIsActive = previousTechExists && previousTech.getStatus().isActive(); + if (previousTechIsActive) { + return Optional.of(previousTech); + } + } + } + + return Optional.empty(); + } + /** * Completes the mothballing of a unit. */ @@ -4858,8 +4881,25 @@ public void startActivating(@Nullable Person activationTech, boolean isGM) { // set this person as tech if (!isSelfCrewed() && (tech != null) && !tech.equals(activationTech)) { remove(tech, true); + tech = activationTech; + } else if (!isSelfCrewed() && (null == tech)) { + tech = activationTech; + } + + if (isSelfCrewed() && !isConventionalInfantry()) { + if (engineerResponsible().isPresent() && engineerResponsible().get().getStatus().isActive()) { + var assignedEngineer = engineerResponsible().get(); + addVesselCrew(assignedEngineer); + } else if (activationTech != null && activationTech.isTechLargeVessel()) { + addVesselCrew(activationTech); + } else { + // In this case there is nothing to be done, we cant activate this unit. + return; + } + resetEngineer(); + } else { + tech = activationTech; } - tech = activationTech; if (!isGM) { setMothballTime(getMothballOrActivationTime()); diff --git a/MekHQ/src/mekhq/gui/CampaignGUI.java b/MekHQ/src/mekhq/gui/CampaignGUI.java index bd8c2a55bd..ade190d01e 100644 --- a/MekHQ/src/mekhq/gui/CampaignGUI.java +++ b/MekHQ/src/mekhq/gui/CampaignGUI.java @@ -1614,7 +1614,7 @@ public void refitUnit(Refit r, boolean selectModelName) { StringBuilder nameBuilder = new StringBuilder(128); nameBuilder.append("") .append(tech.getFullName()) - .append(", ") + .append(", ") .append(SkillType.getColoredExperienceLevelName(tech.getSkillLevel(getCampaign(), false))) .append(" ") .append(tech.getPrimaryRoleDesc()) @@ -1722,7 +1722,7 @@ public void refitUnit(Refit r, boolean selectModelName) { public @Nullable UUID selectTech(Unit u, String desc, boolean ignoreMaintenance) { String name; Map techHash = new LinkedHashMap<>(); - for (Person tech : getCampaign().getTechs()) { + for (Person tech : getCampaign().getTechsExpanded()) { if (!tech.isMothballing() && tech.canTech(u.getEntity())) { int time = tech.getMinutesLeft(); if (!ignoreMaintenance) { diff --git a/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java index 3094684f19..63c0a4e028 100644 --- a/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java @@ -205,7 +205,7 @@ public void actionPerformed(ActionEvent action) { String quality = (String) JOptionPane.showInputDialog(gui.getFrame(), "Choose the new quality level", "Set Quality", JOptionPane.PLAIN_MESSAGE, null, possibilities, PartQuality.QUALITY_D.toName(reverse)); - + PartQuality q = PartQuality.fromName(quality, reverse); for (Unit unit : units) { if (null != unit) { @@ -526,7 +526,7 @@ public void actionPerformed(ActionEvent action) { private @Nullable Person pickTechForMothballOrActivation(Unit unit, String description) { Person tech = null; - if (!unit.isSelfCrewed()) { + if (!unit.isSelfCrewed() || isSelfCrewedButHasNoTech(unit)) { UUID id = gui.selectTech(unit, description, true); if (null != id) { tech = gui.getCampaign().getPerson(id); @@ -544,6 +544,10 @@ public void actionPerformed(ActionEvent action) { return tech; } + private boolean isSelfCrewedButHasNoTech(Unit unit) { + return unit.isSelfCrewed() && unit.engineerResponsible().isEmpty(); + } + @Override protected Optional createPopupMenu() { if (unitTable.getSelectedRowCount() == 0) { diff --git a/MekHQ/unittests/mekhq/campaign/CampaignTest.java b/MekHQ/unittests/mekhq/campaign/CampaignTest.java index 8e7c6c2e6c..1a9c25c752 100644 --- a/MekHQ/unittests/mekhq/campaign/CampaignTest.java +++ b/MekHQ/unittests/mekhq/campaign/CampaignTest.java @@ -133,6 +133,7 @@ void testGetTechs() { when(testCampaign.getTechs()).thenCallRealMethod(); when(testCampaign.getTechs(anyBoolean())).thenCallRealMethod(); when(testCampaign.getTechs(anyBoolean(), anyBoolean())).thenCallRealMethod(); + when(testCampaign.getTechsExpanded(anyBoolean(), anyBoolean(), anyBoolean())).thenCallRealMethod(); // Test just getting the list of active techs. List expected = new ArrayList<>(3);