v3.2.0: massively expanded Ferret Tuft's miss icon functionality

This commit is contained in:
Evan Debenham
2025-07-14 17:35:34 -04:00
parent 9dfcb4b80e
commit ff7a4238a7
9 changed files with 342 additions and 50 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -590,19 +590,19 @@ public abstract class Char extends Actor {
} else {
if (enemy.sprite != null){
if (tuftDodged){
if (hitMissIcon != -1){
//dooking is a playful sound Ferrets can make, like low pitched chirping
// I doubt this will translate, so it's only in English
if (Messages.lang() == Languages.ENGLISH && Random.Int(10) == 0) {
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, "dooked", FloatingText.TUFT);
if (hitMissIcon == FloatingText.MISS_TUFT && Messages.lang() == Languages.ENGLISH && Random.Int(10) == 0) {
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, "dooked", hitMissIcon);
} else {
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, enemy.defenseVerb(), FloatingText.TUFT);
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, enemy.defenseVerb(), hitMissIcon);
}
hitMissIcon = -1;
} else {
enemy.sprite.showStatus(CharSprite.NEUTRAL, enemy.defenseVerb());
}
}
tuftDodged = false;
if (visibleFight) {
//TODO enemy.defenseSound? currently miss plays for monks/crab even when they parry
Sample.INSTANCE.play(Assets.Sounds.MISS);
@@ -611,6 +611,7 @@ public abstract class Char extends Actor {
return false;
}
}
public static int INFINITE_ACCURACY = 1_000_000;
@@ -640,8 +641,10 @@ public abstract class Char extends Actor {
//if accuracy or evasion are large enough, treat them as infinite.
//note that infinite evasion beats infinite accuracy
if (defStat >= INFINITE_EVASION){
hitMissIcon = FloatingText.getMissReasonIcon(attacker, acuStat, defender, INFINITE_EVASION);
return false;
} else if (acuStat >= INFINITE_ACCURACY){
hitMissIcon = FloatingText.getHitReasonIcon(attacker, INFINITE_ACCURACY, defender, defStat);
return true;
}
@@ -675,17 +678,18 @@ public abstract class Char extends Actor {
// + 3%/5%
defRoll *= 1.01f + 0.02f*Dungeon.hero.pointsInTalent(Talent.BLESS);
}
if (defRoll < acuRoll && (defRoll*FerretTuft.evasionMultiplier()) >= acuRoll){
tuftDodged = true;
}
defRoll *= FerretTuft.evasionMultiplier();
return acuRoll >= defRoll;
if (acuRoll >= defRoll){
hitMissIcon = FloatingText.getHitReasonIcon(attacker, acuRoll, defender, defRoll);
return true;
} else {
hitMissIcon = FloatingText.getMissReasonIcon(attacker, acuRoll, defender, defRoll);
return false;
}
}
//TODO this is messy and hacky atm, should consider standardizing this so we can have many 'dodge reasons'
private static boolean tuftDodged = false;
private static int hitMissIcon = -1;
public int attackSkill( Char target ) {
return 0;
@@ -1011,6 +1015,12 @@ public abstract class Char extends Actor {
if (src instanceof Corruption) icon = FloatingText.CORRUPTION;
if (src instanceof AscensionChallenge) icon = FloatingText.AMULET;
if ((icon == FloatingText.PHYS_DMG || icon == FloatingText.PHYS_DMG_NO_BLOCK) && hitMissIcon != -1){
if (icon == FloatingText.PHYS_DMG_NO_BLOCK) hitMissIcon += 18; //extra row
icon = hitMissIcon;
}
hitMissIcon = -1;
sprite.showStatusWithIcon(CharSprite.NEGATIVE, Integer.toString(dmg + shielded), icon);
}

View File

@@ -484,6 +484,20 @@ public class Hero extends Char {
return hit;
}
@Override
public boolean attack(Char enemy, float dmgMulti, float dmgBonus, float accMulti) {
boolean result = super.attack(enemy, dmgMulti, dmgBonus, accMulti);
if (!(belongings.attackingWeapon() instanceof MissileWeapon)){
if (buff(Talent.PreciseAssaultTracker.class) != null){
buff(Talent.PreciseAssaultTracker.class).detach();
} else if (buff(Talent.LiquidAgilACCTracker.class) != null
&& buff(Talent.LiquidAgilACCTracker.class).uses <= 0){
buff(Talent.LiquidAgilACCTracker.class).detach();
}
}
return result;
}
@Override
public int attackSkill( Char target ) {
KindOfWeapon wep = belongings.attackingWeapon();
@@ -517,17 +531,17 @@ public class Hero extends Char {
case 3:
accuracy *= Float.POSITIVE_INFINITY; break;
}
buff(Talent.PreciseAssaultTracker.class).detach();
} else if (buff(Talent.LiquidAgilACCTracker.class) != null){
// 3x/inf. ACC, depending on talent level
accuracy *= pointsInTalent(Talent.LIQUID_AGILITY) == 2 ? Float.POSITIVE_INFINITY : 3f;
Talent.LiquidAgilACCTracker buff = buff(Talent.LiquidAgilACCTracker.class);
buff.uses--;
if (buff.uses <= 0) {
buff.detach();
}
}
}
} else {
if (buff(Momentum.class) != null && buff(Momentum.class).freerunning()){
accuracy *= 1f + pointsInTalent(Talent.PROJECTILE_MOMENTUM)/3f;
}
}
if (buff(Scimitar.SwordDance.class) != null){
@@ -535,9 +549,9 @@ public class Hero extends Char {
}
if (!RingOfForce.fightingUnarmed(this)) {
return (int)(attackSkill * accuracy * wep.accuracyFactor( this, target ));
return Math.max(1, Math.round(attackSkill * accuracy * wep.accuracyFactor( this, target )));
} else {
return (int)(attackSkill * accuracy);
return Math.max(1, Math.round(attackSkill * accuracy));
}
}
@@ -579,7 +593,7 @@ public class Hero extends Char {
evasion = belongings.armor().evasionFactor(this, evasion);
}
return Math.round(evasion);
return Math.max(1, Math.round(evasion));
}
@Override

