com.t3.model.Token.java Source code

Java tutorial

Introduction

Here is the source code for com.t3.model.Token.java

Source

/*
 * Copyright (c) 2014 tabletoptool.com team.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     rptools.com team - initial implementation
 *     tabletoptool.com team - further development
 */
package com.t3.model;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.log4j.Logger;

import com.t3.MD5Key;
import com.t3.client.AppUtil;
import com.t3.client.TabletopTool;
import com.t3.guid.GUID;
import com.t3.guid.UniquelyIdentifiable;
import com.t3.image.ImageUtil;
import com.t3.language.I18N;
import com.t3.model.Zone.Layer;
import com.t3.model.campaign.Campaign;
import com.t3.model.grid.Grid;
import com.t3.transferable.TokenTransferData;
import com.t3.util.ImageManager;
import com.t3.util.StringUtil;
import com.t3.util.guidreference.ZoneReference;
import com.t3.xstreamversioned.version.SerializationVersion;

/**
 * This object represents the placeable objects on a map. For example an icon that represents a character would exist as
 * an {@link Asset} (the image itself) and a location and scale.
 */
@SerializationVersion(0)
public class Token extends BaseModel implements UniquelyIdentifiable {
    private static final Logger log = Logger.getLogger(Token.class);

    private GUID id = new GUID();

    public static final String FILE_EXTENSION = "rptok";
    public static final String FILE_THUMBNAIL = "thumbnail";

    public static final String NAME_USE_FILENAME = "Use Filename";
    public static final String NAME_USE_CREATURE = "Use \"Creature\"";

    public static final String NUM_INCREMENT = "Increment";
    public static final String NUM_RANDOM = "Random";

    public static final String NUM_ON_NAME = "Name";
    public static final String NUM_ON_GM = "GM Name";
    public static final String NUM_ON_BOTH = "Both";

    private boolean beingImpersonated = false;
    private GUID exposedAreaGUID;

    @SerializationVersion(0)
    public enum TokenShape {
        TOP_DOWN("Top down"), CIRCLE("Circle"), SQUARE("Square");

        private String displayName;

        private TokenShape(String displayName) {
            this.displayName = displayName;
        }

        @Override
        public String toString() {
            return displayName;
        }
    }

    @SerializationVersion(0)
    public enum Type {
        PC, NPC
    }

    public static final Comparator<Token> NAME_COMPARATOR = new Comparator<Token>() {
        @Override
        public int compare(Token o1, Token o2) {
            return o1.getName().compareToIgnoreCase(o2.getName());
        }
    };

    private final Map<String, MD5Key> imageAssetMap;
    private String currentImageAsset;

    private int x;
    private int y;
    private int z;

    private int anchorX;
    private int anchorY;

    private double sizeScale = 1;

    private int lastX;
    private int lastY;
    private Path<? extends AbstractPoint> lastPath;

    private boolean snapToScale = true; // Whether the scaleX and scaleY represent snap-to-grid measurements

    // These are the original image width and height
    private int width;
    private int height;

    private double scaleX = 1;
    private double scaleY = 1;

    private Map<Class<? extends Grid>, GUID> sizeMap;

    private boolean snapToGrid = true; // Whether the token snaps to the current grid or is free floating

    private boolean isVisible = true;
    private boolean visibleOnlyToOwner = false;

    private String name;
    private Set<String> ownerList;

    private boolean ownedByAll;

    private ZoneReference zone;

    private TokenShape tokenShape;
    private Type tokenType;
    private Zone.Layer layer;

    private String propertyType = Campaign.DEFAULT_TOKEN_PROPERTY_TYPE;

    private Integer facing = null;

    private Integer haloColorValue;
    private transient Color haloColor;

    private Integer visionOverlayColorValue;
    private transient Color visionOverlayColor;

    private boolean isFlippedX;
    private boolean isFlippedY;

    private MD5Key charsheetImage;
    private MD5Key portraitImage;

    private List<AttachedLightSource> lightSourceList;
    private String sightType;
    private boolean hasSight;

    private String label;

    /**
     * The notes that are displayed for this token.
     */
    private String notes;

    private String gmNotes;

    private String gmName;

    /**
     * The states this token has
     */
    private Set<String> states;

    /**
     * The bars and its values of this token
     */
    private HashMap<String, Float> bars;

    /**
     * Properties
     */
    private CaseInsensitiveMap<String, Object> propertyMap;

    private Map<Integer, MacroButtonProperties> macroPropertiesMap;

    private Map<String, String> speechMap;

    @SerializationVersion(0)
    public enum ChangeEvent {
        name, MACRO_CHANGED
    }

