/* * $Id$ * * Copyright (c) 2000-2003 by Rodney Kinney * * 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.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import VASSAL.build.Buildable; import VASSAL.build.Builder; import VASSAL.build.GameModule; import VASSAL.command.AddPiece; import VASSAL.command.ChangePiece; import VASSAL.command.Command; import VASSAL.command.CommandEncoder; import VASSAL.command.MovePiece; import VASSAL.command.NullCommand; import VASSAL.command.PlayAudioClipCommand; import VASSAL.command.RemovePiece; import VASSAL.counters.ActionButton; import VASSAL.counters.AreaOfEffect; import VASSAL.counters.BasicPiece; import VASSAL.counters.CalculatedProperty; import VASSAL.counters.Clone; import VASSAL.counters.CounterGlobalKeyCommand; import VASSAL.counters.Deck; import VASSAL.counters.Decorator; import VASSAL.counters.Delete; import VASSAL.counters.DynamicProperty; import VASSAL.counters.Embellishment; import VASSAL.counters.Embellishment0; import VASSAL.counters.Footprint; import VASSAL.counters.FreeRotator; import VASSAL.counters.GamePiece; import VASSAL.counters.GlobalHotKey; import VASSAL.counters.Hideable; import VASSAL.counters.Immobilized; import VASSAL.counters.Labeler; import VASSAL.counters.Marker; import VASSAL.counters.MovementMarkable; import VASSAL.counters.NonRectangular; import VASSAL.counters.Obscurable; import VASSAL.counters.Pivot; import VASSAL.counters.PlaceMarker; import VASSAL.counters.PlaySound; import VASSAL.counters.PropertySheet; import VASSAL.counters.Replace; import VASSAL.counters.ReportState; import VASSAL.counters.RestrictCommands; import VASSAL.counters.Restricted; import VASSAL.counters.ReturnToDeck; import VASSAL.counters.SendToLocation; import VASSAL.counters.SetGlobalProperty; import VASSAL.counters.Stack; import VASSAL.counters.SubMenu; import VASSAL.counters.TableInfo; import VASSAL.counters.Translate; import VASSAL.counters.TriggerAction; import VASSAL.counters.UsePrototype; import VASSAL.tools.SequenceEncoder; /** * A {@link CommandEncoder} that handles the basic commands: {@link AddPiece}, * {@link RemovePiece}, {@link ChangePiece}, {@link MovePiece}. If a module * defines custom {@link GamePiece} classes, then this class may be overriden * and imported into the module. Subclasses should override the * {@link #createDecorator} method or, less often, the {@link #createBasic} or * {@link #createPiece} methods to allow instantiation of the custom * {@link GamePiece} classes. */ public class BasicCommandEncoder implements CommandEncoder, Buildable { private static final Logger logger = LoggerFactory.getLogger(BasicCommandEncoder.class); private Map<String,BasicPieceFactory> basicFactories = new HashMap<String,BasicPieceFactory>(); private Map<String,DecoratorFactory> decoratorFactories = new HashMap<String,DecoratorFactory>(); public BasicCommandEncoder() { basicFactories.put(Stack.TYPE, new BasicPieceFactory() { public GamePiece createBasicPiece(String type) { return new Stack(); } }); basicFactories.put(BasicPiece.ID, new BasicPieceFactory() { public GamePiece createBasicPiece(String type) { return new BasicPiece(type); } }); basicFactories.put(Deck.ID, new BasicPieceFactory() { public GamePiece createBasicPiece(String type) { return new Deck(type); } }); decoratorFactories.put(Immobilized.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Immobilized(inner, type); } }); decoratorFactories.put(Embellishment.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { final Embellishment e = new Embellishment(type, inner); if (e.getVersion() == Embellishment.BASE_VERSION) { return new Embellishment0(type, inner); } return e; } }); decoratorFactories.put(Embellishment.OLD_ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Embellishment(type, inner); } }); decoratorFactories.put(Hideable.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Hideable(type, inner); } }); decoratorFactories.put(Obscurable.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Obscurable(type, inner); } }); decoratorFactories.put(Labeler.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Labeler(type, inner); } }); decoratorFactories.put(TableInfo.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new TableInfo(type, inner); } }); decoratorFactories.put(PropertySheet.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new PropertySheet(type, inner); } }); decoratorFactories.put(FreeRotator.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new FreeRotator(type, inner); } }); decoratorFactories.put(Pivot.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Pivot(type, inner); } }); decoratorFactories.put(NonRectangular.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new NonRectangular(type, inner); } }); decoratorFactories.put(Marker.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Marker(type, inner); } }); decoratorFactories.put(Restricted.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Restricted(type, inner); } }); decoratorFactories.put(PlaceMarker.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new PlaceMarker(type, inner); } }); decoratorFactories.put(Replace.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Replace(type, inner); } }); decoratorFactories.put(ReportState.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new ReportState(type, inner); } }); decoratorFactories.put(MovementMarkable.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new MovementMarkable(type, inner); } }); decoratorFactories.put(Footprint.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Footprint(type, inner); } }); decoratorFactories.put(ReturnToDeck.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new ReturnToDeck(type, inner); } }); decoratorFactories.put(SendToLocation.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new SendToLocation(type, inner); } }); decoratorFactories.put(UsePrototype.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new UsePrototype(type, inner); } }); decoratorFactories.put(Clone.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Clone(type, inner); } }); decoratorFactories.put(Delete.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Delete(type, inner); } }); decoratorFactories.put(SubMenu.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new SubMenu(type, inner); } }); decoratorFactories.put(Translate.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new Translate(type, inner); } }); decoratorFactories.put(AreaOfEffect.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new AreaOfEffect(type, inner); } }); decoratorFactories.put(CounterGlobalKeyCommand.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new CounterGlobalKeyCommand(type, inner); } }); decoratorFactories.put(TriggerAction.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new TriggerAction(type, inner); } }); decoratorFactories.put(DynamicProperty.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new DynamicProperty(type, inner); } }); decoratorFactories.put(CalculatedProperty.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new CalculatedProperty(type, inner); } }); decoratorFactories.put(SetGlobalProperty.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new SetGlobalProperty(type, inner); } }); decoratorFactories.put(RestrictCommands.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new RestrictCommands(type, inner); } }); decoratorFactories.put(PlaySound.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new PlaySound(type, inner); } }); decoratorFactories.put(ActionButton.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new ActionButton(type, inner); } }); decoratorFactories.put(GlobalHotKey.ID, new DecoratorFactory() { public Decorator createDecorator(String type, GamePiece inner) { return new GlobalHotKey(type, inner); } }); } /** * Creates a {@link Decorator} instance * * @param type * the type of the Decorator to be created. Once created, the * Decorator should return this value from its * {@link Decorator#myGetType} method. * * @param inner * the inner piece of the Decorator * @see Decorator */ public Decorator createDecorator(String type, GamePiece inner) { Decorator d = null; String prefix = type.substring(0,type.indexOf(';')+1); if (prefix.length() == 0) { prefix = type; } DecoratorFactory f = decoratorFactories.get(prefix); if (f != null) { d = f.createDecorator(type, inner); } else { System.err.println("Unknown type "+type); //$NON-NLS-1$ d = new Marker(Marker.ID,inner); } return d; } /** * Create a GamePiece instance that is not a Decorator * * @param type * the type of the GamePiece. The created piece should return this * value from its {@link GamePiece#getType} method */ protected GamePiece createBasic(String type) { GamePiece p = null; String prefix = type.substring(0,type.indexOf(';')+1); if (prefix.length() == 0) { prefix = type; } BasicPieceFactory f = basicFactories.get(prefix); if (f != null) { p = f.createBasicPiece(type); } return p; } /** * Creates a GamePiece instance from the given type information. Determines * from the type whether the represented piece is a {@link Decorator} or not * and forwards to {@link #createDecorator} or {@link #createBasic}. This * method should generally not need to be overridden. Instead, override * createDecorator or createBasic */ public GamePiece createPiece(String type) { SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type, '\t'); type = st.nextToken(); String innerType = st.hasMoreTokens() ? st.nextToken() : null; if (innerType != null) { GamePiece inner = createPiece(innerType); if (inner == null) { GameModule.getGameModule().getChatter().send("Invalid piece type - see Error Log for details"); //$NON-NLS-1$ logger.warn("Could not create piece with type " + innerType); inner = new BasicPiece(); } Decorator d = createDecorator(type, inner); return d != null ? d : inner; } else { return createBasic(type); } } public void build(org.w3c.dom.Element e) { Builder.build(e, this); } public void addTo(Buildable parent) { ((GameModule) parent).addCommandEncoder(this); } public void add(Buildable b) { } public org.w3c.dom.Element getBuildElement(org.w3c.dom.Document doc) { return doc.createElement(getClass().getName()); } private static final char PARAM_SEPARATOR = '/'; public static final String ADD = "+" + PARAM_SEPARATOR; //$NON-NLS-1$ public static final String REMOVE = "-" + PARAM_SEPARATOR; //$NON-NLS-1$ public static final String CHANGE = "D" + PARAM_SEPARATOR; //$NON-NLS-1$ public static final String MOVE = "M" + PARAM_SEPARATOR; //$NON-NLS-1$ public Command decode(String command) { if (command.length() == 0) { return new NullCommand(); } SequenceEncoder.Decoder st; if (command.startsWith(ADD)) { command = command.substring(ADD.length()); st = new SequenceEncoder.Decoder(command, PARAM_SEPARATOR); String id = unwrapNull(st.nextToken()); String type = st.nextToken(); String state = st.nextToken(); GamePiece p = createPiece(type); if (p == null) { return null; } else { p.setId(id); return new AddPiece(p, state); } } else if (command.startsWith(REMOVE)) { String id = command.substring(REMOVE.length()); GamePiece target = GameModule.getGameModule().getGameState().getPieceForId(id); if (target == null) { return new RemovePiece(id); } else { return new RemovePiece(target); } } else if (command.startsWith(CHANGE)) { command = command.substring(CHANGE.length()); st = new SequenceEncoder.Decoder(command, PARAM_SEPARATOR); String id = st.nextToken(); String newState = st.nextToken(); String oldState = st.hasMoreTokens() ? st.nextToken() : null; return new ChangePiece(id, oldState, newState); } else if (command.startsWith(MOVE)) { command = command.substring(MOVE.length()); st = new SequenceEncoder.Decoder(command, PARAM_SEPARATOR); String id = unwrapNull(st.nextToken()); String newMapId = unwrapNull(st.nextToken()); int newX = Integer.parseInt(st.nextToken()); int newY = Integer.parseInt(st.nextToken()); String newUnderId = unwrapNull(st.nextToken()); String oldMapId = unwrapNull(st.nextToken()); int oldX = Integer.parseInt(st.nextToken()); int oldY = Integer.parseInt(st.nextToken()); String oldUnderId = unwrapNull(st.nextToken()); String playerid = st.nextToken(GameModule.getUserId()); return new MovePiece(id, newMapId, new Point(newX, newY), newUnderId, oldMapId, new Point(oldX, oldY), oldUnderId, playerid); } else { return PlayAudioClipCommand.decode(command); } } private String wrapNull(String s) { return s == null ? "null" : s; //$NON-NLS-1$ } private String unwrapNull(String s) { return "null".equals(s) ? null : s; //$NON-NLS-1$ } public String encode(Command c) { SequenceEncoder se = new SequenceEncoder(PARAM_SEPARATOR); if (c instanceof AddPiece) { AddPiece a = (AddPiece) c; return ADD + se.append(wrapNull(a.getTarget().getId())).append(a.getTarget().getType()).append(a.getState()).getValue(); } else if (c instanceof RemovePiece) { return REMOVE + ((RemovePiece) c).getId(); } else if (c instanceof ChangePiece) { ChangePiece cp = (ChangePiece) c; se.append(cp.getId()).append(cp.getNewState()); if (cp.getOldState() != null) { se.append(cp.getOldState()); } return CHANGE + se.getValue(); } else if (c instanceof MovePiece) { MovePiece mp = (MovePiece) c; se.append(mp.getId()).append(wrapNull(mp.getNewMapId())).append(mp.getNewPosition().x + "").append(mp.getNewPosition().y + "").append( //$NON-NLS-1$ //$NON-NLS-2$ wrapNull(mp.getNewUnderneathId())).append(wrapNull(mp.getOldMapId())).append(mp.getOldPosition().x + "").append(mp.getOldPosition().y + "").append( //$NON-NLS-1$ //$NON-NLS-2$ wrapNull(mp.getOldUnderneathId())).append(mp.getPlayerId()); return MOVE + se.getValue(); } else if (c instanceof NullCommand) { return ""; //$NON-NLS-1$ } else if (c instanceof PlayAudioClipCommand) { return ((PlayAudioClipCommand)c).encode(); } else { return null; } } public static interface DecoratorFactory { Decorator createDecorator(String type, GamePiece inner); } public static interface BasicPieceFactory { GamePiece createBasicPiece(String type); } }