/* * 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.macro.api.views; import java.awt.Color; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Area; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.codehaus.groovy.syntax.ParserException; import com.t3.MD5Key; import com.t3.client.AppUtil; import com.t3.client.T3Util; import com.t3.client.TabletopTool; import com.t3.client.ui.commandpanel.ChatExecutor; import com.t3.client.ui.token.BooleanTokenOverlay; import com.t3.client.ui.zone.ZoneRenderer; import com.t3.client.walker.WalkerMetric; import com.t3.client.walker.ZoneWalker; import com.t3.client.walker.astar.AStarSquareEuclideanWalker; import com.t3.guid.GUID; import com.t3.language.I18N; import com.t3.macro.MacroException; import com.t3.macro.api.functions.token.TokenLocation; import com.t3.macro.api.functions.token.TokenPart; import com.t3.macro.api.views.InitiativeListView.InitiativeEntry; import com.t3.model.AbstractPoint; import com.t3.model.CellPoint; import com.t3.model.Direction; import com.t3.model.LightSource; import com.t3.model.MacroButtonProperties; import com.t3.model.Path; import com.t3.model.Token; import com.t3.model.Token.Type; import com.t3.model.TokenFootprint; import com.t3.model.Zone; import com.t3.model.Zone.Layer; import com.t3.model.ZonePoint; import com.t3.model.campaign.TokenProperty; import com.t3.model.chat.TokenSpeaker; import com.t3.model.grid.Grid; import com.t3.model.grid.SquareGrid; import com.t3.model.initiative.InitiativeList; import com.t3.model.initiative.InitiativeList.TokenInitiative; import com.t3.util.ImageManager; import com.t3.util.TokenUtil; import com.t3.util.math.IntPoint; public class TokenView extends TokenPropertyView { public TokenView(Token token) { super(token); } @Override public int hashCode() { return token.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TokenView other = (TokenView) obj; if (token == null) { if (other.token != null) return false; } else if (!token.equals(other.token)) return false; return true; } @Override public String toString() { return token.toString(); } /////////////////////////////////////////// // Implementation of available functions // /////////////////////////////////////////// /** * Simply writes to the chat as this token * @param message a string or some other kind of objects that is written to the chat */ public void say(Object message) { ChatExecutor.say(message.toString(),new TokenSpeaker(token.getId())); } /** * Whispers to a certain player as this token so that only you two can see it * @param message a string or some other kind of objects that is written to the chat */ public void whisper(Object message, String targetPlayer) { ChatExecutor.whisper(message.toString(), new TokenSpeaker(token.getId()), targetPlayer); } /** * Whispers to to the GM as this token so that only you two can see it * @param message a string or some other kind of objects that is written to the chat */ public void whisperToGM(Object message) { ChatExecutor.gm(message.toString(), new TokenSpeaker(token.getId())); } /** * This writes a message about the token to the chat * @param message a string or some other kind of objects that is written to the chat */ public void emote(Object message) { ChatExecutor.emote(message.toString(), new TokenSpeaker(token.getId())); } /** * This whispers an answer as this token back to last person that wrote to you * @param message a string or some other kind of objects that is written to the chat */ public void reply(Object message) { ChatExecutor.reply(message.toString(), new TokenSpeaker(token.getId())); } /** * The method returns the value of a bar or null if the bar is not visible at the moment. * @param barName the name of the bar you want the value of * @return the value of the bar or null if the bar is invisible */ public Float getBar(String barName) { return token.getBar(barName); } /** * This method is used to set the value of a bar. It also makes the bar visible. * @param barName the name of the bar you want to set the value of * @param value the value the bar should have between 0 and 100 */ public void setBar(String barName, float value) { token.setBar(barName, value); this.sendUpdateToServer(); } /** * Returns if a bar is currently visible * @param barName the name of the bar * @return if this bar is currently visible on this token */ public boolean isBarVisible(String barName) { return token.getBar(barName) != null; } /** * This allows you to show or hide a bar. This will reset the value of the bar! * @param barName the name of the bar * @param show if the bar should be visible or not */ public void setBarVisible(String barName, boolean show) { token.setBar(barName, show ? 1f : null); this.sendUpdateToServer(); } /** * This adds this token to the initiative list if it is not already present there * @return true if the token was added */ public boolean addToInitiative() { return addToInitiative(false, (String)null); } /** * This method adds this token to the initiative list * @param allowDuplicates if this token should be added even if it is already there * @return true if the token was added */ public boolean addToInitiative(boolean allowDuplicates) { return addToInitiative(allowDuplicates, (String)null); } /** * This method adds this token to the initiative list. If it is already present and you don't allow duplicates the given state will override the old one. * Otherwise the given state will be the state of the new initative list entry * @param allowDuplicates if this token should be added if it is already in the intiative list * @param state an optional state that can be displayed with the token name (like a initiative value) * @return if this token was actually added */ public boolean addToInitiative(boolean allowDuplicates, String state) { InitiativeList list = token.getZone().getInitiativeList(); // insert the token if needed TokenInitiative ti = null; if (allowDuplicates || !list.contains(token)) { ti = list.insertToken(-1, token); if (state != null) ti.setState(state); } else { setTokenInitiative(state); return false; } return ti != null; } /** * This method adds this token to the initiative list. If it is already present and you don't allow duplicates the given state will override the old one. * Otherwise the given state will be the state of the new initative list entry * @param allowDuplicates if this token should be added if it is already in the intiative list * @param state an optional state that can be displayed with the token name (like a initiative value) * @return if this token was actually added */ public boolean addToInitiative(boolean allowDuplicates, Number state) { InitiativeList list = token.getZone().getInitiativeList(); // insert the token if needed TokenInitiative ti = null; if (allowDuplicates || !list.contains(token)) { ti = list.insertToken(-1, token); if (state != null) ti.setState(state); } else { setTokenInitiative(state); return false; } return ti != null; } /** * Removes all entries of this token from the initiative list. * @return the number of entries removed */ public int removeFromInitiative() { InitiativeList list = token.getZone().getInitiativeList(); List<Integer> tokens = list.indexOf(token); list.startUnitOfWork(); for (int i = tokens.size() - 1; i >= 0; i--) list.removeToken(tokens.get(i).intValue()); list.finishUnitOfWork(); return tokens.size(); } /** * Gets the name of the token. * @return the name of the token. */ public String getName() { return token.getName(); } /** * Sets the name of the token. * @param name the name of the token. */ public void setName(String name) { token.setName(name); ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); TabletopTool.serverCommand().putToken(renderer.getZone().getId(), token); } /** * Calculates if a certain point on the map is visible for this Token. * @param x the x coordinate of the point you want to test * @param y the y coordinate of the point you want to test * @return if the point is visible */ public boolean isVisible(int x, int y) { Area visArea = TabletopTool.getFrame().getZoneRenderer(token.getZone()).getZoneView().getVisibleArea(token); if (visArea == null) return false; else return visArea.contains(x, y); } /** * Get the first token initiative * @return The first token initiative value for this token */ public InitiativeEntry getInitiative() { Zone zone = token.getZone(); List<Integer> list = zone.getInitiativeList().indexOf(token); if (list.isEmpty()) return null; return new InitiativeEntry(zone.getInitiativeList().getTokenInitiative(list.get(0).intValue())); } /** * Get the all initiatives of this token * @return a list of the initiatives of this token */ public List<InitiativeEntry> getInitiatives() { Zone zone = token.getZone(); List<Integer> list = zone.getInitiativeList().indexOf(token); if (list.isEmpty()) return Collections.emptyList(); List<InitiativeEntry> ret = new ArrayList<InitiativeEntry>(list.size()); for (Integer index : list) ret.add(new InitiativeEntry(zone.getInitiativeList().getTokenInitiative(index.intValue()))); return ret; } /** * This lets you set the initiative of a token. * @param state the new initiative of the token */ public void setTokenInitiative(String state) { InitiativeList list=token.getZone().getInitiativeList(); for(Integer index:list.indexOf(token)) { TokenInitiative ti = list.getTokenInitiative(index); if(ti!=null) ti.setState(state); } } /** * This lets you set the initiative of a token. * @param state the new initiative of the token */ public void setTokenInitiative(Number state) { InitiativeList list=token.getZone().getInitiativeList(); for(Integer index:list.indexOf(token)) { TokenInitiative ti = list.getTokenInitiative(index); if(ti!=null) ti.setState(state); } } /** * @return if this token is visible for the current player */ public boolean isVisible() { return !(token.isVisibleOnlyToOwner() && !AppUtil.playerOwns(token)) && token.isVisible(); } /** * @param visible if this token should be made visible or not */ public void setVisible(boolean visible) { token.setVisible(visible); this.sendUpdateToServer(); } /** * @return if this token is visible to its owner only */ public boolean isVisibleToOwnerOnly() { return token.isVisibleOnlyToOwner(); } /** * @param ownerOnlyVisible if this token should be made visible to its owner only */ public void setVisibleToOwnerOnly(boolean ownerOnlyVisible) { token.setVisibleOnlyToOwner(ownerOnlyVisible); this.sendUpdateToServer(); } /** * This method is used to access the speeches defined in the token properties. * @param speechKey the key of the speech * @return the text associated with the speech key */ public String getSpeech(String speechKey) { return token.getSpeech(speechKey); } /** * This method is used to change the speeches defined in the token properties. * @param speechKey the key to access the speech in the future * @param speech the text to store as a speech */ public void setSpeech(String speechKey, String speech) { token.setSpeech(speechKey, speech); this.sendUpdateToServer(); } /** * @return a set of all the speech keys that are defined for this token */ public Set<String> getSpeechKeys() { return Collections.unmodifiableSet(token.getSpeechNames()); } /** * @return the notes ostored on this token */ public String getNotes() { return token.getNotes(); } /** * This sets the notes of the token * @param notes the new notes */ public void setNotes(String notes) { token.setNotes(notes); this.sendUpdateToServer(); } /** * @return the GM notes of the token */ public String getGMNotes() { return token.getGMNotes(); } /** * This sets the GM notes of the token * @param notes the new GM notes */ public void setGMNotes(String notes) { token.setGMNotes(notes); this.sendUpdateToServer(); } /** * This method allows you to check the token for a certain state. * @param state the state to check for * @return if the token has this state */ public boolean hasState(String state) { return token.hasState(state); } /** * This method allows you to check the token for a certain * @param state the state you want to enable or disable * @param value if this value should be enabled */ public void setState(String state, boolean value) { token.setState(state, value); this.sendUpdateToServer(); } /** * This method allows you to change all the states at once. This is usefull * to clear a token of all its states. * @param value if all values should be enabled */ public void setAllStates(boolean value) { for (String stateName : TabletopTool.getCampaign().getTokenStatesMap().keySet()) token.setState(stateName, value); this.sendUpdateToServer(); } /** * @return the names of all the states defined in the campaign settings */ public Set<String> getStateNames() { return Collections.unmodifiableSet(TabletopTool.getCampaign().getTokenStatesMap().keySet()); } /** * @param group the group which state names you want * @return the names of all the states defined in the campaign settings for the given group */ public Set<String> getStateNames(String group) { Set<String> stateNames; Map<String, BooleanTokenOverlay> states = TabletopTool.getCampaign().getTokenStatesMap(); stateNames = new HashSet<String>(); for (BooleanTokenOverlay bto : states.values()) { if (group.equals(bto.getGroup())) { stateNames.add(bto.getName()); } } return stateNames; } private void sendUpdateToServer() { TabletopTool.serverCommand().putToken(token.getZone().getId(), token); } /** * @return the id of the token */ public GUID getId() { return token.getId(); } /** * Returns a set of the parts of a token that can be seen by this token. * @param target the token of which we want to check what this token can see * @return the set of visible token parts */ public EnumSet<TokenPart> getVisibleTokenParts(TokenView target) { if(!token.getHasSight()) return EnumSet.noneOf(TokenPart.class); ZoneRenderer zr=TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone=zr.getZone(); Area tokensVisibleArea = zr.getZoneView().getVisibleArea(token); if (tokensVisibleArea == null) return EnumSet.noneOf(TokenPart.class); if (target == null) throw new NullPointerException(); if (!target.isVisible() || (target.token.isVisibleOnlyToOwner() && !AppUtil.playerOwns(target.token))) { return EnumSet.noneOf(TokenPart.class); } Grid grid = zone.getGrid(); Rectangle bounds = target.token.getFootprint(grid).getBounds(grid, grid.convert(new ZonePoint(target.token.getX(), target.token.getY()))); if(!target.token.isSnapToGrid()) bounds = target.token.getBounds(zone); EnumSet<TokenPart> ret = EnumSet.noneOf(TokenPart.class); int x = (int) bounds.getX(); int y = (int) bounds.getY(); int w = (int) bounds.getWidth(); int h = (int) bounds.getHeight(); int halfX = x + (w) / 2; int halfY = y + (h) / 2; if (tokensVisibleArea.intersects(bounds)) { if (tokensVisibleArea.contains(new Point(x, y))) ret.add(TokenPart.TOP_LEFT); if (tokensVisibleArea.contains(new Point(x, y + h))) if (tokensVisibleArea.contains(new Point(x + w, y))) ret.add(TokenPart.TOP_RIGHT); if (tokensVisibleArea.contains(new Point(x + w, y + h))) ret.add(TokenPart.BOTTOM_LEFT); if (tokensVisibleArea.contains(new Point(halfX, halfY))) ret.add(TokenPart.BOTTOM_RIGHT); } return ret; } /** * @return if this token has sight */ public boolean hasSight() { return token.getHasSight(); } /** * @param value if this token should have sight */ public void setHasSight(boolean value) { token.setHasSight(value); this.sendUpdateToServer(); TabletopTool.getFrame().getZoneRenderer(token.getZone()).flushLight(); } /** * @return the type of vision this token is using * @see #getHasSight */ public String getVisionType() { return token.getSightType(); } /** * This allows you to set the type of vision this token should use. Be aware that to token can * only see things if {@link #setHasSight} is set. * @param visionType the type of vision this token should use * @see #setHasSight */ public void setVisionType(String visionType) { token.setSightType(visionType); this.sendUpdateToServer(); TabletopTool.getFrame().getZoneRenderer(token.getZone()).flushLight(); } /** * This sets the label of the token. Te label will be shown below the name of the token on the map. * @param label the new label */ public void setLabel(String label) { token.setLabel(label); token.getZone().putToken(token); this.sendUpdateToServer(); } /** * @return the label of the token. * @see #setLabel */ public String getLabel() { return token.getLabel(); } /** * @return the GM name of the token */ public String getGMName() { return token.getGMName(); } /** * This method sets the GM name of the token * @param name the new GM name */ public void setGMName(String name) { token.setGMName(name); Zone zone = token.getZone(); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); } /** * Gets the halo for the token. * @return the halo color or null if there is no halo */ public String getHalo() { if (token.getHaloColor() != null) return "#" + Integer.toHexString(token.getHaloColor().getRGB()).substring(2); else return null; } /** * Sets the halo color of the token. * @param hexColor the color to set in the form #AAAAAA or null to deactivate the halo */ public void setHalo(String hexColor) { if (hexColor == null) { token.setHaloColor(null); } else { Color color = T3Util.getColor(hexColor); token.setHaloColor(color); } Zone zone = token.getZone(); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); } /** * @return if this token has any light source */ public boolean hasLight() { return token.hasLightSources(); } /** * * @param category the category * @return if this token has any light source of the given category */ public boolean hasLight(String category) { for (LightSource ls : TabletopTool.getCampaign().getLightSourcesMap().get(category).values()) { if (token.hasLightSource(ls)) return true; } return false; } /** * * @param category the category of the light source you are looking for * @param name the name of the light source you are looking for * @return if this token has the given light source */ public boolean hasLight(String category, String name) { for (LightSource ls : TabletopTool.getCampaign().getLightSourcesMap().get(category).values()) { if (ls.getName().equals(name)) { if (token.hasLightSource(ls)) { return true; } } } return false; } /** * This method will remove all light sources from the token. */ public void clearLightSources() { token.clearLightSources(); this.sendUpdateToServer(); TabletopTool.getFrame().updateTokenTree(); TabletopTool.getFrame().getZoneRenderer(token.getZone()).flushLight(); } /** * Sets the light value for a token. * @param category the category of the light source. * @param name The name of the light source. * @param active The value to set for the light source, false for off or true for on. * @return false if the light was not found, otherwise true; */ public boolean setLight(String category, String name, boolean active) { boolean found = false; for (LightSource ls : TabletopTool.getCampaign().getLightSourcesMap().get(category).values()) { if (ls.getName().equals(name)) { found = true; if (active) { token.removeLightSource(ls); } else { token.addLightSource(ls, Direction.CENTER); } break; } } ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone = renderer.getZone(); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); TabletopTool.getFrame().updateTokenTree(); renderer.flushLight(); return found; } /** * Gets the names of the light sources that are on. * @return a string list containing the lights that are on. */ public List<String> getLights(Token token) { ArrayList<String> lightList = new ArrayList<String>(); for (Map<GUID, LightSource> category : TabletopTool.getCampaign().getLightSourcesMap().values()) { for (LightSource ls : category.values()) { if (token.hasLightSource(ls)) lightList.add(ls.getName()); } } return lightList; } /** * Gets the names of the light sources that are on. * @param category The category to get the light sources for * @return a string list containing the lights that are on. */ public List<String> getLights(Token token, String category) { ArrayList<String> lightList = new ArrayList<String>(); for (LightSource ls : TabletopTool.getCampaign().getLightSourcesMap().get(category).values()) { if (token.hasLightSource(ls)) { lightList.add(ls.getName()); } } return lightList; } /** * @return the location of the token */ public TokenLocation getLocation() { return getLocation(true); } /** * @param useDistancePerCell if you want the location as distance per cell * @return the location of the token */ public TokenLocation getLocation(boolean useDistancePerCell) { if (useDistancePerCell) { Rectangle tokenBounds = token.getBounds(token.getZone()); return new TokenLocation(tokenBounds.x, tokenBounds.y, token.getZOrder()); } else { Zone zone = token.getZone(); CellPoint cellPoint = zone.getGrid().convert(new ZonePoint(token.getX(), token.getY())); return new TokenLocation(cellPoint.x, cellPoint.y, token.getZOrder()); } } /** * @return the z order of this token. The z value gives the order in which the tokens * are drawn. */ public int getZOrder() { return token.getZOrder(); } /** * This methods sets the z order of this token. * @param newZ the new z order */ public void setZOrder(int newZ) { token.setZOrder(newZ); ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone = renderer.getZone(); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); renderer.flushLight(); } /** * Gets the distance between this token and this one. * @param target the token to calculate the distance to * @return the distance between this token and target. */ public double getDistance(TokenView target) { return getDistance(target,true,null); } /** * Gets the distance between this token and this one. * @param target the token to calculate the distance to * @param metric the metric that should be used to calculate the distance. Can be * NO_GRID, NO_DIAGONALS, MANHATTAN, ONE_TWO_ONE or ONE_ONE_ONE * @return the distance between this token and target. */ public double getDistance(TokenView target, String metric) { return getDistance(target,true,metric); } /** * Gets the distance between this token and this one. * @param target the token to calculate the distance to * @param gridUnits if you want the result in grid units * @return the distance between this token and target. */ public double getDistance(TokenView target, boolean gridUnits) { return getDistance(target,gridUnits,null); } /** * Gets the distance between this token and the given one. * @param target the token to calculate the distance to * @param gridUnits if you want the result in grid units * @param metric the metric that should be used to calculate the distance. Can be * NO_GRID, NO_DIAGONALS, MANHATTAN, ONE_TWO_ONE or ONE_ONE_ONE * @return the distance between this token and target. */ public double getDistance(TokenView target, boolean gridUnits, String metric) { ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Grid grid = renderer.getZone().getGrid(); if (grid.getCapabilities().isPathingSupported() && !"NO_GRID".equals(metric)) { // Get which cells our tokens occupy Set<CellPoint> sourceCells = token.getFootprint(grid).getOccupiedCells(grid.convert(new ZonePoint(token.getX(), token.getY()))); Set<CellPoint> targetCells = target.token.getFootprint(grid).getOccupiedCells(grid.convert(new ZonePoint(target.token.getX(), target.token.getY()))); ZoneWalker walker; if (metric != null && grid instanceof SquareGrid) { try { WalkerMetric wmetric = WalkerMetric.valueOf(metric); walker = new AStarSquareEuclideanWalker(renderer.getZone(), wmetric); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(I18N.getText("macro.function.getDistance.invalidMetric", metric)); } } else { walker = grid.createZoneWalker(); } // Get the distances from each source to target cell and keep the minimum one int distance = Integer.MAX_VALUE; for (CellPoint scell : sourceCells) { for (CellPoint tcell : targetCells) { walker.setWaypoints(scell, tcell); distance = Math.min(distance, walker.getDistance()); } } if (gridUnits) { return distance; } else { return (double)distance / renderer.getZone().getUnitsPerCell(); } } else { double d = token.getFootprint(grid).getScale(); double sourceCenterX = token.getX() + (d * grid.getSize()) / 2; double sourceCenterY = token.getY() + (d * grid.getSize()) / 2; d = target.token.getFootprint(grid).getScale(); double targetCenterX = target.token.getX() + (d * grid.getSize()) / 2; double targetCenterY = target.token.getY() + (d * grid.getSize()) / 2; double a = sourceCenterX - targetCenterX; double b = sourceCenterY - targetCenterY; double h = Math.sqrt(a * a + b * b); h /= renderer.getZone().getGrid().getSize(); if (gridUnits) { h *= renderer.getZone().getUnitsPerCell(); } return h; } } /** * Gets the distance between this token and a given Point. * @param x the x value of the target point * @param y the y value of the target point * @return the distance between this token and target. */ public double getDistance(int x, int y) { return getDistance(x, y,true,null); } /** * Gets the distance between this token and a given Point. * @param x the x value of the target point * @param y the y value of the target point * @param metric the metric that should be used to calculate the distance. Can be * NO_GRID, NO_DIAGONALS, MANHATTAN, ONE_TWO_ONE or ONE_ONE_ONE * @return the distance between this token and target. */ public double getDistance(int x, int y, String metric) { return getDistance(x,y,true,metric); } /** * Gets the distance between this token and a given Point. * @param x the x value of the target point * @param y the y value of the target point * @param gridUnits if you want the result in grid units * @return the distance between this token and target. */ public double getDistance(int x, int y, boolean gridUnits) { return getDistance(x,y,gridUnits,null); } /** * Gets the distance between this token and a given Point. * @param x the x value of the target point * @param y the y value of the target point * @param gridUnits if you want the result in grid units * @param metric the metric that should be used to calculate the distance. Can be * NO_GRID, NO_DIAGONALS, MANHATTAN, ONE_TWO_ONE or ONE_ONE_ONE * @return the distance between this token and target. */ public double getDistance(int x, int y, boolean gridUnits, String metric) { ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Grid grid = renderer.getZone().getGrid(); if (grid.getCapabilities().isPathingSupported() && !"NO_GRID".equals(metric)) { // Get which cells our tokens occupy Set<CellPoint> sourceCells = token.getFootprint(grid).getOccupiedCells(grid.convert(new ZonePoint(token.getX(), token.getY()))); CellPoint targetCell = grid.convert(new ZonePoint(x,y)); ZoneWalker walker; if (metric != null && grid instanceof SquareGrid) { try { WalkerMetric wmetric = WalkerMetric.valueOf(metric); walker = new AStarSquareEuclideanWalker(renderer.getZone(), wmetric); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(I18N.getText("macro.function.getDistance.invalidMetric", metric)); } } else { walker = grid.createZoneWalker(); } // Get the distances from each source to target cell and keep the minimum one int distance = Integer.MAX_VALUE; for (CellPoint scell : sourceCells) { walker.setWaypoints(scell, targetCell); distance = Math.min(distance, walker.getDistance()); } if (gridUnits) { return distance; } else { return (double)distance / renderer.getZone().getUnitsPerCell(); } } else { double d = token.getFootprint(grid).getScale(); double sourceCenterX = token.getX() + (d * grid.getSize()) / 2; double sourceCenterY = token.getY() + (d * grid.getSize()) / 2; double a = sourceCenterX - x; double b = sourceCenterY - y; double h = Math.sqrt(a * a + b * b); h /= renderer.getZone().getGrid().getSize(); if (gridUnits) { h *= renderer.getZone().getUnitsPerCell(); } return h; } } /** * Moves a token to the specified x,y location instantly. * @param x the x coordinate of the destination. * @param y the y coordinate of the destination. * @param gridUnits whether the (x,y) coordinates are in zone coordinates or point to a grid cell */ public void moveToken(int x, int y, boolean gridUnits) { Grid grid = token.getZone().getGrid(); if (gridUnits) { CellPoint cp = new CellPoint(x, y); ZonePoint zp = grid.convert(cp); token.setX(zp.x); token.setY(zp.y); } else { ZonePoint zp = new ZonePoint(x, y); token.setX(zp.x); token.setY(zp.y); } ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone = renderer.getZone(); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); renderer.flushLight(); } /** * @param gridUnits whether the coordinates are in zone coordinates or point to a grid cell * @return the last path this unit took. */ public List<IntPoint> getLastPath(boolean gridUnits) { Path<? extends AbstractPoint> path = token.getLastPath(); List<IntPoint> points = new ArrayList<IntPoint>(); if (path != null) { Zone zone = token.getZone(); AbstractPoint zp = null; for (AbstractPoint pathCells : path.getCellPath()) { if (pathCells instanceof CellPoint) { CellPoint cp = (CellPoint) pathCells; if (gridUnits) zp = cp; else zp = zone.getGrid().convert((CellPoint) pathCells); } else { zp = pathCells; if(gridUnits) zp=zone.getGrid().convert((ZonePoint)zp); } if (zp != null) { points.add(new IntPoint(zp.x,zp.y)); } } } return points; } /** * @return if this token is snap to grid */ public boolean isSnapToGrid() { return token.isSnapToGrid(); } /** * Returns the Rectangle the token would fill if it stould at the given coordinates * @param x the x coodinate (gridless) * @param y the y coodinate (gridless) * @return the bounding rectangle */ public Rectangle getBounds(int x, int y) { Zone zone=token.getZone(); if (token.isSnapToGrid()) { return token.getFootprint(zone.getGrid()).getBounds(zone.getGrid(), zone.getGrid().convert(new ZonePoint(x,y))); } else { return token.getBounds(zone); } } /** * Returns the Rectangle the token fills. * @return the bounding rectangle */ public Rectangle getBounds() { return getBounds(token.getX(),token.getY()); } /** * @return the names of all properties this token has */ public Set<String> getPropertyNames() { return Collections.unmodifiableSet(token.getPropertyNames()); } /** * @param name the name of the property * @return if this token has the given property and if it is not null */ public boolean hasProperty(String name) { Object o=token.getProperty(name); return o!=null && StringUtils.isEmpty(o.toString()); } /** * @return if this token is a PC */ public boolean isPC() { return token.getType()==Type.PC; } /** * @return if this token is a NPC */ public boolean isNPC() { return token.getType()==Type.NPC; } /** * This method makes this token a PC. */ public void setPC() { if(token.getType()!=Type.PC) { ZoneRenderer zr=TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone=zr.getZone(); token.setType(Token.Type.PC); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); zr.flushLight(); TabletopTool.getFrame().updateTokenTree(); } } /** * This method makes this token a NPC. */ public void setNPC() { if(token.getType()!=Type.NPC) { ZoneRenderer zr=TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone=zr.getZone(); token.setType(Token.Type.NPC); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); zr.flushLight(); TabletopTool.getFrame().updateTokenTree(); } } /** * @return the layer this token is one */ public String getLayer() { return token.getLayer().toString(); } /** * This method moves the token to the given layer * @param layer the layer to move this token to * @param forceShape if the shape type of this token should be reset */ public void setLayer(String layer, boolean forceShape) { Layer l=Zone.Layer.valueOf(layer); ZoneRenderer zr=TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone=zr.getZone(); token.setLayer(l); if (forceShape) { switch (l) { case BACKGROUND: case OBJECT: token.setShape(Token.TokenShape.TOP_DOWN); break; case GM: case TOKEN: Image image = ImageManager.getImage(token.getImageAssetId()); if (image == null || image == ImageManager.TRANSFERING_IMAGE) { token.setShape(Token.TokenShape.TOP_DOWN); } else { token.setShape(TokenUtil.guessTokenType(image)); } break; } } TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); zr.flushLight(); TabletopTool.getFrame().updateTokenTree(); } /** * Gets the size of the token. * The sizes returned are: * <ul><li>Fine</li> *<li>Diminutive</li> *<li>Tiny</li> *<li>Small</li> *<li>Medium</li> *<li>Large</li> *<li>Huge</li> *<li>Gargantuan</li> *<li>Colossal</li></ul> * * @return the size of the token. */ public String getSize() { Grid grid = token.getZone().getGrid(); if (token.isSnapToScale()) { return token.getFootprint(grid).getName(); } return ""; } /** * Sets the size of the token. * @param size The size to set the token to. * @return t he new size of the token. * @throws ParserException * if the size specified is an invalid size. */ public String setSize(String size) throws MacroException { if (size.equalsIgnoreCase("native") || size.equalsIgnoreCase("free")) { token.setSnapToScale(false); return this.getSize(); } token.setSnapToScale(true); ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone = renderer.getZone(); Grid grid = zone.getGrid(); for (TokenFootprint footprint : grid.getFootprints()) { if (footprint.getName().equalsIgnoreCase(size)) { token.setFootprint(grid, footprint); renderer.flush(token); renderer.repaint(); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); TabletopTool.getFrame().updateTokenTree(); return this.getSize(); } } throw new MacroException(I18N.getText("macro.function.tokenProperty.invalidSize", "setSize", size)); } /** * @return a list of all the explicit owners */ public Set<String> getOwners() { return Collections.unmodifiableSet(token.getOwners()); } /** * @return if this token is owned by all */ public boolean isOwnedByAll() { return token.isOwnedByAll(); } /** * @param player the player you want to test for * @return if this token is owned by the given player (explicitly or through owned by all) */ public boolean isOwner(String player) { return token.isOwner(player); } /** * This method will reset the given property of this token to the default value * @param property the property to reset */ public void resetProperty(String property) { Zone zone=token.getZone(); token.resetProperty(property); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); } /** * @param property the name of the property you want * @return the value of the given property */ public Object getProperty(String property) { return token.getProperty(property); } /** * This method sets a property of this token * @param property the name of the property you want to set * @param value the value the property should have */ public void setProperty(String property, Object value) { Zone zone=token.getZone(); token.setProperty(property, value); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); } /** * @param property the property you want the default value of * @return the default value of the given property */ public Object getPropertyDefault(String property) { Object val = null; List<TokenProperty> propertyList = TabletopTool.getCampaign().getCampaignProperties().getTokenPropertyList(token.getPropertyType()); if (propertyList != null) { for (TokenProperty tp : propertyList) { if (property.equalsIgnoreCase(tp.getName())) { val = tp.getDefaultValue(); break; } } } if (val == null) { return null; } if (val instanceof String) { // try to convert to a number try { return new BigDecimal(val.toString()).intValue(); } catch (Exception e) { return val; } } else { return val; } } /** * This method will send this token back. This means it will be drawn behind all * the other tokens. */ public void sendToBack() { Zone zone=token.getZone(); TabletopTool.serverCommand().sendTokensToBack(zone.getId(), Collections.singleton(token.getId())); zone.putToken(token); } /** * This method will send this token to the fron. This means it will be drawn * in front of all the other tokens. */ public void bringToFront() { Zone zone=token.getZone(); TabletopTool.serverCommand().bringTokensToFront(zone.getId(), Collections.singleton(token.getId())); zone.putToken(token); } /** * This method will set the properties type of this token. This allows you to * change what properties the token has and which it does not. * @param type the new properties type */ public void setPropertiesType(String type) { Zone zone=token.getZone(); token.setPropertyType(type); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); // FJE Should this be here? Added because other places have it...?! } /** * @return the properties type of the token */ public String getPropertiesType() { return token.getPropertyType(); } /** * @return an Integer indicating the direction the token is facing or null if * it is not facing */ public Integer getFacing() { return token.getFacing(); } /** * This method allows you to set the facing of this token or remove it by * setting it to null * @param direction the direction you want the token to face or null */ public void setFacing(Integer direction) { ZoneRenderer renderer = TabletopTool.getFrame().getZoneRenderer(token.getZone()); Zone zone = renderer.getZone(); token.setFacing(direction); TabletopTool.serverCommand().putToken(zone.getId(), token); if(this.hasLight()) renderer.flushLight(); zone.putToken(token); } /** * This method removes the facing from this token. */ public void removeFacing() { this.setFacing(null); } /** * This method adds an owner to this token. * @param player the player you want to add */ public void addOwner(String player) { Zone zone=token.getZone(); token.addOwner(player); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); } /** * This method removes all owners from this token. It might still be owned by all. */ public void clearAllOwners() { token.clearAllOwners(); Zone zone=token.getZone(); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); } /** * This method allows you to remove one explicit owner of this token * @param player the player you want to disown */ public void removeOwner(String player) { Zone zone=token.getZone(); token.removeOwner(player); TabletopTool.serverCommand().putToken(zone.getId(), token); zone.putToken(token); } /** * @return the width of the token */ public int getWidth() { return token.getBounds(token.getZone()).width; } /** * @return the height of the token */ public int getHeight() { return token.getBounds(token.getZone()).height; } /** * @return the shape of the token. This is one of: * <ul> * <li>Top down</li> * <li>Circle</li> * <li>Square</li> * </ul> */ public String getTokenShape() { return token.getShape().toString(); } /** * This method allows you to set the shape type of this token. It can be set to: * <ul> * <li>Top down</li> * <li>Circle</li> * <li>Square</li> * </ul> * @param shape the new shape */ public void setTokenShape(String shape) { Token.TokenShape newShape = Token.TokenShape.valueOf(shape); token.setShape(newShape); this.sendUpdateToServer(); } /** * @return a copy of this token */ public TokenView copyToken() { Zone zone = token.getZone(); List<Token> allTokens = zone.getTokens(); Token t = new Token(token); if (allTokens != null) { for (Token tok : allTokens) { GUID tea = tok.getExposedAreaGUID(); if (tea != null && tea.equals(t.getExposedAreaGUID())) { t.setExposedAreaGUID(new GUID()); } } } zone.putToken(t); TabletopTool.serverCommand().putToken(zone.getId(), t); TabletopTool.getFrame().getZoneRenderer(token.getZone()).flushLight(); return new TokenView(t); } /** * This method allows you to copy this token in bulk * @param numberOfCopies the number of copies you want * @return a list of the copies created */ public List<TokenView> copyToken(int numberOfCopies) { Zone zone = token.getZone(); List<TokenView> newTokens = new ArrayList<TokenView>(numberOfCopies); List<Token> allTokens = zone.getTokens(); for (int i = 0; i < numberOfCopies; i++) { Token t = new Token(token); if (allTokens != null) { for (Token tok : allTokens) { GUID tea = tok.getExposedAreaGUID(); if (tea != null && tea.equals(t.getExposedAreaGUID())) { t.setExposedAreaGUID(new GUID()); } } } zone.putToken(t); TabletopTool.serverCommand().putToken(zone.getId(), t); newTokens.add(new TokenView(t)); } TabletopTool.getFrame().getZoneRenderer(token.getZone()).flushLight(); return newTokens; } /** * This method will remove this token */ public void removeToken() { Zone zone = token.getZone(); TabletopTool.serverCommand().removeToken(zone.getId(), token.getId()); TabletopTool.getFrame().getZoneRenderer(token.getZone()).flushLight(); } /** * This method allows you to set the token image of this token * @param assetId the asset id of the image */ public void setTokenImage(String assetId) { Zone zone = token.getZone(); token.setImageAsset(null, new MD5Key(assetId)); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); } /** * This method allows you to set the token portrait of this token * @param assetId the asset id of the image */ public void setTokenPortrait(String assetId) { Zone zone = token.getZone(); token.setPortraitImage(new MD5Key(assetId)); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); } /** * This method allows you to set the token handout of this token * @param assetId the asset id of the image */ public void setTokenHandout(String assetId) { Zone zone = token.getZone(); token.setCharsheetImage(new MD5Key(assetId)); zone.putToken(token); TabletopTool.serverCommand().putToken(zone.getId(), token); } /** * @return the asset id of this tokens image */ public String getTokenImage() { return token.getImageAssetId().toString(); } /** * @return the asset id of this tokens portrait */ public String getTokenPortrait() { return token.getPortraitImage().toString(); } /** * @return the asset id of this tokens handout */ public String getTokenHandout() { return token.getCharsheetImage().toString(); } /** * @param macroName the macro button name * @return if this token has a macro button of the given name */ public boolean hasMacro(String macroName) { return token.getMacroNames(false).contains(macroName); } /** * This method will return a macro button of this token for the given name * @param macroName the name of the macro button * @return the macro button */ public MacroButtonView getMacro(String macroName) { return new MacroButtonView(token.getMacro(macroName,false)); } /** * This method will remove a macro button from this token * @param macroName the name of the macro button * @return if it could be removed */ public boolean removeMacro(String macroName) { MacroButtonProperties mbp=token.getMacro(macroName, false); if(mbp!=null) { token.deleteMacroButtonProperty(mbp); TabletopTool.serverCommand().putToken(token.getZone().getId(), token); return true; } return false; } /** * This method will create a new macro button for this token. * @param macroName the name of the new macro button * @return an object representing the button */ public MacroButtonView createMacro(String macroName) { MacroButtonProperties mbp = new MacroButtonProperties(token.getMacroNextIndex()); mbp.setLabel(macroName); mbp.setToken(token); token.getMacroPropertiesMap(true).put(mbp.getIndex(), mbp); TabletopTool.serverCommand().putToken(token.getZone().getId(), token); TabletopTool.getFrame().getSelectionPanel().reset(); return new MacroButtonView(mbp); } public static List<TokenView> makeTokenViewList(List<Token> list) { ArrayList<TokenView> l=new ArrayList<TokenView>(list.size()); for(Token t:list) l.add(new TokenView(t)); return l; } }