Move isWalkable from PredefinedMap to LayeredTileMap

* In effect, this will lower the amount of memory being used while running the game.
* The walkable bitmap will now be loaded at the same time that the image tiles are loaded for maps, and not when starting the game.
This commit is contained in:
Oskar Wiksten
2013-06-15 14:03:31 +02:00
parent 25fe0441c6
commit cab1185f14
11 changed files with 138 additions and 113 deletions

View File

@@ -141,7 +141,7 @@ public final class WorldSetup {
Context ctx = androidContext.get();
int result = Savegames.loadWorld(world, controllers, ctx, loadFromSlot);
if (result == Savegames.LOAD_RESULT_SUCCESS) {
controllers.movementController.cacheCurrentMapData(ctx.getResources(), world.model.currentMap);
controllers.movementController.prepareMapAsCurrentMap(world.model.currentMap, ctx.getResources(), false);
}
return result;
}

View File

@@ -1,11 +1,8 @@
package com.gpl.rpg.AndorsTrail.controller;
import java.util.ArrayList;
import android.os.Handler;
import android.os.Message;
import android.util.FloatMath;
import com.gpl.rpg.AndorsTrail.AndorsTrailPreferences;
import com.gpl.rpg.AndorsTrail.VisualEffectCollection;
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
@@ -21,10 +18,11 @@ import com.gpl.rpg.AndorsTrail.model.actor.Monster;
import com.gpl.rpg.AndorsTrail.model.actor.Player;
import com.gpl.rpg.AndorsTrail.model.item.ItemTraits_OnUse;
import com.gpl.rpg.AndorsTrail.model.item.Loot;
import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap;
import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea;
import com.gpl.rpg.AndorsTrail.util.Coord;
import java.util.ArrayList;
public final class CombatController implements VisualEffectCompletedCallback {
private final ControllerContext controllers;
private final WorldContext world;
@@ -105,11 +103,10 @@ public final class CombatController implements VisualEffectCompletedCallback {
}
public void setCombatSelection(Coord p) {
PredefinedMap map = world.model.currentMap;
Monster m = map.getMonsterAt(p);
Monster m = world.model.currentMap.getMonsterAt(p);
if (m != null) {
setCombatSelection(m, p);
} else if (map.isWalkable(p)) {
} else if (world.model.currentTileMap.isWalkable(p)) {
setCombatSelection(null, p);
}
}

View File

@@ -85,7 +85,7 @@ public final class GameRoundController implements TimedMessageTask.Callback {
private void onNewTick() {
controllers.monsterMovementController.moveMonsters();
controllers.monsterSpawnController.maybeSpawn(world.model.currentMap);
controllers.monsterSpawnController.maybeSpawn(world.model.currentMap, world.model.currentTileMap);
controllers.monsterMovementController.attackWithAgressiveMonsters();
controllers.effectController.updateSplatters(world.model.currentMap);
gameRoundListeners.onNewTick();

View File

@@ -83,7 +83,7 @@ public final class MapController {
for (PredefinedMap m : world.maps.getAllMaps()) {
m.resetTemporaryData();
}
controllers.monsterSpawnController.spawnAll(world.model.currentMap);
controllers.monsterSpawnController.spawnAll(world.model.currentMap, world.model.currentTileMap);
}
public void rest(MapObject area) {

View File

@@ -5,6 +5,7 @@ import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.controller.listeners.MonsterMovementListeners;
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap;
import com.gpl.rpg.AndorsTrail.model.map.MapObject;
import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea;
import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap;
@@ -49,8 +50,10 @@ public final class MonsterMovementController {
}
}
public static boolean monsterCanMoveTo(final PredefinedMap map, final CoordRect p) {
if (!map.isWalkable(p)) return false;
public static boolean monsterCanMoveTo(final PredefinedMap map, final LayeredTileMap tilemap, final CoordRect p) {
if (tilemap != null) {
if (!tilemap.isWalkable(p)) return false;
}
if (map.getMonsterAt(p) != null) return false;
MapObject m = map.getEventObjectAt(p.topLeft);
if (m != null) {
@@ -61,7 +64,8 @@ public final class MonsterMovementController {
private void moveMonster(final Monster m, final MonsterSpawnArea area) {
PredefinedMap map = world.model.currentMap;
m.nextActionTime += getMillisecondsPerMove(m);
LayeredTileMap tileMap = world.model.currentTileMap;
m.nextActionTime += getMillisecondsPerMove(m);
if (m.movementDestination == null) {
// Monster has waited and should start to move again.
m.movementDestination = new Coord(m.position);
@@ -80,7 +84,7 @@ public final class MonsterMovementController {
,m.position.y + sgn(m.movementDestination.y - m.position.y)
);
if (!monsterCanMoveTo(map, m.nextPosition)) {
if (!monsterCanMoveTo(map, tileMap, m.nextPosition)) {
cancelCurrentMonsterMovement(m);
return;
}

View File

@@ -5,6 +5,7 @@ import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.controller.listeners.MonsterSpawnListeners;
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
import com.gpl.rpg.AndorsTrail.model.actor.MonsterType;
import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap;
import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea;
import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap;
import com.gpl.rpg.AndorsTrail.util.Coord;
@@ -21,49 +22,49 @@ public final class MonsterSpawningController {
this.world = world;
}
public void spawnAllInArea(PredefinedMap map, MonsterSpawnArea area, boolean respawnUniqueMonsters) {
public void spawnAllInArea(PredefinedMap map, LayeredTileMap tileMap, MonsterSpawnArea area, boolean respawnUniqueMonsters) {
while (area.isSpawnable(respawnUniqueMonsters)) {
final boolean wasAbleToSpawn = spawnInArea(map, area, null);
final boolean wasAbleToSpawn = spawnInArea(map, tileMap, area, null);
if (!wasAbleToSpawn) break;
}
controllers.actorStatsController.healAllMonsters(area);
}
public void maybeSpawn(PredefinedMap map) {
public void maybeSpawn(PredefinedMap map, LayeredTileMap tileMap) {
for (MonsterSpawnArea a : map.spawnAreas) {
if (!a.isSpawnable(false)) continue;
if (!a.rollShouldSpawn()) continue;
spawnInArea(map, a, world.model.player.position);
spawnInArea(map, tileMap, a, world.model.player.position);
}
}
public void spawnAll(PredefinedMap map) {
public void spawnAll(PredefinedMap map, LayeredTileMap tileMap) {
boolean respawnUniqueMonsters = false;
if (!map.visited) respawnUniqueMonsters = true;
for (MonsterSpawnArea a : map.spawnAreas) {
spawnAllInArea(map, a, respawnUniqueMonsters);
spawnAllInArea(map, tileMap, a, respawnUniqueMonsters);
}
}
private boolean spawnInArea(PredefinedMap map, MonsterSpawnArea a, Coord playerPosition) {
return spawnInArea(map, a, a.getRandomMonsterType(world), playerPosition);
private boolean spawnInArea(PredefinedMap map, LayeredTileMap tileMap, MonsterSpawnArea a, Coord playerPosition) {
return spawnInArea(map, tileMap, a, a.getRandomMonsterType(world), playerPosition);
}
public boolean TEST_spawnInArea(PredefinedMap map, MonsterSpawnArea a, MonsterType type) { return spawnInArea(map, a, type, null); }
private boolean spawnInArea(PredefinedMap map, MonsterSpawnArea a, MonsterType type, Coord playerPosition) {
Coord p = getRandomFreePosition(map, a.area, type.tileSize, playerPosition);
public boolean TEST_spawnInArea(PredefinedMap map, LayeredTileMap tileMap, MonsterSpawnArea a, MonsterType type) { return spawnInArea(map, tileMap, a, type, null); }
private boolean spawnInArea(PredefinedMap map, LayeredTileMap tileMap, MonsterSpawnArea a, MonsterType type, Coord playerPosition) {
Coord p = getRandomFreePosition(map, tileMap, a.area, type.tileSize, playerPosition);
if (p == null) return false;
Monster m = a.spawn(p, type);
monsterSpawnListeners.onMonsterSpawned(map, m);
return true;
}
public static Coord getRandomFreePosition(PredefinedMap map, CoordRect area, Size requiredSize, Coord playerPosition) {
public static Coord getRandomFreePosition(PredefinedMap map, LayeredTileMap tileMap, CoordRect area, Size requiredSize, Coord playerPosition) {
CoordRect p = new CoordRect(requiredSize);
for(int i = 0; i < 100; ++i) {
p.topLeft.set(
area.topLeft.x + Constants.rnd.nextInt(area.size.width)
,area.topLeft.y + Constants.rnd.nextInt(area.size.height));
if (!MonsterMovementController.monsterCanMoveTo(map, p)) continue;
if (!MonsterMovementController.monsterCanMoveTo(map, tileMap, p)) continue;
if (playerPosition != null && p.contains(playerPosition)) continue;
return p.topLeft;
}

View File

@@ -73,32 +73,38 @@ public final class MovementController implements TimedMessageTask.Callback {
final ModelContainer model = world.model;
if (model.currentMap != null) model.currentMap.updateLastVisitTime();
cacheCurrentMapData(res, newMap);
model.currentMap = newMap;
model.player.position.set(place.position.topLeft);
model.player.position.x += Math.min(offset_x, place.position.size.width-1);
model.player.position.y += Math.min(offset_y, place.position.size.height-1);
model.player.lastPosition.set(model.player.position);
if (newMap.visited) playerVisitsMap(newMap);
else playerVisitsMapFirstTime(newMap);
refreshMonsterAggressiveness(newMap, model.player);
controllers.effectController.updateSplatters(newMap);
if (!newMap.visited) {
playerVisitsMapFirstTime(newMap);
}
prepareMapAsCurrentMap(newMap, res, true);
}
private void playerVisitsMapFirstTime(PredefinedMap m) {
m.reset();
controllers.monsterSpawnController.spawnAll(m);
m.createAllContainerLoot();
m.visited = true;
}
private void playerVisitsMap(PredefinedMap m) {
// Respawn everything if a certain time has elapsed.
if (!m.isRecentlyVisited()) controllers.monsterSpawnController.spawnAll(m);
public void prepareMapAsCurrentMap(PredefinedMap newMap, Resources res, boolean spawnMonsters) {
final ModelContainer model = world.model;
model.currentMap = newMap;
cacheCurrentMapData(res, newMap);
if (spawnMonsters) {
if (!newMap.isRecentlyVisited()) {
controllers.monsterSpawnController.spawnAll(newMap, model.currentTileMap);
}
}
newMap.visited = true;
moveBlockedActors(newMap, model.currentTileMap);
refreshMonsterAggressiveness(newMap, model.player);
controllers.effectController.updateSplatters(newMap);
}
private boolean mayMovePlayer() {
return !world.model.uiSelections.isInCombat;
}
@@ -174,7 +180,7 @@ public final class MovementController implements TimedMessageTask.Callback {
,player.position.y + dy
);
if (!world.model.currentMap.isWalkable(player.nextPosition)) return false;
if (!world.model.currentTileMap.isWalkable(player.nextPosition)) return false;
// allow player to enter every field when he is NORMAL
// prevent player from entering "non-monster-fields" when he is AGGRESSIVE
@@ -240,12 +246,13 @@ public final class MovementController implements TimedMessageTask.Callback {
placePlayerAsyncAt(MapObject.MAPEVENT_REST, world.model.player.getSpawnMap(), world.model.player.getSpawnPlace(), 0, 0);
}
public void moveBlockedActors() {
public void moveBlockedActors(PredefinedMap map, LayeredTileMap tileMap) {
final ModelContainer model = world.model;
if (!world.model.currentMap.isWalkable(world.model.player.position)) {
// If the player somehow spawned on an unwalkable tile, we move the player to the first mapchange area.
// This could happen if we change some tile to non-walkable in a future version.
for (MapObject o : model.currentMap.eventObjects) {
// If the player somehow spawned on an unwalkable tile, we move the player to the first mapchange area.
// This could happen if we change some tile to non-walkable in a future version.
if (!tileMap.isWalkable(model.player.position)) {
for (MapObject o : map.eventObjects) {
if (o.type == MapObject.MAPEVENT_NEWMAP) {
model.player.position.set(o.position.topLeft);
break;
@@ -255,22 +262,19 @@ public final class MovementController implements TimedMessageTask.Callback {
// If any monsters somehow spawned on an unwalkable tile, we move the monster to a new position on the spawnarea
// This could happen if we change some tile to non-walkable in a future version.
for (PredefinedMap map : world.maps.getAllMaps()) {
Coord playerPosition = null;
if (map == model.currentMap) playerPosition = model.player.position;
for (MonsterSpawnArea a : map.spawnAreas) {
for (Monster m : a.monsters) {
if (!map.isWalkable(m.rectPosition)) {
Coord p = MonsterSpawningController.getRandomFreePosition(map, a.area, m.tileSize, playerPosition);
if (p == null) continue;
m.position.set(p);
}
Coord playerPosition = model.player.position;
for (MonsterSpawnArea a : map.spawnAreas) {
for (Monster m : a.monsters) {
if (!tileMap.isWalkable(m.rectPosition)) {
Coord p = MonsterSpawningController.getRandomFreePosition(map, tileMap, a.area, m.tileSize, playerPosition);
if (p == null) continue;
m.position.set(p);
}
}
}
}
public void cacheCurrentMapData(final Resources res, final PredefinedMap nextMap) {
private void cacheCurrentMapData(final Resources res, final PredefinedMap nextMap) {
LayeredTileMap mapTiles = TMXMapTranslator.readLayeredTileMap(res, world.tileManager.tileCache, nextMap);
TileCollection cachedTiles = world.tileManager.loadTilesFor(nextMap, mapTiles, world, res);
world.model.currentTileMap = mapTiles;
@@ -279,8 +283,8 @@ public final class MovementController implements TimedMessageTask.Callback {
WorldMapController.updateWorldMap(world, nextMap, mapTiles, cachedTiles, res);
}
private int movementDx;
private int movementDy;
public void startMovement(int dx, int dy, Coord destination) {

View File

@@ -6,6 +6,8 @@ import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.CoordRect;
import com.gpl.rpg.AndorsTrail.util.Size;
public final class LayeredTileMap {
@@ -18,19 +20,61 @@ public final class LayeredTileMap {
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<Integer> usedTileIDs;
private final boolean[][] isWalkable;
public final String colorFilter;
public LayeredTileMap(Size size, MapLayer[] layers, Collection<Integer> usedTileIDs, String colorFilter) {
public LayeredTileMap(
Size size,
MapLayer[] layers,
boolean[][] isWalkable,
Collection<Integer> 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);
this.size = size;
this.layers = layers;
this.usedTileIDs = usedTileIDs;
this.isWalkable = isWalkable;
this.colorFilter = colorFilter;
}
public final boolean isWalkable(final Coord p) {
if (isOutside(p.x, p.y)) return false;
return 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];
}
public final boolean isWalkable(final CoordRect p) {
for (int y = 0; y < p.size.height; ++y) {
for (int x = 0; x < p.size.width; ++x) {
if (!isWalkable(p.topLeft.x + x, p.topLeft.y + y)) return false;
}
}
return true;
}
public final boolean isOutside(final Coord p) { return isOutside(p.x, p.y); }
public final boolean isOutside(final int x, final 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(final CoordRect area) {
if (isOutside(area.topLeft)) return true;
if (area.topLeft.x + area.size.width > size.width) return true;
if (area.topLeft.y + area.size.height > size.height) return true;
return false;
}
public void setColorFilter(Paint mPaint) {
mPaint.setColorFilter(getColorFilter());
}

View File

@@ -32,10 +32,9 @@ public final class PredefinedMap {
public int lastVisitVersion = 0;
private final boolean isOutdoors;
private final boolean[][] isWalkable;
public final ArrayList<BloodSplatter> splatters = new ArrayList<BloodSplatter>();
public PredefinedMap(int xmlResourceId, String name, Size size, boolean[][] isWalkable, MapObject[] eventObjects, MonsterSpawnArea[] spawnAreas, boolean isOutdoors) {
public PredefinedMap(int xmlResourceId, String name, Size size, MapObject[] eventObjects, MonsterSpawnArea[] spawnAreas, boolean isOutdoors) {
this.xmlResourceId = xmlResourceId;
this.name = name;
this.size = size;
@@ -43,28 +42,9 @@ public final class PredefinedMap {
this.spawnAreas = spawnAreas;
assert(size.width > 0);
assert(size.height > 0);
assert(isWalkable.length == size.width);
assert(isWalkable[0].length == size.height);
this.isWalkable = isWalkable;
this.isOutdoors = isOutdoors;
}
public final boolean isWalkable(final Coord p) {
if (isOutside(p.x, p.y)) return false;
return 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];
}
public final boolean isWalkable(final CoordRect p) {
for (int y = 0; y < p.size.height; ++y) {
for (int x = 0; x < p.size.width; ++x) {
if (!isWalkable(p.topLeft.x + x, p.topLeft.y + y)) return false;
}
}
return true;
}
public final boolean isOutside(final Coord p) { return isOutside(p.x, p.y); }
public final boolean isOutside(final int x, final int y) {
if (x < 0) return true;
@@ -73,11 +53,11 @@ public final class PredefinedMap {
if (y >= size.height) return true;
return false;
}
public final boolean isOutside(final CoordRect area) {
if (isOutside(area.topLeft)) return true;
public final boolean isOutside(final CoordRect area) {
if (isOutside(area.topLeft)) return true;
if (area.topLeft.x + area.size.width > size.width) return true;
if (area.topLeft.y + area.size.height > size.height) return true;
return false;
return false;
}
public MapObject findEventObject(int objectType, String name) {
@@ -239,7 +219,7 @@ public final class PredefinedMap {
for(int i = loadedSpawnAreas; i < spawnAreas.length; ++i) {
MonsterSpawnArea area = this.spawnAreas[i];
if (area.isUnique && visited) controllers.monsterSpawnController.spawnAllInArea(this, area, true);
if (area.isUnique && visited) controllers.monsterSpawnController.spawnAllInArea(this, null, area, true);
else area.reset();
}
}

View File

@@ -58,32 +58,15 @@ public final class TMXMapTranslator {
else if(AndorsTrailApplication.DEVELOPMENT_VALIDATEDATA) L.log("OPTIMIZE: Map " + m.name + " has unrecognized property \"" + p.name + "\".");
}
boolean[][] isWalkable = new boolean[m.width][m.height];
for (int y = 0; y < m.height; ++y) {
for (int x = 0; x < m.width; ++x) {
isWalkable[x][y] = true;
}
}
final Size mapSize = new Size(m.width, m.height);
for (TMXLayer layer : m.layers) {
String layerName = layer.name;
assert(layerName != null);
assert(layerName.length() > 0);
layerName = layerName.toLowerCase();
boolean isWalkableLayer = layerName.startsWith("walk");
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 (isWalkableLayer) {
isWalkable[x][y] = false;
} else {
if (!getTile(m, gid, tile)) continue;
tileLoader.prepareTileID(tile.tilesetName, tile.localId);
}
if (!getTile(m, gid, tile)) continue;
tileLoader.prepareTileID(tile.tilesetName, tile.localId);
}
}
}
@@ -187,7 +170,7 @@ public final class TMXMapTranslator {
MonsterSpawnArea[] _spawnAreas = new MonsterSpawnArea[spawnAreas.size()];
_spawnAreas = spawnAreas.toArray(_spawnAreas);
result.add(new PredefinedMap(m.xmlResourceId, m.name, mapSize, isWalkable, _eventObjects, _spawnAreas, isOutdoors));
result.add(new PredefinedMap(m.xmlResourceId, m.name, mapSize, _eventObjects, _spawnAreas, isOutdoors));
}
return result;
@@ -201,6 +184,12 @@ public final class TMXMapTranslator {
,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) {
@@ -208,17 +197,20 @@ public final class TMXMapTranslator {
}
HashSet<Integer> usedTileIDs = new HashSet<Integer>();
for (TMXLayer layer : map.layers) {
int ixMapLayer;
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;
}
@@ -229,14 +221,18 @@ public final class TMXMapTranslator {
if (gid <= 0) continue;
if (!getTile(map, gid, tile)) continue;
int tileID = tileCache.getTileID(tile.tilesetName, tile.localId);
layers[ixMapLayer].tiles[x][y] = tileID;
usedTileIDs.add(tileID);
if (isWalkableLayer) {
isWalkable[x][y] = false;
} else {
int tileID = tileCache.getTileID(tile.tilesetName, tile.localId);
layers[ixMapLayer].tiles[x][y] = tileID;
usedTileIDs.add(tileID);
}
}
}
}
return new LayeredTileMap(mapSize, layers, usedTileIDs, colorFilter);
return new LayeredTileMap(mapSize, layers, isWalkable, usedTileIDs, colorFilter);
}
private static boolean getTile(final TMXLayerMap map, final int gid, final Tile dest) {

View File

@@ -127,7 +127,6 @@ public final class Savegames {
private static void onWorldLoaded(WorldContext world, ControllerContext controllers) {
controllers.actorStatsController.recalculatePlayerStats(world.model.player);
controllers.mapController.resetMapsNotRecentlyVisited();
controllers.movementController.moveBlockedActors();
}
public static FileHeader quickload(Context androidContext, int slot) {