#include "Inventory.h" #include #include "Area.h" #include "Constants.h" #include "Entity.h" #include "Game.h" #include "ItemFilter.h" #include "ItemSlot.h" #include "ItemStack.h" #include "NetworkMessage.h" using namespace Framework; InventoryInteraction::InventoryInteraction( Inventory* current, Inventory* other, Direction dir) : current(current), other(other), dir(dir) { lock(); } InventoryInteraction::InventoryInteraction( const InventoryInteraction& interaction) : InventoryInteraction( interaction.current, interaction.other, interaction.dir) {} InventoryInteraction::~InventoryInteraction() { unlock(); } void InventoryInteraction::lock() { if (!current || !other) return; if (current->location.x < other->location.x) { current->cs.lock(); other->cs.lock(); return; } else if (current->location.x == other->location.x) { if (current->location.y < other->location.y) { current->cs.lock(); other->cs.lock(); return; } else if (current->location.y == other->location.y) { if (current->location.z < other->location.z) { current->cs.lock(); other->cs.lock(); return; } } } other->cs.lock(); current->cs.lock(); } void InventoryInteraction::unlock() { if (!current || !other) return; if (current->location.x < other->location.x) { current->cs.unlock(); other->cs.unlock(); return; } else if (current->location.x == other->location.x) { if (current->location.y < other->location.y) { current->cs.unlock(); other->cs.unlock(); return; } else if (current->location.y == other->location.y) { if (current->location.z < other->location.z) { current->cs.unlock(); other->cs.unlock(); return; } } } other->cs.unlock(); current->cs.unlock(); } void InventoryInteraction::transaction(Inventory* zSource, Inventory* zTarget, ItemFilter* zFilter, Direction sourceView, Direction targetView, int count) { for (auto sourceSlot = zSource->pullSlotsOrder->begin(); sourceSlot;) { while (sourceSlot && (sourceSlot->getNumberOfItems() == 0 || (zFilter && !zFilter->matchSourceSlot(sourceSlot)))) sourceSlot++; if (!sourceSlot) break; // TODO: use target cache ot get list of slots that already contains the // source item bool needNext = 1; for (auto targetSlot = zTarget->pushSlotsOrder->begin(); targetSlot;) { while (targetSlot && (targetSlot->isFull() || (zFilter && !zFilter->matchTargetSlot(targetSlot)))) targetSlot++; if (!targetSlot) break; needNext &= !Inventory::unsafeMove(zSource, zTarget, sourceSlot, targetSlot, sourceView, targetView, count); if (count == 0) return; if (sourceSlot->getNumberOfItems() == 0) break; } if (needNext) sourceSlot++; } } InventoryInteraction& InventoryInteraction::operator=( const InventoryInteraction& data) { if (&data == this) return *this; unlock(); current = data.current; other = data.other; dir = data.dir; lock(); return *this; } void InventoryInteraction::endInteraction() { unlock(); current = 0; other = 0; } void InventoryInteraction::pullItems(int count, ItemFilter* zFilter) { if (!current || !other) return; transaction(other, current, zFilter, getOppositeDirection(dir), dir, count); } void InventoryInteraction::pushItems(int count, ItemFilter* zFilter) { if (!current || !other) return; transaction(current, other, zFilter, dir, getOppositeDirection(dir), count); } MultipleInventoryLock::MultipleInventoryLock(Inventory** inventories, int count) : inventories(new Inventory*[count]), count(count), locked(0) { // sort given inventories in locking order bool* used = new bool[count]; memset(used, 0, count); // TODO: use a performant sorting algorithm for (int i = 0; i < count; i++) { Inventory* min = 0; int minJ = 0; for (int j = 0; j < count; j++) { if (!used[j]) { if (!min) { min = inventories[j]; minJ = j; continue; } if (inventories[j]->location.x < min->location.x) { min = inventories[j]; minJ = j; continue; } if (inventories[j]->location.x == min->location.x) { if (inventories[j]->location.y < min->location.y) { min = inventories[j]; minJ = j; continue; } if (inventories[j]->location.y == min->location.y) { if (inventories[j]->location.z < min->location.z) { min = inventories[j]; minJ = j; continue; } } } } } this->inventories[i] = min; used[minJ] = 1; } lock(); delete[] used; } MultipleInventoryLock::~MultipleInventoryLock() { unlock(); delete[] inventories; } void MultipleInventoryLock::unlock() { if (locked) { locked = 0; for (int i = count - 1; i >= 0; i--) { inventories[i]->cs.unlock(); } } } void MultipleInventoryLock::lock() { if (!locked) { locked = 1; for (int i = 0; i < count; i++) { inventories[i]->cs.lock(); } } } Inventory::Inventory( const Framework::Vec3 location, int dimensionId, bool hasInventory) : ReferenceCounter(), nextSlotId(1), dimensionId(dimensionId), location(location) { if (hasInventory) { pullSlotsOrder = new Framework::RCArray(); pushSlotsOrder = new Framework::RCArray(); itemCache = new Framework::HashMap*>( ITEM_CACHE_SIZE, [](int key) { return key; }); } else { pullSlotsOrder = 0; pushSlotsOrder = 0; itemCache = 0; } } Inventory::~Inventory() { if (pullSlotsOrder) pullSlotsOrder->release(); if (pushSlotsOrder) pushSlotsOrder->release(); if (itemCache) itemCache->release(); } void Inventory::updateCache(ItemSlot* zSlot, int beforeKey) { if (!itemCache) return; int key = zSlot->zStack() ? zSlot->zStack()->zItem()->zItemType()->getId() : -1; if (key == beforeKey) return; if (beforeKey >= 0) { auto tmp = itemCache->safeGet(key, 0); if (tmp) tmp->removeValue(zSlot); } if (zSlot->zStack()) { auto tmp = itemCache->safeGet(key, 0); if (!tmp) { tmp = new Array(); itemCache->put(key, tmp); } tmp->add(zSlot, 0); } } void Inventory::addSlot(ItemSlot* slot) { cs.lock(); ((ItemSlotIDSetter*)slot)->setId(nextSlotId++); int pullPrio = slot->getPullPriority(); int pushPrio = slot->getPushPriority(); int index = 0; for (auto stack : *pullSlotsOrder) { if (stack->getPullPriority() > pullPrio) break; index++; } pullSlotsOrder->add(dynamic_cast(slot->getThis()), index); index = 0; for (auto stack : *pushSlotsOrder) { if (stack->getPushPriority() > pushPrio) break; index++; } pushSlotsOrder->add(slot, index); updateCache(slot, -1); cs.unlock(); } bool Inventory::allowPullStack(ItemSlot* zSlot, Direction dir) const { return pullSlotsOrder; } bool Inventory::allowPushStack( ItemSlot* zSlot, Direction dir, const Item* zItem, int& count) const { return pushSlotsOrder; } void Inventory::afterPullStack( ItemSlot* zSlot, Direction dir, const Item* zItem, int count) { NetworkMessage* msg = new NetworkMessage(); char* message = new char[9]; message[0] = 1; // set count of items *(int*)(message + 1) = zSlot->getId(); *(int*)(message + 5) = zSlot->getNumberOfItems(); msg->setMessage(message, 9); notifyObservers(msg); for (auto call : afterPullStackCalls) call(zSlot, dir, zItem, count); } void Inventory::afterPushStack( ItemSlot* zSlot, Direction dir, const Item* zItem, int count) { updateSlot(zSlot); for (auto call : afterPushStackCalls) call(zSlot, dir, zItem, count); } void Inventory::updateSlot(ItemSlot* zSlot) { NetworkMessage* msg = new NetworkMessage(); char* message = new char[9]; message[0] = 1; // set count of items *(int*)(message + 1) = zSlot->getId(); *(int*)(message + 5) = 0; msg->setMessage(message, 9); notifyObservers(msg); if (zSlot->getNumberOfItems() > 0) { const Item* zItem = zSlot->zStack()->zItem(); NetworkMessage* msg = new NetworkMessage(); char* message = new char[30 + zItem->getName().getLength()]; message[0] = 2; // add new stack *(int*)(message + 1) = zSlot->getId(); *(int*)(message + 5) = zSlot->getNumberOfItems(); *(float*)(message + 9) = zItem->getHp(); *(float*)(message + 13) = zItem->getMaxHp(); *(float*)(message + 17) = zItem->getDurability(); *(float*)(message + 21) = zItem->getMaxDurability(); *(int*)(message + 25) = zItem->zItemType()->getId(); *(message + 29) = (char)zItem->getName().getLength(); memcpy(message + 30, zItem->getName().getText(), zItem->getName().getLength()); msg->setMessage(message, 30 + zItem->getName().getLength()); notifyObservers(msg); } } void Inventory::loadInventory(Framework::StreamReader* zReader) { if (itemCache) { for (auto stack : *pushSlotsOrder) { int size = 0; zReader->lese((char*)&size, 4); if (size != 0) { int id = 0; zReader->lese((char*)&id, 4); Item* item = Game::INSTANCE->zItemType(id)->loadItem(zReader); stack->addItems(new ItemStack(item, size), NO_DIRECTION); } } } } void Inventory::saveInventory(Framework::StreamWriter* zWriter) { if (itemCache) { for (auto slot : *pushSlotsOrder) { const ItemStack* stack = slot->zStack(); int value = 0; if (!stack || !stack->zItem()) { zWriter->schreibe((char*)&value, 4); } else { value = stack->getSize(); zWriter->schreibe((char*)&value, 4); value = stack->zItem()->zItemType()->getId(); zWriter->schreibe((char*)&value, 4); stack->zItem()->zItemType()->saveItem(stack->zItem(), zWriter); } } } } void Inventory::notifyObservers(NetworkMessage* msg) { cs.lock(); int index = 0; Array toDelete; for (auto observer : observers) { Entity* e = Game::INSTANCE->zEntity(observer.getFirst()); if (e) { msg->addressUIElement(observer.getSecond()); Game::INSTANCE->sendMessage(msg->clone(), e); } else toDelete.add(index, 0); index++; } for (int i : toDelete) observers.remove(i); cs.unlock(); msg->release(); } void Inventory::removeObserver(Entity* zSource, Framework::Text id) { cs.lock(); int index = 0; for (auto observer : observers) { if (observer.getFirst() == zSource->getId() && observer.getSecond().istGleich(id)) { observers.remove(index); break; } index++; } cs.unlock(); } void Inventory::addObserver(Entity* zSource, Framework::Text id) { cs.lock(); for (auto observer : observers) { if (observer.getFirst() == zSource->getId() && observer.getSecond().istGleich(id)) { cs.unlock(); return; } } observers.add(ImmutablePair(zSource->getId(), id)); cs.unlock(); for (auto call : observerAddedCalls) call(zSource, id); } void Inventory::lock() { cs.lock(); } void Inventory::unlock() { cs.unlock(); } const ItemSlot* Inventory::zSlot(int id) const { if (itemCache) { for (auto slot : *pushSlotsOrder) { if (slot->getId() == id) return slot; } } return 0; } void Inventory::localTransaction(Array* zSourceSlots, Array* zTargetSlots, ItemFilter* zFilter, int count, Direction outDir, Direction inDir) { if (itemCache) { cs.lock(); auto sourceSlot = zSourceSlots ? zSourceSlots->begin() : pullSlotsOrder->begin(); while (true) { while (sourceSlot && (sourceSlot->getNumberOfItems() == 0 || (zFilter && !zFilter->matchSourceSlot(sourceSlot)))) sourceSlot++; if (!sourceSlot) { cs.unlock(); return; } bool needNext = 1; for (auto targetSlot = zTargetSlots->begin(); targetSlot;) { while ( targetSlot && (targetSlot->isFull() || (zFilter && !zFilter->matchTargetSlot(targetSlot)))) targetSlot++; if (!targetSlot) break; needNext &= !Inventory::unsafeMove( this, this, sourceSlot, targetSlot, outDir, inDir, count); if (count == 0) { cs.unlock(); return; } if (sourceSlot->getNumberOfItems() == 0) break; } if (needNext) sourceSlot++; } cs.unlock(); } } void Inventory::addItems(ItemStack* zItems, Direction dir, ItemFilter* zFilter) { if (itemCache && zItems && zItems->getSize() > 0) { cs.lock(); for (auto targetSlot = pushSlotsOrder->begin(); targetSlot; targetSlot++) { if (!targetSlot->isFull() && (!zFilter || zFilter->matchTargetSlot(targetSlot))) { if (targetSlot->zStack()) { if (targetSlot->zStack()->zItem()->canBeStackedWith( zItems->zItem())) { int number = MIN(targetSlot->numberOfAddableItems(zItems, dir), zItems->getSize()); int tmp = number; if (number > 0 && allowPushStack( targetSlot, dir, zItems->zItem(), tmp)) { number = MIN(number, tmp); ItemStack* stack = zItems->split(number); if (stack) { targetSlot->addItems(stack, dir); afterPushStack(targetSlot, dir, targetSlot->zStack()->zItem(), number); if (stack->getSize()) throw stack; stack->release(); if (!zItems->getSize()) break; } } } } else { int number = MIN(targetSlot->numberOfAddableItems(zItems, dir), zItems->getSize()); int tmp = number; if (number > 0 && allowPushStack( targetSlot, dir, zItems->zItem(), tmp)) { number = MIN(number, tmp); ItemStack* stack = zItems->split(number); if (stack) { targetSlot->addItems(stack, dir); updateCache(targetSlot, -1); afterPushStack(targetSlot, dir, targetSlot->zStack()->zItem(), number); if (stack->getSize()) throw stack; stack->release(); if (!zItems->getSize()) break; } } } } } cs.unlock(); } } void Inventory::addItems(ItemSlot* zSlot, ItemStack* zItems, Direction dir) { if (zSlot->zStack() && !zSlot->zStack()->zItem()->canBeStackedWith(zItems->zItem())) return; bool needUpdate = !zSlot->zStack(); int number = MIN(zSlot->numberOfAddableItems(zItems, dir), zItems->getSize()); int tmp = number; if (number > 0 && allowPushStack(zSlot, dir, zItems->zItem(), tmp)) { number = MIN(number, tmp); ItemStack* stack = zItems->split(number); if (stack) { zSlot->addItems(stack, dir); if (needUpdate) updateCache(zSlot, -1); afterPushStack(zSlot, dir, zSlot->zStack()->zItem(), number); if (stack->getSize()) throw stack; stack->release(); } } } ItemStack* Inventory::takeItemsOut(ItemSlot* zSlot, int count, Direction dir) { if (allowPullStack(zSlot, dir)) { ItemStack* stack = zSlot->takeItemsOut(count, dir); if (stack) { updateCache(zSlot, stack->zItem()->zItemType()->getId()); if (stack->getSize() > 0) afterPullStack(zSlot, dir, stack->zItem(), stack->getSize()); } return stack; } return 0; } InventoryInteraction Inventory::interactWith( Inventory* zInventory, Direction dir) { return InventoryInteraction(this, zInventory, dir); } void Inventory::unsaveAddItem( ItemStack* zStack, Direction dir, ItemFilter* zFilter) { addItems(zStack, dir, zFilter); } int Inventory::numberOfAddableItems( const ItemStack* zStack, Direction dir) const { int count = 0; for (auto targetSlot = pushSlotsOrder->begin(); targetSlot; targetSlot++) { int maxCount = targetSlot->numberOfAddableItems(zStack, dir); int allowed = maxCount; if (allowPushStack(targetSlot, dir, zStack->zItem(), allowed)) count += MIN(maxCount, allowed); } return count; } Framework::ArrayIterator Inventory::begin() { return pullSlotsOrder->begin(); } Framework::ArrayIterator Inventory::end() { return pullSlotsOrder->end(); } void Inventory::inventoryApi(Framework::StreamReader* zRequest, NetworkMessage* zResponse, Entity* zSource) { char type; zRequest->lese(&type, 1); switch (type) { case 0: // request inventory { char idLen; zRequest->lese(&idLen, 1); char* id = new char[idLen + 1]; zRequest->lese(id, idLen); id[(int)idLen] = 0; zResponse->addressUIElement(id); addObserver(zSource, id); delete[] id; char filterLen; zRequest->lese(&filterLen, 1); char* filter = new char[filterLen + 1]; if (filterLen) zRequest->lese(filter, filterLen); filter[(int)filterLen] = 0; InMemoryBuffer buffer; int count = 0; for (ItemSlot* slot : *this) { if (filterLen == 0 || slot->getName().istGleich(filter)) { count++; int id = slot->getId(); buffer.schreibe((char*)&id, 4); int itemCount = slot->getNumberOfItems(); buffer.schreibe((char*)&itemCount, 4); if (itemCount > 0) { float f = slot->zStack()->zItem()->getHp(); buffer.schreibe((char*)&f, 4); f = slot->zStack()->zItem()->getMaxHp(); buffer.schreibe((char*)&f, 4); f = slot->zStack()->zItem()->getDurability(); buffer.schreibe((char*)&f, 4); f = slot->zStack()->zItem()->getMaxDurability(); buffer.schreibe((char*)&f, 4); int id = slot->zStack()->zItem()->zItemType()->getId(); buffer.schreibe((char*)&id, 4); char len = (char)slot->zStack() ->zItem() ->getName() .getLength(); buffer.schreibe((char*)&len, 1); buffer.schreibe( slot->zStack()->zItem()->getName().getText(), slot->zStack()->zItem()->getName().getLength()); } } } delete[] filter; char* msg = new char[5 + buffer.getSize()]; msg[0] = 0; *(int*)(msg + 1) = count; buffer.lese(msg + 5, (int)buffer.getSize()); zResponse->setMessage(msg, 5 + (int)buffer.getSize()); break; } case 1: // remove Observer { char idLen; zRequest->lese(&idLen, 1); char* id = new char[idLen + 1]; zRequest->lese(id, idLen); id[(int)idLen] = 0; removeObserver(zSource, id); delete[] id; break; } case 2: // request item tooltip { char idLen; zRequest->lese(&idLen, 1); char* id = new char[idLen + 1]; zRequest->lese(id, idLen); id[(int)idLen] = 0; zResponse->addressUIElement(id); delete[] id; int slotId; zRequest->lese((char*)&slotId, 4); Text uiml; for (ItemSlot* slot : *pullSlotsOrder) { if (slot->getId() == slotId) { if (slot->zStack() && slot->zStack()->zItem()) uiml = slot->zStack()->zItem()->getTooltipUIML(); } } short len = (short)uiml.getLength(); char* buffer = new char[uiml.getLength() + 7]; buffer[0] = 3; *(int*)(buffer + 1) = slotId; *(short*)(buffer + 5) = len; memcpy(buffer + 7, uiml, len); zResponse->setMessage(buffer, len + 7); break; } } } void Inventory::registerAfterPullStackCall(std::function call) { afterPullStackCalls.add(call); } void Inventory::registerAfterPushStackCall(std::function call) { afterPushStackCalls.add(call); } void Inventory::registerObserverAddedCall( std::function call) { observerAddedCalls.add(call); } int Inventory::getDimensionId() const { return dimensionId; } Framework::Vec3 Inventory::getLocation() const { return location; } bool Inventory::unsafeMove(Inventory* zSource, Inventory* zTarget, ArrayIterator& sourceSlot, ArrayIterator& targetSlot, Direction outDir, Direction inDir, int& count) { if (targetSlot->zStack()) { if (sourceSlot->zStack()->zItem()->canBeStackedWith( targetSlot->zStack()->zItem())) { int number = MIN( targetSlot->numberOfAddableItems(sourceSlot->zStack(), outDir), count); int tmp = number; if (number > 0 && zSource->allowPullStack(sourceSlot, outDir) && zTarget->allowPushStack( targetSlot, inDir, sourceSlot->zStack()->zItem(), tmp)) { number = MIN(number, tmp); ItemStack* stack = sourceSlot->takeItemsOut(number, outDir); if (stack) { targetSlot->addItems(stack, inDir); zSource->updateCache(sourceSlot, targetSlot->zStack()->zItem()->zItemType()->getId()); zSource->afterPullStack(sourceSlot, outDir, targetSlot->zStack()->zItem(), number); zTarget->afterPushStack(targetSlot, inDir, targetSlot->zStack()->zItem(), number); if (stack->getSize()) throw stack; stack->release(); count -= number; return 1; } else targetSlot++; } else targetSlot++; } else targetSlot++; } else { int number = MIN( targetSlot->numberOfAddableItems(sourceSlot->zStack(), outDir), count); int tmp = number; if (number > 0 && zSource->allowPullStack(sourceSlot, outDir) && zTarget->allowPushStack( targetSlot, inDir, sourceSlot->zStack()->zItem(), tmp)) { number = MIN(number, tmp); if (number > 0) { ItemStack* stack = sourceSlot->takeItemsOut(number, outDir); if (stack) { targetSlot->addItems(stack, inDir); zSource->updateCache(sourceSlot, targetSlot->zStack()->zItem()->zItemType()->getId()); zTarget->updateCache(targetSlot, -1); zSource->afterPullStack(sourceSlot, outDir, targetSlot->zStack()->zItem(), number); zTarget->afterPushStack(targetSlot, inDir, targetSlot->zStack()->zItem(), number); if (stack->getSize()) throw stack; stack->release(); count -= number; return 1; } else targetSlot++; } else targetSlot++; } else targetSlot++; } return 0; }