diff --git a/forge-gui-mobile/src/forge/adventure/character/CharacterSprite.java b/forge-gui-mobile/src/forge/adventure/character/CharacterSprite.java index 88ed76daff74..89789953a2b4 100644 --- a/forge-gui-mobile/src/forge/adventure/character/CharacterSprite.java +++ b/forge-gui-mobile/src/forge/adventure/character/CharacterSprite.java @@ -16,6 +16,7 @@ */ public class CharacterSprite extends MapActor { + private static final float MAX_ACTION_ANIMATION_DURATION = 5f; private final HashMap>> animations = new HashMap<>(); float timer; private Animation currentAnimation = null; @@ -128,30 +129,54 @@ static public Animation FlipAnimation(Animation an } public void setAnimation(AnimationTypes type) { - if (currentAnimationType != type) { + Animation animation = getAnimation(type, currentAnimationDir); + if (animation == null) { + return; + } + + if (currentAnimationType != type || currentAnimation != animation || isOneShotAnimation(type)) { currentAnimationType = type; - updateAnimation(); + currentAnimation = animation; + if (isOneShotAnimation(type)) { + timer = 0.0f; + } } } - private void updateAnimation() { - AnimationTypes aniType = currentAnimationType; - AnimationDirections aniDir = currentAnimationDir; - if (!animations.containsKey(aniType)) { - aniType = AnimationTypes.Idle; - } - if (!animations.containsKey(aniType)) { - return; + /** + * Returns the capped duration of an action animation in the sprite's current direction. + * Uses the supplied fallback when the atlas does not define that animation. + */ + public float getActionAnimationDuration(AnimationTypes type, float fallbackDuration) { + Animation animation = getAnimation(type, currentAnimationDir); + float duration = animation == null ? fallbackDuration : animation.getAnimationDuration(); + return Math.min(duration, MAX_ACTION_ANIMATION_DURATION); + } + + private Animation getAnimation(AnimationTypes type, AnimationDirections direction) { + HashMap> dirs = animations.get(type); + if (dirs == null || dirs.isEmpty()) { + return null; } - HashMap> dirs = animations.get(aniType); - if (!dirs.containsKey(aniDir)) { - aniDir = AnimationDirections.Right; + Animation animation = dirs.get(direction); + return animation == null ? dirs.get(AnimationDirections.Right) : animation; + } + + private boolean isOneShotAnimation(AnimationTypes type) { + return type == AnimationTypes.Attack + || type == AnimationTypes.Death + || type == AnimationTypes.Hit; + } + + private void updateAnimation() { + Animation animation = getAnimation(currentAnimationType, currentAnimationDir); + if (animation == null) { + animation = getAnimation(AnimationTypes.Idle, currentAnimationDir); } - if (!dirs.containsKey(aniDir)) { - return; + if (animation != null) { + currentAnimation = animation; } - currentAnimation = dirs.get(aniDir); } public void setDirection(AnimationDirections dir) { @@ -249,7 +274,7 @@ public void draw(Batch batch, float parentAlpha) { if (currentAnimationType.equals(AnimationTypes.Wake)) { currentFrame = currentAnimation.getKeyFrame(wakeTimer, false); } else { - currentFrame = currentAnimation.getKeyFrame(timer, true); + currentFrame = currentAnimation.getKeyFrame(timer, !isOneShotAnimation(currentAnimationType)); } float scale = 1f; diff --git a/forge-gui-mobile/src/forge/adventure/stage/GameStage.java b/forge-gui-mobile/src/forge/adventure/stage/GameStage.java index 4944c9822a4e..d384dea8b885 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/GameStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/GameStage.java @@ -655,8 +655,10 @@ public void resetPlayerLocation() { PointOfInterest poi = Current.world().findPointsOfInterest("Spawn"); if (poi != null) { Forge.advFreezePlayerControls = true; - getPlayerSprite().setAnimation(CharacterSprite.AnimationTypes.Death); - getPlayerSprite().playEffect(Paths.EFFECT_BLOOD, 0.5f); + PlayerSprite playerSprite = getPlayerSprite(); + playerSprite.setAnimation(CharacterSprite.AnimationTypes.Death); + playerSprite.playEffect(Paths.EFFECT_BLOOD, 0.5f); + float deathDuration = playerSprite.getActionAnimationDuration(CharacterSprite.AnimationTypes.Death, 1f); Timer.schedule(new Timer.Task() { @Override public void run() { @@ -669,7 +671,7 @@ public void run() { Forge.clearTransitionScreen(); }, Forge.takeScreenshot())))); } - }, 1f); + }, deathDuration); }//Spawn shouldn't be null } @@ -677,14 +679,16 @@ public void defeatedFromBoss() { if (!Current.player().hasEquippedItem()) return; Forge.advFreezePlayerControls = true; - getPlayerSprite().setAnimation(CharacterSprite.AnimationTypes.Hit); - getPlayerSprite().playEffect(Paths.EFFECT_BLOOD, 0.5f); + PlayerSprite playerSprite = getPlayerSprite(); + playerSprite.setAnimation(CharacterSprite.AnimationTypes.Hit); + playerSprite.playEffect(Paths.EFFECT_BLOOD, 0.5f); + float hitDuration = playerSprite.getActionAnimationDuration(CharacterSprite.AnimationTypes.Hit, 1f); Timer.schedule(new Timer.Task() { @Override public void run() { showImageDialog(Current.generateDefeatMessage(false), getDefeatBadge(), () -> Forge.advFreezePlayerControls = false); } - }, 1f); + }, hitDuration); } private FBufferedImage getDefeatBadge() { diff --git a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java index 276c8597f327..cd2fcd7c7f37 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java @@ -828,13 +828,16 @@ public void setWinner(boolean playerWins, boolean isArena) { currentMob.clearCollisionHeight(); Current.player().win(); player.setAnimation(CharacterSprite.AnimationTypes.Attack); + float attackDuration = Math.max(1f, + player.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 1f)); currentMob.playEffect(Paths.EFFECT_BLOOD, 0.5f); Timer.schedule(new Timer.Task() { @Override public void run() { currentMob.setAnimation(CharacterSprite.AnimationTypes.Death); currentMob.resetCollisionHeight(); - startPause(0.3f, () -> { + float deathDuration = currentMob.getActionAnimationDuration(CharacterSprite.AnimationTypes.Death, 0.3f); + startPause(deathDuration, () -> { MapStage.this.getReward(); AdventureQuestController.instance().updateQuestsWin(currentMob,enemies); AdventureQuestController.instance().showQuestDialogs(MapStage.this); @@ -842,12 +845,15 @@ public void run() { }); player.setAnimation(CharacterSprite.AnimationTypes.Idle); } - }, 1f); + }, attackDuration); } else { currentMob.clearCollisionHeight(); player.setAnimation(CharacterSprite.AnimationTypes.Hit); currentMob.setAnimation(CharacterSprite.AnimationTypes.Attack); - startPause(0.3f, () -> { + float resultAnimationDuration = Math.max( + player.getActionAnimationDuration(CharacterSprite.AnimationTypes.Hit, 0.3f), + currentMob.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 0.3f)); + startPause(resultAnimationDuration, () -> { player.setAnimation(CharacterSprite.AnimationTypes.Idle); currentMob.setAnimation(CharacterSprite.AnimationTypes.Idle); currentMob.resetCollisionHeight(); @@ -1145,7 +1151,10 @@ public void beginDuel(EnemySprite mob) { HapticEngine.vibrate(FPref.UI_VIBRATE_ON_ENEMY_ENCOUNTER, mob.getData().boss ? 400 : 200); Forge.advFreezePlayerControls = true; player.clearCollisionHeight(); - startPause(0.8f, () -> { + float attackDuration = Math.max( + player.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 0.8f), + mob.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 0.8f)); + startPause(attackDuration, () -> { if (started) return; started = true; diff --git a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java index a843e4b434ae..e4620a978eca 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/WorldStage.java @@ -121,7 +121,10 @@ protected void onActing(float delta) { HapticEngine.vibrate(FPref.UI_VIBRATE_ON_ENEMY_ENCOUNTER, mob.getData().boss ? 400 : 200); Forge.advFreezePlayerControls = true; player.clearCollisionHeight(); - startPause(0.8f, () -> { + float attackDuration = Math.max( + player.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 0.8f), + mob.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 0.8f)); + startPause(attackDuration, () -> { Forge.setCursor(null, Forge.magnifyToggle ? "1" : "2"); SoundSystem.instance.play(SoundEffectType.ManaBurn, false); DuelScene duelScene = DuelScene.instance(); @@ -164,13 +167,16 @@ public void setWinner(boolean playerIsWinner, boolean isArena) { currentMob.clearCollisionHeight(); Current.player().win(); player.setAnimation(CharacterSprite.AnimationTypes.Attack); + float attackDuration = Math.max(1f, + player.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 1f)); currentMob.playEffect(Paths.EFFECT_BLOOD, 0.5f); Timer.schedule(new Timer.Task() { @Override public void run() { currentMob.setAnimation(CharacterSprite.AnimationTypes.Death); currentMob.resetCollisionHeight(); - startPause(0.3f, () -> { + float deathDuration = currentMob.getActionAnimationDuration(CharacterSprite.AnimationTypes.Death, 0.3f); + startPause(deathDuration, () -> { RewardScene.instance().loadRewards(currentMob.getRewards(), RewardScene.Type.Loot, null); WorldStage.this.removeEnemy(currentMob); AdventureQuestController.instance().updateQuestsWin(currentMob); @@ -179,12 +185,15 @@ public void run() { currentMob = null; }); } - }, 1f); + }, attackDuration); } else { currentMob.clearCollisionHeight(); player.setAnimation(CharacterSprite.AnimationTypes.Hit); currentMob.setAnimation(CharacterSprite.AnimationTypes.Attack); - startPause(0.5f, () -> { + float resultAnimationDuration = Math.max( + player.getActionAnimationDuration(CharacterSprite.AnimationTypes.Hit, 0.5f), + currentMob.getActionAnimationDuration(CharacterSprite.AnimationTypes.Attack, 0.5f)); + startPause(resultAnimationDuration, () -> { currentMob.resetCollisionHeight(); boolean defeated = Current.player().defeated(); AdventureQuestController.instance().updateQuestsLose(currentMob);