v3.0.0: implemented the beaming ray talent/spell

This commit is contained in:
Evan Debenham
2025-02-16 16:08:16 -05:00
parent b9728d5a66
commit 641172bfa2
5 changed files with 217 additions and 3 deletions

View File

@@ -610,6 +610,14 @@ actors.hero.spells.auraofprotection.desc=The Paladin begins radiating protective
actors.hero.spells.auraofprotection$aurabuff.name=aura of protection
actors.hero.spells.auraofprotection$aurabuff.desc=The Paladin is radiating protective energy around themselves.\n\nAny nearby ally (including the Paladin themselves) takes reduced damage and gains the effect of the Paladin's armor glyph with boosted power.\n\nTurns Remaining: %s.
actors.hero.spells.beamingray.name=beaming ray
actors.hero.spells.beamingray.no_space=There's no space for your ally to appear there.
actors.hero.spells.beamingray.out_of_range=That location is out of range.
actors.hero.spells.beamingray.short_desc=Teleports your ally and grants bonus damage.
actors.hero.spells.beamingray.desc=The Cleric channels the power on their ally into a ray of light that beams them to a location. If the ray collides with an enemy it will place the ally nearby and direct them towards the enemy.\n\nThe ray has a maximum range of %1$d tiles, and will increase Power of Many's bonus damage to +%2$d%% for 10 turns against the closest enemy within 4 tiles.
actors.hero.spells.beamingray$beamingrayboost.name=beaming ray
actors.hero.spells.beamingray$beamingrayboost.desc=This ally was recently teleported by Beaming Ray, giving them bonus damage against the nearest enemy after the teleport. Power of Many also won't expire when this buff is active.\n\nTurns Remaining: %s.
actors.hero.spells.blessspell.name=bless
actors.hero.spells.blessspell.short_desc=Blesses & shields Cleric, blesses & heals others.
actors.hero.spells.blessspell.desc=The Cleric places a holy blessing on themselves or another character they can see.\n\nWhen cast on themselves the effect is weaker, granting %1$d turns of bless and %2$d barrier.\n\nWhen cast on other characters the spell is more powerful, granting %3$d turns of bless and %4$d healing. Excess healing is converted into barrier.
@@ -1237,7 +1245,7 @@ actors.hero.talent.spirit_form.title=spirit form
actors.hero.talent.spirit_form.desc=The Cleric can cast _Spirit Form_, which imbues Trinity with the power of a ring or artifact that the Cleric has identified this run (except the holy tome), at the cost of 4 charges.\n\n_+1:_ When Trinity is used, the Cleric will gain a ring's effect at _+1_ for 20 turns, or will trigger an artifact's effect at _+4._\n\n_+2:_ When Trinity is used, the Cleric will gain a ring's effect at _+2_ for 20 turns, or will trigger an artifact's effect at _+6._\n\n_+3:_ When Trinity is used, the Cleric will gain a ring's effect at _+3_ for 20 turns, or will trigger an artifact's effect at _+8._\n\n_+4:_ When Trinity is used, the Cleric will gain a ring's effect at _+4_ for 20 turns, or will trigger an artifact's effect at _+10._\n\nTrinity can only be imbued with one spirit effect at a time. Artifacts have varied effects and armor charge costs when triggered by Trinity.
actors.hero.talent.beaming_ray.title=beaming ray
actors.hero.talent.beaming_ray.desc=The Cleric can cast _Beaming Ray_ from an empowered ally at the cost of 1 charge. This ray can go through walls and teleports the ally to a targeted location. If an enemy is at that location, the ally appears next to the enemy and will target them.\n\n_+1:_ Beaming Ray has a max range of 4 tiles, and grants your ally an additional _+10% damage_ against the closest enemy for 10 turns.\n\n_+2:_ Beaming Ray has a max range of 8 tiles, and grants your ally an additional _+15% damage_ against the closest enemy for 10 turns.\n\n_+3:_ Beaming Ray has a max range of 12 tiles, and grants your ally an additional _+20% damage_ against the closest enemy for 10 turns.\n\n_+4:_ Beaming Ray has a max range of 16 tiles, and grants your ally an additional _+25% damage_ against the closest enemy for 10 turns.
actors.hero.talent.beaming_ray.desc=The Cleric can cast _Beaming Ray_ from an empowered ally at the cost of 1 charge. This ray can go through walls and teleports the ally to a targeted location. If an enemy is at that location, the ally appears next to the enemy and will target them.\n\n_+1:_ Beaming Ray has a max range of 4 tiles, and increases Power of Many's bonus to _+35% damage_ against the closest enemy for 10 turns.\n\n_+2:_ Beaming Ray has a max range of 8 tiles, and increases Power of Many's bonus to _+40% damage_ against the closest enemy for 10 turns.\n\n_+3:_ Beaming Ray has a max range of 12 tiles, and increases Power of Many's bonus to _+45% damage_ against the closest enemy for 10 turns.\n\n_+4:_ Beaming Ray has a max range of 16 tiles, and increases Power of Many's bonus to _+50% damage_ against the closest enemy for 10 turns.
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 spells to apply to both if either is being targeted.\n\n_+1:_ Life Link lasts for _6 turns_ and grants the ally an additional _-10% damage taken._\n\n_+2:_ Life Link lasts for _8 turns_ and grants the ally an additional _-15% damage taken._\n\n_+3:_ Life Link lasts for _10 turns_ and grants the ally an additional _-20% damage taken._\n\n_+4:_ Life Link lasts for _12 turns_ and grants the ally an additional _-25% damage taken._\n\nSpells that can be shared are: shield of light, divine sense, bless, hallowed ground, mnemonic prayer, lay on hands, aura of protection.
actors.hero.talent.stasis.title=Stasis

