v3.0.0: implemented the base power of many ability

This commit is contained in:
Evan Debenham
2025-02-13 16:36:32 -05:00
parent 40295adc24
commit af7632f9c7
14 changed files with 481 additions and 22 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -370,6 +370,7 @@ actors.buffs.preparation.assassinated=assassinated
actors.buffs.prismaticguard.name=prismatic Guard
actors.buffs.prismaticguard.desc=You are being guarded by a prismatic image which is currently inactive. When enemies are present the prismatic image will spring to action and protect you!\n\nWhile inactive, the prismatic image will steadily recover from any damage it has taken.\n\nCurrent HP: %d/%d.
actors.buffs.prismaticguard.desc_many=This prismatic image is also benefitting from Power of Many. Turns Remaining: %d.
actors.buffs.recharging.name=recharging
actors.buffs.recharging.desc=Energy is coursing through you, improving the rate that your wands and staffs charge.\n\nEach turn this buff will increase current charge by one quarter, in addition to regular recharge.\n\nTurns of recharging remaining: %s.
@@ -577,8 +578,20 @@ actors.hero.abilities.cleric.trinity$wnditemconfirm.body=Assign to body form
actors.hero.abilities.cleric.trinity$wnditemconfirm.mind=Assign to mind form
actors.hero.abilities.cleric.trinity$wnditemconfirm.spirit=Assign to spirit form
actors.hero.abilities.cleric.powerofmany.name=power of many
actors.hero.abilities.cleric.powerofmany.prompt_default=Choose an ally or location
actors.hero.abilities.cleric.powerofmany.prompt_ally=Direct your light ally
actors.hero.abilities.cleric.powerofmany.ally_exists=You already have an empowered ally.
actors.hero.abilities.cleric.powerofmany.no_vision=You can't target a location you can't see.
actors.hero.abilities.cleric.powerofmany.only_allies=You can only empower allies.
actors.hero.abilities.cleric.powerofmany.short_desc=The Cleric channels the _Power of Many_, empowering an existing ally or creating a new one.
actors.hero.abilities.cleric.powerofmany.desc=The Cleric channels the _Power of Many_, which either empowers an existing ally or creates a new empowered one for 100 turns.\n\nWhile empowered by Power of Many, any ally deals +25% damage, takes -25% damage, and shares their vision with the Cleric. They also gain a burst of 25 shielding when the ability is used.\n\nThe Cleric also gains three new spells that can only be cast with an empowered ally. Power of Many won't end while one of these spells is active.
actors.hero.abilities.cleric.powerofmany.desc=The Cleric channels the _Power of Many_, which either empowers an existing ally or creates a new empowered one for 100 turns. Targeting an empty space creates a new ally.\n\nWhile empowered by Power of Many, any ally deals +25% damage, takes -25% damage, and shares their vision with the Cleric. They also gain 25 shielding when the ability is used.\n\nThe Cleric also gains three new spells that can only be cast with an empowered ally. Power of Many won't end while one of these spells is active.
actors.hero.abilities.cleric.powerofmany$powerbuff.name=power of many
actors.hero.abilities.cleric.powerofmany$powerbuff.desc=This ally has been empowered by Power of Many, granting them +25%% damage dealt and -25%% damage taken. They will also share their vision range with the Cleric, and can benefit from power of many spells.\n\nPower of Many will persist when allies such as prismatic images hide away, but its duration will still count down.\n\nTurns Remaining: %s.
actors.hero.abilities.cleric.powerofmany$lightally.name=light ally
actors.hero.abilities.cleric.powerofmany$lightally.direct_defend=Your ally moves to that position.
actors.hero.abilities.cleric.powerofmany$lightally.direct_follow=Your ally moves to follow you.
actors.hero.abilities.cleric.powerofmany$lightally.direct_attack=Your ally moves to attack!
actors.hero.abilities.cleric.powerofmany$lightally.desc=A copy of one of the Cleric's adventuring companions, made out of solid light. While it has the shape of another hero, it does not have any of their unique abilities. This ally will persist for as long as Power of Many is applied to them.\n\nThis ally can be directed at no cost by re-using the Power of Many armor ability.
actors.hero.abilities.ratmogrify.name=ratmogrify
@@ -1228,7 +1241,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.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
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 itself. 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 in in stasis to resummon them, or cast Life Link to pre-emptively apply the 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 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

View File

