v3.2.0: massively expanded Ferret Tuft's miss icon functionality
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 931 B |
@@ -590,19 +590,19 @@ public abstract class Char extends Actor {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (enemy.sprite != null){
|
if (enemy.sprite != null){
|
||||||
if (tuftDodged){
|
if (hitMissIcon != -1){
|
||||||
//dooking is a playful sound Ferrets can make, like low pitched chirping
|
//dooking is a playful sound Ferrets can make, like low pitched chirping
|
||||||
// I doubt this will translate, so it's only in English
|
// I doubt this will translate, so it's only in English
|
||||||
if (Messages.lang() == Languages.ENGLISH && Random.Int(10) == 0) {
|
if (hitMissIcon == FloatingText.MISS_TUFT && Messages.lang() == Languages.ENGLISH && Random.Int(10) == 0) {
|
||||||
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, "dooked", FloatingText.TUFT);
|
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, "dooked", hitMissIcon);
|
||||||
} else {
|
} else {
|
||||||
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, enemy.defenseVerb(), FloatingText.TUFT);
|
enemy.sprite.showStatusWithIcon(CharSprite.NEUTRAL, enemy.defenseVerb(), hitMissIcon);
|
||||||
}
|
}
|
||||||
|
hitMissIcon = -1;
|
||||||
} else {
|
} else {
|
||||||
enemy.sprite.showStatus(CharSprite.NEUTRAL, enemy.defenseVerb());
|
enemy.sprite.showStatus(CharSprite.NEUTRAL, enemy.defenseVerb());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tuftDodged = false;
|
|
||||||
if (visibleFight) {
|
if (visibleFight) {
|
||||||
//TODO enemy.defenseSound? currently miss plays for monks/crab even when they parry
|
//TODO enemy.defenseSound? currently miss plays for monks/crab even when they parry
|
||||||
Sample.INSTANCE.play(Assets.Sounds.MISS);
|
Sample.INSTANCE.play(Assets.Sounds.MISS);
|
||||||
@@ -611,6 +611,7 @@ public abstract class Char extends Actor {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int INFINITE_ACCURACY = 1_000_000;
|
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.
|
//if accuracy or evasion are large enough, treat them as infinite.
|
||||||
//note that infinite evasion beats infinite accuracy
|
//note that infinite evasion beats infinite accuracy
|
||||||
if (defStat >= INFINITE_EVASION){
|
if (defStat >= INFINITE_EVASION){
|
||||||
|
hitMissIcon = FloatingText.getMissReasonIcon(attacker, acuStat, defender, INFINITE_EVASION);
|
||||||
return false;
|
return false;
|
||||||
} else if (acuStat >= INFINITE_ACCURACY){
|
} else if (acuStat >= INFINITE_ACCURACY){
|
||||||
|
hitMissIcon = FloatingText.getHitReasonIcon(attacker, INFINITE_ACCURACY, defender, defStat);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,17 +678,18 @@ public abstract class Char extends Actor {
|
|||||||
// + 3%/5%
|
// + 3%/5%
|
||||||
defRoll *= 1.01f + 0.02f*Dungeon.hero.pointsInTalent(Talent.BLESS);
|
defRoll *= 1.01f + 0.02f*Dungeon.hero.pointsInTalent(Talent.BLESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defRoll < acuRoll && (defRoll*FerretTuft.evasionMultiplier()) >= acuRoll){
|
|
||||||
tuftDodged = true;
|
|
||||||
}
|
|
||||||
defRoll *= FerretTuft.evasionMultiplier();
|
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 int hitMissIcon = -1;
|
||||||
private static boolean tuftDodged = false;
|
|
||||||
|
|
||||||
public int attackSkill( Char target ) {
|
public int attackSkill( Char target ) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1011,6 +1015,12 @@ public abstract class Char extends Actor {
|
|||||||
if (src instanceof Corruption) icon = FloatingText.CORRUPTION;
|
if (src instanceof Corruption) icon = FloatingText.CORRUPTION;
|
||||||
if (src instanceof AscensionChallenge) icon = FloatingText.AMULET;
|
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);
|
sprite.showStatusWithIcon(CharSprite.NEGATIVE, Integer.toString(dmg + shielded), icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -484,6 +484,20 @@ public class Hero extends Char {
|
|||||||
return hit;
|
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
|
@Override
|
||||||
public int attackSkill( Char target ) {
|
public int attackSkill( Char target ) {
|
||||||
KindOfWeapon wep = belongings.attackingWeapon();
|
KindOfWeapon wep = belongings.attackingWeapon();
|
||||||
@@ -517,17 +531,17 @@ public class Hero extends Char {
|
|||||||
case 3:
|
case 3:
|
||||||
accuracy *= Float.POSITIVE_INFINITY; break;
|
accuracy *= Float.POSITIVE_INFINITY; break;
|
||||||
}
|
}
|
||||||
buff(Talent.PreciseAssaultTracker.class).detach();
|
|
||||||
} else if (buff(Talent.LiquidAgilACCTracker.class) != null){
|
} else if (buff(Talent.LiquidAgilACCTracker.class) != null){
|
||||||
// 3x/inf. ACC, depending on talent level
|
// 3x/inf. ACC, depending on talent level
|
||||||
accuracy *= pointsInTalent(Talent.LIQUID_AGILITY) == 2 ? Float.POSITIVE_INFINITY : 3f;
|
accuracy *= pointsInTalent(Talent.LIQUID_AGILITY) == 2 ? Float.POSITIVE_INFINITY : 3f;
|
||||||
Talent.LiquidAgilACCTracker buff = buff(Talent.LiquidAgilACCTracker.class);
|
Talent.LiquidAgilACCTracker buff = buff(Talent.LiquidAgilACCTracker.class);
|
||||||
buff.uses--;
|
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){
|
if (buff(Scimitar.SwordDance.class) != null){
|
||||||
@@ -535,9 +549,9 @@ public class Hero extends Char {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!RingOfForce.fightingUnarmed(this)) {
|
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 {
|
} 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);
|
evasion = belongings.armor().evasionFactor(this, evasion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.round(evasion);
|
return Math.max(1, Math.round(evasion));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ public class Statue extends Mob {
|
|||||||
weapon.enchant( Enchantment.random() );
|
weapon.enchant( Enchantment.random() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Weapon weapon(){
|
||||||
|
return weapon;
|
||||||
|
}
|
||||||
|
|
||||||
private static final String WEAPON = "weapon";
|
private static final String WEAPON = "weapon";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,6 +22,31 @@
|
|||||||
package com.shatteredpixel.shatteredpixeldungeon.effects;
|
package com.shatteredpixel.shatteredpixeldungeon.effects;
|
||||||
|
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
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.GameScene;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
|
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
|
||||||
@@ -35,14 +60,18 @@ import com.watabou.utils.Callback;
|
|||||||
import com.watabou.utils.SparseArray;
|
import com.watabou.utils.SparseArray;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class FloatingText extends RenderedTextBlock {
|
public class FloatingText extends RenderedTextBlock {
|
||||||
|
|
||||||
private static final float LIFESPAN = 1f;
|
private static final float LIFESPAN = 1f;
|
||||||
private static final float DISTANCE = DungeonTilemap.SIZE;
|
private static final float DISTANCE = DungeonTilemap.SIZE;
|
||||||
|
|
||||||
public static final int ICON_SIZE = 7;
|
public static final int ICON_WIDTH = 7;
|
||||||
public static TextureFilm iconFilm = new TextureFilm( Assets.Effects.TEXT_ICONS, ICON_SIZE, ICON_SIZE );
|
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;
|
public static int NO_ICON = -1;
|
||||||
|
|
||||||
@@ -77,8 +106,34 @@ public class FloatingText extends RenderedTextBlock {
|
|||||||
public static int GOLD = 23;
|
public static int GOLD = 23;
|
||||||
public static int ENERGY = 24;
|
public static int ENERGY = 24;
|
||||||
|
|
||||||
//dodge icons
|
//hit reason icons
|
||||||
public static int TUFT = 26;
|
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 Image icon;
|
||||||
private boolean iconLeft;
|
private boolean iconLeft;
|
||||||
@@ -247,4 +302,201 @@ public class FloatingText extends RenderedTextBlock {
|
|||||||
stack.add(txt);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -404,7 +404,12 @@ public class Armor extends EquipableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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 ){
|
public float evasionFactor( Char owner, float evasion ){
|
||||||
|
if (testingNoArmDefSkill) return evasion;
|
||||||
|
|
||||||
if (hasGlyph(Stone.class, owner) && !Stone.testingEvasion()){
|
if (hasGlyph(Stone.class, owner) && !Stone.testingEvasion()){
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -588,6 +588,16 @@ public class DriedRose extends Artifact {
|
|||||||
HT = 20 + 8*rose.level();
|
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
|
@Override
|
||||||
protected boolean act() {
|
protected boolean act() {
|
||||||
updateRose();
|
updateRose();
|
||||||
@@ -611,8 +621,8 @@ public class DriedRose extends Artifact {
|
|||||||
//same accuracy as the hero.
|
//same accuracy as the hero.
|
||||||
int acc = Dungeon.hero.lvl + 9;
|
int acc = Dungeon.hero.lvl + 9;
|
||||||
|
|
||||||
if (rose != null && rose.weapon != null){
|
if (weapon() != null){
|
||||||
acc *= rose.weapon.accuracyFactor( this, target );
|
acc *= weapon().accuracyFactor( this, target );
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@@ -621,22 +631,22 @@ public class DriedRose extends Artifact {
|
|||||||
@Override
|
@Override
|
||||||
public float attackDelay() {
|
public float attackDelay() {
|
||||||
float delay = super.attackDelay();
|
float delay = super.attackDelay();
|
||||||
if (rose != null && rose.weapon != null){
|
if (weapon() != null){
|
||||||
delay *= rose.weapon.delayFactor(this);
|
delay *= weapon().delayFactor(this);
|
||||||
}
|
}
|
||||||
return delay;
|
return delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canAttack(Char enemy) {
|
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
|
@Override
|
||||||
public int damageRoll() {
|
public int damageRoll() {
|
||||||
int dmg = 0;
|
int dmg = 0;
|
||||||
if (rose != null && rose.weapon != null){
|
if (weapon() != null){
|
||||||
dmg += rose.weapon.damageRoll(this);
|
dmg += weapon().damageRoll(this);
|
||||||
} else {
|
} else {
|
||||||
dmg += Random.NormalIntRange(0, 5);
|
dmg += Random.NormalIntRange(0, 5);
|
||||||
}
|
}
|
||||||
@@ -647,13 +657,12 @@ public class DriedRose extends Artifact {
|
|||||||
@Override
|
@Override
|
||||||
public int attackProc(Char enemy, int damage) {
|
public int attackProc(Char enemy, int damage) {
|
||||||
damage = super.attackProc(enemy, damage);
|
damage = super.attackProc(enemy, damage);
|
||||||
if (rose != null) {
|
|
||||||
if (rose.weapon != null) {
|
if (weapon() != null) {
|
||||||
damage = rose.weapon.proc(this, enemy, damage);
|
damage = weapon().proc(this, enemy, damage);
|
||||||
if (!enemy.isAlive() && enemy == Dungeon.hero) {
|
if (!enemy.isAlive() && enemy == Dungeon.hero) {
|
||||||
Dungeon.fail(this);
|
Dungeon.fail(this);
|
||||||
GLog.n(Messages.capitalize(Messages.get(Char.class, "kill", name())));
|
GLog.n(Messages.capitalize(Messages.get(Char.class, "kill", name())));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,8 +671,8 @@ public class DriedRose extends Artifact {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int defenseProc(Char enemy, int damage) {
|
public int defenseProc(Char enemy, int damage) {
|
||||||
if (rose != null && rose.armor != null) {
|
if (armor() != null) {
|
||||||
damage = rose.armor.proc( enemy, this, damage );
|
damage = armor().proc( enemy, this, damage );
|
||||||
}
|
}
|
||||||
return super.defenseProc(enemy, damage);
|
return super.defenseProc(enemy, damage);
|
||||||
}
|
}
|
||||||
@@ -694,8 +703,8 @@ public class DriedRose extends Artifact {
|
|||||||
public int defenseSkill(Char enemy) {
|
public int defenseSkill(Char enemy) {
|
||||||
int defense = super.defenseSkill(enemy);
|
int defense = super.defenseSkill(enemy);
|
||||||
|
|
||||||
if (defense != 0 && rose != null && rose.armor != null ){
|
if (defense != 0 && armor() != null ){
|
||||||
defense = Math.round(rose.armor.evasionFactor( this, defense ));
|
defense = Math.round(armor().evasionFactor( this, defense ));
|
||||||
}
|
}
|
||||||
|
|
||||||
return defense;
|
return defense;
|
||||||
@@ -704,19 +713,19 @@ public class DriedRose extends Artifact {
|
|||||||
@Override
|
@Override
|
||||||
public int drRoll() {
|
public int drRoll() {
|
||||||
int dr = super.drRoll();
|
int dr = super.drRoll();
|
||||||
if (rose != null && rose.armor != null){
|
if (armor() != null){
|
||||||
dr += Random.NormalIntRange( rose.armor.DRMin(), rose.armor.DRMax());
|
dr += Random.NormalIntRange( armor().DRMin(), armor().DRMax());
|
||||||
}
|
}
|
||||||
if (rose != null && rose.weapon != null){
|
if (weapon() != null){
|
||||||
dr += Random.NormalIntRange( 0, rose.weapon.defenseFactor( this ));
|
dr += Random.NormalIntRange( 0, weapon().defenseFactor( this ));
|
||||||
}
|
}
|
||||||
return dr;
|
return dr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int glyphLevel(Class<? extends Armor.Glyph> cls) {
|
public int glyphLevel(Class<? extends Armor.Glyph> cls) {
|
||||||
if (rose != null && rose.armor != null && rose.armor.hasGlyph(cls, this)){
|
if (armor() != null && armor().hasGlyph(cls, this)){
|
||||||
return Math.max(super.glyphLevel(cls), rose.armor.buffedLvl());
|
return Math.max(super.glyphLevel(cls), armor().buffedLvl());
|
||||||
} else {
|
} else {
|
||||||
return super.glyphLevel(cls);
|
return super.glyphLevel(cls);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon.items.trinkets;
|
|||||||
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
|
||||||
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
|
||||||
|
|
||||||
|
//🍋🟩
|
||||||
public class FerretTuft extends Trinket {
|
public class FerretTuft extends Trinket {
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -197,9 +197,6 @@ abstract public class MissileWeapon extends Weapon {
|
|||||||
@Override
|
@Override
|
||||||
public float accuracyFactor(Char owner, Char target) {
|
public float accuracyFactor(Char owner, Char target) {
|
||||||
float accFactor = super.accuracyFactor(owner, 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);
|
accFactor *= adjacentAccFactor(owner, target);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user