v3.0.0: implemented the Hallowed Ground spell/talent

This commit is contained in:
Evan Debenham
2024-12-10 13:29:27 -05:00
parent 1943a9f4ad
commit c8c05fdd74
9 changed files with 241 additions and 6 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -554,8 +554,15 @@ actors.hero.spells.cleanse.desc=The Cleric instantly clears all harmful effects
actors.hero.spells.clericspell.prompt=Choose a target
actors.hero.spells.clericspell.no_target=There is no target there.
actors.hero.spells.clericspell.invalid_target=You can't target that location.
actors.hero.spells.clericspell.charge_cost=Charge cost: %d
actors.hero.spells.hallowedground.name=hallowed ground
actors.hero.spells.hallowedground.prompt=choose a location
actors.hero.spells.hallowedground.short_desc=Heals allies, slows enemies, and spreads grass in an AOE.
actors.hero.spells.hallowedground.desc=The Priest focuses their divine magic into the ground nearby, creating a %1$dx%1$d area of hallowed terrain for 20 turns.\n\nWhen it is cast, this spell heals all allies for 10 HP (healthy allies and the Priest get shielding), briefly roots enemies, and spreads short grass.\n\nThe hallowed terrain heals allies for 1 HP per turn (healthy allies and the Priest get shielding), cripples enemies, and randomly causes tall grass to grow.\n\nHallowed terrain is destroyed by fire, and will produce furrowed grass if passive regen effects are disabled or the Priest has not gained exp in a while.
actors.hero.spells.hallowedground$hallowedterrain.desc=The ground has been hallowed here. Hallowed ground slowly enemies, heals allies, and causes grass to spread.
actors.hero.spells.holyintuition.name=holy intuition
actors.hero.spells.holyintuition.prompt=choose an item
actors.hero.spells.holyintuition.cursed=You sense malevolent magic lurking within this item.
@@ -1082,8 +1089,8 @@ actors.hero.talent.light_reading.desc=_+1:_ The Cleric can use their holy tome w
actors.hero.talent.holy_lance.title=Holy Lance
actors.hero.talent.holy_lance.desc=_+1:_ The Priest can cast _Holy Lance,_ a devastating spell that deals _30-55 damage_ at the cost of 4 charges.\n\n_+2:_ The Priest can cast _Holy Lance,_ a devastating spell that deals _45-83 damage_ at the cost of 4 charges.\n\n_+3:_ The Priest can cast _Holy Lance,_ a devastating spell that deals _60-110 damage_ at the cost of 4 charges.\n\nHoly Lance always deals maximum damage to demonic and undead foes. Holy Lance has a 50 turn cooldown before it can be cast again.
actors.hero.talent.priestt3b.title=Unknown
actors.hero.talent.priestt3b.desc=This talent hasn't been implemented yet, it currently does nothing.
actors.hero.talent.hallowed_ground.title=Hallowed Ground
actors.hero.talent.hallowed_ground.desc=_+1:_ The Priest can cast _Hallowed Ground,_ a spell that hallows terrain in a _3x3 area_ for 20 turns, at the cost of 2 charges.\n\n_+2:_ The Priest can cast _Hallowed Ground,_ a spell that hallows terrain in a _5x5 area_ for 20 turns, at the cost of 2 charges.\n\n_+3:_ The Priest can cast _Hallowed Ground,_ a spell that hallows terrain in a _7x7 area_ for 20 turns, at the cost of 2 charges.\n\nWhen it is cast, Hallowed Ground heals allies for 10 HP, briefly roots enemies, and spreads short grass. Afterward, it slowly heals allies, cripples enemies, and causes tall grass to randomly grow. Hallowed ground grants shielding to the Priest instead of healing and is destroyed by fire.
actors.hero.talent.priestt3c.title=Unknown
actors.hero.talent.priestt3c.desc=This talent hasn't been implemented yet, it currently does nothing.

View File

@@ -70,6 +70,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Ch
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.ElementalStrike;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.NaturesPower;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.warrior.Endure;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.HallowedGround;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Monk;
@@ -1907,6 +1908,12 @@ public class Hero extends Char {
buff(ElementalStrike.ElementalStrikeFurrowCounter.class).detach();
}
}
if (buff(HallowedGround.HallowedFurrowTracker.class) != null){
buff(ElementalStrike.ElementalStrikeFurrowCounter.class).countDown(percent*5f);
if (buff(ElementalStrike.ElementalStrikeFurrowCounter.class).count() <= 0){
buff(ElementalStrike.ElementalStrikeFurrowCounter.class).detach();
}
}
}
boolean levelUp = false;

