v2.0.0: implement base challenge armor ability and all of its text

This commit is contained in:
Evan Debenham
2023-01-19 14:54:10 -05:00
parent ab8b504918
commit 4cb787a4bf
19 changed files with 332 additions and 14 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -457,6 +457,17 @@ actors.hero.abilities.huntress.spirithawk$hawkally.direct_attack=Your hawk moves
actors.hero.abilities.huntress.spirithawk$hawkally.desc=A magical hawk, summoned by the Huntress. It glows a bright ethereal blue, its head constantly shifts around as it surveys the area.\n\nWhile it isn't much of a fighter its speed and vision make it excellent for scouting and distracting enemies.\n\nTurns remaining: %d.
actors.hero.abilities.huntress.spirithawk$hawkally.desc_dodges=Guaranteed dodges remaining: %d.
actors.hero.abilities.duelist.challenge.name=challenge
actors.hero.abilities.duelist.challenge.prompt=Choose an enemy to challenge
actors.hero.abilities.duelist.challenge.already_dueling=You can only be in one duel at a time.
actors.hero.abilities.duelist.challenge.ally_target=You can only challenge enemies.
actors.hero.abilities.duelist.challenge.unreachable_target=You must be able to reach your target.
actors.hero.abilities.duelist.challenge.distant_target=That enemy is too far away to challenge.
actors.hero.abilities.duelist.challenge.short_desc=The Duelist _challenges_ a nearby enemy to a duel, temporarily freezing all other enemies.
actors.hero.abilities.duelist.challenge.desc=The Duelist challenges a nearby enemy to a duel. That enemy is compelled to fight her while all other enemies are temporarily frozen in time.\n\nThe target must be reachable and within 5 tiles of the Duelist. The duel lasts until 10 turns pass, the enemy dies, or the Duelist is more than 5 tiles away from the enemy.\n\nFrozen enemies are invulnerable. The Duelist's allies are not frozen by this ability, but if a boss is targeted its minions will not be frozen either.
actors.hero.abilities.duelist.challenge$duelparticipant.name=duel participant
actors.hero.abilities.duelist.challenge$duelparticipant.desc=This character is participating in a duel. They, and any of their allies or minions, are allowed to act normally while others are frozen.\n\nThe duel will last for a set amount of time, until one participant dies, or until the participants move more than 5 tiles away from eachother.\n\nTurns remaining: %d.
actors.hero.abilities.ratmogrify.name=ratmogrify
actors.hero.abilities.ratmogrify.cant_transform=You can't ratmogrify that!
actors.hero.abilities.ratmogrify.too_strong=That enemy is too strong to ratmogrify!
@@ -848,7 +859,20 @@ actors.hero.talent.secondary_charge.desc=_+1:_ The Champion's secondary weapon c
actors.hero.talent.twin_upgrades.title=twin upgrades
actors.hero.talent.twin_upgrades.desc=_+1:_ If the weaker of the Champion's two equipped weapons is _2 or more tiers lower_ than the stronger one, it is boosted to the stronger weapon's level.\n\n_+2:_ If the weaker of the Champion's two equipped weapons is _1 or more tiers lower_ than the stronger one, it is boosted to the stronger weapon's level.\n\n_+3:_ If the weaker of the Champion's two equipped weapons is _the same tier or lower_ than the stronger one, it is boosted to the stronger weapon's level.
actors.hero.talent.combined_lethality.title=combined lethality
actors.hero.talent.combined_lethality.desc=_+1:_ If the Champion uses two different weapon abilities successively, the second ability will execute any non-boss enemy _below 8% health_.\n\n_+2:_ If the Champion uses two different weapon abilities successively, the second ability will execute any non-boss enemy _below 17% health_.\n\n_+3:_ If the Champion uses two different weapon abilities successively, the second ability will execute any non-boss enemy _below 25% health_.\n\nIf the second ability does not contain an attack, this talent will instead trigger on the Champion's next attack within 5 turns.
actors.hero.talent.combined_lethality.desc=_+1:_ If the Champion uses two different weapon abilities successively, the second ability will execute any non-boss enemy _below 8% HP_.\n\n_+2:_ If the Champion uses two different weapon abilities successively, the second ability will execute any non-boss enemy _below 17% HP_.\n\n_+3:_ If the Champion uses two different weapon abilities successively, the second ability will execute any non-boss enemy _below 25% HP_.\n\nIf the second ability does not contain an attack, this talent will instead trigger on the Champion's next attack within 5 turns.
#second subclass
actors.hero.talent.close_the_gap.title=close the gap
actors.hero.talent.close_the_gap.desc=_+1:_ The Duelist blinks _up to two tiles_ toward her target when starting a duel.\n\n_+2:_ The Duelist blinks _up to three tiles_ toward her target when starting a duel.\n\n_+3:_ The Duelist blinks _up to four tiles_ toward her target when starting a duel.\n\n_+4:_ The Duelist blinks _up to five tiles_ toward her target when starting a duel.\n\nThis blink can go through enemies and hazards, and is taken into account when determining if an enemy is in range to be challenged.
actors.hero.talent.invigorating_victory.title=invigorating victory
actors.hero.talent.invigorating_victory.desc=_+1:_ If the Duelist defeats her target before the duel ends, she heals for _26% of the damage_ she took during the duel.\n\n_+2:_ If the Duelist defeats her target before the duel ends, she heals for _45% of the damage_ she took during the duel.\n\n_+3:_ If the Duelist defeats her target before the duel ends, she heals for _60% of the damage_ she took during the duel.\n\n_+4:_ If the Duelist defeats her target before the duel ends, she heals for _70% of the damage_ she took during the duel.
actors.hero.talent.elimination_match.title=elimination match
actors.hero.talent.elimination_match.desc=_+1:_ The Duelist can immediately challenge another enemy after a duel ends at a _20% reduced_ charge cost.\n\n_+2:_ The Duelist can immediately challenge another enemy after a duel ends at a _36% reduced_ charge cost.\n\n_+3:_ The Duelist can immediately challenge another enemy after a duel ends at a _50% reduced_ charge cost.\n\n_+4:_ The Duelist can immediately challenge another enemy after a duel ends at a _60% reduced_ charge cost.
#second armor ability
#third armor ability
#universal
actors.hero.talent.heroic_energy.title=heroic energy

