Skip to content

Commit

Permalink
Allow locking the base chassis of an Omni-mek
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelbraginskiy committed Feb 5, 2025
1 parent 76fec6f commit 9977dea
Show file tree
Hide file tree
Showing 23 changed files with 286 additions and 52 deletions.
2 changes: 2 additions & 0 deletions megameklab/resources/megameklab/resources/Views.properties
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ MekChassisView.spnTonnage.text=Tonnage:
MekChassisView.spnTonnage.tooltip=Value between 10 and 200 in 5 ton increments. Superheavy (over 100) is only available with IS/Advanced.
MekChassisView.chkOmni.text=Omni
MekChassisView.chkOmni.tooltip=Omni units mount equipment in pods that can be swapped quickly. Not available to Industrial or LAMs.
MekChassisView.chkOmniLock.text=Lock Chassis
MekChassisView.chkOmniLock.tooltip=Make the base chassis read-only so you can only modify Omni pods.
MekChassisView.cbBaseType.text=Base Type:
MekChassisView.cbBaseType.tooltip=<html>LAMs are only available to an IS tech base. QuadVees are only available to a Clan tech base.<br/>Resets unit except when switching between standard and industrial.</html>
MekChassisView.cbMotiveType.text=Motive Type:
Expand Down
16 changes: 16 additions & 0 deletions megameklab/src/megameklab/ui/EntitySource.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,20 @@ default void createNewUnit(long entitytype, Entity oldUnit) {
* of tech.
*/
ITechManager getTechManager();

/**
* Mark the entity provided by this EntitySource a having a read-only chassis, so that the only changes that can be made are to omni pod space.
* No enforcement is provided, you should call {@link EntitySource#canModifyBaseChassis} to determine if non-modifications should be allowed.
* @param locked {@code true} if the entity's base chassis should be treated as read-only
*/
default void setBaseChassisModifiable(boolean locked) {}

/**
* Determine if you should allow modifications to an omni-unit's base chassis.
* This is not enforced, be careful.
* @return {@code true} if you can modify the entity's base chassis.
*/
default boolean canModifyBaseChassis() {
return true;
}
}
33 changes: 30 additions & 3 deletions megameklab/src/megameklab/ui/generalUnit/AbstractEquipmentTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import java.util.List;

import javax.swing.BorderFactory;
Expand All @@ -30,6 +31,8 @@
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;

