Peoplemon  0.1.0
Peoplemon 3 game source documentation
StoreMenu.cpp
Go to the documentation of this file.
2 
3 #include <BLIB/Util/Random.hpp>
4 #include <Core/Items/Item.hpp>
5 #include <Core/Properties.hpp>
6 #include <Core/Resources.hpp>
7 
8 namespace game
9 {
10 namespace state
11 {
12 namespace
13 {
14 const sf::Vector2f QtyPosition{293.f, 399.f};
15 constexpr float DingTime = 0.6f;
16 constexpr core::item::Category cats[3] = {
18 const std::string catTexts[3] = {"Regular Items", "Peopleballs", "TM's"};
19 } // namespace
20 
21 using menu::StoreItemRow;
22 
23 StoreMenu::Item::Item(core::item::Id item, int price)
24 : item(item)
25 , price(price) {}
26 
27 bl::engine::State::Ptr StoreMenu::create(core::system::Systems& systems,
28  const core::event::StoreOpened& data) {
29  return bl::engine::State::Ptr(new StoreMenu(systems, data));
30 }
31 
32 StoreMenu::StoreMenu(core::system::Systems& systems, const core::event::StoreOpened& data)
33 : State(systems, bl::engine::StateMask::Menu)
34 , menuState(MenuState::GetAction)
35 , qtyEntry(systems.engine()) {
36  bgndTxtr = systems.engine().renderer().texturePool().getOrLoadTexture(
37  bl::util::FileUtil::joinPath(core::Properties::MenuImagePath(), "store.png"));
38  background.create(systems.engine(), bgndTxtr);
39 
40  dingSound = bl::audio::AudioSystem::getOrLoadSound(
41  bl::util::FileUtil::joinPath(core::Properties::SoundPath(), "Menu/ding.wav"));
42 
43  if (data.items.empty()) {
44  BL_LOG_INFO
45  << "Received no item list, selling all regular items and Peopleballs at normal price";
46 
47  for (const core::item::Id item : core::item::Item::validIds()) {
50  items.emplace_back(item, core::item::Item::getValue(item));
51  }
52  }
53  }
54  else {
55  items.reserve(data.items.size());
56  for (const auto& item : data.items) { items.emplace_back(item.first, item.second); }
57  }
58 
59  actionMenu.create(systems.engine(),
60  systems.engine().renderer().getObserver(),
61  bl::menu::ArrowSelector::create(14.f, sf::Color::Black));
62  buyMenu.create(systems.engine(),
63  systems.engine().renderer().getObserver(),
64  bl::menu::ArrowSelector::create(8.f, sf::Color::Black));
65  for (auto& sellMenu : sellMenus) {
66  sellMenu.create(systems.engine(),
67  systems.engine().renderer().getObserver(),
68  bl::menu::ArrowSelector::create(8.f, sf::Color::Black));
69  }
70 
71  boxText.create(systems.engine(), core::Properties::MenuFont(), "", 18, sf::Color::Black);
72  boxText.wordWrap(260.f);
73  boxText.getTransform().setPosition(28.f, 408.f);
74  boxText.setParent(background);
75  moneyText.create(systems.engine(), core::Properties::MenuFont(), "", 20, sf::Color(10, 115, 0));
76  moneyText.getTransform().setPosition(605.f, 15.f);
77  moneyText.setParent(background);
78  actionText.create(systems.engine(), core::Properties::MenuFont(), "", 28, sf::Color::Black);
79  actionText.getTransform().setPosition(378.f, 67.f);
80  actionText.setParent(background);
81  catText.create(systems.engine(), core::Properties::MenuFont(), "", 26, sf::Color(0, 60, 20));
82  catText.setParent(background);
83 
84  leftArrow.create(systems.engine(), {0.f, 13.f}, {15.f, 0.f}, {15.f, 26.f});
85  rightArrow.create(systems.engine(), {0.f, 0.f}, {0.f, 26.f}, {15.f, 13.f});
86  leftArrow.setFillColor(sf::Color::Black);
87  rightArrow.setFillColor(sf::Color::Black);
88  rightArrow.getTransform().setPosition(730.f, 73.f);
89  leftArrow.setParent(background);
90  rightArrow.setParent(background);
91 
92  bl::menu::TextItem::Ptr buyItem =
93  bl::menu::TextItem::create("Buy", core::Properties::MenuFont(), sf::Color::Black, 28);
94  bl::menu::TextItem::Ptr sellItem =
95  bl::menu::TextItem::create("Sell", core::Properties::MenuFont(), sf::Color::Black, 28);
96  bl::menu::TextItem::Ptr closeItem =
97  bl::menu::TextItem::create("Close", core::Properties::MenuFont(), sf::Color::Black, 28);
98  buyItem->getSignal(bl::menu::Item::Activated)
99  .willAlwaysCall(std::bind(&StoreMenu::enterState, this, MenuState::BuyMenu));
100  sellItem->getSignal(bl::menu::Item::Activated)
101  .willAlwaysCall(std::bind(&StoreMenu::enterState, this, MenuState::SellMenu));
102  closeItem->getSignal(bl::menu::Item::Activated)
103  .willAlwaysCall(std::bind(&StoreMenu::close, this));
104  actionMenu.setRootItem(buyItem);
105  actionMenu.addItem(sellItem, buyItem.get(), bl::menu::Item::Bottom);
106  actionMenu.addItem(closeItem, sellItem.get(), bl::menu::Item::Bottom);
107  actionMenu.setSelectedItem(buyItem.get());
108  actionMenu.configureBackground(sf::Color::White, sf::Color::Black, 2.f, {18.f, 2.f, 4.f, 4.f});
109  actionMenu.setPosition({20.f, 230.f});
110 
111  buyMenu.setPosition({378.f, 140.f});
112  buyMenu.setMaximumSize({500.f, 420.f});
113  for (int i = 0; i < 3; ++i) {
114  sellMenus[i].setPosition({378.f, 140.f});
115  sellMenus[i].setMaximumSize({500.f, 420.f});
116  }
117  curCat = 0;
118 
119  for (const auto& p : data.sellPrices) { sellPrices.emplace(p.first, p.second); }
120 }
121 
122 const char* StoreMenu::name() const { return "StoreMenu"; }
123 
124 void StoreMenu::activate(bl::engine::Engine& engine) {
125  if (items.empty()) {
126  BL_LOG_ERROR << "Nothing is for sale asshole";
127  engine.popState();
128  return;
129  }
130 
131  // create scene and add to it
132  auto overlay = engine.renderer().getObserver().pushScene<bl::rc::Overlay>();
133  background.addToScene(overlay, bl::rc::UpdateSpeed::Static);
134  actionMenu.addToOverlay(background.entity());
135  buyMenu.addToOverlay(background.entity());
136  for (auto& sellMenu : sellMenus) { sellMenu.addToOverlay(background.entity()); }
137  actionText.addToScene(overlay, bl::rc::UpdateSpeed::Static);
138  boxText.addToScene(overlay, bl::rc::UpdateSpeed::Static);
139  moneyText.addToScene(overlay, bl::rc::UpdateSpeed::Static);
140  catText.addToScene(overlay, bl::rc::UpdateSpeed::Static);
141  leftArrow.addToScene(overlay, bl::rc::UpdateSpeed::Static);
142  rightArrow.addToScene(overlay, bl::rc::UpdateSpeed::Static);
143 
144  // populate buy menu
145  bl::menu::Item::Ptr root = makeItemRow(items.front().item, items.front().price, 0);
146  buyMenu.setRootItem(root);
147  bl::menu::Item* prev = root.get();
148  for (unsigned int i = 1; i < items.size(); ++i) {
149  bl::menu::Item::Ptr row = makeItemRow(items[i].item, items[i].price, i);
150  buyMenu.addItem(row, prev, bl::menu::Item::Bottom);
151  prev = row.get();
152  }
153  buyMenu.addItem(makeCloseItem(), prev, bl::menu::Item::Bottom);
154 
155  std::vector<core::player::Bag::Item> playerItems;
156  for (int i = 0; i < 3; ++i) {
157  systems.player().state().bag.getByCategory(cats[i], playerItems);
158  if (!playerItems.empty()) {
159  bl::menu::Item::Ptr root =
160  makeSellItemRow(playerItems.front().id, playerItems.front().qty);
161  sellMenus[i].setRootItem(root);
162  prev = root.get();
163  for (unsigned int j = 1; j < playerItems.size(); ++j) {
164  bl::menu::Item::Ptr row = makeSellItemRow(playerItems[j].id, playerItems[j].qty);
165  sellMenus[i].addItem(row, prev, bl::menu::Item::Bottom);
166  prev = row.get();
167  }
168  sellMenus[i].addItem(makeCloseItem(), prev, bl::menu::Item::Bottom);
169  }
170  else { sellMenus[i].setRootItem(makeCloseItem()); }
171  }
172  curCat = 0;
173  catSync();
174 
175  // general setup
176  dingTime = 0.f;
177  enterState(MenuState::GetAction);
178  setMoneyText();
179  renderMenu = &buyMenu;
180  systems.engine().inputSystem().getActor().addListener(*this);
181 
182  // impulse buy ability
183  for (const auto& ppl : systems.player().state().peoplemon) {
184  if (ppl.ability() == core::pplmn::SpecialAbility::ImpulseBuy) {
185  if (bl::util::Random::get<int>(0, 100) <= 5) {
187  int cheapestPrice = std::numeric_limits<int>::max();
188  for (const auto item : items) {
189  if (item.price < cheapestPrice) {
190  cheapestPrice = item.price;
191  cheapest = item.item;
192  }
193  }
194 
195  if (cheapestPrice <= systems.player().state().monei) {
196  systems.player().state().monei -= cheapestPrice;
197  systems.player().state().bag.addItem(cheapest);
198  enterState(MenuState::ImpulseBuyMessage);
199  setBoxText(ppl.name() + " made an Impulse Buy and purchased a " +
200  core::item::Item::getName(cheapest) + "!");
201  break;
202  }
203  }
204  }
205  }
206 }
207 
208 void StoreMenu::deactivate(bl::engine::Engine& engine) {
209  engine.renderer().getObserver().popScene();
210  systems.engine().inputSystem().getActor().removeListener(*this);
211  bl::event::Dispatcher::dispatch<core::event::StoreClosed>({});
212 }
213 
214 void StoreMenu::update(bl::engine::Engine&, float dt, float) {
215  dingTime += dt;
216 
217  switch (menuState) {
218  case MenuState::BuyDing:
219  case MenuState::SellDing:
220  case MenuState::TooPoorDing:
221  if (dingTime >= DingTime) {
222  enterState(menuState == MenuState::SellDing ? MenuState::SellMenu : MenuState::BuyMenu);
223  }
224  break;
225  case MenuState::ImpulseBuyMessage:
226  if (dingTime >= DingTime * 2.3f) { enterState(MenuState::GetAction); }
227  break;
228  default:
229  break;
230  }
231 }
232 
233 void StoreMenu::enterState(MenuState newState) {
234  menuState = newState;
235 
236  // TODO - handle visibility
237  buyMenu.setHidden(true);
238  for (auto& sellMenu : sellMenus) { sellMenu.setHidden(true); }
239  qtyEntry.hide();
240  catText.setHidden(true);
241  rightArrow.setHidden(true);
242  leftArrow.setHidden(true);
243 
244  const auto unhideSellItems = [this]() {
245  catText.setHidden(false);
246  rightArrow.setHidden(false);
247  leftArrow.setHidden(false);
248  };
249 
250  switch (newState) {
251  case MenuState::GetAction:
252  setBoxText("Would you like to buy or sell?");
253  break;
254 
255  case MenuState::BuyMenu:
256  renderMenu = &buyMenu;
257  setBoxText("What would you like to buy?");
258  actionText.getSection().setFillColor(sf::Color(20, 60, 240));
259  actionText.getSection().setString("Buying");
260  break;
261 
262  case MenuState::SellMenu:
263  renderMenu = &sellMenus[curCat];
264  setBoxText("What can I take off your hands?");
265  actionText.getSection().setFillColor(sf::Color(240, 60, 20));
266  actionText.getSection().setString("Selling");
267  unhideSellItems();
268  break;
269 
270  case MenuState::BuyQty:
271  qtyEntry.configure(0, systems.player().state().monei / items[buyingItemIndex].price, 1);
272  qtyEntry.setPosition(QtyPosition - qtyEntry.getSize());
273  setBoxText("How many would you like to buy?");
274  break;
275 
276  case MenuState::SellQty:
277  qtyEntry.configure(0, systems.player().state().bag.itemCount(sellingItem->getItem()), 1);
278  qtyEntry.setPosition(QtyPosition - qtyEntry.getSize());
279  setBoxText("How many would you like to sell?");
280  unhideSellItems();
281  break;
282 
283  case MenuState::TooPoorDing:
284  setBoxText("You're too poor!");
285  [[fallthrough]];
286 
287  case MenuState::SellDing:
288  case MenuState::BuyDing:
289  case MenuState::ImpulseBuyMessage:
290  bl::audio::AudioSystem::playOrRestartSound(dingSound);
291  dingTime = 0.f;
292  setMoneyText();
293  if (newState == MenuState::SellDing) { unhideSellItems(); }
294  break;
295 
296  default:
297  break;
298  }
299 
300  renderMenu->setHidden(false);
301 }
302 
303 void StoreMenu::setBoxText(const std::string& t) { boxText.getSection().setString(t); }
304 
305 void StoreMenu::setMoneyText() {
306  moneyText.getSection().setString("$" + std::to_string(systems.player().state().monei));
307 }
308 
309 void StoreMenu::close() { systems.engine().popState(); }
310 
311 bool StoreMenu::observe(const bl::input::Actor&, unsigned int control, bl::input::DispatchType,
312  bool eventTriggered) {
313  if (control == core::input::Control::Back) {
314  if (!eventTriggered) return true;
315 
316  switch (menuState) {
317  case MenuState::GetAction:
318  close();
319  break;
320  case MenuState::BuyMenu:
321  case MenuState::SellMenu:
322  enterState(MenuState::GetAction);
323  break;
324  case MenuState::SellQty:
325  enterState(MenuState::SellMenu);
326  break;
327  case MenuState::BuyQty:
328  enterState(MenuState::BuyMenu);
329  break;
330  default:
331  break;
332  }
333  return true;
334  }
335 
336  bl::menu::Menu* sendTo = nullptr;
337  switch (menuState) {
338  case MenuState::GetAction:
339  sendTo = &actionMenu;
340  break;
341  case MenuState::BuyMenu:
342  sendTo = &buyMenu;
343  break;
344  case MenuState::SellMenu:
345  switch (control) {
347  catRight();
348  break;
350  catLeft();
351  break;
352  default:
353  sendTo = &sellMenus[curCat];
354  break;
355  }
356  break;
357  case MenuState::BuyQty:
358  case MenuState::SellQty:
359  switch (control) {
361  qtyEntry.up(1, eventTriggered);
362  break;
364  qtyEntry.up(10, eventTriggered);
365  break;
367  qtyEntry.down(1, eventTriggered);
368  break;
370  qtyEntry.down(10, eventTriggered);
371  break;
373  if (menuState == MenuState::BuyQty) {
374  const int qty = qtyEntry.curQty();
375  if (qty > 0) {
376  systems.player().state().monei -= qty * items[buyingItemIndex].price;
377  systems.player().state().bag.addItem(items[buyingItemIndex].item, qty);
378  enterState(MenuState::BuyDing);
379  }
380  else { enterState(MenuState::BuyMenu); }
381  }
382  else {
383  const int qty = qtyEntry.curQty();
384  if (qty > 0) {
385  systems.player().state().monei += getSellPrice(sellingItem->getItem()) * qty;
386  systems.player().state().bag.removeItem(sellingItem->getItem(), qty);
387  const int newQty =
388  systems.player().state().bag.itemCount(sellingItem->getItem());
389  if (newQty > 0) { sellingItem->updateQty(newQty); }
390  else { sellMenus[curCat].removeItem(sellingItem); }
391  enterState(MenuState::SellDing);
392  }
393  else { enterState(MenuState::SellMenu); }
394  }
395  break;
396  default:
397  break;
398  }
399  break;
400  default:
401  break;
402  }
403  if (sendTo != nullptr && dingTime >= DingTime) {
404  inputDriver.drive(sendTo);
405  inputDriver.sendControl(control, eventTriggered);
406  }
407 
408  return true;
409 }
410 
411 bl::menu::Item::Ptr StoreMenu::makeItemRow(core::item::Id item, int price, unsigned int i) {
412  StoreItemRow::Ptr row = StoreItemRow::create(item, price);
413  row->getSignal(bl::menu::Item::Activated)
414  .willAlwaysCall(std::bind(&StoreMenu::selectBuyItem, this, i));
415  return row;
416 }
417 
418 bl::menu::Item::Ptr StoreMenu::makeSellItemRow(core::item::Id item, int qty) {
419  StoreItemRow::Ptr row = StoreItemRow::create(qty, item, getSellPrice(item));
420  row->getSignal(bl::menu::Item::Activated)
421  .willAlwaysCall(std::bind(&StoreMenu::selectSellItem, this, row.get()));
422  return row;
423 }
424 
425 bl::menu::Item::Ptr StoreMenu::makeCloseItem() {
426  bl::menu::TextItem::Ptr ci =
427  bl::menu::TextItem::create("Close", core::Properties::MenuFont(), sf::Color::Black, 16);
428  ci->getSignal(bl::menu::Item::Activated).willAlwaysCall(std::bind(&StoreMenu::closeMenu, this));
429  return ci;
430 }
431 
432 void StoreMenu::selectBuyItem(unsigned int i) {
433  buyingItemIndex = i;
434  enterState(systems.player().state().monei >= items[i].price ? MenuState::BuyQty :
435  MenuState::TooPoorDing);
436 }
437 
438 void StoreMenu::closeMenu() { enterState(MenuState::GetAction); }
439 
440 void StoreMenu::selectSellItem(StoreItemRow* row) {
441  sellingItem = row;
442  enterState(MenuState::SellQty);
443 }
444 
445 void StoreMenu::catRight() {
446  if (dingTime >= DingTime) {
447  dingTime = 0.f;
448  curCat = (curCat + 1) % 3;
449  catSync();
450  bl::audio::AudioSystem::playOrRestartSound(core::Properties::MenuMoveSound());
451  }
452 }
453 
454 void StoreMenu::catLeft() {
455  if (dingTime >= DingTime) {
456  dingTime = 0.f;
457  curCat = curCat > 0 ? curCat - 1 : 2;
458  catSync();
459  bl::audio::AudioSystem::playOrRestartSound(core::Properties::MenuMoveSound());
460  }
461 }
462 
463 void StoreMenu::catSync() {
464  catText.getSection().setString(catTexts[curCat]);
465  catText.getTransform().setPosition(745.f - catText.getGlobalSize().x -
466  rightArrow.getGlobalSize().x - 9.f,
467  actionText.getTransform().getGlobalPosition().y);
468  leftArrow.getTransform().setPosition(catText.getTransform().getGlobalPosition().x -
469  leftArrow.getGlobalSize().x - 3.f,
470  rightArrow.getTransform().getGlobalPosition().y);
471  renderMenu = &sellMenus[curCat];
472 }
473 
474 int StoreMenu::getSellPrice(core::item::Id item) const {
475  const auto it = sellPrices.find(item);
476  return it == sellPrices.end() ? core::item::Item::getValue(item) * 4 / 5 : it->second;
477 }
478 
479 } // namespace state
480 } // namespace game
Category
Represents a category that an item can belong to. Used for bag sorting.
Definition: Category.hpp:16
Id
Represents an item in its simplist form.
Definition: Id.hpp:24
@ Peopleball
Peopleballs.
@ TM
TM's. Covers ids [201, 499].
@ Regular
Regular items. Covers ids [1, 100].
Parent namespace for all functionality unique to the game.
Event that is fired by the Core module to signal to the game to open a store.
Definition: Store.hpp:17
const std::vector< std::pair< item::Id, int > > & items
Definition: Store.hpp:29
const std::vector< std::pair< item::Id, int > > & sellPrices
Definition: Store.hpp:30
static Category getCategory(Id item)
Returns the category of the given item.
Definition: Item.cpp:49
static const std::string & getName(item::Id item)
Returns the name of the given item.
Definition: Item.cpp:91
static const std::vector< Id > & validIds()
Returns the list of valid item ids.
Definition: Item.cpp:125
static int getValue(item::Id item)
Returns the value of the given item.
Definition: Item.cpp:119
void getByCategory(item::Category category, std::vector< Item > &result) const
Returns the set of owned items in the given category.
Definition: Bag.cpp:17
bool removeItem(item::Id item, unsigned int qty=1)
Removes the given item from the bag.
Definition: Bag.cpp:52
void addItem(item::Id item, unsigned int qty=1)
Adds the given item to the bag.
Definition: Bag.cpp:44
unsigned int itemCount(item::Id item) const
Returns the number of the given item owned.
Definition: Bag.cpp:37
std::vector< pplmn::OwnedPeoplemon > peoplemon
Definition: State.hpp:46
player::Bag bag
Definition: State.hpp:44
static const sf::VulkanFont & MenuFont()
Definition: Properties.cpp:363
static const std::string & SoundPath()
Definition: Properties.cpp:696
static bl::audio::AudioSystem::Handle MenuMoveSound()
Definition: Properties.cpp:702
static const std::string & MenuImagePath()
Definition: Properties.cpp:303
void configure(int minQty, int maxQty, int qty)
Configures the entry with the given parameters and adds to the overlay.
Definition: QtyEntry.cpp:69
void setPosition(const sf::Vector2f &position)
Sets the position of the selector.
Definition: QtyEntry.cpp:60
void up(int q, bool ignoreDebounce)
Increments the qty up.
Definition: QtyEntry.cpp:105
void down(int q, bool ignoreDebounce)
Decrements the qty down.
Definition: QtyEntry.cpp:112
sf::Vector2f getSize() const
Returns the size of the entry in pixels.
Definition: QtyEntry.cpp:67
void hide()
Removes the quantity entry from the overlay.
Definition: QtyEntry.cpp:53
int curQty() const
Returns the currently selected qty.
Definition: QtyEntry.cpp:103
player::State & state()
Returns the state of the player.
Definition: Player.cpp:154
Owns all primary systems and a reference to the engine.
Definition: Systems.hpp:47
const bl::engine::Engine & engine() const
Const accessor for the Engine.
Definition: Systems.cpp:35
Player & player()
Returns the player system.
Definition: Systems.cpp:59
void updateQty(int qty)
Updates the text for how many items can be sold.
core::item::Id getItem() const
Returns the item that this row represents.
static Ptr create(int qty, core::item::Id item, int price)
Creates a new menu item in sell mode.
std::shared_ptr< StoreItemRow > Ptr
Pointer to the menu item.
Parent to all game states. Provides some commonly required data like core game systems.
Definition: State.hpp:29
core::system::Systems & systems
Definition: State.hpp:66
State that renders and implements a store menu to buy and sell items.
Definition: StoreMenu.hpp:25
virtual void activate(bl::engine::Engine &engine) override
Subscribes to event buses.
Definition: StoreMenu.cpp:124
virtual void deactivate(bl::engine::Engine &engine) override
Unsubscribes from event buses.
Definition: StoreMenu.cpp:208
virtual void update(bl::engine::Engine &engine, float dt, float) override
Updates the store menu.
Definition: StoreMenu.cpp:214
virtual const char * name() const override
Returns "StoreMenu".
Definition: StoreMenu.cpp:122