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