View File

@@ -69,6 +69,10 @@ public class Statue extends Mob {
weapon.cursed = false;
weapon.enchant( Enchantment.random() );
}
public Weapon weapon(){
return weapon;
}
private static final String WEAPON = "weapon";

View File

@@ -22,6 +22,31 @@
package com.shatteredpixel.shatteredpixeldungeon.effects;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Bless;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ChampionEnemy;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Daze;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hex;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Momentum;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.GuidingLight;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.ArmoredStatue;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Statue;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.MirrorImage;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.PrismaticImage;
import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfAccuracy;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfEvasion;
import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.FerretTuft;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Quarterstaff;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Scimitar;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
@@ -35,14 +60,18 @@ import com.watabou.utils.Callback;
import com.watabou.utils.SparseArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
public class FloatingText extends RenderedTextBlock {
private static final float LIFESPAN = 1f;
private static final float DISTANCE = DungeonTilemap.SIZE;
public static final int ICON_SIZE = 7;
public static TextureFilm iconFilm = new TextureFilm( Assets.Effects.TEXT_ICONS, ICON_SIZE, ICON_SIZE );
public static final int ICON_WIDTH = 7;
public static final int ICON_HEIGHT = 8;
public static TextureFilm iconFilm = new TextureFilm( Assets.Effects.TEXT_ICONS, ICON_WIDTH, ICON_HEIGHT );
public static int NO_ICON = -1;
@@ -77,8 +106,34 @@ public class FloatingText extends RenderedTextBlock {
public static int GOLD = 23;
public static int ENERGY = 24;
//dodge icons
public static int TUFT = 26;
//hit reason icons
public static int HIT_WEP = 36;
public static int HIT_ARM = 37;
public static int HIT_BLS = 38;
public static int HIT_HEX = 39;
public static int HIT_DAZE = 40;
public static int HIT_ACC = 41;
public static int HIT_EVA = 42;
public static int HIT_LIQ = 43;
public static int HIT_DANCE = 44;
public static int HIT_SUPR = 45;
public static int HIT_PRES = 46;
public static int HIT_MOMEN = 47;
//extra row for hit icons that are armor-piercing
//miss reason icons
public static int MISS_WEP = 72;
public static int MISS_ARM = 73;
public static int MISS_BLS = 74;
public static int MISS_HEX = 75;
public static int MISS_DAZE = 76;
public static int MISS_ACC = 77;
public static int MISS_EVA = 78;
public static int MISS_LIQ = 79;
public static int MISS_DEF = 80;
public static int MISS_TUFT = 81;
public static int MISS_RUN = 82;
private Image icon;
private boolean iconLeft;
@@ -247,4 +302,201 @@ public class FloatingText extends RenderedTextBlock {
stack.add(txt);
}
}
// *** These are for custom icons that show impact of effects on top of base acc/eve ***
// This logic is honestly a mess. Rather than tracking these modifiers as we
// perform acc/eva calculation, we check and 'unwind' the modifiers from the final rolls.
// This results in a lot of duplicated logic/numbers and awkward workarounds.
// It does work though... mostly.
public static int getHitReasonIcon(Char attacker, float accRoll, Char defender, float defRoll){
HashMap<Integer, Float> hitReasons = new HashMap<>();
//go through some garunteed hit interactions first
if (defRoll == 0 && defender.buff(GuidingLight.Illuminated.class) != null){
return HIT_BLS;
}
if (accRoll == Char.INFINITE_ACCURACY && attacker.invisible > 0){
return HIT_SUPR;
}
if (defRoll == 0 && defender instanceof Mob && ((Mob) defender).surprisedBy(attacker)){
return HIT_SUPR;
}
if (accRoll == Char.INFINITE_ACCURACY
&& attacker.buff(Talent.PreciseAssaultTracker.class) != null){
return HIT_PRES;
}
if (accRoll == Char.INFINITE_ACCURACY
&& attacker.buff(Talent.LiquidAgilACCTracker.class) != null){
return HIT_LIQ;
}
KindOfWeapon wep = null;
if (attacker instanceof Hero) wep = ((Hero) attacker).belongings.attackingWeapon();
if (attacker instanceof MirrorImage) wep = Dungeon.hero.belongings.weapon();
if (attacker instanceof Statue) wep = ((Statue)attacker).weapon();
if (attacker instanceof DriedRose.GhostHero) wep = ((DriedRose.GhostHero)attacker).weapon();
Armor arm = null;
if (defender instanceof Hero) arm = ((Hero) defender).belongings.armor();
if (defender instanceof PrismaticImage) arm = Dungeon.hero.belongings.armor();
if (defender instanceof ArmoredStatue) arm = ((ArmoredStatue)defender).armor();
if (defender instanceof DriedRose.GhostHero) arm = ((DriedRose.GhostHero)defender).armor();
//accuracy boosts (always > 1)
if (wep != null && wep.accuracyFactor(attacker, defender) > 1){
hitReasons.put( HIT_WEP, wep.accuracyFactor(attacker, defender));
}
float blessBoost = 1; //a few different sources contribute to this icon
if (attacker.buff(ChampionEnemy.class) != null
&& attacker.buff(ChampionEnemy.class).evasionAndAccuracyFactor() > 1){
blessBoost *= attacker.buff(ChampionEnemy.class).evasionAndAccuracyFactor();
}
if (attacker.buff(Bless.class) != null) blessBoost *= 1.25f;
if (Dungeon.hero.heroClass != HeroClass.CLERIC
&& Dungeon.hero.hasTalent(Talent.BLESS)
&& attacker.alignment == Char.Alignment.ALLY){
// + 3%/5%
blessBoost *= 1.01f + 0.02f*Dungeon.hero.pointsInTalent(Talent.BLESS);
}
if (blessBoost > 1f) hitReasons.put(HIT_BLS, blessBoost);
if (RingOfAccuracy.accuracyMultiplier(attacker) > 1) hitReasons.put(HIT_ACC, RingOfAccuracy.accuracyMultiplier(attacker));
if (attacker.buff(Scimitar.SwordDance.class) != null) hitReasons.put(HIT_DANCE, 1.5f);
if (!(wep instanceof MissileWeapon)) {
if (attacker.buff(Talent.PreciseAssaultTracker.class) != null){
hitReasons.put(HIT_PRES, Dungeon.hero.pointsInTalent(Talent.PRECISE_ASSAULT) == 2 ? 5f : 2f);
} else if (attacker.buff(Talent.LiquidAgilACCTracker.class) != null) {
hitReasons.put(HIT_LIQ, 3f);
}
} else {
if (attacker.buff(Momentum.class) != null
&& attacker.buff(Momentum.class).freerunning()
&& ((Hero)attacker).hasTalent(Talent.PROJECTILE_MOMENTUM)) {
hitReasons.put(HIT_MOMEN, 1f + ((Hero) attacker).pointsInTalent(Talent.PROJECTILE_MOMENTUM) / 3f);
}
}
//evasion reductions (always < 1)
if (defender.buff(Hex.class) != null) hitReasons.put(HIT_HEX, 0.8f);
if (defender.buff(Daze.class) != null) hitReasons.put(HIT_DAZE, 0.5f);
if (RingOfEvasion.evasionMultiplier(defender) < 1) hitReasons.put(HIT_EVA, RingOfEvasion.evasionMultiplier(defender));
if (arm != null && arm.evasionFactor(defender, 100) < 100) {
//we express armor's normally flat evasion boost as a %, yes this is very awkward
Armor.testingNoArmDefSkill = true;
int baseDef = defender.defenseSkill(attacker);
Armor.testingNoArmDefSkill = false;
hitReasons.put(HIT_ARM, defender.defenseSkill(attacker)/(float)baseDef);
}
//hero specifically gets 1/2 eva when stunned, for mobs its a garunteed hit
if (defender.paralysed > 0) {
if (defender instanceof Hero) hitReasons.put(HIT_SUPR, 0.5f);
else return HIT_SUPR;
}
//sort from largest modifier to smallest one
ArrayList<Integer> sortedReasons = new ArrayList<>(hitReasons.keySet());
Collections.sort(sortedReasons, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
float a1 = hitReasons.get(a) >= 1f ? hitReasons.get(a) : 1 / hitReasons.get(a);
float b1 = hitReasons.get(b) >= 1f ? hitReasons.get(b) : 1 / hitReasons.get(b);
return (int)Math.signum(b1 - a1);
}
});
for (Integer reason : sortedReasons){
if (hitReasons.get(reason) >= 1f) {
accRoll /= hitReasons.get(reason);
} else {
defRoll /= hitReasons.get(reason);
}
if (accRoll < defRoll){
return reason;
}
}
return -1;
}
public static int getMissReasonIcon(Char attacker, float accRoll, Char defender, float defRoll){
HashMap<Integer, Float> missReasons = new HashMap<>();
//some guaranteed dodged first
if (defRoll == Char.INFINITE_EVASION && defender.buff(Talent.LiquidAgilEVATracker.class) != null){
return MISS_LIQ;
}
KindOfWeapon wep = null;
if (attacker instanceof Hero) wep = ((Hero) attacker).belongings.attackingWeapon();
if (attacker instanceof MirrorImage) wep = Dungeon.hero.belongings.weapon();
if (attacker instanceof Statue) wep = ((Statue)attacker).weapon();
if (attacker instanceof DriedRose.GhostHero) wep = ((DriedRose.GhostHero)attacker).weapon();
Armor arm = null;
if (defender instanceof Hero) arm = ((Hero) defender).belongings.armor();
if (defender instanceof PrismaticImage) arm = Dungeon.hero.belongings.armor();
if (defender instanceof ArmoredStatue) arm = ((ArmoredStatue)defender).armor();
if (defender instanceof DriedRose.GhostHero) arm = ((DriedRose.GhostHero)defender).armor();
//evasion boosts (always > 1)
float blessBoost = 1; //a few different sources contribute to this icon
if (defender.buff(ChampionEnemy.class) != null
&& defender.buff(ChampionEnemy.class).evasionAndAccuracyFactor() > 1){
blessBoost *= defender.buff(ChampionEnemy.class).evasionAndAccuracyFactor();
}
if (defender.buff(Bless.class) != null) blessBoost *= 1.25f;
if (Dungeon.hero.heroClass != HeroClass.CLERIC
&& Dungeon.hero.hasTalent(Talent.BLESS)
&& defender.alignment == Char.Alignment.ALLY){
// + 3%/5%
blessBoost *= 1.01f + 0.02f*Dungeon.hero.pointsInTalent(Talent.BLESS);
}
if (blessBoost > 1f) missReasons.put(MISS_BLS, blessBoost);
if (FerretTuft.evasionMultiplier() > 1) missReasons.put(MISS_TUFT, FerretTuft.evasionMultiplier());
if (RingOfEvasion.evasionMultiplier(defender) > 1) missReasons.put(MISS_EVA, RingOfEvasion.evasionMultiplier(defender));
if (defender.buff(Quarterstaff.DefensiveStance.class) != null) missReasons.put(MISS_DEF, 3f);
if (arm != null && arm.evasionFactor(defender, 100) > 100) {
//we express armor's normally flat evasion boost as a %, yes this is very awkward
Armor.testingNoArmDefSkill = true;
int baseDef = defender.defenseSkill(attacker);
Armor.testingNoArmDefSkill = false;
if (defender.buff(Momentum.class) != null){
//this is cheating a little, as evasion aug gets wrapped into this too
missReasons.put(MISS_RUN, defender.defenseSkill(attacker) / (float) baseDef);
} else {
missReasons.put(MISS_ARM, defender.defenseSkill(attacker) / (float) baseDef);
}
}
if (defender.buff(Talent.LiquidAgilEVATracker.class) != null) missReasons.put(MISS_LIQ, 3f);
//accuracy reductions (always < 1)
if (wep != null && wep.accuracyFactor(attacker, defender) < 1){
missReasons.put( MISS_WEP, wep.accuracyFactor(attacker, defender));
}
if (attacker.buff(Hex.class) != null) missReasons.put(MISS_HEX, 0.8f);
if (attacker.buff(Daze.class) != null) missReasons.put(MISS_DAZE, 0.5f);
if (RingOfAccuracy.accuracyMultiplier(attacker) < 1) missReasons.put(MISS_ACC, RingOfAccuracy.accuracyMultiplier(attacker));
//sort from largest modifier to smallest one
ArrayList<Integer> sortedReasons = new ArrayList<>(missReasons.keySet());
Collections.sort(sortedReasons, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
float a1 = missReasons.get(a) >= 1f ? missReasons.get(a) : 1 / missReasons.get(a);
float b1 = missReasons.get(b) >= 1f ? missReasons.get(b) : 1 / missReasons.get(b);
return (int)Math.signum(b1 - a1);
}
});
for (Integer reason : sortedReasons){
if (missReasons.get(reason) >= 1f) {
defRoll /= missReasons.get(reason);
} else {
accRoll /= missReasons.get(reason);
}
if (defRoll < accRoll){
return reason;
}
}
return -1;
}
}

