xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gpl.rpg.AndorsTrail"
android:versionCode="25"
- android:versionName="0.6.10b2"
+ android:versionName="0.6.10b3"
android:installLocation="auto"
>
<uses-sdk
{No problem, he is as good as dead.|guthbered_lookforsigns_9||||}
{Are you sure more violence will really solve this conflict?|guthbered_lookforsigns_10||||}
}|};
-{guthbered_lookforsigns_9|Excellent. Return to me once he is dead.|prim_hunt:80|||};
+{guthbered_lookforsigns_9|Excellent. Return to me once you are done.|prim_hunt:80|||};
{guthbered_lookforsigns_10|No, not really. But for now, it looks like the only option we have.|||{
{I will remove him, but I will try to find a peaceful solution to this.|guthbered_lookforsigns_9||||}
{Very well. He is as good as dead.|guthbered_lookforsigns_9||||}
public static final boolean DEVELOPMENT_VALIDATEDATA = false;\r
public static final boolean DEVELOPMENT_DEBUGMESSAGES = false;\r
public static final int CURRENT_VERSION = 25;\r
- public static final String CURRENT_VERSION_DISPLAY = "0.6.10b2";\r
+ public static final String CURRENT_VERSION_DISPLAY = "0.6.10b3";\r
\r
public final WorldContext world = new WorldContext();\r
public final WorldSetup setup = new WorldSetup(world, this);\r
if (effects_hit.isEmpty()) effects_hit = null;
if (effects_kill.isEmpty()) effects_kill = null;
heroinfo_itemeffects.update(null, null, effects_hit, effects_kill);
- heroinfo_basetraits.update(player.actorTraits.baseCombatTraits);
+ heroinfo_basetraits.update(player.actorTraits);
}
private void updateConditions() {
public void applyUseEffect(Actor source, Actor target, ItemTraits_OnUse effect) {
if (effect == null) return;
- applyStatsModifierEffect(source, effect, 1);
if (effect.addedConditions_source != null) {
for (ActorConditionEffect e : effect.addedConditions_source) {
rollForConditionEffect(source, e);
}
}
}
+ applyStatsModifierEffect(source, effect, 1);
}
private static void rollForConditionEffect(Actor actor, ActorConditionEffect conditionEffect) {
view.mainActivity.mainview
, actor.position
, visualEffectID
- , effectValue);
+ , effectValue
+ , null
+ , 0);
}
}
import com.gpl.rpg.AndorsTrail.R;
import com.gpl.rpg.AndorsTrail.context.ViewContext;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
+import com.gpl.rpg.AndorsTrail.controller.VisualEffectController.VisualEffectCompletedCallback;
import com.gpl.rpg.AndorsTrail.model.AttackResult;
import com.gpl.rpg.AndorsTrail.model.CombatTraits;
import com.gpl.rpg.AndorsTrail.model.ModelContainer;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.view.MainView;
-public final class CombatController {
+public final class CombatController implements VisualEffectCompletedCallback {
private final ViewContext context;
private final WorldContext world;
private final ModelContainer model;
updateTurnInfo();
}
public void exitCombat(boolean pickupLootBags) {
- context.effectController.waitForCurrentEffect();
setCombatSelection(null, null);
context.mainActivity.combatview.setVisibility(View.GONE);
model.uiSelections.isInCombat = false;
if (isMonsterTurn()) {
forceFinishMonsterAction();
} else if (world.model.uiSelections.selectedMonster != null) {
- executeAttack();
+ executePlayerAttack();
} else if (world.model.uiSelections.selectedPosition != null) {
executeCombatMove(world.model.uiSelections.selectedPosition);
} else if (canExitCombat()) {
Monster m = getAdjacentMonster();
if (m == null) return;
setCombatSelection(m);
- executeAttack();
+ executePlayerAttack();
}
}
executeCombatMove(world.model.player.nextPosition);
}
- private void executeAttack() {
- context.effectController.waitForCurrentEffect();
-
+ private Monster currentlyAttackedMonster;
+ private AttackResult lastAttackResult;
+ private void executePlayerAttack() {
if (!useAPs(model.player.combatTraits.attackCost)) return;
- Monster target = model.uiSelections.selectedMonster;
-
- AttackResult attack = playerAttacks(model, target);
+ if (context.effectController.isRunningVisualEffect()) return;
+ final Monster target = model.uiSelections.selectedMonster;
+ this.currentlyAttackedMonster = target;
+
+ final AttackResult attack = playerAttacks(model, target);
+ this.lastAttackResult = attack;
+
Resources r = context.mainActivity.getResources();
if (attack.isHit) {
String msg;
msg += " " + r.getString(R.string.combat_result_herokillsmonster, monsterName, attack.damage);
}
message(msg);
- startAttackEffect(attack, model.uiSelections.selectedPosition);
- if (attack.targetDied) {
- playerKilledMonster(target);
- Monster nextMonster = getAdjacentMonster();
- if (nextMonster == null) {
- exitCombat(true);
- return;
- } else {
- context.effectController.waitForCurrentEffect();
- setCombatSelection(nextMonster);
- }
- }
+
+ startAttackEffect(attack, model.uiSelections.selectedPosition, this, CALLBACK_PLAYERATTACK);
} else {
message(r.getString(R.string.combat_result_heromiss));
+ playerAttackCompleted();
+ }
+ }
+
+ private void playerAttackCompleted() {
+ if (lastAttackResult.targetDied) {
+ playerKilledMonster(currentlyAttackedMonster);
+ Monster nextMonster = getAdjacentMonster();
+ if (nextMonster == null) {
+ exitCombat(true);
+ return;
+ } else {
+ setCombatSelection(nextMonster);
+ }
}
context.mainActivity.updateStatus();
-
maybeAutoEndTurn();
}
CombatController.this.handleNextMonsterAction();
}
};
+
public void endPlayerTurn() {
model.player.ap.current = 0;
for (MonsterSpawnArea a : model.currentMap.spawnAreas) {
private void handleNextMonsterAction() {
if (!context.model.uiSelections.isMainActivityVisible) return;
- context.effectController.waitForCurrentEffect();
-
currentActiveMonster = determineNextMonster(currentActiveMonster);
if (currentActiveMonster == null) {
endMonsterTurn();
return;
}
+
context.mainActivity.combatview.updateTurnInfo(currentActiveMonster);
Resources r = context.mainActivity.getResources();
AttackResult attack = monsterAttacks(model, currentActiveMonster);
+ this.lastAttackResult = attack;
+
String monsterName = currentActiveMonster.actorTraits.name;
if (attack.isHit) {
- startAttackEffect(attack, model.player.position);
if (attack.isCriticalHit) {
message(r.getString(R.string.combat_result_monsterhitcritical, monsterName, attack.damage));
} else {
message(r.getString(R.string.combat_result_monsterhit, monsterName, attack.damage));
}
- if (attack.targetDied) {
- context.controller.handlePlayerDeath();
- return;
- }
+ context.mainActivity.updateStatus();
+
+ startAttackEffect(attack, model.player.position, this, CALLBACK_MONSTERATTACK);
} else {
message(r.getString(R.string.combat_result_monstermiss, monsterName));
+ context.mainActivity.updateStatus();
+
+ monsterTurnHandler.sendEmptyMessageDelayed(0, context.preferences.attackspeed_milliseconds);
+ }
+ }
+
+ private static final int CALLBACK_MONSTERATTACK = 0;
+ private static final int CALLBACK_PLAYERATTACK = 1;
+
+ @Override
+ public void onVisualEffectCompleted(int callbackValue) {
+ if (callbackValue == CALLBACK_MONSTERATTACK) {
+ monsterAttackCompleted();
+ } else if (callbackValue == CALLBACK_PLAYERATTACK) {
+ playerAttackCompleted();
}
- context.mainActivity.updateStatus();
- monsterTurnHandler.sendEmptyMessageDelayed(0, context.preferences.attackspeed_milliseconds);
}
- private void startAttackEffect(AttackResult attack, final Coord position) {
- if (context.preferences.attackspeed_milliseconds <= 0) return;
+ private void monsterAttackCompleted() {
+ if (lastAttackResult.targetDied) {
+ context.controller.handlePlayerDeath();
+ return;
+ }
+ handleNextMonsterAction();
+ }
+
+ private void startAttackEffect(AttackResult attack, final Coord position, VisualEffectCompletedCallback callback, int callbackValue) {
+ if (context.preferences.attackspeed_milliseconds <= 0) {
+ callback.onVisualEffectCompleted(callbackValue);
+ return;
+ }
context.effectController.startEffect(
context.mainActivity.mainview
, position
, VisualEffectCollection.EFFECT_BLOOD
- , attack.damage);
+ , attack.damage
+ , callback
+ , callbackValue);
}
private void endMonsterTurn() {
currentActiveMonster = null;
public void handlePlayerDeath() {
view.combatController.exitCombat(false);
- view.effectController.waitForCurrentEffect();
final Player player = model.player;
int lostExp = player.levelExperience.current * Constants.PERCENT_EXP_LOST_WHEN_DIED / 100;
lostExp -= lostExp * player.getSkillLevel(SkillCollection.SKILL_LOWER_EXPLOSS) * SkillCollection.PER_SKILLPOINT_INCREASE_EXPLOSS_PERCENT / 100;
TileCollection cachedTiles = world.tileManager.loadTilesFor(nextMap, mapTiles, world, res);
world.model.currentTileMap = mapTiles;
world.tileManager.currentMapTiles = cachedTiles;
+ world.tileManager.cacheAdjacentMaps(res, world, nextMap);
}
+
private int movementDx;
private int movementDy;
public void startMovement(int dx, int dy, Coord destination) {
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
+import android.os.AsyncTask;
import com.gpl.rpg.AndorsTrail.VisualEffectCollection;
import com.gpl.rpg.AndorsTrail.VisualEffectCollection.VisualEffect;
this.effectTypes = world.visualEffectTypes;
}
- public void startEffect(MainView mainview, Coord position, int effectID, int displayValue) {
+ public void startEffect(MainView mainview, Coord position, int effectID, int displayValue, VisualEffectCompletedCallback callback, int callbackValue) {
VisualEffectAnimation e = currentEffect;
if (e != null) {
e.killjoin();
}
- currentEffect = new VisualEffectAnimation(effectTypes.effects[effectID], position, mainview, displayValue);
- currentEffect.start();
+ currentEffect = new VisualEffectAnimation(effectTypes.effects[effectID], position, mainview, displayValue, callback, callbackValue);
+ currentEffect.execute();
}
-
- public final class VisualEffectAnimation extends Thread {
+
+ public final class VisualEffectAnimation extends AsyncTask<Void, Integer, Void> {
@Override
- public void run() {
- while (isAlive) {
- update();
- try {
- sleep(8);
- } catch (InterruptedException e) {
- isAlive = false;
- }
- }
- view.redrawArea(area, MainView.REDRAW_AREA_EFFECT_COMPLETED);
- VisualEffectController.this.currentEffect = null;
+ protected Void doInBackground(Void... arg0) {
+ final int sleepInterval = effect.millisecondPerFrame / 2;
+ try {
+ while (isAlive) {
+ update();
+ Thread.sleep(sleepInterval);
+ if (isCancelled()) return null;
+ }
+ Thread.sleep(effect.millisecondPerFrame);
+ } catch (InterruptedException e) { }
+
+ return null;
}
- public void killjoin() {
+ @Override
+ protected void onCancelled() {
isAlive = false;
- safejoin();
- }
- public void safejoin() {
- try {
- join();
- } catch (InterruptedException e) {}
}
+
+ public void killjoin() { this.cancel(true); }
+
private void update() {
int elapsed = (int)(System.currentTimeMillis() - startTime);
if (elapsed > effect.duration) {
return;
}
- int currentFrame = (int) Math.floor((float)elapsed / effect.millisecondPerFrame);
- setCurrentTile(currentFrame);
- }
+ int currentFrame = (int) Math.floor(elapsed / effect.millisecondPerFrame);
+
+ if (currentFrame > effect.lastFrame) currentFrame = effect.lastFrame;
+ if (currentFrame < 0) currentFrame = 0;
+ final boolean changed = currentFrame != this.lastFrame;
+ if (!changed) return;
+
+ this.lastFrame = currentFrame;
+ this.publishProgress(currentFrame);
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ super.onProgressUpdate(progress);
+ redrawFrame(progress[0]);
+ }
+ private void redrawFrame(int frame) {
+ int tileID = effect.frameIconIDs[frame];
+ int textYOffset = -2 * (frame);
+ if (frame >= beginFadeAtFrame && displayText != null) {
+ this.textPaint.setAlpha(255 * (effect.lastFrame - frame) / (effect.lastFrame - beginFadeAtFrame));
+ }
+ view.redrawAreaWithEffect(area, this, tileID, textYOffset, this.textPaint);
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ VisualEffectController.this.currentEffect = null;
+ view.redrawArea(area, MainView.REDRAW_AREA_EFFECT_COMPLETED);
+ if (callback != null) callback.onVisualEffectCompleted(callbackValue);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ this.isAlive = true;
+ redrawFrame(0);
+ }
+
+ private boolean isAlive = false;
+ private int lastFrame = 0;
+
private final VisualEffect effect;
private final long startTime;
private final MainView view;
public final Coord position;
- private final CoordRect area;
public final String displayText;
- public final Paint textPaint = new Paint();
- public int currentTileID = 0;
- public int textYOffset = 0;
- private boolean isAlive = false;
+ private final CoordRect area;
+ private final Paint textPaint = new Paint();
+ private final int beginFadeAtFrame;
+ private final VisualEffectCompletedCallback callback;
+ private final int callbackValue;
- public VisualEffectAnimation(VisualEffect effect, Coord position, MainView view, int displayValue) {
+ public VisualEffectAnimation(VisualEffect effect, Coord position, MainView view, int displayValue, VisualEffectCompletedCallback callback, int callbackValue) {
this.position = position;
+ this.callback = callback;
+ this.callbackValue = callbackValue;
this.area = new CoordRect(new Coord(position.x, position.y - 1), new Size(1, 2));
this.effect = effect;
this.displayText = (displayValue == 0) ? null : String.valueOf(displayValue);
this.textPaint.setTextSize(view.scaledTileSize * 0.5f); // 32dp.
this.textPaint.setAlpha(255);
this.textPaint.setTextAlign(Align.CENTER);
- this.isAlive = true;
this.startTime = System.currentTimeMillis();
this.view = view;
- setCurrentTile(0);
- }
-
- private void setCurrentTile(int currentFrame) {
- if (currentFrame > effect.lastFrame) currentFrame = effect.lastFrame;
- if (currentFrame < 0) currentFrame = 0;
- int newTileID = effect.frameIconIDs[currentFrame];
- final boolean changed = newTileID != this.currentTileID;
- this.currentTileID = newTileID;
- this.textYOffset = -2 * (currentFrame);
- final int beginFadeAtFrame = effect.lastFrame / 2;
- if (currentFrame >= beginFadeAtFrame) {
- this.textPaint.setAlpha(255 * (effect.lastFrame - currentFrame) / (effect.lastFrame - beginFadeAtFrame));
- }
-
- if (changed) {
- view.redrawAreaWithEffect(area, this);
- }
+ this.beginFadeAtFrame = effect.lastFrame / 2;
}
}
-
- public void waitForCurrentEffect() {
- VisualEffectAnimation e = currentEffect;
- if (e != null) {
- e.safejoin();
- }
+
+
+ public static interface VisualEffectCompletedCallback {
+ public void onVisualEffectCompleted(int callbackValue);
}
+
public void killCurrentEffect() {
VisualEffectAnimation e = currentEffect;
if (e != null) {
e.killjoin();
}
}
+
+ public boolean isRunningVisualEffect() {
+ return currentEffect != null;
+ }
}
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.os.AsyncTask;
import android.widget.ImageView;
import com.gpl.rpg.AndorsTrail.AndorsTrailPreferences;
import com.gpl.rpg.AndorsTrail.model.item.ItemType;
import com.gpl.rpg.AndorsTrail.model.item.ItemContainer.ItemEntry;
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;
+import com.gpl.rpg.AndorsTrail.model.map.TMXMapTranslator;
public final class TileManager {
public static final int CHAR_HERO = 1;
public final TileCache tileCache = new TileCache();
public final TileCollection preloadedTiles = new TileCollection(72);
public TileCollection currentMapTiles;
+ public TileCollection adjacentMapTiles;
private final HashSet<Integer> preloadedTileIDs = new HashSet<Integer>();
}
public TileCollection loadTilesFor(PredefinedMap map, LayeredTileMap tileMap, WorldContext world, Resources r) {
+ HashSet<Integer> iconIDs = getTileIDsFor(map, tileMap, world);
+ TileCollection result = tileCache.loadTilesFor(iconIDs, r);
+ for(int i : preloadedTileIDs) {
+ result.setBitmap(i, preloadedTiles.getBitmap(i));
+ }
+ return result;
+ }
+
+ public HashSet<Integer> getTileIDsFor(PredefinedMap map, LayeredTileMap tileMap, WorldContext world) {
HashSet<Integer> iconIDs = new HashSet<Integer>();
for(MonsterSpawnArea a : map.spawnAreas) {
for(String monsterTypeID : a.monsterTypeIDs) {
}
}
iconIDs.addAll(tileMap.usedTileIDs);
-
- TileCollection result = tileCache.loadTilesFor(iconIDs, r);
- for(int i : preloadedTileIDs) {
- result.setBitmap(i, preloadedTiles.getBitmap(i));
- }
- return result;
+ return iconIDs;
}
public void setDensity(Resources r) {
}
}
-
public void loadPreloadedTiles(Resources r) {
int maxTileID = tileCache.getMaxTileID();
for(int i = TileManager.CHAR_HERO; i <= maxTileID; ++i) {
}
tileCache.loadTilesFor(preloadedTileIDs, r, preloadedTiles);
}
+
+ public void cacheAdjacentMaps(final Resources res, final WorldContext world, final PredefinedMap nextMap) {
+ (new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ adjacentMapTiles = null;
+
+ HashSet<String> adjacentMapNames = new HashSet<String>();
+ for (MapObject o : nextMap.eventObjects) {
+ if (o.type != MapObject.MAPEVENT_NEWMAP) continue;
+ adjacentMapNames.add(o.map);
+ }
+
+ HashSet<Integer> tileIDs = new HashSet<Integer>();
+ for (String mapName : adjacentMapNames) {
+ PredefinedMap adjacentMap = world.maps.findPredefinedMap(mapName);
+ LayeredTileMap adjacentMapTiles = TMXMapTranslator.readLayeredTileMap(res, tileCache, adjacentMap);
+ tileIDs.addAll(getTileIDsFor(adjacentMap, adjacentMapTiles, world));
+ }
+
+ adjacentMapTiles = tileCache.loadTilesFor(tileIDs, res);
+ return null;
+ }
+ }).execute();
+ }
}
}
private void redrawArea_(CoordRect area) {
if (!hasSurface) return;
- if (!preferences.optimizedDrawing) area = mapViewArea;
+ //if (!preferences.optimizedDrawing) area = mapViewArea;
final PredefinedMap currentMap = model.currentMap;
boolean b = currentMap.isOutside(area);
}
}
+ private boolean shouldRedrawEverythingForVisualEffect() {
+ if (preferences.optimizedDrawing) return false;
+ if (model.uiSelections.isInCombat) return false; // Discard the "optimized drawing" setting while in combat.
+ return true;
+ }
private final Rect redrawRect = new Rect();
- public void redrawAreaWithEffect(CoordRect area, final VisualEffectAnimation effect) {
+ public void redrawAreaWithEffect(CoordRect area, final VisualEffectAnimation effect, int tileID, int textYOffset, Paint textPaint) {
if (!hasSurface) return;
- if (!preferences.optimizedDrawing) area = mapViewArea;
+ if (shouldRedrawEverythingForVisualEffect()) area = mapViewArea;
final PredefinedMap currentMap = model.currentMap;
if (currentMap.isOutside(area)) return;
c.translate(screenOffset.x, screenOffset.y);
c.scale(scale, scale);
doDrawRect(c, area);
- drawFromMapPosition(c, area, effect.position, effect.currentTileID);
+ drawFromMapPosition(c, area, effect.position, tileID);
if (effect.displayText != null) {
- drawEffectText(c, area, effect);
+ drawEffectText(c, area, effect, textYOffset, textPaint);
}
}
} finally {
}
}
- private void drawEffectText(Canvas canvas, final CoordRect area, final VisualEffectAnimation e) {
+ private void drawEffectText(Canvas canvas, final CoordRect area, final VisualEffectAnimation e, int textYOffset, Paint textPaint) {
int x = (e.position.x - mapViewArea.topLeft.x) * tileSize + tileSize/2;
- int y = (e.position.y - mapViewArea.topLeft.y) * tileSize + tileSize/2 + e.textYOffset;
- canvas.drawText(e.displayText, x, y, e.textPaint);
+ int y = (e.position.y - mapViewArea.topLeft.y) * tileSize + tileSize/2 + textYOffset;
+ canvas.drawText(e.displayText, x, y, textPaint);
}
public void notifyMapChanged() {