summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/being.cpp2
-rw-r--r--src/being.h2
-rw-r--r--src/equipment.h25
-rw-r--r--src/game.cpp11
-rw-r--r--src/game.h9
-rw-r--r--src/gui/equipmentwindow.cpp229
-rw-r--r--src/gui/equipmentwindow.h29
-rw-r--r--src/gui/itempopup.cpp35
-rw-r--r--src/gui/itempopup.h11
-rw-r--r--src/gui/popupmenu.cpp25
-rw-r--r--src/gui/popupmenu.h2
-rw-r--r--src/gui/quitdialog.cpp2
-rw-r--r--src/gui/quitdialog.h6
-rw-r--r--src/gui/setup_keyboard.cpp3
-rw-r--r--src/gui/specialswindow.h3
-rw-r--r--src/gui/viewport.cpp4
-rw-r--r--src/gui/viewport.h2
-rw-r--r--src/gui/widgets/button.cpp74
-rw-r--r--src/gui/widgets/button.h18
-rw-r--r--src/gui/widgets/tabbedarea.cpp8
-rw-r--r--src/gui/widgets/tabbedarea.h2
-rw-r--r--src/gui/windowmenu.cpp108
-rw-r--r--src/gui/windowmenu.h12
-rw-r--r--src/localplayer.cpp15
-rw-r--r--src/localplayer.h9
-rw-r--r--src/net/inventoryhandler.h32
-rw-r--r--src/net/manaserv/beinghandler.cpp34
-rw-r--r--src/net/manaserv/beinghandler.h10
-rw-r--r--src/net/manaserv/charhandler.cpp12
-rw-r--r--src/net/manaserv/inventoryhandler.cpp351
-rw-r--r--src/net/manaserv/inventoryhandler.h88
-rw-r--r--src/net/manaserv/manaserv_protocol.h38
-rw-r--r--src/net/manaserv/playerhandler.cpp8
-rw-r--r--src/net/manaserv/playerhandler.h2
-rw-r--r--src/net/playerhandler.h8
-rw-r--r--src/net/tmwa/inventoryhandler.cpp5
-rw-r--r--src/net/tmwa/inventoryhandler.h67
-rw-r--r--src/net/tmwa/playerhandler.cpp6
-rw-r--r--src/net/tmwa/playerhandler.h2
39 files changed, 958 insertions, 351 deletions
diff --git a/src/being.cpp b/src/being.cpp
index f65afbd3..04cd6719 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -1067,8 +1067,6 @@ void Being::updateColors()
void Being::setSprite(unsigned int slot, int id, const std::string &color,
bool isWeapon)
{
- assert(slot < Net::getCharHandler()->maxSprite());
-
if (slot >= size())
ensureSize(slot + 1);
diff --git a/src/being.h b/src/being.h
index 1cf713fa..c6fd63ed 100644
--- a/src/being.h
+++ b/src/being.h
@@ -319,7 +319,7 @@ class Being : public ActorSprite, public EventListener
* in ticks per tile for eAthena,
* in tiles per second for Manaserv (0.1 precision).
*/
- void setMoveSpeed(const Vector &speed);
+ virtual void setMoveSpeed(const Vector &speed);
/**
* Gets the original Move speed.
diff --git a/src/equipment.h b/src/equipment.h
index 2ef970ea..564eb2ee 100644
--- a/src/equipment.h
+++ b/src/equipment.h
@@ -22,7 +22,7 @@
#ifndef EQUIPMENT_H
#define EQUIPMENT_H
-#define EQUIPMENT_SIZE 11
+#include <string>
class Item;
@@ -35,16 +35,33 @@ class Equipment
class Backend {
public:
- virtual Item *getEquipment(int index) const = 0;
+ virtual Item *getEquipment(int slotIndex) const = 0;
+ virtual std::string getSlotName(int slotIndex) const
+ { return std::string(); }
+
virtual void clear() = 0;
virtual ~Backend() { }
+ virtual int getSlotNumber() const = 0;
+ virtual void triggerUnequip(int slotIndex) const = 0;
+ private:
+ virtual void readEquipFile()
+ {}
};
/**
* Get equipment at the given slot.
*/
- Item *getEquipment(int index) const
- { return mBackend ? mBackend->getEquipment(index) : 0; }
+ Item *getEquipment(int slotIndex) const
+ { return mBackend ? mBackend->getEquipment(slotIndex) : 0; }
+
+ const std::string getSlotName(int slotIndex) const
+ { return mBackend ? mBackend->getSlotName(slotIndex) : std::string(); }
+
+ int getSlotNumber() const
+ { return mBackend ? mBackend->getSlotNumber() : 0; }
+
+ void triggerUnequip(int slotIndex) const
+ { if (mBackend) mBackend->triggerUnequip(slotIndex); }
/**
* Clears equipment.
diff --git a/src/game.cpp b/src/game.cpp
index af9c2c39..813253ce 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -64,7 +64,6 @@
#include "gui/textdialog.h"
#include "gui/trade.h"
#include "gui/viewport.h"
-#include "gui/windowmenu.h"
#include "gui/widgets/chattab.h"
#include "gui/widgets/emoteshortcutcontainer.h"
@@ -154,15 +153,7 @@ static void createGuiWindows()
minimap = new Minimap;
chatWindow = new ChatWindow;
tradeWindow = new TradeWindow;
- switch (Net::getNetworkType())
- {
- case ServerInfo::TMWATHENA:
- case ServerInfo::MANASERV:
- default:
- equipmentWindow =
- new TmwAthena::TaEquipmentWindow(PlayerInfo::getEquipment());
- break;
- }
+ equipmentWindow = new EquipmentWindow(PlayerInfo::getEquipment());
statusWindow = new StatusWindow;
inventoryWindow = new InventoryWindow(PlayerInfo::getInventory());
skillDialog = new SkillDialog;
diff --git a/src/game.h b/src/game.h
index 22e242c9..9e38f928 100644
--- a/src/game.h
+++ b/src/game.h
@@ -24,8 +24,9 @@
#include <string>
+#include "gui/windowmenu.h"
+
class Map;
-class WindowMenu;
/**
* The main class responsible for running the game. The game starts after you
@@ -73,6 +74,12 @@ class Game
int getCurrentTileWidth() const;
int getCurrentTileHeight() const;
+ /**
+ * Update the key shortcuts in the window menu.
+ */
+ void updateWindowMenuCaptions()
+ { mWindowMenu->updatePopUpCaptions(); }
+
private:
int mLastTarget;
bool mDisconnected;
diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp
index c17b5e04..209ecdb0 100644
--- a/src/gui/equipmentwindow.cpp
+++ b/src/gui/equipmentwindow.cpp
@@ -54,7 +54,8 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment):
Window(_("Equipment")),
mEquipBox(0),
mSelected(-1),
- mEquipment(equipment)
+ mEquipment(equipment),
+ mBoxesNumber(0)
{
mItemPopup = new ItemPopup;
setupWindow->registerWindowForReset(this);
@@ -80,9 +81,28 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment):
add(mUnequip);
}
+void EquipmentWindow::loadEquipBoxes()
+{
+ if (mEquipBox)
+ delete[] mEquipBox;
+
+ // Load equipment boxes.
+ mBoxesNumber = mEquipment->getSlotNumber();
+ mEquipBox = new EquipBox[mBoxesNumber];
+
+ for (int i = 0; i < mBoxesNumber; ++i)
+ {
+ Position boxPosition = Net::getInventoryHandler()->getBoxPosition(i);
+ mEquipBox[i].posX = boxPosition.x + getPadding();
+ mEquipBox[i].posY = boxPosition.y + getTitleBarHeight();
+ }
+}
+
EquipmentWindow::~EquipmentWindow()
{
delete mItemPopup;
+ if (mEquipBox)
+ delete[] mEquipBox;
}
void EquipmentWindow::draw(gcn::Graphics *graphics)
@@ -91,32 +111,84 @@ void EquipmentWindow::draw(gcn::Graphics *graphics)
Window::draw(graphics);
Window::drawChildren(graphics);
+
+ // Draw equipment boxes
+ Graphics *g = static_cast<Graphics*>(graphics);
+
+ for (int i = 0; i < mBoxesNumber; i++)
+ {
+ if (i == mSelected)
+ {
+ const gcn::Color color = Theme::getThemeColor(Theme::HIGHLIGHT);
+
+ // Set color to the highlight color
+ g->setColor(gcn::Color(color.r, color.g, color.b, getGuiAlpha()));
+ g->fillRectangle(gcn::Rectangle(mEquipBox[i].posX,
+ mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT));
+ }
+
+ // Set color black
+ g->setColor(gcn::Color(0, 0, 0));
+ // Draw box border
+ g->drawRectangle(gcn::Rectangle(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT));
+
+ Item *item = mEquipment->getEquipment(i);
+ if (item)
+ {
+ // Draw Item.
+ Image *image = item->getImage();
+ // Ensure the image is drawn with maximum opacity
+ image->setAlpha(1.0f);
+ g->drawImage(image,
+ mEquipBox[i].posX + 2,
+ mEquipBox[i].posY + 2);
+ if (i == TmwAthena::EQUIP_PROJECTILE_SLOT)
+ {
+ g->setColor(Theme::getThemeColor(Theme::TEXT));
+ graphics->drawText(toString(item->getQuantity()),
+ mEquipBox[i].posX + (BOX_WIDTH / 2),
+ mEquipBox[i].posY - getFont()->getHeight(),
+ gcn::Graphics::CENTER);
+ }
+ }
+ }
}
void EquipmentWindow::action(const gcn::ActionEvent &event)
{
if (event.getId() == "unequip" && mSelected > -1)
{
- Item *item = mEquipment->getEquipment(mSelected);
- item->doEvent(Event::DoUnequip);
+ mEquipment->triggerUnequip(mSelected);
setSelected(-1);
}
}
Item *EquipmentWindow::getItem(int x, int y) const
{
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
+ for (int i = 0; i < mBoxesNumber; ++i)
{
- for (int i = 0; i < TmwAthena::EQUIP_VECTOR_END; i++)
- {
- gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
- BOX_WIDTH, BOX_HEIGHT);
+ gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT);
- if (tRect.isPointInRect(x, y))
- return mEquipment->getEquipment(i);
- }
+ if (tRect.isPointInRect(x, y))
+ return mEquipment->getEquipment(i);
+ }
+ return 0;
+}
+
+const std::string EquipmentWindow::getSlotName(int x, int y) const
+{
+ for (int i = 0; i < mBoxesNumber; ++i)
+ {
+ gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT);
+
+ if (tRect.isPointInRect(x, y))
+ return mEquipment->getSlotName(i);
}
- return NULL;
+ return std::string();
}
void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent)
@@ -125,51 +197,58 @@ void EquipmentWindow::mousePressed(gcn::MouseEvent& mouseEvent)
const int x = mouseEvent.getX();
const int y = mouseEvent.getY();
+ Item *item = 0;
- if (mouseEvent.getButton() == gcn::MouseEvent::LEFT)
+ // Checks if any of the presses were in the equip boxes.
+ for (int i = 0; i < mBoxesNumber; ++i)
{
- // Checks if any of the presses were in the equip boxes.
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
- {
- for (int i = 0; i < TmwAthena::EQUIP_VECTOR_END; i++)
- {
- Item *item = mEquipment->getEquipment(i);
- gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
- BOX_WIDTH, BOX_HEIGHT);
+ item = mEquipment->getEquipment(i);
+ gcn::Rectangle tRect(mEquipBox[i].posX, mEquipBox[i].posY,
+ BOX_WIDTH, BOX_HEIGHT);
- if (tRect.isPointInRect(x, y) && item)
- setSelected(i);
- }
+ if (tRect.isPointInRect(x, y) && item)
+ {
+ setSelected(i);
+ break;
}
}
- else if (mouseEvent.getButton() == gcn::MouseEvent::RIGHT)
+
+ if (mouseEvent.getButton() == gcn::MouseEvent::RIGHT)
{
- if (Item *item = getItem(x, y))
+ if (item)
{
/* Convert relative to the window coordinates to absolute screen
* coordinates.
*/
const int mx = x + getX();
const int my = y + getY();
- viewport->showPopup(this, mx, my, item, true);
+ viewport->showPopup(this, mx, my, item, true, false);
}
}
}
-// Show ItemTooltip
void EquipmentWindow::mouseMoved(gcn::MouseEvent &event)
{
const int x = event.getX();
const int y = event.getY();
- Item *item = getItem(x, y);
+ int mouseX, mouseY;
+ SDL_GetMouseState(&mouseX, &mouseY);
- if (item)
+ // Show ItemTooltip
+ std::string slotName = getSlotName(x, y);
+ if (!slotName.empty())
{
- int mouseX, mouseY;
- SDL_GetMouseState(&mouseX, &mouseY);
+ mItemPopup->setEquipmentText(slotName);
+
+ Item *item = getItem(x, y);
+ if (item)
+ {
+ mItemPopup->setItem(item->getInfo());
+ }
+ else
+ mItemPopup->setNoItem();
- mItemPopup->setItem(item->getInfo());
mItemPopup->position(x + getX(), y + getY());
}
else
@@ -178,7 +257,6 @@ void EquipmentWindow::mouseMoved(gcn::MouseEvent &event)
}
}
-// Hide ItemTooltip
void EquipmentWindow::mouseExited(gcn::MouseEvent &event)
{
mItemPopup->setVisible(false);
@@ -189,86 +267,3 @@ void EquipmentWindow::setSelected(int index)
mSelected = index;
mUnequip->setEnabled(mSelected != -1);
}
-
-namespace TmwAthena {
-
-TaEquipmentWindow::TaEquipmentWindow(Equipment *equipment):
- EquipmentWindow(equipment)
-{
- // Positions of the boxes, 2nd dimension is X and Y respectively.
- const int boxPosition[][2] = {
- { 90, 40 }, // EQUIP_TORSO_SLOT
- { 8, 78 }, // EQUIP_GLOVES_SLOT
- { 70, 0 }, // EQUIP_HEAD_SLOT
- { 50, 208 }, // EQUIP_LEGS_SLOT
- { 90, 208 }, // EQUIP_FEET_SLOT
- { 8, 168 }, // EQUIP_RING1_SLOT
- { 129, 168 }, // EQUIP_RING2_SLOT
- { 50, 40 }, // EQUIP_NECK_SLOT
- { 8, 123 }, // EQUIP_FIGHT1_SLOT
- { 129, 123 }, // EQUIP_FIGHT2_SLOT
- { 129, 78 } // EQUIP_PROJECTILE_SLOT
- };
-
- // Load equipment boxes.
- mEquipBox = new EquipBox[TmwAthena::EQUIP_VECTOR_END];
-
- for (int i = 0; i < TmwAthena::EQUIP_VECTOR_END; i++)
- {
- mEquipBox[i].posX = boxPosition[i][0] + getPadding();
- mEquipBox[i].posY = boxPosition[i][1] + getTitleBarHeight();
- }
-}
-
-TaEquipmentWindow::~TaEquipmentWindow()
-{
- delete[] mEquipBox;
-}
-
-void TaEquipmentWindow::draw(gcn::Graphics *graphics)
-{
- EquipmentWindow::draw(graphics);
-
- // Draw equipment boxes
- Graphics *g = static_cast<Graphics*>(graphics);
-
- for (int i = 0; i < TmwAthena::EQUIP_VECTOR_END; i++)
- {
- if (i == mSelected)
- {
- const gcn::Color color = Theme::getThemeColor(Theme::HIGHLIGHT);
-
- // Set color to the highlight color
- g->setColor(gcn::Color(color.r, color.g, color.b, getGuiAlpha()));
- g->fillRectangle(gcn::Rectangle(mEquipBox[i].posX, mEquipBox[i].posY,
- BOX_WIDTH, BOX_HEIGHT));
- }
-
- // Set color black
- g->setColor(gcn::Color(0, 0, 0));
- // Draw box border
- g->drawRectangle(gcn::Rectangle(mEquipBox[i].posX, mEquipBox[i].posY,
- BOX_WIDTH, BOX_HEIGHT));
-
- Item *item = mEquipment->getEquipment(i);
- if (item)
- {
- // Draw Item.
- Image *image = item->getImage();
- image->setAlpha(1.0f); // Ensure the image is drawn with maximum opacity
- g->drawImage(image,
- mEquipBox[i].posX + 2,
- mEquipBox[i].posY + 2);
- if (i == TmwAthena::EQUIP_PROJECTILE_SLOT)
- {
- g->setColor(Theme::getThemeColor(Theme::TEXT));
- graphics->drawText(toString(item->getQuantity()),
- mEquipBox[i].posX + (BOX_WIDTH / 2),
- mEquipBox[i].posY - getFont()->getHeight(),
- gcn::Graphics::CENTER);
- }
- }
- }
-}
-
-} // namespace TmwAthena
diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h
index 5ba15ae3..57a13d40 100644
--- a/src/gui/equipmentwindow.h
+++ b/src/gui/equipmentwindow.h
@@ -53,6 +53,17 @@ class EquipmentWindow : public Window, public gcn::ActionListener
void mousePressed(gcn::MouseEvent& mouseEvent);
+ /**
+ * Loads the correct amount of displayed equip boxes.
+ */
+ void loadEquipBoxes();
+
+ /**
+ * Returns the current selected slot or -1 if none.
+ */
+ int getSelected()
+ { return mSelected; }
+
protected:
/**
* Equipment box.
@@ -67,12 +78,14 @@ class EquipmentWindow : public Window, public gcn::ActionListener
int mSelected; /**< Index of selected item. */
Equipment *mEquipment;
+ int mBoxesNumber; /**< Number of equipment boxes to display */
private:
void mouseExited(gcn::MouseEvent &event);
void mouseMoved(gcn::MouseEvent &event);
Item *getItem(int x, int y) const;
+ const std::string getSlotName(int x, int y) const;
void setSelected(int index);
@@ -80,22 +93,6 @@ class EquipmentWindow : public Window, public gcn::ActionListener
gcn::Button *mUnequip;
};
-namespace TmwAthena {
-
-class TaEquipmentWindow : public EquipmentWindow
-{
- public:
- TaEquipmentWindow(Equipment *equipment);
- ~TaEquipmentWindow();
-
- /**
- * Draws the equipment window using TmwAthena routine.
- */
- void draw(gcn::Graphics *graphics);
-};
-
-} // namespace TmwAthena
-
extern EquipmentWindow *equipmentWindow;
#endif // EQUIPMENTWINDOW_H
diff --git a/src/gui/itempopup.cpp b/src/gui/itempopup.cpp
index 60943756..d65764a5 100644
--- a/src/gui/itempopup.cpp
+++ b/src/gui/itempopup.cpp
@@ -123,6 +123,34 @@ ItemPopup::~ItemPopup()
}
}
+void ItemPopup::setEquipmentText(const std::string& text)
+{
+ mItemEquipSlot = text;
+}
+
+void ItemPopup::setNoItem()
+{
+ mIcon->setImage(0);
+
+ std::string caption = _("No item");
+ if (!mItemEquipSlot.empty())
+ {
+ caption += " (";
+ caption += mItemEquipSlot;
+ caption += ")";
+ }
+ mItemName->setCaption(caption);
+ mItemName->adjustSize();
+
+ mItemName->setForegroundColor(Theme::getThemeColor(Theme::GENERIC));
+ mItemName->setPosition(getPadding(), getPadding());
+
+ mItemDesc->setText(std::string());
+ mItemEffect->setText(std::string());
+
+ setContentSize(mItemName->getWidth() + 2 * getPadding(), 0);
+}
+
void ItemPopup::setItem(const ItemInfo &item, bool showImage)
{
if (item.getName() == mItemName->getCaption())
@@ -157,7 +185,11 @@ void ItemPopup::setItem(const ItemInfo &item, bool showImage)
mItemType = item.getItemType();
- mItemName->setCaption(item.getName());
+ std::string caption = item.getName();
+ if (!mItemEquipSlot.empty())
+ caption += " (" + mItemEquipSlot + ")";
+
+ mItemName->setCaption(caption);
mItemName->adjustSize();
mItemName->setForegroundColor(getColorFromItemType(mItemType));
mItemName->setPosition(getPadding() + space, getPadding());
@@ -226,5 +258,6 @@ void ItemPopup::mouseMoved(gcn::MouseEvent &event)
// When the mouse moved on top of the popup, hide it
setVisible(false);
+ mItemEquipSlot.clear();
}
diff --git a/src/gui/itempopup.h b/src/gui/itempopup.h
index f054ddf5..95adcf45 100644
--- a/src/gui/itempopup.h
+++ b/src/gui/itempopup.h
@@ -49,6 +49,16 @@ class ItemPopup : public Popup
~ItemPopup();
/**
+ * Tells the item popup to say: No Item.
+ */
+ void setNoItem();
+
+ /**
+ * Tells in which equipment slot the item is equipped.
+ */
+ void setEquipmentText(const std::string& text = std::string());
+
+ /**
* Sets the info to be displayed given a particular item.
*/
void setItem(const ItemInfo &item, bool showImage = false);
@@ -60,6 +70,7 @@ class ItemPopup : public Popup
TextBox *mItemDesc;
TextBox *mItemEffect;
TextBox *mItemWeight;
+ std::string mItemEquipSlot;
ItemType mItemType;
Icon *mIcon;
};
diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp
index 1c2f3b60..fbe3c739 100644
--- a/src/gui/popupmenu.cpp
+++ b/src/gui/popupmenu.cpp
@@ -32,6 +32,7 @@
#include "playerrelations.h"
#include "gui/chat.h"
+#include "gui/equipmentwindow.h"
#include "gui/inventorywindow.h"
#include "gui/itemamount.h"
@@ -266,15 +267,20 @@ void PopupMenu::handleLink(const std::string &link)
{
}
- else if (link == "activate")
+ else if (link == "activate" || link == "equip" || link == "unequip")
{
assert(mItem);
if (mItem->isEquippable())
{
if (mItem->isEquipped())
- mItem->doEvent(Event::DoUnequip);
+ {
+ PlayerInfo::getEquipment()->triggerUnequip(
+ equipmentWindow->getSelected());
+ }
else
+ {
mItem->doEvent(Event::DoEquip);
+ }
}
else
{
@@ -347,7 +353,7 @@ void PopupMenu::handleLink(const std::string &link)
}
void PopupMenu::showPopup(Window *parent, int x, int y, Item *item,
- bool isInventory)
+ bool isInventory, bool canDrop)
{
assert(item);
mItem = item;
@@ -364,17 +370,20 @@ void PopupMenu::showPopup(Window *parent, int x, int y, Item *item,
if (item->getInfo().getEquippable())
{
if (item->isEquipped())
- mBrowserBox->addRow(strprintf("@@equip|%s@@", _("Unequip")));
+ mBrowserBox->addRow(strprintf("@@unequip|%s@@", _("Unequip")));
else
mBrowserBox->addRow(strprintf("@@equip|%s@@", _("Equip")));
}
if (item->getInfo().getActivatable())
mBrowserBox->addRow(strprintf("@@activate|%s@@", _("Activate")));
- if (item->getQuantity() > 1)
- mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
- else
- mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));
+ if (canDrop)
+ {
+ if (item->getQuantity() > 1)
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop...")));
+ else
+ mBrowserBox->addRow(strprintf("@@drop|%s@@", _("Drop")));
+ }
if (Net::getInventoryHandler()->canSplit(item))
{
diff --git a/src/gui/popupmenu.h b/src/gui/popupmenu.h
index 969c5c20..111f94ed 100644
--- a/src/gui/popupmenu.h
+++ b/src/gui/popupmenu.h
@@ -55,7 +55,7 @@ class PopupMenu : public Popup, public LinkHandler
* at the specified mouse coordinates.
*/
void showPopup(Window *parent, int x, int y, Item *item,
- bool isInventory);
+ bool isInventory, bool canDrop = true);
/**
* Handles link action.
diff --git a/src/gui/quitdialog.cpp b/src/gui/quitdialog.cpp
index 6ecc62a6..3da07206 100644
--- a/src/gui/quitdialog.cpp
+++ b/src/gui/quitdialog.cpp
@@ -44,7 +44,9 @@ QuitDialog::QuitDialog(QuitDialog** pointerToMe):
mSwitchAccountServer = new RadioButton(_("Switch server"), "quitdialog");
mSwitchCharacter = new RadioButton(_("Switch character"), "quitdialog");
mOkButton = new Button(_("OK"), "ok", this);
+ mOkButton->setButtonIcon("button-icon-confirm.png");
mCancelButton = new Button(_("Cancel"), "cancel", this);
+ mCancelButton->setButtonIcon("button-icon-cancel.png");
addKeyListener(this);
diff --git a/src/gui/quitdialog.h b/src/gui/quitdialog.h
index 65a325b8..21fe2f8a 100644
--- a/src/gui/quitdialog.h
+++ b/src/gui/quitdialog.h
@@ -29,6 +29,8 @@
#include <vector>
+class Button;
+
/**
* The quit dialog.
*
@@ -62,8 +64,8 @@ class QuitDialog : public Window, public gcn::ActionListener,
gcn::RadioButton *mForceQuit;
gcn::RadioButton *mSwitchAccountServer;
gcn::RadioButton *mSwitchCharacter;
- gcn::Button *mOkButton;
- gcn::Button *mCancelButton;
+ Button *mOkButton;
+ Button *mCancelButton;
QuitDialog **mMyPointer;
};
diff --git a/src/gui/setup_keyboard.cpp b/src/gui/setup_keyboard.cpp
index d3ee3937..b8ce3e89 100644
--- a/src/gui/setup_keyboard.cpp
+++ b/src/gui/setup_keyboard.cpp
@@ -22,6 +22,7 @@
#include "gui/setup_keyboard.h"
+#include "game.h"
#include "keyboardconfig.h"
#include "gui/gui.h"
@@ -190,6 +191,8 @@ void Setup_Keyboard::refreshAssignedKey(int index)
}
mKeyListModel->setElementAt(index, caption);
+ if (Game *game = Game::instance())
+ game->updateWindowMenuCaptions();
}
void Setup_Keyboard::newKeyCallback(int index)
diff --git a/src/gui/specialswindow.h b/src/gui/specialswindow.h
index dedeeffc..b440ce13 100644
--- a/src/gui/specialswindow.h
+++ b/src/gui/specialswindow.h
@@ -51,6 +51,9 @@ class SpecialsWindow : public Window, public gcn::ActionListener {
void draw(gcn::Graphics *graphics);
+ bool hasSpecials()
+ { return !mEntries.empty(); }
+
private:
// (re)constructs the list of specials
void rebuild(const std::map<int, Special> &specialData);
diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp
index 0353fd44..945be7de 100644
--- a/src/gui/viewport.cpp
+++ b/src/gui/viewport.cpp
@@ -530,9 +530,9 @@ void Viewport::mouseReleased(gcn::MouseEvent &event)
}
void Viewport::showPopup(Window *parent, int x, int y, Item *item,
- bool isInventory)
+ bool isInventory, bool canDrop)
{
- mPopupMenu->showPopup(parent, x, y, item, isInventory);
+ mPopupMenu->showPopup(parent, x, y, item, isInventory, canDrop);
}
void Viewport::closePopupMenu()
diff --git a/src/gui/viewport.h b/src/gui/viewport.h
index 5814f08e..e5fb92b0 100644
--- a/src/gui/viewport.h
+++ b/src/gui/viewport.h
@@ -106,7 +106,7 @@ class Viewport : public WindowContainer, public gcn::MouseListener,
* TODO Find some way to get rid of Item here
*/
void showPopup(Window *parent, int x, int y, Item *item,
- bool isInventory = true);
+ bool isInventory = true, bool canDrop = true);
/**
* Closes the popup menu. Needed for when the player dies or switching
diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp
index f072ef61..61ec28a2 100644
--- a/src/gui/widgets/button.cpp
+++ b/src/gui/widgets/button.cpp
@@ -25,6 +25,7 @@
#include "graphics.h"
#include "gui/palette.h"
+#include "gui/textpopup.h"
#include "resources/image.h"
#include "resources/theme.h"
@@ -36,7 +37,8 @@
int Button::mInstances = 0;
float Button::mAlpha = 1.0;
-ImageRect *Button::mButton;
+ImageRect* Button::mButton;
+TextPopup* Button::mTextPopup = 0;
enum{
BUTTON_STANDARD, // 0
@@ -81,23 +83,26 @@ Button::Button(const std::string &caption, const std::string &actionEventId,
adjustSize();
}
-void Button::setButtonIcon(const std::string& iconFile, int frameHeight,
- int frameWidth)
+bool Button::setButtonIcon(const std::string& iconFile)
{
// We clean up possible older references.
if (mButtonIcon)
removeButtonIcon();
// If nothing relevant was set, we can quit now.
- if (iconFile.empty() || !frameWidth || !frameHeight)
- return;
+ if (iconFile.empty())
+ return false;
// Load the icon frames.
Image *btnIcons = Theme::getImageFromTheme(iconFile);
if (!btnIcons)
- return;
+ return false;
- if (btnIcons->getWidth() > 0 && btnIcons->getHeight() > 0)
+ // Compute the sub images size.
+ const int frameWidth = btnIcons->getWidth() / 4;
+ const int frameHeight = btnIcons->getHeight();
+
+ if (frameWidth > 0 && frameHeight > 0)
{
mButtonIcon = new Image*[BUTTON_COUNT];
for (int mode = 0; mode < BUTTON_COUNT; ++mode)
@@ -110,6 +115,7 @@ void Button::setButtonIcon(const std::string& iconFile, int frameHeight,
}
btnIcons->decRef();
+ return (mButtonIcon);
}
void Button::removeButtonIcon()
@@ -159,6 +165,10 @@ void Button::init()
btn[mode]->decRef();
}
updateAlpha();
+
+ // Load the popup
+ if (!mTextPopup)
+ mTextPopup = new TextPopup();
}
mInstances++;
}
@@ -175,6 +185,9 @@ Button::~Button()
dtor<Image*>());
}
delete[] mButton;
+
+ // Remove the popup
+ delete mTextPopup;
}
removeButtonIcon();
}
@@ -301,3 +314,50 @@ void Button::setCaption(const std::string& caption)
mCaption = caption;
adjustSize();
}
+
+void Button::logic()
+{
+ gcn::Button::logic();
+ mTextPopup->logic();
+}
+
+void Button::mouseMoved(gcn::MouseEvent &event)
+{
+ gcn::Button::mouseMoved(event);
+ mTextPopup->mouseMoved(event);
+
+ int x = event.getX();
+ int y = event.getY();
+
+ if (event.getSource() == this && !mPopupText.empty())
+ {
+ if (mParent)
+ {
+ x += mParent->getX();
+ y += mParent->getY();
+ }
+
+ mTextPopup->show(x + getX(), y + getY(), mPopupText);
+ }
+ else
+ {
+ mTextPopup->setVisible(false);
+ }
+}
+
+void Button::mouseExited(gcn::MouseEvent &event)
+{
+ gcn::Button::mouseExited(event);
+ mTextPopup->mouseExited(event);
+
+ mTextPopup->setVisible(false);
+}
+
+void Button::setButtonPopupText(const std::string& text)
+{
+ mPopupText = text;
+ if (!mPopupText.empty())
+ mTextPopup->show(getX(), getY(), mPopupText);
+ else
+ mTextPopup->setVisible(false);
+}
diff --git a/src/gui/widgets/button.h b/src/gui/widgets/button.h
index 6d8f773c..7463d2ad 100644
--- a/src/gui/widgets/button.h
+++ b/src/gui/widgets/button.h
@@ -26,6 +26,7 @@
class ImageRect;
class Image;
+class TextPopup;
/**
* Button widget. Same as the Guichan button but with custom look.
@@ -71,8 +72,18 @@ class Button : public gcn::Button
* Standard, Highlighted, Pressed, and Disabled.
* If the image is too short, the missing states won't be loaded.
*/
- void setButtonIcon(const std::string& iconFile = std::string(),
- int frameHeight = 0, int frameWidth = 0);
+ bool setButtonIcon(const std::string& iconFile = std::string());
+
+ /**
+ * Set the button popup text when hovering it for a few seconds.
+ *
+ * @note: An empty text will disable the popup.
+ */
+ void setButtonPopupText(const std::string& text = std::string());
+
+ void logic();
+ void mouseMoved(gcn::MouseEvent &event);
+ void mouseExited(gcn::MouseEvent &event);
private:
void init();
@@ -84,6 +95,9 @@ class Button : public gcn::Button
static float mAlpha;
Image** mButtonIcon; /**< Button Icons graphics */
+
+ static TextPopup* mTextPopup; /**< The buttons popup */
+ std::string mPopupText; /**< the current button text */
};
#endif
diff --git a/src/gui/widgets/tabbedarea.cpp b/src/gui/widgets/tabbedarea.cpp
index 412d3ddc..9b51b311 100644
--- a/src/gui/widgets/tabbedarea.cpp
+++ b/src/gui/widgets/tabbedarea.cpp
@@ -33,8 +33,12 @@ TabbedArea::TabbedArea() : gcn::TabbedArea(),
mWidgetContainer->setOpaque(false);
addWidgetListener(this);
- mArrowButton[0] = new Button("<", "shift_left", this);
- mArrowButton[1] = new Button(">", "shift_right", this);
+ mArrowButton[0] = new Button("", "shift_left", this);
+ mArrowButton[1] = new Button("", "shift_right", this);
+ if (!mArrowButton[0]->setButtonIcon("tab_arrows_left.png"))
+ mArrowButton[0]->setCaption("<");
+ if (!mArrowButton[1]->setButtonIcon("tab_arrows_right.png"))
+ mArrowButton[1]->setCaption(">");
add(mArrowButton[0]);
add(mArrowButton[1]);
diff --git a/src/gui/widgets/tabbedarea.h b/src/gui/widgets/tabbedarea.h
index d364eac5..cb29a756 100644
--- a/src/gui/widgets/tabbedarea.h
+++ b/src/gui/widgets/tabbedarea.h
@@ -117,7 +117,7 @@ class TabbedArea : public gcn::TabbedArea, public gcn::WidgetListener
typedef std::vector< std::pair<gcn::Tab*, gcn::Widget*> > TabContainer;
/** The tab arrows */
- gcn::Button *mArrowButton[2];
+ Button *mArrowButton[2];
/** Check whether the arrow should be clickable */
void updateArrowEnableState();
diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp
index 542ab4a0..2cac55b5 100644
--- a/src/gui/windowmenu.cpp
+++ b/src/gui/windowmenu.cpp
@@ -50,20 +50,27 @@ WindowMenu::WindowMenu():
{
int x = 0, h = 0;
- addButton(":-)", x, h);
- addButton(N_("Status"), x, h);
- addButton(N_("Equipment"), x, h);
- addButton(N_("Inventory"), x, h);
+ addButton(":-)", x, h, "button-icon-smilies.png");
+ addButton(N_("Status"), x, h, "button-icon-status.png",
+ KeyboardConfig::KEY_WINDOW_STATUS);
+ addButton(N_("Inventory"), x, h, "button-icon-inventory.png",
+ KeyboardConfig::KEY_WINDOW_INVENTORY);
+ addButton(N_("Equipment"), x, h, "button-icon-equipment.png",
+ KeyboardConfig::KEY_WINDOW_EQUIPMENT);
if (skillDialog->hasSkills())
- addButton(N_("Skills"), x, h);
+ addButton(N_("Skills"), x, h, "button-icon-skills.png",
+ KeyboardConfig::KEY_WINDOW_SKILL);
- // if (specialsWindow->hasSpecials())
- addButton(N_("Specials"), x, h);
+ if (specialsWindow->hasSpecials())
+ addButton(N_("Specials"), x, h, "button-icon-specials.png");
- addButton(N_("Social"), x, h);
- addButton(N_("Shortcut"), x, h);
- addButton(N_("Setup"), x, h);
+ addButton(N_("Social"), x, h, "button-icon-social.png",
+ KeyboardConfig::KEY_WINDOW_SOCIAL);
+ addButton(N_("Shortcuts"), x, h, "button-icon-shortcut.png",
+ KeyboardConfig::KEY_WINDOW_SHORTCUT);
+ addButton(N_("Setup"), x, h, "button-icon-setup.png",
+ KeyboardConfig::KEY_WINDOW_SETUP);
setDimension(gcn::Rectangle(graphics->getWidth() - x - 3, 3,
x - 3, h));
@@ -124,7 +131,7 @@ void WindowMenu::action(const gcn::ActionEvent &event)
{
window = socialWindow;
}
- else if (event.getId() == "Shortcut")
+ else if (event.getId() == "Shortcuts")
{
window = itemShortcutWindow;
}
@@ -156,11 +163,84 @@ void WindowMenu::valueChanged(const gcn::SelectionEvent &event)
}
}
-void WindowMenu::addButton(const char* text, int &x, int &h)
+static std::string createShortcutCaption(const std::string& text,
+ KeyboardConfig::KeyAction key)
{
- gcn::Button *btn = new Button(gettext(text), text, this);
+ std::string caption = gettext(text.c_str());
+ if (key != KeyboardConfig::KEY_NO_VALUE)
+ {
+ caption += " (";
+ caption += SDL_GetKeyName((SDLKey) keyboard.getKeyValue(key));
+ caption += ")";
+ }
+ return caption;
+}
+
+void WindowMenu::addButton(const std::string& text, int &x, int &h,
+ const std::string& iconPath,
+ KeyboardConfig::KeyAction key)
+{
+ Button *btn = new Button("", text, this);
+ if (!iconPath.empty() && btn->setButtonIcon(iconPath))
+ {
+ // When in image button mode, we have room to show
+ // the keyboard shortcut.
+ btn->setButtonPopupText(createShortcutCaption(text, key));
+ }
+ else
+ {
+ btn->setCaption(gettext(text.c_str()));
+ }
+
btn->setPosition(x, 0);
add(btn);
x += btn->getWidth() + 3;
- h = btn->getHeight();
+ h = std::max(h, btn->getHeight());
+}
+
+void WindowMenu::updatePopUpCaptions()
+{
+ for (WidgetList::iterator it = mWidgets.begin(); it != mWidgets.end(); ++it)
+ {
+ Button *button = dynamic_cast<Button*> (*it);
+ if (button)
+ {
+ std::string eventId = button->getActionEventId();
+ if (eventId == "Status")
+ {
+ button->setButtonPopupText(createShortcutCaption("Status",
+ KeyboardConfig::KEY_WINDOW_STATUS));
+ }
+ else if (eventId == "Equipment")
+ {
+ button->setButtonPopupText(createShortcutCaption("Equipment",
+ KeyboardConfig::KEY_WINDOW_EQUIPMENT));
+ }
+ else if (eventId == "Inventory")
+ {
+ button->setButtonPopupText(createShortcutCaption("Inventory",
+ KeyboardConfig::KEY_WINDOW_INVENTORY));
+ }
+ else if (eventId == "Skills")
+ {
+ button->setButtonPopupText(createShortcutCaption("Skills",
+ KeyboardConfig::KEY_WINDOW_SKILL));
+ }
+ else if (eventId == "Social")
+ {
+ button->setButtonPopupText(createShortcutCaption("Social",
+ KeyboardConfig::KEY_WINDOW_SOCIAL));
+ }
+ else if (eventId == "Shortcuts")
+ {
+ button->setButtonPopupText(createShortcutCaption("Shortcuts",
+ KeyboardConfig::KEY_WINDOW_SHORTCUT));
+ }
+ else if (eventId == "Setup")
+ {
+ button->setButtonPopupText(createShortcutCaption("Setup",
+ KeyboardConfig::KEY_WINDOW_SETUP));
+ }
+ }
+ }
}
diff --git a/src/gui/windowmenu.h b/src/gui/windowmenu.h
index 2bb3e764..962abaf5 100644
--- a/src/gui/windowmenu.h
+++ b/src/gui/windowmenu.h
@@ -22,6 +22,8 @@
#ifndef WINDOWMENU_H
#define WINDOWMENU_H
+#include "keyboardconfig.h"
+
#include "gui/widgets/container.h"
#include <guichan/actionlistener.hpp>
@@ -47,8 +49,16 @@ class WindowMenu : public Container,
void valueChanged(const gcn::SelectionEvent &event);
+ /**
+ * Update the pop-up captions with new key shortcuts.
+ */
+ void updatePopUpCaptions();
+
private:
- inline void addButton(const char* text, int &x, int &h);
+ inline void addButton(const std::string& text, int &x, int &h,
+ const std::string& iconPath = std::string(),
+ KeyboardConfig::KeyAction key =
+ KeyboardConfig::KEY_NO_VALUE);
EmotePopup *mEmotePopup;
};
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
index 1a7f20e1..78395438 100644
--- a/src/localplayer.cpp
+++ b/src/localplayer.cpp
@@ -58,11 +58,6 @@
#include <cassert>
-// This is the minimal delay between to permitted
-// setDestination() calls using the keyboard.
-// TODO: This can fine tuned later on when running is added...
-const short walkingKeyboardDelay = 1000;
-
#define AWAY_LIMIT_TIMER 60
LocalPlayer *player_node = NULL;
@@ -79,6 +74,7 @@ LocalPlayer::LocalPlayer(int id, int subtype):
mWalkingDir(0),
mPathSetByMouse(false),
mLocalWalkTime(-1),
+ mKeyboardMoveDelay(500),
mMessageTime(0),
mShowIp(false),
mAwayDialog(0),
@@ -741,8 +737,9 @@ void LocalPlayer::setWalkingDir(int dir)
// If the delay to send another walk message to the server hasn't expired,
// don't do anything or we could get disconnected for spamming the server
- if (get_elapsed_time(mLocalWalkTime) < walkingKeyboardDelay)
+ if (get_elapsed_time(mLocalWalkTime) < mKeyboardMoveDelay)
return;
+ mLocalWalkTime = tick_time;
mWalkingDir = dir;
@@ -808,6 +805,12 @@ void LocalPlayer::stopWalking(bool sendToServer)
clearPath();
}
+void LocalPlayer::setMoveSpeed(const Vector& speed)
+{
+ Being::setMoveSpeed(speed);
+ mKeyboardMoveDelay = Net::getPlayerHandler()->getKeyboardMoveDelay(speed);
+}
+
void LocalPlayer::toggleSit()
{
if (mLastAction != -1)
diff --git a/src/localplayer.h b/src/localplayer.h
index ab309d8f..ae27e51e 100644
--- a/src/localplayer.h
+++ b/src/localplayer.h
@@ -147,6 +147,8 @@ class LocalPlayer : public Being
*/
void setTarget(Being *target);
+ void setMoveSpeed(const Vector &speed);
+
/**
* Sets a new destination for this being to walk to.
*/
@@ -253,6 +255,13 @@ class LocalPlayer : public Being
int mLocalWalkTime; /**< Timestamp used to control keyboard walk
messages flooding */
+ /**
+ * The delay between two permitted setDestination() call using
+ * the keyboard.
+ * It's set in milliseconds per tile.
+ */
+ int mKeyboardMoveDelay;
+
typedef std::pair<std::string, int> MessagePair;
/** Queued messages*/
std::list<MessagePair> mMessages;
diff --git a/src/net/inventoryhandler.h b/src/net/inventoryhandler.h
index 93b56a40..83ef91a7 100644
--- a/src/net/inventoryhandler.h
+++ b/src/net/inventoryhandler.h
@@ -24,11 +24,27 @@
#include "inventory.h"
#include "item.h"
+#include "position.h"
#include <iosfwd>
namespace Net {
+// Default positions of the boxes, 2nd dimension is X and Y respectively.
+const int fallBackBoxesPosition[][2] = {
+ { 90, 40 }, // EQUIP_TORSO_SLOT
+ { 8, 78 }, // EQUIP_GLOVES_SLOT
+ { 70, 0 }, // EQUIP_HEAD_SLOT
+ { 50, 208 }, // EQUIP_LEGS_SLOT
+ { 90, 208 }, // EQUIP_FEET_SLOT
+ { 8, 168 }, // EQUIP_RING1_SLOT
+ { 129, 168 }, // EQUIP_RING2_SLOT
+ { 50, 40 }, // EQUIP_NECK_SLOT
+ { 8, 123 }, // EQUIP_FIGHT1_SLOT
+ { 129, 123 }, // EQUIP_FIGHT2_SLOT
+ { 129, 78 } // EQUIP_PROJECTILE_SLOT
+};
+
class InventoryHandler
{
public:
@@ -38,6 +54,22 @@ class InventoryHandler
// TODO: fix/remove me
virtual size_t getSize(int type) const = 0;
+
+ virtual bool isWeaponSlot(unsigned int slotTypeId) const = 0;
+
+ virtual bool isAmmoSlot(unsigned int slotTypeId) const = 0;
+
+ virtual unsigned int getVisibleSlotsNumber() const
+ { return 0; }
+
+ virtual Position getBoxPosition(unsigned int slotIndex) const
+ {
+ if (slotIndex < (sizeof(fallBackBoxesPosition)
+ / sizeof(fallBackBoxesPosition[0][0])))
+ return Position(fallBackBoxesPosition[slotIndex][0],
+ fallBackBoxesPosition[slotIndex][1]);
+ return Position(0,0);
+ }
};
} // namespace Net
diff --git a/src/net/manaserv/beinghandler.cpp b/src/net/manaserv/beinghandler.cpp
index 4d45da8a..44c3d77b 100644
--- a/src/net/manaserv/beinghandler.cpp
+++ b/src/net/manaserv/beinghandler.cpp
@@ -31,8 +31,10 @@
#include "gui/okdialog.h"
+#include "net/net.h"
#include "net/messagein.h"
+#include "net/manaserv/inventoryhandler.h"
#include "net/manaserv/playerhandler.h"
#include "net/manaserv/manaserv_protocol.h"
@@ -93,29 +95,17 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
static void handleLooks(Being *being, Net::MessageIn &msg)
{
- // Order of sent slots. Has to be in sync with the server code.
- static int const nb_slots = 4;
- static int const slots[nb_slots] =
- { SPRITE_WEAPON, SPRITE_HAT, SPRITE_TOPCLOTHES,
- SPRITE_BOTTOMCLOTHES };
+ int lookChanges = msg.readInt8();
- int mask = msg.readInt8();
-
- if (mask & (1 << 7))
- {
- // The equipment has to be cleared first.
- for (int i = 0; i < nb_slots; ++i)
- {
- being->setSprite(slots[i], 0);
- }
- }
+ if (lookChanges <= 0)
+ return;
- // Fill slots enumerated by the bitmask.
- for (int i = 0; i < nb_slots; ++i)
+ while (lookChanges-- > 0)
{
- if (!(mask & (1 << i))) continue;
- int id = msg.readInt16();
- being->setSprite(slots[i], id,"", (slots[i] == SPRITE_WEAPON));
+ unsigned int slotTypeId = msg.readInt8();
+ being->setSprite(slotTypeId + FIXED_SPRITE_LAYER_SIZE,
+ msg.readInt16(), "",
+ Net::getInventoryHandler()->isWeaponSlot(slotTypeId));
}
}
@@ -154,7 +144,7 @@ void BeingHandler::handleBeingEnterMessage(Net::MessageIn &msg)
being->setName(name);
}
int hs = msg.readInt8(), hc = msg.readInt8();
- being->setSprite(SPRITE_HAIR, hs * -1, ColorDB::get(hc));
+ being->setSprite(SPRITE_LAYER_HAIR, hs * -1, ColorDB::get(hc));
being->setGender(msg.readInt8() == GENDER_MALE ?
GENDER_MALE : GENDER_FEMALE);
handleLooks(being, msg);
@@ -342,7 +332,7 @@ void BeingHandler::handleBeingLooksChangeMessage(Net::MessageIn &msg)
{
int style = msg.readInt16();
int color = msg.readInt16();
- being->setSprite(SPRITE_HAIR, style * -1, ColorDB::get(color));
+ being->setSprite(SPRITE_LAYER_HAIR, style * -1, ColorDB::get(color));
}
}
diff --git a/src/net/manaserv/beinghandler.h b/src/net/manaserv/beinghandler.h
index 04c766d9..4a1f9f21 100644
--- a/src/net/manaserv/beinghandler.h
+++ b/src/net/manaserv/beinghandler.h
@@ -28,6 +28,16 @@
namespace ManaServ {
+/**
+ * enum for sprites layers.
+ */
+enum SpriteLayer
+{
+ SPRITE_LAYER_BASE = 0,
+ SPRITE_LAYER_HAIR,
+ FIXED_SPRITE_LAYER_SIZE
+};
+
class BeingHandler : public MessageHandler
{
public:
diff --git a/src/net/manaserv/charhandler.cpp b/src/net/manaserv/charhandler.cpp
index 79f3b35a..b1ddc96a 100644
--- a/src/net/manaserv/charhandler.cpp
+++ b/src/net/manaserv/charhandler.cpp
@@ -34,6 +34,8 @@
#include "net/manaserv/connection.h"
#include "net/manaserv/gamehandler.h"
+#include "net/manaserv/beinghandler.h"
+#include "net/manaserv/inventoryhandler.h"
#include "net/manaserv/messagein.h"
#include "net/manaserv/messageout.h"
#include "net/manaserv/manaserv_protocol.h"
@@ -355,17 +357,19 @@ void CharHandler::switchCharacter()
unsigned int CharHandler::baseSprite() const
{
- return SPRITE_BASE;
+ return SPRITE_LAYER_BASE;
}
unsigned int CharHandler::hairSprite() const
{
- return SPRITE_HAIR;
+ return SPRITE_LAYER_HAIR;
}
unsigned int CharHandler::maxSprite() const
{
- return SPRITE_VECTOREND;
+ static unsigned int visibleSlots = FIXED_SPRITE_LAYER_SIZE
+ + Net::getInventoryHandler()->getVisibleSlotsNumber();
+ return visibleSlots;
}
void CharHandler::updateCharacters()
@@ -387,7 +391,7 @@ void CharHandler::updateCharacters()
LocalPlayer *player = character->dummy = new LocalPlayer;
player->setName(info.name);
player->setGender(info.gender);
- player->setSprite(SPRITE_HAIR, info.hairStyle * -1,
+ player->setSprite(SPRITE_LAYER_HAIR, info.hairStyle * -1,
ColorDB::get(info.hairColor));
character->data.mAttributes[LEVEL] = info.level;
character->data.mAttributes[CHAR_POINTS] = info.characterPoints;
diff --git a/src/net/manaserv/inventoryhandler.cpp b/src/net/manaserv/inventoryhandler.cpp
index c8dae1c3..67c79a17 100644
--- a/src/net/manaserv/inventoryhandler.cpp
+++ b/src/net/manaserv/inventoryhandler.cpp
@@ -29,6 +29,7 @@
#include "log.h"
#include "playerinfo.h"
+#include "gui/equipmentwindow.h"
#include "gui/inventorywindow.h"
#include "net/manaserv/connection.h"
@@ -38,88 +39,157 @@
#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
+{
+ Slots::const_iterator it = mSlots.find(slotIndex);
+ return it == mSlots.end() ? 0 : it->second.item;
+}
+
+std::string EquipBackend::getSlotName(int slotIndex) const
+{
+ Slots::const_iterator it = mSlots.find(slotIndex);
+ return it == mSlots.end() ? std::string() : it->second.name;
}
-Item *EquipBackend::getEquipment(int index) const
+void EquipBackend::triggerUnequip(int slotIndex) const
{
- if (index < 0 || (unsigned) index >= mSlots.size())
- return 0;
- return mSlots.at(index);
+ // First get the itemInstance
+ Slots::const_iterator 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 (std::vector<Item*>::iterator i = mSlots.begin(), i_end = mSlots.end();
- i != i_end; ++i)
+ for (Slots::iterator i = mSlots.begin(), i_end = mSlots.end();
+ i != i_end; ++i)
{
- if (Item *item = *i)
- item->setEquipped(false);
+ if (i->second.item)
+ {
+ delete i->second.item;
+ i->second.item = 0;
+ }
}
- mSlots.assign(mSlots.size(), 0);
+ mSlots.clear();
}
-void EquipBackend::equip(int inventorySlot, int equipSlot, int amountUsed)
+void EquipBackend::equip(int itemId, int slotTypeId, int amountUsed,
+ int itemInstance)
{
- if (equipSlot < 0 || (unsigned) equipSlot >= mSlotTypes.size())
+ if (itemInstance <= 0)
{
- logger->log("ManaServ::EquipBackend: Equipment slot out of range");
+ logger->log("ManaServ::EquipBackend: Equipment slot %i"
+ " has an invalid item instance.", slotTypeId);
return;
}
- const SlotType &slotType = mSlotTypes.at(equipSlot);
- Item *item = PlayerInfo::getInventory()->getItem(inventorySlot);
+ Slots::iterator it = mSlots.begin();
+ Slots::iterator it_end = mSlots.end();
+ bool slotTypeFound = false;
+ for (; it != it_end; ++it)
+ if (it->second.slotTypeId == (unsigned)slotTypeId)
+ slotTypeFound = true;
- if (!item)
+ if (!slotTypeFound)
{
- logger->log("ManaServ::EquipBackend: No item at index %d",
- inventorySlot);
+ logger->log("ManaServ::EquipBackend: Equipment slot %i"
+ " is not existing.", slotTypeId);
return;
}
- // Start at first index and search upwards for free slots to place the
- // item at the given inventory slot in
- int i = slotType.firstIndex;
- const int end_i = i + slotType.count;
+ if (!itemDb->exists(itemId))
+ {
+ logger->log("ManaServ::EquipBackend: No item with id %d",
+ itemId);
+ return;
+ }
- for (; i < end_i && amountUsed > 0; ++i)
+ // 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 (!mSlots.at(i))
+ // 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))
{
- mSlots[i] = item;
+ it->second.itemInstance = itemInstance;
+ it->second.item = new Item(itemId, 1, true);
--amountUsed;
-
- item->setEquipped(true);
- inventoryWindow->updateButtons();
}
}
}
-void EquipBackend::unequip(int inventorySlot)
+void EquipBackend::unequip(int itemInstance)
{
- Item *item = PlayerInfo::getInventory()->getItem(inventorySlot);
-
- if (!item)
+ Slots::iterator it = mSlots.begin();
+ Slots::iterator 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: No item at index %d",
- inventorySlot);
+ logger->log("ManaServ::EquipBackend: Equipment item instance %i"
+ " is not existing. The item couldn't be unequipped!",
+ itemInstance);
return;
}
- for (unsigned i = 0; i < mSlots.size(); ++i)
- if (mSlots.at(i) == item)
- mSlots[i] = 0;
+ for (it = mSlots.begin(); it != it_end; ++it)
+ {
+ if (it->second.itemInstance != (unsigned)itemInstance)
+ continue;
- item->setEquipped(false);
- inventoryWindow->updateButtons();
+ // 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 = 0;
+ }
+ }
}
void EquipBackend::event(Event::Channel, const Event &event)
@@ -130,38 +200,108 @@ void EquipBackend::event(Event::Channel, const Event &event)
void EquipBackend::readEquipFile()
{
- mSlots.clear();
- mSlotTypes.clear();
+ clear();
- XML::Document doc("equip.xml");
+ 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.xml!");
+ logger->log("ManaServ::EquipBackend: Error while reading "
+ EQUIP_FILE "!");
return;
}
- int slotCount = 0;
+ // The current client slot index
+ unsigned int slotIndex = 0;
+ mVisibleSlots = 0;
- for_each_xml_child_node(childNode, rootNode)
+ for_each_xml_child_node(slotNode, rootNode)
{
- if (!xmlStrEqual(childNode->name, BAD_CAST "slot"))
+ if (!xmlStrEqual(slotNode->name, BAD_CAST "slot"))
continue;
- SlotType slotType;
- slotType.name = XML::getProperty(childNode, "name", std::string());
- slotType.count = XML::getProperty(childNode, "count", 1);
- slotType.visible = XML::getBoolProperty(childNode, "visible", false);
- slotType.firstIndex = slotCount;
+ 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);
+ }
- mSlotTypes.push_back(slotType);
- slotCount += slotType.count;
+ 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));
}
+}
- mSlots.resize(slotCount);
+bool EquipBackend::isWeaponSlot(int slotTypeId) const
+{
+ for (Slots::const_iterator it = mSlots.begin(), it_end = mSlots.end();
+ it != it_end; ++it)
+ {
+ if (it->second.slotTypeId == (unsigned)slotTypeId)
+ return it->second.weaponSlot;
+ }
+ return false;
}
+bool EquipBackend::isAmmoSlot(int slotTypeId) const
+{
+ for (Slots::const_iterator it = mSlots.begin(), it_end = mSlots.end();
+ it != it_end; ++it)
+ {
+ if (it->second.slotTypeId == (unsigned)slotTypeId)
+ return it->second.ammoSlot;
+ }
+ return false;
+}
+
+Position EquipBackend::getBoxPosition(unsigned int slotIndex) const
+{
+ if (slotIndex < mBoxesPositions.size())
+ return mBoxesPositions.at(slotIndex);
+ return Position(0, 0);
+}
InventoryHandler::InventoryHandler()
{
@@ -193,13 +333,42 @@ void InventoryHandler::handleMessage(Net::MessageIn &msg)
int amount = msg.readInt16();
PlayerInfo::setInventoryItem(slot, id, amount);
}
+
+ // A map of { item instance, {slot type id, item id, amount used}}
+ std::map<int, EquipItemInfo> equipItemsInfo;
+ std::map<int, EquipItemInfo>::iterator it;
while (msg.getUnreadLength())
{
- int equipSlot = msg.readInt8();
- int inventorySlot = msg.readInt16();
+ 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++;
+ }
+ }
- mEquipBackend.equip(inventorySlot, equipSlot);
+ 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;
@@ -214,28 +383,35 @@ void InventoryHandler::handleMessage(Net::MessageIn &msg)
break;
case GPMSG_EQUIP:
- while (msg.getUnreadLength())
{
- int inventorySlot = msg.readInt16();
- int equipSlotCount = msg.readInt8();
+ int itemId = msg.readInt16();
+ int equipSlotCount = msg.readInt16();
- if (equipSlotCount == 0)
- {
- // No slots means to unequip this item
- mEquipBackend.unequip(inventorySlot);
- }
- else
+ if (equipSlotCount <= 0)
+ break;
+
+ // Otherwise equip the item in the given slots
+ while (equipSlotCount--)
{
- // Otherwise equip the item in the given slots
- while (equipSlotCount--)
- {
- unsigned int equipSlot = msg.readInt8();
- unsigned int amountUsed = msg.readInt8();
+ unsigned int parameter = msg.readInt16();
+ unsigned int amountUsed = msg.readInt16();
- mEquipBackend.equip(inventorySlot, equipSlot,
- amountUsed);
+ 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;
}
@@ -247,8 +423,9 @@ void InventoryHandler::event(Event::Channel channel,
if (channel == Event::ItemChannel)
{
Item *item = event.getItem("item");
+ int itemInstance = event.getInt("itemInstance", 0);
- if (!item)
+ if (!item && itemInstance == 0)
return;
int index = item->getInvIndex();
@@ -256,19 +433,19 @@ void InventoryHandler::event(Event::Channel channel,
if (event.getType() == Event::DoEquip)
{
MessageOut msg(PGMSG_EQUIP);
- msg.writeInt8(index);
+ msg.writeInt16(index);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoUnequip)
{
MessageOut msg(PGMSG_UNEQUIP);
- msg.writeInt8(index);
+ msg.writeInt16(itemInstance);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoUse)
{
MessageOut msg(PGMSG_USE_ITEM);
- msg.writeInt8(index);
+ msg.writeInt16(index);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoDrop)
@@ -276,8 +453,8 @@ void InventoryHandler::event(Event::Channel channel,
int amount = event.getInt("amount", 1);
MessageOut msg(PGMSG_DROP);
- msg.writeInt8(index);
- msg.writeInt8(amount);
+ msg.writeInt16(index);
+ msg.writeInt16(amount);
gameServerConnection->send(msg);
}
else if (event.getType() == Event::DoSplit)
@@ -288,9 +465,9 @@ void InventoryHandler::event(Event::Channel channel,
if (newIndex > Inventory::NO_SLOT_INDEX)
{
MessageOut msg(PGMSG_MOVE_ITEM);
- msg.writeInt8(index);
- msg.writeInt8(newIndex);
- msg.writeInt8(amount);
+ msg.writeInt16(index);
+ msg.writeInt16(newIndex);
+ msg.writeInt16(amount);
gameServerConnection->send(msg);
}
}
@@ -304,9 +481,9 @@ void InventoryHandler::event(Event::Channel channel,
return;
MessageOut msg(PGMSG_MOVE_ITEM);
- msg.writeInt8(index);
- msg.writeInt8(newIndex);
- msg.writeInt8(item->getQuantity());
+ msg.writeInt16(index);
+ msg.writeInt16(newIndex);
+ msg.writeInt16(item->getQuantity());
gameServerConnection->send(msg);
}
else
@@ -315,7 +492,8 @@ void InventoryHandler::event(Event::Channel channel,
int destination = event.getInt("destination");
int amount = event.getInt("amount", 1);*/
- // TODO
+ // TODO Support drag'n'drop to the map ground, or with other
+ // windows.
}
}
}
@@ -323,8 +501,7 @@ void InventoryHandler::event(Event::Channel channel,
bool InventoryHandler::canSplit(const Item *item)
{
- return item && !item->getInfo().getEquippable()
- && item->getQuantity() > 1;
+ return item && item->getQuantity() > 1;
}
size_t InventoryHandler::getSize(int type) const
diff --git a/src/net/manaserv/inventoryhandler.h b/src/net/manaserv/inventoryhandler.h
index 255f601c..446105ee 100644
--- a/src/net/manaserv/inventoryhandler.h
+++ b/src/net/manaserv/inventoryhandler.h
@@ -38,26 +38,82 @@ class EquipBackend : public Equipment::Backend, public EventListener
public:
EquipBackend();
- Item *getEquipment(int index) const;
+ ~EquipBackend();
+
+ Item *getEquipment(int slotIndex) const;
+ std::string getSlotName(int slotIndex) const;
void clear();
- void equip(int inventorySlot, int equipSlot, int amountUsed = 1);
- void unequip(int inventorySlot);
+ void equip(int itemId, int slotTypeId, int amountUsed = 1,
+ int itemInstance = 0);
+ void unequip(int slotTypeId);
void event(Event::Channel channel, const Event &event);
+ int getSlotNumber() const
+ { return mSlots.size(); }
+
+ unsigned int getVisibleSlotsNumber() const
+ { return mVisibleSlots; }
+
+ void triggerUnequip(int slotIndex) const;
+
+ bool isWeaponSlot(int slotTypeId) const;
+ bool isAmmoSlot(int slotTypeId) const;
+
+ Position getBoxPosition(unsigned int slotIndex) const;
+
private:
void readEquipFile();
- struct SlotType {
+ void readBoxNode(xmlNodePtr slotNode);
+
+ struct Slot {
+ Slot():
+ item(0),
+ slotTypeId(0),
+ subId(0),
+ itemInstance(0),
+ weaponSlot(false),
+ ammoSlot(false)
+ {}
+
+ // Generic info
std::string name;
- int count;
- bool visible;
- int firstIndex;
- };
- std::vector<Item*> mSlots;
- std::vector<SlotType> mSlotTypes;
+ // The Item reference, used for graphical representation
+ // and info.
+ Item *item;
+
+ // Manaserv specific info
+
+ // Used to know which (server-side) slot id it is.
+ unsigned int slotTypeId;
+ // Static part
+ // The sub id is used to know in which order the slots are
+ // when the slotType has more than one slot capacity:
+ // I.e.: capacity = 6, subId will be between 1 and 6
+ // for each slots in the map.
+ // This is used to sort the multimap along with the slot id.
+ unsigned int subId;
+
+ // This is the (per character) unique item Id, used especially when
+ // equipping the same item multiple times on the same slot type.
+ unsigned int itemInstance;
+
+ // Tell whether the slot is a weapon slot
+ bool weaponSlot;
+
+ // Tell whether the slot is an ammo slot
+ bool ammoSlot;
+ };
+
+ unsigned int mVisibleSlots;
+
+ // slot client index, slot info
+ typedef std::map<unsigned int, Slot> Slots;
+ Slots mSlots;
+ std::vector<Position> mBoxesPositions;
};
class InventoryHandler : public MessageHandler, Net::InventoryHandler,
@@ -74,6 +130,18 @@ class InventoryHandler : public MessageHandler, Net::InventoryHandler,
size_t getSize(int type) const;
+ bool isWeaponSlot(unsigned int slotTypeId) const
+ { return mEquipBackend.isWeaponSlot(slotTypeId); }
+
+ bool isAmmoSlot(unsigned int slotTypeId) const
+ { return mEquipBackend.isAmmoSlot(slotTypeId); }
+
+ unsigned int getVisibleSlotsNumber() const
+ { return mEquipBackend.getVisibleSlotsNumber(); }
+
+ Position getBoxPosition(unsigned int slotIndex) const
+ { return mEquipBackend.getBoxPosition(slotIndex); }
+
private:
EquipBackend mEquipBackend;
};
diff --git a/src/net/manaserv/manaserv_protocol.h b/src/net/manaserv/manaserv_protocol.h
index 27d7c7b8..49548132 100644
--- a/src/net/manaserv/manaserv_protocol.h
+++ b/src/net/manaserv/manaserv_protocol.h
@@ -24,7 +24,10 @@
namespace ManaServ {
-enum { PROTOCOL_VERSION = 1 };
+enum {
+ PROTOCOL_VERSION = 1,
+ SUPPORTED_DB_VERSION = 15
+};
/**
* Enumerated type for communicated messages:
@@ -91,13 +94,13 @@ enum {
GPMSG_PLAYER_MAP_CHANGE = 0x0100, // S filename, W x, W y
GPMSG_PLAYER_SERVER_CHANGE = 0x0101, // B*32 token, S game address, W game port
PGMSG_PICKUP = 0x0110, // W*2 position
- PGMSG_DROP = 0x0111, // B slot, B amount
- PGMSG_EQUIP = 0x0112, // B slot
- PGMSG_UNEQUIP = 0x0113, // B slot
- PGMSG_MOVE_ITEM = 0x0114, // B slot1, B slot2, B amount
+ PGMSG_DROP = 0x0111, // W slot, W amount
+ PGMSG_EQUIP = 0x0112, // W inventory slot
+ PGMSG_UNEQUIP = 0x0113, // W item Instance id
+ PGMSG_MOVE_ITEM = 0x0114, // W slot1, W slot2, W amount
GPMSG_INVENTORY = 0x0120, // { W slot, W item id [, W amount] (if item id is nonzero) }*
- GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount }, { B equip slot, W invy slot}*
- GPMSG_EQUIP = 0x0122, // { W Invy slot, B equip slot type count { B equip slot, B number used} }*
+ GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount }, { W equip slot type, W item id, W item instance}*
+ GPMSG_EQUIP = 0x0122, // W item Id, W equip slot type count //{ W equip slot, W capacity used, W item instance}*//<- When equipping, //{ W item instance, W 0}*//<- When unequipping
GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, D base value (in 1/256ths), D modified value (in 1/256ths)}*
GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { W skill, D exp got, D exp needed }*
GPMSG_LEVELUP = 0x0150, // W new level, W character points, W correction points
@@ -108,12 +111,12 @@ enum {
GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, W attribute
PGMSG_RESPAWN = 0x0180, // -
GPMSG_BEING_ENTER = 0x0200, // B type, W being id, B action, W*2 position, B direction
- // character: S name, B hair style, B hair color, B gender, B item bitmask, { W item id }*
+ // character: S name, B hair style, B hair color, B gender, B sprite layers changed, { B slot type, W item id }*
// monster: W type id
// npc: W type id
GPMSG_BEING_LEAVE = 0x0201, // W being id
GPMSG_ITEM_APPEAR = 0x0202, // W item id, W*2 position
- GPMSG_BEING_LOOKS_CHANGE = 0x0210, // W weapon, W hat, W top clothes, W bottom clothes
+ GPMSG_BEING_LOOKS_CHANGE = 0x0210, // B sprite layers changed, { B slot type, W item id }*
PGMSG_WALK = 0x0260, // W*2 destination
PGMSG_ACTION_CHANGE = 0x0270, // B Action
GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action
@@ -421,23 +424,6 @@ enum BeingDirection
RIGHT = 8
};
-/**
- * enum for sprites layers.
- * WARNING: Has to be in sync with the same enum in the Sprite class
- * of the client!
- */
-enum SpriteLayer
-{
- SPRITE_BASE = 0,
- SPRITE_SHOE,
- SPRITE_BOTTOMCLOTHES,
- SPRITE_TOPCLOTHES,
- SPRITE_HAIR,
- SPRITE_HAT,
- SPRITE_WEAPON,
- SPRITE_VECTOREND
-};
-
} // namespace ManaServ
#endif // MANASERV_PROTOCOL_H
diff --git a/src/net/manaserv/playerhandler.cpp b/src/net/manaserv/playerhandler.cpp
index a114da3d..d7c3dab6 100644
--- a/src/net/manaserv/playerhandler.cpp
+++ b/src/net/manaserv/playerhandler.cpp
@@ -442,4 +442,12 @@ Vector PlayerHandler::getPixelsPerTickMoveSpeed(const Vector &speed, Map *map)
return speedInTicks;
}
+int PlayerHandler::getKeyboardMoveDelay(const Vector& speed)
+{
+ int maxSpeed = std::max(speed.x, speed.y);
+ if (maxSpeed <= 0)
+ maxSpeed = 2;
+ return 1000 / maxSpeed;
+}
+
} // namespace ManaServ
diff --git a/src/net/manaserv/playerhandler.h b/src/net/manaserv/playerhandler.h
index 3e3f8aad..0edb4354 100644
--- a/src/net/manaserv/playerhandler.h
+++ b/src/net/manaserv/playerhandler.h
@@ -69,6 +69,8 @@ class PlayerHandler : public MessageHandler, public Net::PlayerHandler
Vector getPixelsPerTickMoveSpeed(const Vector &speed, Map *map = 0);
+ int getKeyboardMoveDelay(const Vector& speed);
+
bool usePixelPrecision()
{ return true; }
diff --git a/src/net/playerhandler.h b/src/net/playerhandler.h
index f9396caf..b52b6315 100644
--- a/src/net/playerhandler.h
+++ b/src/net/playerhandler.h
@@ -71,12 +71,18 @@ class PlayerHandler
virtual Vector getDefaultMoveSpeed() const = 0;
/**
- * Convert the original speed in pixel per tick for internal use.
+ * Convert the original server-dependant speed for internal use.
*/
virtual Vector getPixelsPerTickMoveSpeed(const Vector &speed,
Map *map = 0) = 0;
/**
+ * Convert the original speed into the keyboard move delay.
+ * The delay is set in milliseconds per tiles.
+ */
+ virtual int getKeyboardMoveDelay(const Vector& speed) = 0;
+
+ /**
* Tells whether the client has to use pixel paths.
* Return false when tiles-center positions only are to be used.
*/
diff --git a/src/net/tmwa/inventoryhandler.cpp b/src/net/tmwa/inventoryhandler.cpp
index ff875e69..76eb85f5 100644
--- a/src/net/tmwa/inventoryhandler.cpp
+++ b/src/net/tmwa/inventoryhandler.cpp
@@ -31,6 +31,7 @@
#include "localplayer.h"
#include "log.h"
+#include "gui/equipmentwindow.h"
#include "gui/widgets/chattab.h"
#include "net/messagein.h"
@@ -388,6 +389,10 @@ void InventoryHandler::handleMessage(Net::MessageIn &msg)
{
mEquips.setEquipment(getSlot(equipType), index);
}
+
+ // Load the equipment boxes
+ if (equipmentWindow)
+ equipmentWindow->loadEquipBoxes();
}
break;
diff --git a/src/net/tmwa/inventoryhandler.h b/src/net/tmwa/inventoryhandler.h
index 218723e6..4c29e95b 100644
--- a/src/net/tmwa/inventoryhandler.h
+++ b/src/net/tmwa/inventoryhandler.h
@@ -34,6 +34,10 @@
#include "net/tmwa/messagehandler.h"
+#include "resources/iteminfo.h"
+
+#include "utils/gettext.h"
+
#include <list>
namespace TmwAthena {
@@ -52,9 +56,40 @@ class EquipBackend : public Equipment::Backend
return PlayerInfo::getInventory()->getItem(invyIndex);
}
+ std::string getSlotName(int slotIndex) const
+ {
+ switch (slotIndex)
+ {
+ case EQUIP_TORSO_SLOT:
+ return std::string(_("Torso"));
+ case EQUIP_ARMS_SLOT:
+ return std::string(_("Arms"));
+ case EQUIP_HEAD_SLOT:
+ return std::string(_("Head"));
+ case EQUIP_LEGS_SLOT:
+ return std::string(_("Legs"));
+ case EQUIP_FEET_SLOT:
+ return std::string(_("Feet"));
+ case EQUIP_RING1_SLOT:
+ return std::string(_("Ring 1/2"));
+ case EQUIP_RING2_SLOT:
+ return std::string(_("Ring 2/2"));
+ case EQUIP_NECKLACE_SLOT:
+ return std::string(_("Necklace"));
+ case EQUIP_FIGHT1_SLOT:
+ return std::string(_("Hand 1/2"));
+ case EQUIP_FIGHT2_SLOT:
+ return std::string(_("Hand 2/2"));
+ case EQUIP_PROJECTILE_SLOT:
+ return std::string(_("Ammo"));
+ default:
+ return std::string();
+ }
+ }
+
void clear()
{
- for (int i = 0; i < EQUIPMENT_SIZE; i++)
+ for (int i = 0; i < EQUIP_VECTOR_END; i++)
{
if (mEquipment[i] != -1)
{
@@ -82,8 +117,30 @@ class EquipBackend : public Equipment::Backend
inventoryWindow->updateButtons();
}
+ void triggerUnequip(int slotIndex) const
+ {
+ Item *item = getEquipment(slotIndex);
+ if (item)
+ item->doEvent(Event::DoUnequip);
+ }
+
+ int getSlotNumber() const
+ { return EQUIP_VECTOR_END; }
+
+ // Note the slot type id is equal to the slot Index for tA.
+ bool isWeaponSlot(unsigned int slotTypeId) const
+ {
+ return (slotTypeId == EQUIP_FIGHT1_SLOT
+ || slotTypeId == EQUIP_FIGHT1_SLOT);
+ }
+
+ bool isAmmoSlot(unsigned int slotTypeId) const
+ {
+ return (slotTypeId == EQUIP_PROJECTILE_SLOT);
+ }
+
private:
- int mEquipment[EQUIPMENT_SIZE];
+ int mEquipment[EQUIP_VECTOR_END];
};
/**
@@ -129,6 +186,12 @@ class InventoryHandler : public MessageHandler, public Net::InventoryHandler,
size_t getSize(int type) const;
+ bool isWeaponSlot(unsigned int slotTypeId) const
+ { return mEquips.isWeaponSlot(slotTypeId); }
+
+ bool isAmmoSlot(unsigned int slotTypeId) const
+ { return mEquips.isAmmoSlot(slotTypeId); }
+
private:
EquipBackend mEquips;
InventoryItems mInventoryItems;
diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp
index f30baecd..dd228f11 100644
--- a/src/net/tmwa/playerhandler.cpp
+++ b/src/net/tmwa/playerhandler.cpp
@@ -22,6 +22,7 @@
#include "net/tmwa/playerhandler.h"
#include "net/tmwa/beinghandler.h"
+#include "client.h"
#include "configuration.h"
#include "game.h"
#include "localplayer.h"
@@ -661,4 +662,9 @@ Vector PlayerHandler::getPixelsPerTickMoveSpeed(const Vector &speed, Map *map)
return speedInTicks;
}
+int PlayerHandler::getKeyboardMoveDelay(const Vector& speed)
+{
+ return std::min(speed.x, speed.y) * MILLISECONDS_IN_A_TICK;
+}
+
} // namespace TmwAthena
diff --git a/src/net/tmwa/playerhandler.h b/src/net/tmwa/playerhandler.h
index 63812f47..3e22be22 100644
--- a/src/net/tmwa/playerhandler.h
+++ b/src/net/tmwa/playerhandler.h
@@ -62,6 +62,8 @@ class PlayerHandler : public MessageHandler, public Net::PlayerHandler
Vector getPixelsPerTickMoveSpeed(const Vector &speed, Map *map = 0);
+ int getKeyboardMoveDelay(const Vector& speed);
+
bool usePixelPrecision()
{ return false; }
};