Peoplemon  0.1.0
Peoplemon 3 game source documentation
Map.cpp
Go to the documentation of this file.
1 #include <Core/Maps/Map.hpp>
2 
3 #include <BLIB/Audio.hpp>
4 #include <BLIB/Cameras.hpp>
5 #include <Core/Events/Maps.hpp>
6 #include <Core/Properties.hpp>
7 #include <Core/Resources.hpp>
11 #include <Core/Systems/Systems.hpp>
12 #include <cmath>
13 #include <numeric>
14 
15 namespace core
16 {
17 namespace map
18 {
19 std::vector<Town> Map::flymapTowns;
20 
22 : weatherField(Weather::None)
23 , systems(nullptr)
24 , eventRegions({}, 1.f, 1.f) // no allocations
25 , activated(false) {}
26 
28  // Clear batch buffers first for speedier cleanup than if tiles are destroyed first
29  renderLevels.clear();
30 }
31 
32 bool Map::enter(system::Systems& game, std::uint16_t spawnId, const std::string& prevMap,
33  const bl::tmap::Position& prevPlayerPos) {
34  BL_LOG_INFO << "Entering map " << nameField << " at spawn " << spawnId;
35 
36  systems = &game;
37  bl::event::Dispatcher::dispatch<event::MapSwitch>({*this});
38 
39  // One time activation if not yet activated
40  if (!activated) {
41  activated = true;
42  BL_LOG_INFO << "Activating map " << nameField;
43 
44  // Load tileset and init tiles
45  tileset = TilesetManager::load(Tileset::getFullPath(tilesetField));
46  if (!tileset) return false;
47  tileset->activate(game.engine());
48 
49  // Prepare rendering
50  if (!scene) { prepareRender(); }
51 
52  // Initialize weather, lighting, and wild peoplemon
55 
56  // Load and parse scripts
59  onEnterScript.reset(new bl::script::Script(loadScriptField));
60  onExitScript.reset(new bl::script::Script(unloadScriptField));
61 
62  // Build event zones data structure
63  eventRegions.setSize({0.f,
64  0.f,
65  static_cast<float>(size.x * Properties::PixelsPerTile()),
66  static_cast<float>(size.y * Properties::PixelsPerTile())},
67  static_cast<float>(Properties::WindowWidth()),
68  static_cast<float>(Properties::WindowHeight()));
69  for (const Event& event : eventsField) {
70  eventRegions.add(sf::Vector2f(event.position * Properties::PixelsPerTile()), &event);
71  }
72 
73  BL_LOG_INFO << nameField << " activated";
74  }
75 
76  // Add our scene
77  game.engine().renderer().getObserver().pushScene(scene);
78 
79  // Spawn player
80  auto spawnIt = spawns.find(spawnId);
81  bl::tmap::Position spawnPos = prevPlayerPos;
82  spawnPos.direction = bl::tmap::oppositeDirection(spawnPos.direction);
83  spawnPos = spawnPos.move(spawnPos.direction);
84  if (spawnId != 0 && spawnIt != spawns.end()) { spawnPos = spawnIt->second.position; }
85  else if (spawnId == 0 && spawnIt != spawns.end()) {
86  BL_LOG_WARN << "Spawn id 0 is reserved, falling back on default behavior";
87  }
88  else if (spawnId != 0) {
89  BL_LOG_ERROR << "Invalid spawn id: " << spawnId;
90  return false;
91  }
92  if (!game.player().spawnPlayer(spawnPos, *this)) {
93  BL_LOG_ERROR << "Failed to spawn player";
94  return false;
95  }
97  currentTown = getTown(spawnPos.position);
99 
100  // Activate camera and weather
101  weather.activate(game, *this);
102 
103  // Ensure lighting is updated for time
105 
106  // Spawn npcs and trainers
107  for (const CharacterSpawn& spawn : characterField) {
108  if (game.entity().spawnCharacter(spawn, *this) == bl::ecs::InvalidEntity) {
109  BL_LOG_WARN << "Failed to spawn character: " << spawn.file;
110  }
111  }
112 
113  // Spawn items
114  for (const Item& item : itemsField) { game.entity().spawnItem(item, *this); }
115 
116  // Run on load script
117  onEnterScript->resetContext(script::MapChangeContext(game, prevMap, nameField, spawnId));
118  onEnterScript->run(&game.engine().scriptManager());
119 
120  // subscribe to get entity position updates
121  bl::event::Dispatcher::subscribe(this);
122 
123  // start music
124  playlistHandle = bl::audio::AudioSystem::getOrLoadPlaylist(
125  bl::util::FileUtil::joinPath(Properties::PlaylistPath(), playlistField));
126  if (playlistHandle != bl::audio::AudioSystem::InvalidHandle) {
127  bl::audio::AudioSystem::replacePlaylist(playlistHandle);
128  }
129 
130  bl::event::Dispatcher::dispatch<event::MapEntered>({*this});
131  BL_LOG_INFO << "Entered map: " << nameField;
132 
133  return true;
134 }
135 
136 void Map::exit(system::Systems& game, const std::string& newMap) {
137  BL_LOG_INFO << "Exiting map " << nameField;
138  bl::event::Dispatcher::dispatch<event::MapExited>({*this});
139 
140  // remove our scene
141  game.engine().renderer().getObserver().removeScene(scene);
142 
143  // unsubscribe from entity events
144  bl::event::Dispatcher::unsubscribe(this);
145 
146  // shut down light system
148 
149  // run exit script
150  if (onExitScript) {
151  onExitScript->resetContext(script::MapChangeContext(game, nameField, newMap, 0));
152  onExitScript->run(&game.engine().scriptManager());
153  }
154 
155  // TODO - pause weather
156 
157  BL_LOG_INFO << "Exited map: " << nameField;
158 }
159 
161  bl::cam::Camera2D* cam = systems.engine().renderer().getObserver().setCamera<bl::cam::Camera2D>(
162  glm::vec2{sizePixels().x, sizePixels().y},
163  glm::vec2{Properties::WindowSize().x, Properties::WindowSize().y});
164  cam->setController<bl::cam::c2d::ConstrainedFollower>(
165  systems.engine().ecs(),
166  systems.player().player(),
167  sf::FloatRect(0.f, 0.f, sizePixels().x, sizePixels().y));
168  cam->setNearAndFarPlanes(-getMinDepth(), 0.f);
169 }
170 
171 const std::string& Map::name() const { return nameField; }
172 
173 const sf::Vector2i& Map::sizeTiles() const { return size; }
174 
175 sf::Vector2f Map::sizePixels() const {
176  return {static_cast<float>(Properties::PixelsPerTile() * size.x),
177  static_cast<float>(Properties::PixelsPerTile() * size.y)};
178 }
179 
180 std::uint8_t Map::levelCount() const { return levels.size(); }
181 
183 
185 
186 void Map::update(float dt) {
187  weather.update(dt);
188  lighting.update(dt);
189 }
190 
191 std::string Map::getMapFile(const std::string& file) {
192  std::string path = bl::util::FileUtil::getExtension(file) == "map" ? file : file + ".map";
193  if (!bl::resource::FileSystem::resourceExists(path)) {
194  path = bl::util::FileUtil::joinPath(Properties::MapPath(), path);
195  }
196  if (!bl::resource::FileSystem::resourceExists(path)) {
197  BL_LOG_ERROR << "Failed to find map '" << file << "'. Tried '" << path << "'";
198  return "";
199  }
200  return path;
201 }
202 
203 bool Map::loadDev(std::istream& input) {
204  if (!bl::serial::json::Serializer<Map>::deserializeStream(input, *this)) return false;
205  finishLoad();
206  return true;
207 }
208 
209 bool Map::loadProd(bl::serial::binary::InputStream& input) {
210  if (!bl::serial::binary::Serializer<Map>::deserialize(input, *this)) return false;
211  finishLoad();
212  return true;
213 }
214 
216  // renderRange = sf::IntRect(0, 0, 1, 1);
220  size = {static_cast<int>(levels.front().collisionLayer().width()),
221  static_cast<int>(levels.front().collisionLayer().height())};
222  if (townTiles.getWidth() != static_cast<unsigned int>(size.x) ||
223  townTiles.getHeight() != static_cast<unsigned int>(size.y)) {
224  townTiles.setSize(size.x, size.y, 0);
225  }
226  isWorldMap = name() == "Worldmap";
227 }
228 
229 bool Map::save(const std::string& file) {
230  if (!isWorldMap) {
231  std::ofstream output(bl::util::FileUtil::startsWithPath(file, Properties::MapPath()) ?
232  file.c_str() :
233  bl::util::FileUtil::joinPath(Properties::MapPath(), file).c_str());
234  return bl::serial::json::Serializer<Map>::serializeStream(output, *this, 4, 0);
235  }
236  else {
237  // always save worldmap in binary otherwise it is too big
238  bl::serial::binary::OutputFile output(
239  bl::util::FileUtil::startsWithPath(file, Properties::MapPath()) ?
240  file.c_str() :
241  bl::util::FileUtil::joinPath(Properties::MapPath(), file).c_str());
242  return bl::serial::binary::Serializer<Map>::serialize(output, *this);
243  }
244 }
245 
246 bool Map::saveBundle(bl::serial::binary::OutputStream& output,
247  bl::resource::bundle::FileHandlerContext& ctx) const {
248  if (!bl::serial::binary::Serializer<Map>::serialize(output, *this)) return false;
249 
250  const auto addScript = [&ctx](const std::string& src) {
251  std::string s = src;
252  if (bl::script::Script::getFullScriptPath(s)) { ctx.addDependencyFile(s); }
253  };
254  addScript(loadScriptField);
255  addScript(unloadScriptField);
256  for (const Event& e : eventsField) { addScript(e.script); }
257 
258  const auto addCharacter = [&ctx](const std::string& cf) {
259  std::string path = bl::util::FileUtil::joinPath(Properties::NpcPath(), cf);
260  if (bl::util::FileUtil::exists(path)) { ctx.addDependencyFile(path); }
261  else {
262  path = bl::util::FileUtil::joinPath(Properties::TrainerPath(), cf);
263  if (bl::util::FileUtil::exists(path)) { ctx.addDependencyFile(path); }
264  }
265  };
266  for (const CharacterSpawn& s : characterField) { addCharacter(s.file); }
267 
268  return true;
269 }
270 
271 bool Map::contains(const bl::tmap::Position& pos) const {
272  return pos.position.x >= 0 && pos.position.y >= 0 && pos.position.x < size.x &&
273  pos.position.y < size.y && pos.level < levels.size();
274 }
275 
276 bl::tmap::Position Map::adjacentTile(const bl::tmap::Position& pos, bl::tmap::Direction dir) const {
277  bl::tmap::Position npos = pos.move(dir);
278  if (npos.position == pos.position) return npos;
279 
280  // Handle up transitions (move out of tile)
281  if (contains(pos)) {
282  switch (transitionField(pos.position.x, pos.position.y)) {
284  if (dir == bl::tmap::Direction::Left) npos.level += 1;
285  break;
287  if (dir == bl::tmap::Direction::Right) npos.level += 1;
288  break;
290  if (dir == bl::tmap::Direction::Down) npos.level += 1;
291  break;
293  if (dir == bl::tmap::Direction::Up) npos.level += 1;
294  break;
295  default:
296  break;
297  }
298  }
299 
300  if (contains(npos)) {
301  switch (transitionField(npos.position.x, npos.position.y)) {
303  if (dir == bl::tmap::Direction::Right) npos.level -= 1;
304  break;
306  if (dir == bl::tmap::Direction::Left) npos.level -= 1;
307  break;
309  if (dir == bl::tmap::Direction::Up) npos.level -= 1;
310  break;
312  if (dir == bl::tmap::Direction::Down) npos.level -= 1;
313  break;
314  default:
315  break;
316  }
317  }
318 
319  if (npos.level >= levels.size()) {
320  BL_LOG_WARN << "Bad level transition at (" << npos.position.x << ", " << npos.position.y
321  << ") to out of range level " << npos.level
322  << ". Number of levels: " << levels.size();
323  npos.level = levels.size() - 1;
324  }
325 
326  return npos;
327 }
328 
329 bool Map::movePossible(const bl::tmap::Position& pos, bl::tmap::Direction dir) const {
330  bl::tmap::Position npos = pos.move(dir);
331  if (npos.position == pos.position) return true;
332  if (!contains(npos)) return false;
333 
334  switch (levels.at(npos.level).collisionLayer().get(npos.position.x, npos.position.y)) {
335  case Collision::Blocked:
336  return false;
337  case Collision::Open:
338  return true;
339  case Collision::TopOpen:
340  return dir == bl::tmap::Direction::Down;
342  return dir == bl::tmap::Direction::Left;
344  return dir == bl::tmap::Direction::Up;
345  case Collision::LeftOpen:
346  return dir == bl::tmap::Direction::Right;
348  return dir == bl::tmap::Direction::Down || dir == bl::tmap::Direction::Left;
350  return dir == bl::tmap::Direction::Up || dir == bl::tmap::Direction::Left;
352  return dir == bl::tmap::Direction::Up || dir == bl::tmap::Direction::Right;
354  return dir == bl::tmap::Direction::Down || dir == bl::tmap::Direction::Right;
356  return dir == bl::tmap::Direction::Up || dir == bl::tmap::Direction::Down;
358  return dir == bl::tmap::Direction::Right || dir == bl::tmap::Direction::Left;
360  return dir != bl::tmap::Direction::Down;
362  return dir != bl::tmap::Direction::Left;
364  case Collision::LedgeHop:
365  return dir != bl::tmap::Direction::Up;
367  return dir != bl::tmap::Direction::Right;
372  default:
373  BL_LOG_WARN << "Bad collision at (" << npos.position.x << ", " << npos.position.y << ")";
374  return false;
375  }
376 }
377 
378 bool Map::isLedgeHop(const bl::tmap::Position& pos, bl::tmap::Direction dir) const {
379  if (dir != bl::tmap::Direction::Down) return false;
380  if (!contains(pos)) return false;
381  return levels.at(pos.level).collisionLayer().get(pos.position.x, pos.position.y) ==
383 }
384 
385 void Map::observe(const event::EntityMoved& movedEvent) {
386  triggerAnimation(movedEvent.position);
387 
388  if (movedEvent.entity != systems->player().player() || systems->flight().flying()) { return; }
389 
390  const auto trigger = [this, &movedEvent](const Event& event) {
391  script::LegacyWarn::warn(event.script);
392  BL_LOG_INFO << movedEvent.entity << " triggered event at (" << event.position.x << ", "
393  << event.position.y << ")";
394  bl::script::Script s(
395  event.script,
396  script::MapEventContext(*systems, movedEvent.entity, event, movedEvent.position));
397  s.run(&systems->engine().scriptManager());
398  };
399 
400  const auto visitor = [&movedEvent, &trigger](const Event* ep) {
401  const Event& e = *ep;
402  const sf::IntRect area(e.position, e.areaSize);
403  const bool wasIn = area.contains(
404  {movedEvent.previousPosition.position.x, movedEvent.previousPosition.position.y});
405  const bool isIn =
406  area.contains({movedEvent.position.position.x, movedEvent.position.position.y});
407 
408  switch (e.trigger) {
410  if (!wasIn && isIn) trigger(e);
411  break;
412 
414  if (wasIn && !isIn) trigger(e);
415  break;
416 
418  if (wasIn != isIn) trigger(e);
419  break;
420 
422  if (isIn) trigger(e);
423  break;
424 
425  default:
426  break;
427  }
428  };
429  eventRegions.forAllInCellAndNeighbors(
430  movedEvent.position.getWorldPosition(Properties::PixelsPerTile()), visitor);
431 
432  Town* newTown = getTown(movedEvent.position.position);
433  if (newTown != currentTown) {
434  currentTown = newTown;
436  }
437 }
438 
439 void Map::triggerAnimation(const bl::tmap::Position& pos) {
440  if (contains(pos)) {
441  auto& level = levels[pos.level];
442  for (auto& layer : level.bottomLayers()) {
443  layer.getRef(pos.position.x, pos.position.y).step();
444  }
445  for (auto& layer : level.ysortLayers()) {
446  layer.getRef(pos.position.x, pos.position.y).step();
447  }
448  for (auto& layer : level.topLayers()) {
449  layer.getRef(pos.position.x, pos.position.y).step();
450  }
451  }
452 }
453 
454 bool Map::interact(bl::ecs::Entity interactor, const bl::tmap::Position& pos) {
455  const auto trigger = [this, interactor, &pos](const Event& event) {
456  script::LegacyWarn::warn(event.script);
457  BL_LOG_INFO << interactor << " triggered event at (" << pos.position.x << ", "
458  << pos.position.y << ")";
459  bl::script::Script s(event.script,
460  script::MapEventContext(*systems, interactor, event, pos));
461  s.run(&systems->engine().scriptManager());
462  };
463 
464  bool found = false;
465  const auto visitor = [&trigger, &pos, &found](const Event* ep) -> bool {
466  const Event& e = *ep;
467  const sf::IntRect area(e.position, e.areaSize);
468  if (area.contains({pos.position.x, pos.position.y}) &&
470  trigger(e);
471  found = true;
472  return true; // ends iteration
473  }
474  return false;
475  };
476  eventRegions.forAllInCellAndNeighbors(pos.getWorldPosition(Properties::PixelsPerTile()),
477  visitor);
478  if (found) return true;
479 
480  if (interactor == systems->player().player()) {
481  const Collision col =
482  levels[pos.level].collisionLayer().get(pos.position.x, pos.position.y);
483  switch (col) {
484  case Collision::LedgeHop:
486  "This ledge is pretty tall. I'll have to find a way around.");
487  return true;
491  "I bet the shoes I'm wearing will let me walk right over this!");
492  }
493  else { systems->hud().displayMessage("There's no way I can walk on water! Unless..."); }
494  return true;
498  "With my super awesome upgrades Jesus Shoes I bet I can walk right up this!");
499  }
500  else {
502  "Even my Jesus Shoes can't get up here. Maybe there's a new model available.");
503  }
504  return true;
505  default:
506  break;
507  }
508 
509  // check ledge hop down
510  const bl::tmap::Position prev = pos.move(bl::tmap::oppositeDirection(pos.direction));
511  const Collision oncol =
512  levels[pos.level].collisionLayer().get(prev.position.x, prev.position.y);
513  if (oncol == Collision::LedgeHop && pos.direction == bl::tmap::Direction::Down) {
514  systems->hud().displayMessage("I think I can jump down without getting hurt, but I "
515  "won't be able to get back up.");
516  return true;
517  }
518  }
519 
520  return false;
521 }
522 
523 void Map::clear() {
524  // Clear batch buffers first for speedier cleanup than if tiles are destroyed first
525  renderLevels.clear();
526 
527  levels.clear();
528  spawns.clear();
529  characterField.clear();
530  itemsField.clear();
531  eventsField.clear();
532  lightingSystem().clear();
533  catchRegionsField.clear();
534  transitionField.clear();
535  eventRegions.clear();
537  weather.set(Weather::None, true);
538  cleanupRender();
539 }
540 
541 Town* Map::getTown(const glm::i32vec2& pos) {
542  if (pos.x < 0 || pos.y < 0 || pos.x >= size.x || pos.y >= size.y) { return &defaultTown; }
543  const std::uint8_t i = townTiles(pos.x, pos.y);
544  if (i == 0) return &defaultTown;
545  if (static_cast<std::uint8_t>(i - 1) >= towns.size()) return &defaultTown;
546  return &towns[i - 1];
547 }
548 
549 void Map::enterTown(Town* town) {
550  using bl::audio::AudioSystem;
551  using bl::util::FileUtil;
552 
553  systems->hud().displayEntryCard(town->name);
554  weatherSystem().set(town->weather);
555 
556  const AudioSystem::Handle plst = AudioSystem::getOrLoadPlaylist(
557  FileUtil::joinPath(Properties::PlaylistPath(), town->playlist));
558  if (plst != AudioSystem::InvalidHandle) { AudioSystem::replacePlaylist(plst, 1.5f, 1.5f); }
559 
560  if (spawns.find(town->pcSpawn) != spawns.end()) {
562  }
563 
564  if (isWorldMap) { systems->player().state().visitedTowns.emplace(town->name); }
565 }
566 
567 const CatchRegion* Map::getCatchRegion(const bl::tmap::Position& pos) const {
568  const auto& tiles = pos.position;
569  if (pos.level >= levels.size() || tiles.x < 0 ||
570  static_cast<unsigned int>(tiles.x) >= levels.front().catchLayer().width() || tiles.y < 0 ||
571  static_cast<unsigned int>(tiles.y) >= levels.front().catchLayer().height()) {
572  BL_LOG_ERROR << "Out of bounds position: " << pos;
573  return nullptr;
574  }
575 
576  std::uint8_t ci = levels[pos.level].catchLayer().get(tiles.x, tiles.y);
577  if (ci == 0) { return nullptr; }
578  --ci; // convert to index
579  if (ci >= catchRegionsField.size()) {
580  BL_LOG_ERROR << "Bad catch zone index (" << static_cast<int>(ci)
581  << ") at position: " << pos;
582  return nullptr;
583  }
584  return &catchRegionsField[ci];
585 }
586 
587 const std::vector<Town>& Map::FlyMapTowns() {
588  if (flymapTowns.empty()) loadFlymapTowns();
589  return flymapTowns;
590 }
591 
593  bl::resource::Ref<Map> world =
594  MapManager::load(bl::util::FileUtil::joinPath(core::Properties::MapPath(), "WorldMap.map"));
595  if (!world) {
596  BL_LOG_CRITICAL << "Failed to load world map";
597  return;
598  }
599 
600  flymapTowns = world->towns;
601 }
602 
603 bool Map::canFlyFromHere() const { return isWorldMap; }
604 
605 const bl::tmap::Position* Map::getSpawnPosition(unsigned int spid) const {
606  const auto sit = spawns.find(spid);
607  return sit != spawns.end() ? &sit->second.position : nullptr;
608 }
609 
610 const std::string& Map::getLocationName(const bl::tmap::Position& pos) const {
611  return const_cast<Map*>(this)->getTown(pos.position)->name;
612 }
613 
615  BL_LOG_INFO << "Generating map geometry...";
616  scene = systems->engine().renderer().scenePool().allocateScene<bl::rc::scene::Scene2D>();
617 
618  tileset->activate(systems->engine());
619  for (unsigned int i = 0; i < levels.size(); ++i) { setupLevel(i); }
620 
621  BL_LOG_INFO << "Map geometry generated";
622 }
623 
624 void Map::setupLevel(unsigned int level) {
625  const auto pos = std::next(renderLevels.begin(), level);
626  auto& rl = *renderLevels.emplace(pos);
627  rl.create(systems->engine(),
628  tileset->combinedTextures,
629  levels[level].layerCount(),
630  sf::Vector2u(size),
631  scene);
632  for (unsigned int j = 0; j < levels[level].layerCount(); ++j) { setupLayer(level, j); }
633 }
634 
635 void Map::setupLayer(unsigned int level, unsigned int layer) {
636  for (unsigned int x = 0; x < size.x; ++x) {
637  for (unsigned int y = 0; y < size.y; ++y) { setupTile(level, layer, {x, y}); }
638  }
639 }
640 
641 void Map::setupTile(unsigned int level, unsigned int layer, const sf::Vector2u& pos) {
642  Tile& tile = levels[level].getLayer(layer).getRef(pos.x, pos.y);
643  if (tile.id() == Tile::Blank) {
644  tile.renderObject.emplace<std::monostate>();
645  return;
646  }
647  const glm::vec2 offset(pos.x * Properties::PixelsPerTile(),
648  pos.y * Properties::PixelsPerTile());
649 
650  auto it = renderLevels.begin();
651  std::advance(it, level);
652  auto& zone = *(it->zones[layer]);
653  const bool isYsort =
654  layer >= levels[level].bottomLayers().size() &&
655  layer < levels[level].bottomLayers().size() + levels[level].ysortLayers().size();
656 
657  const auto getBottomDepth = [this, &pos, layer, level, &tile]() {
658  const unsigned int th = tileset->tileHeight(tile.id(), tile.isAnimation());
659  unsigned int size = th / Properties::PixelsPerTile();
660  if (th % Properties::PixelsPerTile() != 0) { ++size; }
661  return getDepthForPosition(level, pos.y + size, layer);
662  };
663 
664  if (tile.isAnimation()) {
665  const auto ait = tileset->sharedAnimations.find(tile.id());
666  bl::ecs::Entity playerEntity = bl::ecs::InvalidEntity;
667  bl::gfx::DiscreteAnimation2DPlayer player;
668  bool isBatchSlideshow = true;
669  if (ait != tileset->sharedAnimations.end()) { playerEntity = ait->second.entity(); }
670  else {
671  auto anim = tileset->getAnim(tile.id());
672  if (!anim) {
673  BL_LOG_ERROR << "Failed to find animation for tile id: " << tile.id();
674  tile.set(Tile::Blank, false);
675  return;
676  }
677  if (anim->isSlideshow()) {
678  player.create(
679  systems->engine(), anim, bl::gfx::DiscreteAnimation2DPlayer::Slideshow);
680  // will be cleaned up when dependency is removed in ECS
681  playerEntity = player.entity();
682  }
683  else {
684  isBatchSlideshow = false;
685  bl::gfx::Animation2D& vanim =
686  *tile.renderObject.emplace<std::shared_ptr<bl::gfx::Animation2D>>(
687  std::make_shared<bl::gfx::Animation2D>(systems->engine(), anim));
688  vanim.addToScene(scene, bl::rc::UpdateSpeed::Static);
689  vanim.getTransform().setPosition(offset);
690  if (isYsort) {
691  const float topDepth = getDepthForPosition(level, pos.y, layer);
692  const float bottomDepth = getBottomDepth();
693  vanim.getTransform().setDepth((topDepth + bottomDepth) * 0.5f);
694  }
695  else { vanim.getTransform().setDepth(getDepthForPosition(level, pos.y, layer)); }
696  }
697  }
698  if (isBatchSlideshow) {
699  bl::gfx::BatchSlideshowSimple& anim =
700  tile.renderObject.emplace<bl::gfx::BatchSlideshowSimple>(
701  systems->engine(), zone.tileAnims, playerEntity);
702 
703  for (auto& v : anim.getVertices()) {
704  v.pos.x += offset.x;
705  v.pos.y += offset.y;
706  if (!isYsort) { v.pos.z = getDepthForPosition(level, pos.y, layer); }
707  }
708  if (isYsort) {
709  const float topDepth = getDepthForPosition(level, pos.y, layer);
710  const float bottomDepth = getBottomDepth();
711  anim.getVertices()[0].pos.z = topDepth;
712  anim.getVertices()[1].pos.z = topDepth;
713  anim.getVertices()[2].pos.z = bottomDepth;
714  anim.getVertices()[3].pos.z = bottomDepth;
715  }
716  anim.commit();
717  }
718  }
719  else {
720  const sf::FloatRect src = tileset->getTileTextureBounds(tile.id());
721  if (src.width < 0.f) {
722  BL_LOG_ERROR << "Failed to find texture for tile id: " << tile.id();
723  return;
724  }
725  bl::gfx::BatchSpriteSimple& sprite =
726  tile.renderObject.emplace<bl::gfx::BatchSpriteSimple>(zone.tileSprites, src);
727  float depth = getDepthForPosition(level, pos.y, layer);
728  if (isYsort) {
729  const float bottomDepth = getBottomDepth();
730  depth = (depth + bottomDepth) * 0.5f;
731  }
732  for (auto& v : sprite.getVertices()) {
733  v.pos.x += offset.x;
734  v.pos.y += offset.y;
735  v.pos.z = depth;
736  }
737  sprite.commit();
738  }
739 }
740 
741 float Map::getDepthForPosition(unsigned int level, unsigned int y, int layer) const {
742  if (level >= levels.size()) {
743  BL_LOG_ERROR << "Got invalid level: " << level;
744  return 0.f;
745  }
746 
747  const LayerSet& lvl = levels[level];
748  const unsigned int maxLayers = std::accumulate(
749  levels.begin(),
750  levels.end(),
751  levels.front().layerCount(),
752  [](unsigned int val, const LayerSet& level) { return std::max(val, level.layerCount()); });
753 
754  // render entities as top ysort layer
755  if (layer < 0) { layer = lvl.bottomLayers().size() + lvl.ysortLayers().size() - 1; }
756 
757  const float zoneBias = y * maxLayers + layer * size.y;
758  const float maxZoneBias = size.y * maxLayers + (maxLayers - 1) * size.y;
759  const float depthPerLevel = maxZoneBias * 3.f;
760  const float levelBias = static_cast<float>(level) * depthPerLevel;
761 
762  // bottom tiles
763  if (layer < lvl.bottomLayers().size()) { return -(zoneBias + levelBias); }
764 
765  // ysort tiles + entities
766  if (layer < lvl.bottomLayers().size() + lvl.ysortLayers().size()) {
767  const float ysortZoneBias = y * maxLayers + layer;
768  return -(ysortZoneBias + maxZoneBias + levelBias);
769  }
770 
771  // top tiles
772  return -(zoneBias + maxZoneBias * 2.f + levelBias);
773 }
774 
775 float Map::getMinDepth() const {
776  return getDepthForPosition(levels.size() - 1, size.y - 1, levels.back().layerCount() - 1);
777 }
778 
780  renderLevels.clear();
781  for (auto& level : levels) {
782  for (unsigned int j = 0; j < level.layerCount(); ++j) {
783  for (unsigned int x = 0; x < size.x; ++x) {
784  for (unsigned int y = 0; y < size.y; ++y) {
785  level.getLayer(j).getRef(x, y).renderObject.emplace<std::monostate>();
786  }
787  }
788  }
789  }
790 }
791 
792 void Map::setupEntityPosition(bl::ecs::Entity ent) {
793  auto set = systems->engine()
794  .ecs()
795  .getComponentSet<bl::ecs::Require<bl::tmap::Position, bl::com::Transform2D>,
796  bl::ecs::Optional<component::Renderable>>(ent);
797  if (!set.isValid()) {
798  BL_LOG_ERROR << "Cannot setup position for entity " << ent << ", missing components";
799  return;
800  }
801 
802  bl::com::Transform2D* transform = set.get<bl::com::Transform2D>();
803  bl::tmap::Position* pos = set.get<bl::tmap::Position>();
804  pos->transform = transform;
805  transform->setDepth(getDepthForPosition(pos->level, pos->position.y));
806  pos->syncTransform(Properties::PixelsPerTile());
807 
809  if (rc) { rc->notifyMoveState(pos->direction, false, false); }
810 }
811 
812 } // namespace map
813 } // namespace core
Collision
The different types of collisions in maps.
Definition: Collision.hpp:16
@ HorizontalRightUp
Entities moving horizontally move up when going right and down when going left.
@ VerticalTopDown
Entities moving vertically move down when going north and up when going south.
@ VerticalTopUp
Entities moving vertically move up when going north and down when going south.
@ HorizontalRightDown
Entities moving horizontally move down when going right and up when going left.
Core classes and functionality for both the editor and game.
bl::util::FileUtil FileUtil
Parent namespace for all functionality unique to the game.
Adding this component to an entity will allow it to be rendered.
Definition: Renderable.hpp:28
void notifyMoveState(bl::tmap::Direction dir, bool moving, bool running)
Call when the entity starts or stops moving or changes direction.
Definition: Renderable.cpp:130
Fired after an entity begins moving from one position to another.
Definition: EntityMoved.hpp:18
const bl::tmap::Position previousPosition
The previous position of the entity.
Definition: EntityMoved.hpp:23
const bl::tmap::Position & position
The current position of the entity.
Definition: EntityMoved.hpp:26
const bl::ecs::Entity entity
The entity that moved.
Definition: EntityMoved.hpp:20
Represents a class of catchable peoplemon.
Definition: CatchRegion.hpp:18
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
sf::Vector2i position
Definition: Event.hpp:39
@ OnInteract
The event triggers when the player interacts with the zone.
@ OnExit
The event triggers when the player steps out of the zone.
@ onEnterOrExit
The event triggers when the player either enters or exits the zone.
@ WhileIn
The event triggers repeatedly while the player is in the zone.
@ OnEnter
The event triggers when the player steps into the zone.
Trigger trigger
Definition: Event.hpp:38
sf::Vector2i areaSize
Definition: Event.hpp:40
Basic struct representing a pickup-able item in Map.
Definition: Item.hpp:16
Encapsulates a set of layers for a given map height. Maps have one LayerSet per height level,...
Definition: LayerSet.hpp:51
std::vector< TileLayer > & bottomLayers()
Returns a reference to the bottom tiles in this set.
Definition: LayerSet.cpp:33
std::vector< TileLayer > & ysortLayers()
Returns a reference to the sorted tiles in this set.
Definition: LayerSet.cpp:35
System for handling lighting in Maps. Manages all the lights and performs rendering....
void update(float dt)
Updates the lighting system.
void unsubscribe()
Unsubscribes the lighting system from the event dispatcher.
void activate(bl::rc::lgt::Scene2DLighting &sceneLighting)
Adds all lights into the scene.
void subscribe()
Subscribes the lighting system to time events for ambient light level.
void clear()
Clears all lights from the map, including the persisted light data.
The primary map class that represents a usable map in the game.
Definition: Map.hpp:49
float getMinDepth() const
Returns the maximum depth possible for this map. Maximum is always 0.f. Minimum is negative,...
Definition: Map.cpp:775
virtual void observe(const event::EntityMoved &moveEvent) override
Event listener for moving entities. Used to trigger map events.
Definition: Map.cpp:385
bool enter(system::Systems &systems, std::uint16_t spawnId, const std::string &prevMap, const bl::tmap::Position &prevPlayerPos)
Initializes runtime data structures and spawns entities into the game. Also runs the on-load script.
Definition: Map.cpp:32
bl::ctr::Grid< const Event * > eventRegions
Definition: Map.hpp:323
bl::tmap::Position adjacentTile(const bl::tmap::Position &pos, bl::tmap::Direction dir) const
Returns the adjacent position to the given position when moving in the given direction....
Definition: Map.cpp:276
std::vector< Item > itemsField
Definition: Map.hpp:307
bool isLedgeHop(const bl::tmap::Position &position, bl::tmap::Direction dir) const
Test whether the given movement will be a ledge hop or not.
Definition: Map.cpp:378
std::unordered_map< std::uint16_t, Spawn > spawns
Definition: Map.hpp:305
LightingSystem lighting
Definition: Map.hpp:309
std::string nameField
Definition: Map.hpp:298
const CatchRegion * getCatchRegion(const bl::tmap::Position &position) const
Returns the catch region at the given position if the position is on a catch tile.
Definition: Map.cpp:567
std::vector< LayerSet > levels
Definition: Map.hpp:303
bool isWorldMap
Definition: Map.hpp:324
bool loadDev(std::istream &input)
Loads the map from the given file. Will try to determine if the extension or path need to be added in...
Definition: Map.cpp:203
Town * getTown(const glm::i32vec2 &pos)
Definition: Map.cpp:541
void setupCamera(system::Systems &systems)
Configures the game camera for the player in the map.
Definition: Map.cpp:160
const std::string & getLocationName(const bl::tmap::Position &pos) const
Returns the name of the town or route at the given position.
Definition: Map.cpp:610
virtual ~Map()
Destroy the Map.
Definition: Map.cpp:27
std::string unloadScriptField
Definition: Map.hpp:300
void enterTown(Town *town)
Definition: Map.cpp:549
bool save(const std::string &file)
Saves the map to the given file. The path should be relative to the maps directory and include the ....
Definition: Map.cpp:229
static std::vector< Town > flymapTowns
Definition: Map.hpp:343
std::vector< Event > eventsField
Definition: Map.hpp:308
bool saveBundle(bl::serial::binary::OutputStream &output, bl::resource::bundle::FileHandlerContext &ctx) const
Saves the data from this object to the given bundle and registers dependency files.
Definition: Map.cpp:246
Weather weather
Definition: Map.hpp:320
float getDepthForPosition(unsigned int level, unsigned int y, int layer=-1) const
Computes the depth to use at the given y position, taking into account the map size,...
Definition: Map.cpp:741
Map()
Creates an empty Map.
Definition: Map.cpp:21
std::string loadScriptField
Definition: Map.hpp:299
Weather::Type weatherField
Definition: Map.hpp:302
LightingSystem & lightingSystem()
Returns a reference to the lighting system in this map.
Definition: Map.cpp:184
const bl::tmap::Position * getSpawnPosition(unsigned int spawnId) const
Returns the position of the given player spawn, or nullptr if not found.
Definition: Map.cpp:605
bool canFlyFromHere() const
Returns whether or not the player can fly from this map.
Definition: Map.cpp:603
sf::Vector2i size
Definition: Map.hpp:318
void finishLoad()
Definition: Map.cpp:215
bl::resource::Ref< Tileset > tileset
Definition: Map.hpp:319
bool contains(const bl::tmap::Position &position) const
Returns whether or not the map contains the given position.
Definition: Map.cpp:271
void prepareRender()
Definition: Map.cpp:614
bool interact(bl::ecs::Entity interactor, const bl::tmap::Position &interactPos)
Lets entities interact with the map itself. This is called by the Interaction system.
Definition: Map.cpp:454
std::unique_ptr< bl::script::Script > onEnterScript
Definition: Map.hpp:321
std::uint8_t levelCount() const
Returns the number of levels in the map.
Definition: Map.cpp:180
void setupTile(unsigned int level, unsigned int layer, const sf::Vector2u &pos)
Definition: Map.cpp:641
std::unique_ptr< bl::script::Script > onExitScript
Definition: Map.hpp:322
static const std::vector< Town > & FlyMapTowns()
Returns the set of towns that can be flown to.
Definition: Map.cpp:587
void exit(system::Systems &systems, const std::string &newMap)
Removes spawned entities and runs the on-unload script.
Definition: Map.cpp:136
std::vector< Town > towns
Definition: Map.hpp:312
bl::ctr::Vector2D< std::uint8_t > townTiles
Definition: Map.hpp:313
const sf::Vector2i & sizeTiles() const
Returns the size of the map in tiles.
Definition: Map.cpp:173
std::vector< CharacterSpawn > characterField
Definition: Map.hpp:306
std::string tilesetField
Definition: Map.hpp:304
Town defaultTown
Definition: Map.hpp:316
void clear()
Definition: Map.cpp:523
bl::ctr::Vector2D< LevelTransition > transitionField
Definition: Map.hpp:311
void triggerAnimation(const bl::tmap::Position &position)
Definition: Map.cpp:439
bl::audio::AudioSystem::Handle playlistHandle
Definition: Map.hpp:325
void setupLayer(unsigned int level, unsigned int layer)
Definition: Map.cpp:635
bl::rc::lgt::Scene2DLighting & getSceneLighting()
Returns the scene lighting for this map. Only valid after enter() is called.
Definition: Map.hpp:285
std::string playlistField
Definition: Map.hpp:301
static std::string getMapFile(const std::string &partialFile)
Returns the full path to the map file from the given partial file. Accounts for missing extension.
Definition: Map.cpp:191
Weather & weatherSystem()
Returns a reference to the weather system in this map.
Definition: Map.cpp:182
bool loadProd(bl::serial::binary::InputStream &input)
Loads the map from the given file. Will try to determine if the extension or path need to be added in...
Definition: Map.cpp:209
void setupEntityPosition(bl::ecs::Entity entity)
Performs the final setup of the position components for the given entity. Must already have a bl::tma...
Definition: Map.cpp:792
bl::rc::SceneRef scene
Definition: Map.hpp:328
bool activated
Definition: Map.hpp:326
Town * currentTown
Definition: Map.hpp:317
std::vector< CatchRegion > catchRegionsField
Definition: Map.hpp:310
static void loadFlymapTowns()
Definition: Map.cpp:592
void cleanupRender()
Definition: Map.cpp:779
void update(float dt)
Updates internal logic over the elapsed time.
Definition: Map.cpp:186
system::Systems * systems
Definition: Map.hpp:315
bool movePossible(const bl::tmap::Position &position, bl::tmap::Direction dir) const
Returns whether or not a particular movement is possible. Does not take into account entities blockin...
Definition: Map.cpp:329
void setupLevel(unsigned int level)
Definition: Map.cpp:624
const std::string & name() const
Returns the name of the map.
Definition: Map.cpp:171
sf::Vector2f sizePixels() const
Returns the size of the map in pixels.
Definition: Map.cpp:175
std::list< RenderLevel > renderLevels
Definition: Map.hpp:329
Data representation of a tile in a Map TileLayer.
Definition: Tile.hpp:30
IdType id() const
Returns the id of the image or animation of this tile.
Definition: Tile.cpp:22
static constexpr IdType Blank
Special id for blank tiles.
Definition: Tile.hpp:36
bool isAnimation() const
Returns whether this tile is an animation or not.
Definition: Tile.cpp:20
void set(IdType id, bool anim)
Sets the information of the tile.
Definition: Tile.cpp:24
static std::string getFullPath(const std::string &path)
Generates the full path to the given tileset file.
Definition: Tileset.cpp:190
Represents a town, route, or region within a map. Maps may have many towns. Individual tiles are asso...
Definition: Town.hpp:22
std::uint16_t pcSpawn
Definition: Town.hpp:26
std::string playlist
Definition: Town.hpp:24
Weather::Type weather
Definition: Town.hpp:25
std::string name
Definition: Town.hpp:23
Parent weather system for maps. Manages active weather.
Definition: Weather.hpp:32
@ None
No weather will occur.
Definition: Weather.hpp:40
void update(float dt)
Updates the current weather.
Definition: Weather.cpp:68
void activate(system::Systems &systems, Map &map)
Activates the weather system.
Definition: Weather.cpp:49
void set(Type type, bool immediate=false)
Sets the current weather type.
Definition: Weather.cpp:54
bool hasItem(item::Id item) const
Returns true if at least one of the given items is owned.
Definition: Bag.cpp:42
player::Bag bag
Definition: State.hpp:44
std::unordered_set< std::string > visitedTowns
Definition: State.hpp:48
static int WindowWidth()
Definition: Properties.cpp:250
static const std::string & TrainerPath()
Definition: Properties.cpp:533
static const std::string & MapPath()
Definition: Properties.cpp:339
static int PixelsPerTile()
Definition: Properties.cpp:279
static const std::string & NpcPath()
Definition: Properties.cpp:521
static sf::Vector2f WindowSize()
Definition: Properties.cpp:262
static int WindowHeight()
Definition: Properties.cpp:256
static const std::string & PlaylistPath()
Definition: Properties.cpp:321
static void warn(const std::string &script)
In debug mode logs a warning if the given script is detected to be legacy.
Definition: LegacyWarn.hpp:26
Special context for map enter and exit scripts.
Special script context for map events. Adds default built-ins and the map event built-ins.
bool flying() const
Returns whether or not the player is currently flying.
Definition: Flight.cpp:48
void displayEntryCard(const std::string &name)
Displays a card to indicate entering a new town, route, or map.
Definition: HUD.cpp:129
void displayMessage(const std::string &message, const Callback &cb=[](const std::string &) {})
Displays a message in the HUD textbox. Messages are queued in order that they arrive.
Definition: HUD.cpp:92
player::State & state()
Returns the state of the player.
Definition: Player.cpp:154
bl::ecs::Entity player() const
Returns the id of the player entity.
Definition: Player.cpp:55
Owns all primary systems and a reference to the engine.
Definition: Systems.hpp:47
const bl::engine::Engine & engine() const
Const accessor for the Engine.
Definition: Systems.cpp:35
Player & player()
Returns the player system.
Definition: Systems.cpp:59
Flight & flight()
Returns the flight system.
Definition: Systems.cpp:83
HUD & hud()
Returns the HUD.
Definition: Systems.cpp:69
World & world()
Modifiable accessor for the world system.
Definition: Systems.cpp:43
void setWhiteoutMap(unsigned int spawn)
Sets the respawn point to the given spawn in the current map.
Definition: World.cpp:79