diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index 54b11d81a..def777c40 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -373,14 +373,14 @@ items.artifacts.sandalsofnature.desc_seeds=You have fed the footwear %d seeds. items.artifacts.talismanofforesight.name=talisman of foresight items.artifacts.talismanofforesight.ac_scry=SCRY -items.artifacts.talismanofforesight.no_charge=Your talisman isn't fully charged yet. -items.artifacts.talismanofforesight.scry=The Talisman floods your mind with knowledge about the current floor. -items.artifacts.talismanofforesight.desc=A smooth stone with strange engravings on it. You feel like it's watching everything around you, keeping an eye out for anything unusual. -items.artifacts.talismanofforesight.desc_worn=When you hold the talisman you feel like your senses are heightened. -items.artifacts.talismanofforesight.desc_cursed=The cursed talisman is intently staring into you, making it impossible to concentrate. +items.artifacts.talismanofforesight.low_charge=The talisman requires at least 5% charge to scry. +items.artifacts.talismanofforesight.prompt=Choose a location to scan. +items.artifacts.talismanofforesight.levelup=Your Talisman grows stronger! +items.artifacts.talismanofforesight.full_charge=Your Talisman is fully charged! +items.artifacts.talismanofforesight.desc=A smooth stone with strange engravings on it. You feel like it's watching everything around you, keeping an eye out for anything hidden. +items.artifacts.talismanofforesight.desc_worn=When you hold the talisman you feel like your senses are heightened. The talisman is slowly building charge as time passes and when you discover hidden doors or traps.\n\nThe talisman can expend its charge to 'scry' in a cone shape, which reveals all tiles and secrets in the scanned area, and will grant you vision of enemies and items for a short time. +items.artifacts.talismanofforesight.desc_cursed=The cursed talisman is intently staring into you, dulling your senses. items.artifacts.talismanofforesight$foresight.name=Foresight -items.artifacts.talismanofforesight$foresight.levelup=Your Talisman grows stronger! -items.artifacts.talismanofforesight$foresight.full_charge=Your Talisman is fully charged! items.artifacts.talismanofforesight$foresight.uneasy=You feel uneasy. items.artifacts.talismanofforesight$foresight.desc=You feel very nervous, as if there is nearby unseen danger. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index c9ac5f3cd..781fef8ae 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -37,6 +37,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Ankh; import com.shatteredpixel.shatteredpixeldungeon.items.Generator; import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight; import com.shatteredpixel.shatteredpixeldungeon.items.potions.Potion; import com.shatteredpixel.shatteredpixeldungeon.items.rings.Ring; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.Scroll; @@ -799,6 +800,22 @@ public class Dungeon { } } + for (TalismanOfForesight.CharAwareness c : hero.buffs(TalismanOfForesight.CharAwareness.class)){ + Char ch = (Char) Actor.findById(c.charID); + if (ch == null) continue; + BArray.or( level.visited, level.heroFOV, ch.pos - 1 - level.width(), 3, level.visited ); + BArray.or( level.visited, level.heroFOV, ch.pos - 1, 3, level.visited ); + BArray.or( level.visited, level.heroFOV, ch.pos - 1 + level.width(), 3, level.visited ); + GameScene.updateFog(ch.pos, 2); + } + + for (TalismanOfForesight.HeapAwareness h : hero.buffs(TalismanOfForesight.HeapAwareness.class)){ + BArray.or( level.visited, level.heroFOV, h.pos - 1 - level.width(), 3, level.visited ); + BArray.or( level.visited, level.heroFOV, h.pos - 1, 3, level.visited ); + BArray.or( level.visited, level.heroFOV, h.pos - 1 + level.width(), 3, level.visited ); + GameScene.updateFog(h.pos, 2); + } + GameScene.afterObserve(); } 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 8f885bbd2..56d7c7cb2 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 @@ -1764,8 +1764,13 @@ public class Hero extends Char { smthFound = true; - if (talisman != null && !talisman.isCursed()) - talisman.charge(); + if (talisman != null){ + if (oldValue == Terrain.SECRET_TRAP){ + talisman.charge(2); + } else if (oldValue == Terrain.SECRET_DOOR){ + talisman.charge(10); + } + } } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/TalismanOfForesight.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/TalismanOfForesight.java index 487cdac40..7e4fadb9c 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/TalismanOfForesight.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/artifacts/TalismanOfForesight.java @@ -23,12 +23,20 @@ package com.shatteredpixel.shatteredpixeldungeon.items.artifacts; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; -import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Awareness; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; +import com.shatteredpixel.shatteredpixeldungeon.effects.CheckedCell; +import com.shatteredpixel.shatteredpixeldungeon.items.Heap; +import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfMagicMapping; import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; +import com.shatteredpixel.shatteredpixeldungeon.mechanics.ConeAOE; 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.BuffIndicator; @@ -58,8 +66,7 @@ public class TalismanOfForesight extends Artifact { @Override public ArrayList actions( Hero hero ) { ArrayList actions = super.actions( hero ); - if (isEquipped( hero ) && charge == chargeCap && !cursed) - actions.add(AC_SCRY); + if (isEquipped( hero ) && !cursed) actions.add(AC_SCRY); return actions; } @@ -68,34 +75,9 @@ public class TalismanOfForesight extends Artifact { super.execute(hero, action); if (action.equals(AC_SCRY)){ - - if (!isEquipped(hero)) GLog.i( Messages.get(Artifact.class, "need_to_equip") ); - else if (charge != chargeCap) GLog.i( Messages.get(this, "no_charge") ); - else { - hero.sprite.operate(hero.pos); - hero.busy(); - Sample.INSTANCE.play(Assets.Sounds.BEACON); - charge = 0; - for (int i = 0; i < Dungeon.level.length(); i++) { - - int terr = Dungeon.level.map[i]; - if ((Terrain.flags[terr] & Terrain.SECRET) != 0) { - - GameScene.updateMap(i); - - if (Dungeon.level.heroFOV[i]) { - GameScene.discoverTile(i, terr); - } - } - } - - GLog.p( Messages.get(this, "scry") ); - - updateQuickslot(); - - Buff.affect(hero, Awareness.class, Awareness.DURATION); - Dungeon.observe(); - } + if (!isEquipped(hero)) GLog.i( Messages.get(Artifact.class, "need_to_equip") ); + else if (charge < 5) GLog.i( Messages.get(this, "low_charge") ); + else GameScene.selectCell(scry); } } @@ -107,7 +89,7 @@ public class TalismanOfForesight extends Artifact { @Override public void charge(Hero target) { if (charge < chargeCap){ - charge += 4f; + charge += 2f; if (charge >= chargeCap) { charge = chargeCap; partialCharge = 0; @@ -131,7 +113,119 @@ public class TalismanOfForesight extends Artifact { return desc; } - + + private float maxDist(){ + return Math.min(5 + 2*level(), (charge-3)/0.88f); + } + + private CellSelector.Listener scry = new CellSelector.Listener(){ + + @Override + public void onSelect(Integer target) { + if (target != null && target != curUser.pos){ + + //enforces at least 2 tiles of distance + if (Dungeon.level.adjacent(target, curUser.pos)){ + target += (target - curUser.pos); + } + + float dist = Dungeon.level.trueDistance(curUser.pos, target); + + if (dist >= 3 && dist > maxDist()){ + Ballistica trajectory = new Ballistica(curUser.pos, target, Ballistica.STOP_TARGET); + int i = 0; + while (i < trajectory.path.size() + && Dungeon.level.trueDistance(curUser.pos, trajectory.path.get(i)) <= maxDist()){ + target = trajectory.path.get(i); + i++; + } + dist = Dungeon.level.trueDistance(curUser.pos, target); + } + + //starts at 200 degrees, loses 8% per tile of distance + float angle = Math.round(200*(float)Math.pow(0.92, dist)); + ConeAOE cone = new ConeAOE(curUser.pos, target, angle); + + int earnedExp = 0; + boolean noticed = false; + for (int cell : cone.cells){ + GameScene.effectOverFog(new CheckedCell( cell, curUser.pos )); + if (Dungeon.level.discoverable[cell] && !(Dungeon.level.mapped[cell] || Dungeon.level.visited[cell])){ + Dungeon.level.mapped[cell] = true; + earnedExp++; + } + + if (Dungeon.level.secret[cell]) { + Dungeon.level.discover(cell); + + if (Dungeon.level.heroFOV[cell]) { + int oldValue = Dungeon.level.map[cell]; + GameScene.discoverTile(cell, Dungeon.level.map[cell]); + Dungeon.level.discover( cell ); + ScrollOfMagicMapping.discover(cell); + noticed = true; + + if (oldValue == Terrain.SECRET_TRAP){ + earnedExp += 10; + } else if (oldValue == Terrain.SECRET_DOOR){ + earnedExp += 100; + } + } + } + + Char ch = Actor.findChar(cell); + if (ch != null && ch.alignment != Char.Alignment.NEUTRAL && ch.alignment != curUser.alignment){ + Buff.append(curUser, CharAwareness.class, 5 + 2*level()).charID = ch.id(); + + if (!curUser.fieldOfView[ch.pos]){ + earnedExp += 10; + } + } + + Heap h = Dungeon.level.heaps.get(cell); + if (h != null){ + Buff.append(curUser, HeapAwareness.class, 5 + 2*level()).pos = h.pos; + + if (!h.seen){ + earnedExp += 10; + } + } + + } + + exp += earnedExp; + if (exp >= 100 + 50*level() && level() < levelCap) { + exp -= 100 + 50*level(); + upgrade(); + GLog.p( Messages.get(TalismanOfForesight.class, "levelup") ); + } + updateQuickslot(); + + charge -= 3 + dist*0.88f; + partialCharge -= (dist*0.88f)%1f; + if (partialCharge < 0 && charge > 0){ + partialCharge ++; + charge --; + } + updateQuickslot(); + Dungeon.observe(); + Dungeon.hero.checkVisibleMobs(); + GameScene.updateFog(); + + curUser.sprite.zap(target); + Sample.INSTANCE.play(Assets.Sounds.TELEPORT); + if (noticed) Sample.INSTANCE.play(Assets.Sounds.SECRET); + + } + + } + + @Override + public String prompt() { + return Messages.get(TalismanOfForesight.class, "prompt"); + } + }; + private static final String WARN = "warn"; @Override @@ -143,10 +237,10 @@ public class TalismanOfForesight extends Artifact { @Override public void restoreFromBundle(Bundle bundle) { super.restoreFromBundle(bundle); - warn = bundle.getInt(WARN); + warn = bundle.getBoolean(WARN); } - private int warn = 0; + private boolean warn = false; public class Foresight extends ArtifactBuff{ @@ -191,23 +285,21 @@ public class TalismanOfForesight extends Artifact { } if (smthFound && !cursed){ - if (warn == 0){ + if (!warn){ GLog.w( Messages.get(this, "uneasy") ); if (target instanceof Hero){ ((Hero)target).interrupt(); } + warn = true; } - warn = 3; } else { - if (warn > 0){ - warn --; - } + warn = false; } - //fully charges in 2000 turns at lvl=0, scaling to 667 turns at lvl = 10. + //fully charges in 2000 turns at lvl=0, scaling to 1000 turns at lvl = 10. LockedFloor lock = target.buff(LockedFloor.class); if (charge < chargeCap && !cursed && (lock == null || lock.regenOn())) { - partialCharge += 0.05+(level()*0.01); + partialCharge += 0.05f+(level()*0.005f); if (partialCharge > 1 && charge < chargeCap) { partialCharge--; @@ -215,22 +307,15 @@ public class TalismanOfForesight extends Artifact { updateQuickslot(); } else if (charge >= chargeCap) { partialCharge = 0; - GLog.p( Messages.get(this, "full_charge") ); + GLog.p( Messages.get(TalismanOfForesight.class, "full_charge") ); } } return true; } - public void charge(){ - charge = Math.min(charge+(2+(level()/3)), chargeCap); - exp++; - if (exp >= 4 && level() < levelCap) { - upgrade(); - GLog.p( Messages.get(this, "levelup") ); - exp -= 4; - } - updateQuickslot(); + public void charge(int boost){ + charge = Math.min((charge+boost), chargeCap); } @Override @@ -245,10 +330,64 @@ public class TalismanOfForesight extends Artifact { @Override public int icon() { - if (warn == 0) - return BuffIndicator.NONE; - else + if (warn) return BuffIndicator.FORESIGHT; + else + return BuffIndicator.NONE; } } + + public static class CharAwareness extends FlavourBuff { + + public int charID; + + private static final String ID = "id"; + + @Override + public void detach() { + super.detach(); + Dungeon.observe(); + GameScene.updateFog(); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + charID = bundle.getInt(ID); + } + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(ID, charID); + } + + } + + public static class HeapAwareness extends FlavourBuff { + + public int pos; + + private static final String POS = "pos"; + + @Override + public void detach() { + super.detach(); + Dungeon.observe(); + GameScene.updateFog(); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + pos = bundle.getInt(POS); + } + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(POS, pos); + } + } + } 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 386970b57..47b813419 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -54,6 +54,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Heap; import com.shatteredpixel.shatteredpixeldungeon.items.Item; import com.shatteredpixel.shatteredpixeldungeon.items.Stylus; import com.shatteredpixel.shatteredpixeldungeon.items.Torch; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesight; import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TimekeepersHourglass; import com.shatteredpixel.shatteredpixeldungeon.items.food.SmallRation; import com.shatteredpixel.shatteredpixeldungeon.items.potions.PotionOfStrength; @@ -1082,6 +1083,23 @@ public abstract class Level implements Bundlable { } } + for (TalismanOfForesight.CharAwareness a : c.buffs(TalismanOfForesight.CharAwareness.class)){ + Char ch = (Char) Actor.findById(a.charID); + if (ch == null) { + a.detach(); + continue; + } + int p = ch.pos; + for (int i : PathFinder.NEIGHBOURS9) + fieldOfView[p+i] = true; + } + + for (TalismanOfForesight.HeapAwareness h : c.buffs(TalismanOfForesight.HeapAwareness.class)){ + int p = h.pos; + for (int i : PathFinder.NEIGHBOURS9) + fieldOfView[p+i] = true; + } + for (Mob ward : mobs){ if (ward instanceof WandOfWarding.Ward){ if (ward.fieldOfView == null || ward.fieldOfView.length != length()){