View File

@@ -181,7 +181,7 @@ public enum Talent {
//Cleric T3
CLEANSE(169, 3), LIGHT_READING(170, 3),
//Priest T3
HOLY_LANCE(171, 3), PRIESTT3B(172, 3), PRIESTT3C(173, 3),
HOLY_LANCE(171, 3), HALLOWED_GROUND(172, 3), PRIESTT3C(173, 3),
//Paladin T3
PALADINT3A(174, 3), PALADINT3B(175, 3), PALADINT3C(176, 3),
//Cleric A1 T4
@@ -1019,7 +1019,7 @@ public enum Talent {
Collections.addAll(tierTalents, UNENCUMBERED_SPIRIT, MONASTIC_VIGOR, COMBINED_ENERGY);
break;
case PRIEST:
Collections.addAll(tierTalents, HOLY_LANCE, PRIESTT3B, PRIESTT3C);
Collections.addAll(tierTalents, HOLY_LANCE, HALLOWED_GROUND, PRIESTT3C);
break;
case PALADIN:
Collections.addAll(tierTalents, PALADINT3A, PALADINT3B, PALADINT3C);

View File

@@ -76,12 +76,15 @@ public class BlessSpell extends TargetedClericSpell {
int totalHeal = 5 + 10*hero.pointsInTalent(Talent.BLESS);
if (ch.HT - ch.HP < totalHeal){
int barrier = totalHeal - (ch.HT - ch.HP);
barrier = Math.max(barrier, 0);
if (ch.HP != ch.HT) {
ch.HP = ch.HT;
ch.sprite.showStatusWithIcon(CharSprite.POSITIVE, Integer.toString(totalHeal - barrier), FloatingText.HEALING);
}
Buff.affect(ch, Barrier.class).setShield(barrier);
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, Integer.toString(barrier), FloatingText.SHIELDING );
if (barrier > 0) {
Buff.affect(ch, Barrier.class).setShield(barrier);
ch.sprite.showStatusWithIcon(CharSprite.POSITIVE, Integer.toString(barrier), FloatingText.SHIELDING);
}
} else {
ch.HP = ch.HP + totalHeal;
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, Integer.toString(totalHeal), FloatingText.HEALING );

View File

@@ -124,6 +124,10 @@ public abstract class ClericSpell {
spells.add(HolyLance.INSTANCE);
}
if (cleric.hasTalent(Talent.HALLOWED_GROUND)){
spells.add(HallowedGround.INSTANCE);
}
}
return spells;
@@ -143,6 +147,7 @@ public abstract class ClericSpell {
spells.add(Cleanse.INSTANCE);
spells.add(Radiance.INSTANCE);
spells.add(HolyLance.INSTANCE);
spells.add(HallowedGround.INSTANCE);
return spells;
}
}

View File

