1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2004-2009  The Mana World Development Team
4  *  Copyright (C) 2009-2010  The Mana Developers
5  *  Copyright (C) 2011-2019  The ManaPlus Developers
6  *  Copyright (C) 2019-2021  Andrei Karas
7  *
8  *  This file is part of The ManaPlus Client.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "gui/windows/tradewindow.h"
25 
26 #include "configuration.h"
27 #include "game.h"
28 
29 #include "being/localplayer.h"
30 #include "being/playerinfo.h"
31 #include "being/playerrelations.h"
32 
33 #include "enums/gui/layouttype.h"
34 
35 #include "gui/gui.h"
36 
37 #include "gui/fonts/font.h"
38 
39 #include "gui/windows/inventorywindow.h"
40 #include "gui/windows/itemamountwindow.h"
41 #include "gui/windows/setupwindow.h"
42 
43 #include "gui/widgets/button.h"
44 #include "gui/widgets/containerplacer.h"
45 #include "gui/widgets/itemcontainer.h"
46 #include "gui/widgets/label.h"
47 #include "gui/widgets/layout.h"
48 #include "gui/widgets/scrollarea.h"
49 #include "gui/widgets/textfield.h"
50 
51 #include "gui/widgets/tabs/chat/chattab.h"
52 
53 #include "resources/db/unitsdb.h"
54 
55 #include "resources/item/item.h"
56 
57 #include "net/net.h"
58 #include "net/tradehandler.h"
59 
60 #include "utils/delete2.h"
61 #include "utils/gettext.h"
62 
63 #include "debug.h"
64 
65 TradeWindow *tradeWindow = nullptr;
66 
67 // TRANSLATORS: trade window button
68 #define CAPTION_PROPOSE _("Propose trade")
69 // TRANSLATORS: trade window button
70 #define CAPTION_CONFIRMED _("Confirmed. Waiting...")
71 // TRANSLATORS: trade window button
72 #define CAPTION_ACCEPT _("Agree trade")
73 // TRANSLATORS: trade window button
74 #define CAPTION_ACCEPTED _("Agreed. Waiting...")
75 
TradeWindow()76 TradeWindow::TradeWindow() :
77     // TRANSLATORS: trade window caption
78     Window(_("Trade: You"), Modal_false, nullptr, "trade.xml"),
79     ActionListener(),
80     SelectionListener(),
81     mMyInventory(new Inventory(InventoryType::Trade, -1)),
82     mPartnerInventory(new Inventory(InventoryType::Trade, -1)),
83     mMyItemContainer(new ItemContainer(this, mMyInventory, 100000,
84         ShowEmptyRows_false, ForceQuantity_false)),
85     mPartnerItemContainer(new ItemContainer(this, mPartnerInventory, 100000,
86         ShowEmptyRows_false, ForceQuantity_false)),
87     // TRANSLATORS: trade window money label
88     mMoneyLabel(new Label(this, strprintf(_("You get %s"), ""))),
89     // TRANSLATORS: trade window button
90     mAddButton(new Button(this, _("Add"), "add", BUTTON_SKIN, this)),
91     mOkButton(new Button(this, "", "",
92         BUTTON_SKIN, this)),  // Will be filled in later
93     // TRANSLATORS: trade window money change button
94     mMoneyChangeButton(new Button(this, _("Change"), "money",
95         BUTTON_SKIN, this)),
96     mMoneyField(new TextField(this, std::string(),
97         LoseFocusOnTab_true, nullptr, std::string(), false)),
98     mAutoAddItem(nullptr),
99     mAutoAddToNick(""),
100     mGotMoney(0),
101     mGotMaxMoney(0),
102     mAutoMoney(0),
103     mAutoAddAmount(0),
104     mStatus(PROPOSING),
105     mOkOther(false),
106     mOkMe(false)
107 {
108     setWindowName("Trade");
109     setResizable(true);
110     setCloseButton(true);
111     setStickyButtonLock(true);
112     setDefaultSize(386, 180, ImagePosition::CENTER, 0, 0);
113     setMinWidth(310);
114     setMinHeight(180);
115 
116     if (setupWindow != nullptr)
117         setupWindow->registerWindowForReset(this);
118 
119     const Font *const fnt = mOkButton->getFont();
120     int width = std::max(fnt->getWidth(CAPTION_PROPOSE),
121         fnt->getWidth(CAPTION_CONFIRMED));
122     width = std::max(width, fnt->getWidth(CAPTION_ACCEPT));
123     width = std::max(width, fnt->getWidth(CAPTION_ACCEPTED));
124 
125     mOkButton->setWidth(8 + width);
126 
127     mMyItemContainer->addSelectionListener(this);
128 
129     ScrollArea *const myScroll = new ScrollArea(this, mMyItemContainer,
130         Opaque_true, "trade_background.xml");
131     myScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
132 
133     mPartnerItemContainer->addSelectionListener(this);
134 
135     ScrollArea *const partnerScroll = new ScrollArea(this,
136         mPartnerItemContainer,
137         Opaque_true, "trade_background.xml");
138     partnerScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
139 
140     // TRANSLATORS: trade window money label
141     Label *const moneyLabel2 = new Label(this, _("You give:"));
142 
143     mMoneyField->setWidth(40);
144 
145     place(1, 0, mMoneyLabel, 1, 1);
146     place(0, 1, myScroll, 1, 1).setPadding(3);
147     place(1, 1, partnerScroll, 1, 1).setPadding(3);
148     ContainerPlacer placer = getPlacer(0, 0);
149     placer(0, 0, moneyLabel2, 1, 1);
150     placer(1, 0, mMoneyField, 2, 1);
151     placer(3, 0, mMoneyChangeButton, 1, 1).setHAlign(LayoutCell::LEFT);
152     placer = getPlacer(0, 2);
153     placer(0, 0, mAddButton, 1, 1);
154     placer(1, 0, mOkButton, 1, 1);
155     Layout &layout = getLayout();
156     layout.extend(0, 2, 2, 1);
157     layout.setRowHeight(1, LayoutType::SET);
158     layout.setRowHeight(2, 0);
159     layout.setColWidth(0, LayoutType::SET);
160     layout.setColWidth(1, LayoutType::SET);
161 
162     loadWindowState();
163     enableVisibleSound(true);
164 
165     reset();
166 }
167 
~TradeWindow()168 TradeWindow::~TradeWindow()
169 {
170     delete2(mMyInventory)
171     delete2(mPartnerInventory)
172 }
173 
setMoney(const int amount)174 void TradeWindow::setMoney(const int amount)
175 {
176     if (amount < 0 || amount < mGotMaxMoney)
177     {
178         if (config.getBoolValue("securetrades"))
179         {
180             close();
181             return;
182         }
183         mMoneyLabel->setForegroundColorAll(
184             getThemeColor(ThemeColorId::WARNING, 255U),
185             getThemeColor(ThemeColorId::WARNING_OUTLINE, 255U));
186     }
187     else
188     {
189         mMoneyLabel->setForegroundColorAll(
190             getThemeColor(ThemeColorId::LABEL, 255U),
191             getThemeColor(ThemeColorId::LABEL_OUTLINE, 255U));
192         mGotMaxMoney = amount;
193     }
194 
195     mGotMoney = amount;
196     // TRANSLATORS: trade window money label
197     mMoneyLabel->setCaption(strprintf(_("You get %s"),
198         UnitsDb::formatCurrency(amount).c_str()));
199     mMoneyLabel->adjustSize();
200 }
201 
addItem(const int id,const ItemTypeT type,const bool own,const int quantity,const uint8_t refine,const ItemColor color,const Identified identified,const Damaged damaged,const Favorite favorite) const202 void TradeWindow::addItem(const int id,
203                           const ItemTypeT type,
204                           const bool own,
205                           const int quantity,
206                           const uint8_t refine,
207                           const ItemColor color,
208                           const Identified identified,
209                           const Damaged damaged,
210                           const Favorite favorite) const
211 {
212     Inventory *const inv = own ? mMyInventory : mPartnerInventory;
213     inv->addItem(id,
214         type,
215         quantity,
216         refine,
217         color,
218         identified,
219         damaged,
220         favorite,
221         Equipm_false,
222         Equipped_false);
223 }
224 
addItem2(const int id,const ItemTypeT type,const int * const cards,const ItemOptionsList * const options,const int sz,const bool own,const int quantity,const uint8_t refine,const ItemColor color,const Identified identified,const Damaged damaged,const Favorite favorite,const Equipm equipment) const225 void TradeWindow::addItem2(const int id,
226                            const ItemTypeT type,
227                            const int *const cards,
228                            const ItemOptionsList *const options,
229                            const int sz,
230                            const bool own,
231                            const int quantity,
232                            const uint8_t refine,
233                            const ItemColor color,
234                            const Identified identified,
235                            const Damaged damaged,
236                            const Favorite favorite,
237                            const Equipm equipment) const
238 {
239     Inventory *const inv = own ? mMyInventory : mPartnerInventory;
240     const int slot = inv->addItem(id,
241         type,
242         quantity,
243         refine,
244         color,
245         identified,
246         damaged,
247         favorite,
248         equipment,
249         Equipped_false);
250     if (slot >= 0)
251     {
252         inv->setCards(slot, cards, sz);
253         inv->setOptions(slot, options);
254     }
255 }
256 
changeQuantity(const int index,const bool own,const int quantity) const257 void TradeWindow::changeQuantity(const int index, const bool own,
258                                  const int quantity) const
259 {
260     Item *item;
261     if (own)
262         item = mMyInventory->getItem(index);
263     else
264         item = mPartnerInventory->getItem(index);
265     if (item != nullptr)
266         item->setQuantity(quantity);
267 }
268 
increaseQuantity(const int index,const bool own,const int quantity) const269 void TradeWindow::increaseQuantity(const int index, const bool own,
270                                    const int quantity) const
271 {
272     Item *item;
273     if (own)
274         item = mMyInventory->getItem(index);
275     else
276         item = mPartnerInventory->getItem(index);
277     if (item != nullptr)
278         item->increaseQuantity(quantity);
279 }
280 
reset()281 void TradeWindow::reset()
282 {
283     mMyInventory->clear();
284     mPartnerInventory->clear();
285     mOkOther = false;
286     mOkMe = false;
287     setMoney(0);
288     mMoneyField->setEnabled(true);
289     mMoneyField->setText("");
290     mMoneyLabel->setForegroundColorAll(
291         getThemeColor(ThemeColorId::LABEL, 255U),
292         getThemeColor(ThemeColorId::LABEL_OUTLINE, 255U));
293     mAddButton->setEnabled(true);
294     mMoneyChangeButton->setEnabled(true);
295     mGotMoney = 0;
296     mGotMaxMoney = 0;
297     setStatus(PREPARING);
298 }
299 
receivedOk(const bool own)300 void TradeWindow::receivedOk(const bool own)
301 {
302     if (own)
303         mOkMe = true;
304     else
305         mOkOther = true;
306 
307     if (mOkMe && mOkOther)
308         setStatus(ACCEPTING);
309 }
310 
completeTrade()311 void TradeWindow::completeTrade()
312 {
313     if (config.getBoolValue("tradescreenshot"))
314         Game::createScreenshot(std::string());
315     setVisible(Visible_false);
316     reset();
317 }
318 
tradeItem(const Item * const item,const int quantity,const bool check) const319 void TradeWindow::tradeItem(const Item *const item, const int quantity,
320                             const bool check) const
321 {
322     if (check && !checkItem(item))
323         return;
324 
325     tradeHandler->addItem(item, quantity);
326 }
327 
valueChanged(const SelectionEvent & event)328 void TradeWindow::valueChanged(const SelectionEvent &event)
329 {
330     if ((mMyItemContainer == nullptr) || (mPartnerItemContainer == nullptr))
331         return;
332 
333     /* If an item is selected in one container, make sure no item is selected
334      * in the other container.
335      */
336     if (event.getSource() == mMyItemContainer &&
337         (mMyItemContainer->getSelectedItem() != nullptr))
338     {
339         mPartnerItemContainer->selectNone();
340     }
341     else if (mPartnerItemContainer->getSelectedItem() != nullptr)
342     {
343         mMyItemContainer->selectNone();
344     }
345 }
346 
setStatus(const Status s)347 void TradeWindow::setStatus(const Status s)
348 {
349     if (s == mStatus)
350         return;
351     mStatus = s;
352 
353     switch (s)
354     {
355         case PREPARING:
356             mOkButton->setCaption(CAPTION_PROPOSE);
357             mOkButton->setActionEventId("ok");
358             break;
359         case PROPOSING:
360             mOkButton->setCaption(CAPTION_CONFIRMED);
361             mOkButton->setActionEventId("");
362             break;
363         case ACCEPTING:
364             mOkButton->setCaption(CAPTION_ACCEPT);
365             mOkButton->setActionEventId("trade");
366             break;
367         case ACCEPTED:
368             mOkButton->setCaption(CAPTION_ACCEPTED);
369             mOkButton->setActionEventId("");
370             break;
371         default:
372             break;
373     }
374 
375     mOkButton->setEnabled((s != PROPOSING && s != ACCEPTED));
376 }
377 
action(const ActionEvent & event)378 void TradeWindow::action(const ActionEvent &event)
379 {
380     if (inventoryWindow == nullptr)
381         return;
382 
383     Item *const item = inventoryWindow->getSelectedItem();
384     const std::string &eventId = event.getId();
385 
386     if (eventId == "add")
387     {
388         if (mStatus != PREPARING)
389             return;
390 
391         if (!inventoryWindow->isWindowVisible())
392         {
393             inventoryWindow->setVisible(Visible_true);
394             return;
395         }
396 
397         if (item == nullptr)
398             return;
399 
400         if (mMyInventory->getFreeSlot() == -1)
401             return;
402 
403         if (!checkItem(item))
404             return;
405 
406         // Choose amount of items to trade
407         ItemAmountWindow::showWindow(ItemAmountWindowUsage::TradeAdd,
408             this,
409             item,
410             0,
411             0);
412 
413         setStatus(PREPARING);
414     }
415     else if (eventId == "cancel")
416     {
417         setVisible(Visible_false);
418         reset();
419         PlayerInfo::setTrading(Trading_false);
420         tradeHandler->cancel();
421     }
422     else if (eventId == "ok")
423     {
424         mMoneyField->setEnabled(false);
425         mAddButton->setEnabled(false);
426         mMoneyChangeButton->setEnabled(false);
427         receivedOk(true);
428         setStatus(PROPOSING);
429         tradeHandler->confirm();
430     }
431     else if (eventId == "trade")
432     {
433         receivedOk(true);
434         setStatus(ACCEPTED);
435         tradeHandler->finish();
436     }
437     else if (eventId == "money")
438     {
439         if (mStatus != PREPARING)
440             return;
441 
442         int v = atoi(mMoneyField->getText().c_str());
443         const int curMoney = PlayerInfo::getAttribute(Attributes::MONEY);
444         if (v > curMoney)
445         {
446             if (localChatTab != nullptr)
447             {
448                 // TRANSLATORS: trade error
449                 localChatTab->chatLog(_("You don't have enough money."),
450                     ChatMsgType::BY_SERVER,
451                     IgnoreRecord_false,
452                     TryRemoveColors_true);
453             }
454             v = curMoney;
455         }
456         tradeHandler->setMoney(v);
457         mMoneyField->setText(strprintf("%d", v));
458     }
459 }
460 
close()461 void TradeWindow::close()
462 {
463     tradeHandler->cancel();
464     clear();
465 }
466 
clear()467 void TradeWindow::clear()
468 {
469     mAutoAddItem = nullptr;
470     mAutoAddToNick.clear();
471     mAutoMoney = 0;
472     mAutoAddAmount = 0;
473     mGotMoney = 0;
474     mGotMaxMoney = 0;
475     mMoneyLabel->setForegroundColorAll(
476         getThemeColor(ThemeColorId::LABEL, 255U),
477         getThemeColor(ThemeColorId::LABEL_OUTLINE, 255U));
478 }
479 
addAutoItem(const std::string & nick,Item * const item,const int amount)480 void TradeWindow::addAutoItem(const std::string &nick, Item* const item,
481                               const int amount)
482 {
483     mAutoAddToNick = nick;
484     mAutoAddItem = item;
485     mAutoAddAmount = amount;
486 }
487 
addAutoMoney(const std::string & nick,const int money)488 void TradeWindow::addAutoMoney(const std::string &nick, const int money)
489 {
490     mAutoAddToNick = nick;
491     mAutoMoney = money;
492 }
493 
initTrade(const std::string & nick)494 void TradeWindow::initTrade(const std::string &nick)
495 {
496     if (localPlayer == nullptr)
497         return;
498 
499     if (!mAutoAddToNick.empty() && mAutoAddToNick == nick)
500     {
501         if ((mAutoAddItem != nullptr) && (mAutoAddItem->getQuantity() != 0))
502         {
503             const Inventory *const inv = PlayerInfo::getInventory();
504             if (inv != nullptr)
505             {
506                 const Item *const item = inv->findItem(mAutoAddItem->getId(),
507                     mAutoAddItem->getColor());
508                 if (item != nullptr)
509                     tradeItem(item, mAutoAddAmount, false);
510             }
511         }
512         if (mAutoMoney != 0)
513         {
514             tradeHandler->setMoney(mAutoMoney);
515             mMoneyField->setText(strprintf("%d", mAutoMoney));
516         }
517     }
518     clear();
519     if (!playerRelations.isGoodName(nick))
520         setCaptionFont(gui->getSecureFont());
521 }
522 
checkItem(const Item * const item) const523 bool TradeWindow::checkItem(const Item *const item) const
524 {
525     if (item == nullptr)
526         return false;
527 
528     const int itemId = item->getId();
529     if (PlayerInfo::isItemProtected(itemId))
530         return false;
531     const Item *const tItem = mMyInventory->findItem(
532         itemId, item->getColor());
533 
534     if ((tItem != nullptr) && (tItem->getQuantity() > 1
535         || item->getQuantity() > 1))
536     {
537         if (localChatTab != nullptr)
538         {
539             // TRANSLATORS: trade error
540             localChatTab->chatLog(_("Failed adding item. You can not "
541                 "overlap one kind of item on the window."),
542                 ChatMsgType::BY_SERVER,
543                 IgnoreRecord_false,
544                 TryRemoveColors_true);
545         }
546         return false;
547     }
548     if (Net::getNetworkType() != ServerType::TMWATHENA &&
549         item->isEquipped() == Equipped_true)
550     {
551         if (localChatTab != nullptr)
552         {
553             localChatTab->chatLog(
554                 // TRANSLATORS: trade error
555                 _("Failed adding item. You can not trade equipped items."),
556                 ChatMsgType::BY_SERVER,
557                 IgnoreRecord_false,
558                 TryRemoveColors_true);
559         }
560         return false;
561     }
562     return true;
563 }
564 
isInpupFocused() const565 bool TradeWindow::isInpupFocused() const
566 {
567     return mMoneyField != nullptr && mMoneyField->isFocused();
568 }
569