1 /* $Id$ */ 2 /*************************************************************************** 3 * (C) Copyright 2003 - Marauroa * 4 *************************************************************************** 5 *************************************************************************** 6 * * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2 of the License, or * 10 * (at your option) any later version. * 11 * * 12 ***************************************************************************/ 13 package games.stendhal.client; 14 15 import java.awt.Color; 16 import java.awt.Composite; 17 import java.io.IOException; 18 import java.io.InputStream; 19 import java.net.URL; 20 import java.util.ArrayList; 21 import java.util.List; 22 23 import org.apache.log4j.Logger; 24 25 import games.stendhal.client.sprite.DataLoader; 26 import games.stendhal.client.sprite.FlippedSprite; 27 import games.stendhal.client.sprite.Sprite; 28 import games.stendhal.client.sprite.SpriteStore; 29 import games.stendhal.client.sprite.SpriteTileset; 30 import games.stendhal.client.sprite.Tileset; 31 import games.stendhal.client.sprite.TilesetAnimationMap; 32 import games.stendhal.client.sprite.TilesetGroupAnimationMap; 33 import games.stendhal.common.tiled.TileSetDefinition; 34 import marauroa.common.net.InputSerializer; 35 36 /** It is class to get tiles from the tileset. */ 37 class TileStore implements Tileset { 38 /** Tiled reserves 3 highest bits for tile flipping. */ 39 private static final int TILE_FLIP_MASK = 0xE0000000; 40 /** 41 * Tiled reserves 3 highest bits for tile flipping. Mask for getting the 42 * actual tile number. 43 */ 44 private static final int TILE_ID_MASK = 0xFFFFFFFF ^ TILE_FLIP_MASK; 45 /** the logger instance. */ 46 private static final Logger logger = Logger.getLogger(TileStore.class); 47 48 /** 49 * The base directory for tileset resources. 50 */ 51 private static final String baseFolder = getResourceBase(); 52 53 /** 54 * The tileset animation map. 55 */ 56 private static final TilesetGroupAnimationMap animationMap = createAnimationMap(); 57 58 /** 59 * A cache of loaded tilesets. 60 */ 61 private static final MemoryCache<String, Tileset> tilesetsLoaded = new MemoryCache<String, Tileset>(); 62 63 /** 64 * The sprite store. 65 */ 66 private final SpriteStore store; 67 68 /** 69 * The tile sprites. 70 */ 71 private final ArrayList<Sprite> tiles; 72 /** Tilesets waiting to be added to the store. */ 73 private final List<TileSetDefinition> tilesets = new ArrayList<TileSetDefinition>(); 74 /** 75 * <code>true</code>, if the store has been successfully validated, 76 * otherwise <code>false</code>. 77 */ 78 private boolean validated; 79 80 /** 81 * Get the animation map. 82 * 83 * @return animation map 84 */ getAnimationMap()85 public static TilesetGroupAnimationMap getAnimationMap() { 86 return animationMap; 87 } 88 89 /** 90 * Create a tile store. 91 */ TileStore()92 public TileStore() { 93 this(SpriteStore.get()); 94 } 95 96 /** 97 * Create a tile store with a specific sprite store. 98 * 99 * @param store 100 * A sprite store. 101 */ TileStore(final SpriteStore store)102 private TileStore(final SpriteStore store) { 103 this.store = store; 104 105 tiles = new ArrayList<Sprite>(); 106 tiles.add(store.getEmptySprite()); 107 } 108 109 // 110 // TileStore 111 // 112 113 /** 114 * Add a tileset. 115 * 116 * @param tsdef 117 * The tileset definition. 118 * @param color Color for modifying the tileset image, or <code>null</code> 119 * @param blend Blend mode for applying the adjustment color, or 120 * <code>null</code> 121 */ add(final TileSetDefinition tsdef, Color color, Composite blend)122 private void add(final TileSetDefinition tsdef, Color color, Composite blend) { 123 String ref = tsdef.getSource(); 124 final int baseindex = tsdef.getFirstGid(); 125 126 /* 127 * Strip off leading path info TODO: Remove this earlier in the stage 128 * (server side?) 129 */ 130 if (ref.startsWith("../../")) { 131 ref = ref.substring(6); 132 } 133 134 /* 135 * Make sure we are the right size 136 */ 137 final int mapsize = tiles.size(); 138 139 if (mapsize > baseindex) { 140 logger.debug("Tileset base index mismatch (" + mapsize + " > " 141 + baseindex + "): " + ref); 142 for (int i = baseindex; i < mapsize; i++) { 143 tiles.remove(baseindex); 144 } 145 } else if (mapsize < baseindex) { 146 logger.debug("Tileset base index mismatch (" + mapsize + " < " 147 + baseindex + "): " + ref); 148 /* 149 * Pad missing entries 150 */ 151 for (int i = mapsize; i < baseindex; i++) { 152 tiles.add(null); 153 } 154 } 155 156 String realRef; 157 if ((color != null) && (blend != null)) { 158 realRef = store.createModifiedRef(ref, color, blend); 159 } else { 160 realRef = ref; 161 } 162 Tileset tileset = tilesetsLoaded.get(realRef); 163 164 if (tileset == null) { 165 tileset = new SpriteTileset(store, baseFolder + ref, color, blend); 166 tilesetsLoaded.put(realRef, tileset); 167 } 168 169 final int size = tileset.getSize(); 170 171 tiles.ensureCapacity(baseindex + size); 172 173 for (int i = 0; i < size; i++) { 174 tiles.add(tileset.getSprite(i)); 175 } 176 177 /* 178 * Override the animated tiles (if any) 179 */ 180 final TilesetAnimationMap tsam = animationMap.get(ref); 181 182 if (tsam != null) { 183 for (int i = 0; i < size; i++) { 184 final Sprite sprite = tsam.getSprite(tileset, i); 185 186 if (sprite != null) { 187 tiles.set(baseindex + i, sprite); 188 } 189 } 190 } 191 } 192 193 /** 194 * Add tilesets. The store will require validating afterwards. 195 * 196 * @param in 197 * The object stream. 198 * <code>null</code> 199 * 200 * @throws IOException 201 * @throws ClassNotFoundException 202 */ addTilesets(final InputSerializer in)203 void addTilesets(final InputSerializer in) throws IOException, 204 ClassNotFoundException { 205 final int amount = in.readInt(); 206 207 for (int i = 0; i < amount; i++) { 208 final TileSetDefinition tileset = (TileSetDefinition) in.readObject(new TileSetDefinition(null, null, -1)); 209 tilesets.add(tileset); 210 } 211 } 212 /** 213 * Try finishing the tile store withan adjustment color and blend mode for 214 * the tilesets. 215 * 216 * @param color Color for adjusting the tileset, or <code>null</code> 217 * @param blend blend mode for applying the adjustment color, or 218 * @return <code>true</code> if the store has been validated successfully, 219 * <code>false</code> otherwise 220 */ validate(final Color color, final Composite blend)221 boolean validate(final Color color, final Composite blend) { 222 if (!validated) { 223 if (!tilesets.isEmpty()) { 224 for (TileSetDefinition def : tilesets) { 225 add(def, color, blend); 226 } 227 tilesets.clear(); 228 validated = true; 229 return true; 230 } 231 return false; 232 } 233 return true; 234 } 235 236 /** 237 * Create the tileset animation map. 238 * 239 * @return A tileset animation map. 240 */ createAnimationMap()241 private static TilesetGroupAnimationMap createAnimationMap() { 242 final TilesetGroupAnimationMap map = new TilesetGroupAnimationMap(); 243 244 final URL url = DataLoader.getResource(baseFolder + "tileset/animation.seq"); 245 246 if (url != null) { 247 try { 248 final InputStream in = url.openStream(); 249 250 try { 251 map.load(in); 252 } finally { 253 in.close(); 254 } 255 } catch (final IOException ex) { 256 logger.error("Error loading tileset animation map", ex); 257 } 258 } 259 260 return map; 261 } 262 263 /** 264 * Get the base directory for tileset resources. 265 * 266 * Hack: Read the tileset directly from tiled/tileset if started from an 267 * IDE. 268 * @return the / separated url to the resource 269 */ getResourceBase()270 private static String getResourceBase() { 271 String path = "data/"; 272 273 if (DataLoader.getResource("tiled/tileset/README") != null) { 274 logger.debug("Developing mode, loading tileset from tiled/tileset instead of data/tileset"); 275 path = "tiled/"; 276 } 277 278 return path; 279 } 280 281 // 282 // Tileset 283 // 284 285 /** 286 * Get the number of tiles. 287 * 288 * @return The number of tiles. 289 */ 290 @Override getSize()291 public int getSize() { 292 return tiles.size(); 293 } 294 295 /** 296 * Get the sprite for an index tile of the tileset. 297 * 298 * @param index 299 * The index with-in the tileset. 300 * 301 * @return A sprite, or <code>null</code> if no mapped sprite. 302 */ 303 @Override getSprite(int index)304 public Sprite getSprite(int index) { 305 int flip = index & TILE_FLIP_MASK; 306 index &= TILE_ID_MASK; 307 if (index >= tiles.size() || index < 0) { 308 logger.error("Accessing unassigned sprite at: " + index); 309 return store.getEmptySprite(); 310 } 311 312 Sprite sprite = tiles.get(index); 313 314 if (sprite == null) { 315 logger.error("Accessing unassigned sprite at: " + index); 316 return store.getEmptySprite(); 317 } 318 319 if (flip != 0) { 320 sprite = new FlippedSprite(sprite, flip); 321 } 322 323 return sprite; 324 } 325 } 326