diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 48f6f72c8..d28174105 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -387,13 +387,19 @@ actors.hero.abilities.huntress.spectralblades.name=spectral blades actors.hero.abilities.huntress.spectralblades.short_desc=The Huntress throws _Spectral Blades_ at a target, inflicting damage depending on her currently equipped melee weapon. actors.hero.abilities.huntress.spectralblades.desc=The Huntress throws out a spectral blade at an enemy, which acts like a ranged strike from her melee weapon. All of the normal effects of a melee weapon will activate, including enchantments.\n\nThis ability costs 25 charge. actors.hero.abilities.huntress.naturespower.name=nature's power -actors.hero.abilities.huntress.naturespower$naturespowertracker.name=nature's power -actors.hero.abilities.huntress.naturespower$naturespowertracker.desc=The Huntress is briefly empowered, increasing her movement speed and attack speed with her bow.\n\nTurns remaining: %s. actors.hero.abilities.huntress.naturespower.short_desc=The Huntress calls upon _Nature's Power_, increasing her movement speed and her bow's rate of fire for a short time. actors.hero.abilities.huntress.naturespower.desc=The Huntress temporarily empowers herself and her bow with the power of nature! This lasts for 8 turns.\n\nDuring this time, the huntress moves at 2x speed, and attacks 33% faster with her bow. The bonus bow effects do not trigger if the sniper is using her special attacks.\n\nThis ability costs 35 charge. +actors.hero.abilities.huntress.naturespower$naturespowertracker.name=nature's power +actors.hero.abilities.huntress.naturespower$naturespowertracker.desc=The Huntress is briefly empowered, increasing her movement speed and attack speed with her bow.\n\nTurns remaining: %s. actors.hero.abilities.huntress.spirithawk.name=spirit hawk +actors.hero.abilities.huntress.spirithawk.no_space=There is no free space near you. actors.hero.abilities.huntress.spirithawk.short_desc=The Huntress summons a _Spirit Hawk_ familiar, which can help her scout locations and distract enemies. -actors.hero.abilities.huntress.spirithawk.desc=TODO +actors.hero.abilities.huntress.spirithawk.desc=The Huntress summons a spirit hawk familiar, which can be directed by using the ability again while it is summoned. The hawk lasts for 50 turns.\n\nThe hawk has minimal health and attacking power, but is fast, evasive, and accurate. It shares its entire field of vision with the huntress at all times. It is immune to all area-bound effects, such as fire and poison gas. It will not attack unless specifically directed to.\n\nSummoning the hawk costs 35 charge. +actors.hero.abilities.huntress.spirithawk$hawkally.name=spirit hawk +actors.hero.abilities.huntress.spirithawk$hawkally.direct_follow=Your hawk moves to follow you. +actors.hero.abilities.huntress.spirithawk$hawkally.direct_attack=Your hawk moves to attack! +actors.hero.abilities.huntress.spirithawk$hawkally.desc=A magical hawk, summoned by the Huntress.\n\nWhile it isn't much of a fighter its speed and vision make it excellent for scouting and distracting enemies.\n\nTurns remaining: %d. +actors.hero.abilities.huntress.spirithawk$hawkally.desc_dodges=Guaranteed dodges remaining: %d. actors.hero.abilities.ratmogrify.name=ratmogrify actors.hero.abilities.ratmogrify.cant_transform=You can't ratmogrify that! @@ -706,12 +712,12 @@ actors.hero.talent.natures_wrath.desc=_+1:_ While nature's power is active, shot actors.hero.talent.wild_momentum.title=wild momentum actors.hero.talent.wild_momentum.desc=_+1:_ Killing an enemy with the spirit bow prolongs the duration of nature's power by _1 turn_. The duration can be extended by a max of _2 turns_.\n\n_+2:_ Killing an enemy with the spirit bow prolongs the duration of nature's power by _2 turns_. The duration can be extended by a max of _4 turns_.\n\n_+3:_ Killing an enemy with the spirit bow prolongs the duration of nature's power by _3 turns_. The duration can be extended by a max of _6 turns_.\n\n_+4:_ Killing an enemy with the spirit bow prolongs the duration of nature's power by _4 turns_. The duration can be extended by a max of _8 turns_. -actors.hero.talent.huntress_3_1.title=TODO NAME -actors.hero.talent.huntress_3_1.desc=_+1:_ \n\n_+2:_ \n\n_+3:_ \n\n_+4:_ -actors.hero.talent.huntress_3_2.title=TODO NAME -actors.hero.talent.huntress_3_2.desc=_+1:_ \n\n_+2:_ \n\n_+3:_ \n\n_+4:_ -actors.hero.talent.huntress_3_3.title=TODO NAME -actors.hero.talent.huntress_3_3.desc=_+1:_ \n\n_+2:_ \n\n_+3:_ \n\n_+4:_ +actors.hero.talent.eagle_eye.title=eagle eye +actors.hero.talent.eagle_eye.desc=_+1:_ The spirit hawk's vision range is increased to _7 tiles_ from 6.\n\n_+2:_ The spirit hawk's vision range is increased to _8 tiles_ from 6.\n\n_+3:_ The spirit hawk's vision range is increased to _8 tiles_ from 6, and it gets _2 tiles_ of mind vision.\n\n_+4:_ The spirit hawk's vision range is increased to _8 tiles_ from 6, and it gets _3 tiles_ of mind vision. +actors.hero.talent.go_for_the_eyes.title=go for the eyes +actors.hero.talent.go_for_the_eyes.desc=_+1:_ Attacks from the spirit hawk blind enemies for _2 turns_.\n\n_+2:_ Attacks from the spirit hawk blind enemies for _3 turns_.\n\n_+3:_ Attacks from the spirit hawk blind enemies for _4 turns_.\n\n_+4:_ Attacks from the spirit hawk blind enemies for _5 turns_. +actors.hero.talent.swift_spirit.title=swift spirit +actors.hero.talent.swift_spirit.desc=_+1:_ The spirit hawk's movement speed is increased to _2.5 tiles_ from 2, and it is guaranteed to dodge the first _1 attack_ made against it.\n\n_+2:_ The spirit hawk's movement speed is increased to _3 tiles_ from 2, and it is guaranteed to dodge the first _2 attacks_ made against it.\n\n_+3:_ The spirit hawk's movement speed is increased to _3.5 tiles_ from 2, and it is guaranteed to dodge the first _3 attacks_ made against it.\n\n_+4:_The spirit hawk's movement speed is increased to _4 tiles_ from 2, and it is guaranteed to dodge the first _4 attacks_ made against it. #universal actors.hero.talent.heroic_energy.title=heroic energy diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java index 7c274cf73..703227859 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java @@ -30,6 +30,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.MindVision; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.RevealedArea; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Ghost; @@ -43,6 +44,8 @@ import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.TalismanOfForesi import com.shatteredpixel.shatteredpixeldungeon.items.potions.Potion; import com.shatteredpixel.shatteredpixeldungeon.items.rings.Ring; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.Scroll; +import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfRegrowth; +import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfWarding; import com.shatteredpixel.shatteredpixeldungeon.journal.Notes; import com.shatteredpixel.shatteredpixeldungeon.levels.CavesLevel; import com.shatteredpixel.shatteredpixeldungeon.levels.CityLevel; @@ -764,6 +767,33 @@ public class Dungeon { GameScene.updateFog(a.pos, 2); } + for (Char ch : Actor.chars()){ + if (ch instanceof WandOfWarding.Ward + || ch instanceof WandOfRegrowth.Lotus + || ch instanceof SpiritHawk.HawkAlly){ + x = ch.pos % level.width(); + y = ch.pos / level.width(); + + //left, right, top, bottom + dist = ch.viewDistance+1; + l = Math.max( 0, x - dist ); + r = Math.min( x + dist, level.width() - 1 ); + t = Math.max( 0, y - dist ); + b = Math.min( y + dist, level.height() - 1 ); + + width = r - l + 1; + height = b - t + 1; + + pos = l + t * level.width(); + + for (int i = t; i <= b; i++) { + BArray.or( level.visited, level.heroFOV, pos, width, level.visited ); + pos+=level.width(); + } + GameScene.updateFog(ch.pos, dist); + } + } + GameScene.afterObserve(); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java index 4aff54470..b8909e7c2 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/Talent.java @@ -136,8 +136,8 @@ public enum Talent { FAN_OF_BLADES(113, 4), PROJECTING_BLADES(114, 4), SPIRIT_BLADES(115, 4), //Natures Power T4 GROWING_POWER(116, 4), NATURES_WRATH(117, 4), WILD_MOMENTUM(118, 4), - //??? T4 - HUNTRESS_3_1(119, 4), HUNTRESS_3_2(120, 4), HUNTRESS_3_3(121, 4), + //Spirit Hawk T4 + EAGLE_EYE(119, 4), GO_FOR_THE_EYES(120, 4), SWIFT_SPIRIT(121, 4), //universal T4 HEROIC_ENERGY(123, 4), diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpiritHawk.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpiritHawk.java index 3c35fc3f2..99c047ecd 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpiritHawk.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/huntress/SpiritHawk.java @@ -21,20 +21,234 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress; +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; +import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; +import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Electricity; +import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Freezing; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Bleeding; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Blindness; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.BlobImmunity; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility; +import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly; import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; +import com.shatteredpixel.shatteredpixeldungeon.sprites.BatSprite; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.utils.Bundle; +import com.watabou.utils.GameMath; +import com.watabou.utils.PathFinder; +import com.watabou.utils.Random; + +import java.util.ArrayList; public class SpiritHawk extends ArmorAbility { + @Override + public String targetingPrompt() { + if (getHawk() == null) { + return super.targetingPrompt(); + } else { + return Messages.get(this, "prompt"); + } + } + + { + baseChargeUse = 35f; + } + + @Override + public float chargeUse(Hero hero) { + if (getHawk() == null) { + return super.chargeUse(hero); + } else { + return 0; + } + } + @Override protected void activate(ClassArmor armor, Hero hero, Integer target) { - //TODO + HawkAlly ally = getHawk(); + + if (ally != null){ + if (target == null){ + return; + } else { + ally.directTocell(target); + } + } else { + ArrayList spawnPoints = new ArrayList<>(); + for (int i = 0; i < PathFinder.NEIGHBOURS8.length; i++) { + int p = hero.pos + PathFinder.NEIGHBOURS8[i]; + if (Actor.findChar(p) == null && (Dungeon.level.passable[p] || Dungeon.level.avoid[p])) { + spawnPoints.add(p); + } + } + + if (!spawnPoints.isEmpty()){ + ally = new HawkAlly(); + + ally.pos = Random.element(spawnPoints); + + GameScene.add(ally); + Dungeon.level.occupyCell(ally); + Dungeon.observe(); + + armor.charge -= chargeUse(hero); + armor.updateQuickslot(); + Invisibility.dispel(); + hero.spendAndNext(Actor.TICK); + + } else { + GLog.w(Messages.get(this, "no_space")); + } + } + } @Override public Talent[] talents() { - return new Talent[]{Talent.HUNTRESS_3_1, Talent.HUNTRESS_3_2, Talent.HUNTRESS_3_3, Talent.HEROIC_ENERGY}; + return new Talent[]{Talent.EAGLE_EYE, Talent.GO_FOR_THE_EYES, Talent.SWIFT_SPIRIT, Talent.HEROIC_ENERGY}; + } + + private static HawkAlly getHawk(){ + for (Char ch : Actor.chars()){ + if (ch instanceof HawkAlly){ + return (HawkAlly) ch; + } + } + return null; + } + + public static class HawkAlly extends DirectableAlly { + + { + spriteClass = HawkSprite.class; + + HP = HT = 10; + defenseSkill = 50; + + flying = true; + viewDistance = (int)GameMath.gate(6, 6+Dungeon.hero.pointsInTalent(Talent.EAGLE_EYE), 8); + attacksAutomatically = false; + + immunities.addAll(new BlobImmunity().immunities()); + } + + @Override + public int attackSkill(Char target) { + return 50; + } + + private int dodgesUsed = 0; + private float timeRemaining = 50f; + + @Override + public int defenseSkill(Char enemy) { + if (dodgesUsed < Dungeon.hero.pointsInTalent(Talent.SWIFT_SPIRIT)){ + dodgesUsed++; + return Char.INFINITE_EVASION; + } + return super.defenseSkill(enemy); + } + + @Override + public int damageRoll() { + return Random.NormalIntRange(5, 10); + } + + @Override + public int attackProc(Char enemy, int damage) { + damage = super.attackProc( enemy, damage ); + if (Dungeon.hero.hasTalent(Talent.GO_FOR_THE_EYES)) { + Buff.prolong( enemy, Blindness.class, 1 + Dungeon.hero.pointsInTalent(Talent.GO_FOR_THE_EYES) ); + } + + return damage; + } + + @Override + protected boolean act() { + viewDistance = (int)GameMath.gate(6, 6+Dungeon.hero.pointsInTalent(Talent.EAGLE_EYE), 8); + boolean result = super.act(); + Dungeon.level.updateFieldOfView( this, fieldOfView ); + GameScene.updateFog(pos, viewDistance+(int)Math.ceil(speed())); + return result; + } + + @Override + public float speed() { + return 2f + Dungeon.hero.pointsInTalent(Talent.SWIFT_SPIRIT)/2f; + } + + @Override + protected void spend(float time) { + super.spend(time); + timeRemaining -= time; + if (timeRemaining <= 0){ + die(null); + } + } + + @Override + public void destroy() { + super.destroy(); + Dungeon.observe(); + GameScene.updateFog(); + } + + @Override + public void followHero() { + GLog.i(Messages.get(this, "direct_follow")); + super.followHero(); + } + + @Override + public void targetChar(Char ch) { + GLog.i(Messages.get(this, "direct_attack")); + super.targetChar(ch); + } + + @Override + public String description() { + String message = Messages.get(this, "desc", (int)timeRemaining); + if (dodgesUsed < Dungeon.hero.pointsInTalent(Talent.SWIFT_SPIRIT)){ + message += "\n" + Messages.get(this, "desc_dodges", (Dungeon.hero.pointsInTalent(Talent.SWIFT_SPIRIT) - dodgesUsed)); + } + return message; + } + + private static final String DODGES_USED = "dodges_used"; + private static final String TIME_REMAINING = "time_remaining"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(DODGES_USED, dodgesUsed); + bundle.put(TIME_REMAINING, timeRemaining); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + dodgesUsed = bundle.getInt(DODGES_USED); + timeRemaining = bundle.getFloat(TIME_REMAINING); + } + } + + //TODO real sprite + public static class HawkSprite extends BatSprite { + + @Override + public void resetColor() { + super.resetColor(); + tint(0, 1f, 1f, 1f); + } + } } 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 b33b6968d..bd3b2e988 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java @@ -44,6 +44,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Shadows; 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.huntress.SpiritHawk; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bestiary; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; @@ -1120,6 +1121,18 @@ public abstract class Level implements Bundlable { } } + if (c instanceof SpiritHawk.HawkAlly && Dungeon.hero.pointsInTalent(Talent.EAGLE_EYE) >= 3){ + int range = 1+(Dungeon.hero.pointsInTalent(Talent.EAGLE_EYE)-2); + for (Mob mob : mobs) { + int p = mob.pos; + if (!fieldOfView[p] && distance(c.pos, p) <= range) { + for (int i : PathFinder.NEIGHBOURS9) { + fieldOfView[mob.pos + i] = true; + } + } + } + } + //Currently only the hero can get mind vision or awareness if (c.isAlive() && c == Dungeon.hero) { @@ -1171,7 +1184,9 @@ public abstract class Level implements Bundlable { } for (Mob m : mobs){ - if (m instanceof WandOfWarding.Ward || m instanceof WandOfRegrowth.Lotus){ + if (m instanceof WandOfWarding.Ward + || m instanceof WandOfRegrowth.Lotus + || m instanceof SpiritHawk.HawkAlly){ if (m.fieldOfView == null || m.fieldOfView.length != length()){ m.fieldOfView = new boolean[length()]; Dungeon.level.updateFieldOfView( m, m.fieldOfView );