/* * 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.campaign; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.t3.MD5Key; import com.t3.client.TabletopTool; import com.t3.client.ui.ExportDialog; import com.t3.client.ui.ToolbarPanel; import com.t3.client.ui.token.BarTokenOverlay; import com.t3.client.ui.token.BooleanTokenOverlay; import com.t3.client.ui.token.ImageTokenOverlay; import com.t3.client.ui.token.MultipleImageBarTokenOverlay; import com.t3.client.ui.token.SingleImageBarTokenOverlay; import com.t3.client.ui.token.TwoImageBarTokenOverlay; import com.t3.guid.GUID; import com.t3.model.Asset; import com.t3.model.LightSource; import com.t3.model.LookupTable; import com.t3.model.MacroButtonProperties; import com.t3.model.SightType; import com.t3.model.Zone; import com.t3.net.Location; import com.t3.xstreamversioned.version.SerializationVersion; /** * <p> * This object contains {@link Zone}s and {@link Asset}s that make up a campaign as well as links to a variety of other * campaign characteristics (campaign macros, properties, lookup tables, and so on). * </p> * <p> * Roughly this is equivalent to multiple tabs that will appear on the client and all of the images that will appear on * it (and also campaign macro buttons). * </p> */ @SerializationVersion(0) public class Campaign { /** * The only built-in property type is "Basic". Any others are user-defined. */ public static final String DEFAULT_TOKEN_PROPERTY_TYPE = "Basic"; private GUID id = new GUID(); private Map<GUID, Zone> zones = Collections.synchronizedMap(new LinkedHashMap<GUID, Zone>()); private static ExportDialog exportDialog; // this is the new export dialog (different name for upward compatibility) // Static data isn't written to the campaign file when saved; these two fields hold the output location and type, and the // settings of all JToggleButton objects (JRadioButtons and JCheckBoxes). private Location exportLocation; // FJE 2011-01-14 private Map<String, Boolean> exportSettings; // the state of each checkbox/radiobutton for the Export>ScreenshotAs dialog private CampaignProperties campaignProperties = new CampaignProperties(); private transient boolean isBeingSerialized; // campaign macro button properties. these are saved along with the campaign. // as of 1.3b32 private List<MacroButtonProperties> macroButtonProperties = new ArrayList<MacroButtonProperties>(); // need to have a counter for additions to macroButtonProperties array // otherwise deletions/insertions from/to that array will go out of sync private int macroButtonLastIndex = 0; // DEPRECATED: As of 1.3b20 these are now in campaignProperties, but are here for backward compatibility private Map<String, List<TokenProperty>> tokenTypeMap; private List<String> remoteRepositoryList; private Map<String, Map<GUID, LightSource>> lightSourcesMap; private Map<String, LookupTable> lookupTableMap; /** * This flag indicates whether the manual fog tools have been used in this campaign while a server is not running. * See {@link ToolbarPanel#createFogPanel()} for details. * <p> * <ul> * <li>null - server never started for this campaign * <li>false - server started and IndividualFog == off * <li>true - server started and IndividualFog == on * </ul> */ private Boolean hasUsedFogToolbar = null; public Campaign() { macroButtonLastIndex = 0; macroButtonProperties = new ArrayList<MacroButtonProperties>(); } private void checkCampaignPropertyConversion() { if (campaignProperties == null) { campaignProperties = new CampaignProperties(); } if (tokenTypeMap != null) { campaignProperties.setTokenTypeMap(tokenTypeMap); tokenTypeMap = null; } if (remoteRepositoryList != null) { campaignProperties.setRemoteRepositoryList(remoteRepositoryList); remoteRepositoryList = null; } if (lightSourcesMap != null) { campaignProperties.setLightSourcesMap(lightSourcesMap); lightSourcesMap = null; } if (lookupTableMap != null) { campaignProperties.setLookupTableMap(lookupTableMap); lookupTableMap = null; } } public List<String> getRemoteRepositoryList() { checkCampaignPropertyConversion(); // TODO: Remove, for compatibility 1.3b19-1.3b20 return campaignProperties.getRemoteRepositoryList(); } public Campaign(Campaign campaign) { zones = Collections.synchronizedMap(new LinkedHashMap<GUID, Zone>()); /* * JFJ 2010-10-27 Don't forget that since these are new zones AND new tokens created here from the old one, if * you have any data that needs to transfer over you will need to manually copy it as is done below for the * campaign properties and macro buttons. */ for (Entry<GUID, Zone> entry : campaign.zones.entrySet()) { Zone copy = new Zone(entry.getValue()); zones.put(copy.getId(), copy); } campaignProperties = new CampaignProperties(campaign.campaignProperties); macroButtonProperties = new ArrayList<MacroButtonProperties>(campaign.getMacroButtonPropertiesArray()); } public GUID getId() { return id; } /** * This is a workaround to avoid the renderer and the serializer interating on the drawables at the same time */ public boolean isBeingSerialized() { return isBeingSerialized; } /** * This is a workaround to avoid the renderer and the serializer interating on the drawables at the same time */ public void setBeingSerialized(boolean isBeingSerialized) { this.isBeingSerialized = isBeingSerialized; } public List<String> getTokenTypes() { List<String> list = new ArrayList<String>(getTokenTypeMap().keySet()); Collections.sort(list); return list; } public List<String> getSightTypes() { List<String> list = new ArrayList<String>(getSightTypeMap().keySet()); Collections.sort(list); return list; } public void setSightTypes(List<SightType> typeList) { checkCampaignPropertyConversion(); Map<String, SightType> map = new HashMap<String, SightType>(); for (SightType sightType : typeList) { map.put(sightType.getName(), sightType); } campaignProperties.setSightTypeMap(map); } public List<TokenProperty> getTokenPropertyList(String tokenType) { return getTokenTypeMap().containsKey(tokenType) ? getTokenTypeMap().get(tokenType) : new ArrayList<TokenProperty>(); } public void putTokenType(String name, List<TokenProperty> propertyList) { getTokenTypeMap().put(name, propertyList); } /** * Stub that calls <code>campaignProperties.getTokenTypeMap()</code>. * * @return */ public Map<String, List<TokenProperty>> getTokenTypeMap() { checkCampaignPropertyConversion(); // TODO: Remove, for compatibility 1.3b19-1.3b20 return campaignProperties.getTokenTypeMap(); } /** * Convenience method that calls {@link #getSightTypeMap()} and returns the value for the key <code>type</code>. * * @return */ public SightType getSightType(String type) { return getSightTypeMap().get((type != null && getSightTypeMap().containsKey(type)) ? type : campaignProperties.getDefaultSightType()); } /** * Stub that calls <code>campaignProperties.getSightTypeMap()</code>. * * @return */ public Map<String, SightType> getSightTypeMap() { checkCampaignPropertyConversion(); return campaignProperties.getSightTypeMap(); } /** * Stub that calls <code>campaignProperties.getLookupTableMap()</code>. * * @return */ public Map<String, LookupTable> getLookupTableMap() { checkCampaignPropertyConversion(); // TODO: Remove, for compatibility 1.3b19-1.3b20 return campaignProperties.getLookupTableMap(); } /** * Convenience method that iterates through {@link #getLightSourcesMap()} and returns the value for the key * <code>lightSourceId</code>. * * @return */ public LightSource getLightSource(GUID lightSourceId) { for (Map<GUID, LightSource> map : getLightSourcesMap().values()) { if (map.containsKey(lightSourceId)) { return map.get(lightSourceId); } } return null; } /** * Stub that calls <code>campaignProperties.getLightSourcesMap()</code>. * * @return */ public Map<String, Map<GUID, LightSource>> getLightSourcesMap() { checkCampaignPropertyConversion(); // TODO: Remove, for compatibility 1.3b19-1.3b20 return campaignProperties.getLightSourcesMap(); } /** * Convenience method that calls {@link #getLightSourcesMap()} and returns the value for the key <code>type</code>. * * @return */ public Map<GUID, LightSource> getLightSourceMap(String type) { return getLightSourcesMap().get(type); } /** * Stub that calls <code>campaignProperties.getTokenStatesMap()</code>. * * @return */ public Map<String, BooleanTokenOverlay> getTokenStatesMap() { return campaignProperties.getTokenStatesMap(); } /** * Stub that calls <code>campaignProperties.getTokenBarsMap()</code>. * * @return */ public Map<String, BarTokenOverlay> getTokenBarsMap() { return campaignProperties.getTokenBarsMap(); } /* * public void setExportInfo(ExportInfo exportInfo) { this.exportInfo = exportInfo; } * * public ExportInfo getExportInfo() { return exportInfo; } */ public void setId(GUID id) { this.id = id; } /** * Returns an <code>ArrayList</code> of all available <code>Zone</code>s from the <code>zones</code> * <code>LinkedHashMap</code>. * * @return */ public List<Zone> getZones() { return new ArrayList<Zone>(zones.values()); } /** * Return the <code>Zone</code> with the given GUID. * * @param id * @return */ public Zone getZone(GUID id) { return zones.get(id); } /** * Create an entry for the given <code>Zone</code> in the map, using <code>zone</code>'s {@link Zone#getId()} * method. * * @param zone */ public void putZone(Zone zone) { zones.put(zone.getId(), zone); } public void removeAllZones() { zones.clear(); } public void removeZone(GUID id) { zones.remove(id); } public boolean containsAsset(Asset asset) { return containsAsset(asset.getId()); } public boolean containsAsset(MD5Key key) { for (Zone zone : zones.values()) { Set<MD5Key> assetSet = zone.getAllAssetIds(); if (assetSet.contains(key)) { return true; } } return false; } /** * Whether a server has been started using this campaign and, if so, whether the IndividualFog feature was turned on * at the time. This method returns <code>true</code> IFF a server has been started with the IF feature turned on. * * @return <code>true</code> if IF feature has ever been used; <code>false</code> otherwise */ public boolean hasUsedFogToolbar() { return hasUsedFogToolbar == null ? false : hasUsedFogToolbar.booleanValue(); } public void setHasUsedFogToolbar(boolean b) { hasUsedFogToolbar = new Boolean(b); } public void mergeCampaignProperties(CampaignProperties properties) { properties.mergeInto(campaignProperties); } public void replaceCampaignProperties(CampaignProperties properties) { campaignProperties = new CampaignProperties(properties); } /** * Get a copy of the properties. This is for persistence. Modification of the properties do not affect this campaign */ public CampaignProperties getCampaignProperties() { return new CampaignProperties(campaignProperties); } public List<MacroButtonProperties> getMacroButtonPropertiesArray() { return macroButtonProperties; } public void setMacroButtonPropertiesArray(List<MacroButtonProperties> properties) { macroButtonProperties = properties; } public void saveMacroButtonProperty(MacroButtonProperties properties) { // find the matching property in the array //TODO: hashmap? or equals()? or what? for (MacroButtonProperties prop : macroButtonProperties) { if (prop.getIndex() == properties.getIndex()) { prop.setColorKey(properties.getColorKey()); prop.setCommand(properties.getCommand()); prop.setHotKey(properties.getHotKey()); prop.setLabel(properties.getLabel()); prop.setGroup(properties.getGroup()); prop.setSortby(properties.getSortby()); prop.setFontColorKey(properties.getFontColorKey()); prop.setFontSize(properties.getFontSize()); prop.setMinWidth(properties.getMinWidth()); prop.setMaxWidth(properties.getMaxWidth()); prop.setToolTip(properties.getToolTip()); prop.setAllowPlayerEdits(properties.getAllowPlayerEdits()); TabletopTool.getFrame().getCampaignPanel().reset(); return; } } macroButtonProperties.add(properties); TabletopTool.getFrame().getCampaignPanel().reset(); } public int getMacroButtonNextIndex() { for (MacroButtonProperties prop : macroButtonProperties) { if (prop.getIndex() > macroButtonLastIndex) { macroButtonLastIndex = prop.getIndex(); } } return ++macroButtonLastIndex; } public void deleteMacroButton(MacroButtonProperties properties) { macroButtonProperties.remove(properties); TabletopTool.getFrame().getCampaignPanel().reset(); } /** * <p> * This method iterates through all Zones, TokenStates, TokenBars, and LookupTables and writes the keys into a new, * empty set. That set is the return value. * * @return */ public Set<MD5Key> getAllAssetIds() { // Maps (tokens are implicit) Set<MD5Key> assetSet = new HashSet<MD5Key>(); for (Zone zone : getZones()) { assetSet.addAll(zone.getAllAssetIds()); } // States for (BooleanTokenOverlay overlay : getCampaignProperties().getTokenStatesMap().values()) { if (overlay instanceof ImageTokenOverlay) { assetSet.add(((ImageTokenOverlay) overlay).getAssetId()); } } // Bars for (BarTokenOverlay overlay : getCampaignProperties().getTokenBarsMap().values()) { if (overlay instanceof SingleImageBarTokenOverlay) { assetSet.add(((SingleImageBarTokenOverlay) overlay).getAssetId()); } else if (overlay instanceof TwoImageBarTokenOverlay) { assetSet.add(((TwoImageBarTokenOverlay) overlay).getTopAssetId()); assetSet.add(((TwoImageBarTokenOverlay) overlay).getBottomAssetId()); } else if (overlay instanceof MultipleImageBarTokenOverlay) { assetSet.addAll(Arrays.asList(((MultipleImageBarTokenOverlay) overlay).getAssetIds())); } // endif } // Tables for (LookupTable table : getCampaignProperties().getLookupTableMap().values()) { assetSet.addAll(table.getAllAssetIds()); } return assetSet; } /** @return Getter for initiativeOwnerPermissions */ public boolean isInitiativeOwnerPermissions() { return campaignProperties != null ? campaignProperties.isInitiativeOwnerPermissions() : false; } /** * @param initiativeOwnerPermissions * Setter for initiativeOwnerPermissions */ public void setInitiativeOwnerPermissions(boolean initiativeOwnerPermissions) { campaignProperties.setInitiativeOwnerPermissions(initiativeOwnerPermissions); } /** @return Getter for initiativeMovementLock */ public boolean isInitiativeMovementLock() { return campaignProperties != null ? campaignProperties.isInitiativeMovementLock() : false; } /** * @param initiativeMovementLock * Setter for initiativeMovementLock */ public void setInitiativeMovementLock(boolean initiativeMovementLock) { campaignProperties.setInitiativeMovementLock(initiativeMovementLock); } /** @return Getter for characterSheets */ public Map<String, String> getCharacterSheets() { return getCampaignProperties().getCharacterSheets(); } public ExportDialog getExportDialog() { if (exportDialog == null) { try { exportDialog = new ExportDialog(); } catch (Exception e) { return null; } } // TODO: Ugh, what a kludge. This needs to be refactored so that the settings are separate from the dialog // and easily accessible from elsewhere. I want separate XML files in the .cmpgn file eventually so that // will be a good time to do this. exportDialog.setExportSettings(exportSettings); exportDialog.setExportLocation(exportLocation); return exportDialog; } public void setExportDialog(ExportDialog d) { exportDialog = d; exportSettings = d.getExportSettings(); exportLocation = d.getExportLocation(); } }