/*
* The ManaPlus Client
* Copyright (C) 2007-2009 The Mana World Development Team
* Copyright (C) 2009-2010 The Mana Developers
* Copyright (C) 2011-2012 The ManaPlus Developers
*
* This file is part of The ManaPlus Client.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/outfitwindow.h"
#include "configuration.h"
#include "equipment.h"
#include "graphics.h"
#include "inventory.h"
#include "item.h"
#include "localplayer.h"
#include "logger.h"
#include "playerinfo.h"
#include "gui/chatwindow.h"
#include "gui/theme.h"
#include "gui/viewport.h"
#include "gui/widgets/button.h"
#include "gui/widgets/checkbox.h"
#include "gui/widgets/chattab.h"
#include "gui/widgets/label.h"
#include "gui/widgets/layout.h"
#include "net/inventoryhandler.h"
#include "net/net.h"
#include "resources/image.h"
#include "resources/resourcemanager.h"
#include "utils/gettext.h"
#include "utils/stringutils.h"
#include <vector>
#include "debug.h"
float OutfitWindow::mAlpha = 1.0;
OutfitWindow::OutfitWindow():
Window(_("Outfits"), false, nullptr, "outfits.xml"),
mBoxWidth(33),
mBoxHeight(33),
mGridWidth(4),
mGridHeight(3),
mItemClicked(false),
mItemMoved(nullptr),
mItemSelected(-1),
mItemColorSelected(1),
mCurrentOutfit(0),
mAwayOutfit(0)
{
setWindowName("Outfits");
setResizable(true);
setCloseButton(true);
setStickyButtonLock(true);
setDefaultSize(250, 400, 150, 230);
setMinWidth(145);
setMinHeight(220);
mBorderColor = Theme::getThemeColor(Theme::BORDER, 64);
mBackgroundColor = Theme::getThemeColor(Theme::BACKGROUND, 32);
mPreviousButton = new Button(_("<"), "previous", this);
mNextButton = new Button(_(">"), "next", this);
mCurrentLabel = new Label(strprintf(_("Outfit: %d"), 1));
mCurrentLabel->setAlignment(gcn::Graphics::CENTER);
mKeyLabel = new Label(strprintf(_("Key: %s"),
keyName(mCurrentOutfit).c_str()));
mKeyLabel->setAlignment(gcn::Graphics::CENTER);
mUnequipCheck = new CheckBox(_("Unequip first"),
serverConfig.getValueBool("OutfitUnequip0", true));
mAwayOutfitCheck = new CheckBox(_("Away outfit"),
serverConfig.getValue("OutfitAwayIndex", OUTFITS_COUNT - 1));
mUnequipCheck->setActionEventId("unequip");
mUnequipCheck->addActionListener(this);
mAwayOutfitCheck->setActionEventId("away");
mAwayOutfitCheck->addActionListener(this);
place(0, 3, mKeyLabel, 4);
place(0, 4, mPreviousButton, 1);
place(1, 4, mCurrentLabel, 2);
place(3, 4, mNextButton, 1);
place(0, 5, mUnequipCheck, 4);
place(0, 6, mAwayOutfitCheck, 4);
Layout &layout = getLayout();
layout.setRowHeight(0, Layout::AUTO_SET);
layout.setColWidth(4, Layout::CENTER);
loadWindowState();
load();
}
OutfitWindow::~OutfitWindow()
{
save();
}
void OutfitWindow::load(bool oldConfig)
{
Configuration *cfg;
if (oldConfig)
cfg = &config;
else
cfg = &serverConfig;
memset(mItems, -1, sizeof(mItems));
memset(mItemColors, 1, sizeof(mItemColors));
for (int o = 0; o < OUTFITS_COUNT; o++)
{
std::string outfit = cfg->getValue("Outfit" + toString(o), "-1");
std::string buf;
std::stringstream ss(outfit);
std::vector<int> tokens;
while (ss >> buf)
tokens.push_back(atoi(buf.c_str()));
for (int i = 0; i < static_cast<int>(tokens.size())
&& i < OUTFIT_ITEM_COUNT; i++)
{
mItems[o][i] = tokens[i];
}
outfit = cfg->getValue("OutfitColor" + toString(o), "1");
std::stringstream ss2(outfit);
tokens.clear();
while (ss2 >> buf)
tokens.push_back(atoi(buf.c_str()));
for (int i = 0; i < static_cast<int>(tokens.size())
&& i < OUTFIT_ITEM_COUNT; i++)
{
mItemColors[o][i] = tokens[i];
}
mItemsUnequip[o] = cfg->getValueBool("OutfitUnequip" + toString(o),
true);
}
mAwayOutfit = cfg->getValue("OutfitAwayIndex", OUTFITS_COUNT - 1);
if (mAwayOutfit >= OUTFITS_COUNT)
mAwayOutfit = OUTFITS_COUNT - 1;
if (mAwayOutfitCheck)
mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit);
}
void OutfitWindow::save()
{
std::string outfitStr;
std::string outfitColorsStr;
for (int o = 0; o < OUTFITS_COUNT; o++)
{
bool good = false;
for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
{
int res = mItems[o][i] ? mItems[o][i] : -1;
if (res != -1)
good = true;
outfitStr += toString(res);
if (i < OUTFIT_ITEM_COUNT - 1)
outfitStr += " ";
outfitColorsStr += toString((int)mItemColors[o][i]);
if (i < OUTFIT_ITEM_COUNT - 1)
outfitColorsStr += " ";
}
if (good)
{
serverConfig.setValue("Outfit" + toString(o), outfitStr);
serverConfig.setValue("OutfitColor" + toString(o),
outfitColorsStr);
}
else
{
serverConfig.deleteKey("Outfit" + toString(o));
serverConfig.deleteKey("OutfitColor" + toString(o));
}
if (mItemsUnequip[o])
{
serverConfig.deleteKey("OutfitUnequip" + toString(o));
}
else
{
serverConfig.setValue("OutfitUnequip" + toString(o),
mItemsUnequip[o]);
}
outfitStr = "";
outfitColorsStr = "";
}
serverConfig.setValue("OutfitAwayIndex", mAwayOutfit);
}
void OutfitWindow::action(const gcn::ActionEvent &event)
{
if (event.getId() == "next")
{
next();
}
else if (event.getId() == "previous")
{
previous();
}
else if (event.getId() == "unequip")
{
if (mCurrentOutfit < OUTFITS_COUNT)
mItemsUnequip[mCurrentOutfit] = mUnequipCheck->isSelected();
}
else if (event.getId() == "away")
{
mAwayOutfit = mCurrentOutfit;
if (!mAwayOutfitCheck->isSelected())
mAwayOutfitCheck->setSelected(true);
}
}
void OutfitWindow::wearOutfit(int outfit, bool unwearEmpty, bool select)
{
bool isEmpty = true;
Item *item;
if (outfit < 0 || outfit > OUTFITS_COUNT)
return;
for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
{
item = PlayerInfo::getInventory()->findItem(
mItems[outfit][i], mItemColors[outfit][i]);
if (item && !item->isEquipped() && item->getQuantity())
{
if (item->isEquipment())
{
Net::getInventoryHandler()->equipItem(item);
isEmpty = false;
}
}
}
if ((!isEmpty || unwearEmpty) && outfit < OUTFITS_COUNT
&& mItemsUnequip[outfit])
{
unequipNotInOutfit(outfit);
}
if (select)
{
mCurrentOutfit = outfit;
showCurrentOutfit();
}
}
void OutfitWindow::copyOutfit(int outfit)
{
copyOutfit(outfit, mCurrentOutfit);
}
void OutfitWindow::copyOutfit(int src, int dst)
{
if (src < 0 || src > OUTFITS_COUNT
|| dst < 0 || dst > OUTFITS_COUNT)
{
return;
}
for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
{
mItems[dst][i] = mItems[src][i];
}
}
void OutfitWindow::draw(gcn::Graphics *graphics)
{
Window::draw(graphics);
Graphics *g = static_cast<Graphics*>(graphics);
for (int i = 0; i < OUTFIT_ITEM_COUNT; i++)
{
const int itemX = 10 + ((i % mGridWidth) * mBoxWidth);
const int itemY = 25 + ((i / mGridWidth) * mBoxHeight);
graphics->setColor(mBorderColor);
graphics->drawRectangle(gcn::Rectangle(itemX, itemY, 32, 32));
graphics->setColor(mBackgroundColor);
graphics->fillRectangle(gcn::Rectangle(itemX, itemY, 32, 32));
if (mItems[mCurrentOutfit][i] < 0)
continue;
bool foundItem = false;
Inventory *inv = PlayerInfo::getInventory();
if (inv)
{
Item *item = inv->findItem(mItems[mCurrentOutfit][i],
mItemColors[mCurrentOutfit][i]);
if (item)
{
// Draw item icon.
Image* image = item->getImage();
if (image)
{
g->drawImage(image, itemX, itemY);
foundItem = true;
}
}
}
if (!foundItem)
{
Image *image = Item::getImage(mItems[mCurrentOutfit][i],
mItemColors[mCurrentOutfit][i]);
if (image)
g->drawImage(image, itemX, itemY);
}
}
if (mItemMoved)
{
// Draw the item image being dragged by the cursor.
Image* image = mItemMoved->getImage();
if (image)
{
const int tPosX = mCursorPosX - (image->mBounds.w / 2);
const int tPosY = mCursorPosY - (image->mBounds.h / 2);
g->drawImage(image, tPosX, tPosY);
}
}
}
void OutfitWindow::mouseDragged(gcn::MouseEvent &event)
{
if (event.getButton() == gcn::MouseEvent::LEFT)
{
if (!mItemMoved && mItemClicked)
{
const int index = getIndexFromGrid(event.getX(), event.getY());
if (index == -1)
{
Window::mouseDragged(event);
return;
}
const int itemId = mItems[mCurrentOutfit][index];
const int itemColor = mItemColors[mCurrentOutfit][index];
if (itemId < 0)
{
Window::mouseDragged(event);
return;
}
mMoved = false;
event.consume();
Inventory *inv = PlayerInfo::getInventory();
if (inv)
{
Item *item = inv->findItem(itemId, itemColor);
if (item)
mItemMoved = item;
else
mItemMoved = nullptr;
mItems[mCurrentOutfit][index] = -1;
}
}
if (mItemMoved)
{
mCursorPosX = event.getX();
mCursorPosY = event.getY();
}
}
Window::mouseDragged(event);
}
void OutfitWindow::mousePressed(gcn::MouseEvent &event)
{
const int index = getIndexFromGrid(event.getX(), event.getY());
if (index == -1)
{
if (event.getButton() == gcn::MouseEvent::RIGHT && viewport)
viewport->showOutfitsPopup();
Window::mousePressed(event);
return;
}
mMoved = false;
event.consume();
// Stores the selected item if there is one.
if (isItemSelected())
{
mItems[mCurrentOutfit][index] = mItemSelected;
mItemColors[mCurrentOutfit][index] = mItemColorSelected;
// mItemSelected = -1;
}
else if (mItems[mCurrentOutfit][index])
{
mItemClicked = true;
}
Window::mousePressed(event);
}
void OutfitWindow::mouseReleased(gcn::MouseEvent &event)
{
if (event.getButton() == gcn::MouseEvent::LEFT)
{
// if (isItemSelected())
// mItemSelected = -1;
const int index = getIndexFromGrid(event.getX(), event.getY());
if (index == -1)
{
mItemMoved = nullptr;
Window::mouseReleased(event);
return;
}
mMoved = false;
event.consume();
if (mItemMoved)
{
mItems[mCurrentOutfit][index] = mItemMoved->getId();
mItemColors[mCurrentOutfit][index] = mItemMoved->getColor();
mItemMoved = nullptr;
}
if (mItemClicked)
mItemClicked = false;
}
Window::mouseReleased(event);
}
int OutfitWindow::getIndexFromGrid(int pointX, int pointY) const
{
const gcn::Rectangle tRect = gcn::Rectangle(
10, 25, mGridWidth * mBoxWidth, mGridHeight * mBoxHeight);
if (!tRect.isPointInRect(pointX, pointY))
return -1;
const int index = (((pointY - 25) / mBoxHeight) * mGridWidth) +
(pointX - 10) / mBoxWidth;
if (index >= OUTFIT_ITEM_COUNT || index < 0)
return -1;
return index;
}
void OutfitWindow::unequipNotInOutfit(int outfit)
{
// here we think that outfit is correct index
Inventory *inventory = PlayerInfo::getInventory();
if (!inventory)
return;
for (unsigned i = 0; i < inventory->getSize(); i++)
{
if (inventory->getItem(i) && inventory->getItem(i)->isEquipped())
{
bool found = false;
for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++)
{
if (inventory->getItem(i)->getId() == mItems[outfit][f])
{
found = true;
break;
}
}
if (!found)
Net::getInventoryHandler()->unequipItem(inventory->getItem(i));
}
}
}
int OutfitWindow::keyToNumber(SDLKey key) const
{
int outfitNum = -1;
switch (key)
{
case SDLK_1:
case SDLK_2:
case SDLK_3:
case SDLK_4:
case SDLK_5:
case SDLK_6:
case SDLK_7:
case SDLK_8:
case SDLK_9:
outfitNum = key - SDLK_1;
break;
case SDLK_0:
outfitNum = 9;
break;
case SDLK_MINUS:
outfitNum = 10;
break;
case SDLK_EQUALS:
outfitNum = 11;
break;
case SDLK_BACKSPACE:
outfitNum = 12;
break;
case SDLK_INSERT:
outfitNum = 13;
break;
case SDLK_HOME:
outfitNum = 14;
break;
case SDLK_q:
outfitNum = 15;
break;
case SDLK_w:
outfitNum = 16;
break;
case SDLK_e:
outfitNum = 17;
break;
case SDLK_r:
outfitNum = 18;
break;
case SDLK_t:
outfitNum = 19;
break;
case SDLK_y:
outfitNum = 20;
break;
case SDLK_u:
outfitNum = 21;
break;
case SDLK_i:
outfitNum = 22;
break;
case SDLK_o:
outfitNum = 23;
break;
case SDLK_p:
outfitNum = 24;
break;
case SDLK_LEFTBRACKET:
outfitNum = 25;
break;
case SDLK_RIGHTBRACKET:
outfitNum = 26;
break;
case SDLK_BACKSLASH:
outfitNum = 27;
break;
case SDLK_a:
outfitNum = 28;
break;
case SDLK_s:
outfitNum = 29;
break;
case SDLK_d:
outfitNum = 30;
break;
case SDLK_f:
outfitNum = 31;
break;
case SDLK_g:
outfitNum = 32;
break;
case SDLK_h:
outfitNum = 33;
break;
case SDLK_j:
outfitNum = 34;
break;
case SDLK_k:
outfitNum = 35;
break;
case SDLK_l:
outfitNum = 36;
break;
case SDLK_SEMICOLON:
outfitNum = 37;
break;
case SDLK_QUOTE:
outfitNum = 38;
break;
case SDLK_z:
outfitNum = 39;
break;
case SDLK_x:
outfitNum = 40;
break;
case SDLK_c:
outfitNum = 41;
break;
case SDLK_v:
outfitNum = 42;
break;
case SDLK_b:
outfitNum = 43;
break;
case SDLK_n:
outfitNum = 44;
break;
case SDLK_m:
outfitNum = 45;
break;
case SDLK_COMMA:
outfitNum = 46;
break;
case SDLK_PERIOD:
outfitNum = 47;
break;
case SDLK_SLASH:
outfitNum = 48;
break;
default:
break;
}
return outfitNum;
}
SDLKey OutfitWindow::numberToKey(int number) const
{
SDLKey key = SDLK_UNKNOWN;
switch (number)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
key = static_cast<SDLKey>(
static_cast<unsigned int>(SDLK_1) + number);
break;
case 9:
key = SDLK_0;
break;
case 10:
key = SDLK_MINUS;
break;
case 11:
key = SDLK_EQUALS;
break;
case 12:
key = SDLK_BACKSPACE;
break;
case 13:
key = SDLK_INSERT;
break;
case 14:
key = SDLK_HOME;
break;
case 15:
key = SDLK_q;
break;
case 16:
key = SDLK_w;
break;
case 17:
key = SDLK_e;
break;
case 18:
key = SDLK_r;
break;
case 19:
key = SDLK_t;
break;
case 20:
key = SDLK_y;
break;
case 21:
key = SDLK_u;
break;
case 22:
key = SDLK_i;
break;
case 23:
key = SDLK_o;
break;
case 24:
key = SDLK_p;
break;
case 25:
key = SDLK_LEFTBRACKET;
break;
case 26:
key = SDLK_RIGHTBRACKET;
break;
case 27:
key = SDLK_BACKSLASH;
break;
case 28:
key = SDLK_a;
break;
case 29:
key = SDLK_s;
break;
case 30:
key = SDLK_d;
break;
case 31:
key = SDLK_f;
break;
case 32:
key = SDLK_g;
break;
case 33:
key = SDLK_h;
break;
case 34:
key = SDLK_j;
break;
case 35:
key = SDLK_k;
break;
case 36:
key = SDLK_l;
break;
case 37:
key = SDLK_SEMICOLON;
break;
case 38:
key = SDLK_QUOTE;
break;
case 39:
key = SDLK_z;
break;
case 40:
key = SDLK_x;
break;
case 41:
key = SDLK_c;
break;
case 42:
key = SDLK_v;
break;
case 43:
key = SDLK_b;
break;
case 44:
key = SDLK_n;
break;
case 45:
key = SDLK_m;
break;
case 46:
key = SDLK_COMMA;
break;
case 47:
key = SDLK_PERIOD;
break;
case 48:
key = SDLK_SLASH;
break;
default:
break;
}
return key;
}
std::string OutfitWindow::keyName(int number)
{
return SDL_GetKeyName(numberToKey(number));
}
void OutfitWindow::next()
{
if (mCurrentOutfit < (OUTFITS_COUNT - 1))
mCurrentOutfit++;
else
mCurrentOutfit = 0;
showCurrentOutfit();
}
void OutfitWindow::previous()
{
if (mCurrentOutfit > 0)
mCurrentOutfit--;
else
mCurrentOutfit = OUTFITS_COUNT - 1;
showCurrentOutfit();
}
void OutfitWindow::showCurrentOutfit()
{
mCurrentLabel->setCaption(strprintf(_("Outfit: %d"), mCurrentOutfit + 1));
mUnequipCheck->setSelected(mItemsUnequip[mCurrentOutfit]);
mKeyLabel->setCaption(strprintf(_("Key: %s"),
keyName(mCurrentOutfit).c_str()));
mAwayOutfitCheck->setSelected(mAwayOutfit == mCurrentOutfit);
}
void OutfitWindow::wearNextOutfit(bool all)
{
bool fromStart = false;
next();
if (!all && mCurrentOutfit < OUTFITS_COUNT)
{
while (!mItemsUnequip[mCurrentOutfit])
{
next();
if (mCurrentOutfit == 0)
{
if (!fromStart)
fromStart = true;
else
return;
}
}
}
wearOutfit(mCurrentOutfit);
}
void OutfitWindow::wearPreviousOutfit(bool all)
{
bool fromStart = false;
previous();
if (!all && mCurrentOutfit < OUTFITS_COUNT)
{
while (!mItemsUnequip[mCurrentOutfit])
{
previous();
if (mCurrentOutfit == 0)
{
if (!fromStart)
fromStart = true;
else
return;
}
}
}
wearOutfit(mCurrentOutfit);
}
void OutfitWindow::copyFromEquiped()
{
copyFromEquiped(mCurrentOutfit);
}
void OutfitWindow::copyFromEquiped(int dst)
{
Inventory *inventory = PlayerInfo::getInventory();
if (!inventory)
return;
int outfitCell = 0;
for (unsigned i = 0; i < inventory->getSize(); i++)
{
if (inventory->getItem(i) && inventory->getItem(i)->isEquipped())
{
mItems[dst][outfitCell] = inventory->getItem(i)->getId();
mItemColors[dst][outfitCell++] = inventory->getItem(i)->getColor();
if (outfitCell >= OUTFIT_ITEM_COUNT)
break;
}
}
}
void OutfitWindow::wearAwayOutfit()
{
copyFromEquiped(OUTFITS_COUNT);
wearOutfit(mAwayOutfit, false);
}
void OutfitWindow::unwearAwayOutfit()
{
wearOutfit(OUTFITS_COUNT);
}
void OutfitWindow::setItemSelected(Item *item)
{
if (item)
{
mItemSelected = item->getId();
mItemColorSelected = item->getColor();
}
else
{
mItemSelected = -1;
mItemColorSelected = 1;
}
}
void OutfitWindow::clearCurrentOutfit()
{
for (unsigned f = 0; f < OUTFIT_ITEM_COUNT; f++)
{
mItems[mCurrentOutfit][f] = -1;
mItemColors[mCurrentOutfit][f] = 1;
}
}