1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2011 by The Allacrost Project
3 //            Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 //                         All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See https://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 /** ****************************************************************************
12 *** \file    shop_sell.cpp
13 *** \author  Tyler Olsen, roots@allacrost.org
14 *** \author  Yohann Ferreira, yohann ferreira orange fr
15 *** \brief   Source file for sell interface of shop mode
16 ***
17 *** \note The contents of this file are near identical to the contents of
18 *** shop_sell.cpp. When making any changes to this file, please look to shop_sell.cpp
19 *** to see if it should have similar changes made.
20 *** ***************************************************************************/
21 
22 #include "shop_sell.h"
23 
24 #include "shop.h"
25 
26 #include "engine/audio/audio.h"
27 #include "engine/input.h"
28 #include "engine/system.h"
29 #include "engine/video/video.h"
30 
31 #include "common/global/global.h"
32 
33 using namespace vt_utils;
34 using namespace vt_audio;
35 using namespace vt_input;
36 using namespace vt_system;
37 using namespace vt_video;
38 using namespace vt_gui;
39 using namespace vt_global;
40 
41 namespace vt_shop
42 {
43 
44 namespace private_shop
45 {
46 
47 // *****************************************************************************
48 // ***** SellInterface class methods
49 // *****************************************************************************
50 
SellInterface()51 SellInterface::SellInterface() :
52     _view_mode(SHOP_VIEW_MODE_INVALID),
53     _selected_object(nullptr),
54     _sell_deal_types(0),
55     _number_categories(0),
56     _current_category(0)
57 {
58     _category_header.SetStyle(TextStyle("title24"));
59     _category_header.SetText(UTranslate("Category"));
60 
61     _name_header.SetStyle(TextStyle("title24"));
62     _name_header.SetText(UTranslate("Name"));
63 
64     _properties_header.SetDimensions(300.0f, 30.0f, 2, 1, 2, 1);
65     _properties_header.SetOptionAlignment(VIDEO_X_RIGHT, VIDEO_Y_CENTER);
66     _properties_header.SetTextStyle(TextStyle("title24"));
67     _properties_header.SetCursorState(VIDEO_CURSOR_STATE_HIDDEN);
68     _properties_header.AddOption(UTranslate("Price"));
69     _properties_header.AddOption(UTranslate("Own"));
70 
71     _selected_name.SetStyle(TextStyle("text22"));
72 
73     _selected_properties.SetOwner(ShopMode::CurrentInstance()->GetBottomWindow());
74     _selected_properties.SetPosition(480.0f, 70.0f);
75     _selected_properties.SetDimensions(300.0f, 30.0f, 2, 1, 2, 1);
76     _selected_properties.SetOptionAlignment(VIDEO_X_RIGHT, VIDEO_Y_CENTER);
77     _selected_properties.SetTextStyle(TextStyle("text22"));
78     _selected_properties.SetCursorState(VIDEO_CURSOR_STATE_HIDDEN);
79     _selected_properties.AddOption(ustring());
80     _selected_properties.AddOption(ustring());
81 }
82 
83 
84 
~SellInterface()85 SellInterface::~SellInterface()
86 {
87     for(uint32_t i = 0; i < _list_displays.size(); i++) {
88         delete _list_displays[i];
89     }
90 }
91 
92 
_UpdateAvailableSellDealTypes()93 void SellInterface::_UpdateAvailableSellDealTypes()
94 {
95     _sell_deal_types = 0;
96 
97     // Determine what types of objects the shop deals in based on the managed object list
98     std::map<uint32_t, ShopObject *>* shop_objects = ShopMode::CurrentInstance()->GetAvailableSell();
99     for(auto it = shop_objects->begin(); it != shop_objects->end(); ++it) {
100         // Key items can't be sold.
101         if (it->second->GetObject()->IsKeyItem())
102             continue;
103 
104         vt_global::GLOBAL_OBJECT object_type = it->second->GetObject()->GetObjectType();
105         switch(object_type) {
106         case GLOBAL_OBJECT_ITEM:
107             _sell_deal_types |= DEALS_ITEMS;
108             break;
109         case GLOBAL_OBJECT_WEAPON:
110             _sell_deal_types |= DEALS_WEAPONS;
111             break;
112         case GLOBAL_OBJECT_HEAD_ARMOR:
113             _sell_deal_types |= DEALS_HEAD_ARMOR;
114             break;
115         case GLOBAL_OBJECT_TORSO_ARMOR:
116             _sell_deal_types |= DEALS_TORSO_ARMOR;
117             break;
118         case GLOBAL_OBJECT_ARM_ARMOR:
119             _sell_deal_types |= DEALS_ARM_ARMOR;
120             break;
121         case GLOBAL_OBJECT_LEG_ARMOR:
122             _sell_deal_types |= DEALS_LEG_ARMOR;
123             break;
124         case GLOBAL_OBJECT_SPIRIT:
125             _sell_deal_types |= DEALS_SPIRIT;
126             break;
127         default:
128             IF_PRINT_WARNING(SHOP_DEBUG) << "unknown object type sold in shop: " << object_type << std::endl;
129             break;
130         }
131     }
132 }
133 
134 
_RefreshItemCategories()135 void SellInterface::_RefreshItemCategories()
136 {
137     // Clear the data
138     _category_icons.clear();
139     _category_names.clear();
140     ShopMedia *shop_media = ShopMode::CurrentInstance()->Media();
141     std::vector<ustring>* all_category_names = shop_media->GetAllCategoryNames();
142     std::vector<StillImage>* all_category_icons = GlobalManager->Media().GetAllItemCategoryIcons();
143 
144     // Determine which categories are used in this shop and populate the true containers with that data
145     _UpdateAvailableSellDealTypes();
146 
147     uint8_t bit_x = 0x01; // Used to do a bit-by-bit analysis of the obj_types variable
148     for(uint8_t i = 0; i < GLOBAL_OBJECT_TOTAL; i++, bit_x <<= 1) {
149         // Check whether the type is available by doing a bit-wise comparison
150         if(_sell_deal_types & bit_x) {
151             _category_names.push_back(all_category_names->at(i));
152             _category_icons.push_back(&all_category_icons->at(i));
153         }
154     }
155 
156     // If here is more than one category, add the text/icon for all wares
157     if(_category_names.size() > 1) {
158         _category_names.push_back(all_category_names->at(8));
159         _category_icons.push_back(&all_category_icons->at(8));
160     }
161 
162     _number_categories = _category_names.size();
163 }
164 
165 
_PopulateLists()166 void SellInterface::_PopulateLists()
167 {
168     // ---------- (1): Prepare object data containers and determine category index mappings
169     // Containers of object data used to populate the display lists
170     std::vector<std::vector<ShopObject *> > object_data;
171 
172     for(uint32_t i = 0; i < _number_categories; i++) {
173         object_data.push_back(std::vector<ShopObject *>());
174     }
175 
176     // Holds the index to the object_data vector where the container for a specific object type is located
177     std::vector<uint32_t> type_index(GLOBAL_OBJECT_TOTAL, 0);
178     // Used to set the appropriate data in the type_index vector
179     uint32_t next_index = 0;
180     // Used to do a bit-by-bit analysis of the deal_types variable
181     uint8_t bit_x = 0x01;
182 
183     // This loop determines where each type of object should be placed in the object_data container. For example,
184     // if the available categories in the shop are items, weapons, spirits, and all wares, the size of object_data
185     // will be four. When we go to add an object of one of these types into the object_data container, we need
186     // to know the correct index for each type of object. These indeces are stored in the type_index vector. The
187     // size of this vector is the number of object types, so it becomes simple to map each object type to its correct
188     // location in object_data.
189     for(uint8_t i = 0; i < GLOBAL_OBJECT_TOTAL; i++, bit_x <<= 1) {
190         // Check if the type is available by doing a bit-wise comparison
191         if(_sell_deal_types & bit_x) {
192             type_index[i] = next_index++;
193         }
194     }
195 
196     // Populate the object_data containers
197 
198     // Pointer to the container of all objects that are bought/sold/traded in the ship
199     std::map<uint32_t, ShopObject *>* shop_objects = ShopMode::CurrentInstance()->GetAvailableSell();
200 
201     for(auto it = shop_objects->begin(); it != shop_objects->end(); ++it) {
202         ShopObject* obj = it->second;
203 
204         // Key items are not permitted to be sold
205         if(!obj || obj->GetObject()->IsKeyItem())
206             continue;
207 
208         if(obj->GetOwnCount() > 0) {
209             switch(obj->GetObject()->GetObjectType()) {
210             case GLOBAL_OBJECT_ITEM:
211                 object_data[type_index[0]].push_back(obj);
212                 break;
213             case GLOBAL_OBJECT_WEAPON:
214                 object_data[type_index[1]].push_back(obj);
215                 break;
216             case GLOBAL_OBJECT_HEAD_ARMOR:
217                 object_data[type_index[2]].push_back(obj);
218                 break;
219             case GLOBAL_OBJECT_TORSO_ARMOR:
220                 object_data[type_index[3]].push_back(obj);
221                 break;
222             case GLOBAL_OBJECT_ARM_ARMOR:
223                 object_data[type_index[4]].push_back(obj);
224                 break;
225             case GLOBAL_OBJECT_LEG_ARMOR:
226                 object_data[type_index[5]].push_back(obj);
227                 break;
228             case GLOBAL_OBJECT_SPIRIT:
229                 object_data[type_index[6]].push_back(obj);
230                 break;
231             default:
232                 IF_PRINT_WARNING(SHOP_DEBUG) << "added object of unknown type: "
233                                              << obj->GetObject()->GetObjectType()
234                                              << std::endl;
235                 break;
236             }
237 
238             // If there is an "All Wares" category, make sure the object gets added there as well
239             if(_number_categories > 1) {
240                 object_data.back().push_back(obj);
241             }
242         }
243     }
244 
245     // ---------- (3): Create the sell displays using the object data that is now ready
246     for(uint32_t i = 0; i < object_data.size(); i++) {
247         _list_displays[i]->PopulateList(object_data[i]);
248     }
249 } // void SellInterface::_PopulateLists()
250 
251 
Reinitialize()252 void SellInterface::Reinitialize()
253 {
254     _RefreshItemCategories();
255 
256     // (Re)create the sell displays and populate them with the object data
257     for(uint32_t i = 0; i < _list_displays.size(); ++i)
258         delete _list_displays[i];
259     _list_displays.clear();
260 
261     for(uint32_t i = 0; i < _number_categories; ++i)
262         _list_displays.push_back(new SellListDisplay());
263 
264     _PopulateLists();
265 
266     // Initialize other class members and states appropriately
267     // Set the initial category to the last category that was added (this is usually "All Wares")
268     _current_category = _number_categories > 0 ? _number_categories - 1 : 0;
269 
270     // Initialize the category display with the initial category
271     if(_number_categories > 0) {
272         _category_display.ChangeCategory(_category_names[_current_category], _category_icons[_current_category]);
273         _selected_object = _list_displays[_current_category]->GetSelectedObject();
274     }
275     ChangeViewMode(SHOP_VIEW_MODE_LIST);
276 }
277 
MakeActive()278 void SellInterface::MakeActive()
279 {
280     Reinitialize();
281 
282     if(_list_displays.empty()) {
283         ShopMode::CurrentInstance()->ChangeState(SHOP_STATE_ROOT);
284         return;
285     }
286 
287     _selected_object = _list_displays[_current_category]->GetSelectedObject();
288     ShopMode::CurrentInstance()->ObjectViewer()->ChangeViewMode(_view_mode);
289     ShopMode::CurrentInstance()->ObjectViewer()->SetSelectedObject(_selected_object);
290     _category_display.ChangeViewMode(_view_mode);
291     _category_display.SetSelectedObject(_selected_object);
292 }
293 
TransactionNotification()294 void SellInterface::TransactionNotification()
295 {
296     Reinitialize();
297 }
298 
Update()299 void SellInterface::Update()
300 {
301     ShopMode* shop = ShopMode::CurrentInstance();
302 
303     if(_view_mode == SHOP_VIEW_MODE_LIST && shop->IsInputEnabled()) {
304         if((InputManager->ConfirmPress()) && (_selected_object != nullptr)) {
305             GlobalManager->Media().PlaySound("confirm");
306             ChangeViewMode(SHOP_VIEW_MODE_INFO);
307         } else if(InputManager->CancelPress()) {
308             shop->ChangeState(SHOP_STATE_ROOT);
309             GlobalManager->Media().PlaySound("cancel");
310         }
311 
312         // Swap cycles through the object categories
313         else if(InputManager->MenuPress() && (_number_categories > 1)) {
314             if(_ChangeCategory(true))
315                 shop->ObjectViewer()->SetSelectedObject(_selected_object);
316             GlobalManager->Media().PlaySound("confirm");
317         }
318 
319         // Up/down changes the selected object in the current list
320         else if(InputManager->UpPress() && (_selected_object != nullptr)) {
321             if(_ChangeSelection(false)) {
322                 shop->ObjectViewer()->SetSelectedObject(_selected_object);
323                 GlobalManager->Media().PlaySound("bump");
324             }
325         } else if(InputManager->DownPress() && (_selected_object != nullptr)) {
326             if(_ChangeSelection(true)) {
327                 shop->ObjectViewer()->SetSelectedObject(_selected_object);
328                 GlobalManager->Media().PlaySound("bump");
329             }
330         }
331     } // if (_view_mode == SHOP_VIEW_MODE_LIST)
332 
333     else if(_view_mode == SHOP_VIEW_MODE_INFO && shop->IsInputEnabled()) {
334         if(InputManager->ConfirmPress()) {
335             ChangeViewMode(SHOP_VIEW_MODE_LIST);
336             shop->ChangeState(SHOP_STATE_ROOT);
337             if (shop->CompleteTransaction()) {
338                 GlobalManager->Media().PlaySound("coins");
339             }
340             else {
341                 GlobalManager->Media().PlaySound("cancel");
342             }
343             shop->ClearOrder();
344             shop->ChangeState(SHOP_STATE_SELL);
345         } else if(InputManager->CancelPress()) {
346             ChangeViewMode(SHOP_VIEW_MODE_LIST);
347 
348             while(_list_displays[_current_category]->ChangeSellQuantity(false))
349             {}
350 
351             GlobalManager->Media().PlaySound("cancel");
352             shop->ClearOrder();
353         }
354 
355         // Left/right change the quantity of the object to sell
356         else if(InputManager->LeftPress()) {
357             if(_list_displays[_current_category]->ChangeSellQuantity(false)) {
358                 shop->ObjectViewer()->UpdateCountText();
359                 GlobalManager->Media().PlaySound("confirm");
360             } else
361                 GlobalManager->Media().PlaySound("bump");
362         } else if(InputManager->RightPress()) {
363             if(_list_displays[_current_category]->ChangeSellQuantity(true)) {
364                 shop->ObjectViewer()->UpdateCountText();
365                 GlobalManager->Media().PlaySound("confirm");
366             } else
367                 GlobalManager->Media().PlaySound("bump");
368         }
369     }
370 
371     _category_display.Update();
372     _list_displays[_current_category]->Update();
373     shop->ObjectViewer()->Update();
374 }
375 
Draw()376 void SellInterface::Draw()
377 {
378     if(_view_mode == SHOP_VIEW_MODE_LIST) {
379         VideoManager->SetDrawFlags(VIDEO_X_CENTER, VIDEO_Y_BOTTOM, 0);
380         VideoManager->Move(200.0f, 210.0f);
381         _category_header.Draw();
382 
383         VideoManager->SetDrawFlags(VIDEO_X_LEFT, 0);
384         VideoManager->MoveRelative(95.0f, 0.0f);
385         _name_header.Draw();
386 
387         _properties_header.Draw();
388 
389         _category_display.Draw();
390         _list_displays[_current_category]->Draw();
391     } else if(_view_mode == SHOP_VIEW_MODE_INFO) {
392         VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, 0);
393         VideoManager->Move(295.0f, 593.0f);
394         _name_header.Draw();
395         _properties_header.Draw();
396 
397         VideoManager->MoveRelative(0.0f, 50.0f);
398         if (!_selected_icon.GetFilename().empty())
399             _selected_icon.Draw();
400         VideoManager->MoveRelative(30.0f, 0.0f);
401         _selected_name.Draw();
402         _selected_properties.Draw();
403 
404         _category_display.Draw();
405     }
406 
407     ShopMode::CurrentInstance()->ObjectViewer()->Draw();
408 }
409 
ChangeViewMode(SHOP_VIEW_MODE new_mode)410 void SellInterface::ChangeViewMode(SHOP_VIEW_MODE new_mode)
411 {
412     if(_view_mode == new_mode)
413         return;
414 
415     if(new_mode == SHOP_VIEW_MODE_LIST) {
416         _view_mode = new_mode;
417         ShopMode::CurrentInstance()->ObjectViewer()->ChangeViewMode(_view_mode);
418         _category_display.ChangeViewMode(_view_mode);
419 
420         _properties_header.SetOwner(ShopMode::CurrentInstance()->GetMiddleWindow());
421         _properties_header.SetPosition(480.0f, 10.0f);
422     } else if(new_mode == SHOP_VIEW_MODE_INFO) {
423         _view_mode = new_mode;
424         ShopMode::CurrentInstance()->ObjectViewer()->ChangeViewMode(_view_mode);
425         _category_display.ChangeViewMode(_view_mode);
426         _category_display.SetSelectedObject(_selected_object);
427 
428         _properties_header.SetOwner(ShopMode::CurrentInstance()->GetBottomWindow());
429         _properties_header.SetPosition(480.0f, 15.0f);
430 
431         _selected_name.SetText(_selected_object->GetObject()->GetName());
432         _selected_icon = _selected_object->GetObject()->GetIconImage();
433         _selected_icon.SetDimensions(30.0f, 30.0f);
434         _selected_properties.SetOptionText(0, MakeUnicodeString(NumberToString(_selected_object->GetSellPrice())));
435         _selected_properties.SetOptionText(1, MakeUnicodeString("×" + NumberToString(_selected_object->GetOwnCount())));
436     } else {
437         IF_PRINT_WARNING(SHOP_DEBUG) << "tried to change to an invalid/unsupported view mode: "
438                                      << new_mode << std::endl;
439     }
440 }
441 
442 
_ChangeCategory(bool left_or_right)443 bool SellInterface::_ChangeCategory(bool left_or_right)
444 {
445     if(_number_categories <= 1)
446         return false;
447 
448     if(left_or_right == false) {
449         _current_category = (_current_category == 0) ?
450             (_number_categories - 1) :
451             (_current_category - 1);
452     } else {
453         _current_category = (_current_category >= (_number_categories - 1)) ?
454             0 :
455             (_current_category + 1);
456     }
457 
458     _category_display.ChangeCategory(_category_names[_current_category], _category_icons[_current_category]);
459 
460     ShopObject *last_obj = _selected_object;
461     _selected_object = _list_displays[_current_category]->GetSelectedObject();
462     return last_obj != _selected_object;
463 }
464 
465 
466 
_ChangeSelection(bool down)467 bool SellInterface::_ChangeSelection(bool down)
468 {
469     if(_current_category >= _list_displays.size())
470         return false;
471 
472     SellListDisplay *selected_list = _list_displays[_current_category];
473 
474     if(!selected_list)
475         return false;
476 
477     if(!down)
478         selected_list->InputUp();
479     else
480         selected_list->InputDown();
481 
482     ShopObject *last_obj = _selected_object;
483     _selected_object = _list_displays[_current_category]->GetSelectedObject();
484     return last_obj != _selected_object;
485 }
486 
487 // *****************************************************************************
488 // ***** SellListDisplay class methods
489 // *****************************************************************************
490 
ReconstructList()491 void SellListDisplay::ReconstructList()
492 {
493     _identify_list.ClearOptions();
494     _property_list.ClearOptions();
495 
496     for(uint32_t i = 0; i < _objects.size(); i++) {
497         ShopObject* obj = _objects[i];
498         // Add an entry with the icon image of the object (scaled down by 4x to 30x30 pixels) followed by the object name
499         if (obj->GetObject()->GetIconImage().GetFilename().empty()) {
500             _identify_list.AddOption(MakeUnicodeString("<30>") + obj->GetObject()->GetName());
501         }
502         else {
503             _identify_list.AddOption(MakeUnicodeString("<" + obj->GetObject()->GetIconImage().GetFilename() + "><30>")
504                                      + obj->GetObject()->GetName());
505             _identify_list.GetEmbeddedImage(i)->SetDimensions(30.0f, 30.0f);
506         }
507 
508         // Add an option for each object property in the order of: price, and number owned.
509         _property_list.AddOption(MakeUnicodeString(NumberToString(obj->GetSellPrice())));
510         _property_list.AddOption(MakeUnicodeString("×" + NumberToString(obj->GetOwnCount())));
511     }
512 
513     if(_objects.empty() == false) {
514         _identify_list.SetSelection(0);
515         _property_list.SetSelection(0);
516     }
517 }
518 
519 
ChangeSellQuantity(bool more,uint32_t amount)520 bool SellListDisplay::ChangeSellQuantity(bool more, uint32_t amount)
521 {
522     ShopObject *obj = GetSelectedObject();
523     if(!obj) {
524         PRINT_WARNING << "function could not perform operation because list was empty"
525                       << std::endl;
526         return false;
527     }
528 
529     // Holds the amount that the quantity will actually increase or decrease by. May be less than the
530     // amount requested if there is an limitation such as shop stock or available funds
531     uint32_t change_amount = amount;
532 
533     if(!more) {
534         // Make sure that there is at least one more count to sell and that the player has enough funds to return it
535         if(obj->GetSellCount() == 0 ||
536                 obj->GetSellPrice() > ShopMode::CurrentInstance()->GetTotalRemaining()) {
537             return false;
538         }
539 
540         // Determine if there's a sufficient count selected to decrement by the desire amount. If not, return as many as possible
541         if(amount > obj->GetSellCount()) {
542             change_amount = obj->GetSellCount();
543         }
544 
545         // Determine how many of the possible amount to sell the player can actually perform. This is necessary to check
546         // because by reducing sales revenue, its possible that marked costs exceed what the player can afford with this
547         // lost revenue.
548         int32_t total_lost_revenue = change_amount * obj->GetSellPrice();
549         int32_t total_remaining = static_cast<int32_t>(ShopMode::CurrentInstance()->GetTotalRemaining());
550         while(total_lost_revenue > total_remaining) {
551             change_amount--;
552             total_lost_revenue -= obj->GetSellPrice();
553         }
554 
555         obj->DecrementSellCount(change_amount);
556         ShopMode::CurrentInstance()->UpdateFinances(-obj->GetSellPrice() *
557                                                     change_amount);
558         return true;
559     }
560 
561     // more
562     // Make sure that there is at least one more object available to sell in the player's inventory
563     if(obj->GetSellCount() >= obj->GetOwnCount()) {
564         return false;
565     }
566 
567     // Determine if there's enough of the object in stock to sell.
568     // If not, sell as many left as possible
569     if((obj->GetOwnCount() - obj->GetSellCount()) < change_amount) {
570         change_amount = obj->GetOwnCount() - obj->GetSellCount();
571     }
572 
573     obj->IncrementSellCount(change_amount);
574 
575     ShopMode::CurrentInstance()->UpdateFinances(obj->GetSellPrice() *
576                                                 change_amount);
577     return true;
578 } // bool SellListDisplay::ChangeSellQuantity(bool more, uint32_t amount)
579 
580 } // namespace private_shop
581 
582 } // namespace vt_shop
583