diff --git a/AndorsTrail/res/xml/template.tmx b/AndorsTrail/res/xml/template.tmx index 45836efb7..1f7e672b9 100644 --- a/AndorsTrail/res/xml/template.tmx +++ b/AndorsTrail/res/xml/template.tmx @@ -1,125 +1,124 @@ - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -144,4 +143,5 @@ + diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java index 2bb46e9c3..00a9ee139 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/AndorsTrailApplication.java @@ -18,7 +18,7 @@ public final class AndorsTrailApplication extends Application { public static final boolean DEVELOPMENT_VALIDATEDATA = true; public static final boolean DEVELOPMENT_DEBUGMESSAGES = true; public static final boolean DEVELOPMENT_INCOMPATIBLE_SAVEGAMES = DEVELOPMENT_DEBUGRESOURCES; - public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? 999 : 35; + public static final int CURRENT_VERSION = DEVELOPMENT_INCOMPATIBLE_SAVEGAMES ? 999 : 36; public static final String CURRENT_VERSION_DISPLAY = "0.7.0dev"; private final AndorsTrailPreferences preferences = new AndorsTrailPreferences(); diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java index fcd60d5ee..44e615078 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/activity/MainActivity.java @@ -121,6 +121,7 @@ public final class MainActivity extends Activity implements PlayerMovementListen break; case INTENTREQUEST_CONVERSATION: MovementController.refreshMonsterAggressiveness(world.model.currentMap, world.model.player); + controllers.mapController.applyCurrentMapReplacements(getResources(), true); break; case INTENTREQUEST_SAVEGAME: if (resultCode != Activity.RESULT_OK) break; @@ -138,15 +139,26 @@ public final class MainActivity extends Activity implements PlayerMovementListen final Player player = world.model.player; return Savegames.saveWorld(world, this, slot, getString(R.string.savegame_currenthero_displayinfo, player.getLevel(), player.getTotalExperience(), player.getGold())); } - + + @Override + protected void onStart() { + super.onStart(); + if (!AndorsTrailApplication.getApplicationFromActivity(this).getWorldSetup().isSceneReady) return; + subscribeToModelChanges(); + } + + @Override + protected void onStop() { + super.onStop(); + unsubscribeFromModel(); + } + @Override protected void onPause() { super.onPause(); controllers.gameRoundController.pause(); controllers.movementController.stopMovement(); - unsubscribeFromModel(); - save(Savegames.SLOT_QUICKSAVE); } @@ -155,8 +167,6 @@ public final class MainActivity extends Activity implements PlayerMovementListen super.onResume(); if (!AndorsTrailApplication.getApplicationFromActivity(this).getWorldSetup().isSceneReady) return; - subscribeToModelChanges(); - controllers.gameRoundController.resume(); if (world.model.uiSelections.isInCombat) { diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ConversationController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ConversationController.java index 047703952..390b31479 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ConversationController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ConversationController.java @@ -20,7 +20,6 @@ import com.gpl.rpg.AndorsTrail.model.item.Loot; import com.gpl.rpg.AndorsTrail.model.quest.QuestLogEntry; import com.gpl.rpg.AndorsTrail.model.quest.QuestProgress; import com.gpl.rpg.AndorsTrail.util.ConstRange; -import com.gpl.rpg.AndorsTrail.util.Coord; import java.util.ArrayList; diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ItemController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ItemController.java index 16a991f41..f700a6bfe 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ItemController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ItemController.java @@ -6,7 +6,6 @@ import java.util.Collection; import com.gpl.rpg.AndorsTrail.AndorsTrailPreferences; import com.gpl.rpg.AndorsTrail.context.ControllerContext; import com.gpl.rpg.AndorsTrail.context.WorldContext; -import com.gpl.rpg.AndorsTrail.controller.listeners.LootBagListeners; import com.gpl.rpg.AndorsTrail.controller.listeners.QuickSlotListeners; import com.gpl.rpg.AndorsTrail.model.ModelContainer; import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection; @@ -23,8 +22,7 @@ public final class ItemController { private final ControllerContext controllers; private final WorldContext world; - public final QuickSlotListeners quickSlotListeners = new QuickSlotListeners(); - public final LootBagListeners lootBagListeners = new LootBagListeners(); + public final QuickSlotListeners quickSlotListeners = new QuickSlotListeners(); public ItemController(ControllerContext controllers, WorldContext world) { this.controllers = controllers; @@ -213,7 +211,7 @@ public final class ItemController { if (loot.hasItems()) return false; world.model.currentMap.removeGroundLoot(loot); - lootBagListeners.onLootBagRemoved(world.model.currentMap, loot.position); + controllers.mapController.mapLayoutListeners.onLootBagRemoved(world.model.currentMap, loot.position); return true; // The bag was removed. } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java index 3541ff256..c2aa9e4ba 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MapController.java @@ -1,13 +1,17 @@ package com.gpl.rpg.AndorsTrail.controller; +import android.content.res.Resources; import com.gpl.rpg.AndorsTrail.context.ControllerContext; import com.gpl.rpg.AndorsTrail.context.WorldContext; +import com.gpl.rpg.AndorsTrail.controller.listeners.MapLayoutListeners; import com.gpl.rpg.AndorsTrail.controller.listeners.WorldEventListeners; import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection; import com.gpl.rpg.AndorsTrail.model.actor.Monster; import com.gpl.rpg.AndorsTrail.model.actor.Player; -import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; +import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap; import com.gpl.rpg.AndorsTrail.model.map.MapObject; +import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; +import com.gpl.rpg.AndorsTrail.model.map.ReplaceableMapSection; import com.gpl.rpg.AndorsTrail.util.Coord; public final class MapController { @@ -15,6 +19,7 @@ public final class MapController { private final ControllerContext controllers; private final WorldContext world; public final WorldEventListeners worldEventListeners = new WorldEventListeners(); + public final MapLayoutListeners mapLayoutListeners = new MapLayoutListeners(); public MapController(ControllerContext controllers, WorldContext world) { this.controllers = controllers; @@ -106,4 +111,31 @@ public final class MapController { m.resetTemporaryData(); } } + + public void applyCurrentMapReplacements(final Resources res, boolean updateWorldmap) { + if (!applyReplacements(world.model.currentMap, world.model.currentTileMap)) return; + world.maps.worldMapRequiresUpdate = true; + + if (!updateWorldmap) return; + WorldMapController.updateWorldMap(world, res); + mapLayoutListeners.onMapTilesChanged(world.model.currentMap, world.model.currentTileMap); + } + + private boolean applyReplacements(PredefinedMap map, LayeredTileMap tileMap) { + boolean hasUpdated = false; + if (tileMap.replacements != null) { + for(ReplaceableMapSection replacement : tileMap.replacements) { + if (replacement.isApplied) continue; + if (!satisfiesCondition(replacement)) continue; + tileMap.applyReplacement(replacement); + hasUpdated = true; + } + } + map.lastSeenLayoutHash = tileMap.getCurrentLayoutHash(); + return hasUpdated; + } + + public boolean satisfiesCondition(ReplaceableMapSection replacement) { + return world.model.player.hasExactQuestProgress(replacement.requireQuestStage); + } } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MovementController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MovementController.java index 4532bd2c7..c335cce29 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MovementController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/MovementController.java @@ -88,6 +88,7 @@ public final class MovementController implements TimedMessageTask.Callback { private void playerVisitsMapFirstTime(PredefinedMap m) { m.reset(); m.createAllContainerLoot(); + world.maps.worldMapRequiresUpdate = true; } public void prepareMapAsCurrentMap(PredefinedMap newMap, Resources res, boolean spawnMonsters) { @@ -99,10 +100,12 @@ public final class MovementController implements TimedMessageTask.Callback { controllers.monsterSpawnController.spawnAll(newMap, model.currentTileMap); } } + controllers.mapController.applyCurrentMapReplacements(res, false); newMap.visited = true; moveBlockedActors(newMap, model.currentTileMap); refreshMonsterAggressiveness(newMap, model.player); controllers.effectController.updateSplatters(newMap); + WorldMapController.updateWorldMap(world, res); } private boolean mayMovePlayer() { @@ -294,8 +297,6 @@ public final class MovementController implements TimedMessageTask.Callback { world.model.currentTileMap = mapTiles; world.tileManager.currentMapTiles = cachedTiles; world.tileManager.cacheAdjacentMaps(res, world, nextMap); - - WorldMapController.updateWorldMap(world, nextMap, mapTiles, cachedTiles, res); } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/WorldMapController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/WorldMapController.java index d64db9b75..c9d82cc2d 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/WorldMapController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/WorldMapController.java @@ -4,8 +4,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.util.Collection; -import java.util.HashSet; +import java.util.*; import android.content.Context; import android.content.Intent; @@ -22,10 +21,7 @@ import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; import com.gpl.rpg.AndorsTrail.R; import com.gpl.rpg.AndorsTrail.activity.DisplayWorldMapActivity; import com.gpl.rpg.AndorsTrail.context.WorldContext; -import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap; -import com.gpl.rpg.AndorsTrail.model.map.MapLayer; -import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; -import com.gpl.rpg.AndorsTrail.model.map.WorldMapSegment; +import com.gpl.rpg.AndorsTrail.model.map.*; import com.gpl.rpg.AndorsTrail.model.map.WorldMapSegment.NamedWorldMapArea; import com.gpl.rpg.AndorsTrail.model.map.WorldMapSegment.WorldMapSegmentMap; import com.gpl.rpg.AndorsTrail.resource.tiles.TileCollection; @@ -38,14 +34,22 @@ public final class WorldMapController { private static final int WORLDMAP_SCREENSHOT_TILESIZE = 8; public static final int WORLDMAP_DISPLAY_TILESIZE = WORLDMAP_SCREENSHOT_TILESIZE; - - public static void updateWorldMap(final WorldContext world, final PredefinedMap map, final LayeredTileMap mapTiles, final TileCollection cachedTiles, final Resources res) { - + + public static void updateWorldMap(final WorldContext world, final Resources res) { + updateWorldMap(world, world.model.currentMap, world.model.currentTileMap, world.tileManager.currentMapTiles, res); + } + + private static void updateWorldMap( + final WorldContext world, + final PredefinedMap map, + final LayeredTileMap mapTiles, + final TileCollection cachedTiles, + final Resources res) { final String worldMapSegmentName = world.maps.getWorldMapSegmentNameForMap(map.name); if (worldMapSegmentName == null) return; - - if (!shouldUpdateWorldMap(map, worldMapSegmentName)) return; - + + if (!shouldUpdateWorldMap(map, worldMapSegmentName, world.maps.worldMapRequiresUpdate)) return; + (new AsyncTask() { @Override protected Void doInBackground(Void... arg0) { @@ -53,24 +57,27 @@ public final class WorldMapController { try { updateCachedBitmap(map, renderer); updateWorldMapSegment(res, world, worldMapSegmentName); + world.maps.worldMapRequiresUpdate = false; + if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) { + L.log("WorldMapController: Updated worldmap segment " + worldMapSegmentName + " for map " + map.name); + } } catch (IOException e) { L.log("Error creating worldmap file for map " + map.name + " : " + e.toString()); } - return null; + return null; } }).execute(); } - private static boolean shouldUpdateWorldMap(PredefinedMap map, String worldMapSegmentName) { + private static boolean shouldUpdateWorldMap(PredefinedMap map, String worldMapSegmentName, boolean forceUpdate) { + if (forceUpdate) return true; if (!map.visited) return true; File file = getFileForMap(map); if (!file.exists()) return true; - if (map.lastVisitVersion < AndorsTrailApplication.CURRENT_VERSION) return true; - file = getCombinedWorldMapFile(worldMapSegmentName); if (!file.exists()) return true; - + return false; } @@ -78,9 +85,7 @@ public final class WorldMapController { ensureWorldmapDirectoryExists(); File file = getFileForMap(map); - if (file.exists()) { - if (map.lastVisitVersion == AndorsTrailApplication.CURRENT_VERSION) return; - } + if (file.exists()) return; Bitmap image = renderer.drawMap(); FileOutputStream fos = new FileOutputStream(file); @@ -88,6 +93,17 @@ public final class WorldMapController { fos.flush(); fos.close(); image.recycle(); + L.log("WorldMapController: Wrote " + file.getAbsolutePath()); + + // Before we had the hash as part of the filename, the png files were just named [mapname].png + // let's just remove those old files since the new hash-based filenames contain the same data. + if (map.lastSeenLayoutHash.length() > 0) { + File oldFile = getFileForMap(map.name); + if (!oldFile.getName().equalsIgnoreCase(file.getName())) { + oldFile.delete(); + L.log("WorldMapController: Deleted " + oldFile.getAbsolutePath()); + } + } } private static final class MapRenderer { @@ -114,15 +130,15 @@ public final class WorldMapController { canvas.scale(scale, scale); synchronized (cachedTiles) { - drawMapLayer(canvas, mapTiles.layers[LayeredTileMap.LAYER_GROUND]); - tryDrawMapLayer(canvas, LayeredTileMap.LAYER_OBJECTS); - tryDrawMapLayer(canvas, LayeredTileMap.LAYER_ABOVE); + drawMapLayer(canvas, mapTiles.currentLayout.layerGround); + tryDrawMapLayer(canvas, mapTiles.currentLayout.layerObjects); + tryDrawMapLayer(canvas, mapTiles.currentLayout.layerAbove); } return image; } - - private void tryDrawMapLayer(Canvas canvas, final int layerIndex) { - if (mapTiles.layers.length > layerIndex) drawMapLayer(canvas, mapTiles.layers[layerIndex]); + + private void tryDrawMapLayer(Canvas canvas, final MapLayer layer) { + if (layer != null) drawMapLayer(canvas, layer); } private void drawMapLayer(Canvas canvas, final MapLayer layer) { @@ -144,11 +160,14 @@ public final class WorldMapController { if (!dir.exists()) dir.mkdir(); dir = new File(dir, Constants.FILENAME_WORLDMAP_DIRECTORY); if (!dir.exists()) dir.mkdir(); - + File noMediaFile = new File(dir, ".nomedia"); if (!noMediaFile.exists()) noMediaFile.createNewFile(); } - private static File getFileForMap(PredefinedMap map) { return getFileForMap(map.name); } + private static File getFileForMap(PredefinedMap map) { + if (map.lastSeenLayoutHash.length() <= 0) return getFileForMap(map.name); + else return getFileForMap(map.name + "." + map.lastSeenLayoutHash); + } private static File getFileForMap(String mapName) { return new File(getWorldmapDirectory(), mapName + ".png"); } @@ -161,19 +180,19 @@ public final class WorldMapController { return new File(getWorldmapDirectory(), Constants.FILENAME_WORLDMAP_HTMLFILE_PREFIX + segmentName + Constants.FILENAME_WORLDMAP_HTMLFILE_SUFFIX); } - private static boolean shouldDisplayMapOnWorldmap(String mapName) { - File f = WorldMapController.getFileForMap(mapName); - return f.exists(); - } private static String getWorldMapSegmentAsHtml(Resources res, WorldContext world, String segmentName) { WorldMapSegment segment = world.maps.worldMapSegments.get(segmentName); - Collection displayedMapNames = new HashSet(); + Map displayedMapFilenamesPerMapName = new HashMap(segment.maps.size()); Coord offsetWorldmapTo = new Coord(999999, 999999); for (WorldMapSegmentMap map : segment.maps.values()) { - if (!shouldDisplayMapOnWorldmap(map.mapName)) continue; + PredefinedMap predefinedMap = world.maps.findPredefinedMap(map.mapName); + if (predefinedMap == null) continue; + if (!predefinedMap.visited) continue; + File f = WorldMapController.getFileForMap(predefinedMap); + if (!f.exists()) continue; + displayedMapFilenamesPerMapName.put(map.mapName, f); - displayedMapNames.add(map.mapName); offsetWorldmapTo.x = Math.min(offsetWorldmapTo.x, map.worldPosition.x); offsetWorldmapTo.y = Math.min(offsetWorldmapTo.y, map.worldPosition.y); } @@ -182,8 +201,8 @@ public final class WorldMapController { StringBuilder mapsAsHtml = new StringBuilder(1000); for (WorldMapSegmentMap segmentMap : segment.maps.values()) { - File f = WorldMapController.getFileForMap(segmentMap.mapName); - if (!f.exists()) continue; + File f = displayedMapFilenamesPerMapName.get(segmentMap.mapName); + if (f == null) continue; Size size = getMapSize(segmentMap, world); mapsAsHtml @@ -212,7 +231,7 @@ public final class WorldMapController { StringBuilder namedAreasAsHtml = new StringBuilder(500); for (NamedWorldMapArea area : segment.namedAreas.values()) { - CoordRect r = determineNamedAreaBoundary(area, segment, world, displayedMapNames); + CoordRect r = determineNamedAreaBoundary(area, segment, world, displayedMapFilenamesPerMapName.keySet()); if (r == null) continue; namedAreasAsHtml .append("
displayedMapNames) { + private static CoordRect determineNamedAreaBoundary(NamedWorldMapArea area, WorldMapSegment segment, WorldContext world, Set displayedMapNames) { Coord topLeft = null; Coord bottomRight = null; diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/LootBagListeners.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/LootBagListeners.java deleted file mode 100644 index 440908eac..000000000 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/LootBagListeners.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.gpl.rpg.AndorsTrail.controller.listeners; - -import com.gpl.rpg.AndorsTrail.util.ListOfListeners; -import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; -import com.gpl.rpg.AndorsTrail.util.Coord; - -public final class LootBagListeners extends ListOfListeners implements LootBagListener { - - private final Function2 onLootBagCreated = new Function2() { - @Override public void call(LootBagListener listener, PredefinedMap map, Coord p) { listener.onLootBagCreated(map, p); } - }; - - private final Function2 onLootBagRemoved = new Function2() { - @Override public void call(LootBagListener listener, PredefinedMap map, Coord p) { listener.onLootBagRemoved(map, p); } - }; - - @Override - public void onLootBagCreated(PredefinedMap map, Coord p) { - callAllListeners(this.onLootBagCreated, map, p); - } - - @Override - public void onLootBagRemoved(PredefinedMap map, Coord p) { - callAllListeners(this.onLootBagRemoved, map, p); - } -} diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/LootBagListener.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/MapLayoutListener.java similarity index 61% rename from AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/LootBagListener.java rename to AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/MapLayoutListener.java index 816c9129e..897e6eb9a 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/LootBagListener.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/MapLayoutListener.java @@ -1,9 +1,11 @@ package com.gpl.rpg.AndorsTrail.controller.listeners; +import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap; import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; import com.gpl.rpg.AndorsTrail.util.Coord; -public interface LootBagListener { +public interface MapLayoutListener { void onLootBagCreated(PredefinedMap map, Coord p); void onLootBagRemoved(PredefinedMap map, Coord p); + void onMapTilesChanged(PredefinedMap map, LayeredTileMap tileMap); } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/MapLayoutListeners.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/MapLayoutListeners.java new file mode 100644 index 000000000..946c36d05 --- /dev/null +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/listeners/MapLayoutListeners.java @@ -0,0 +1,36 @@ +package com.gpl.rpg.AndorsTrail.controller.listeners; + +import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap; +import com.gpl.rpg.AndorsTrail.util.ListOfListeners; +import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap; +import com.gpl.rpg.AndorsTrail.util.Coord; + +public final class MapLayoutListeners extends ListOfListeners implements MapLayoutListener { + + private final Function2 onLootBagCreated = new Function2() { + @Override public void call(MapLayoutListener listener, PredefinedMap map, Coord p) { listener.onLootBagCreated(map, p); } + }; + + private final Function2 onLootBagRemoved = new Function2() { + @Override public void call(MapLayoutListener listener, PredefinedMap map, Coord p) { listener.onLootBagRemoved(map, p); } + }; + + private final Function2 onMapTilesChanged = new Function2() { + @Override public void call(MapLayoutListener listener, PredefinedMap map, LayeredTileMap tileMap) { listener.onMapTilesChanged(map, tileMap); } + }; + + @Override + public void onLootBagCreated(PredefinedMap map, Coord p) { + callAllListeners(this.onLootBagCreated, map, p); + } + + @Override + public void onLootBagRemoved(PredefinedMap map, Coord p) { + callAllListeners(this.onLootBagRemoved, map, p); + } + + @Override + public void onMapTilesChanged(PredefinedMap map, LayeredTileMap tileMap) { + callAllListeners(this.onMapTilesChanged, map, tileMap); + } +} diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/LayeredTileMap.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/LayeredTileMap.java index 851826ca3..86ee8d55c 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/LayeredTileMap.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/LayeredTileMap.java @@ -11,46 +11,38 @@ import com.gpl.rpg.AndorsTrail.util.CoordRect; import com.gpl.rpg.AndorsTrail.util.Size; public final class LayeredTileMap { - public static final int LAYER_GROUND = 0; - public static final int LAYER_OBJECTS = 1; - public static final int LAYER_ABOVE = 2; - private static final ColorFilter colorFilterBlack20 = createGrayScaleColorFilter(0.2f); private static final ColorFilter colorFilterBlack40 = createGrayScaleColorFilter(0.4f); private static final ColorFilter colorFilterBlack60 = createGrayScaleColorFilter(0.6f); private static final ColorFilter colorFilterBlack80 = createGrayScaleColorFilter(0.8f); private final Size size; - public final MapLayer[] layers; - public final Collection usedTileIDs; - private final boolean[][] isWalkable; + public final MapSection currentLayout; + private String currentLayoutHash; + public final ReplaceableMapSection[] replacements; public final String colorFilter; - + public final Collection usedTileIDs; public LayeredTileMap( Size size, - MapLayer[] layers, - boolean[][] isWalkable, - Collection usedTileIDs, - String colorFilter) { - assert(size.width > 0); - assert(size.height > 0); - assert(layers.length == 3); - assert(isWalkable.length == size.width); - assert(isWalkable[0].length == size.height); + MapSection layout, + ReplaceableMapSection[] replacements, + String colorFilter, + Collection usedTileIDs) { this.size = size; - this.layers = layers; - this.usedTileIDs = usedTileIDs; - this.isWalkable = isWalkable; + this.currentLayout = layout; + this.replacements = replacements; this.colorFilter = colorFilter; + this.usedTileIDs = usedTileIDs; + this.currentLayoutHash = currentLayout.calculateHash(); } public final boolean isWalkable(final Coord p) { if (isOutside(p.x, p.y)) return false; - return isWalkable[p.x][p.y]; + return currentLayout.isWalkable[p.x][p.y]; } public final boolean isWalkable(final int x, final int y) { if (isOutside(x, y)) return false; - return isWalkable[x][y]; + return currentLayout.isWalkable[x][y]; } public final boolean isWalkable(final CoordRect p) { for (int y = 0; y < p.size.height; ++y) { @@ -98,4 +90,13 @@ public final class LayeredTileMap { 0.00f, 0.00f, 0.00f, 1.0f, 0.0f }); } + + public String getCurrentLayoutHash() { + return currentLayoutHash; + } + + public void applyReplacement(ReplaceableMapSection replacement) { + replacement.apply(currentLayout); + currentLayoutHash = currentLayout.calculateHash(); + } } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java index d56649a8a..ded0dcba7 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapCollection.java @@ -17,6 +17,7 @@ import java.util.List; public final class MapCollection { private final HashMap predefinedMaps = new HashMap(); public final HashMap worldMapSegments = new HashMap(); + public boolean worldMapRequiresUpdate = true; public MapCollection() {} @@ -43,6 +44,7 @@ public final class MapCollection { for (PredefinedMap m : getAllMaps()) { m.reset(); } + worldMapRequiresUpdate = true; } public String getWorldMapSegmentNameForMap(String mapName) { diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapLayer.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapLayer.java index 906ae4dc3..0d2dbfa98 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapLayer.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapLayer.java @@ -4,27 +4,12 @@ import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.Size; public final class MapLayer { - private final Size size; public final int[][] tiles; public MapLayer(Size size) { - this.size = size; tiles = new int[size.width][size.height]; } public void setTile(int type, int x, int y) { tiles[x][y] = type; } - public void setTile(int type, Coord p) { - setTile(type, p.x, p.y); - } - public final boolean isOutside(int x, int y) { - if (x < 0) return true; - if (y < 0) return true; - if (x >= size.width) return true; - if (y >= size.height) return true; - return false; - } - public final boolean isOutside(Coord p) { - return isOutside(p.x, p.y); - } } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapSection.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapSection.java new file mode 100644 index 000000000..b1b07e1e1 --- /dev/null +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/MapSection.java @@ -0,0 +1,54 @@ +package com.gpl.rpg.AndorsTrail.model.map; + +import com.gpl.rpg.AndorsTrail.util.ByteUtils; +import com.gpl.rpg.AndorsTrail.util.CoordRect; + +public final class MapSection { + public final MapLayer layerGround; + public final MapLayer layerObjects; + public final MapLayer layerAbove; + public final boolean[][] isWalkable; + private final byte[] layoutHash; + + public MapSection( + MapLayer layerGround, + MapLayer layerObjects, + MapLayer layerAbove, + boolean[][] isWalkable, + byte[] layoutHash) { + this.layerGround = layerGround; + this.layerObjects = layerObjects; + this.layerAbove = layerAbove; + this.isWalkable = isWalkable; + this.layoutHash = layoutHash; + } + + public void replaceLayerContentsWith(final MapSection replaceLayersWith, final CoordRect replacementArea) { + replaceTileLayerSection(layerGround, replaceLayersWith.layerGround, replacementArea); + replaceTileLayerSection(layerObjects, replaceLayersWith.layerObjects, replacementArea); + replaceTileLayerSection(layerAbove, replaceLayersWith.layerAbove, replacementArea); + if (replaceLayersWith.isWalkable != null) { + final int dy = replacementArea.topLeft.y; + final int height = replacementArea.size.height; + for (int sx = 0, dx = replacementArea.topLeft.x; sx < replacementArea.size.width; ++sx, ++dx) { + System.arraycopy(replaceLayersWith.isWalkable[sx], 0, isWalkable[dx], dy, height); + } + } + for(int i = 0; i < layoutHash.length; ++i) { + layoutHash[i] ^= replaceLayersWith.layoutHash[i]; + } + } + + private static void replaceTileLayerSection(MapLayer dest, MapLayer src, CoordRect area) { + if (src == null) return; + final int dy = area.topLeft.y; + final int height = area.size.height; + for (int sx = 0, dx = area.topLeft.x; sx < area.size.width; ++sx, ++dx) { + System.arraycopy(src.tiles[sx], 0, dest.tiles[dx], dy, height); + } + } + + public String calculateHash() { + return ByteUtils.toHexString(layoutHash, 4); + } +} diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java index fb5568da8..d3e322d6f 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/PredefinedMap.java @@ -29,7 +29,7 @@ public final class PredefinedMap { public final ArrayList groundBags = new ArrayList(); public boolean visited = false; public long lastVisitTime = VISIT_RESET; - public int lastVisitVersion = 0; + public String lastSeenLayoutHash = ""; private final boolean isOutdoors; public final ArrayList splatters = new ArrayList(); @@ -136,7 +136,7 @@ public final class PredefinedMap { } groundBags.clear(); visited = false; - lastVisitVersion = 0; + lastSeenLayoutHash = ""; } public boolean isRecentlyVisited() { @@ -145,7 +145,6 @@ public final class PredefinedMap { } public void updateLastVisitTime() { lastVisitTime = System.currentTimeMillis(); - lastVisitVersion = AndorsTrailApplication.CURRENT_VERSION; } public void resetTemporaryData() { for(MonsterSpawnArea a : spawnAreas) { @@ -213,10 +212,13 @@ public final class PredefinedMap { lastVisitTime = src.readLong(); if (visited) { - if (fileversion <= 30) lastVisitVersion = 30; - else lastVisitVersion = src.readInt(); + if (fileversion > 30 && fileversion < 36) { + /*int lastVisitVersion = */src.readInt(); + } + if (fileversion < 36) lastSeenLayoutHash = ""; + else lastSeenLayoutHash = src.readUTF(); } - + for(int i = loadedSpawnAreas; i < spawnAreas.length; ++i) { MonsterSpawnArea area = this.spawnAreas[i]; if (area.isUnique && visited) controllers.monsterSpawnController.spawnAllInArea(this, null, area, true); @@ -235,6 +237,8 @@ public final class PredefinedMap { } dest.writeBoolean(visited); dest.writeLong(lastVisitTime); - if (visited) dest.writeInt(lastVisitVersion); + if (visited) { + dest.writeUTF(lastSeenLayoutHash); + } } } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/ReplaceableMapSection.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/ReplaceableMapSection.java new file mode 100644 index 000000000..7ec9012d4 --- /dev/null +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/ReplaceableMapSection.java @@ -0,0 +1,25 @@ +package com.gpl.rpg.AndorsTrail.model.map; + +import com.gpl.rpg.AndorsTrail.model.quest.QuestProgress; +import com.gpl.rpg.AndorsTrail.util.CoordRect; + +public final class ReplaceableMapSection { + public boolean isApplied = false; + public final CoordRect replacementArea; + public final MapSection replaceLayersWith; + public final QuestProgress requireQuestStage; + + public ReplaceableMapSection( + CoordRect replacementArea, + MapSection replaceLayersWith, + QuestProgress requireQuestStage) { + this.replacementArea = replacementArea; + this.replaceLayersWith = replaceLayersWith; + this.requireQuestStage = requireQuestStage; + } + + public void apply(MapSection dest) { + dest.replaceLayerContentsWith(replaceLayersWith, replacementArea); + isApplied = true; + } +} diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapFileParser.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapFileParser.java index 3c4f9b523..ae545414b 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapFileParser.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapFileParser.java @@ -1,31 +1,31 @@ package com.gpl.rpg.AndorsTrail.model.map; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import com.gpl.rpg.AndorsTrail.util.Base64; +import com.gpl.rpg.AndorsTrail.util.L; +import com.gpl.rpg.AndorsTrail.util.XmlResourceParserUtils; +import org.xmlpull.v1.XmlPullParserException; + import java.io.ByteArrayInputStream; import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; -import org.xmlpull.v1.XmlPullParserException; - -import com.gpl.rpg.AndorsTrail.util.Base64; -import com.gpl.rpg.AndorsTrail.util.L; -import com.gpl.rpg.AndorsTrail.util.XmlResourceParserUtils; - -import android.content.res.Resources; -import android.content.res.XmlResourceParser; - public final class TMXMapFileParser { - public static TMXMap read(Resources r, int xmlResourceId, String name) { - return read(r.getXml(xmlResourceId), xmlResourceId, name); + public static TMXObjectMap readObjectMap(Resources r, int xmlResourceId, String name) { + return readObjectMap(r.getXml(xmlResourceId), xmlResourceId, name); } - public static TMXLayerMap readLayeredTileMap(Resources r, int xmlResourceId, String name) { + public static TMXLayerMap readLayerMap(Resources r, int xmlResourceId, String name) { return readLayerMap(r.getXml(xmlResourceId), name); } - private static TMXMap read(XmlResourceParser xrp, int xmlResourceId, String name) { - final TMXMap map = new TMXMap(); + private static TMXObjectMap readObjectMap(XmlResourceParser xrp, int xmlResourceId, String name) { + final TMXObjectMap map = new TMXObjectMap(); map.xmlResourceId = xmlResourceId; try { // Map format: http://sourceforge.net/apps/mediawiki/tiled/index.php?title=Examining_the_map_format @@ -35,7 +35,6 @@ public final class TMXMapFileParser { String s = xrp.getName(); if (s.equals("map")) { map.name = name; - map.orientation = xrp.getAttributeValue(null, "orientation"); map.width = xrp.getAttributeIntValue(null, "width", -1); map.height = xrp.getAttributeIntValue(null, "height", -1); map.tilewidth = xrp.getAttributeIntValue(null, "tilewidth", -1); @@ -72,8 +71,11 @@ public final class TMXMapFileParser { if (eventType == XmlResourceParser.START_TAG) { String s = xrp.getName(); if (s.equals("map")) { + map.name = name; map.width = xrp.getAttributeIntValue(null, "width", -1); map.height = xrp.getAttributeIntValue(null, "height", -1); + map.tilewidth = xrp.getAttributeIntValue(null, "tilewidth", -1); + map.tileheight = xrp.getAttributeIntValue(null, "tileheight", -1); XmlResourceParserUtils.readCurrentTagUntilEnd(xrp, new XmlResourceParserUtils.TagHandler() { public void handleTag(XmlResourceParser xrp, String tagName) throws XmlPullParserException, IOException { if (tagName.equals("tileset")) { @@ -82,6 +84,8 @@ public final class TMXMapFileParser { layers.add(readTMXMapLayer(xrp)); } else if (tagName.equals("property")) { map.properties.add(readTMXProperty(xrp)); + } else if (tagName.equals("objectgroup")) { + map.objectGroups.add(readTMXObjectGroup(xrp)); } } }); @@ -196,11 +200,17 @@ public final class TMXMapFileParser { for(int y = 0; y < layer.height; ++y) { for(int x = 0; x < layer.width; ++x, i += 4) { int gid = readIntLittleEndian(buffer, i); - //if (gid != 0) L.log(getHexString(buffer, i) + " -> " + gid); layer.gids[x][y] = gid; - //L.log("(" + x + "," + y + ") : " + layer.gids[x][y]); } } + + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(buffer); + layer.layoutHash = digest.digest(); + } catch (NoSuchAlgorithmException e) { + throw new IOException("Failed to create layout hash for map layer " + layer.name); + } } private static TMXProperty readTMXProperty(XmlResourceParser xrp) { @@ -241,19 +251,21 @@ public final class TMXMapFileParser { (buffer[offset + 3] << 24) & 0xff000000; } - public static final class TMXMap extends TMXLayerMap { + public static final class TMXObjectMap extends TMXMap { public int xmlResourceId; - public String name; - public String orientation; - public int tilewidth; - public int tileheight; public final ArrayList objectGroups = new ArrayList(); } - public static class TMXLayerMap { - public int width; - public int height; + public static final class TMXLayerMap extends TMXMap { public TMXTileSet[] tileSets; public TMXLayer[] layers; + public final ArrayList objectGroups = new ArrayList(); + } + public static class TMXMap { + public String name; + public int width; + public int height; + public int tilewidth; + public int tileheight; public final ArrayList properties = new ArrayList(); } public static final class TMXTileSet { @@ -269,6 +281,7 @@ public final class TMXMapFileParser { public int width; public int height; public int[][] gids; + public byte[] layoutHash; } public static final class TMXObjectGroup { public String name; diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapTranslator.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapTranslator.java index cfa5d236d..e962e6db4 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapTranslator.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/map/TMXMapTranslator.java @@ -1,8 +1,8 @@ package com.gpl.rpg.AndorsTrail.model.map; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; import com.gpl.rpg.AndorsTrail.model.actor.MonsterType; @@ -12,12 +12,12 @@ import com.gpl.rpg.AndorsTrail.model.item.DropListCollection; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXLayer; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXLayerMap; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXMap; +import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXObjectMap; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXObject; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXObjectGroup; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXProperty; import com.gpl.rpg.AndorsTrail.model.map.TMXMapFileParser.TMXTileSet; import com.gpl.rpg.AndorsTrail.model.quest.QuestProgress; -import com.gpl.rpg.AndorsTrail.resource.DynamicTileLoader; import com.gpl.rpg.AndorsTrail.resource.tiles.TileCache; import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.CoordRect; @@ -28,25 +28,24 @@ import com.gpl.rpg.AndorsTrail.util.Size; import android.content.res.Resources; public final class TMXMapTranslator { - private final ArrayList maps = new ArrayList(); + private final ArrayList maps = new ArrayList(); public void read(Resources r, int xmlResourceId, String name) { - maps.add(TMXMapFileParser.read(r, xmlResourceId, name)); + maps.add(TMXMapFileParser.readObjectMap(r, xmlResourceId, name)); } public static LayeredTileMap readLayeredTileMap(Resources res, TileCache tileCache, PredefinedMap map) { - TMXLayerMap resultMap = TMXMapFileParser.readLayeredTileMap(res, map.xmlResourceId, map.name); - return transformMap(resultMap, tileCache, map.name); + TMXLayerMap resultMap = TMXMapFileParser.readLayerMap(res, map.xmlResourceId, map.name); + return transformMap(resultMap, tileCache); } public ArrayList transformMaps(MonsterTypeCollection monsterTypes, DropListCollection dropLists) { return transformMaps(maps, monsterTypes, dropLists); } - public ArrayList transformMaps(Collection maps, MonsterTypeCollection monsterTypes, DropListCollection dropLists) { + public ArrayList transformMaps(Collection maps, MonsterTypeCollection monsterTypes, DropListCollection dropLists) { ArrayList result = new ArrayList(); - Tile tile = new Tile(); - for (TMXMap m : maps) { + for (TMXObjectMap m : maps) { assert(m.name != null); assert(m.name.length() > 0); assert(m.width > 0); @@ -64,13 +63,7 @@ public final class TMXMapTranslator { for (TMXObjectGroup group : m.objectGroups) { for (TMXObject object : group.objects) { - final Coord topLeft = new Coord( - Math.round(((float)object.x) / m.tilewidth) - ,Math.round(((float)object.y) / m.tileheight) - ); - final int width = Math.round(((float)object.width) / m.tilewidth); - final int height = Math.round(((float)object.height) / m.tileheight); - final CoordRect position = new CoordRect(topLeft, new Size(width, height)); + final CoordRect position = getTMXObjectPosition(object, m); if (object.type == null) { if (AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) @@ -148,6 +141,8 @@ public final class TMXMapTranslator { DropList dropList = dropLists.getDropList(object.name); if (dropList == null) continue; mapObjects.add(MapObject.createNewContainerArea(position, dropList)); + } else if (object.type.equals("replace")) { + // Do nothing. Will be handled when reading map layers instead. } else if (AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) { L.log("OPTIMIZE: Map " + m.name + ", has unrecognized object type \"" + object.type + "\" for name \"" + object.name + "\"."); } @@ -163,66 +158,178 @@ public final class TMXMapTranslator { return result; } - - private static LayeredTileMap transformMap(TMXLayerMap map, TileCache tileCache, String mapName) { + private static CoordRect getTMXObjectPosition(TMXObject object, TMXMap m) { + final Coord topLeft = new Coord( + Math.round(((float)object.x) / m.tilewidth) + ,Math.round(((float)object.y) / m.tileheight) + ); + final int width = Math.round(((float)object.width) / m.tilewidth); + final int height = Math.round(((float)object.height) / m.tileheight); + return new CoordRect(topLeft, new Size(width, height)); + } + + private static final String LAYERNAME_GROUND = "ground"; + private static final String LAYERNAME_OBJECTS = "objects"; + private static final String LAYERNAME_ABOVE = "above"; + private static final String LAYERNAME_WALKABLE = "walkable"; + private static final SetOfLayerNames defaultLayerNames = new SetOfLayerNames(LAYERNAME_GROUND, LAYERNAME_OBJECTS, LAYERNAME_ABOVE, LAYERNAME_WALKABLE); + + private static LayeredTileMap transformMap(TMXLayerMap map, TileCache tileCache) { final Size mapSize = new Size(map.width, map.height); - final MapLayer[] layers = new MapLayer[] { - new MapLayer(mapSize) - ,new MapLayer(mapSize) - ,new MapLayer(mapSize) - }; - boolean[][] isWalkable = new boolean[map.width][map.height]; - for (int y = 0; y < map.height; ++y) { - for (int x = 0; x < map.width; ++x) { - isWalkable[x][y] = true; - } - } - Tile tile = new Tile(); String colorFilter = null; for (TMXProperty prop : map.properties) { if (prop.name.equalsIgnoreCase("colorfilter")) colorFilter = prop.value; } HashSet usedTileIDs = new HashSet(); + HashMap layersPerLayerName = new HashMap(); for (TMXLayer layer : map.layers) { - int ixMapLayer = 0; String layerName = layer.name; assert(layerName != null); assert(layerName.length() > 0); layerName = layerName.toLowerCase(); - boolean isWalkableLayer = false; - if (layerName.startsWith("object")) { - ixMapLayer = LayeredTileMap.LAYER_OBJECTS; - } else if (layerName.startsWith("ground")) { - ixMapLayer = LayeredTileMap.LAYER_GROUND; - } else if (layerName.startsWith("above")) { - ixMapLayer = LayeredTileMap.LAYER_ABOVE; - } else if (layerName.startsWith("walk")) { - isWalkableLayer = true; - } else { - continue; + if (AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) { + if (layersPerLayerName.containsKey(layerName)) { + L.log("WARNING: Map \"" + map.name + "\" contains multiple layers with name \"" + layerName + "\"."); + } } - - for (int y = 0; y < layer.height; ++y) { - for (int x = 0; x < layer.width; ++x) { - int gid = layer.gids[x][y]; - if (gid <= 0) continue; - - if (!getTile(map, gid, tile)) continue; + layersPerLayerName.put(layerName, layer); + } - if (isWalkableLayer) { - isWalkable[x][y] = false; - } else { - int tileID = tileCache.getTileID(tile.tilesetName, tile.localId); - layers[ixMapLayer].tiles[x][y] = tileID; - usedTileIDs.add(tileID); + MapSection defaultLayout = transformMapSection(map, + tileCache, + new CoordRect(new Coord(0,0), mapSize), + layersPerLayerName, + usedTileIDs, + defaultLayerNames); + + ArrayList replaceableSections = new ArrayList(); + for (TMXObjectGroup objectGroup : map.objectGroups) { + for(TMXObject obj : objectGroup.objects) { + if ("replace".equals(obj.type)) { + final CoordRect position = getTMXObjectPosition(obj, map); + SetOfLayerNames layerNames = new SetOfLayerNames(); + for (TMXProperty prop : obj.properties) { + if (prop.name.equalsIgnoreCase(LAYERNAME_GROUND)) layerNames.groundLayerName = prop.value; + else if (prop.name.equalsIgnoreCase(LAYERNAME_OBJECTS)) layerNames.objectsLayerName = prop.value; + else if (prop.name.equalsIgnoreCase(LAYERNAME_ABOVE)) layerNames.aboveLayersName = prop.value; + else if (prop.name.equalsIgnoreCase(LAYERNAME_WALKABLE)) layerNames.walkableLayersName = prop.value; + else if (AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) { + L.log("OPTIMIZE: Map " + map.name + " contains replace area with unknown property \"" + prop.name + "\"."); + } } + MapSection replacementSection = transformMapSection(map, tileCache, position, layersPerLayerName, usedTileIDs, layerNames); + QuestProgress requireQuestStage = QuestProgress.parseQuestProgress(obj.name); + if (requireQuestStage == null) { + if (AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) { + L.log("OPTIMIZE: Map " + map.name + " contains replace area that cannot be parsed as a quest stage."); + } + continue; + } + replaceableSections.add(new ReplaceableMapSection(position, replacementSection, requireQuestStage)); } } } - return new LayeredTileMap(mapSize, layers, isWalkable, usedTileIDs, colorFilter); + + ReplaceableMapSection[] replaceableSections_ = null; + if (!replaceableSections.isEmpty()) { + replaceableSections_ = replaceableSections.toArray(new ReplaceableMapSection[replaceableSections.size()]); + } + return new LayeredTileMap(mapSize, defaultLayout, replaceableSections_, colorFilter, usedTileIDs); } - + + private static MapSection transformMapSection( + TMXLayerMap srcMap, + TileCache tileCache, + CoordRect area, + HashMap layersPerLayerName, + HashSet usedTileIDs, + SetOfLayerNames layerNames + ) { + final MapLayer layerGround = transformMapLayer(layersPerLayerName, layerNames.groundLayerName, srcMap, tileCache, area, usedTileIDs); + final MapLayer layerObjects = transformMapLayer(layersPerLayerName, layerNames.objectsLayerName, srcMap, tileCache, area, usedTileIDs); + final MapLayer layerAbove = transformMapLayer(layersPerLayerName, layerNames.aboveLayersName, srcMap, tileCache, area, usedTileIDs); + boolean[][] isWalkable = transformWalkableMapLayer(findLayer(layersPerLayerName, layerNames.walkableLayersName, srcMap.name), area); + byte[] layoutHash = calculateLayoutHash(srcMap, layersPerLayerName, layerNames); + return new MapSection(layerGround, layerObjects, layerAbove, isWalkable, layoutHash); + } + + private static TMXLayer findLayer(HashMap layersPerLayerName, String layerName, String mapName) { + if (layerName == null) return null; + if (layerName.length() == 0) return null; + TMXLayer result = layersPerLayerName.get(layerName); + if (AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) { + if (result == null) { + L.log("WARNING: Cannot find maplayer \"" + layerName + "\" requested by map \"" + mapName + "\"."); + } + } + return result; + } + + private static MapLayer transformMapLayer( + HashMap layersPerLayerName, + String layerName, + TMXLayerMap srcMap, + TileCache tileCache, + CoordRect area, + HashSet usedTileIDs + ) { + TMXLayer srcLayer = findLayer(layersPerLayerName, layerName, srcMap.name); + if (srcLayer == null) return null; + final MapLayer result = new MapLayer(area.size); + Tile tile = new Tile(); + for (int dy = 0, sy = area.topLeft.y; dy < area.size.height; ++dy, ++sy) { + for (int dx = 0, sx = area.topLeft.x; dx < area.size.width; ++dx, ++sx) { + int gid = srcLayer.gids[sx][sy]; + if (gid <= 0) continue; + + if (!getTile(srcMap, gid, tile)) continue; + + int tileID = tileCache.getTileID(tile.tilesetName, tile.localId); + result.tiles[dx][dy] = tileID; + usedTileIDs.add(tileID); + } + } + return result; + } + + private static boolean[][] transformWalkableMapLayer(TMXLayer srcLayer, CoordRect area) { + if (srcLayer == null) return null; + final boolean[][] isWalkable = new boolean[area.size.width][area.size.height]; + for (int x = 0; x < area.size.width; ++x) { + Arrays.fill(isWalkable[x], true); + } + for (int dy = 0, sy = area.topLeft.y; dy < area.size.height; ++dy, ++sy) { + for (int dx = 0, sx = area.topLeft.x; dx < area.size.width; ++dx, ++sx) { + int gid = srcLayer.gids[sx][sy]; + if (gid > 0) { + isWalkable[dx][dy] = false; + } + } + } + return isWalkable; + } + + private static byte[] calculateLayoutHash(TMXLayerMap map, HashMap layersPerLayerName, SetOfLayerNames layerNames) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digestLayer(layersPerLayerName, layerNames.groundLayerName, map, digest); + digestLayer(layersPerLayerName, layerNames.objectsLayerName, map, digest); + digestLayer(layersPerLayerName, layerNames.aboveLayersName, map, digest); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + L.log("ERROR: Failed to create layout hash for map " + map.name + " : " + e.toString()); + } + return new byte[0]; + } + + private static void digestLayer(HashMap layersPerLayerName, String layerName, TMXLayerMap map, MessageDigest digest) { + TMXLayer srcLayer = findLayer(layersPerLayerName, layerName, map.name); + if (srcLayer == null) return; + if (srcLayer.layoutHash == null) return; + digest.update(srcLayer.layoutHash); + } + private static boolean getTile(final TMXLayerMap map, final int gid, final Tile dest) { for(int i = map.tileSets.length - 1; i >= 0; --i) { TMXTileSet ts = map.tileSets[i]; @@ -232,7 +339,7 @@ public final class TMXMapTranslator { return true; } } - L.log("WARNING: Cannot find tile for gid " + gid); //(" + x + ", " + y + "), ") + L.log("WARNING: Cannot find tile for gid " + gid); return false; } @@ -240,73 +347,23 @@ public final class TMXMapTranslator { public String tilesetName; public int localId; } - - /* - public static final class TMXMap extends TMXLayerMap { - public int xmlResourceId; - public String name; - public String orientation; - public int tilewidth; - public int tileheight; - public ArrayList objectGroups = new ArrayList(); - } - public static class TMXLayerMap { - public int width; - public int height; - public TMXTileSet[] tileSets; - public TMXLayer[] layers; - } - public static final class TMXTileSet { - public int firstgid; - public String name; - public int tilewidth; - public int tileheight; - public String imageSource; - public String imageName; - } - public static final class TMXLayer { - public String name; - public int width; - public int height; - public int[][] gids; - } - public static final class TMXObjectGroup { - public String name; - public int width; - public int height; - public ArrayList objects = new ArrayList(); - } - public static final class TMXObject { - public String name; - public String type; - public int x; - public int y; - public int width; - public int height; - public ArrayList properties = new ArrayList(); - } - public static final class TMXProperty { - public String name; - public String value; - } - */ - /* - - - - - - - - H4sIAAAAAAAAA/NgYGDwIBK7AbEnHkyOOmwYXR02MwZSHQyTah4xGADnAt2SkAEAAA== - - - - - H4sIAAAAAAAAA2NgoA1gYUHlP2HGro6NBbt4MysqXw2oLhEqlgSlU4H0YjR12EAbUE0KFnXPgG5iRLJ/GQ6zHuNwOy7gxE6aemQAAJRT7VKQAQAA - - - - */ + private static final class SetOfLayerNames { + public String groundLayerName; + public String objectsLayerName; + public String aboveLayersName; + public String walkableLayersName; + public SetOfLayerNames() { + this.groundLayerName = null; + this.objectsLayerName = null; + this.aboveLayersName = null; + this.walkableLayersName = null; + } + public SetOfLayerNames(String groundLayerName, String objectsLayerName, String aboveLayersName, String walkableLayersName) { + this.groundLayerName = groundLayerName; + this.objectsLayerName = objectsLayerName; + this.aboveLayersName = aboveLayersName; + this.walkableLayersName = walkableLayersName; + } + } } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/util/ByteUtils.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/util/ByteUtils.java new file mode 100644 index 000000000..b3db4f716 --- /dev/null +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/util/ByteUtils.java @@ -0,0 +1,16 @@ +package com.gpl.rpg.AndorsTrail.util; + +public final class ByteUtils { + public static String toHexString(byte[] bytes) { return toHexString(bytes, bytes.length); } + public static String toHexString(byte[] bytes, int numBytes) { + if (bytes == null) return ""; + if (bytes.length == 0) return ""; + StringBuffer result = new StringBuffer(); + for (int i = 0; i < Math.min(numBytes, bytes.length); i++) { + String h = Integer.toHexString(0xFF & bytes[i]); + if (h.length() < 2) result.append('0'); + result.append(h); + } + return result.toString(); + } +} diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java index 61e7e517b..5059e1517 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java @@ -38,7 +38,7 @@ public final class MainView extends SurfaceView CombatSelectionListener, MonsterSpawnListener, MonsterMovementListener, - LootBagListener, + MapLayoutListener, VisualEffectFrameListener, GameRoundListener { @@ -276,8 +276,8 @@ public final class MainView extends SurfaceView private void doDrawRect(Canvas canvas, CoordRect area) { - drawMapLayer(canvas, area, currentTileMap.layers[LayeredTileMap.LAYER_GROUND]); - tryDrawMapLayer(canvas, area, LayeredTileMap.LAYER_OBJECTS); + drawMapLayer(canvas, area, currentTileMap.currentLayout.layerGround); + tryDrawMapLayer(canvas, area, currentTileMap.currentLayout.layerObjects); for (BloodSplatter splatter : currentMap.splatters) { drawFromMapPosition(canvas, area, splatter.position, splatter.iconID); @@ -296,7 +296,7 @@ public final class MainView extends SurfaceView } } - tryDrawMapLayer(canvas, area, LayeredTileMap.LAYER_ABOVE); + tryDrawMapLayer(canvas, area, currentTileMap.currentLayout.layerAbove); if (model.uiSelections.selectedPosition != null) { if (model.uiSelections.selectedMonster != null) { @@ -307,8 +307,8 @@ public final class MainView extends SurfaceView } } - private void tryDrawMapLayer(Canvas canvas, final CoordRect area, final int layerIndex) { - if (currentTileMap.layers.length > layerIndex) drawMapLayer(canvas, area, currentTileMap.layers[layerIndex]); + private void tryDrawMapLayer(Canvas canvas, final CoordRect area, final MapLayer layer) { + if (layer != null) drawMapLayer(canvas, area, layer); } private void drawMapLayer(Canvas canvas, final CoordRect area, final MapLayer layer) { @@ -401,7 +401,7 @@ public final class MainView extends SurfaceView public void subscribe() { controllers.gameRoundController.gameRoundListeners.add(this); controllers.effectController.visualEffectFrameListeners.add(this); - controllers.itemController.lootBagListeners.add(this); + controllers.mapController.mapLayoutListeners.add(this); controllers.movementController.playerMovementListeners.add(this); controllers.combatController.combatSelectionListeners.add(this); controllers.monsterSpawnController.monsterSpawnListeners.add(this); @@ -412,7 +412,7 @@ public final class MainView extends SurfaceView controllers.monsterSpawnController.monsterSpawnListeners.remove(this); controllers.combatController.combatSelectionListeners.remove(this); controllers.movementController.playerMovementListeners.remove(this); - controllers.itemController.lootBagListeners.remove(this); + controllers.mapController.mapLayoutListeners.remove(this); controllers.effectController.visualEffectFrameListeners.remove(this); controllers.gameRoundController.gameRoundListeners.remove(this); } @@ -494,6 +494,12 @@ public final class MainView extends SurfaceView redrawTile(p, REDRAW_TILE_BAG); } + @Override + public void onMapTilesChanged(PredefinedMap map, LayeredTileMap tileMap) { + if (map != currentMap) return; + redrawAll(REDRAW_ALL_MAP_CHANGED); + } + @Override public void onNewAnimationFrame(VisualEffectAnimation animation, int tileID, int textYOffset) { redrawAreaWithEffect(animation, tileID, textYOffset);