Rewrote ItemContainers from scratch
- ItemContainers have been entirely rewritten - ItemSlots are now wrappers, not owners - Added InventoryOwners - Not yet used - Added ItemDataWithContainers - Added some basic windowing functionality to WindowedHUD
This commit is contained in:
		@@ -104,7 +104,7 @@ public class HUDManager implements HUDWorkspace {
 | 
			
		||||
			InventoryComponent component = render.createComponent(inventory, this);
 | 
			
		||||
			openInventory(component);
 | 
			
		||||
		} catch (Exception e) {
 | 
			
		||||
			throw CrashReports.report(null, "Could not open inventory %s", inventory.getId());
 | 
			
		||||
			throw CrashReports.report(e, "Could not open inventory %s", inventory.getId());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,9 @@ import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
 | 
			
		||||
import ru.windcorp.progressia.client.graphics.input.WheelScrollEvent;
 | 
			
		||||
import ru.windcorp.progressia.common.Units;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemDataContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.Items;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainerMixed;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.Items;
 | 
			
		||||
 | 
			
		||||
public class InteractiveSlotComponent extends Button {
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +76,7 @@ public class InteractiveSlotComponent extends Button {
 | 
			
		||||
 | 
			
		||||
	private void onMainAction() {
 | 
			
		||||
		ItemSlot handSlot = workspace.getHand().slot();
 | 
			
		||||
		ItemSlot invSlot = getSlotOrAllocate();
 | 
			
		||||
		ItemSlot invSlot = getSlot();
 | 
			
		||||
		if (invSlot == null) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
@@ -108,13 +107,12 @@ public class InteractiveSlotComponent extends Button {
 | 
			
		||||
		if (success) {
 | 
			
		||||
			requestReassembly();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		cleanContainerUpIfNecessary();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void pickAll(ItemSlot handSlot) {
 | 
			
		||||
		for (ItemSlot invSlot : getSlot().getContainer()) {
 | 
			
		||||
			Items.pour(invSlot, handSlot);
 | 
			
		||||
		int maxIndex = getSlot().getContainer().getMaxIndex();
 | 
			
		||||
		for (int index = 0; index < maxIndex; ++index) {
 | 
			
		||||
			Items.pour(new ItemSlot(getSlot().getContainer(), index), handSlot);
 | 
			
		||||
			if (handSlot.isEmpty()) {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
@@ -123,7 +121,7 @@ public class InteractiveSlotComponent extends Button {
 | 
			
		||||
 | 
			
		||||
	private void onAltAction() {
 | 
			
		||||
		ItemSlot handSlot = workspace.getHand().slot();
 | 
			
		||||
		ItemSlot invSlot = getSlotOrAllocate();
 | 
			
		||||
		ItemSlot invSlot = getSlot();
 | 
			
		||||
		if (invSlot == null) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
@@ -135,7 +133,7 @@ public class InteractiveSlotComponent extends Button {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!success && handSlot.isEmpty()) {
 | 
			
		||||
			success = Items.pour(invSlot, handSlot, invSlot.getAmount() / 2) != 0;
 | 
			
		||||
			success = Items.pour(invSlot, handSlot, invSlot.getCount() / 2) != 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!success) {
 | 
			
		||||
@@ -149,19 +147,17 @@ public class InteractiveSlotComponent extends Button {
 | 
			
		||||
		if (success) {
 | 
			
		||||
			requestReassembly();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cleanContainerUpIfNecessary();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private boolean tryToOpen(ItemSlot invSlot) {
 | 
			
		||||
		if (invSlot.getAmount() != 1) {
 | 
			
		||||
		if (invSlot.getCount() != 1) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		if (!(invSlot.getContents() instanceof ItemDataContainer)) {
 | 
			
		||||
		if (!(invSlot.getItem() instanceof ItemDataContainer)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ItemDataContainer item = (ItemDataContainer) invSlot.getContents();
 | 
			
		||||
		ItemDataContainer item = (ItemDataContainer) invSlot.getItem();
 | 
			
		||||
		return item.open(workspace.getPlayerEntity()) != null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -181,38 +177,4 @@ public class InteractiveSlotComponent extends Button {
 | 
			
		||||
		return slotComponent.getSlot();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private ItemSlot getSlotOrAllocate() {
 | 
			
		||||
		ItemSlot slot = slotComponent.getSlot();
 | 
			
		||||
		
 | 
			
		||||
		if (slot == null) {
 | 
			
		||||
 | 
			
		||||
			int slotIndex = slotComponent.getSlotIndex();
 | 
			
		||||
			ItemContainer uncastContainer = slotComponent.getSlotContainer();
 | 
			
		||||
 | 
			
		||||
			assert slotIndex >= 0 : "Slot index is negative: " + slotIndex;
 | 
			
		||||
			assert slotIndex >= uncastContainer.getSlotCount()
 | 
			
		||||
				: "Slot index is valid (" + slotIndex + ") but container does not provide a slot";
 | 
			
		||||
 | 
			
		||||
			if (uncastContainer instanceof ItemContainerMixed) {
 | 
			
		||||
				ItemContainerMixed container = (ItemContainerMixed) uncastContainer;
 | 
			
		||||
				container.addSlots(slotIndex - container.getSlotCount() + 1);
 | 
			
		||||
				
 | 
			
		||||
				slot = slotComponent.getSlot();
 | 
			
		||||
				assert slot != null : "Could not allocate slot for index " + slotIndex;
 | 
			
		||||
			} else {
 | 
			
		||||
				// Leave slot null
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return slot;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void cleanContainerUpIfNecessary() {
 | 
			
		||||
		ItemContainer container = slotComponent.getSlotContainer();
 | 
			
		||||
		if (container instanceof ItemContainerMixed) {
 | 
			
		||||
			((ItemContainerMixed) container).cleanUpSlots();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,8 @@ public class InventoryWindow extends Panel {
 | 
			
		||||
	
 | 
			
		||||
	private final InventoryComponent content;
 | 
			
		||||
	private final HUDWorkspace workspace;
 | 
			
		||||
	
 | 
			
		||||
	Object layoutCookie;
 | 
			
		||||
 | 
			
		||||
	public InventoryWindow(String name, InventoryComponent component, HUDWorkspace workspace) {
 | 
			
		||||
		super(name, new LayoutVertical(15, 15));
 | 
			
		||||
 
 | 
			
		||||
@@ -51,8 +51,7 @@ public class SlotComponent extends Component {
 | 
			
		||||
	private static Renderable containerOpenDecoration = null;
 | 
			
		||||
	private static Renderable containerOpenableDecoration = null;
 | 
			
		||||
 | 
			
		||||
	private final ItemContainer container;
 | 
			
		||||
	private final int index;
 | 
			
		||||
	private final ItemSlot slot;
 | 
			
		||||
 | 
			
		||||
	private float scale = 2;
 | 
			
		||||
 | 
			
		||||
@@ -66,8 +65,7 @@ public class SlotComponent extends Component {
 | 
			
		||||
 | 
			
		||||
	public SlotComponent(String name, ItemContainer container, int index) {
 | 
			
		||||
		super(name);
 | 
			
		||||
		this.container = container;
 | 
			
		||||
		this.index = index;
 | 
			
		||||
		this.slot = new ItemSlot(container, index);
 | 
			
		||||
 | 
			
		||||
		setScale(2);
 | 
			
		||||
 | 
			
		||||
@@ -95,18 +93,18 @@ public class SlotComponent extends Component {
 | 
			
		||||
	 * @return the container
 | 
			
		||||
	 */
 | 
			
		||||
	public ItemContainer getSlotContainer() {
 | 
			
		||||
		return container;
 | 
			
		||||
		return slot.getContainer();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the index
 | 
			
		||||
	 */
 | 
			
		||||
	public int getSlotIndex() {
 | 
			
		||||
		return index;
 | 
			
		||||
		return slot.getIndex();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ItemSlot getSlot() {
 | 
			
		||||
		return container.getSlot(index);
 | 
			
		||||
		return slot;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public SlotComponent setScale(float scale) {
 | 
			
		||||
@@ -142,14 +140,7 @@ public class SlotComponent extends Component {
 | 
			
		||||
 | 
			
		||||
	private void updateItemRenderer() {
 | 
			
		||||
		ItemData contents;
 | 
			
		||||
		
 | 
			
		||||
		ItemSlot slot = getSlot();
 | 
			
		||||
		
 | 
			
		||||
		if (slot == null) {
 | 
			
		||||
			contents = null;
 | 
			
		||||
		} else {
 | 
			
		||||
			contents = slot.getContents();
 | 
			
		||||
		}
 | 
			
		||||
		contents = slot.getItem();
 | 
			
		||||
 | 
			
		||||
		if (contents == null) {
 | 
			
		||||
			itemRenderer = null;
 | 
			
		||||
@@ -160,7 +151,7 @@ public class SlotComponent extends Component {
 | 
			
		||||
				itemRenderer = ItemRenderRegistry.getInstance().get(contents.getId()).createRenderable(contents);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			int newAmount = slot.getAmount();
 | 
			
		||||
			int newAmount = slot.getCount();
 | 
			
		||||
			if (newAmount != amountDisplayInt) {
 | 
			
		||||
				amountDisplayInt = newAmount;
 | 
			
		||||
				amountDisplayString = newAmount == 1 ? "" : Integer.toString(newAmount);
 | 
			
		||||
@@ -195,7 +186,7 @@ public class SlotComponent extends Component {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void renderDecorations(ShapeRenderHelper renderer) {
 | 
			
		||||
		ItemData contents = getSlot().getContents();
 | 
			
		||||
		ItemData contents = getSlot().getItem();
 | 
			
		||||
 | 
			
		||||
		if (contents instanceof ItemDataContainer) {
 | 
			
		||||
			ItemDataContainer asContainer = (ItemDataContainer) contents;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,13 +17,49 @@
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.client.graphics.world.hud;
 | 
			
		||||
 | 
			
		||||
import glm.vec._2.Vec2;
 | 
			
		||||
import glm.vec._2.i.Vec2i;
 | 
			
		||||
import ru.windcorp.progressia.client.graphics.gui.Component;
 | 
			
		||||
import ru.windcorp.progressia.client.graphics.gui.Layout;
 | 
			
		||||
 | 
			
		||||
public class WindowedHUD extends Component {
 | 
			
		||||
	
 | 
			
		||||
	private static class Cookie {
 | 
			
		||||
		private final Vec2 relPos = new Vec2();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public WindowedHUD(String name) {
 | 
			
		||||
		super(name);
 | 
			
		||||
		setLayout(null);
 | 
			
		||||
		setLayout(new Layout() {
 | 
			
		||||
			@Override
 | 
			
		||||
			public void layout(Component c) {
 | 
			
		||||
				for (Component component : c.getChildren()) {
 | 
			
		||||
					InventoryWindow window = (InventoryWindow) component;
 | 
			
		||||
					
 | 
			
		||||
					window.setSize(window.getPreferredSize());
 | 
			
		||||
					
 | 
			
		||||
					Cookie cookie = (Cookie) window.layoutCookie;
 | 
			
		||||
					
 | 
			
		||||
					if (cookie == null) {
 | 
			
		||||
						window.layoutCookie = cookie = new Cookie();
 | 
			
		||||
						cookie.relPos.x = 0.5f;
 | 
			
		||||
						cookie.relPos.y = 2 / 3.0f;
 | 
			
		||||
					}
 | 
			
		||||
					
 | 
			
		||||
					cookie.relPos.clamp(0, 1);
 | 
			
		||||
					
 | 
			
		||||
					window.setPosition(
 | 
			
		||||
						(int) (cookie.relPos.x * c.getWidth() - window.getWidth() / 2.0f),
 | 
			
		||||
						(int) (cookie.relPos.y * c.getHeight() - window.getHeight())
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			@Override
 | 
			
		||||
			public Vec2i calculatePreferredSize(Component c) {
 | 
			
		||||
				throw new AssertionError("welp this wasnt supposed to hapen :(");
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void addWindow(InventoryWindow window) {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ package ru.windcorp.progressia.client.world.item.inventory;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import com.google.common.eventbus.Subscribe;
 | 
			
		||||
 | 
			
		||||
@@ -33,19 +34,24 @@ import ru.windcorp.progressia.client.graphics.world.hud.Bar;
 | 
			
		||||
import ru.windcorp.progressia.client.graphics.world.hud.HUDWorkspace;
 | 
			
		||||
import ru.windcorp.progressia.client.graphics.world.hud.InteractiveSlotComponent;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotAddedEvent;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotRemovedEvent;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotChangedEvent;
 | 
			
		||||
 | 
			
		||||
public class ContainerComponentSimple extends ContainerComponent {
 | 
			
		||||
 | 
			
		||||
	private final Group slots = new Group("Inventory.Slots", new LayoutGrid(0, 15));
 | 
			
		||||
	private final Collection<InteractiveSlotComponent> slotCollection = new ArrayList<>();
 | 
			
		||||
	private final List<InteractiveSlotComponent> slotCollection = new ArrayList<>();
 | 
			
		||||
	
 | 
			
		||||
	private final ItemContainer container;
 | 
			
		||||
	private final HUDWorkspace workspace;
 | 
			
		||||
	
 | 
			
		||||
	private int tmp__getSlotsPerRow() {
 | 
			
		||||
		return 6;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ContainerComponentSimple(ItemContainer container, HUDWorkspace workspace) {
 | 
			
		||||
		super("Inventory");
 | 
			
		||||
		this.container = container;
 | 
			
		||||
		this.workspace = workspace;
 | 
			
		||||
		
 | 
			
		||||
		if (container.getInventory() != null) {
 | 
			
		||||
			container.getInventory().subscribe(this);
 | 
			
		||||
@@ -77,14 +83,12 @@ public class ContainerComponentSimple extends ContainerComponent {
 | 
			
		||||
 | 
			
		||||
		addChild(slotsAndVolumeBar.setLayoutHint(LayoutBorderHorizontal.CENTER));
 | 
			
		||||
		addChild(massBar.setLayoutHint(LayoutBorderHorizontal.LEFT));
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < container.getSlotCount(); ++i) {
 | 
			
		||||
			addSlot(container, i, workspace);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		onSlotChanged(null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void addSlot(ItemContainer container, int index, HUDWorkspace workspace) {
 | 
			
		||||
		final int maxX = 6;
 | 
			
		||||
	private void addSlot(int index) {
 | 
			
		||||
		final int maxX = tmp__getSlotsPerRow();
 | 
			
		||||
 | 
			
		||||
		InteractiveSlotComponent component = new InteractiveSlotComponent("Inventory.Slot." + index, container, index, workspace);
 | 
			
		||||
 | 
			
		||||
@@ -92,6 +96,10 @@ public class ContainerComponentSimple extends ContainerComponent {
 | 
			
		||||
		slots.addChild(component.setLayoutHint(pos));
 | 
			
		||||
		slotCollection.add(component);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	private void removeSlot(int index) {
 | 
			
		||||
		slots.removeChild(slotCollection.remove(index));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public ItemContainer getContainer() {
 | 
			
		||||
@@ -104,13 +112,26 @@ public class ContainerComponentSimple extends ContainerComponent {
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Subscribe
 | 
			
		||||
	private void onSlotAdded(ItemSlotAddedEvent e) {
 | 
			
		||||
	private void onSlotChanged(ItemSlotChangedEvent e) {
 | 
			
		||||
		if (e != null && e.getContainer() != container) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Subscribe
 | 
			
		||||
	private void onSlotAdded(ItemSlotRemovedEvent e) {
 | 
			
		||||
		int wantSlots = container.getLastFilledSlot();
 | 
			
		||||
		int slotsPerRow = tmp__getSlotsPerRow();
 | 
			
		||||
		
 | 
			
		||||
		wantSlots = (wantSlots / slotsPerRow + 2) * slotsPerRow;
 | 
			
		||||
		if (wantSlots < slotsPerRow) {
 | 
			
		||||
			wantSlots = slotsPerRow;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		while (wantSlots > slotCollection.size()) {
 | 
			
		||||
			addSlot(slotCollection.size());
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		while (wantSlots < slotCollection.size()) {
 | 
			
		||||
			removeSlot(slotCollection.size() - 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,12 +17,18 @@
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item;
 | 
			
		||||
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Iterators;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.state.ObjectStateField;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.InventoryOwner;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.InventorySimple;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.InventoryUser;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainerMixedSimple;
 | 
			
		||||
 | 
			
		||||
public class ItemDataContainer extends ItemData {
 | 
			
		||||
public class ItemDataContainer extends ItemData implements InventoryOwner, ItemDataWithContainers {
 | 
			
		||||
 | 
			
		||||
	private final ObjectStateField<InventorySimple> inventory = field("Core:Contents").setShared().def(this::createInventory)
 | 
			
		||||
		.build();
 | 
			
		||||
@@ -53,13 +59,19 @@ public class ItemDataContainer extends ItemData {
 | 
			
		||||
	protected InventorySimple createInventory() {
 | 
			
		||||
		return new InventorySimple(
 | 
			
		||||
			getId(),
 | 
			
		||||
			new ItemContainerMixedSimple(getId(), containerMassLimit, containerVolumeLimit, 10)
 | 
			
		||||
			this,
 | 
			
		||||
			new ItemContainerMixedSimple(getId(), containerMassLimit, containerVolumeLimit)
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public InventorySimple getInventory() {
 | 
			
		||||
		return inventory.get(this);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public Iterator<? extends ItemContainer> getAllContainers() {
 | 
			
		||||
		return Iterators.forArray(getInventory().getContainers());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isOpen() {
 | 
			
		||||
		return !getInventory().getUsers().isEmpty();
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,14 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory.event;
 | 
			
		||||
package ru.windcorp.progressia.common.world.item;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
 | 
			
		||||
public class ItemSlotAddedEvent extends ItemSlotEvent {
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
 | 
			
		||||
	public ItemSlotAddedEvent(ItemSlot slot) {
 | 
			
		||||
		super(slot);
 | 
			
		||||
	}
 | 
			
		||||
public interface ItemDataWithContainers {
 | 
			
		||||
	
 | 
			
		||||
	Iterator<? extends ItemContainer> getAllContainers();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,120 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Progressia
 | 
			
		||||
 * Copyright (C)  2020-2021  Wind Corporation and contributors
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
 | 
			
		||||
public class Items {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to transfer as many items as possible, but no more than
 | 
			
		||||
	 * {@code max}, between two slots. The origin of the items is {@code from},
 | 
			
		||||
	 * the destination is {@code into}. This method does nothing if the items
 | 
			
		||||
	 * could not be removed from origin or could not be inserted into
 | 
			
		||||
	 * destination, such as when {@code into} contains different items.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param from the slot to transfer item from
 | 
			
		||||
	 * @param into the destination of the items
 | 
			
		||||
	 * @param max  the maximum amount of items to transfer. Use
 | 
			
		||||
	 *             {@code Integer.MAX_VALUE} to remove the limit
 | 
			
		||||
	 * @return the actual amount of items moved between slots
 | 
			
		||||
	 */
 | 
			
		||||
	public static int pour(ItemSlot from, ItemSlot into, int max) {
 | 
			
		||||
		synchronized (from) {
 | 
			
		||||
			synchronized (into) {
 | 
			
		||||
 | 
			
		||||
				ItemData item = from.getContents();
 | 
			
		||||
 | 
			
		||||
				int originalAmount = from.getAmount();
 | 
			
		||||
				int transferAmount = Math.min(originalAmount, max);
 | 
			
		||||
 | 
			
		||||
				while (transferAmount > 0 && !into.canInsert(item, transferAmount)) {
 | 
			
		||||
					transferAmount--;
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				if (transferAmount == 0) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!from.canRemove(transferAmount)) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				into.setContents(item, into.getAmount() + transferAmount);
 | 
			
		||||
				from.setAmount(originalAmount - transferAmount);
 | 
			
		||||
 | 
			
		||||
				return transferAmount;
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to transfer as many items as possible between two slots. The
 | 
			
		||||
	 * origin of the items is {@code from}, the destination is {@code into}.
 | 
			
		||||
	 * This method does nothing if the items could not be removed from origin or
 | 
			
		||||
	 * could not be inserted into destination, such as when {@code into}
 | 
			
		||||
	 * contains different items.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param from the slot to transfer item from
 | 
			
		||||
	 * @param into the destination of the items
 | 
			
		||||
	 * @return the actual amount of items moved between slots
 | 
			
		||||
	 */
 | 
			
		||||
	public static int pour(ItemSlot from, ItemSlot into) {
 | 
			
		||||
		return pour(from, into, Integer.MAX_VALUE);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to swap the contents of the two slots.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param a one of the slots
 | 
			
		||||
	 * @param b the other slot
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return whether the swap succeeded
 | 
			
		||||
	 */
 | 
			
		||||
	public static boolean swap(ItemSlot a, ItemSlot b) {
 | 
			
		||||
		synchronized (a) {
 | 
			
		||||
			synchronized (b) {
 | 
			
		||||
 | 
			
		||||
				ItemData aItem = a.getContents();
 | 
			
		||||
				int aAmount = a.getAmount();
 | 
			
		||||
				
 | 
			
		||||
				ItemData bItem = b.getContents();
 | 
			
		||||
				int bAmount = b.getAmount();
 | 
			
		||||
				
 | 
			
		||||
				a.clear();
 | 
			
		||||
				b.clear();
 | 
			
		||||
				
 | 
			
		||||
				if (a.canInsert(bItem, bAmount) && b.canInsert(aItem, aAmount)) {
 | 
			
		||||
					a.setContents(bItem, bAmount);
 | 
			
		||||
					b.setContents(aItem, aAmount);
 | 
			
		||||
					return true;
 | 
			
		||||
				} else {
 | 
			
		||||
					a.setContents(aItem, aAmount);
 | 
			
		||||
					b.setContents(bItem, bAmount);
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private Items() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Progressia
 | 
			
		||||
 * Copyright (C)  2020-2021  Wind Corporation and contributors
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A generalization of mass and volume. Not to be extended by mods.
 | 
			
		||||
 */
 | 
			
		||||
public enum LinearItemProperty {
 | 
			
		||||
	
 | 
			
		||||
	MASS,
 | 
			
		||||
	VOLUME;
 | 
			
		||||
	
 | 
			
		||||
	public float get(ItemData item) {
 | 
			
		||||
		switch (this) {
 | 
			
		||||
		case MASS:
 | 
			
		||||
			return item.getMass();
 | 
			
		||||
		case VOLUME:
 | 
			
		||||
			return item.getVolume();
 | 
			
		||||
		default:
 | 
			
		||||
			throw new AssertionError();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public float get(ItemSlot slot) {
 | 
			
		||||
		switch (this) {
 | 
			
		||||
		case MASS:
 | 
			
		||||
			return slot.getMass();
 | 
			
		||||
		case VOLUME:
 | 
			
		||||
			return slot.getVolume();
 | 
			
		||||
		default:
 | 
			
		||||
			throw new AssertionError();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public float get(ItemContainer container) {
 | 
			
		||||
		switch (this) {
 | 
			
		||||
		case MASS:
 | 
			
		||||
			return container.getMass();
 | 
			
		||||
		case VOLUME:
 | 
			
		||||
			return container.getVolume();
 | 
			
		||||
		default:
 | 
			
		||||
			throw new AssertionError();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public float getLimit(ItemContainer container) {
 | 
			
		||||
		switch (this) {
 | 
			
		||||
		case MASS:
 | 
			
		||||
			return container.getMassLimit();
 | 
			
		||||
		case VOLUME:
 | 
			
		||||
			return container.getVolumeLimit();
 | 
			
		||||
		default:
 | 
			
		||||
			throw new AssertionError();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -36,19 +36,25 @@ import ru.windcorp.progressia.common.world.item.inventory.event.InventoryOpenedE
 | 
			
		||||
 | 
			
		||||
public class Inventory extends Namespaced implements Encodable {
 | 
			
		||||
	
 | 
			
		||||
	private final InventoryOwner owner;
 | 
			
		||||
	private final ItemContainer[] containers;
 | 
			
		||||
	private final List<InventoryUser> users = new ArrayList<>();
 | 
			
		||||
	
 | 
			
		||||
	private EventBus eventBus = null;
 | 
			
		||||
 | 
			
		||||
	public Inventory(String id, ItemContainer... containers) {
 | 
			
		||||
	public Inventory(String id, InventoryOwner owner, ItemContainer... containers) {
 | 
			
		||||
		super(id);
 | 
			
		||||
		this.owner = owner;
 | 
			
		||||
		this.containers = containers;
 | 
			
		||||
		for (ItemContainer container : containers) {
 | 
			
		||||
			container.setInventory(this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public InventoryOwner getOwner() {
 | 
			
		||||
		return owner;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized void open(InventoryUser user) {
 | 
			
		||||
		users.add(user);
 | 
			
		||||
		subscribe(user);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,8 @@
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory.event;
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
 | 
			
		||||
public class ItemSlotRemovedEvent extends ItemSlotEvent {
 | 
			
		||||
 | 
			
		||||
	public ItemSlotRemovedEvent(ItemSlot slot) {
 | 
			
		||||
		super(slot);
 | 
			
		||||
	}
 | 
			
		||||
public interface InventoryOwner {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -19,8 +19,8 @@ package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
 | 
			
		||||
public class InventorySimple extends Inventory {
 | 
			
		||||
 | 
			
		||||
	public InventorySimple(String id, ItemContainer container) {
 | 
			
		||||
		super(id, container);
 | 
			
		||||
	public InventorySimple(String id, InventoryOwner owner, ItemContainer container) {
 | 
			
		||||
		super(id, owner, container);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public ItemContainer getContainer() {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,79 +18,335 @@
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.NoSuchElementException;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
import gnu.trove.iterator.TIntIterator;
 | 
			
		||||
import gnu.trove.set.TIntSet;
 | 
			
		||||
import gnu.trove.set.hash.TIntHashSet;
 | 
			
		||||
import ru.windcorp.progressia.common.state.Encodable;
 | 
			
		||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemDataWithContainers;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotChangedEvent;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A collection of {@link ItemSlot}s representing a single storage unit. A
 | 
			
		||||
 * container may impose limits on the maximum total mass and volume of its
 | 
			
		||||
 * contents, although this is not enforced on data structure level.
 | 
			
		||||
 * The base class of item containers. An item container is a set of slots with
 | 
			
		||||
 * defined limits on mass and volume. Containers are typically grouped into an
 | 
			
		||||
 * {@link Inventory} by in-game function.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * At any moment container has a definite amount of slots, each identified by a
 | 
			
		||||
 * unique index. If a container has <i>n</i> slots, its slots are numbered 0
 | 
			
		||||
 * through <i>n</i> - 1.
 | 
			
		||||
 * A container contains some number of slots indexed from 0. Users may inspect
 | 
			
		||||
 * the contents of any slot. Slots may contain an {@link ItemData} and an item
 | 
			
		||||
 * count. Item count is zero iff the {@link ItemData} is {@code null}, in which
 | 
			
		||||
 * case the slot is considered empty.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Item containers also implement mass and volume calculation. Both properties
 | 
			
		||||
 * are determined by the corresponding {@link ItemData} methods, and combine
 | 
			
		||||
 * linearly:
 | 
			
		||||
 * {@code mass(2 * stick + 5 * apple) = 2 * mass(stick) + 5 * mass(apple)},
 | 
			
		||||
 * empty slots do not contribute any mass of volume. Users may query these
 | 
			
		||||
 * computed values. In addition, a limit may be imposed on both properties.
 | 
			
		||||
 * Containers will refuse operations which would lead to the violation of these
 | 
			
		||||
 * limits. However, containers cannot detect excess mass or volume resulting
 | 
			
		||||
 * from
 | 
			
		||||
 * a implementation-triggered change of limits.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * As a safeguard against memory leaks, all containers must be provided with a
 | 
			
		||||
 * maximum possible size. Item containers will refuse operations on slots with
 | 
			
		||||
 * indices exceeding or equal to the maximum possible size.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Implementors will typically find the following subclasses useful:
 | 
			
		||||
 * <ul>
 | 
			
		||||
 * <li>{@link ItemContainerMixed} for containers allowing an arbitrary amount of
 | 
			
		||||
 * slots
 | 
			
		||||
 * <ul>
 | 
			
		||||
 * <li>see {@link ItemContainerMixedSimple} for a complete implementation</li>
 | 
			
		||||
 * </ul>
 | 
			
		||||
 * </li>
 | 
			
		||||
 * <li>{@link ItemContainerSingle} for containers providing only one slot</li>
 | 
			
		||||
 * </ul>
 | 
			
		||||
 */
 | 
			
		||||
public abstract class ItemContainer extends Namespaced implements Encodable, Iterable<ItemSlot> {
 | 
			
		||||
	
 | 
			
		||||
	private Inventory inventory;
 | 
			
		||||
public abstract class ItemContainer extends Namespaced implements Encodable {
 | 
			
		||||
 | 
			
		||||
	public ItemContainer(String id) {
 | 
			
		||||
	private Inventory inventory;
 | 
			
		||||
	private final int maxPossibleSize;
 | 
			
		||||
 | 
			
		||||
	private final TIntSet subContainersCache = new TIntHashSet();
 | 
			
		||||
 | 
			
		||||
	public ItemContainer(String id, int maxPossibleSize) {
 | 
			
		||||
		super(id);
 | 
			
		||||
		this.maxPossibleSize = maxPossibleSize;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the inventory
 | 
			
		||||
	 */
 | 
			
		||||
	public Inventory getInventory() {
 | 
			
		||||
		return inventory;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	protected void setInventory(Inventory inventory) {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param inventory the inventory to set
 | 
			
		||||
	 */
 | 
			
		||||
	void setInventory(Inventory inventory) {
 | 
			
		||||
		this.inventory = inventory;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Retrieves the slot with the given index.
 | 
			
		||||
	 * Retrieves the item in the slot at the specified index.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param index the index of the slot to retrieve
 | 
			
		||||
	 * @return the slot or {@code null} if the slot does not exist
 | 
			
		||||
	 * @param index the index of the slot to query
 | 
			
		||||
	 * @return the item or {@code null} if the slot is empty.
 | 
			
		||||
	 */
 | 
			
		||||
	public abstract ItemSlot getSlot(int index);
 | 
			
		||||
	public abstract ItemData getItem(int index);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns the current slot count of this container.
 | 
			
		||||
	 * Retrieves the item count in the slot at the specified index.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return the number of slots in this container
 | 
			
		||||
	 * @param index the index of the slot to query
 | 
			
		||||
	 * @return the item count or {@code 0} if the slot is empty
 | 
			
		||||
	 */
 | 
			
		||||
	public abstract int getSlotCount();
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public abstract void forEach(Consumer<? super ItemSlot> action);
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public Iterator<ItemSlot> iterator() {
 | 
			
		||||
		return new Iterator<ItemSlot>() {
 | 
			
		||||
			
 | 
			
		||||
			private int index = 0;
 | 
			
		||||
	public abstract int getCount(int index);
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public boolean hasNext() {
 | 
			
		||||
				return index < getSlotCount();
 | 
			
		||||
			}
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns a slot index strictly greater than all indices containing
 | 
			
		||||
	 * items. Use this index as upper bound when iterating the container's
 | 
			
		||||
	 * items. The slot at this index is empty.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return a strict upper bound on the indices of filled slots
 | 
			
		||||
	 */
 | 
			
		||||
	public abstract int getMaxIndex();
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public ItemSlot next() {
 | 
			
		||||
				if (!hasNext()) {
 | 
			
		||||
					throw new NoSuchElementException("index = " + index + ", size = " + getSlotCount());
 | 
			
		||||
				}
 | 
			
		||||
				ItemSlot slot = getSlot(index);
 | 
			
		||||
				index++;
 | 
			
		||||
				return slot;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		};
 | 
			
		||||
	/**
 | 
			
		||||
	 * Computes and returns the slot index of the last non-empty slot in this
 | 
			
		||||
	 * container. This operation is potentially slower than
 | 
			
		||||
	 * {@link #getMaxIndex()}, which is less precise.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return the index of the last filled slot, or -1 if no such slot exists
 | 
			
		||||
	 */
 | 
			
		||||
	public int getLastFilledSlot() {
 | 
			
		||||
		int index = getMaxIndex();
 | 
			
		||||
		while (isEmpty(index) && index >= 0) {
 | 
			
		||||
			index--;
 | 
			
		||||
		}
 | 
			
		||||
		return index;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determines whether items of the specified kind could be inserted into
 | 
			
		||||
	 * this container assuming a slot is available and mass and volume
 | 
			
		||||
	 * requirements of the container are satisfied.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * This method should only be a function of the configuration of the
 | 
			
		||||
	 * container and of the item; it must not depend on the contents of the
 | 
			
		||||
	 * container.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param item the item to check, not null
 | 
			
		||||
	 * @return {@code true} iff the item is allowed in this container
 | 
			
		||||
	 */
 | 
			
		||||
	protected boolean canAdd(ItemData item) {
 | 
			
		||||
		if (item == null) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (containsRecursively(item, this)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private boolean containsRecursively(ItemData haystack, ItemContainer needle) {
 | 
			
		||||
		if (!(haystack instanceof ItemDataWithContainers)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		ItemDataWithContainers haystackWithTastyHay = (ItemDataWithContainers) haystack;
 | 
			
		||||
		
 | 
			
		||||
		Iterator<? extends ItemContainer> iterator = haystackWithTastyHay.getAllContainers();
 | 
			
		||||
		while (iterator.hasNext()) {
 | 
			
		||||
			ItemContainer container = iterator.next();
 | 
			
		||||
			
 | 
			
		||||
			if (container == needle) {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			TIntIterator indexIterator = container.subContainersCache.iterator();
 | 
			
		||||
			
 | 
			
		||||
			while (indexIterator.hasNext()) {
 | 
			
		||||
				
 | 
			
		||||
				int subHaystackIndex = indexIterator.next();
 | 
			
		||||
				ItemData subHaystackOrMaybeNot = container.getItem(subHaystackIndex);
 | 
			
		||||
				if (containsRecursively(subHaystackOrMaybeNot, needle)) {
 | 
			
		||||
					return true;
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determines whether inventory users are allowed to manually remove items
 | 
			
		||||
	 * from this container.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * This method should only be a function of the configuration of the
 | 
			
		||||
	 * container; it must not depend on the contents of the container.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return {@code true} iff items could be removed from this container
 | 
			
		||||
	 */
 | 
			
		||||
	protected boolean isRemovingAllowed() {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determines whether the items can be added at the specified index. The
 | 
			
		||||
	 * slot must already contain items equal to {@code item} parameter or be
 | 
			
		||||
	 * empty. Neither total mass nor volume of the added items may exceed the
 | 
			
		||||
	 * available mass and volume of the container. Additional restrictions may
 | 
			
		||||
	 * apply.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * When index is valid, {@code item == null} and {@code count == 0}, this
 | 
			
		||||
	 * method returns {@code true}. Otherwise, when {@code count <= 0}, this
 | 
			
		||||
	 * method returns {@code false}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param index the index of the slot to query
 | 
			
		||||
	 * @param item  the item type
 | 
			
		||||
	 * @param count the amount of items to add
 | 
			
		||||
	 * @return {@code true} iff the operation is possible
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean canAdd(int index, ItemData item, int count) {
 | 
			
		||||
		if (index < 0 || index >= maxPossibleSize) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (item == null && count == 0) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (count < 0) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ItemData currentItem = getItem(index);
 | 
			
		||||
		if (currentItem == null) {
 | 
			
		||||
			// Pass
 | 
			
		||||
		} else if (currentItem.equals(item)) {
 | 
			
		||||
			// Pass
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!canAdd(item)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		float addedMass = item.getMass() * count;
 | 
			
		||||
		if (getMass() + addedMass > getMassLimit()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		float addedVolume = item.getVolume() * count;
 | 
			
		||||
		if (getVolume() + addedVolume > getVolumeLimit()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determines whether the items can be removed from the specified index. The
 | 
			
		||||
	 * slot must already contain items equal to {@code item} parameter. The item
 | 
			
		||||
	 * count must be no lower than {@code count} parameter. Additional
 | 
			
		||||
	 * restrictions may apply.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * When index is valid, {@code item == null} and {@code count == 0}, this
 | 
			
		||||
	 * method returns {@code true}. Otherwise, when {@code count <= 0}, this
 | 
			
		||||
	 * method returns {@code false}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param index the index of the slot to query
 | 
			
		||||
	 * @param item  the item type
 | 
			
		||||
	 * @param count the amount of items to remove
 | 
			
		||||
	 * @return {@code true} iff the operation is possible
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean canRemove(int index, ItemData item, int count) {
 | 
			
		||||
		if (index < 0 || index >= maxPossibleSize) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (item == null && count == 0) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (count < 0) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!isRemovingAllowed()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ItemData currentItem = getItem(index);
 | 
			
		||||
		if (currentItem == null) {
 | 
			
		||||
			return false;
 | 
			
		||||
		} else if (currentItem.equals(item)) {
 | 
			
		||||
			// Pass
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (getCount(index) < count) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to add the provided items to the specified slot in the
 | 
			
		||||
	 * container. This method modifies the data structure directly; use an
 | 
			
		||||
	 * appropriate {@link Items} method to add items in a safe and convenient
 | 
			
		||||
	 * way.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * A {@link #canAdd(int, ItemData, int)} check is performed. If the check
 | 
			
		||||
	 * fails, the method does not alter the contents of the container and
 | 
			
		||||
	 * returns {@code false}. If the check succeeds, the item type and item
 | 
			
		||||
	 * count of the referenced slot are altered appropriately and {@code true}
 | 
			
		||||
	 * is returned.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * When {@code item == null} or {@code count <= 0}, this method returns
 | 
			
		||||
	 * {@code false}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param index the index of the slot to alter
 | 
			
		||||
	 * @param item  the item type
 | 
			
		||||
	 * @param count the amount of items to add
 | 
			
		||||
	 * @return {@code true} iff the container was changed as the result of this
 | 
			
		||||
	 *         operation
 | 
			
		||||
	 */
 | 
			
		||||
	protected abstract boolean add(int index, ItemData item, int count);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to remove the provided items from the specified slot in the
 | 
			
		||||
	 * container. This method modifies the data structure directly; use an
 | 
			
		||||
	 * appropriate {@link Items} method to remove items in a safe and convenient
 | 
			
		||||
	 * way.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * A {@link #canRemove(int, ItemData, int)} check is performed. If the check
 | 
			
		||||
	 * fails, the method does not alter the contents of the container and
 | 
			
		||||
	 * returns {@code false}. If the check succeeds, the item type and item
 | 
			
		||||
	 * count of the referenced slot are altered appropriately and {@code true}
 | 
			
		||||
	 * is returned.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * When {@code item == null} or {@code count <= 0}, this method returns
 | 
			
		||||
	 * {@code false}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param index the index of the slot to alter
 | 
			
		||||
	 * @param item  the item type
 | 
			
		||||
	 * @param count the amount of items to remove
 | 
			
		||||
	 * @return {@code true} iff the container was changed as the result of this
 | 
			
		||||
	 *         operation
 | 
			
		||||
	 */
 | 
			
		||||
	protected abstract boolean remove(int index, ItemData item, int count);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Computes and returns the mass limit that the container imposes.
 | 
			
		||||
	 * 
 | 
			
		||||
@@ -108,33 +364,162 @@ public abstract class ItemContainer extends Namespaced implements Encodable, Ite
 | 
			
		||||
	 *         boundary is set
 | 
			
		||||
	 */
 | 
			
		||||
	public abstract float getVolumeLimit();
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	public synchronized float getMass() {
 | 
			
		||||
		float sum = 0;
 | 
			
		||||
		for (int i = 0; i < getSlotCount(); ++i) {
 | 
			
		||||
			ItemSlot slot = getSlot(i);
 | 
			
		||||
			
 | 
			
		||||
			if (slot.isEmpty()) {
 | 
			
		||||
		for (int i = 0; i < getMaxIndex(); ++i) {
 | 
			
		||||
			ItemData data = getItem(i);
 | 
			
		||||
 | 
			
		||||
			if (data == null) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			sum += slot.getContents().getMass() * slot.getAmount();
 | 
			
		||||
		}
 | 
			
		||||
		return sum;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized float getVolume() {
 | 
			
		||||
		float sum = 0;
 | 
			
		||||
		for (int i = 0; i < getSlotCount(); ++i) {
 | 
			
		||||
			ItemSlot slot = getSlot(i);
 | 
			
		||||
			
 | 
			
		||||
			if (slot.isEmpty()) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			sum += slot.getContents().getVolume() * slot.getAmount();
 | 
			
		||||
 | 
			
		||||
			sum += data.getMass() * getCount(i);
 | 
			
		||||
		}
 | 
			
		||||
		return sum;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public synchronized float getVolume() {
 | 
			
		||||
		float sum = 0;
 | 
			
		||||
		for (int i = 0; i < getMaxIndex(); ++i) {
 | 
			
		||||
			ItemData data = getItem(i);
 | 
			
		||||
 | 
			
		||||
			if (data == null) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			sum += data.getVolume() * getCount(i);
 | 
			
		||||
		}
 | 
			
		||||
		return sum;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void fireSlotChangeEvent(int index) {
 | 
			
		||||
		if (getItem(index) instanceof ItemDataWithContainers) {
 | 
			
		||||
			subContainersCache.add(index);
 | 
			
		||||
		} else {
 | 
			
		||||
			subContainersCache.remove(index);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		Inventory inventory = this.inventory;
 | 
			
		||||
		if (inventory == null || inventory.getEventBus() == null) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		inventory.getEventBus().post(new ItemSlotChangedEvent(this, index));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks class invariants and throws an {@link IllegalStateException} in
 | 
			
		||||
	 * case of discrepancies.
 | 
			
		||||
	 */
 | 
			
		||||
	protected synchronized void checkState() {
 | 
			
		||||
		int maxIndex = getMaxIndex();
 | 
			
		||||
		for (int index = 0; index < maxIndex; ++index) {
 | 
			
		||||
 | 
			
		||||
			ItemData item = getItem(index);
 | 
			
		||||
			int count = getCount(index);
 | 
			
		||||
 | 
			
		||||
			if ((item == null) != (count == 0)) {
 | 
			
		||||
				if (item == null) {
 | 
			
		||||
					throw new IllegalStateException("Item is null but count (" + count + ") != 0 in slot " + index);
 | 
			
		||||
				} else {
 | 
			
		||||
					throw new IllegalStateException("Item is " + item + " but count is zero in slot " + index);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (count < 0) {
 | 
			
		||||
				throw new IllegalStateException("count is negative: " + count + " in slot " + index);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			boolean isContainer = item instanceof ItemDataWithContainers;
 | 
			
		||||
			if (isContainer != subContainersCache.contains(index)) {
 | 
			
		||||
				if (!isContainer) {
 | 
			
		||||
					throw new IllegalStateException(
 | 
			
		||||
						"subContainersCache is invalid: item in slot " + index + " (" + item
 | 
			
		||||
							+ ") is cached as a container"
 | 
			
		||||
					);
 | 
			
		||||
				} else {
 | 
			
		||||
					throw new IllegalStateException(
 | 
			
		||||
						"subContainersCache is invalid: item in slot " + index + " (" + item
 | 
			
		||||
							+ ") is not cached as a container"
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (isContainer) {
 | 
			
		||||
				if (containsRecursively(item, this)) {
 | 
			
		||||
					throw new IllegalStateException("Recursion detected in slot " + index);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Using negation in following checks to trigger errors if any value is
 | 
			
		||||
		// NaN
 | 
			
		||||
		// (since all comparisons return false if any operand is NaN)
 | 
			
		||||
 | 
			
		||||
		float mass = getMass();
 | 
			
		||||
		if (!(mass >= 0)) {
 | 
			
		||||
			throw new IllegalStateException("Mass is negative: " + mass);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		float massLimit = getMassLimit();
 | 
			
		||||
		if (!(mass <= massLimit)) {
 | 
			
		||||
			throw new IllegalStateException("Mass is greater than mass limit: " + mass + " > " + massLimit);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		float volume = getVolume();
 | 
			
		||||
		if (!(volume >= 0)) {
 | 
			
		||||
			throw new IllegalStateException("Volume is negative: " + volume);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		float volumeLimit = getVolumeLimit();
 | 
			
		||||
		if (!(volume <= volumeLimit)) {
 | 
			
		||||
			throw new IllegalStateException("Volume is greater than volume limit: " + volume + " > " + volumeLimit);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@FunctionalInterface
 | 
			
		||||
	public interface SlotConsumer {
 | 
			
		||||
		void accept(ItemData item, int count);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Invokes the provided action for each slot in this container. The action
 | 
			
		||||
	 * is run for empty slots, in which case call is invoked with parameters
 | 
			
		||||
	 * {@code (null, 0)} for each empty slot. The exact amount of invocations is
 | 
			
		||||
	 * determined by {@link #getMaxIndex()}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param action the action to run
 | 
			
		||||
	 * @see #forEachItem(SlotConsumer)
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized void forEachSlot(SlotConsumer action) {
 | 
			
		||||
		int maxIndex = getMaxIndex();
 | 
			
		||||
		for (int i = 0; i < maxIndex; ++i) {
 | 
			
		||||
			action.accept(getItem(i), getCount(i));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Invokes the provided action for each non-empty slot in this container.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param action the action to run
 | 
			
		||||
	 * @see #forEachSlot(SlotConsumer)
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized void forEachItem(SlotConsumer action) {
 | 
			
		||||
		int maxIndex = getMaxIndex();
 | 
			
		||||
		for (int i = 0; i < maxIndex; ++i) {
 | 
			
		||||
 | 
			
		||||
			int count = getCount(i);
 | 
			
		||||
			if (count != 0) {
 | 
			
		||||
				action.accept(getItem(i), count);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isEmpty(int index) {
 | 
			
		||||
		return getCount(index) == 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,10 +28,20 @@ public class ItemContainerEquipment extends ItemContainerSingle {
 | 
			
		||||
	private final SpeciesData.EquipmentSlot equipmentSlot;
 | 
			
		||||
 | 
			
		||||
	public ItemContainerEquipment(String id, SpeciesData.EquipmentSlot equipmentSlot) {
 | 
			
		||||
		super(id, EQUIP_MASS_LIMIT, EQUIP_VOLUME_LIMIT);
 | 
			
		||||
		super(id);
 | 
			
		||||
		this.equipmentSlot = equipmentSlot;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getMassLimit() {
 | 
			
		||||
		return EQUIP_MASS_LIMIT;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolumeLimit() {
 | 
			
		||||
		return EQUIP_VOLUME_LIMIT;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public SpeciesData.EquipmentSlot getEquipmentSlot() {
 | 
			
		||||
		return equipmentSlot;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,10 +29,20 @@ public class ItemContainerHand extends ItemContainerSingle {
 | 
			
		||||
	private final SpeciesData.Hand hand;
 | 
			
		||||
 | 
			
		||||
	public ItemContainerHand(String id, Hand hand) {
 | 
			
		||||
		super(id, HAND_MASS_LIMIT, HAND_VOLUME_LIMIT);
 | 
			
		||||
		super(id);
 | 
			
		||||
		this.hand = hand;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getMassLimit() {
 | 
			
		||||
		return HAND_MASS_LIMIT;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolumeLimit() {
 | 
			
		||||
		return HAND_VOLUME_LIMIT;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public SpeciesData.Hand getHand() {
 | 
			
		||||
		return hand;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,198 +20,214 @@ package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
import java.io.DataInput;
 | 
			
		||||
import java.io.DataOutput;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
import com.google.common.eventbus.Subscribe;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.state.Encodable;
 | 
			
		||||
import ru.windcorp.progressia.common.state.IOContext;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotAddedEvent;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotChangedEvent;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotRemovedEvent;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An {@link ItemContainer} capable of storing multiple item stacks. The set
 | 
			
		||||
 * of slots is dynamic: new slots may be added and existing slots may be removed
 | 
			
		||||
 * at will.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class ItemContainerMixed extends ItemContainer {
 | 
			
		||||
 | 
			
		||||
	public static final int MAX_SLOTS = 10000;
 | 
			
		||||
	
 | 
			
		||||
	private final List<ItemSlot> slots = new ArrayList<>();
 | 
			
		||||
	private static final int GROWTH_STEP = 10;
 | 
			
		||||
	private static final int MINIMUM_CAPACITY = 10;
 | 
			
		||||
	
 | 
			
		||||
	private ItemData[] items = new ItemData[MINIMUM_CAPACITY];
 | 
			
		||||
	private int[] counts = new int[MINIMUM_CAPACITY];
 | 
			
		||||
 | 
			
		||||
	public ItemContainerMixed(String id) {
 | 
			
		||||
		super(id);
 | 
			
		||||
		super(id, MAX_SLOTS);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	protected void setInventory(Inventory inventory) {
 | 
			
		||||
		if (getInventory() != null) {
 | 
			
		||||
			getInventory().unsubscribe(this);
 | 
			
		||||
	protected void setCapacity(int minimumCapacity) {
 | 
			
		||||
		if (minimumCapacity < 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		super.setInventory(inventory);
 | 
			
		||||
		int newCapacity = ((minimumCapacity - MINIMUM_CAPACITY - 1) / GROWTH_STEP + 1) * GROWTH_STEP + MINIMUM_CAPACITY;
 | 
			
		||||
		
 | 
			
		||||
		if (getInventory() != null) {
 | 
			
		||||
			getInventory().subscribe(this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Appends additional empty slots to the end of this container.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param amount the amount of slots to add
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized void addSlots(int amount) {
 | 
			
		||||
		Inventory inventory = getInventory();
 | 
			
		||||
		ItemData[] newItems = new ItemData[newCapacity];
 | 
			
		||||
		int[] newCounts = new int[newCapacity];
 | 
			
		||||
		
 | 
			
		||||
		for (int i = 0; i < amount; ++i) {
 | 
			
		||||
			ItemSlot slot = createSlot(slots.size());
 | 
			
		||||
			slots.add(slot);
 | 
			
		||||
			slot.setContainer(this);
 | 
			
		||||
			
 | 
			
		||||
			if (inventory != null) {
 | 
			
		||||
				inventory.getEventBus().post(new ItemSlotAddedEvent(slot));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		int length = Math.min(this.items.length, newItems.length);
 | 
			
		||||
		System.arraycopy(this.items, 0, newItems, 0, length);
 | 
			
		||||
		System.arraycopy(this.counts, 0, newCounts, 0, length);
 | 
			
		||||
		
 | 
			
		||||
		this.items = newItems;
 | 
			
		||||
		this.counts = newCounts;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Instantiates a new slot object that will be appended to the container. 
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param index the index that the new slot will receive
 | 
			
		||||
	 * @return the new slot
 | 
			
		||||
	 */
 | 
			
		||||
	protected abstract ItemSlot createSlot(int index);
 | 
			
		||||
	
 | 
			
		||||
	@Subscribe
 | 
			
		||||
	private void onSlotChanged(ItemSlotChangedEvent e) {
 | 
			
		||||
		cleanUpSlots();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public void cleanUpSlots() {
 | 
			
		||||
		Inventory inventory = getInventory();
 | 
			
		||||
		Collection<ItemSlotRemovedEvent> events = null;
 | 
			
		||||
		
 | 
			
		||||
		// Do not remove slot 0
 | 
			
		||||
		for (int i = slots.size() - 1; i > 0; --i) {
 | 
			
		||||
			ItemSlot slot = slots.get(i);
 | 
			
		||||
			if (slot.isEmpty()) {
 | 
			
		||||
				slots.remove(i);
 | 
			
		||||
				
 | 
			
		||||
				if (inventory != null) {
 | 
			
		||||
					if (events == null) {
 | 
			
		||||
						events = new ArrayList<>(slots.size() - i);
 | 
			
		||||
					}
 | 
			
		||||
					events.add(new ItemSlotRemovedEvent(slot));
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
	protected void ensureCapacity(int minimumCapacity) {
 | 
			
		||||
		if (items.length >= minimumCapacity) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (events != null) {
 | 
			
		||||
			// events != null only if inventory != null 
 | 
			
		||||
			events.forEach(inventory.getEventBus()::post);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public ItemSlot getSlot(int index) {
 | 
			
		||||
		if (index < 0 || index >= slots.size()) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
		return slots.get(index);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getSlotCount() {
 | 
			
		||||
		return slots.size();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void forEach(Consumer<? super ItemSlot> action) {
 | 
			
		||||
		slots.forEach(action);
 | 
			
		||||
		setCapacity(minimumCapacity);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public synchronized void read(DataInput input, IOContext context) throws IOException {
 | 
			
		||||
		synchronized (slots) {
 | 
			
		||||
		int size = input.readInt();
 | 
			
		||||
		
 | 
			
		||||
		ensureCapacity(size);
 | 
			
		||||
 | 
			
		||||
			int needSlots = input.readInt();
 | 
			
		||||
			int hasSlots = slots.size();
 | 
			
		||||
		for (int index = 0; index < size; ++index) {
 | 
			
		||||
 | 
			
		||||
			int costOfResetting = needSlots;
 | 
			
		||||
			int costOfEditing = Math.abs(needSlots - hasSlots);
 | 
			
		||||
			ItemData item;
 | 
			
		||||
			int count = input.readInt();
 | 
			
		||||
 | 
			
		||||
			if (costOfResetting < costOfEditing) {
 | 
			
		||||
				slots.clear();
 | 
			
		||||
				addSlots(needSlots);
 | 
			
		||||
			if (count != 0) {
 | 
			
		||||
				String id = input.readUTF();
 | 
			
		||||
				item = ItemDataRegistry.getInstance().create(id);
 | 
			
		||||
				item.read(input, context);
 | 
			
		||||
			} else {
 | 
			
		||||
				while (slots.size() > needSlots) {
 | 
			
		||||
					slots.remove(slots.size() - 1);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (slots.size() < needSlots) {
 | 
			
		||||
					addSlots(needSlots - slots.size());
 | 
			
		||||
				}
 | 
			
		||||
				item = null;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (int i = 0; i < needSlots; ++i) {
 | 
			
		||||
				slots.get(i).read(input, context);
 | 
			
		||||
			}
 | 
			
		||||
			items[index] = item;
 | 
			
		||||
			counts[index] = count;
 | 
			
		||||
			
 | 
			
		||||
			fireSlotChangeEvent(index);
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		checkState();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public synchronized void write(DataOutput output, IOContext context) throws IOException {
 | 
			
		||||
		synchronized (slots) {
 | 
			
		||||
 | 
			
		||||
			output.writeInt(slots.size());
 | 
			
		||||
			for (int i = 0; i < slots.size(); ++i) {
 | 
			
		||||
				slots.get(i).write(output, context);
 | 
			
		||||
		int size = items.length;
 | 
			
		||||
		output.writeInt(size);
 | 
			
		||||
 | 
			
		||||
		for (int index = 0; index < size; ++index) {
 | 
			
		||||
			output.writeInt(counts[index]);
 | 
			
		||||
			ItemData item = items[index];
 | 
			
		||||
 | 
			
		||||
			if (item != null) {
 | 
			
		||||
				output.writeUTF(item.getId());
 | 
			
		||||
				item.write(output, context);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void copy(Encodable destination) {
 | 
			
		||||
		ItemContainerMixed container = (ItemContainerMixed) destination;
 | 
			
		||||
		List<ItemSlot> mySlots = this.slots;
 | 
			
		||||
		List<ItemSlot> containerSlots = container.slots;
 | 
			
		||||
		ItemContainerMixed other = (ItemContainerMixed) destination;
 | 
			
		||||
		int myLength = this.items.length;
 | 
			
		||||
 | 
			
		||||
		synchronized (mySlots) {
 | 
			
		||||
			synchronized (containerSlots) {
 | 
			
		||||
		synchronized (this) {
 | 
			
		||||
			synchronized (other) {
 | 
			
		||||
				
 | 
			
		||||
				other.setCapacity(myLength);
 | 
			
		||||
				System.arraycopy(this.counts, 0, other.counts, 0, myLength);
 | 
			
		||||
 | 
			
		||||
				int needSlots = mySlots.size();
 | 
			
		||||
				int hasSlots = containerSlots.size();
 | 
			
		||||
				for (int i = 0; i < myLength; ++i) {
 | 
			
		||||
					ItemData myItem = this.items[i];
 | 
			
		||||
					ItemData otherItem;
 | 
			
		||||
 | 
			
		||||
				int costOfResetting = needSlots;
 | 
			
		||||
				int costOfEditing = Math.abs(needSlots - hasSlots);
 | 
			
		||||
 | 
			
		||||
				if (costOfResetting < costOfEditing) {
 | 
			
		||||
					containerSlots.clear();
 | 
			
		||||
					container.addSlots(needSlots);
 | 
			
		||||
				} else {
 | 
			
		||||
					while (containerSlots.size() > needSlots) {
 | 
			
		||||
						slots.remove(containerSlots.size() - 1);
 | 
			
		||||
					if (myItem == null) {
 | 
			
		||||
						otherItem = null;
 | 
			
		||||
					} else {
 | 
			
		||||
						otherItem = ItemDataRegistry.getInstance().create(myItem.getId());
 | 
			
		||||
						myItem.copy(otherItem);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if (containerSlots.size() < needSlots) {
 | 
			
		||||
						addSlots(needSlots - containerSlots.size());
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for (int i = 0; i < needSlots; ++i) {
 | 
			
		||||
					mySlots.get(i).copy(containerSlots.get(i));
 | 
			
		||||
					other.items[i] = otherItem;
 | 
			
		||||
					other.fireSlotChangeEvent(i);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public ItemData getItem(int index) {
 | 
			
		||||
		if (index < 0 || index >= items.length) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
		return items[index];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getCount(int index) {
 | 
			
		||||
		if (index < 0 || index >= counts.length) {
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
		return counts[index];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getMaxIndex() {
 | 
			
		||||
		return items.length;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean add(int index, ItemData item, int count) {
 | 
			
		||||
		if (!canAdd(index, item, count)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (item != null) {
 | 
			
		||||
			ensureCapacity(index + 1);
 | 
			
		||||
			this.items[index] = item;
 | 
			
		||||
			this.counts[index] += count;
 | 
			
		||||
			fireSlotChangeEvent(index);
 | 
			
		||||
			checkState();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean remove(int index, ItemData item, int count) {
 | 
			
		||||
		if (!canRemove(index, item, count)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (count != 0) {
 | 
			
		||||
			this.counts[index] -= count;
 | 
			
		||||
			
 | 
			
		||||
			if (this.counts[index] == 0) {
 | 
			
		||||
				this.items[index] = null;
 | 
			
		||||
				shrinkIfPossible();
 | 
			
		||||
			}
 | 
			
		||||
	
 | 
			
		||||
			fireSlotChangeEvent(index);
 | 
			
		||||
			checkState();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void shrinkIfPossible() {
 | 
			
		||||
		int upperBound;
 | 
			
		||||
		
 | 
			
		||||
		for (upperBound = counts.length; upperBound > MINIMUM_CAPACITY; --upperBound) {
 | 
			
		||||
			if (counts[upperBound - 1] != 0) {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (upperBound != counts.length) {
 | 
			
		||||
			setCapacity(upperBound);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected synchronized void checkState() {
 | 
			
		||||
		super.checkState();
 | 
			
		||||
		
 | 
			
		||||
		if (items.length > MAX_SLOTS) {
 | 
			
		||||
			throw new IllegalStateException("Container has more than " + MAX_SLOTS + " slots (items): " + items.length);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (counts.length > MAX_SLOTS) {
 | 
			
		||||
			throw new IllegalStateException(
 | 
			
		||||
				"Container has more than " + MAX_SLOTS + " slots (counts): " + counts.length
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,22 +21,11 @@ public class ItemContainerMixedSimple extends ItemContainerMixed {
 | 
			
		||||
	
 | 
			
		||||
	private final float massLimit;
 | 
			
		||||
	private final float volumeLimit;
 | 
			
		||||
 | 
			
		||||
	public ItemContainerMixedSimple(String id, float massLimit, float volumeLimit) {
 | 
			
		||||
		this(id, massLimit, volumeLimit, 1);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public ItemContainerMixedSimple(String id, float massLimit, float volumeLimit, int startingSlots) {
 | 
			
		||||
	public ItemContainerMixedSimple(String id, float massLimit, float volumeLimit) {
 | 
			
		||||
		super(id);
 | 
			
		||||
		this.massLimit = massLimit;
 | 
			
		||||
		this.volumeLimit = volumeLimit;
 | 
			
		||||
		
 | 
			
		||||
		addSlots(startingSlots);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public ItemSlot createSlot(int index) {
 | 
			
		||||
		return new ItemSlot();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
 
 | 
			
		||||
@@ -20,48 +20,89 @@ package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
import java.io.DataInput;
 | 
			
		||||
import java.io.DataOutput;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.state.Encodable;
 | 
			
		||||
import ru.windcorp.progressia.common.state.IOContext;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
 | 
			
		||||
 | 
			
		||||
public abstract class ItemContainerSingle extends ItemContainer {
 | 
			
		||||
	
 | 
			
		||||
	private final ItemSlot slot = new ItemSlot();
 | 
			
		||||
	
 | 
			
		||||
	private final float massLimit;
 | 
			
		||||
	private final float volumeLimit;
 | 
			
		||||
	private ItemData item;
 | 
			
		||||
	private int count;
 | 
			
		||||
	private ItemSlot slot = new ItemSlot(this, 0);
 | 
			
		||||
 | 
			
		||||
	public ItemContainerSingle(String id, float massLimit, float volumeLimit) {
 | 
			
		||||
		super(id);
 | 
			
		||||
		this.massLimit = massLimit;
 | 
			
		||||
		this.volumeLimit = volumeLimit;
 | 
			
		||||
	public ItemContainerSingle(String id) {
 | 
			
		||||
		super(id, 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public synchronized void read(DataInput input, IOContext context) throws IOException {
 | 
			
		||||
		count = input.readInt();
 | 
			
		||||
		if (count != 0) {
 | 
			
		||||
			String id = input.readUTF();
 | 
			
		||||
			item = ItemDataRegistry.getInstance().create(id);
 | 
			
		||||
			item.read(input, context);
 | 
			
		||||
		} else {
 | 
			
		||||
			item = null;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		slot.setContainer(this);
 | 
			
		||||
		fireSlotChangeEvent(0);
 | 
			
		||||
		checkState();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void read(DataInput input, IOContext context) throws IOException {
 | 
			
		||||
		slot.read(input, context);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void write(DataOutput output, IOContext context) throws IOException {
 | 
			
		||||
		slot.write(output, context);
 | 
			
		||||
	public synchronized void write(DataOutput output, IOContext context) throws IOException {
 | 
			
		||||
		output.writeInt(count);
 | 
			
		||||
		if (item != null) {
 | 
			
		||||
			output.writeUTF(item.getId());
 | 
			
		||||
			item.write(output, context);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void copy(Encodable destination) {
 | 
			
		||||
		slot.copy(((ItemContainerSingle) destination).slot);
 | 
			
		||||
		ItemContainerSingle other = (ItemContainerSingle) destination;
 | 
			
		||||
 | 
			
		||||
		synchronized (this) {
 | 
			
		||||
			synchronized (other) {
 | 
			
		||||
				other.count = this.count;
 | 
			
		||||
				
 | 
			
		||||
				if (this.item == null) {
 | 
			
		||||
					other.item = null;
 | 
			
		||||
				} else {
 | 
			
		||||
					if (other.item == null || !other.item.isLike(this.item)) {
 | 
			
		||||
						other.item = ItemDataRegistry.getInstance().create(this.item.getId());
 | 
			
		||||
					}
 | 
			
		||||
					this.item.copy(other.item);
 | 
			
		||||
					other.fireSlotChangeEvent(0);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public ItemSlot getSlot(int index) {
 | 
			
		||||
		if (index == 0) {
 | 
			
		||||
			return slot;
 | 
			
		||||
		} else {
 | 
			
		||||
	public ItemData getItem(int index) {
 | 
			
		||||
		if (index != 0) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
		return item;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public ItemData getItem() {
 | 
			
		||||
		return item;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getCount(int index) {
 | 
			
		||||
		if (index != 0) {
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
		return count;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public int getCount() {
 | 
			
		||||
		return count;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public ItemSlot slot() {
 | 
			
		||||
@@ -69,23 +110,42 @@ public abstract class ItemContainerSingle extends ItemContainer {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int getSlotCount() {
 | 
			
		||||
	public int getMaxIndex() {
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public void forEach(Consumer<? super ItemSlot> action) {
 | 
			
		||||
		action.accept(slot);
 | 
			
		||||
	protected boolean add(int index, ItemData item, int count) {
 | 
			
		||||
		if (!canAdd(index, item, count)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (item != null) {
 | 
			
		||||
			this.item = item;
 | 
			
		||||
			this.count += count;
 | 
			
		||||
			fireSlotChangeEvent(0);
 | 
			
		||||
			checkState();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getMassLimit() {
 | 
			
		||||
		return massLimit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public float getVolumeLimit() {
 | 
			
		||||
		return volumeLimit;
 | 
			
		||||
	protected boolean remove(int index, ItemData item, int count) {
 | 
			
		||||
		if (!canRemove(index, item, count)) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (count != 0) {
 | 
			
		||||
			this.count -= count;
 | 
			
		||||
			if (this.count == 0) {
 | 
			
		||||
				this.item = null;
 | 
			
		||||
			}
 | 
			
		||||
			fireSlotChangeEvent(0);
 | 
			
		||||
			checkState();
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,182 +17,124 @@
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
 | 
			
		||||
import java.io.DataInput;
 | 
			
		||||
import java.io.DataOutput;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.state.Encodable;
 | 
			
		||||
import ru.windcorp.progressia.common.state.IOContext;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotChangedEvent;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An entity optionally containing an {@link ItemData}. Item slots are typically
 | 
			
		||||
 * found in {@link ItemContainerMixed}s.
 | 
			
		||||
 * A reference to a slot in a container. The container and the index of an
 | 
			
		||||
 * {@code ItemSlot} cannot be changed.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@code ItemSlot}s are wrapper objects; there may be multiple objects
 | 
			
		||||
 * referencing a single slot. Slot objects are considered
 | 
			
		||||
 * {@linkplain #equals(Object) equal} iff their indices are equal and they refer
 | 
			
		||||
 * to the same container.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This class provides public methods for fetching slot contents but not for
 | 
			
		||||
 * changing them. To alter a slot, use an appropriate method from {@link Items}.
 | 
			
		||||
 */
 | 
			
		||||
public class ItemSlot implements Encodable {
 | 
			
		||||
public class ItemSlot {
 | 
			
		||||
 | 
			
		||||
	private final ItemContainer container;
 | 
			
		||||
	private final int index;
 | 
			
		||||
 | 
			
		||||
	public ItemSlot(ItemContainer container, int index) {
 | 
			
		||||
		this.container = container;
 | 
			
		||||
		this.index = index;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private ItemData contents;
 | 
			
		||||
	private int amount;
 | 
			
		||||
	
 | 
			
		||||
	private ItemContainer container;
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the container
 | 
			
		||||
	 */
 | 
			
		||||
	public ItemContainer getContainer() {
 | 
			
		||||
		return container;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the index
 | 
			
		||||
	 */
 | 
			
		||||
	public int getIndex() {
 | 
			
		||||
		return index;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Inventory getInventory() {
 | 
			
		||||
		return getContainer().getInventory();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param container the container to set
 | 
			
		||||
	 */
 | 
			
		||||
	void setContainer(ItemContainer container) {
 | 
			
		||||
		this.container = container;
 | 
			
		||||
		return container.getInventory();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Retrieves the contents of this slot.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return the stored {@link ItemData} or {@code null}
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized final ItemData getContents() {
 | 
			
		||||
		return contents;
 | 
			
		||||
	public ItemData getItem() {
 | 
			
		||||
		return container.getItem(index);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Sets the new contents of this slot. If an item stack was present
 | 
			
		||||
	 * previously, it is discarded. If the contents are {@code null}, the slot
 | 
			
		||||
	 * is emptied.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * When the slot receives non-null contents, the new amount must be a
 | 
			
		||||
	 * positive integer. When the slot is emptied, {@code amount} must be 0.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param contents the new contents of this slot or {@code null} to clear
 | 
			
		||||
	 *                 the slot
 | 
			
		||||
	 * @param amount   the amount of items to set.
 | 
			
		||||
	 *                 {@code (amount == 0) == (contents == null)} must be true.
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized final void setContents(ItemData contents, int amount) {
 | 
			
		||||
		this.contents = contents;
 | 
			
		||||
		this.amount = amount;
 | 
			
		||||
		
 | 
			
		||||
		checkState();
 | 
			
		||||
		
 | 
			
		||||
		Inventory inventory = getInventory();
 | 
			
		||||
		if (inventory != null) {
 | 
			
		||||
			inventory.getEventBus().post(new ItemSlotChangedEvent(this));
 | 
			
		||||
		}
 | 
			
		||||
	public int getCount() {
 | 
			
		||||
		return container.getCount(index);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Sets the amount of items stored in this slot.
 | 
			
		||||
	 * <p>
 | 
			
		||||
	 * Setting the amount to zero also erases the slot's contents.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param amount the new amount
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized void setAmount(int amount) {
 | 
			
		||||
		setContents(amount == 0 ? null : contents, amount);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Clears this slot
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized void clear() {
 | 
			
		||||
		setContents(null, 0);
 | 
			
		||||
	public boolean isEmpty() {
 | 
			
		||||
		return container.isEmpty(index);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Retrieves the amount of items stored in this slot. If not items are
 | 
			
		||||
	 * present, this returns 0.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return the amount of items stored
 | 
			
		||||
	 */
 | 
			
		||||
	public synchronized int getAmount() {
 | 
			
		||||
		return amount;
 | 
			
		||||
	public boolean canAdd(ItemData item, int count) {
 | 
			
		||||
		return container.canAdd(index, item, count);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized boolean isEmpty() {
 | 
			
		||||
		return amount == 0;
 | 
			
		||||
 | 
			
		||||
	public boolean canRemove(ItemData item, int count) {
 | 
			
		||||
		return container.canRemove(index, item, count);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized boolean canInsert(ItemData contents, int amount) {
 | 
			
		||||
		if (contents == null) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (container.getMass() + contents.getMass() * amount > container.getMassLimit()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (container.getVolume() + contents.getVolume() * amount > container.getVolumeLimit()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return this.contents == null || this.contents.equals(contents);
 | 
			
		||||
 | 
			
		||||
	protected boolean add(ItemData item, int count) {
 | 
			
		||||
		return container.add(index, item, count);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public synchronized boolean canRemove(int amount) {
 | 
			
		||||
		return this.amount >= amount;
 | 
			
		||||
 | 
			
		||||
	protected boolean remove(ItemData item, int count) {
 | 
			
		||||
		return container.remove(index, item, count);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	private synchronized void checkState() {
 | 
			
		||||
		if ((contents == null) != (amount == 0)) {
 | 
			
		||||
			if (contents == null) {
 | 
			
		||||
				throw new IllegalArgumentException("Contents is null but amount (" + amount + ") != 0");
 | 
			
		||||
			} else {
 | 
			
		||||
				throw new IllegalArgumentException("Contents is " + contents + " but amount is zero");
 | 
			
		||||
 | 
			
		||||
	public float getMass() {
 | 
			
		||||
		synchronized (container) {
 | 
			
		||||
			int count = getCount();
 | 
			
		||||
			if (count == 0) {
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		if (amount < 0) {
 | 
			
		||||
			throw new IllegalArgumentException("amount is negative: " + amount);
 | 
			
		||||
			return count * getItem().getMass();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public synchronized void read(DataInput input, IOContext context) throws IOException {
 | 
			
		||||
		amount = input.readInt();
 | 
			
		||||
		if (amount != 0) {
 | 
			
		||||
			String id = input.readUTF();
 | 
			
		||||
			contents = ItemDataRegistry.getInstance().create(id);
 | 
			
		||||
			contents.read(input, context);
 | 
			
		||||
		} else {
 | 
			
		||||
			contents = null;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		checkState();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public synchronized void write(DataOutput output, IOContext context) throws IOException {
 | 
			
		||||
		output.writeInt(amount);
 | 
			
		||||
		if (contents != null) {
 | 
			
		||||
			output.writeUTF(contents.getId());
 | 
			
		||||
			contents.write(output, context);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void copy(Encodable destination) {
 | 
			
		||||
		ItemSlot slot = (ItemSlot) destination;
 | 
			
		||||
 | 
			
		||||
		slot.amount = this.amount;
 | 
			
		||||
		
 | 
			
		||||
		if (this.contents == null) {
 | 
			
		||||
			slot.contents = null;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (slot.contents == null || !slot.contents.isLike(this.contents)) {
 | 
			
		||||
				slot.contents = ItemDataRegistry.getInstance().create(this.contents.getId());
 | 
			
		||||
	public float getVolume() {
 | 
			
		||||
		synchronized (container) {
 | 
			
		||||
			int count = getCount();
 | 
			
		||||
			if (count == 0) {
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
			this.contents.copy(slot.contents);
 | 
			
		||||
			return count * getItem().getVolume();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * For purposes of equality checking, all container instances are considered
 | 
			
		||||
	 * different
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public int hashCode() {
 | 
			
		||||
		final int prime = 31;
 | 
			
		||||
		int result = 1;
 | 
			
		||||
		result = prime * result + System.identityHashCode(container);
 | 
			
		||||
		result = prime * result + index;
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean equals(Object obj) {
 | 
			
		||||
		if (this == obj)
 | 
			
		||||
			return true;
 | 
			
		||||
		if (obj == null)
 | 
			
		||||
			return false;
 | 
			
		||||
		if (getClass() != obj.getClass())
 | 
			
		||||
			return false;
 | 
			
		||||
		ItemSlot other = (ItemSlot) obj;
 | 
			
		||||
		if (container != other.container)
 | 
			
		||||
			return false;
 | 
			
		||||
		if (index != other.index)
 | 
			
		||||
			return false;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,264 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Progressia
 | 
			
		||||
 * Copyright (C)  2020-2021  Wind Corporation and contributors
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.LinearItemProperty;
 | 
			
		||||
 | 
			
		||||
public class Items {
 | 
			
		||||
 | 
			
		||||
	private static int pour(ItemContainer from, int fromIndex, ItemContainer into, int intoIndex, int max) {
 | 
			
		||||
		synchronized (from) {
 | 
			
		||||
			synchronized (into) {
 | 
			
		||||
 | 
			
		||||
				if (!from.isRemovingAllowed()) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				ItemData item = from.getItem(fromIndex);
 | 
			
		||||
				
 | 
			
		||||
				if (item == null) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!into.canAdd(item)) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!into.isEmpty(intoIndex) && !into.getItem(intoIndex).equals(item)) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				int originalCount = from.getCount(fromIndex);
 | 
			
		||||
				int transferCount = Math.min(originalCount, max);
 | 
			
		||||
 | 
			
		||||
				for (LinearItemProperty prop : LinearItemProperty.values()) {
 | 
			
		||||
					int canFitPropwise = (int) Math.floor((prop.getLimit(into) - prop.get(into)) / prop.get(item));
 | 
			
		||||
					if (canFitPropwise < transferCount) {
 | 
			
		||||
						transferCount = canFitPropwise;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (transferCount == 0) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!into.canAdd(intoIndex, item, transferCount)) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!from.remove(fromIndex, item, transferCount)) {
 | 
			
		||||
					return 0;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				boolean success = into.add(intoIndex, item, transferCount);
 | 
			
		||||
				assert success : "Wait, canAdd and canRemove promised the operation would be safe!";
 | 
			
		||||
 | 
			
		||||
				return transferCount;
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to transfer as many items as possible, but no more than
 | 
			
		||||
	 * {@code max}, between two slots. The origin of the items is {@code from},
 | 
			
		||||
	 * the destination is {@code into}. This method does nothing if the items
 | 
			
		||||
	 * could not be removed from origin or could not be inserted into
 | 
			
		||||
	 * destination, such as when {@code into} contains different items.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param from the slot to transfer item from
 | 
			
		||||
	 * @param into the destination of the items
 | 
			
		||||
	 * @param max  the maximum amount of items to transfer. Use
 | 
			
		||||
	 *             {@code Integer.MAX_VALUE} to remove the limit
 | 
			
		||||
	 * @return the actual amount of items moved between slots
 | 
			
		||||
	 */
 | 
			
		||||
	public static int pour(ItemSlot from, ItemSlot into, int max) {
 | 
			
		||||
		return pour(from.getContainer(), from.getIndex(), into.getContainer(), into.getIndex(), max);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to transfer as many items as possible between two slots. The
 | 
			
		||||
	 * origin of the items is {@code from}, the destination is {@code into}.
 | 
			
		||||
	 * This method does nothing if the items could not be removed from origin or
 | 
			
		||||
	 * could not be inserted into destination, such as when {@code into}
 | 
			
		||||
	 * contains different items.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param from the slot to transfer item from
 | 
			
		||||
	 * @param into the destination of the items
 | 
			
		||||
	 * @return the actual amount of items moved between slots
 | 
			
		||||
	 */
 | 
			
		||||
	public static int pour(ItemSlot from, ItemSlot into) {
 | 
			
		||||
		return pour(from.getContainer(), from.getIndex(), into.getContainer(), into.getIndex(), Integer.MAX_VALUE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to swap the contents of the two slots. Swapping contents of two
 | 
			
		||||
	 * empty slots results in a no-op and is considered a success.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param a one of the slots
 | 
			
		||||
	 * @param b the other slot
 | 
			
		||||
	 * @return whether the swap succeeded
 | 
			
		||||
	 */
 | 
			
		||||
	public static boolean swap(ItemSlot a, ItemSlot b) {
 | 
			
		||||
		synchronized (a.getContainer()) {
 | 
			
		||||
			synchronized (b.getContainer()) {
 | 
			
		||||
 | 
			
		||||
				if (a.isEmpty() && b.isEmpty()) {
 | 
			
		||||
					return true;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!a.isEmpty() && !a.getContainer().isRemovingAllowed()) {
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (!b.isEmpty() && !b.getContainer().isRemovingAllowed()) {
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				ItemData aItem = a.getItem();
 | 
			
		||||
				int aCount = a.getCount();
 | 
			
		||||
 | 
			
		||||
				ItemData bItem = b.getItem();
 | 
			
		||||
				int bCount = b.getCount();
 | 
			
		||||
 | 
			
		||||
				a.remove(aItem, aCount);
 | 
			
		||||
				b.remove(bItem, bCount);
 | 
			
		||||
 | 
			
		||||
				if (a.canAdd(bItem, bCount) && b.canAdd(aItem, aCount)) {
 | 
			
		||||
					a.add(bItem, bCount);
 | 
			
		||||
					b.add(aItem, aCount);
 | 
			
		||||
					return true;
 | 
			
		||||
				} else {
 | 
			
		||||
					a.add(aItem, aCount);
 | 
			
		||||
					b.add(bItem, bCount);
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to place new items into the specified slot. Either all or none
 | 
			
		||||
	 * of the requested items will be spawned.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param into  destination slot
 | 
			
		||||
	 * @param item  the item to add
 | 
			
		||||
	 * @param count the item count
 | 
			
		||||
	 * @return whether the addition succeeded
 | 
			
		||||
	 */
 | 
			
		||||
	public static boolean spawn(ItemSlot into, ItemData item, int count) {
 | 
			
		||||
		synchronized (into.getContainer()) {
 | 
			
		||||
			return into.add(item, count);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Attempts to remove items from the specified slot. Either all or none of
 | 
			
		||||
	 * the requested items will be destroyed.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param from  the slot
 | 
			
		||||
	 * @param item  the item to remove
 | 
			
		||||
	 * @param count the item count
 | 
			
		||||
	 * @return whether the removal succeeded
 | 
			
		||||
	 */
 | 
			
		||||
	public static boolean destroy(ItemSlot from, ItemData item, int count) {
 | 
			
		||||
		synchronized (from.getContainer()) {
 | 
			
		||||
			return from.remove(item, count);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static int pour(ItemSlot from, ItemContainer into, int max) {
 | 
			
		||||
		synchronized (from.getContainer()) {
 | 
			
		||||
			synchronized (into) {
 | 
			
		||||
 | 
			
		||||
				int totalPoured = 0;
 | 
			
		||||
 | 
			
		||||
				for (int index = 0; max > 0 && index <= into.getMaxIndex(); ++index) {
 | 
			
		||||
					int poured = pour(from.getContainer(), from.getIndex(), into, index, max);
 | 
			
		||||
					max -= poured;
 | 
			
		||||
					totalPoured += poured;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return totalPoured;
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static int pour(ItemSlot from, ItemContainer into) {
 | 
			
		||||
		return pour(from, into, Integer.MAX_VALUE);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static boolean spawn(ItemContainer into, ItemData item, int count) {
 | 
			
		||||
		synchronized (into) {
 | 
			
		||||
			
 | 
			
		||||
			if (item == null && count == 0) {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (count < 0) {
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (!into.canAdd(item)) {
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			for (LinearItemProperty prop : LinearItemProperty.values()) {
 | 
			
		||||
				float requested = prop.get(item) * count;
 | 
			
		||||
				float available = prop.getLimit(into) - prop.get(into);
 | 
			
		||||
				if (requested > available) {
 | 
			
		||||
					return false;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			int compatibleSlot = -1;
 | 
			
		||||
			int firstEmptySlot = -1;
 | 
			
		||||
			
 | 
			
		||||
			for (int index = 0; index <= into.getMaxIndex(); ++index) {
 | 
			
		||||
				ItemData inSlot = into.getItem(index);
 | 
			
		||||
				if (inSlot == null) {
 | 
			
		||||
					if (firstEmptySlot == -1) {
 | 
			
		||||
						firstEmptySlot = index;
 | 
			
		||||
					}
 | 
			
		||||
				} else if (inSlot.equals(item)) {
 | 
			
		||||
					compatibleSlot = index;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (compatibleSlot == -1) {
 | 
			
		||||
				compatibleSlot = firstEmptySlot;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (compatibleSlot == -1) {
 | 
			
		||||
				// Means the inventory is full due to slot limit
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			return into.add(compatibleSlot, item, count);
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private Items() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory.event;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
 | 
			
		||||
public class ItemSlotChangedEvent extends ItemSlotEvent {
 | 
			
		||||
@@ -25,4 +26,8 @@ public class ItemSlotChangedEvent extends ItemSlotEvent {
 | 
			
		||||
		super(slot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ItemSlotChangedEvent(ItemContainer container, int index) {
 | 
			
		||||
		super(container, index);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,21 +17,38 @@
 | 
			
		||||
 */
 | 
			
		||||
package ru.windcorp.progressia.common.world.item.inventory.event;
 | 
			
		||||
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemContainer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
 | 
			
		||||
 | 
			
		||||
public abstract class ItemSlotEvent extends ItemContainerEvent {
 | 
			
		||||
	
 | 
			
		||||
	private final ItemSlot slot;
 | 
			
		||||
	private ItemSlot slot = null;
 | 
			
		||||
	private final int index;
 | 
			
		||||
 | 
			
		||||
	public ItemSlotEvent(ItemContainer container, int index) {
 | 
			
		||||
		super(container);
 | 
			
		||||
		this.index = index;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public ItemSlotEvent(ItemSlot slot) {
 | 
			
		||||
		super(slot.getContainer());
 | 
			
		||||
		this(slot.getContainer(), slot.getIndex());
 | 
			
		||||
		this.slot = slot;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the index
 | 
			
		||||
	 */
 | 
			
		||||
	public int getIndex() {
 | 
			
		||||
		return index;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the slot
 | 
			
		||||
	 */
 | 
			
		||||
	public ItemSlot getSlot() {
 | 
			
		||||
		if (slot == null) {
 | 
			
		||||
			slot = new ItemSlot(getContainer(), index);
 | 
			
		||||
		}
 | 
			
		||||
		return slot;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ import ru.windcorp.progressia.common.world.entity.EntityData;
 | 
			
		||||
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
 | 
			
		||||
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
 | 
			
		||||
import ru.windcorp.progressia.common.world.item.inventory.Items;
 | 
			
		||||
import ru.windcorp.progressia.server.events.PlayerJoinedEvent;
 | 
			
		||||
import ru.windcorp.progressia.test.TestContent;
 | 
			
		||||
 | 
			
		||||
@@ -63,8 +64,8 @@ public class PlayerManager {
 | 
			
		||||
	private EntityDataPlayer spawnPlayerEntity(String login) {
 | 
			
		||||
		EntityDataPlayer player = (EntityDataPlayer) EntityDataRegistry.getInstance().create("Core:Player");
 | 
			
		||||
 | 
			
		||||
		player.getHand(0).slot().setContents(ItemDataRegistry.getInstance().create("Test:Stick"), 20);
 | 
			
		||||
		player.getEquipmentSlot(0).slot().setContents(ItemDataRegistry.getInstance().create("Test:CardboardBackpack"), 1);
 | 
			
		||||
		Items.spawn(player.getHand(0).slot(), ItemDataRegistry.getInstance().create("Test:Stick"), 5);
 | 
			
		||||
		Items.spawn(player.getEquipmentSlot(0).slot(), ItemDataRegistry.getInstance().create("Test:CardboardBackpack"), 1);
 | 
			
		||||
 | 
			
		||||
		player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation());
 | 
			
		||||
		player.setUpVector(new Vec3(0, 0, 1));
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user