v3.0.0: initial base impl of stasis, missing big chunks of functionality

This commit is contained in:
Evan Debenham
2025-02-17 17:28:46 -05:00
parent 344b313b59
commit fbcf0da0d6
4 changed files with 188 additions and 1 deletions

View File

@@ -750,6 +750,12 @@ actors.hero.spells.smite.name=smite
actors.hero.spells.smite.short_desc=Guarantees a hit with bonus damage and enchant power. actors.hero.spells.smite.short_desc=Guarantees a hit with bonus damage and enchant power.
actors.hero.spells.smite.desc=The Paladin infuses a melee strike with righteous destructive power.\n\nIn addition to dealing normal melee damage, Smite makes non-encumbered attacks guaranteed to hit, has +300%% enchantment power, and deals %1$d - %2$d bonus magic damage.\n\nSmite's bonus magic damage scales with the Paladin's level, and it will always deal maximum bonus magic damage against undead or demonic foes. actors.hero.spells.smite.desc=The Paladin infuses a melee strike with righteous destructive power.\n\nIn addition to dealing normal melee damage, Smite makes non-encumbered attacks guaranteed to hit, has +300%% enchantment power, and deals %1$d - %2$d bonus magic damage.\n\nSmite's bonus magic damage scales with the Paladin's level, and it will always deal maximum bonus magic damage against undead or demonic foes.
actors.hero.spells.stasis.name=stasis
actors.hero.spells.stasis.short_desc=Removes ally from dungeon and preserves them.
actors.hero.spells.stasis.desc=The Cleric focuses their connection with their ally toward themselves, causing the ally to be temporarily held in a sort of energy stasis within the Cleric. The ally will be preserved for up to %d turns, with all positive effects on them being unaffected by their time in stasis.\n\nOnce the spell ends naturally, or once the Cleric re-casts it, the ally will appear next to the Cleric.
actors.hero.spells.stasis$stasisbuff.name=ally in stasis
actors.hero.spells.stasis$stasisbuff.desc=The Cleric currently has an ally stored in stasis. All positive effects on this ally are preserved, including Power of Many, and they will reappear next to the Cleric when the effect ends. The effect can also be ended early by re-casting Stasis.\n\nAlly in stasis: %1$s.\nTurns Remaining: %2$s.
actors.hero.spells.sunray.name=sunray actors.hero.spells.sunray.name=sunray
actors.hero.spells.sunray.short_desc=Deals ranged magic damage and blinds a target once. actors.hero.spells.sunray.short_desc=Deals ranged magic damage and blinds a target once.
actors.hero.spells.sunray.desc=The Cleric fires a ray of blinding light at a target, dealing %1$d-%2$d damage and blinding them for %3$d turns. Sunray always deals maximum damage to undead and demonic targets.\n\nAfter being struck with this spell an enemy's vision will adjust, preventing them from being blinded by it again. However, if they are struck again while blinded by this spell, then the light is overwhelming and paralyses them instead. actors.hero.spells.sunray.desc=The Cleric fires a ray of blinding light at a target, dealing %1$d-%2$d damage and blinding them for %3$d turns. Sunray always deals maximum damage to undead and demonic targets.\n\nAfter being struck with this spell an enemy's vision will adjust, preventing them from being blinded by it again. However, if they are struck again while blinded by this spell, then the light is overwhelming and paralyses them instead.
@@ -1255,7 +1261,7 @@ actors.hero.talent.beaming_ray.desc=The Cleric can cast _Beaming Ray_ from an em
actors.hero.talent.life_link.title=life link actors.hero.talent.life_link.title=life link
actors.hero.talent.life_link.desc=The Cleric can cast _Life Link_ between themselves and an empowered ally at the cost of 2 charges. This spell causes damage to be shared between the Cleric and their ally, and causes beneficial cleric spells of tier 3 or lower to apply to both if either is being affected.\n\n_+1:_ Life Link lasts for _6 turns_ and increases Power of Many's bonus to _-35% damage taken._\n\n_+2:_ Life Link lasts for _8 turns_ and increases Power of Many's bonus to _-40% damage taken._\n\n_+3:_ Life Link lasts for _10 turns_ and increases Power of Many's bonus to _-45% damage taken._\n\n_+4:_ Life Link lasts for _12 turns_ and increases Power of Many's bonus to _-50% damage taken._ actors.hero.talent.life_link.desc=The Cleric can cast _Life Link_ between themselves and an empowered ally at the cost of 2 charges. This spell causes damage to be shared between the Cleric and their ally, and causes beneficial cleric spells of tier 3 or lower to apply to both if either is being affected.\n\n_+1:_ Life Link lasts for _6 turns_ and increases Power of Many's bonus to _-35% damage taken._\n\n_+2:_ Life Link lasts for _8 turns_ and increases Power of Many's bonus to _-40% damage taken._\n\n_+3:_ Life Link lasts for _10 turns_ and increases Power of Many's bonus to _-45% damage taken._\n\n_+4:_ Life Link lasts for _12 turns_ and increases Power of Many's bonus to _-50% damage taken._
actors.hero.talent.stasis.title=Stasis actors.hero.talent.stasis.title=Stasis
actors.hero.talent.stasis.desc=The Cleric can cast _Stasis_ on an empowered ally at the cost of 1 charge. Stasis temporarily removes the ally from the dungeon and preserves the remaining time on all buffs, including Power of Many. The ally will reappear next to you when the effect ends. Stasis can be re-cast at no cost to end the effect early.\n\n_+1:_ Stasis lasts for a max of _40 turns._\n\n_+2:_ Stasis lasts for a max of _60 turns._\n\n_+3:_ Stasis lasts for a max of _80 turns._\n\n_+4:_ Stasis lasts for a max of _100 turns._\n\nThe Cleric can also cast Beaming Ray when an ally is in stasis to resummon them, or cast Life Link to pre-emptively apply its effect. An ally in stasis still benefits from life link if relevant. actors.hero.talent.stasis.desc=The Cleric can cast _Stasis_ on an empowered ally at the cost of 1 charge. Stasis temporarily removes the ally from the dungeon and preserves the remaining time on all buffs, including Power of Many. The ally will reappear next to you when the effect ends. Stasis can be re-cast instantly and at no cost to end the effect early.\n\n_+1:_ Stasis lasts for a max of _40 turns._\n\n_+2:_ Stasis lasts for a max of _60 turns._\n\n_+3:_ Stasis lasts for a max of _80 turns._\n\n_+4:_ Stasis lasts for a max of _100 turns._\n\nThe Cleric can also cast Beaming Ray when an ally is in stasis to resummon them, or cast Life Link to pre-emptively apply its effect. An ally in stasis still benefits from life link if relevant.
#universal #universal

