diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 2a271dcd7..7fea18ee2 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -1,49 +1,67 @@ ###blobs +actors.blobs.blizzard.name=blizzard actors.blobs.blizzard.desc=A blizzard is swirling here. +actors.blobs.confusiongas.name=confusion gas actors.blobs.confusiongas.desc=A cloud of confusion gas is swirling here. +actors.blobs.electricity.name=electricity actors.blobs.electricity.desc=A field of electricity is sparking brightly here. actors.blobs.electricity.rankings_desc=Electrocuted actors.blobs.electricity.ondeath=You were shocked to death... +actors.blobs.fire.name=fire actors.blobs.fire.desc=A fire is raging here. +actors.blobs.foliage.name=foliage actors.blobs.foliage.desc=Shafts of light pierce the gloom of the underground garden. +actors.blobs.freezing.name=freezing air actors.blobs.freezing.desc=The air is unnaturally frigid here. +actors.blobs.goowarn.name=dark energy actors.blobs.goowarn.desc=Specks of dark energy are swarming here! +actors.blobs.inferno.name=inferno actors.blobs.inferno.desc=An inferno is raging here. +actors.blobs.paralyticgas.name=paralytic gas actors.blobs.paralyticgas.desc=A cloud of paralytic gas is swirling here. +actors.blobs.regrowth.name=regrowth + +actors.blobs.sacrificialfire.name=sacrificial fire actors.blobs.sacrificialfire.desc=There is an altar here with a sacrificial fire burning atop it. Any creature that's killed here will be consumed as an offering to the spirits of the dungeon.\n\nPerhaps a reward will be given if enough sacrifices are made? actors.blobs.sacrificialfire.worthy=The fire consumes your offering and grows stronger. actors.blobs.sacrificialfire.unworthy=The fire consumes your offering, but doesn't change. actors.blobs.sacrificialfire.reward=The fire flares and then dissipates, leaving a reward behind! +actors.blobs.smokescreen.name=smokescreen actors.blobs.smokescreen.desc=A cloud of thick black smoke is swirling here. +actors.blobs.stenchgas.name=stench gas actors.blobs.stenchgas.desc=A cloud of fetid stench is swirling here. +actors.blobs.stormcloud.name=storm clouds actors.blobs.stormcloud.desc=A cloud of billowing water vapor is swirling here. +actors.blobs.toxicgas.name=toxic gas actors.blobs.toxicgas.desc=A greenish cloud of toxic gas is swirling here. actors.blobs.toxicgas.rankings_desc=Suffocated actors.blobs.toxicgas.ondeath=You died from the toxic gas... +actors.blobs.corrosivegas.name=corrosive gas actors.blobs.corrosivegas.desc=A cloud of deadly caustic gas is swirling here. +actors.blobs.waterofawareness.name=water of awareness actors.blobs.waterofawareness.procced=As you take a sip, you feel knowledge pour into your mind. actors.blobs.waterofawareness.desc=Power of knowledge radiates from the water of this well. Drinking from the well will fully identify all equipped items, identify curses on all items in your inventory, and reveal all items on the current floor. +actors.blobs.waterofhealth.name=water of health actors.blobs.waterofhealth.procced=As you take a sip, you feel your wounds heal completely. actors.blobs.waterofhealth.desc=Power of health radiates from the water of this well. Drinking from this well will heal your wounds, satisfy hunger, and cleanse curses on any worn items. -actors.blobs.wateroftransmutation.desc=Power of change radiates from the water of this well. Throw an item into the well to turn it into something else. - +actors.blobs.web.name=spider web actors.blobs.web.desc=A thick web is covering everything here. Anything that touches or is thrown through the web will break it, but will also be stuck in place. diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index eb7dcdcde..889e78038 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -1341,9 +1341,10 @@ items.stones.stoneofshock.desc=This runestone unleashes a blast of electrical en ###trinkets items.trinkets.chaoticcenser.name=chaotic censer -items.trinkets.chaoticcenser.desc=After some time in the alchemy pot this incense-burning censer appears to be producing smoke all on its own! These gasses build up and will spew forth from the censer in random directions and semi-random intervals. It seems capable of producing all sorts of gasses, but the position they shoot out in seems to be more likely to be in your favour at least. -items.trinkets.chaoticcenser.typical_stats_desc=Typically this trinket will spawn a harmful gas nearby roughly every _%d_ turns. The gas is more likely to appear when enemies are present, and less likely to appear in enclosed spaces. At higher levels these gases are more likely to be exotic and powerful. -items.trinkets.chaoticcenser.stats_desc=At its current level, this trinket will spawn a harmful gas nearby roughly every _%d_ turns. The gas is more likely to appear when enemies are present, and less likely to appear in enclosed spaces. At higher levels these gases are more likely to be exotic and powerful. +items.trinkets.chaoticcenser.spew=Your censer is about to spew: %s. +items.trinkets.chaoticcenser.desc=After some time in the alchemy pot this incense-burning censer appears to be producing smoke all on its own! These gasses build up and will spew forth from the censer toward enemies at semi-random intervals. It seems capable of producing all sorts of gasses, but you'll get a moment of warning just before the censer activates. +items.trinkets.chaoticcenser.typical_stats_desc=Typically this trinket will spawn a harmful gas near an enemy roughly every _%d_ turns. Gasses will only appear when enemies are present. At higher levels these gases are more likely to be exotic and powerful. +items.trinkets.chaoticcenser.stats_desc=At its current level, this trinket will spawn a harmful gas near an enemy roughly every _%d_ turns. Gasses will only appear when enemies are present. At higher levels these gases are more likely to be exotic and powerful. items.trinkets.dimensionalsundial.name=dimensional sundial items.trinkets.dimensionalsundial.warning=Your sundial isn't casting a shadow, you feel uneasy. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ChaoticCenser.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ChaoticCenser.java index 05df6ac68..734c3e699 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ChaoticCenser.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ChaoticCenser.java @@ -23,7 +23,6 @@ package com.shatteredpixel.shatteredpixeldungeon.items.trinkets; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; -import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blizzard; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Blob; @@ -36,15 +35,16 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.StenchGas; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.StormCloud; import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.ToxicGas; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff; import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Regeneration; -import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Shopkeeper; import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; -import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica; +import com.shatteredpixel.shatteredpixeldungeon.effects.TargetedCell; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; import com.shatteredpixel.shatteredpixeldungeon.ui.TargetHealthIndicator; +import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.watabou.noosa.audio.Sample; import com.watabou.utils.BArray; import com.watabou.utils.Bundle; @@ -99,91 +99,42 @@ public class ChaoticCenser extends Trinket { if (avgTurns == -1){ spend(Random.NormalIntRange(1, 5)); return true; - } else if (left > avgTurns*1.1f){ - left = Random.IntRange((int) (avgTurns*0.9f), (int) (avgTurns*1.1f)); + } else if (left > avgTurns*1.2f){ + left = Random.IntRange((int) (avgTurns*0.833f), (int) (avgTurns*1.2f)); } - float triggerChance = 0; - if (left > 0 && left <= 30) { + if (left <= 0) { + + Char enemy = null; if (TargetHealthIndicator.instance != null && TargetHealthIndicator.instance.isVisible() && TargetHealthIndicator.instance.target() != null && TargetHealthIndicator.instance.target().alignment == Char.Alignment.ENEMY && TargetHealthIndicator.instance.target().isAlive()) { - triggerChance = 0.75f; - } - } else if (left > -30 && left <= 0) { - - if (TargetHealthIndicator.instance != null && TargetHealthIndicator.instance.isVisible() - && TargetHealthIndicator.instance.target() != null - && TargetHealthIndicator.instance.target().alignment == Char.Alignment.ENEMY - && TargetHealthIndicator.instance.target().isAlive()) { - triggerChance = 1f; - } else if (Dungeon.level.openSpace[target.pos]){ - triggerChance = 0.2f; - } - - } else if (left <= -30) { - triggerChance = 1f; - - } - - if (triggerChance > 0) { - if (safeAreaDelay >= 0) { - boolean safeArea = false; - - //shops are a safe area - for (Char ch : Actor.chars()) { - if (ch instanceof Shopkeeper - && Dungeon.level.distance(target.pos, ch.pos) <= 6 - && new Ballistica(target.pos, ch.pos, Ballistica.PROJECTILE).collisionPos == ch.pos) { - safeArea = true; - } - } - - //enclosed spaces are a safe area if no enemies are present - if ((TargetHealthIndicator.instance == null || TargetHealthIndicator.instance.target() == null - || TargetHealthIndicator.instance.target().alignment != Char.Alignment.ENEMY - || !TargetHealthIndicator.instance.target().isAlive()) - && !Dungeon.level.openSpace[target.pos]) { - safeArea = true; - } - - if (safeArea){ - int delay = Random.NormalIntRange(1, 5); - spend(delay); - safeAreaDelay -= delay; - return true; + if (produceGas(TargetHealthIndicator.instance.target())){ + Sample.INSTANCE.play(Assets.Sounds.GAS, 0.5f); + Dungeon.hero.interrupt(); + left += Random.IntRange((int) (avgTurns * 0.9f), (int) (avgTurns * 1.1f)); } } - } - if (Random.Float() < triggerChance){ - if (produceGas()) { - Sample.INSTANCE.play(Assets.Sounds.GAS); - Dungeon.hero.interrupt(); - left += Random.IntRange((int) (avgTurns * 0.9f), (int) (avgTurns * 1.1f)); - } } //buff ticks an average of every 3 turns - int delay = Random.NormalIntRange(1, 5); + int delay = Random.NormalIntRange(1, 3); spend(delay); - safeAreaDelay = Math.min(safeAreaDelay+2*delay, 100); - left -= delay; + left = (int)Math.max(left-delay, -avgTurns/3f); return true; } private static String LEFT = "left"; - private static String SAFE_AREA_DELAY = "safe_area_delay"; @Override public void storeInBundle(Bundle bundle) { super.storeInBundle(bundle); bundle.put(LEFT, left); - bundle.put(SAFE_AREA_DELAY, safeAreaDelay); } @Override @@ -191,12 +142,11 @@ public class ChaoticCenser extends Trinket { super.restoreFromBundle(bundle); if (bundle.contains(LEFT)){ left = bundle.getInt(LEFT); - safeAreaDelay = bundle.getInt(SAFE_AREA_DELAY); } } } - private static boolean produceGas(){ + private static boolean produceGas( Char target ){ int level = trinketLevel(ChaoticCenser.class); if (level < 0 || level > 3){ @@ -222,69 +172,53 @@ public class ChaoticCenser extends Trinket { break; } - Char target = null; - if (TargetHealthIndicator.instance != null && TargetHealthIndicator.instance.isVisible() - && TargetHealthIndicator.instance.target() != null - && TargetHealthIndicator.instance.target().alignment == Char.Alignment.ENEMY - && TargetHealthIndicator.instance.target().isAlive()) { - target = TargetHealthIndicator.instance.target(); - } - HashMap candidateCells = new HashMap<>(); - PathFinder.buildDistanceMap(Dungeon.hero.pos, BArray.not(Dungeon.level.solid, null), 5); + PathFinder.buildDistanceMap(Dungeon.hero.pos, BArray.not(Dungeon.level.solid, null), 6); - //spawn gas in a random visible cell 2-5 tiles away, likelihood is 3,4 > 2,5 + //spawn gas in a random visible cell 2-6 tiles away for (int i = 0; i < Dungeon.level.length(); i++){ if (Dungeon.level.heroFOV[i] && PathFinder.distance[i] < Integer.MAX_VALUE) { - switch (PathFinder.distance[i]) { - case 3: - case 4: - candidateCells.put(i, 2f); - break; - case 2: - case 5: - candidateCells.put(i, 1f); - break; + if (PathFinder.distance[i] >= 2 && PathFinder.distance[i] <= 6) { + candidateCells.put(i, 0f); } } } - //unless we have a target, then strongly prefer cells closer to target - if (target != null){ - int targetpos = target.pos; - if (Dungeon.level.trueDistance(target.pos, Dungeon.hero.pos) >= 4){ - //if target is a distance from the hero, aim in front of them instead - for (int i : PathFinder.NEIGHBOURS8){ - while (!Dungeon.level.solid[targetpos+i] - && Dungeon.level.trueDistance(target.pos+i, Dungeon.hero.pos) < Dungeon.level.trueDistance(targetpos, Dungeon.hero.pos)){ - targetpos = target.pos+i; - } + //strongly prefer cells closer to target + int targetpos = target.pos; + if (Dungeon.level.trueDistance(target.pos, Dungeon.hero.pos) >= 4){ + //if target is a distance from the hero, aim in front of them instead + for (int i : PathFinder.NEIGHBOURS8){ + while (!Dungeon.level.solid[targetpos+i] + && Dungeon.level.trueDistance(target.pos+i, Dungeon.hero.pos) < Dungeon.level.trueDistance(targetpos, Dungeon.hero.pos)){ + targetpos = target.pos+i; } } - float closest = 100; - for (int cell : candidateCells.keySet()){ - float dist = Dungeon.level.distance(cell, targetpos); - if (dist < closest){ - closest = dist; - } + } + float closest = 100; + for (int cell : candidateCells.keySet()){ + float dist = Dungeon.level.distance(cell, targetpos); + if (dist < closest){ + closest = dist; } - for (int cell : candidateCells.keySet()){ - float dist = Dungeon.level.distance(cell, targetpos); - if (dist - closest == 0) { - candidateCells.put(cell, 4f); - } else if (dist - closest <= 1) { - candidateCells.put(cell, 1f); - } else { - candidateCells.put(cell, 0f); - } + } + for (int cell : candidateCells.keySet()){ + float dist = Dungeon.level.distance(cell, targetpos); + if (dist - closest == 0) { + candidateCells.put(cell, 8f); + } else if (dist - closest <= 1) { + candidateCells.put(cell, 1f); + } else { + candidateCells.put(cell, 0f); } } if (!candidateCells.isEmpty()) { Integer targetCell = Random.chances(candidateCells); if (targetCell != null) { - GameScene.add(Blob.seed(targetCell, (int) gasQuantity, gasToSpawn)); - MagicMissile.boltFromChar(Dungeon.hero.sprite.parent, MISSILE_VFX.get(gasToSpawn), Dungeon.hero.sprite, targetCell, null); + Buff.affect(Dungeon.hero, GasSpewer.class, Dungeon.hero.cooldown()).set(targetCell, gasToSpawn, (int)gasQuantity); + GLog.w(Messages.get(ChaoticCenser.class, "spew", Messages.titleCase(Messages.get(gasToSpawn, "name")) )); + target.sprite.parent.addToBack(new TargetedCell(targetCell, 0xFF0000)); return true; } } @@ -293,6 +227,66 @@ public class ChaoticCenser extends Trinket { } + public static class GasSpewer extends FlavourBuff { + + private int targetCell; + + private int depth; + private int branch; + + private Class gasType; + private int gasQuantity; + + public void set( int targetCell, Class gasType, int gasQuantity){ + this.targetCell = targetCell; + + depth = Dungeon.depth; + branch = Dungeon.branch; + + this.gasType = gasType; + this.gasQuantity = gasQuantity; + } + + @Override + public boolean act() { + + if (depth == Dungeon.depth && branch == Dungeon.branch){ + GameScene.add(Blob.seed(targetCell, gasQuantity, gasType)); + MagicMissile.boltFromChar(Dungeon.hero.sprite.parent, MISSILE_VFX.get(gasType), Dungeon.hero.sprite, targetCell, null); + Sample.INSTANCE.play(Assets.Sounds.GAS); + } + + detach(); + return true; + } + + private static final String CELL = "cell"; + private static final String DEPTH = "depth"; + private static final String BRANCH = "branch"; + private static final String GAS_TYPE = "gas_type"; + private static final String GAS_QUANTITY = "gas_quantity"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(CELL, targetCell); + bundle.put(DEPTH, depth); + bundle.put(BRANCH, branch); + bundle.put(GAS_TYPE, gasType); + bundle.put(GAS_QUANTITY, gasQuantity); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + targetCell = bundle.getInt(CELL); + depth = bundle.getInt(DEPTH); + branch = bundle.getInt(BRANCH); + gasType = bundle.getClass(GAS_TYPE); + gasQuantity = bundle.getInt(GAS_QUANTITY); + } + } + private static final float[][] GAS_CAT_CHANCES = new float[4][3]; static { GAS_CAT_CHANCES[0] = new float[]{70, 25, 5};