diff --git a/megameklab/resources/megameklab/resources/Views.properties b/megameklab/resources/megameklab/resources/Views.properties index 08e97ca7c..dd05df323 100644 --- a/megameklab/resources/megameklab/resources/Views.properties +++ b/megameklab/resources/megameklab/resources/Views.properties @@ -284,8 +284,10 @@ PlatoonTypeView.cbMotiveType.microlite=VTOL (Microlite) PlatoonTypeView.cbMotiveType.umu=SCUBA (Foot) PlatoonTypeView.cbMotiveType.umu_motorized=SCUBA (Motorized) PlatoonTypeView.cbMotiveType.submarine=SCUBA (Submarine) +PlatoonTypeView.cbMotiveType.beast_mounted=Beast-Mounted PlatoonTypeView.cbMotiveType.text=Motive Type: PlatoonTypeView.cbMotiveType.tooltip=Determines MP, movement mode, and maximum platoon and squad size. +PlatoonTypeView.lblBeastMountLabel.text=Mount Type: PlatoonTypeView.spnNumSquads.text=# Squads: PlatoonTypeView.spnNumSquads.tooltip=The number of squads in the platoon. PlatoonTypeView.spnSquadSize.text=Squad Size: @@ -310,6 +312,47 @@ InfantryWeaponView.cbNumGuns.tooltip=The number of field guns deployed by the un InfantryWeaponView.chkAntiMek.text=Anti-Mek Training: InfantryWeaponView.chkAntiMek.tooltip=A unit with anti-mek training has a higher platoon tonnage due to special equipment. +CIMountView.colType=Creature Type +CIMountView.colType.tooltip=The common name of the creature +CIMountView.colSize=Creature Size +CIMountView.colSize.tooltip=The size class of the creature +CIMountView.colWeight=Weight +CIMountView.colWeight.tooltip=The weight of each creature in tons. This does not include any mounted soldiers. +CIMountView.colMP=MP (Type) +CIMountView.colMP.tooltip=The number of movement points and movement type. +CIMountView.colBonusDamage=Bonus vs. Infantry (Vehicles) +CIMountView.colBonusDamage.tooltip=Bonus damage against infantry or vehicles in the same hex. +CIMountView.colDivisor=Damage Divisor +CIMountView.colDivisor.tooltip=Reduces incoming damage like an infantry armor kit. +CIMountView.colTerrain=Terrain Restrictions +CIMountView.colTerrain.tooltip=The mounted infantry unit cannot enter restricted terrain. +CIMountView.1groundMP=1 Ground MP on Land +CIMountView.asSubmarines=As Submarines +CIMountView.asVTOL=As VTOLs +CIMountView.waterDepth.format=Water (Depth %d+) +CIMountView.txtMountName.text=Type: +CIMountView.txtMountName.tooltip=The name of the species used as a mount +CIMountView.cbSize.text=Size: +CIMountView.cbSize.tooltip=The mount's size class +CIMountView.txtWeight.text=Weight: +CIMountView.txtWeight.tooltip=The weight of a single creature in tons +CIMountView.spnMovementPoints.text=Movement Points +CIMountView.spnMovementPoints.tooltip=The mount's speed using its primary movment mode +CIMountView.cbMovementMode.text=Movement Mode: +CIMountView.cbMovementMode.tooltip=The mount's primary movement mode +CIMountView.spnInfantryBonus.text=Infantry Bonus (D6): +CIMountView.spnInfantryBonus.tooltip=The number of dice of additional damage dealt against a conventional infantry target in the same hex +CIMountView.spnVehicleBonus.text=Vehicle Bonus: +CIMountView.spnVehicleBonus.tooltip=The amount of additional damage done to a vehicle target in the same hex +CIMountView.spnDamageDivisor.text=Damage Divisor: +CIMountView.spnDamageDivisor.tooltip=The armor protection provided by the mount +CIMountView.spnSecondaryGround.text=Secondary Ground MP: +CIMountView.spnSecondaryGround.tooltip=Movement points when using ground movement +CIMountView.spnMaxWaterDepth.text=Max Water Depth: +CIMountView.spnMaxWaterDepth.tooltip=The maximum depth of water that the unit can enter +CIMountView.spnUWEndurance.text=Endurance: +CIMountView.spnUWEndurance.tooltip=The number of turns the mount can spend underwater + BAEnhancementView.chkPartialWing.text=Partial Wing BAEnhancementView.chkPartialWing.tooltip=Increases jump MP when used in an atmosphere. IS tech base only. BAEnhancementView.chkJumpBooster.text=Jump Booster diff --git a/megameklab/src/megameklab/printing/PrintInfantry.java b/megameklab/src/megameklab/printing/PrintInfantry.java index 6798d68d4..b36c1f80b 100644 --- a/megameklab/src/megameklab/printing/PrintInfantry.java +++ b/megameklab/src/megameklab/printing/PrintInfantry.java @@ -190,9 +190,24 @@ private String generateNotesText(InfantryWeapon rangeWeapon) { if (infantry.hasSpaceSuit()) { sj.add("Can operate in vacuum."); } + int burst = 0; if (rangeWeapon.hasFlag(WeaponType.F_INF_BURST) || infantry.primaryWeaponDamageCapped()) { - sj.add("+1D6 damage vs. conventional infantry."); + burst = 1; + } + if (infantry.getMount() != null) { + burst += infantry.getMount().getBurstDamageDice(); + } + if (burst > 0) { + sj.add(String.format("+%dD6 damage vs. conventional infantry.", burst)); + } + if (infantry.getMount() != null) { + if (infantry.getMount().getVehicleDamage() > 0) { + sj.add(String.format("+%d damage vs. vehicles and 'Mechs", infantry.getMount().getVehicleDamage())); + } + if (infantry.getMount().getSize().toHitMod != 0) { + sj.add(String.format("%d attacker to-hit", infantry.getMount().getSize().toHitMod)); + } } if (rangeWeapon.hasFlag(WeaponType.F_INF_NONPENETRATING)) { sj.add("Can only damage conventional infantry units."); diff --git a/megameklab/src/megameklab/ui/infantry/CIMountView.java b/megameklab/src/megameklab/ui/infantry/CIMountView.java new file mode 100644 index 000000000..17a046909 --- /dev/null +++ b/megameklab/src/megameklab/ui/infantry/CIMountView.java @@ -0,0 +1,531 @@ +/* + * MegaMekLab + * Copyright (c) 2023 - The MegaMek Team. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +package megameklab.ui.infantry; + +import megamek.common.*; +import megameklab.ui.EntitySource; +import megameklab.ui.util.CustomComboBox; +import megameklab.ui.util.IView; +import megameklab.ui.util.RefreshListener; + +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.NumberFormat; +import java.util.Objects; +import java.util.ResourceBundle; + +public class CIMountView extends IView implements ActionListener, ChangeListener { + + private RefreshListener refresh = null; + + private final static String CARD_TABLE = "table"; + private final static String CARD_CUSTOM = "custom"; + + private final JButton btnSetMount = new JButton("Set Mount"); + private final JRadioButton rbtnStats = new JRadioButton("Stats"); + private final JRadioButton rbtnCustom = new JRadioButton("Custom"); + private final CreatureTableModel tableModel = new CreatureTableModel(); + private final JTable creatureTable = new JTable(); + private final JPanel creatureView = new JPanel(); + private final CardLayout equipmentLayout = new CardLayout(); + + private final JTextField txtMountName = new JTextField(); + private final JComboBox cbSize = new CustomComboBox<>(InfantryMount.BeastSize::displayName); + private final JTextField txtWeight = new JTextField(); + private final JSpinner spnMovementPoints = new JSpinner(); + private final JComboBox cbMovementMode = new JComboBox<>(); + private final JSpinner spnInfantryBonus = new JSpinner(); + private final JSpinner spnVehicleBonus = new JSpinner(); + private final JSpinner spnDamageDivisor = new JSpinner(); + private final JLabel lblMaxWaterDepth = new JLabel(); + private final JSpinner spnMaxWaterDepth = new JSpinner(); + private final JLabel lblSecondaryGround = new JLabel(); + private final JSpinner spnSecondaryGround = new JSpinner(); + private final JLabel lblUWEndurance = new JLabel(); + private final JSpinner spnUWEndurance = new JSpinner(); + + private final Dimension fieldSize = new Dimension(200, 28); + private final Dimension spinnerSize = new Dimension(120, 28); + private final ResourceBundle resourceMap = ResourceBundle.getBundle("megameklab.resources.Views"); + + public CIMountView(EntitySource eSource) { + super(eSource); + creatureTable.setModel(tableModel); + creatureTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + creatureTable.setShowGrid(false); + creatureTable.setDoubleBuffered(true); + TableCellRenderer renderer = tableModel.new Renderer(); + for (int i = 0; i < tableModel.getColumnCount(); i++) { + TableColumn column = creatureTable.getColumnModel().getColumn(i); + column.setCellRenderer(renderer); + } + creatureTable.getSelectionModel().addListSelectionListener(ev -> checkValid()); + + ButtonGroup bgroupView = new ButtonGroup(); + bgroupView.add(rbtnStats); + bgroupView.add(rbtnCustom); + + setUpPanels(); + rbtnStats.setSelected(true); + refresh(); + } + + private void setUpPanels() { + JPanel databasePanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + + gbc.gridx = 0; + gbc.gridy = 0; + gbc.anchor = GridBagConstraints.WEST; + databasePanel.add(btnSetMount, gbc); + btnSetMount.addActionListener(this); + + JPanel btnPanel = new JPanel(); + rbtnStats.addActionListener(ev -> { + equipmentLayout.show(creatureView, CARD_TABLE); + checkValid(); + }); + rbtnCustom.addActionListener(ev -> { + equipmentLayout.show(creatureView, CARD_CUSTOM); + checkValid(); + }); + btnPanel.add(rbtnStats); + btnPanel.add(rbtnCustom); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridwidth = GridBagConstraints.REMAINDER; + databasePanel.add(btnPanel, gbc); + + creatureView.setLayout(equipmentLayout); + + gbc.insets = new Insets(2,0,0,0); + gbc.gridx = 0; + gbc.gridy = 2; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.BOTH; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + databasePanel.add(creatureView, gbc); + + setLayout(new BorderLayout()); + this.add(databasePanel, BorderLayout.CENTER); + + JPanel tableView = new JPanel(new GridBagLayout()); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.BOTH; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + JScrollPane scroll = new JScrollPane(); + scroll.setViewportView(creatureTable); + tableView.add(scroll, gbc); + + creatureView.add(tableView, CARD_TABLE); + + JPanel customView = new JPanel(new GridBagLayout()); + gbc = new GridBagConstraints(); + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.fill = GridBagConstraints.REMAINDER; + + gbc.gridx = 0; + gbc.gridy = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.txtMountName.text")), gbc); + gbc.gridx = 1; + txtMountName.setToolTipText(resourceMap.getString("CIMountView.txtMountName.tooltip")); + setFieldSize(txtMountName, fieldSize); + txtMountName.addCaretListener(ev -> checkValid()); + customView.add(txtMountName, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.cbSize.text")), gbc); + gbc.gridx = 1; + cbSize.setToolTipText(resourceMap.getString("CIMountView.cbSize.tooltip")); + for (var size : InfantryMount.BeastSize.values()) { + cbSize.addItem(size); + } + setFieldSize(cbSize, fieldSize); + customView.add(cbSize, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.txtWeight.text")), gbc); + gbc.gridx = 1; + txtWeight.setToolTipText(resourceMap.getString("CIMountView.txtWeight.tooltip")); + setFieldSize(txtWeight, spinnerSize); + txtWeight.addCaretListener(ev -> checkValid()); + customView.add(txtWeight, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.spnMovementPoints.text")), gbc); + gbc.gridx = 1; + spnMovementPoints.setToolTipText(resourceMap.getString("CIMountView.spnMovementPoints.tooltip")); + initializeSpinner(spnMovementPoints); + spnMovementPoints.setModel(new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1)); + customView.add(spnMovementPoints, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.cbMovementMode.text")), gbc); + gbc.gridx = 1; + cbMovementMode.setToolTipText(resourceMap.getString("CIMountView.cbMovementMode.tooltip")); + cbMovementMode.addItem(EntityMovementMode.INF_LEG); + cbMovementMode.addItem(EntityMovementMode.INF_JUMP); + cbMovementMode.addItem(EntityMovementMode.VTOL); + cbMovementMode.addItem(EntityMovementMode.NAVAL); + cbMovementMode.addItem(EntityMovementMode.SUBMARINE); + setFieldSize(cbMovementMode, fieldSize); + cbMovementMode.addItemListener(ev -> movementModeChanged()); + customView.add(cbMovementMode, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.spnInfantryBonus.text")), gbc); + gbc.gridx = 1; + spnInfantryBonus.setToolTipText(resourceMap.getString("CIMountView.spnInfantryBonus.tooltip")); + initializeSpinner(spnInfantryBonus); + spnInfantryBonus.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1)); + customView.add(spnInfantryBonus, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.spnVehicleBonus.text")), gbc); + gbc.gridx = 1; + spnVehicleBonus.setToolTipText(resourceMap.getString("CIMountView.spnVehicleBonus.tooltip")); + initializeSpinner(spnVehicleBonus); + spnVehicleBonus.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1)); + customView.add(spnVehicleBonus, gbc); + + gbc.gridy++; + gbc.gridx = 0; + customView.add(new JLabel(resourceMap.getString("CIMountView.spnDamageDivisor.text")), gbc); + gbc.gridx = 1; + spnDamageDivisor.setToolTipText(resourceMap.getString("CIMountView.spnDamageDivisor.tooltip")); + initializeSpinner(spnDamageDivisor); + spnDamageDivisor.setModel(new SpinnerNumberModel(1.0, 1.0, Double.MAX_VALUE, 1.0)); + customView.add(spnDamageDivisor, gbc); + + gbc.gridy++; + gbc.gridx = 0; + lblMaxWaterDepth.setText(resourceMap.getString("CIMountView.spnMaxWaterDepth.text")); + customView.add(lblMaxWaterDepth, gbc); + gbc.gridx = 1; + spnMaxWaterDepth.setToolTipText(resourceMap.getString("CIMountView.spnMaxWaterDepth.tooltip")); + spnMaxWaterDepth.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1)); + initializeSpinner(spnMaxWaterDepth); + customView.add(spnMaxWaterDepth, gbc); + + gbc.gridy++; + gbc.gridx = 0; + lblSecondaryGround.setText(resourceMap.getString("CIMountView.spnSecondaryGround.text")); + customView.add(lblSecondaryGround, gbc); + gbc.gridx = 1; + spnSecondaryGround.setToolTipText(resourceMap.getString("CIMountView.spnSecondaryGround.tooltip")); + initializeSpinner(spnSecondaryGround); + spnSecondaryGround.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1)); + customView.add(spnSecondaryGround, gbc); + + gbc.gridy++; + gbc.gridx = 0; + lblUWEndurance.setText(resourceMap.getString("CIMountView.spnUWEndurance.text")); + customView.add(lblUWEndurance, gbc); + gbc.gridx = 1; + spnUWEndurance.setToolTipText(resourceMap.getString("CIMountView.spnUWEndurance.tooltip")); + initializeSpinner(spnUWEndurance); + spnUWEndurance.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1)); + customView.add(spnUWEndurance, gbc); + + gbc.gridx = 1; + gbc.gridy++; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + customView.add(new JLabel(), gbc); + + creatureView.add(customView, CARD_CUSTOM); + movementModeChanged(); + checkValid(); + } + + private void initializeSpinner(JSpinner spinner) { + JFormattedTextField tf = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField(); + tf.setEditable(false); + tf.setBackground(UIManager.getColor("TextField.background")); + setFieldSize(spinner, spinnerSize); + } + + public void setFieldSize(JComponent box, Dimension maxSize) { + box.setPreferredSize(maxSize); + box.setMaximumSize(maxSize); + box.setMinimumSize(maxSize); + } + + public void refresh() { + InfantryMount mount = getInfantry().getMount(); + if (mount != null) { + txtMountName.setText(mount.getName()); + cbSize.setSelectedItem(mount.getSize()); + txtWeight.setText(String.valueOf(mount.getWeight())); + spnMovementPoints.setValue(mount.getMP()); + cbMovementMode.setSelectedItem(mount.getMovementMode()); + spnInfantryBonus.setValue(mount.getBurstDamageDice()); + spnVehicleBonus.setValue(mount.getVehicleDamage()); + spnDamageDivisor.setValue(mount.getDamageDivisor()); + spnMaxWaterDepth.setValue(mount.getMaxWaterDepth()); + spnSecondaryGround.setValue(mount.getSecondaryGroundMP()); + spnUWEndurance.setValue(mount.getUWEndurance()); + } + } + + private void movementModeChanged() { + EntityMovementMode mode = Objects.requireNonNull((EntityMovementMode) cbMovementMode.getSelectedItem()); + lblMaxWaterDepth.setVisible(!mode.isSubmarine()); + spnMaxWaterDepth.setVisible(!mode.isSubmarine()); + lblSecondaryGround.setVisible(!mode.isLegInfantry()); + spnSecondaryGround.setVisible(!mode.isLegInfantry()); + lblUWEndurance.setVisible(mode.isSubmarine()); + spnUWEndurance.setVisible(mode.isSubmarine()); + } + + public void addRefreshedListener(RefreshListener l) { + refresh = l; + } + + private void checkValid() { + boolean valid; + if (rbtnStats.isSelected()) { + valid = creatureTable.getSelectedRow() >= 0; + } else { + valid = !txtMountName.getText().isEmpty(); + try { + valid &= Double.parseDouble(txtWeight.getText()) > 0; + } catch(NumberFormatException ignored) { + valid = false; + } + } + btnSetMount.setEnabled(valid); + } + + private InfantryMount selectedMount(int rowIndex) { + if ((rowIndex >= 0) && (rowIndex < tableModel.getRowCount())) { + return InfantryMount.sampleMounts.get(rowIndex); + } else { + return null; + } + } + + private double getCustomWeight() { + double weight = 0.5; + try { + weight = Double.parseDouble(txtWeight.getText()); + } catch (NumberFormatException ex) { + txtWeight.setText(String.valueOf(weight)); + } + return weight; + } + + private InfantryMount customMount() { + EntityMovementMode mode = (EntityMovementMode) Objects.requireNonNull(cbMovementMode.getSelectedItem()); + return new InfantryMount( + txtMountName.getText(), + (InfantryMount.BeastSize) cbSize.getSelectedItem(), + getCustomWeight(), + (Integer) spnMovementPoints.getValue(), + (EntityMovementMode) cbMovementMode.getSelectedItem(), + (Integer) spnInfantryBonus.getValue(), + (Integer) spnVehicleBonus.getValue(), + (Double) spnDamageDivisor.getValue(), + mode.isSubmarine() ? Integer.MAX_VALUE : (Integer) spnMaxWaterDepth.getValue(), + mode.isLegInfantry() ? 0 : (Integer) spnSecondaryGround.getValue(), + mode.isSubmarine() ? (Integer) spnUWEndurance.getValue() : 0 + ); + } + + @Override + public void actionPerformed(ActionEvent evt) { + removeAllListeners(); + if (evt.getSource().equals(btnSetMount)) { + if (rbtnStats.isSelected()) { + int view = creatureTable.getSelectedRow(); + if (view < 0) { + // Nothing is selected + return; + } + int selected = creatureTable.convertRowIndexToModel(view); + InfantryMount newMount = selectedMount(selected); + if ((getInfantry().getMount() != null) && (getInfantry().getMount().getMovementMode().isSubmarine()) + && ((newMount == null) || !newMount.getMovementMode().isSubmarine())) { + getInfantry().setSpecializations(getInfantry().getSpecializations() & ~Infantry.SCUBA); + } + getInfantry().setMount(selectedMount(selected)); + } else { + getInfantry().setMount(customMount()); + } + } + addAllListeners(); + if (refresh != null) { + refresh.refreshStructure(); + refresh.refreshStatus(); + refresh.refreshPreview(); + } + refresh(); + } + + private void addAllListeners() { + } + + private void removeAllListeners() { + } + + @Override + public void stateChanged(ChangeEvent e) { + JSpinner field = (JSpinner) e.getSource(); + double value = (Double) field.getModel().getValue(); + getInfantry().setArmorDamageDivisor(value); + if (refresh != null) { + refresh.refreshStructure(); + refresh.refreshStatus(); + refresh.refreshPreview(); + } + refresh(); + } + + private static final class CreatureTableModel implements TableModel { + private enum Columns { + TYPE("CIMountView.colType", "CIMountView.colType.tooltip", SwingConstants.LEFT), + SIZE("CIMountView.colSize", "CIMountView.colSize.tooltip", SwingConstants.LEFT), + WEIGHT("CIMountView.colWeight", "CIMountView.colWeight.tooltip", SwingConstants.LEFT), + MP("CIMountView.colMP", "CIMountView.colMP.tooltip", SwingConstants.LEFT), + BONUS_DAMAGE("CIMountView.colBonusDamage", "CIMountView.colBonusDamage.tooltip", SwingConstants.CENTER), + DIVISOR("CIMountView.colDivisor", "CIMountView.colDivisor.tooltip", SwingConstants.CENTER), + TERRAIN("CIMountView.colTerrain", "CIMountView.colTerrain.tooltip", SwingConstants.LEFT); + + final String resId; + final String tooltipId; + final int alignment; + + Columns(String resId, String tooltipId, int alignment) { + this.resId = resId; + this.tooltipId = tooltipId; + this.alignment = alignment; + } + } + + private final ResourceBundle resourceMap = ResourceBundle.getBundle("megameklab.resources.Views"); + + @Override + public int getRowCount() { + return InfantryMount.sampleMounts.size(); + } + + @Override + public int getColumnCount() { + return Columns.values().length; + } + + @Override + public String getColumnName(int columnIndex) { + return resourceMap.getString(Columns.values()[columnIndex].resId); + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + var creature = InfantryMount.sampleMounts.get(rowIndex); + switch (Columns.values()[columnIndex]) { + case TYPE: + return creature.getName(); + case SIZE: + return creature.getSize().displayName(); + case WEIGHT: + return NumberFormat.getInstance().format(creature.getWeight()); + case MP: + return String.format("%d (%s)", creature.getMP(), creature.getMovementMode().toString()); + case BONUS_DAMAGE: + return String.format("%+d%s (%d)", creature.getBurstDamageDice(), + creature.getBurstDamageDice() > 0 ? "D6" : "", creature.getVehicleDamage()); + case DIVISOR: + return NumberFormat.getInstance().format(creature.getDamageDivisor()); + case TERRAIN: + if (creature.getSecondaryGroundMP() > 0) { + return resourceMap.getString("CIMountView.1groundMP"); + } else if (creature.getMovementMode().isSubmarine()) { + return resourceMap.getString("CIMountView.asSubmarines"); + } else if (creature.getMovementMode().isVTOL()) { + return resourceMap.getString("CIMountView.asVTOL"); + } else { + return String.format(resourceMap.getString("CIMountView.waterDepth.format"), + creature.getMaxWaterDepth() + 1); + } + } + return "?"; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + + } + + @Override + public void addTableModelListener(TableModelListener l) { + + } + + @Override + public void removeTableModelListener(TableModelListener l) { + + } + + int getAlignment(int columnIndex) { + return Columns.values()[columnIndex].alignment; + } + + String getTooltip(int columnIndex) { + return resourceMap.getString(Columns.values()[columnIndex].tooltipId); + } + + public class Renderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, boolean isSelected, boolean hasFocus, int row, + int column) { + super.getTableCellRendererComponent(table, value, isSelected, + hasFocus, row, column); + int actualCol = table.convertColumnIndexToModel(column); + setHorizontalAlignment(getAlignment(actualCol)); + setToolTipText(getTooltip(actualCol)); + return this; + } + } + } +} diff --git a/megameklab/src/megameklab/ui/infantry/CIPlatoonTypeView.java b/megameklab/src/megameklab/ui/infantry/CIPlatoonTypeView.java index cf52cc0ed..f82bea0f6 100644 --- a/megameklab/src/megameklab/ui/infantry/CIPlatoonTypeView.java +++ b/megameklab/src/megameklab/ui/infantry/CIPlatoonTypeView.java @@ -31,6 +31,7 @@ import megamek.common.EntityMovementMode; import megamek.common.ITechManager; import megamek.common.Infantry; +import megamek.common.InfantryMount; import megamek.common.verifier.TestInfantry; import megameklab.ui.util.CustomComboBox; import megameklab.ui.generalUnit.BuildView; @@ -62,7 +63,8 @@ private enum InfantryMotiveType { MICROLITE(EntityMovementMode.VTOL, false, "PlatoonTypeView.cbMotiveType.microlite"), UMU(EntityMovementMode.INF_UMU, false, "PlatoonTypeView.cbMotiveType.umu"), UMU_MOTORIZED(EntityMovementMode.INF_UMU, false, "PlatoonTypeView.cbMotiveType.umu_motorized"), - SUBMARINE(EntityMovementMode.SUBMARINE, false, "PlatoonTypeView.cbMotiveType.submarine"); + SUBMARINE(EntityMovementMode.SUBMARINE, false, "PlatoonTypeView.cbMotiveType.submarine"), + BEAST_MOUNTED(EntityMovementMode.NONE, false, "PlatoonTypeView.cbMotiveType.beast_mounted"); final EntityMovementMode mode; final boolean legalFieldGun; @@ -84,11 +86,14 @@ private enum InfantryMotiveType { private final JSpinner spnSquadSize = new JSpinner(spnSquadSizeModel); private final JLabel lblMaxSize = new JLabel(); private final JLabel lblMaxSquadSize = new JLabel(); + private final JLabel lblBeastMountLabel = new JLabel(); + private final JLabel lblBeastMountType = new JLabel(); private final ITechManager techManager; private int specialization = 0; private boolean isFieldGunner = false; + private InfantryMount mount = null; public CIPlatoonTypeView(ITechManager techManager) { this.techManager = techManager; @@ -112,13 +117,20 @@ private void initUI() { cbMotiveType.setToolTipText(resourceMap.getString("PlatoonTypeView.cbMotiveType.tooltip")); add(cbMotiveType, gbc); cbMotiveType.addActionListener(this); - + + gbc.gridx = 0; + gbc.gridy++; + gbc.gridwidth = 1; + lblBeastMountLabel.setText(resourceMap.getString("PlatoonTypeView.lblBeastMountLabel.text")); + add(lblBeastMountLabel, gbc); + gbc.gridx++; + add(lblBeastMountType, gbc); + gbc.gridx = 0; - gbc.gridy = 1; + gbc.gridy++; gbc.gridwidth = 1; add(new JLabel(resourceMap.getString("PlatoonTypeView.spnNumSquads.text")), gbc); gbc.gridx = 1; - gbc.gridy = 1; gbc.gridwidth = 3; setFieldSize(spnNumSquads, spinnerSize); spnNumSquads.setToolTipText(resourceMap.getString("PlatoonTypeView.spnNumSquads.tooltip")); @@ -126,11 +138,10 @@ private void initUI() { spnNumSquads.addChangeListener(this); gbc.gridx = 0; - gbc.gridy = 2; + gbc.gridy++; gbc.gridwidth = 1; add(new JLabel(resourceMap.getString("PlatoonTypeView.spnSquadSize.text")), gbc); gbc.gridx = 1; - gbc.gridy = 2; gbc.gridwidth = 3; setFieldSize(spnSquadSize, spinnerSize); spnSquadSize.setToolTipText(resourceMap.getString("PlatoonTypeView.spnSquadSize.tooltip")); @@ -139,20 +150,18 @@ private void initUI() { gbc.insets = new Insets(0, 5, 0, 5); gbc.gridx = 0; - gbc.gridy = 3; + gbc.gridy++; gbc.gridwidth = 1; add(new JLabel(resourceMap.getString("PlatoonTypeView.lblMaxSize.text")), gbc); gbc.gridx = 1; - gbc.gridy = 3; lblMaxSize.setToolTipText(resourceMap.getString("PlatoonTypeView.lblMaxSize.tooltip")); add(lblMaxSize, gbc); gbc.gridx = 2; - gbc.gridy = 3; + gbc.gridy++; gbc.gridwidth = 1; add(new JLabel(resourceMap.getString("PlatoonTypeView.lblMaxSquadSize.text")), gbc); gbc.gridx = 3; - gbc.gridy = 3; lblMaxSquadSize.setToolTipText(resourceMap.getString("PlatoonTypeView.lblMaxSquadSize.tooltip")); add(lblMaxSquadSize, gbc); @@ -161,15 +170,18 @@ private void initUI() { public void setFromEntity(Infantry inf) { specialization = inf.getSpecializations(); isFieldGunner = inf.hasFieldWeapon(); + mount = inf.getMount(); refresh(); cbMotiveType.removeActionListener(this); - if (inf.getMovementMode() == EntityMovementMode.VTOL) { + if (inf.getMount() != null) { + cbMotiveType.setSelectedItem(InfantryMotiveType.BEAST_MOUNTED); + } else if (inf.getMovementMode() == EntityMovementMode.VTOL) { cbMotiveType.setSelectedItem(inf.hasMicrolite() ? InfantryMotiveType.MICROLITE : InfantryMotiveType.VTOL); } else if (inf.getMovementMode() == EntityMovementMode.INF_UMU) { cbMotiveType.setSelectedItem((inf.getOriginalJumpMP() > 1) ? InfantryMotiveType.UMU_MOTORIZED : InfantryMotiveType.UMU); - } else { + } else { for (var type : InfantryMotiveType.values()) { if (type.mode == inf.getMovementMode()) { cbMotiveType.setSelectedItem(type); @@ -178,14 +190,14 @@ public void setFromEntity(Infantry inf) { } cbMotiveType.addActionListener(this); - if (inf.getSquadCount() <= (Integer)spnNumSquadsModel.getMaximum()) { + if (inf.getSquadCount() <= (Integer) spnNumSquadsModel.getMaximum()) { spnNumSquads.removeChangeListener(this); spnNumSquads.setValue(inf.getSquadCount()); spnNumSquads.addChangeListener(this); } else { spnNumSquads.setValue(spnNumSquadsModel.getMaximum()); } - if (inf.getSquadSize() <= (Integer)spnSquadSizeModel.getMaximum()) { + if (inf.getSquadSize() <= (Integer) spnSquadSizeModel.getMaximum()) { spnSquadSize.removeChangeListener(this); spnSquadSize.setValue(inf.getSquadSize()); spnSquadSize.addChangeListener(this); @@ -219,8 +231,8 @@ public void refresh() { } int maxSize = TestInfantry.maxUnitSize(getMovementMode(), isAltMode(), - (specialization & (Infantry.COMBAT_ENGINEERS | Infantry.MOUNTAIN_TROOPS)) != 0); - int maxSquad = TestInfantry.maxSquadSize(getMovementMode(), isAltMode()); + (specialization & (Infantry.COMBAT_ENGINEERS | Infantry.MOUNTAIN_TROOPS)) != 0, mount); + int maxSquad = TestInfantry.maxSquadSize(getMovementMode(), isAltMode(), mount); spnNumSquads.removeChangeListener(this); spnSquadSize.removeChangeListener(this); spnNumSquadsModel.setMaximum(maxSize / spnSquadSizeModel.getNumber().intValue()); @@ -230,6 +242,14 @@ public void refresh() { lblMaxSize.setText(String.valueOf(maxSize)); lblMaxSquadSize.setText(String.valueOf(maxSquad)); + if (getMovementMode().isNone()) { + lblBeastMountType.setText(mount.getName()); + lblBeastMountLabel.setVisible(true); + lblBeastMountType.setVisible(true); + } else { + lblBeastMountLabel.setVisible(false); + lblBeastMountType.setVisible(false); + } } public EntityMovementMode getMovementMode() { diff --git a/megameklab/src/megameklab/ui/infantry/CIStructureTab.java b/megameklab/src/megameklab/ui/infantry/CIStructureTab.java index 3abe56ebf..cfb18c55b 100644 --- a/megameklab/src/megameklab/ui/infantry/CIStructureTab.java +++ b/megameklab/src/megameklab/ui/infantry/CIStructureTab.java @@ -53,13 +53,14 @@ public class CIStructureTab extends ITab implements InfantryBuildListener { public static final int T_FIELD_GUNS = 1; public static final int T_ARMOR_KIT = 2; public static final int T_SPECIALIZATION = 3; - public static final int T_AUGMENTATION = 4; + public static final int T_MOUNT = 4; + public static final int T_AUGMENTATION = 5; private BasicInfoView panBasicInfo; private CIPlatoonTypeView panPlatoonType; private CIWeaponView panWeapons; - private String[] tabNames = {"Weapons", "Field Guns", "Armor Kit", "Specializations", "Augmentation"}; + private String[] tabNames = {"Weapons", "Field Guns", "Armor Kit", "Specializations", "Mount", "Augmentation"}; private JTextField txtArmor = new JTextField("None"); private JTextPane txtSpecializations = new JTextPane(); @@ -71,6 +72,7 @@ public class CIStructureTab extends ITab implements InfantryBuildListener { private CIFieldGunView fieldGunView; private CIArmorView armorView; private CISpecializationView specializationView; + private CIMountView mountView; private CIAugmentationView augmentationView; public CIStructureTab(EntitySource eSource) { @@ -82,6 +84,7 @@ public CIStructureTab(EntitySource eSource) { fieldGunView = new CIFieldGunView(eSource, panBasicInfo); armorView = new CIArmorView(eSource, panBasicInfo); specializationView = new CISpecializationView(eSource); + mountView = new CIMountView(eSource); augmentationView = new CIAugmentationView(eSource); setUpPanels(); refresh(); @@ -98,7 +101,7 @@ public void setUpPanels() { txtArmor.setEditable(false); txtSpecializations.setEditable(false); - txtSpecializations.setContentType("text/html"); + txtSpecializations.setContentType("text/html"); txtAugmentations.setEditable(false); txtAugmentations.setContentType("text/html"); @@ -138,6 +141,7 @@ public void setUpPanels() { equipmentPane.addTab(tabNames[T_FIELD_GUNS], fieldGunView); equipmentPane.addTab(tabNames[T_ARMOR_KIT], armorView); equipmentPane.addTab(tabNames[T_SPECIALIZATION], specializationView); + equipmentPane.addTab(tabNames[T_MOUNT], mountView); equipmentPane.addTab(tabNames[T_AUGMENTATION], augmentationView); leftPanel.add(panBasicInfo); @@ -225,6 +229,7 @@ public void refresh() { fieldGunView.refresh(); armorView.refresh(); specializationView.refresh(); + mountView.refresh(); augmentationView.refresh(); enableTabs(); @@ -251,6 +256,7 @@ public void addRefreshedListener(RefreshListener l) { weaponView.addRefreshedListener(refresh); fieldGunView.addRefreshedListener(refresh); armorView.addRefreshedListener(refresh); + mountView.addRefreshedListener(refresh); augmentationView.addRefreshedListener(refresh); } @@ -285,12 +291,13 @@ private void enableTabs() { if (level.ordinal() >= SimpleTechLevel.ADVANCED.ordinal()) { txtArmor.setEnabled(true); txtSpecializations.setEnabled(true); - equipmentPane.setEnabledAt(T_FIELD_GUNS, + equipmentPane.setEnabledAt(T_FIELD_GUNS, getInfantry().getMovementMode() == EntityMovementMode.INF_MOTORIZED || getInfantry().getMovementMode() == EntityMovementMode.TRACKED || getInfantry().getMovementMode() == EntityMovementMode.WHEELED); equipmentPane.setEnabledAt(T_ARMOR_KIT, true); equipmentPane.setEnabledAt(T_SPECIALIZATION, true); + equipmentPane.setEnabledAt(T_MOUNT, getInfantry().getMount() != null); //Experimental level txtAugmentations.setEnabled(level.ordinal() >= SimpleTechLevel.EXPERIMENTAL.ordinal()); equipmentPane.setEnabledAt(T_AUGMENTATION, level.ordinal() >= SimpleTechLevel.EXPERIMENTAL.ordinal()); @@ -301,6 +308,7 @@ private void enableTabs() { equipmentPane.setEnabledAt(T_FIELD_GUNS, false); equipmentPane.setEnabledAt(T_ARMOR_KIT, false); equipmentPane.setEnabledAt(T_SPECIALIZATION, false); + equipmentPane.setEnabledAt(T_MOUNT, false); equipmentPane.setEnabledAt(T_AUGMENTATION, false); } if (!equipmentPane.isEnabledAt(equipmentPane.getSelectedIndex())) { @@ -340,7 +348,7 @@ public void yearChanged(int year) { @Override public void updateTechLevel() { - if (!panBasicInfo.isLegal(Infantry.getMotiveTechAdvancement(getInfantry().getMovementMode()))) { + if (!panBasicInfo.isLegal(getInfantry().getMotiveTechAdvancement())) { motiveTypeChanged(EntityMovementMode.INF_LEG, false); } getInfantry().setTechLevel(panBasicInfo.getTechLevel().getCompoundTechLevel(panBasicInfo.useClanTechBase())); @@ -387,8 +395,12 @@ public void manualBVChanged(int manualBV) { public void motiveTypeChanged(EntityMovementMode movementMode, boolean alt) { if (alt && (movementMode == EntityMovementMode.INF_UMU)) { getInfantry().setMotorizedScuba(); + } else if (movementMode.isNone()) { + // Pick a default + getInfantry().setMount(InfantryMount.HORSE); } else { getInfantry().setMovementMode(movementMode); + getInfantry().setMount(null); } getInfantry().setMicrolite(alt && (movementMode == EntityMovementMode.VTOL)); @@ -401,6 +413,7 @@ && getInfantry().getMovementMode() != EntityMovementMode.WHEELED) { panPlatoonType.setFromEntity(getInfantry()); panWeapons.setFromEntity(getInfantry()); specializationView.refresh(); + mountView.refresh(); refresh.refreshPreview(); refresh.refreshStatus(); }