View File

@@ -37,6 +37,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.BeamingRay; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.BeamingRay;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.LifeLinkSpell; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.LifeLinkSpell;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.Stasis;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
@@ -113,6 +114,10 @@ public class PowerOfMany extends ArmorAbility {
allyExists = true; allyExists = true;
} }
if (hero.buff(Stasis.StasisBuff.class) != null){
allyExists = true;
}
if (ally instanceof LightAlly){ if (ally instanceof LightAlly){
if (target == null){ if (target == null){
return; return;

View File

@@ -197,6 +197,9 @@ public abstract class ClericSpell {
if (cleric.hasTalent(Talent.LIFE_LINK)){ if (cleric.hasTalent(Talent.LIFE_LINK)){
spells.add(LifeLinkSpell.INSTANCE); spells.add(LifeLinkSpell.INSTANCE);
} }
if (cleric.hasTalent(Talent.STASIS)){
spells.add(Stasis.INSTANCE);
}
} }
@@ -231,6 +234,7 @@ public abstract class ClericSpell {
spells.add(SpiritForm.INSTANCE); spells.add(SpiritForm.INSTANCE);
spells.add(BeamingRay.INSTANCE); spells.add(BeamingRay.INSTANCE);
spells.add(LifeLinkSpell.INSTANCE); spells.add(LifeLinkSpell.INSTANCE);
spells.add(Stasis.INSTANCE);
return spells; return spells;
} }
} }

