Peoplemon  0.1.0
Peoplemon 3 game source documentation
HUD.cpp
Go to the documentation of this file.
1 #include <Core/Systems/HUD.hpp>
2 
3 #include <Core/Properties.hpp>
4 #include <Core/Resources.hpp>
6 
7 namespace core
8 {
9 namespace system
10 {
11 namespace
12 {
13 constexpr float ChoiceHeight = 25.f;
14 constexpr float ChoicePadding = 8.f;
15 constexpr float TextPadding = 10.f;
16 constexpr float FlashOn = 0.75f;
17 constexpr float FlashOff = 0.65f;
18 
19 bool isNextCommand(unsigned int cmd) {
20  return cmd == input::Control::Back || cmd == input::Control::Interact;
21 }
22 
23 } // namespace
24 
26 : owner(owner)
27 , state(Hidden)
28 , inputListener(*this)
29 , screenKeyboard(owner.engine(), std::bind(&HUD::keyboardSubmit, this, std::placeholders::_1))
30 , entryCard(owner.engine())
31 , currentOverlay(nullptr)
32 , qtyEntry(owner.engine()) {}
33 
34 void HUD::init(bl::engine::Engine&) {
35  textboxTxtr =
36  owner.engine().renderer().texturePool().getOrLoadTexture(Properties::TextboxFile());
37 
38  const glm::vec2& boxSize = textboxTxtr->size();
39  textbox.create(owner.engine(), textboxTxtr);
40  textbox.getTransform().setPosition(Properties::WindowSize().x * 0.5f - boxSize.x * 0.5f,
41  Properties::WindowSize().y - boxSize.y);
42  textbox.getTransform().setDepth(bl::cam::OverlayCamera::MinDepth);
43  choiceBoxX = boxSize.x * 2.f * 0.75f + 3.f;
44 
45  displayText.create(
46  owner.engine(), Properties::MenuFont(), "", Properties::HudFontSize(), sf::Color::Black);
47  displayText.getTransform().setPosition(TextPadding, 8.f);
48  displayText.wordWrap(textboxTxtr->size().x - TextPadding * 2.f);
49  displayText.setParent(textbox);
50 
51  promptTriangle.create(owner.engine(), {0.f, 0.f}, {12.f, 5.5f}, {0.f, 11.f});
52  promptTriangle.setFillColor(sf::Color(255, 77, 0));
53  promptTriangle.setOutlineColor(sf::Color(255, 238, 128, 185));
54  promptTriangle.setOutlineThickness(1.5f);
55  promptTriangle.getTransform().setPosition(boxSize - glm::vec2(18.f, 14.f));
56  promptTriangle.setParent(textbox);
57 
58  choiceMenu.create(owner.engine(),
59  owner.engine().renderer().getObserver(),
60  bl::menu::ArrowSelector::create(10.f, sf::Color::Black));
61  choiceMenu.setPadding({0.f, ChoicePadding});
62  choiceMenu.setMinHeight(ChoiceHeight);
63  choiceMenu.configureBackground(sf::Color::White, sf::Color::Black, 2.f, {18.f, 2.f, 4.f, 4.f});
64 
65  qtyEntry.setPosition(
66  {textbox.getTransform().getLocalPosition().x + textboxTxtr->size().x - 50.f,
67  textbox.getTransform().getLocalPosition().y - 70.f});
68 }
69 
70 void HUD::update(std::mutex&, float dt, float, float, float) {
71  switch (state) {
72  case Printing:
73  if (currentMessage.update(dt)) {
74  displayText.getSection().setString(std::string(currentMessage.getVisible()));
75  }
76  if (currentMessage.finished()) printDoneStateTransition();
77  break;
78 
79  case WaitingKeyboard:
80  case WaitingPrompt:
81  case WaitingSticky:
82  // noop
83  break;
84 
85  default:
86  break;
87  }
88 
89  entryCard.update(dt);
90 }
91 
92 void HUD::displayMessage(const std::string& msg, const Callback& cb) {
93  queuedOutput.emplace(msg, false, true, cb);
94  ensureActive();
95 }
96 
97 void HUD::displayStickyMessage(const std::string& msg, bool ghost, const Callback& cb) {
98  queuedOutput.emplace(msg, true, ghost, cb);
99  ensureActive();
100 }
101 
102 bool HUD::dismissStickyMessage(bool ignoreGhost) {
103  if (queuedOutput.empty()) return false;
104  if (queuedOutput.front().getType() != Item::Message) return false;
105  if (!queuedOutput.front().isSticky()) return false;
106  if (!ignoreGhost && !currentMessage.finished()) return false;
107 
108  next();
109  return true;
110 }
111 
112 void HUD::promptUser(const std::string& prompt, const std::vector<std::string>& choices,
113  const Callback& cb) {
114  queuedOutput.emplace(prompt, choices, cb);
115  ensureActive();
116 }
117 
118 void HUD::getInputString(const std::string& prompt, unsigned int mn, unsigned int mx,
119  const Callback& cb) {
120  queuedOutput.emplace(prompt, mn, mx, cb);
121  ensureActive();
122 }
123 
124 void HUD::getQty(const std::string& prompt, int minQty, int maxQty, const QtyCallback& cb) {
125  queuedOutput.emplace(prompt, minQty, maxQty, cb);
126  ensureActive();
127 }
128 
129 void HUD::displayEntryCard(const std::string& name) { entryCard.display(name); }
130 
131 void HUD::hideEntryCard() { entryCard.hide(); }
132 
133 void HUD::ensureActive() {
134  if (state == Hidden && !queuedOutput.empty()) {
135  owner.engine().inputSystem().getActor().addListener(inputListener);
136  startPrinting();
137  }
138 }
139 
140 void HUD::startPrinting() {
141  setState(Printing);
142  currentMessage.setContent(queuedOutput.front().getMessage());
143  if (!queuedOutput.front().ghostWrite()) { currentMessage.showAll(); }
144  displayText.getSection().setString(std::string{currentMessage.getVisible()});
145 }
146 
147 void HUD::printDoneStateTransition() {
148  switch (queuedOutput.front().getType()) {
149  case Item::Message:
150  if (!queuedOutput.front().isSticky()) { setState(WaitingContinue); }
151  else {
152  setState(WaitingSticky);
153  queuedOutput.front().getCallback()(queuedOutput.front().getMessage());
154  }
155  break;
156 
157  case Item::Prompt: {
158  const std::vector<std::string>& choices = queuedOutput.front().getChoices();
159  bl::menu::Item::Ptr mitem =
160  bl::menu::TextItem::create(choices.empty() ? "INVALID" : choices.front(),
162  sf::Color::Black,
164  mitem->getSignal(bl::menu::Item::Activated)
165  .willAlwaysCall(std::bind(&HUD::choiceMade, this, 0));
166  choiceMenu.setRootItem(mitem);
167 
168  bl::menu::Item* prev = mitem.get();
169  for (unsigned int i = 1; i < choices.size(); ++i) {
170  mitem = bl::menu::TextItem::create(
171  choices[i], Properties::MenuFont(), sf::Color::Black, Properties::HudFontSize());
172  mitem->getSignal(bl::menu::Item::Activated)
173  .willAlwaysCall(std::bind(&HUD::choiceMade, this, i));
174  choiceMenu.addItem(mitem, prev, bl::menu::Item::Bottom);
175  prev = mitem.get();
176  }
177  const sf::FloatRect bounds = choiceMenu.getBounds();
178  const float y = Properties::WindowSize().y - bounds.height - 18.f;
179  choiceMenu.setPosition({choiceBoxX + 18.f, y + 2.f});
180  choiceDriver.drive(&choiceMenu);
181  setState(WaitingPrompt); // TODO - why does this need to be after setting root item?
182  } break;
183 
184  case Item::Keyboard:
185  setState(WaitingKeyboard);
186  screenKeyboard.start(queuedOutput.front().minInputLength(),
187  queuedOutput.front().maxInputLength());
188  screenKeyboard.setPosition(
189  {Properties::WindowSize().x * 0.5f - screenKeyboard.getSize().x * 0.5f,
190  textbox.getTransform().getLocalPosition().y - screenKeyboard.getSize().y - 2.f});
191  break;
192 
193  case Item::Qty:
194  setState(WaitingQty);
195  qtyEntry.configure(queuedOutput.front().getMinQty(), queuedOutput.front().getMaxQty(), 1);
196  break;
197 
198  default:
199  setState(Hidden);
200  next();
201  break;
202  }
203 }
204 
205 void HUD::choiceMade(unsigned int i) {
206  queuedOutput.front().getCallback()(queuedOutput.front().getChoices()[i]);
207  choiceDriver.drive(nullptr);
208  next();
209 }
210 
211 void HUD::qtySelected(int qty) {
212  queuedOutput.front().getQtyCallback()(qty);
213  next();
214 }
215 
216 void HUD::next() {
217  queuedOutput.pop();
218  if (!queuedOutput.empty()) { startPrinting(); }
219  else {
220  setState(Hidden);
221  owner.engine().inputSystem().getActor().removeListener(inputListener);
222  }
223 }
224 
225 void HUD::keyboardSubmit(const std::string& i) {
226  queuedOutput.front().getCallback()(i);
227  screenKeyboard.stop();
228  next();
229 }
230 
231 HUD::HudListener::HudListener(HUD& o)
232 : owner(o) {}
233 
234 bool HUD::HudListener::observe(const bl::input::Actor&, unsigned int ctrl, bl::input::DispatchType,
235  bool eventTriggered) {
236  switch (owner.state) {
237  case Printing:
238  if (isNextCommand(ctrl) && eventTriggered) {
239  owner.currentMessage.showAll();
240  owner.displayText.getSection().setString(owner.currentMessage.getContent());
241  owner.printDoneStateTransition();
242  }
243  break;
244 
245  case WaitingContinue:
246  if (isNextCommand(ctrl) && eventTriggered) {
247  owner.queuedOutput.front().getCallback()(owner.queuedOutput.front().getMessage());
248  owner.next();
249  }
250  break;
251 
252  case WaitingPrompt:
253  owner.choiceDriver.sendControl(ctrl, eventTriggered);
254  break;
255 
256  case WaitingKeyboard:
257  owner.screenKeyboard.process(ctrl, eventTriggered);
258  break;
259 
260  case WaitingQty:
261  switch (ctrl) {
263  owner.qtyEntry.down(1, eventTriggered);
264  break;
266  owner.qtyEntry.up(1, eventTriggered);
267  break;
269  owner.qtyEntry.up(10, eventTriggered);
270  break;
272  owner.qtyEntry.down(10, eventTriggered);
273  break;
275  owner.qtySelected(owner.qtyEntry.curQty());
276  break;
278  owner.qtySelected(0);
279  break;
280  default:
281  break;
282  }
283  break;
284 
285  case HUD::WaitingSticky:
286  // ignore input
287  break;
288 
289  default:
290  BL_LOG_WARN << "Input received by HUD while in invalid state: " << owner.state;
291  break;
292  }
293 
294  return true;
295 }
296 
297 void HUD::setState(State ns) {
298  textbox.setHidden(false);
299  if (state == WaitingKeyboard && ns != WaitingKeyboard) { screenKeyboard.stop(); }
300  if (state == WaitingQty && ns != WaitingQty) { qtyEntry.hide(); }
301  if (ns != WaitingContinue) { promptTriangle.stopFlashing(); }
302  if (state == Hidden || !currentOverlay ||
303  owner.engine().renderer().getObserver().getOrCreateSceneOverlay() != currentOverlay) {
304  currentOverlay = owner.engine().renderer().getObserver().getOrCreateSceneOverlay();
305  textbox.addToScene(currentOverlay, bl::rc::UpdateSpeed::Static);
306  displayText.addToScene(currentOverlay, bl::rc::UpdateSpeed::Static);
307  promptTriangle.addToScene(currentOverlay, bl::rc::UpdateSpeed::Static);
308  }
309  if (state == WaitingPrompt && ns != WaitingPrompt) { choiceMenu.removeFromScene(); }
310 
311  state = ns;
312  switch (state) {
313  case Hidden:
314  textbox.setHidden(true);
315  textbox.removeFromScene();
316  displayText.removeFromScene();
317  promptTriangle.removeFromScene();
318  choiceMenu.removeFromScene();
319  currentOverlay = nullptr;
320  break;
321  case WaitingContinue:
322  promptTriangle.flash(FlashOn, FlashOff);
323  break;
324  case WaitingPrompt:
325  choiceMenu.addToOverlay();
326  break;
327  case WaitingQty:
328  case Printing:
329  case WaitingSticky:
330  case WaitingKeyboard:
331  default:
332  // noop
333  break;
334  }
335 }
336 
337 HUD::Item::Item(const std::string& msg, bool sticky, bool ghost, const HUD::Callback& cb)
338 : type(Message)
339 , cb(cb)
340 , message(msg)
341 , data(std::in_place_type_t<std::pair<bool, bool>>{}, sticky, ghost) {}
342 
343 HUD::Item::Item(const std::string& p, const std::vector<std::string>& c, const HUD::Callback& cb)
344 : type(Prompt)
345 , cb(cb)
346 , message(p)
347 , data(std::in_place_type_t<std::vector<std::string>>{}, c) {}
348 
349 HUD::Item::Item(const std::string& prompt, unsigned int minLen, unsigned int maxLen,
350  const Callback& cb)
351 : type(Keyboard)
352 , cb(cb)
353 , message(prompt)
354 , data(std::in_place_type_t<std::pair<unsigned int, unsigned int>>{}, minLen, maxLen) {}
355 
356 HUD::Item::Item(const std::string& prompt, int minQty, int maxQty, const QtyCallback& cb)
357 : type(Qty)
358 , cb(cb)
359 , message(prompt)
360 , data(std::in_place_type_t<std::pair<int, int>>{}, minQty, maxQty) {}
361 
362 HUD::Item::Type HUD::Item::getType() const { return type; }
363 
364 const std::string& HUD::Item::getMessage() const { return message; }
365 
366 bool HUD::Item::isSticky() const { return std::get_if<std::pair<bool, bool>>(&data)->first; }
367 
368 bool HUD::Item::ghostWrite() const {
369  const auto* p = std::get_if<std::pair<bool, bool>>(&data);
370  return p ? p->second : true;
371 }
372 
373 const std::vector<std::string>& HUD::Item::getChoices() const {
374  return *std::get_if<std::vector<std::string>>(&data);
375 }
376 
377 unsigned int HUD::Item::minInputLength() const {
378  return std::get_if<std::pair<unsigned int, unsigned int>>(&data)->first;
379 }
380 
381 unsigned int HUD::Item::maxInputLength() const {
382  return std::get_if<std::pair<unsigned int, unsigned int>>(&data)->second;
383 }
384 
385 int HUD::Item::getMinQty() const { return std::get_if<std::pair<int, int>>(&data)->first; }
386 
387 int HUD::Item::getMaxQty() const { return std::get_if<std::pair<int, int>>(&data)->second; }
388 
389 const HUD::Callback& HUD::Item::getCallback() const { return *std::get_if<Callback>(&cb); }
390 
391 const HUD::QtyCallback& HUD::Item::getQtyCallback() const { return *std::get_if<QtyCallback>(&cb); }
392 
393 HUD::EntryCard::EntryCard(bl::engine::Engine& engine)
394 : engine(engine)
395 , currentOverlay(nullptr) {}
396 
397 void HUD::EntryCard::ensureCreated() {
398  if (!txtr) {
399  txtr = engine.renderer().texturePool().getOrLoadTexture(
400  bl::util::FileUtil::joinPath(Properties::MenuImagePath(), "HUD/namecard.png"));
401  card.create(engine, txtr);
402  text.create(engine, Properties::MenuFont(), "", 20, sf::Color(20, 200, 240));
403  text.setParent(card);
404  text.wordWrapToParent(0.98f);
405  }
406 
407  if (!currentOverlay ||
408  engine.renderer().getObserver().getOrCreateSceneOverlay() != currentOverlay) {
409  currentOverlay = engine.renderer().getObserver().getOrCreateSceneOverlay();
410  card.addToScene(currentOverlay, bl::rc::UpdateSpeed::Dynamic);
411  text.addToScene(currentOverlay, bl::rc::UpdateSpeed::Dynamic);
412  }
413 }
414 
415 void HUD::EntryCard::update(float dt) {
416  constexpr float MoveSpeed = 150.f;
417  constexpr float HoldTime = 2.5f;
418 
419  switch (state) {
420  case Dropping:
421  stateVar -= MoveSpeed * dt;
422  card.getTransform().setPosition(0.f, -stateVar);
423  if (stateVar <= 0.f) {
424  stateVar = 0.f;
425  state = Holding;
426  card.getTransform().setPosition(0.f, 0.f);
427  }
428  break;
429 
430  case Rising:
431  stateVar += MoveSpeed * dt;
432  card.getTransform().setPosition(0.f, -stateVar);
433  if (stateVar >= txtr->size().y) { hide(); }
434  break;
435 
436  case Holding:
437  stateVar += dt;
438  if (stateVar >= HoldTime) {
439  state = Rising;
440  stateVar = 0.f;
441  }
442  break;
443 
444  case Hidden:
445  default:
446  break;
447  }
448 }
449 
450 void HUD::EntryCard::display(const std::string& t) {
451  constexpr float Padding = 10.f;
452 
453  ensureCreated();
454  text.getSection().setString(t);
455  text.getTransform().setPosition(txtr->size() * 0.5f - text.getLocalSize() * 0.5f);
456 
457  state = Dropping;
458  stateVar = txtr->size().y;
459 }
460 
461 void HUD::EntryCard::hide() {
462  if (state != State::Hidden) {
463  state = State::Hidden;
464  card.removeFromScene();
465  }
466 }
467 
468 } // namespace system
469 } // namespace core
Type
The type classification of an item. This is used to determine when an item may be used and how to use...
Definition: Type.hpp:17
Core classes and functionality for both the editor and game.
static const sf::VulkanFont & MenuFont()
Definition: Properties.cpp:363
static const std::string & MenuImagePath()
Definition: Properties.cpp:303
static sf::Vector2f WindowSize()
Definition: Properties.cpp:262
static const std::string & TextboxFile()
Definition: Properties.cpp:607
static unsigned int HudFontSize()
Definition: Properties.cpp:613
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 hide()
Removes the quantity entry from the overlay.
Definition: QtyEntry.cpp:53
sf::Vector2f getSize() const
Returns the size of the screen keyboard.
void start(unsigned int minLen=0, unsigned int maxLen=16)
Subscribes the keyboard to the event bus.
void setPosition(const sf::Vector2f &position)
Sets the position of the keyboard. The default is (??)
void stop()
Unsubscribes the keyboard from the event bus.
The primary HUD system for the player. Manages displaying messages and asking questions....
Definition: HUD.hpp:31
void displayEntryCard(const std::string &name)
Displays a card to indicate entering a new town, route, or map.
Definition: HUD.cpp:129
void hideEntryCard()
Hides the entry card.
Definition: HUD.cpp:131
std::function< void(const std::string &value)> Callback
Signature for HUD callbacks. Used for both messages completing and choices made.
Definition: HUD.hpp:39
void displayMessage(const std::string &message, const Callback &cb=[](const std::string &) {})
Displays a message in the HUD textbox. Messages are queued in order that they arrive.
Definition: HUD.cpp:92
void getQty(const std::string &prompt, int minQty, int maxQty, const QtyCallback &cb)
Gets a number from the player.
Definition: HUD.cpp:124
void promptUser(const std::string &prompt, const std::vector< std::string > &choices, const Callback &cb)
Asks the player a question through the HUD.
Definition: HUD.cpp:112
std::function< void(int qty)> QtyCallback
Called when a qty is entered.
Definition: HUD.hpp:47
void displayStickyMessage(const std::string &message, bool ghostWrite, const Callback &cb=[](const std::string &) {})
Displays a message in the HUD textbox. Sticky messages stay displayed until programmatically dismisse...
Definition: HUD.cpp:97
void getInputString(const std::string &prompt, unsigned int minLen, unsigned int maxLen, const Callback &cb)
Prompts the player to input a string using the screen keyboard.
Definition: HUD.cpp:118
HUD(Systems &owner)
Construct a new HUD system.
Definition: HUD.cpp:25
bool dismissStickyMessage(bool ignoreGhostWrite=true)
Dismisses the currently active sticky message.
Definition: HUD.cpp:102
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