/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.logic.inventory; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.event.ReceiveEvent; import org.terasology.entitySystem.systems.BaseComponentSystem; import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.logic.inventory.action.MoveItemAction; import org.terasology.logic.inventory.action.SwitchItemAction; import org.terasology.logic.inventory.events.AbstractMoveItemRequest; import org.terasology.logic.inventory.events.InventoryChangeAcknowledgedRequest; import org.terasology.logic.inventory.events.MoveItemAmountRequest; import org.terasology.logic.inventory.events.MoveItemRequest; import org.terasology.logic.inventory.events.MoveItemToSlotsRequest; import org.terasology.logic.players.LocalPlayer; import org.terasology.registry.In; import org.terasology.registry.Share; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @RegisterSystem(RegisterMode.REMOTE_CLIENT) @Share(value = InventoryManager.class) public class InventoryClientSystem extends BaseComponentSystem implements InventoryManager { @In private LocalPlayer localPlayer; private Map<Integer, AbstractMoveItemRequest> pendingMoves = new LinkedHashMap<>(); private int changeId; // We support only these two events on the client, as these are the events used by inventory UI @ReceiveEvent(components = {InventoryComponent.class}) public void switchItemRequest(SwitchItemAction event, EntityRef entity) { switchItem(entity, event.getInstigator(), event.getSlotFrom(), event.getTo(), event.getSlotTo()); } @ReceiveEvent(components = {InventoryComponent.class}) public void moveItemRequest(MoveItemAction event, EntityRef entity) { moveItem(entity, event.getInstigator(), event.getSlotFrom(), event.getTo(), event.getSlotTo(), event.getCount()); } @ReceiveEvent(components = {InventoryComponent.class}) public void inventoryChangeAcknowledge(InventoryChangeAcknowledgedRequest event, EntityRef entity) { //TODO: This does not ever get triggered because the event is sent to the client, not the character. // If it did get triggered, it causes a mess of question mark items. AbstractMoveItemRequest removedRequest = pendingMoves.remove(event.getChangeId()); if (removedRequest != null) { destroyClientTempEntities(removedRequest); } recalculatePredictedState(); } private void destroyClientTempEntities(AbstractMoveItemRequest removedRequest) { for (EntityRef tempEntity : removedRequest.getClientSideTempEntities()) { if (tempEntity.exists()) { tempEntity.destroy(); } } } private void recalculatePredictedState() { for (AbstractMoveItemRequest request : pendingMoves.values()) { // For each remaining pending request, we need to destroy all temp entities that request previously created, // then redo the change requested and store new temp entities in the request to be destroyed once that // pending request is acknowledged by the server if (request instanceof MoveItemRequest) { MoveItemRequest r = (MoveItemRequest) request; destroyClientTempEntities(r); Collection<EntityRef> newClientTempEntities = new HashSet<>(); moveItemFillClientTempEntities(request.getFromInventory(), r.getInstigator(), r.getFromSlot(), r.getToInventory(), r.getToSlot(), newClientTempEntities); r.setClientSideTempEntities(newClientTempEntities); } else if (request instanceof MoveItemAmountRequest) { MoveItemAmountRequest r = (MoveItemAmountRequest) request; destroyClientTempEntities(r); Collection<EntityRef> newClientTempEntities = new HashSet<>(); moveItemAmountFillClientTempEntities(r.getFromInventory(), r.getInstigator(), r.getFromSlot(), r.getToInventory(), r.getToSlot(), r.getAmount(), newClientTempEntities); r.setClientSideTempEntities(newClientTempEntities); } else if (request instanceof MoveItemToSlotsRequest) { MoveItemToSlotsRequest r = (MoveItemToSlotsRequest) request; destroyClientTempEntities(r); Collection<EntityRef> newClientTempEntities = new HashSet<>(); moveItemToSlotsFillClientTempEntities(r.getInstigator(), r.getFromInventory(), r.getFromSlot(), r.getToInventory(), r.getToSlots(), newClientTempEntities); r.setClientSideTempEntities(newClientTempEntities); } } } @Override public boolean canStackTogether(EntityRef itemA, EntityRef itemB) { return InventoryUtils.canStackInto(itemA, itemB); } @Override public int getStackSize(EntityRef item) { return InventoryUtils.getStackCount(item); } @Override public EntityRef getItemInSlot(EntityRef inventoryEntity, int slot) { return InventoryUtils.getItemAt(inventoryEntity, slot); } @Override public int findSlotWithItem(EntityRef inventoryEntity, EntityRef item) { return InventoryUtils.getSlotWithItem(inventoryEntity, item); } @Override public int getNumSlots(EntityRef inventoryEntity) { return InventoryUtils.getSlotCount(inventoryEntity); } @Override public boolean giveItem(EntityRef inventory, EntityRef instigator, EntityRef item) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public boolean giveItem(EntityRef inventory, EntityRef instigator, EntityRef item, int slot) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public boolean giveItem(EntityRef inventory, EntityRef instigator, EntityRef item, List<Integer> slots) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public EntityRef removeItem(EntityRef inventory, EntityRef instigator, EntityRef item, boolean destroyRemoved) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public EntityRef removeItem(EntityRef inventory, EntityRef instigator, EntityRef item, boolean destroyRemoved, int count) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public EntityRef removeItem(EntityRef inventory, EntityRef instigator, List<EntityRef> items, boolean destroyRemoved) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public EntityRef removeItem(EntityRef inventory, EntityRef instigator, List<EntityRef> items, boolean destroyRemoved, int count) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public EntityRef removeItem(EntityRef inventory, EntityRef instigator, int slotNo, boolean destroyRemoved, int count) { throw new UnsupportedOperationException("This operation cannot be invoked on the client"); } @Override public boolean moveItem(EntityRef fromInventory, EntityRef instigator, int slotFrom, EntityRef toInventory, int slotTo, int count) { Collection<EntityRef> clientTempEntities = new HashSet<>(); if (moveItemAmountFillClientTempEntities(fromInventory, instigator, slotFrom, toInventory, slotTo, count, clientTempEntities)) { return false; } MoveItemAmountRequest request = new MoveItemAmountRequest(instigator, fromInventory, slotFrom, toInventory, slotTo, count, changeId++, clientTempEntities); pendingMoves.put(request.getChangeId(), request); localPlayer.getClientEntity().send(request); return true; } private boolean moveItemAmountFillClientTempEntities(EntityRef fromInventory, EntityRef instigator, int slotFrom, EntityRef toInventory, int slotTo, int count, Collection<EntityRef> clientTempEntities) { EntityRef itemAtBefore = InventoryUtils.getItemAt(toInventory, slotTo); boolean itemExisted = itemAtBefore.exists(); if (!InventoryUtils.moveItemAmount(instigator, fromInventory, slotFrom, toInventory, slotTo, count)) { return true; } if (!itemExisted) { clientTempEntities.add(InventoryUtils.getItemAt(toInventory, slotTo)); } return false; } @Override public boolean moveItemToSlots(EntityRef instigator, EntityRef fromInventory, int slotFrom, EntityRef toInventory, List<Integer> toSlots) { Collection<EntityRef> clientTempEntities = new HashSet<>(); if (moveItemToSlotsFillClientTempEntities(instigator, fromInventory, slotFrom, toInventory, toSlots, clientTempEntities)) { return false; } MoveItemToSlotsRequest request = new MoveItemToSlotsRequest(instigator, fromInventory, slotFrom, toInventory, toSlots, changeId++, clientTempEntities); pendingMoves.put(request.getChangeId(), request); localPlayer.getClientEntity().send(request); return true; } private boolean moveItemToSlotsFillClientTempEntities(EntityRef instigator, EntityRef fromInventory, int slotFrom, EntityRef toInventory, List<Integer> toSlots, Collection<EntityRef> clientTempEntities) { Set<Integer> emptySlotsBefore = new HashSet<>(); for (Integer toSlot : toSlots) { if (!InventoryUtils.getItemAt(toInventory, toSlot).exists()) { emptySlotsBefore.add(toSlot); } } if (!InventoryUtils.moveItemToSlots(instigator, fromInventory, slotFrom, toInventory, toSlots)) { return true; } for (Integer slot : emptySlotsBefore) { EntityRef itemAt = InventoryUtils.getItemAt(toInventory, slot); if (itemAt.exists()) { clientTempEntities.add(itemAt); } } return false; } @Override public boolean switchItem(EntityRef fromInventory, EntityRef instigator, int slotFrom, EntityRef toInventory, int slotTo) { Collection<EntityRef> clientTempEntities = new HashSet<>(); if (moveItemFillClientTempEntities(fromInventory, instigator, slotFrom, toInventory, slotTo, clientTempEntities)) { return false; } MoveItemRequest request = new MoveItemRequest(instigator, fromInventory, slotFrom, toInventory, slotTo, changeId++, clientTempEntities); pendingMoves.put(request.getChangeId(), request); localPlayer.getClientEntity().send(request); return true; } private boolean moveItemFillClientTempEntities(EntityRef fromInventory, EntityRef instigator, int slotFrom, EntityRef toInventory, int slotTo, Collection<EntityRef> clientTempEntities) { boolean slotFromEmpty = !InventoryUtils.getItemAt(fromInventory, slotFrom).exists(); boolean slotToEmpty = !InventoryUtils.getItemAt(toInventory, slotTo).exists(); if (!InventoryUtils.moveItem(instigator, fromInventory, slotFrom, toInventory, slotTo)) { return true; } if (slotFromEmpty) { EntityRef itemAt = InventoryUtils.getItemAt(fromInventory, slotFrom); if (itemAt.exists()) { clientTempEntities.add(itemAt); } } if (slotToEmpty) { EntityRef itemAt = InventoryUtils.getItemAt(toInventory, slotTo); if (itemAt.exists()) { clientTempEntities.add(itemAt); } } return false; } }