diff --git a/megamek/docs/Bot Stuff/Princess Notes.txt b/megamek/docs/Bot Stuff/Princess Notes.txt index 7e232d72445..3885a641d2e 100644 --- a/megamek/docs/Bot Stuff/Princess Notes.txt +++ b/megamek/docs/Bot Stuff/Princess Notes.txt @@ -2,10 +2,10 @@ MegaMek contains a single bot client, Princess. First, we'll go through the basi 1) Behavior - There are 4 behaviors pre-configured with Princess: - DEFAULT - Basic "balanced" settings. - BERSERK - Highly aggressive. Princess will ignore danger to herself and will not run away under any circumstances. - COWARDLY - Really doesn't want to get shot. - ESCAPE - Not quite as fearful as cowardly but she will try to escape from her home board edge ASAP. + �DEFAULT - Basic "balanced" settings. + �BERSERK - Highly aggressive. Princess will ignore danger to herself and will not run away under any circumstances. + �COWARDLY - Really doesn't want to get shot. + �ESCAPE - Not quite as fearful as cowardly but she will try to escape from her home board edge ASAP. You can also enter a name to save your own configurations for later games or for use with the replacePlayer command. @@ -13,7 +13,7 @@ Verbosity - Used to set the level of logging Princess will use. DEBUG mode is ve Forced Withdrawal - Used to make the Princess follow the Forced Withdrawal rules outlined in Total Warfare. -Immediate Withdrawal - Used to make Princess send her units to her home edge from the start of the game, regardless of damage level. This can be handy for Chase and Breakthrough scenarios. +Immediate Withdrawal - Used to make Princess send her units to her home edge from the start of the game, regardless of damage level. This can be handy for Chase and Breakthrough scenarios. Auto Flee Board - (Only available when Immediate Withdrawal is in effect.) If this is checked, Princess will issue the Flee command as soon as her units reach her home edge, regardless of damage level. @@ -95,7 +95,7 @@ Displays a list of available chat commands for Princess. Replacing an Absent Player with the Princess Bot -If a player has become disconnected (the word "(ghost)" appears next to their name, you can replace that player with a bot. There are two methods for replacing a player. The first is the Replace Player command under the File menu. This will bring up a version of the Bot Config Dialog along with a list of players that can be replaced. The second option is to use the /replacePlayer command in the chat window: +If a player has become disconnected (the word "(ghost)" appears next to their name, you can replace that player with a bot. There are two methods for replacing a player. The first is the Edit Bots command under the File menu. This will bring up a version of the Bot Config Dialog along with a list of players that can be replaced. The second option is to use the /replacePlayer command in the chat window: /replacePlayer -b:Princess <-c:ConfigName> <-v:VerbosityLevel> <-p:>PlayerName diff --git a/megamek/docs/help/en/PrincessBotDocumentation.html b/megamek/docs/help/en/PrincessBotDocumentation.html index cd5ce8e9802..3721960775e 100644 --- a/megamek/docs/help/en/PrincessBotDocumentation.html +++ b/megamek/docs/help/en/PrincessBotDocumentation.html @@ -217,7 +217,7 @@

Replacing an Absent

