/* * 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 */ package com.shatteredpixel.shatteredpixeldungeon.items; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import com.watabou.noosa.audio.Sample; 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; import com.shatteredpixel.shatteredpixeldungeon.ui.QuickSlot; import com.shatteredpixel.shatteredpixeldungeon.utils.GLog; import com.shatteredpixel.shatteredpixeldungeon.utils.Utils; import com.watabou.utils.Bundlable; import com.watabou.utils.Bundle; import com.watabou.utils.Callback; 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"; public int image = 0; 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; private static Comparator itemComparator = new Comparator() { @Override public int compare( Item lhs, Item rhs ) { return Generator.Category.order( lhs ) - Generator.Category.order( rhs ); } }; public ArrayList actions( Hero hero ) { ArrayList actions = new ArrayList(); 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 ); } public void doThrow( Hero hero ) { 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 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 ); } items.add( this ); QuickSlot.refresh(); Collections.sort( items, itemComparator ); return true; } else { GLog.n( TXT_PACK_FULL, name() ); return false; } } public boolean collect() { return collect( Dungeon.hero.belongings.backpack ); } public final Item detach( Bag container ) { if (quantity <= 0) { return null; } else if (quantity == 1) { return detachAll( container ); } else { quantity--; updateQuickslot(); try { Item detached = getClass().newInstance(); detached.onDetach( ); return detached; } catch (Exception e) { return null; } } } public final Item detachAll( Bag container ) { for (Item item : container.items) { if (item == this) { container.items.remove( this ); item.onDetach( ); QuickSlot.refresh(); return this; } else if (item instanceof Bag) { Bag bag = (Bag)item; if (bag.contains( this )) { return detachAll( bag ); } } } return this; } public boolean isSimilar( Item item ) { return getClass() == item.getClass(); } protected void onDetach(){} 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 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() { if ((stackable && Dungeon.quickslot == getClass()) || Dungeon.quickslot == this) { QuickSlot.refresh(); } } 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"; private static final String QUICKSLOT = "quickslot"; @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 ); if (this == Dungeon.quickslot) { bundle.put( QUICKSLOT, true ); } } @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 ); if (bundle.getBoolean( QUICKSLOT )) { Dungeon.quickslot = this; } } 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 ); QuickSlot.target( this, enemy ); float delay = TIME_TO_THROW; if (this instanceof MissileWeapon) { // FIXME 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"; } }; }