View File

@@ -82,6 +82,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Ch
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.DeathMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.warrior.Endure;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.AuraOfProtection;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.BeamingRay;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.GuidingLight;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.ShieldOfLight;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Brute;
@@ -427,7 +428,12 @@ public abstract class Char extends Actor {
}
if (buff( PowerOfMany.PowerBuff.class) != null){
dmg *= 1.25f;
if (buff( BeamingRay.BeamingRayBoost.class) != null
&& buff( BeamingRay.BeamingRayBoost.class).object == enemy.id()){
dmg *= 1.3f + 0.05f*Dungeon.hero.pointsInTalent(Talent.BEAMING_RAY);
} else {
dmg *= 1.25f;
}
}
for (ChampionEnemy buff : buffs(ChampionEnemy.class)){

View File

@@ -35,6 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.BeamingRay;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
@@ -90,6 +91,10 @@ public class PowerOfMany extends ArmorAbility {
}
}
public boolean useTargeting(){
return false;
}
@Override
protected void activate(ClassArmor armor, Hero hero, Integer target) {
@@ -167,7 +172,7 @@ public class PowerOfMany extends ArmorAbility {
return new Talent[]{Talent.BEAMING_RAY, Talent.LIFE_LINK, Talent.STASIS, Talent.HEROIC_ENERGY};
}
private static Char getPoweredAlly(){
public static Char getPoweredAlly(){
for (Char ch : Actor.chars()){
if (ch.buff(PowerBuff.class) != null){
return ch;
@@ -201,6 +206,15 @@ public class PowerOfMany extends ArmorAbility {
else target.sprite.remove(CharSprite.State.GLOWING);
}
@Override
public boolean act() {
if (target.buff(BeamingRay.BeamingRayBoost.class) != null){
spend(TICK);
return true;
}
return super.act();
}
@Override
public void detach() {
super.detach();

View File

@@ -0,0 +1,178 @@
/*
* 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.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff;
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.actors.mobs.npcs.DirectableAlly;
import com.shatteredpixel.shatteredpixeldungeon.effects.Beam;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTilemap;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
public class BeamingRay extends TargetedClericSpell {
public static BeamingRay INSTANCE = new BeamingRay();
@Override
public int icon() {
return HeroIcon.BEAMING_RAY;
}
@Override
public String desc() {
return Messages.get(this, "desc", 4*Dungeon.hero.pointsInTalent(Talent.BEAMING_RAY), 30 + 5*Dungeon.hero.pointsInTalent(Talent.BEAMING_RAY)) + "\n\n" + Messages.get(this, "charge_cost", (int)chargeUse(Dungeon.hero));
}
@Override
public int targetingFlags() {
return Ballistica.STOP_TARGET;
}
@Override
public boolean canCast(Hero hero) {
return super.canCast(hero)
&& hero.hasTalent(Talent.BEAMING_RAY)
&& PowerOfMany.getPoweredAlly() != null;
}
@Override
protected void onTargetSelected(HolyTome tome, Hero hero, Integer target) {
if (target == null){
return;
}
Char ally = PowerOfMany.getPoweredAlly();
int telePos = target;
if (Dungeon.level.solid[telePos] || !Dungeon.level.heroFOV[telePos] || Actor.findChar(telePos) != null){
telePos = -1;
for (int i : PathFinder.NEIGHBOURS8){
if (Actor.findChar(target+i) == null && !Dungeon.level.solid[target+i] && Dungeon.level.heroFOV[target+i]){
if (telePos == -1 || Dungeon.level.trueDistance(telePos, ally.pos) > Dungeon.level.trueDistance(target+i, ally.pos)){
telePos = target+i;
}
}
}
}
if (telePos == -1){
GLog.w(Messages.get(this, "no_space"));
return;
}
if (Dungeon.level.distance(ally.pos, telePos) > 4*hero.pointsInTalent(Talent.BEAMING_RAY)){
GLog.w(Messages.get(this, "out_of_range"));
return;
}
Char chTarget = null;
if (Actor.findChar(target) != null && Actor.findChar(target).alignment == Char.Alignment.ENEMY){
chTarget = Actor.findChar(target);
}
hero.sprite.zap(telePos);
hero.sprite.parent.add(
new Beam.SunRay(ally.sprite.center(), DungeonTilemap.raisedTileCenterToWorld(telePos)));
ScrollOfTeleportation.appear(ally, telePos);
if (chTarget == null){
for (Char ch : Actor.chars()){
if (ch.alignment == Char.Alignment.ENEMY && Dungeon.level.distance(ch.pos, telePos) <= 4){
if (chTarget == null || Dungeon.level.trueDistance(chTarget.pos, ally.pos) < Dungeon.level.trueDistance(ch.pos, ally.pos)) {
chTarget = ch;
}
}
}
}
if (chTarget != null) {
if (ally instanceof DirectableAlly) {
((DirectableAlly) ally).targetChar(chTarget);
} else if (ally instanceof Mob) {
((Mob) ally).aggro(chTarget);
}
FlavourBuff.prolong(ally, BeamingRayBoost.class, BeamingRayBoost.DURATION).object = chTarget.id();
} else {
//just the buff with no target
FlavourBuff.prolong(ally, BeamingRayBoost.class, BeamingRayBoost.DURATION);
}
hero.spendAndNext(Actor.TICK);
Dungeon.observe();
GameScene.updateFog();
onSpellCast(tome, hero);
}
public static class BeamingRayBoost extends FlavourBuff {
{
type = buffType.POSITIVE;
}
public int object = 0;
public static final float DURATION = 10f;
private static final String OBJECT = "object";
@Override
public void storeInBundle( Bundle bundle ) {
super.storeInBundle( bundle );
bundle.put( OBJECT, object );
}
@Override
public void restoreFromBundle( Bundle bundle ) {
super.restoreFromBundle( bundle );
object = bundle.getInt( OBJECT );
}
@Override
public int icon() {
return BuffIndicator.HOLY_WEAPON;
}
@Override
public float iconFadePercent() {
return Math.max(0, (DURATION - visualcooldown()) / DURATION);
}
}
}

View File

@@ -184,6 +184,10 @@ public abstract class ClericSpell {
spells.add(SpiritForm.INSTANCE);
}
if (cleric.hasTalent(Talent.BEAMING_RAY)){
spells.add(BeamingRay.INSTANCE);
}
}
return spells;
@@ -212,6 +216,10 @@ public abstract class ClericSpell {
spells.add(DivineIntervention.INSTANCE);
spells.add(Judgement.INSTANCE);
spells.add(Flash.INSTANCE);
spells.add(BodyForm.INSTANCE);
spells.add(MindForm.INSTANCE);
spells.add(SpiritForm.INSTANCE);
spells.add(BeamingRay.INSTANCE);
return spells;
}
}