diff --git a/core/src/main/assets/interfaces/buffs.png b/core/src/main/assets/interfaces/buffs.png index f1e5d34c3..8772efc8e 100644 Binary files a/core/src/main/assets/interfaces/buffs.png and b/core/src/main/assets/interfaces/buffs.png differ diff --git a/core/src/main/assets/interfaces/large_buffs.png b/core/src/main/assets/interfaces/large_buffs.png index 244aa5769..520b4b6ec 100644 Binary files a/core/src/main/assets/interfaces/large_buffs.png and b/core/src/main/assets/interfaces/large_buffs.png differ diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index 0a1e050c7..9beb9f2c7 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -92,7 +92,7 @@ items.armor.armor.cursed_worn=Because this armor is cursed, you are powerless to items.armor.armor.cursed=You can feel a malevolent magic lurking within this armor. items.armor.armor.weak_cursed=Despite the curse, you are able to unequip this armor. items.armor.armor.not_cursed=This armor is free of malevolent magic. -items.armor.armor.seal_attached=The Warrior's broken seal is attached to this armor, it is providing him up to _%d shielding_. +items.armor.armor.seal_attached=The Warrior's broken seal is attached to this armor. When he about to be injured below half health, it will grant him _%d shielding_. items.armor.armor$glyph.glyph=glyph items.armor.armor$glyph.killed=%s killed you... items.armor.armor$glyph.rankings_desc=Killed by a glyph @@ -2260,11 +2260,14 @@ items.brokenseal.prompt=Select an armor items.brokenseal.unknown_armor=You must identify that armor first. items.brokenseal.cursed_armor=The seal won't apply to cursed armor. items.brokenseal.affix=You affix the seal to your armor! -items.brokenseal.desc=A wax seal, affixed to armor as a symbol of valor. All the markings on the seal have worn off with age and it is broken in half down the middle.\n\nA memento from his home, the seal helps the warrior persevere. While wearing the seal the warrior will slowly generate shielding on top of his health based on the quality of his armor.\n\nThe seal can be _affixed to armor,_ and moved between armors. It can carry a single upgrade with it, so long as that upgrade was applied to the armor while the seal was attached to it. +items.brokenseal.desc=A wax seal, affixed to armor as a symbol of valor. All the markings on the seal have worn off with age and it is broken in half down the middle.\n\nA memento from his home, the seal helps the warrior persevere. While wearing the seal the warrior will instantly gain shielding when he is about to be damaged to half health or lower.\n\nThe seal can be _affixed to armor,_ and moved between armors. It can carry a single upgrade with it, so long as that upgrade was applied to the armor while the seal was attached to it. items.brokenseal.inscribed=The seal is inscribed with a _%s._ items.brokenseal.choose_title=Choose a Glyph items.brokenseal.choose_desc=Both this armor and the broken seal are carrying a glyph. Pick which glyph should be kept.\n\nNote that if you pick the glyph that is currently on the armor, the seal will not be able to transfer it later. items.brokenseal.discover_hint=One of the heroes starts with this item. +items.brokenseal$warriorshield.name=Warrior Shield +items.brokenseal$warriorshield.desc_active=The Warrior's broken seal is currently helping him persevere, granting him shielding on top of his health. There is a 100 turn cooldown after the shielding initially triggers before it can be used again.\n\nThis shield does not decay over time, but will end if no enemies are nearby for a few turns. When it ends, any unused shielding will reduce the cooldown, up to a max of 50%%.\n\nShield remaining: %1$d.\n\nCurrent Cooldown: %2$d. +items.brokenseal$warriorshield.desc_cooldown=The Warrior has recently gained shielding from his broken seal, and must wait until he can benefit from its shielding effect again.\n\nTurns Remaining: %d. items.dewdrop.name=dewdrop items.dewdrop.already_full=You already have full health. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java index 47281ec43..79e60eb00 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java @@ -99,6 +99,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.MirrorImage; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.PrismaticImage; import com.shatteredpixel.shatteredpixeldungeon.effects.FloatingText; import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShadowParticle; +import com.shatteredpixel.shatteredpixeldungeon.items.BrokenSeal; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor; import com.shatteredpixel.shatteredpixeldungeon.items.armor.curses.Bulk; @@ -903,6 +904,17 @@ public abstract class Char extends Actor { buff( Paralysis.class ).processDamage(dmg); } + BrokenSeal.WarriorShield shield = buff(BrokenSeal.WarriorShield.class); + if (!(src instanceof Hunger) + && dmg > 0 + //either HP is already half or below (ignoring shield) + // or the hit will reduce it to half or below + && (HP <= HT/2 || HP + shielding() - dmg <= HT/2) + && shield != null && !shield.coolingDown()){ + sprite.showStatusWithIcon(CharSprite.POSITIVE, Integer.toString(buff(BrokenSeal.WarriorShield.class).maxShield()), FloatingText.SHIELDING); + shield.activate(); + } + int shielded = dmg; dmg = ShieldBuff.processDamage(this, dmg, src); shielded -= dmg; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/BrokenSeal.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/BrokenSeal.java index b1431c4c8..39435f6b2 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/BrokenSeal.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/BrokenSeal.java @@ -23,7 +23,6 @@ package com.shatteredpixel.shatteredpixeldungeon.items; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Regeneration; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ShieldBuff; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Belongings; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; @@ -35,12 +34,15 @@ import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.shatteredpixel.shatteredpixeldungeon.windows.WndBag; import com.shatteredpixel.shatteredpixeldungeon.windows.WndOptions; import com.shatteredpixel.shatteredpixeldungeon.windows.WndUseItem; +import com.watabou.noosa.Image; import com.watabou.noosa.audio.Sample; import com.watabou.utils.Bundle; +import com.watabou.utils.GameMath; import java.util.ArrayList; import java.util.Arrays; @@ -88,7 +90,8 @@ public class BrokenSeal extends Item { } public int maxShield( int armTier, int armLvl ){ - return armTier + armLvl + Dungeon.hero.pointsInTalent(Talent.IRON_WILL); + // 5-15, based on equip tier and iron will + return 3 + 2*armTier + Dungeon.hero.pointsInTalent(Talent.IRON_WILL); } @Override @@ -213,35 +216,112 @@ public class BrokenSeal extends Item { public static class WarriorShield extends ShieldBuff { { + type = buffType.POSITIVE; + detachesAtZero = false; + shieldUsePriority = 2; } private Armor armor; - private float partialShield; + + private int cooldown = 0; + private int turnsSinceEnemies = 0; + + private static int COOLDOWN_START = 100; + + @Override + public int icon() { + if (coolingDown() || shielding() > 0){ + return BuffIndicator.SEAL_SHIELD; + } else { + return BuffIndicator.NONE; + } + } + + @Override + public void tintIcon(Image icon) { + if (coolingDown() && shielding() == 0){ + icon.brightness(0.3f); + } else { + icon.resetColor(); + } + } + + @Override + public float iconFadePercent() { + if (shielding() > 0){ + return GameMath.gate(0, 1f - shielding()/(float)maxShield(), 1); + } else if (coolingDown()){ + return GameMath.gate(0, cooldown / (float)COOLDOWN_START, 1); + } else { + return 0; + } + } + + @Override + public String iconTextDisplay() { + if (shielding() > 0){ + return Integer.toString(shielding()); + } else if (coolingDown()){ + return Integer.toString(cooldown); + } else { + return ""; + } + } + + @Override + public String desc() { + if (shielding() > 0){ + return Messages.get(this, "desc_active", shielding(), cooldown); + } else { + return Messages.get(this, "desc_cooldown", cooldown); + } + } @Override public synchronized boolean act() { - if (Regeneration.regenOn() && shielding() < maxShield()) { - partialShield += 1/30f; + if (cooldown > 0){ + cooldown--; + } + + if (shielding() > 0){ + if (Dungeon.hero.visibleEnemies() == 0){ + turnsSinceEnemies++; + //TODO + if (turnsSinceEnemies >= 5){ + float percentLeft = shielding() / (float)maxShield(); + cooldown -= COOLDOWN_START*(percentLeft/2f); //max of 50% cooldown refund + decShield(shielding()); + } + } else { + turnsSinceEnemies = 0; + } } - while (partialShield >= 1){ - incShield(); - partialShield--; - } - - if (shielding() <= 0 && maxShield() <= 0){ + if (shielding() <= 0 && maxShield() <= 0 && cooldown == 0){ detach(); } spend(TICK); return true; } + + public synchronized void activate() { + setShield(maxShield()); + cooldown = COOLDOWN_START; + turnsSinceEnemies = 0; + } + + public boolean coolingDown(){ + return cooldown > 0; + } public synchronized void supercharge(int maxShield){ if (maxShield > shielding()){ setShield(maxShield); } + cooldown = COOLDOWN_START; + turnsSinceEnemies = 0; } public synchronized void setArmor(Armor arm){ @@ -260,5 +340,28 @@ public class BrokenSeal extends Item { return 0; } } + + public static final String COOLDOWN = "cooldown"; + public static final String TURNS_SINCE_ENEMIES = "turns_since_enemies"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(COOLDOWN, cooldown); + bundle.put(TURNS_SINCE_ENEMIES, turnsSinceEnemies); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + if (bundle.contains(COOLDOWN)) { + cooldown = bundle.getInt(COOLDOWN); + turnsSinceEnemies = bundle.getInt(TURNS_SINCE_ENEMIES); + } else { + //TODO what about berserker runs in progress? + // we could potentially screw someone who had a big shield prior to v3.1 + setShield(0); //clears old pre-v3.1 shield + } + } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/BuffIndicator.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/BuffIndicator.java index 27f3839ed..b82afea9b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/BuffIndicator.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/BuffIndicator.java @@ -133,6 +133,7 @@ public class BuffIndicator extends Component { public static final int ILLUMINATED = 81; public static final int TRINITY_FORM= 82; public static final int MANY_POWER = 83; + public static final int SEAL_SHIELD = 84; public static final int SIZE_SMALL = 7; public static final int SIZE_LARGE = 16;