2014-07-27 13:39:07 +00:00
|
|
|
/*
|
|
|
|
* Pixel Dungeon
|
|
|
|
* Copyright (C) 2012-2014 Oleg Dolya
|
|
|
|
*
|
|
|
|
* 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/>
|
|
|
|
*/
|
2014-08-03 18:46:22 +00:00
|
|
|
package com.shatteredpixel.shatteredpixeldungeon.items;
|
2014-07-27 13:39:07 +00:00
|
|
|
|
2014-08-03 18:46:22 +00:00
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.Assets;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.Badges;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.actors.Actor;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.actors.Char;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.SnipersMark;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Hero;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.effects.Speck;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.missiles.MissileWeapon;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.mechanics.Ballistica;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.CellSelector;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.sprites.MissileSprite;
|
2015-01-20 22:26:53 +00:00
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlotButton;
|
2014-08-03 18:46:22 +00:00
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.utils.GLog;
|
|
|
|
import com.shatteredpixel.shatteredpixeldungeon.utils.Utils;
|
2014-10-22 21:11:30 +00:00
|
|
|
import com.watabou.noosa.audio.Sample;
|
2014-07-27 13:39:07 +00:00
|
|
|
import com.watabou.utils.Bundlable;
|
|
|
|
import com.watabou.utils.Bundle;
|
|
|
|
import com.watabou.utils.Callback;
|
|
|
|
|
2014-10-22 21:11:30 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Comparator;
|
|
|
|
|
2014-07-27 13:39:07 +00:00
|
|
|
public class Item implements Bundlable {
|
|
|
|
|
|
|
|
private static final String TXT_PACK_FULL = "Your pack is too full for the %s";
|
|
|
|
|
|
|
|
private static final String TXT_TO_STRING = "%s";
|
|
|
|
private static final String TXT_TO_STRING_X = "%s x%d";
|
|
|
|
private static final String TXT_TO_STRING_LVL = "%s%+d";
|
|
|
|
private static final String TXT_TO_STRING_LVL_X = "%s%+d x%d";
|
|
|
|
|
|
|
|
protected static final float TIME_TO_THROW = 1.0f;
|
|
|
|
protected static final float TIME_TO_PICK_UP = 1.0f;
|
|
|
|
protected static final float TIME_TO_DROP = 0.5f;
|
|
|
|
|
|
|
|
public static final String AC_DROP = "DROP";
|
|
|
|
public static final String AC_THROW = "THROW";
|
|
|
|
|
|
|
|
public String defaultAction;
|
|
|
|
|
|
|
|
protected String name = "smth";
|
2014-08-12 20:59:12 +00:00
|
|
|
public int image = 0;
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
public boolean stackable = false;
|
|
|
|
protected int quantity = 1;
|
|
|
|
|
|
|
|
public int level = 0;
|
|
|
|
public boolean levelKnown = false;
|
|
|
|
|
|
|
|
public boolean cursed;
|
|
|
|
public boolean cursedKnown;
|
|
|
|
|
|
|
|
// Unique items persist through revival
|
|
|
|
public boolean unique = false;
|
2014-10-22 21:11:30 +00:00
|
|
|
|
|
|
|
// whether an item can be included in heroes remains
|
|
|
|
public boolean bones = false;
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
private static Comparator<Item> itemComparator = new Comparator<Item>() {
|
|
|
|
@Override
|
|
|
|
public int compare( Item lhs, Item rhs ) {
|
|
|
|
return Generator.Category.order( lhs ) - Generator.Category.order( rhs );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
public ArrayList<String> actions( Hero hero ) {
|
|
|
|
ArrayList<String> actions = new ArrayList<String>();
|
|
|
|
actions.add( AC_DROP );
|
|
|
|
actions.add( AC_THROW );
|
|
|
|
return actions;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean doPickUp( Hero hero ) {
|
|
|
|
if (collect( hero.belongings.backpack )) {
|
|
|
|
|
|
|
|
GameScene.pickUp( this );
|
|
|
|
Sample.INSTANCE.play( Assets.SND_ITEM );
|
|
|
|
hero.spendAndNext( TIME_TO_PICK_UP );
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void doDrop( Hero hero ) {
|
|
|
|
hero.spendAndNext( TIME_TO_DROP );
|
|
|
|
Dungeon.level.drop( detachAll( hero.belongings.backpack ), hero.pos ).sprite.drop( hero.pos );
|
|
|
|
}
|
2014-10-25 18:11:46 +00:00
|
|
|
|
|
|
|
public void syncVisuals(){
|
|
|
|
//do nothing by default, as most items need no visual syncing.
|
|
|
|
}
|
|
|
|
|
|
|
|
public void doThrow( Hero hero ) {
|
2014-07-27 13:39:07 +00:00
|
|
|
GameScene.selectCell( thrower );
|
|
|
|
}
|
|
|
|
|
|
|
|
public void execute( Hero hero, String action ) {
|
|
|
|
|
|
|
|
curUser = hero;
|
|
|
|
curItem = this;
|
|
|
|
|
|
|
|
if (action.equals( AC_DROP )) {
|
|
|
|
|
|
|
|
doDrop( hero );
|
|
|
|
|
|
|
|
} else if (action.equals( AC_THROW )) {
|
|
|
|
|
|
|
|
doThrow( hero );
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void execute( Hero hero ) {
|
|
|
|
execute( hero, defaultAction );
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void onThrow( int cell ) {
|
|
|
|
Heap heap = Dungeon.level.drop( this, cell );
|
|
|
|
if (!heap.isEmpty()) {
|
|
|
|
heap.sprite.drop( cell );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean collect( Bag container ) {
|
|
|
|
|
|
|
|
ArrayList<Item> items = container.items;
|
|
|
|
|
|
|
|
if (items.contains( this )) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Item item:items) {
|
|
|
|
if (item instanceof Bag && ((Bag)item).grab( this )) {
|
|
|
|
return collect( (Bag)item );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stackable) {
|
|
|
|
for (Item item:items) {
|
|
|
|
if (isSimilar( item )) {
|
|
|
|
item.quantity += quantity;
|
|
|
|
item.updateQuickslot();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (items.size() < container.size) {
|
|
|
|
|
|
|
|
if (Dungeon.hero != null && Dungeon.hero.isAlive()) {
|
|
|
|
Badges.validateItemLevelAquired( this );
|
|
|
|
}
|
|
|
|
|
2015-01-19 17:01:04 +00:00
|
|
|
items.add( this );
|
2015-01-20 05:56:36 +00:00
|
|
|
Dungeon.quickslot.replaceSimilar(this);
|
2015-01-20 22:26:53 +00:00
|
|
|
QuickSlotButton.refresh();
|
2014-07-27 13:39:07 +00:00
|
|
|
Collections.sort( items, itemComparator );
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
GLog.n( TXT_PACK_FULL, name() );
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean collect() {
|
|
|
|
return collect( Dungeon.hero.belongings.backpack );
|
|
|
|
}
|
|
|
|
|
2014-10-21 04:38:15 +00:00
|
|
|
public final Item detach( Bag container ) {
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
if (quantity <= 0) {
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
} else
|
|
|
|
if (quantity == 1) {
|
|
|
|
|
2015-01-19 17:01:04 +00:00
|
|
|
if (stackable == true){
|
2015-01-20 05:56:36 +00:00
|
|
|
Dungeon.quickslot.convertToPlaceholder(this);
|
2015-01-19 17:01:04 +00:00
|
|
|
}
|
|
|
|
|
2014-07-27 13:39:07 +00:00
|
|
|
return detachAll( container );
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
quantity--;
|
|
|
|
updateQuickslot();
|
|
|
|
|
2014-10-21 04:38:15 +00:00
|
|
|
try {
|
2014-11-01 21:14:49 +00:00
|
|
|
|
|
|
|
//pssh, who needs copy constructors?
|
2014-10-21 04:38:15 +00:00
|
|
|
Item detached = getClass().newInstance();
|
2014-11-01 21:14:49 +00:00
|
|
|
Bundle copy = new Bundle();
|
|
|
|
this.storeInBundle(copy);
|
|
|
|
detached.restoreFromBundle(copy);
|
|
|
|
detached.quantity(1);
|
|
|
|
|
2014-10-21 04:38:15 +00:00
|
|
|
detached.onDetach( );
|
|
|
|
return detached;
|
2014-07-27 13:39:07 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-21 04:38:15 +00:00
|
|
|
public final Item detachAll( Bag container ) {
|
2015-01-20 05:56:36 +00:00
|
|
|
Dungeon.quickslot.clearItem( this );
|
2015-01-20 22:26:53 +00:00
|
|
|
QuickSlotButton.refresh();
|
2015-01-20 05:56:36 +00:00
|
|
|
|
2014-10-21 04:38:15 +00:00
|
|
|
for (Item item : container.items) {
|
|
|
|
if (item == this) {
|
2015-01-20 22:26:53 +00:00
|
|
|
container.items.remove(this);
|
|
|
|
item.onDetach();
|
2014-10-21 04:38:15 +00:00
|
|
|
return this;
|
|
|
|
} else if (item instanceof Bag) {
|
|
|
|
Bag bag = (Bag)item;
|
|
|
|
if (bag.contains( this )) {
|
|
|
|
return detachAll( bag );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isSimilar( Item item ) {
|
|
|
|
return getClass() == item.getClass();
|
|
|
|
}
|
2014-10-21 04:38:15 +00:00
|
|
|
|
|
|
|
protected void onDetach(){}
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
public Item upgrade() {
|
|
|
|
|
|
|
|
cursed = false;
|
|
|
|
cursedKnown = true;
|
|
|
|
this.level++;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Item upgrade( int n ) {
|
|
|
|
for (int i=0; i < n; i++) {
|
|
|
|
upgrade();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Item degrade() {
|
|
|
|
|
|
|
|
this.level--;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Item degrade( int n ) {
|
|
|
|
for (int i=0; i < n; i++) {
|
|
|
|
degrade();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int visiblyUpgraded() {
|
|
|
|
return levelKnown ? level : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean visiblyCursed() {
|
|
|
|
return cursed && cursedKnown;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isUpgradable() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isIdentified() {
|
|
|
|
return levelKnown && cursedKnown;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isEquipped( Hero hero ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Item identify() {
|
|
|
|
|
|
|
|
levelKnown = true;
|
|
|
|
cursedKnown = true;
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void evoke( Hero hero ) {
|
|
|
|
hero.sprite.emitter().burst( Speck.factory( Speck.EVOKE ), 5 );
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
|
|
|
|
if (levelKnown && level != 0) {
|
|
|
|
if (quantity > 1) {
|
|
|
|
return Utils.format( TXT_TO_STRING_LVL_X, name(), level, quantity );
|
|
|
|
} else {
|
|
|
|
return Utils.format( TXT_TO_STRING_LVL, name(), level );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (quantity > 1) {
|
|
|
|
return Utils.format( TXT_TO_STRING_X, name(), quantity );
|
|
|
|
} else {
|
|
|
|
return Utils.format( TXT_TO_STRING, name() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public String name() {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
public final String trueName() {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int image() {
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
public ItemSprite.Glowing glowing() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String info() {
|
|
|
|
return desc();
|
|
|
|
}
|
|
|
|
|
|
|
|
public String desc() {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
public int quantity() {
|
|
|
|
return quantity;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void quantity( int value ) {
|
|
|
|
quantity = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int price() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Item virtual( Class<? extends Item> cl ) {
|
|
|
|
try {
|
|
|
|
|
|
|
|
Item item = (Item)cl.newInstance();
|
|
|
|
item.quantity = 0;
|
|
|
|
return item;
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Item random() {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String status() {
|
|
|
|
return quantity != 1 ? Integer.toString( quantity ) : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void updateQuickslot() {
|
2015-01-20 05:56:36 +00:00
|
|
|
if (Dungeon.quickslot.contains( this )) {
|
2015-01-20 22:26:53 +00:00
|
|
|
QuickSlotButton.refresh();
|
2014-07-27 13:39:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final String QUANTITY = "quantity";
|
|
|
|
private static final String LEVEL = "level";
|
|
|
|
private static final String LEVEL_KNOWN = "levelKnown";
|
|
|
|
private static final String CURSED = "cursed";
|
|
|
|
private static final String CURSED_KNOWN = "cursedKnown";
|
2015-01-19 17:01:04 +00:00
|
|
|
private static final String OLDSLOT = "quickslot";
|
|
|
|
private static final String QUICKSLOT = "quickslotpos";
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void storeInBundle( Bundle bundle ) {
|
|
|
|
bundle.put( QUANTITY, quantity );
|
|
|
|
bundle.put( LEVEL, level );
|
|
|
|
bundle.put( LEVEL_KNOWN, levelKnown );
|
|
|
|
bundle.put( CURSED, cursed );
|
|
|
|
bundle.put( CURSED_KNOWN, cursedKnown );
|
2015-01-20 05:56:36 +00:00
|
|
|
if (Dungeon.quickslot.contains(this)) {
|
|
|
|
bundle.put( QUICKSLOT, Dungeon.quickslot.getSlot(this) );
|
2014-07-27 13:39:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void restoreFromBundle( Bundle bundle ) {
|
|
|
|
quantity = bundle.getInt( QUANTITY );
|
|
|
|
levelKnown = bundle.getBoolean( LEVEL_KNOWN );
|
|
|
|
cursedKnown = bundle.getBoolean( CURSED_KNOWN );
|
|
|
|
|
|
|
|
int level = bundle.getInt( LEVEL );
|
|
|
|
if (level > 0) {
|
|
|
|
upgrade( level );
|
|
|
|
} else if (level < 0) {
|
|
|
|
degrade( -level );
|
|
|
|
}
|
|
|
|
|
|
|
|
cursed = bundle.getBoolean( CURSED );
|
2015-01-19 17:01:04 +00:00
|
|
|
|
2015-01-20 05:56:36 +00:00
|
|
|
//only want to populate slot on first load.
|
|
|
|
if (Dungeon.hero == null) {
|
|
|
|
//support for pre-0.2.3 saves and rankings
|
|
|
|
if (bundle.contains(OLDSLOT)) {
|
|
|
|
Dungeon.quickslot.setSlot(0, this);
|
|
|
|
} else if (bundle.contains(QUICKSLOT)) {
|
|
|
|
Dungeon.quickslot.setSlot(bundle.getInt(QUICKSLOT), this);
|
|
|
|
}
|
2014-07-27 13:39:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void cast( final Hero user, int dst ) {
|
|
|
|
|
|
|
|
final int cell = Ballistica.cast( user.pos, dst, false, true );
|
|
|
|
user.sprite.zap( cell );
|
|
|
|
user.busy();
|
|
|
|
|
|
|
|
Char enemy = Actor.findChar( cell );
|
2015-01-20 22:26:53 +00:00
|
|
|
QuickSlotButton.target(enemy);
|
2014-07-27 13:39:07 +00:00
|
|
|
|
|
|
|
float delay = TIME_TO_THROW;
|
|
|
|
if (this instanceof MissileWeapon) {
|
|
|
|
|
2014-10-21 04:38:15 +00:00
|
|
|
// FIXME
|
2014-07-27 13:39:07 +00:00
|
|
|
delay *= ((MissileWeapon)this).speedFactor( user );
|
|
|
|
if (enemy != null && enemy.buff( SnipersMark.class ) != null) {
|
|
|
|
delay *= 0.5f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
final float finalDelay = delay;
|
|
|
|
|
|
|
|
((MissileSprite)user.sprite.parent.recycle( MissileSprite.class )).
|
|
|
|
reset( user.pos, cell, this, new Callback() {
|
|
|
|
@Override
|
|
|
|
public void call() {
|
|
|
|
Item.this.detach( user.belongings.backpack ).onThrow( cell );
|
|
|
|
user.spendAndNext( finalDelay );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static Hero curUser = null;
|
|
|
|
protected static Item curItem = null;
|
|
|
|
protected static CellSelector.Listener thrower = new CellSelector.Listener() {
|
|
|
|
@Override
|
|
|
|
public void onSelect( Integer target ) {
|
|
|
|
if (target != null) {
|
|
|
|
curItem.cast( curUser, target );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
|
|
public String prompt() {
|
|
|
|
return "Choose direction of throw";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|