Smoother combat animations.

Load maps resources of adjacent maps in the background while on a map (reduces load times when changing maps)
Bugfix: Shop max hp and max ap on player overview screen.
Bump version number to beta3.

git-svn-id: https://andors-trail.googlecode.com/svn/trunk@185 08aca716-68be-ccc6-4d58-36f5abd142ac
This commit is contained in:
oskar.wiksten
2011-10-17 22:39:05 +00:00
parent 1554aa95c1
commit 2a46837b2c
11 changed files with 214 additions and 114 deletions

View File

@@ -4,7 +4,7 @@
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

View File

@@ -1374,7 +1374,7 @@
{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||||}

View File

@@ -20,7 +20,7 @@ public final class AndorsTrailApplication extends Application {
public static final boolean DEVELOPMENT_VALIDATEDATA = false;
public static final boolean DEVELOPMENT_DEBUGMESSAGES = false;
public static final int CURRENT_VERSION = 25;
public static final String CURRENT_VERSION_DISPLAY = "0.6.10b2";
public static final String CURRENT_VERSION_DISPLAY = "0.6.10b3";
public final WorldContext world = new WorldContext();
public final WorldSetup setup = new WorldSetup(world, this);

View File

@@ -127,7 +127,7 @@ public final class HeroinfoActivity_Stats extends Activity {
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() {

View File

@@ -179,7 +179,6 @@ public class ActorStatsController {
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);
@@ -192,6 +191,7 @@ public class ActorStatsController {
}
}
}
applyStatsModifierEffect(source, effect, 1);
}
private static void rollForConditionEffect(Actor actor, ActorConditionEffect conditionEffect) {
@@ -239,7 +239,9 @@ public class ActorStatsController {
view.mainActivity.mainview
, actor.position
, visualEffectID
, effectValue);
, effectValue
, null
, 0);
}
}

View File

@@ -13,6 +13,7 @@ import com.gpl.rpg.AndorsTrail.VisualEffectCollection;
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;
@@ -27,7 +28,7 @@ import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea;
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;
@@ -58,7 +59,6 @@ public final class CombatController {
updateTurnInfo();
}
public void exitCombat(boolean pickupLootBags) {
context.effectController.waitForCurrentEffect();
setCombatSelection(null, null);
context.mainActivity.combatview.setVisibility(View.GONE);
model.uiSelections.isInCombat = false;
@@ -146,7 +146,7 @@ public final class CombatController {
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()) {
@@ -157,7 +157,7 @@ public final class CombatController {
Monster m = getAdjacentMonster();
if (m == null) return;
setCombatSelection(m);
executeAttack();
executePlayerAttack();
}
}
@@ -169,13 +169,17 @@ public final class CombatController {
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;
@@ -190,24 +194,27 @@ public final class CombatController {
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();
}
@@ -279,6 +286,7 @@ public final class CombatController {
CombatController.this.handleNextMonsterAction();
}
};
public void endPlayerTurn() {
model.player.ap.current = 0;
for (MonsterSpawnArea a : model.currentMap.spawnAreas) {
@@ -317,42 +325,67 @@ public final class CombatController {
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;

View File

@@ -61,7 +61,6 @@ public final class Controller {
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;

View File

@@ -269,8 +269,10 @@ public final class MovementController implements TimedMessageTask.Callback {
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) {

View File

@@ -3,6 +3,7 @@ package com.gpl.rpg.AndorsTrail.controller;
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;
@@ -20,40 +21,39 @@ public final class VisualEffectController {
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) {
@@ -61,24 +61,66 @@ public final class VisualEffectController {
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);
@@ -87,40 +129,25 @@ public final class VisualEffectController {
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 static interface VisualEffectCompletedCallback {
public void onVisualEffectCompleted(int callbackValue);
}
public void waitForCurrentEffect() {
VisualEffectAnimation e = currentEffect;
if (e != null) {
e.safejoin();
}
}
public void killCurrentEffect() {
VisualEffectAnimation e = currentEffect;
if (e != null) {
e.killjoin();
}
}
public boolean isRunningVisualEffect() {
return currentEffect != null;
}
}

View File

@@ -7,6 +7,7 @@ import android.graphics.Bitmap;
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;
@@ -19,8 +20,10 @@ import com.gpl.rpg.AndorsTrail.model.item.ItemContainer;
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;
@@ -46,6 +49,7 @@ public final class TileManager {
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>();
@@ -69,6 +73,15 @@ public final class TileManager {
}
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) {
@@ -76,12 +89,7 @@ public final class TileManager {
}
}
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) {
@@ -125,7 +133,6 @@ public final class TileManager {
}
}
public void loadPreloadedTiles(Resources r) {
int maxTileID = tileCache.getMaxTileID();
for(int i = TileManager.CHAR_HERO; i <= maxTileID; ++i) {
@@ -133,4 +140,29 @@ public final class TileManager {
}
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();
}
}

View File

@@ -173,7 +173,7 @@ public final class MainView extends SurfaceView implements SurfaceHolder.Callbac
}
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);
@@ -198,10 +198,15 @@ public final class MainView extends SurfaceView implements SurfaceHolder.Callbac
}
}
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;
@@ -214,9 +219,9 @@ public final class MainView extends SurfaceView implements SurfaceHolder.Callbac
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 {
@@ -328,10 +333,10 @@ public final class MainView extends SurfaceView implements SurfaceHolder.Callbac
}
}
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() {