diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 211842c1928..a3287a2975f 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -1418,9 +1418,9 @@ CustomMechDialog.labDeploymentOffset=Deployment Zone Offset: CustomMechDialog.labDeploymentOffsetTip=Deployment Zone Offset, in hexes from corresponding map edge CustomMechDialog.labDeploymentWidth=Deployment Zone Width: CustomMechDialog.labDeploymentWidthTip=Deployment Zone width, in hexes -CustomMechDialog.labDeployShutdown=Deploy Shutdown -CustomMechDialog.labDeployProne=Deploy Prone -CustomMechDialog.labDeployHullDown=Deploy Hull Down +CustomMechDialog.labDeployShutdown=Shutdown +CustomMechDialog.labDeployProne=Prone +CustomMechDialog.labDeployHullDown=Hull Down CustomMechDialog.labDEST=DEST suit? CustomMechDialog.labDriving=Driving: CustomMechDialog.labDump=Dump this ammobin @@ -1486,6 +1486,7 @@ CustomMechDialog.South=South CustomMechDialog.StartOfGame=Start of game CustomMechDialog.switchToRapidFire=Machine Gun ({0}) Switch to rapid-fire mode CustomMechDialog.tabDeployment=Deployment +CustomMechDialog.tabState=State CustomMechDialog.tabEquipment=Equipment CustomMechDialog.tabPilot=Pilot CustomMechDialog.tabCrew=Crew Abilities @@ -4435,3 +4436,8 @@ CASCardPanel.MUL=Open MUL CASCardPanel.conversionReport=Conversion Report CASCardPanel.font=Font: CASCardPanel.cardSize=Card Size: + +#Gamemaster Menu Text +Gamemaster.Gamemaster=Gamemaster +Gamemaster.EditDamage=Edit Damage +Gamemaster.Configure=Configure \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index 86d83b3792a..1b8fc0a4d8d 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -2244,6 +2244,16 @@ public void gamePlayerConnected(GamePlayerConnectedEvent e) { } + @Override + public void gameEntityChange(GameEntityChangeEvent e) { + if ((unitDisplay != null) && (unitDisplay.getCurrentEntity() != null) + && (e.getEntity() != null) + && (unitDisplay.getCurrentEntity().getId() == e.getEntity().getId())) { + // underlying object may have changed, so reset + unitDisplay.displayEntity(e.getEntity()); + } + } + @Override public void gameReport(GameReportEvent e) { // Normally the Report Display is updated when the panel is diff --git a/megamek/src/megamek/client/ui/swing/CustomMechDialog.java b/megamek/src/megamek/client/ui/swing/CustomMechDialog.java index a2c196429e2..70f6b9f689d 100644 --- a/megamek/src/megamek/client/ui/swing/CustomMechDialog.java +++ b/megamek/src/megamek/client/ui/swing/CustomMechDialog.java @@ -24,7 +24,6 @@ import megamek.common.util.fileUtils.MegaMekFile; import megamek.common.verifier.*; import megamek.common.weapons.bayweapons.ArtilleryBayWeapon; -import megamek.common.weapons.bayweapons.BayWeapon; import megamek.common.weapons.bayweapons.CapitalMissileBayWeapon; import javax.swing.*; @@ -35,7 +34,6 @@ import java.awt.event.*; import java.util.List; import java.util.*; -import java.util.stream.Collectors; /** * A dialog that a player can use to customize his mech before battle. @@ -78,12 +76,12 @@ public class CustomMechDialog extends AbstractButtonDialog implements ActionList Messages.getString("CustomMechDialog.labDeploymentWidth"), SwingConstants.RIGHT); private final JComboBox choDeploymentRound = new JComboBox<>(); private final JComboBox choDeploymentZone = new JComboBox<>(); - + // this might seem like kind of a dumb way to declare it, but JFormattedTextField doesn't have an overload that // takes both a number formatter and a default value. private final NumberFormatter numFormatter = new NumberFormatter(); private final DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(numFormatter); - + private final JFormattedTextField txtDeploymentOffset = new JFormattedTextField(formatterFactory); private final JFormattedTextField txtDeploymentWidth = new JFormattedTextField(formatterFactory); @@ -148,6 +146,7 @@ public class CustomMechDialog extends AbstractButtonDialog implements ActionList private ArrayList partRepsComps = new ArrayList<>(); private final boolean editable; + private final boolean editableDeployment; private OffBoardDirection direction = OffBoardDirection.NONE; private int distance = 17; @@ -157,6 +156,13 @@ public class CustomMechDialog extends AbstractButtonDialog implements ActionList * Creates new CustomMechDialog */ public CustomMechDialog(ClientGUI clientgui, Client client, List entities, boolean editable) { + this(clientgui, client, entities, editable, true); + } + + /** + * Creates new CustomMechDialog + */ + public CustomMechDialog(ClientGUI clientgui, Client client, List entities, boolean editable, boolean editableDeployment) { super(clientgui.getFrame(), "CustomizeMechDialog", "CustomMechDialog.title"); this.entities = entities; @@ -164,6 +170,7 @@ public CustomMechDialog(ClientGUI clientgui, Client client, List entitie this.client = client; this.space = clientgui.getClient().getMapSettings().getMedium() == Board.T_SPACE; this.editable = editable; + this.editableDeployment = editableDeployment; // Ensure we have at least one passed entity, anything less makes no sense if (entities.size() < 1) { @@ -172,7 +179,6 @@ public CustomMechDialog(ClientGUI clientgui, Client client, List entitie initialize(); } - public String getSelectedTab() { return tabAll.getTitleAt(tabAll.getSelectedIndex()); } @@ -182,7 +188,7 @@ public void setSelectedTab(int idx) { tabAll.setSelectedIndex(idx); } } - + public void setSelectedTab(String tabName) { for (int i = 0; i < tabAll.getTabCount(); i++) { if (tabAll.getTitleAt(i).equals(tabName)) { @@ -331,7 +337,7 @@ private void addOption(IOption option, GridBagLayout gridbag, GridBagConstraints PilotSPAHelper.weaponSpecialistValidWeaponNames(entity, gameOptions()).forEach(optionComp::addValue); optionComp.setSelected(option.stringValue()); } - + if ((OptionsConstants.GUNNERY_SANDBLASTER).equals(option.getName())) { optionComp.addValue(Messages.getString("CustomMechDialog.None")); PilotSPAHelper.sandblasterValidWeaponNames(entity, gameOptions()).forEach(optionComp::addValue); @@ -396,7 +402,7 @@ public int getStatus() { private void refreshDeployment() { Entity entity = entities.get(0); - + if (entity instanceof QuadVee) { choStartingMode.removeItemListener(this); choStartingMode.removeAllItems(); @@ -423,11 +429,11 @@ private void refreshDeployment() { updateStartingModeOptions(); choStartingMode.addItemListener(this); } - + choDeploymentZone.removeItemListener(this); txtDeploymentOffset.setEnabled(false); txtDeploymentWidth.setEnabled(false); - + choDeploymentRound.removeAllItems(); choDeploymentRound.addItem(Messages.getString("CustomMechDialog.StartOfGame")); @@ -445,7 +451,7 @@ private void refreshDeployment() { if (entity.getTransportId() != Entity.NONE) { choDeploymentRound.setEnabled(false); } - + choDeploymentZone.removeAllItems(); choDeploymentZone.addItem(Messages.getString("CustomMechDialog.useOwners")); choDeploymentZone.addItem(Messages.getString("CustomMechDialog.deployAny")); @@ -461,16 +467,23 @@ private void refreshDeployment() { choDeploymentZone.addItem(Messages.getString("CustomMechDialog.deployCenter")); choDeploymentZone.setSelectedIndex(entity.getStartingPos(false) + 1); - + choDeploymentZone.addItemListener(this); txtDeploymentOffset.setText(Integer.toString(entity.getStartingOffset(false))); txtDeploymentWidth.setText(Integer.toString(entity.getStartingWidth(false))); - + boolean enableDeploymentZoneControls = choDeploymentZone.isEnabled() && (choDeploymentZone.getSelectedIndex() > 0); txtDeploymentOffset.setEnabled(enableDeploymentZoneControls); txtDeploymentWidth.setEnabled(enableDeploymentZoneControls); - + + // disable some options if not allowed to edit deployment + choStartingMode.setEnabled(editableDeployment); + choDeploymentZone.setEnabled(editableDeployment); + txtDeploymentOffset.setEnabled(editableDeployment); + txtDeploymentWidth.setEnabled(editableDeployment); + choDeploymentRound.setEnabled(editableDeployment); + chHidden.removeActionListener(this); boolean enableHidden = !(entity instanceof Dropship) && !entity.isAirborne() && !entity.isAirborneVTOLorWIGE(); labHidden.setEnabled(enableHidden); @@ -531,7 +544,7 @@ public void actionPerformed(ActionEvent actionEvent) { maxDistance = nDistance; } } - + } Slider sl = new Slider( clientgui.frame, @@ -545,7 +558,7 @@ public void actionPerformed(ActionEvent actionEvent) { butOffBoardDistance.setText(Integer.toString(distance)); return; } - + if (actionEvent.getActionCommand().equals("missing")) { //If we're down to a single crew member, do not allow any more to be removed. final long remaining = Arrays.stream(panCrewMember).filter(p -> !p.getMissing()).count(); @@ -887,34 +900,15 @@ protected void okAction() { } okay = true; - clientgui.chatlounge.refreshEntities(); + if ((clientgui != null) && (clientgui.chatlounge != null)) { + clientgui.chatlounge.refreshEntities(); + } // Check validity of units after customization + EntityVerifier verifier = EntityVerifier.getInstance(new MegaMekFile( + Configuration.unitsDir(), EntityVerifier.CONFIG_FILENAME).getFile()); for (Entity entity : entities) { - EntityVerifier verifier = EntityVerifier.getInstance(new MegaMekFile( - Configuration.unitsDir(), EntityVerifier.CONFIG_FILENAME).getFile()); - TestEntity testEntity = null; - if (entity instanceof Mech) { - testEntity = new TestMech((Mech) entity, verifier.mechOption, null); - } else if ((entity instanceof Tank) - && !(entity instanceof GunEmplacement)) { - if (entity.isSupportVehicle()) { - testEntity = new TestSupportVehicle(entity, verifier.tankOption, null); - } else { - testEntity = new TestTank((Tank) entity, verifier.tankOption, null); - } - } else if (entity.getEntityType() == Entity.ETYPE_AERO - && entity.getEntityType() != Entity.ETYPE_DROPSHIP - && entity.getEntityType() != Entity.ETYPE_SMALL_CRAFT - && entity.getEntityType() != Entity.ETYPE_FIGHTER_SQUADRON - && entity.getEntityType() != Entity.ETYPE_JUMPSHIP - && entity.getEntityType() != Entity.ETYPE_SPACE_STATION) { - testEntity = new TestAero((Aero) entity, verifier.mechOption, null); - } else if (entity instanceof BattleArmor) { - testEntity = new TestBattleArmor((BattleArmor) entity, verifier.baOption, null); - } else if (entity instanceof Infantry) { - testEntity = new TestInfantry((Infantry) entity, verifier.infOption, null); - } + TestEntity testEntity = getTestEntity(entity, verifier); int gameTL = TechConstants.getGameTechLevel(client.getGame(), entity.isClan()); entity.setDesignValid((testEntity == null) || testEntity.correctEntity(new StringBuffer(), gameTL)); } @@ -922,6 +916,37 @@ protected void okAction() { setVisible(false); } + /** + * copied from megameklab.util.UnitUtil.getEntityVerifier + * @param unit the supplied entity + * @param entityVerifier the entity verifier loaded from a UnitVerifierOptions.xml + * @return a TestEntity instance for the supplied Entity. + */ + public static TestEntity getTestEntity(Entity unit, EntityVerifier entityVerifier) { + // FIXME move the same method from megameklab.util.UnitUtil.getEntityVerifier to common + TestEntity testEntity = null; + if (unit.hasETypeFlag(Entity.ETYPE_MECH)) { + testEntity = new TestMech((Mech) unit, entityVerifier.mechOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_PROTOMECH)) { + testEntity = new TestProtomech((Protomech) unit, entityVerifier.protomechOption, null); + } else if (unit.isSupportVehicle()) { + testEntity = new TestSupportVehicle(unit, entityVerifier.tankOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_TANK)) { + testEntity = new TestTank((Tank) unit, entityVerifier.tankOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_SMALL_CRAFT)) { + testEntity = new TestSmallCraft((SmallCraft) unit, entityVerifier.aeroOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_JUMPSHIP)) { + testEntity = new TestAdvancedAerospace((Jumpship) unit, entityVerifier.aeroOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_AERO)) { + testEntity = new TestAero((Aero) unit, entityVerifier.aeroOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_BATTLEARMOR)) { + testEntity = new TestBattleArmor((BattleArmor) unit, entityVerifier.baOption, null); + } else if (unit.hasETypeFlag(Entity.ETYPE_INFANTRY)) { + testEntity = new TestInfantry((Infantry)unit, entityVerifier.infOption, null); + } + return testEntity; + } + @Override public void itemStateChanged(ItemEvent itemEvent) { if (itemEvent.getSource().equals(choStartingMode)) { @@ -935,7 +960,7 @@ public void itemStateChanged(ItemEvent itemEvent) { chDeployProne.setSelected(false); return; } - + if (itemEvent.getSource().equals(choDeploymentZone)) { boolean enableDeploymentZoneControls = choDeploymentZone.isEnabled() && (choDeploymentZone.getSelectedIndex() > 0); txtDeploymentOffset.setEnabled(enableDeploymentZoneControls); @@ -1074,7 +1099,9 @@ protected Container createCenterPane() { } tabAll.addTab(Messages.getString("CustomMechDialog.tabEquipment"), scrEquip); } - tabAll.addTab(Messages.getString("CustomMechDialog.tabDeployment"), new JScrollPane(panDeploy)); + tabAll.addTab(Messages.getString( + editableDeployment ? "CustomMechDialog.tabDeployment" : "CustomMechDialog.tabState" ), + new JScrollPane(panDeploy)); if (quirksEnabled && !multipleEntities) { JScrollPane scrQuirks = new JScrollPane(panQuirks); scrQuirks.getVerticalScrollBar().setUnitIncrement(16); diff --git a/megamek/src/megamek/client/ui/swing/MapMenu.java b/megamek/src/megamek/client/ui/swing/MapMenu.java index 6fa774ac85b..1f03cf25e04 100644 --- a/megamek/src/megamek/client/ui/swing/MapMenu.java +++ b/megamek/src/megamek/client/ui/swing/MapMenu.java @@ -30,6 +30,8 @@ import megamek.common.actions.WeaponAttackAction; import megamek.common.annotations.Nullable; import megamek.common.options.OptionsConstants; +import megamek.common.util.fileUtils.MegaMekFile; +import megamek.common.verifier.*; import megamek.common.weapons.other.CLFireExtinguisher; import megamek.common.weapons.other.ISFireExtinguisher; import org.apache.logging.log4j.LogManager; @@ -41,6 +43,7 @@ import java.awt.event.MouseEvent; import java.math.BigInteger; import java.util.*; +import java.util.List; /** * Context menu for the board. @@ -177,13 +180,13 @@ private boolean createMenu() { this.add(menu); itemCount++; } - + menu = createRotateTurretMenu(); if (menu.getItemCount() > 0) { this.add(menu); itemCount++; } - + } else if ((currentPanel instanceof PhysicalDisplay)) { menu = createPhysicalMenu(false); @@ -212,7 +215,7 @@ private boolean createMenu() { add(item); } } - + menu = touchOffExplosivesMenu(); if (menu.getItemCount() > 0) { this.add(menu); @@ -225,6 +228,12 @@ private boolean createMenu() { itemCount++; } + menu = createGamemasterMenu(); + if (menu.getItemCount() > 0) { + this.add(menu); + itemCount++; + } + return itemCount > 0; } @@ -382,6 +391,58 @@ private JMenu createSpecialHexDisplayMenu() { return menu; } + /** + * Create various menus related to GameMaster (GM) mode + * + * @return + */ + private JMenu createGamemasterMenu() { + JMenu menu = new JMenu(Messages.getString("Gamemaster.Gamemaster")); + if (!client.getLocalPlayer().getGameMaster()) { + return menu; + } else { + + JMenu dmgMenu = new JMenu(Messages.getString("Gamemaster.EditDamage")); + JMenu cfgMenu = new JMenu(Messages.getString("Gamemaster.Configure")); + var entities = client.getGame().getEntitiesVector(coords); + for (Entity entity : entities ) { + dmgMenu.add(createUnitEditorMenuItem(entity)); + cfgMenu.add(createCustomMechMenuItem(entity)); + } + if (dmgMenu.getItemCount() != 0) { + menu.add(dmgMenu); + } + if (cfgMenu.getItemCount() != 0) { + menu.add(cfgMenu); + } + return menu; + } + } + + JMenuItem createCustomMechMenuItem(Entity entity) { + JMenuItem item = new JMenuItem(entity.getDisplayName()); + item.addActionListener(evt -> { + CustomMechDialog med = new CustomMechDialog(gui, client, Collections.singletonList(entity), true, false); + gui.getBoardView().setShouldIgnoreKeys(true); + med.setVisible(true); + client.sendUpdateEntity(entity); + gui.getBoardView().setShouldIgnoreKeys(false); + }); + return item; + } + + JMenuItem createUnitEditorMenuItem(Entity entity) { + JMenuItem item = new JMenuItem(entity.getDisplayName()); + item.addActionListener(evt -> { + UnitEditorDialog med = new UnitEditorDialog(gui.getFrame(), entity); + gui.getBoardView().setShouldIgnoreKeys(true); + med.setVisible(true); + client.sendUpdateEntity(entity); + gui.getBoardView().setShouldIgnoreKeys(false); + }); + return item; + } + private JMenu createSelectMenu() { JMenu menu = new JMenu("Select"); // add select options @@ -398,9 +459,9 @@ private JMenu createSelectMenu() { private JMenu createViewMenu() { JMenu menu = new JMenu("View"); Game game = client.getGame(); - + Player localPlayer = client.getLocalPlayer(); - + for (Entity entity : game.getEntitiesVector(coords, true)) { // Only add the unit if it's actually visible // With double blind on, the game may unseen units @@ -661,7 +722,7 @@ private JMenu createWeaponsFireMenu() { if (myEntity.isHidden()) { return menu; } - + menu.add(createFireJMenuItem()); menu.add(createSkipJMenuItem()); menu.add(createAlphaStrikeJMenuItem()); @@ -916,7 +977,7 @@ private JMenuItem createProneJMenuItem() { private JMenu createConvertMenu() { JMenu menu = new JMenu(Messages.getString("MovementDisplay.moveModeConvert")); - + if (myEntity instanceof Mech && ((Mech) myEntity).hasTracks()) { menu.add(createConvertMenuItem("MovementDisplay.moveModeLeg", MovementDisplay.MoveCommand.MOVE_MODE_LEG, false)); @@ -928,7 +989,7 @@ private JMenu createConvertMenu() { myEntity.getConversionMode() == QuadVee.CONV_MODE_MECH)); menu.add(createConvertMenuItem("MovementDisplay.moveModeVee", MovementDisplay.MoveCommand.MOVE_MODE_VEE, - myEntity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)); + myEntity.getConversionMode() == QuadVee.CONV_MODE_VEHICLE)); } else if (myEntity instanceof LandAirMech) { int currentMode = myEntity.getConversionMode(); JMenuItem item = createConvertMenuItem("MovementDisplay.moveModeMech", @@ -992,9 +1053,9 @@ private JMenu createTargetMenu() { final boolean isTargetingDisplay = (currentPanel instanceof TargetingPhaseDisplay); final boolean canStartFires = client.getGame().getOptions() .booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_START_FIRE); - + Player localPlayer = client.getLocalPlayer(); - + // Add menu item to target each entity in the coords for (Entity entity : client.getGame().getEntitiesVector(coords)) { // Only add the unit if it's actually visible diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java b/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java index 0f872f3b3d1..1d5d383e377 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java @@ -571,37 +571,40 @@ public void displayEntity(Entity en) { if (en == null) { return; } - String enName = en.getShortName(); - switch (en.getDamageLevel()) { - case Entity.DMG_CRIPPLED: - enName += " [CRIPPLED]"; - break; - case Entity.DMG_HEAVY: - enName += " [HEAVY DMG]"; - break; - case Entity.DMG_MODERATE: - enName += " [MODERATE DMG]"; - break; - case Entity.DMG_LIGHT: - enName += " [LIGHT DMG]"; - break; - default: - enName += " [UNDAMAGED]"; - } + currentlyDisplaying = en; + updateDisplay(); + } + protected void updateDisplay() { if (clientgui != null) { + String enName = currentlyDisplaying.getShortName(); + switch (currentlyDisplaying.getDamageLevel()) { + case Entity.DMG_CRIPPLED: + enName += " [CRIPPLED]"; + break; + case Entity.DMG_HEAVY: + enName += " [HEAVY DMG]"; + break; + case Entity.DMG_MODERATE: + enName += " [MODERATE DMG]"; + break; + case Entity.DMG_LIGHT: + enName += " [LIGHT DMG]"; + break; + default: + enName += " [UNDAMAGED]"; + } + clientgui.getUnitDisplayDialog().setTitle(enName); labTitle.setText(enName); } - currentlyDisplaying = en; - - mPan.displayMech(en); - pPan.displayMech(en); - aPan.displayMech(en); - wPan.displayMech(en); - sPan.displayMech(en); - ePan.displayMech(en); + mPan.displayMech(currentlyDisplaying); + pPan.displayMech(currentlyDisplaying); + aPan.displayMech(currentlyDisplaying); + wPan.displayMech(currentlyDisplaying); + sPan.displayMech(currentlyDisplaying); + ePan.displayMech(currentlyDisplaying); } /**