/* * Copyright (c) 2000-2013 by Rodney Kinney, Brent Easton * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.build.module; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Iterator; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.WindowConstants; import net.miginfocom.swing.MigLayout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import VASSAL.build.AbstractConfigurable; import VASSAL.build.GameModule; import VASSAL.build.GpIdChecker; import VASSAL.build.GpIdSupport; import VASSAL.build.widget.PieceSlot; import VASSAL.command.ChangePiece; import VASSAL.command.Command; import VASSAL.command.NullCommand; import VASSAL.command.RemovePiece; import VASSAL.counters.Deck; import VASSAL.counters.Decorator; import VASSAL.counters.GamePiece; import VASSAL.counters.Properties; import VASSAL.counters.Stack; import VASSAL.i18n.Resources; import VASSAL.tools.ErrorDialog; /** * GameRefresher Replace all counters in the same game with the current version * of the counters defined in the module * * Note: Counters that are Hidden or Obscured to us cannot be updated. * */ public final class GameRefresher implements GameComponent { private static final Logger logger = LoggerFactory.getLogger(GameRefresher.class); private Action refreshAction; protected GpIdSupport gpIdSupport; protected GpIdChecker gpIdChecker; protected int updatedCount; protected int notFoundCount; protected int notOwnedCount; protected RefreshDialog dialog; protected boolean testMode; public GameRefresher(GpIdSupport gpIdSupport) { this.gpIdSupport = gpIdSupport; } public void addTo(AbstractConfigurable parent) { refreshAction = new AbstractAction(Resources.getString("GameRefresher.refresh_counters")) { //$NON-NLS-1$ private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { new GameRefresher(gpIdSupport).start(); } }; refreshAction.setEnabled(false); GameModule.getGameModule().getGameState().addGameComponent(this); } public Action getRefreshAction() { return refreshAction; } public boolean isTestMode() { return testMode; } public void start() { dialog = new RefreshDialog(this); dialog.setVisible(true); dialog = null; } public void execute(boolean testMode, boolean useName) { this.testMode = testMode; final GameModule theModule = GameModule.getGameModule(); updatedCount = 0; notFoundCount = 0; notOwnedCount = 0; /* * 1. Use the GpIdChecker to build a cross-reference of all available * PieceSlots and PlaceMarker's in the module. */ gpIdChecker = new GpIdChecker(useName); for (PieceSlot slot : theModule.getAllDescendantComponentsOf(PieceSlot.class)) { gpIdChecker.add(slot); } if (gpIdChecker.hasErrors()) { // Any errors should have been resolved by the GpId check at startup, so // this error indicates // a bug in GpIdChecker.fixErrors(). ErrorDialog.show("GameRefresher.no_gpids"); //$NON-NLS-1$ gpIdChecker = null; return; } /* * 2. Make a list of all pieces in the game that we have access to */ final Command command = new NullCommand(); final ArrayList<GamePiece> pieces = new ArrayList<GamePiece>(); for (GamePiece piece : theModule.getGameState().getAllPieces()) { if (piece instanceof Deck) { for (Iterator<GamePiece> i = ((Stack) piece).getPiecesInVisibleOrderIterator(); i.hasNext();) { pieces.add(0, i.next()); } } else if (piece instanceof Stack) { for (Iterator<GamePiece> i = ((Stack) piece).getPiecesInVisibleOrderIterator(); i.hasNext();) { final GamePiece p = i.next(); if (!Boolean.TRUE.equals(p.getProperty(Properties.INVISIBLE_TO_ME)) && !Boolean.TRUE.equals(p.getProperty(Properties.OBSCURED_TO_ME))) { pieces.add(0, p); } else { notOwnedCount++; } } } else if (piece.getParent() == null) { if (!Boolean.TRUE.equals(piece.getProperty(Properties.INVISIBLE_TO_ME)) && !Boolean.TRUE.equals(piece.getProperty(Properties.OBSCURED_TO_ME))) { pieces.add(0, piece); } else { notOwnedCount++; } } } /* * 3. Generate the commands to update the pieces */ for (GamePiece piece : pieces) { if (isTestMode()) { testGamePiece(piece); } else { processGamePiece(piece, command); } } if (isTestMode()) { dialog.addMessage(Resources.getString("GameRefresher.counters_refreshed_test", updatedCount)); if (notOwnedCount > 0) { dialog.addMessage(Resources.getString("GameRefresher.counters_not_owned_test", notOwnedCount)); } if (notFoundCount > 0) { dialog.addMessage(Resources.getString("GameRefresher.counters_not_found_test", notFoundCount)); } } else { final String player = GlobalOptions.getInstance().getPlayerId(); final Chatter chatter = theModule.getChatter(); final Command msg = new Chatter.DisplayText(chatter, "----------"); msg.append(new Chatter.DisplayText(chatter, Resources.getString("GameRefresher.run_refresh_counters", player))); msg.append(new Chatter.DisplayText(chatter, Resources.getString("GameRefresher.counters_refreshed", player, updatedCount))); if (notOwnedCount > 0) { msg.append(new Chatter.DisplayText(chatter, Resources.getString("GameRefresher.counters_not_owned", player, notOwnedCount))); } if (notFoundCount > 0) { msg.append(new Chatter.DisplayText(chatter, Resources.getString("GameRefresher.counters_not_found", player, notFoundCount))); } msg.append(new Chatter.DisplayText(chatter, "----------")); msg.execute(); command.append(msg); // Send the update to other clients theModule.sendAndLog(command); } gpIdChecker = null; } private void processGamePiece(GamePiece piece, Command command) { final Map map = piece.getMap(); if (map == null) { logger.error("Can't refresh piece " + piece.getName() + ": No Map"); return; } final Point pos = piece.getPosition(); GamePiece newPiece = gpIdChecker.createUpdatedPiece(piece); final Stack oldStack = piece.getParent(); final int oldPos = oldStack == null ? 0 : oldStack.indexOf(piece); // Remove the old Piece if different if (piece.equals(newPiece)) { notFoundCount++; logger.error("Can't refresh piece " + piece.getName() + ": Can't find matching Piece Slot"); } else { updatedCount++; if (! isTestMode()) { // Place the new Piece. final Command place = map.placeOrMerge(newPiece, pos); command.append(place); // Delete the old piece final Command remove = new RemovePiece(Decorator.getOutermost(piece)); remove.execute(); command.append(remove); } } if (! isTestMode()) { // If still in the same stack, move to correct position final Stack newStack = newPiece.getParent(); if (newStack != null && oldStack != null && newStack == oldStack) { final int newPos = newStack.indexOf(newPiece); if (newPos >= 0 && oldPos >= 0 && newPos != oldPos) { final String oldState = newStack.getState(); newStack.insert(newPiece, oldPos); command.append(new ChangePiece(newStack.getId(), oldState, newStack.getState())); } } } } private void testGamePiece(GamePiece piece) { final Map map = piece.getMap(); if (map == null) { logger.error("Can't refresh piece " + piece.getName() + ": No Map"); return; } if (gpIdChecker.findUpdatedPiece(piece)) { updatedCount++; } else { notFoundCount++; logger.error("Can't refresh piece " + piece.getName() + ": Can't find matching Piece Slot"); } } public Command getRestoreCommand() { return null; } /** * Enable Refresh menu item when game is running only. */ public void setup(boolean gameStarting) { refreshAction.setEnabled(gameStarting); } class RefreshDialog extends JDialog { private static final long serialVersionUID = 1L; private GameRefresher refresher; private JTextArea results; private JCheckBox nameCheck; RefreshDialog (GameRefresher refresher) { this.refresher = refresher; setTitle(Resources.getString("GameRefresher.refresh_counters")); setModal(true); initComponents(); } protected void initComponents() { setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { exit(); } }); setLayout(new MigLayout("wrap 1","[center]")); final JPanel buttonPanel = new JPanel(new MigLayout()); final JButton testButton = new JButton(Resources.getString("General.test")); testButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { test(); }}); final JButton runButton = new JButton(Resources.getString("General.run")); runButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { run(); }}); final JButton exitButton = new JButton(Resources.getString("General.exit")); exitButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { exit(); }}); buttonPanel.add(testButton); buttonPanel.add(runButton); buttonPanel.add(exitButton); add(buttonPanel); results = new JTextArea(7, 40); results.setEditable(false); add(results); nameCheck = new JCheckBox(Resources.getString("GameRefresher.use_basic_name")); add(nameCheck); pack(); } protected void exit() { setVisible(false); } protected void test() { results.setText(Resources.getString("GameRefresher.refresh_counters_test")); refresher.execute (true, nameCheck.isSelected()); } protected void run() { results.setText(""); refresher.execute (false, nameCheck.isSelected()); exit(); } public void addMessage(String mess) { results.setText(results.getText()+"\n"+mess); } } }