View File

@@ -403,8 +403,13 @@ public class Armor extends EquipableItem {
return lvl;
}
}
//This exists so we can test what a char's base evasion would be without armor affecting it
//more ugly static vars yaaay~
public static boolean testingNoArmDefSkill = false;
public float evasionFactor( Char owner, float evasion ){
if (testingNoArmDefSkill) return evasion;
if (hasGlyph(Stone.class, owner) && !Stone.testingEvasion()){
return 0;

View File

@@ -588,6 +588,16 @@ public class DriedRose extends Artifact {
HT = 20 + 8*rose.level();
}
public Weapon weapon(){
if (rose != null) return rose.weapon;
else return null;
}
public Armor armor(){
if (rose != null) return rose.armor;
else return null;
}
@Override
protected boolean act() {
updateRose();
@@ -611,8 +621,8 @@ public class DriedRose extends Artifact {
//same accuracy as the hero.
int acc = Dungeon.hero.lvl + 9;
if (rose != null && rose.weapon != null){
acc *= rose.weapon.accuracyFactor( this, target );
if (weapon() != null){
acc *= weapon().accuracyFactor( this, target );
}
return acc;
@@ -621,22 +631,22 @@ public class DriedRose extends Artifact {
@Override
public float attackDelay() {
float delay = super.attackDelay();
if (rose != null && rose.weapon != null){
delay *= rose.weapon.delayFactor(this);
if (weapon() != null){
delay *= weapon().delayFactor(this);
}
return delay;
}
@Override
protected boolean canAttack(Char enemy) {
return super.canAttack(enemy) || (rose != null && rose.weapon != null && rose.weapon.canReach(this, enemy.pos));
return super.canAttack(enemy) || (weapon() != null && weapon().canReach(this, enemy.pos));
}
@Override
public int damageRoll() {
int dmg = 0;
if (rose != null && rose.weapon != null){
dmg += rose.weapon.damageRoll(this);
if (weapon() != null){
dmg += weapon().damageRoll(this);
} else {
dmg += Random.NormalIntRange(0, 5);
}
@@ -647,13 +657,12 @@ public class DriedRose extends Artifact {
@Override
public int attackProc(Char enemy, int damage) {
damage = super.attackProc(enemy, damage);
if (rose != null) {
if (rose.weapon != null) {
damage = rose.weapon.proc(this, enemy, damage);
if (!enemy.isAlive() && enemy == Dungeon.hero) {
Dungeon.fail(this);
GLog.n(Messages.capitalize(Messages.get(Char.class, "kill", name())));
}
if (weapon() != null) {
damage = weapon().proc(this, enemy, damage);
if (!enemy.isAlive() && enemy == Dungeon.hero) {
Dungeon.fail(this);
GLog.n(Messages.capitalize(Messages.get(Char.class, "kill", name())));
}
}
@@ -662,8 +671,8 @@ public class DriedRose extends Artifact {
@Override
public int defenseProc(Char enemy, int damage) {
if (rose != null && rose.armor != null) {
damage = rose.armor.proc( enemy, this, damage );
if (armor() != null) {
damage = armor().proc( enemy, this, damage );
}
return super.defenseProc(enemy, damage);
}
@@ -694,8 +703,8 @@ public class DriedRose extends Artifact {
public int defenseSkill(Char enemy) {
int defense = super.defenseSkill(enemy);
if (defense != 0 && rose != null && rose.armor != null ){
defense = Math.round(rose.armor.evasionFactor( this, defense ));
if (defense != 0 && armor() != null ){
defense = Math.round(armor().evasionFactor( this, defense ));
}
return defense;
@@ -704,19 +713,19 @@ public class DriedRose extends Artifact {
@Override
public int drRoll() {
int dr = super.drRoll();
if (rose != null && rose.armor != null){
dr += Random.NormalIntRange( rose.armor.DRMin(), rose.armor.DRMax());
if (armor() != null){
dr += Random.NormalIntRange( armor().DRMin(), armor().DRMax());
}
if (rose != null && rose.weapon != null){
dr += Random.NormalIntRange( 0, rose.weapon.defenseFactor( this ));
if (weapon() != null){
dr += Random.NormalIntRange( 0, weapon().defenseFactor( this ));
}
return dr;
}
@Override
public int glyphLevel(Class<? extends Armor.Glyph> cls) {
if (rose != null && rose.armor != null && rose.armor.hasGlyph(cls, this)){
return Math.max(super.glyphLevel(cls), rose.armor.buffedLvl());
if (armor() != null && armor().hasGlyph(cls, this)){
return Math.max(super.glyphLevel(cls), armor().buffedLvl());
} else {
return super.glyphLevel(cls);
}

View File

@@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon.items.trinkets;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
//🍋‍🟩
public class FerretTuft extends Trinket {
{

View File

@@ -197,9 +197,6 @@ abstract public class MissileWeapon extends Weapon {
@Override
public float accuracyFactor(Char owner, Char target) {
float accFactor = super.accuracyFactor(owner, target);
if (owner instanceof Hero && owner.buff(Momentum.class) != null && owner.buff(Momentum.class).freerunning()){
accFactor *= 1f + ((Hero) owner).pointsInTalent(Talent.PROJECTILE_MOMENTUM)/3f;
}
accFactor *= adjacentAccFactor(owner, target);