From a9c179f4526c4d45bdae4e0aa5bb0a98a8e8d112 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Mon, 17 Feb 2025 15:06:28 -0500 Subject: [PATCH] v3.0.0: implemented life link, minus cleric spell sharing --- .../assets/messages/actors/actors.properties | 10 +- .../shatteredpixeldungeon/actors/Char.java | 12 +- .../actors/buffs/LifeLink.java | 8 ++ .../hero/abilities/cleric/PowerOfMany.java | 4 +- .../actors/hero/spells/ClericSpell.java | 4 + .../actors/hero/spells/LifeLinkSpell.java | 104 ++++++++++++++++++ 6 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/LifeLinkSpell.java diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index e8d0a6e7f..a98e4ad38 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -275,7 +275,7 @@ actors.buffs.levitation.name=levitating actors.buffs.levitation.desc=A magical force is levitating you over the ground, making you feel weightless.\n\nLevitating characters move silently and ignore all ground-based effects. Traps won't trigger, water won't put out fire, plants won't be trampled, roots will miss, and pits will be hovered over. Be careful, as all these things can come into effect the second the levitation ends!\n\nTurns of levitation remaining: %s. actors.buffs.lifelink.name=life link -actors.buffs.lifelink.desc=This character's life force is linked to another character nearby. Any damage taken is shared between them.\n\nWhenever this character takes damage, half of it will be dealt to the life link target instead.\n\nTurns of life link remaining: %s, or until the linked character dies. +actors.buffs.lifelink.desc=This character's life force is linked to another character nearby. Any damage taken is shared between them.\n\nWhenever this character takes damage, half of it will be dealt to the life link target instead.\n\nTurns of life link remaining: %s. actors.buffs.light.name=illuminated actors.buffs.light.desc=Even in the Darkest Dungeon, a steady light at your side is always comforting.\n\nLight helps keep darkness at bay, allowing you to see a reasonable distance despite the environment.\n\nTurns of illumination remaining: %s. @@ -710,6 +710,12 @@ actors.hero.spells.layonhands.name=lay on hands actors.hero.spells.layonhands.short_desc=Instantly heals an adjacent character or shields the Paladin. actors.hero.spells.layonhands.desc=The Paladin channels holy energy through their hands, healing or protecting whatever they touch.\n\nThe Paladin can cast this spell on any adjacent character to give them %1$d healing, or on themselves to gain %1$d shielding. Excess healing from this spell is converted into shielding.\n\nThis spell is cast instantaneously and can be cast repeatedly, but cannot grant more than three casts worth of shielding at a time. +actors.hero.spells.lifelinkspell.name=life link +actors.hero.spells.lifelinkspell.short_desc=Shares damage with an ally, and reduces their damage taken. +actors.hero.spells.lifelinkspell.desc=The Cleric strengthens the link between themselves and their empowered ally. With this empowered link, any damage taken will be shared between both the hero and their ally for %1$d turns, and Power of Many's defensive boost will be increased to -%2$d%% damage taken. Note that the damage is shared after armor, but before Power of Many's damage reduction.\n\nWhile this effect is active, beneficial Cleric spells of T3 or below will apply to both the Cleric and their ally if they would have just applied to one of them. +actors.hero.spells.lifelinkspell$lifelinkspellbuff.name=life link protection +actors.hero.spells.lifelinkspell$lifelinkspellbuff.desc=The Cleric recently cast Life Link on this ally.\n\nIn addition to Life Link's regular benefits, this character also takes reduced damage, any positive Cleric spells are applied to both the Cleric and this ally instead of just one, and Power of Many won't expire while this buff is active.\n\nTurns Remaining %s. + actors.hero.spells.mindform.name=mind form actors.hero.spells.mindform.short_desc=Assigns Trinity to a wand or thrown weapon. actors.hero.spells.mindform.desc=The Cleric chooses a wand or thrown weapons that they have identified this run and imbues Trinity with its effect.\n\nWhen Trinity is used, The Cleric can make a single attack as if they were using the chosen item at +%d. This will trigger all effects that using that type of item would normally trigger.\n\nThis spell replaces any other mind effect that Trinity is currently imbued with. @@ -1247,7 +1253,7 @@ actors.hero.talent.spirit_form.desc=The Cleric can cast _Spirit Form_, which imb 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 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.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.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. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java index 904781152..deaf5f32e 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/Char.java @@ -84,6 +84,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.warrior.En 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.LifeLinkSpell; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.ShieldOfLight; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Brute; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.CrystalSpire; @@ -462,10 +463,6 @@ public abstract class Char extends Actor { dmg *= 0.925f - 0.075f*Dungeon.hero.pointsInTalent(Talent.AURA_OF_PROTECTION); } - if (enemy.buff(PowerOfMany.PowerBuff.class) != null){ - dmg *= 0.75f; - } - if (enemy.buff(MonkEnergy.MonkAbility.Meditate.MeditateResistance.class) != null){ dmg *= 0.2f; } @@ -802,7 +799,12 @@ public abstract class Char extends Actor { && Dungeon.hero.buff(AuraOfProtection.AuraBuff.class) != null) { damage *= 0.925f - 0.075f*Dungeon.hero.pointsInTalent(Talent.AURA_OF_PROTECTION); } - if (buff(PowerOfMany.PowerBuff.class) != null){ + } + + if (buff(PowerOfMany.PowerBuff.class) != null){ + if (buff(LifeLinkSpell.LifeLinkSpellBuff.class) != null){ + damage *= 0.70f - 0.05f*Dungeon.hero.pointsInTalent(Talent.LIFE_LINK); + } else { damage *= 0.75f; } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/LifeLink.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/LifeLink.java index af390e59e..87711696b 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/LifeLink.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/buffs/LifeLink.java @@ -21,8 +21,10 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.buffs; +import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Actor; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent; import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; import com.watabou.noosa.Image; import com.watabou.utils.Bundle; @@ -73,4 +75,10 @@ public class LifeLink extends FlavourBuff { icon.hardlight(1, 0, 1); } + @Override + public float iconFadePercent() { + int duration = 4 + 2*Dungeon.hero.pointsInTalent(Talent.LIFE_LINK); + return Math.max(0, (duration - visualcooldown()) / duration); + } + } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/cleric/PowerOfMany.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/cleric/PowerOfMany.java index 31b3561a0..5181b77d1 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/cleric/PowerOfMany.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/abilities/cleric/PowerOfMany.java @@ -36,6 +36,7 @@ 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.hero.spells.LifeLinkSpell; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob; import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly; import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; @@ -208,7 +209,8 @@ public class PowerOfMany extends ArmorAbility { @Override public boolean act() { - if (target.buff(BeamingRay.BeamingRayBoost.class) != null){ + if (target.buff(BeamingRay.BeamingRayBoost.class) != null + || target.buff(LifeLinkSpell.LifeLinkSpellBuff.class) != null){ spend(TICK); return true; } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java index 1e32a27d3..874d0f93d 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/ClericSpell.java @@ -187,6 +187,9 @@ public abstract class ClericSpell { if (cleric.hasTalent(Talent.BEAMING_RAY)){ spells.add(BeamingRay.INSTANCE); } + if (cleric.hasTalent(Talent.LIFE_LINK)){ + spells.add(LifeLinkSpell.INSTANCE); + } } @@ -220,6 +223,7 @@ public abstract class ClericSpell { spells.add(MindForm.INSTANCE); spells.add(SpiritForm.INSTANCE); spells.add(BeamingRay.INSTANCE); + spells.add(LifeLinkSpell.INSTANCE); return spells; } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/LifeLinkSpell.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/LifeLinkSpell.java new file mode 100644 index 000000000..a0bfaa9b1 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/spells/LifeLinkSpell.java @@ -0,0 +1,104 @@ +/* + * 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 + */ + +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.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.effects.Beam; +import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.HolyTome; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator; +import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon; + +public class LifeLinkSpell extends ClericSpell { + + public static LifeLinkSpell INSTANCE = new LifeLinkSpell(); + + @Override + public int icon() { + return HeroIcon.LIFE_LINK; + } + + @Override + public String desc() { + return Messages.get(this, "desc", 4 + 2*Dungeon.hero.pointsInTalent(Talent.LIFE_LINK), 30 + 5*Dungeon.hero.pointsInTalent(Talent.LIFE_LINK)) + "\n\n" + Messages.get(this, "charge_cost", (int)chargeUse(Dungeon.hero)); + } + + @Override + public boolean canCast(Hero hero) { + return super.canCast(hero) + && hero.hasTalent(Talent.LIFE_LINK) + && PowerOfMany.getPoweredAlly() != null; + } + + @Override + public float chargeUse(Hero hero) { + return 2; + } + + @Override + public void onCast(HolyTome tome, Hero hero) { + + Char ally = PowerOfMany.getPoweredAlly(); + + hero.sprite.zap(ally.pos); + hero.sprite.parent.add( + new Beam.HealthRay(hero.sprite.center(), ally.sprite.center())); + + int duration = 4 + 2*hero.pointsInTalent(Talent.LIFE_LINK); + + Buff.prolong(hero, LifeLink.class, duration).object = ally.id(); + Buff.prolong(ally, LifeLink.class, duration).object = hero.id(); + + Buff.prolong(ally, LifeLinkSpellBuff.class, duration); + + hero.spendAndNext(Actor.TICK); + + onSpellCast(tome, hero); + + } + + public static class LifeLinkSpellBuff extends FlavourBuff{ + + { + type = buffType.POSITIVE; + } + + @Override + public int icon() { + return BuffIndicator.HOLY_ARMOR; + } + + @Override + public float iconFadePercent() { + int duration = 4 + 2*Dungeon.hero.pointsInTalent(Talent.LIFE_LINK); + return Math.max(0, (duration - visualcooldown()) / duration); + } + } +}