v3.1.1: various stability improvements for low/inconsistent FPS:

- game now has a max delta time of 200ms
- added some checks to ensure non-looped animations do not complete twice
- adjusted 'enemy' reference in hero to 'attackTarget'
This commit is contained in:
Evan Debenham
2025-06-11 17:27:41 -04:00
parent 9fb18b3b45
commit b6dee52811
8 changed files with 32 additions and 30 deletions
@@ -283,7 +283,9 @@ public class Game implements ApplicationListener {
} }
protected void update() { protected void update() {
Game.elapsed = Game.timeScale * Gdx.graphics.getDeltaTime(); //game will not process more than 200ms of graphics time per frame
float frameDelta = Math.min(0.2f, Gdx.graphics.getDeltaTime());
Game.elapsed = Game.timeScale * frameDelta;
Game.timeTotal += Game.elapsed; Game.timeTotal += Game.elapsed;
Game.realTime = TimeUtils.millis(); Game.realTime = TimeUtils.millis();
@@ -63,9 +63,11 @@ public class MovieClip extends Image {
while (frameTimer > curAnim.delay) { while (frameTimer > curAnim.delay) {
frameTimer -= curAnim.delay; frameTimer -= curAnim.delay;
if (curFrame >= curAnim.frames.length - 1) { if (curFrame >= curAnim.frames.length - 1) {
curFrame = curAnim.frames.length - 1;
if (curAnim.looped) { if (curAnim.looped) {
curFrame = 0; curFrame = 0;
} else {
curFrame = curAnim.frames.length - 1;
frameTimer = 0;
} }
finished = true; finished = true;
if (listener != null) { if (listener != null) {
@@ -215,7 +215,8 @@ public class Hero extends Char {
public HeroAction curAction = null; public HeroAction curAction = null;
public HeroAction lastAction = null; public HeroAction lastAction = null;
private Char enemy; //reference to the enemy the hero is currently in the process of attacking
private Char attackTarget;
public boolean resting = false; public boolean resting = false;
@@ -460,7 +461,7 @@ public class Hero extends Char {
public boolean shoot( Char enemy, MissileWeapon wep ) { public boolean shoot( Char enemy, MissileWeapon wep ) {
this.enemy = enemy; attackTarget = enemy;
boolean wasEnemy = enemy.alignment == Alignment.ENEMY boolean wasEnemy = enemy.alignment == Alignment.ENEMY
|| (enemy instanceof Mimic && enemy.alignment == Alignment.NEUTRAL); || (enemy instanceof Mimic && enemy.alignment == Alignment.NEUTRAL);
@@ -479,6 +480,7 @@ public class Hero extends Char {
Buff.affect( this, Sai.ComboStrikeTracker.class).addHit(); Buff.affect( this, Sai.ComboStrikeTracker.class).addHit();
} }
attackTarget = null;
return hit; return hit;
} }
@@ -1373,15 +1375,15 @@ public class Hero extends Char {
private boolean actAttack( HeroAction.Attack action ) { private boolean actAttack( HeroAction.Attack action ) {
enemy = action.target; attackTarget = action.target;
if (isCharmedBy( enemy )){ if (isCharmedBy(attackTarget)){
GLog.w( Messages.get(Charm.class, "cant_attack")); GLog.w( Messages.get(Charm.class, "cant_attack"));
ready(); ready();
return false; return false;
} }
if (enemy.isAlive() && canAttack( enemy ) && enemy.invisible == 0) { if (attackTarget.isAlive() && canAttack(attackTarget) && attackTarget.invisible == 0) {
if (heroClass != HeroClass.DUELIST if (heroClass != HeroClass.DUELIST
&& hasTalent(Talent.AGGRESSIVE_BARRIER) && hasTalent(Talent.AGGRESSIVE_BARRIER)
@@ -1393,26 +1395,29 @@ public class Hero extends Char {
Buff.affect(this, Talent.AggressiveBarrierCooldown.class, 50f); Buff.affect(this, Talent.AggressiveBarrierCooldown.class, 50f);
} }
sprite.attack( enemy.pos ); //attack target cleared on onAttackComplete
sprite.attack( attackTarget.pos );
return false; return false;
} else { } else {
if (fieldOfView[enemy.pos] && getCloser( enemy.pos )) { if (fieldOfView[attackTarget.pos] && getCloser( attackTarget.pos )) {
attackTarget = null;
return true; return true;
} else { } else {
ready(); ready();
attackTarget = null;
return false; return false;
} }
} }
} }
public Char enemy(){ public Char attackTarget(){
return enemy; return attackTarget;
} }
public void rest( boolean fullRest ) { public void rest( boolean fullRest ) {
@@ -2257,23 +2262,23 @@ public class Hero extends Char {
@Override @Override
public void onAttackComplete() { public void onAttackComplete() {
if (enemy == null){ if (attackTarget == null){
curAction = null; curAction = null;
super.onAttackComplete(); super.onAttackComplete();
return; return;
} }
AttackIndicator.target(enemy); AttackIndicator.target(attackTarget);
boolean wasEnemy = enemy.alignment == Alignment.ENEMY boolean wasEnemy = attackTarget.alignment == Alignment.ENEMY
|| (enemy instanceof Mimic && enemy.alignment == Alignment.NEUTRAL); || (attackTarget instanceof Mimic && attackTarget.alignment == Alignment.NEUTRAL);
boolean hit = attack( enemy ); boolean hit = attack(attackTarget);
Invisibility.dispel(); Invisibility.dispel();
spend( attackDelay() ); spend( attackDelay() );
if (hit && subClass == HeroSubClass.GLADIATOR && wasEnemy){ if (hit && subClass == HeroSubClass.GLADIATOR && wasEnemy){
Buff.affect( this, Combo.class ).hit( enemy ); Buff.affect( this, Combo.class ).hit(attackTarget);
} }
if (hit && heroClass == HeroClass.DUELIST && wasEnemy){ if (hit && heroClass == HeroClass.DUELIST && wasEnemy){
@@ -2281,6 +2286,7 @@ public class Hero extends Char {
} }
curAction = null; curAction = null;
attackTarget = null;
super.onAttackComplete(); super.onAttackComplete();
} }
@@ -48,7 +48,7 @@ public class AssassinsBlade extends MeleeWeapon {
public int damageRoll(Char owner) { public int damageRoll(Char owner) {
if (owner instanceof Hero) { if (owner instanceof Hero) {
Hero hero = (Hero)owner; Hero hero = (Hero)owner;
Char enemy = hero.enemy(); Char enemy = hero.attackTarget();
if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) { if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) {
//deals 50% toward max to max on surprise, instead of min to max. //deals 50% toward max to max on surprise, instead of min to max.
int diff = max() - min(); int diff = max() - min();
@@ -62,7 +62,7 @@ public class Dagger extends MeleeWeapon {
public int damageRoll(Char owner) { public int damageRoll(Char owner) {
if (owner instanceof Hero) { if (owner instanceof Hero) {
Hero hero = (Hero)owner; Hero hero = (Hero)owner;
Char enemy = hero.enemy(); Char enemy = hero.attackTarget();
if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) { if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) {
//deals 75% toward max to max on surprise, instead of min to max. //deals 75% toward max to max on surprise, instead of min to max.
int diff = max() - min(); int diff = max() - min();
@@ -48,7 +48,7 @@ public class Dirk extends MeleeWeapon {
public int damageRoll(Char owner) { public int damageRoll(Char owner) {
if (owner instanceof Hero) { if (owner instanceof Hero) {
Hero hero = (Hero)owner; Hero hero = (Hero)owner;
Char enemy = hero.enemy(); Char enemy = hero.attackTarget();
if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) { if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) {
//deals 67% toward max to max on surprise, instead of min to max. //deals 67% toward max to max on surprise, instead of min to max.
int diff = max() - min(); int diff = max() - min();
@@ -22,7 +22,6 @@
package com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles; package com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles;
import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
@@ -39,18 +38,11 @@ public class Kunai extends MissileWeapon {
baseUses = 5; baseUses = 5;
} }
private Char enemy;
@Override
protected void onThrow(int cell) {
enemy = Actor.findChar(cell);
super.onThrow(cell);
}
@Override @Override
public int damageRoll(Char owner) { public int damageRoll(Char owner) {
if (owner instanceof Hero) { if (owner instanceof Hero) {
Hero hero = (Hero)owner; Hero hero = (Hero)owner;
Char enemy = hero.attackTarget();
if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) { if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) {
//deals 60% toward max to max on surprise, instead of min to max. //deals 60% toward max to max on surprise, instead of min to max.
int diff = max() - min(); int diff = max() - min();
@@ -50,7 +50,7 @@ public class ThrowingKnife extends MissileWeapon {
public int damageRoll(Char owner) { public int damageRoll(Char owner) {
if (owner instanceof Hero) { if (owner instanceof Hero) {
Hero hero = (Hero)owner; Hero hero = (Hero)owner;
Char enemy = hero.enemy(); Char enemy = hero.attackTarget();
if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) { if (enemy instanceof Mob && ((Mob) enemy).surprisedBy(hero)) {
//deals 75% toward max to max on surprise, instead of min to max. //deals 75% toward max to max on surprise, instead of min to max.
int diff = max() - min(); int diff = max() - min();