    public Token(Token token) {
        this(token.name, token.getImageAssetId());
        currentImageAsset = token.currentImageAsset;

        x = token.x;
        y = token.y;
        z = token.z;

        // These properties shouldn't be transferred, they are more transient and relate to token history, not to new tokens
        //      lastX = token.lastX;
        //      lastY = token.lastY;
        //      lastPath = token.lastPath;

        snapToScale = token.snapToScale;
        width = token.width;
        height = token.height;
        scaleX = token.scaleX;
        scaleY = token.scaleY;
        facing = token.facing;
        tokenShape = token.tokenShape;
        tokenType = token.tokenType;
        haloColorValue = token.haloColorValue;

        snapToGrid = token.snapToGrid;
        isVisible = token.isVisible;
        visibleOnlyToOwner = token.visibleOnlyToOwner;
        name = token.name;
        notes = token.notes;
        gmName = token.gmName;
        gmNotes = token.gmNotes;
        label = token.label;

        isFlippedX = token.isFlippedX;
        isFlippedY = token.isFlippedY;

        layer = token.layer;

        visionOverlayColor = token.visionOverlayColor;

        charsheetImage = token.charsheetImage;
        portraitImage = token.portraitImage;
        anchorX = token.anchorX;
        anchorY = token.anchorY;
        sizeScale = token.sizeScale;
        sightType = token.sightType;
        hasSight = token.hasSight;
        propertyType = token.propertyType;

        ownedByAll = token.ownedByAll;
        if (token.ownerList != null) {
            ownerList = new HashSet<String>();
            ownerList.addAll(token.ownerList);
        }
        if (token.lightSourceList != null) {
            lightSourceList = new ArrayList<AttachedLightSource>(token.lightSourceList);
        }
        if (token.states != null)
            states.addAll(token.states);
        if (token.bars != null)
            bars.putAll(token.bars);
        if (token.propertyMap != null) {
            getPropertyMap().clear();
            getPropertyMap().putAll(token.propertyMap);
        }
        if (token.macroPropertiesMap != null) {
            macroPropertiesMap = new HashMap<Integer, MacroButtonProperties>(token.macroPropertiesMap);
        }
        if (token.speechMap != null) {
            speechMap = new HashMap<String, String>(token.speechMap);
        }
        if (token.imageAssetMap != null) {
            imageAssetMap.putAll(token.imageAssetMap);
        }
        if (token.sizeMap != null) {
            sizeMap = new HashMap<Class<? extends Grid>, GUID>(token.sizeMap);
        }
        exposedAreaGUID = token.exposedAreaGUID;
    }

    public Token() {
        imageAssetMap = new HashMap<String, MD5Key>();
    }

    public Token(MD5Key assetID) {
        this("", assetID);
    }

    public Token(String name, MD5Key assetId) {
        this.name = name;
        states = new HashSet<String>();
        bars = new HashMap<String, Float>();
        imageAssetMap = new HashMap<String, MD5Key>();

        // NULL key is the default
        imageAssetMap.put(null, assetId);
    }

    /**
     * This token object has just been imported on a map and needs to have most of its internal data wiped clean. This
     * prevents a token from being imported that makes use of the wrong property types, vision types, ownership, macros,
     * and so on. Basically anything related to the presentation of the token on-screen + the two notes fields is kept.
     * Note that the sightType is set to the campaign's default sight type, and the property type is not changed at all.
     * This will usually be correct since the default sight is what most tokens have and the property type is probably
     * specific to the campaign -- hopefully the properties were set up before the token/map was imported.
     */
    public void imported() {
        // anchorX, anchorY?
        beingImpersonated = false;
        // hasSight?
        // height?
        lastPath = null;
        lastX = lastY = 0;
        // lightSourceList?
        //      macroPropertiesMap = null;
        ownerList = null;
        //      propertyMapCI = null;
        //      propertyType = "Basic";
        sightType = TabletopTool.getCampaign().getCampaignProperties().getDefaultSightType();
        //      state = null;
    }