@@ -1540,6 +1540,7 @@ items.wands.wandoflivingearth.bmage_desc=When _the Battlemage_ strikes an enemy
items.wands.wandoflivingearth.eleblast_desc=An elemental blast with a staff of living earth deals 50% damage, and heals an active earthen guardian for each enemy hit.
items.wands.wandoflivingearth$rockarmor.name=rock armor
items.wands.wandoflivingearth$rockarmor.desc=Magical rocks are surrounding your body, when you are attacked they will attempt to block for you, and will reduce the damage you take by 50%%. Each damage blocked scrapes away some of the rock however.\n\nRemaining Armor: %1$d.\n\nIf enough rock is built around you, the next zap from your wand of living earth will cause the rocks to form up into a guardian which will fight with you.\n\nArmor needed for Guardian: %2$d.
items.wands.wandoflivingearth$rockarmor.desc_many=This earthen guardian is also benefitting from Power of Many. Turns Remaining: %d.
items.wands.wandoflivingearth$earthguardian.name=earthen guardian
items.wands.wandoflivingearth$earthguardian.desc=The rocks from your wand of living earth have formed into a protective earthen guardian! This rocky protector will attack nearby enemies, which will force them to attack the guardian instead of you. When all nearby threats are gone, the guardian will re-form around you, and will return when you next use your wand.
items.wands.wandoflivingearth$earthguardian.wand_info=The guardian's defensive power is tied to the level of your wand. It currently blocks _%1$d-%2$d damage._

View File

