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;
19 using namespace bl::gui;
23 , mapArea([this](const sf::Vector2f& p, const sf::Vector2i& t) { onMapClick(p, t); },
28 mapArea.editMap().removeAllTiles(
id, isAnim);
32 [
this](
unsigned int l,
bool up) { mapArea.editMap().shiftLevel(l, up); },
33 [
this]() { mapArea.editMap().appendLevel(); })
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);
40 [
this](
unsigned int level,
unsigned int layer,
bool up) {
41 mapArea.editMap().shiftLayer(level, layer, up);
43 [
this](
unsigned int level,
unsigned int layer,
bool visible) {
44 mapArea.editMap().setLayerVisible(level, layer, visible);
46 , activeTool(Tool::Metadata)
47 , activeSubtool(Subtool::Set)
48 , selectionState(NoSelection)
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))
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));
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*) {
70 mapArea.disableControls();
71 mapPicker.open(FilePicker::CreateNew,
"New map", parent);
74 Button::Ptr loadMapBut = Button::create(
"Load Map");
75 loadMapBut->getSignal(Event::LeftClicked).willCall([
this](
const Event&, Element*) {
78 mapArea.disableControls();
79 mapPicker.open(FilePicker::PickExisting,
"Load map", parent);
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(
87 std::string(
"Failed to save map: " + mapArea.editMap().name()).c_str(),
92 else { tileset.markSaved(); }
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);
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);
126 lightingBox->pack(minLightSlider,
true,
false);
127 lightingBox->pack(maxLightSlider,
true,
false);
129 Box::Ptr tileBox = Box::create(LinePacker::create(LinePacker::Vertical, 4));
130 Box::Ptr box = Box::create(LinePacker::create(LinePacker::Horizontal, 4, LinePacker::Uniform));
132 levelSelect = ComboBox::create();
133 levelSelect->getSignal(Event::ValueChanged).willAlwaysCall([
this](
const Event&, Element* e) {
134 onLevelChange(
dynamic_cast<ComboBox*
>(e)->getSelectedOption());
136 box->pack(levelSelect,
true,
true);
138 layerSelect = ComboBox::create();
139 box->pack(layerSelect,
true,
true);
140 tileBox->pack(box,
true,
false);
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;
148 RadioButton::Ptr fillBut = RadioButton::create(
"Fill",
"fill", tileSetBut->getRadioGroup());
149 fillBut->getSignal(Event::LeftClicked).willAlwaysCall([
this](
const Event&, Element*) {
150 activeSubtool = Subtool::Fill;
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;
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;
162 Button::Ptr tileDeselectBut = Button::create(
"Deselect");
163 tileDeselectBut->getSignal(Event::LeftClicked).willAlwaysCall([
this](
const Event&, Element*) {
164 setSelectionState(NoSelection);
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);
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);
180 Box::Ptr infoBox = Box::create(LinePacker::create(LinePacker::Vertical, 4));
181 row = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
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());
188 box->pack(Label::create(
"Name:"),
false,
true);
189 box->pack(nameEntry,
true,
true);
190 row->pack(box,
true,
false);
192 Button::Ptr resizeBut = Button::create(
"Resize Map");
194 row->pack(resizeBut);
195 infoBox->pack(row,
true,
false);
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());
204 playlistLabel->setHorizontalAlignment(RenderSettings::Left);
205 row->pack(pickPlaylistBut);
206 row->pack(playlistLabel,
true,
false);
207 infoBox->pack(row,
true,
false);
209 row = Box::create(LinePacker::create(LinePacker::Horizontal, 6));
210 row->pack(Label::create(
"Weather:"));
212 weatherEntry->setTooltip(
"Set the weather for the entire map");
213 weatherEntry->getSignal(Event::ValueChanged).willAlwaysCall([
this](
const Event&, Element*) {
215 if (mapArea.editMap().weatherSystem().getType() != type) {
216 mapArea.editMap().setWeather(type);
219 row->pack(weatherEntry);
220 infoBox->pack(row,
true,
false);
222 box = Box::create(LinePacker::create(LinePacker::Vertical, 4.f));
223 row = Box::create(LinePacker::create(LinePacker::Horizontal, 4.f));
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*) {
229 if (mapArea.editMap().weatherSystem().getType() != type) {
230 mapArea.editMap().weatherSystem().set(type);
233 row->pack(tempWeatherEntry,
false,
true);
234 row->pack(but,
false,
true);
235 box->pack(row,
true,
false);
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()) {
249 systems.clock().set({6, 0});
252 systems.clock().set({12, 0});
255 systems.clock().set({18, 0});
259 systems.clock().set({0, 0});
263 row->pack(timeSetEntry,
false,
true);
264 row->pack(but,
false,
true);
265 box->pack(row,
true,
false);
267 Notebook::Ptr editBook = Notebook::create();
268 editBook->addPage(
"tiles",
"Tiles", tileBox);
272 layerPage.getContent(),
273 [
this]() { layerPage.pack(); },
274 [
this]() { layerPage.unpack(); });
278 levelPage.getContent(),
279 [
this]() { levelPage.pack(); },
280 [
this]() { levelPage.unpack(); });
281 editBook->addPage(
"state",
"State", box);
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);
301 box = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
302 box->pack(spawnRotate);
303 box->pack(spawnDelete);
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);
317 Box::Ptr itemBox = Box::create(LinePacker::create(LinePacker::Horizontal, 4));
318 box = Box::create(LinePacker::create(LinePacker::Vertical, 4));
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);
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);
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);
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; });
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());
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());
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);
409 const auto editClosed = [
this]() {
414 RadioButton::Group* tilesetButGroup = tileSetBut->getRadioGroup();
415 const auto editOpened = [
this, editBook, tilesetButGroup]() {
416 activeTool = Tool::MapEdit;
419 if (editBook->getActivePageName() ==
"tiles") {
420 if (tilesetButGroup->getActiveButton()->getName() ==
"set") {
421 activeSubtool = Subtool::Set;
423 else if (tilesetButGroup->getActiveButton()->getName() ==
"clear") {
424 activeSubtool = Subtool::Clear;
426 else if (tilesetButGroup->getActiveButton()->getName() ==
"select") {
427 activeSubtool = Subtool::Select;
430 else if (editBook->getActivePageName() ==
"layers")
432 else if (editBook->getActivePageName() ==
"levels")
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; }
443 Notebook::Ptr controlBook = Notebook::create();
444 controlBook->addPage(
"map",
"Props", infoBox, [
this]() { activeTool = Tool::Metadata; });
445 controlBook->addPage(
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; });
457 controlPane->pack(mapCtrlBox,
true,
false);
458 controlPane->pack(controlBook,
true,
false);
459 controlPane->pack(tileset.getContent(),
true,
true);
461 content->pack(controlPane,
false,
true);
462 content->pack(mapArea.getContent(),
true,
true);
464 mapArea.editMap().editorLoad(
"WorldMap.map");
467 void Map::update(
float) {
468 switch (tileset.getActiveTool()) {
469 case Tileset::CollisionTiles:
471 levelSelect->getSelectedOption());
474 case Tileset::CatchTiles:
476 levelSelect->getSelectedOption());
479 case Tileset::TownTiles:
480 mapArea.editMap().setRenderOverlay(activeTool == Tool::Spawns ?
486 case Tileset::LevelTiles:
491 switch (activeTool) {
498 levelSelect->getSelectedOption());
508 if (mapArea.editMap().unsavedChanges() || tileset.unsavedChanges()) {
509 saveMapBut->setColor(sf::Color(200, 185, 20), sf::Color::Red);
511 else { saveMapBut->setColor(sf::Color::Green, sf::Color::Black); }
514 void Map::setSelectionState(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});
520 else { mapArea.editMap().showSelection({0, 0, 0, 0}); }
523 void Map::doLoadMap(
const std::string& file) {
527 const std::string f =
528 bl::util::FileUtil::getExtension(file) ==
"map" ? file : file +
".map";
529 newMapWindow.show(parent, f);
532 if (!mapArea.editMap().editorLoad(file)) {
533 bl::dialog::tinyfd_messageBox(
"Error Loading Map",
534 std::string(
"Failed to load map: " + file).c_str(),
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);
549 void Map::onMapClick(
const sf::Vector2f& pixels,
const sf::Vector2i& tiles) {
550 BL_LOG_INFO <<
"Clicked (" << tiles.x <<
", " << tiles.y <<
")";
552 switch (activeTool) {
554 switch (activeSubtool) {
556 switch (tileset.getActiveTool()) {
558 if (selectionState == SelectionMade) {
559 mapArea.editMap().setTileArea(levelSelect->getSelectedOption(),
560 layerSelect->getSelectedOption(),
562 tileset.getActiveTile(),
566 mapArea.editMap().setTile(levelSelect->getSelectedOption(),
567 layerSelect->getSelectedOption(),
569 tileset.getActiveTile(),
573 case Tileset::Animations:
574 if (selectionState == SelectionMade) {
575 mapArea.editMap().setTileArea(levelSelect->getSelectedOption(),
576 layerSelect->getSelectedOption(),
578 tileset.getActiveAnim(),
582 mapArea.editMap().setTile(levelSelect->getSelectedOption(),
583 layerSelect->getSelectedOption(),
585 tileset.getActiveAnim(),
589 case Tileset::CollisionTiles:
590 if (selectionState == SelectionMade) {
591 mapArea.editMap().setCollisionArea(
592 levelSelect->getSelectedOption(), selection, tileset.getActiveCollision());
595 mapArea.editMap().setCollision(
596 levelSelect->getSelectedOption(), tiles, tileset.getActiveCollision());
599 case Tileset::CatchTiles:
600 if (selectionState == SelectionMade) {
601 mapArea.editMap().setCatchArea(
602 levelSelect->getSelectedOption(), selection, tileset.getActiveCatch());
605 mapArea.editMap().setCatch(
606 levelSelect->getSelectedOption(), tiles, tileset.getActiveCatch());
609 case Tileset::TownTiles:
610 if (selectionState == SelectionMade) {
611 mapArea.editMap().setTownTileArea(selection, tileset.getActiveTown());
613 else { mapArea.editMap().setTownTile(tiles, tileset.getActiveTown()); }
615 case Tileset::LevelTiles:
616 if (selectionState == SelectionMade) {
617 mapArea.editMap().setLevelTileArea(selection, tileset.getActiveLevel());
619 else { mapArea.editMap().setLevelTile(tiles, tileset.getActiveLevel()); }
627 switch (tileset.getActiveTool()) {
629 mapArea.editMap().fillTile(levelSelect->getSelectedOption(),
630 layerSelect->getSelectedOption(),
632 tileset.getActiveTile(),
635 case Tileset::Animations:
636 mapArea.editMap().fillTile(levelSelect->getSelectedOption(),
637 layerSelect->getSelectedOption(),
639 tileset.getActiveTile(),
642 case Tileset::CollisionTiles:
643 mapArea.editMap().fillCollision(
644 levelSelect->getSelectedOption(), tiles, tileset.getActiveCollision());
646 case Tileset::TownTiles:
647 mapArea.editMap().fillTownTiles(tiles, tileset.getActiveTown());
649 case Tileset::CatchTiles:
650 mapArea.editMap().fillCatch(
651 levelSelect->getSelectedOption(), tiles, tileset.getActiveCatch());
659 switch (tileset.getActiveTool()) {
661 case Tileset::Animations:
662 if (selectionState == SelectionMade) {
663 mapArea.editMap().setTileArea(levelSelect->getSelectedOption(),
664 layerSelect->getSelectedOption(),
670 mapArea.editMap().setTile(levelSelect->getSelectedOption(),
671 layerSelect->getSelectedOption(),
677 case Tileset::CollisionTiles:
678 if (selectionState == SelectionMade) {
679 mapArea.editMap().setCollisionArea(
683 mapArea.editMap().setCollision(
687 case Tileset::CatchTiles:
688 if (selectionState == SelectionMade) {
689 mapArea.editMap().setCatchArea(levelSelect->getSelectedOption(), selection, 0);
691 else { mapArea.editMap().setCatch(levelSelect->getSelectedOption(), tiles, 0); }
693 case Tileset::TownTiles:
694 if (selectionState == SelectionMade) {
695 mapArea.editMap().setTownTileArea(selection, 0);
697 else { mapArea.editMap().setTownTile(tiles, 0); }
704 case Subtool::Select:
705 switch (selectionState) {
708 selection.left = tiles.x;
709 selection.top = tiles.y;
710 setSelectionState(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);
721 setSelectionState(NoSelection);
732 if (createEventRadio->getValue()) {
733 mapArea.disableControls();
734 eventEditor.open(parent,
nullptr, tiles);
736 else if (editEventRadio->getValue()) {
739 mapArea.disableControls();
740 eventEditor.open(parent, e, tiles);
743 else if (deleteEventRadio->getValue()) {
746 std::stringstream ss;
748 ss <<
"Delete event?";
749 std::string s = ss.str();
751 if (c ==
'"' || c ==
'\'') { c =
'`'; }
753 if (1 == bl::dialog::tinyfd_messageBox(
754 "Delete Event?", s.c_str(),
"yesno",
"warning", 0)) {
755 mapArea.editMap().removeEvent(e);
762 if (spawnCreate->getValue()) {
763 const char*
id = bl::dialog::tinyfd_inputBox(
"Spawn ID",
"Enter spawn id: ",
"0");
766 const unsigned int n = std::atoi(
id);
767 if (mapArea.editMap().spawnIdUnused(n)) {
768 mapArea.editMap().addSpawn(
769 levelSelect->getSelectedOption(),
772 static_cast<bl::tmap::Direction
>(spawnDirEntry->getSelectedOption()));
777 else if (spawnRotate->getValue()) {
778 mapArea.editMap().rotateSpawn(levelSelect->getSelectedOption(), tiles);
780 else if (spawnDelete->getValue()) {
781 mapArea.editMap().removeSpawn(levelSelect->getSelectedOption(), tiles);
786 if (npcSpawn->getValue()) {
788 mapArea.editMap().getNpcSpawn(levelSelect->getSelectedOption(), tiles);
790 mapArea.disableControls();
791 characterEditor.open(parent, levelSelect->getSelectedOption(), tiles,
nullptr);
794 else if (npcEdit->getValue()) {
796 mapArea.editMap().getNpcSpawn(levelSelect->getSelectedOption(), tiles);
798 mapArea.disableControls();
799 characterEditor.open(parent, levelSelect->getSelectedOption(), tiles, s);
802 else if (npcDelete->getValue()) {
804 mapArea.editMap().getNpcSpawn(levelSelect->getSelectedOption(), tiles);
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);
816 if (itemSpawn->getValue()) {
817 mapArea.editMap().addOrEditItem(levelSelect->getSelectedOption(),
819 itemSelector->currentItem(),
820 !itemHidden->getValue());
822 else if (itemRead->getValue()) {
823 const auto i = mapArea.editMap().getItem(levelSelect->getSelectedOption(), tiles);
825 itemSelector->setItem(i.first);
826 itemHidden->setValue(!i.second);
829 else if (itemDelete->getValue()) {
830 mapArea.editMap().removeItem(levelSelect->getSelectedOption(), tiles);
835 if (lightCreateOrEdit->getValue()) {
836 mapArea.editMap().setLight(sf::Vector2i(pixels),
837 std::atoi(lightRadiusEntry->getInput().c_str()));
839 else if (lightRemove->getValue()) { mapArea.editMap().removeLight(sf::Vector2i(pixels)); }
843 testingTab.notifyClick(
844 mapArea.editMap().currentFile(), levelSelect->getSelectedOption(), tiles);
853 void Map::onChoosePlaylist(
const std::string& file) {
854 mapArea.editMap().setPlaylist(file);
855 playlistLabel->setText(file);
858 bool Map::checkUnsaved() {
859 if (mapArea.editMap().unsavedChanges()) {
860 return bl::dialog::tinyfd_messageBox(
862 std::string(mapArea.editMap().name() +
" has unsaved changes, discard them?")
871 void Map::syncGui() {
872 tileset.loadTileset(mapArea.editMap().tilesetField);
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));
880 levelSelect->setSelectedOption(0);
882 levelPage.sync(mapArea.editMap().levelFilter);
883 layerPage.sync(mapArea.editMap().levels, mapArea.editMap().layerFilter);
885 nameEntry->setInput(mapArea.editMap().name());
886 playlistLabel->setText(mapArea.editMap().playlistField);
887 weatherEntry->setSelectedOption(
static_cast<int>(mapArea.editMap().weatherField));
889 onEnterLabel->setText(mapArea.editMap().getOnEnterScript());
890 onExitLabel->setText(mapArea.editMap().getOnExitScript());
895 void Map::onLevelChange(
unsigned int l) {
896 if (l >= mapArea.editMap().levels.size()) {
897 BL_LOG_ERROR <<
"Out of range level: " << l;
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();
906 layerSelect->clearOptions();
908 for (
unsigned int j = 0; j < bc; ++j, ++i) {
909 layerSelect->addOption(
"Layer " + std::to_string(i));
911 for (
unsigned int j = 0; j < yc; ++j, ++i) {
912 layerSelect->addOption(
"Layer " + std::to_string(i) +
" (ysort)");
914 for (
unsigned int j = 0; j < tc; ++j, ++i) {
915 layerSelect->addOption(
"Layer " + std::to_string(i));
917 layerSelect->setSelectedOption(0);
920 void Map::onChooseScript(
const std::string& s) {
921 if (choosingOnloadScript) { mapArea.editMap().setOnEnterScript(s); }
922 else { mapArea.editMap().setOnExitScript(s); }
926 if (orig) { mapArea.editMap().editEvent(orig, val); }
927 else { mapArea.editMap().createEvent(val); }
928 mapArea.enableControls();
933 if (orig) { mapArea.editMap().editNpcSpawn(orig, spawn); }
934 else { mapArea.editMap().addNpcSpawn(spawn); }
935 mapArea.enableControls();
938 void Map::onLightingChange() {
939 if (sunlightBut->getValue()) {
940 minLightSlider->setVisible(
true);
941 maxLightSlider->setPrompt(
"Max Light");
944 minLightSlider->setVisible(
false);
945 maxLightSlider->setPrompt(
"Ambient Light");
947 lightingSetBut->setColor(sf::Color::Yellow, sf::Color::Black);
950 void Map::onLightingReset() {
952 lightingSetBut->setColor(sf::Color::Green, sf::Color::Black);
955 void Map::onLightingSave() {
956 mapArea.editMap().setAmbientLight(
957 minLightSlider->getLightLevel(), maxLightSlider->getLightLevel(), sunlightBut->getValue());
958 lightingSetBut->setColor(sf::Color::Green, sf::Color::Black);
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());
969 minLightSlider->setVisible(
true);
970 maxLightSlider->setPrompt(
"Max Light");
973 minLightSlider->setVisible(
false);
974 maxLightSlider->setPrompt(
"Ambient Light");
977 lightingSetBut->setColor(sf::Color::Green, sf::Color::Black);
980 void Map::setLightingDefault() {
981 minLightSlider->setLightLevel(75);
982 maxLightSlider->setLightLevel(255);
983 lightingSetBut->setColor(sf::Color::Yellow, sf::Color::Black);
986 void Map::startMapRender() {
987 mapArea.disableControls();
988 renderMapWindow.open(parent);
991 void Map::doMapRender() { mapArea.editMap().staticRender(renderMapWindow); }
All classes and functionality used for implementing the game editor.
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.
static constexpr IdType Blank
Special id for blank tiles.
static const std::string & MapPath()
Owns all primary systems and a reference to the engine.
void appendBottomLayer(unsigned int level)
Creates a new (empty) bottom layer.
void setLevelVisible(unsigned int level, bool visible)
Shows or hides the given level.
@ 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.
static Ptr create(const std::string &prompt, const ChangeCb &onChange)
Create a new light level slider.
static Ptr create()
Creates a new weather selector.
Map(core::system::Systems &systems)
Construct a new Map page.
void syncGui()
Updates the GUI elements to sync with the data.
Base class for all editor pages.
component::EditMap & editMap()
Returns the contained map.