View File

@@ -357,6 +357,15 @@ public abstract class Actor implements Bundlable {
}
}
}
//'freezes' a character in time for a specified amount of time
//USE CAREFULLY! Manipulating time like this is useful for some gameplay effects but is tricky
public static void delayChar( Char ch, float time ){
ch.spendConstant(time);
for (Buff b : ch.buffs()){
b.spendConstant(time);
}
}
public static synchronized Char findChar( int pos ) {
for (Char ch : chars){

View File

@@ -70,6 +70,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.duelist.Challenge;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.DeathMark;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.warrior.Endure;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Elemental;
@@ -842,6 +843,10 @@ public abstract class Char extends Actor {
}
}
if (sprite != null && buff(Challenge.SpectatorFreeze.class) != null){
return; //can't add buffs while frozen and game is loaded
}
buffs.add( buff );
if (Actor.chars().contains(this)) Actor.add( buff );
@@ -985,7 +990,7 @@ public abstract class Char extends Actor {
//similar to isImmune, but only factors in damage.
//Is used in AI decision-making
public boolean isInvulnerable( Class effect ){
return false;
return buff(Challenge.SpectatorFreeze.class) != null;
}
protected HashSet<Property> properties = new HashSet<>();
@@ -1004,6 +1009,7 @@ public abstract class Char extends Actor {
new HashSet<Class>( Arrays.asList(AllyBuff.class, Dread.class) )),
MINIBOSS ( new HashSet<Class>(),
new HashSet<Class>( Arrays.asList(AllyBuff.class, Dread.class) )),
BOSS_MINION,
UNDEAD,
DEMONIC,
INORGANIC ( new HashSet<Class>(),

View File

@@ -2052,7 +2052,7 @@ public class Hero extends Char {
@Override
public boolean isInvulnerable(Class effect) {
return buff(AnkhInvulnerability.class) != null;
return super.isInvulnerable(effect) || buff(AnkhInvulnerability.class) != null;
}
public boolean search( boolean intentional ) {

View File

@@ -29,6 +29,7 @@ import com.shatteredpixel.shatteredpixeldungeon.QuickSlot;
import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Challenge;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.NaturesPower;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpectralBlades;
@@ -254,7 +255,7 @@ public enum HeroClass {
case HUNTRESS:
return new ArmorAbility[]{new SpectralBlades(), new NaturesPower(), new SpiritHawk()};
case DUELIST:
return new ArmorAbility[]{};
return new ArmorAbility[]{new Challenge()};
}
}

View File

@@ -158,7 +158,7 @@ public enum Talent {
//Duelist S2 T3
DUELIST_S2_1(142, 3), DUELIST_S2_2(143, 3), DUELIST_S2_3(144, 3),
//Duelist A1 T4
DUELIST_A1_1(145, 4), DUELIST_A1_2(146, 4), DUELIST_A1_3(147, 4),
CLOSE_THE_GAP(145, 4), INVIGORATING_VICTORY(146, 4), ELIMINATION_MATCH(147, 4),
//Duelist A2 T4
DUELIST_A2_1(148, 4), DUELIST_A2_2(149, 4), DUELIST_A2_3(150, 4),
//Duelist A3 T4

View File

@@ -0,0 +1,250 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2023 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.abilities.duelist;
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.BlobImmunity;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Doom;
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.ArmorAbility;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mob;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.ClassArmor;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.ui.BuffIndicator;
import com.shatteredpixel.shatteredpixeldungeon.ui.HeroIcon;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
public class Challenge extends ArmorAbility {
{
baseChargeUse = 35;
}
@Override
public int icon() {
return HeroIcon.CHALLENGE;
}
@Override
public String targetingPrompt() {
return Messages.get(this, "prompt");
}
@Override
public int targetedPos(Char user, int dst) {
return dst;
}
@Override
protected void activate(ClassArmor armor, Hero hero, Integer target) {
if (target == null || !Dungeon.level.heroFOV[target]){
return;
}
Char targetCh = Actor.findChar(target);
if (hero.buff(DuelParticipant.class) != null){
GLog.w(Messages.get(this, "already_dueling"));
return;
}
if (targetCh != null){
if (targetCh.alignment == hero.alignment){
GLog.w(Messages.get(this, "ally_target"));
return;
}
boolean[] passable = Dungeon.level.passable.clone();
for (Char c : Actor.chars()) {
if (c != hero) passable[c.pos] = false;
}
PathFinder.buildDistanceMap(targetCh.pos, passable);
if (PathFinder.distance[hero.pos] == Integer.MAX_VALUE){
GLog.w(Messages.get(this, "unreachable_target"));
return;
}
if (Dungeon.level.distance(hero.pos, targetCh.pos) >= 5){
GLog.w(Messages.get(this, "distant_target"));
return;
}
boolean bossTarget = Char.hasProp(targetCh, Char.Property.BOSS);
for (Char toFreeze : Actor.chars()){
if (toFreeze != targetCh && toFreeze.alignment != hero.alignment
&& (!bossTarget || !(Char.hasProp(targetCh, Char.Property.BOSS) || Char.hasProp(targetCh, Char.Property.BOSS_MINION)))) {
Actor.delayChar(toFreeze, DuelParticipant.DURATION);
Buff.affect(toFreeze, SpectatorFreeze.class, DuelParticipant.DURATION);
}
}
Buff.affect(targetCh, DuelParticipant.class);
Buff.affect(hero, DuelParticipant.class);
if (targetCh instanceof Mob){
((Mob) targetCh).aggro(hero);
}
GameScene.flash(0x80FFFFFF);
Sample.INSTANCE.play(Assets.Sounds.DESCEND);
armor.charge -= chargeUse( hero );
armor.updateQuickslot();
hero.sprite.zap(target);
hero.next();
}
}
@Override
public Talent[] talents() {
return new Talent[]{Talent.CLOSE_THE_GAP, Talent.INVIGORATING_VICTORY, Talent.ELIMINATION_MATCH, Talent.HEROIC_ENERGY};
}
public static class DuelParticipant extends Buff {
public static float DURATION = 10f;
private int left = (int)DURATION;
@Override
public int icon() {
return BuffIndicator.CHALLENGE;
}
@Override
public float iconFadePercent() {
return Math.max(0, (DURATION - left) / DURATION);
}
@Override
public String iconTextDisplay() {
return Integer.toString(left);
}
@Override
public boolean act() {
left--;
if (left == 0) {
detach();
} else {
Char other = null;
for (Char ch : Actor.chars()){
if (ch != target && ch.buff(DuelParticipant.class) != null){
other = ch;
}
}
if (other == null){
detach();
} else if (Dungeon.level.distance(target.pos, other.pos) > 5){
detach();
}
}
spend(TICK);
return true;
}
@Override
public void detach() {
super.detach();
if (!target.isAlive()) {
if (target.alignment != Dungeon.hero.alignment){
Sample.INSTANCE.play(Assets.Sounds.BOSS);
GameScene.flash(0x80FFFFFF);
}
for (Char ch : Actor.chars()) {
if (ch.buff(SpectatorFreeze.class) != null) {
ch.buff(SpectatorFreeze.class).detach();
}
if (ch.buff(DuelParticipant.class) != null && ch != target) {
ch.buff(DuelParticipant.class).detach();
}
}
} else if (target == Dungeon.hero){
GameScene.flash(0x80FFFFFF);
}
}
@Override
public String desc() {
return Messages.get(this, "desc", left);
}
private static final String LEFT = "left";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(LEFT, left);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
left = bundle.getInt(LEFT);
}
}
public static class SpectatorFreeze extends FlavourBuff {
@Override
public void fx(boolean on) {
if (on) {
target.sprite.add(CharSprite.State.DARKENED);
target.sprite.add(CharSprite.State.PARALYSED);
} else {
//allies can't be spectator frozen, so just check doom
if (target.buff(Doom.class) == null) target.sprite.remove(CharSprite.State.DARKENED);
if (target.paralysed == 0) target.sprite.remove(CharSprite.State.PARALYSED);
}
}
@Override
public void detach(){
super.detach();
if (cooldown() > 0) {
Actor.delayChar(target, -cooldown());
}
}
{
immunities.addAll(new BlobImmunity().immunities());
}
}
}

View File

@@ -503,7 +503,7 @@ public class DM300 extends Mob {
invulnWarned = true;
GLog.w(Messages.get(this, "charging_hint"));
}
return supercharged;
return supercharged || super.isInvulnerable(effect);
}
public void supercharge(){

View File

@@ -438,7 +438,11 @@ public class DwarfKing extends Mob {
@Override
public boolean isInvulnerable(Class effect) {
return phase == 2 && effect != KingDamager.class;
if (effect == KingDamager.class){
return false;
} else {
return phase == 2 || super.isInvulnerable(effect);
}
}
@Override
@@ -562,6 +566,7 @@ public class DwarfKing extends Mob {
public static class DKGhoul extends Ghoul {
{
properties.add(Property.BOSS_MINION);
state = HUNTING;
}
@@ -574,12 +579,14 @@ public class DwarfKing extends Mob {
public static class DKMonk extends Monk {
{
properties.add(Property.BOSS_MINION);
state = HUNTING;
}
}
public static class DKWarlock extends Warlock {
{
properties.add(Property.BOSS_MINION);
state = HUNTING;
}
@@ -594,6 +601,7 @@ public class DwarfKing extends Mob {
public static class DKGolem extends Golem {
{
properties.add(Property.BOSS_MINION);
state = HUNTING;
}
}

View File

@@ -28,6 +28,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.blobs.SacrificialFire;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.AllyBuff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.ChampionEnemy;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.duelist.Challenge;
import com.shatteredpixel.shatteredpixeldungeon.effects.Pushing;
import com.shatteredpixel.shatteredpixeldungeon.items.Gold;
import com.shatteredpixel.shatteredpixeldungeon.levels.features.Chasm;
@@ -261,7 +262,10 @@ public class Ghoul extends Mob {
return true;
}
turnsToRevive--;
//have to delay this manually here are a downed ghouls can't be directly frozen otherwise
if (target.buff(Challenge.DuelParticipant.class) == null) {
turnsToRevive--;
}
if (turnsToRevive <= 0){
if (Actor.findChar( ghoul.pos ) != null) {
ArrayList<Integer> candidates = new ArrayList<>();
@@ -345,7 +349,10 @@ public class Ghoul extends Mob {
public static Ghoul searchForHost(Ghoul dieing){
for (Char ch : Actor.chars()){
if (ch != dieing && ch instanceof Ghoul && ch.alignment == dieing.alignment){
//don't count hero ally ghouls or duel frozen ghouls
if (ch != dieing && ch instanceof Ghoul
&& ch.alignment == dieing.alignment
&& ch.buff(Challenge.SpectatorFreeze.class) == null){
if (ch.fieldOfView == null){
ch.fieldOfView = new boolean[Dungeon.level.length()];
Dungeon.level.updateFieldOfView( ch, ch.fieldOfView );

View File

@@ -63,6 +63,7 @@ public class Pylon extends Mob {
maxLvl = -2;
properties.add(Property.MINIBOSS);
properties.add(Property.BOSS_MINION);
properties.add(Property.INORGANIC);
properties.add(Property.ELECTRIC);
properties.add(Property.IMMOVABLE);
@@ -178,7 +179,7 @@ public class Pylon extends Mob {
@Override
public boolean isInvulnerable(Class effect) {
//immune to damage when inactive
return (alignment == Alignment.NEUTRAL);
return alignment == Alignment.NEUTRAL || super.isInvulnerable(effect);
}
@Override

View File

@@ -368,7 +368,7 @@ public class YogDzewa extends Mob {
@Override
public boolean isInvulnerable(Class effect) {
return phase == 0 || findFist() != null;
return phase == 0 || findFist() != null || super.isInvulnerable(effect);
}
@Override
@@ -629,6 +629,7 @@ public class YogDzewa extends Mob {
maxLvl = -2;
properties.add(Property.DEMONIC);
properties.add(Property.BOSS_MINION);
}
@Override
@@ -649,15 +650,22 @@ public class YogDzewa extends Mob {
}
//used so death to yog's ripper demons have their own rankings description
public static class YogRipper extends RipperDemon {}
public static class YogRipper extends RipperDemon {
{
maxLvl = -2;
properties.add(Property.BOSS_MINION);
}
}
public static class YogEye extends Eye {
{
maxLvl = -2;
properties.add(Property.BOSS_MINION);
}
}
public static class YogScorpio extends Scorpio {
{
maxLvl = -2;
properties.add(Property.BOSS_MINION);
}
}
}

View File

@@ -116,7 +116,7 @@ public abstract class YogFist extends Mob {
invulnWarned = true;
GLog.w(Messages.get(this, "invuln_warn"));
}
return isNearYog();
return isNearYog() || super.isInvulnerable(effect);
}
@Override

View File

@@ -118,6 +118,7 @@ public class BuffIndicator extends Component {
public static final int DUEL_DANCE = 64;
public static final int DUEL_BRAWL = 65;
public static final int DUEL_XBOW = 66;
public static final int CHALLENGE = 67;
public static final int SIZE_SMALL = 7;
public static final int SIZE_LARGE = 16;

View File

@@ -61,7 +61,10 @@ public class HeroIcon extends Image {
public static final int SPECTRAL_BLADES = 25;
public static final int NATURES_POWER = 26;
public static final int SPIRIT_HAWK = 27;
public static final int RATMOGRIFY = 28;
public static final int CHALLENGE = 28;
public static final int DUELIST_2 = 29;
public static final int DUELIST_3 = 30;
public static final int RATMOGRIFY = 33;
public HeroIcon(HeroSubClass subCls){
super( Assets.Interfaces.HERO_ICONS );