diff --git a/core/src/main/assets/messages/actors/actors.properties b/core/src/main/assets/messages/actors/actors.properties index 8e4751ba7..3eb35a673 100644 --- a/core/src/main/assets/messages/actors/actors.properties +++ b/core/src/main/assets/messages/actors/actors.properties @@ -504,7 +504,7 @@ actors.hero.heroclass.huntress_unlock=To unlock the Huntress _hit enemies with t actors.hero.heroclass.duelist=Duelist actors.hero.heroclass.duelist_desc_short=The Duelist is a weapons master who can use her weapon to trigger a _special weapon ability._ This special ability is _different for every weapon._ -actors.hero.heroclass.duelist_desc=The Duelist starts with a _unique Rapier_ with a special ability.\n\nThe Duelist gets a _different special ability_ for every weapon in the game.\n\nThe duelist also starts with _three throwing knives_, cloth armor, a waterskin, and a velvet pouch.\n\nThe Duelist automatically identifies:\n_-_ Scrolls of Identify\n_-_ Potions of Strength\n_-_ Scrolls of Mirror Image +actors.hero.heroclass.duelist_desc=The Duelist starts with a _unique rapier_ with a special ability that recharges over time.\n\nEvery weapon in the game has a _different special ability_ that the Duelist can use.\n\nThe duelist also starts with _three throwing knives_, cloth armor, a waterskin, and a velvet pouch.\n\nThe Duelist automatically identifies:\n_-_ Scrolls of Identify\n_-_ Potions of Strength\n_-_ Scrolls of Mirror Image actors.hero.heroclass.duelist_unlock=To unlock the Duelist _equip a tier 2 or higher weapon with no strength penalty._ actors.hero.herosubclass.berserker=berserker diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties index 415edab55..23d0ac470 100644 --- a/core/src/main/assets/messages/items/items.properties +++ b/core/src/main/assets/messages/items/items.properties @@ -1566,6 +1566,10 @@ items.weapon.melee.meleeweapon.stats_known=This _tier-%1$d_ melee weapon deals _ items.weapon.melee.meleeweapon.stats_unknown=Typically this _tier-%1$d_ melee weapon deals _%2$d-%3$d damage_ and requires _%4$d strength_ to use properly. items.weapon.melee.meleeweapon.probably_too_heavy=Probably this weapon is too heavy for you. items.weapon.melee.meleeweapon.stats_desc= +items.weapon.melee.meleeweapon.charge_use=This will consume _%s_ charge. +items.weapon.melee.meleeweapon.ability_equip=You must equip that weapon to use its ability. +items.weapon.melee.meleeweapon.ability_charge=You don't have enough energy to use that ability. +items.weapon.melee.meleeweapon.prompt=Select a Target items.weapon.melee.shortsword.name=shortsword items.weapon.melee.shortsword.desc=A quite short sword, only a few inches longer than a dagger. @@ -1576,7 +1580,9 @@ items.weapon.melee.quarterstaff.desc=A staff of hardwood, its ends are shod with items.weapon.melee.rapier.name=rapier items.weapon.melee.rapier.stats_desc=This weapon blocks 0-1 damage. -items.weapon.melee.rapier.desc=TODO +items.weapon.melee.rapier.ability_name=lunge +items.weapon.melee.rapier.ability_desc=The duelist can _lunge_ with a rapier at an enemy 1 tile away. This moves into the enemy, deals +33% damage, and is guaranteed to hit. +items.weapon.melee.rapier.desc=A slim straight sword that offers some protection in exchange for less slashing power. items.weapon.melee.roundshield.name=round shield items.weapon.melee.roundshield.typical_stats_desc=Typically this weapon blocks 0-%d damage. This blocking scales with upgrades. diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java index 3b7f56b06..b7046f629 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/hero/HeroClass.java @@ -27,6 +27,7 @@ import com.shatteredpixel.shatteredpixeldungeon.Challenges; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; 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.huntress.NaturesPower; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk; @@ -63,6 +64,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Dagger; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Gloves; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff; +import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Rapier; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.WornShortsword; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.ThrowingKnife; @@ -212,6 +214,9 @@ public enum HeroClass { private static void initDuelist( Hero hero ) { (hero.belongings.weapon = new Rapier()).identify(); + hero.belongings.weapon.activate(hero); + + Buff.affect(hero, MeleeWeapon.Charger.class).charge = 100f; ThrowingKnife knives = new ThrowingKnife(); knives.quantity(3).collect(); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/ClassArmor.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/ClassArmor.java index 20fdf9d0c..2cf3e6288 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/ClassArmor.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/ClassArmor.java @@ -164,7 +164,7 @@ abstract public class ClassArmor extends Armor { @Override public String actionName(String action, Hero hero) { if (hero.armorAbility != null && action.equals(AC_ABILITY)){ - return hero.armorAbility.name().toUpperCase(); + return Messages.upperCase(hero.armorAbility.name()); } else { return super.actionName(action, hero); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MagesStaff.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MagesStaff.java index 63a5f6ea7..4e2244289 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MagesStaff.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MagesStaff.java @@ -115,6 +115,7 @@ public class MagesStaff extends MeleeWeapon { @Override public void activate( Char ch ) { + super.activate(ch); applyWandChargeBuff(ch); } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MeleeWeapon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MeleeWeapon.java index e0669c067..8cdf0f469 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MeleeWeapon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/MeleeWeapon.java @@ -23,25 +23,39 @@ package com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff; +import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LockedFloor; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.HeroClass; import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; +import com.shatteredpixel.shatteredpixeldungeon.scenes.CellSelector; +import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; +import com.watabou.utils.Bundle; import com.watabou.utils.Random; +import java.text.DecimalFormat; import java.util.ArrayList; public class MeleeWeapon extends Weapon { public static String AC_ABILITY = "ABILITY"; + @Override + public void activate(Char ch) { + super.activate(ch); + if (ch instanceof Hero && ((Hero) ch).heroClass == HeroClass.DUELIST){ + Buff.affect(ch, Charger.class); + } + } + @Override public String defaultAction() { if (Dungeon.hero != null && Dungeon.hero.heroClass == HeroClass.DUELIST){ return AC_ABILITY; } else { - return null; + return super.defaultAction(); } } @@ -54,19 +68,68 @@ public class MeleeWeapon extends Weapon { return actions; } + @Override + public String actionName(String action, Hero hero) { + if (action.equals(AC_ABILITY)){ + return Messages.upperCase(Messages.get(this, "ability_name")); + } else { + return super.actionName(action, hero); + } + } + @Override public void execute(Hero hero, String action) { super.execute(hero, action); if (action.equals(AC_ABILITY)){ - if (!isEquipped(hero)){ - GLog.w("Need to Equip!"); + if (!isEquipped(hero)) { + GLog.w(Messages.get(this, "ability_equip")); + } else if (Buff.affect(hero, Charger.class).charge < abilityChargeUse()) { + GLog.w(Messages.get(this, "ability_charge")); } else { - GLog.i("Weapon Ability TODO"); + + if (targetingPrompt() == null){ + Buff.affect(hero, Charger.class).charge -= abilityChargeUse(); + duelistAbility(hero, hero.pos); + updateQuickslot(); + } else { + usesTargeting = useTargeting(); + GameScene.selectCell(new CellSelector.Listener() { + @Override + public void onSelect(Integer cell) { + if (cell != null) { + Buff.affect(hero, Charger.class).charge -= abilityChargeUse(); + duelistAbility(hero, cell); + updateQuickslot(); + } + } + + @Override + public String prompt() { + return targetingPrompt(); + } + }); + } } } } + //leave null for no targeting + public String targetingPrompt(){ + return null; + } + + public boolean useTargeting(){ + return targetingPrompt() != null; + } + + //TODO make abstract + protected void duelistAbility( Hero hero, Integer target ){} + + public float abilityChargeUse(){ + return 33f; //TODO + } + public int tier; @Override @@ -147,6 +210,11 @@ public class MeleeWeapon extends Weapon { info += "\n\n" + Messages.get(Weapon.class, "not_cursed"); } } + + if (Dungeon.hero.heroClass == HeroClass.DUELIST){ + info += "\n\n" + Messages.get(this, "ability_desc"); + info += " " + Messages.get(MeleeWeapon.class, "charge_use", new DecimalFormat("#.##").format(abilityChargeUse())); + } return info; } @@ -154,7 +222,17 @@ public class MeleeWeapon extends Weapon { public String statsInfo(){ return Messages.get(this, "stats_desc"); } - + + @Override + public String status() { + if (isEquipped(Dungeon.hero) + && Dungeon.hero.buff(Charger.class) != null) { + return Messages.format( "%.0f%%", Math.floor(Dungeon.hero.buff(Charger.class).charge) ); + } else { + return super.status(); + } + } + @Override public int value() { int price = 20 * tier; @@ -173,4 +251,40 @@ public class MeleeWeapon extends Weapon { return price; } + public static class Charger extends Buff { + + public float charge; + //offhand charge as well? + + @Override + public boolean act() { + LockedFloor lock = target.buff(LockedFloor.class); + if (lock == null || lock.regenOn()) { + charge += 100 / 300f; //300 turns to full charge + updateQuickslot(); + if (charge > 100) { + charge = 100; + } + } + spend(TICK); + return true; + } + + + + public static final String CHARGE = "charge"; + + @Override + public void storeInBundle(Bundle bundle) { + super.storeInBundle(bundle); + bundle.put(CHARGE, charge); + } + + @Override + public void restoreFromBundle(Bundle bundle) { + super.restoreFromBundle(bundle); + charge = bundle.getFloat(CHARGE); + } + } + } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/Rapier.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/Rapier.java index ed418e680..b082a76b8 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/Rapier.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/melee/Rapier.java @@ -23,6 +23,7 @@ package com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee; import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; public class Rapier extends MeleeWeapon { @@ -50,4 +51,8 @@ public class Rapier extends MeleeWeapon { return 1; //1 extra defence } + @Override + public String targetingPrompt() { + return Messages.get(this, "prompt"); + } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/messages/Messages.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/messages/Messages.java index 4ab0f3bfd..fce677800 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/messages/Messages.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/messages/Messages.java @@ -191,4 +191,12 @@ public class Messages { //Otherwise, use sentence case return capitalize(str); } + + public static String upperCase( String str ){ + return str.toUpperCase(locale); + } + + public static String lowerCase( String str ){ + return str.toLowerCase(locale); + } } \ No newline at end of file