diff options
Diffstat (limited to 'src')
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; @@ -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; } }; |