From 9654542d1f5a2f651023da9f9171f517fe69cec4 Mon Sep 17 00:00:00 2001 From: LingASDJ <2735951230@qq.com> Date: Tue, 20 Sep 2022 00:04:45 +0800 Subject: [PATCH] Add Beta21-p1.8 All Commit --- core/src/main/assets/interfaces/badges.png | Bin 17256 -> 17836 bytes .../assets/messages/actors/actors.properties | 8 + .../assets/messages/items/items.properties | 9 +- .../main/assets/messages/misc/misc.properties | 4 + .../messages/windows/windows.properties | 3 + .../shatteredpixeldungeon/Badges.java | 1 + .../shatteredpixeldungeon/Dungeon.java | 3 + .../LevelSwitchListener.java | 12 + .../shatteredpixeldungeon/SPDSettings.java | 12 +- .../shatteredpixeldungeon/Statistics.java | 26 +- .../actors/hero/Hero.java | 2 + .../actors/mobs/npcs/Ghost.java | 4 +- .../custom/buffs/AbsoluteBlindness.java | 87 +++ .../custom/buffs/AttributeModifier.java | 215 +++++++ .../custom/buffs/ConsistBleeding.java | 151 +++++ .../custom/buffs/CountBuff.java | 36 ++ .../custom/buffs/DummyChar.java | 71 +++ .../custom/buffs/FlavourBuffOpen.java | 9 + .../custom/buffs/IgnoreArmor.java | 34 + .../custom/buffs/PositiveBuffProhibition.java | 46 ++ .../custom/buffs/ZeroAttack.java | 33 + .../custom/buffs/ZeroDefense.java | 33 + .../custom/ch/GameTracker.java | 154 +++++ .../testmode/ImmortalShieldAffecter.java | 60 ++ .../shatteredpixeldungeon/items/Heap.java | 10 + .../items/wands/Wand.java | 182 +++++- .../items/wands/WandOfMagicMissile.java | 8 + .../items/weapon/melee/EndingBlade.java | 588 +++++++++++++++++- .../shatteredpixeldungeon/levels/Level.java | 4 + .../levels/RegularLevel.java | 57 ++ .../mechanics/BallisticaReal.java | 329 ++++++++++ .../ui/RenderedTextBlock.java | 3 + .../windows/WndHero.java | 147 ++++- .../windows/WndSettings.java | 64 ++ 34 files changed, 2359 insertions(+), 46 deletions(-) create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/LevelSwitchListener.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AbsoluteBlindness.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AttributeModifier.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ConsistBleeding.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/CountBuff.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/DummyChar.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/FlavourBuffOpen.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/IgnoreArmor.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/PositiveBuffProhibition.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroAttack.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroDefense.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/ch/GameTracker.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/testmode/ImmortalShieldAffecter.java create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/BallisticaReal.java diff --git a/core/src/main/assets/interfaces/badges.png b/core/src/main/assets/interfaces/badges.png index ce25af09c91c34edc67d0a2592c617d34a569322..0b9410bf77b7062e33bd86fafd5d680e47a2cf95 100644 GIT binary patch literal 17836 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 floorsExplored = new SparseArray<>(); public static void reset() { @@ -79,6 +89,10 @@ public class Statistics { fireGirlnoshopping = false; deadshoppingdied = false; + + second_elapsed = 0f; + real_seconds = 0; + turnsPassed = 0f; } @@ -108,7 +122,7 @@ public class Statistics { private static final String SHOPPINGDIED = "deadshoppingdied"; private static final String EXLEVEL = "Exlevel"; - + public static void storeInBundle( Bundle bundle ) { bundle.put( GOLD, goldCollected ); bundle.put( DEEPEST, deepestFloor ); @@ -133,6 +147,11 @@ public class Statistics { bundle.put( NOSHOPPING, fireGirlnoshopping ); bundle.put( SHOPPINGDIED, deadshoppingdied ); + + //SPD + bundle.put("real_time_passed", second_elapsed); + bundle.put("real_seconds_passed", real_seconds); + bundle.put("turns_passed", turnsPassed); } public static void restoreFromBundle( Bundle bundle ) { @@ -158,6 +177,11 @@ public class Statistics { fireGirlnoshopping = bundle.getBoolean( NOSHOPPING ); deadshoppingdied = bundle.getBoolean( SHOPPINGDIED ); + + //SPD + second_elapsed = bundle.getFloat("real_time_passed"); + real_seconds = bundle.getLong("real_seconds_passed"); + turnsPassed = bundle.getFloat("turns_passed"); } public static void preview( GamesInProgress.Info info, Bundle bundle ){ diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java index 5aa8a9688..1f2b639fb 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Hero.java @@ -79,6 +79,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Monk; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Snake; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.lb.BlackSoul; +import com.shatteredpixel.shatteredpixeldungeon.custom.ch.GameTracker; import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; import com.shatteredpixel.shatteredpixeldungeon.effects.CheckedCell; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; @@ -494,6 +495,7 @@ public class Hero extends Char { } Buff.affect( this, Regeneration.class ); Buff.affect( this, Hunger.class ); + Buff.affect(this, GameTracker.class); } public int tier() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java index 6243f3929..7c1bb6895 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java @@ -313,10 +313,10 @@ public class Ghost extends NPC { Generator.Category c = Generator.wepTiers[wepTier - 1]; weapon = (MeleeWeapon) Reflection.newInstance(c.classes[Random.chances(c.probs)]); - //30%:+0, 25%:+1, 15%:+2, 10%:+3, 15%:+4, 5%+5 + //26%:+0, 25%:+1, 15%:+2, 10%:+3, 5%:+4, 5%+5 float itemLevelRoll = Random.Float(); int itemLevel; - if (itemLevelRoll < 0.1f){ + if (itemLevelRoll < 0.74f){ itemLevel = 0; } else if (itemLevelRoll < 0.75f){ itemLevel = 1; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AbsoluteBlindness.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AbsoluteBlindness.java new file mode 100644 index 000000000..d7d46f49c --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AbsoluteBlindness.java @@ -0,0 +1,87 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.custom.messages.M; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.watabou.noosa.Image; +import com.watabou.utils.Bundle; + +public class AbsoluteBlindness extends Buff { + { + actPriority = VFX_PRIO; + announced = true; + type=buffType.NEGATIVE; + } + + protected float left=0f; + private int storedViewDistance; + @Override + public boolean act(){ + spend(TICK); + if(target.viewDistance>0 && storedViewDistance != target.viewDistance){ + storedViewDistance = target.viewDistance; + } + target.viewDistance = 0; + left-=1f; + if(left<0) detach(); + return true; + } + + @Override + public void storeInBundle(Bundle b){ + super.storeInBundle(b); + b.put("stroedVD", storedViewDistance); + b.put("blindLeft", left); + } + + @Override + public void restoreFromBundle(Bundle b){ + super.restoreFromBundle(b); + storedViewDistance = b.getInt("stroedVD"); + left = b.getFloat("blindLeft") + 1f; + } + //deprecate + public AbsoluteBlindness storeVD(int vd){ + if(vd>0) { + storedViewDistance = vd; + } + return this; + } + + public AbsoluteBlindness addLeft(float left){ + this.left += left; + return this; + } + + @Override + public void detach(){ + //target.viewDistance = storedViewDistance; + target.viewDistance = Dungeon.level.viewDistance; + Dungeon.observe(); + super.detach(); + } + @Override + public int icon(){ + return BuffIndicator.BLINDNESS; + } + @Override + public void tintIcon(Image icon){ + icon.hardlight(0x3366D4); + } + + @Override + public String toString() { + return M.L(this, "name"); + } + + @Override + public String heroMessage() { + return M.L(this, "heromsg"); + } + + @Override + public String desc() { + return M.L(this, "desc"); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AttributeModifier.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AttributeModifier.java new file mode 100644 index 000000000..05bda6150 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/AttributeModifier.java @@ -0,0 +1,215 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.custom.utils.GME; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.watabou.utils.Bundle; + +public class AttributeModifier extends FlavourBuff { + { + type = buffType.NEUTRAL; + announced = true; + } + + public float atk_m = 1f; + public float def_m = 1f; + public float acc_m = 1f; + public float eva_m = 1f; + public float dmg_m = 1f; + public float hp_m = 1f; + public float atk_l = 0f; + public float def_l = 0f; + public float acc_l = 0f; + public float eva_l = 0f; + public float dmg_l = 0f; + public float hp_l = 0f; + public int orig_HT = -1; + + public void merge(AttributeModifier another){ + atk_m *= another.atk_m; + def_m *= another.def_m; + acc_m *= another.acc_m; + eva_m *= another.eva_m; + dmg_m *= another.dmg_m; + hp_m *= another.hp_m; + atk_l += another.atk_l; + def_l += another.def_l; + acc_l += another.acc_l; + eva_l += another.eva_l; + dmg_l += another.dmg_l; + hp_l += another.hp_l; + } + + public int affectAtk(float attackPower){ + return GME.accurateRound(attackPower * atk_m + atk_l); + } + + public int affectDef(float defensePower){ + return GME.accurateRound(defensePower * def_m + def_l); + } + + public float affectAcc(float accuracy){ + return accuracy * acc_m + acc_l; + } + + public float affectEva(float evasion){ + return evasion * eva_m + eva_l; + } + + public int affectDmg(float damage){ + return GME.accurateRound(damage * dmg_m + dmg_l); + } + + public void affectHp(Char ch){ + float percent = (float) ch.HP / ch.HT; + //HT of Char is bundled, need to roll back first + if(orig_HT < 0) { + orig_HT = ch.HT; + }else{ + ch.HT = orig_HT; + } + ch.HT = (int)(ch.HT * hp_m + hp_l); + if(ch.HT <= 0){ + ch.HT = 1; + } + ch.HP = GME.accurateRound(ch.HT * percent); + if(ch.HP <= 0){ + ch.HP = 1; + } + } + + public void setHPBack(Char ch){ + float percent = (float) ch.HP / ch.HT; + ch.HT = orig_HT; + boolean alive = ch.isAlive(); + ch.HP = GME.accurateRound(ch.HT * percent); + if(ch.HP <= 0 && alive) ch.HP = 1; + } + + public AttributeModifier setAtk(float m, float l){ + atk_m = m; atk_l = l; + return this; + } + + public AttributeModifier setDef(float m, float l){ + def_m = m; def_l = l; + return this; + } + + public AttributeModifier setAcc(float m, float l){ + acc_m = m; acc_l = l; + return this; + } + + public AttributeModifier setEva(float m, float l){ + eva_m = m; eva_l = l; + return this; + } + + public AttributeModifier setDmg(float m, float l){ + dmg_m = m; dmg_l = l; + return this; + } + + public AttributeModifier setHP(float m, float l){ + hp_m = m; hp_l = l; + if(target != null){ + affectHp(target); + } + return this; + } + + public AttributeModifier setAll(float[] mul, float[] add){ + atk_m = mul[0]; + def_m = mul[1]; + acc_m = mul[2]; + eva_m = mul[3]; + dmg_m = mul[4]; + hp_m = mul[5]; + atk_l = add[0]; + def_l = add[1]; + acc_l = add[2]; + eva_l = add[3]; + dmg_l = add[4]; + hp_l = add[5]; + if(target != null){ + affectHp(target); + } + return this; + } + + @Override + public boolean attachTo(Char target) { + for(Buff b: target.buffs()){ + if(b instanceof AttributeModifier){ + merge((AttributeModifier) b); + b.detach(); + } + } + boolean ret_val = super.attachTo(target); + if(ret_val){ + affectHp(target); + } + return ret_val; + } + + @Override + public void detach() { + setHPBack(target); + super.detach(); + } + + @Override + public int icon() { + return BuffIndicator.COMBO; + } + + @Override + public String toString() { + return Messages.get(this, "name"); + } + + @Override + public String desc() { + return Messages.get(this, "desc", atk_m, atk_l, def_m, def_l, acc_m, acc_l, eva_m, eva_l, dmg_m, dmg_l, hp_m, hp_l, target.HP, target.HT); + } + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put("atk_mul", atk_m); + bundle.put("def_mul", def_m); + bundle.put("acc_mul", acc_m); + bundle.put("eva_mul", eva_m); + bundle.put("dmg_mul", dmg_m); + bundle.put("hp_mul", hp_m); + bundle.put("atk_lin", atk_l); + bundle.put("def_lin", def_l); + bundle.put("acc_lin", acc_l); + bundle.put("eva_lin", eva_l); + bundle.put("dmg_lin", dmg_l); + bundle.put("hp_lin", hp_l); + bundle.put("orig_hp", orig_HT); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + atk_m = bundle.getFloat("atk_mul"); + def_m = bundle.getFloat("def_mul"); + acc_m = bundle.getFloat("acc_mul"); + eva_m = bundle.getFloat("eva_mul"); + dmg_m = bundle.getFloat("dmg_mul"); + hp_m = bundle.getFloat("hp_mul"); + atk_l = bundle.getFloat("atk_lin"); + def_l = bundle.getFloat("def_lin"); + acc_l = bundle.getFloat("acc_lin"); + eva_l = bundle.getFloat("eva_lin"); + dmg_l = bundle.getFloat("dmg_lin"); + hp_l = bundle.getFloat("hp_lin"); + orig_HT = bundle.getInt("orig_hp"); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ConsistBleeding.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ConsistBleeding.java new file mode 100644 index 000000000..df14f76d7 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ConsistBleeding.java @@ -0,0 +1,151 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.custom.messages.M; +import com.shatteredpixel.shatteredpixeldungeon.effects.Splash; +import com.shatteredpixel.shatteredpixeldungeon.effects.Wound; +import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.utils.Bundle; +import com.watabou.utils.PointF; +import com.watabou.utils.Random; + +public class ConsistBleeding extends Buff{ + protected float[] lasting; + protected float[] dmg; + protected float percentDamage=0; + + public ConsistBleeding(){ + lasting = new float[5]; + dmg = new float[5]; + } + + public boolean newLayer(float time, float damagePerAct){ + for(int i=0;i<5;++i){ + if(lasting[i]<=0f){ + lasting[i]=time; + dmg[i]=damagePerAct; + return true; + } + } + return false; + } + + public void burst(){ + float damage = 0; + for(int i = 0; i<5; ++i){ + damage += Math.max(0, dmg[i])*lasting[i]; + } + target.sprite.showStatus(CharSprite.NEGATIVE, "!!!"); + if (target.sprite.visible) { + Splash.at( target.sprite.center(), -PointF.PI / 2, PointF.PI / 6, + target.sprite.blood(), 30 ); + } + Wound.hit(target); + + damage *= Random.Float(1.7f, 2.1f); + target.damage((int)damage, this); + if(target == Dungeon.hero && !target.isAlive()){ + Dungeon.fail(getClass()); + GLog.n(M.L(this, "burst_die")); + } + detach(); + } + + public void resetList(){ + for(int i=0; i<5; ++i){ + lasting[i]=0; + dmg[i]=0; + } + } + + public void decreaseTime(){ + for(int i=0; i<5; ++i){ + lasting[i]-=1f; + } + } + + public float oneDamage(){ + float damage = 0; + for(int i=0; i<5; ++i){ + damage += dmg[i]; + } + return damage; + } + + public int getLayer(){ + int layer = 0; + for(int i=0;i<5;++i){ + layer += lasting[i]>0f?1:0; + } + return layer; + } + + @Override + public boolean act(){ + spend(TICK); + percentDamage += oneDamage(); + target.damage((int)percentDamage, this); + + if (target.sprite.visible) { + Splash.at( target.sprite.center(), -PointF.PI / 2, PointF.PI / 6, + target.sprite.blood(), Math.min( 10 *(int)percentDamage / target.HT, 10 ) ); + } + if(target == Dungeon.hero && !target.isAlive()){ + Dungeon.fail(getClass()); + GLog.n(M.L(this, "bleed_die")); + } + + percentDamage -= (int)percentDamage; + + decreaseTime(); + + if(getLayer()==0) detach(); + + return true; + } + + @Override + public void storeInBundle(Bundle b){ + super.storeInBundle(b); + b.put("leftTime", lasting); + b.put("damageLeft", dmg); + b.put("percentDamage", percentDamage); + } + + @Override + public void restoreFromBundle(Bundle b){ + super.restoreFromBundle(b); + lasting = b.getFloatArray("leftTime"); + dmg = b.getFloatArray("damageLeft"); + percentDamage = b.getFloat("percentDamage"); + } + + @Override + public int icon(){ + return BuffIndicator.BLEEDING; + } + + @Override + public String desc() { + return M.L(this, "desc", getLayer(), oneDamage()); + } + + @Override + public String toString() { + return M.L(this, "name"); + } + + @Override + public String heroMessage() { + return M.L(this, "heromsg"); + } + + @Override + public float iconFadePercent() { + return Math.max(0, (5 - getLayer()) / 5f); + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/CountBuff.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/CountBuff.java new file mode 100644 index 000000000..a1223a75e --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/CountBuff.java @@ -0,0 +1,36 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.watabou.utils.Bundle; + +public class CountBuff extends Buff { + + private float count = 0; + + public void countUp( float inc ){ + count += inc; + } + + public void countDown( float inc ){ + count -= inc; + } + + public float count(){ + return count; + } + + private static final String COUNT = "count"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(COUNT, count); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + count = bundle.getFloat(COUNT); + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/DummyChar.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/DummyChar.java new file mode 100644 index 000000000..d53ca6c7a --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/DummyChar.java @@ -0,0 +1,71 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.watabou.utils.Callback; + +//It's called char, but it serves for buffs, not chars. + +//Currently DummyChar is only used to hold "level buffs". +//Level buffs are buffs that not affected by chars. +//This kind of buff should be attached to a "static" char that is sealed in level, not existing chars. +//Or it would be problematic if the holder dies, or leaves the level. + +//might be better to maintain a dummyChar array. Use id to distinguish between dcs. +public final class DummyChar extends Char { + + { + alignment = Alignment.NEUTRAL; + } + + @Override + protected boolean act() { + spend(TICK); + if(buffs().isEmpty()){ + killDummyChar(); + } + return true; + } + + @Override + public void damage(int dmg, Object src) { + } + + @Override + public boolean canInteract(Char c) { + return false; + } + + //return the first DummyChar, + public static DummyChar findDummyChar(){ + for(Char ch: Actor.chars()){ + if(ch instanceof DummyChar){ + return (DummyChar) ch; + } + } + return null; + } + + //return existing dummyChar, or create a new dummyChar + public static DummyChar getDC(){ + for(Char ch: Actor.chars()){ + if(ch instanceof DummyChar){ + return (DummyChar) ch; + } + } + DummyChar dc = new DummyChar(); + Actor.add(dc); + return dc; + } + + //remove existing dummyChar + public static boolean killDummyChar() { + DummyChar dc = findDummyChar(); + if (dc == null) { + return false; + } + Actor.remove(dc); + return true; + } + +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/FlavourBuffOpen.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/FlavourBuffOpen.java new file mode 100644 index 000000000..9b5c17a9c --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/FlavourBuffOpen.java @@ -0,0 +1,9 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; + +public class FlavourBuffOpen extends FlavourBuff { + public void setDuration(float time){ + spend(time); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/IgnoreArmor.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/IgnoreArmor.java new file mode 100644 index 000000000..c75ed9d87 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/IgnoreArmor.java @@ -0,0 +1,34 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.watabou.noosa.Image; + +public class IgnoreArmor extends FlavourBuff { + { + type = buffType.NEUTRAL; + announced = true; + } + + @Override + public int icon() { + return BuffIndicator.VULNERABLE; + } + + @Override + public void tintIcon(Image icon) { + super.tintIcon(icon); + icon.hardlight(0.8f, 0f, 0f); + } + + @Override + public String toString() { + return Messages.get(this, "name"); + } + + @Override + public String desc() { + return Messages.get(this, "desc", dispTurns()); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/PositiveBuffProhibition.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/PositiveBuffProhibition.java new file mode 100644 index 000000000..20ab11e4a --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/PositiveBuffProhibition.java @@ -0,0 +1,46 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.watabou.noosa.Image; + +public class PositiveBuffProhibition extends FlavourBuff { + { + type = buffType.NEUTRAL; + announced = true; + } + + @Override + public int icon() { + return BuffIndicator.DEGRADE; + } + + @Override + public void tintIcon(Image icon) { + super.tintIcon(icon); + icon.hardlight(0f, 1f, 0f); + } + + @Override + public String toString() { + return Messages.get(this, "name"); + } + + @Override + public String desc() { + return Messages.get(this, "desc", dispTurns()); + } + + @Override + public boolean attachTo(Char target) { + for(Buff b: target.buffs()){ + if(b.type == buffType.POSITIVE && !b.revivePersists){ + b.detach(); + } + } + return super.attachTo(target); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroAttack.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroAttack.java new file mode 100644 index 000000000..2349b3a42 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroAttack.java @@ -0,0 +1,33 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.watabou.noosa.Image; + +public class ZeroAttack extends FlavourBuff { + { + type = buffType.NEUTRAL; + announced = true; + } + + @Override + public int icon() { + return BuffIndicator.WEAKNESS; + } + + @Override + public void tintIcon(Image icon) { + icon.hardlight(.5f, .5f, .5f); + } + + @Override + public String toString() { + return Messages.get(this, "name"); + } + + @Override + public String desc() { + return Messages.get(this, "desc", dispTurns()); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroDefense.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroDefense.java new file mode 100644 index 000000000..267027249 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/buffs/ZeroDefense.java @@ -0,0 +1,33 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.buffs; + +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.watabou.noosa.Image; + +public class ZeroDefense extends FlavourBuff { + { + type = buffType.NEUTRAL; + announced = true; + } + + @Override + public int icon() { + return BuffIndicator.VULNERABLE; + } + + @Override + public void tintIcon(Image icon) { + icon.hardlight(.5f, .5f, .5f); + } + + @Override + public String toString() { + return Messages.get(this, "name"); + } + + @Override + public String desc() { + return Messages.get(this, "desc", dispTurns()); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/ch/GameTracker.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/ch/GameTracker.java new file mode 100644 index 000000000..014f492c6 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/ch/GameTracker.java @@ -0,0 +1,154 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.ch; + +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.Statistics; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Ghost; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Imp; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.RedDragon; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker; +import com.shatteredpixel.shatteredpixeldungeon.items.Heap; +import com.shatteredpixel.shatteredpixeldungeon.items.Item; +import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.Artifact; +import com.shatteredpixel.shatteredpixeldungeon.items.rings.Ring; +import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon; +import com.shatteredpixel.shatteredpixeldungeon.levels.RegularLevel; +import com.watabou.noosa.Game; +import com.watabou.noosa.Visual; +import com.watabou.utils.Bundle; + +public class GameTracker extends Buff { + { + actPriority = VFX_PRIO - 1; + revivePersists = true; + } + private VirtualVisualTimer vvt; + private int maxDepth = -1; + private String allItemInfo = ""; + + @Override + public boolean act() { + spend(TICK/2f); + if(vvt==null) { + vvt = new VirtualVisualTimer(); + Dungeon.hero.sprite.parent.add(vvt); + } + if(!vvt.alive || !vvt.active || vvt.parent == null){ + vvt.revive(); + vvt.active = true; + Dungeon.hero.sprite.parent.add(vvt); + vvt.parent = Dungeon.hero.sprite.parent; + } + if(maxDepth < Dungeon.depth){ + spend(-TICK); + maxDepth = Dungeon.depth; + updateItemInfo(); + } + return true; + } + + public void onNewLevel(){ + if(maxDepth < Dungeon.depth){ + spend(-TICK); + maxDepth = Dungeon.depth; + updateItemInfo(); + } + } + + private void updateItemInfo(){ + if(Dungeon.level instanceof RegularLevel){ + StringBuilder info = new StringBuilder(""); + info.append("dungeon_depth: ").append(maxDepth).append("\n"); + for(Heap heap: Dungeon.level.heaps.valueList()){ + if((!heap.autoExplored) || heap.type == Heap.Type.CHEST || heap.type == Heap.Type.LOCKED_CHEST || heap.type == Heap.Type.CRYSTAL_CHEST){ + for(Item item : heap.items){ + appendDesc(item, info, ""); + } + } + } + for(Mob m : Dungeon.level.mobs.toArray(new Mob[0])){ + if(m instanceof Mimic){ + for(Item item: ((Mimic) m).items){ + appendDesc(item, info, "MIMIC_HOLD"); + } + } + if(m instanceof Ghost){ + appendDesc(Ghost.Quest.weapon, info, "QUEST_REWARD"); + appendDesc(Ghost.Quest.armor, info, "QUEST_REWARD"); + } + if(m instanceof Wandmaker){ + appendDesc(Wandmaker.Quest.wand1, info, "QUEST_REWARD"); + appendDesc(Wandmaker.Quest.wand2, info, "QUEST_REWARD"); + } + if(m instanceof RedDragon){ + appendDesc(RedDragon.Quest.weapon, info, "QUEST_REWARD"); + appendDesc(RedDragon.Quest.armor, info, "QUEST_REWARD"); + } + if(m instanceof Imp){ + appendDesc(Imp.Quest.reward, info, "QUEST_REWARD"); + } + } + allItemInfo += info.toString(); + } + } + + private void appendDesc(Item item, StringBuilder info, String prefix){ + if(item != null) { + if ( + ((item instanceof Weapon || item instanceof Armor) && item.level() > 0) + || (item instanceof Ring || item instanceof Wand || item instanceof Artifact) + ) { + String name = item.trueName(); + int index = name.indexOf('+'); + if(index > 0){ + name = name.substring(0, index - 3); + } + info.append(prefix).append(name).append('+').append(item.level()).append(item.cursed ? " CURSED\n" : "\n"); + } + } + } + + public String itemInfo(){ + return allItemInfo; + } + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put("allItemInfo", allItemInfo); + bundle.put("recordedMaxDepth", maxDepth); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + allItemInfo = bundle.getString("allItemInfo"); + maxDepth = bundle.getInt("recordedMaxDepth"); + } + + public static class VirtualVisualTimer extends Visual { + public VirtualVisualTimer(){ + super(0, 0, 0, 0); + } + + @Override + public void update() { + super.update(); + Statistics.second_elapsed += Game.elapsed; + if(Statistics.second_elapsed > 1f){ + Statistics.real_seconds += Math.floor(Statistics.second_elapsed); + Statistics.second_elapsed -= Math.floor(Statistics.second_elapsed); + } + if(Statistics.turnsPassed < Statistics.duration){ + Statistics.turnsPassed = Statistics.duration; + }else{ + Statistics.turnsPassed = Statistics.duration + Actor.now(); + } + } + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/testmode/ImmortalShieldAffecter.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/testmode/ImmortalShieldAffecter.java new file mode 100644 index 000000000..8d908ca86 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/custom/testmode/ImmortalShieldAffecter.java @@ -0,0 +1,60 @@ +package com.shatteredpixel.shatteredpixeldungeon.custom.testmode; + +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; +import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; +import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; + +import java.util.ArrayList; + +public class ImmortalShieldAffecter extends TestItem { + { + image = ItemSpriteSheet.ROUND_SHIELD; + defaultAction = AC_SWITCH; + } + private static final String AC_SWITCH = "switch"; + + @Override + public ArrayList actions(Hero hero ) { + ArrayList actions = super.actions(hero); + actions.add(AC_SWITCH); + return actions; + } + + @Override + public void execute(Hero hero, String action ) { + super.execute(hero, action); + if(action.equals(AC_SWITCH)){ + if(isImmortal(hero)){ + Buff.detach(hero, ImmortalShield.class); + }else{ + Buff.affect(hero, ImmortalShield.class); + } + } + } + + private boolean isImmortal(Char target){ + return target.buff(ImmortalShield.class)!=null; + } + + public static class ImmortalShield extends Buff{ + { + type = buffType.NEUTRAL; + announced = false; + revivePersists = true; + } + + @Override + public boolean act(){ + spend(TICK); + return true; + } + + @Override + public void fx(boolean on) { + if (on) target.sprite.add(CharSprite.State.SHIELDED); + else target.sprite.remove(CharSprite.State.SHIELDED); + } + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java index 1422b6233..645f5c950 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Heap.java @@ -74,6 +74,8 @@ public class Heap implements Bundlable { public ItemSprite sprite; public boolean seen = false; public boolean haunted = false; + + public boolean autoExplored = false; //used to determine if this heap should count for exploration bonus public LinkedList items = new LinkedList<>(); @@ -407,6 +409,8 @@ public class Heap implements Bundlable { private static final String TYPE = "type"; private static final String ITEMS = "items"; private static final String HAUNTED = "haunted"; + + private static final String AUTO_EXPLORED = "auto_explored"; @SuppressWarnings("unchecked") @Override @@ -431,6 +435,9 @@ public class Heap implements Bundlable { } haunted = bundle.getBoolean( HAUNTED ); + + //SPD + autoExplored = bundle.getBoolean( AUTO_EXPLORED ); } @@ -441,6 +448,9 @@ public class Heap implements Bundlable { bundle.put( TYPE, type.toString() ); bundle.put( ITEMS, items ); bundle.put( HAUNTED, haunted ); + + //SPD + bundle.put( AUTO_EXPLORED, autoExplored ); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/Wand.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/Wand.java index 5f341243a..5cc7771b1 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/Wand.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/Wand.java @@ -26,18 +26,51 @@ import com.shatteredpixel.shatteredpixeldungeon.Badges; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Bleeding; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Burning; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Charm; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Chill; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corrosion; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corruption; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Cripple; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Doom; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Dread; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Drowsy; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Frost; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hex; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicImmune; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSleep; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Ooze; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Poison; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Recharging; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Roots; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ScrollEmpower; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Slow; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.SoulMark; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vulnerable; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.mage.WildMagic; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bee; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Piranha; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Statue; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Swarm; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Wraith; import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight; @@ -61,6 +94,7 @@ import com.watabou.utils.PointF; import com.watabou.utils.Random; import java.util.ArrayList; +import java.util.HashMap; public abstract class Wand extends Item { public static final String AC_ZAP = "ZAP"; @@ -72,7 +106,7 @@ public abstract class Wand extends Item { } private static final float TIME_TO_ZAP = 1f; - + public int maxCharges = initialCharges(); public int curCharges = maxCharges; public float partialCharge = 0f; @@ -381,7 +415,7 @@ public abstract class Wand extends Item { particle.radiateXY(0.5f); } - protected void wandUsed() { + public void wandUsed() { if (!isIdentified()) { float uses = Math.min( availableUsesToID, Talent.itemIDSpeedFactor(Dungeon.hero, this) ); availableUsesToID -= uses; @@ -531,6 +565,150 @@ public abstract class Wand extends Item { return collisionProperties; } + + //浊焰 + + private static final float MINOR_DEBUFF_WEAKEN = 1/4f; + private static final HashMap, Float> MINOR_DEBUFFS = new HashMap<>(); + static{ + MINOR_DEBUFFS.put(Weakness.class, 2f); + MINOR_DEBUFFS.put(Vulnerable.class, 2f); + MINOR_DEBUFFS.put(Cripple.class, 1f); + MINOR_DEBUFFS.put(Blindness.class, 1f); + MINOR_DEBUFFS.put(Terror.class, 1f); + + MINOR_DEBUFFS.put(Chill.class, 0f); + MINOR_DEBUFFS.put(Ooze.class, 0f); + MINOR_DEBUFFS.put(Roots.class, 0f); + MINOR_DEBUFFS.put(Vertigo.class, 0f); + MINOR_DEBUFFS.put(Drowsy.class, 0f); + MINOR_DEBUFFS.put(Bleeding.class, 0f); + MINOR_DEBUFFS.put(Burning.class, 0f); + MINOR_DEBUFFS.put(Poison.class, 0f); + } + + private static final float MAJOR_DEBUFF_WEAKEN = 1/2f; + private static final HashMap, Float> MAJOR_DEBUFFS = new HashMap<>(); + static{ + MAJOR_DEBUFFS.put(Amok.class, 3f); + MAJOR_DEBUFFS.put(Slow.class, 2f); + MAJOR_DEBUFFS.put(Hex.class, 2f); + MAJOR_DEBUFFS.put(Paralysis.class, 1f); + + MAJOR_DEBUFFS.put(Dread.class, 0f); + MAJOR_DEBUFFS.put(Charm.class, 0f); + MAJOR_DEBUFFS.put(MagicalSleep.class, 0f); + MAJOR_DEBUFFS.put(SoulMark.class, 0f); + MAJOR_DEBUFFS.put(Corrosion.class, 0f); + MAJOR_DEBUFFS.put(Frost.class, 0f); + MAJOR_DEBUFFS.put(Doom.class, 0f); + } + + public void onZapX(Ballistica bolt) { + Char ch = Actor.findChar(bolt.collisionPos); + + if (ch != null){ + + if (!(ch instanceof Mob)){ + return; + } + + Mob enemy = (Mob) ch; + + float corruptingPower = 3 + buffedLvl()/2f; + + //base enemy resistance is usually based on their exp, but in special cases it is based on other criteria + float enemyResist = 1 + enemy.EXP; + if (ch instanceof Mimic || ch instanceof Statue){ + enemyResist = 1 + Dungeon.depth; + } else if (ch instanceof Piranha || ch instanceof Bee) { + enemyResist = 1 + Dungeon.depth/2f; + } else if (ch instanceof Wraith) { + //divide by 5 as wraiths are always at full HP and are therefore ~5x harder to corrupt + enemyResist = (1f + Dungeon.depth/3f) / 5f; + } else if (ch instanceof Swarm){ + //child swarms don't give exp, so we force this here. + enemyResist = 1 + 3; + } + + //100% health: 5x resist 75%: 3.25x resist 50%: 2x resist 25%: 1.25x resist + enemyResist *= 1 + 4*Math.pow(enemy.HP/(float)enemy.HT, 2); + + //debuffs placed on the enemy reduce their resistance + for (Buff buff : enemy.buffs()){ + if (MAJOR_DEBUFFS.containsKey(buff.getClass())) enemyResist *= (1f-MAJOR_DEBUFF_WEAKEN); + else if (MINOR_DEBUFFS.containsKey(buff.getClass())) enemyResist *= (1f-MINOR_DEBUFF_WEAKEN); + else if (buff.type == Buff.buffType.NEGATIVE) enemyResist *= (1f-MINOR_DEBUFF_WEAKEN); + } + + //cannot re-corrupt or doom an enemy, so give them a major debuff instead + if(enemy.buff(Corruption.class) != null || enemy.buff(Doom.class) != null){ + corruptingPower = enemyResist - 0.001f; + } + + if (corruptingPower > enemyResist){ + corruptEnemy( enemy ); + } else { + float debuffChance = corruptingPower / enemyResist; + if (Random.Float() < debuffChance){ + debuffEnemy( enemy, MAJOR_DEBUFFS); + } else { + debuffEnemy( enemy, MINOR_DEBUFFS); + } + } + + wandProc(ch, chargesPerCast()); + Sample.INSTANCE.play( Assets.Sounds.HIT_MAGIC, 1, 0.8f * Random.Float(0.87f, 1.15f) ); + + } else { + Dungeon.level.pressCell(bolt.collisionPos); + } + } + + private void debuffEnemy( Mob enemy, HashMap, Float> category ){ + + //do not consider buffs which are already assigned, or that the enemy is immune to. + HashMap, Float> debuffs = new HashMap<>(category); + for (Buff existing : enemy.buffs()){ + if (debuffs.containsKey(existing.getClass())) { + debuffs.put(existing.getClass(), 0f); + } + } + for (Class toAssign : debuffs.keySet()){ + if (debuffs.get(toAssign) > 0 && enemy.isImmune(toAssign)){ + debuffs.put(toAssign, 0f); + } + } + + //all buffs with a > 0 chance are flavor buffs + Class debuffCls = (Class) Random.chances(debuffs); + + if (debuffCls != null){ + Buff.append(enemy, debuffCls, 6 + buffedLvl()*3); + } else { + //if no debuff can be applied (all are present), then go up one tier + if (category == MINOR_DEBUFFS) debuffEnemy( enemy, MAJOR_DEBUFFS); + else if (category == MAJOR_DEBUFFS) corruptEnemy( enemy ); + } + } + + + private void corruptEnemy( Mob enemy ){ + //cannot re-corrupt or doom an enemy, so give them a major debuff instead + if(enemy.buff(Corruption.class) != null || enemy.buff(Doom.class) != null){ + GLog.w( Messages.get(this, "already_corrupted") ); + return; + } + + if (!enemy.isImmune(Corruption.class)){ + Corruption.corruptionHeal(enemy); + + AllyBuff.affectAndLoot(enemy, curUser, Corruption.class); + } else { + Buff.affect(enemy, Doom.class); + } + } + public static class PlaceHolder extends Wand { { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfMagicMissile.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfMagicMissile.java index 10e03a6dc..c47891faf 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfMagicMissile.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/wands/WandOfMagicMissile.java @@ -28,6 +28,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; import com.shatteredpixel.shatteredpixeldungeon.effects.SpellSprite; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.EndingBlade; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff; import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; @@ -103,6 +104,7 @@ public class WandOfMagicMissile extends DamageWand { private int level = 0; private Wand wandJustApplied; //we don't bundle this as it's only used right as the buff is applied + private EndingBlade wandJustAppliedX; //we don't bundle this as it's only used right as the buff is applied public void setup(Wand wand){ if (level < wand.buffedLvl()){ @@ -128,6 +130,12 @@ public class WandOfMagicMissile extends DamageWand { return result; } + public EndingBlade wandJustAppliedX(){ + EndingBlade result = this.wandJustAppliedX; + this.wandJustAppliedX = null; + return result; + } + @Override public int icon() { return BuffIndicator.UPGRADE; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/EndingBlade.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/EndingBlade.java index 412e120c3..3784b28b2 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/EndingBlade.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/EndingBlade.java @@ -1,23 +1,81 @@ package com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee; +import com.shatteredpixel.shatteredpixeldungeon.Assets; +import com.shatteredpixel.shatteredpixeldungeon.Badges; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Burning; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Chill; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corrosion; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Corruption; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Cripple; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Doom; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Drowsy; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Frost; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hex; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicImmune; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MagicalSleep; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Ooze; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Paralysis; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Poison; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ScrollEmpower; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Slow; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.SoulMark; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Vertigo; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bee; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Piranha; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Statue; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Swarm; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Wraith; +import com.shatteredpixel.shatteredpixeldungeon.custom.utils.BallisticaReal; +import com.shatteredpixel.shatteredpixeldungeon.effects.Beam; +import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter; +import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile; +import com.shatteredpixel.shatteredpixeldungeon.effects.particles.PurpleParticle; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight; import com.shatteredpixel.shatteredpixeldungeon.items.bombs.LaserPython; +import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRecharging; +import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand; +import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfMagicMissile; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Blazing; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Grim; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Kinetic; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Shocking; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Unstable; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.scenes.CellSelector; +import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; +import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlotButton; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.noosa.audio.Sample; import com.watabou.utils.Bundle; +import com.watabou.utils.Callback; +import com.watabou.utils.Point; +import com.watabou.utils.PointF; +import com.watabou.utils.Random; import java.util.ArrayList; +import java.util.HashMap; public class EndingBlade extends Weapon { + { image = ItemSpriteSheet.ENDDIED; tier = 4; @@ -51,12 +109,14 @@ public class EndingBlade extends Weapon { private static final String FIRST = "first"; private static final String INTRPS = "intrps"; + private static final String TDM = "shocking_ordinals"; @Override public void storeInBundle(Bundle bundle) { super.storeInBundle(bundle); bundle.put(FIRST, firstx); bundle.put(INTRPS, fireenergy); + bundle.put(TDM, TIME_TO_DIED); } @Override @@ -64,10 +124,11 @@ public class EndingBlade extends Weapon { super.restoreFromBundle(bundle); firstx = bundle.getBoolean(FIRST); fireenergy = bundle.getInt(INTRPS); + TIME_TO_DIED = bundle.getInt(TDM); } public String desc() { - return Messages.get(this, "desc")+"_"+fireenergy+"_"; + return Messages.get(this, "desc")+"_"+fireenergy+"_"+Messages.get(this, "desc_2")+"_"+TIME_TO_DIED+"_"; } //每100点浊焰能量自动升级 @@ -75,7 +136,7 @@ public class EndingBlade extends Weapon { public int level() { return fireenergy/100; } - + private int TIME_TO_DIED = 0; public void execute( Hero hero, String action ) { super.execute( hero, action ); @@ -92,7 +153,16 @@ public class EndingBlade extends Weapon { } break; case AC_DIEDGHOST: - GLog.p("2"); + if(level >= 5 && TIME_TO_DIED == 0) { + curUser = hero; + curItem = this; + GameScene.selectCell(zapper); + TIME_TO_DIED = 20; + } else if (TIME_TO_DIED != 0) { + GLog.n("道具正在冷却"); + } else { + GLog.n("等级不足"); + } break; case AC_HEALRESET: GLog.w("3"); @@ -100,8 +170,508 @@ public class EndingBlade extends Weapon { } } + public int maxCharges = initialCharges(); + public int curCharges = maxCharges; + public int collisionProperties(int target){ + return collisionProperties; + } + protected int collisionProperties = Ballistica.MAGIC_BOLT; + protected int initialCharges() { + return 2; + } + + protected static CellSelector.Listener zapper = new CellSelector.Listener() { + + @Override + public void onSelect( Integer target ) { + + if (target != null) { + + //FIXME this safety check shouldn't be necessary + //it would be better to eliminate the curItem static variable. + final EndingBlade curWand; + if (curItem instanceof EndingBlade) { + curWand = (EndingBlade) EndingBlade.curItem; + } else { + return; + } + + final Ballistica shot = new Ballistica( curUser.pos, target, curWand.collisionProperties(target)); + int cell = shot.collisionPos; + + if (target == curUser.pos || cell == curUser.pos) { + if (target == curUser.pos && curUser.hasTalent(Talent.SHIELD_BATTERY)){ + float shield = curUser.HT * (0.04f*curWand.curCharges); + if (curUser.pointsInTalent(Talent.SHIELD_BATTERY) == 2) shield *= 1.5f; + Buff.affect(curUser, Barrier.class).setShield(Math.round(shield)); + curWand.curCharges = 0; + curUser.sprite.operate(curUser.pos); + Sample.INSTANCE.play(Assets.Sounds.CHARGEUP); + ScrollOfRecharging.charge(curUser); + updateQuickslot(); + curUser.spend(Actor.TICK); + return; + } + GLog.i( Messages.get(Wand.class, "self_target") ); + return; + } + + curUser.sprite.zap(cell); + + //attempts to target the cell aimed at if something is there, otherwise targets the collision pos. + if (Actor.findChar(target) != null) + QuickSlotButton.target(Actor.findChar(target)); + else + QuickSlotButton.target(Actor.findChar(cell)); + + if (curWand.tryToZap(curUser, target)) { + + curUser.busy(); + curWand.fxs(shot, new Callback() { + public void call() { + curWand.onZap(shot); + curWand.wandUsed(); + } + }); + curWand.cursedKnown = true; + + } + + } + } + + @Override + public String prompt() { + return Messages.get(Wand.class, "prompt"); + } + }; + + public boolean tryToZap( Hero owner, int target ){ + + if (owner.buff(MagicImmune.class) != null){ + GLog.w( Messages.get(Wand.class, "no_magic") ); + return false; + } + + return true; + } + + protected Wand.Charger charger; + + private static final int USES_TO_ID = 10; + private float usesLeftToID = USES_TO_ID; + private float availableUsesToID = USES_TO_ID/2f; + private static final float TIME_TO_ZAP = 1f; + + public void wandUsed() { + if (!isIdentified()) { + float uses = Math.min( availableUsesToID, Talent.itemIDSpeedFactor(Dungeon.hero, this) ); + availableUsesToID -= uses; + usesLeftToID -= uses; + if (usesLeftToID <= 0 || Dungeon.hero.pointsInTalent(Talent.SCHOLARS_INTUITION) == 2) { + identify(); + GLog.p( Messages.get(Wand.class, "identify") ); + Badges.validateItemLevelAquired( this ); + } + } + + curCharges -= cursed ? 1 : chargesPerCast(); + + //remove magic charge at a higher priority, if we are benefiting from it are and not the + //wand that just applied it + WandOfMagicMissile.MagicCharge buff = curUser.buff(WandOfMagicMissile.MagicCharge.class); + if (buff != null + && buff.wandJustAppliedX() != this + && buff.level() == buffedLvl() + && buffedLvl() > super.buffedLvl()){ + buff.detach(); + } else { + ScrollEmpower empower = curUser.buff(ScrollEmpower.class); + if (empower != null){ + empower.use(); + } + } + + //if the wand is owned by the hero, but not in their inventory, it must be in the staff + if (charger != null + && charger.target == Dungeon.hero + && !Dungeon.hero.belongings.contains(this)) { + if (curCharges == 0 && Dungeon.hero.hasTalent(Talent.BACKUP_BARRIER)) { + //grants 3/5 shielding + Buff.affect(Dungeon.hero, Barrier.class).setShield(1 + 2 * Dungeon.hero.pointsInTalent(Talent.BACKUP_BARRIER)); + } + if (Dungeon.hero.hasTalent(Talent.EMPOWERED_STRIKE)){ + Buff.prolong(Dungeon.hero, Talent.EmpoweredStrikeTracker.class, 10f); + } + } + + Invisibility.dispel(); + updateQuickslot(); + + curUser.spendAndNext( TIME_TO_ZAP ); + } + + protected int nReflections(int level){ + //return Math.min(2+level/3, 5); + return 5; + } + + protected float reflectionDamageFactor(int reflections){ + return 1f+0.1f*reflections*reflections; + } + + public void onZap( Ballistica beam ) { + int count = 0; + for(BallisticaReal b: beams){ + onZapX(b, count); + ++count; + } + Char ch = Actor.findChar(beam.collisionPos); + + if (ch != null){ + + if (!(ch instanceof Mob)){ + return; + } + + Mob enemy = (Mob) ch; + + float corruptingPower = 3 + buffedLvl()/2f; + + //base enemy resistance is usually based on their exp, but in special cases it is based on other criteria + float enemyResist = 1 + enemy.EXP; + if (ch instanceof Mimic || ch instanceof Statue){ + enemyResist = 1 + Dungeon.depth; + } else if (ch instanceof Piranha || ch instanceof Bee) { + enemyResist = 1 + Dungeon.depth/2f; + } else if (ch instanceof Wraith) { + //divide by 5 as wraiths are always at full HP and are therefore ~5x harder to corrupt + enemyResist = (1f + Dungeon.depth/3f) / 5f; + } else if (ch instanceof Swarm){ + //child swarms don't give exp, so we force this here. + enemyResist = 1 + 3; + } + + //100% health: 5x resist 75%: 3.25x resist 50%: 2x resist 25%: 1.25x resist + enemyResist *= 1 + 4*Math.pow(enemy.HP/(float)enemy.HT, 2); + + //debuffs placed on the enemy reduce their resistance + for (Buff buff : enemy.buffs()){ + if (MAJOR_DEBUFFS.containsKey(buff.getClass())) enemyResist *= (1f-MAJOR_DEBUFF_WEAKEN); + else if (MINOR_DEBUFFS.containsKey(buff.getClass())) enemyResist *= (1f-MINOR_DEBUFF_WEAKEN); + else if (buff.type == Buff.buffType.NEGATIVE) enemyResist *= (1f-MINOR_DEBUFF_WEAKEN); + } + + //cannot re-corrupt or doom an enemy, so give them a major debuff instead + if(enemy.buff(Corruption.class) != null || enemy.buff(Doom.class) != null){ + corruptingPower = enemyResist - 0.001f; + } + + if (corruptingPower > enemyResist){ + corruptEnemy( enemy ); + } else { + float debuffChance = corruptingPower / enemyResist; + if (Random.Float() < debuffChance){ + debuffEnemy( enemy, MAJOR_DEBUFFS); + } else { + debuffEnemy( enemy, MINOR_DEBUFFS); + } + } + + wandProc(ch, chargesPerCast()); + Sample.INSTANCE.play( Assets.Sounds.HIT_MAGIC, 1, 0.8f * Random.Float(0.87f, 1.15f) ); + } + } + + public void onZapX(BallisticaReal beam,int reflection) { + int level = buffedLvl(); + + int maxDistance = beam.dist; + + ArrayList chars = new ArrayList<>(); + + for (int c : beam.subPath((reflection>0?0:1), maxDistance)) { + //prevent self_damage + if(c==beam.sourceI && c==curUser.pos) continue; + + Char ch; + if ((ch = Actor.findChar( c )) != null) { + + chars.add( ch ); + } + + if (Dungeon.level.flamable[c]) { + Dungeon.level.destroy( c ); + GameScene.updateMap( c ); + + } + + CellEmitter.center( c ).burst( PurpleParticle.BURST, Random.IntRange( 1, 2 ) ); + + } + Dungeon.observe(); + + boolean alive = curUser.isAlive(); + + int lvl = level; + for (Char ch : chars) { + wandProc(ch, chargesPerCast()); + int damage = Math.round(2*reflectionDamageFactor(reflection)); + if(!ch.equals(curUser)) { + ch.damage(damage, this); + }else{ + ch.damage(damage/6, this); + } + ch.sprite.centerEmitter().burst( PurpleParticle.BURST, Random.IntRange( 1, 2 ) ); + ch.sprite.flash(); + } + + if (!curUser.isAlive() && alive) { + Dungeon.fail( getClass() ); + GLog.n(Messages.get(this, "ondeath")); + } + } + + protected int chargesPerCast() { + return 2; + } + + protected void wandProc(Char target, int chargesUsed){ + wandProc(target, buffedLvl(), chargesUsed); + } + + //TODO Consider externalizing char awareness buff + protected static void wandProc(Char target, int wandLevel, int chargesUsed){ + if (Dungeon.hero.hasTalent(Talent.ARCANE_VISION)) { + int dur = 5 + 5*Dungeon.hero.pointsInTalent(Talent.ARCANE_VISION); + Buff.append(Dungeon.hero, TalismanOfForesight.CharAwareness.class, dur).charID = target.id(); + } + + if (target != Dungeon.hero && + Dungeon.hero.subClass == HeroSubClass.WARLOCK && + //standard 1 - 0.92^x chance, plus 7%. Starts at 15% + Random.Float() > (Math.pow(0.92f, (wandLevel*chargesUsed)+1) - 0.07f)){ + SoulMark.prolong(target, SoulMark.class, SoulMark.DURATION + wandLevel); + } + } + + private static final float MINOR_DEBUFF_WEAKEN = 1/4f; + private static final HashMap, Float> MINOR_DEBUFFS = new HashMap<>(); + static{ + MINOR_DEBUFFS.put(Weakness.class, 2f); + MINOR_DEBUFFS.put(Corruption.class, 2f); + MINOR_DEBUFFS.put(Cripple.class, 1f); + MINOR_DEBUFFS.put(Blindness.class, 1f); + MINOR_DEBUFFS.put(Terror.class, 1f); + + MINOR_DEBUFFS.put(Chill.class, 0f); + MINOR_DEBUFFS.put(Ooze.class, 0f); + MINOR_DEBUFFS.put(Corruption.class, 4f); + MINOR_DEBUFFS.put(Vertigo.class, 0f); + MINOR_DEBUFFS.put(Drowsy.class, 0f); + MINOR_DEBUFFS.put(Corruption.class, 10f); + MINOR_DEBUFFS.put(Burning.class, 0f); + MINOR_DEBUFFS.put(Poison.class, 0f); + } + + private static final float MAJOR_DEBUFF_WEAKEN = 1/2f; + private static final HashMap, Float> MAJOR_DEBUFFS = new HashMap<>(); + static{ + MAJOR_DEBUFFS.put(Corruption.class, 3f); + MAJOR_DEBUFFS.put(Slow.class, 2f); + MAJOR_DEBUFFS.put(Hex.class, 2f); + MAJOR_DEBUFFS.put(Paralysis.class, 1f); + + MAJOR_DEBUFFS.put(Corruption.class, 0f); + MAJOR_DEBUFFS.put(Corruption.class, 0f); + MAJOR_DEBUFFS.put(MagicalSleep.class, 0f); + MAJOR_DEBUFFS.put(SoulMark.class, 0f); + MAJOR_DEBUFFS.put(Corrosion.class, 0f); + MAJOR_DEBUFFS.put(Frost.class, 0f); + MAJOR_DEBUFFS.put(Doom.class, 0f); + } + + + + private void debuffEnemy( Mob enemy, HashMap, Float> category ){ + + //do not consider buffs which are already assigned, or that the enemy is immune to. + HashMap, Float> debuffs = new HashMap<>(category); + for (Buff existing : enemy.buffs()){ + if (debuffs.containsKey(existing.getClass())) { + debuffs.put(existing.getClass(), 0f); + } + } + for (Class toAssign : debuffs.keySet()){ + if (debuffs.get(toAssign) > 0 && enemy.isImmune(toAssign)){ + debuffs.put(toAssign, 0f); + } + } + + //all buffs with a > 0 chance are flavor buffs + Class debuffCls = (Class) Random.chances(debuffs); + + if (debuffCls != null){ + Buff.append(enemy, debuffCls, 6 + buffedLvl()*3); + } else { + //if no debuff can be applied (all are present), then go up one tier + if (category == MINOR_DEBUFFS) debuffEnemy( enemy, MAJOR_DEBUFFS); + else if (category == MAJOR_DEBUFFS) corruptEnemy( enemy ); + } + } + + + private void corruptEnemy( Mob enemy ){ + //cannot re-corrupt or doom an enemy, so give them a major debuff instead + if(enemy.buff(Corruption.class) != null || enemy.buff(Doom.class) != null){ + GLog.w( Messages.get(this, "already_corrupted") ); + return; + } + + if (!enemy.isImmune(Corruption.class)){ + Corruption.corruptionHeal(enemy); + + AllyBuff.affectAndLoot(enemy, curUser, Corruption.class); + } else { + Buff.affect(enemy, Doom.class); + } + } + + protected static ArrayList fxS = new ArrayList<>(); + protected static ArrayList fxE = new ArrayList<>(); + + public void fxs(Ballistica beam, Callback callback) { + MagicMissile.boltFromChar( curUser.sprite.parent, + MagicMissile.SHADOW, + curUser.sprite, + beam.collisionPos, + callback); + Sample.INSTANCE.play( Assets.Sounds.ZAP ); + buildBeams(beam); + int size = fxE.size(); + for(int i=0;i beams = new ArrayList<>(); + protected static ArrayList offset_1 = new ArrayList<>(); + protected void buildOffsetArray(int level){ + offset_1.clear(); + float offPerStep = 45f* (1.9f+level) / (1.9f+level*3f); + int maxDSB; + if(level>7){maxDSB = 0;} + else if(level>3){maxDSB = 0;} + else{maxDSB = 0;} + //offset_1.add(0f); + if(maxDSB>0) { + for (int i = -maxDSB; i < maxDSB + 1; ++i) { + offset_1.add(i * offPerStep / maxDSB); + } + }else{ + offset_1.add(0f); + } + + // if(level<9) return base_3; + //else return base_4; + } + + protected PointF pointToF(Point p){ + return new PointF(p.x, p.y); + } + + protected void addBeam(BallisticaReal beam){ + beams.add(beam); + fxS.add(beam.sourceF); + fxE.add(beam.collisionF); + } + + protected void buildBeams(Ballistica beam){ + fxS.clear(); + fxE.clear(); + beams.clear(); + buildOffsetArray(this.level()); + //addBeam(new Ballistica(beam.sourcePos, beam.collisionPos, Ballistica.STOP_SOLID | Ballistica.IGNORE_SOFT_SOLID)); + float angle = PointF.angle(new PointF(pointToF(Dungeon.level.cellToPoint(beam.sourcePos))), + new PointF(pointToF(Dungeon.level.cellToPoint(beam.collisionPos)))); + angle /= PointF.G2R; + if(angle<0f) angle += 360f; + //GLog.i("%f,", angle); + int scatter = offset_1.size(); + for(int i=0;i0; + boolean right = dx>0; + boolean horizontalWall = false; + boolean verticalWall = false; + + int xTile=(int)e.x; + if(e.x-(int)e.x<1e-5 && right){ + xTile-=1; + } + int yTile=(int)e.y; + if(e.y-(int)e.y<1e-5 && up){ + yTile-=1; + } + final int[] neigh = new int[]{-1, 1, -Dungeon.level.width(), Dungeon.level.width()}; + boolean[] isWall = new boolean[4]; + for(int i=0; i<4; ++i){ + isWall[i]=Dungeon.level.solid[xTile+yTile*Dungeon.level.width()+neigh[i]]; + } + if(e.x-(int)e.x<1e-5){ + verticalWall = (right && isWall[1]) || (!right && isWall[0]); + } + if(e.y-(int)e.y<1e-5){ + horizontalWall = (up && isWall[3]) || (!up && isWall[2]); + } + + if(horizontalWall != verticalWall){ + if(horizontalWall) return horizontalReflectAngle(angle); + else return verticalReflectAngle(angle); + }else if(horizontalWall && verticalWall){ + if(Math.abs(dx)= 10){ fireenergy += 0; //武器最高级 @@ -168,14 +744,16 @@ public class EndingBlade extends Weapon { String info = desc(); if (levelKnown) { - info += "\n\n" + Messages.get(MeleeWeapon.class, "stats_known", tier, augment.damageFactor(min()), augment.damageFactor(max()), STRReq()); + info += "\n\n" + Messages.get(MeleeWeapon.class, "stats_known", 4+fireenergy/100, + augment.damageFactor(min()), + augment.damageFactor(max()), STRReq()); if (STRReq() > Dungeon.hero.STR()) { info += " " + Messages.get(Weapon.class, "too_heavy"); } else if (Dungeon.hero.STR() > STRReq()){ info += " " + Messages.get(Weapon.class, "excess_str", Dungeon.hero.STR() - STRReq()); } } else { - info += "\n\n" + Messages.get(MeleeWeapon.class, "stats_unknown", tier, min(0), max(0), STRReq(0)); + info += "\n\n" + Messages.get(MeleeWeapon.class, "stats_unknown", 4+fireenergy/100, min(0), max(0), STRReq(0)); if (STRReq(0) > Dungeon.hero.STR()) { info += " " + Messages.get(MeleeWeapon.class, "probably_too_heavy"); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java index c69f413bc..848fed83e 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -110,6 +110,10 @@ public abstract class Level implements Bundlable { SECRETS } + public boolean isLevelExplored( int depth ){ + return false; + } + protected int width; protected int height; protected int length; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java index baf1aa308..a99422c53 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java @@ -29,12 +29,15 @@ import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.Statistics; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob; +import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.SacrificialFire; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.RandomBuff; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GoldenMimic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Statue; import com.shatteredpixel.shatteredpixeldungeon.items.Generator; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; @@ -43,13 +46,16 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose; import com.shatteredpixel.shatteredpixeldungeon.items.food.SmallRation; import com.shatteredpixel.shatteredpixeldungeon.items.journal.GuidePage; import com.shatteredpixel.shatteredpixeldungeon.items.keys.GoldenKey; +import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key; import com.shatteredpixel.shatteredpixeldungeon.journal.Document; +import com.shatteredpixel.shatteredpixeldungeon.journal.Notes; import com.shatteredpixel.shatteredpixeldungeon.levels.builders.Builder; import com.shatteredpixel.shatteredpixeldungeon.levels.builders.FigureEightBuilder; import com.shatteredpixel.shatteredpixeldungeon.levels.builders.LoopBuilder; import com.shatteredpixel.shatteredpixeldungeon.levels.painters.Painter; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.secret.SecretRoom; +import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.MagicalFireRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.NxhyShopRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.NyzBombAndBooksRoom; import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.PitRoom; @@ -83,6 +89,57 @@ public abstract class RegularLevel extends Level { protected Room roomEntrance; protected Room roomExit; + + @Override + public boolean isLevelExplored( int depth ) { + //A level is considered fully explored if: + + //There are no levelgen heaps which are undiscovered, in an openable container, or which contain keys + for (Heap h : heaps.valueList()){ + if (h.autoExplored) continue; + + if (!h.seen || (h.type != Heap.Type.HEAP && h.type != Heap.Type.FOR_SALE && h.type != Heap.Type.CRYSTAL_CHEST)){ + return false; + } + for (Item i : h.items){ + if (i instanceof Key){ + return false; + } + } + } + + //There is no magical fire or sacrificial fire + for (Blob b : blobs.values()){ + if (b.volume > 0 && (b instanceof MagicalFireRoom.EternalFire || b instanceof SacrificialFire)){ + return false; + } + } + + //There are no statues or mimics (unless they were made allies) + for (Mob m : mobs.toArray(new Mob[0])){ + if (m.alignment != Char.Alignment.ALLY && (m instanceof Statue || m instanceof Mimic)){ + return false; + } + } + + //There are no barricades, locked doors, or hidden doors + for (int i = 0; i < length; i++){ + if (map[i] == Terrain.BARRICADE || map[i] == Terrain.LOCKED_DOOR || map[i] == Terrain.SECRET_DOOR){ + return false; + } + } + + //There are no unused keys for this depth in the journal + for (Notes.KeyRecord rec : Notes.getRecords(Notes.KeyRecord.class)){ + if (rec.depth() == depth){ + return false; + } + } + + //Note that it is NOT required for the player to see every tile or discover every trap. + return true; + } + @Override protected boolean build() { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/BallisticaReal.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/BallisticaReal.java new file mode 100644 index 000000000..23c9c18ac --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/mechanics/BallisticaReal.java @@ -0,0 +1,329 @@ +package com.shatteredpixel.shatteredpixeldungeon.mechanics; + +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap; +import com.watabou.utils.Point; +import com.watabou.utils.PointF; + +import java.util.ArrayList; +import java.util.List; + +//REAL ballistica means works like real; +//Normally ballistica in game can go through corners when the beam go through a wall +//but the tile of wall is not built in trace. (Because there is only one tile for each tile in main direction) +//However, REAL ballistica can't go through corners. +//It considers all the points the beam cross with each x and y integer +//So complex actions like reflection can work correctly. +//Walls, target cells, chars are considered as a whole tile. Even 0.001 tile collision would stop. +//Yeah it would be quite odd when this happens, but it works for now. +//Integer pos is considered at the center of tile. +public class BallisticaReal { + //Why here we do NOT record FULL path? + //first, introduction of range makes real collision quite messy if we add end point to path. + //second, real beam collides with boundary, which means it has already considered the next cell. + //if we want to control the full path, just build a never-stop one and go by tiles. + //third, it would cost much more if go through full path. There are cases when it updates each flash. + public ArrayList pathI = new ArrayList<>(); + public Integer sourceI = null; + public Integer collisionI = null; + public Integer dist = 0; + + public ArrayList pathF = new ArrayList<>(); + public PointF sourceF = null; + public PointF collisionF = null; + + public static final int STOP_TARGET = 1; //ballistica will stop at the target cell + public static final int STOP_CHARS = 2; //ballistica will stop on first char hit + public static final int STOP_SOLID = 4; //ballistica will stop on solid terrain + public static final int IGNORE_SOFT_SOLID = 8; //ballistica will ignore soft solid terrain, such as doors and webs + + public static final int PROJECTILE = STOP_TARGET | STOP_CHARS | STOP_SOLID; + + public static final int MAGIC_BOLT = STOP_CHARS | STOP_SOLID; + + public static final int WONT_STOP = 0; + + + + public BallisticaReal(PointF from, PointF to, int params) { + //sourcepos need to offset a bit to judge cases the from is on the line + //for example, from = "30, 20.5", to = "20, 0.5", x>=30 is wall + //if we do not offset, the source will be 30, 20, and when it collides with y=20, + //the wall judgement is centered at (30, 20), not (29, 20). First judgement will look at (30, 19) instead of (29,19) + //When it collides, it regards the upper ceil, wall, not empty. And the ballistica will end here. + + //if the point is on the edge of cell, give it a tiny offset. + if(from.x-(int)from.x==0f || from.y-(int)from.y==0f) { + sourceI = pointToCell(pointFloatToInt(from.clone().offset(to.x - from.x > 0 ? 0.001f : -0.001f, to.y - from.y > 0 ? 0.001f : -0.001f), false)); + }else{ + sourceI = pointToCell(pointFloatToInt(from.clone(), false)); + } + sourceF = from; + buildTrace(sourceF, to, + (params & STOP_TARGET) > 0, + (params & STOP_CHARS) > 0, + (params & STOP_SOLID) > 0, + (params & IGNORE_SOFT_SOLID) > 0); + + if (collisionI != null) { + dist = pathI.indexOf(collisionI); + } else if (!pathI.isEmpty()) { + collisionI = pathI.get(dist = pathI.size() - 1); + collisionF = pathF.get(pathF.size()-1); + } else { + dist = 0; + } + } + + public BallisticaReal(int from, int to, int params){ + this(pointIntToFloat(cellToPoint(from)).offset(0.5f, 0.45f), + pointIntToFloat(cellToPoint(to)).offset(0.5f, 0.45f), + params); + } + + public BallisticaReal(PointF from, float angle, float range, int params){ + this(from, new PointF(from.x, from.y).offset(poleToPointF(angle, range)), params); + } + + public BallisticaReal(int from, float angle, float range, int params){ + this(pointIntToFloat(cellToPoint(from)).offset(0.5f, 0.45f),angle,range,params); + } + + private void buildTrace(PointF from, PointF to, boolean stopTarget, boolean stopChars, boolean stopTerrain, boolean ignoreSoftSolid){ + PointF vector = new PointF(to.x - from.x, to.y - from.y); + + float dx; + float dy; + float movX = Math.abs(vector.x); + float movY = Math.abs(vector.y); + //too short move ,return + if(movX < 0.001f && movY < 0.001f){ + int end = pointToCell(pointFloatToInt(from, false)); + pathI.add(end); + pathF.add(from); + cld(end); + cldF(from); + return; + } + //actually it is abandoned + if(movX>movY){ + dx = (vector.x>0?1f:-1f); + dy = movY/movX*(vector.y>0?1f:-1f); + }else{ + dy = (vector.y>0?1f:-1f); + dx = movX/movY*(vector.x>0?1f:-1f); + } + //which direction? + boolean up = dy>0; + boolean right = dx>0; + boolean vertical = Math.abs(dx)<0.001f; + boolean horizontal = Math.abs(dy)<0.001f; + //record current point + PointF curPosF = new PointF(from.x, from.y); + Point curPos = cellToPoint(sourceI);//pointFloatToInt(from, false); + //we shot into one tile, and meet one border. passable caches whether beam can pass this border. + boolean[] canPass = new boolean[4]; + final int[] neigh = new int[]{-1, 1, Dungeon.level.width(), -Dungeon.level.width()}; + + pathI.add(sourceI); + pathF.add(sourceF); + /* + if (stopTerrain && Dungeon.level.solid[sourceI]) { + if (ignoreSoftSolid && (Dungeon.level.passable[sourceI] || Dungeon.level.avoid[sourceI])) { + //do nothing + } else { + cld(sourceI); + cldF(sourceF); + return; + } + } else if (stopChars && Actor.findChar( sourceI ) != null) { + cld(sourceI); + cldF(sourceF); + return; + } else if (sourceF.equals(pointFloatToInt(to, false)) && stopTarget){ + cld(sourceI); + cldF(sourceF); + return; + } + */ + + while(isInsideMap(curPosF)){ + int cell= pointToCell(curPos); + boolean passable; + //build passable + for(int i=0;i<4;++i){ + int target = cell + neigh[i]; + passable = true; + if (stopTerrain && Dungeon.level.solid[target]) { + if (ignoreSoftSolid && (Dungeon.level.passable[target] || Dungeon.level.avoid[target])) { + //do nothing + } else { + passable = false; + } + } else if (stopChars && Actor.findChar( target ) != null) { + passable = false; + } else if (curPos.equals(pointFloatToInt(to, false)) && stopTarget){ + passable = false; + } + + canPass[i]=passable; + } + //will meet which border + boolean xOnLine = curPosF.x-(int)curPosF.x==0; + boolean yOnLine = curPosF.y-(int)curPosF.y==0; + float tx = (float) (xOnLine?(Math.floor(curPosF.x) + (right?1f:-1f)):(Math.floor(curPosF.x) + (right?1f:0f))); + float ty = (float) (yOnLine?(Math.floor(curPosF.y) + (up?1f:-1f)):(Math.floor(curPosF.y) + (up?1f:0f))); + //meet x border, y border, or both? And where? + PointF nx, ny; + if(vertical){ + ny = new PointF(curPosF.x, ty); + nx = null; + }else if(horizontal){ + nx = new PointF(tx, curPosF.y); + ny = null; + }else{ + nx = new PointF(tx, (tx-curPosF.x)*dy/dx+curPosF.y); + ny = new PointF((ty-curPosF.y)*dx/dy+curPosF.x, ty); + + if(nx.y==ny.y){ + //nx==ny means reaching xy cross + //do nothing + }else if((up && nx.y>ny.y) || (!up && nx.y=movX) || + (ny != null && Math.abs(ny.y - from.y)>=movY)) + { + cldF(to); + cld(pointToCell(pointFloatToInt(to, false))); + pathI.add(collisionI); + pathF.add(collisionF); + return; + } + } + + //next cell, next pointF. Handle xy logic first. + int nextCell=cell; + if(ny==null){ + nextCell += dx>0? 1:-1; + }else if(nx==null){ + nextCell += dy>0?Dungeon.level.width():-Dungeon.level.width(); + }else{ + //if reach the xy cross, judge x and y + nextCell += dx>0? 1:-1; + for(int i=0; i<4; ++i){ + if(neigh[i]+cell!=nextCell) continue; + if(!canPass[i]){ + cld(nextCell+dy>0?Dungeon.level.width():-Dungeon.level.width()); + cldF(nx); + return; + } + } + nextCell += dy>0?Dungeon.level.width():-Dungeon.level.width(); + for(int i=0; i<4; ++i){ + if(neigh[i]+cell!=nextCell) continue; + if(!canPass[i]){ + cld(nextCell); + cldF(ny); + return; + } + } + //if x and y let it pass, the go on + } + + //judge whether can pass, and collide. For x or y logic + pathI.add(nextCell); + pathF.add(ny==null?nx.clone():ny.clone()); + //has judged xy cross + if(nx == null || ny == null) { + for (int i = 0; i < 4; ++i) { + if (neigh[i] + cell != nextCell) continue; + if (!canPass[i]) { + cld(nextCell); + cldF(ny == null ? nx : ny); + return; + } + } + } + //go on one point, and continue + curPosF=(ny==null?nx.clone():ny.clone()); + curPos=cellToPoint(nextCell); + + } + + } + + private void cld(int cell){ + if (collisionI == null) + collisionI = cell; + } + + //using with new pointF + private void cldF(PointF cell){ + if (collisionF == null) { + collisionF = cell.clone(); + } + } + + public List subPath(int start, int end){ + try { + end = Math.min( end, pathI.size()-1); + return pathI.subList(start, end+1); + } catch (Exception e){ + ShatteredPixelDungeon.reportException(e); + return new ArrayList<>(); + } + } + + public static PointF pointToScreen(PointF p){ + return p.clone().scale(DungeonTilemap.SIZE); + } + + public static PointF raisedPointToScreen(PointF p){ + return p.clone().scale(DungeonTilemap.SIZE); + } + + + + private static final float A2P = 0.0174533f; + + //We assume up is positive y but in game it is opposite. + private static PointF poleToPointF(float angle, float range){ + return new PointF((float)(range*Math.cos(angle*A2P)), (float)(range*Math.sin(angle*A2P))); + } + + private static boolean isInsideMap(PointF posF){ + return !(posF.x<1f || posF.x> Dungeon.level.width()-1f || posF.y<1f || posF.y>Dungeon.level.height()-1f); + } + + private static Point cellToPoint(int cell ){ + return new Point(cell % Dungeon.level.width(), cell / Dungeon.level.width()); + } + + private static int pointToCell( Point p ){ + return p.x + p.y*Dungeon.level.width(); + } + + private static Point pointFloatToInt(PointF p, boolean round){ + if(round){ + return new Point(Math.round(p.x), Math.round(p.y)); + } + return new Point((int)p.x, (int)p.y); + } + + private static PointF pointIntToFloat(Point p){ + return new PointF(p.x, p.y); + } + + public static PointF coordToScreen(PointF p){ + return p.clone().offset(0.5f, 0.5f).scale(DungeonTilemap.SIZE); + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextBlock.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextBlock.java index fe867275c..0fd813b9b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextBlock.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextBlock.java @@ -337,4 +337,7 @@ public class RenderedTextBlock extends Component { } } } + + protected void hardlight(float r, float g, float b) { + } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java index c552a938a..ddddf32e0 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndHero.java @@ -21,11 +21,15 @@ package com.shatteredpixel.shatteredpixeldungeon.windows; +import static com.shatteredpixel.shatteredpixeldungeon.SPDSettings.HelpSettings; + import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.shatteredpixel.shatteredpixeldungeon.Statistics; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Hunger; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; +import com.shatteredpixel.shatteredpixeldungeon.custom.ch.GameTracker; import com.shatteredpixel.shatteredpixeldungeon.custom.messages.M; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; @@ -36,6 +40,7 @@ import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIcon; import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; import com.shatteredpixel.shatteredpixeldungeon.ui.IconButton; import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; +import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextBlock; import com.shatteredpixel.shatteredpixeldungeon.ui.ScrollPane; import com.shatteredpixel.shatteredpixeldungeon.ui.StatusPane; @@ -50,12 +55,13 @@ import com.watabou.noosa.ui.Component; import java.util.ArrayList; import java.util.Locale; +import java.util.regex.Pattern; public class WndHero extends WndTabbed { - + private static final int WIDTH = 130; private static final int HEIGHT = 120; - + private StatsTab stats; private TalentsTab talents; private BuffsTab buffs; @@ -63,11 +69,11 @@ public class WndHero extends WndTabbed { public static int lastIdx = 0; public WndHero() { - + super(); - + resize( WIDTH, HEIGHT ); - + stats = new StatsTab(); add( stats ); @@ -79,7 +85,7 @@ public class WndHero extends WndTabbed { add( buffs ); buffs.setRect(0, 0, WIDTH, HEIGHT); buffs.setupList(); - + add( new IconTab( Icons.get(Icons.RANKINGS) ) { protected void select( boolean value ) { super.select( value ); @@ -125,11 +131,11 @@ public class WndHero extends WndTabbed { } private class StatsTab extends Group { - - private static final int GAP = 6; - + + private static final int GAP = 4; + private float pos; - + public StatsTab() { initialize(); } @@ -140,18 +146,15 @@ public class WndHero extends WndTabbed { if (g != null) g.destroy(); } clear(); - + Hero hero = Dungeon.hero; IconTitle title = new IconTitle(); title.icon( HeroSprite.avatar(hero.heroClass, hero.tier()) ); - if (hero.name().equals(hero.className())) { - title.label(Messages.get(this, "title", Integer.valueOf(hero.lvl), hero.className()).toUpperCase(Locale.ENGLISH)); - } else { - title.label((hero.name() + "\n" + Messages.get(this, "title", Integer.valueOf(hero.lvl), - hero.className())).toUpperCase(Locale.ENGLISH)); - } - + if (hero.name().equals(hero.className())) + title.label( Messages.get(this, "title", hero.lvl, hero.className() ).toUpperCase( Locale.ENGLISH ) ); + else + title.label((hero.name() + "\n" + Messages.get(this, "title", hero.lvl, hero.className())).toUpperCase(Locale.ENGLISH)); title.color(Window.TITLE_COLOR); title.setRect( 0, 0, WIDTH-16, 0 ); add(title); @@ -176,7 +179,7 @@ public class WndHero extends WndTabbed { infoButton.setRect(title.right(), 0, 16, 16); add(infoButton); - pos = title.bottom() + 2*GAP; + pos = title.bottom() + GAP; int strBonus = hero.STR() - hero.STR; if (strBonus > 0) statSlot( Messages.get(this, "str"), hero.STR + " + " + strBonus ); @@ -190,33 +193,119 @@ public class WndHero extends WndTabbed { statSlot( Messages.get(this, "gold"), Statistics.goldCollected ); statSlot( Messages.get(this, "depth"), Statistics.deepestFloor ); - - statSlot( M.L(HeroStat.class,"seed_dungeon"), DungeonSeed.convertToCode(Dungeon.seed).toUpperCase()); + statSlot( Messages.get(HeroStat.class, "seed_dungeon"), DungeonSeed.convertToCode(Dungeon.seed) ); pos += GAP; + + Hunger hunger = Dungeon.hero.buff(Hunger.class); + String hunger_str = "null"; + if(hunger != null){ + hunger_str = hunger.hunger() + "/" + Hunger.STARVING; + } + statSlot( M.L(HeroStat.class, "hunger"), hunger_str); + + pos += GAP; + + RedButton buttonItem = new RedButton(M.L(HeroStat.class, "item_enter"), 8){ + @Override + protected void onClick() { + super.onClick(); + GameScene.show(new StatsTab.WndTreasureGenerated()); + } + }; + add(buttonItem); + buttonItem.setRect(2, pos, WIDTH - 4, 16); + if(HelpSettings()){ + buttonItem.active = true; + } else { + buttonItem.active = false; + buttonItem.visible = false; + } + } private void statSlot( String label, String value ) { - - RenderedTextBlock txt = PixelScene.renderTextBlock( label, 8 ); + + RenderedTextBlock txt = PixelScene.renderTextBlock( label, 7 ); txt.setPos(0, pos); add( txt ); - - txt = PixelScene.renderTextBlock( value, 8 ); - txt.setPos(WIDTH * 0.6f, pos); + + txt = PixelScene.renderTextBlock( value, 7 ); + txt.setPos(WIDTH * 0.5f, pos); PixelScene.align(txt); add( txt ); - + pos += GAP + txt.height(); } - + private void statSlot( String label, int value ) { statSlot( label, Integer.toString( value ) ); } - + public float height() { return pos; } + + private class WndTreasureGenerated extends Window{ + private static final int WIDTH = 120; + private static final int HEIGHT = 144; + + public WndTreasureGenerated(){ + super(); + resize(WIDTH, HEIGHT); + ScrollPane pane = new ScrollPane(new Component()); + Component content = pane.content(); + this.add(pane); + pane.setRect(0,0,WIDTH, HEIGHT); + + GameTracker gmt = Dungeon.hero.buff(GameTracker.class); + if(gmt != null){ + String allInfo = gmt.itemInfo(); + String[] result = allInfo.split("\n"); + float pos = 2; + for(String info: result){ + if(info.contains("dungeon_depth")){ + pos += 4; + RenderedTextBlock depthText = PixelScene.renderTextBlock(info.replace("dungeon_depth: ", M.L(HeroStat.class, "item_wnd_depth")), 8); + depthText.maxWidth(WIDTH); + depthText.hardlight(0xFFFF00); + content.add(depthText); + depthText.setPos(0, pos); + pos += 8; + }else{ + pos += 1; + info = info.replace("MIMIC_HOLD", M.L(HeroStat.class, "item_wnd_mimic")); + info = info.replace("QUEST_REWARD", M.L(HeroStat.class, "item_wnd_reward")); + info = info.replace("CURSED", M.L(HeroStat.class, "item_wnd_cursed")); + RenderedTextBlock itemText = PixelScene.renderTextBlock(info, 6); + itemText.maxWidth(WIDTH); + content.add(itemText); + itemText.setPos(0, pos); + pos += 6; + String level = Pattern.compile("[^0-9]").matcher(info).replaceAll("").trim(); + try{ + int lvl = Integer.parseInt(level); + if(lvl == 1){ + itemText.hardlight(0x57FAFF); + }else if(lvl == 2){ + itemText.hardlight(0xA000A0); + }else if(lvl == 3){ + itemText.hardlight(0xFFB700); + }else if(lvl >= 4) { + itemText.hardlight(Window.Pink_COLOR); + } + }catch (Exception e){ + + } + + + } + } + content.setSize(WIDTH, pos + 2); + } + pane.scrollTo(0, 0); + } + } } public class TalentsTab extends Component { diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndSettings.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndSettings.java index 9ba4a7010..e34bbbff0 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndSettings.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndSettings.java @@ -32,6 +32,7 @@ import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; import com.shatteredpixel.shatteredpixeldungeon.services.news.News; import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite; +import com.shatteredpixel.shatteredpixeldungeon.sprites.CrossDiedSprites; import com.shatteredpixel.shatteredpixeldungeon.ui.CheckBox; import com.shatteredpixel.shatteredpixeldungeon.ui.GameLog; import com.shatteredpixel.shatteredpixeldungeon.ui.Icons; @@ -66,6 +67,7 @@ public class WndSettings extends WndTabbed { private AudioTab audio; private LangsTab langs; private ExtendTab extabs; + private HelpTab helps; public static int last_index = 0; @@ -176,6 +178,20 @@ public class WndSettings extends WndTabbed { } }); + helps = new HelpTab(); + helps.setSize(width, 0); + height = Math.max(height, audio.height()); + add( helps ); + + add( new IconTab(new CrossDiedSprites()){ + @Override + protected void select(boolean value) { + super.select(value); + helps.visible = helps.active = value; + if (value) last_index = 6; + } + }); + resize(width, (int)Math.ceil(height)); layoutTabs(); @@ -663,6 +679,54 @@ public class WndSettings extends WndTabbed { } + private static class HelpTab extends Component { + + RenderedTextBlock title; + ColorBlock sep1; + CheckBox LockFing; + + @Override + protected void createChildren() { + title = PixelScene.renderTextBlock(Messages.get(this, "title"), 9); + title.hardlight(TITLE_COLOR); + add(title); + + sep1 = new ColorBlock(1, 1, 0xFF000000); + add(sep1); + + LockFing = new CheckBox( Messages.get(this, "helpsettings") ) { + @Override + protected void onClick() { + super.onClick(); + SPDSettings.HelpSettings(checked()); + } + }; + LockFing.checked(SPDSettings.HelpSettings()); + add(LockFing); + } + + @Override + protected void layout() { + + float bottom = y; + + title.setPos((width - title.width())/2, bottom + GAP); + sep1.size(width, 1); + sep1.y = title.bottom() + 2*GAP; + + bottom = sep1.y + 1; + + if (width > 200){ + LockFing.setRect(0, bottom, width, SLIDER_HEIGHT); + } else { + LockFing.setRect(0, bottom + GAP, width, SLIDER_HEIGHT); + } + + height = LockFing.bottom(); + } + + } + private static class DataTab extends Component{ RenderedTextBlock title;