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