    public void setHasSight(boolean hasSight) {
        this.hasSight = hasSight;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public boolean isMarker() {
        return isStamp() && (!StringUtil.isEmpty(notes) || !StringUtil.isEmpty(gmNotes) || portraitImage != null);
    }

    public String getPropertyType() {
        return propertyType;
    }

    public void setPropertyType(String propertyType) {
        this.propertyType = propertyType;
    }

    public String getGMNotes() {
        return gmNotes;
    }

    public void setGMNotes(String notes) {
        gmNotes = notes;
    }

    public String getGMName() {
        return gmName;
    }

    public void setGMName(String name) {
        gmName = name;
    }

    public boolean hasHalo() {
        return haloColorValue != null;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public void setHaloColor(Color color) {
        if (color != null) {
            haloColorValue = color.getRGB();
        } else {
            haloColorValue = null;
        }
        haloColor = color;
    }

    public Color getHaloColor() {
        if (haloColor == null && haloColorValue != null) {
            haloColor = new Color(haloColorValue);
        }
        return haloColor;
    }

    public boolean isObjectStamp() {
        return getLayer() == Zone.Layer.OBJECT;
    }

    public boolean isGMStamp() {
        return getLayer() == Zone.Layer.GM;
    }

    public boolean isBackgroundStamp() {
        return getLayer() == Zone.Layer.BACKGROUND;
    }

    public boolean isStamp() {
        return getLayer() != Layer.TOKEN;
    }

    public boolean isToken() {
        return getLayer() == Zone.Layer.TOKEN;
    }

    public TokenShape getShape() {
        return tokenShape != null ? tokenShape : TokenShape.SQUARE;
    }

    public void setShape(TokenShape type) {
        this.tokenShape = type;
    }

    public Type getType() {
        return tokenType != null ? tokenType : Type.NPC;
    }

    public void setType(Type type) {
        tokenType = type;
        if (type == Type.PC)
            hasSight = true;
    }

    public Zone.Layer getLayer() {
        return layer != null ? layer : Zone.Layer.TOKEN;
    }

    public void setLayer(Zone.Layer layer) {
        this.layer = layer;
    }

    public boolean hasFacing() {
        return facing != null;
    }

    public void setFacing(Integer facing) {
        while (facing != null && (facing > 180 || facing < -179)) {
            facing += facing > 180 ? -360 : 0;
            facing += facing < -179 ? 360 : 0;
        }
        this.facing = facing;
    }

    public Integer getFacing() {
        return facing;
    }

    public boolean getHasSight() {
        return hasSight;
    }

    public void addLightSource(LightSource source, Direction direction) {
        if (lightSourceList == null) {
            lightSourceList = new ArrayList<AttachedLightSource>();
        }
        if (!lightSourceList.contains(source))
            lightSourceList.add(new AttachedLightSource(source, direction));
    }

    public void removeLightSourceType(LightSource.Type lightType) {
        if (lightSourceList != null) {
            for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
                AttachedLightSource als = i.next();
                LightSource lightSource = als.getLightSource();
                if (lightSource != null && lightSource.getType() == lightType)
                    i.remove();
            }
        }
    }

    public void removeGMAuras() {
        if (lightSourceList != null) {
            for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
                AttachedLightSource als = i.next();
                LightSource lightSource = als.getLightSource();
                if (lightSource != null) {
                    List<Light> lights = lightSource.getLightList();
                    for (Light light : lights) {
                        if (light != null && light.isGM())
                            i.remove();
                    }
                }
            }
        }
    }

