v1.4.0: soft reworked the Berserker. Berserk is now manually triggered

This commit is contained in:
Evan Debenham
2022-08-26 16:38:52 -04:00
parent cd05ee3e58
commit fd18ebdb47
8 changed files with 160 additions and 61 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -91,15 +91,17 @@ actors.buffs.barrier.name=barrier
actors.buffs.barrier.desc=A durable bubble of force which blocks all damage.\n\nThe barrier will take damage for whatever it is protecting so long as there is shielding left. The shielding will also slowly decay over time.\n\nShielding remaining: %d.
actors.buffs.berserk.angered=angered
actors.buffs.berserk.berserk=verserking
actors.buffs.berserk.berserk=berserking
actors.buffs.berserk.exhausted=exhausted
actors.buffs.berserk.recovering=recovering
actors.buffs.berserk.angered_desc=The severity of the berserker's injuries strengthen his blows. As the berserker takes physical damage, his rage will build, granting him bonus damage. Damage which is blocked by armor still counts towards building rage.\n\nRage will fade over time. The lower the berserker's health, the longer it will last.\n\nIf the berserker is brought to 0 hp while at full rage, and is wearing his seal, he will go berserk and _refuse to die_ for a short time.\n\nCurrent Rage: _%.0f%%_\n_+%.0f%%_ damage
actors.buffs.berserk.berserk_desc=At the brink of death, fear and uncertainty bleed away, leaving only anger. In this state of near-death the berserker is extremely powerful _dealing +50% damage, gaining bonus shielding, and refusing to die._\n\nThis bonus shielding is stronger the better the berserker's armor, and will deplete over time. When this shielding is reduced to 0, the berserker will give in and die.\n\nAny form of healing will return the berserker to stability, but he will be exhausted. While exhausted, the berserker will need to gain experience before being able to build rage again.
actors.buffs.berserk.recovering_desc=Inner strength has its limits. The berserker must rest before using his rage again.\n\nWhile recovering the berserker does not build any rage from taking damage.\n\nLevels until recovered: _%.2f_
actors.buffs.berserk.no_rages=Berserking will also permanently wear on him, reducing his max health each time.
actors.buffs.berserk.past_rages=Times berserker has raged: _%d_\nMax health reduced to: _%d%%_
actors.buffs.berserk.angered_desc=The severity of the berserker's injuries strengthen his blows. As the berserker takes physical damage, his rage will build, granting him bonus damage. Damage which is blocked by armor still counts towards building rage.\n\nRage will fade over time. The lower the berserker's health, the longer it will last.\n\nAt 100%% rage, the Berserker can go berserk, granting him 2x his seal's max shielding, increasing to 6x if he has very low health. This shielding decays over time and once it runs out the berserker will have to rest before building rage again.\n\nCurrent Rage: _%.0f%%_\n_+%.0f%%_ damage
actors.buffs.berserk.berserk_desc=Fear and uncertainty bleed away, leaving only anger. In this state the Berserker is extremely powerful, _dealing +50% damage and gaining bonus shielding._\n\nThis bonus shielding is equal to 2x his seal's max shielding, increasing to 6x if he has very low health. When this shielding is reduced to 0, berserking will end.\n\nAfter raging, the Berserker will need to recover before being able to build rage again.
actors.buffs.berserk.recovering_desc=Inner strength has its limits. The Berserker must rest before using his rage again.\n\nWhile recovering the Berserker does not build any rage from taking damage.
actors.buffs.berserk.recovering_desc_turns=Turns until recovered: _%d_
actors.buffs.berserk.recovering_desc_levels=Levels until recovered: _%.2f_
actors.buffs.berserk.rankings_desc=Berserked to Death
actors.buffs.berserk.action_name=Berserk
actors.buffs.berserk.no_seal=You need your broken seal to berserk!
actors.buffs.bleeding.name=bleeding
actors.buffs.bleeding.ondeath=You bled to death...
@@ -496,8 +498,8 @@ actors.hero.heroclass.huntress_desc=The Huntress starts with a _unique spirit bo
actors.hero.heroclass.huntress_unlock=The Huntress is a master of thrown weapons, and has a _unique magical bow_ with infinite arrows.\n\nTo unlock her _hit enemies with thrown weapons 10 times in one game._
actors.hero.herosubclass.berserker=berserker
actors.hero.herosubclass.berserker_short_desc=The _Berserker_ builds rage as he takes damage. Rage increases his damage, and can let him briefly cheat death.
actors.hero.herosubclass.berserker_desc=The Berserker gains rage as he takes physical damage, including damage that gets blocked by his armor! Rage steadily fades away over time, but fades more slowly if he is at low HP.\n\nThe Berserker deals up to +50% damage at 100% rage. When at 0 health and 100% rage, the Berserker will briefly gain 8x his normal maximum shielding and will refuse to die as long as he has shielding left. The Berserker needs time to recover after he defies death however.
actors.hero.herosubclass.berserker_short_desc=The _Berserker_ builds rage as he takes damage. Rage increases his damage and can be activated at 100% for bonus shielding.
actors.hero.herosubclass.berserker_desc=The Berserker gains rage as he takes physical damage, including damage that gets blocked by his armor! Rage steadily fades away over time, but fades more slowly if he is at low HP.\n\nThe Berserker deals up to +50% damage at 100% rage. At 100% rage he can also go berserk, gaining 2-6x his seal's maximum shielding depending on his missing health and keeping his rage at 100% as long as he has shielding left. However, the Berserker needs time to recover after he goes berserk.
actors.hero.herosubclass.gladiator=gladiator
actors.hero.herosubclass.gladiator_short_desc=The _Gladiator_ builds combo when he makes successful attacks. He can spend combo to use unique abilities.
actors.hero.herosubclass.gladiator_desc=The Gladiator builds one point of combo every time he makes a successful attack with a melee or thrown weapon. If the Gladiator does not make a successful attack within 5 turns, his combo is reset.\n\nAs combo builds it unlocks a variety of combo abilities. A new ability becomes available at 2, 4, 6, 8, and 10 combo. Some abilities help build more combo, while others let the Gladiator spend his combo on powerful attacks.
@@ -557,9 +559,9 @@ actors.hero.talent.strongman.title=strongman
actors.hero.talent.strongman.desc=_+1:_ The Warrior's strength is _increased by 8%_, rounded down.\n\n_+2:_ The Warrior's strength is _increased by 13%_, rounded down.\n\n_+3:_ The Warrior's strength is _increased by 18%_, rounded down.
actors.hero.talent.endless_rage.title=endless rage
actors.hero.talent.endless_rage.desc=_+1:_ The Berserker can reach a max of _110% rage_.\n\n_+2:_ The Berserker can reach a max of _120% rage_.\n\n_+3:_ The Berserker can reach a max of _130% rage_.\n\nNote that rage above 100% will not grant more than +50% damage.
actors.hero.talent.berserking_stamina.title=berserking stamina
actors.hero.talent.berserking_stamina.desc=_+1:_ The Berserker gains _25% more shield_ when berserking, and the berserking cooldown is reduced to _1.67 levels_ from 2.\n\n_+2:_ The Berserker gains _50% more shield_ when berserking, and the berserking cooldown is reduced to _1.33 levels_ from 2.\n\n_+3:_ The Berserker gains _75% more shield_ when berserking, and the berserking cooldown is reduced to _1 level_ from 2.
actors.hero.talent.endless_rage.desc=_+1:_ The Berserker can reach a max of _116% rage_.\n\n_+2:_ The Berserker can reach a max of _133% rage_.\n\n_+3:_ The Berserker can reach a max of _150% rage_.\n\nEach point of rage above 100% increases berserk shielding by 1% and reduces cooldown by 1%, but does not increase the damage bonus beyond +50%.
actors.hero.talent.deathless_fury.title=deathless fury
actors.hero.talent.deathless_fury.desc=_+1:_ Berserk automatically triggers if the Berserker is about to die and rage is at or above 100%, but it has a cooldown of _4 hero levels_ when this happens.\n\n_+2:_ Berserk automatically triggers if the Berserker is about to die and rage is at or above 100%, but it has a cooldown of _3 hero levels_ when this happens.\n\n_+3:_ Berserk automatically triggers if the Berserker is about to die and rage is at or above 100%, but it has a cooldown of _2 hero levels_ when this happens.\n\nNote that the Berserker will still die if he has 0 HP when berserking ends.
actors.hero.talent.enraged_catalyst.title=enraged catalyst
actors.hero.talent.enraged_catalyst.desc=_+1:_ Enchantments and curses on the Berserker's weapon trigger more often the more rage he has, to a maximum of _15% more often_ at 100% rage.\n\n_+2:_ Enchantments and curses on the Berserker's weapon trigger more often the more rage he has, to a maximum of _30% more often_ at 100% rage.\n\n_+3:_ Enchantments and curses on the Berserker's weapon trigger more often the more rage he has, to a maximum of _45% more often_ at 100% rage.

