diff --git a/core/src/main/assets/interfaces/badges.png b/core/src/main/assets/interfaces/badges.png index ce25af09c..0b9410bf7 100644 Binary files a/core/src/main/assets/interfaces/badges.png and b/core/src/main/assets/interfaces/badges.png differ diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 588501069..602f2e0af 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -1,3 +1,11 @@ +###SPD1.3 +text.herostat.item_wnd_depth = 层数: +text.herostat.item_wnd_mimic = 宝箱怪: +text.herostat.item_wnd_reward = 任务: +text.herostat.item_wnd_cursed = 诅咒的 +text.herostat.item_basic = 常规: +text.herostat.item_enter = 查看物品生成 + ###MLPD actors.mobs.bosses.crossdiedtower.dead=控制目标已丢失,已自动摧毁…… actors.mobs.bosses.crossdiedtower.name=激光十字晶柱 diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index d654f7f56..9edccabcb 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -6,10 +6,6 @@ items.bombs.laserpython.name=激光十字晶柱召唤器 items.bombs.laserpython.desc=这枚召唤器会在爆炸后立刻生成一个十字晶柱。 items.bombs.laserpython.didnot_pick=你无法拾取该物品…… -items.weapon.melee.endingblade.ac_lastcrystal=Γ激光晶柱Γ -items.weapon.melee.endingblade.ac_diedghost=✦死亡宣告✦ -items.weapon.melee.endingblade.ac_healreset=_亡者归来_ - items.artifacts.wraithamulet.name=暗金宝石护符 items.artifacts.wraithamulet.desc=来自于异世界的产物,能使自己进入虚无化。当护符能量足够多的时候,你还可以对敌人实行一次暗夜袭击,护符能量将会随着时间自动恢复。\n\n这个护符是证明给强者之人用的,冰雪魔女已经输的心服口服,现在她将此护身符给予给你。 items.artifacts.wraithamulet.ac_ghost=遁入虚无 @@ -95,9 +91,14 @@ items.quest.skeletongold.wow=染血金币发出诡异的光芒,你感到下层 items.weapon.melee.endingblade.name=终焉 items.weapon.melee.endingblade.desc=不知道从哪来的一个拆开的场记板,貌似沾染了焰之诅咒,会随机产生_一种诅咒_,有着强大的侵蚀能力,一旦装备了它就无法脱身了……\n\n这个武器在攻击敌人的时候能吸收一定的浊焰能量,在汲取一定的浊焰能量后会产生新的能力以及自我升级。\n\n当前的浊焰能量: +items.weapon.melee.endingblade.desc_2=\n\n死亡宣告当前冷却值为: items.weapon.melee.endingblade.cursed=当你装备上这个武器后,一股无形的力量将你束缚住…… items.weapon.melee.endingblade.donot_eqip=终焉的诅咒已经浸染你的身体,你无力脱下它。 +items.weapon.melee.endingblade.ac_lastcrystal=Γ激光晶柱Γ +items.weapon.melee.endingblade.ac_diedghost=✦死亡宣告✦ +items.weapon.melee.endingblade.ac_healreset=_亡者归来_ + #MLPDSTOREYBOOKS items.books.bookslist.hellfirebooks.name=《浊焰事件》 diff --git a/core/src/main/assets/messages/misc/misc.properties b/core/src/main/assets/messages/misc/misc.properties index b7163a1d8..ae55723ca 100644 --- a/core/src/main/assets/messages/misc/misc.properties +++ b/core/src/main/assets/messages/misc/misc.properties @@ -67,6 +67,10 @@ badges$badge.games_played_3=进行250场游戏 badges$badge.games_played_4=进行1000场游戏 badges$badge.godd_make=老人与海\n累计完成老杖匠的全部任务\n\n_奖励:0层随机戒指(四大基座上)_ badges$badge.clear_water=净化大师\n完成挑战:污泥浊水\n\n_奖励:敬请期待_ + +badges$badge.ghostdage=幽灵大哥\n_在幽灵处获得一次+4品质武器或护甲\n\n_(镀层需求:+5品质武器) +//badges$badge.ghostdage=幽灵大哥\n在幽灵处获得一次_+5_品质武器或护甲\n\nΞ你已成功镀层Ξ + badges$badge.halofire_died=死于磷火烈焰 badges$badge.happy_end=幸福结局 badges$badge.champion_1x=开启1项挑战通关 diff --git a/core/src/main/assets/messages/windows/windows.properties b/core/src/main/assets/messages/windows/windows.properties index eae5f83af..6de9bc51f 100644 --- a/core/src/main/assets/messages/windows/windows.properties +++ b/core/src/main/assets/messages/windows/windows.properties @@ -2,6 +2,9 @@ windows.textchallenges.seed_custom_title = 种子 windows.textchallenges.hint = 不输入即为随机种子 windows.textchallenges.delete_seed_input = 清除 +windows.wndsettings$helptab.title=辅助功能 +windows.wndsettings$helptab.helpsettings=启用物品生成查询器 + windows.wndkingshop.buy=购买 windows.wndkingshop.cancel=取消 windows.wndkingshop.king=商人领主 diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java index 54a63992c..a450f7a47 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Badges.java @@ -109,6 +109,7 @@ public class Badges { GAMES_PLAYED_1 ( 54, true ), GODD_MAKE ( 82 ), CLEAR_WATER ( 83 ), + GHOSTDAGE ( 84 ), //gold PIRANHAS ( 64 ), //these names are a bit outdated, but it doesn't really matter. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index 7b098f326..7bcaa6f0b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -686,6 +686,9 @@ public class Dungeon { hero.curAction = hero.lastAction = null; + //SPD + LevelSwitchListener.onLevelSwitch(); + observe(); try { saveAll(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/LevelSwitchListener.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/LevelSwitchListener.java new file mode 100644 index 000000000..4326341ea --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/LevelSwitchListener.java @@ -0,0 +1,12 @@ +package com.shatteredpixel.shatteredpixeldungeon; + +import com.shatteredpixel.shatteredpixeldungeon.custom.ch.GameTracker; + +public class LevelSwitchListener { + public static void onLevelSwitch(){ + GameTracker gmt = Dungeon.hero.buff(GameTracker.class); + if(gmt != null){ + gmt.onNewLevel(); + } + } +} diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java index 8aaffae37..2bd55a91a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java @@ -109,9 +109,7 @@ public class SPDSettings extends GameSettings { private static final String KEY_PAGE = "page_ui"; - private static final String KEY_PCUI = "pc_ui"; - - private static final String KEY_SWAP = "quickswap"; + private static final String HelpSettings = "helpsettings"; public static void fullscreen( boolean value ) { put( KEY_FULLSCREEN, value ); @@ -447,6 +445,14 @@ public class SPDSettings extends GameSettings { return getBoolean(KEY_DARK, false); } + public static void HelpSettings(boolean value) { + put( HelpSettings, value ); + } + + public static boolean HelpSettings() { + return getBoolean(HelpSettings, false); + } + public static boolean ClassSkin() { return getBoolean(KEY_SKIN, false); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java index 054ed1750..45f27987d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Statistics.java @@ -22,6 +22,7 @@ package com.shatteredpixel.shatteredpixeldungeon; import com.watabou.utils.Bundle; +import com.watabou.utils.SparseArray; public class Statistics { public static int realdeepestFloor; @@ -53,6 +54,15 @@ public class Statistics { public static boolean fireGirlnoshopping = false; public static boolean deadshoppingdied = false; + + //Directly add float time will cause accuracy lose and stop timing if time is long enough + //so use long to record seconds, float to count sub-seconds. + //SPD-V1.3.2-ITEM SPAWN CODE + public static long real_seconds = 0; + public static float second_elapsed = 0; + public static float turnsPassed = 0f; + + public static SparseArray 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;