v3.2.0: reworked thrown weapons to work in 'sets':

- sets are upgraded/enchanted/etc. as a unit, default size of 3
- upgrading completely repairs a set
- durability boost per upgrade down to 1.5x from 3x
- Internal buff added to prevent upgrade repair duplication exploits
- lots of effects that apply to melee weapons now apply to thrown: identification, enchants, curses, etc.
- liquid metal recipe adjusted (liquid metal itself still needs changes)
- Huntress ID talent changed now that thrown weapons are IDable
- darts effectively unchanged, they all belong to the same set, still not upgradeable
- no balance changes to accomodate this yet, tbd.
This commit is contained in:
Evan Debenham
2025-07-09 17:17:13 -04:00
parent 09c307032c
commit 954340216d
24 changed files with 402 additions and 152 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

@@ -1091,7 +1091,7 @@ actors.hero.talent.perfect_copy.desc=_+1:_ The shadow clone gains _10%_ of the h
actors.hero.talent.natures_bounty.title=nature's bounty
actors.hero.talent.natures_bounty.desc=_+1:_ The Huntress can find _4 berries_ hidden in tall grass as she explores the next few floors of the dungeon.\n\n_+2:_ The Huntress can find _6 berries_ hidden in tall grass as she explores the next few floors of the dungeon.\n\nBerries are eaten quickly, restore a small amount of satiety, and might contain a usable seed.
actors.hero.talent.survivalists_intuition.title=survivalist's intuition
actors.hero.talent.survivalists_intuition.desc=_+1:_ The Huntress identifies all equipment _1.75x faster_.\n\n_+2:_ The Huntress identifies all equipment _2.5x faster_.
actors.hero.talent.survivalists_intuition.desc=_+1:_ The Huntress identifies thrown weapons _3x faster_.\n\n_+2:_ The Huntress identifies thrown weapons _when she attacks with them_.
actors.hero.talent.followup_strike.title=followup strike
actors.hero.talent.followup_strike.desc=_+1:_ When the Huntress hits an enemy with her bow or a thrown weapon, her next melee attack against that enemy deals _2 bonus damage_.\n\n_+2:_ When the Huntress hits an enemy with her bow or a thrown weapon, her next melee attack against that enemy deals _3 bonus damage_.
actors.hero.talent.natures_aid.title=nature's aid
@@ -1163,7 +1163,7 @@ items.scrolls.scrollofupgrade.name=scroll of upgrade
items.scrolls.scrollofupgrade.inv_title=Upgrade an item
items.scrolls.scrollofupgrade.weaken_curse=The scroll of upgrade weakens the curse on your item.
items.scrolls.scrollofupgrade.remove_curse=The scroll of upgrade cleanses the curse on your item!
items.scrolls.scrollofupgrade.desc=This scroll will upgrade a single item. Wands will increase in power and number of charges, weapons and armor will deal and block more damage, and the effects of rings will intensify.\n\nIt can even weaken or sometimes totally dispel curses, though it is not as potent as a scroll of remove curse. Unfortunately, the upgrading magic can also erase enchantments or glyphs on higher level gear.
items.scrolls.scrollofupgrade.desc=This scroll will upgrade a single item or set of thrown weapons. Wands will increase in power and number of charges, weapons and armor will deal and block more damage, thrown weapon sets will be fully repaired and more durable, and the effects of rings will intensify.\n\nIt can even weaken or sometimes totally dispel curses, though it is not as potent as a scroll of remove curse. Unfortunately, the upgrading magic can also erase enchantments or glyphs on higher level gear.
@@ -2105,7 +2105,7 @@ items.weapon.missiles.darts.dart.tip_two=tip 2 darts with 1 seed
items.weapon.missiles.darts.dart.tip_one=tip 1 dart with 1 seed
items.weapon.missiles.darts.dart.tip_cancel=cancel
items.weapon.missiles.darts.dart.desc=These simple shafts of spike-tipped wood are weighted to fly true and sting their prey with a flick of the wrist. They can be tipped with seeds to gain bonus effects when thrown.
items.weapon.missiles.darts.dart.unlimited_uses=However, due to their simple construction darts will effectively last forever.
items.weapon.missiles.darts.dart.unlimited_uses=Due to their simple construction darts will effectively last forever.
items.weapon.missiles.darts.displacingdart.name=displacing dart
items.weapon.missiles.darts.displacingdart.desc=These darts are tipped with a fadeleaf-based compound which will teleport their target a short distance away.
@@ -2138,7 +2138,6 @@ items.weapon.missiles.darts.tippeddart.clean_desc=This action will remove the se
items.weapon.missiles.darts.tippeddart.clean_all=clean all
items.weapon.missiles.darts.tippeddart.clean_one=clean one
items.weapon.missiles.darts.tippeddart.cancel=cancel
items.weapon.missiles.darts.tippeddart.durability=Tipped darts will lose their tips and become regular darts when used.
items.weapon.missiles.darts.tippeddart.uses_left=This stack of darts has _%d/%d_ uses left before one tip wears off.
items.weapon.missiles.darts.tippeddart.unlimited_uses=_But these are of such high quality that they will effectively last forever._
items.weapon.missiles.darts.tippeddart.about_to_break=Your dart's tip is about to expire.
@@ -2169,13 +2168,18 @@ items.weapon.missiles.javelin.desc=These larger throwing spears are weighted to
items.weapon.missiles.kunai.name=kunai
items.weapon.missiles.kunai.desc=These small knives are very powerful in the hands of a skilled user. They are most effective against unaware enemies.
items.weapon.missiles.missileweapon.stats=This _tier-%1$d_ thrown weapon deals _%2$d-%3$d damage_ and requires _%4$d strength_ to use properly.
items.weapon.missiles.missileweapon.distance=This weapon is designed to be used at a distance, it is more accurate against distant enemies, but also much less accurate at melee range.
items.weapon.missiles.missileweapon.stats_known=This set of _tier-%1$d_ thrown weapons deals _%2$d-%3$d damage_ and requires _%4$d strength_ to use properly.
items.weapon.missiles.missileweapon.stats_unknown=Typically this set of _tier-%1$d_ thrown weapons deals _%2$d-%3$d damage_ and requires _%4$d strength_ to use properly.
items.weapon.missiles.missileweapon.probably_too_heavy=Probably this weapon is too heavy for you.
items.weapon.missiles.missileweapon.distance=Thrown weapons are more accurate against distant enemies, but less accurate at melee range.
items.weapon.missiles.missileweapon.durability=Thrown weapons will wear out and break as they are used.
items.weapon.missiles.missileweapon.uses_left=This stack of weapons has _%d/%d_ uses left before one breaks.
items.weapon.missiles.missileweapon.unlimited_uses=_But these are of such high quality that they will effectively last forever._
items.weapon.missiles.missileweapon.uses_left=This set of thrown weapons has _%d/%d_ uses left before one breaks.
items.weapon.missiles.missileweapon.unlimited_uses=This set is of such high quality that it will effectively last forever.
items.weapon.missiles.missileweapon.unknown_uses=Typically this set of thrown weapons will have _%d_ uses before one breaks.
items.weapon.missiles.missileweapon.curse_discover=This thrown weapon is cursed!
items.weapon.missiles.missileweapon.about_to_break=Your thrown weapon is about to break.
items.weapon.missiles.missileweapon.has_broken=One of your thrown weapons has broken.
items.weapon.missiles.missileweapon.dust=The thrown weapon crumbles to dust as you touch it.
items.weapon.missiles.missileweapon$placeholder.name=thrown weapon
items.weapon.missiles.shuriken.name=shuriken
@@ -2195,11 +2199,11 @@ items.weapon.missiles.throwingspear.name=throwing spear
items.weapon.missiles.throwingspear.desc=These lightweight spears have thin frames which are clearly designed to be thrown, and not thrusted.
items.weapon.missiles.throwingspike.name=throwing spike
items.weapon.missiles.throwingspike.desc=These pointed shafts of metal are meant to be thrown into distant enemies. While they aren't very strong, their simple all-metal construction makes them reasonably durable.
items.weapon.missiles.throwingspike.desc=These durable pointed shafts of metal are meant to be thrown into distant enemies.
items.weapon.missiles.throwingspike.discover_hint=One of the heroes starts with this item.
items.weapon.missiles.throwingstone.name=throwing stone
items.weapon.missiles.throwingstone.desc=These stones are sanded down to make them able to be thrown with more power than a regular stone. Despite the craftsmanship they still aren't a very reliable weapon, but at least they won't stick to enemies.
items.weapon.missiles.throwingstone.desc=These stones are sanded down to make them able to be thrown with more power than a regular stone.
items.weapon.missiles.throwingstone.discover_hint=One of the heroes starts with this item.
items.weapon.missiles.tomahawk.name=tomahawk
@@ -77,7 +77,7 @@ journal.document.alchemy_guide.exotic_scrolls.body=Exotic scrolls can be made wi
journal.document.alchemy_guide.bombs.title=Enhanced Bombs
journal.document.alchemy_guide.bombs.body=A standard black powder bomb can be mixed with a specific item to create an enhanced bomb.
journal.document.alchemy_guide.weapons.title=Enhancing Weapons
journal.document.alchemy_guide.weapons.body=Some of the lighter or more magical weapons can be used in alchemy!\n\nEach thrown weapon produces enough liquid metal to fully repair another weapon of the same level and tier.\n\nOne wand will produce enough arcane resin to upgrade two wands of the same level, but no higher than +3.
journal.document.alchemy_guide.weapons.body=Some of the lighter or more magical weapons can be used in alchemy!\n\nEach thrown weapon set produces enough liquid metal to fully repair another set of the same level and tier. Note that alchemizing a set will destroy the whole set, even if it isn't all present!\n\nOne wand will produce enough arcane resin to upgrade two wands of the same level, but no higher than +3.
journal.document.alchemy_guide.brews_elixirs.title=Brews and Elixirs
journal.document.alchemy_guide.brews_elixirs.body=Brews and elixirs are advanced potions with a variety of effects, usually with a single use.
journal.document.alchemy_guide.spells.title=Spells
@@ -375,6 +375,7 @@ windows.wndupgrade.damage=Damage
windows.wndupgrade.blocking=Blocking
windows.wndupgrade.weight=Weight
windows.wndupgrade.durability=Durability
windows.wndupgrade.quantity=Quantity
windows.wndupgrade.zap_damage=Zap Damage
windows.wndupgrade.corrosion_damage=Corrosion Damage
windows.wndupgrade.ward_damage=Ward Damage
@@ -1067,7 +1067,8 @@ public class Hero extends Char {
|| item instanceof TimekeepersHourglass.sandBag
|| item instanceof DriedRose.Petal
|| item instanceof Key
|| item instanceof Guidebook) {
|| item instanceof Guidebook
|| item.quantity() == 0) {
//Do Nothing
} else if (item instanceof DarkGold) {
DarkGold existing = belongings.getItem(DarkGold.class);
@@ -690,6 +690,10 @@ public enum Talent {
if (item instanceof Wand){
factor *= 1f + 2.0f*hero.pointsInTalent(SCHOLARS_INTUITION);
}
// 3x/instant speed with Huntress talent (see MissileWeapon.proc)
if (item instanceof MissileWeapon){
factor *= 1f + 2.0f*hero.pointsInTalent(SURVIVALISTS_INTUITION);
}
// 2x/instant for Rogue (see onItemEqupped), also id's type on equip/on pickup
if (item instanceof Ring){
factor *= 1f + hero.pointsInTalent(THIEFS_INTUITION);
@@ -23,6 +23,7 @@ package com.shatteredpixel.shatteredpixeldungeon.items;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.effects.Splash;
@@ -128,6 +129,10 @@ public class LiquidMetal extends Item {
return item instanceof MissileWeapon && !(item instanceof Dart);
}
//TODO this needs to fix broken thrown weapons too
// should also only apply to IDed thrown weapons?
// TODO maybe thrown weps and wands can just be known uncursed in order to make recipe?
@Override
public void onSelect( Item item ) {
if (item != null && item instanceof MissileWeapon) {
@@ -171,31 +176,24 @@ public class LiquidMetal extends Item {
@Override
public boolean testIngredients(ArrayList<Item> ingredients) {
for (Item i : ingredients){
if (!(i instanceof MissileWeapon)){
return false;
}
}
return !ingredients.isEmpty();
return ingredients.size() == 1
&& ingredients.get(0) instanceof MissileWeapon
&& ingredients.get(0).isIdentified()
&& !ingredients.get(0).cursed;
}
@Override
public int cost(ArrayList<Item> ingredients) {
int cost = 1;
for (Item i : ingredients){
cost += i.quantity();
}
return cost;
return 3;
}
@Override
public Item brew(ArrayList<Item> ingredients) {
Item result = sampleOutput(ingredients);
for (Item i : ingredients){
i.quantity(0);
}
MissileWeapon w = (MissileWeapon) ingredients.get(0);
w.quantity(0);
Buff.affect(Dungeon.hero, MissileWeapon.UpgradedSetTracker.class).levelThresholds.put(w.setID, Integer.MAX_VALUE);
return result;
}
@@ -168,7 +168,7 @@ public abstract class Recipe {
//*******
private static Recipe[] variableRecipes = new Recipe[]{
new LiquidMetal.Recipe()
//none for now
};
private static Recipe[] oneIngredientRecipes = new Recipe[]{
@@ -176,6 +176,7 @@ public abstract class Recipe {
new ExoticPotion.PotionToExotic(),
new ExoticScroll.ScrollToExotic(),
new ArcaneResin.Recipe(),
new LiquidMetal.Recipe(),
new BlizzardBrew.Recipe(),
new InfernalBrew.Recipe(),
new AquaBrew.Recipe(),
@@ -254,9 +255,10 @@ public abstract class Recipe {
}
public static boolean usableInRecipe(Item item){
//only upgradeable thrown weapons and wands allowed among equipment items
if (item instanceof EquipableItem){
//only thrown weapons and wands allowed among equipment items
return item.isIdentified() && !item.cursed && item instanceof MissileWeapon;
return item.isIdentified() && !item.cursed &&
item instanceof MissileWeapon && item.isUpgradable();
} else if (item instanceof Wand) {
return item.isIdentified() && !item.cursed;
} else {
@@ -26,8 +26,8 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Talent;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Catalog;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
@@ -76,7 +76,7 @@ public class PotionOfMastery extends ExoticPotion {
@Override
public boolean itemSelectable(Item item) {
return
(item instanceof MeleeWeapon && !((MeleeWeapon) item).masteryPotionBonus)
(item instanceof Weapon && !(item instanceof SpiritBow) && !((Weapon) item).masteryPotionBonus)
|| (item instanceof Armor && !((Armor) item).masteryPotionBonus);
}
@@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon.items.scrolls;
import com.shatteredpixel.shatteredpixeldungeon.Challenges;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.Statistics;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
import com.shatteredpixel.shatteredpixeldungeon.effects.Transmuting;
import com.shatteredpixel.shatteredpixeldungeon.items.EquipableItem;
@@ -124,7 +125,11 @@ public class ScrollOfTransmutation extends InventoryScroll {
}
Dungeon.hero.spend(-Dungeon.hero.cooldown()); //cancel equip/unequip time
} else {
item.detach(Dungeon.hero.belongings.backpack);
if (item instanceof MissileWeapon){
item.detachAll(Dungeon.hero.belongings.backpack);
} else {
item.detach(Dungeon.hero.belongings.backpack);
}
if (!result.collect()) {
Dungeon.level.drop(result, curUser.pos).sprite.drop();
} else if (result.stackable && Dungeon.hero.belongings.getSimilar(result) != null){
@@ -237,7 +242,7 @@ public class ScrollOfTransmutation extends InventoryScroll {
} while (Challenges.isItemBlocked(n) || n.getClass() == w.getClass());
n.level(0);
n.quantity(1);
n.quantity(w.quantity());
int level = w.trueLevel();
if (level > 0) {
n.upgrade( level );
@@ -253,6 +258,11 @@ public class ScrollOfTransmutation extends InventoryScroll {
n.cursed = w.cursed;
n.augment = w.augment;
n.enchantHardened = w.enchantHardened;
//technically a new set, ensure old one is destroyed (except for darts)
if (w instanceof MissileWeapon && w.isUpgradable()){
Buff.affect(Dungeon.hero, MissileWeapon.UpgradedSetTracker.class).levelThresholds.put(((MissileWeapon) w).setID, Integer.MAX_VALUE);
}
return n;
@@ -29,9 +29,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor;
import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.InventoryScroll;
import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfEnchantment;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
@@ -68,7 +66,7 @@ public class ScrollOfEnchantment extends ExoticScroll {
}
public static boolean enchantable( Item item ){
return (item instanceof MeleeWeapon || item instanceof SpiritBow || item instanceof Armor);
return (item instanceof Weapon || item instanceof Armor);
}
private void confirmCancelation() {
@@ -32,11 +32,8 @@ import com.shatteredpixel.shatteredpixeldungeon.items.quest.MetalShard;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfMight;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRemoveCurse;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MagesStaff;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Catalog;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.watabou.noosa.audio.Sample;
@@ -53,7 +50,7 @@ public class CurseInfusion extends InventorySpell {
@Override
protected boolean usableOnItem(Item item) {
return ((item instanceof EquipableItem && !(item instanceof MissileWeapon)) || item instanceof Wand);
return ((item instanceof EquipableItem && item.isUpgradable()) || item instanceof Wand);
}
@Override
@@ -63,7 +60,7 @@ public class CurseInfusion extends InventorySpell {
Sample.INSTANCE.play(Assets.Sounds.CURSED);
item.cursed = true;
if (item instanceof MeleeWeapon || item instanceof SpiritBow) {
if (item instanceof Weapon) {
Weapon w = (Weapon) item;
if (w.enchantment != null) {
//if we are freshly applying curse infusion, don't replace an existing curse
@@ -295,6 +295,13 @@ public class SpiritBow extends Weapon {
image = ItemSpriteSheet.SPIRIT_ARROW;
hitSound = Assets.Sounds.HIT_ARROW;
setID = 0;
}
@Override
public int defaultQuantity() {
return 1;
}
@Override
@@ -67,6 +67,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Vampir
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.RunicBlade;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.Scimitar;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Catalog;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
@@ -108,10 +109,12 @@ abstract public class Weapon extends KindOfWeapon {
}
public Augment augment = Augment.NONE;
private static final int USES_TO_ID = 20;
private float usesLeftToID = USES_TO_ID;
private float availableUsesToID = USES_TO_ID/2f;
protected int usesToID(){
return 20;
}
protected float usesLeftToID = usesToID();
protected float availableUsesToID = usesToID()/2f;
public Enchantment enchantment;
public boolean enchantHardened = false;
@@ -191,9 +194,10 @@ abstract public class Weapon extends KindOfWeapon {
public void onHeroGainExp( float levelPercent, Hero hero ){
levelPercent *= Talent.itemIDSpeedFactor(hero, this);
if (!levelKnown && isEquipped(hero) && availableUsesToID <= USES_TO_ID/2f) {
if (!levelKnown && (isEquipped(hero) || this instanceof MissileWeapon)
&& availableUsesToID <= usesToID()/2f) {
//gains enough uses to ID over 0.5 levels
availableUsesToID = Math.min(USES_TO_ID/2f, availableUsesToID + levelPercent * USES_TO_ID);
availableUsesToID = Math.min(usesToID()/2f, availableUsesToID + levelPercent * usesToID());
}
}
@@ -233,8 +237,8 @@ abstract public class Weapon extends KindOfWeapon {
@Override
public void reset() {
super.reset();
usesLeftToID = USES_TO_ID;
availableUsesToID = USES_TO_ID/2f;
usesLeftToID = usesToID();
availableUsesToID = usesToID()/2f;
}
@Override
@@ -28,13 +28,13 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.particles.SmokeParticle;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.bombs.Bomb;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.utils.Bundle;
import com.watabou.utils.PathFinder;
import com.watabou.utils.Random;
public class Explosive extends Weapon.Enchantment {
@@ -47,7 +47,7 @@ public class Explosive extends Weapon.Enchantment {
public int proc( Weapon weapon, Char attacker, Char defender, int damage ) {
//average value of 5, or 20 hits to an explosion
int durToReduce = Math.round(Random.IntRange(0, 10) * procChanceMultiplier(attacker));
int durToReduce = Math.round(24 * procChanceMultiplier(attacker));
int currentDurability = durability;
durability -= durToReduce;
@@ -82,9 +82,20 @@ public class Explosive extends Weapon.Enchantment {
Item.updateQuickslot();
}
if (weapon instanceof MissileWeapon
&& ((MissileWeapon)weapon).parent != null && ((MissileWeapon) weapon).parent.enchantment instanceof Explosive){
((Explosive) ((MissileWeapon) weapon).parent.enchantment).durability = durability;
}
return damage;
}
public void merge(Explosive other){
if (other.durability < durability){
durability = other.durability;
}
}
@Override
public boolean curse() {
return true;
@@ -21,6 +21,7 @@
package com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
@@ -36,23 +37,29 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
import com.shatteredpixel.shatteredpixeldungeon.items.bags.MagicalHolster;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfSharpshooting;
import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ParchmentScrap;
import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ShardOfOblivion;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.SpiritBow;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Explosive;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.enchantments.Projecting;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.darts.Dart;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
import com.watabou.noosa.audio.Sample;
import com.watabou.utils.Bundle;
import com.watabou.utils.Random;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
abstract public class MissileWeapon extends Weapon {
{
stackable = true;
levelKnown = true;
quantity = defaultQuantity();
bones = true;
@@ -60,6 +67,9 @@ abstract public class MissileWeapon extends Weapon {
usesTargeting = true;
}
//TODO maybe make this like actor IDs, instead of random? collisions unlikely, but it's messy
public long setID = new SecureRandom().nextLong();
//whether or not this instance of the item exists purely to trigger its effect. i.e. no dropping
public boolean spawnedForEffect = false;
@@ -72,9 +82,13 @@ abstract public class MissileWeapon extends Weapon {
public boolean holster;
//used to reduce durability from the source weapon stack, rather than the one being thrown.
protected MissileWeapon parent;
public MissileWeapon parent;
public int tier;
protected int usesToID(){
return 10; //half of a melee weapon
}
@Override
public int min() {
@@ -107,7 +121,11 @@ abstract public class MissileWeapon extends Weapon {
}
public int STRReq(int lvl){
return STRReq(tier, lvl) - 1; //1 less str than normal for their tier
int req = STRReq(tier, lvl) - 1; //1 less str than normal for their tier
if (masteryPotionBonus){
req -= 2;
}
return req;
}
//use the parent item if this has been thrown from a parent
@@ -118,41 +136,24 @@ abstract public class MissileWeapon extends Weapon {
return super.buffedLvl();
}
}
public Item upgrade( boolean enchant ) {
if (!bundleRestoring) {
durability = MAX_DURABILITY;
quantity = fullSetQuantity = defaultQuantity();
Buff.affect(Dungeon.hero, UpgradedSetTracker.class).levelThresholds.put(setID, level()+1);
}
return super.upgrade( enchant );
}
@Override
//FIXME some logic here assumes the items are in the player's inventory. Might need to adjust
public Item upgrade() {
if (!bundleRestoring) {
durability = MAX_DURABILITY;
if (quantity > 1) {
MissileWeapon upgraded = (MissileWeapon) split(1);
upgraded.parent = null;
upgraded = (MissileWeapon) upgraded.upgrade();
//try to put the upgraded into inventory, if it didn't already merge
if (upgraded.quantity() == 1 && !upgraded.collect()) {
Dungeon.level.drop(upgraded, Dungeon.hero.pos);
}
updateQuickslot();
return upgraded;
} else {
super.upgrade();
Item similar = Dungeon.hero.belongings.getSimilar(this);
if (similar != null){
detach(Dungeon.hero.belongings.backpack);
Item result = similar.merge(this);
updateQuickslot();
return result;
}
updateQuickslot();
return this;
}
} else {
return super.upgrade();
quantity = fullSetQuantity = defaultQuantity();
Buff.affect(Dungeon.hero, UpgradedSetTracker.class).levelThresholds.put(setID, level()+1);
}
return super.upgrade();
}
@Override
@@ -169,7 +170,7 @@ abstract public class MissileWeapon extends Weapon {
}
public boolean isSimilar( Item item ) {
return level() == item.level() && getClass() == item.getClass();
return trueLevel() == item.trueLevel() && getClass() == item.getClass() && setID == (((MissileWeapon) item).setID);
}
@Override
@@ -269,23 +270,82 @@ abstract public class MissileWeapon extends Weapon {
}
}
return super.proc(attacker, defender, damage);
if ((cursed || hasCurseEnchant()) && !cursedKnown){
GLog.n(Messages.get(this, "curse_discover"));
}
cursedKnown = true;
if (parent != null) parent.cursedKnown = true;
//instant ID with the right talent
if (attacker == Dungeon.hero && Dungeon.hero.pointsInTalent(Talent.SURVIVALISTS_INTUITION) == 2){
usesLeftToID = Math.min(usesLeftToID, 0);
}
int result = super.proc(attacker, defender, damage);
//handle ID progress over parent/child
if (parent != null && parent.usesLeftToID > usesLeftToID){
float diff = parent.usesLeftToID - usesLeftToID;
parent.usesLeftToID -= diff;
parent.availableUsesToID -= diff;
if (usesLeftToID <= 0) {
if (ShardOfOblivion.passiveIDDisabled()){
parent.setIDReady();
} else {
parent.identify();
}
}
}
return result;
}
@Override
public Item virtual() {
Item item = super.virtual();
((MissileWeapon)item).setID = setID;
return item;
}
public int defaultQuantity(){
return 3;
}
//this is tracked to show warnings when upgrading and some of the set isn't present
public int fullSetQuantity = defaultQuantity();
@Override
public Item random() {
if (!stackable) return this;
//2: 66.67% (2/3)
//3: 26.67% (4/15)
//4: 6.67% (1/15)
quantity = 2;
if (Random.Int(3) == 0) {
quantity++;
//+0: 75% (3/4)
//+1: 20% (4/20)
//+2: 5% (1/20)
int n = 0;
if (Random.Int(4) == 0) {
n++;
if (Random.Int(5) == 0) {
quantity++;
n++;
}
}
level(n);
//we use a separate RNG here so that variance due to things like parchment scrap
//does not affect levelgen
Random.pushGenerator(Random.Long());
//30% chance to be cursed
//10% chance to be enchanted
float effectRoll = Random.Float();
if (effectRoll < 0.3f * ParchmentScrap.curseChanceMultiplier()) {
enchant(Enchantment.randomCurse());
cursed = true;
} else if (effectRoll >= 1f - (0.1f * ParchmentScrap.enchantChanceMultiplier())){
enchant();
}
Random.popGenerator();
return this;
}
@@ -327,14 +387,16 @@ abstract public class MissileWeapon extends Weapon {
durability += amount;
durability = Math.min(durability, MAX_DURABILITY);
}
public float durabilityPerUse(){
//classes that override durabilityPerUse can turn rounding off, to do their own rounding after more logic
return durabilityPerUse(true);
public final float durabilityPerUse(){
return durabilityPerUse(level());
}
protected final float durabilityPerUse( boolean rounded){
float usages = baseUses * (float)(Math.pow(3, level()));
//classes that add steps onto durabilityPerUse can turn rounding off, to do their own rounding after more logic
protected boolean useRoundingInDurabilityCalc = true;
public float durabilityPerUse( int level ){
float usages = baseUses * (float)(Math.pow(1.5f, level));
//+50%/75% durability
if (Dungeon.hero != null && Dungeon.hero.hasTalent(Talent.DURABLE_PROJECTILES)){
@@ -349,7 +411,7 @@ abstract public class MissileWeapon extends Weapon {
//at 100 uses, items just last forever.
if (usages >= 100f) return 0;
if (rounded){
if (useRoundingInDurabilityCalc){
usages = Math.round(usages);
//add a tiny amount to account for rounding error for calculations like 1/3
return (MAX_DURABILITY/usages) + 0.001f;
@@ -366,6 +428,7 @@ abstract public class MissileWeapon extends Weapon {
if (parent.durability <= parent.durabilityPerUse()){
durability = 0;
parent.durability = MAX_DURABILITY;
parent.fullSetQuantity--;
if (parent.durabilityPerUse() < 100f) {
GLog.n(Messages.get(this, "has_broken"));
}
@@ -419,6 +482,34 @@ abstract public class MissileWeapon extends Weapon {
quantity -= 1;
durability += MAX_DURABILITY;
}
masteryPotionBonus = masteryPotionBonus || ((MissileWeapon) other).masteryPotionBonus;
levelKnown = levelKnown || other.levelKnown;
cursedKnown = cursedKnown || other.cursedKnown;
enchantHardened = enchantHardened || ((MissileWeapon) other).enchantHardened;
//if other has a curse/enchant status that's a higher priority, copy it. in the following order:
//curse infused
if (!curseInfusionBonus && ((MissileWeapon) other).curseInfusionBonus && ((MissileWeapon) other).hasCurseEnchant()){
enchantment = ((MissileWeapon) other).enchantment;
curseInfusionBonus = true;
cursed = cursed || other.cursed;
//enchanted
} else if (!curseInfusionBonus && !hasGoodEnchant() && ((MissileWeapon) other).hasGoodEnchant()){
enchantment = ((MissileWeapon) other).enchantment;
cursed = other.cursed;
//nothing
} else if (!curseInfusionBonus && hasCurseEnchant() && !((MissileWeapon) other).hasCurseEnchant()){
enchantment = ((MissileWeapon) other).enchantment;
cursed = other.cursed;
}
//cursed (no copy as other cannot have a higher priority status)
//special case for explosive, as it tracks a variable
if (((MissileWeapon) other).enchantment instanceof Explosive
&& enchantment instanceof Explosive){
((Explosive) enchantment).merge((Explosive) ((MissileWeapon) other).enchantment);
}
}
return this;
}
@@ -428,7 +519,7 @@ abstract public class MissileWeapon extends Weapon {
bundleRestoring = true;
Item split = super.split(amount);
bundleRestoring = false;
//unless the thrown weapon will break, split off a max durability item and
//have it reduce the durability of the main stack. Cleaner to the player this way
if (split != null){
@@ -443,59 +534,71 @@ abstract public class MissileWeapon extends Weapon {
@Override
public boolean doPickUp(Hero hero, int pos) {
parent = null;
return super.doPickUp(hero, pos);
if (!UpgradedSetTracker.pickupValid(hero, this)){
Sample.INSTANCE.play( Assets.Sounds.ITEM );
hero.spendAndNext( TIME_TO_PICK_UP );
GLog.w(Messages.get(this, "dust"));
quantity(0);
return true;
} else {
return super.doPickUp(hero, pos);
}
}
@Override
public boolean isIdentified() {
return true;
return levelKnown && cursedKnown;
}
@Override
public String info() {
String info = super.info();
info += "\n\n" + Messages.get( MissileWeapon.class, "stats",
tier,
Math.round(augment.damageFactor(min())),
Math.round(augment.damageFactor(max())),
STRReq());
if (Dungeon.hero != null) {
if (STRReq() > Dungeon.hero.STR()) {
info += " " + Messages.get(Weapon.class, "too_heavy");
} else if (Dungeon.hero.STR() > STRReq()) {
info += " " + Messages.get(Weapon.class, "excess_str", Dungeon.hero.STR() - STRReq());
if (levelKnown) {
info += "\n\n" + Messages.get(MissileWeapon.class, "stats_known", tier, augment.damageFactor(min()), augment.damageFactor(max()), STRReq());
if (Dungeon.hero != null) {
if (STRReq() > Dungeon.hero.STR()) {
info += " " + Messages.get(Weapon.class, "too_heavy");
} else if (Dungeon.hero.STR() > STRReq()) {
info += " " + Messages.get(Weapon.class, "excess_str", Dungeon.hero.STR() - STRReq());
}
}
} else {
info += "\n\n" + Messages.get(MissileWeapon.class, "stats_unknown", tier, min(0), max(0), STRReq(0));
if (Dungeon.hero != null && STRReq(0) > Dungeon.hero.STR()) {
info += " " + Messages.get(MissileWeapon.class, "probably_too_heavy");
}
}
if (enchantment != null && (cursedKnown || !enchantment.curse())){
info += "\n\n" + Messages.get(Weapon.class, "enchanted", enchantment.name());
if (enchantHardened) info += " " + Messages.get(Weapon.class, "enchant_hardened");
info += " " + Messages.get(enchantment, "desc");
} else if (enchantHardened){
info += "\n\n" + Messages.get(Weapon.class, "hardened_no_enchant");
}
if (cursed && isEquipped( Dungeon.hero )) {
info += "\n\n" + Messages.get(Weapon.class, "cursed_worn");
} else if (cursedKnown && cursed) {
if (cursedKnown && cursed) {
info += "\n\n" + Messages.get(Weapon.class, "cursed");
} else if (!isIdentified() && cursedKnown){
info += "\n\n" + Messages.get(Weapon.class, "not_cursed");
}
info += "\n\n" + Messages.get(MissileWeapon.class, "distance");
info += "\n\n" + Messages.get(this, "durability");
if (durabilityPerUse() > 0){
info += " " + Messages.get(this, "uses_left",
(int)Math.ceil(durability/durabilityPerUse()),
(int)Math.ceil(MAX_DURABILITY/durabilityPerUse()));
} else {
info += " " + Messages.get(this, "unlimited_uses");
if (levelKnown) {
if (durabilityPerUse() > 0) {
info += "\n\n" + Messages.get(this, "uses_left",
(int) Math.ceil(durability / durabilityPerUse()),
(int) Math.ceil(MAX_DURABILITY / durabilityPerUse()));
} else {
info += "\n\n" + Messages.get(this, "unlimited_uses");
}
} else {
info += "\n\n" + Messages.get(this, "unknown_uses", (int) Math.ceil(MAX_DURABILITY / durabilityPerUse(0)));
}
return info;
}
@@ -504,14 +607,19 @@ abstract public class MissileWeapon extends Weapon {
return 6 * tier * quantity * (level() + 1);
}
private static final String SET_ID = "set_id";
private static final String SPAWNED = "spawned";
private static final String DURABILITY = "durability";
private static final String FULL_QUANTITY = "full_quantity";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(SET_ID, setID);
bundle.put(SPAWNED, spawnedForEffect);
bundle.put(DURABILITY, durability);
bundle.put(FULL_QUANTITY, fullSetQuantity);
}
private static boolean bundleRestoring = false;
@@ -521,8 +629,29 @@ abstract public class MissileWeapon extends Weapon {
bundleRestoring = true;
super.restoreFromBundle(bundle);
bundleRestoring = false;
if (bundle.contains(SET_ID)){
setID = bundle.getLong(SET_ID);
//pre v3.2.0 logic
} else {
//if we have a higher than 0 level, assume that this was a solitary thrown wep upgrade
//turn it into a set of full quantity
if (level() > 0){
//set ID will be a random long
quantity = defaultQuantity();
fullSetQuantity = quantity;
//otherwise treat all currently spawned thrown weapons of the same class as if they are part of the same set
//darts already do this though and need no conversion
} else if (!(this instanceof Dart)){
levelKnown = cursedKnown = true;
setID = getClass().getSimpleName().hashCode();
}
}
spawnedForEffect = bundle.getBoolean(SPAWNED);
durability = bundle.getFloat(DURABILITY);
fullSetQuantity = bundle.getInt(FULL_QUANTITY);
}
public static class PlaceHolder extends MissileWeapon {
@@ -533,7 +662,13 @@ abstract public class MissileWeapon extends Weapon {
@Override
public boolean isSimilar(Item item) {
return item instanceof MissileWeapon;
//yes, even though it uses a dart outline
return item instanceof MissileWeapon && !(item instanceof Dart);
}
@Override
public String status() {
return null;
}
@Override
@@ -541,4 +676,50 @@ abstract public class MissileWeapon extends Weapon {
return "";
}
}
//also used by liquid metal crafting to track when a set is consumed
public static class UpgradedSetTracker extends Buff {
public HashMap<Long, Integer> levelThresholds = new HashMap<>();
public static boolean pickupValid(Hero h, MissileWeapon w){
if (h.buff(UpgradedSetTracker.class) != null){
HashMap<Long, Integer> levelThresholds = h.buff(UpgradedSetTracker.class).levelThresholds;
if (levelThresholds.containsKey(w.setID)){
return w.level() >= levelThresholds.get(w.setID);
}
return true;
}
return true;
}
public static final String SET_IDD = "set_ids";
public static final String SET_LEVELS = "set_levels";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
long[] IDs = new long[levelThresholds.size()];
int[] levels = new int[levelThresholds.size()];
int i = 0;
for (Long ID : levelThresholds.keySet()){
IDs[i] = ID;
levels[i] = levelThresholds.get(ID);
i++;
}
bundle.put(SET_IDD, IDs);
bundle.put(SET_LEVELS, levels);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
long[] IDs = bundle.getLongArray(SET_IDD);
int[] levels = bundle.getIntArray(SET_LEVELS);
levelThresholds.clear();
for (int i = 0; i <IDs.length; i++){
levelThresholds.put(IDs[i], levels[i]);
}
}
}
}
@@ -48,6 +48,8 @@ import java.util.ArrayList;
public class Dart extends MissileWeapon {
{
levelKnown = true;
image = ItemSpriteSheet.DART;
hitSound = Assets.Sounds.HIT_ARROW;
hitSoundPitch = 1.3f;
@@ -56,6 +58,9 @@ public class Dart extends MissileWeapon {
//infinite, even with penalties
baseUses = 1000;
//all darts share a set ID
setID = 0L;
}
protected static final String AC_TIP = "TIP";
@@ -242,7 +247,17 @@ public class Dart extends MissileWeapon {
public boolean isUpgradable() {
return false;
}
@Override
public boolean isIdentified() {
return true;
}
@Override
public int defaultQuantity() {
return 2;
}
@Override
public int value() {
return super.value()/2; //half normal value
@@ -51,7 +51,7 @@ public class RotDart extends TippedDart {
}
@Override
public float durabilityPerUse() {
public float durabilityPerUse(int level) {
return MAX_DURABILITY/5f; //always 5 uses
}
}
@@ -163,9 +163,13 @@ public abstract class TippedDart extends Dart {
private static int targetPos = -1;
{
useRoundingInDurabilityCalc = false;
}
@Override
public float durabilityPerUse() {
float use = super.durabilityPerUse(false);
public float durabilityPerUse(int level) {
float use = super.durabilityPerUse(level);
if (Dungeon.hero != null) {
use /= (1 + Dungeon.hero.pointsInTalent(Talent.DURABLE_TIPS));
@@ -38,6 +38,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Recipe;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.AlchemistsToolkit;
import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.TrinketCatalyst;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Catalog;
import com.shatteredpixel.shatteredpixeldungeon.journal.Document;
import com.shatteredpixel.shatteredpixeldungeon.journal.Journal;
@@ -299,7 +300,7 @@ public class AlchemyScene extends PixelScene {
if (item != null && inputs[0] != null) {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i].item() == null) {
if (item instanceof LiquidMetal){
if (item instanceof LiquidMetal || item instanceof MissileWeapon){
inputs[i].item(item.detachAll(Dungeon.hero.belongings.backpack));
} else {
inputs[i].item(item.detach(Dungeon.hero.belongings.backpack));
@@ -570,7 +571,7 @@ public class AlchemyScene extends PixelScene {
if (item != null && inputs[0] != null) {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i].item() == null) {
if (item instanceof LiquidMetal){
if (item instanceof LiquidMetal || item instanceof MissileWeapon){
inputs[i].item(item.detachAll(Dungeon.hero.belongings.backpack));
} else {
inputs[i].item(item.detach(Dungeon.hero.belongings.backpack));
@@ -792,7 +793,7 @@ public class AlchemyScene extends PixelScene {
ArrayList<Item> found = inventory.getAllSimilar(finding);
while (!found.isEmpty() && needed > 0){
Item detached;
if (finding instanceof LiquidMetal) {
if (finding instanceof LiquidMetal || finding instanceof MissileWeapon) {
detached = found.get(0).detachAll(inventory.backpack);
} else {
detached = found.get(0).detach(inventory.backpack);
@@ -345,14 +345,6 @@ public class QuickRecipe extends Component {
result.add(new QuickRecipe( new LiquidMetal.Recipe(),
new ArrayList<Item>(Arrays.asList(new MissileWeapon.PlaceHolder())),
new LiquidMetal()));
result.add(new QuickRecipe( new LiquidMetal.Recipe(),
new ArrayList<Item>(Arrays.asList(new MissileWeapon.PlaceHolder().quantity(2))),
new LiquidMetal()));
result.add(new QuickRecipe( new LiquidMetal.Recipe(),
new ArrayList<Item>(Arrays.asList(new MissileWeapon.PlaceHolder().quantity(3))),
new LiquidMetal()));
result.add(null);
result.add(null);
result.add(new QuickRecipe( new ArcaneResin.Recipe(),
new ArrayList<Item>(Arrays.asList(new Wand.PlaceHolder())),
new ArcaneResin()));
@@ -24,6 +24,7 @@ package com.shatteredpixel.shatteredpixeldungeon.windows;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Badges;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.Buff;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Belongings;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith;
@@ -35,7 +36,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor;
import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfUpgrade;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.melee.MeleeWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Catalog;
import com.shatteredpixel.shatteredpixeldungeon.journal.Notes;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
@@ -272,13 +273,16 @@ public class WndBlacksmith extends Window {
if (second.isEquipped( Dungeon.hero )) {
((EquipableItem)second).doUnequip( Dungeon.hero, false );
}
second.detach( Dungeon.hero.belongings.backpack );
second.detachAll( Dungeon.hero.belongings.backpack );
if (second instanceof Armor){
BrokenSeal seal = ((Armor) second).checkSeal();
if (seal != null){
Dungeon.level.drop( seal, Dungeon.hero.pos );
}
} else if (second instanceof MissileWeapon){
Buff.affect(Dungeon.hero, MissileWeapon.UpgradedSetTracker.class)
.levelThresholds.put(((MissileWeapon) second).setID, Integer.MAX_VALUE);
}
//preserves enchant/glyphs if present
@@ -346,8 +350,8 @@ public class WndBlacksmith extends Window {
} else if (item1.getClass() != item2.getClass()) {
btnReforge.enable(false);
//and not the literal same item (unless quantity is >1)
} else if (item1 == item2 && item1.quantity() == 1) {
//and not the literal same item
} else if (item1 == item2) {
btnReforge.enable(false);
} else {
@@ -375,7 +379,7 @@ public class WndBlacksmith extends Window {
public boolean itemSelectable(Item item) {
return item.isUpgradable()
&& item.isIdentified() && !item.cursed
&& ((item instanceof MeleeWeapon && !((Weapon) item).enchantHardened)
&& ((item instanceof Weapon && !((Weapon) item).enchantHardened)
|| (item instanceof Armor && !((Armor) item).glyphHardened));
}
@@ -245,12 +245,24 @@ public class WndUpgrade extends Window {
//durability
if (toUpgrade instanceof MissileWeapon){
//missile weapons are always IDed currently, so we always use true level
int uses1 = (int)Math.ceil(100f/((MissileWeapon) toUpgrade).durabilityPerUse());
int uses2 = (int)Math.ceil(300f/((MissileWeapon) toUpgrade).durabilityPerUse());
int uses1, uses2;
if (toUpgrade.levelKnown) {
uses1 = (int) Math.ceil(100f / ((MissileWeapon) toUpgrade).durabilityPerUse(toUpgrade.level()));
uses2 = (int) Math.ceil(100f / ((MissileWeapon) toUpgrade).durabilityPerUse(toUpgrade.level()+1));
} else {
uses1 = (int) Math.ceil(100f / ((MissileWeapon) toUpgrade).durabilityPerUse(0));
uses2 = (int) Math.ceil(100f / ((MissileWeapon) toUpgrade).durabilityPerUse(1));
}
bottom = fillFields(Messages.get(this, "durability"),
uses1 >= 100 ? "" : Integer.toString(uses1),
uses2 >= 100 ? "" : Integer.toString(uses2),
bottom);
bottom = fillFields(Messages.get(this, "quantity"),
Integer.toString(toUpgrade.quantity()),
Integer.toString(((MissileWeapon) toUpgrade).defaultQuantity()),
bottom);
}
//we use a separate reference for wand properties so that mage's staff can include them
@@ -393,6 +405,10 @@ public class WndUpgrade extends Window {
bottom = addMessage(Messages.get(this, "resin"), CharSprite.WARNING, bottom);
}
if (toUpgrade instanceof MissileWeapon && toUpgrade.quantity() < ((MissileWeapon) toUpgrade).fullSetQuantity){
bottom = addMessage("Weapons from this set that aren't in your inventory will crumble to dust.", CharSprite.WARNING, bottom);
}
// *** Buttons for confirming/cancelling ***
btnUpgrade = new RedButton(Messages.get(this, "upgrade")){