@@ -0,0 +1,212 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2024 Evan Debenham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells;
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.Blob;
import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.Fire;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.CounterBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Cripple;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Regeneration;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Roots;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.effects.BlobEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.CellEmitter;
import com.shatteredpixel.shatteredpixeldungeon.effects.FloatingText;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.LeafParticle;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShaftParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.BArray;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
public class HallowedGround extends TargetedClericSpell {
public static final HallowedGround INSTANCE = new HallowedGround();
@Override
public int icon() {
return HeroIcon.HALLOWED_GROUND;
}
@Override
public float chargeUse(Hero hero) {
return 2;
}
@Override
protected void onTargetSelected(HolyTome tome, Hero hero, Integer target) {
if (target == null){
return;
}
if (Dungeon.level.solid[target] || !Dungeon.level.heroFOV[target]){
GLog.w(Messages.get(this, "invalid_target"));
return;
}
PathFinder.buildDistanceMap(target, BArray.not(Dungeon.level.solid, null), hero.pointsInTalent(Talent.HALLOWED_GROUND));
for (int i = 0; i < Dungeon.level.length(); i++){
if (PathFinder.distance[i] != Integer.MAX_VALUE){
int c = Dungeon.level.map[i];
if (c == Terrain.EMPTY || c == Terrain.EMBERS || c == Terrain.EMPTY_DECO) {
Level.set( i, Terrain.GRASS);
GameScene.updateMap( i );
CellEmitter.get(i).burst(LeafParticle.LEVEL_SPECIFIC, 2);
}
GameScene.add(Blob.seed(i, 20, HallowedTerrain.class));
CellEmitter.get(i).burst(ShaftParticle.FACTORY, 2);
Char ch = Actor.findChar(i);
if (ch != null){
if (ch.alignment == Char.Alignment.ALLY){
if (ch == Dungeon.hero || ch.HP == ch.HT){
Buff.affect(ch, Barrier.class).incShield(10);
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, "10", FloatingText.SHIELDING );
} else {
int barrier = 10 - (ch.HT - ch.HP);
barrier = Math.max(barrier, 0);
ch.HP += 10 - barrier;
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, Integer.toString(10-barrier), FloatingText.HEALING );
if (barrier > 0){
Buff.affect(ch, Barrier.class).incShield(barrier);
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, Integer.toString(barrier), FloatingText.SHIELDING );
}
}
} else if (!ch.flying) {
Buff.affect(ch, Roots.class, 1f);
}
}
}
}
//5 casts per hero level before furrowing
Buff.affect(hero, HallowedFurrowTracker.class).countUp(1);
Sample.INSTANCE.play(Assets.Sounds.MELD);
hero.sprite.zap(target);
hero.spendAndNext( 1f );
onSpellCast(tome, hero);
}
public String desc(){
int area = 1 + 2*Dungeon.hero.pointsInTalent(Talent.HALLOWED_GROUND);
return Messages.get(this, "desc", area) + "\n\n" + Messages.get(this, "charge_cost", (int)chargeUse(Dungeon.hero));
}
public static class HallowedTerrain extends Blob {
@Override
protected void evolve() {
int cell;
Fire fire = (Fire)Dungeon.level.blobs.get( Fire.class );
// on avg, hallowed ground produces 9/17/25 tiles of grass, 100/67/50% of total tiles
int chance = 10 + 10*Dungeon.hero.pointsInTalent(Talent.HALLOWED_GROUND);
for (int i = area.left-1; i <= area.right; i++) {
for (int j = area.top-1; j <= area.bottom; j++) {
cell = i + j*Dungeon.level.width();
if (cur[cell] > 0) {
//fire destroys hallowed terrain
if (fire != null && fire.volume > 0 && fire.cur[cell] > 0){
off[cell] = cur[cell] = 0;
continue;
}
int c = Dungeon.level.map[cell];
if (c == Terrain.GRASS) {
if (Random.Int(chance) == 0) {
if (!Regeneration.regenOn()
|| (Dungeon.hero.buff(HallowedFurrowTracker.class) != null && Dungeon.hero.buff(HallowedFurrowTracker.class).count() > 5)){
Level.set(cell, Terrain.FURROWED_GRASS);
} else {
Level.set(cell, Terrain.HIGH_GRASS);
}
GameScene.updateMap(cell);
CellEmitter.get(cell).burst(LeafParticle.LEVEL_SPECIFIC, 5);
}
} else if (c == Terrain.EMPTY || c == Terrain.EMBERS || c == Terrain.EMPTY_DECO) {
Level.set(cell, Terrain.GRASS);
GameScene.updateMap(cell);
CellEmitter.get(cell).burst(LeafParticle.LEVEL_SPECIFIC, 2);
}
Char ch = Actor.findChar(cell);
if (ch != null){
if (ch.alignment == Char.Alignment.ALLY){
if (ch == Dungeon.hero || ch.HP == ch.HT){
Buff.affect(ch, Barrier.class).incShield(1);
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, "1", FloatingText.SHIELDING );
} else {
ch.HP++;
ch.sprite.showStatusWithIcon( CharSprite.POSITIVE, "1", FloatingText.HEALING );
}
} else if (!ch.flying && ch.buff(Roots.class) == null){
Buff.prolong(ch, Cripple.class, 1f);
}
}
off[cell] = cur[cell] - 1;
volume += off[cell];
} else {
off[cell] = 0;
}
}
}
}
@Override
public void use(BlobEmitter emitter) {
super.use( emitter );
emitter.pour( ShaftParticle.FACTORY, 1f );
}
@Override
public String tileDesc() {
return Messages.get(this, "desc");
}
}
public static class HallowedFurrowTracker extends CounterBuff{{revivePersists = true;}}
}

View File

@@ -85,6 +85,7 @@ public class HeroIcon extends Image {
public static final int CLEANSE = 49;
public static final int RADIANCE = 50;
public static final int HOLY_LANCE = 51;
public static final int HALLOWED_GROUND = 52;
//all cleric spells have a separate icon with no background for the action indicator
public static final int SPELL_ACTION_OFFSET = 32;