    public void removeOwnerOnlyAuras() {
        if (lightSourceList != null) {
            for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
                AttachedLightSource als = i.next();
                LightSource lightSource = als.getLightSource();
                if (lightSource != null) {
                    List<Light> lights = lightSource.getLightList();
                    for (Light light : lights) {
                        if (light.isOwnerOnly())
                            i.remove();
                    }
                }
            }
        }
    }

    public boolean hasOwnerOnlyAuras() {
        if (lightSourceList != null) {
            for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
                AttachedLightSource als = i.next();
                LightSource lightSource = als.getLightSource();
                if (lightSource != null) {
                    List<Light> lights = lightSource.getLightList();
                    for (Light light : lights) {
                        if (light.isOwnerOnly())
                            return true;
                    }
                }
            }
        }
        return false;
    }

    public boolean hasGMAuras() {
        if (lightSourceList != null) {
            for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
                AttachedLightSource als = i.next();
                LightSource lightSource = als.getLightSource();
                if (lightSource != null) {
                    List<Light> lights = lightSource.getLightList();
                    for (Light light : lights) {
                        if (light.isGM())
                            return true;
                    }
                }
            }
        }
        return false;
    }

    public boolean hasLightSourceType(LightSource.Type lightType) {
        if (lightSourceList != null) {
            for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
                AttachedLightSource als = i.next();
                LightSource lightSource = als.getLightSource();
                if (lightSource != null && lightSource.getType() == lightType)
                    return true;
            }
        }
        return false;
    }

    public void removeLightSource(LightSource source) {
        if (lightSourceList == null) {
            return;
        }
        for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
            AttachedLightSource als = i.next();
            if (als != null && als.getLightSourceReference() != null
                    && als.getLightSourceReference().getId().equals(source.getId())) {
                i.remove();
            }
        }
    }

    //My Addition
    public void clearLightSources() {
        if (lightSourceList == null) {
            return;
        }
        lightSourceList = null;
    }

    //End My Addition

    public boolean hasLightSource(LightSource source) {
        if (lightSourceList == null) {
            return false;
        }
        for (ListIterator<AttachedLightSource> i = lightSourceList.listIterator(); i.hasNext();) {
            AttachedLightSource als = i.next();
            if (als != null && als.getLightSourceReference() != null
                    && als.getLightSourceReference().getId().equals(source.getId())) {
                return true;
            }
        }
        return false;
    }

    public boolean hasLightSources() {
        return lightSourceList != null && !lightSourceList.isEmpty();
    }

    public List<AttachedLightSource> getLightSources() {
        return lightSourceList != null ? Collections.unmodifiableList(lightSourceList)
                : new LinkedList<AttachedLightSource>();
    }

    public synchronized void addOwner(String playerId) {
        this.ownedByAll = false;
        if (ownerList == null) {
            ownerList = new HashSet<String>();
        }
        ownerList.add(playerId);
    }

    public synchronized boolean hasOwners() {
        return ownedByAll || (ownerList != null && !ownerList.isEmpty());
    }

    public synchronized void removeOwner(String playerId) {
        ownedByAll = false;
        if (ownerList == null) {
            return;
        }
        ownerList.remove(playerId);
        if (ownerList.size() == 0) {
            ownerList = null;
        }
    }

    public synchronized void setOwnedByAll(boolean ownedByAll) {
        this.ownedByAll = ownedByAll;
        if (ownedByAll)
            ownerList = null;
    }

    public Set<String> getOwners() {
        return ownerList != null ? Collections.unmodifiableSet(ownerList) : new HashSet<String>();
    }

    public boolean isOwnedByAll() {
        return this.ownedByAll;
    }

    public synchronized void clearAllOwners() {
        ownerList = null;
    }

    public synchronized boolean isOwner(String playerId) {
        return this.ownedByAll || (ownerList != null && ownerList.contains(playerId))
                || TabletopTool.getPlayer().isGM();
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Token)) {
            return false;
        }
        return id.equals(((Token) o).id);
    }

    public void setZOrder(int z) {
        this.z = z;
    }

    public int getZOrder() {
        return z;
    }

    /**
     * Set the name of this token to the provided string. There is a potential exposure of information to the player in
     * this method: through repeated attempts to name a token they own to another name, they could determine which token
     * names the GM is already using. Fortunately, the showError() call makes this extremely unlikely due to the
     * interactive nature of a failure.
     * 
     * @param name
     * @throws IOException
     */
    public void setName(String name) throws IllegalArgumentException {
        //Let's see if there is another Token with that name (only if Player is not GM)
        if (!TabletopTool.getPlayer().isGM()) {// && !TabletopTool.getParser().isMacroTrusted()) { //FIXMESOON reinstate trusted macro laws?
            Zone curZone = TabletopTool.getFrame().getCurrentZoneRenderer().getZone();
            List<Token> tokensList = curZone.getTokens();

            for (int i = 0; i < tokensList.size(); i++) {
                String curTokenName = tokensList.get(i).getName();
                if (curTokenName.equalsIgnoreCase(name)) {
                    TabletopTool.showError(I18N.getText("Token.error.unableToRename", name));
                    throw new IllegalArgumentException("Player dropped token with duplicate name");
                }
            }
        }
        this.name = name;
        fireModelChangeEvent(new ModelChangeEvent(this, ChangeEvent.name, name));
    }

    public MD5Key getImageAssetId() {
        MD5Key assetId = imageAssetMap.get(currentImageAsset);
        if (assetId == null) {
            assetId = imageAssetMap.get(null); // default image
        }
        return assetId;
    }

    public void setImageAsset(String name, MD5Key assetId) {
        imageAssetMap.put(name, assetId);
    }

    public void setImageAsset(String name) {
        currentImageAsset = name;
    }

    public Set<MD5Key> getAllImageAssets() {
        Set<MD5Key> assetSet = new HashSet<MD5Key>(imageAssetMap.values());
        assetSet.add(charsheetImage);
        assetSet.add(portraitImage);
        assetSet.remove(null); // Clean up from any null values from above
        return assetSet;
    }

    public MD5Key getPortraitImage() {
        return portraitImage;
    }

    public void setPortraitImage(MD5Key image) {
        portraitImage = image;
    }

    public MD5Key getCharsheetImage() {
        return charsheetImage;
    }

    public void setCharsheetImage(MD5Key charsheetImage) {
        this.charsheetImage = charsheetImage;
    }

    @Override
    public GUID getId() {
        return id;
    }

    public void setId(GUID id) {
        this.id = id;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        lastX = this.x;
        this.x = x;
    }

    public void setY(int y) {
        lastY = this.y;
        this.y = y;
    }

    public void applyMove(int xOffset, int yOffset, Path<? extends AbstractPoint> path) {
        setX(x + xOffset);
        setY(y + yOffset);
        lastPath = path;
    }

    public void setLastPath(Path<? extends AbstractPoint> path) {
        lastPath = path;
    }

    public int getLastY() {
        return lastY;
    }

    public int getLastX() {
        return lastX;
    }

    public Path<? extends AbstractPoint> getLastPath() {
        return lastPath;
    }

    public double getScaleX() {
        return scaleX;
    }

    public double getScaleY() {
        return scaleY;
    }

    public void setScaleX(double scaleX) {
        this.scaleX = scaleX;
    }

    public void setScaleY(double scaleY) {
        this.scaleY = scaleY;
    }

    /**
     * @return Returns the snapScale.
     */
    public boolean isSnapToScale() {
        return snapToScale;
    }

    /**
     * @param snapScale
     *            The snapScale to set.
     */
    public void setSnapToScale(boolean snapScale) {
        this.snapToScale = snapScale;
    }

    public void setVisible(boolean visible) {
        this.isVisible = visible;
    }

    public boolean isVisible() {
        return isVisible;
    }

    /**
     * @return the visibleOnlyToOwner
     */
    public boolean isVisibleOnlyToOwner() {
        return visibleOnlyToOwner;
    }

    /**
     * @param visibleOnlyToOwner
     *            the visibleOnlyToOwner to set
     */
    public void setVisibleOnlyToOwner(boolean visibleOnlyToOwner) {
        this.visibleOnlyToOwner = visibleOnlyToOwner;
    }

    public String getName() {
        return name != null ? name : "";
    }

    public Rectangle getBounds(Zone zone) {
        Grid grid = zone.getGrid();
        TokenFootprint footprint = getFootprint(grid);
        Rectangle footprintBounds = footprint.getBounds(grid, grid.convert(new ZonePoint(getX(), getY())));

        double w = footprintBounds.width;
        double h = footprintBounds.height;

        // Sizing
        if (!isSnapToScale()) {
            w = this.width * getScaleX();
            h = this.height * getScaleY();
        } else {
            w = footprintBounds.width * footprint.getScale() * sizeScale;
            h = footprintBounds.height * footprint.getScale() * sizeScale;
        }
        // Positioning
        if (!isSnapToGrid()) {
            footprintBounds.x = getX();
            footprintBounds.y = getY();
        } else {
            if (!isBackgroundStamp()) {
                // Center it on the footprint
                footprintBounds.x -= (w - footprintBounds.width) / 2;
                footprintBounds.y -= (h - footprintBounds.height) / 2;
            } else {
                //              footprintBounds.x -= zone.getGrid().getSize()/2;
                //              footprintBounds.y -= zone.getGrid().getSize()/2;
            }
        }
        footprintBounds.width = (int) w; // perhaps make this a double
        footprintBounds.height = (int) h;

        // Offset
        footprintBounds.x += anchorX;
        footprintBounds.y += anchorY;
        return footprintBounds;
    }

    public String getSightType() {
        return sightType;
    }

    public void setSightType(String sightType) {
        this.sightType = sightType;
    }

    /**
     * @return Returns the size.
     */
    public TokenFootprint getFootprint(Grid grid) {
        return grid.getFootprint(getSizeMap().get(grid.getClass()));
    }

    public TokenFootprint setFootprint(Grid grid, TokenFootprint footprint) {
        return grid.getFootprint(getSizeMap().put(grid.getClass(), footprint.getId()));
    }

    private Map<Class<? extends Grid>, GUID> getSizeMap() {
        if (sizeMap == null) {
            sizeMap = new HashMap<Class<? extends Grid>, GUID>();
        }
        return sizeMap;
    }

    public boolean isSnapToGrid() {
        return snapToGrid;
    }

    public void setSnapToGrid(boolean snapToGrid) {
        this.snapToGrid = snapToGrid;
    }

    /**
     * Get if this token has a certain state
     * @param state the name of the state you want to check for
     * @return if the token has the state
     */
    public boolean hasState(String state) {
        return states.contains(state);
    }

    /**
     * Get the value of a bar of this token
     * @param barName the name of the bar you want to get
     * @return the value of the bar ior null if the bar is not visible
     */
    public Float getBar(String barName) {
        return bars.get(barName);
    }

    /**
     * This adds or removes a state from this token
     * @param state the state you want to set
     * @param value if the token should have the state or not
     * @return if the token had the state before the change
     */
    public boolean setState(String state, boolean value) {
        if (value)
            return !states.add(state);
        else
            return states.remove(state);
    }

    /**
     * This sets a bar of this token
     * @param barName the name of the bar you want to set
     * @param value the value the bar should have between 0 and 1 or null if it should be hidden
     * @return the bar value the token had before the change
     */
    public Float setBar(String barName, Float value) {
        if (value == null)
            return bars.remove(barName);
        else
            return bars.put(barName, value);
    }

    public void resetProperty(String key) {
        getPropertyMap().remove(key);
    }

    public Object setProperty(String key, Object value) {
        return getPropertyMap().put(key, value);
    }

    //overthink this -> this should return the default if it is null
    public Object getProperty(String key) {
        Object value = getPropertyMap().get(key);

        //      // Short name ?
        //      if (value == null) {
        //         for (EditTokenProperty property : TabletopTool.getCampaign().getCampaignProperties().getTokenPropertyList(getPropertyType())) {
        //            if (property.getShortName().equals(key)) {
        //               value = getPropertyMap().get(property.getShortName().toUpperCase());
        //            }
        //         }
        //      }
        return value;
    }

    /**
     * Returns all property names, all in lowercase.
     * 
     * @return
     */
    public Set<String> getPropertyNames() {
        return getPropertyMap().keySet();
    }

    private CaseInsensitiveMap<String, Object> getPropertyMap() {
        if (propertyMap == null) {
            propertyMap = new CaseInsensitiveMap<String, Object>();
        }
        return propertyMap;
    }

    public int getMacroNextIndex() {
        if (macroPropertiesMap == null) {
            macroPropertiesMap = new HashMap<Integer, MacroButtonProperties>();
        }
        Set<Integer> indexSet = macroPropertiesMap.keySet();
        int maxIndex = 0;
        for (int index : indexSet) {
            if (index > maxIndex)
                maxIndex = index;
        }
        return maxIndex + 1;
    }

    public Map<Integer, MacroButtonProperties> getMacroPropertiesMap(boolean secure) {
        if (macroPropertiesMap == null) {
            macroPropertiesMap = new HashMap<Integer, MacroButtonProperties>();
        }
        if (secure && !AppUtil.playerOwns(this)) {
            return new HashMap<Integer, MacroButtonProperties>();
        } else {
            return macroPropertiesMap;
        }
    }

    public MacroButtonProperties getMacro(int index, boolean secure) {
        return getMacroPropertiesMap(secure).get(index);
    }

    // avoid this; it loads the first macro with this label, but there could be more than one macro with that label
    public MacroButtonProperties getMacro(String label, boolean secure) {
        Set<Integer> keys = getMacroPropertiesMap(secure).keySet();
        for (int key : keys) {
            MacroButtonProperties prop = macroPropertiesMap.get(key);
            if (prop.getLabel().equals(label)) {
                return prop;
            }
        }
        return null;
    }

    public List<MacroButtonProperties> getMacroList(boolean secure) {
        Set<Integer> keys = getMacroPropertiesMap(secure).keySet();
        List<MacroButtonProperties> list = new ArrayList<MacroButtonProperties>();
        for (int key : keys) {
            list.add(macroPropertiesMap.get(key));
        }
        return list;
    }

    public void replaceMacroList(List<MacroButtonProperties> newMacroList) {
        // used by the token edit dialog, which will handle resetting panels and putting token to zone
        macroPropertiesMap.clear();
        for (MacroButtonProperties macro : newMacroList) {
            if (macro.getLabel() == null || macro.getLabel().trim().length() == 0
                    || macro.getCommand().trim().length() == 0) {
                continue;
            }
            macroPropertiesMap.put(macro.getIndex(), macro);

            // Allows the token macro panels to update only if a macro changes
            fireModelChangeEvent(new ModelChangeEvent(this, ChangeEvent.MACRO_CHANGED, id));
        }
    }

    public List<String> getMacroNames(boolean secure) {
        List<String> list = new ArrayList<String>();
        for (Entry<Integer, MacroButtonProperties> entry : getMacroPropertiesMap(secure).entrySet())
            list.add(entry.getValue().getLabel());
        return list;
    }

    public boolean hasMacros(boolean secure) {
        if (!getMacroPropertiesMap(secure).isEmpty()) {
            return true;
        }
        return false;
    }

    public void saveMacroButtonProperty(MacroButtonProperties prop) {
        getMacroPropertiesMap(false).put(prop.getIndex(), prop);
        TabletopTool.getFrame().resetTokenPanels();
        TabletopTool.serverCommand().putToken(TabletopTool.getFrame().getCurrentZoneRenderer().getZone().getId(),
                this);

        // Lets the token macro panels update only if a macro changes
        fireModelChangeEvent(new ModelChangeEvent(this, ChangeEvent.MACRO_CHANGED, id));
    }

    public void deleteMacroButtonProperty(MacroButtonProperties prop) {
        getMacroPropertiesMap(false).remove(prop.getIndex());
        TabletopTool.getFrame().resetTokenPanels();
        TabletopTool.serverCommand().putToken(TabletopTool.getFrame().getCurrentZoneRenderer().getZone().getId(),
                this);

        // Lets the token macro panels update only if a macro changes
        fireModelChangeEvent(new ModelChangeEvent(this, ChangeEvent.MACRO_CHANGED, id));
    }

    public void setSpeechMap(Map<String, String> map) {
        getSpeechMap().clear();
        getSpeechMap().putAll(map);
    }

    public Set<String> getSpeechNames() {
        return getSpeechMap().keySet();
    }

    public String getSpeech(String key) {
        return getSpeechMap().get(key);
    }

    public void setSpeech(String key, String value) {
        getSpeechMap().put(key, value);
    }

    private Map<String, String> getSpeechMap() {
        if (speechMap == null) {
            speechMap = new HashMap<String, String>();
        }
        return speechMap;
    }

    /**
     * This will remove all states from this token
     */
    public void removeAllStates() {
        states.clear();
    }

    /**
     * This will remove all bars from this token
     */
    public void removeAllBars() {
        bars.clear();
    }

    /** @return Getter for notes */
    public String getNotes() {
        return notes;
    }

    /**
     * @param aNotes
     *            Setter for notes
     */
    public void setNotes(String aNotes) {
        notes = aNotes;
    }

    public boolean isFlippedY() {
        return isFlippedY;
    }

    public void setFlippedY(boolean isFlippedY) {
        this.isFlippedY = isFlippedY;
    }

    public boolean isFlippedX() {
        return isFlippedX;
    }

    public void setFlippedX(boolean isFlippedX) {
        this.isFlippedX = isFlippedX;
    }

    public Color getVisionOverlayColor() {
        if (visionOverlayColor == null && visionOverlayColorValue != null) {
            visionOverlayColor = new Color(visionOverlayColorValue);
        }
        return visionOverlayColor;
    }

    public void setVisionOverlayColor(Color color) {
        if (color != null) {
            visionOverlayColorValue = color.getRGB();
        } else {
            visionOverlayColorValue = null;
        }
        visionOverlayColor = color;
    }

    @Override
    public String toString() {
        return "Token: " + id;
    }

    public void setAnchor(int x, int y) {
        anchorX = x;
        anchorY = y;
    }

    public Point getAnchor() {
        return new Point(anchorX, anchorY);
    }

    public double getSizeScale() {
        return sizeScale;
    }

    public void setSizeScale(double scale) {
        sizeScale = scale;
    }

    /**
     * Convert the token into a hash map. This is used to ship all of the properties for the token to other apps that do
     * need access to the <code>Token</code> class.
     * 
     * @return A map containing the properties of the token.
     */
    public TokenTransferData toTransferData() {
        TokenTransferData td = new TokenTransferData();
        td.setName(name);
        td.setPlayers(ownerList);
        td.setVisible(isVisible);
        td.setLocation(new Point(x, y));
        td.setFacing(facing);

        // Set the properties
        td.put(TokenTransferData.ID, id.toString());
        td.put(TokenTransferData.ASSET_ID, imageAssetMap.get(null));
        td.put(TokenTransferData.Z, z);
        td.put(TokenTransferData.SNAP_TO_SCALE, snapToScale);
        td.put(TokenTransferData.WIDTH, scaleX);
        td.put(TokenTransferData.HEIGHT, scaleY);
        td.put(TokenTransferData.SNAP_TO_GRID, snapToGrid);
        td.put(TokenTransferData.OWNER_TYPE, ownedByAll);
        td.put(TokenTransferData.VISIBLE_OWNER_ONLY, visibleOnlyToOwner);
        td.put(TokenTransferData.TOKEN_TYPE, tokenShape);
        td.put(TokenTransferData.NOTES, notes);
        td.put(TokenTransferData.GM_NOTES, gmNotes);
        td.put(TokenTransferData.GM_NAME, gmName);
        td.put(TokenTransferData.STATES, states);
        td.put(TokenTransferData.BARS, bars);

        // Create the image from the asset and add it to the map
        Image image = ImageManager.getImageAndWait(imageAssetMap.get(null));
        if (image != null)
            td.setToken(new ImageIcon(image)); // Image icon makes it serializable.
        return td;
    }

    /**
     * Constructor to create a new token from a transfer object containing its property values. This is used to read in
     * a new token from other apps that don't have access to the <code>Token</code> class.
     * 
     * @param td
     *            Read the values from this transfer object.
     */
    public Token(TokenTransferData td) {
        imageAssetMap = new HashMap<String, MD5Key>();
        states = new HashSet<String>();
        if (td.get(TokenTransferData.STATES) != null)
            states.addAll((Set<String>) td.get(TokenTransferData.STATES));
        bars = new HashMap<String, Float>();
        if (td.get(TokenTransferData.BARS) != null)
            bars.putAll((HashMap<String, Float>) td.get(TokenTransferData.BARS));
        if (td.getLocation() != null) {
            x = td.getLocation().x;
            y = td.getLocation().y;
        }
        snapToScale = getBoolean(td, TokenTransferData.SNAP_TO_SCALE, true);
        scaleX = getInt(td, TokenTransferData.WIDTH, 1);
        scaleY = getInt(td, TokenTransferData.HEIGHT, 1);
        snapToGrid = getBoolean(td, TokenTransferData.SNAP_TO_GRID, true);
        isVisible = td.isVisible();
        visibleOnlyToOwner = getBoolean(td, TokenTransferData.VISIBLE_OWNER_ONLY, false);
        name = td.getName();
        ownerList = td.getPlayers();
        ownedByAll = getBoolean(td, TokenTransferData.OWNER_TYPE, ownerList == null ? true : false);
        tokenShape = (TokenShape) td.get(TokenTransferData.TOKEN_TYPE);
        facing = td.getFacing();
        notes = (String) td.get(TokenTransferData.NOTES);
        gmNotes = (String) td.get(TokenTransferData.GM_NOTES);
        gmName = (String) td.get(TokenTransferData.GM_NAME);

        // Get the image and portrait for the token
        Asset asset = createAssetFromIcon(td.getToken());
        if (asset != null)
            imageAssetMap.put(null, asset.getId());
        asset = createAssetFromIcon((ImageIcon) td.get(TokenTransferData.PORTRAIT));
        if (asset != null)
            portraitImage = asset.getId();

        // Get the macros
        @SuppressWarnings("unchecked")
        Map<String, Map<String, String>> macros = (Map<String, Map<String, String>>) td
                .get(TokenTransferData.MACROS);
        for (Map<String, String> macroButtonProperties : macros.values()) {
            MacroButtonProperties mbp = new MacroButtonProperties(this, macroButtonProperties);
            getMacroPropertiesMap(false).put(mbp.getIndex(), mbp);
        }

        // Get all of the non tabletoptool specific state
        for (String key : td.keySet()) {
            if (key.startsWith(TokenTransferData.T3PREFIX))
                continue;
            setProperty(key, td.get(key));
        }
    }

    private Asset createAssetFromIcon(ImageIcon icon) {
        if (icon == null)
            return null;

        // Make sure there is a buffered image for it
        Image image = icon.getImage();
        if (!(image instanceof BufferedImage)) {
            image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), Transparency.TRANSLUCENT);
            Graphics2D g = ((BufferedImage) image).createGraphics();
            icon.paintIcon(null, g, 0, 0);
        }
        // Create the asset
        Asset asset = null;
        try {
            asset = new Asset(name, ImageUtil.imageToBytes((BufferedImage) image));
            if (!AssetManager.hasAsset(asset))
                AssetManager.putAsset(asset);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return asset;
    }

    /**
     * Get an integer value from the map or return the default value
     * 
     * @param map
     *            Get the value from this map
     * @param propName
     *            The name of the property being read.
     * @param defaultValue
     *            The value for the property if it is not set in the map.
     * @return The value for the passed property
     */
    private static int getInt(Map<String, Object> map, String propName, int defaultValue) {
        Integer integer = (Integer) map.get(propName);
        if (integer == null)
            return defaultValue;
        return integer.intValue();
    }

    /**
     * Get a boolean value from the map or return the default value
     * 
     * @param map
     *            Get the value from this map
     * @param propName
     *            The name of the property being read.
     * @param defaultValue
     *            The value for the property if it is not set in the map.
     * @return The value for the passed property
     */
    private static boolean getBoolean(Map<String, Object> map, String propName, boolean defaultValue) {
        Boolean bool = (Boolean) map.get(propName);
        if (bool == null)
            return defaultValue;
        return bool.booleanValue();
    }

    public static boolean isTokenFile(String filename) {
        return filename != null && filename.toLowerCase().endsWith(FILE_EXTENSION);
    }

    public Icon getIcon(int width, int height) {
        ImageIcon icon = new ImageIcon(ImageManager.getImageAndWait(getImageAssetId()));
        Image image = icon.getImage().getScaledInstance(width, height, Image.SCALE_DEFAULT);
        return new ImageIcon(image);
    }

    public boolean isBeingImpersonated() {
        return beingImpersonated;
    }

    public void setBeingImpersonated(boolean bool) {
        beingImpersonated = bool;
    }

    public void deleteMacroGroup(String macroGroup, Boolean secure) {
        List<MacroButtonProperties> tempMacros = new ArrayList<MacroButtonProperties>(getMacroList(true));
        for (MacroButtonProperties nextProp : tempMacros) {
            if (macroGroup.equals(nextProp.getGroup())) {
                getMacroPropertiesMap(secure).remove(nextProp.getIndex());
            }
        }
        TabletopTool.getFrame().resetTokenPanels();
        TabletopTool.serverCommand().putToken(TabletopTool.getFrame().getCurrentZoneRenderer().getZone().getId(),
                this);
    }

    public void deleteAllMacros(Boolean secure) {
        List<MacroButtonProperties> tempMacros = new ArrayList<MacroButtonProperties>(getMacroList(true));
        for (MacroButtonProperties nextProp : tempMacros) {
            getMacroPropertiesMap(secure).remove(nextProp.getIndex());
        }
        TabletopTool.getFrame().resetTokenPanels();
        TabletopTool.serverCommand().putToken(TabletopTool.getFrame().getCurrentZoneRenderer().getZone().getId(),
                this);
    }

    public static final Comparator<Token> COMPARE_BY_NAME = new Comparator<Token>() {
        @Override
        public int compare(Token o1, Token o2) {
            if (o1 == null || o2 == null) {
                return 0;
            }
            return o1.getName().compareTo(o2.getName());
        }
    };
    public static final Comparator<Token> COMPARE_BY_ZORDER = new Comparator<Token>() {
        @Override
        public int compare(Token o1, Token o2) {
            if (o1 == null || o2 == null) {
                return 0;
            }
            return o1.z < o2.z ? -1 : o1.z == o2.z ? 0 : 1;
        }
    };

    @Override
    protected Object readResolve() {
        super.readResolve();
        // 1.3 b77
        if (exposedAreaGUID == null) {
            exposedAreaGUID = new GUID();
        }
        return this;
    }

    /**
     * @param exposedAreaGUID
     *            the exposedAreaGUID to set
     */
    public void setExposedAreaGUID(GUID exposedAreaGUID) {
        this.exposedAreaGUID = exposedAreaGUID;
    }

    /**
     * @return the exposedAreaGUID
     */
    public GUID getExposedAreaGUID() {
        return exposedAreaGUID;
    }

    public Zone getZone() {
        return zone.value();
    }

    public void setZone(Zone zone) {
        this.zone = new ZoneReference(zone);
    }
}