/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2012 The Mana Developers
*
* This file is part of The Mana Client.
*
* 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 2 of the License, or
* 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 .
*/
#include "net/manaserv/inventoryhandler.h"
#include "equipment.h"
#include "inventory.h"
#include "item.h"
#include "itemshortcut.h"
#include "localplayer.h"
#include "log.h"
#include "playerinfo.h"
#include "gui/equipmentwindow.h"
#include "gui/inventorywindow.h"
#include "net/manaserv/connection.h"
#include "net/manaserv/messagein.h"
#include "net/manaserv/messageout.h"
#include "net/manaserv/manaserv_protocol.h"
#include "resources/iteminfo.h"
#include "utils/stringutils.h"
#define EQUIP_FILE "equip.xml"
extern Net::InventoryHandler *inventoryHandler;
namespace ManaServ {
struct EquipItemInfo
{
EquipItemInfo(int itemId, int slotTypeId, int amountUsed):
mItemId(itemId), mSlotTypeId(slotTypeId), mAmountUsed(amountUsed)
{}
int mItemId, mSlotTypeId, mAmountUsed;
};
extern Connection *gameServerConnection;
EquipBackend::EquipBackend()
{
listen(Event::ClientChannel);
mVisibleSlots = 0;
}
EquipBackend::~EquipBackend()
{
clear();
}
Item *EquipBackend::getEquipment(int slotIndex) const
{
auto it = mSlots.find(slotIndex);
return it == mSlots.end() ? nullptr : it->second.item;
}
std::string EquipBackend::getSlotName(int slotIndex) const
{
auto it = mSlots.find(slotIndex);
return it == mSlots.end() ? std::string() : it->second.name;
}
void EquipBackend::triggerUnequip(int slotIndex) const
{
// First get the itemInstance
auto it = mSlots.find(slotIndex);
if (it == mSlots.end() || it->second.itemInstance == 0 || !it->second.item)
return;
Event event(Event::DoUnequip);
event.setItem("item", it->second.item);
event.setInt("itemInstance", it->second.itemInstance);
event.trigger(Event::ItemChannel);
}
void EquipBackend::clear()
{
for (auto &slot : mSlots)
{
if (slot.second.item)
{
delete slot.second.item;
slot.second.item = nullptr;
}
}
mSlots.clear();
}
void EquipBackend::equip(int itemId, int slotTypeId, int amountUsed,
int itemInstance)
{
if (itemInstance <= 0)
{
logger->log("ManaServ::EquipBackend: Equipment slot %i"
" has an invalid item instance.", slotTypeId);
return;
}
auto it = mSlots.begin();
auto it_end = mSlots.end();
bool slotTypeFound = false;
for (; it != it_end; ++it)
if (it->second.slotTypeId == (unsigned)slotTypeId)
slotTypeFound = true;
if (!slotTypeFound)
{
logger->log("ManaServ::EquipBackend: Equipment slot %i"
" is not existing.", slotTypeId);
return;
}
if (!itemDb->exists(itemId))
{
logger->log("ManaServ::EquipBackend: No item with id %d",
itemId);
return;
}
// Place the item in the slots with corresponding id until
// the capacity requested has been reached
for (it = mSlots.begin(); it != it_end && amountUsed > 0; ++it)
{
// If we're on the right slot type and that its unit
// isn't already equipped, we can equip there.
// The slots are already sorted by id, and subId anyway.
if (it->second.slotTypeId == (unsigned)slotTypeId
&& (!it->second.itemInstance) && (!it->second.item))
{
it->second.itemInstance = itemInstance;
it->second.item = new Item(itemId, 1, true);
--amountUsed;
}
}
}
void EquipBackend::unequip(int itemInstance)
{
auto it = mSlots.begin();
auto it_end = mSlots.end();
bool itemInstanceFound = false;
for (; it != it_end; ++it)
if (it->second.itemInstance == (unsigned)itemInstance)
itemInstanceFound = true;
if (!itemInstanceFound)
{
logger->log("ManaServ::EquipBackend: Equipment item instance %i"
" is not existing. The item couldn't be unequipped!",
itemInstance);
return;
}
for (it = mSlots.begin(); it != it_end; ++it)
{
if (it->second.itemInstance != (unsigned)itemInstance)
continue;
// We remove the item
it->second.itemInstance = 0;
// We also delete the item objects
if (it->second.item)
{
delete it->second.item;
it->second.item = nullptr;
}
}
}
void EquipBackend::event(Event::Channel, const Event &event)
{
if (event.getType() == Event::LoadingDatabases)
readEquipFile();
}
void EquipBackend::readEquipFile()
{
clear();
XML::Document doc(EQUIP_FILE);
xmlNodePtr rootNode = doc.rootNode();
if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "equip-slots"))
{
logger->log("ManaServ::EquipBackend: Error while reading "
EQUIP_FILE "!");
return;
}
// The current client slot index
unsigned int slotIndex = 0;
mVisibleSlots = 0;
for_each_xml_child_node(slotNode, rootNode)
{
if (!xmlStrEqual(slotNode->name, BAD_CAST "slot"))
continue;
Slot slot;
slot.slotTypeId = XML::getProperty(slotNode, "id", 0);
std::string name = XML::getProperty(slotNode, "name", std::string());
const int capacity = XML::getProperty(slotNode, "capacity", 1);
slot.weaponSlot = XML::getBoolProperty(slotNode, "weapon", false);
slot.ammoSlot = XML::getBoolProperty(slotNode, "ammo", false);
if (XML::getBoolProperty(slotNode, "visible", false))
++mVisibleSlots;
if (slot.slotTypeId > 0 && capacity > 0)
{
if (name.empty())
slot.name = toString(slot.slotTypeId);
else
slot.name = name;
// The map is filled until the capacity is reached
for (int i = 1; i < capacity + 1; ++i)
{
// Add the capacity part in the name
// when there is more than one slot unit. i.e: 1/3, 2/3
if (capacity > 1)
{
slot.name = name + " " + toString(i)
+ "/" + toString(capacity);
}
slot.subId = i;
mSlots.insert(std::make_pair(slotIndex, slot));
++slotIndex;
}
}
// Read the box properties
readBoxNode(slotNode);
}
}
void EquipBackend::readBoxNode(xmlNodePtr slotNode)
{
for_each_xml_child_node(boxNode, slotNode)
{
if (!xmlStrEqual(boxNode->name, BAD_CAST "box"))
continue;
int x = XML::getProperty(boxNode, "x" , 0);
int y = XML::getProperty(boxNode, "y" , 0);
mBoxesPositions.push_back(Position(x, y));
std::string backgroundFile =
XML::getProperty(boxNode, "background" , std::string());
mBoxesBackgroundFile.push_back(backgroundFile);
}
}
bool EquipBackend::isWeaponSlot(int slotTypeId) const
{
for (const auto &slot : mSlots)
{
if (slot.second.slotTypeId == (unsigned)slotTypeId)
return slot.second.weaponSlot;
}
return false;
}
bool EquipBackend::isAmmoSlot(int slotTypeId) const
{
for (const auto &slot : mSlots)
{
if (slot.second.slotTypeId == (unsigned)slotTypeId)
return slot.second.ammoSlot;
}
return false;
}
Position EquipBackend::getBoxPosition(unsigned int slotIndex) const
{
if (slotIndex < mBoxesPositions.size())
return mBoxesPositions.at(slotIndex);
return Position(0, 0);
}
const std::string& EquipBackend::getBoxBackground(unsigned int slotIndex) const
{
if (slotIndex < mBoxesBackgroundFile.size())
return mBoxesBackgroundFile.at(slotIndex);
return Net::empty;
}
InventoryHandler::InventoryHandler()
{
static const Uint16 _messages[] = {
GPMSG_INVENTORY_FULL,
GPMSG_INVENTORY,
GPMSG_EQUIP,
0
};
handledMessages = _messages;
inventoryHandler = this;
listen(Event::ItemChannel);
}
void InventoryHandler::handleMessage(MessageIn &msg)
{
switch (msg.getId())
{
case GPMSG_INVENTORY_FULL:
{
PlayerInfo::clearInventory();
PlayerInfo::getEquipment()->setBackend(&mEquipBackend);
int count = msg.readInt16();
while (count--)
{
int slot = msg.readInt16();
int id = msg.readInt16();
int amount = msg.readInt16();
PlayerInfo::setInventoryItem(slot, id, amount);
}
// A map of { item instance, {slot type id, item id, amount used}}
std::map equipItemsInfo;
std::map::iterator it;
while (msg.getUnreadLength())
{
int slotTypeId = msg.readInt16();
int itemId = msg.readInt16();
int itemInstance = msg.readInt16();
// Turn the data received into a usable format
it = equipItemsInfo.find(itemInstance);
if (it == equipItemsInfo.end())
{
// Add a new entry
equipItemsInfo.insert(std::make_pair(itemInstance,
EquipItemInfo(itemId, slotTypeId, 1)));
}
else
{
// Add amount to the existing entry
it->second.mAmountUsed++;
}
}
for (it = equipItemsInfo.begin(); it != equipItemsInfo.end();
++it)
{
mEquipBackend.equip(it->second.mItemId,
it->second.mSlotTypeId,
it->second.mAmountUsed,
it->first);
}
// The backend is ready, we can setup the equipment window.
if (equipmentWindow)
equipmentWindow->loadEquipBoxes();
}
break;
case GPMSG_INVENTORY:
while (msg.getUnreadLength())
{
unsigned int slot = msg.readInt16();
int id = msg.readInt16();
unsigned int amount = id ? msg.readInt16() : 0;
PlayerInfo::setInventoryItem(slot, id, amount);
}
break;
case GPMSG_EQUIP:
{
int itemId = msg.readInt16();
int equipSlotCount = msg.readInt16();
if (equipSlotCount <= 0)
break;
// Otherwise equip the item in the given slots
while (equipSlotCount--)
{
unsigned int parameter = msg.readInt16();
unsigned int amountUsed = msg.readInt16();
if (amountUsed == 0)
{
// No amount means to unequip this item
// Note that in that case, the parameter is
// in fact the itemInstanceId
mEquipBackend.unequip(parameter);
}
else
{
int itemInstance = msg.readInt16();
// The parameter is in that case the slot type id.
mEquipBackend.equip(itemId, parameter,
amountUsed, itemInstance);
}
}
}
break;
}
}
void InventoryHandler::event(Event::Channel channel,
const Event &event)
{
if (channel == Event::ItemChannel)
{
Item *item = event.getItem("item");
int itemInstance = event.getInt("itemInstance", 0);
if (!item && itemInstance == 0)
return;
int index = item->getInvIndex();
if (event.getType() == Event::DoEquip)
{
MessageOut msg(PGMSG_EQUIP);
msg.writeInt16(index);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoUnequip)
{
MessageOut msg(PGMSG_UNEQUIP);
msg.writeInt16(itemInstance);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoUse)
{
MessageOut msg(PGMSG_USE_ITEM);
msg.writeInt16(index);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoDrop)
{
int amount = event.getInt("amount", 1);
MessageOut msg(PGMSG_DROP);
msg.writeInt16(index);
msg.writeInt16(amount);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoSplit)
{
int amount = event.getInt("amount", 1);
int newIndex = PlayerInfo::getInventory()->getFreeSlot();
if (newIndex > Inventory::NO_SLOT_INDEX)
{
MessageOut msg(PGMSG_MOVE_ITEM);
msg.writeInt16(index);
msg.writeInt16(newIndex);
msg.writeInt16(amount);
gameServerConnection->send(msg);
}
}
else if (event.getType() == Event::DoMove)
{
int newIndex = event.getInt("newIndex", -1);
if (newIndex >= 0)
{
if (index == newIndex)
return;
MessageOut msg(PGMSG_MOVE_ITEM);
msg.writeInt16(index);
msg.writeInt16(newIndex);
msg.writeInt16(item->getQuantity());
gameServerConnection->send(msg);
}
else
{
/*int source = event.getInt("source");
int destination = event.getInt("destination");
int amount = event.getInt("amount", 1);*/
// TODO Support drag'n'drop to the map ground, or with other
// windows.
}
}
}
}
bool InventoryHandler::canSplit(const Item *item)
{
return item && item->getQuantity() > 1;
}
size_t InventoryHandler::getSize(int type) const
{
switch (type)
{
case Inventory::INVENTORY:
case Inventory::TRADE:
return 50;
case Inventory::STORAGE:
return 300;
default:
return 0;
}
}
} // namespace ManaServ