Peoplemon  0.1.0
Peoplemon 3 game source documentation
TrainerEditorWindow.cpp
Go to the documentation of this file.
2 
3 #include <Core/Files/Trainer.hpp>
5 #include <Core/Properties.hpp>
6 
7 namespace editor
8 {
9 namespace component
10 {
11 namespace
12 {
13 const std::string EmptyFile = "<no file selected>";
14 
15 std::string pplToStr(const core::pplmn::OwnedPeoplemon& ppl) {
16  std::stringstream ss;
17  ss << "Id: " << ppl.id() << ": " << core::pplmn::Peoplemon::name(ppl.id()) << " (Level "
18  << ppl.currentLevel() << ")";
19  return ss.str();
20 }
21 
22 int parsePayout(const std::string& payout) {
23  for (char c : payout) {
24  if (c < '0' || c > '9') return -1;
25  }
26  const int p = std::atoi(payout.c_str());
27  return p <= 200 ? p : -1;
28 }
29 
30 } // namespace
31 
32 using namespace bl::gui;
34 
36 : selectCb(cb)
37 , closeCb(ccb)
38 , clean(true)
39 , itemSelector(ItemSelector::create([this](core::item::Id item) { curItem = item; }))
40 , pplWindow(
41  [this]() {
42  window->setForceFocus(true);
43  const auto val = pplWindow.getValue();
44  if (editPplIndex.has_value()) {
45  peoplemon[editPplIndex.value()] = val;
46  pplBox->editOptionText(editPplIndex.value(), pplToStr(val));
47  }
48  else {
49  peoplemon.emplace_back(val);
50  pplBox->addOption(pplToStr(val));
51  }
52  makeDirty();
53  },
54  [this]() { window->setForceFocus(true); })
55 , filePicker(core::Properties::TrainerPath(), {"tnr"},
56  std::bind(&TrainerEditorWindow::onChooseFile, this, std::placeholders::_1),
57  [this]() {
58  filePicker.close();
59  window->setForceFocus(true);
60  })
61 , animWindow(true, std::bind(&TrainerEditorWindow::onChooseAnimation, this, std::placeholders::_1),
62  std::bind(&TrainerEditorWindow::forceWindowOnTop, this))
63 , behaviorEditor(
64  std::bind(&TrainerEditorWindow::makeDirty, this), [this]() { window->setForceFocus(false); },
65  std::bind(&TrainerEditorWindow::forceWindowOnTop, this))
66 , conversationWindow(
67  std::bind(&TrainerEditorWindow::onChooseConversation, this, std::placeholders::_1),
68  std::bind(&TrainerEditorWindow::forceWindowOnTop, this)) {
69  window = Window::create(LinePacker::create(LinePacker::Vertical, 8), "Trainer Editor");
70  window->getSignal(Event::Closed).willAlwaysCall([this](const Event&, Element*) { hide(); });
71 
72  Box::Ptr row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
73  Button::Ptr newBut = Button::create("New");
74  newBut->setTooltip("Create a new Trainer file");
75  newBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
76  if (confirmDiscard()) {
77  makingNew = true;
78  window->setForceFocus(false);
79  filePicker.open(FilePicker::CreateNew, "New Trainer", parent, true);
80  }
81  });
82  Button::Ptr setBut = Button::create("Set File");
83  setBut->setTooltip("Change the file to save this Trainer to");
84  setBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
85  if (fileLabel->getText() == EmptyFile || confirmDiscard()) {
86  makingNew = true;
87  window->setForceFocus(false);
88  filePicker.open(FilePicker::CreateOrPick, "Set Trainer File", parent, false);
89  }
90  });
91  Button::Ptr openBut = Button::create("Open");
92  openBut->setTooltip("Open a different Trainer file");
93  openBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
94  if (confirmDiscard()) {
95  makingNew = false;
96  window->setForceFocus(false);
97  filePicker.open(FilePicker::PickExisting, "Open Trainer", parent, false);
98  }
99  });
100  saveBut = Button::create("Save");
101  saveBut->setTooltip("Overwrite the current Trainer file");
102  saveBut->setColor(sf::Color::Yellow, sf::Color::Blue);
103  saveBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
104  if (validate(true)) {
105  core::file::Trainer trainer;
106  trainer.name = nameEntry->getInput();
107  trainer.prebattleConversation = bbLabel->getText();
108  trainer.postBattleConversation = abLabel->getText();
109  trainer.lostBattleLine = loseLineEntry->getInput();
110  trainer.animation = animLabel->getText();
111  trainer.behavior = behaviorEditor.getValue();
112  trainer.visionRange = visionRangeEntry->getSelectedOption();
113  trainer.items = items;
114  trainer.peoplemon = peoplemon;
115  trainer.payout = parsePayout(payoutEntry->getInput());
116  if (!trainer.save(bl::util::FileUtil::joinPath(core::Properties::TrainerPath(),
117  fileLabel->getText()))) {
118  bl::dialog::tinyfd_messageBox("Error", "Failed to save Trainer", "ok", "error", 1);
119  }
120  else { makeClean(); }
121  }
122  });
123  fileLabel = Label::create("");
124  fileLabel->setColor(sf::Color::Cyan, sf::Color::Cyan);
125  row->pack(newBut, false, true);
126  row->pack(setBut, false, true);
127  row->pack(openBut, false, true);
128  row->pack(saveBut, false, true);
129  row->pack(fileLabel, true, true);
130  window->pack(row, true, false);
131 
132  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
133  Label::Ptr label = Label::create("Name:");
134  nameEntry = TextEntry::create();
135  nameEntry->getSignal(Event::ValueChanged)
136  .willAlwaysCall(std::bind(&TrainerEditorWindow::makeDirty, this));
137  row->pack(label, false, true);
138  row->pack(nameEntry, true, true);
139  window->pack(row, true, false);
140 
141  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
142  Button::Ptr animBut = Button::create("Select Anim");
143  animBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
144  window->setForceFocus(false);
145  animWindow.open(parent, core::Properties::CharacterAnimationPath(), animLabel->getText());
146  });
147  animLabel = Label::create("animation.anim");
148  animLabel->setColor(sf::Color::Cyan, sf::Color::Cyan);
149  row->pack(animBut, false, true);
150  row->pack(animLabel, true, true);
151  window->pack(row, true, false);
152 
153  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
154  Button::Ptr convBut = Button::create("Before Battle Conversation");
155  convBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
156  window->setForceFocus(false);
157  selectingBB = true;
158  conversationWindow.open(parent, bbLabel->getText());
159  });
160  bbLabel = Label::create("conversation.conv");
161  bbLabel->setColor(sf::Color::Cyan, sf::Color::Cyan);
162  row->pack(convBut, false, true);
163  row->pack(bbLabel, true, true);
164  window->pack(row, true, false);
165 
166  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
167  convBut = Button::create("After Battle Conversation");
168  convBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
169  window->setForceFocus(false);
170  selectingBB = false;
171  conversationWindow.open(parent, abLabel->getText());
172  });
173  abLabel = Label::create("conversation.conv");
174  abLabel->setColor(sf::Color::Cyan, sf::Color::Cyan);
175  row->pack(convBut, false, true);
176  row->pack(abLabel, true, true);
177  window->pack(row, true, false);
178 
179  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
180  row->pack(Label::create("Lose Line:"), false, true);
181  loseLineEntry = TextEntry::create();
182  loseLineEntry->getSignal(Event::ValueChanged)
183  .willAlwaysCall(std::bind(&TrainerEditorWindow::makeDirty, this));
184  row->pack(loseLineEntry, true, true);
185  window->pack(row, true, false);
186 
187  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
188  behaviorEditor.pack(row);
189  window->pack(row, true, false);
190 
191  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
192  row->pack(Label::create("Vision Range (tiles):"), false, true);
193  visionRangeEntry = ComboBox::create();
194  visionRangeEntry->setMaxHeight(300.f);
195  for (int i = 0; i <= 20; ++i) { visionRangeEntry->addOption(std::to_string(i)); }
196  visionRangeEntry->setSelectedOption(4);
197  visionRangeEntry->getSignal(Event::ValueChanged)
198  .willAlwaysCall(std::bind(&TrainerEditorWindow::makeDirty, this));
199  row->pack(visionRangeEntry, false, true);
200  window->pack(row);
201 
202  Label::Ptr l = Label::create("Items:");
203  l->setHorizontalAlignment(RenderSettings::Alignment::Left);
204  window->pack(l, true, false);
205  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
206  itemSelectBox = SelectBox::create();
207  itemSelectBox->setMaxSize({1200.f, 90.f});
208  itemSelectBox->setRequisition({250.f, 108.f});
209  row->pack(itemSelectBox, true, true);
210  Box::Ptr column = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
211  Box::Ptr subRow = Box::create(LinePacker::create(LinePacker::Horizontal));
212  subRow->pack(itemSelector);
213  Button::Ptr but = Button::create("Add");
214  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
215  if (curItem != core::item::Id::Unknown) {
216  items.push_back(curItem);
217  itemSelectBox->addOption(core::item::Item::getName(curItem));
218  makeDirty();
219  }
220  });
221  subRow->pack(but);
222  column->pack(subRow, true, false);
223  but = Button::create("Remove");
224  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
225  const auto sel = itemSelectBox->getSelectedOption();
226  if (sel.has_value()) {
227  items.erase(items.begin() + sel.value());
228  itemSelectBox->removeOption(sel.value());
229  makeDirty();
230  }
231  });
232  but->setColor(sf::Color::Red, sf::Color::Black);
233  column->pack(but);
234  row->pack(column, true, true);
235  window->pack(row, true, false);
236 
237  l = Label::create("Peoplemon:");
238  l->setHorizontalAlignment(RenderSettings::Alignment::Left);
239  window->pack(l, true, false);
240  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
241  pplBox = SelectBox::create();
242  pplBox->setMaxSize({1200.f, 90.f});
243  pplBox->setRequisition({250.f, 108.f});
244  row->pack(pplBox, true, true);
245  column = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
246  but = Button::create("Add");
247  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
248  if (peoplemon.size() < 6) {
249  editPplIndex.reset();
250  window->setForceFocus(false);
251  pplWindow.show(parent);
252  }
253  });
254  column->pack(but);
255  but = Button::create("Edit");
256  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
257  editPplIndex = pplBox->getSelectedOption();
258  window->setForceFocus(false);
259  pplWindow.show(parent,
260  editPplIndex.has_value() ? peoplemon[editPplIndex.value()] :
262  });
263  column->pack(but);
264  but = Button::create("Remove");
265  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
266  const auto sel = pplBox->getSelectedOption();
267  if (sel.has_value()) {
268  peoplemon.erase(peoplemon.begin() + sel.value());
269  pplBox->removeOption(sel.value());
270  makeDirty();
271  }
272  });
273  but->setColor(sf::Color::Red, sf::Color::Black);
274  column->pack(but);
275  row->pack(column, false, true);
276  window->pack(row, true, false);
277 
278  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
279  row->pack(Label::create("Battle Base Payout (0-200):"), false, true);
280  payoutEntry = TextEntry::create();
281  payoutEntry->setInput("40");
282  payoutEntry->setRequisition({60.f, 10.f});
283  row->pack(payoutEntry, false, true);
284  window->pack(row, true, false);
285 
286  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
287  Button::Ptr selectBut = Button::create("Use Trainer");
288  selectBut->setTooltip("Use the current Trainer file");
289  selectBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
290  if (confirmDiscard()) {
291  if (validate(false)) {
292  selectCb(fileLabel->getText());
293  hide();
294  }
295  }
296  });
297  selectBut->setColor(sf::Color::Blue, sf::Color::Black);
298  Button::Ptr cancelBut = Button::create("Cancel");
299  cancelBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
300  hide();
301  });
302  cancelBut->setColor(sf::Color::Red, sf::Color::Black);
303  row->pack(selectBut, false, true);
304  row->pack(cancelBut, false, true);
305  window->pack(row, true, false);
306 }
307 
308 void TrainerEditorWindow::show(GUI* p, const std::string& file) {
309  parent = p;
310  reset();
311  if (!file.empty()) {
312  fileLabel->setText(file);
313  load(file);
314  }
315  makeClean();
316  parent->pack(window);
317  window->setForceFocus(true);
318  itemSelector->refresh();
319 }
320 
321 void TrainerEditorWindow::onChooseFile(const std::string& file) {
322  const std::string f =
323  bl::util::FileUtil::getExtension(file) == core::Properties::TrainerFileExtension() ?
324  file :
326  filePicker.close();
327  window->setForceFocus(true);
328  if (makingNew) { makeDirty(); }
329  else {
330  reset();
331  load(f);
332  makeClean();
333  }
334  fileLabel->setText(f);
335 }
336 
337 void TrainerEditorWindow::makeClean() {
338  saveBut->setColor(sf::Color::Green, sf::Color::Blue);
339  clean = true;
340 }
341 
342 void TrainerEditorWindow::makeDirty() {
343  saveBut->setColor(sf::Color::Yellow, sf::Color::Blue);
344  clean = false;
345 }
346 
347 void TrainerEditorWindow::reset() {
348  fileLabel->setText(EmptyFile);
349  nameEntry->setInput("");
350  animLabel->setText("<no anim selected>");
351  bbLabel->setText("<no conv selected>");
352  abLabel->setText("<no conv selected>");
353  loseLineEntry->setInput("");
354  behaviorEditor.configure(parent, {});
355  visionRangeEntry->setSelectedOption(4);
356  items.clear();
357  itemSelectBox->clearOptions();
358  pplBox->clearOptions();
359  peoplemon.clear();
360  payoutEntry->setInput("40");
361 }
362 
363 void TrainerEditorWindow::load(const std::string& file) {
364  core::file::Trainer trainer;
365  if (trainer.load(FileUtil::joinPath(core::Properties::TrainerPath(), file))) {
366  nameEntry->setInput(trainer.name);
367  animLabel->setText(trainer.animation);
368  bbLabel->setText(trainer.prebattleConversation);
369  abLabel->setText(trainer.postBattleConversation);
370  loseLineEntry->setInput(trainer.lostBattleLine);
371  visionRangeEntry->setSelectedOption(trainer.visionRange);
372  behaviorEditor.configure(parent, trainer.behavior);
373  items = trainer.items;
374  for (const auto item : items) { itemSelectBox->addOption(core::item::Item::getName(item)); }
375  peoplemon = trainer.peoplemon;
376  for (const auto& ppl : peoplemon) { pplBox->addOption(pplToStr(ppl)); }
377  payoutEntry->setInput(std::to_string(trainer.payout));
378  }
379  else {
380  bl::dialog::tinyfd_messageBox(
381  "Error", std::string("Failed to load Trainer:\n" + file).c_str(), "ok", "error", 1);
382  }
383 }
384 
385 bool TrainerEditorWindow::validate(bool saving) const {
386  if (saving) {
387  if (fileLabel->getText() == EmptyFile) {
388  bl::dialog::tinyfd_messageBox("Warning", "Please select a file", "ok", "warning", 1);
389  return false;
390  }
391  }
392  else {
393  if (!FileUtil::exists(
394  FileUtil::joinPath(core::Properties::TrainerPath(), fileLabel->getText()))) {
395  bl::dialog::tinyfd_messageBox("Warning", "Bad file selected", "ok", "warning", 1);
396  return false;
397  }
398  }
399  if (nameEntry->getInput().empty()) {
400  bl::dialog::tinyfd_messageBox("Warning", "Enter a name", "ok", "warning", 1);
401  return false;
402  }
403  if (!FileUtil::directoryExists(
404  FileUtil::joinPath(core::Properties::CharacterAnimationPath(), animLabel->getText()))) {
405  bl::dialog::tinyfd_messageBox("Warning", "Bad animation", "ok", "warning", 1);
406  return false;
407  }
408  std::string p = FileUtil::joinPath(core::Properties::ConversationPath(), bbLabel->getText());
409  if (!FileUtil::exists(p)) {
410  BL_LOG_INFO << p;
411  bl::dialog::tinyfd_messageBox(
412  "Warning", "Bad before-battle conversation", "ok", "warning", 1);
413  return false;
414  }
415  p = FileUtil::joinPath(core::Properties::ConversationPath(), abLabel->getText());
416  if (!FileUtil::exists(p)) {
417  BL_LOG_INFO << p;
418  bl::dialog::tinyfd_messageBox(
419  "Warning", "Bad afer-battle conversation", "ok", "warning", 1);
420  return false;
421  }
422  if (loseLineEntry->getInput().empty()) {
423  bl::dialog::tinyfd_messageBox("Warning", "Lose line is empty", "ok", "warning", 1);
424  return false;
425  }
426  if (behaviorEditor.getValue().type() == core::file::Behavior::Type::FollowingPath &&
427  behaviorEditor.getValue().path().paces.empty()) {
428  bl::dialog::tinyfd_messageBox("Warning", "Behavior path is empty", "ok", "warning", 1);
429  return false;
430  }
431  if (peoplemon.empty()) {
432  bl::dialog::tinyfd_messageBox("Warning", "Trainer has no peoplemon", "ok", "warning", 1);
433  return false;
434  }
435  if (parsePayout(payoutEntry->getInput()) <= 0) {
436  bl::dialog::tinyfd_messageBox(
437  "Warning", "Payout must be number in range (0, 200]", "ok", "warning", 1);
438  return false;
439  }
440  return true;
441 }
442 
443 bool TrainerEditorWindow::confirmDiscard() const {
444  if (!clean) {
445  return 1 == bl::dialog::tinyfd_messageBox(
446  "Warning", "Discard unsaved changes?", "yesno", "warning", 0);
447  }
448  return true;
449 }
450 
452  window->remove();
453  filePicker.close();
454  animWindow.hide();
455  behaviorEditor.hide();
456  window->setForceFocus(false);
457  closeCb();
458 }
459 
460 void TrainerEditorWindow::onChooseAnimation(const std::string& f) {
461  animLabel->setText(f);
462  makeDirty();
463 }
464 
465 void TrainerEditorWindow::onChooseConversation(const std::string& c) {
466  if (selectingBB) { bbLabel->setText(c); }
467  else { abLabel->setText(c); }
468  window->setForceFocus(true);
469  makeDirty();
470 }
471 
472 void TrainerEditorWindow::forceWindowOnTop() { window->setForceFocus(true); }
473 
474 } // namespace component
475 } // namespace editor
Id
The id of a peoplemon.
Definition: Id.hpp:16
Core classes and functionality for both the editor and game.
All classes and functionality used for implementing the game editor.
Definition: Tile.hpp:11
bl::util::FileUtil FileUtil
Type type() const
The type of behavior this is.
Definition: Behavior.cpp:75
Path & path()
The data for path following.
Definition: Behavior.cpp:110
std::vector< Pace > paces
The sections of the path.
Definition: Behavior.hpp:95
Static data for trainers in the world.
Definition: Trainer.hpp:20
std::string lostBattleLine
Definition: Trainer.hpp:75
std::vector< pplmn::OwnedPeoplemon > peoplemon
Definition: Trainer.hpp:78
std::uint8_t visionRange
Definition: Trainer.hpp:76
std::uint8_t payout
Definition: Trainer.hpp:80
std::string postBattleConversation
Definition: Trainer.hpp:74
std::string animation
Definition: Trainer.hpp:72
std::vector< item::Id > items
Definition: Trainer.hpp:79
bool save(const std::string &file) const
Saves the trainer data to the given file.
Definition: Trainer.cpp:14
std::string name
Definition: Trainer.hpp:71
bool load(const std::string &file, bl::tmap::Direction spawnDir=bl::tmap::Direction::Down)
Loads the trainer data from the given file.
Definition: Trainer.cpp:43
Behavior behavior
Definition: Trainer.hpp:77
std::string prebattleConversation
Definition: Trainer.hpp:73
static const std::string & getName(item::Id item)
Returns the name of the given item.
Definition: Item.cpp:91
Represents an instance of a peoplemon. Can be a wild peoplemon, trainer, or player peoplemon....
unsigned int currentLevel() const
Returns the current level of this peoplemon.
Id id() const
Returns the id of this peoplemon.
static const std::string & name(Id id)
Returns the name of the given Peoplemon.
Definition: Peoplemon.cpp:126
static const std::string & TrainerPath()
Definition: Properties.cpp:533
static const std::string & ConversationPath()
Definition: Properties.cpp:539
static const std::string & CharacterAnimationPath()
Definition: Properties.cpp:557
static const std::string & TrainerFileExtension()
Definition: Properties.cpp:527
void hide()
Removes the window from view.
const core::file::Behavior & getValue() const
Returns the behavior value.
void hide()
Hides the editor window if opened.
void configure(bl::gui::GUI *parent, const core::file::Behavior &behavior)
Sets the parent GUI object and current value. Call before using.
Wrapper over ComboBox that allows selection of any item in the item database.
void show(bl::gui::GUI *parent, const std::string &file)
Opens the trainer editor.
TrainerEditorWindow(const SelectCb &cb, const CloseCb &closeCb)
Construct a new Trainer Editor Window.
std::function< void(const std::string &file)> SelectCb
Callback for when a trainer file is selected.
void hide()
Hides the window and all created child windows.
std::function< void()> CloseCb
Called when the window is closed.