@@ -34,6 +34,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.RevealedArea;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Terror;
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.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.DivineSense;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic;
@@ -1002,7 +1003,8 @@ public class Dungeon {
for (Char ch : Actor.chars()){
if (ch instanceof WandOfWarding.Ward
|| ch instanceof WandOfRegrowth.Lotus
|| ch instanceof SpiritHawk.HawkAlly){
|| ch instanceof SpiritHawk.HawkAlly
|| ch.buff(PowerOfMany.PowerBuff.class) != null){
x = ch.pos % level.width();
y = ch.pos / level.width();

View File

@@ -77,6 +77,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Weakness;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Challenge;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.DeathMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.warrior.Endure;
@@ -425,6 +426,10 @@ public abstract class Char extends Actor {
dmg *= 1.5f;
}
if (buff( PowerOfMany.PowerBuff.class) != null){
dmg *= 1.25f;
}
for (ChampionEnemy buff : buffs(ChampionEnemy.class)){
dmg *= buff.meleeDamageFactor();
}
@@ -451,6 +456,10 @@ 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;
}
@@ -777,12 +786,18 @@ public abstract class Char extends Actor {
}
}
//temporarily assign to a float to avoid rounding a bunch
float damage = dmg;
//if dmg is from a character we already reduced it in defenseProc
if (!(src instanceof Char)) {
if (Dungeon.hero.alignment == alignment
&& Dungeon.level.distance(pos, Dungeon.hero.pos) <= 2
&& Dungeon.hero.buff(AuraOfProtection.AuraBuff.class) != null) {
dmg *= 0.925f - 0.075f*Dungeon.hero.pointsInTalent(Talent.AURA_OF_PROTECTION);
damage *= 0.925f - 0.075f*Dungeon.hero.pointsInTalent(Talent.AURA_OF_PROTECTION);
}
if (buff(PowerOfMany.PowerBuff.class) != null){
damage *= 0.75f;
}
}
@@ -805,10 +820,10 @@ public abstract class Char extends Actor {
Buff.detach(this, MagicalSleep.class);
}
if (this.buff(Doom.class) != null && !isImmune(Doom.class)){
dmg *= 1.67f;
damage *= 1.67f;
}
if (alignment != Alignment.ALLY && this.buff(DeathMark.DeathMarkTracker.class) != null){
dmg *= 1.25f;
damage *= 1.25f;
}
if (buff(Sickle.HarvestBleedTracker.class) != null){
@@ -827,15 +842,19 @@ public abstract class Char extends Actor {
}
}
for (ChampionEnemy buff : buffs(ChampionEnemy.class)){
dmg = (int) Math.ceil(dmg * buff.damageTakenFactor());
}
Class<?> srcClass = src.getClass();
if (isImmune( srcClass )) {
dmg = 0;
damage = 0;
} else {
dmg = Math.round( dmg * resist( srcClass ));
damage *= resist( srcClass );
}
dmg = Math.round(damage);
//we ceil these specifically to favor the player vs. champ dmg reduction
// most important vs. giant champions in the earlygame
for (ChampionEnemy buff : buffs(ChampionEnemy.class)){
dmg = (int) Math.ceil(dmg * buff.damageTakenFactor());
}
//TODO improve this when I have proper damage source logic

View File

@@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon.actors.buffs;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.PrismaticImage;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
@@ -41,6 +42,8 @@ public class PrismaticGuard extends Buff {
}
private float HP;
private float powerOfManyTurns = 0;
@Override
public boolean act() {
@@ -72,6 +75,9 @@ public class PrismaticGuard extends Buff {
if (bestPos != -1) {
PrismaticImage pris = new PrismaticImage();
pris.duplicate(hero, (int)Math.floor(HP) );
if (powerOfManyTurns > 0){
Buff.affect(pris, PowerOfMany.PowerBuff.class, powerOfManyTurns);
}
pris.state = pris.HUNTING;
GameScene.add(pris, 1);
ScrollOfTeleportation.appear(pris, bestPos);
@@ -86,16 +92,32 @@ public class PrismaticGuard extends Buff {
spend(TICK);
}
LockedFloor lock = target.buff(LockedFloor.class);
if (HP < maxHP() && Regeneration.regenOn()){
HP += 0.1f;
}
if (powerOfManyTurns > 0){
powerOfManyTurns--;
if (powerOfManyTurns <= 0){
powerOfManyTurns = 0;
BuffIndicator.refreshHero();
}
}
return true;
}
public void set( int HP ){
this.HP = HP;
powerOfManyTurns = 0;
}
public void set( PrismaticImage img){
this.HP = img.HP;
if (img.buff(PowerOfMany.PowerBuff.class) != null){
powerOfManyTurns = img.buff(PowerOfMany.PowerBuff.class).cooldown()+1;
} else {
powerOfManyTurns = 0;
}
}
public int maxHP(){
@@ -105,6 +127,10 @@ public class PrismaticGuard extends Buff {
public static int maxHP( Hero hero ){
return 10 + (int)Math.floor(hero.lvl * 2.5f); //half of hero's HP
}
public boolean isEmpowered(){
return powerOfManyTurns > 0;
}
@Override
public int icon() {
@@ -113,7 +139,11 @@ public class PrismaticGuard extends Buff {
@Override
public void tintIcon(Image icon) {
icon.hardlight(1f, 1f, 2f);
if (isEmpowered()){
icon.hardlight(3f, 3f, 2f);
} else {
icon.hardlight(1f, 1f, 2f);
}
}
@Override
@@ -128,20 +158,27 @@ public class PrismaticGuard extends Buff {
@Override
public String desc() {
return Messages.get(this, "desc", (int)HP, maxHP());
String desc = Messages.get(this, "desc", (int)HP, maxHP());
if (isEmpowered()){
desc += "\n\n" + Messages.get(this, "desc_many", (int)powerOfManyTurns);
}
return desc;
}
private static final String HEALTH = "hp";
private static final String POWER_TURNS = "power_turns";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(HEALTH, HP);
bundle.put(POWER_TURNS, powerOfManyTurns);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
HP = bundle.getFloat(HEALTH);
powerOfManyTurns = bundle.getFloat(POWER_TURNS);
}
}

View File

@@ -21,17 +21,140 @@
package com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric;
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.AllyBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Barrier;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.FlavourBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Invisibility;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.PrismaticGuard;
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.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.DirectableAlly;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.effects.particles.ShaftParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfTeleportation;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfLivingEarth;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.HeroSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.MobSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.TextureFilm;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Random;
public class PowerOfMany extends ArmorAbility {
@Override
public float chargeUse(Hero hero) {
if (getPoweredAlly() instanceof LightAlly){
return 0;
}
return super.chargeUse(hero);
}
@Override
public String targetingPrompt() {
Char ally = getPoweredAlly();
boolean allyExists = ally != null;
if (Dungeon.hero.buff(PrismaticGuard.class) != null
&& Dungeon.hero.buff(PrismaticGuard.class).isEmpowered()){
allyExists = true;
}
if (Dungeon.hero.buff(WandOfLivingEarth.RockArmor.class) != null
&& Dungeon.hero.buff(WandOfLivingEarth.RockArmor.class).isEmpowered()){
allyExists = true;
}
if (ally instanceof LightAlly){
return Messages.get(this, "prompt_ally");
} else if (!allyExists){
return Messages.get(this, "prompt_default");
} else {
return null;
}
}
@Override
protected void activate(ClassArmor armor, Hero hero, Integer target) {
Char ally = getPoweredAlly();
boolean allyExists = ally != null;
if (hero.buff(PrismaticGuard.class) != null
&& hero.buff(PrismaticGuard.class).isEmpowered()){
allyExists = true;
}
if (hero.buff(WandOfLivingEarth.RockArmor.class) != null
&& hero.buff(WandOfLivingEarth.RockArmor.class).isEmpowered()){
allyExists = true;
}
if (ally instanceof LightAlly){
if (target == null){
return;
} else {
((LightAlly) ally).directTocell(target);
}
} else if (allyExists) {
GLog.w( Messages.get(this, "ally_exists"));
} else {
if (target == null){
return;
}
if (!Dungeon.level.heroFOV[target]){
GLog.w(Messages.get(this, "no_vision"));
return;
}
//pre-calculate as cost becomes 0 if light ally starts to exist
float chargeUse = chargeUse(hero);
Char ch = Actor.findChar(target);
if (ch != null){
if (ch.alignment != Char.Alignment.ALLY || ch == Dungeon.hero){
GLog.w(Messages.get(this, "only_allies"));
return;
}
} else {
ch = new LightAlly(hero.lvl);
ch.pos = target;
GameScene.add((Mob) ch);
ScrollOfTeleportation.appear(ch, ch.pos);
}
Buff.affect(ch, PowerBuff.class, 100f);
Buff.affect(ch, Barrier.class).setShield(25);
armor.charge -= chargeUse;
armor.updateQuickslot();
hero.sprite.zap(target);
Sample.INSTANCE.play(Assets.Sounds.CHARGEUP);
Invisibility.dispel();
hero.spendAndNext(Actor.TICK);
}
}
@Override
@@ -44,4 +167,216 @@ public class PowerOfMany extends ArmorAbility {
return new Talent[]{Talent.BEAMING_RAY, Talent.LIFE_LINK, Talent.STASIS, Talent.HEROIC_ENERGY};
}
private static Char getPoweredAlly(){
for (Char ch : Actor.chars()){
if (ch.buff(PowerBuff.class) != null){
return ch;
}
}
return null;
}
public static class PowerBuff extends FlavourBuff {
public static float DURATION = 100f;
{
type = buffType.POSITIVE;
announced = true;
}
@Override
public int icon() {
return BuffIndicator.MANY_POWER;
}
@Override
public float iconFadePercent() {
return Math.max(0, (DURATION - visualcooldown()) / DURATION);
}
@Override
public void fx(boolean on) {
if (on) target.sprite.add(CharSprite.State.GLOWING);
else target.sprite.remove(CharSprite.State.GLOWING);
}
@Override
public void detach() {
super.detach();
if (target instanceof LightAlly){
target.die(this);
}
Dungeon.observe();
GameScene.updateFog();
}
}
public static class LightAlly extends DirectableAlly {
{
spriteClass = LightAllySprite.class;
HP = HT = 80;
immunities.add(AllyBuff.class);
properties.add(Property.INORGANIC);
}
HeroClass cls;
public LightAlly(){
super();
cls = HeroClass.values()[Random.Int(5)];
}
public LightAlly(int heroLevel ){
this();
defenseSkill = heroLevel + 5; //equal to base hero defense skill
}
@Override
protected boolean act() {
int oldPos = pos;
boolean result = super.act();
//partially simulates how the hero switches to idle animation
if ((pos == target || oldPos == pos) && sprite.looping()){
sprite.idle();
}
return result;
}
@Override
public void defendPos(int cell) {
GLog.i(Messages.get(this, "direct_defend"));
super.defendPos(cell);
}
@Override
public void followHero() {
GLog.i(Messages.get(this, "direct_follow"));
super.followHero();
}
@Override
public void targetChar(Char ch) {
GLog.i(Messages.get(this, "direct_attack"));
super.targetChar(ch);
}
@Override
public int attackSkill(Char target) {
return defenseSkill+5; //equal to base hero attack skill
}
@Override
public int damageRoll() {
return Random.NormalIntRange(5, 30); //+0 greatsword
}
@Override
public int drRoll() {
return super.drRoll() + Random.NormalIntRange(1, 5); //+0 plate
}
@Override
public float speed() {
float speed = super.speed();
//moves 2 tiles at a time when returning to the hero
if (state == WANDERING
&& defendingPos == -1
&& Dungeon.level.distance(pos, Dungeon.hero.pos) > 1){
speed *= 2;
}
return speed;
}
@Override
public CharSprite sprite() {
CharSprite sprite = super.sprite();
((LightAllySprite)sprite).setup(cls);
return sprite;
}
private static final String HERO_CLS = "hero_cls";
private static final String DEF_SKILL = "def_skill";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(HERO_CLS, cls);
bundle.put(DEF_SKILL, defenseSkill);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
cls = bundle.getEnum(HERO_CLS, HeroClass.class);
defenseSkill = bundle.getInt(DEF_SKILL);
}
}
public static class LightAllySprite extends MobSprite {
public LightAllySprite() {
super();
setup(HeroClass.values()[Random.Int(5)]);
}
public void setup(HeroClass cls){
texture(cls.spritesheet());
TextureFilm film = new TextureFilm( HeroSprite.tiers(), 6, 12, 15 );
idle = new Animation( 1, true );
idle.frames( film, 0, 0, 0, 1, 0, 0, 1, 1 );
run = new Animation( 20, true );
run.frames( film, 2, 3, 4, 5, 6, 7 );
die = new Animation( 20, false );
die.frames( film, 0 );
attack = new Animation( 15, false );
attack.frames( film, 13, 14, 15, 0 );
play(idle, true);
resetColor();
}
@Override
public void link(Char ch) {
super.link(ch);
if (ch instanceof LightAlly){
setup(((LightAlly) ch).cls);
}
}
@Override
public void resetColor() {
super.resetColor();
alpha(0.8f);
tint(1.33f, 1.33f, 0.8f, 0.6f);
rm = gm = bm = 0;
}
@Override
public void die() {
super.die();
emitter().start( ShaftParticle.FACTORY, 0.3f, 4 );
emitter().start( Speck.factory( Speck.LIGHT ), 0.2f, 3 );
}
@Override
public void draw() {
if (alpha() >= 0.8f) alpha(0.8f);
rm = gm = bm = 0; //always flat and transparent
super.draw();
}
}
}

View File

@@ -51,6 +51,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Feint;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.ShadowClone;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.ClericSpell;
@@ -249,7 +250,15 @@ public abstract class Mob extends Char {
return true;
}
return state.act( enemyInFOV, justAlerted );
boolean result = state.act( enemyInFOV, justAlerted );
//for updating hero FOV
if (buff(PowerOfMany.PowerBuff.class) != null){
Dungeon.level.updateFieldOfView( this, fieldOfView );
GameScene.updateFog(pos, viewDistance+(int)Math.ceil(speed()));
}
return result;
}
//FIXME this is sort of a band-aid correction for allies needing more intelligent behaviour

View File

@@ -247,7 +247,7 @@ public class PrismaticImage extends NPC {
@Override
public boolean act(boolean enemyInFOV, boolean justAlerted) {
if (!enemyInFOV){
Buff.affect(hero, PrismaticGuard.class).set( HP );
Buff.affect(hero, PrismaticGuard.class).set( PrismaticImage.this );
destroy();
CellEmitter.get(pos).start( Speck.factory(Speck.LIGHT), 0.2f, 3 );
sprite.die();

View File

@@ -30,6 +30,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Amok;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.NPC;
@@ -110,6 +111,10 @@ public class WandOfLivingEarth extends DamageWand {
guardian = new EarthGuardian();
guardian.setInfo(curUser, buffedLvl(), buff.armor);
if (buff.powerOfManyTurns > 0){
Buff.affect(guardian, PowerOfMany.PowerBuff.class, buff.powerOfManyTurns);
}
//if the collision pos is occupied (likely will be), then spawn the guardian in the
//adjacent cell which is closes to the user of the wand.
if (ch != null){
@@ -255,7 +260,22 @@ public class WandOfLivingEarth extends DamageWand {
private int wandLevel;
private int armor;
private void addArmor( int wandLevel, int toAdd ){
private float powerOfManyTurns = 0;
@Override
public boolean act() {
if (powerOfManyTurns > 0){
powerOfManyTurns--;
if (powerOfManyTurns <= 0){
powerOfManyTurns = 0;
BuffIndicator.refreshHero();
}
}
spend(TICK);
return true;
}
private void addArmor(int wandLevel, int toAdd ){
this.wandLevel = Math.max(this.wandLevel, wandLevel);
armor += toAdd;
armor = Math.min(armor, 2*armorToGuardian());
@@ -276,6 +296,10 @@ public class WandOfLivingEarth extends DamageWand {
}
}
public boolean isEmpowered(){
return powerOfManyTurns > 0;
}
@Override
public int icon() {
return BuffIndicator.ARMOR;
@@ -283,7 +307,11 @@ public class WandOfLivingEarth extends DamageWand {
@Override
public void tintIcon(Image icon) {
icon.brightness(0.6f);
if (isEmpowered()){
icon.hardlight(1.8f, 1.8f, 0.6f);
} else {
icon.brightness(0.6f);
}
}
@Override
@@ -298,17 +326,24 @@ public class WandOfLivingEarth extends DamageWand {
@Override
public String desc() {
return Messages.get( this, "desc", armor, armorToGuardian());
String desc = Messages.get( this, "desc", armor, armorToGuardian());
if (isEmpowered()){
desc += "\n\n" + Messages.get(this, "desc_many", (int)powerOfManyTurns);
}
return desc;
}
private static final String WAND_LEVEL = "wand_level";
private static final String ARMOR = "armor";
private static final String POWER_TURNS = "power_turns";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(WAND_LEVEL, wandLevel);
bundle.put(ARMOR, armor);
bundle.put(POWER_TURNS, powerOfManyTurns);
}
@Override
@@ -316,6 +351,7 @@ public class WandOfLivingEarth extends DamageWand {
super.restoreFromBundle(bundle);
wandLevel = bundle.getInt(WAND_LEVEL);
armor = bundle.getInt(ARMOR);
powerOfManyTurns = bundle.getFloat(POWER_TURNS);
}
}
@@ -423,6 +459,9 @@ public class WandOfLivingEarth extends DamageWand {
public boolean act(boolean enemyInFOV, boolean justAlerted) {
if (!enemyInFOV){
Buff.affect(Dungeon.hero, RockArmor.class).addArmor(wandLevel, HP);
if (buff(PowerOfMany.PowerBuff.class) != null){
Buff.affect(Dungeon.hero, RockArmor.class).powerOfManyTurns = buff(PowerOfMany.PowerBuff.class).cooldown()+1;
}
Dungeon.hero.sprite.centerEmitter().burst(MagicMissile.EarthParticle.ATTRACT, 8 + wandLevel/2);
destroy();
sprite.die();

View File

@@ -22,6 +22,7 @@
package com.shatteredpixel.shatteredpixeldungeon.journal;
import com.shatteredpixel.shatteredpixeldungeon.Badges;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.ShadowClone;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.SmokeBomb;
@@ -235,7 +236,7 @@ public enum Bestiary {
ALLY.addEntities(MirrorImage.class, PrismaticImage.class,
DriedRose.GhostHero.class,
WandOfWarding.Ward.class, WandOfWarding.Ward.WardSentry.class, WandOfLivingEarth.EarthGuardian.class,
ShadowClone.ShadowAlly.class, SmokeBomb.NinjaLog.class, SpiritHawk.HawkAlly.class);
ShadowClone.ShadowAlly.class, SmokeBomb.NinjaLog.class, SpiritHawk.HawkAlly.class, PowerOfMany.LightAlly.class);
TRAP.addEntities(WornDartTrap.class, PoisonDartTrap.class, DisintegrationTrap.class, GatewayTrap.class,
ChillingTrap.class, BurningTrap.class, ShockingTrap.class, AlarmTrap.class, GrippingTrap.class, TeleportationTrap.class, OozeTrap.class,

View File

@@ -48,6 +48,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Shadows;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroSubClass;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.cleric.PowerOfMany;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.spells.DivineSense;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollGeomancer;
@@ -1415,7 +1416,8 @@ public abstract class Level implements Bundlable {
for (Mob m : mobs){
if (m instanceof WandOfWarding.Ward
|| m instanceof WandOfRegrowth.Lotus
|| m instanceof SpiritHawk.HawkAlly){
|| m instanceof SpiritHawk.HawkAlly
|| m.buff(PowerOfMany.PowerBuff.class) != null){
if (m.fieldOfView == null || m.fieldOfView.length != length()){
m.fieldOfView = new boolean[length()];
Dungeon.level.updateFieldOfView( m, m.fieldOfView );

View File

@@ -132,6 +132,7 @@ public class BuffIndicator extends Component {
public static final int PROT_AURA = 80;
public static final int ILLUMINATED = 81;
public static final int TRINITY_FORM= 82;
public static final int MANY_POWER = 83;
public static final int SIZE_SMALL = 7;
public static final int SIZE_LARGE = 16;