import megamek.client.ui.swing.util.UIUtil;
Expand Down Expand Up @@ -61,8 +64,10 @@
* @author neoancient
* @author Simon (Juliez)
*/
public abstract class AbstractEquipmentTab extends ITab {
public abstract class AbstractEquipmentTab extends ITab implements ListSelectionListener {
private static final MMLogger logger = MMLogger.create(AbstractEquipmentTab.class);
private final JButton removeButton;
private final JButton removeAllButton;

private RefreshListener refresh;

Expand Down Expand Up @@ -95,9 +100,9 @@ public AbstractEquipmentTab(EntitySource eSource) {
equipmentScroll.setViewportView(loadOutTable);
getLoadOut().forEach(loadOutModel::addCrit);

JButton removeButton = new JButton("Remove");
removeButton = new JButton("Remove");
removeButton.setMnemonic('R');
JButton removeAllButton = new JButton("Remove All");
removeAllButton = new JButton("Remove All");
removeAllButton.setMnemonic('l');
removeButton.addActionListener(this::removeSelectedEquipment);
removeAllButton.addActionListener(this::removeAllEquipment);
Expand Down Expand Up @@ -129,6 +134,8 @@ public Dimension getMinimumSize() {
pane.setOneTouchExpandable(true);
setLayout(new BorderLayout());
add(pane, BorderLayout.CENTER);

loadOutTable.getSelectionModel().addListSelectionListener(this);
}

public void addRefreshedListener(RefreshListener l) {
Expand Down Expand Up @@ -201,6 +208,12 @@ public void refresh() {
loadOutModel.removeAllCrits();
getLoadOut().forEach(loadOutModel::addCrit);
fireTableRefresh();

if (!eSource.canModifyBaseChassis()) {
removeAllButton.setEnabled(
loadOutModel.getCrits().stream().allMatch(Mounted::isOmniPodMounted)
);
}
}

public void refreshTable() {
Expand All @@ -222,4 +235,18 @@ private void refreshOtherTabs() {
refresh.refreshSummary();
}
}

@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getSource() == loadOutTable.getSelectionModel()) {
if (eSource.canModifyBaseChassis()) {
return;
}

var hasFixed = Arrays.stream(loadOutTable.getSelectionModel().getSelectedIndices())
.mapToObj(i -> loadOutModel.getCrits().get(i))
.anyMatch(m -> !m.isOmniPodMounted());
removeButton.setEnabled(!hasFixed);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,12 @@ public void showPatchwork(boolean show) {
public void armorPointsChanged(int location, int front, int rear) {
listeners.forEach(l -> l.armorPointsChanged(location, front, rear));
}

public void omniLock(boolean unlocked) {
for (ArmorLocationView locView : locationViews) {
locView.omniLock(unlocked);
}
btnAutoAllocate.setEnabled(unlocked);
panLocations.setEnabled(unlocked);
}
}
41 changes: 23 additions & 18 deletions megameklab/src/megameklab/ui/generalUnit/ArmorLocationView.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
/**
* Panel used to set armor value for a single location. Optionally used for rear location as well,
* and can be used to set the armor type for units with patchwork armor.
*
*
* @author Neoancient
*/
public class ArmorLocationView extends BuildView implements ChangeListener {
Expand All @@ -46,22 +46,22 @@ public void addListener(ArmorLocationListener l) {
public void removeListener(ArmorLocationListener l) {
listeners.remove(l);
}

private final SpinnerNumberModel spnPointsModel = new SpinnerNumberModel(0, 0, null, 1);
private final SpinnerNumberModel spnPointsRearModel = new SpinnerNumberModel(0, 0, null, 1);
private final JSpinner spnPoints = new JSpinner(spnPointsModel);
private final JSpinner spnPointsRear = new JSpinner(spnPointsRearModel);
private final JLabel lblRear = new JLabel();
private final JLabel lblMaxPoints = new JLabel();

private final int location;
private final String maxFormat;
private Integer maxPoints;
private boolean hasRear = false;

ArmorLocationView(int location) {
this.location = location;

ResourceBundle resourceMap = ResourceBundle.getBundle("megameklab.resources.Views");
lblRear.setText(resourceMap.getString("ArmorLocationView.lblRear.text"));
maxFormat = resourceMap.getString("ArmorLocationView.lblMax.format");
Expand All @@ -80,12 +80,12 @@ public void removeListener(ArmorLocationListener l) {
add(spnPointsRear, gbc);
gbc.gridy++;
gbc.weighty = 1.0;
add(lblMaxPoints, gbc);
add(lblMaxPoints, gbc);
}

/**
* Changes the location name in the title and whether it has a rear armor location.
*
*
* @param locName
* @param rear
*/
Expand All @@ -98,18 +98,18 @@ public void updateLocation(String locName, boolean rear) {
spnPointsRear.setValue(0);
}
}

/**
* @return The index (LOC_* constant) of the location managed by this view.
*/
public int getLocationIndex() {
return location;
}

/**
* Sets the maximum number of armor points that can be assigned to this location.
* A value of null indicates that there is no maximum.
*
*
* @param max
*/
public void setMaxPoints(@Nullable Integer max) {
Expand All @@ -123,17 +123,17 @@ public void setMaxPoints(@Nullable Integer max) {
lblMaxPoints.setText(String.format(maxFormat, max));
}
}

public void setMinimum(int minimum) {
spnPointsModel.setMinimum(minimum);
if (getPoints() < minimum) {
spnPointsModel.setValue(minimum);
}
}

/**
* Sets the number of points for this location. If the location has rear armor, this sets only the front.
*
*
* @param points
*/
public void setPoints(int points) {
Expand All @@ -148,7 +148,7 @@ public void setPoints(int points) {
}
spnPoints.addChangeListener(this);
}

/**
* @return The number of points of armor for this location (front).
*/
Expand All @@ -158,7 +158,7 @@ public int getPoints() {

/**
* Sets the number of points of armor for this location in the rear.
*
*
* @param points
*/
public void setPointsRear(int points) {
Expand All @@ -173,17 +173,22 @@ public void setPointsRear(int points) {
}
spnPointsRear.addChangeListener(this);
}

/**
* @return The number of points of rear armor in this location.
*/
public int getPointsRear() {
return spnPointsRearModel.getNumber().intValue();
}

@Override
public void stateChanged(ChangeEvent e) {
listeners.forEach(l -> l.armorPointsChanged(location, getPoints(), getPointsRear()));
}

public void omniLock(boolean unlocked) {
spnPoints.setEnabled(unlocked);
spnPointsRear.setEnabled(unlocked);
}

}
8 changes: 8 additions & 0 deletions megameklab/src/megameklab/ui/generalUnit/BasicInfoView.java
Original file line number Diff line number Diff line change
Expand Up @@ -619,4 +619,12 @@ private void openMUL() {
JOptionPane.showMessageDialog(this, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE);
}
}

public void omniLock(boolean unlocked) {
// If we allow these to change, they might change to a value where the base chassis is invalid, forcing it to change.
// By locking them when the base chassis is locked, we force the user to be careful around these fields.
txtYear.setEnabled(unlocked);
cbTechBase.setEnabled(unlocked);
cbTechLevel.setEnabled(unlocked);
}
}
5 changes: 5 additions & 0 deletions megameklab/src/megameklab/ui/generalUnit/HeatSinkView.java
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,9 @@ private void reportChange() {
}
}

public void omniLock(boolean unlocked) {
cbHSType.setEnabled(unlocked);
spnBaseCount.setEnabled(unlocked);
}

}
8 changes: 8 additions & 0 deletions megameklab/src/megameklab/ui/generalUnit/MVFArmorView.java
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,12 @@ public void actionPerformed(ActionEvent e) {
}
}

public void omniLock(boolean unlocked) {
cbArmorType.setEnabled(unlocked);
spnTonnage.setEnabled(unlocked);
chkPatchwork.setEnabled(unlocked);
btnMaximize.setEnabled(unlocked);
btnUseRemaining.setEnabled(unlocked);
}

}
31 changes: 26 additions & 5 deletions megameklab/src/megameklab/ui/generalUnit/MovementView.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public void removeListener(BuildListener l) {
private String[] runNames;
private String[] jumpNames;

private int baseChassisJets;
private int lockedMinJump = 0;

public MovementView(ITechManager techManager) {
this.techManager = techManager;
initUI();
Expand Down Expand Up @@ -184,12 +187,14 @@ public void setFromEntity(Entity en) {
industrial = (en instanceof Mek) && ((Mek) en).isIndustrial();
refresh();

Optional<MiscType> jj = en.getMisc().stream().map(Mounted::getType)
.filter(eq -> eq.hasFlag(MiscType.F_JUMP_JET) || eq.hasFlag(MiscType.F_UMU)
|| eq.hasFlag(MiscType.F_JUMP_BOOSTER)).findAny();
if (jj.isPresent()) {
List<MiscMounted> jets = en.getMisc().stream().filter(m -> {
var type = m.getType();
return type.hasFlag(MiscType.F_JUMP_JET) || type.hasFlag(MiscType.F_UMU) || type.hasFlag(MiscType.F_JUMP_BOOSTER);
}).toList();

if (!jets.isEmpty()) {
cbJumpType.removeActionListener(this);
cbJumpType.setSelectedItem(jj.get());
cbJumpType.setSelectedItem(jets.get(0).getType());
cbJumpType.addActionListener(this);
}
// LAMs have a minimum jump MP of 3, which implies a minimum walk
Expand Down Expand Up @@ -289,6 +294,7 @@ public void setFromEntity(Entity en) {
jump = jump0;
}
}
minJump = Math.max(minJump, lockedMinJump);
spnJumpModel.setMinimum(minJump);
spnJumpModel.setMaximum(maxJump);
spnJump.setValue(Math.max(minJump, jump0));
Expand Down Expand Up @@ -316,6 +322,8 @@ public void setFromEntity(Entity en) {
} else if (jump0 < minJump) {
spnJump.setValue(spnJumpModel.getMinimum());
}

baseChassisJets = (int) jets.stream().filter(m -> !m.isOmniPodMounted()).count();
}

public void refresh() {
Expand Down Expand Up @@ -433,4 +441,17 @@ public void actionPerformed(ActionEvent e) {
listeners.forEach(l -> l.jumpTypeChanged(getJumpJet()));
}
}

public void omniLock(boolean unlocked) {
spnWalk.setEnabled(unlocked);
if (unlocked) {
lockedMinJump = 0;
spnJumpModel.setMinimum(0);
} else {
lockedMinJump = baseChassisJets;
spnJumpModel.setMinimum(baseChassisJets);
}
spnJump.setModel(spnJumpModel);
cbJumpType.setEnabled(unlocked || baseChassisJets == 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,10 @@ public void actionPerformed(ActionEvent e) {
}
}

public void omniLock(boolean unlocked) {
for (var i : combos) {
i.setEnabled(unlocked);
}
}

}
3 changes: 2 additions & 1 deletion megameklab/src/megameklab/ui/listeners/MekBuildListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@

/**
* Listener for views used by Meks.
*
*
* @author Neoancient
*
*/
public interface MekBuildListener extends BuildListener {
void tonnageChanged(double tonnage);
void omniChanged(boolean omni);
void omniLockChanged(boolean omniLock);
void typeChanged(int baseType, int motiveType, long etype);
void structureChanged(EquipmentType structure);
void engineChanged(Engine engine);
Expand Down
Loading

0 comments on commit 9977dea

Please sign in to comment.