3 #include <BLIB/Audio.hpp>
4 #include <BLIB/Cameras.hpp>
24 , eventRegions({}, 1.f, 1.f)
33 const bl::tmap::Position& prevPlayerPos) {
34 BL_LOG_INFO <<
"Entering map " <<
nameField <<
" at spawn " << spawnId;
37 bl::event::Dispatcher::dispatch<event::MapSwitch>({*
this});
42 BL_LOG_INFO <<
"Activating map " <<
nameField;
77 game.engine().renderer().getObserver().pushScene(
scene);
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";
88 else if (spawnId != 0) {
89 BL_LOG_ERROR <<
"Invalid spawn id: " << spawnId;
92 if (!
game.player().spawnPlayer(spawnPos, *
this)) {
93 BL_LOG_ERROR <<
"Failed to spawn player";
108 if (
game.entity().spawnCharacter(spawn, *
this) == bl::ecs::InvalidEntity) {
109 BL_LOG_WARN <<
"Failed to spawn character: " << spawn.file;
121 bl::event::Dispatcher::subscribe(
this);
130 bl::event::Dispatcher::dispatch<event::MapEntered>({*
this});
131 BL_LOG_INFO <<
"Entered map: " <<
nameField;
137 BL_LOG_INFO <<
"Exiting map " <<
nameField;
138 bl::event::Dispatcher::dispatch<event::MapExited>({*
this});
141 game.engine().renderer().getObserver().removeScene(
scene);
144 bl::event::Dispatcher::unsubscribe(
this);
157 BL_LOG_INFO <<
"Exited map: " <<
nameField;
161 bl::cam::Camera2D* cam =
systems.
engine().renderer().getObserver().setCamera<bl::cam::Camera2D>(
164 cam->setController<bl::cam::c2d::ConstrainedFollower>(
192 std::string path = bl::util::FileUtil::getExtension(file) ==
"map" ? file : file +
".map";
193 if (!bl::resource::FileSystem::resourceExists(path)) {
196 if (!bl::resource::FileSystem::resourceExists(path)) {
197 BL_LOG_ERROR <<
"Failed to find map '" << file <<
"'. Tried '" << path <<
"'";
204 if (!bl::serial::json::Serializer<Map>::deserializeStream(input, *
this))
return false;
210 if (!bl::serial::binary::Serializer<Map>::deserialize(input, *
this))
return false;
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)) {
234 return bl::serial::json::Serializer<Map>::serializeStream(output, *
this, 4, 0);
238 bl::serial::binary::OutputFile output(
242 return bl::serial::binary::Serializer<Map>::serialize(output, *
this);
247 bl::resource::bundle::FileHandlerContext& ctx)
const {
248 if (!bl::serial::binary::Serializer<Map>::serialize(output, *
this))
return false;
250 const auto addScript = [&ctx](
const std::string& src) {
252 if (bl::script::Script::getFullScriptPath(s)) { ctx.addDependencyFile(s); }
258 const auto addCharacter = [&ctx](
const std::string& cf) {
260 if (bl::util::FileUtil::exists(path)) { ctx.addDependencyFile(path); }
263 if (bl::util::FileUtil::exists(path)) { ctx.addDependencyFile(path); }
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();
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;
284 if (dir == bl::tmap::Direction::Left) npos.level += 1;
287 if (dir == bl::tmap::Direction::Right) npos.level += 1;
290 if (dir == bl::tmap::Direction::Down) npos.level += 1;
293 if (dir == bl::tmap::Direction::Up) npos.level += 1;
303 if (dir == bl::tmap::Direction::Right) npos.level -= 1;
306 if (dir == bl::tmap::Direction::Left) npos.level -= 1;
309 if (dir == bl::tmap::Direction::Up) npos.level -= 1;
312 if (dir == bl::tmap::Direction::Down) npos.level -= 1;
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;
330 bl::tmap::Position npos = pos.move(dir);
331 if (npos.position == pos.position)
return true;
334 switch (
levels.at(npos.level).collisionLayer().get(npos.position.x, npos.position.y)) {
340 return dir == bl::tmap::Direction::Down;
342 return dir == bl::tmap::Direction::Left;
344 return dir == bl::tmap::Direction::Up;
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;
365 return dir != bl::tmap::Direction::Up;
367 return dir != bl::tmap::Direction::Right;
373 BL_LOG_WARN <<
"Bad collision at (" << npos.position.x <<
", " << npos.position.y <<
")";
379 if (dir != bl::tmap::Direction::Down)
return false;
381 return levels.at(pos.level).collisionLayer().get(pos.position.x, pos.position.y) ==
390 const auto trigger = [
this, &movedEvent](
const Event& event) {
392 BL_LOG_INFO << movedEvent.
entity <<
" triggered event at (" <<
event.position.x <<
", "
393 <<
event.position.y <<
")";
394 bl::script::Script s(
400 const auto visitor = [&movedEvent, &trigger](
const Event* ep) {
401 const Event& e = *ep;
403 const bool wasIn = area.contains(
406 area.contains({movedEvent.
position.position.x, movedEvent.
position.position.y});
410 if (!wasIn && isIn) trigger(e);
414 if (wasIn && !isIn) trigger(e);
418 if (wasIn != isIn) trigger(e);
422 if (isIn) trigger(e);
441 auto& level =
levels[pos.level];
442 for (
auto& layer : level.bottomLayers()) {
443 layer.getRef(pos.position.x, pos.position.y).step();
445 for (
auto& layer : level.ysortLayers()) {
446 layer.getRef(pos.position.x, pos.position.y).step();
448 for (
auto& layer : level.topLayers()) {
449 layer.getRef(pos.position.x, pos.position.y).step();
454 bool Map::interact(bl::ecs::Entity interactor,
const bl::tmap::Position& pos) {
455 const auto trigger = [
this, interactor, &pos](
const Event& event) {
457 BL_LOG_INFO << interactor <<
" triggered event at (" << pos.position.x <<
", "
458 << pos.position.y <<
")";
459 bl::script::Script s(event.script,
465 const auto visitor = [&trigger, &pos, &found](
const Event* ep) ->
bool {
466 const Event& e = *ep;
468 if (area.contains({pos.position.x, pos.position.y}) &&
478 if (found)
return true;
482 levels[pos.level].collisionLayer().get(pos.position.x, pos.position.y);
486 "This ledge is pretty tall. I'll have to find a way around.");
491 "I bet the shoes I'm wearing will let me walk right over this!");
498 "With my super awesome upgrades Jesus Shoes I bet I can walk right up this!");
502 "Even my Jesus Shoes can't get up here. Maybe there's a new model available.");
510 const bl::tmap::Position prev = pos.move(bl::tmap::oppositeDirection(pos.direction));
512 levels[pos.level].collisionLayer().get(prev.position.x, prev.position.y);
515 "won't be able to get back up.");
543 const std::uint8_t i =
townTiles(pos.x, pos.y);
546 return &
towns[i - 1];
550 using bl::audio::AudioSystem;
556 const AudioSystem::Handle plst = AudioSystem::getOrLoadPlaylist(
558 if (plst != AudioSystem::InvalidHandle) { AudioSystem::replacePlaylist(plst, 1.5f, 1.5f); }
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;
576 std::uint8_t ci =
levels[pos.level].catchLayer().get(tiles.x, tiles.y);
577 if (ci == 0) {
return nullptr; }
580 BL_LOG_ERROR <<
"Bad catch zone index (" <<
static_cast<int>(ci)
581 <<
") at position: " << pos;
593 bl::resource::Ref<Map> world =
596 BL_LOG_CRITICAL <<
"Failed to load world map";
606 const auto sit =
spawns.find(spid);
607 return sit !=
spawns.end() ? &sit->second.position :
nullptr;
615 BL_LOG_INFO <<
"Generating map geometry...";
616 scene =
systems->
engine().renderer().scenePool().allocateScene<bl::rc::scene::Scene2D>();
621 BL_LOG_INFO <<
"Map geometry generated";
625 const auto pos = std::next(
renderLevels.begin(), level);
629 levels[level].layerCount(),
632 for (
unsigned int j = 0; j <
levels[level].layerCount(); ++j) {
setupLayer(level, j); }
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}); }
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);
644 tile.renderObject.emplace<std::monostate>();
651 std::advance(it, level);
652 auto& zone = *(it->zones[layer]);
654 layer >=
levels[level].bottomLayers().size() &&
655 layer <
levels[level].bottomLayers().size() +
levels[level].ysortLayers().size();
657 const auto getBottomDepth = [
this, &pos, layer, level, &tile]() {
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(); }
673 BL_LOG_ERROR <<
"Failed to find animation for tile id: " << tile.
id();
677 if (anim->isSlideshow()) {
679 systems->
engine(), anim, bl::gfx::DiscreteAnimation2DPlayer::Slideshow);
681 playerEntity = player.entity();
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);
692 const float bottomDepth = getBottomDepth();
693 vanim.getTransform().setDepth((topDepth + bottomDepth) * 0.5f);
698 if (isBatchSlideshow) {
699 bl::gfx::BatchSlideshowSimple& anim =
700 tile.renderObject.emplace<bl::gfx::BatchSlideshowSimple>(
703 for (
auto& v : anim.getVertices()) {
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;
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();
725 bl::gfx::BatchSpriteSimple& sprite =
726 tile.renderObject.emplace<bl::gfx::BatchSpriteSimple>(zone.tileSprites, src);
729 const float bottomDepth = getBottomDepth();
730 depth = (depth + bottomDepth) * 0.5f;
732 for (
auto& v : sprite.getVertices()) {
742 if (level >=
levels.size()) {
743 BL_LOG_ERROR <<
"Got invalid level: " << level;
748 const unsigned int maxLayers = std::accumulate(
751 levels.front().layerCount(),
752 [](
unsigned int val,
const LayerSet& level) { return std::max(val, level.layerCount()); });
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;
763 if (layer < lvl.
bottomLayers().size()) {
return -(zoneBias + levelBias); }
767 const float ysortZoneBias = y * maxLayers + layer;
768 return -(ysortZoneBias + maxZoneBias + levelBias);
772 return -(zoneBias + maxZoneBias * 2.f + levelBias);
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>();
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";
802 bl::com::Transform2D* transform = set.get<bl::com::Transform2D>();
803 bl::tmap::Position* pos = set.get<bl::tmap::Position>();
804 pos->transform = transform;
Collision
The different types of collisions in maps.
@ 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.
void notifyMoveState(bl::tmap::Direction dir, bool moving, bool running)
Call when the entity starts or stops moving or changes direction.
Fired after an entity begins moving from one position to another.
const bl::tmap::Position previousPosition
The previous position of the entity.
const bl::tmap::Position & position
The current position of the entity.
const bl::ecs::Entity entity
The entity that moved.
Represents a class of catchable peoplemon.
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.
@ 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.
Basic struct representing a pickup-able item in Map.
Encapsulates a set of layers for a given map height. Maps have one LayerSet per height level,...
std::vector< TileLayer > & bottomLayers()
Returns a reference to the bottom tiles in this set.
std::vector< TileLayer > & ysortLayers()
Returns a reference to the sorted tiles in this set.
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.
float getMinDepth() const
Returns the maximum depth possible for this map. Maximum is always 0.f. Minimum is negative,...
virtual void observe(const event::EntityMoved &moveEvent) override
Event listener for moving entities. Used to trigger map events.
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.
bl::ctr::Grid< const Event * > eventRegions
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....
std::vector< Item > itemsField
bool isLedgeHop(const bl::tmap::Position &position, bl::tmap::Direction dir) const
Test whether the given movement will be a ledge hop or not.
std::unordered_map< std::uint16_t, Spawn > spawns
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.
std::vector< LayerSet > levels
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...
Town * getTown(const glm::i32vec2 &pos)
void setupCamera(system::Systems &systems)
Configures the game camera for the player in the map.
const std::string & getLocationName(const bl::tmap::Position &pos) const
Returns the name of the town or route at the given position.
virtual ~Map()
Destroy the Map.
std::string unloadScriptField
void enterTown(Town *town)
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 ....
static std::vector< Town > flymapTowns
std::vector< Event > eventsField
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.
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,...
Map()
Creates an empty Map.
std::string loadScriptField
Weather::Type weatherField
LightingSystem & lightingSystem()
Returns a reference to the lighting system in this map.
const bl::tmap::Position * getSpawnPosition(unsigned int spawnId) const
Returns the position of the given player spawn, or nullptr if not found.
bool canFlyFromHere() const
Returns whether or not the player can fly from this map.
bl::resource::Ref< Tileset > tileset
bool contains(const bl::tmap::Position &position) const
Returns whether or not the map contains the given position.
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.
std::unique_ptr< bl::script::Script > onEnterScript
std::uint8_t levelCount() const
Returns the number of levels in the map.
void setupTile(unsigned int level, unsigned int layer, const sf::Vector2u &pos)
std::unique_ptr< bl::script::Script > onExitScript
static const std::vector< Town > & FlyMapTowns()
Returns the set of towns that can be flown to.
void exit(system::Systems &systems, const std::string &newMap)
Removes spawned entities and runs the on-unload script.
std::vector< Town > towns
bl::ctr::Vector2D< std::uint8_t > townTiles
const sf::Vector2i & sizeTiles() const
Returns the size of the map in tiles.
std::vector< CharacterSpawn > characterField
bl::ctr::Vector2D< LevelTransition > transitionField
void triggerAnimation(const bl::tmap::Position &position)
bl::audio::AudioSystem::Handle playlistHandle
void setupLayer(unsigned int level, unsigned int layer)
bl::rc::lgt::Scene2DLighting & getSceneLighting()
Returns the scene lighting for this map. Only valid after enter() is called.
std::string playlistField
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.
Weather & weatherSystem()
Returns a reference to the weather system in this map.
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...
void setupEntityPosition(bl::ecs::Entity entity)
Performs the final setup of the position components for the given entity. Must already have a bl::tma...
std::vector< CatchRegion > catchRegionsField
static void loadFlymapTowns()
void update(float dt)
Updates internal logic over the elapsed time.
system::Systems * systems
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...
void setupLevel(unsigned int level)
const std::string & name() const
Returns the name of the map.
sf::Vector2f sizePixels() const
Returns the size of the map in pixels.
std::list< RenderLevel > renderLevels
Data representation of a tile in a Map TileLayer.
IdType id() const
Returns the id of the image or animation of this tile.
static constexpr IdType Blank
Special id for blank tiles.
bool isAnimation() const
Returns whether this tile is an animation or not.
void set(IdType id, bool anim)
Sets the information of the tile.
static std::string getFullPath(const std::string &path)
Generates the full path to the given tileset file.
Represents a town, route, or region within a map. Maps may have many towns. Individual tiles are asso...
Parent weather system for maps. Manages active weather.
@ None
No weather will occur.
void update(float dt)
Updates the current weather.
void activate(system::Systems &systems, Map &map)
Activates the weather system.
void set(Type type, bool immediate=false)
Sets the current weather type.
bool hasItem(item::Id item) const
Returns true if at least one of the given items is owned.
std::unordered_set< std::string > visitedTowns
static const std::string & TrainerPath()
static const std::string & MapPath()
static int PixelsPerTile()
static const std::string & NpcPath()
static sf::Vector2f WindowSize()
static int WindowHeight()
static const std::string & PlaylistPath()
static void warn(const std::string &script)
In debug mode logs a warning if the given script is detected to be legacy.
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.
void displayEntryCard(const std::string &name)
Displays a card to indicate entering a new town, route, or map.
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.
player::State & state()
Returns the state of the player.
bl::ecs::Entity player() const
Returns the id of the player entity.
Owns all primary systems and a reference to the engine.
const bl::engine::Engine & engine() const
Const accessor for the Engine.
Player & player()
Returns the player system.
Flight & flight()
Returns the flight system.
HUD & hud()
Returns the HUD.
World & world()
Modifiable accessor for the world system.
void setWhiteoutMap(unsigned int spawn)
Sets the respawn point to the given spawn in the current map.