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);
|
InventoryComponent component = render.createComponent(inventory, this);
|
||||||
openInventory(component);
|
openInventory(component);
|
||||||
} catch (Exception e) {
|
} 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.client.graphics.input.WheelScrollEvent;
|
||||||
import ru.windcorp.progressia.common.Units;
|
import ru.windcorp.progressia.common.Units;
|
||||||
import ru.windcorp.progressia.common.world.item.ItemDataContainer;
|
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.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.ItemSlot;
|
||||||
|
import ru.windcorp.progressia.common.world.item.inventory.Items;
|
||||||
|
|
||||||
public class InteractiveSlotComponent extends Button {
|
public class InteractiveSlotComponent extends Button {
|
||||||
|
|
||||||
@ -77,7 +76,7 @@ public class InteractiveSlotComponent extends Button {
|
|||||||
|
|
||||||
private void onMainAction() {
|
private void onMainAction() {
|
||||||
ItemSlot handSlot = workspace.getHand().slot();
|
ItemSlot handSlot = workspace.getHand().slot();
|
||||||
ItemSlot invSlot = getSlotOrAllocate();
|
ItemSlot invSlot = getSlot();
|
||||||
if (invSlot == null) {
|
if (invSlot == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -108,13 +107,12 @@ public class InteractiveSlotComponent extends Button {
|
|||||||
if (success) {
|
if (success) {
|
||||||
requestReassembly();
|
requestReassembly();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanContainerUpIfNecessary();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pickAll(ItemSlot handSlot) {
|
private void pickAll(ItemSlot handSlot) {
|
||||||
for (ItemSlot invSlot : getSlot().getContainer()) {
|
int maxIndex = getSlot().getContainer().getMaxIndex();
|
||||||
Items.pour(invSlot, handSlot);
|
for (int index = 0; index < maxIndex; ++index) {
|
||||||
|
Items.pour(new ItemSlot(getSlot().getContainer(), index), handSlot);
|
||||||
if (handSlot.isEmpty()) {
|
if (handSlot.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -123,7 +121,7 @@ public class InteractiveSlotComponent extends Button {
|
|||||||
|
|
||||||
private void onAltAction() {
|
private void onAltAction() {
|
||||||
ItemSlot handSlot = workspace.getHand().slot();
|
ItemSlot handSlot = workspace.getHand().slot();
|
||||||
ItemSlot invSlot = getSlotOrAllocate();
|
ItemSlot invSlot = getSlot();
|
||||||
if (invSlot == null) {
|
if (invSlot == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -135,7 +133,7 @@ public class InteractiveSlotComponent extends Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!success && handSlot.isEmpty()) {
|
if (!success && handSlot.isEmpty()) {
|
||||||
success = Items.pour(invSlot, handSlot, invSlot.getAmount() / 2) != 0;
|
success = Items.pour(invSlot, handSlot, invSlot.getCount() / 2) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@ -149,19 +147,17 @@ public class InteractiveSlotComponent extends Button {
|
|||||||
if (success) {
|
if (success) {
|
||||||
requestReassembly();
|
requestReassembly();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanContainerUpIfNecessary();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryToOpen(ItemSlot invSlot) {
|
private boolean tryToOpen(ItemSlot invSlot) {
|
||||||
if (invSlot.getAmount() != 1) {
|
if (invSlot.getCount() != 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(invSlot.getContents() instanceof ItemDataContainer)) {
|
if (!(invSlot.getItem() instanceof ItemDataContainer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDataContainer item = (ItemDataContainer) invSlot.getContents();
|
ItemDataContainer item = (ItemDataContainer) invSlot.getItem();
|
||||||
return item.open(workspace.getPlayerEntity()) != null;
|
return item.open(workspace.getPlayerEntity()) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,38 +177,4 @@ public class InteractiveSlotComponent extends Button {
|
|||||||
return slotComponent.getSlot();
|
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 InventoryComponent content;
|
||||||
private final HUDWorkspace workspace;
|
private final HUDWorkspace workspace;
|
||||||
|
|
||||||
|
Object layoutCookie;
|
||||||
|
|
||||||
public InventoryWindow(String name, InventoryComponent component, HUDWorkspace workspace) {
|
public InventoryWindow(String name, InventoryComponent component, HUDWorkspace workspace) {
|
||||||
super(name, new LayoutVertical(15, 15));
|
super(name, new LayoutVertical(15, 15));
|
||||||
|
@ -51,8 +51,7 @@ public class SlotComponent extends Component {
|
|||||||
private static Renderable containerOpenDecoration = null;
|
private static Renderable containerOpenDecoration = null;
|
||||||
private static Renderable containerOpenableDecoration = null;
|
private static Renderable containerOpenableDecoration = null;
|
||||||
|
|
||||||
private final ItemContainer container;
|
private final ItemSlot slot;
|
||||||
private final int index;
|
|
||||||
|
|
||||||
private float scale = 2;
|
private float scale = 2;
|
||||||
|
|
||||||
@ -66,8 +65,7 @@ public class SlotComponent extends Component {
|
|||||||
|
|
||||||
public SlotComponent(String name, ItemContainer container, int index) {
|
public SlotComponent(String name, ItemContainer container, int index) {
|
||||||
super(name);
|
super(name);
|
||||||
this.container = container;
|
this.slot = new ItemSlot(container, index);
|
||||||
this.index = index;
|
|
||||||
|
|
||||||
setScale(2);
|
setScale(2);
|
||||||
|
|
||||||
@ -95,18 +93,18 @@ public class SlotComponent extends Component {
|
|||||||
* @return the container
|
* @return the container
|
||||||
*/
|
*/
|
||||||
public ItemContainer getSlotContainer() {
|
public ItemContainer getSlotContainer() {
|
||||||
return container;
|
return slot.getContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the index
|
* @return the index
|
||||||
*/
|
*/
|
||||||
public int getSlotIndex() {
|
public int getSlotIndex() {
|
||||||
return index;
|
return slot.getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemSlot getSlot() {
|
public ItemSlot getSlot() {
|
||||||
return container.getSlot(index);
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SlotComponent setScale(float scale) {
|
public SlotComponent setScale(float scale) {
|
||||||
@ -142,14 +140,7 @@ public class SlotComponent extends Component {
|
|||||||
|
|
||||||
private void updateItemRenderer() {
|
private void updateItemRenderer() {
|
||||||
ItemData contents;
|
ItemData contents;
|
||||||
|
contents = slot.getItem();
|
||||||
ItemSlot slot = getSlot();
|
|
||||||
|
|
||||||
if (slot == null) {
|
|
||||||
contents = null;
|
|
||||||
} else {
|
|
||||||
contents = slot.getContents();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contents == null) {
|
if (contents == null) {
|
||||||
itemRenderer = null;
|
itemRenderer = null;
|
||||||
@ -160,7 +151,7 @@ public class SlotComponent extends Component {
|
|||||||
itemRenderer = ItemRenderRegistry.getInstance().get(contents.getId()).createRenderable(contents);
|
itemRenderer = ItemRenderRegistry.getInstance().get(contents.getId()).createRenderable(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
int newAmount = slot.getAmount();
|
int newAmount = slot.getCount();
|
||||||
if (newAmount != amountDisplayInt) {
|
if (newAmount != amountDisplayInt) {
|
||||||
amountDisplayInt = newAmount;
|
amountDisplayInt = newAmount;
|
||||||
amountDisplayString = newAmount == 1 ? "" : Integer.toString(newAmount);
|
amountDisplayString = newAmount == 1 ? "" : Integer.toString(newAmount);
|
||||||
@ -195,7 +186,7 @@ public class SlotComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void renderDecorations(ShapeRenderHelper renderer) {
|
private void renderDecorations(ShapeRenderHelper renderer) {
|
||||||
ItemData contents = getSlot().getContents();
|
ItemData contents = getSlot().getItem();
|
||||||
|
|
||||||
if (contents instanceof ItemDataContainer) {
|
if (contents instanceof ItemDataContainer) {
|
||||||
ItemDataContainer asContainer = (ItemDataContainer) contents;
|
ItemDataContainer asContainer = (ItemDataContainer) contents;
|
||||||
|
@ -17,13 +17,49 @@
|
|||||||
*/
|
*/
|
||||||
package ru.windcorp.progressia.client.graphics.world.hud;
|
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.Component;
|
||||||
|
import ru.windcorp.progressia.client.graphics.gui.Layout;
|
||||||
|
|
||||||
public class WindowedHUD extends Component {
|
public class WindowedHUD extends Component {
|
||||||
|
|
||||||
|
private static class Cookie {
|
||||||
|
private final Vec2 relPos = new Vec2();
|
||||||
|
}
|
||||||
|
|
||||||
public WindowedHUD(String name) {
|
public WindowedHUD(String name) {
|
||||||
super(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) {
|
public void addWindow(InventoryWindow window) {
|
||||||
|
@ -19,6 +19,7 @@ package ru.windcorp.progressia.client.world.item.inventory;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
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.HUDWorkspace;
|
||||||
import ru.windcorp.progressia.client.graphics.world.hud.InteractiveSlotComponent;
|
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.ItemContainer;
|
||||||
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;
|
|
||||||
|
|
||||||
public class ContainerComponentSimple extends ContainerComponent {
|
public class ContainerComponentSimple extends ContainerComponent {
|
||||||
|
|
||||||
private final Group slots = new Group("Inventory.Slots", new LayoutGrid(0, 15));
|
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 ItemContainer container;
|
||||||
|
private final HUDWorkspace workspace;
|
||||||
|
|
||||||
|
private int tmp__getSlotsPerRow() {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
public ContainerComponentSimple(ItemContainer container, HUDWorkspace workspace) {
|
public ContainerComponentSimple(ItemContainer container, HUDWorkspace workspace) {
|
||||||
super("Inventory");
|
super("Inventory");
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
this.workspace = workspace;
|
||||||
|
|
||||||
if (container.getInventory() != null) {
|
if (container.getInventory() != null) {
|
||||||
container.getInventory().subscribe(this);
|
container.getInventory().subscribe(this);
|
||||||
@ -77,14 +83,12 @@ public class ContainerComponentSimple extends ContainerComponent {
|
|||||||
|
|
||||||
addChild(slotsAndVolumeBar.setLayoutHint(LayoutBorderHorizontal.CENTER));
|
addChild(slotsAndVolumeBar.setLayoutHint(LayoutBorderHorizontal.CENTER));
|
||||||
addChild(massBar.setLayoutHint(LayoutBorderHorizontal.LEFT));
|
addChild(massBar.setLayoutHint(LayoutBorderHorizontal.LEFT));
|
||||||
|
|
||||||
for (int i = 0; i < container.getSlotCount(); ++i) {
|
onSlotChanged(null);
|
||||||
addSlot(container, i, workspace);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSlot(ItemContainer container, int index, HUDWorkspace workspace) {
|
private void addSlot(int index) {
|
||||||
final int maxX = 6;
|
final int maxX = tmp__getSlotsPerRow();
|
||||||
|
|
||||||
InteractiveSlotComponent component = new InteractiveSlotComponent("Inventory.Slot." + index, container, index, workspace);
|
InteractiveSlotComponent component = new InteractiveSlotComponent("Inventory.Slot." + index, container, index, workspace);
|
||||||
|
|
||||||
@ -92,6 +96,10 @@ public class ContainerComponentSimple extends ContainerComponent {
|
|||||||
slots.addChild(component.setLayoutHint(pos));
|
slots.addChild(component.setLayoutHint(pos));
|
||||||
slotCollection.add(component);
|
slotCollection.add(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeSlot(int index) {
|
||||||
|
slots.removeChild(slotCollection.remove(index));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemContainer getContainer() {
|
public ItemContainer getContainer() {
|
||||||
@ -104,13 +112,26 @@ public class ContainerComponentSimple extends ContainerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@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;
|
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.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.InventorySimple;
|
||||||
import ru.windcorp.progressia.common.world.item.inventory.InventoryUser;
|
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;
|
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)
|
private final ObjectStateField<InventorySimple> inventory = field("Core:Contents").setShared().def(this::createInventory)
|
||||||
.build();
|
.build();
|
||||||
@ -53,13 +59,19 @@ public class ItemDataContainer extends ItemData {
|
|||||||
protected InventorySimple createInventory() {
|
protected InventorySimple createInventory() {
|
||||||
return new InventorySimple(
|
return new InventorySimple(
|
||||||
getId(),
|
getId(),
|
||||||
new ItemContainerMixedSimple(getId(), containerMassLimit, containerVolumeLimit, 10)
|
this,
|
||||||
|
new ItemContainerMixedSimple(getId(), containerMassLimit, containerVolumeLimit)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InventorySimple getInventory() {
|
public InventorySimple getInventory() {
|
||||||
return inventory.get(this);
|
return inventory.get(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<? extends ItemContainer> getAllContainers() {
|
||||||
|
return Iterators.forArray(getInventory().getContainers());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isOpen() {
|
public boolean isOpen() {
|
||||||
return !getInventory().getUsers().isEmpty();
|
return !getInventory().getUsers().isEmpty();
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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) {
|
public interface ItemDataWithContainers {
|
||||||
super(slot);
|
|
||||||
}
|
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 {
|
public class Inventory extends Namespaced implements Encodable {
|
||||||
|
|
||||||
|
private final InventoryOwner owner;
|
||||||
private final ItemContainer[] containers;
|
private final ItemContainer[] containers;
|
||||||
private final List<InventoryUser> users = new ArrayList<>();
|
private final List<InventoryUser> users = new ArrayList<>();
|
||||||
|
|
||||||
private EventBus eventBus = null;
|
private EventBus eventBus = null;
|
||||||
|
|
||||||
public Inventory(String id, ItemContainer... containers) {
|
public Inventory(String id, InventoryOwner owner, ItemContainer... containers) {
|
||||||
super(id);
|
super(id);
|
||||||
|
this.owner = owner;
|
||||||
this.containers = containers;
|
this.containers = containers;
|
||||||
for (ItemContainer container : containers) {
|
for (ItemContainer container : containers) {
|
||||||
container.setInventory(this);
|
container.setInventory(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InventoryOwner getOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void open(InventoryUser user) {
|
public synchronized void open(InventoryUser user) {
|
||||||
users.add(user);
|
users.add(user);
|
||||||
subscribe(user);
|
subscribe(user);
|
||||||
|
@ -15,14 +15,8 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 interface InventoryOwner {
|
||||||
|
|
||||||
public class ItemSlotRemovedEvent extends ItemSlotEvent {
|
|
||||||
|
|
||||||
public ItemSlotRemovedEvent(ItemSlot slot) {
|
|
||||||
super(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -19,8 +19,8 @@ package ru.windcorp.progressia.common.world.item.inventory;
|
|||||||
|
|
||||||
public class InventorySimple extends Inventory {
|
public class InventorySimple extends Inventory {
|
||||||
|
|
||||||
public InventorySimple(String id, ItemContainer container) {
|
public InventorySimple(String id, InventoryOwner owner, ItemContainer container) {
|
||||||
super(id, container);
|
super(id, owner, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemContainer getContainer() {
|
public ItemContainer getContainer() {
|
||||||
|
@ -18,79 +18,335 @@
|
|||||||
package ru.windcorp.progressia.common.world.item.inventory;
|
package ru.windcorp.progressia.common.world.item.inventory;
|
||||||
|
|
||||||
import java.util.Iterator;
|
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.state.Encodable;
|
||||||
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
|
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
|
* The base class of item containers. An item container is a set of slots with
|
||||||
* container may impose limits on the maximum total mass and volume of its
|
* defined limits on mass and volume. Containers are typically grouped into an
|
||||||
* contents, although this is not enforced on data structure level.
|
* {@link Inventory} by in-game function.
|
||||||
* <p>
|
* <p>
|
||||||
* At any moment container has a definite amount of slots, each identified by a
|
* A container contains some number of slots indexed from 0. Users may inspect
|
||||||
* unique index. If a container has <i>n</i> slots, its slots are numbered 0
|
* the contents of any slot. Slots may contain an {@link ItemData} and an item
|
||||||
* through <i>n</i> - 1.
|
* 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;
|
|
||||||
|
|
||||||
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);
|
super(id);
|
||||||
|
this.maxPossibleSize = maxPossibleSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the inventory
|
||||||
|
*/
|
||||||
public Inventory getInventory() {
|
public Inventory getInventory() {
|
||||||
return inventory;
|
return inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setInventory(Inventory inventory) {
|
/**
|
||||||
|
* @param inventory the inventory to set
|
||||||
|
*/
|
||||||
|
void setInventory(Inventory inventory) {
|
||||||
this.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
|
* @param index the index of the slot to query
|
||||||
* @return the slot or {@code null} if the slot does not exist
|
* @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);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<ItemSlot> iterator() {
|
|
||||||
return new Iterator<ItemSlot>() {
|
|
||||||
|
|
||||||
private int index = 0;
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean hasNext() {
|
* Returns a slot index strictly greater than all indices containing
|
||||||
return index < getSlotCount();
|
* 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() {
|
* Computes and returns the slot index of the last non-empty slot in this
|
||||||
if (!hasNext()) {
|
* container. This operation is potentially slower than
|
||||||
throw new NoSuchElementException("index = " + index + ", size = " + getSlotCount());
|
* {@link #getMaxIndex()}, which is less precise.
|
||||||
}
|
*
|
||||||
ItemSlot slot = getSlot(index);
|
* @return the index of the last filled slot, or -1 if no such slot exists
|
||||||
index++;
|
*/
|
||||||
return slot;
|
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.
|
* 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
|
* boundary is set
|
||||||
*/
|
*/
|
||||||
public abstract float getVolumeLimit();
|
public abstract float getVolumeLimit();
|
||||||
|
|
||||||
public synchronized float getMass() {
|
public synchronized float getMass() {
|
||||||
float sum = 0;
|
float sum = 0;
|
||||||
for (int i = 0; i < getSlotCount(); ++i) {
|
for (int i = 0; i < getMaxIndex(); ++i) {
|
||||||
ItemSlot slot = getSlot(i);
|
ItemData data = getItem(i);
|
||||||
|
|
||||||
if (slot.isEmpty()) {
|
if (data == null) {
|
||||||
continue;
|
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);
|
|
||||||
|
|
||||||
if (slot.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sum += slot.getContents().getVolume() * slot.getAmount();
|
|
||||||
}
|
}
|
||||||
return sum;
|
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;
|
private final SpeciesData.EquipmentSlot equipmentSlot;
|
||||||
|
|
||||||
public ItemContainerEquipment(String id, SpeciesData.EquipmentSlot equipmentSlot) {
|
public ItemContainerEquipment(String id, SpeciesData.EquipmentSlot equipmentSlot) {
|
||||||
super(id, EQUIP_MASS_LIMIT, EQUIP_VOLUME_LIMIT);
|
super(id);
|
||||||
this.equipmentSlot = equipmentSlot;
|
this.equipmentSlot = equipmentSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getMassLimit() {
|
||||||
|
return EQUIP_MASS_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getVolumeLimit() {
|
||||||
|
return EQUIP_VOLUME_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
public SpeciesData.EquipmentSlot getEquipmentSlot() {
|
public SpeciesData.EquipmentSlot getEquipmentSlot() {
|
||||||
return equipmentSlot;
|
return equipmentSlot;
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,20 @@ public class ItemContainerHand extends ItemContainerSingle {
|
|||||||
private final SpeciesData.Hand hand;
|
private final SpeciesData.Hand hand;
|
||||||
|
|
||||||
public ItemContainerHand(String id, Hand hand) {
|
public ItemContainerHand(String id, Hand hand) {
|
||||||
super(id, HAND_MASS_LIMIT, HAND_VOLUME_LIMIT);
|
super(id);
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getMassLimit() {
|
||||||
|
return HAND_MASS_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getVolumeLimit() {
|
||||||
|
return HAND_VOLUME_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
public SpeciesData.Hand getHand() {
|
public SpeciesData.Hand getHand() {
|
||||||
return hand;
|
return hand;
|
||||||
}
|
}
|
||||||
|
@ -20,198 +20,214 @@ package ru.windcorp.progressia.common.world.item.inventory;
|
|||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
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.Encodable;
|
||||||
import ru.windcorp.progressia.common.state.IOContext;
|
import ru.windcorp.progressia.common.state.IOContext;
|
||||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotAddedEvent;
|
import ru.windcorp.progressia.common.world.item.ItemData;
|
||||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotChangedEvent;
|
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
|
||||||
import ru.windcorp.progressia.common.world.item.inventory.event.ItemSlotRemovedEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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) {
|
public ItemContainerMixed(String id) {
|
||||||
super(id);
|
super(id, MAX_SLOTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void setCapacity(int minimumCapacity) {
|
||||||
protected void setInventory(Inventory inventory) {
|
if (minimumCapacity < 0) {
|
||||||
if (getInventory() != null) {
|
return;
|
||||||
getInventory().unsubscribe(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setInventory(inventory);
|
int newCapacity = ((minimumCapacity - MINIMUM_CAPACITY - 1) / GROWTH_STEP + 1) * GROWTH_STEP + MINIMUM_CAPACITY;
|
||||||
|
|
||||||
if (getInventory() != null) {
|
ItemData[] newItems = new ItemData[newCapacity];
|
||||||
getInventory().subscribe(this);
|
int[] newCounts = new int[newCapacity];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
int length = Math.min(this.items.length, newItems.length);
|
||||||
ItemSlot slot = createSlot(slots.size());
|
System.arraycopy(this.items, 0, newItems, 0, length);
|
||||||
slots.add(slot);
|
System.arraycopy(this.counts, 0, newCounts, 0, length);
|
||||||
slot.setContainer(this);
|
|
||||||
|
this.items = newItems;
|
||||||
if (inventory != null) {
|
this.counts = newCounts;
|
||||||
inventory.getEventBus().post(new ItemSlotAddedEvent(slot));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected void ensureCapacity(int minimumCapacity) {
|
||||||
* Instantiates a new slot object that will be appended to the container.
|
if (items.length >= minimumCapacity) {
|
||||||
*
|
return;
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events != null) {
|
setCapacity(minimumCapacity);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void read(DataInput input, IOContext context) throws IOException {
|
public synchronized void read(DataInput input, IOContext context) throws IOException {
|
||||||
synchronized (slots) {
|
int size = input.readInt();
|
||||||
|
|
||||||
|
ensureCapacity(size);
|
||||||
|
|
||||||
int needSlots = input.readInt();
|
for (int index = 0; index < size; ++index) {
|
||||||
int hasSlots = slots.size();
|
|
||||||
|
|
||||||
int costOfResetting = needSlots;
|
ItemData item;
|
||||||
int costOfEditing = Math.abs(needSlots - hasSlots);
|
int count = input.readInt();
|
||||||
|
|
||||||
if (costOfResetting < costOfEditing) {
|
if (count != 0) {
|
||||||
slots.clear();
|
String id = input.readUTF();
|
||||||
addSlots(needSlots);
|
item = ItemDataRegistry.getInstance().create(id);
|
||||||
|
item.read(input, context);
|
||||||
} else {
|
} else {
|
||||||
while (slots.size() > needSlots) {
|
item = null;
|
||||||
slots.remove(slots.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slots.size() < needSlots) {
|
|
||||||
addSlots(needSlots - slots.size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < needSlots; ++i) {
|
items[index] = item;
|
||||||
slots.get(i).read(input, context);
|
counts[index] = count;
|
||||||
}
|
|
||||||
|
fireSlotChangeEvent(index);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void write(DataOutput output, IOContext context) throws IOException {
|
public synchronized void write(DataOutput output, IOContext context) throws IOException {
|
||||||
synchronized (slots) {
|
|
||||||
|
|
||||||
output.writeInt(slots.size());
|
int size = items.length;
|
||||||
for (int i = 0; i < slots.size(); ++i) {
|
output.writeInt(size);
|
||||||
slots.get(i).write(output, context);
|
|
||||||
|
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
|
@Override
|
||||||
public void copy(Encodable destination) {
|
public void copy(Encodable destination) {
|
||||||
ItemContainerMixed container = (ItemContainerMixed) destination;
|
ItemContainerMixed other = (ItemContainerMixed) destination;
|
||||||
List<ItemSlot> mySlots = this.slots;
|
int myLength = this.items.length;
|
||||||
List<ItemSlot> containerSlots = container.slots;
|
|
||||||
|
|
||||||
synchronized (mySlots) {
|
synchronized (this) {
|
||||||
synchronized (containerSlots) {
|
synchronized (other) {
|
||||||
|
|
||||||
|
other.setCapacity(myLength);
|
||||||
|
System.arraycopy(this.counts, 0, other.counts, 0, myLength);
|
||||||
|
|
||||||
int needSlots = mySlots.size();
|
for (int i = 0; i < myLength; ++i) {
|
||||||
int hasSlots = containerSlots.size();
|
ItemData myItem = this.items[i];
|
||||||
|
ItemData otherItem;
|
||||||
|
|
||||||
int costOfResetting = needSlots;
|
if (myItem == null) {
|
||||||
int costOfEditing = Math.abs(needSlots - hasSlots);
|
otherItem = null;
|
||||||
|
} else {
|
||||||
if (costOfResetting < costOfEditing) {
|
otherItem = ItemDataRegistry.getInstance().create(myItem.getId());
|
||||||
containerSlots.clear();
|
myItem.copy(otherItem);
|
||||||
container.addSlots(needSlots);
|
|
||||||
} else {
|
|
||||||
while (containerSlots.size() > needSlots) {
|
|
||||||
slots.remove(containerSlots.size() - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (containerSlots.size() < needSlots) {
|
other.items[i] = otherItem;
|
||||||
addSlots(needSlots - containerSlots.size());
|
other.fireSlotChangeEvent(i);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < needSlots; ++i) {
|
|
||||||
mySlots.get(i).copy(containerSlots.get(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 massLimit;
|
||||||
private final float volumeLimit;
|
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);
|
super(id);
|
||||||
this.massLimit = massLimit;
|
this.massLimit = massLimit;
|
||||||
this.volumeLimit = volumeLimit;
|
this.volumeLimit = volumeLimit;
|
||||||
|
|
||||||
addSlots(startingSlots);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemSlot createSlot(int index) {
|
|
||||||
return new ItemSlot();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,48 +20,89 @@ package ru.windcorp.progressia.common.world.item.inventory;
|
|||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import ru.windcorp.progressia.common.state.Encodable;
|
import ru.windcorp.progressia.common.state.Encodable;
|
||||||
import ru.windcorp.progressia.common.state.IOContext;
|
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 {
|
public abstract class ItemContainerSingle extends ItemContainer {
|
||||||
|
|
||||||
private final ItemSlot slot = new ItemSlot();
|
private ItemData item;
|
||||||
|
private int count;
|
||||||
private final float massLimit;
|
private ItemSlot slot = new ItemSlot(this, 0);
|
||||||
private final float volumeLimit;
|
|
||||||
|
|
||||||
public ItemContainerSingle(String id, float massLimit, float volumeLimit) {
|
public ItemContainerSingle(String id) {
|
||||||
super(id);
|
super(id, 1);
|
||||||
this.massLimit = massLimit;
|
}
|
||||||
this.volumeLimit = volumeLimit;
|
|
||||||
|
@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
|
@Override
|
||||||
public void read(DataInput input, IOContext context) throws IOException {
|
public synchronized void write(DataOutput output, IOContext context) throws IOException {
|
||||||
slot.read(input, context);
|
output.writeInt(count);
|
||||||
}
|
if (item != null) {
|
||||||
|
output.writeUTF(item.getId());
|
||||||
@Override
|
item.write(output, context);
|
||||||
public void write(DataOutput output, IOContext context) throws IOException {
|
}
|
||||||
slot.write(output, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copy(Encodable destination) {
|
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
|
@Override
|
||||||
public ItemSlot getSlot(int index) {
|
public ItemData getItem(int index) {
|
||||||
if (index == 0) {
|
if (index != 0) {
|
||||||
return slot;
|
|
||||||
} else {
|
|
||||||
return null;
|
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() {
|
public ItemSlot slot() {
|
||||||
@ -69,23 +110,42 @@ public abstract class ItemContainerSingle extends ItemContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSlotCount() {
|
public int getMaxIndex() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void forEach(Consumer<? super ItemSlot> action) {
|
protected boolean add(int index, ItemData item, int count) {
|
||||||
action.accept(slot);
|
if (!canAdd(index, item, count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
this.item = item;
|
||||||
|
this.count += count;
|
||||||
|
fireSlotChangeEvent(0);
|
||||||
|
checkState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getMassLimit() {
|
protected boolean remove(int index, ItemData item, int count) {
|
||||||
return massLimit;
|
if (!canRemove(index, item, count)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public float getVolumeLimit() {
|
if (count != 0) {
|
||||||
return volumeLimit;
|
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;
|
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.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
|
* A reference to a slot in a container. The container and the index of an
|
||||||
* found in {@link ItemContainerMixed}s.
|
* {@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
|
* @return the container
|
||||||
*/
|
*/
|
||||||
public ItemContainer getContainer() {
|
public ItemContainer getContainer() {
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the index
|
||||||
|
*/
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
public Inventory getInventory() {
|
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() {
|
||||||
* Retrieves the contents of this slot.
|
return container.getItem(index);
|
||||||
*
|
|
||||||
* @return the stored {@link ItemData} or {@code null}
|
|
||||||
*/
|
|
||||||
public synchronized final ItemData getContents() {
|
|
||||||
return contents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public int getCount() {
|
||||||
* Sets the new contents of this slot. If an item stack was present
|
return container.getCount(index);
|
||||||
* 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() {
|
||||||
* Sets the amount of items stored in this slot.
|
return container.isEmpty(index);
|
||||||
* <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 canAdd(ItemData item, int count) {
|
||||||
* Retrieves the amount of items stored in this slot. If not items are
|
return container.canAdd(index, item, count);
|
||||||
* present, this returns 0.
|
|
||||||
*
|
|
||||||
* @return the amount of items stored
|
|
||||||
*/
|
|
||||||
public synchronized int getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isEmpty() {
|
public boolean canRemove(ItemData item, int count) {
|
||||||
return amount == 0;
|
return container.canRemove(index, item, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean canInsert(ItemData contents, int amount) {
|
protected boolean add(ItemData item, int count) {
|
||||||
if (contents == null) {
|
return container.add(index, item, count);
|
||||||
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) {
|
protected boolean remove(ItemData item, int count) {
|
||||||
return this.amount >= amount;
|
return container.remove(index, item, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void checkState() {
|
public float getMass() {
|
||||||
if ((contents == null) != (amount == 0)) {
|
synchronized (container) {
|
||||||
if (contents == null) {
|
int count = getCount();
|
||||||
throw new IllegalArgumentException("Contents is null but amount (" + amount + ") != 0");
|
if (count == 0) {
|
||||||
} else {
|
return 0;
|
||||||
throw new IllegalArgumentException("Contents is " + contents + " but amount is zero");
|
|
||||||
}
|
}
|
||||||
}
|
return count * getItem().getMass();
|
||||||
|
|
||||||
if (amount < 0) {
|
|
||||||
throw new IllegalArgumentException("amount is negative: " + amount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public float getVolume() {
|
||||||
public synchronized void read(DataInput input, IOContext context) throws IOException {
|
synchronized (container) {
|
||||||
amount = input.readInt();
|
int count = getCount();
|
||||||
if (amount != 0) {
|
if (count == 0) {
|
||||||
String id = input.readUTF();
|
return 0;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
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;
|
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;
|
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
|
||||||
|
|
||||||
public class ItemSlotChangedEvent extends ItemSlotEvent {
|
public class ItemSlotChangedEvent extends ItemSlotEvent {
|
||||||
@ -25,4 +26,8 @@ public class ItemSlotChangedEvent extends ItemSlotEvent {
|
|||||||
super(slot);
|
super(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ItemSlotChangedEvent(ItemContainer container, int index) {
|
||||||
|
super(container, index);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,21 +17,38 @@
|
|||||||
*/
|
*/
|
||||||
package ru.windcorp.progressia.common.world.item.inventory.event;
|
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;
|
import ru.windcorp.progressia.common.world.item.inventory.ItemSlot;
|
||||||
|
|
||||||
public abstract class ItemSlotEvent extends ItemContainerEvent {
|
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) {
|
public ItemSlotEvent(ItemSlot slot) {
|
||||||
super(slot.getContainer());
|
this(slot.getContainer(), slot.getIndex());
|
||||||
this.slot = slot;
|
this.slot = slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the index
|
||||||
|
*/
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the slot
|
* @return the slot
|
||||||
*/
|
*/
|
||||||
public ItemSlot getSlot() {
|
public ItemSlot getSlot() {
|
||||||
|
if (slot == null) {
|
||||||
|
slot = new ItemSlot(getContainer(), index);
|
||||||
|
}
|
||||||
return slot;
|
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.EntityDataPlayer;
|
||||||
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
|
import ru.windcorp.progressia.common.world.entity.EntityDataRegistry;
|
||||||
import ru.windcorp.progressia.common.world.item.ItemDataRegistry;
|
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.server.events.PlayerJoinedEvent;
|
||||||
import ru.windcorp.progressia.test.TestContent;
|
import ru.windcorp.progressia.test.TestContent;
|
||||||
|
|
||||||
@ -63,8 +64,8 @@ public class PlayerManager {
|
|||||||
private EntityDataPlayer spawnPlayerEntity(String login) {
|
private EntityDataPlayer spawnPlayerEntity(String login) {
|
||||||
EntityDataPlayer player = (EntityDataPlayer) EntityDataRegistry.getInstance().create("Core:Player");
|
EntityDataPlayer player = (EntityDataPlayer) EntityDataRegistry.getInstance().create("Core:Player");
|
||||||
|
|
||||||
player.getHand(0).slot().setContents(ItemDataRegistry.getInstance().create("Test:Stick"), 20);
|
Items.spawn(player.getHand(0).slot(), ItemDataRegistry.getInstance().create("Test:Stick"), 5);
|
||||||
player.getEquipmentSlot(0).slot().setContents(ItemDataRegistry.getInstance().create("Test:CardboardBackpack"), 1);
|
Items.spawn(player.getEquipmentSlot(0).slot(), ItemDataRegistry.getInstance().create("Test:CardboardBackpack"), 1);
|
||||||
|
|
||||||
player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation());
|
player.setPosition(getServer().getWorld().getGenerator().suggestSpawnLocation());
|
||||||
player.setUpVector(new Vec3(0, 0, 1));
|
player.setUpVector(new Vec3(0, 0, 1));
|
||||||
|
Reference in New Issue
Block a user