View File

@@ -29,7 +29,10 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.SpellSprite;
import com.shatteredpixel.shatteredpixeldungeon.items.BrokenSeal.WarriorShield;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.ui.ActionIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIcon;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.Image;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
@@ -37,7 +40,7 @@ import com.watabou.utils.GameMath;
import java.text.DecimalFormat;
public class Berserk extends Buff {
public class Berserk extends Buff implements ActionIndicator.Action {
{
type = buffType.POSITIVE;
@@ -48,14 +51,18 @@ public class Berserk extends Buff {
}
private State state = State.NORMAL;
private static final float LEVEL_RECOVER_START = 2f;
private static final float LEVEL_RECOVER_START = 5f;
private float levelRecovery;
private static final int TURN_RECOVERY_START = 100;
private int turnRecovery;
public int powerLossBuffer = 0;
private float power = 0;
private static final String STATE = "state";
private static final String LEVEL_RECOVERY = "levelrecovery";
private static final String TURN_RECOVERY = "turn_recovery";
private static final String POWER = "power";
private static final String POWER_BUFFER = "power_buffer";
@@ -65,7 +72,8 @@ public class Berserk extends Buff {
bundle.put(STATE, state);
bundle.put(POWER, power);
bundle.put(POWER_BUFFER, powerLossBuffer);
if (state == State.RECOVERING) bundle.put(LEVEL_RECOVERY, levelRecovery);
bundle.put(LEVEL_RECOVERY, levelRecovery);
bundle.put(TURN_RECOVERY, turnRecovery);
}
@Override
@@ -75,33 +83,50 @@ public class Berserk extends Buff {
state = bundle.getEnum(STATE, State.class);
power = bundle.getFloat(POWER);
powerLossBuffer = bundle.getInt(POWER_BUFFER);
if (state == State.RECOVERING) levelRecovery = bundle.getFloat(LEVEL_RECOVERY);
levelRecovery = bundle.getFloat(LEVEL_RECOVERY);
turnRecovery = bundle.getInt(TURN_RECOVERY);
if (power >= 1f && state == State.NORMAL){
ActionIndicator.setAction(this);
}
}
@Override
public boolean act() {
if (berserking()){
ShieldBuff buff = target.buff(WarriorShield.class);
if (target.HP <= 0) {
if (target.shielding() > 0) {
int dmg = 1 + (int)Math.ceil(target.shielding() * 0.05f);
if (buff != null && buff.shielding() > 0) {
buff.absorbDamage(dmg);
} else {
//if there is no shield buff, or it is empty, then try to remove from other shielding buffs
dmg = buff.absorbDamage(dmg);
}
if (dmg > 0){
//if there is leftover damage, then try to remove from other shielding buffs
for (ShieldBuff s : target.buffs(ShieldBuff.class)){
dmg = s.absorbDamage(dmg);
if (dmg == 0) break;
}
}
if (target.shielding() <= 0) {
if (target.shielding() <= 0){
state = State.RECOVERING;
power = 0f;
BuffIndicator.refreshHero();
if (!target.isAlive()){
target.die(this);
if (!target.isAlive()) Dungeon.fail(this.getClass());
}
}
} else {
state = State.RECOVERING;
power = 0f;
if (!target.isAlive()){
target.die(this);
if (!target.isAlive()) Dungeon.fail(this.getClass());
}
} else {
state = State.RECOVERING;
levelRecovery = LEVEL_RECOVER_START - Dungeon.hero.pointsInTalent(Talent.BERSERKING_STAMINA)/3f;
if (buff != null) buff.absorbDamage(buff.shielding());
power = 0f;
}
} else if (state == State.NORMAL) {
if (powerLossBuffer > 0){
@@ -109,17 +134,27 @@ public class Berserk extends Buff {
} else {
power -= GameMath.gate(0.1f, power, 1f) * 0.067f * Math.pow((target.HP / (float) target.HT), 2);
if (power < 1f){
ActionIndicator.clearAction(this);
}
if (power <= 0) {
detach();
}
}
} else if (state == State.RECOVERING && levelRecovery == 0){
turnRecovery--;
if (turnRecovery <= 0){
turnRecovery = 0;
state = State.NORMAL;
}
}
spend(TICK);
return true;
}
public float rageAmount(){
return Math.min(1f, power);
public float enchantFactor(float chance){
return chance + (Math.min(1f, power) * 0.15f) * ((Hero) target).pointsInTalent(Talent.ENRAGED_CATALYST);
}
public float damageFactor(float dmg){
@@ -127,43 +162,94 @@ public class Berserk extends Buff {
}
public boolean berserking(){
if (target.HP == 0 && state == State.NORMAL && power >= 1f){
WarriorShield shield = target.buff(WarriorShield.class);
if (shield != null){
state = State.BERSERK;
int shieldAmount = shield.maxShield() * 8;
shieldAmount = Math.round(shieldAmount * (1f + Dungeon.hero.pointsInTalent(Talent.BERSERKING_STAMINA)/4f));
shield.supercharge(shieldAmount);
SpellSprite.show(target, SpellSprite.BERSERK);
Sample.INSTANCE.play( Assets.Sounds.CHALLENGE );
GameScene.flash(0xFF0000);
}
if (target.HP == 0
&& state == State.NORMAL
&& power >= 1f
&& target.buff(WarriorShield.class) != null
&& ((Hero)target).hasTalent(Talent.DEATHLESS_FURY)){
startBerserking();
ActionIndicator.clearAction(this);
}
return state == State.BERSERK && target.shielding() > 0;
}
private void startBerserking(){
state = State.BERSERK;
SpellSprite.show(target, SpellSprite.BERSERK);
Sample.INSTANCE.play( Assets.Sounds.CHALLENGE );
GameScene.flash(0xFF0000);
if (target.HP > 0) {
turnRecovery = TURN_RECOVERY_START;
levelRecovery = 0;
} else {
levelRecovery = LEVEL_RECOVER_START - ((Hero)target).pointsInTalent(Talent.DEATHLESS_FURY);
turnRecovery = 0;
}
//base multiplier scales at 2/3/4/5/6x at 100/37/20/9/0% HP
float shieldMultiplier = 2f + 4*(float)Math.pow((1f-(target.HP/(float)target.HT)), 3);
//Endless rage effect on shield and cooldown
if (power > 1f){
shieldMultiplier *= power;
levelRecovery *= 2f - power;
turnRecovery *= 2f - power;
}
WarriorShield shield = target.buff(WarriorShield.class);
int shieldAmount = Math.round(shield.maxShield() * shieldMultiplier);
shield.supercharge(shieldAmount);
BuffIndicator.refreshHero();
}
public void damage(int damage){
if (state == State.RECOVERING) return;
float maxPower = 1f + 0.1f*((Hero)target).pointsInTalent(Talent.ENDLESS_RAGE);
if (state != State.NORMAL) return;
float maxPower = 1f + 0.1667f*((Hero)target).pointsInTalent(Talent.ENDLESS_RAGE);
power = Math.min(maxPower, power + (damage/(float)target.HT)/3f );
BuffIndicator.refreshHero(); //show new power immediately
powerLossBuffer = 3; //2 turns until rage starts dropping
if (power >= 1f){
ActionIndicator.setAction(this);
}
}
public void recover(float percent){
if (levelRecovery > 0){
if (state == State.RECOVERING && levelRecovery > 0){
levelRecovery -= percent;
if (levelRecovery <= 0) {
state = State.NORMAL;
levelRecovery = 0;
if (turnRecovery == 0){
state = State.NORMAL;
}
}
}
}
@Override
public String actionName() {
return Messages.get(this, "action_name");
}
@Override
public Image actionIcon() {
//TODO, should look into these in general honestly
return new BuffIcon(BuffIndicator.FURY, true);
}
@Override
public void doAction() {
WarriorShield shield = target.buff(WarriorShield.class);
if (shield != null) {
startBerserking();
ActionIndicator.clearAction(this);
} else {
GLog.w(Messages.get(this, "no_seal"));
}
}
@Override
public int icon() {
return BuffIndicator.BERSERK;
@@ -189,11 +275,16 @@ public class Berserk extends Buff {
public float iconFadePercent() {
switch (state){
case NORMAL: default:
return Math.max(0f, 1f - power);
float maxPower = 1f + 0.1667f*((Hero)target).pointsInTalent(Talent.ENDLESS_RAGE);
return (maxPower - power)/maxPower;
case BERSERK:
return 0f;
case RECOVERING:
return 1f - levelRecovery/LEVEL_RECOVER_START;
if (levelRecovery > 0) {
return 1f - levelRecovery/(LEVEL_RECOVER_START-Dungeon.hero.pointsInTalent(Talent.DEATHLESS_FURY));
} else {
return 1f - turnRecovery/(float)TURN_RECOVERY_START;
}
}
}
@@ -202,7 +293,11 @@ public class Berserk extends Buff {
case NORMAL: case BERSERK: default:
return (int)(power*100) + "%";
case RECOVERING:
return new DecimalFormat("#.#").format(levelRecovery);
if (levelRecovery > 0) {
return new DecimalFormat("#.#").format(levelRecovery);
} else {
return Integer.toString(turnRecovery);
}
}
}
@@ -227,7 +322,11 @@ public class Berserk extends Buff {
case BERSERK:
return Messages.get(this, "berserk_desc");
case RECOVERING:
return Messages.get(this, "recovering_desc", levelRecovery);
if (levelRecovery > 0){
return Messages.get(this, "recovering_desc") + "\n\n" + Messages.get(this, "recovering_desc_levels", levelRecovery);
} else {
return Messages.get(this, "recovering_desc") + "\n\n" + Messages.get(this, "recovering_desc_turns", turnRecovery);
}
}
}

View File

@@ -42,7 +42,7 @@ public class Regeneration extends Buff {
if (target.HP < regencap() && !((Hero)target).isStarving()) {
LockedFloor lock = target.buff(LockedFloor.class);
if (target.HP > 0 && (lock == null || lock.regenOn())) {
if (lock == null || lock.regenOn()) {
target.HP += 1;
if (target.HP == regencap()) {
((Hero) target).resting = false;

View File

@@ -59,10 +59,8 @@ import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.messages.Languages;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.HeroSelectScene;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.watabou.noosa.Image;
import com.watabou.noosa.audio.Sample;
@@ -84,7 +82,7 @@ public enum Talent {
//Warrior T3
HOLD_FAST(9, 3), STRONGMAN(10, 3),
//Berserker T3
ENDLESS_RAGE(11, 3), BERSERKING_STAMINA(12, 3), ENRAGED_CATALYST(13, 3),
ENDLESS_RAGE(11, 3), DEATHLESS_FURY(12, 3), ENRAGED_CATALYST(13, 3),
//Gladiator T3
CLEAVE(14, 3), LETHAL_DEFENSE(15, 3), ENHANCED_COMBO(16, 3),
//Heroic Leap T4
@@ -633,7 +631,7 @@ public enum Talent {
//tier 3
switch (cls){
case BERSERKER: default:
Collections.addAll(tierTalents, ENDLESS_RAGE, BERSERKING_STAMINA, ENRAGED_CATALYST);
Collections.addAll(tierTalents, ENDLESS_RAGE, DEATHLESS_FURY, ENRAGED_CATALYST);
break;
case GLADIATOR:
Collections.addAll(tierTalents, CLEAVE, LETHAL_DEFENSE, ENHANCED_COMBO);

View File

@@ -367,12 +367,11 @@ abstract public class Weapon extends KindOfWeapon {
protected float procChanceMultiplier( Char attacker ){
float multi = RingOfArcana.enchantPowerMultiplier(attacker);
if (attacker instanceof Hero && ((Hero) attacker).hasTalent(Talent.ENRAGED_CATALYST)){
Berserk rage = attacker.buff(Berserk.class);
if (rage != null) {
multi += (rage.rageAmount() * 0.15f) * ((Hero) attacker).pointsInTalent(Talent.ENRAGED_CATALYST);
}
Berserk rage = attacker.buff(Berserk.class);
if (rage != null) {
multi += rage.enchantFactor(multi);
}
if (attacker.buff(Talent.SpiritBladesTracker.class) != null
&& ((Hero)attacker).pointsInTalent(Talent.SPIRIT_BLADES) == 4){
multi += 0.1f;

View File

@@ -119,8 +119,9 @@ public class ActionIndicator extends Tag {
}
public static void clearAction(Action action){
if (ActionIndicator.action == action)
if (ActionIndicator.action == action) {
ActionIndicator.action = null;
}
}
public static void updateIcon(){

View File

@@ -262,7 +262,7 @@ public class StatusPane extends Component {
shieldedHP.scale.x = health/(float)max;
if (shield > health) {
rawShielding.scale.x = shield / (float) max;
rawShielding.scale.x = Math.min(1, shield / (float) max);
} else {
rawShielding.scale.x = 0;
}