If a player has become disconnected (the word "(ghost)" appears next to their name, you can replace that player with a bot.  - There are two methods for replacing a player. The first is the Replace Player command under the File + There are two methods for replacing a player. The first is the Edit Bots command under the File menu. This will bring up a version of the Bot Config Dialog along with a list of players that can be replaced. The second option is to use the /replacePlayer command in the chat window:

diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 211842c1928..be0beb7e7a8 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -942,7 +942,7 @@ CommonMenuBar.fileBoardSaveAsImageUnits=Save As Image with Units... CommonMenuBar.fileBoardSaveAsImageUnits.tooltip=Save the current board as a PNG image, with all units CommonMenuBar.fileGameConnect=Connect... CommonMenuBar.fileGameConnectBot=Connect as Bot... -CommonMenuBar.replacePlayer=Replace Player... +CommonMenuBar.editBots=Edit Bots... CommonMenuBar.fileGameLoad=Load Game... CommonMenuBar.fileGameSave=Save Game... CommonMenuBar.fileGameSaveServer=Save on Server... @@ -4367,15 +4367,21 @@ BotConfigDialog.saveNewPrompt=Choose a name for the preset BotConfigDialog.saveNewTaken=That name already exists. BotConfigDialog.previousConfig=Previous Configuration -#Replace Player Dialog -ReplacePlayersDialog.title=Replace Players with Princess Bots -ReplacePlayersDialog.princess=Princess -ReplacePlayersDialog.noReplacement=No replacement -ReplacePlayersDialog.ghostPlayerHeader=Ghost Player -ReplacePlayersDialog.configAvailableHeader=Princess Config -ReplacePlayersDialog.chooseReplacementHeader=Replace With -ReplacePlayersDialog.configAvailable=Available -ReplacePlayersDialog.config=Bot Settings... +#Edit Bots Dialog +EditBotsDialog.title=Replace Ghosts Players with Princess Bots and Edit Local Bots +EditBotsDialog.optionReplace=Replace with Princess +EditBotsDialog.optionDoNotReplace=Keep as Ghost +EditBotsDialog.optionNone=No Action +EditBotsDialog.optionEdit=Edit Bot +EditBotsDialog.optionKick=Kick Bot +EditBotsDialog.playerNameHeader=Player +EditBotsDialog.playerTypeHeader=Type +EditBotsDialog.configAvailableHeader=Saved Bot Config +EditBotsDialog.chooseActionHeader=Action +EditBotsDialog.configAvailable=Restore Saved Config +EditBotsDialog.config=Bot Settings... +EditBotsDialog.local=Local +EditBotsDialog.remote=Remote #Point Blank Shot Dialog StatusBarPhaseDisplay.pointblankShot=Pointblank shot from a hidden unit! diff --git a/megamek/i18n/megamek/client/messages_de.properties b/megamek/i18n/megamek/client/messages_de.properties index a1706ad9497..bfb8ade7f97 100644 --- a/megamek/i18n/megamek/client/messages_de.properties +++ b/megamek/i18n/megamek/client/messages_de.properties @@ -355,7 +355,7 @@ CommonMenuBar.fileGameQuickLoad=Schnellladen CommonMenuBar.fileGameSaveServer=Auf dem Server speichern... CommonMenuBar.fileUnitsSave=Speichern... CommonMenuBar.fileUnitsBrowse=Verfgbare Einheiten... -CommonMenuBar.replacePlayer=Spieler ersetzen... +CommonMenuBar.editBots=Spieler ersetzen... CommonMenuBar.viewPlayerSettings=Spielereinstellungen CommonMenuBar.viewAccessibilityWindow=Zugnglichkeits-Fenster CommonMenuBar.viewIncGUIScale=GUI vergrern diff --git a/megamek/i18n/megamek/client/messages_es.properties b/megamek/i18n/megamek/client/messages_es.properties index 527594bdfc0..1f81db3be2c 100644 --- a/megamek/i18n/megamek/client/messages_es.properties +++ b/megamek/i18n/megamek/client/messages_es.properties @@ -1002,7 +1002,7 @@ CommonMenuBar.fileBoardSaveAsImageUnits=Guardar como Imagen con Unidades... CommonMenuBar.fileBoardSaveAsImageUnits.tooltip=Guarde el tablero actual como una imagen PNG, con todas las unidades CommonMenuBar.fileGameConnect=Conectar... CommonMenuBar.fileGameConnectBot=Conectar un Bot... -CommonMenuBar.replacePlayer=Sustituir Jugador... +CommonMenuBar.editBots=Sustituir Jugador... CommonMenuBar.fileGameLoad=Cargar Partida... CommonMenuBar.fileGameSave=Guardar Partida... CommonMenuBar.fileGameSaveServer=Guardar Servidor... @@ -4210,15 +4210,15 @@ BotConfigDialog.saveNewPrompt=Elija un nombre para el ajuste preestablecido BotConfigDialog.saveNewTaken=Ese nombre ya existe. BotConfigDialog.previousConfig=Configuración Anterior -#Replace Player Dialog -ReplacePlayersDialog.title=Reemplazar Jugadores con Princess Bots -ReplacePlayersDialog.princess=Princess -ReplacePlayersDialog.noReplacement=Sin reemplazo -ReplacePlayersDialog.ghostPlayerHeader=Jugador Fantasma -ReplacePlayersDialog.configAvailableHeader=Configuración de Princess -ReplacePlayersDialog.chooseReplacementHeader=Reemplazar con -ReplacePlayersDialog.configAvailable=Disponible -ReplacePlayersDialog.config=Configuración del Bot... +#Edit Bots Dialog +EditBotsDialog.title=Reemplazar Jugadores con Princess Bots +EditBotsDialog.optionReplace=Princess +EditBotsDialog.optionDoNotReplace=Sin reemplazo +EditBotsDialog.playerNameHeader=Jugador Fantasma +EditBotsDialog.configAvailableHeader=Configuración de Princess +EditBotsDialog.chooseActionHeader=Reemplazar con +EditBotsDialog.configAvailable=Disponible +EditBotsDialog.config=Configuración del Bot... #Point Blank Shot Dialog StatusBarPhaseDisplay.pointblankShot=¡Disparo a quemarropa desde una unidad oculta! diff --git a/megamek/i18n/megamek/client/messages_ru.properties b/megamek/i18n/megamek/client/messages_ru.properties index 82fb6da107e..ba571ea1071 100644 --- a/megamek/i18n/megamek/client/messages_ru.properties +++ b/megamek/i18n/megamek/client/messages_ru.properties @@ -402,7 +402,7 @@ CommonMenuBar.fileBoardSaveAs=Сохранить как... CommonMenuBar.fileBoardSaveAsImage=Сохранить как рисунок... CommonMenuBar.fileGameConnect=Присоединиться... CommonMenuBar.fileGameConnectBot=Присоединиться как бот... -CommonMenuBar.replacePlayer=Заменить игрока... +CommonMenuBar.editBots=Заменить игрока... CommonMenuBar.fileGameNew=Новая CommonMenuBar.fileGameOpen=Открыть... CommonMenuBar.fileGameSave=Сохранить локально... diff --git a/megamek/src/megamek/client/Client.java b/megamek/src/megamek/client/Client.java index 78ddb07762e..953b7c0eb78 100644 --- a/megamek/src/megamek/client/Client.java +++ b/megamek/src/megamek/client/Client.java @@ -107,7 +107,7 @@ public class Client implements IClientCommandHandler { private final UnitNameTracker unitNameTracker = new UnitNameTracker(); /** The bots controlled by the local player; maps a bot's name String to a bot's client. */ - public Map bots = new TreeMap<>(String::compareTo); + public Map localBots = new TreeMap<>(String::compareTo); // Hashtable for storing image tags containing base64Text src private Hashtable imgCache; @@ -1397,7 +1397,7 @@ protected void handlePacket(Packet c) { receivePlayerInfo(c); break; case PLAYER_REMOVE: - for (Iterator botIterator = bots.values().iterator(); botIterator.hasNext(); ) { + for (Iterator botIterator = localBots.values().iterator(); botIterator.hasNext(); ) { Client bot = botIterator.next(); if (bot.localPlayerNumber == c.getIntValue(0)) { botIterator.remove(); @@ -1880,7 +1880,7 @@ public void setCurrentHex(Coords hex) { /** Returns true when the player is a bot added/controlled by this client. */ public boolean isLocalBot(Player player) { - return bots.containsKey(player.getName()); + return localBots.containsKey(player.getName()); } /** @@ -1888,6 +1888,6 @@ public boolean isLocalBot(Player player) { * the player is not a local bot, returns null. */ public Client getBotClient(Player player) { - return bots.get(player.getName()); + return localBots.get(player.getName()); } } diff --git a/megamek/src/megamek/client/bot/BotClient.java b/megamek/src/megamek/client/bot/BotClient.java index d8a960e8156..b17aa28de52 100644 --- a/megamek/src/megamek/client/bot/BotClient.java +++ b/megamek/src/megamek/client/bot/BotClient.java @@ -16,7 +16,6 @@ import megamek.client.Client; import megamek.client.bot.princess.CardinalEdge; import megamek.client.ui.swing.ClientGUI; -import megamek.client.ui.swing.ReportDisplay; import megamek.common.*; import megamek.common.actions.EntityAction; import megamek.common.actions.WeaponAttackAction; @@ -40,10 +39,10 @@ public abstract class BotClient extends Client { private List currentTurnEnemyEntities; private List currentTurnFriendlyEntities; - + // a frame, to show stuff in public JFrame frame; - + /** * Keeps track of whether this client has started to calculate a turn this phase. */ @@ -67,9 +66,9 @@ public void run() { public BotClient(String playerName, String host, int port) { super(playerName, host, port); - + boardClusterTracker = new BoardClusterTracker(); - + game.addGameListener(new GameListenerAdapter() { @Override @@ -203,7 +202,7 @@ public boolean isBot() { protected abstract void calculateDeployment() throws Exception; protected void initTargeting() { } - + /** * Calculates the targeting/offboard turn * This includes firing TAG and non-direct-fire artillery @@ -226,11 +225,11 @@ protected void calculatePrephaseTurn() { @Nullable protected abstract PhysicalOption calculatePhysicalTurn(); - - protected Vector calculatePointBlankShot(int firingEntityID, int targetID) { + + protected Vector calculatePointBlankShot(int firingEntityID, int targetID) { return new Vector<>(); } - + protected int pickTagTarget(GameCFREvent evt) { return 0; } @@ -256,37 +255,37 @@ protected boolean keepGameLog() { } /** - * Helper function that determines which of this bot's entities are stranded inside immobilized transports. + * Helper function that determines which of this bot's entities are stranded inside immobilized transports. * @return Array of entity IDs. */ public int[] getStrandedEntities() { List entitiesToUnload = new ArrayList<>(); - + // Basically, we loop through all entities owned by the current player // And if the entity happens to be in a disabled transport, then we unload it // unless doing so would kill it or be illegal due to stacking violation for (Entity currentEntity : getGame().getPlayerEntities(getLocalPlayer(), true)) { Entity transport = currentEntity.getTransportId() != Entity.NONE ? getGame().getEntity(currentEntity.getTransportId()) : null; - + if (transport != null && transport.isPermanentlyImmobilized(true)) { boolean stackingViolation = null != Compute.stackingViolation(game, currentEntity.getId(), transport.getPosition()); boolean unloadFatal = currentEntity.isBoardProhibited(getGame().getBoard().getType()) || currentEntity.isLocationProhibited(transport.getPosition()); - + if (!stackingViolation && !unloadFatal) { entitiesToUnload.add(currentEntity.getId()); } } } - + int[] entityIDs = new int[entitiesToUnload.size()]; for (int x = 0; x < entitiesToUnload.size(); x++) { entityIDs[x] = entitiesToUnload.get(x); } - + return entityIDs; } - + public List getEntitiesOwned() { ArrayList result = new ArrayList<>(); for (Entity entity : game.getEntitiesVector()) { @@ -297,20 +296,20 @@ public List getEntitiesOwned() { } return result; } - + protected Entity getArbitraryEntity() { for (Entity entity : game.getEntitiesVector()) { if (entity.getOwner().equals(getLocalPlayer())) { return entity; } } - + return null; } /** * Lazy-loaded list of enemy entities that we should consider firing at. - * Only good for the current entity turn calculation, as this list can change between individual entity turns. + * Only good for the current entity turn calculation, as this list can change between individual entity turns. */ public List getEnemyEntities() { if (currentTurnEnemyEntities == null) { @@ -319,18 +318,18 @@ public List getEnemyEntities() { if (entity.getOwner().isEnemyOf(getLocalPlayer()) && (entity.getPosition() != null) && !entity.isOffBoard() && (entity.getCrew() != null) && !entity.getCrew().isDead()) { - + currentTurnEnemyEntities.add(entity); } } } - + return currentTurnEnemyEntities; } /** * Lazy-loaded list of friendly entities. - * Only good for the current entity turn calculation, as this list can change between individual entity turns. + * Only good for the current entity turn calculation, as this list can change between individual entity turns. */ public List getFriendEntities() { if (currentTurnFriendlyEntities == null) { @@ -342,7 +341,7 @@ public List getFriendEntities() { } } } - + return currentTurnFriendlyEntities; } @@ -470,7 +469,7 @@ private Entity getRandomUnmovedEntity() { } return unMoved.get(Compute.randomInt(unMoved.size())); } - + /** * Calculate what to do on my turn. * Has a retry mechanism for when the turn calculation fails due to concurrency issues @@ -478,7 +477,7 @@ private Entity getRandomUnmovedEntity() { private synchronized void calculateMyTurn() { int retryCount = 0; boolean success = false; - + while ((retryCount < BOT_TURN_RETRY_COUNT) && !success) { success = calculateMyTurnWorker(); @@ -551,7 +550,7 @@ private synchronized boolean calculateMyTurnWorker() { } else if (game.getPhase().isPremovement() || game.getPhase().isPrefiring()) { calculatePrephaseTurn(); } - + return true; } catch (Exception ex) { LogManager.getLogger().error("", ex); @@ -577,7 +576,7 @@ public double getMassOfAllInBuilding(final Game game, final Coords coords) { return mass; } - + /** * Gets valid and empty starting coords around the specified point. This * method iterates through the list of Coords and returns the first Coords @@ -708,7 +707,7 @@ protected List getStartingCoordsArray(Entity deployed_ent) { } double highestFitness = -5000; - + for (RankedCoords coord : validCoords) { // Calculate the fitness factor for each hex and save it to the array @@ -875,7 +874,7 @@ protected List getStartingCoordsArray(Entity deployed_ent) { coord.fitness -= potentialBuildingDamage(coord.getX(), coord.getY(), deployed_ent); } - + // ProtoMech // -> // -> Trees increase fitness by +2 (minor) @@ -888,7 +887,7 @@ protected List getStartingCoordsArray(Entity deployed_ent) { // Make sure I'm not stuck in a dead-end. coord.fitness += calculateEdgeAccessFitness(deployed_ent, board); - + if (coord.fitness > highestFitness) { highestFitness = coord.fitness; } @@ -903,7 +902,7 @@ protected List getStartingCoordsArray(Entity deployed_ent) { rc.fitness += getClusterTracker().getBoardClusterSize(deployed_ent, rc.coords, false); } } - + // Now sort the valid array. Collections.sort(validCoords); @@ -917,7 +916,7 @@ protected List getStartingCoordsArray(Entity deployed_ent) { /** * Determines if the given entity has reasonable access to the "opposite" edge of the board from its - * current position. Returns 0 if this can be accomplished without destroying any terrain, + * current position. Returns 0 if this can be accomplished without destroying any terrain, * -50 if this can be accomplished but terrain must be destroyed, * -100 if this cannot be accomplished at all */ @@ -926,12 +925,12 @@ private int calculateEdgeAccessFitness(Entity entity, Board board) { if (entity.isAirborne() || entity instanceof VTOL) { return 0; } - + CardinalEdge destinationEdge = BoardUtilities.determineOppositeEdge(entity); - + int noReductionZoneSize = getClusterTracker().getDestinationCoords(entity, destinationEdge, false).size(); int reductionZoneSize = getClusterTracker().getDestinationCoords(entity, destinationEdge, true).size(); - + if (noReductionZoneSize > 0) { return 0; } else if (reductionZoneSize > 0) { @@ -1147,7 +1146,7 @@ protected void correctName(Packet inP) throws Exception { // If we have a clientgui, it keeps track of a Name -> Client map, and // we need to update that map with this name change. if (getClientGUI() != null) { - Map bots = getClientGUI().getBots(); + Map bots = getClientGUI().getLocalBots(); String oldName = getName(); String newName = (String) (inP.getObject(0)); if (!this.equals(bots.get(oldName))) { @@ -1170,13 +1169,13 @@ public void setClientGUI(ClientGUI clientgui) { public void endOfTurnProcessing() { // Do nothing; } - + @Override @SuppressWarnings("unchecked") protected void receiveBuildingCollapse(Packet packet) { game.getBoard().collapseBuilding((Vector) packet.getObject(0)); } - + /** * The bot client doesn't really need a text report * Let's save ourselves a little processing time and not deal with any of it @@ -1185,14 +1184,14 @@ protected void receiveBuildingCollapse(Packet packet) { public String receiveReport(Vector v) { return ""; } - + /** * The bot client has no need of image tag caching * Let's save ourselves some CPU and memory and not deal with it */ @Override protected void cacheImgTag(Entity entity) { - + } public BoardClusterTracker getClusterTracker() { diff --git a/megamek/src/megamek/client/ui/dialogs/BotConfigDialog.java b/megamek/src/megamek/client/ui/dialogs/BotConfigDialog.java index c65a0bfaa73..9f2be6ee622 100644 --- a/megamek/src/megamek/client/ui/dialogs/BotConfigDialog.java +++ b/megamek/src/megamek/client/ui/dialogs/BotConfigDialog.java @@ -60,20 +60,20 @@ public class BotConfigDialog extends AbstractButtonDialog implements ActionListe private JLabel nameLabel = new JLabel(Messages.getString("BotConfigDialog.nameLabel")); private TipTextField nameField = new TipTextField("", 16); - + private JButton addTargetButton = new TipButton(Messages.getString("BotConfigDialog.addHexTarget")); private JButton addUnitButton = new TipButton(Messages.getString("BotConfigDialog.addUnitTarget")); private JButton removeTargetButton = new TipButton(Messages.getString("BotConfigDialog.removeTarget")); private DefaultListModel targetsListModel = new DefaultListModel<>(); private TipList targetsList = new TipList<>(targetsListModel); - + private MMToggleButton forcedWithdrawalCheck = new TipMMToggleButton(Messages.getString("BotConfigDialog.forcedWithdrawalCheck")); private JLabel withdrawEdgeLabel = new JLabel(Messages.getString("BotConfigDialog.retreatEdgeLabel")); private MMComboBox withdrawEdgeCombo = new TipCombo<>("EdgeToWithdraw", CardinalEdge.values()); private MMToggleButton autoFleeCheck = new TipMMToggleButton(Messages.getString("BotConfigDialog.autoFleeCheck")); private JLabel fleeEdgeLabel = new JLabel(Messages.getString("BotConfigDialog.homeEdgeLabel")); private MMComboBox fleeEdgeCombo = new TipCombo<>("EdgeToFlee", CardinalEdge.values()); - + private TipSlider aggressionSlidebar = new TipSlider(SwingConstants.HORIZONTAL, 0, 10, 5); private TipSlider fallShameSlidebar = new TipSlider(SwingConstants.HORIZONTAL, 0, 10, 5); private TipSlider herdingSlidebar = new TipSlider(SwingConstants.HORIZONTAL, 0, 10, 5); @@ -81,40 +81,40 @@ public class BotConfigDialog extends AbstractButtonDialog implements ActionListe private TipSlider braverySlidebar = new TipSlider(SwingConstants.HORIZONTAL, 0, 10, 5); private TipButton savePreset = new TipButton(Messages.getString("BotConfigDialog.save")); private TipButton saveNewPreset = new TipButton(Messages.getString("BotConfigDialog.saveNew")); - + private JButton princessHelpButton = new JButton(Messages.getString("BotConfigDialog.help")); - + private JPanel presetsPanel; private JLabel chooseLabel = new JLabel(Messages.getString("BotConfigDialog.behaviorNameLabel")); /** A copy of the current presets. Modifications will only be saved when accepted. */ private List presets; private PresetsModel presetsModel = new PresetsModel(); private JList presetsList = new JList<>(presetsModel); - + private final JButton butOK = new JButton(Messages.getString("Okay")); private final JButton butCancel = new JButton(Messages.getString("Cancel")); /** The pre-existing bot player name that is affected by the dialog, if there is one. Null otherwise. */ private final String fixedBotPlayerName; private final boolean isNewBot; - + /** Stores the currently chosen preset. Used to detect if the player has changed the sliders. */ private BehaviorSettings chosenPreset; - + /** Stores the original Behavior if one was given in the constructor (= a save game Behavior). */ private final BehaviorSettings saveGameBehavior; - + /** A ClientGUI given to the dialog. */ private final ClientGUI clientGui; - + /** Convenience field for clientGui.getClient(). */ private final Client client; - + public BotConfigDialog(JFrame parent, @Nullable String botName) { this(parent, botName, null, null); - } - - public BotConfigDialog(JFrame parent, @Nullable String botName, + } + + public BotConfigDialog(JFrame parent, @Nullable String botName, @Nullable BehaviorSettings behavior, @Nullable ClientGUI cg) { super(parent, "BotConfigDialog", "BotConfigDialog.title"); fixedBotPlayerName = botName; @@ -128,7 +128,7 @@ public BotConfigDialog(JFrame parent, @Nullable String botName, updateDialogFields(); adaptToGUIScale(); } - + @Override protected void initialize() { // Make Enter confirm and close the dialog @@ -143,7 +143,7 @@ public void actionPerformed(ActionEvent evt) { }); super.initialize(); } - + @Override protected Container createCenterPane() { JPanel result = new JPanel(); @@ -152,21 +152,21 @@ protected Container createCenterPane() { result.add(settingSection()); return result; } - + /** The setting section contains the presets list on the left side and the princess settings on the right. */ private JPanel settingSection() { var princessScroll = new JScrollPane(princessPanel()); princessScroll.getVerticalScrollBar().setUnitIncrement(16); princessScroll.setBorder(null); presetsPanel = presetsPanel(); - + var result = new JPanel(new BorderLayout(0, 0)); result.setAlignmentX(LEFT_ALIGNMENT); result.add(princessScroll, BorderLayout.CENTER); result.add(presetsPanel, BorderLayout.LINE_START); return result; } - + /** The princess panel contains the individual princess settings. */ private JPanel princessPanel() { JPanel result = new JPanel(); @@ -176,7 +176,7 @@ private JPanel princessPanel() { result.add(targetsSection()); return result; } - + private JPanel nameSection() { JPanel result = new JPanel(); result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS)); @@ -184,7 +184,7 @@ private JPanel nameSection() { Content panContent = new Content(); panContent.setLayout(new BoxLayout(panContent, BoxLayout.PAGE_AXIS)); result.add(panContent); - + var namePanel = new JPanel(); nameField.setToolTipText(Messages.getString("BotConfigDialog.namefield.tooltip")); // When the dialog configures an existing player, the name must not be changed @@ -194,14 +194,14 @@ private JPanel nameSection() { nameLabel.setDisplayedMnemonic(KeyEvent.VK_N); namePanel.add(nameLabel); namePanel.add(nameField); - + panContent.add(namePanel); return result; } - - /** + + /** * Returns a name that is not used by another player. Starts with "Princess" - * and subsequently adds numbers (Princess1 etc.) until a free one is found. + * and subsequently adds numbers (Princess1 etc.) until a free one is found. */ private String getFreePrincessName() { String baseName = "Princess"; @@ -216,19 +216,19 @@ private String getFreePrincessName() { } return name; } - + /** The presets panel has a list of behavior presets for Princess. */ private JPanel presetsPanel() { var result = new JPanel(); result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS)); result.setBorder(new EmptyBorder(0, 10, 0, 20)); - + chooseLabel.setAlignmentX(CENTER_ALIGNMENT); chooseLabel.setDisplayedMnemonic(KeyEvent.VK_P); chooseLabel.setLabelFor(presetsList); var headerPanel = new FixedYPanel(); headerPanel.add(chooseLabel); - + presetsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); presetsList.addListSelectionListener(this); presetsList.setCellRenderer(new PresetsRenderer()); @@ -237,10 +237,10 @@ private JPanel presetsPanel() { result.add(headerPanel); result.add(Box.createVerticalStrut(10)); result.add(presetsList); - + return result; } - + private JPanel behaviorSection() { JPanel result = new OptionPanel("BotConfigDialog.behaviorSection"); Content panContent = new Content(); @@ -275,11 +275,11 @@ private JPanel behaviorSection() { Messages.getString("BotConfigDialog.fallShameSliderMax"), Messages.getString("BotConfigDialog.fallShameToolTip"), Messages.getString("BotConfigDialog.fallShameSliderTitle"))); - + var buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10)); buttonPanel.setAlignmentX(SwingConstants.CENTER); result.add(buttonPanel); - + savePreset.addActionListener(this); savePreset.setMnemonic(KeyEvent.VK_S); savePreset.setToolTipText(Messages.getString("BotConfigDialog.saveTip")); @@ -291,31 +291,31 @@ private JPanel behaviorSection() { return result; } - + private JPanel targetsSection() { JPanel result = new OptionPanel("BotConfigDialog.targetsSection"); Content panContent = new Content(); panContent.setLayout(new BoxLayout(panContent, BoxLayout.PAGE_AXIS)); result.add(panContent); - + addTargetButton.setMnemonic(KeyEvent.VK_X); addTargetButton.addActionListener(this); - + addUnitButton.setMnemonic(KeyEvent.VK_U); addUnitButton.addActionListener(this); - + removeTargetButton.setMnemonic(KeyEvent.VK_R); removeTargetButton.setFont(UIUtil.getScaledFont()); removeTargetButton.setForeground(GUIPreferences.getInstance().getWarningColor()); removeTargetButton.addActionListener(this); - + JPanel removeButtonPanel = new FixedXPanel(new FlowLayout(FlowLayout.RIGHT)); removeButtonPanel.add(removeTargetButton); - + var addButtonPanel = new FixedXPanel(new FlowLayout(FlowLayout.LEFT)); addButtonPanel.add(addUnitButton); addButtonPanel.add(addTargetButton); - + var buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS)); buttonPanel.add(addButtonPanel); @@ -331,13 +331,13 @@ private JPanel targetsSection() { targetsList.setVisibleRowCount(6); targetsList.setPrototypeCellValue(new Coords(21, 22)); JScrollPane targetsListScroller = new JScrollPane(targetsList); - + panContent.add(buttonPanel); panContent.add(targetsListScroller); - + return result; } - + private JPanel retreatSection() { JPanel result = new OptionPanel("BotConfigDialog.retreatSection"); Content panContent = new Content(); @@ -347,12 +347,12 @@ private JPanel retreatSection() { autoFleeCheck.setToolTipText(Messages.getString("BotConfigDialog.autoFleeTooltip")); autoFleeCheck.addActionListener(this); autoFleeCheck.setMnemonic(KeyEvent.VK_F); - + fleeEdgeCombo.removeItem(CardinalEdge.NONE); fleeEdgeCombo.setToolTipText(Messages.getString("BotConfigDialog.homeEdgeTooltip")); fleeEdgeCombo.setSelectedIndex(0); fleeEdgeCombo.addActionListener(this); - + forcedWithdrawalCheck.setToolTipText(Messages.getString("BotConfigDialog.forcedWithdrawalTooltip")); forcedWithdrawalCheck.addActionListener(this); forcedWithdrawalCheck.setMnemonic(KeyEvent.VK_W); @@ -374,10 +374,10 @@ private JPanel retreatSection() { panContent.add(firstLine); panContent.add(Box.createVerticalStrut(5)); panContent.add(secondLine); - + return result; } - + /** Returns the full behavior setting that the player entered in this dialog. */ public BehaviorSettings getBehaviorSettings() { savePrincessProperties(); @@ -391,16 +391,16 @@ protected void updatePresetFields() { herdingSlidebar.setValue(princessBehavior.getHerdMentalityIndex()); braverySlidebar.setValue(princessBehavior.getBraveryIndex()); } - + private void updateDialogFields() { updatePresetFields(); forcedWithdrawalCheck.setSelected(princessBehavior.isForcedWithdrawal()); withdrawEdgeCombo.setSelectedItem(princessBehavior.getRetreatEdge()); - + autoFleeCheck.setSelected(princessBehavior.shouldAutoFlee()); fleeEdgeCombo.setSelectedItem(princessBehavior.getDestinationEdge()); - + targetsListModel.clear(); for (String t : princessBehavior.getStrategicBuildingTargets()) { int begin = t.indexOf("(") + 1; @@ -415,7 +415,7 @@ private void updateDialogFields() { targetsListModel.addAll(princessBehavior.getPriorityUnitTargets()); updateEnabledStates(); } - + /** Updates all necessary enabled states of buttons/dropdowns. */ private void updateEnabledStates() { fleeEdgeLabel.setEnabled(autoFleeCheck.isSelected()); @@ -425,10 +425,10 @@ private void updateEnabledStates() { savePreset.setEnabled(isChangedPreset()); removeTargetButton.setEnabled(!targetsList.isSelectionEmpty()); } - + /** Returns true if a preset is selected and is different from the current slider settings. */ private boolean isChangedPreset() { - return (chosenPreset != null) + return (chosenPreset != null) && (chosenPreset.getSelfPreservationIndex() != selfPreservationSlidebar.getValue() || chosenPreset.getHyperAggressionIndex() != aggressionSlidebar.getValue() || chosenPreset.getFallShameIndex() != fallShameSlidebar.getValue() @@ -436,7 +436,7 @@ private boolean isChangedPreset() { || chosenPreset.getBraveryIndex() != braverySlidebar.getValue()); } - private JPanel buildSlider(JSlider thisSlider, String minMsgProperty, + private JPanel buildSlider(JSlider thisSlider, String minMsgProperty, String maxMsgProperty, String toolTip, String title) { var result = new TipPanel(); result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS)); @@ -445,13 +445,13 @@ private JPanel buildSlider(JSlider thisSlider, String minMsgProperty, thisSlider.setPaintLabels(false); thisSlider.setSnapToTicks(true); thisSlider.addChangeListener(this); - + var panLabels = new JPanel(); panLabels.setLayout(new BoxLayout(panLabels, BoxLayout.LINE_AXIS)); panLabels.add(new JLabel(minMsgProperty, SwingConstants.LEFT)); panLabels.add(Box.createHorizontalGlue()); panLabels.add(new JLabel(maxMsgProperty, SwingConstants.RIGHT)); - + result.add(panLabels); result.add(thisSlider); return result; @@ -464,28 +464,28 @@ protected JPanel createButtonPanel() { butOK.addActionListener(this::okButtonActionPerformed); butOK.setMnemonic(KeyEvent.VK_K); result.add(butOK); - + butCancel.addActionListener(this::cancelActionPerformed); butCancel.setMnemonic(KeyEvent.VK_C); result.add(butCancel); - + princessHelpButton.addActionListener(this); princessHelpButton.setMnemonic(KeyEvent.VK_H); result.add(princessHelpButton); return result; } - + private void showPrincessHelp() { new PrincessHelpDialog(getFrame()).setVisible(true); } - + @Override protected void okButtonActionPerformed(ActionEvent evt) { - // Only allow adding the bot if its name is unique. It does not strictly have + // Only allow adding the bot if its name is unique. It does not strictly have // to be unique among all players as the server will make it unique by // adding ".2" when necessary. But it has to be unique among local bots as otherwise - // the connection between the client.bots stored name and the server-given name + // the connection between the client.bots stored name and the server-given name // gets lost. It doesn't hurt to check against all players though. boolean playerNameTaken = (client != null) && client.getGame().getPlayersVector().stream() .anyMatch(p -> p.getName().equals(nameField.getText())); @@ -495,17 +495,17 @@ protected void okButtonActionPerformed(ActionEvent evt) { super.okButtonActionPerformed(evt); } } - + @Override protected void okAction() { savePrincessProperties(); - + if (client != null) { String msg = client.getLocalPlayer() + " changed settings for bot " + getBotName(); client.sendServerChat(Player.PLAYER_NONE, msg); } } - + @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == addTargetButton) { @@ -529,19 +529,19 @@ public void actionPerformed(ActionEvent e) { } else if (e.getSource() == autoFleeCheck) { updateEnabledStates(); - + } else if (e.getSource() == forcedWithdrawalCheck) { updateEnabledStates(); - + } else if (e.getSource() == princessHelpButton) { showPrincessHelp(); - + } else if (e.getSource() == savePreset) { savePreset(); - + } else if (e.getSource() == saveNewPreset) { saveAsNewPreset(); - + } } @@ -553,7 +553,7 @@ private void saveAsNewPreset() { return; } if (!behaviorSettingsFactory.getBehaviorNameList().contains(name)) { - // OK: this name is not taken. Save the preset + // OK: this name is not taken. Save the preset writePreset(name); updatePresets(); presetsList.setSelectedValue(name, true); @@ -563,7 +563,7 @@ private void saveAsNewPreset() { JOptionPane.showMessageDialog(getFrame(), Messages.getString("BotConfigDialog.saveNewTaken")); } } - + /** Saves the current Behavior to the currently selected Behavior Preset. */ private void savePreset() { if (!presetsList.isSelectionEmpty()) { @@ -571,8 +571,8 @@ private void savePreset() { updatePresets(); } } - - /** + + /** * Writes the current Behavior under the given name to the stored Behavior Presets. * Will overwrite a Behavior Preset if there is one with the same name. */ @@ -598,10 +598,10 @@ private void removePreset(String name) { behaviorSettingsFactory.saveBehaviorSettings(false); updatePresets(); } - + /** Copies the Configuration from another local bot player. */ private void copyFromOtherBot(String botName) { - var bc = client.bots.get(botName); + var bc = client.localBots.get(botName); if (bc instanceof Princess) { try { princessBehavior = ((Princess) bc).getBehaviorSettings().getCopy(); @@ -611,7 +611,7 @@ private void copyFromOtherBot(String botName) { } } } - + private void savePrincessProperties() { BehaviorSettings tempBehavior = new BehaviorSettings(); tempBehavior.setFallShameIndex(fallShameSlidebar.getValue()); @@ -636,28 +636,28 @@ private void savePrincessProperties() { public String getBotName() { return nameField.getText(); } - + public void setBotName(String value) { nameField.setText(value); } - + @Override public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) { return; } - + if (event.getSource() == targetsList.getSelectionModel()) { updateEnabledStates(); - + } else if (event.getSource() == presetsList) { presetSelected(); } } - + /** Shows a popup menu for a behavior preset, allowing to delete it. */ private MouseListener presetsMouseListener = new MouseAdapter() { - + @Override public void mouseReleased(MouseEvent e) { int row = presetsList.locationToIndex(e.getPoint()); @@ -670,7 +670,7 @@ public void mouseReleased(MouseEvent e) { popup.show(e.getComponent(), e.getX(), e.getY()); } } - + @Override public void mouseClicked(MouseEvent evt) { if (SwingUtilities.isLeftMouseButton(evt) && evt.getClickCount() == 1) { @@ -678,11 +678,11 @@ public void mouseClicked(MouseEvent evt) { } } }; - - /** + + /** * Called when a Preset is selected. This will often be called twice when clicking * with the mouse (by the listselectionlistener and the mouselistener). In this way - * the list will react when copying a Preset from another bot and then clicking the + * the list will react when copying a Preset from another bot and then clicking the * already selected Preset again. And it will also react to keyboard navigation. */ private void presetSelected() { @@ -710,21 +710,21 @@ private void presetSelected() { } updateEnabledStates(); } - + /** Sets up/Updates the displayed preset list (e.g. after adding or deleting a preset) */ private void updatePresets() { presets = new ArrayList<>(Arrays.asList(behaviorSettingsFactory.getBehaviorNames())); - + // Add the Configuration from a save game, if any to the top of the list if (saveGameBehavior != null) { presets.add(0, Messages.getString("BotConfigDialog.previousConfig")); } - + // Other local bot Configurations if (client != null) { - // Find if there actually are other bots - Set otherBots = new HashSet<>(client.bots.keySet()); + // Find if there actually are other bots + Set otherBots = new HashSet<>(client.localBots.keySet()); if (fixedBotPlayerName != null) { otherBots.remove(fixedBotPlayerName); } @@ -735,9 +735,9 @@ private void updatePresets() { } ((PresetsModel) presetsList.getModel()).fireUpdate(); } - + private class PresetsModel extends DefaultListModel { - + @Override public int getSize() { return presets.size(); @@ -747,16 +747,16 @@ public int getSize() { public String getElementAt(int index) { return presets.get(index); } - + /** Call when elements of the list change. */ private void fireUpdate() { fireContentsChanged(this, 0, getSize() - 1); } } - /** + /** * A renderer for the Behavior Presets list. Adapts the font size to the gui scaling and - * colors the special list elements (other bot Configurations and original Config). + * colors the special list elements (other bot Configurations and original Config). */ private class PresetsRenderer extends DefaultListCellRenderer { @@ -776,14 +776,14 @@ public Component getListCellRendererComponent(JList list, Object value, int i return comp; } } - + /** A renderer for the strategic targets list. */ private class TargetsRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - + String content; boolean invalid = true; if (value instanceof Coords) { @@ -801,13 +801,13 @@ public Component getListCellRendererComponent(JList list, Object value, int i } else if (!board.getHex(coords).containsAnyTerrainOf(BUILDING, FUEL_TANK, BRIDGE)) { content += Messages.getString("BotConfigDialog.hexListNoBg"); } else { - final Hex hex = board.getHex(coords); + final Hex hex = board.getHex(coords); final Building bldg = board.getBuildingAt(coords); if (hex.containsTerrain(BUILDING)) { content += Messages.getString("BotConfigDialog.hexListBldg", Building.typeName(bldg.getType()), Building.className(bldg.getBldgClass()), hex.terrainLevel(BLDG_ELEV), hex.terrainLevel(BLDG_CF)); } else if (hex.containsTerrain(FUEL_TANK)) { - content += Messages.getString("BotConfigDialog.hexListFuel", + content += Messages.getString("BotConfigDialog.hexListFuel", hex.terrainLevel(FUEL_TANK_CF), hex.terrainLevel(FUEL_TANK_MAGN)); } else { content += Messages.getString("BotConfigDialog.hexListBrdg", Building.typeName(bldg.getType()), diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index 97c251563ea..06d08a0cd45 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -73,7 +73,6 @@ import java.net.URL; import java.util.List; import java.util.*; -import java.util.stream.Collectors; public class ClientGUI extends JPanel implements BoardViewListener, ActionListener, ComponentListener, IPreferenceChangeListener { @@ -109,7 +108,7 @@ public class ClientGUI extends JPanel implements BoardViewListener, public static final String FILE_GAME_SCENARIO = "fileGameScenario"; public static final String FILE_GAME_CONNECT_BOT = "fileGameConnectBot"; public static final String FILE_GAME_CONNECT = "fileGameConnect"; - public static final String FILE_GAME_REPLACE_PLAYER = "replacePlayer"; + public static final String FILE_GAME_EDIT_BOTS = "editBots"; // board submenu public static final String BOARD_NEW = "fileBoardNew"; public static final String BOARD_OPEN = "fileBoardOpen"; @@ -901,8 +900,8 @@ public void actionPerformed(ActionEvent event) { boardSaveAsImage(false); ignoreHotKeys = false; break; - case FILE_GAME_REPLACE_PLAYER: - replacePlayer(); + case FILE_GAME_EDIT_BOTS: + editBots(); break; case VIEW_ACCESSIBILITY_WINDOW: toggleAccessibilityWindow(); @@ -2271,10 +2270,10 @@ public void gameEnd(GameEndEvent e) { bv.clearMovementData(); bv.clearFieldOfFire(); bv.clearSensorsRanges(); - for (Client client2 : getBots().values()) { + for (Client client2 : getLocalBots().values()) { client2.die(); } - getBots().clear(); + getLocalBots().clear(); // Make a list of the player's living units. ArrayList living = getClient().getGame().getPlayerEntities(getClient().getLocalPlayer(), false); @@ -2577,8 +2576,8 @@ public Client getClient() { return client; } - public Map getBots() { - return client.bots; + public Map getLocalBots() { + return client.localBots; } /** @@ -2794,16 +2793,8 @@ public void componentShown(ComponentEvent evt) { } - void replacePlayer() { - Set ghostPlayers = client.getGame().getPlayersVector().stream() - .filter(Player::isGhost).collect(Collectors.toSet()); - if (ghostPlayers.isEmpty()) { - doAlertDialog( Messages.getString("ClientGUI.noGhostPlayersToReplace"), - Messages.getString("ClientGUI.noGhosts"), JOptionPane.INFORMATION_MESSAGE); - return; - } - - var rpd = new ReplacePlayersDialog(frame, this); + void editBots() { + var rpd = new EditBotsDialog(frame, this); rpd.setVisible(true); if (rpd.getResult() == DialogResult.CANCELLED) { return; @@ -2811,17 +2802,32 @@ void replacePlayer() { AddBotUtil util = new AddBotUtil(); Map newBotSettings = rpd.getNewBots(); - for (String player : newBotSettings.keySet()) { + for (String ghostName : newBotSettings.keySet()) { StringBuilder message = new StringBuilder(); - Princess princess = util.addBot(newBotSettings.get(player), player, - client.getGame(), client.getHost(), client.getPort(), message); + Princess princess = util.replaceGhostWithBot(newBotSettings.get(ghostName), ghostName, + client, message); systemMessage(message.toString()); // Make this princess a locally owned bot if in the lobby. This way it // can be configured, and it will faithfully press Done when the local player does. if ((princess != null) && client.getGame().getPhase().isLounge()) { - getBots().put(player, princess); + getLocalBots().put(ghostName, princess); } } + + Map changedBots = rpd.getChangedBots(); + for (String botName : changedBots.keySet()) { + StringBuilder message = new StringBuilder(); + util.changeBotSettings(changedBots.get(botName), botName, + client, message); + systemMessage(message.toString()); + } + + Set kickBotNames = rpd.getKickBots(); + for (String botName : kickBotNames) { + StringBuilder message = new StringBuilder(); + util.kickBot( botName, client, message); + systemMessage(message.toString()); + } } /** diff --git a/megamek/src/megamek/client/ui/swing/CommonMenuBar.java b/megamek/src/megamek/client/ui/swing/CommonMenuBar.java index 866edaaa823..c1823c70a90 100644 --- a/megamek/src/megamek/client/ui/swing/CommonMenuBar.java +++ b/megamek/src/megamek/client/ui/swing/CommonMenuBar.java @@ -67,7 +67,7 @@ public class CommonMenuBar extends JMenuBar implements ActionListener, IPreferen private JMenuItem gameQLoad = new JMenuItem(getString("CommonMenuBar.fileGameQuickLoad")); private JMenuItem gameSaveServer = new JMenuItem(getString("CommonMenuBar.fileGameSaveServer")); private JCheckBoxMenuItem gameRoundReport = new JCheckBoxMenuItem(getString("CommonMenuBar.viewRoundReport")); - private JMenuItem gameReplacePlayer = new JMenuItem(getString("CommonMenuBar.replacePlayer")); + private JMenuItem gameEditBots = new JMenuItem(getString("CommonMenuBar.editBots")); private JCheckBoxMenuItem gamePlayerList = new JCheckBoxMenuItem(getString("CommonMenuBar.viewPlayerList")); private JMenuItem gameGameOptions = new JMenuItem(getString("CommonMenuBar.viewGameOptions")); private JMenuItem gamePlayerSettings = new JMenuItem(getString("CommonMenuBar.viewPlayerSettings")); @@ -183,7 +183,7 @@ public CommonMenuBar() { add(menu); menu.setMnemonic(VK_G); - initMenuItem(gameReplacePlayer, menu, FILE_GAME_REPLACE_PLAYER, VK_R); + initMenuItem(gameEditBots, menu, FILE_GAME_EDIT_BOTS, VK_R); menu.addSeparator(); initMenuItem(gameGameOptions, menu, VIEW_GAME_OPTIONS, VK_O); @@ -351,7 +351,7 @@ private void setKeyBinds() { gameQSave.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.QUICK_SAVE)); gameSave.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.LOCAL_SAVE)); gameLoad.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.LOCAL_LOAD)); - gameReplacePlayer.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.REPLACE_PLAYER)); + gameEditBots.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.REPLACE_PLAYER)); } @Override @@ -445,7 +445,7 @@ private synchronized void updateEnabledStates() { gameSave.setEnabled(isLobby || (isInGame && canSave)); gameSaveServer.setEnabled(isLobby || (isInGame && canSave)); gameQSave.setEnabled(isLobby || (isInGame && canSave)); - gameReplacePlayer.setEnabled(isLobby || (isInGame && canSave)); + gameEditBots.setEnabled(isLobby || (isInGame && canSave)); boardSave.setEnabled(isBoardEditor); boardSaveAs.setEnabled(isBoardEditor || isInGame); // TODO: should work in the lobby boardSaveAsImage.setEnabled(isBoardEditor || isInGame); // TODO: should work in the lobby diff --git a/megamek/src/megamek/client/ui/swing/EditBotsDialog.java b/megamek/src/megamek/client/ui/swing/EditBotsDialog.java new file mode 100644 index 00000000000..4d49ecb6d95 --- /dev/null +++ b/megamek/src/megamek/client/ui/swing/EditBotsDialog.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek 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 3 of the License, or + * (at your option) any later version. + * + * MegaMek 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. + * + * You should have received a copy of the GNU General Public License + * along with MegaMek. If not, see . + */ +package megamek.client.ui.swing; + +import megamek.client.Client; +import megamek.client.bot.princess.BehaviorSettings; +import megamek.client.bot.princess.Princess; +import megamek.client.bot.princess.PrincessException; +import megamek.client.ui.Messages; +import megamek.client.ui.baseComponents.AbstractButtonDialog; +import megamek.client.ui.dialogs.BotConfigDialog; +import megamek.client.ui.enums.DialogResult; +import megamek.client.ui.swing.util.UIUtil; +import megamek.common.Game; +import megamek.common.Player; +import org.apache.logging.log4j.LogManager; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; + +public class EditBotsDialog extends AbstractButtonDialog { + // replace ghost combo box choices + private static final int REPLACE_WITH_PRINCESS_INDEX = 1; + + // edit existing local bot combo box choices + private static final int KICK_INDEX = 1; + private static final int EDIT_CONFIG_INDEX = 2; + + /** A ClientGUI given to the dialog. */ + private final ClientGUI clientGui; + + /** Convenience field for clientGui.getClient().getGame(). */ + private final Game game; + + /** The id ordered list of displayed ghosts and bots players */ + private List ghostAndBotPlayers; + + /** Maps a ghost player to the combobox that sets its replacement */ + private Map> ghostChoosers = new HashMap<>(); + + /** Maps a bot player to the combobox that allows it to be edited or kicked */ + private Map> localBotChoosers = new HashMap<>(); + /** Maps a bot player to the combobox that allows it to be kicked */ + private Map> remoteBotChoosers = new HashMap<>(); + + /** Maps a ghost player to the config button for the bot settings */ + private Map configButtons = new HashMap<>(); + + /** Maps a ghost player to bot settings chosen for it */ + private Map botConfigs = new HashMap<>(); + private final static String LOCAL = Messages.getString("EditBotsDialog.local"); + private final static String REMOTE = Messages.getString("EditBotsDialog.remote"); + + protected EditBotsDialog(JFrame frame, ClientGUI clientGUI) { + super(frame, "EditBotsDialog", "EditBotsDialog.title"); + this.clientGui = clientGUI; + game = clientGui.getClient().getGame(); + refreshPlayers(); + initialize(); + } + + @Override + protected void initialize() { + super.initialize(); + adaptToGUIScale(); + try { + finalizeInitialization(); + } catch (Exception ex) { + LogManager.getLogger().error("Error finalizing the EditBotsDialog. Returning the created dialog, but this is likely to cause some oddities.", ex); + } + } + + protected void refreshPlayers() { + ghostAndBotPlayers = game.getPlayersVector().stream() + .filter(client -> client.isGhost() || client.isBot()) + .sorted(Comparator.comparingInt(Player::getId)).collect(Collectors.toList()); + ghostAndBotPlayers.forEach(player -> botConfigs.put(player, new BehaviorSettings())); + } + + @Override + protected Container createCenterPane() { + Vector ghostOptions = new Vector<>(); + ghostOptions.add(Messages.getString("EditBotsDialog.optionDoNotReplace")); + ghostOptions.add(Messages.getString("EditBotsDialog.optionReplace")); + + Vector localBotOptions = new Vector<>(); + localBotOptions.add(Messages.getString("EditBotsDialog.optionNone")); + localBotOptions.add(Messages.getString("EditBotsDialog.optionKick")); + localBotOptions.add(Messages.getString("EditBotsDialog.optionEdit")); + + Vector remoteBotOptions = new Vector<>(); + remoteBotOptions.add(Messages.getString("EditBotsDialog.optionNone")); + remoteBotOptions.add(Messages.getString("EditBotsDialog.optionKick")); + + var gridPanel = new JPanel(); + gridPanel.setLayout(new GridLayout(0, 5, 2,2)); + //column labels + gridPanel.add(new JLabel(Messages.getString("EditBotsDialog.playerNameHeader"))); + gridPanel.add(new JLabel(Messages.getString("EditBotsDialog.playerTypeHeader"))); + gridPanel.add(new JLabel(Messages.getString("EditBotsDialog.chooseActionHeader"))); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel(Messages.getString("EditBotsDialog.configAvailableHeader"))); + //spacer row + gridPanel.add(new JSeparator()); + gridPanel.add(new JSeparator()); + gridPanel.add(new JSeparator()); + gridPanel.add(new JSeparator()); + gridPanel.add(new JSeparator()); + + // The rows for the ghost players + Map savedSettings = game.getBotSettings(); + for (Player player : ghostAndBotPlayers) { + // Name + gridPanel.add(new JLabel(player.toString())); + boolean savedSettingsExist = (savedSettings != null) && savedSettings.containsKey(player.getName()); + + if (player.isGhost()) { + gridPanel.add(new JLabel( "Ghost")); + + var ghostChooser = new JComboBox<>(ghostOptions); + ghostChoosers.put(player, ghostChooser); + if (savedSettingsExist) { + // it was presumably Princess before, so default to replace + ghostChooser.setSelectedIndex(REPLACE_WITH_PRINCESS_INDEX); + botConfigs.put(player, savedSettings.get(player.getName())); + try { + // Copy to protect the saved settings + botConfigs.put(player, savedSettings.get(player.getName()).getCopy()); + } catch (PrincessException ex) { + LogManager.getLogger().error("", ex); + // fallback to default + } + } + ghostChooser.addActionListener(evt -> updateButtonStates()); + + var cPanel = new JPanel(); + cPanel.add(ghostChooser); + gridPanel.add(cPanel); + + gridPanel.add(configButton(player)); + gridPanel.add(restoreButton(player, savedSettingsExist)); + } else if (clientGui.getClient().isLocalBot(player)) { + Client bot = clientGui.getClient().getBotClient(player); + if (bot instanceof Princess) { + gridPanel.add(new JLabel(LOCAL + " Princess")); + Princess princess = (Princess) bot; + try { + // Copy to protect the current settings + botConfigs.put(player, princess.getBehaviorSettings().getCopy()); + } catch (PrincessException e) { + LogManager.getLogger().error("", e); + // fallback to default + } + + var botChooser = new JComboBox<>(localBotOptions); + localBotChoosers.put(player, botChooser); + botChooser.setSelectedIndex(0); + botChooser.addActionListener(e -> updateButtonStates()); + + var cPanel = new JPanel(); + cPanel.add(botChooser); + gridPanel.add(cPanel); + + gridPanel.add(configButton(player)); + gridPanel.add(restoreButton(player, savedSettingsExist)); + } else if (bot == null){ + gridPanel.add(new JLabel(LOCAL + " Null Bot")); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + } else { + // Not a Ghost or Princess, something else, maybe a TestBot + gridPanel.add(new JLabel( LOCAL + ' ' +bot.getClass().toString())); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + } + } else if (player.isBot()) { + gridPanel.add(new JLabel(REMOTE + " Bot")); + var botChooser = new JComboBox<>(remoteBotOptions); + remoteBotChoosers.put(player, botChooser); + botChooser.setSelectedIndex(0); + var cPanel = new JPanel(); + cPanel.add(botChooser); + gridPanel.add(cPanel); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + } else { + gridPanel.add(new JLabel( + (clientGui.getClient().getLocalPlayer().equals(player) ? LOCAL : REMOTE) + ' ' + + (player.isGameMaster() ? "GM" : player.isObserver() ? "Observer" : "Player")) + ); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + gridPanel.add(new JLabel()); + } + } + + updateButtonStates(); + + var result = new JPanel(new FlowLayout(FlowLayout.LEFT)); + result.setBorder(new EmptyBorder(10, 20, 40, 20)); + result.add(gridPanel); + + return new JScrollPane(result); + } + + private JComponent configButton(Player player) { + var princessConfigButton = new JButton(Messages.getString("EditBotsDialog.config")); + configButtons.put(player, princessConfigButton); + princessConfigButton.addActionListener(e -> callConfig(player)); + + var panel = new JPanel(); + panel.add(princessConfigButton); + return panel; + } + + private JComponent restoreButton(Player player, boolean savedSettingsExist ) { + if (savedSettingsExist) { + JButton restoreButton = new JButton(Messages.getString("EditBotsDialog.configAvailable")); + restoreButton.addActionListener(e -> callRestoreConfig(player)); + + var rPanel = new JPanel(); + rPanel.add(restoreButton); + return rPanel; + } else { + return new JLabel(); + } + } + + /** Called from the config buttons. Opens a BotConfig Dialog and saves the result, if any. */ + private void callConfig(Player botOrGhost) { + var bcd = new BotConfigDialog(getFrame(), botOrGhost.getName(), botConfigs.get(botOrGhost), clientGui); + bcd.setVisible(true); + if (bcd.getResult() == DialogResult.CONFIRMED) { + botConfigs.put(botOrGhost, bcd.getBehaviorSettings()); + if (localBotChoosers.containsKey(botOrGhost)) { + localBotChoosers.get(botOrGhost).setSelectedIndex(EDIT_CONFIG_INDEX); + } + } + } + + private void callRestoreConfig(Player botOrGhost) { + Map savedSettings = game.getBotSettings(); + if ((savedSettings != null) && savedSettings.containsKey(botOrGhost.getName())) { + try { + // Don't change the existing saved settings + botConfigs.put(botOrGhost, savedSettings.get(botOrGhost.getName()).getCopy()); + } catch (PrincessException e) { + LogManager.getLogger().error("", e); + // fallback to default + } + } + } + + /** Updates the config button enabled states (only enabled when Princess bot is selected). */ + private void updateButtonStates() { + for (Player ghost : ghostChoosers.keySet()) { + JButton button = configButtons.get(ghost); + button.setEnabled(ghostChoosers.get(ghost).getSelectedIndex() == REPLACE_WITH_PRINCESS_INDEX); + } + + for (Player bot : localBotChoosers.keySet()) { + JButton button = configButtons.get(bot); + button.setEnabled(localBotChoosers.get(bot).getSelectedIndex() != KICK_INDEX); + } + } + + /** + * @return the result of the dialog with respect to ghost players to be replaced by Princess bots. + * The returned map links zero, one or more BehaviorSettings (a Princess configuration) + * to the ghost player name they were chosen for. The returned map only + * includes entries for those ghost players that had a Princess Bot replacement selected. + * The result may be empty, but not null. + */ + public Map getNewBots() { + var result = new HashMap(); + for (Player ghost : ghostChoosers.keySet()) { + JComboBox chooser = ghostChoosers.get(ghost); + if (chooser.getSelectedIndex() == REPLACE_WITH_PRINCESS_INDEX) { + result.put(ghost.getName(), botConfigs.get(ghost)); + } + } + return result; + } + + /** + * @return the result of the dialog with respect to Princess bots whose configuration is to be changed + * The returned map links zero, one or more BehaviorSettings (a Princess configuration) + * to the Princess player name they were chosen for. The returned map only + * includes entries for those Princess players that had the Edit option selected. + * The result may be empty, but not null. + */ + public Map getChangedBots() { + // All local bots with edited configs option selected + var result = new HashMap(); + for (Player bot : localBotChoosers.keySet()) { + JComboBox chooser = localBotChoosers.get(bot); + if (chooser.getSelectedIndex() == EDIT_CONFIG_INDEX) { + result.put(bot.getName(), botConfigs.get(bot)); + } + } + return result; + } + + /** + * Returns the result of the dialog with respect to selected princess bots to be kicked + * The result may be empty, but not null. + * @return a Set of bot player names to be kicked. May be empty but not null + */ + public Set getKickBots() { + var result = new HashSet(); + for (Player bot : localBotChoosers.keySet()) { + JComboBox chooser = localBotChoosers.get(bot); + if (chooser.getSelectedIndex() == KICK_INDEX) { + result.add(bot.getName()); + } + } + for (Player bot : remoteBotChoosers.keySet()) { + JComboBox chooser = remoteBotChoosers.get(bot); + if (chooser.getSelectedIndex() == KICK_INDEX) { + result.add(bot.getName()); + } + } + return result; + } + + private void adaptToGUIScale() { + UIUtil.adjustDialog(this, UIUtil.FONT_SCALE1); + } +} \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/ForceGeneratorViewUi.java b/megamek/src/megamek/client/ui/swing/ForceGeneratorViewUi.java index 3ed69f872a1..db188107f58 100644 --- a/megamek/src/megamek/client/ui/swing/ForceGeneratorViewUi.java +++ b/megamek/src/megamek/client/ui/swing/ForceGeneratorViewUi.java @@ -47,7 +47,7 @@ * Presents controls for selecting parameters of the force to generate and a tree structure showing * the generated force. The left and right sides of the view are made available separately for use by * RandomArmyDialog. - * + * * @author Neoancient */ public class ForceGeneratorViewUi implements ActionListener { @@ -197,7 +197,7 @@ public void treeExpanded(TreeExpansionEvent evt) { scroll.setBorder(BorderFactory.createTitledBorder(Messages.getString("RandomArmyDialog.Army"))); tblChosen.addMouseListener(tableMouseListener); tblChosen.addKeyListener(tableKeyListener); - + leftPanel = new JPanel(); leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); leftPanel.add(panControls); @@ -226,11 +226,11 @@ public void addChosenUnits(String playerName) { && (forceTree.getModel().getRoot() instanceof ForceDescriptor)) { configureNetworks((ForceDescriptor) forceTree.getModel().getRoot()); } - + List entities = new ArrayList<>(modelChosen.allEntities().size()); Client c = null; if (null != playerName) { - c = clientGui.getBots().get(playerName); + c = clientGui.getLocalBots().get(playerName); } if (null == c) { c = clientGui.getClient(); @@ -583,7 +583,7 @@ public static class ChosenEntityModel extends AbstractTableModel { public boolean hasEntity(final @Nullable Entity en) { return (en != null) && entityIds.contains(en.getExternalIdAsString()); } - + public void addEntity(Entity en) { if (!entityIds.contains(en.getExternalIdAsString())) { entities.add(en); diff --git a/megamek/src/megamek/client/ui/swing/RandomArmyDialog.java b/megamek/src/megamek/client/ui/swing/RandomArmyDialog.java index 0807dc66d0a..6be72ff4642 100644 --- a/megamek/src/megamek/client/ui/swing/RandomArmyDialog.java +++ b/megamek/src/megamek/client/ui/swing/RandomArmyDialog.java @@ -58,10 +58,10 @@ public class RandomArmyDialog extends JDialog implements ActionListener, TreeSel private static final int TAB_RAT_GENERATOR = 2; private static final int TAB_FORMATION_BUILDER = 3; private static final int TAB_FORCE_GENERATOR = 4; - + private static final String CARD_PREVIEW = "card_preview"; private static final String CARD_FORCE_TREE = "card_force_tree"; - + private ClientGUI m_clientgui; private Client m_client; AdvancedSearchDialog2 asd; @@ -154,7 +154,7 @@ public class RandomArmyDialog extends JDialog implements ActionListener, TreeSel .getString("RandomArmyDialog.Pad")); private JCheckBox m_chkCanon = new JCheckBox(Messages .getString("RandomArmyDialog.Canon")); - + private RandomUnitGenerator rug; private UnitTable generatedRAT; @@ -178,7 +178,7 @@ public RandomArmyDialog(ClientGUI cl) { rug = RandomUnitGenerator.getInstance(); rug.registerListener(this); if (rug.isInitialized()) { - m_ratStatus = new JLabel(Messages.getString("RandomArmyDialog.ratStatusDoneLoading")); + m_ratStatus = new JLabel(Messages.getString("RandomArmyDialog.ratStatusDoneLoading")); } else { m_ratStatus = new JLabel(Messages.getString("RandomArmyDialog.ratStatusLoading")); } @@ -208,7 +208,7 @@ public RandomArmyDialog(ClientGUI cl) { this.m_bRandomSkills.setEnabled(true); } }); - + m_pRightPane.add(m_pPreview, CARD_PREVIEW); m_pRightPane.add(m_pForceGen.getRightPanel(), CARD_FORCE_TREE); m_pRightPane.setMinimumSize(new Dimension(0,0)); @@ -223,13 +223,13 @@ public RandomArmyDialog(ClientGUI cl) { add(m_pSplit, BorderLayout.CENTER); validate(); setLocationRelativeTo(cl.frame); - + m_pSplit.setDividerLocation(GUIP.getRndArmySplitPos()); setSize(GUIP.getRndArmySizeWidth(), GUIP.getRndArmySizeHeight()); setLocation(GUIP.getRndArmyPosX(), GUIP.getRndArmyPosY()); adaptToGUIScale(); - + m_client.getGame().addGameListener(gameListener); addWindowListener(windowListener); } @@ -644,16 +644,16 @@ public void actionPerformed(ActionEvent ev) { Client c = null; if (m_chPlayer.getSelectedIndex() > 0) { String name = (String) m_chPlayer.getSelectedItem(); - c = m_clientgui.getBots().get(name); + c = m_clientgui.getLocalBots().get(name); } if (c == null) { c = m_client; } for (MechSummary ms : armyModel.getAllUnits()) { try { - Entity e = new MechFileParser(ms.getSourceFile(), + Entity e = new MechFileParser(ms.getSourceFile(), ms.getEntryName()).getEntity(); - + autoSetSkillsAndName(e); e.setOwner(c.getLocalPlayer()); if (!c.getGame().getPhase().isLounge()) { @@ -679,7 +679,7 @@ public void actionPerformed(ActionEvent ev) { m_lUnitsBVTotal.setText(msg_bvtotal + "0"); m_lArmyBVTotal.setText(msg_bvtotal + "0"); } - + // Save preferences GUIP.setRATBVMin(m_tBVmin.getText()); GUIP.setRATBVMax(m_tBVmax.getText()); @@ -696,7 +696,7 @@ public void actionPerformed(ActionEvent ev) { } else { GUIP.setRATSelectedRAT(""); } - + setVisible(false); } else if (ev.getSource().equals(m_bClear)) { armyModel.clearData(); @@ -760,7 +760,7 @@ public void actionPerformed(ActionEvent ev) { EnumSet.noneOf(MissionRole.class), 0, fRec)); List numUnits = new ArrayList<>(); numUnits.add(m_pFormationOptions.getNumUnits()); - + if (m_pFormationOptions.getIntegerOption("numOtherUnits") > 0) { if (m_pFormationOptions.getIntegerOption("otherUnitType") >= 0) { params.add(new UnitTable.Parameters(fRec, @@ -782,7 +782,7 @@ public void actionPerformed(ActionEvent ev) { //BA do not count for formation rules; add as a separate formation } } - + if (ft != null) { unitList.addAll(ft.generateFormation(params, numUnits, m_pFormationOptions.getIntegerOption("network"), false)); @@ -926,14 +926,14 @@ private void saveWindowSettings() { GUIP.setRndArmySplitPos(m_pSplit.getDividerLocation()); } }; - + private void updatePlayerChoice() { String lastChoice = (String) m_chPlayer.getSelectedItem(); String clientName = m_clientgui.getClient().getName(); m_chPlayer.removeAllItems(); m_chPlayer.setEnabled(true); m_chPlayer.addItem(clientName); - for (Client client : m_clientgui.getBots().values()) { + for (Client client : m_clientgui.getLocalBots().values()) { Player player = m_client.getGame().getPlayer(client.getLocalPlayerNumber()); if (!player.isObserver()) { @@ -985,13 +985,13 @@ private void updateRATs() { Iterator rats = rug.getRatList(); if (null == rats) { return; - } + } RatTreeNode ratTree = rug.getRatTree(); DefaultMutableTreeNode root = new DefaultMutableTreeNode(ratTree.name); createRatTreeNodes(root, ratTree); m_treeRAT.setModel(new DefaultTreeModel(root)); - + String selectedRATPath = GUIP.getRATSelectedRAT(); if (!selectedRATPath.isBlank()) { String[] nodes = selectedRATPath.replace('[', ' ') @@ -1000,7 +1000,7 @@ private void updateRATs() { m_treeRAT.setSelectionPath(path); } } - + private void updateRATYear() { GameOptions gameOptions = m_client.getGame().getOptions(); int gameYear = gameOptions.intOption("year"); @@ -1167,7 +1167,7 @@ public void gameSettingsChange(GameSettingsChangeEvent evt) { } } }; - + /** * A table model for displaying units */ @@ -1275,7 +1275,7 @@ public List getAllUnits() { return data; } } - + /** * A table model for displaying a generated RAT */ @@ -1303,7 +1303,7 @@ public void refreshData() { public int getColumnCount() { return N_COL; } - + public int getPreferredWidth(int col) { switch (col) { case COL_WEIGHT: @@ -1320,7 +1320,7 @@ public int getPreferredWidth(int col) { return 0; } } - + @Override public String getColumnName(int column) { switch (column) { diff --git a/megamek/src/megamek/client/ui/swing/RandomNameDialog.java b/megamek/src/megamek/client/ui/swing/RandomNameDialog.java index ee652abc08f..e2fe91629c9 100644 --- a/megamek/src/megamek/client/ui/swing/RandomNameDialog.java +++ b/megamek/src/megamek/client/ui/swing/RandomNameDialog.java @@ -104,7 +104,7 @@ private void updatePlayerChoice() { chPlayer.setEnabled(true); chPlayer.addItem(clientName); - for (Iterator i = clientgui.getBots().values().iterator(); i.hasNext();) { + for (Iterator i = clientgui.getLocalBots().values().iterator(); i.hasNext();) { chPlayer.addItem(i.next().getName()); } if (chPlayer.getItemCount() == 1) { @@ -143,7 +143,7 @@ public void actionPerformed(ActionEvent ev) { Client c = null; if (chPlayer.getSelectedIndex() > 0) { String name = (String) chPlayer.getSelectedItem(); - c = clientgui.getBots().get(name); + c = clientgui.getLocalBots().get(name); } if (c == null) { c = client; diff --git a/megamek/src/megamek/client/ui/swing/ReplacePlayersDialog.java b/megamek/src/megamek/client/ui/swing/ReplacePlayersDialog.java deleted file mode 100644 index 6bc76f6caa1..00000000000 --- a/megamek/src/megamek/client/ui/swing/ReplacePlayersDialog.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved. - * - * This file is part of MegaMek. - * - * MegaMek 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 3 of the License, or - * (at your option) any later version. - * - * MegaMek 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. - * - * You should have received a copy of the GNU General Public License - * along with MegaMek. If not, see . - */ -package megamek.client.ui.swing; - -import megamek.client.bot.princess.BehaviorSettings; -import megamek.client.ui.Messages; -import megamek.client.ui.baseComponents.AbstractButtonDialog; -import megamek.client.ui.dialogs.BotConfigDialog; -import megamek.client.ui.enums.DialogResult; -import megamek.client.ui.swing.util.UIUtil; -import megamek.common.Game; -import megamek.common.Player; -import org.apache.logging.log4j.LogManager; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.Vector; -import java.util.stream.Collectors; - -public class ReplacePlayersDialog extends AbstractButtonDialog { - private static final int PRINCESS_INDEX = 1; - - /** A ClientGUI given to the dialog. */ - private final ClientGUI clientGui; - - /** Convenience field for clientGui.getClient().getGame(). */ - private final Game game; - - /** The list of displayed ghost players */ - private Set ghostPlayers; - - /** Maps a ghost player to the combobox that sets its replacement */ - private Map> playerChoosers = new HashMap<>(); - - /** Maps a ghost player to the config button for the bot settings */ - private Map configButtons = new HashMap<>(); - - /** Maps a ghost player to bot settings chosen for it */ - private Map botConfigs = new HashMap<>(); - - protected ReplacePlayersDialog(JFrame frame, ClientGUI cg) { - super(frame, "ReplacePlayersDialog", "ReplacePlayersDialog.title"); - clientGui = cg; - game = clientGui.getClient().getGame(); - ghostPlayers = game.getPlayersVector().stream() - .filter(Player::isGhost).collect(Collectors.toSet()); - // Add a new Princess behavior for each ghost. It will be overwritten by savegame behaviors - ghostPlayers.forEach(p -> botConfigs.put(p, new BehaviorSettings())); - initialize(); - } - - @Override - protected void initialize() { - super.initialize(); - adaptToGUIScale(); - try { - finalizeInitialization(); - } catch (Exception ex) { - LogManager.getLogger().error("Error finalizing the ReplacePlayersDialog. Returning the created dialog, but this is likely to cause some oddities.", ex); - } - } - - @Override - protected Container createCenterPane() { - // Construct the available replacements for the ComboBox chooser - Vector replacements = new Vector<>(); - replacements.add(Messages.getString("ReplacePlayersDialog.noReplacement")); - replacements.add(Messages.getString("ReplacePlayersDialog.princess")); - - var gridPanel = new JPanel(); - gridPanel.setLayout(new GridLayout(ghostPlayers.size() + 2, 4, 2, 2)); - gridPanel.add(new JLabel(Messages.getString("ReplacePlayersDialog.ghostPlayerHeader"))); - gridPanel.add(new JLabel(Messages.getString("ReplacePlayersDialog.configAvailableHeader"))); - gridPanel.add(new JLabel(Messages.getString("ReplacePlayersDialog.chooseReplacementHeader"))); - gridPanel.add(new JLabel("")); - gridPanel.add(new JSeparator()); - gridPanel.add(new JSeparator()); - gridPanel.add(new JSeparator()); - gridPanel.add(new JSeparator()); - - // The rows for the ghost players - Map savedSettings = game.getBotSettings(); - for (Player ghost : ghostPlayers) { - // Name - gridPanel.add(new JLabel(ghost.getName())); - - boolean savedSettingsExist = (savedSettings != null) && savedSettings.containsKey(ghost.getName()); - - // Does it have a princess config saved - if (savedSettingsExist) { - gridPanel.add(new JLabel(Messages.getString("ReplacePlayersDialog.configAvailable"))); - } else { - gridPanel.add(new JLabel("")); - } - - // The replacement chooser - var chooser = new JComboBox<>(replacements); - playerChoosers.put(ghost, chooser); - if (savedSettingsExist) { - chooser.setSelectedIndex(PRINCESS_INDEX); - botConfigs.put(ghost, savedSettings.get(ghost.getName())); - } - chooser.addActionListener(e -> updateButtonStates()); - var cPanel = new JPanel(); - cPanel.add(chooser); - gridPanel.add(cPanel); - - // The bot config button - var button = new JButton(Messages.getString("ReplacePlayersDialog.config")); - button.addActionListener(e -> callConfig(ghost)); - button.setEnabled(savedSettingsExist); - configButtons.put(ghost, button); - var panel = new JPanel(); - panel.add(button); - gridPanel.add(panel); - } - - updateButtonStates(); - - var result = new JPanel(new FlowLayout(FlowLayout.LEFT)); - result.setBorder(new EmptyBorder(10, 20, 40, 20)); - result.add(gridPanel); - - return new JScrollPane(result); - } - - /** Called from the config buttons. Opens a BotConfig Dialog and saves the result, if any. */ - private void callConfig(Player ghost) { - var bcd = new BotConfigDialog(getFrame(), ghost.getName(), botConfigs.get(ghost), clientGui); - bcd.setVisible(true); - if (bcd.getResult() == DialogResult.CONFIRMED) { - botConfigs.put(ghost, bcd.getBehaviorSettings()); - } - } - - /** Updates the config button enabled states (only enabled when Princess bot is selected). */ - private void updateButtonStates() { - for (Player ghost : ghostPlayers) { - JButton button = configButtons.get(ghost); - button.setEnabled(playerChoosers.get(ghost).getSelectedIndex() == PRINCESS_INDEX); - } - } - - /** - * Returns the result of the dialog with respect to selected princess bots. - * The returned map links zero, one or more BehaviorSettings (a Princess configuration) - * to the ghost player name they were chosen for. The returned map only - * includes entries for those ghost players that had a Princess Bot replacement selected. - * The result may be empty, but not null. - */ - public Map getNewBots() { - var result = new HashMap(); - for (Player ghost : ghostPlayers) { - JComboBox chooser = playerChoosers.get(ghost); - if (chooser.getSelectedIndex() == PRINCESS_INDEX) { - result.put(ghost.getName(), botConfigs.get(ghost)); - } - } - return result; - } - - private void adaptToGUIScale() { - UIUtil.adjustDialog(this, UIUtil.FONT_SCALE1); - } -} \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/SkillGenerationDialog.java b/megamek/src/megamek/client/ui/swing/SkillGenerationDialog.java index 88ce75215a3..13f9783fc91 100644 --- a/megamek/src/megamek/client/ui/swing/SkillGenerationDialog.java +++ b/megamek/src/megamek/client/ui/swing/SkillGenerationDialog.java @@ -110,7 +110,7 @@ protected JPanel createButtonPanel() { final DefaultComboBoxModel clientsModel = new DefaultComboBoxModel<>(); clientsModel.addElement(getClientGUI().getClient().getName()); - clientsModel.addAll(getClientGUI().getBots().values().stream().map(Client::getName) + clientsModel.addAll(getClientGUI().getLocalBots().values().stream().map(Client::getName) .collect(Collectors.toList())); final MMComboBox comboClients = new MMComboBox<>("comboClients", clientsModel); comboClients.setToolTipText(resources.getString("comboClients.toolTipText")); @@ -118,7 +118,7 @@ protected JPanel createButtonPanel() { comboClients.setEnabled(comboClients.getItemCount() > 1); comboClients.addActionListener(evt -> getSkillGenerationOptionsPanel().changeClient( (comboClients.getSelectedIndex() > 0) - ? getClientGUI().getBots().get(comboClients.getSelectedItem()) + ? getClientGUI().getLocalBots().get(comboClients.getSelectedItem()) : getClientGUI().getClient())); panel.add(comboClients); return panel; diff --git a/megamek/src/megamek/client/ui/swing/dialog/MegaMekUnitSelectorDialog.java b/megamek/src/megamek/client/ui/swing/dialog/MegaMekUnitSelectorDialog.java index 366999655ed..7975d4679ea 100644 --- a/megamek/src/megamek/client/ui/swing/dialog/MegaMekUnitSelectorDialog.java +++ b/megamek/src/megamek/client/ui/swing/dialog/MegaMekUnitSelectorDialog.java @@ -103,7 +103,7 @@ protected void select(boolean close) { String name = (String) comboPlayer.getSelectedItem(); if (comboPlayer.getSelectedIndex() > 0) { - client = clientGUI.getBots().get(name); + client = clientGUI.getLocalBots().get(name); } if (client == null) { @@ -145,7 +145,7 @@ private void updatePlayerChoice() { comboPlayer.removeAllItems(); comboPlayer.setEnabled(true); comboPlayer.addItem(clientName); - for (Client client : clientGUI.getBots().values()) { + for (Client client : clientGUI.getLocalBots().values()) { comboPlayer.addItem(client.getName()); } if (comboPlayer.getItemCount() == 1) { diff --git a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java index cb7b61fdf7e..78f4bf15cbe 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java +++ b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java @@ -91,7 +91,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; @@ -100,7 +99,7 @@ import static megamek.common.util.CollectionUtil.theElement; import static megamek.common.util.CollectionUtil.union; -public class ChatLounge extends AbstractPhaseDisplay implements +public class ChatLounge extends AbstractPhaseDisplay implements ListSelectionListener, IMapSettingsObserver, IPreferenceChangeListener { private static final long serialVersionUID = 1454736776730903786L; @@ -115,7 +114,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements private JPanel panUnits = new JPanel(); private JPanel panMap = new JPanel(); private JPanel panTeam = new JPanel(); - + // Labels private JLabel lblMapSummary = new JLabel(""); private JLabel lblGameYear = new JLabel(""); @@ -148,7 +147,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements private JButton butCollapse = new JButton(Messages.getString("ChatLounge.butCollapse")); private JButton butExpand = new JButton(Messages.getString("ChatLounge.butExpand")); private MekTableModel mekModel; - + /* Force Tree */ private MekTreeForceModel mekForceTreeModel; JTree mekForceTree; @@ -179,14 +178,14 @@ public class ChatLounge extends AbstractPhaseDisplay implements private JTextField fldMapHeight = new JTextField(3); private FixedYPanel panMapHeight = new FixedYPanel(); private FixedYPanel panMapWidth = new FixedYPanel(); - + private JLabel lblSpaceBoardWidth = new JLabel(Messages.getString("ChatLounge.labBoardWidth")); private JTextField fldSpaceBoardWidth = new JTextField(3); private JLabel lblSpaceBoardHeight = new JLabel(Messages.getString("ChatLounge.labBoardHeight")); private JTextField fldSpaceBoardHeight = new JTextField(3); private FixedYPanel panSpaceBoardHeight = new FixedYPanel(); private FixedYPanel panSpaceBoardWidth = new FixedYPanel(); - + private JLabel lblBoardSize = new JLabel(Messages.getString("ChatLounge.labBoardSize")); private JButton butHelp = new JButton(" " + Messages.getString("ChatLounge.butHelp") + " "); @@ -210,10 +209,10 @@ public class ChatLounge extends AbstractPhaseDisplay implements private Game boardPreviewGame = new Game(); private BoardView previewBV; Dimension currentMapButtonSize = new Dimension(0, 0); - + private ArrayList invalidBoards = new ArrayList<>(); private ArrayList serverBoards = new ArrayList<>(); - + private JSplitPane splGroundMap; private JLabel lblSearch = new JLabel(Messages.getString("ChatLounge.labSearch")); private JTextField fldSearch = new JTextField(10); @@ -222,27 +221,27 @@ public class ChatLounge extends AbstractPhaseDisplay implements private MekTableSorter activeSorter; private ArrayList unitSorters = new ArrayList<>(); private ArrayList bvSorters = new ArrayList<>(); - + private JButton butAddY = new JButton(Messages.getString("ChatLounge.butAdd")); private JButton butAddX = new JButton(Messages.getString("ChatLounge.butAdd")); private JButton butSaveMapSetup = new JButton(Messages.getString("ChatLounge.map.saveMapSetup") + " *"); private JButton butLoadMapSetup = new JButton(Messages.getString("ChatLounge.map.loadMapSetup")); - + /* Team Overview Panel */ private TeamOverviewPanel panTeamOverview; JButton butDetach = new JButton(Messages.getString("ChatLounge.butDetach")); private JSplitPane splitPaneMain; ClientDialog teamOverviewWindow; - + private ImageLoader loader; private Map baseImages = new HashMap<>(); - - private MapListMouseAdapter mapListMouseListener = new MapListMouseAdapter(); - - LobbyActions lobbyActions = new LobbyActions(this); - + + private MapListMouseAdapter mapListMouseListener = new MapListMouseAdapter(); + + LobbyActions lobbyActions = new LobbyActions(this); + private Map boardTags = new HashMap<>(); - + LobbyKeyDispatcher lobbyKeyDispatcher = new LobbyKeyDispatcher(this); private static final String CL_KEY_FILEEXTENTION_BOARD = ".board"; @@ -297,37 +296,37 @@ public ChatLounge(ClientGUI clientgui) { public void setBottom(JComponent comp) { splitPaneMain.setBottomComponent(comp); } - + /** Sets up all the listeners that the lobby works with. */ private void setupListeners() { // Make sure that no listeners are already registered from calling a refresh... method removeAllListeners(); - + GUIP.addPreferenceChangeListener(this); PreferenceManager.getClientPreferences().addPreferenceChangeListener(this); MechSummaryCache.getInstance().addListener(mechSummaryCacheListener); clientgui.getClient().getGame().addGameListener(this); clientgui.getBoardView().addBoardViewListener(this); - + loader = new ImageLoader(); loader.execute(); tablePlayers.getSelectionModel().addListSelectionListener(this); tablePlayers.addMouseListener(new PlayerTableMouseAdapter()); - + lisBoardsAvailable.addListSelectionListener(this); lisBoardsAvailable.addMouseListener(mapListMouseListener); lisBoardsAvailable.addMouseMotionListener(mapListMouseListener); - + teamOverviewWindow.addWindowListener(teamOverviewWindowListener); - + mekTable.addMouseListener(mekTableMouseAdapter); mekTable.getTableHeader().addMouseListener(mekTableHeaderMouseListener); mekTable.addKeyListener(mekTableKeyListener); - + mekForceTree.addKeyListener(mekTreeKeyListener); mekForceTree.addMouseListener(mekForceTreeMouseListener); - + butAdd.addActionListener(lobbyListener); butAddBot.addActionListener(lobbyListener); butArmy.addActionListener(lobbyListener); @@ -365,7 +364,7 @@ private void setupListeners() { butForceView.addActionListener(lobbyListener); butCollapse.addActionListener(lobbyListener); butExpand.addActionListener(lobbyListener); - + fldMapWidth.addActionListener(lobbyListener); fldMapHeight.addActionListener(lobbyListener); fldMapWidth.addFocusListener(focusListener); @@ -374,16 +373,16 @@ private void setupListeners() { fldSpaceBoardHeight.addActionListener(lobbyListener); fldSpaceBoardWidth.addFocusListener(focusListener); fldSpaceBoardHeight.addFocusListener(focusListener); - + comboTeam.addActionListener(lobbyListener); - + KeyboardFocusManager kbfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); kbfm.addKeyEventDispatcher(lobbyKeyDispatcher); } /** Applies changes to the board and map size when the textfields lose focus. */ FocusListener focusListener = new FocusAdapter() { - + @Override public void focusLost(FocusEvent e) { if (e.getSource() == fldMapWidth) { @@ -394,10 +393,10 @@ public void focusLost(FocusEvent e) { setManualBoardWidth(); } else if (e.getSource() == fldSpaceBoardHeight) { setManualBoardHeight(); - } + } } - }; - + }; + /** Shows the camo chooser and sets the selected camo. */ ActionListener camoListener = e -> { // Show the CamoChooser for the selected player @@ -417,22 +416,22 @@ public void focusLost(FocusEvent e) { butCamo.setIcon(player.getCamouflage().getImageIcon()); getSelectedClient().sendPlayerInfo(); }; - - + + private void setupTeamOverview() { panTeamOverview = new TeamOverviewPanel(clientgui); FixedYPanel panDetach = new FixedYPanel(new FlowLayout(FlowLayout.LEFT)); panDetach.add(butDetach); - + panTeam.setLayout(new BoxLayout(panTeam, BoxLayout.PAGE_AXIS)); panTeam.add(panDetach); panTeam.add(panTeamOverview); - + // setup (but don't show) the detached team overview window teamOverviewWindow = new ClientDialog(clientgui.frame, Messages.getString("ChatLounge.name.teamOverview"), false); teamOverviewWindow.setSize(clientgui.frame.getWidth() / 2, clientgui.frame.getHeight() / 2); } - + /** Re-attaches the Team Overview panel to the tab when the detached window is closed. */ WindowListener teamOverviewWindowListener = new WindowAdapter() { @Override @@ -447,7 +446,7 @@ public void windowClosing(WindowEvent e) { panTabs.repaint(); } }; - + /** Initializes the Mek Table sorting algorithms. */ private void setupSorters() { unitSorters.add(new PlayerTransportIDSorter(clientgui)); @@ -499,7 +498,7 @@ private void setupEntities() { mekForceTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); mekForceTree.setExpandsSelectedPaths(true); ToolTipManager.sharedInstance().registerComponent(mekForceTree); - + scrMekTable = new JScrollPane(mekTable); scrMekTable.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); } @@ -531,7 +530,7 @@ private void setupUnitConfig() { panUnitInfoGrid.add(butLoadList); panUnitInfoGrid.add(butSaveList); panUnitInfoGrid.add(butNames); - + panUnitInfo.add(panUnitInfoAdd); panUnitInfo.add(panUnitInfoGrid); } @@ -551,16 +550,16 @@ private void setupPlayerConfig() { setupTeamCombo(); butCamo.setActionCommand(CL_ACTIONCOMMAND_CAMO); refreshCamoButton(); - + panPlayerInfo = new FixedYPanel(new GridLayout(1, 2, 2, 2)); panPlayerInfo.setBorder(BorderFactory.createTitledBorder(Messages.getString("ChatLounge.name.playerSetup"))); - + JPanel panPlayerInfoBts = new JPanel(new GridLayout(4, 1, 2, 2)); panPlayerInfoBts.add(comboTeam); panPlayerInfoBts.add(butConfigPlayer); panPlayerInfoBts.add(butAddBot); panPlayerInfoBts.add(butRemoveBot); - + panPlayerInfo.add(panPlayerInfoBts); panPlayerInfo.add(butCamo); @@ -573,14 +572,14 @@ private void setupUnitsPanel() { viewGroup.add(butListView); viewGroup.add(butForceView); butListView.setSelected(true); - + butCollapse.setEnabled(false); butExpand.setEnabled(false); - + lblGameYear.setAlignmentX(JPanel.CENTER_ALIGNMENT); lblTechLevel.setAlignmentX(JPanel.CENTER_ALIGNMENT); butOptions.setAlignmentX(JPanel.CENTER_ALIGNMENT); - + FixedXPanel leftSide = new FixedXPanel(); leftSide.setLayout(new BoxLayout(leftSide, BoxLayout.PAGE_AXIS)); leftSide.add(Box.createVerticalStrut(scaleForGUI(20))); @@ -593,7 +592,7 @@ private void setupUnitsPanel() { leftSide.add(panPlayerInfo); leftSide.add(Box.createVerticalStrut(scaleForGUI(5))); leftSide.add(scrPlayers); - + JPanel topRight = new FixedYPanel(); topRight.add(butListView); topRight.add(butForceView); @@ -603,12 +602,12 @@ private void setupUnitsPanel() { topRight.add(Box.createHorizontalStrut(30)); topRight.add(butCollapse); topRight.add(butExpand); - + JPanel rightSide = new JPanel(); rightSide.setLayout(new BoxLayout(rightSide, BoxLayout.PAGE_AXIS)); rightSide.add(topRight); rightSide.add(scrMekTable); - + panUnits.setLayout(new BoxLayout(panUnits, BoxLayout.LINE_AXIS)); panUnits.add(leftSide); panUnits.add(rightSide); @@ -620,7 +619,7 @@ private void setupMapPanel() { refreshMapUI(); panMap.setLayout(new BoxLayout(panMap, BoxLayout.PAGE_AXIS)); - + // Ground, Atmo, Space Map Buttons FixedYPanel panMapType = new FixedYPanel(); panMapType.setAlignmentX(JPanel.CENTER_ALIGNMENT); @@ -631,7 +630,7 @@ private void setupMapPanel() { grpMap.add(butLowAtmoMap); grpMap.add(butHighAtmoMap); grpMap.add(butSpaceMap); - + // Planetary Conditions and Random Map Settings buttons FixedYPanel panSettings = new FixedYPanel(); panSettings.setAlignmentX(JPanel.CENTER_ALIGNMENT); @@ -642,15 +641,15 @@ private void setupMapPanel() { panTopRows.setLayout(new BoxLayout(panTopRows, BoxLayout.PAGE_AXIS)); panTopRows.add(panMapType); panTopRows.add(panSettings); - + JPanel panHelp = new JPanel(new GridLayout(1, 1)); panHelp.add(butHelp); - + FixedYPanel panTopRowsHelp = new FixedYPanel(new FlowLayout(FlowLayout.CENTER, 30, 5)); panTopRowsHelp.add(panTopRows); panTopRowsHelp.add(panHelp); panMap.add(panTopRowsHelp); - + // Main part: Map Assembly panMap.add(panGroundMap); @@ -672,25 +671,25 @@ public void componentResized(ComponentEvent e) { updateMapButtons(); } }); - + panMapWidth.add(lblMapWidth); panMapWidth.add(butMapShrinkW); panMapWidth.add(fldMapWidth); panMapWidth.add(butMapGrowW); - + panMapHeight.add(lblMapHeight); panMapHeight.add(butMapShrinkH); panMapHeight.add(fldMapHeight); panMapHeight.add(butMapGrowH); - + panSpaceBoardWidth.add(lblSpaceBoardWidth); panSpaceBoardWidth.add(fldSpaceBoardWidth); panSpaceBoardWidth.setVisible(false); - + panSpaceBoardHeight.add(lblSpaceBoardHeight); panSpaceBoardHeight.add(fldSpaceBoardHeight); panSpaceBoardHeight.setVisible(false); - + FixedYPanel bottomPanel = new FixedYPanel(); bottomPanel.setBorder(new EmptyBorder(10, 0, 0, 0)); bottomPanel.add(butBoardPreview); @@ -702,14 +701,14 @@ public void componentResized(ComponentEvent e) { // The left side panel including the game map preview JPanel panMapPreview = new JPanel(); panMapPreview.setLayout(new BoxLayout(panMapPreview, BoxLayout.PAGE_AXIS)); - + panMapPreview.add(panMapWidth); panMapPreview.add(panMapHeight); panMapPreview.add(panSpaceBoardWidth); panMapPreview.add(panSpaceBoardHeight); panMapPreview.add(panMapButtons); panMapPreview.add(bottomPanel); - + // The right side panel including the list of available boards comMapSizes = new JComboBox<>(); refreshMapSizes(); @@ -722,13 +721,13 @@ public void componentResized(ComponentEvent e) { lisBoardsAvailable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); scrBoardsAvailable = new JScrollPane(lisBoardsAvailable); refreshBoardsAvailable(); - + JPanel panAvail = new JPanel(); panAvail.setLayout(new BoxLayout(panAvail, BoxLayout.PAGE_AXIS)); panAvail.setBorder(new EmptyBorder(0, 20, 0, 0)); panAvail.add(setupAvailTopPanel()); panAvail.add(scrBoardsAvailable); - + // The splitpane holding the left and right side panels splGroundMap = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panMapPreview, panAvail); splGroundMap.addComponentListener(new ComponentAdapter() { @@ -736,7 +735,7 @@ public void componentResized(ComponentEvent e) { public void componentResized(ComponentEvent e) { splGroundMap.setDividerLocation(getDividerLocation()); } - + @Override public void componentShown(ComponentEvent e) { splGroundMap.setDividerLocation(getDividerLocation()); @@ -767,15 +766,15 @@ public void componentShown(ComponentEvent e) { } refreshMapButtons(); } - - /** - * Sets up and returns the panel above the available boards list - * containing the search bar and the map size chooser. + + /** + * Sets up and returns the panel above the available boards list + * containing the search bar and the map size chooser. */ private JPanel setupAvailTopPanel() { FixedYPanel result = new FixedYPanel(new FlowLayout(FlowLayout.CENTER, 20, 2)); result.setBorder(new EmptyBorder(5, 5, 5, 5)); - + fldSearch.getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent e) { @@ -792,7 +791,7 @@ public void insertUpdate(DocumentEvent e) { updateSearch(fldSearch.getText()); } }); - + result.add(lblBoardSize); result.add(comMapSizes); result.add(new JLabel(" ")); @@ -802,8 +801,8 @@ public void insertUpdate(DocumentEvent e) { return result; } - - /** + + /** * Reacts to changes in the available boards search field, showing matching boards * for the search string when it has at least 3 characters * and reverting to all boards when the search string is empty. @@ -815,10 +814,10 @@ private void updateSearch(String searchString) { refreshBoardsAvailable(getSearchedItems(searchString)); } } - - /** + + /** * Returns the available boards that match the given search string - * (path or file name contains the search string.) + * (path or file name contains the search string.) * The search string is split at ";" and search results for the tokens * are ANDed. */ @@ -839,8 +838,8 @@ protected List getSearchedItems(String searchString) { } return result; } - - /** + + /** * Returns a suitable divider location for the splitpane that contains * the available boards list and the map preview. The divider location * gives between 30% and 50% of space to the map preview depending @@ -863,14 +862,14 @@ private void refreshMapChoice() { } else if (mapSettings.getMedium() == MapSettings.MEDIUM_SPACE) { button = butSpaceMap; } - + if (!button.isSelected()) { button.removeActionListener(lobbyListener); button.setSelected(true); button.addActionListener(lobbyListener); } } - + /** Updates the list of available map sizes. */ private void refreshMapSizes() { int oldSelection = comMapSizes.getSelectedIndex(); @@ -887,7 +886,7 @@ private void refreshMapSizes() { /** * Refreshes the map assembly UI from the current map settings. Does NOT trigger further - * changes or result in packets to the server. + * changes or result in packets to the server. */ private void refreshMapUI() { boolean inSpace = mapSettings.getMedium() == MapSettings.MEDIUM_SPACE; @@ -909,7 +908,7 @@ private void refreshMapUI() { butLoadMapSetup.setEnabled(!inSpace); butMapShrinkW.setEnabled(mapSettings.getMapWidth() > 1); butMapShrinkH.setEnabled(mapSettings.getMapHeight() > 1); - + butGroundMap.removeActionListener(lobbyListener); butLowAtmoMap.removeActionListener(lobbyListener); butHighAtmoMap.removeActionListener(lobbyListener); @@ -925,7 +924,7 @@ private void refreshMapUI() { butLowAtmoMap.addActionListener(lobbyListener); butHighAtmoMap.addActionListener(lobbyListener); butSpaceMap.addActionListener(lobbyListener); - + fldMapWidth.removeActionListener(lobbyListener); fldMapHeight.removeActionListener(lobbyListener); fldSpaceBoardWidth.removeActionListener(lobbyListener); @@ -940,7 +939,7 @@ private void refreshMapUI() { fldSpaceBoardHeight.addActionListener(lobbyListener); } - /** + /** * Refreshes the list of available boards with all available boards plus * GENERATED. Useful for first setup, when the server transmits new * map settings and when the text search field is empty. @@ -951,13 +950,13 @@ private void refreshBoardsAvailable() { } lisBoardsAvailable.setFixedCellHeight(-1); lisBoardsAvailable.setFixedCellWidth(-1); - List availBoards = new ArrayList<>(); + List availBoards = new ArrayList<>(); availBoards.add(MapSettings.BOARD_GENERATED); availBoards.addAll(mapSettings.getBoardsAvailableVector()); refreshBoardTags(); refreshBoardsAvailable(availBoards); } - + private void refreshBoardTags() { boardTags.clear(); for (String boardName : mapSettings.getBoardsAvailableVector()) { @@ -966,9 +965,9 @@ private void refreshBoardTags() { boardTags.put(boardName, String.join("||", tags).toLowerCase()); } } - - /** - * Refreshes the list of available maps with the given list of boards. + + /** + * Refreshes the list of available maps with the given list of boards. */ private void refreshBoardsAvailable(List boardList) { lisBoardsAvailable.removeListSelectionListener(this); @@ -982,11 +981,11 @@ private void refreshBoardsAvailable(List boardList) { lisBoardsAvailable.clearSelection(); lisBoardsAvailable.addListSelectionListener(this); } - + public boolean isMultipleBoards() { return mapSettings.getMapHeight() * mapSettings.getMapWidth() > 1; } - + MapSettings oldMapSettings = MapSettings.getInstance(); /** @@ -1027,25 +1026,25 @@ private void refreshMapButtons() { if (boardName == null) { continue; } - if (!button.getBoard().equals(boardName) + if (!button.getBoard().equals(boardName) || oldMapSettings.getMedium() != mapSettings.getMedium() - || (!mapSettings.equalMapGenParameters(oldMapSettings) + || (!mapSettings.equalMapGenParameters(oldMapSettings) && mapSettings.getMapWidth() == oldMapSettings.getMapWidth() && mapSettings.getMapHeight() == oldMapSettings.getMapHeight())) { Board buttonBoard; Image image; // Generated and space boards use a generated example - if (boardName.startsWith(MapSettings.BOARD_GENERATED) + if (boardName.startsWith(MapSettings.BOARD_GENERATED) || (mapSettings.getMedium() == MapSettings.MEDIUM_SPACE)) { buttonBoard = BoardUtilities.generateRandom(mapSettings); image = Minimap.getMinimapImageMaxZoom(buttonBoard); - } else { + } else { String boardForImage = boardName; // For a surprise board, just use the first board as example if (boardName.startsWith(MapSettings.BOARD_SURPRISE)) { boardForImage = extractSurpriseMaps(boardName).get(0); } - + boolean rotateBoard = false; // for a rotation board, set a flag (when appropriate) and fix the name if (boardForImage.startsWith(Board.BOARD_REQUEST_ROTATION)) { @@ -1053,10 +1052,10 @@ private void refreshMapButtons() { if ((mapSettings.getBoardWidth() % 2) == 0) { rotateBoard = true; } - + boardForImage = boardForImage.replace(Board.BOARD_REQUEST_ROTATION, ""); } - + File boardFile = new MegaMekFile(Configuration.boardsDir(), boardForImage + CL_KEY_FILEEXTENTION_BOARD).getFile(); if (boardFile.exists()) { buttonBoard = new Board(16, 17); @@ -1083,7 +1082,7 @@ private void refreshMapButtons() { } } oldMapSettings = MapSettings.getInstance(mapSettings); - + if (buttonSize != null) { for (MapPreviewButton button: mapButtons) { button.setPreviewSize(buttonSize); @@ -1115,7 +1114,7 @@ private void refreshMapButtons() { comMapSizes.addActionListener(lobbyListener); } - + private void markServerSideBoard(BufferedImage image) { Graphics g = image.getGraphics(); setHighQualityRendering(g); @@ -1137,10 +1136,10 @@ public void previewGameBoard() { boardPreviewGame.setBoard(newBoard); boardPreviewW.setVisible(true); } - - /** + + /** * Returns the game map as it is currently set in the map settings tab. - * When onlyFixedBoards is true, all Generated and Surprise boards are + * When onlyFixedBoards is true, all Generated and Surprise boards are * replaced by empty boards, otherwise they are filled with a generated or * a choice of the surprise maps. */ @@ -1150,17 +1149,17 @@ public Board getPossibleGameBoard(boolean onlyFixedBoards) { List rotateBoard = new ArrayList<>(); for (int i = 0; i < (mapSettings.getMapWidth() * mapSettings.getMapHeight()); i++) { sheetBoards[i] = new Board(); - + String name = mapSettings.getBoardsSelectedVector().get(i); if ((name.startsWith(MapSettings.BOARD_GENERATED) || name.startsWith(MapSettings.BOARD_SURPRISE)) && onlyFixedBoards) { sheetBoards[i] = Board.createEmptyBoard(mapSettings.getBoardWidth(), mapSettings.getBoardHeight()); - } else if (name.startsWith(MapSettings.BOARD_GENERATED) + } else if (name.startsWith(MapSettings.BOARD_GENERATED) || (mapSettings.getMedium() == MapSettings.MEDIUM_SPACE)) { sheetBoards[i] = BoardUtilities.generateRandom(mapSettings); } else { boolean flipBoard = false; - + if (name.startsWith(MapSettings.BOARD_SURPRISE)) { List boardList = extractSurpriseMaps(name); int rnd = (int) (Math.random() * boardList.size()); @@ -1191,15 +1190,15 @@ private void refreshGameSettings() { refreshMapSettings(); refreshDoneButton(); } - + /** - * Refreshes the Mek Table contents + * Refreshes the Mek Table contents */ public void refreshEntities() { refreshTree(); refreshMekTable(); } - + private void refreshMekTable() { List enIds = getSelectedEntities().stream().map(Entity::getId).collect(toList()); mekModel.clearData(); @@ -1208,40 +1207,40 @@ private void refreshMekTable() { boolean localUnits = false; GameOptions opts = clientgui.getClient().getGame().getOptions(); - + for (Entity entity : allEntities) { // Remember if the local player has units. if (!localUnits && entity.getOwner().equals(localPlayer())) { localUnits = true; } - if (!opts.booleanOption(OptionsConstants.RPG_PILOT_ADVANTAGES)) { + if (!opts.booleanOption(OptionsConstants.RPG_PILOT_ADVANTAGES)) { entity.getCrew().clearOptions(PilotOptions.LVL3_ADVANTAGES); } - if (!opts.booleanOption(OptionsConstants.EDGE)) { + if (!opts.booleanOption(OptionsConstants.EDGE)) { entity.getCrew().clearOptions(PilotOptions.EDGE_ADVANTAGES); } - if (!opts.booleanOption(OptionsConstants.RPG_MANEI_DOMINI)) { + if (!opts.booleanOption(OptionsConstants.RPG_MANEI_DOMINI)) { entity.getCrew().clearOptions(PilotOptions.MD_ADVANTAGES); } - if (!opts.booleanOption(OptionsConstants.ADVANCED_STRATOPS_PARTIALREPAIRS)) { + if (!opts.booleanOption(OptionsConstants.ADVANCED_STRATOPS_PARTIALREPAIRS)) { entity.clearPartialRepairs(); } - + // Remove some deployment options when a unit is carried - if (entity.getTransportId() != Entity.NONE) { + if (entity.getTransportId() != Entity.NONE) { entity.setHidden(false); entity.setProne(false); entity.setHullDown(false); } - - if (!opts.booleanOption(OptionsConstants.ADVANCED_HIDDEN_UNITS)) { + + if (!opts.booleanOption(OptionsConstants.ADVANCED_HIDDEN_UNITS)) { entity.setHidden(false); } - + // Handle the "Blind Drop" option. In blind drop, units must be added // but they will be obscured in the table. In real blind drop, units // don't even get added to the table. Teams see their units in any case. @@ -1261,13 +1260,13 @@ private void refreshMekTable() { } } } - + /** Adjusts the mektable to compact/normal mode. */ private void toggleCompact() { setTableRowHeights(); mekModel.refreshCells(); mekForceTreeModel.nodeChanged((TreeNode) mekForceTreeModel.getRoot()); - + } /** Refreshes the player info table. */ @@ -1290,7 +1289,7 @@ private void refreshPlayerTable() { } } - /** Updates the camo button to displays the camo of the currently selected player. */ + /** Updates the camo button to displays the camo of the currently selected player. */ private void refreshCamoButton() { if ((tablePlayers == null) || (playerModel == null) || (tablePlayers.getSelectedRowCount() == 0)) { return; @@ -1363,16 +1362,16 @@ void loadOnto(Entity carried, int carrierId, int bayNumber) { return; } } - + getLocalClient(carried).sendLoadEntity(carried.getId(), carrierId, bayNumber); - // TODO: it would probably be a good idea + // TODO: it would probably be a good idea // to disable some settings for loaded units in customMechDialog } - /** + /** * Have the given entity disembark if it is carried by another unit. * Entities that are modified and need an update to be sent to the server - * are added to the given updateCandidates. + * are added to the given updateCandidates. */ void disembark(Entity entity, Collection updateCandidates) { if (entity.getTransportId() == Entity.NONE) { @@ -1386,11 +1385,11 @@ void disembark(Entity entity, Collection updateCandidates) { updateCandidates.add(carrier); } } - - /** + + /** * Have the given entity disembark if it is carried by a unit of another player. * Entities that were modified and need an update to be sent to the server - * are added to the given updateCandidate set. + * are added to the given updateCandidate set. */ void disembarkDifferentOwner(Entity entity, Collection updateCandidates) { if (entity.getTransportId() == Entity.NONE) { @@ -1401,60 +1400,60 @@ void disembarkDifferentOwner(Entity entity, Collection updateCandidates) disembark(entity, updateCandidates); } } - - /** + + /** * Have the given entities offload all the units they are carrying. - * Returns a set of entities that need to be sent to the server. + * Returns a set of entities that need to be sent to the server. */ void offloadAll(Collection entities, Collection updateCandidates) { for (Entity carrier: editableEntities(entities)) { offloadFrom(carrier, updateCandidates); } } - - /** + + /** * Have the given entity offload all the units it is carrying. - * Returns a set of entities that need to be sent to the server. + * Returns a set of entities that need to be sent to the server. */ void offloadFrom(Entity entity, Collection updateCandidates) { if (isEditable(entity)) { for (Entity carriedUnit: entity.getLoadedUnits()) { disembark(carriedUnit, updateCandidates); - } + } } } - - /** + + /** * Have the given entity offload all units of different players it is carrying. - * Returns a set of entities that need to be sent to the server. + * Returns a set of entities that need to be sent to the server. */ void offloadFromDifferentOwner(Entity entity, Collection updateCandidates) { for (Entity carriedUnit: entity.getLoadedUnits()) { if (ownerOf(carriedUnit) != ownerOf(entity)) { disembark(carriedUnit, updateCandidates); } - } + } } - - /** - * Sends the entities in the given Collection to the Server. + + /** + * Sends the entities in the given Collection to the Server. * Sends only those that can be edited, i.e. the player's own - * or his bots' units. + * or his bots' units. */ void sendUpdate(Collection updateCandidates) { for (Entity e: editableEntities(updateCandidates)) { getLocalClient(e).sendUpdateEntity(e); } } - - /** - * Sends the entities in the given Collection to the Server. + + /** + * Sends the entities in the given Collection to the Server. * Sends only those that can be edited, i.e. the player's own * or his bots' units. Will separate the units into update - * packets for the local player and any local bots so that the + * packets for the local player and any local bots so that the * server accepts all changes (as the server does not know of * local bots and rejects updates that are not for the sending client - * or its teammates. + * or its teammates. */ void sendUpdates(Collection entities) { List owners = entities.stream().map(Entity::getOwner).distinct().collect(toList()); @@ -1463,9 +1462,9 @@ void sendUpdates(Collection entities) { entities.stream().filter(e -> e.getOwner().equals(owner)).collect(toList()))); } } - - /** - * Disembarks all given entities from any transports they are in. + + /** + * Disembarks all given entities from any transports they are in. */ void disembarkAll(Collection entities) { Set updateCandidates = new HashSet<>(); @@ -1473,21 +1472,21 @@ void disembarkAll(Collection entities) { sendUpdate(updateCandidates); } - /** + /** * Returns true when the given entity may be configured by the local player, * i.e. if it is his own unit or one of his bot's units. *

Note that this is more restrictive than the Server is. The Server - * accepts entity changes also for teammates so that entity updates that + * accepts entity changes also for teammates so that entity updates that * signal transporting a teammate's unit don't get rejected. I feel that * configuration other than transporting units should be limited to one's * own units (and bots) though. */ boolean isEditable(Entity entity) { - return clientgui.getBots().containsKey(entity.getOwner().getName()) + return clientgui.getLocalBots().containsKey(entity.getOwner().getName()) || (entity.getOwnerId() == localPlayer().getId()); } - - /** + + /** * Returns true when the given entity may NOT be configured by the local player, * i.e. if it is not own unit or one of his bot's units. * @see #isEditable(Entity) @@ -1495,8 +1494,8 @@ boolean isEditable(Entity entity) { boolean isNotEditable(Entity entity) { return !isEditable(entity); } - - /** + + /** * Returns true when all given entities may be configured by the local player, * i.e. if they are his own units or one of his bot's units. * @see #isEditable(Entity) @@ -1504,16 +1503,16 @@ boolean isNotEditable(Entity entity) { boolean isEditable(Collection entities) { return !entities.stream().anyMatch(this::isNotEditable); } - - /** + + /** * Returns the Client associated with a given entity that may be configured * by the local player (his own unit or one of his bot's units). * For a unit that cannot be configured (owned by a remote player) the client * of the local player is returned. */ Client getLocalClient(Entity entity) { - if (clientgui.getBots().containsKey(entity.getOwner().getName())) { - return clientgui.getBots().get(entity.getOwner().getName()); + if (clientgui.getLocalBots().containsKey(entity.getOwner().getName())) { + return clientgui.getLocalBots().get(entity.getOwner().getName()); } else { return clientgui.getClient(); } @@ -1524,7 +1523,7 @@ public void configPlayer() { if (null == c) { return; } - + PlayerSettingsDialog psd = new PlayerSettingsDialog(clientgui, c); if (psd.showDialog().isConfirmed()) { Player player = c.getLocalPlayer(); @@ -1539,16 +1538,16 @@ public void configPlayer() { // The deployment position int startPos = psd.getStartPos(); final GameOptions gOpts = clientgui.getClient().getGame().getOptions(); - + player.setStartingPos(startPos); player.setStartOffset(psd.getStartOffset()); player.setStartWidth(psd.getStartWidth()); c.sendPlayerInfo(); - - // If the gameoption set_arty_player_homeedge is set, adjust the player's offboard + + // If the gameoption set_arty_player_homeedge is set, adjust the player's offboard // arty units to be behind the newly selected home edge. OffBoardDirection direction = OffBoardDirection.translateStartPosition(startPos); - if (direction != OffBoardDirection.NONE && + if (direction != OffBoardDirection.NONE && gOpts.booleanOption(OptionsConstants.BASE_SET_ARTY_PLAYER_HOMEEDGE)) { for (Entity entity: c.getGame().getPlayerEntities(c.getLocalPlayer(), false)) { if (entity.getOffBoardDirection() != OffBoardDirection.NONE) { @@ -1566,7 +1565,7 @@ private void addUnit() { clientgui.getMechSelectorDialog().updateOptionValues(); clientgui.getMechSelectorDialog().setVisible(true); } - + private void createArmy() { clientgui.getRandomArmyDialog().setVisible(true); } @@ -1612,7 +1611,7 @@ public void gamePhaseChange(GamePhaseChangeEvent e) { if (isIgnoringEvents()) { return; } - + if (clientgui.getClient().getGame().getPhase().isLounge()) { refreshDoneButton(); refreshGameSettings(); @@ -1658,7 +1657,7 @@ public void gameSettingsChange(GameSettingsChangeEvent e) { public void gameClientFeedbackRequest(GameCFREvent evt) { // Do nothing } - + private ActionListener lobbyListener = new ActionListener() { @Override public void actionPerformed(ActionEvent ev) { @@ -1666,7 +1665,7 @@ public void actionPerformed(ActionEvent ev) { if (isIgnoringEvents()) { return; } - + if (ev.getSource().equals(butAdd)) { addUnit(); } else if (ev.getSource().equals(butArmy)) { @@ -1700,7 +1699,7 @@ public void actionPerformed(ActionEvent ev) { return; } clientgui.loadListFile(c.getLocalPlayer()); - + } else if (ev.getSource().equals(butSaveList)) { // Allow the player to save their current // list of entities to a file. @@ -1712,32 +1711,32 @@ public void actionPerformed(ActionEvent ev) { } clientgui.saveListFile(c.getGame().getPlayerEntities(c.getLocalPlayer(), false), c.getLocalPlayer().getName()); - + } else if (ev.getSource().equals(butAddBot)) { configAndCreateBot(null); - + } else if (ev.getSource().equals(butRemoveBot)) { removeBot(); - + } else if (ev.getSource().equals(butShowUnitID)) { PreferenceManager.getClientPreferences().setShowUnitId(butShowUnitID.isSelected()); mekModel.refreshCells(); repaint(); - + } else if (ev.getSource() == butConditions) { PlanetaryConditionsDialog pcd = new PlanetaryConditionsDialog(clientgui); boolean userOkay = pcd.showDialog(); if (userOkay) { clientgui.getClient().sendPlanetaryConditions(pcd.getConditions()); } - + } else if (ev.getSource() == butRandomMap) { RandomMapDialog rmd = new RandomMapDialog(clientgui.frame, ChatLounge.this, clientgui.getClient(), mapSettings); rmd.activateDialog(clientgui.getBoardView().getTilesetManager().getThemes()); - + } else if (ev.getSource().equals(butBoardPreview)) { previewGameBoard(); - + } else if (ev.getSource().equals(comMapSizes)) { if (Messages.getString("ChatLounge.CustomMapSize").equals(comMapSizes.getSelectedItem())) { refreshMapUI(); @@ -1747,67 +1746,67 @@ public void actionPerformed(ActionEvent ev) { resetAvailBoardSelection = true; resetSelectedBoards = true; clientgui.getClient().sendMapSettings(mapSettings); - } - + } + } else if (ev.getSource() == butGroundMap) { mapSettings.setMedium(MapSettings.MEDIUM_GROUND); refreshMapUI(); clientgui.getClient().sendMapSettings(mapSettings); - + } else if (ev.getSource() == butSpaceMap) { mapSettings.setMedium(MapSettings.MEDIUM_SPACE); mapSettings.setBoardSize(50, 50); mapSettings.setMapSize(1, 1); refreshMapUI(); clientgui.getClient().sendMapDimensions(mapSettings); - + } else if (ev.getSource() == butLowAtmoMap) { mapSettings.setMedium(MapSettings.MEDIUM_ATMOSPHERE); refreshMapUI(); clientgui.getClient().sendMapSettings(mapSettings); - + } else if (ev.getSource() == butAddX || ev.getSource() == butMapGrowW) { int newMapWidth = mapSettings.getMapWidth() + 1; mapSettings.setMapSize(newMapWidth, mapSettings.getMapHeight()); clientgui.getClient().sendMapDimensions(mapSettings); - + } else if (ev.getSource() == butAddY || ev.getSource() == butMapGrowH) { int newMapHeight = mapSettings.getMapHeight() + 1; mapSettings.setMapSize(mapSettings.getMapWidth(), newMapHeight); clientgui.getClient().sendMapDimensions(mapSettings); - + } else if (ev.getSource() == butSaveMapSetup) { saveMapSetup(); - + } else if (ev.getSource() == butLoadMapSetup) { loadMapSetup(); - + } else if (ev.getSource() == fldMapWidth) { setManualMapWidth(); - + } else if (ev.getSource() == fldMapHeight) { setManualMapHeight(); - + } else if (ev.getSource() == fldSpaceBoardWidth) { setManualBoardWidth(); - + } else if (ev.getSource() == fldSpaceBoardHeight) { setManualBoardHeight(); - + } else if (ev.getSource() == butMapShrinkW) { if (mapSettings.getMapWidth() > 1) { int newMapWidth = mapSettings.getMapWidth() - 1; mapSettings.setMapSize(newMapWidth, mapSettings.getMapHeight()); clientgui.getClient().sendMapDimensions(mapSettings); } - + } else if (ev.getSource() == butMapShrinkH) { if (mapSettings.getMapHeight() > 1) { int newMapHeight = mapSettings.getMapHeight() - 1; mapSettings.setMapSize(mapSettings.getMapWidth(), newMapHeight); clientgui.getClient().sendMapDimensions(mapSettings); } - + } else if (ev.getSource() == butDetach) { butDetach.setEnabled(false); panTeam.remove(panTeamOverview); @@ -1816,7 +1815,7 @@ public void actionPerformed(ActionEvent ev) { teamOverviewWindow.add(panTeamOverview); teamOverviewWindow.center(); teamOverviewWindow.setVisible(true); - + } else if (ev.getSource() == butCancelSearch) { fldSearch.setText(""); @@ -1826,7 +1825,7 @@ public void actionPerformed(ActionEvent ev) { Messages.getString("ChatLounge.map.title.mapAssemblyHelp"), true, true); final int height = 600; final int width = 600; - + final JEditorPane pane = new JEditorPane(); pane.setName(CL_KEY_NAMEHELPPANE); pane.setEditable(false); @@ -1853,40 +1852,40 @@ public void actionPerformed(ActionEvent ev) { Dimension sz = new Dimension(scaleForGUI(width), scaleForGUI(height)); dialog.setPreferredSize(sz); dialog.setVisible(true); - + } else if (ev.getSource() == butListView) { scrMekTable.setViewportView(mekTable); butCollapse.setEnabled(false); butExpand.setEnabled(false); - + } else if (ev.getSource() == butForceView) { scrMekTable.setViewportView(mekForceTree); butCollapse.setEnabled(true); butExpand.setEnabled(true); - + } else if (ev.getSource() == butCollapse) { collapseTree(); - + } else if (ev.getSource() == butExpand) { expandTree(); - } + } } }; - + /** Expands the Mek Force Tree fully. */ private void expandTree() { for (int i = 0; i < mekForceTree.getRowCount(); i++) { mekForceTree.expandRow(i); } } - + /** Collapses the Mek Force Tree fully. */ private void collapseTree() { for (int i = 0; i < mekForceTree.getRowCount(); i++) { mekForceTree.collapseRow(i); } } - + private void configAndCreateBot(@Nullable Player toReplace) { BehaviorSettings behavior = null; String botName = null; @@ -1899,24 +1898,24 @@ private void configAndCreateBot(@Nullable Player toReplace) { if (bcd.getResult() == DialogResult.CANCELLED) { return; } - Princess botClient = Princess.createPrincess(bcd.getBotName(), client().getHost(), + Princess botClient = Princess.createPrincess(bcd.getBotName(), client().getHost(), client().getPort(), bcd.getBehaviorSettings()); botClient.setClientGUI(clientgui); botClient.getGame().addGameListener(new BotGUI(getClientgui().getFrame(), botClient)); try { botClient.connect(); - clientgui.getBots().put(bcd.getBotName(), botClient); + clientgui.getLocalBots().put(bcd.getBotName(), botClient); } catch (Exception e) { clientgui.doAlertDialog(Messages.getString("ChatLounge.AlertBot.title"), Messages.getString("ChatLounge.AlertBot.message")); botClient.die(); } } - - /** + + /** * Opens a file chooser and saves the current map setup to the file, * if any was chosen. - * @see MapSetup + * @see MapSetup */ private void saveMapSetup() { JFileChooser fc = new JFileChooser(Configuration.dataDir() + CL_KEY_FILEPATH_MAPSETUP); @@ -1949,10 +1948,10 @@ private void saveMapSetup() { } } - /** + /** * Opens a file chooser and loads a new map setup from the file, * if any was chosen. - * @see MapSetup + * @see MapSetup */ private void loadMapSetup() { JFileChooser fc = new JFileChooser(Configuration.dataDir() + CL_KEY_FILEPATH_MAPSETUP); @@ -1982,29 +1981,29 @@ private void loadMapSetup() { LogManager.getLogger().error("", ex); } } - + private void removeBot() { Client c = getSelectedClient(); - if (!client().bots.containsValue(c)) { + if (!client().localBots.containsValue(c)) { LobbyErrors.showOnlyOwnBot(clientgui.frame); return; } // Delete units first, which safely disembarks and offloads them // Don't delete the bot's forces, as that could also delete other players' entitites c.die(); - clientgui.getBots().remove(c.getName()); + clientgui.getLocalBots().remove(c.getName()); } - + private void doBotSettings() { Player player = playerModel.getPlayerAt(tablePlayers.getSelectedRow()); - Princess bot = (Princess) clientgui.getBots().get(player.getName()); + Princess bot = (Princess) clientgui.getLocalBots().get(player.getName()); var bcd = new BotConfigDialog(clientgui.frame, bot.getLocalPlayer().getName(), bot.getBehaviorSettings(), clientgui); bcd.setVisible(true); if (bcd.getResult() == DialogResult.CONFIRMED) { bot.setBehaviorSettings(bcd.getBehaviorSettings()); } } - + // Put a filter on the files that the user can select the proper file. FileFilter XMLFileFilter = new FileFilter() { @Override @@ -2017,7 +2016,7 @@ public String getDescription() { return Messages.getString("ChatLounge.map.SetupXMLfiles"); } }; - + private void setManualMapWidth() { try { int newMapWidth = Integer.parseInt(fldMapWidth.getText()); @@ -2029,7 +2028,7 @@ private void setManualMapWidth() { // no number, no new map width } } - + private void setManualMapHeight() { try { int newMapHeight = Integer.parseInt(fldMapHeight.getText()); @@ -2041,7 +2040,7 @@ private void setManualMapHeight() { // no number, no new map height } } - + private void setManualBoardWidth() { try { int newBoardWidth = Integer.parseInt(fldSpaceBoardWidth.getText()); @@ -2053,7 +2052,7 @@ private void setManualBoardWidth() { // no number, no new board width } } - + private void setManualBoardHeight() { try { int newBoardHeight = Integer.parseInt(fldSpaceBoardHeight.getText()); @@ -2090,7 +2089,7 @@ private void refreshLabels() { txt += opts.intOption(OptionsConstants.ALLOWED_YEAR); lblGameYear.setText(txt); lblGameYear.setToolTipText(scaleStringForGUI(Messages.getString("ChatLounge.tooltip.techYear"))); - + String tlString = TechConstants.getLevelDisplayableName(TechConstants.T_TECH_UNKNOWN); IOption tlOpt = opts.getOption(OptionsConstants.ALLOWED_TECHLEVEL); if (tlOpt != null) { @@ -2098,9 +2097,9 @@ private void refreshLabels() { } lblTechLevel.setText(Messages.getString("ChatLounge.TechLevel") + tlString); lblTechLevel.setToolTipText(scaleStringForGUI(Messages.getString("ChatLounge.tooltip.techYear"))); - + txt = Messages.getString("ChatLounge.MapSummary"); - txt += (mapSettings.getBoardWidth() * mapSettings.getMapWidth()) + " x " + txt += (mapSettings.getBoardWidth() * mapSettings.getMapWidth()) + " x " + (mapSettings.getBoardHeight() * mapSettings.getMapHeight()); if (butGroundMap.isSelected()) { txt += Messages.getString("ChatLounge.name.groundMap"); @@ -2120,17 +2119,17 @@ private void refreshLabels() { } else { selectedMaps.append(map); } - selectedMaps.append("
"); + selectedMaps.append("
"); } lblMapSummary.setToolTipText(scaleStringForGUI(selectedMaps.toString())); } - + @Override public void ready() { final Client client = clientgui.getClient(); final Game game = client.getGame(); final GameOptions gOpts = game.getOptions(); - + // enforce exclusive deployment zones in double blind for (Player player: client.getGame().getPlayersVector()) { if (!isValidStartPos(game, player)) { @@ -2148,7 +2147,7 @@ public void ready() { players.add(client.getLocalPlayer().getName()); } - for (Client bc : clientgui.getBots().values()) { + for (Client bc : clientgui.getLocalBots().values()) { if ((game.getLiveCommandersOwnedBy(bc.getLocalPlayer()) < 1) && (game.getEntitiesOwnedBy(bc.getLocalPlayer()) > 0)) { players.add(bc.getLocalPlayer().getName()); @@ -2168,7 +2167,7 @@ public void ready() { boolean done = !localPlayer().isDone(); client.sendDone(done); refreshDoneButton(done); - for (Client botClient : clientgui.getBots().values()) { + for (Client botClient : clientgui.getLocalBots().values()) { botClient.sendDone(done); } } @@ -2181,8 +2180,8 @@ Client getSelectedClient() { Player player = playerModel.getPlayerAt(tablePlayers.getSelectedRow()); if (localPlayer().equals(player)) { return client(); - } else if (client().bots.containsKey(player.getName())) { - return client().bots.get(player.getName()); + } else if (client().localBots.containsKey(player.getName())) { + return client().localBots.get(player.getName()); } else { return null; } @@ -2198,26 +2197,26 @@ public void removeAllListeners() { GUIP.removePreferenceChangeListener(this); PreferenceManager.getClientPreferences().removePreferenceChangeListener(this); MechSummaryCache.getInstance().removeListener(mechSummaryCacheListener); - + if (loader != null) { loader.cancel(true); } - + tablePlayers.getSelectionModel().removeListSelectionListener(this); tablePlayers.removeMouseListener(new PlayerTableMouseAdapter()); - + lisBoardsAvailable.removeListSelectionListener(this); lisBoardsAvailable.removeMouseListener(mapListMouseListener); lisBoardsAvailable.removeMouseMotionListener(mapListMouseListener); teamOverviewWindow.removeWindowListener(teamOverviewWindowListener); - + mekTable.removeMouseListener(mekTableMouseAdapter); mekForceTree.removeMouseListener(mekForceTreeMouseListener); mekTable.getTableHeader().removeMouseListener(mekTableHeaderMouseListener); mekTable.removeKeyListener(mekTableKeyListener); mekForceTree.removeKeyListener(mekTreeKeyListener); - + butAdd.removeActionListener(lobbyListener); butAddBot.removeActionListener(lobbyListener); butArmy.removeActionListener(lobbyListener); @@ -2255,14 +2254,14 @@ public void removeAllListeners() { butForceView.removeActionListener(lobbyListener); butCollapse.removeActionListener(lobbyListener); butExpand.removeActionListener(lobbyListener); - + fldMapWidth.removeActionListener(lobbyListener); fldMapHeight.removeActionListener(lobbyListener); fldSpaceBoardWidth.removeActionListener(lobbyListener); fldSpaceBoardHeight.removeActionListener(lobbyListener); - + comboTeam.removeActionListener(lobbyListener); - + KeyboardFocusManager kbfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); kbfm.removeKeyEventDispatcher(lobbyKeyDispatcher); } @@ -2271,14 +2270,14 @@ public void removeAllListeners() { * Returns true if the given list of entities can be configured as a group. * This requires that they all have the same owner, and that none of the * units are being transported. Also, the owner must be the player or one - * of his bots. + * of his bots. */ boolean canConfigureMultipleDeployment(Collection entities) { - return haveSingleOwner(entities) + return haveSingleOwner(entities) && !containsTransportedUnit(entities) && canEditAny(entities); } - + /** * Returns true if the given collection contains at least one entity * that the local player can edit, i.e. is his own or belongs to @@ -2289,7 +2288,7 @@ boolean canConfigureMultipleDeployment(Collection entities) { boolean canEditAny(Collection entities) { return entities.stream().anyMatch(this::isEditable); } - + /** * Returns true if the local player can see all the given entities. * This is true except when a blind drop option is active and one or more @@ -2316,23 +2315,23 @@ boolean canSeeAll(Collection entities) { boolean canSee(Entity entity) { return canSeeAll(Arrays.asList(entity)); } - + boolean entityInLocalTeam(Entity entity) { return !localPlayer().isEnemyOf(entity.getOwner()); } - + @Override public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) { return; } - + if (event.getSource().equals(tablePlayers.getSelectionModel())) { refreshPlayerConfig(); } } - + /** Adapts the enabled state of the player config UI items to the player selection. */ private void refreshPlayerConfig() { var selPlayers = getselectedPlayers(); @@ -2355,18 +2354,18 @@ private void refreshPlayerConfig() { setTeamSelectedItem(selPlayer.getTeam()); } } - + /** Sets (without firing events) the team combobox. */ private void setTeamSelectedItem(int team) { comboTeam.removeActionListener(lobbyListener); comboTeam.setSelectedIndex(team); comboTeam.addActionListener(lobbyListener); } - - /** - * Returns false when any blind-drop option is active and player is not on the local team; - * true otherwise. When true, individual units of the given player should not be shown/saved/etc. - */ + + /** + * Returns false when any blind-drop option is active and player is not on the local team; + * true otherwise. When true, individual units of the given player should not be shown/saved/etc. + */ private boolean unitsVisible(Player player) { GameOptions opts = clientgui.getClient().getGame().getOptions(); boolean isBlindDrop = opts.booleanOption(OptionsConstants.BASE_BLIND_DROP) @@ -2383,14 +2382,14 @@ public void mouseClicked(MouseEvent e) { Player player = playerModel.getPlayerAt(row); if (player != null) { boolean isLocalPlayer = player.equals(localPlayer()); - boolean isLocalBot = clientgui.getBots().get(player.getName()) != null; + boolean isLocalBot = clientgui.getLocalBots().get(player.getName()) != null; if ((isLocalPlayer || isLocalBot)) { configPlayer(); } } } } - + @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { @@ -2409,12 +2408,12 @@ private void showPopup(MouseEvent e) { if (tablePlayers.getSelectedRowCount() == 0) { return; } - ScalingPopup popup = PlayerTablePopup.playerTablePopup(clientgui, + ScalingPopup popup = PlayerTablePopup.playerTablePopup(clientgui, playerTableActionListener, getselectedPlayers()); popup.show(e.getComponent(), e.getX(), e.getY()); } } - + private ActionListener playerTableActionListener = evt -> { if (tablePlayers.getSelectedRowCount() == 0) { return; @@ -2460,7 +2459,7 @@ private void showPopup(MouseEvent e) { }; private ArrayList getselectedPlayers() { - var result = new ArrayList(); + var result = new ArrayList(); for (int row: tablePlayers.getSelectedRows()) { Player player = playerModel.getPlayerAt(row); if (player != null) { @@ -2494,7 +2493,7 @@ public void keyPressed(KeyEvent evt) { } } }; - + /** Copies the selected units, if any, from the displayed Unit Table / Force Tree to the clipboard. */ public void copyToClipboard() { List entities = isForceView() ? getTreeSelectedEntities() : getSelectedEntities(); @@ -2502,7 +2501,7 @@ public void copyToClipboard() { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(stringSelection, null); } - + /** Reads the clipboard and adds units, if it can parse them. */ public void importClipboard() { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); @@ -2546,7 +2545,7 @@ public void importClipboard() { } } } - + /** * @return a String representing the entities to export to the clipboard. */ @@ -2572,7 +2571,7 @@ private String clipboardString(Collection entities) { } return result.toString(); } - + /** Returns a list of entities selected in the ForceTree. May be empty, but not null. */ private List getTreeSelectedEntities() { TreePath[] selection = mekForceTree.getSelectionPaths(); @@ -2583,13 +2582,13 @@ private List getTreeSelectedEntities() { Object selected = path.getLastPathComponent(); if (selected instanceof Entity) { entities.add((Entity) selected); - } + } } } } return entities; } - + /** Returns a list of forces selected in the ForceTree. May be empty, but not null. */ private List getTreeSelectedForces() { TreePath[] selection = mekForceTree.getSelectionPaths(); @@ -2600,13 +2599,13 @@ private List getTreeSelectedForces() { Object selected = path.getLastPathComponent(); if (selected instanceof Force) { selForces.add((Force) selected); - } + } } } } return selForces; } - + /** The key listener for the Force Tree. */ KeyListener mekTreeKeyListener = new KeyAdapter() { @@ -2616,35 +2615,35 @@ public void keyPressed(KeyEvent e) { List selForces = getTreeSelectedForces(); boolean onlyOneEntity = (selEntities.size() == 1) && selForces.isEmpty(); int code = e.getKeyCode(); - + if (code == KeyEvent.VK_SPACE) { e.consume(); mechReadoutAction(selEntities, canSeeAll(selEntities), false, getClientgui().getFrame()); - + } else if (code == KeyEvent.VK_ENTER && onlyOneEntity) { e.consume(); lobbyActions.customizeMech(selEntities.get(0)); - + } else if (code == KeyEvent.VK_UP && e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK) { e.consume(); lobbyActions.forceMove(selForces, selEntities, true); - + } else if (code == KeyEvent.VK_DOWN && e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK) { e.consume(); lobbyActions.forceMove(selForces, selEntities, false); - + } else if ((code == KeyEvent.VK_DELETE) || (code == KeyEvent.VK_BACK_SPACE)) { e.consume(); lobbyActions.delete(selForces, selEntities, true); - + } else if (code == KeyEvent.VK_RIGHT && e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK) { e.consume(); expandTree(); - + } else if (code == KeyEvent.VK_LEFT && e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK) { e.consume(); collapseTree(); - + } } }; @@ -2717,9 +2716,9 @@ private void showPopup(MouseEvent e) { popup.show(e.getComponent(), e.getX() + MAP_POPUP_OFFSET, e.getY() + MAP_POPUP_OFFSET); } } - + public class MekForceTreeMouseAdapter extends MouseInputAdapter { - + @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { @@ -2750,7 +2749,7 @@ private void showPopup(MouseEvent e) { TreePath[] selection = mekForceTree.getSelectionPaths(); List entities = new ArrayList<>(); List selForces = new ArrayList<>(); - + if (selection != null) { for (TreePath path: selection) { if (path != null) { @@ -2759,7 +2758,7 @@ private void showPopup(MouseEvent e) { entities.add((Entity) selected); } else if (selected instanceof Force) { selForces.add((Force) selected); - } + } } } } @@ -2817,7 +2816,7 @@ private void showPopup(MouseEvent e) { popup.show(e.getComponent(), e.getX(), e.getY()); } } - + /** Refreshes the Mek Tree, restoring expansion state and selection. */ private void refreshTree() { // Refresh the force tree and restore selection/expand status @@ -2830,7 +2829,7 @@ private void refreshTree() { } } } - + Forces forces = game().getForces(); List expandedForces = new ArrayList<>(); for (int i = 0; i < mekForceTree.getRowCount(); i++) { @@ -2842,7 +2841,7 @@ private void refreshTree() { } } } - + mekForceTree.setUI(null); try { mekForceTreeModel.refreshData(); @@ -2862,12 +2861,12 @@ private void refreshTree() { } } - - /** + + /** * Returns a TreePath in the force tree for a possibly outdated entity * or force. Outdated means a new object of the type was sent by the server - * and has replaced this object. Also works for the game's current objects though. - * Uses the force's/entity's id to get the + * and has replaced this object. Also works for the game's current objects though. + * Uses the force's/entity's id to get the * game's real object with the same id. Used to reconstruct the selection * and expansion state of the force tree after an update. */ @@ -2904,28 +2903,28 @@ private TreePath getPath(Object outdatedEntry) { throw new IllegalArgumentException(Messages.getString("ChatLounge.TreePath.methodRequiresEntityForce")); } } - - /** + + /** * Returns a Collection that contains only those of the given entities - * that the local player can affect, i.e. his units or those of his bots. + * that the local player can affect, i.e. his units or those of his bots. * The returned Collection is a new Collection and can be safely altered. * (The entities are not copies of course.) - *

See also {@link #isEditable(Entity)} + *

See also {@link #isEditable(Entity)} */ private Set editableEntities(Collection entities) { return entities.stream().filter(this::isEditable).collect(Collectors.toSet()); } - - - /** - * Returns true if the given carrier and carried can be edited to have the - * carrier transport the given carried entity. That is the case when they - * are teammates and one of the entities can be edited by the local player. + + + /** + * Returns true if the given carrier and carried can be edited to have the + * carrier transport the given carried entity. That is the case when they + * are teammates and one of the entities can be edited by the local player. * Note: this method does NOT check if the loading is rules-valid. *

See also {@link #isEditable(Entity)} */ private boolean isLoadable(Entity carried, Entity carrier) { - return !carrier.getOwner().isEnemyOf(carried.getOwner()) + return !carrier.getOwner().isEnemyOf(carried.getOwner()) && (isEditable(carrier) || isEditable(carried)); } @@ -2947,14 +2946,14 @@ public void preferenceChange(PreferenceChangeEvent e) { break; } } - + /** Silently adapts the state of the "Show IDs" button to the Client prefs. */ private void setButUnitIDState() { butShowUnitID.removeActionListener(lobbyListener); butShowUnitID.setSelected(PreferenceManager.getClientPreferences().getShowUnitId()); butShowUnitID.addActionListener(lobbyListener); } - + /** Sets the row height of the MekTable according to compact mode and GUI scale */ private void setTableRowHeights() { int rowbaseHeight = butCompact.isSelected() ? MEKTABLE_ROWHEIGHT_COMPACT : MEKTABLE_ROWHEIGHT_FULL; @@ -2976,7 +2975,7 @@ private void updateTableHeaders() { if (activeSorter.getColumnIndex() == i) { headerText += "   " + guiScaledFontHTML(uiGray()); if (activeSorter.getSortingDirection() == MekTableSorter.Sorting.ASCENDING) { - headerText += "\u25B4 "; + headerText += "\u25B4 "; } else { headerText += "\u25BE "; } @@ -2985,7 +2984,7 @@ private void updateTableHeaders() { tabCol.setHeaderValue(headerText); } header.repaint(); - + // The player table header = tablePlayers.getTableHeader(); colMod = header.getColumnModel(); @@ -3000,7 +2999,7 @@ private void updateTableHeaders() { private Player ownerOf(Entity entity) { return clientgui.getClient().getGame().getPlayer(entity.getOwnerId()); } - + /** Sets the column width of the given table column of the MekTable with the value stored in the GUIP. */ private void setColumnWidth(TableColumn column) { String key; @@ -3017,7 +3016,7 @@ private void setColumnWidth(TableColumn column) { } column.setPreferredWidth(GUIP.getInt(key)); } - + /** Adapts the whole Lobby UI (both panels) to the current guiScale. */ private void adaptToGUIScale() { updateTableHeaders(); @@ -3040,10 +3039,10 @@ private void adaptToGUIScale() { String searchTip = Messages.getString("ChatLounge.map.searchTip") + "
"; searchTip += autoTagHTMLTable(); fldSearch.setToolTipText(UIUtil.scaleStringForGUI(searchTip)); - + ((TitledBorder) panUnitInfo.getBorder()).setTitleFont(scaledFont); ((TitledBorder) panPlayerInfo.getBorder()).setTitleFont(scaledFont); - + int scaledBorder = UIUtil.scaleForGUI(TEAMOVERVIEW_BORDER); panTeam.setBorder(new EmptyBorder(scaledBorder, scaledBorder, scaledBorder, scaledBorder)); @@ -3059,11 +3058,11 @@ private void adaptToGUIScale() { Point locationOnScreen = MouseInfo.getPointerInfo().getLocation(); Point locationOnComponent = new Point(locationOnScreen); SwingUtilities.convertPointFromScreen(locationOnComponent, mekTable); - MouseEvent event = new MouseEvent(mekTable, -1, time, 0, + MouseEvent event = new MouseEvent(mekTable, -1, time, 0, locationOnComponent.x, locationOnComponent.y, 0, 0, 1, false, 0); manager.mouseMoved(event); } - + private String autoTagHTMLTable() { String result = ""+ UIUtil.guiScaledFontHTML(); int colCount = 0; @@ -3085,11 +3084,11 @@ private String autoTagHTMLTable() { result += "
"; return result; } - - - /** + + + /** * Mouse Listener for the table header of the Mek Table. - * Saves column widths of the Mek Table when the mouse button is released. + * Saves column widths of the Mek Table when the mouse button is released. * Also switches between table sorting types */ MouseListener mekTableHeaderMouseListener = new MouseAdapter() { @@ -3111,10 +3110,10 @@ private void changeSorter(MouseEvent e) { } GUIP.setValue(key, column.getWidth()); } - + changeMekTableSorter(e); } - + @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { @@ -3124,7 +3123,7 @@ public void mouseReleased(MouseEvent e) { changeSorter(e); } } - + private void sorterPopup(MouseEvent e) { ScalingPopup popup = new ScalingPopup(); GameOptions opts = clientgui.getClient().getGame().getOptions(); @@ -3140,16 +3139,16 @@ private void sorterPopup(MouseEvent e) { popup.show(e.getComponent(), e.getX(), e.getY()); } }; - + /** - * Sets the sorting used in the Mek Table depending on the column header - * that was clicked. - */ + * Sets the sorting used in the Mek Table depending on the column header + * that was clicked. + */ private void changeMekTableSorter(MouseEvent e) { int col = mekTable.columnAtPoint(e.getPoint()); MekTableSorter previousSorter = activeSorter; List sorters; - + // find the right list of sorters (or do nothing, if the column is not sortable) if (col == MekTableModel.COL_UNIT) { sorters = unitSorters; @@ -3158,7 +3157,7 @@ private void changeMekTableSorter(MouseEvent e) { } else { return; } - + // Select the next allowed sorter and refresh the display if the sorter was changed nextSorter(sorters); if (activeSorter != previousSorter) { @@ -3166,7 +3165,7 @@ private void changeMekTableSorter(MouseEvent e) { updateTableHeaders(); } } - + /** Selects the next allowed sorter in the given list of sorters. */ private void nextSorter(List sorters) { // Set the next sorter as active, if this column was already sorted, or @@ -3178,7 +3177,7 @@ private void nextSorter(List sorters) { index = (index + 1) % sorters.size(); activeSorter = sorters.get(index); } - + // Find an allowed sorter (e.g. blind drop may prohibit some) int counter = 0; // Endless loop safeguard while (!activeSorter.isAllowed(clientgui.getClient().getGame().getOptions()) @@ -3188,14 +3187,14 @@ private void nextSorter(List sorters) { } } - /** Returns true when the compact view is active. */ + /** Returns true when the compact view is active. */ public boolean isCompact() { return butCompact.isSelected(); } - - /** - * Returns a list of the selected entities in the Mek table. - * The list may be empty but not null. + + /** + * Returns a list of the selected entities in the Mek table. + * The list may be empty but not null. */ private List getSelectedEntities() { ArrayList result = new ArrayList<>(); @@ -3208,15 +3207,15 @@ private List getSelectedEntities() { } return result; } - + /** Helper method to shorten calls. */ Player localPlayer() { return clientgui.getClient().getLocalPlayer(); } - + private void redrawMapTable(Image image) { if (image != null) { - if (lisBoardsAvailable.getFixedCellHeight() != image.getHeight(null) + if (lisBoardsAvailable.getFixedCellHeight() != image.getHeight(null) || lisBoardsAvailable.getFixedCellWidth() != image.getWidth(null)) { lisBoardsAvailable.setFixedCellHeight(image.getHeight(null)); lisBoardsAvailable.setFixedCellWidth(image.getWidth(null)); @@ -3238,7 +3237,7 @@ private synchronized void add(String name) { } } } - + private Image prepareImage(String boardName) { File boardFile = new MegaMekFile(Configuration.boardsDir(), boardName + CL_KEY_FILEEXTENTION_BOARD).getFile(); Board board; @@ -3319,17 +3318,17 @@ protected Void doInBackground() throws Exception { } Map mapIcons = new HashMap<>(); - + /** A renderer for the list of available boards. */ public class BoardNameRenderer extends DefaultListCellRenderer { private static final long serialVersionUID = -3218595828938299222L; - + private float oldGUIScale = GUIP.getGUIScale(); private Image image; private ImageIcon icon; - + @Override - public Component getListCellRendererComponent(JList list, Object value, + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String board = (String) value; @@ -3337,7 +3336,7 @@ public Component getListCellRendererComponent(JList list, Object value, if (board.startsWith(MapSettings.BOARD_GENERATED)) { board += mapSettings.getBoardSize(); } - + // If the gui scaling has changed, clear out all images, triggering a reload float currentGUIScale = GUIP.getGUIScale(); if (currentGUIScale != oldGUIScale) { @@ -3347,7 +3346,7 @@ public Component getListCellRendererComponent(JList list, Object value, baseImages.clear(); } } - + // If an icon is present for the current board, use it icon = mapIcons.get(board); if (icon != null) { @@ -3366,17 +3365,17 @@ public Component getListCellRendererComponent(JList list, Object value, } else { // There is a base image: make it into an icon, store it and use it if (!lisBoardsAvailable.isEnabled()) { - ImageFilter filter = new GrayFilter(true, 50); - ImageProducer producer = new FilteredImageSource(image.getSource(), filter); - image = Toolkit.getDefaultToolkit().createImage(producer); + ImageFilter filter = new GrayFilter(true, 50); + ImageProducer producer = new FilteredImageSource(image.getSource(), filter); + image = Toolkit.getDefaultToolkit().createImage(producer); } icon = new ImageIcon(image); - + mapIcons.put(board, icon); setIcon(icon); } } - + // Found or created an icon; finish the panel setText(""); if (lisBoardsAvailable.isEnabled()) { @@ -3384,7 +3383,7 @@ public Component getListCellRendererComponent(JList list, Object value, } else { setToolTipText(null); } - + if (isSelected) { setForeground(list.getSelectionForeground()); setBackground(list.getSelectionBackground()); @@ -3392,14 +3391,14 @@ public Component getListCellRendererComponent(JList list, Object value, setForeground(list.getForeground()); setBackground(list.getBackground()); } - + return this; } } private class MekTable extends JTable { private static final long serialVersionUID = -4054214297803021212L; - + public MekTable(MekTableModel mekModel) { super(mekModel); } @@ -3433,10 +3432,10 @@ void updateMapButtons() { Dimension size = maxMapButtonSize(); for (MapPreviewButton button: mapButtons) { button.setPreviewSize(size); - } + } } } - + void updateMapButtons(Dimension size) { if (!currentMapButtonSize.equals(size)) { currentMapButtonSize = size; @@ -3462,8 +3461,8 @@ Dimension optMapButtonSize(Image image) { int h = (int) (factor * image.getHeight(null)); return new Dimension(w, h); } - - /** + + /** * Returns true when the string boardName contains an invalid board. boardName may * denote a generated board (which is never invalid) or a surprise board * with several actual board names attached which will return true when at least @@ -3472,18 +3471,18 @@ Dimension optMapButtonSize(Image image) { boolean hasInvalidBoard(String boardName) { return hasSpecialBoard(boardName, invalidBoards); } - - /** - * Returns true when the string boardName contains a board that isn't present on - * the client (only on the server). boardName may denote a generated board - * (which is never serverside) or a surprise board with several actual board names + + /** + * Returns true when the string boardName contains a board that isn't present on + * the client (only on the server). boardName may denote a generated board + * (which is never serverside) or a surprise board with several actual board names * attached which will return true when at least one of the boards is serverside. */ boolean hasServerSideBoard(String boardName) { return hasSpecialBoard(boardName, serverBoards); } - - /** + + /** * Returns true when boardName (if a single board) or any of the boards contained * in boardName (if a surprise board list) is contained in the provided list. Returns * false for generated boards. @@ -3498,8 +3497,8 @@ private boolean hasSpecialBoard(String boardName, Collection list) { } } - /** - * Returns a tooltip for the provided boardName that may be a single board or + /** + * Returns a tooltip for the provided boardName that may be a single board or * a generated or surprise board. Adds info for serverside or invalid boards. */ String createBoardTooltip(String boardName) { @@ -3523,7 +3522,7 @@ String createBoardTooltip(String boardName) { return result; } - + ActionListener mekTableHeaderAListener = e -> { MekTableSorter previousSorter = activeSorter; for (MekTableSorter sorter: union(unitSorters, bvSorters)) { @@ -3537,20 +3536,20 @@ String createBoardTooltip(String boardName) { updateTableHeaders(); } }; - + Game game() { return clientgui.getClient().getGame(); } - + /** Convenience for clientgui.getClient() */ Client client() { return clientgui.getClient(); } - + boolean isForceView() { return butForceView.isSelected(); } - + public void killPreviewBV() { if (previewBV != null) { previewBV.die(); diff --git a/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java b/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java index 166048d0b36..6d76e2c7ca6 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java +++ b/megamek/src/megamek/client/ui/swing/lobby/LobbyActions.java @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with MegaMek. If not, see . - */ + */ package megamek.client.ui.swing.lobby; import megamek.client.Client; @@ -66,7 +66,7 @@ public class LobbyActions { lobby = cl; } - /** Sets a deployment round for the given entities. Sends an update to the server. */ + /** Sets a deployment round for the given entities. Sends an update to the server. */ void applyDeployment(Collection entities, int newRound) { if (!validateUpdate(entities)) { return; @@ -138,9 +138,9 @@ void applyProne(Collection entities, String info) { } sendUpdates(updateCandidates); } - + /** - * Attaches the given force as a subforce to the given new parent. + * Attaches the given force as a subforce to the given new parent. * Does NOT work for newParentId == NO_FORCE. Use promoteForce to do this. * Does not allow attaching a force to one of its own subforces. */ @@ -150,13 +150,13 @@ void forceAttach(int forceId, int newParentId) { || (forceId == newParentId)) { return; } - + Force force = forces.getForce(forceId); Force newParent = forces.getForce(newParentId); List subForces = forces.getFullSubForces(force); Player owner = forces.getOwner(force); Player newParentOwner = forces.getOwner(newParent); - + if (owner.isEnemyOf(newParentOwner)) { LobbyErrors.showOnlyTeam(frame()); return; @@ -172,10 +172,10 @@ void forceAttach(int forceId, int newParentId) { var forceList = new ArrayList<>(List.of(force)); client().sendForceParent(forceList, newParentId); } - + /** - * Makes the given forces top-level, detaching them from any former parents. + * Makes the given forces top-level, detaching them from any former parents. */ void forcePromote(Collection forceIds) { var forces = game().getForces(); @@ -204,10 +204,10 @@ void configureDamage(Collection entities) { med.setVisible(true); sendUpdates(entities); } - - /** + + /** * Moves a force or entity within another force by one position. If up is true, - * moves upward, otherwise downward. + * moves upward, otherwise downward. */ void forceMove(Collection forceList, Collection entityList, boolean up) { // May only move a single force or a single entity @@ -223,7 +223,7 @@ void forceMove(Collection forceList, Collection entityList, boole return; } var forces = game().getForces(); - var changedForce = new HashSet(); + var changedForce = new HashSet(); if (up) { if (!forceList.isEmpty()) { changedForce.addAll(forces.moveUp(CollectionUtil.anyOneElement(forceList))); @@ -242,10 +242,10 @@ void forceMove(Collection forceList, Collection entityList, boole client().sendUpdateForce(changedForce); } } - - /** - * Displays a CamoChooser to choose an individual camo for the given entities. - * The camo will only be applied to units configurable by the local player, + + /** + * Displays a CamoChooser to choose an individual camo for the given entities. + * The camo will only be applied to units configurable by the local player, * i.e. his own units or those of his bots. */ public void individualCamo(Collection entities) { @@ -288,10 +288,10 @@ public void customizeMechs(Collection entities) { String ownerName = randomSelected.getOwner().getName(); int ownerId = randomSelected.getOwner().getId(); - boolean editable = client().bots.get(ownerName) != null; + boolean editable = client().localBots.get(ownerName) != null; Client client; if (editable) { - client = client().bots.get(ownerName); + client = client().localBots.get(ownerName); } else { editable |= ownerId == localPlayer().getId(); client = client(); @@ -300,7 +300,7 @@ public void customizeMechs(Collection entities) { CustomMechDialog cmd = new CustomMechDialog(lobby.getClientgui(), client, new ArrayList<>(entities), editable); cmd.setSize(new Dimension(GUIPreferences.getInstance().getCustomUnitWidth(), GUIPreferences.getInstance().getCustomUnitHeight())); - cmd.setTitle(Messages.getString("ChatLounge.CustomizeUnits")); + cmd.setTitle(Messages.getString("ChatLounge.CustomizeUnits")); cmd.setVisible(true); GUIPreferences.getInstance().setCustomUnitHeight(cmd.getSize().height); GUIPreferences.getInstance().setCustomUnitWidth(cmd.getSize().width); @@ -347,10 +347,10 @@ public void customizeMech(Entity entity) { if (!validateUpdate(Arrays.asList(entity))) { return; } - boolean editable = client().bots.get(entity.getOwner().getName()) != null; + boolean editable = client().localBots.get(entity.getOwner().getName()) != null; Client c; if (editable) { - c = client().bots.get(entity.getOwner().getName()); + c = client().localBots.get(entity.getOwner().getName()); } else { editable |= entity.getOwnerId() == localPlayer().getId(); c = client(); @@ -429,9 +429,9 @@ public void customizeMech(Entity entity) { } } - /** + /** * Sets random skills for the given entities, as far as they can - * be configured by the local player. + * be configured by the local player. */ void setRandomSkills(Collection entities) { if (!validateUpdate(entities)) { @@ -444,9 +444,9 @@ void setRandomSkills(Collection entities) { sendUpdates(entities); } - /** + /** * Sets random names for the given entities' pilots, as far as they can - * be configured by the local player. + * be configured by the local player. */ void setRandomNames(Collection entities) { if (!validateUpdate(entities)) { @@ -462,9 +462,9 @@ void setRandomNames(Collection entities) { sendUpdates(entities); } - /** + /** * Sets random callsigns for the given entities' pilots, as far as they can - * be configured by the local player. + * be configured by the local player. */ void setRandomCallsigns(Collection entities) { if (!validateUpdate(entities)) { @@ -477,9 +477,9 @@ void setRandomCallsigns(Collection entities) { } sendUpdates(entities); } - + /** - * Asks for a name and creates a new top-level force of that name. + * Asks for a name and creates a new top-level force of that name. */ void forceCreateEmpty() { // Ask for a name @@ -489,10 +489,10 @@ void forceCreateEmpty() { } client().sendAddForce(Force.createToplevelForce(name, localPlayer()), new ArrayList<>()); } - + /** - * Asks for a name and creates a new top-level force of that name with the - * selected entities in it. + * Asks for a name and creates a new top-level force of that name with the + * selected entities in it. */ void forceCreateFrom(Collection entities) { if (!validateUpdate(entities)) { @@ -509,10 +509,10 @@ void forceCreateFrom(Collection entities) { } client().sendAddForce(Force.createToplevelForce(name, CollectionUtil.anyOneElement(entities).getOwner()), entities); } - + /** * Asks for a name and creates a new subforce of that name for the force given - * as the parentId. + * as the parentId. */ void forceCreateSub(int parentId) { // Ask for a name @@ -522,7 +522,7 @@ void forceCreateSub(int parentId) { } client().sendAddForce(Force.createSubforce(name, game().getForces().getForce(parentId)), new ArrayList<>()); } - + /** * Toggles burst MG fire for the given entities to the state given as burstOn */ @@ -541,17 +541,17 @@ void toggleBurstMg(Collection entities, boolean burstOn) { } sendUpdates(updateCandidates); } - + /** Adds the given entities as strategic targets for the given local bot. */ void setPrioTarget(String botName, Collection entities) { - Map bots = lobby.getClientgui().getBots(); + Map bots = lobby.getClientgui().getLocalBots(); if (!bots.containsKey(botName) || !(bots.get(botName) instanceof Princess)) { return; } BehaviorSettings behavior = ((Princess) bots.get(botName)).getBehaviorSettings(); entities.forEach(e -> behavior.addPriorityUnit(e.getId())); } - + /** * Toggles hot loading LRMs for the given entities to the state given as hotLoadOn */ @@ -561,7 +561,7 @@ void toggleHotLoad(Collection entities, boolean hotLoadOn) { } Set updateCandidates = new HashSet<>(); for (Entity entity: entities) { - for (Mounted m: entity.getAmmo()) { + for (Mounted m: entity.getAmmo()) { // setHotLoad checks the Ammo to see if it can be hotloaded m.setHotLoad(hotLoadOn); // TODO: The following should ideally be part of setHotLoad in Mounted @@ -575,7 +575,7 @@ void toggleHotLoad(Collection entities, boolean hotLoadOn) { } sendUpdates(updateCandidates); } - + public void load(Collection selEntities, String info) { StringTokenizer stLoad = new StringTokenizer(info, ":"); int loaderId = Integer.parseInt(stLoad.nextToken()); @@ -587,7 +587,7 @@ public void load(Collection selEntities, String info) { if (entities.isEmpty()) { return; } - + // If a unit of the selected units is currently loaded onto another, 2nd unit of the selected // units, do not continue. The player should unload units first. This would require // a server update offloading that second unit AND embarking it. Currently not possible @@ -599,7 +599,7 @@ public void load(Collection selEntities, String info) { LobbyErrors.showNoDualLoad(frame()); return; } - + boolean loadRear = false; if (stLoad.hasMoreTokens()) { loadRear = Boolean.parseBoolean(stLoad.nextToken()); @@ -607,7 +607,7 @@ public void load(Collection selEntities, String info) { StringBuilder errorMsg = new StringBuilder(); if (!LobbyUtility.validateLobbyLoad(entities, loader, bayNumber, loadRear, errorMsg)) { - JOptionPane.showMessageDialog(frame(), errorMsg.toString(), + JOptionPane.showMessageDialog(frame(), errorMsg.toString(), Messages.getString("LoadingBay.error"), JOptionPane.ERROR_MESSAGE); return; } @@ -624,14 +624,14 @@ public void load(Collection selEntities, String info) { lobby.loadOnto(e, loaderId, bayNumber); } } - + /** Asks for a new name for the provided forceId and applies it. */ void forceRename(int forceId) { Forces forces = game().getForces(); if (!forces.contains(forceId)) { return; } - Force force = forces.getForce(forceId); + Force force = forces.getForce(forceId); if (!isEditable(force)) { LobbyErrors.showCannotConfigEnemies(frame()); return; @@ -645,13 +645,13 @@ void forceRename(int forceId) { var forceList = new ArrayList<>(List.of(force)); // must be mutable client().sendUpdateForce(forceList); } - + /** - * Deletes the given forces and entities. Asks for confirmation if confirm is true. + * Deletes the given forces and entities. Asks for confirmation if confirm is true. */ void delete(Collection foDelete, Collection enDelete, boolean confirm) { Forces forces = game().getForces(); - // Remove redundant forces = subforces of other forces in the list + // Remove redundant forces = subforces of other forces in the list Set allSubForces = new HashSet<>(); foDelete.forEach(f -> allSubForces.addAll(forces.getFullSubForces(f))); foDelete.removeIf(allSubForces::contains); @@ -661,7 +661,7 @@ void delete(Collection foDelete, Collection enDelete, boolean con foDelete.stream().map(forces::getFullEntities).map(ForceAssignable::filterToEntityList).forEach(inForces::addAll); enDelete.removeIf(inForces::contains); Set finalEnDelete = new HashSet<>(enDelete); - + if (!enDelete.isEmpty() && !validateUpdate(finalEnDelete)) { return; } @@ -686,17 +686,17 @@ void delete(Collection foDelete, Collection enDelete, boolean con return; } } - + // Send a command to remove the forceless entities Set senders = finalEnDelete.stream().map(this::correctSender).collect(toSet()); for (Client sender: senders) { - // Gather the entities for this sending client; + // Gather the entities for this sending client; // Serialization doesn't like the toList() result, therefore the new ArrayList List ids = new ArrayList<>(finalEnDelete.stream() .filter(e -> correctSender(e).equals(sender)).map(Entity::getId).collect(toList())); sender.sendDeleteEntities(ids); } - + // Send a command to remove the forces (with entities) senders = finalFoDelete.stream().map(this::correctSender).collect(toSet()); for (Client sender: senders) { @@ -706,11 +706,11 @@ void delete(Collection foDelete, Collection enDelete, boolean con sender.sendDeleteForces(foList); } } - + /** * Removes the given entities from their force(s), making them force-less. * Entities must have a single owner and be editable (local units or local bot's units) - * (Having multiple owners makes sending updates correctly for one's own bots difficult) + * (Having multiple owners makes sending updates correctly for one's own bots difficult) */ void forceRemoveEntity(Collection entities) { if (!validateUpdate(entities)) { @@ -718,9 +718,9 @@ void forceRemoveEntity(Collection entities) { } client().sendAddEntitiesToForce(entities, Force.NO_FORCE); } - + /** - * Swaps pilots between the given entity + * Swaps pilots between the given entity * and another entity of the given id */ void swapPilots(Collection entities, int targetId) { @@ -741,12 +741,12 @@ void swapPilots(Collection entities, int targetId) { selected.setCrew(temp); sendUpdates(Arrays.asList(target, selected)); } - - /** + + /** * Disconnects the passed entities from their C3 network, if any. * Due to the way C3 networks are represented in Entity, units * cannot disconnect from a C3 network with an id that is the - * entity's own id. + * entity's own id. */ void c3DisconnectFromNetwork(Collection entities) { if (!validateUpdate(entities)) { @@ -755,10 +755,10 @@ void c3DisconnectFromNetwork(Collection entities) { Set updateCandidates = performDisconnect(entities); sendUpdates(updateCandidates); } - - /** - * Performs a disconnect from C3 networks for the given entities without sending an update. - * Returns a set of all affected units. + + /** + * Performs a disconnect from C3 networks for the given entities without sending an update. + * Returns a set of all affected units. */ private HashSet performDisconnect(Collection entities) { HashSet updateCandidates = new HashSet<>(); @@ -780,7 +780,7 @@ private HashSet performDisconnect(Collection entities) { } return updateCandidates; } - + /** Sets the entities' C3M to act as a Company Master. */ void c3SetCompanyMaster(Collection entities) { if (!validateUpdate(entities)) { @@ -793,7 +793,7 @@ void c3SetCompanyMaster(Collection entities) { entities.forEach(e -> e.setC3Master(e.getId(), true)); sendUpdates(entities); } - + /** Sets the entities' C3M to act as a Lance Master (aka normal mode). */ void c3SetLanceMaster(Collection entities) { if (!validateUpdate(entities)) { @@ -806,8 +806,8 @@ void c3SetLanceMaster(Collection entities) { entities.forEach(e -> e.setC3Master(-1, true)); sendUpdates(entities); } - - /** + + /** * Connects the passed entities to a nonhierarchic C3 (NC3, C3i or Nova CEWS) * identified by masterID. */ @@ -837,7 +837,7 @@ void c3JoinNh(Collection entities, int masterID, boolean disconnectFirst sendUpdates(entities); } - /** + /** * Connects the passed entities to a standard C3M * identified by masterID. */ @@ -873,8 +873,8 @@ void c3Connect(Collection entities, int masterID, boolean disconnectFirs entities.forEach(e -> e.setC3Master(master, true)); sendUpdates(updateCandidates); } - - /** + + /** * Change the given entities' controller to the player with ID newOwnerId. * If the given forceList is not empty, an error message will be shown. */ @@ -890,7 +890,7 @@ void changeOwner(Collection entities, Collection forceList, int n } client().sendChangeOwner(entities, newOwnerId); } - + /** Change the team of a controlled player (the local player or one of his bots). */ void changeTeam(Collection players, int team) { var toSend = new HashSet(); @@ -900,7 +900,7 @@ void changeTeam(Collection players, int team) { .forEach(toSend::add); client().sendChangeTeam(toSend, team); } - + /** * Add the entities to the force if admissible (the entities must all be editable * by the local player and be allied to the force's owner. @@ -917,14 +917,14 @@ void forceAddEntity(Collection entities, int forceId) { } client().sendAddEntitiesToForce(entities, forceId); } - - /** - * Changes the owner of the given forces to a different player without - * affecting force structure. + + /** + * Changes the owner of the given forces to a different player without + * affecting force structure. * When assigning the force only to an enemy, it would dislodge that force * from its parent and dislodge all units from it and leave it an empty * force for the enemy. That seems useless. Therefore this is restricted - * to only assign to team members of the former owner. + * to only assign to team members of the former owner. */ void forceAssignOnly(Collection forceList, int newOwnerId) { Player newOwner = game().getPlayer(newOwnerId); @@ -946,8 +946,8 @@ void forceAssignOnly(Collection forceList, int newOwnerId) { } client().sendUpdateForce(changedForces); } - - /** + + /** * Changes the owner of the given forces to a different player together with * all subforces and units. */ @@ -962,7 +962,7 @@ void forceAssignFull(Collection forceList, int newOwnerId) { } client().sendAssignForceFull(forceList, newOwnerId); } - + void unloadFromBay(Collection entities, int bayId) { if (entities.size() != 1) { LobbyErrors.showSingleUnit(frame(), "offload from bays"); @@ -983,7 +983,7 @@ void unloadFromBay(Collection entities, int bayId) { } sendUpdates(updateCandidates); } - + /** * Creates a fighter squadron from the given list of entities. * Checks if all entities are fighters and if the number of entities @@ -1002,35 +1002,35 @@ void createSquadron(Collection entities) { return; } boolean largeSquadrons = game().getOptions().booleanOption(OptionsConstants.ADVAERORULES_ALLOW_LARGE_SQUADRONS); - if ((!largeSquadrons && entities.size() > FighterSquadron.MAX_SIZE) + if ((!largeSquadrons && entities.size() > FighterSquadron.MAX_SIZE) || entities.size() > FighterSquadron.ALTERNATE_MAX_SIZE) { LobbyErrors.showSquadronTooMany(frame()); } - + // Ask for a squadron name String name = JOptionPane.showInputDialog(frame(), "Choose a squadron designation"); if ((name == null) || name.isBlank()) { return; } - + // Now, actually create the squadron FighterSquadron fs = new FighterSquadron(name); fs.setOwner(createSquadronOwner(entities)); List fighterIds = new ArrayList<>(entities.stream().map(Entity::getId).collect(toList())); correctSender(fs).sendAddSquadron(fs, fighterIds); } - - /** + + /** * Returns a likely owner client; if any of the fighter belongs to the local * player, returns the local player. If not, returns a local bot if any of the - * fighters belongs to that; finally, returns the owner of a random one of the + * fighters belongs to that; finally, returns the owner of a random one of the * fighters. */ private Player createSquadronOwner(Collection entities) { if (entities.stream().anyMatch(e -> e.getOwner().equals(localPlayer()))) { return localPlayer(); } else { - for (Entry en: client().bots.entrySet()) { + for (Entry en: client().localBots.entrySet()) { Player bot = en.getValue().getLocalPlayer(); if (entities.stream().anyMatch(e -> e.getOwner().equals(bot))) { return en.getValue().getLocalPlayer(); @@ -1073,17 +1073,17 @@ private boolean validateUpdate(Collection entities) { return true; } - /** - * Sends the entities in the given Collection to the Server. + /** + * Sends the entities in the given Collection to the Server. * Sends only those that can be edited, i.e. the player's own * or his bots' units. Will separate the units into update - * packets for the local player and any local bots so that the + * packets for the local player and any local bots so that the * server accepts all changes (as the server does not know of * local bots and rejects updates that are not for the sending client - * or its teammates. + * or its teammates. */ void sendUpdates(Collection entities) { - // Gather the necessary sending clients; this list may contain null if some units + // Gather the necessary sending clients; this list may contain null if some units // cannot be affected at all, i.e. are enemies to localplayer and all his bots List senders = entities.stream().map(this::correctSender).distinct().collect(toList()); for (Client sender: senders) { @@ -1093,47 +1093,47 @@ void sendUpdates(Collection entities) { sender.sendUpdateEntity(new ArrayList<>(entities.stream().filter(e -> correctSender(e).equals(sender)).collect(toList()))); } } - - /** - * Sends the entities and forces in the given Collections to the Server. + + /** + * Sends the entities and forces in the given Collections to the Server. * Sends only those that can be edited, i.e. the player's own * or his bots' units. Will separate the units into update - * packets for the local player and any local bots so that the + * packets for the local player and any local bots so that the * server accepts all changes (as the server does not know of * local bots and rejects updates that are not for the sending client - * or its teammates. + * or its teammates. */ void sendUpdates(Collection changedEntities, Collection changedForces) { - // Gather the necessary sending clients; this list may contain null if some units + // Gather the necessary sending clients; this list may contain null if some units // cannot be affected at all, i.e. are enemies to localplayer and all his bots Set senders = new HashSet<>(); senders.addAll(changedEntities.stream().map(this::correctSender).distinct().collect(toList())); senders.addAll(changedForces.stream().map(this::correctSender).distinct().collect(toList())); - + for (Client sender: senders) { if (sender == null) { continue; } List enList = changedEntities.stream().filter(e -> correctSender(e).equals(sender)).collect(toList()); List foList = changedForces.stream().filter(f -> correctSender(f).equals(sender)).collect(toList()); - + if (foList.isEmpty()) { - sender.sendUpdateEntity(enList); + sender.sendUpdateEntity(enList); } else { sender.sendUpdateForce(foList, enList); } } } - + void sendSingleUpdate(Collection changedEntities, Collection changedForces) { if (!areAllied(changedEntities, changedForces)) { LogManager.getLogger().error("Cannot send force update unless all changed entities and forces are allied!"); return; } - + } - /** + /** * Returns the best sending client for an update of the given entity or * null if none can be found (entity is an enemy to the local player and all his bots) */ @@ -1141,22 +1141,22 @@ private Client correctSender(Entity entity) { Player owner = entity.getOwner(); if (localPlayer().equals(owner)) { return client(); - } else if (client().bots.containsKey(owner.getName())) { - return client().bots.get(owner.getName()); + } else if (client().localBots.containsKey(owner.getName())) { + return client().localBots.get(owner.getName()); } else if (!localPlayer().isEnemyOf(owner)) { return client(); } else { - for (Client bot: client().bots.values()) { + for (Client bot: client().localBots.values()) { if (!bot.getLocalPlayer().isEnemyOf(owner)) { return bot; } } } - + return null; } - - /** + + /** * Returns the best sending client for an update of the given force or * null if none can be found (force is an enemy to the local player and all his bots) */ @@ -1164,12 +1164,12 @@ private Client correctSender(Force force) { Player owner = game().getForces().getOwner(force); if (localPlayer().equals(owner)) { return client(); - } else if (client().bots.containsKey(owner.getName())) { - return client().bots.get(owner.getName()); + } else if (client().localBots.containsKey(owner.getName())) { + return client().localBots.get(owner.getName()); } else if (!localPlayer().isEnemyOf(owner) || isEditable(force)) { return client(); } else { - for (Client bot: client().bots.values()) { + for (Client bot: client().localBots.values()) { if (!bot.getLocalPlayer().isEnemyOf(owner)) { return bot; } @@ -1178,25 +1178,25 @@ private Client correctSender(Force force) { return null; } - /** + /** * Returns true when the given entity may be configured by the local player, * i.e. if it is his own unit or one of his bot's units. *

Note that this is more restrictive than the Server is. The Server - * accepts entity changes also for teammates so that entity updates that - * signal transporting a teammate's unit don't get rejected. + * accepts entity changes also for teammates so that entity updates that + * signal transporting a teammate's unit don't get rejected. * I think it's important to generally limit entity changes by other players * to avoid collisions of updates. - * TODO: A possible enhancement might be a GM mode for MM, where only one + * TODO: A possible enhancement might be a GM mode for MM, where only one * player is allowed to change everything. */ boolean isEditable(Entity entity) { - return client().bots.containsKey(entity.getOwner().getName()) + return client().localBots.containsKey(entity.getOwner().getName()) || (entity.getOwnerId() == localPlayer().getId()) || (entity.partOfForce() && isSelfOrLocalBot(game().getForces().getOwner(entity.getForceId()))) || (entity.partOfForce() && isEditable(game().getForces().getForce(entity))); } - /** + /** * Returns true when the given entity may NOT be configured by the local player, * i.e. if it is not own unit or one of his bot's units. * @see #isEditable(Entity) @@ -1205,7 +1205,7 @@ boolean isNotEditable(Entity entity) { return !isEditable(entity); } - /** + /** * Returns true when all given entities may be configured by the local player, * i.e. if they are his own units or one of his bot's units. * @see #isEditable(Entity) @@ -1229,42 +1229,42 @@ boolean canSeeAll(Collection entities) { boolean entityInLocalTeam(Entity entity) { return !localPlayer().isEnemyOf(entity.getOwner()); } - + boolean isSelfOrLocalBot(Player player) { - return client().bots.containsKey(player.getName()) || localPlayer().equals(player); + return client().localBots.containsKey(player.getName()) || localPlayer().equals(player); } /** Returns true if the entity is an enemy of the local player. */ boolean isLocalEnemy(Entity entity) { return localPlayer().isEnemyOf(entity.getOwner()); } - + /** * A force is editable to the local player if any forces in its force chain * (this includes the force itself) is owned by the local player or one of the * local bots. This allows editing forces of other players if they are a subforce - * of a local/bot force. + * of a local/bot force. */ boolean isEditable(Force force) { List chain = game().getForces().forceChain(force); return chain.stream().map(f -> game().getForces().getOwner(f)).anyMatch(this::isSelfOrLocalBot); } - + boolean isEditable(int forceId) { - return game().getForces().contains(forceId) && isEditable(game().getForces().getForce(forceId)); + return game().getForces().contains(forceId) && isEditable(game().getForces().getForce(forceId)); } - + boolean areForcesEditable(Collection forces) { return forces.stream().allMatch(this::isEditable); } - + /** * Returns true if no two of the given entities are enemies. This is - * true when all entities belong to a single player. If they belong to - * different players, it is true when all belong to the same team and + * true when all entities belong to a single player. If they belong to + * different players, it is true when all belong to the same team and * that team is one of Teams 1 through 5 (not "No Team"). *

Returns true when entities is empty or has only one entity. The case of - * entities being empty should be considered by the caller. + * entities being empty should be considered by the caller. */ private boolean areAllied(Collection entities) { if (entities.isEmpty()) { @@ -1274,9 +1274,9 @@ private boolean areAllied(Collection entities) { Entity randomEntry = entities.stream().findAny().get(); return entities.stream().noneMatch(e -> e.isEnemyOf(randomEntry)); } - + /** - * Returns true if no two of the given entities and forces are enemies. Also checks + * Returns true if no two of the given entities and forces are enemies. Also checks * between forces and entities. * @see #areAllied(Collection) * @see #areForcesAllied(Collection) @@ -1297,16 +1297,16 @@ private boolean areAllied(Collection entities, Collection forces) Force randomForce = forces.stream().findAny().get(); Player forceOwner = game().getForces().getOwner(randomForce); return areAllied(entities) && areForcesAllied(forces) && !entityOwner.isEnemyOf(forceOwner); - + } - + /** * Returns true if no two of the given forces are enemies. This is - * true when all forces belong to a single player. If they belong to - * different players, it is true when all belong to the same team and + * true when all forces belong to a single player. If they belong to + * different players, it is true when all belong to the same team and * that team is one of Teams 1 through 5 (not "No Team"). *

Returns true when forces is empty or has only one force. The case of - * forces being empty should be considered by the caller. + * forces being empty should be considered by the caller. */ private boolean areForcesAllied(Collection forces) { if (forces.isEmpty()) { @@ -1317,19 +1317,19 @@ private boolean areForcesAllied(Collection forces) { Player owner = game().getForces().getOwner(randomEntry); return forces.stream().noneMatch(f -> game().getForces().getOwner(f).isEnemyOf(owner)); } - + private Game game() { return lobby.game(); } - + private Client client() { return lobby.client(); } - + private JFrame frame() { return lobby.getClientgui().getFrame(); } - + private Player localPlayer() { return client().getLocalPlayer(); } diff --git a/megamek/src/megamek/client/ui/swing/lobby/LobbyMekPopup.java b/megamek/src/megamek/client/ui/swing/lobby/LobbyMekPopup.java index 52e694ec484..9bbf745487f 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/LobbyMekPopup.java +++ b/megamek/src/megamek/client/ui/swing/lobby/LobbyMekPopup.java @@ -52,7 +52,7 @@ /** Creates the Lobby Mek right-click pop-up menu for both the sortable table and the force tree. */ class LobbyMekPopup { - + static final String LMP_SKILLS = "SKILLS"; static final String LMP_CALLSIGN = "CALLSIGN"; static final String LMP_NAME = "NAME"; @@ -107,15 +107,15 @@ class LobbyMekPopup { static final String LMP_ALPHASTRIKE = "ALPHASTRIKE"; private static final String NOINFO = "|-1"; - + static final String LMP_UNLOADALLFROMBAY = "UNLOADALLFROMBAY"; - + static ScalingPopup getPopup(List entities, List forces, ActionListener listener, ChatLounge lobby) { ClientGUI clientGui = lobby.getClientgui(); Game game = lobby.game(); GameOptions opts = game.getOptions(); - + boolean optQuirks = opts.booleanOption(OptionsConstants.ADVANCED_STRATOPS_QUIRKS); boolean optBurstMG = opts.booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_BURST); boolean optLRMHotLoad = opts.booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_HOTLOAD); @@ -125,7 +125,7 @@ static ScalingPopup getPopup(List entities, List forces, ActionLi Set joinedEntities = new HashSet<>(entities); for (Force force: forces) { joinedEntities.addAll(ForceAssignable.filterToEntityList(game.getForces().getFullEntities(force))); - } + } // Find certain unit features among all units the player can access // Used to hide some menu items entirely like "Form Squadron" when there's no fighter in the game @@ -188,15 +188,15 @@ static ScalingPopup getPopup(List entities, List forces, ActionLi if (optBurstMG || optLRMHotLoad) { popup.add(equipMenu(anyRFMGOn, anyRFMGOff, anyHLOn, anyHLOff, optLRMHotLoad, optBurstMG, listener, seIds)); } - + if (optQuirks) { popup.add(quirksMenu(!entities.isEmpty() && canSeeAll, listener, eIds)); } - + popup.add(ScalingPopup.spacer()); popup.add(changeOwnerMenu(!entities.isEmpty() || !forces.isEmpty(), clientGui, listener, entities, forces)); popup.add(loadMenu(clientGui, true, listener, joinedEntities)); - + if (accessibleCarriers) { popup.add(menuItem("Disembark / leave from carriers", LMP_UNLOAD + NOINFO + seIds, !noneEmbarked, listener)); popup.add(menuItem("Offload all carried units", LMP_UNLOADALL + NOINFO + seIds, anyCarrier, listener)); @@ -282,7 +282,7 @@ private static JMenuItem forceTreeMenu(Force force, Game game, String enToken, A static String idString(Game game, int id) { if (PreferenceManager.getClientPreferences().getShowUnitId()) { - return " [" + id + "]"; + return " [" + id + "]"; } else { return ""; } @@ -303,7 +303,7 @@ private static JMenu loadMenu(ClientGUI cg, boolean enabled, ActionListener list .filter(e -> !entities.contains(e)) .filter(e -> canLoadAll(e, entities)) .forEach(e -> menu.add(menuItem( - "" + e.getShortNameRaw() + idString(game, e.getId()) + " (Free Collars: " + ((Jumpship) e).getFreeDockingCollars() + ")", + "" + e.getShortNameRaw() + idString(game, e.getId()) + " (Free Collars: " + ((Jumpship) e).getFreeDockingCollars() + ")", LMP_LOAD + "|" + e.getId() + ":-1" + enToken(entities), enabled, listener))); } else if (entities.stream().noneMatch(e -> e.hasETypeFlag(Entity.ETYPE_PROTOMECH))) { // Standard loading, not ProtoMeks, not DropShip -> JumpShip @@ -312,7 +312,7 @@ private static JMenu loadMenu(ClientGUI cg, boolean enabled, ActionListener list .filter(e -> !entities.contains(e)) .filter(e -> canLoadAll(e, entities)) .forEach(e -> menu.add(menuItem( - "" + e.getShortNameRaw() + idString(game, e.getId()), + "" + e.getShortNameRaw() + idString(game, e.getId()), LMP_LOAD + "|" + e.getId() + ":-1" + enToken(entities), enabled, listener))); } } @@ -380,7 +380,7 @@ private static JMenu squadronMenu(ClientGUI cg, boolean enabled, ActionListener .filter(e -> e instanceof FighterSquadron) .filter(e -> !entities.contains(e)) .filter(e -> canLoadAll(e, entities)) - .forEach(e -> menu.add(menuItem("Join " + e.getShortName(), + .forEach(e -> menu.add(menuItem("Join " + e.getShortName(), LMP_LOAD + "|" + e.getId() + ":-1" + enToken(entities), enabled, listener))); } menu.setEnabled(enabled && (menu.getItemCount() > 0)); @@ -394,8 +394,8 @@ private static JMenu squadronMenu(ClientGUI cg, boolean enabled, ActionListener private static JMenu prioTargetMenu(ClientGUI cg, boolean enabled, ActionListener listener, Collection entities) { JMenu menu = new JMenu("Set Priority Target for"); - if (enabled && !cg.getBots().isEmpty()) { - for (String bot : cg.getBots().keySet()) { + if (enabled && !cg.getLocalBots().isEmpty()) { + for (String bot : cg.getLocalBots().keySet()) { menu.add(menuItem(bot, LMP_PRIO_TARGET + "|" + bot + enToken(entities), enabled, listener)); } } @@ -456,7 +456,7 @@ private static JMenu deployMenu(ClientGUI clientGui, boolean enabled, ActionList for (int i = 11; i < 41; i++) { veryLateMenu.add(menuItem("Before round " + i, LMP_DEPLOY + "|" + i + eIds, enabled, listener)); } - + lateMenu.add(veryLateMenu); menu.add(lateMenu); } @@ -505,7 +505,7 @@ private static JMenu c3Menu(boolean enabled, Collection entities, Client menu.add(menuItem("Form C3 Lance", LMP_C3FORMC3 + "|" + master.getId() + enToken(entities), true, listener)); } } - + // Special treatment if a group of NhC3 is selected if (entities.size() > 1 && entities.size() <= 6) { Entity master = anyOneElement(entities); @@ -519,7 +519,7 @@ private static JMenu c3Menu(boolean enabled, Collection entities, Client entity = entities.stream().filter(e -> e.hasC3S() || e.hasNhC3()).findAny().orElse(entity); Game game = cg.getClient().getGame(); ArrayList usedNetIds = new ArrayList<>(); - + for (Entity other : cg.getClient().getEntitiesVector()) { // ignore enemies and self; only link the same type of C3 if (entity.isEnemyOf(other) || entity.equals(other) @@ -561,7 +561,7 @@ private static JMenu c3Menu(boolean enabled, Collection entities, Client item += (nodes == 0 ? " - full" : " - " + nodes + " free spots"); menu.add(menuItem(item, LMP_C3CONNECT + "|" + other.getId() + enToken(entities), nodes != 0, listener)); - } else if (other.isC3CompanyCommander() == entity.hasC3M() + } else if (other.isC3CompanyCommander() == entity.hasC3M() && !entity.isC3CompanyCommander()) { String item = "Connect to " + other.getShortNameRaw() + idString(game, other.getId()); item += " (" + other.getC3NetId() + ")"; @@ -639,17 +639,17 @@ private static JMenu equipMenu(boolean anyRFOn, boolean anyRFOff, boolean anyHLO boolean anyHLOff, boolean optHL, boolean optRF, ActionListener listener, String eIds) { JMenu menu = new JMenu(Messages.getString("ChatLounge.Equipment")); - menu.setEnabled(anyRFOff || anyRFOn || anyHLOff || anyHLOn); + menu.setEnabled(anyRFOff || anyRFOn || anyHLOff || anyHLOn); if (optRF) { - menu.add(menuItem(Messages.getString("ChatLounge.RapidFireToggleOn"), LMP_RAPIDFIREMG_ON + NOINFO + eIds, + menu.add(menuItem(Messages.getString("ChatLounge.RapidFireToggleOn"), LMP_RAPIDFIREMG_ON + NOINFO + eIds, anyRFOff, listener)); - menu.add(menuItem(Messages.getString("ChatLounge.RapidFireToggleOff"), LMP_RAPIDFIREMG_OFF + NOINFO + eIds, + menu.add(menuItem(Messages.getString("ChatLounge.RapidFireToggleOff"), LMP_RAPIDFIREMG_OFF + NOINFO + eIds, anyRFOn, listener)); } if (optHL) { - menu.add(menuItem(Messages.getString("ChatLounge.HotLoadToggleOn"), LMP_HOTLOAD_ON + NOINFO + eIds, + menu.add(menuItem(Messages.getString("ChatLounge.HotLoadToggleOn"), LMP_HOTLOAD_ON + NOINFO + eIds, anyHLOff, listener)); - menu.add(menuItem(Messages.getString("ChatLounge.HotLoadToggleOff"), LMP_HOTLOAD_OFF + NOINFO + eIds, + menu.add(menuItem(Messages.getString("ChatLounge.HotLoadToggleOff"), LMP_HOTLOAD_OFF + NOINFO + eIds, anyHLOn, listener)); } return menu; @@ -677,7 +677,7 @@ private static JMenu offloadBayMenu(boolean enabled, Collection entities /** * Returns the "Swap Pilot" submenu, allowing to swap the unit - * pilot with a pilot of an equivalent unit. Does work with multiple + * pilot with a pilot of an equivalent unit. Does work with multiple * selected units but expects the Lobby to issue an error message as * only one unit can swap pilot with one other unit. */ @@ -690,11 +690,11 @@ private static JMenu swapPilotMenu(boolean enabled, Collection entities, Entity entity = anyOneElement(entities); for (Entity swapper: game.getEntitiesVector()) { // only swap your own pilots and with the same unit and crew type - if (swapper.getOwnerId() == entity.getOwnerId() + if (swapper.getOwnerId() == entity.getOwnerId() && swapper.getId() != entity.getId() && swapper.getUnitType() == entity.getUnitType() && swapper.getCrew().getCrewType() == entity.getCrew().getCrewType()) { - + String item = "" + swapper.getShortNameRaw() + idString(game, swapper.getId()); String command = LMP_SWAP + "|" + swapper.getId() + enToken(entities); menu.add(menuItem(item, command, enabled, listener)); @@ -807,7 +807,7 @@ private static String enToken(Collection entities) { return "|" + String.join(",", ids); } - /** + /** * Returns a command string token containing the IDs of the given forces * E.g. 2,14,44,22 */ @@ -832,7 +832,7 @@ private static boolean hasRapidFireMG(Entity entity) { return false; } - /** Returns true when the entity has an MG set to normal (non-rapid) fire. */ + /** Returns true when the entity has an MG set to normal (non-rapid) fire. */ private static boolean hasNormalFireMG(Entity entity) { for (Mounted m: entity.getWeaponList()) { EquipmentType etype = m.getType(); @@ -843,7 +843,7 @@ private static boolean hasNormalFireMG(Entity entity) { return false; } - /** Returns true when the entity has a weapon with ammo set to hot-loaded. */ + /** Returns true when the entity has a weapon with ammo set to hot-loaded. */ private static boolean hasHotLoaded(Entity entity) { for (Mounted ammo: entity.getAmmo()) { AmmoType etype = (AmmoType) ammo.getType(); @@ -854,7 +854,7 @@ private static boolean hasHotLoaded(Entity entity) { return false; } - /** Returns true when the entity has a weapon with ammo set to non-hot-loaded. */ + /** Returns true when the entity has a weapon with ammo set to non-hot-loaded. */ private static boolean hasNonHotLoaded(Entity entity) { for (Mounted ammo: entity.getAmmo()) { AmmoType etype = (AmmoType) ammo.getType(); diff --git a/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java b/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java index ad25b0481f7..3b17a45c1e5 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java +++ b/megamek/src/megamek/client/ui/swing/lobby/PlayerTable.java @@ -80,7 +80,7 @@ public String getToolTipText(MouseEvent e) { if ((lobby.client() instanceof BotClient) && player.equals(lobby.localPlayer())) { String msg_thisbot = Messages.getString("ChatLounge.ThisBot"); result.append(" (" + UIUtil.BOT_MARKER + " " + msg_thisbot + ")"); - } else if (lobby.client().bots.containsKey(player.getName())) { + } else if (lobby.client().localBots.containsKey(player.getName())) { String msg_yourbot = Messages.getString("ChatLounge.YourBot"); result.append(" (" + UIUtil.BOT_MARKER + " " + msg_yourbot + ")"); } else if (lobby.localPlayer().equals(player)) { @@ -98,7 +98,7 @@ public String getToolTipText(MouseEvent e) { result.append(msg_noinitiativemodifier); } if (lobby.game().getOptions().booleanOption(OptionsConstants.ADVANCED_MINEFIELDS)) { - int mines = player.getNbrMFConventional() + player.getNbrMFActive() + int mines = player.getNbrMFConventional() + player.getNbrMFActive() + player.getNbrMFInferno() + player.getNbrMFVibra(); String msg_totalminefields = Messages.getString("ChatLounge.TotalMinefields"); result.append("
" + msg_totalminefields + ": ").append(mines); @@ -177,7 +177,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole StringBuilder result = new StringBuilder("" + UIUtil.guiScaledFontHTML()); // First Line - Player Name if ((lobby.client() instanceof BotClient) && player.equals(lobby.localPlayer()) - || lobby.client().bots.containsKey(player.getName())) { + || lobby.client().localBots.containsKey(player.getName())) { result.append(UIUtil.BOT_MARKER); } result.append(player.getName()); @@ -208,12 +208,12 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole result.append(msg_start + ": " + msg_none); } result.append(""); - + if (!LobbyUtility.isValidStartPos(lobby.game(), player)) { - result.append(guiScaledFontHTML(uiYellow())); + result.append(guiScaledFontHTML(uiYellow())); result.append(WARNING_SIGN + ""); } - + // Player BV result.append(UIUtil.DOT_SPACER); result.append(guiScaledFontHTML()); diff --git a/megamek/src/megamek/client/ui/swing/lobby/PlayerTablePopup.java b/megamek/src/megamek/client/ui/swing/lobby/PlayerTablePopup.java index f0e49a86abc..97d64a2fef8 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/PlayerTablePopup.java +++ b/megamek/src/megamek/client/ui/swing/lobby/PlayerTablePopup.java @@ -15,7 +15,7 @@ * * You should have received a copy of the GNU General Public License * along with MegaMek. If not, see . - */ + */ package megamek.client.ui.swing.lobby; import java.awt.event.ActionListener; @@ -31,14 +31,14 @@ import static megamek.client.ui.swing.util.UIUtil.*; -/** +/** * A popup menu for the lobby's player table. * Offers configuration, bot settings and team assignment. - * + * * @author Simon (Juliez) */ class PlayerTablePopup { - + static final String PTP_CONFIG = "CONFIG"; static final String PTP_BOTREMOVE = "BOTREMOVE"; static final String PTP_BOTSETTINGS = "BOTSETTINGS"; @@ -46,30 +46,30 @@ class PlayerTablePopup { static final String PTP_DEPLOY = "DEPLOY"; static final String PTP_REPLACE = "REPLACE"; - static ScalingPopup playerTablePopup(ClientGUI clientGui, ActionListener listener, + static ScalingPopup playerTablePopup(ClientGUI clientGui, ActionListener listener, Collection players) { - + ScalingPopup popup = new ScalingPopup(); - + var cl = clientGui.getClient(); var isOnePlayer = players.size() == 1; var singlePlayer = CollectionUtil.anyOneElement(players); var allOwnedBots = players.stream().allMatch(cl::isLocalBot); - var isConfigurable = isOnePlayer + var isConfigurable = isOnePlayer && (allOwnedBots || (cl.getLocalPlayer().equals(singlePlayer))); var allConfigurable = players.stream().allMatch(p -> cl.isLocalBot(p) || cl.getLocalPlayer().equals(p)); var isSingleGhost = isOnePlayer && singlePlayer.isGhost(); - + popup.add(menuItem("Player Settings...", PTP_CONFIG, isConfigurable, listener)); popup.add(teamMenu(allConfigurable, listener)); popup.add(startPosMenu(allConfigurable, listener)); popup.add(ScalingPopup.spacer()); popup.add(menuItem("Remove Bot", PTP_BOTREMOVE, isOnePlayer && allOwnedBots, listener)); popup.add(menuItem("Bot Settings...", PTP_BOTSETTINGS, isOnePlayer && allOwnedBots, listener)); - popup.add(menuItem("Replace Player...", PTP_REPLACE, isSingleGhost, listener)); - + popup.add(menuItem("Edit Bots...", PTP_REPLACE, isSingleGhost, listener)); + return popup; - + } /** Returns the "Team" submenu, allowing to assign a player to a team. */ @@ -82,7 +82,7 @@ private static JMenu teamMenu(boolean enabled, ActionListener listener) { menu.setEnabled(enabled); return menu; } - + /** Returns the "Starting Position" submenu, allowing to assign deployment positions. */ private static JMenu startPosMenu(boolean enabled, ActionListener listener) { JMenu menu = new JMenu("Deployment Area"); diff --git a/megamek/src/megamek/client/ui/swing/lobby/sorters/MekTreeTopLevelSorter.java b/megamek/src/megamek/client/ui/swing/lobby/sorters/MekTreeTopLevelSorter.java index aa5fb0c9edf..7df9da9c9b5 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/sorters/MekTreeTopLevelSorter.java +++ b/megamek/src/megamek/client/ui/swing/lobby/sorters/MekTreeTopLevelSorter.java @@ -39,7 +39,7 @@ public int compare(final Object a, final Object b) { || !((b instanceof Entity) || (b instanceof Force))) { throw new IllegalArgumentException("Can only compare Entities/Forces"); } - + Game game = client.getGame(); Forces forces = game.getForces(); @@ -63,8 +63,8 @@ public int compare(final Object a, final Object b) { idB = ((Entity) b).getId(); } - boolean isLocalBotA = (ownerA != null) && client.bots.containsKey(ownerA.getName()); - boolean isLocalBotB = (ownerB != null) && client.bots.containsKey(ownerB.getName()); + boolean isLocalBotA = (ownerA != null) && client.localBots.containsKey(ownerA.getName()); + boolean isLocalBotB = (ownerB != null) && client.localBots.containsKey(ownerB.getName()); boolean isLocalAllyA = (ownerA) != null && !ownerA.equals(localPlayer) && !ownerA.isEnemyOf(localPlayer); diff --git a/megamek/src/megamek/common/util/AddBotUtil.java b/megamek/src/megamek/common/util/AddBotUtil.java index 5649879c107..29a6555be60 100644 --- a/megamek/src/megamek/common/util/AddBotUtil.java +++ b/megamek/src/megamek/common/util/AddBotUtil.java @@ -13,6 +13,7 @@ */ package megamek.common.util; +import megamek.client.Client; import megamek.client.bot.BotClient; import megamek.client.bot.TestBot; import megamek.client.bot.princess.BehaviorSettings; @@ -58,9 +59,9 @@ private String concatResults() { } public String addBot(final String[] args, - final Game game, - final String host, - final int port) { + final Game game, + final String host, + final int port) { if (2 > args.length) { results.add(USAGE); return concatResults(); @@ -158,12 +159,13 @@ public String addBot(final String[] args, } if (!GraphicsEnvironment.isHeadless()) { + // GUI to show bot help nag if needed // FIXME : I should be able to access the JFrame by proper ways botClient.getGame().addGameListener(new BotGUI(new JFrame(), botClient)); } try { botClient.connect(); - } catch (final Exception e) { + } catch (final Exception ex) { results.add(botName + " failed to connect."); return concatResults(); } @@ -178,10 +180,16 @@ public String addBot(final String[] args, return concatResults(); } - public @Nullable Princess addBot(final BehaviorSettings behavior, final String playerName, - final Game game, final String host, final int port, StringBuilder message) { - + public @Nullable Princess replaceGhostWithBot(final BehaviorSettings behavior, final String playerName, + final Client client, + StringBuilder message) { + Objects.requireNonNull(client); Objects.requireNonNull(behavior); + + final Game game = client.getGame(); + final String host = client.getHost(); + final int port = client.getPort(); + Objects.requireNonNull(game); Optional possible = game.getPlayersVector().stream() @@ -193,17 +201,14 @@ public String addBot(final String[] args, message.append("Player '" + playerName + "' is not a ghost."); return null; } - + final Player target = possible.get(); final Princess princess = new Princess(target.getName(), host, port); princess.setBehaviorSettings(behavior); - if (!GraphicsEnvironment.isHeadless()) { - // FIXME : I should be able to use a frame through proper channels - princess.getGame().addGameListener(new BotGUI(new JFrame(), princess)); - } + try { princess.connect(); - } catch (final Exception e) { + } catch (final Exception ex) { message.append("Princess failed to connect."); } princess.setLocalPlayerNumber(target.getId()); @@ -211,6 +216,86 @@ public String addBot(final String[] args, return princess; } + /** + * Replace a ghost player or an existing Princess bot with a new bot + * @return the new Princess bot or null if not able to replace + */ + public @Nullable Princess changeBotSettings(final BehaviorSettings behavior, final String playerName, + final Client client, + StringBuilder message) { + Objects.requireNonNull(client); + Objects.requireNonNull(behavior); + + final Game game = client.getGame(); + final String host = client.getHost(); + final int port = client.getPort(); + + Objects.requireNonNull(game); + + Optional possible = game.getPlayersVector().stream() + .filter(p -> p.getName().equals(playerName)).findFirst(); + if (possible.isEmpty()) { + message.append("No player with the name '" + playerName + "'."); + return null; + } else if (!possible.get().isGhost() && !possible.get().isBot()) { + message.append("Player '" + playerName + "' is neither a ghost nor an existing bot."); + return null; + } + + final Player target = possible.get(); + if (target.isGhost()) { + final Princess princess = new Princess(target.getName(), host, port); + princess.setBehaviorSettings(behavior); + try { + princess.connect(); + } catch (final Exception ex) { + message.append("Princess failed to connect."); + } + princess.setLocalPlayerNumber(target.getId()); + message.append("Princess has replaced " + playerName + "."); + return princess; + } else { + Client bot = client.localBots.get(target.getName()); + if (bot == null) { + message.append("Player '" + playerName + "' is not a local bot."); + return null; + } else if (!(bot instanceof Princess)) { + message.append("Player '" + playerName + "' is not a Princess bot."); + return null; + } + Princess princess = (Princess) bot; + princess.setBehaviorSettings(behavior); + return princess; + } + } + + public boolean kickBot(final String playerName, + final Client client, + StringBuilder message) { + + Objects.requireNonNull(client); + final Game game = client.getGame(); + Objects.requireNonNull(game); + + Optional possible = game.getPlayersVector().stream() + .filter(p -> p.getName().equals(playerName)).findFirst(); + + if (possible.isEmpty()) { + message.append("No player with the name '" + playerName + "'."); + return false; + } else if (possible.get().isGhost()) { + message.append("Player '" + playerName + "' is a ghost."); + return false; + } else if (!possible.get().isBot()) { + message.append("Player '" + playerName + "' is not a bot."); + return false; + } + + final Player target = possible.get(); + client.sendChat("/kick "+target.getId()); + return true; + } + BotClient makeNewPrincessClient(final Player target, final String host, final int port) { return new Princess(target.getName(), host, port); }