/* * 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); } }