View File

@@ -0,0 +1,172 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2025 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.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LifeLink;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.effects.MagicMissile;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
import java.util.ArrayList;
public class Stasis extends ClericSpell {
public static Stasis INSTANCE = new Stasis();
@Override
public int icon() {
return HeroIcon.STASIS;
}
@Override
public String desc() {
return Messages.get(this, "desc", 20 + 20*Dungeon.hero.pointsInTalent(Talent.STASIS)) + "\n\n" + Messages.get(this, "charge_cost", (int)chargeUse(Dungeon.hero));
}
@Override
public boolean canCast(Hero hero) {
return super.canCast(hero)
&& hero.hasTalent(Talent.STASIS)
&& (PowerOfMany.getPoweredAlly() != null || hero.buff(StasisBuff.class) != null);
}
@Override
public float chargeUse(Hero hero) {
if (hero.buff(StasisBuff.class) != null){
return 0;
}
return super.chargeUse(hero);
}
@Override
public void onCast(HolyTome tome, Hero hero) {
onSpellCast(tome, hero);
if (hero.buff(StasisBuff.class) != null){
hero.sprite.operate(hero.pos);
hero.buff(StasisBuff.class).act();
return;
}
Char ally = PowerOfMany.getPoweredAlly();
hero.sprite.zap(ally.pos);
MagicMissile.boltFromChar(hero.sprite.parent, MagicMissile.LIGHT_MISSILE, ally.sprite, hero.pos, null);
//TODO preserve positive effects on ally, don't care about negative ones?
Actor.remove(ally);
Buff.prolong(hero, StasisBuff.class, 20 + 20*hero.pointsInTalent(Talent.STASIS)).stasisAlly = (Mob)ally;
Sample.INSTANCE.play(Assets.Sounds.TELEPORT);
if (hero.buff(LifeLink.class) != null && hero.buff(LifeLink.class).object == ally.id()){
hero.buff(LifeLink.class).detach();
}
//TODO will need to make affordances for lots of edge cases like earth golem, wards limits, ghost hero, PoE ally, others?
//TODO need code in beamign ray and life link to work here, also life link cleric spells?
hero.spendAndNext(Actor.TICK);
}
public static class StasisBuff extends FlavourBuff {
{
type = buffType.POSITIVE;
}
@Override
public int icon() {
return BuffIndicator.MANY_POWER;
}
@Override
public float iconFadePercent() {
int duration = 20 + 20*Dungeon.hero.pointsInTalent(Talent.STASIS);
return Math.max(0, (duration - visualcooldown()) / duration);
}
@Override
public String desc() {
return Messages.get(this, "desc", Messages.titleCase(stasisAlly.name()), dispTurns());
}
@Override
public boolean act() {
ArrayList<Integer> spawnPoints = new ArrayList<>();
for (int i = 0; i < PathFinder.NEIGHBOURS8.length; i++) {
int p = target.pos + PathFinder.NEIGHBOURS8[i];
if (Actor.findChar(p) == null && (Dungeon.level.passable[p] || Dungeon.level.avoid[p])) {
spawnPoints.add(p);
}
}
if (spawnPoints.isEmpty()){
spawnPoints.add(target.pos + PathFinder.NEIGHBOURS8[Random.Int(8)]);
}
stasisAlly.pos = Random.element(spawnPoints);
stasisAlly.clearTime();
GameScene.add(stasisAlly);
ScrollOfTeleportation.appear(stasisAlly, stasisAlly.pos);
Sample.INSTANCE.play(Assets.Sounds.TELEPORT);
return super.act();
}
Mob stasisAlly;
private static final String ALLY = "ally";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(ALLY, stasisAlly);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
stasisAlly = (Mob)bundle.get(ALLY);
}
}
}