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