Peoplemon  0.1.0
Peoplemon 3 game source documentation
Map.cpp
Go to the documentation of this file.
1 #include <Editor/Pages/Map.hpp>
2 
3 #include <Core/Properties.hpp>
4 
5 namespace editor
6 {
7 namespace page
8 {
9 namespace
10 {
11 bool isNum(const char* s) {
12  for (unsigned int i = 0; i < strlen(s); ++i) {
13  if (s[i] < '0' || s[i] > '9') return false;
14  }
15  return true;
16 }
17 } // namespace
18 
19 using namespace bl::gui;
20 
22 : Page(s)
23 , mapArea([this](const sf::Vector2f& p, const sf::Vector2i& t) { onMapClick(p, t); },
24  std::bind(&Map::syncGui, this), s)
25 , tileset(
26  s.engine(),
27  [this](core::map::Tile::IdType id, bool isAnim) {
28  mapArea.editMap().removeAllTiles(id, isAnim);
29  },
30  mapArea)
31 , levelPage([this](unsigned int l, bool v) { mapArea.editMap().setLevelVisible(l, v); },
32  [this](unsigned int l, bool up) { mapArea.editMap().shiftLevel(l, up); },
33  [this]() { mapArea.editMap().appendLevel(); })
34 , layerPage([this](unsigned int l) { mapArea.editMap().appendBottomLayer(l); },
35  [this](unsigned int l) { mapArea.editMap().appendYsortLayer(l); },
36  [this](unsigned int l) { mapArea.editMap().appendTopLayer(l); },
37  [this](unsigned int level, unsigned int layer) {
38  mapArea.editMap().removeLayer(level, layer);
39  },
40  [this](unsigned int level, unsigned int layer, bool up) {
41  mapArea.editMap().shiftLayer(level, layer, up);
42  },
43  [this](unsigned int level, unsigned int layer, bool visible) {
44  mapArea.editMap().setLayerVisible(level, layer, visible);
45  })
46 , activeTool(Tool::Metadata)
47 , activeSubtool(Subtool::Set)
48 , selectionState(NoSelection)
49 , mapPicker(core::Properties::MapPath(), {"map", "p3m"},
50  std::bind(&Map::doLoadMap, this, std::placeholders::_1),
51  [this]() { mapPicker.close(); })
52 , newMapWindow(std::bind(&Map::makeNewMap, this, std::placeholders::_1, std::placeholders::_2,
53  std::placeholders::_3, std::placeholders::_4, std::placeholders::_5))
54 , playlistEditor(std::bind(&Map::onChoosePlaylist, this, std::placeholders::_1), []() {})
55 , scriptSelector(std::bind(&Map::onChooseScript, this, std::placeholders::_1), []() {})
56 , choosingOnloadScript(false)
57 , eventEditor(std::bind(&Map::onEventEdit, this, std::placeholders::_1, std::placeholders::_2))
58 , characterEditor(
59  std::bind(&Map::onCharacterEdit, this, std::placeholders::_1, std::placeholders::_2))
60 , renderMapWindow(std::bind(&Map::doMapRender, this)) {
61  content = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
62  content->setOutlineThickness(0.f);
63  Box::Ptr controlPane = Box::create(LinePacker::create(LinePacker::Vertical, 4));
64 
65  Box::Ptr mapCtrlBox = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
66  Button::Ptr newMapBut = Button::create("New Map");
67  newMapBut->getSignal(Event::LeftClicked).willCall([this](const Event&, Element*) {
68  if (checkUnsaved()) {
69  makingNewMap = true;
70  mapArea.disableControls();
71  mapPicker.open(FilePicker::CreateNew, "New map", parent);
72  }
73  });
74  Button::Ptr loadMapBut = Button::create("Load Map");
75  loadMapBut->getSignal(Event::LeftClicked).willCall([this](const Event&, Element*) {
76  if (checkUnsaved()) {
77  makingNewMap = false;
78  mapArea.disableControls();
79  mapPicker.open(FilePicker::PickExisting, "Load map", parent);
80  }
81  });
82  saveMapBut = Button::create("Save Map");
83  saveMapBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
84  if (!mapArea.editMap().editorSave()) {
85  bl::dialog::tinyfd_messageBox(
86  "Error Saving Map",
87  std::string("Failed to save map: " + mapArea.editMap().name()).c_str(),
88  "ok",
89  "error",
90  1);
91  }
92  else { tileset.markSaved(); }
93  });
94  Button::Ptr renderMapBut = Button::create("Render");
95  renderMapBut->getSignal(Event::LeftClicked)
96  .willAlwaysCall(std::bind(&Map::startMapRender, this));
97  mapCtrlBox->pack(newMapBut);
98  mapCtrlBox->pack(loadMapBut);
99  mapCtrlBox->pack(saveMapBut);
100  mapCtrlBox->pack(renderMapBut);
101 
102  const auto lightingChangeCb = std::bind(&Map::onLightingChange, this);
103  Box::Ptr lightingOuterBox = Box::create(LinePacker::create());
104  Box::Ptr lightingBox = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
105  Box::Ptr row = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
106  Button::Ptr lightReset = Button::create("Reset");
107  lightReset->getSignal(Event::LeftClicked)
108  .willAlwaysCall(std::bind(&Map::onLightingReset, this));
109  Button::Ptr lightDefault = Button::create("Default");
110  lightDefault->getSignal(Event::LeftClicked)
111  .willAlwaysCall(std::bind(&Map::setLightingDefault, this));
112  lightingSetBut = Button::create("Set");
113  lightingSetBut->getSignal(Event::LeftClicked)
114  .willAlwaysCall(std::bind(&Map::onLightingSave, this));
115  row->pack(lightDefault);
116  row->pack(lightReset);
117  row->pack(lightingSetBut);
118  lightingBox->pack(row);
119  sunlightBut = CheckButton::create("Adjust for sunlight");
120  sunlightBut->getSignal(Event::LeftClicked).willAlwaysCall(lightingChangeCb);
121  row->pack(sunlightBut);
122  minLightSlider =
123  component::LightSlider::create("Min Light", std::bind(&Map::onLightingChange, this));
124  maxLightSlider =
125  component::LightSlider::create("Max Light", std::bind(&Map::onLightingChange, this));
126  lightingBox->pack(minLightSlider, true, false);
127  lightingBox->pack(maxLightSlider, true, false);
128 
129  Box::Ptr tileBox = Box::create(LinePacker::create(LinePacker::Vertical, 4));
130  Box::Ptr box = Box::create(LinePacker::create(LinePacker::Horizontal, 4, LinePacker::Uniform));
131 
132  levelSelect = ComboBox::create();
133  levelSelect->getSignal(Event::ValueChanged).willAlwaysCall([this](const Event&, Element* e) {
134  onLevelChange(dynamic_cast<ComboBox*>(e)->getSelectedOption());
135  });
136  box->pack(levelSelect, true, true);
137 
138  layerSelect = ComboBox::create();
139  box->pack(layerSelect, true, true);
140  tileBox->pack(box, true, false);
141 
142  box = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
143  RadioButton::Ptr tileSetBut = RadioButton::create("Set", "set");
144  tileSetBut->setValue(true);
145  tileSetBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
146  activeSubtool = Subtool::Set;
147  });
148  RadioButton::Ptr fillBut = RadioButton::create("Fill", "fill", tileSetBut->getRadioGroup());
149  fillBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
150  activeSubtool = Subtool::Fill;
151  });
152  RadioButton::Ptr tileClearBut =
153  RadioButton::create("Clear", "clear", tileSetBut->getRadioGroup());
154  tileClearBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
155  activeSubtool = Subtool::Clear;
156  });
157  RadioButton::Ptr tileSelectBut =
158  RadioButton::create("Select", "select", tileSetBut->getRadioGroup());
159  tileSelectBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
160  activeSubtool = Subtool::Select;
161  });
162  Button::Ptr tileDeselectBut = Button::create("Deselect");
163  tileDeselectBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
164  setSelectionState(NoSelection);
165  });
166  Button::Ptr selectAllBut = Button::create("All");
167  selectAllBut->setTooltip("Select all tiles");
168  selectAllBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
169  selection = {sf::Vector2i(0, 0), mapArea.editMap().sizeTiles()};
170  setSelectionState(SelectionMade);
171  });
172  box->pack(tileSetBut, true, true);
173  box->pack(fillBut, true, true);
174  box->pack(tileClearBut, true, true);
175  box->pack(tileSelectBut, true, true);
176  box->pack(selectAllBut, true, true);
177  box->pack(tileDeselectBut, true, true);
178  tileBox->pack(box, true, false);
179 
180  Box::Ptr infoBox = Box::create(LinePacker::create(LinePacker::Vertical, 4));
181  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
182 
183  box = Box::create(LinePacker::create(LinePacker::Horizontal));
184  nameEntry = TextEntry::create(1);
185  nameEntry->getSignal(Event::ValueChanged).willAlwaysCall([this](const Event&, Element*) {
186  mapArea.editMap().setName(nameEntry->getInput());
187  });
188  box->pack(Label::create("Name:"), false, true);
189  box->pack(nameEntry, true, true);
190  row->pack(box, true, false);
191 
192  Button::Ptr resizeBut = Button::create("Resize Map");
193  // TODO - implement map resizing
194  row->pack(resizeBut);
195  infoBox->pack(row, true, false);
196 
197  row = Box::create(LinePacker::create(LinePacker::Horizontal));
198  playlistLabel = Label::create("playerlistFile.bplst");
199  Button::Ptr pickPlaylistBut = Button::create("Pick Playlist");
200  pickPlaylistBut->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
201  mapArea.disableControls();
202  playlistEditor.open(parent, playlistLabel->getText());
203  });
204  playlistLabel->setHorizontalAlignment(RenderSettings::Left);
205  row->pack(pickPlaylistBut);
206  row->pack(playlistLabel, true, false);
207  infoBox->pack(row, true, false);
208 
209  row = Box::create(LinePacker::create(LinePacker::Horizontal, 6));
210  row->pack(Label::create("Weather:"));
211  weatherEntry = component::WeatherSelect::create();
212  weatherEntry->setTooltip("Set the weather for the entire map");
213  weatherEntry->getSignal(Event::ValueChanged).willAlwaysCall([this](const Event&, Element*) {
214  const core::map::Weather::Type type = weatherEntry->selectedWeather();
215  if (mapArea.editMap().weatherSystem().getType() != type) {
216  mapArea.editMap().setWeather(type);
217  }
218  });
219  row->pack(weatherEntry);
220  infoBox->pack(row, true, false);
221 
222  box = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
223  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
224  tempWeatherEntry = component::WeatherSelect::create();
225  Button::Ptr but = Button::create("Set Weather");
226  but->setTooltip("Sets the current weather. Does not save weather to map file");
227  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
228  const core::map::Weather::Type type = tempWeatherEntry->selectedWeather();
229  if (mapArea.editMap().weatherSystem().getType() != type) {
230  mapArea.editMap().weatherSystem().set(type);
231  }
232  });
233  row->pack(tempWeatherEntry, false, true);
234  row->pack(but, false, true);
235  box->pack(row, true, false);
236 
237  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
238  timeSetEntry = ComboBox::create();
239  timeSetEntry->addOption("Morning");
240  timeSetEntry->addOption("Noon");
241  timeSetEntry->addOption("Evening");
242  timeSetEntry->addOption("Midnight");
243  timeSetEntry->setSelectedOption(0);
244  but = Button::create("Set Time");
245  but->setTooltip("Sets the current time. Does not save to map file");
246  but->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
247  switch (timeSetEntry->getSelectedOption()) {
248  case 0:
249  systems.clock().set({6, 0}); // 6 am
250  break;
251  case 1:
252  systems.clock().set({12, 0}); // noon
253  break;
254  case 2:
255  systems.clock().set({18, 0}); // 6 pm
256  break;
257  case 3:
258  default:
259  systems.clock().set({0, 0}); // midnight
260  break;
261  }
262  });
263  row->pack(timeSetEntry, false, true);
264  row->pack(but, false, true);
265  box->pack(row, true, false);
266 
267  Notebook::Ptr editBook = Notebook::create();
268  editBook->addPage("tiles", "Tiles", tileBox);
269  editBook->addPage(
270  "layers",
271  "Layers",
272  layerPage.getContent(),
273  [this]() { layerPage.pack(); },
274  [this]() { layerPage.unpack(); });
275  editBook->addPage(
276  "levels",
277  "Levels",
278  levelPage.getContent(),
279  [this]() { levelPage.pack(); },
280  [this]() { levelPage.unpack(); });
281  editBook->addPage("state", "State", box);
282 
283  Box::Ptr spawnBox = Box::create(LinePacker::create(LinePacker::Vertical, 4));
284  box = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
285  spawnCreate = RadioButton::create("Spawn", "spawn");
286  spawnCreate->setTooltip("Create player spawns for when entering the map");
287  spawnCreate->setValue(true);
288  spawnDirEntry = ComboBox::create();
289  spawnDirEntry->addOption("Up");
290  spawnDirEntry->addOption("Right");
291  spawnDirEntry->addOption("Down");
292  spawnDirEntry->addOption("Left");
293  spawnDirEntry->setSelectedOption(0);
294  spawnRotate = RadioButton::create("Rotate", "rotate", spawnCreate->getRadioGroup());
295  Label::Ptr label = Label::create("Delete");
296  label->setColor(sf::Color(200, 20, 20), sf::Color::Transparent);
297  spawnDelete = RadioButton::create(label, "delete", spawnCreate->getRadioGroup());
298  box->pack(spawnCreate);
299  box->pack(spawnDirEntry);
300  spawnBox->pack(box);
301  box = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
302  box->pack(spawnRotate);
303  box->pack(spawnDelete);
304  spawnBox->pack(box);
305 
306  Box::Ptr npcBox = Box::create(LinePacker::create(LinePacker::Vertical, 8));
307  npcSpawn = RadioButton::create("Spawn", "spawn");
308  npcEdit = RadioButton::create("Edit", "edit", npcSpawn->getRadioGroup());
309  label = Label::create("Delete");
310  label->setColor(sf::Color(200, 20, 20), sf::Color::Transparent);
311  npcDelete = RadioButton::create(label, "delete", npcSpawn->getRadioGroup());
312  npcSpawn->setValue(true);
313  npcBox->pack(npcSpawn);
314  npcBox->pack(npcEdit);
315  npcBox->pack(npcDelete);
316 
317  Box::Ptr itemBox = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
318  box = Box::create(LinePacker::create(LinePacker::Vertical, 4));
319  itemSelector = component::ItemSelector::create();
320  itemSpawn = RadioButton::create("Spawn/Edit", "spawn");
321  itemSpawn->setValue(true);
322  itemRead = RadioButton::create("Read", "read", itemSpawn->getRadioGroup());
323  itemRead->setTooltip("Populate this form with details from an item in the map");
324  itemHidden = CheckButton::create("Hidden item");
325  itemHidden->setTooltip("Check this to make the item invisible");
326  label = Label::create("Delete");
327  label->setColor(sf::Color(200, 20, 20), sf::Color::Transparent);
328  itemDelete = RadioButton::create(label, "delete", itemSpawn->getRadioGroup());
329  box->pack(itemSpawn);
330  box->pack(itemRead);
331  box->pack(itemDelete);
332  itemBox->pack(box, true, false);
333  box = Box::create(LinePacker::create(LinePacker::Vertical, 4));
334  box->pack(itemSelector, true, false);
335  box->pack(itemHidden);
336  itemBox->pack(box, true, true);
337 
338  Box::Ptr lightBox = Box::create(LinePacker::create(LinePacker::Vertical, 4));
339  box = Box::create(LinePacker::create(LinePacker::Horizontal, 6));
340  lightCreateOrEdit = RadioButton::create("Create/Modify", "create");
341  lightCreateOrEdit->setValue(true);
342  lightRadiusEntry = TextEntry::create();
343  lightRadiusEntry->setMode(TextEntry::Mode::Integer);
344  lightRadiusEntry->setRequisition({80, 0});
345  lightRadiusEntry->setInput("100");
346  box->pack(lightCreateOrEdit, false, false);
347  box->pack(Label::create("Radius (pixels):"), false, false);
348  box->pack(lightRadiusEntry, true, false);
349  lightBox->pack(box, true, false);
350  label = Label::create("Delete");
351  label->setColor(sf::Color(200, 20, 20), sf::Color::Transparent);
352  lightRemove = RadioButton::create(label, "delete", lightCreateOrEdit->getRadioGroup());
353  lightBox->pack(lightRemove);
354 
355  objectBook = Notebook::create();
356  objectBook->addPage("spawns", "Spawns", spawnBox, [this]() { activeTool = Tool::Spawns; });
357  objectBook->addPage("ai", "Characters", npcBox, [this]() { activeTool = Tool::NPCs; });
358  objectBook->addPage("items", "Items", itemBox, [this]() { activeTool = Tool::Items; });
359  objectBook->addPage("lights", "Lights", lightBox, [this]() { activeTool = Tool::Lights; });
360 
361  Box::Ptr eventBox = Box::create(LinePacker::create(LinePacker::Horizontal, 2));
362  ScrollArea::Ptr scroll = ScrollArea::create(LinePacker::create(LinePacker::Vertical, 6));
363  scroll->setMaxSize({320.f, 3000.f});
364  scroll->setNeverShowVerticalScrollbar(true);
365  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
366  row->pack(Label::create("Enter:"));
367  onEnterLabel = Label::create("onEnter.bs");
368  onEnterLabel->setColor(sf::Color(20, 20, 220), sf::Color::Transparent);
369  row->pack(onEnterLabel, true, false);
370  scroll->pack(row, true, false);
371  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
372  row->pack(Label::create("Exit:"));
373  onExitLabel = Label::create("onExit.bs");
374  onExitLabel->setColor(sf::Color(20, 20, 220), sf::Color::Transparent);
375  row->pack(onExitLabel, true, false);
376  scroll->pack(row, true, false);
377  row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
378  Button::Ptr pickButton = Button::create("Set OnEnter");
379  pickButton->setTooltip("Set the script that runs when the player enters the map");
380  pickButton->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
381  choosingOnloadScript = true;
382  mapArea.disableControls();
383  scriptSelector.open(parent, mapArea.editMap().getOnEnterScript());
384  });
385  row->pack(pickButton);
386  pickButton = Button::create("Set OnExit");
387  pickButton->setTooltip("Set the script that runs when the player exits the map");
388  pickButton->getSignal(Event::LeftClicked).willAlwaysCall([this](const Event&, Element*) {
389  choosingOnloadScript = false;
390  mapArea.disableControls();
391  scriptSelector.open(parent, mapArea.editMap().getOnExitScript());
392  });
393  row->pack(pickButton);
394  scroll->pack(row, true, false);
395  eventBox->pack(scroll, true, true);
396  box = Box::create(LinePacker::create(LinePacker::Vertical, 4));
397  createEventRadio = RadioButton::create("Create Event", "create");
398  createEventRadio->setValue(true);
399  label = Label::create("Delete Event");
400  label->setColor(sf::Color(200, 20, 20), sf::Color::Transparent);
401  box->pack(createEventRadio);
402  editEventRadio = RadioButton::create("Edit Event", "edit", createEventRadio->getRadioGroup());
403  box->pack(editEventRadio);
404  deleteEventRadio = RadioButton::create(label, "delete", createEventRadio->getRadioGroup());
405  box->pack(deleteEventRadio);
406  eventBox->pack(Separator::create(Separator::Vertical));
407  eventBox->pack(box, false, true);
408 
409  const auto editClosed = [this]() {
410  layerPage.unpack();
411  levelPage.unpack();
412  };
413 
414  RadioButton::Group* tilesetButGroup = tileSetBut->getRadioGroup();
415  const auto editOpened = [this, editBook, tilesetButGroup]() {
416  activeTool = Tool::MapEdit;
417  activeSubtool = Subtool::None;
418 
419  if (editBook->getActivePageName() == "tiles") {
420  if (tilesetButGroup->getActiveButton()->getName() == "set") {
421  activeSubtool = Subtool::Set;
422  }
423  else if (tilesetButGroup->getActiveButton()->getName() == "clear") {
424  activeSubtool = Subtool::Clear;
425  }
426  else if (tilesetButGroup->getActiveButton()->getName() == "select") {
427  activeSubtool = Subtool::Select;
428  }
429  }
430  else if (editBook->getActivePageName() == "layers")
431  layerPage.pack();
432  else if (editBook->getActivePageName() == "levels")
433  levelPage.pack();
434  };
435 
436  const auto onObjectActive = [this]() {
437  if (objectBook->getActivePageName() == "spawns") { activeTool = Tool::Spawns; }
438  else if (objectBook->getActivePageName() == "items") { activeTool = Tool::Items; }
439  else if (objectBook->getActivePageName() == "ai") { activeTool = Tool::NPCs; }
440  else if (objectBook->getActivePageName() == "lights") { activeTool = Tool::Lights; }
441  };
442 
443  Notebook::Ptr controlBook = Notebook::create();
444  controlBook->addPage("map", "Props", infoBox, [this]() { activeTool = Tool::Metadata; });
445  controlBook->addPage(
446  "lighting",
447  "Lighting",
448  lightingOuterBox,
449  [lightingBox, lightingOuterBox]() { lightingOuterBox->pack(lightingBox, true, true); },
450  [lightingBox]() { lightingBox->remove(); });
451  controlBook->addPage("edit", "Map", editBook, editOpened, editClosed);
452  controlBook->addPage("obj", "Entities", objectBook, onObjectActive);
453  controlBook->addPage("events", "Scripts", eventBox, [this]() { activeTool = Tool::Events; });
454  controlBook->addPage(
455  "testing", "Testing", testingTab.getContent(), [this]() { activeTool = Tool::Testing; });
456 
457  controlPane->pack(mapCtrlBox, true, false);
458  controlPane->pack(controlBook, true, false);
459  controlPane->pack(tileset.getContent(), true, true);
460 
461  content->pack(controlPane, false, true);
462  content->pack(mapArea.getContent(), true, true);
463 
464  mapArea.editMap().editorLoad("WorldMap.map");
465 }
466 
467 void Map::update(float) {
468  switch (tileset.getActiveTool()) {
469  case Tileset::CollisionTiles:
470  mapArea.editMap().setRenderOverlay(component::EditMap::RenderOverlay::Collisions,
471  levelSelect->getSelectedOption());
472  break;
473 
474  case Tileset::CatchTiles:
475  mapArea.editMap().setRenderOverlay(component::EditMap::RenderOverlay::CatchTiles,
476  levelSelect->getSelectedOption());
477  break;
478 
479  case Tileset::TownTiles:
480  mapArea.editMap().setRenderOverlay(activeTool == Tool::Spawns ?
483  0);
484  break;
485 
486  case Tileset::LevelTiles:
487  mapArea.editMap().setRenderOverlay(component::EditMap::RenderOverlay::LevelTransitions, 0);
488  break;
489 
490  default:
491  switch (activeTool) {
492  case Tool::Events:
493  mapArea.editMap().setRenderOverlay(component::EditMap::RenderOverlay::Events, 0);
494  break;
495 
496  case Tool::Spawns:
497  mapArea.editMap().setRenderOverlay(component::EditMap::RenderOverlay::Spawns,
498  levelSelect->getSelectedOption());
499  break;
500 
501  default:
502  mapArea.editMap().setRenderOverlay(component::EditMap::RenderOverlay::None, 0);
503  break;
504  }
505  break;
506  }
507 
508  if (mapArea.editMap().unsavedChanges() || tileset.unsavedChanges()) {
509  saveMapBut->setColor(sf::Color(200, 185, 20), sf::Color::Red);
510  }
511  else { saveMapBut->setColor(sf::Color::Green, sf::Color::Black); }
512 }
513 
514 void Map::setSelectionState(SelectionState ns) {
515  selectionState = ns;
516  if (selectionState == SelectionMade) { mapArea.editMap().showSelection(selection); }
517  else if (selectionState == Selecting) {
518  mapArea.editMap().showSelection({selection.left, selection.top, -1, -1});
519  }
520  else { mapArea.editMap().showSelection({0, 0, 0, 0}); }
521 }
522 
523 void Map::doLoadMap(const std::string& file) {
524  mapPicker.close();
525 
526  if (makingNewMap) {
527  const std::string f =
528  bl::util::FileUtil::getExtension(file) == "map" ? file : file + ".map";
529  newMapWindow.show(parent, f);
530  }
531  else {
532  if (!mapArea.editMap().editorLoad(file)) {
533  bl::dialog::tinyfd_messageBox("Error Loading Map",
534  std::string("Failed to load map: " + file).c_str(),
535  "ok",
536  "error",
537  1);
538  }
539  }
540 
541  syncGui();
542 }
543 
544 void Map::makeNewMap(const std::string& file, const std::string& name, const std::string& tileset,
545  unsigned int w, unsigned int h) {
546  mapArea.editMap().newMap(file, name, tileset, w, h);
547 }
548 
549 void Map::onMapClick(const sf::Vector2f& pixels, const sf::Vector2i& tiles) {
550  BL_LOG_INFO << "Clicked (" << tiles.x << ", " << tiles.y << ")";
551 
552  switch (activeTool) {
553  case Tool::MapEdit:
554  switch (activeSubtool) {
555  case Subtool::Set:
556  switch (tileset.getActiveTool()) {
557  case Tileset::Tiles:
558  if (selectionState == SelectionMade) {
559  mapArea.editMap().setTileArea(levelSelect->getSelectedOption(),
560  layerSelect->getSelectedOption(),
561  selection,
562  tileset.getActiveTile(),
563  false);
564  }
565  else {
566  mapArea.editMap().setTile(levelSelect->getSelectedOption(),
567  layerSelect->getSelectedOption(),
568  tiles,
569  tileset.getActiveTile(),
570  false);
571  }
572  break;
573  case Tileset::Animations:
574  if (selectionState == SelectionMade) {
575  mapArea.editMap().setTileArea(levelSelect->getSelectedOption(),
576  layerSelect->getSelectedOption(),
577  selection,
578  tileset.getActiveAnim(),
579  true);
580  }
581  else {
582  mapArea.editMap().setTile(levelSelect->getSelectedOption(),
583  layerSelect->getSelectedOption(),
584  tiles,
585  tileset.getActiveAnim(),
586  true);
587  }
588  break;
589  case Tileset::CollisionTiles:
590  if (selectionState == SelectionMade) {
591  mapArea.editMap().setCollisionArea(
592  levelSelect->getSelectedOption(), selection, tileset.getActiveCollision());
593  }
594  else {
595  mapArea.editMap().setCollision(
596  levelSelect->getSelectedOption(), tiles, tileset.getActiveCollision());
597  }
598  break;
599  case Tileset::CatchTiles:
600  if (selectionState == SelectionMade) {
601  mapArea.editMap().setCatchArea(
602  levelSelect->getSelectedOption(), selection, tileset.getActiveCatch());
603  }
604  else {
605  mapArea.editMap().setCatch(
606  levelSelect->getSelectedOption(), tiles, tileset.getActiveCatch());
607  }
608  break;
609  case Tileset::TownTiles:
610  if (selectionState == SelectionMade) {
611  mapArea.editMap().setTownTileArea(selection, tileset.getActiveTown());
612  }
613  else { mapArea.editMap().setTownTile(tiles, tileset.getActiveTown()); }
614  break;
615  case Tileset::LevelTiles:
616  if (selectionState == SelectionMade) {
617  mapArea.editMap().setLevelTileArea(selection, tileset.getActiveLevel());
618  }
619  else { mapArea.editMap().setLevelTile(tiles, tileset.getActiveLevel()); }
620  break;
621  default:
622  break;
623  }
624  break;
625 
626  case Subtool::Fill:
627  switch (tileset.getActiveTool()) {
628  case Tileset::Tiles:
629  mapArea.editMap().fillTile(levelSelect->getSelectedOption(),
630  layerSelect->getSelectedOption(),
631  tiles,
632  tileset.getActiveTile(),
633  false);
634  break;
635  case Tileset::Animations:
636  mapArea.editMap().fillTile(levelSelect->getSelectedOption(),
637  layerSelect->getSelectedOption(),
638  tiles,
639  tileset.getActiveTile(),
640  true);
641  break;
642  case Tileset::CollisionTiles:
643  mapArea.editMap().fillCollision(
644  levelSelect->getSelectedOption(), tiles, tileset.getActiveCollision());
645  break;
646  case Tileset::TownTiles:
647  mapArea.editMap().fillTownTiles(tiles, tileset.getActiveTown());
648  break;
649  case Tileset::CatchTiles:
650  mapArea.editMap().fillCatch(
651  levelSelect->getSelectedOption(), tiles, tileset.getActiveCatch());
652  break;
653  default:
654  break;
655  }
656  break;
657 
658  case Subtool::Clear:
659  switch (tileset.getActiveTool()) {
660  case Tileset::Tiles:
661  case Tileset::Animations:
662  if (selectionState == SelectionMade) {
663  mapArea.editMap().setTileArea(levelSelect->getSelectedOption(),
664  layerSelect->getSelectedOption(),
665  selection,
667  false);
668  }
669  else {
670  mapArea.editMap().setTile(levelSelect->getSelectedOption(),
671  layerSelect->getSelectedOption(),
672  tiles,
674  false);
675  }
676  break;
677  case Tileset::CollisionTiles:
678  if (selectionState == SelectionMade) {
679  mapArea.editMap().setCollisionArea(
680  levelSelect->getSelectedOption(), selection, core::map::Collision::Open);
681  }
682  else {
683  mapArea.editMap().setCollision(
684  levelSelect->getSelectedOption(), tiles, core::map::Collision::Open);
685  }
686  break;
687  case Tileset::CatchTiles:
688  if (selectionState == SelectionMade) {
689  mapArea.editMap().setCatchArea(levelSelect->getSelectedOption(), selection, 0);
690  }
691  else { mapArea.editMap().setCatch(levelSelect->getSelectedOption(), tiles, 0); }
692  break;
693  case Tileset::TownTiles:
694  if (selectionState == SelectionMade) {
695  mapArea.editMap().setTownTileArea(selection, 0);
696  }
697  else { mapArea.editMap().setTownTile(tiles, 0); }
698  break;
699  default:
700  break;
701  }
702  break;
703 
704  case Subtool::Select:
705  switch (selectionState) {
706  case SelectionMade:
707  case NoSelection:
708  selection.left = tiles.x;
709  selection.top = tiles.y;
710  setSelectionState(Selecting);
711  break;
712  case Selecting: {
713  const int minX = std::min(selection.left, tiles.x);
714  const int minY = std::min(selection.top, tiles.y);
715  const int maxX = std::max(selection.left, tiles.x);
716  const int maxY = std::max(selection.top, tiles.y);
717  selection = {minX, minY, maxX - minX + 1, maxY - minY + 1};
718  setSelectionState(SelectionMade);
719  } break;
720  default:
721  setSelectionState(NoSelection);
722  break;
723  }
724  break;
725 
726  default:
727  break;
728  }
729  break;
730 
731  case Tool::Events:
732  if (createEventRadio->getValue()) {
733  mapArea.disableControls();
734  eventEditor.open(parent, nullptr, tiles);
735  }
736  else if (editEventRadio->getValue()) {
737  const core::map::Event* e = mapArea.editMap().getEvent(tiles);
738  if (e) {
739  mapArea.disableControls();
740  eventEditor.open(parent, e, tiles);
741  }
742  }
743  else if (deleteEventRadio->getValue()) {
744  const core::map::Event* e = mapArea.editMap().getEvent(tiles);
745  if (e) {
746  std::stringstream ss;
747  ss << e->script << '\n';
748  ss << "Delete event?";
749  std::string s = ss.str();
750  for (char& c : s) {
751  if (c == '"' || c == '\'') { c = '`'; }
752  }
753  if (1 == bl::dialog::tinyfd_messageBox(
754  "Delete Event?", s.c_str(), "yesno", "warning", 0)) {
755  mapArea.editMap().removeEvent(e);
756  }
757  }
758  }
759  break;
760 
761  case Tool::Spawns:
762  if (spawnCreate->getValue()) {
763  const char* id = bl::dialog::tinyfd_inputBox("Spawn ID", "Enter spawn id: ", "0");
764  if (id) {
765  if (isNum(id)) {
766  const unsigned int n = std::atoi(id);
767  if (mapArea.editMap().spawnIdUnused(n)) {
768  mapArea.editMap().addSpawn(
769  levelSelect->getSelectedOption(),
770  tiles,
771  n,
772  static_cast<bl::tmap::Direction>(spawnDirEntry->getSelectedOption()));
773  }
774  }
775  }
776  }
777  else if (spawnRotate->getValue()) {
778  mapArea.editMap().rotateSpawn(levelSelect->getSelectedOption(), tiles);
779  }
780  else if (spawnDelete->getValue()) {
781  mapArea.editMap().removeSpawn(levelSelect->getSelectedOption(), tiles);
782  }
783  break;
784 
785  case Tool::NPCs:
786  if (npcSpawn->getValue()) {
787  const core::map::CharacterSpawn* s =
788  mapArea.editMap().getNpcSpawn(levelSelect->getSelectedOption(), tiles);
789  if (!s) {
790  mapArea.disableControls();
791  characterEditor.open(parent, levelSelect->getSelectedOption(), tiles, nullptr);
792  }
793  }
794  else if (npcEdit->getValue()) {
795  const core::map::CharacterSpawn* s =
796  mapArea.editMap().getNpcSpawn(levelSelect->getSelectedOption(), tiles);
797  if (s) {
798  mapArea.disableControls();
799  characterEditor.open(parent, levelSelect->getSelectedOption(), tiles, s);
800  }
801  }
802  else if (npcDelete->getValue()) {
803  const core::map::CharacterSpawn* s =
804  mapArea.editMap().getNpcSpawn(levelSelect->getSelectedOption(), tiles);
805  if (s) {
806  const std::string msg = "Delete spawn for character: " + s->file + "?";
807  if (1 == bl::dialog::tinyfd_messageBox(
808  "Delete Character?", msg.c_str(), "yesno", "warning", 0)) {
809  mapArea.editMap().removeNpcSpawn(s);
810  }
811  }
812  }
813  break;
814 
815  case Tool::Items:
816  if (itemSpawn->getValue()) {
817  mapArea.editMap().addOrEditItem(levelSelect->getSelectedOption(),
818  tiles,
819  itemSelector->currentItem(),
820  !itemHidden->getValue());
821  }
822  else if (itemRead->getValue()) {
823  const auto i = mapArea.editMap().getItem(levelSelect->getSelectedOption(), tiles);
824  if (i.first != core::item::Id::Unknown) {
825  itemSelector->setItem(i.first);
826  itemHidden->setValue(!i.second);
827  }
828  }
829  else if (itemDelete->getValue()) {
830  mapArea.editMap().removeItem(levelSelect->getSelectedOption(), tiles);
831  }
832  break;
833 
834  case Tool::Lights:
835  if (lightCreateOrEdit->getValue()) {
836  mapArea.editMap().setLight(sf::Vector2i(pixels),
837  std::atoi(lightRadiusEntry->getInput().c_str()));
838  }
839  else if (lightRemove->getValue()) { mapArea.editMap().removeLight(sf::Vector2i(pixels)); }
840  break;
841 
842  case Tool::Testing:
843  testingTab.notifyClick(
844  mapArea.editMap().currentFile(), levelSelect->getSelectedOption(), tiles);
845  break;
846 
847  case Tool::Metadata:
848  default:
849  break;
850  }
851 }
852 
853 void Map::onChoosePlaylist(const std::string& file) {
854  mapArea.editMap().setPlaylist(file);
855  playlistLabel->setText(file);
856 }
857 
858 bool Map::checkUnsaved() {
859  if (mapArea.editMap().unsavedChanges()) {
860  return bl::dialog::tinyfd_messageBox(
861  "Unsaved Changes",
862  std::string(mapArea.editMap().name() + " has unsaved changes, discard them?")
863  .c_str(),
864  "yesno",
865  "warning",
866  0) == 1;
867  }
868  return true;
869 }
870 
871 void Map::syncGui() {
872  tileset.loadTileset(mapArea.editMap().tilesetField);
873  tileset.refresh();
874  tileset.setGUI(parent);
875  testingTab.registerGUI(parent);
876  levelSelect->clearOptions();
877  for (unsigned int i = 0; i < mapArea.editMap().levelCount(); ++i) {
878  levelSelect->addOption("Level " + std::to_string(i));
879  }
880  levelSelect->setSelectedOption(0);
881  onLevelChange(0);
882  levelPage.sync(mapArea.editMap().levelFilter);
883  layerPage.sync(mapArea.editMap().levels, mapArea.editMap().layerFilter);
884 
885  nameEntry->setInput(mapArea.editMap().name());
886  playlistLabel->setText(mapArea.editMap().playlistField);
887  weatherEntry->setSelectedOption(static_cast<int>(mapArea.editMap().weatherField));
888 
889  onEnterLabel->setText(mapArea.editMap().getOnEnterScript());
890  onExitLabel->setText(mapArea.editMap().getOnExitScript());
891 
892  syncLighting();
893 }
894 
895 void Map::onLevelChange(unsigned int l) {
896  if (l >= mapArea.editMap().levels.size()) {
897  BL_LOG_ERROR << "Out of range level: " << l;
898  return;
899  }
900 
901  auto& level = mapArea.editMap().levels[l];
902  const unsigned int bc = level.bottomLayers().size();
903  const unsigned int yc = level.ysortLayers().size();
904  const unsigned int tc = level.topLayers().size();
905 
906  layerSelect->clearOptions();
907  unsigned int i = 0;
908  for (unsigned int j = 0; j < bc; ++j, ++i) {
909  layerSelect->addOption("Layer " + std::to_string(i));
910  }
911  for (unsigned int j = 0; j < yc; ++j, ++i) {
912  layerSelect->addOption("Layer " + std::to_string(i) + " (ysort)");
913  }
914  for (unsigned int j = 0; j < tc; ++j, ++i) {
915  layerSelect->addOption("Layer " + std::to_string(i));
916  }
917  layerSelect->setSelectedOption(0);
918 }
919 
920 void Map::onChooseScript(const std::string& s) {
921  if (choosingOnloadScript) { mapArea.editMap().setOnEnterScript(s); }
922  else { mapArea.editMap().setOnExitScript(s); }
923 }
924 
925 void Map::onEventEdit(const core::map::Event* orig, const core::map::Event& val) {
926  if (orig) { mapArea.editMap().editEvent(orig, val); }
927  else { mapArea.editMap().createEvent(val); }
928  mapArea.enableControls();
929 }
930 
931 void Map::onCharacterEdit(const core::map::CharacterSpawn* orig,
932  const core::map::CharacterSpawn& spawn) {
933  if (orig) { mapArea.editMap().editNpcSpawn(orig, spawn); }
934  else { mapArea.editMap().addNpcSpawn(spawn); }
935  mapArea.enableControls();
936 }
937 
938 void Map::onLightingChange() {
939  if (sunlightBut->getValue()) {
940  minLightSlider->setVisible(true);
941  maxLightSlider->setPrompt("Max Light");
942  }
943  else {
944  minLightSlider->setVisible(false);
945  maxLightSlider->setPrompt("Ambient Light");
946  }
947  lightingSetBut->setColor(sf::Color::Yellow, sf::Color::Black);
948 }
949 
950 void Map::onLightingReset() {
951  syncLighting();
952  lightingSetBut->setColor(sf::Color::Green, sf::Color::Black);
953 }
954 
955 void Map::onLightingSave() {
956  mapArea.editMap().setAmbientLight(
957  minLightSlider->getLightLevel(), maxLightSlider->getLightLevel(), sunlightBut->getValue());
958  lightingSetBut->setColor(sf::Color::Green, sf::Color::Black);
959 }
960 
961 void Map::syncLighting() {
962  const auto& lighting = mapArea.editMap().lightingSystem();
963  const bool sunlight = lighting.adjustsForSunlight();
964  sunlightBut->setValue(sunlight);
965  minLightSlider->setLightLevel(lighting.getMinLightLevel());
966  maxLightSlider->setLightLevel(lighting.getMaxLightLevel());
967 
968  if (sunlight) {
969  minLightSlider->setVisible(true);
970  maxLightSlider->setPrompt("Max Light");
971  }
972  else {
973  minLightSlider->setVisible(false);
974  maxLightSlider->setPrompt("Ambient Light");
975  }
976 
977  lightingSetBut->setColor(sf::Color::Green, sf::Color::Black);
978 }
979 
980 void Map::setLightingDefault() {
981  minLightSlider->setLightLevel(75);
982  maxLightSlider->setLightLevel(255);
983  lightingSetBut->setColor(sf::Color::Yellow, sf::Color::Black);
984 }
985 
986 void Map::startMapRender() {
987  mapArea.disableControls();
988  renderMapWindow.open(parent);
989 }
990 
991 void Map::doMapRender() { mapArea.editMap().staticRender(renderMapWindow); }
992 
993 } // namespace page
994 } // namespace editor
All classes and functionality used for implementing the game editor.
Definition: Tile.hpp:11
Represents a character to be spawned into a map on load.
Represents an event in a Map. A script that is run on a trigger within a given region.
Definition: Event.hpp:19
std::string script
Definition: Event.hpp:41
static constexpr IdType Blank
Special id for blank tiles.
Definition: Tile.hpp:36
std::uint16_t IdType
Definition: Tile.hpp:33
Type
The type of weather.
Definition: Weather.hpp:38
static const std::string & MapPath()
Definition: Properties.cpp:339
Owns all primary systems and a reference to the engine.
Definition: Systems.hpp:47
void appendBottomLayer(unsigned int level)
Creates a new (empty) bottom layer.
Definition: EditMap.cpp:598
void setLevelVisible(unsigned int level, bool visible)
Shows or hides the given level.
Definition: EditMap.cpp:297
@ Collisions
Renders collisions for the current level.
@ Towns
Renders colored tiles to indicate towns/routes.
@ LevelTransitions
Renders level transitions.
@ CatchTiles
Renders catch tiles for the current level.
@ Events
Renders events in the map.
@ Spawns
Renders spawns in the map.
static Ptr create(const ChangeCb &cb=[](core::item::Id) {})
Creates a new ItemSelector.
Definition: ItemSelector.cpp:9
static Ptr create(const std::string &prompt, const ChangeCb &onChange)
Create a new light level slider.
Definition: LightSlider.cpp:9
static Ptr create()
Creates a new weather selector.
Map(core::system::Systems &systems)
Construct a new Map page.
Definition: Map.cpp:21
void syncGui()
Updates the GUI elements to sync with the data.
Definition: Map.cpp:871
Base class for all editor pages.
Definition: Page.hpp:32
component::EditMap & editMap()
Returns the contained map.
Definition: MapArea.cpp:65