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:
parent
be6203719a
commit
3641c4130b
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,6 +44,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));
|
||||
this.content = component;
|
||||
|
@ -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);
|
||||
@ -78,13 +84,11 @@ 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);
|
||||
|
||||
@ -93,6 +97,10 @@ public class ContainerComponentSimple extends ContainerComponent {
|
||||
slotCollection.add(component);
|
||||
}
|
||||
|
||||
private void removeSlot(int index) {
|
||||
slots.removeChild(slotCollection.remove(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer getContainer() {
|
||||
return container;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
int wantSlots = container.getLastFilledSlot();
|
||||
int slotsPerRow = tmp__getSlotsPerRow();
|
||||
|
||||
@Subscribe
|
||||
private void onSlotAdded(ItemSlotRemovedEvent e) {
|
||||
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,7 +59,8 @@ public class ItemDataContainer extends ItemData {
|
||||
protected InventorySimple createInventory() {
|
||||
return new InventorySimple(
|
||||
getId(),
|
||||
new ItemContainerMixedSimple(getId(), containerMassLimit, containerVolumeLimit, 10)
|
||||
this,
|
||||
new ItemContainerMixedSimple(getId(), containerMassLimit, containerVolumeLimit)
|
||||
);
|
||||
}
|
||||
|
||||
@ -61,6 +68,11 @@ public class ItemDataContainer extends ItemData {
|
||||
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> {
|
||||
public abstract class ItemContainer extends Namespaced implements Encodable {
|
||||
|
||||
private Inventory inventory;
|
||||
private final int maxPossibleSize;
|
||||
|
||||
public ItemContainer(String id) {
|
||||
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();
|
||||
public abstract int getCount(int index);
|
||||
|
||||
@Override
|
||||
public abstract void forEach(Consumer<? super ItemSlot> action);
|
||||
/**
|
||||
* 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 Iterator<ItemSlot> iterator() {
|
||||
return new Iterator<ItemSlot>() {
|
||||
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < getSlotCount();
|
||||
}
|
||||
|
||||
@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.
|
||||
*
|
||||
@ -111,30 +367,159 @@ public abstract class ItemContainer extends Namespaced implements Encodable, Ite
|
||||
|
||||
public synchronized float getMass() {
|
||||
float sum = 0;
|
||||
for (int i = 0; i < getSlotCount(); ++i) {
|
||||
ItemSlot slot = getSlot(i);
|
||||
for (int i = 0; i < getMaxIndex(); ++i) {
|
||||
ItemData data = getItem(i);
|
||||
|
||||
if (slot.isEmpty()) {
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sum += slot.getContents().getMass() * slot.getAmount();
|
||||
sum += data.getMass() * getCount(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public synchronized float getVolume() {
|
||||
float sum = 0;
|
||||
for (int i = 0; i < getSlotCount(); ++i) {
|
||||
ItemSlot slot = getSlot(i);
|
||||
for (int i = 0; i < getMaxIndex(); ++i) {
|
||||
ItemData data = getItem(i);
|
||||
|
||||
if (slot.isEmpty()) {
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sum += slot.getContents().getVolume() * slot.getAmount();
|
||||
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 {
|
||||
|
||||
private final List<ItemSlot> slots = new ArrayList<>();
|
||||
public static final int MAX_SLOTS = 10000;
|
||||
|
||||
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);
|
||||
}
|
||||
ItemData[] newItems = new ItemData[newCapacity];
|
||||
int[] newCounts = new int[newCapacity];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
int needSlots = input.readInt();
|
||||
int hasSlots = slots.size();
|
||||
ensureCapacity(size);
|
||||
|
||||
int costOfResetting = needSlots;
|
||||
int costOfEditing = Math.abs(needSlots - hasSlots);
|
||||
for (int index = 0; index < size; ++index) {
|
||||
|
||||
if (costOfResetting < costOfEditing) {
|
||||
slots.clear();
|
||||
addSlots(needSlots);
|
||||
ItemData item;
|
||||
int count = input.readInt();
|
||||
|
||||
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) {
|
||||
|
||||
int needSlots = mySlots.size();
|
||||
int hasSlots = containerSlots.size();
|
||||
other.setCapacity(myLength);
|
||||
System.arraycopy(this.counts, 0, other.counts, 0, myLength);
|
||||
|
||||
int costOfResetting = needSlots;
|
||||
int costOfEditing = Math.abs(needSlots - hasSlots);
|
||||
for (int i = 0; i < myLength; ++i) {
|
||||
ItemData myItem = this.items[i];
|
||||
ItemData otherItem;
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,20 +23,9 @@ public class ItemContainerMixedSimple extends ItemContainerMixed {
|
||||
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) {
|
||||
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 ItemData item;
|
||||
private int count;
|
||||
private ItemSlot slot = new ItemSlot(this, 0);
|
||||
|
||||
private final float massLimit;
|
||||
private final float volumeLimit;
|
||||
|
||||
public ItemContainerSingle(String id, float massLimit, float volumeLimit) {
|
||||
super(id);
|
||||
this.massLimit = massLimit;
|
||||
this.volumeLimit = volumeLimit;
|
||||
|
||||
slot.setContainer(this);
|
||||
public ItemContainerSingle(String id) {
|
||||
super(id, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInput input, IOContext context) throws IOException {
|
||||
slot.read(input, context);
|
||||
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;
|
||||
}
|
||||
|
||||
fireSlotChangeEvent(0);
|
||||
checkState();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
protected boolean remove(int index, ItemData item, int count) {
|
||||
if (!canRemove(index, item, count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVolumeLimit() {
|
||||
return volumeLimit;
|
||||
if (count != 0) {
|
||||
this.count -= count;
|
||||
if (this.count == 0) {
|
||||
this.item = null;
|
||||
}
|
||||
fireSlotChangeEvent(0);
|
||||
checkState();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,26 +17,29 @@
|
||||
*/
|
||||
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 ItemData contents;
|
||||
private int amount;
|
||||
private final ItemContainer container;
|
||||
private final int index;
|
||||
|
||||
private ItemContainer container;
|
||||
public ItemSlot(ItemContainer container, int index) {
|
||||
this.container = container;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the container
|
||||
@ -45,154 +48,93 @@ public class ItemSlot implements Encodable {
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return getContainer().getInventory();
|
||||
return container.getInventory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param container the container to set
|
||||
*/
|
||||
void setContainer(ItemContainer container) {
|
||||
this.container = container;
|
||||
public ItemData getItem() {
|
||||
return container.getItem(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the contents of this slot.
|
||||
*
|
||||
* @return the stored {@link ItemData} or {@code null}
|
||||
*/
|
||||
public synchronized final ItemData getContents() {
|
||||
return contents;
|
||||
public int getCount() {
|
||||
return container.getCount(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 boolean isEmpty() {
|
||||
return container.isEmpty(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);
|
||||
public boolean canAdd(ItemData item, int count) {
|
||||
return container.canAdd(index, item, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears this slot
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
setContents(null, 0);
|
||||
public boolean canRemove(ItemData item, int count) {
|
||||
return container.canRemove(index, item, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
protected boolean add(ItemData item, int count) {
|
||||
return container.add(index, item, count);
|
||||
}
|
||||
|
||||
public synchronized boolean isEmpty() {
|
||||
return amount == 0;
|
||||
protected boolean remove(ItemData item, int count) {
|
||||
return container.remove(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);
|
||||
}
|
||||
|
||||
public synchronized boolean canRemove(int amount) {
|
||||
return this.amount >= amount;
|
||||
}
|
||||
|
||||
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