Files
2026-02-02 04:50:13 +01:00

375 lines
10 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* This code is based on the CRAB engine
*
* Copyright (c) Arvind Raja Yadav
*
* Licensed under MIT
*
*/
#include "crab/image/ImageManager.h"
#include "crab/text/TextManager.h"
#include "crab/TMX/TMXMap.h"
namespace Crab {
using namespace TMX;
using namespace pyrodactyl::image;
static bool propCompare(const MapLayer &l1, const MapLayer &l2) {
return l1._pos.y + l1._pos.h < l2._pos.y + l2._pos.h;
}
TMXMap::TMXMap() {
_tileRows = 0;
_tileCols = 0;
_pathRows = 0;
_pathCols = 0;
_w = 0;
_h = 0;
_spriteLayer = 0;
_grid = nullptr;
_movementCosts._noWalk = 0;
_movementCosts._open = 0;
_movementCosts._stairs = 0;
}
//------------------------------------------------------------------------
// Purpose: Load stuff via a .tmx file set to xml storage (no compression)
//------------------------------------------------------------------------
void TMXMap::load(const Common::Path &path, const Common::String &filename) {
XMLDoc conf(path.appendComponent(filename));
if (conf.ready()) {
rapidxml::xml_node<char> *node = conf.doc()->first_node("map");
if (nodeValid(node)) {
loadNum(_tileRows, "width", node);
loadNum(_tileCols, "height", node);
loadNum(_tileSize.x, "tilewidth", node);
loadNum(_tileSize.y, "tileheight", node);
// Pathfinding info load
_pathSize.x = 0;
_pathSize.y = 0;
loadNum(_pathSize.x, "pathwidth", node);
loadNum(_pathSize.y, "pathheight", node);
// Load costs...Not sure if this is right. (SZ)
loadNum(_movementCosts._open, "opencost", node);
loadNum(_movementCosts._noWalk, "nowalkcost", node);
loadNum(_movementCosts._stairs, "stairscost", node);
// if(path_size.x == 0)
// path_size.x = tile_size.x;
// if(path_size.y == 0)
// path_size.y = tile_size.y;
// Testing
if (_pathSize.x == 0)
_pathSize.x = 40;
if (_pathSize.y == 0)
_pathSize.y = 40;
_w = _tileRows * _tileSize.x;
_h = _tileCols * _tileSize.y;
_pathRows = (int)ceil((float)_w / (float)_pathSize.x + .5f); // Adding .5 before casting in order to round up (SZ)
_pathCols = (int)ceil((float)_h / (float)_pathSize.y + .5f);
g_engine->_imageManager->_tileset.load(path, node);
// Reset the layer at which sprites are drawn
_spriteLayer = 0;
uint layerCount = 0;
// We need to cycle through all tile and object layers in order to
// see the level at which the sprites will be drawn
for (auto groupnode = node->first_node(); groupnode != nullptr; groupnode = groupnode->next_sibling()) {
// Store the name for easy comparison
Common::String name = groupnode->name();
if (name == "layer" || name == "imagelayer") { // Is this a tile or an image layer
MapLayer l;
l.load(path, groupnode);
l._pos.x *= _tileSize.x;
l._pos.y *= _tileSize.y;
l._pos.w *= _tileSize.x;
l._pos.h *= _tileSize.y;
if (l._type == LAYER_PROP)
_prop.push_back(l);
else
_layer.push_back(l);
layerCount++;
} else if (name == "objectgroup") { // Is this an object layer
Common::String groupName;
loadStr(groupName, "name", groupnode);
if (groupName == "exit") {
for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
pyrodactyl::level::Exit le(n);
_areaExit.push_back(le);
}
} else if (groupName == "walk") {
auto n = groupnode->first_node("object");
if (n != nullptr)
_areaWalk.load(n, true, "x", "y", "width", "height");
} else if (groupName == "no_walk") {
for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
Shape s;
s.load(n);
_areaNowalk.push_back(s);
}
} else if (groupName == "trigger") {
for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
Shape s;
s.load(n);
uint pos = _areaTrig.size();
loadNum(pos, "name", n);
if (_areaTrig.size() <= pos)
_areaTrig.resize(pos + 1);
_areaTrig[pos] = s;
}
} else if (groupName == "stairs") {
for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
pyrodactyl::level::Stairs s;
s.load(n);
_areaStairs.push_back(s);
}
} else if (groupName == "music") {
for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
pyrodactyl::level::MusicArea ma;
ma.load(n);
_areaMusic.push_back(ma);
}
} else if (groupName == "sprites")
_spriteLayer = layerCount;
}
}
// Sort the props in the level according to y axis
Common::sort(_prop.begin(), _prop.end(), propCompare);
}
}
}
//------------------------------------------------------------------------
// Purpose: Use this if you want to load a poly line from tmx
//------------------------------------------------------------------------
// void TMXMap::LoadPath(rapidxml::xml_node<char> *node)
//{
// int pos = 0;
// for (auto n = node->first_node("object"); n != nullptr; n = n->next_sibling("object"), ++pos)
// {
// Vector2i start;
// start.load(n);
//
// rapidxml::xml_node<char> *linenode = n->first_node("polyline");
// if (linenode != nullptr)
// {
// Common::String points, x, y;
// loadStr(points, "points", linenode);
//
// path.resize(pos + 1);
// bool comma = false;
// for (auto i = points.begin(); i != points.end(); ++i)
// {
// if (*i == ',') comma = true;
// else if (*i == ' ')
// {
// path[pos].push_back(GetPoint(start, x, y));
// comma = false;
// x.clear();
// y.clear();
// }
// else if (comma) y.push_back(*i);
// else x.push_back(*i);
// }
// path[pos].push_back(GetPoint(start, x, y));
// }
// }
//}
//------------------------------------------------------------------------
// Purpose: Convert point from string to vector
//------------------------------------------------------------------------
// const Vector2i TMXMap::GetPoint(const Vector2i &ref, Common::String &x, Common::String &y)
//{
// Vector2i v;
// v.x = ref.x + StringToNumber<int>(x);
// v.y = ref.y + StringToNumber<int>(y);
// return v;
//}
//------------------------------------------------------------------------
// Purpose: Clear all data from the level
//------------------------------------------------------------------------
void TMXMap::reset() {
g_engine->_imageManager->_tileset.reset();
_layer.clear();
_areaNowalk.clear();
_areaExit.clear();
_areaTrig.clear();
_areaStairs.clear();
_areaMusic.clear();
_prop.clear();
_spriteLayer = 0;
}
//------------------------------------------------------------------------
// Purpose: Draw functions
//------------------------------------------------------------------------
void TMXMap::drawDebug(const Rect &camera) {
using namespace pyrodactyl::text;
for (auto &i : _areaTrig)
i.draw(-camera.x, -camera.y, 0, 0, 254, 254);
for (auto &i : _areaExit)
i._dim.draw(-camera.x, -camera.y, 0, 254, 254, 254);
for (auto &i : _prop)
i._pos.draw(-camera.x, -camera.y, 254, 0, 254, 254);
for (auto &i : _areaNowalk)
i.draw(-camera.x, -camera.y, 254, 0, 0, 254);
for (auto &i : _areaMusic)
i.draw(-camera.x, -camera.y, 254, 254, 0, 254);
for (auto &i : _areaStairs) {
i.draw(-camera.x, -camera.y, 0, 254, 0, 254);
}
// Draw the pathfinding grid (SZ)
for (int x = 0; x < _grid->getDimensions().x; ++x) {
for (int y = 0; y < _grid->getDimensions().y; ++y) {
if (_grid->getNodeAtCoords(x, y)->getMovementCost() < 0.0f)
_grid->getNodeAtCoords(x, y)->getRect().draw(-camera.x, -camera.y, 0, 0, 0, 254);
}
}
for (auto &i : _layer)
i._pos.draw(-camera.x, -camera.y, 254, 216, 0);
_areaWalk.draw(-camera.x, -camera.y, 254, 254, 254, 254);
}
//------------------------------------------------------------------------
// Purpose: Collision functions
//------------------------------------------------------------------------
void TMXMap::collideWithNoWalk(const Rect boundingBox, Common::List<CollisionData> &colliders) {
CollisionData res;
for (auto &i : _areaNowalk) {
res = i.collide(boundingBox);
if (res._intersect)
colliders.push_back(res);
}
}
bool TMXMap::insideNoWalk(const Vector2i &pos) {
for (auto &i : _areaNowalk)
if (i.contains(pos))
return true;
return false;
}
bool TMXMap::insideWalk(const Rect &boundingBox) {
if (_areaWalk.contains(boundingBox))
return true;
return false;
}
bool TMXMap::insideWalk(const Vector2i &pos) {
if (_areaWalk.contains(pos))
return true;
return false;
}
bool TMXMap::collideWithTrigger(const Rect rect, int index) {
if (_areaTrig.size() > (uint)index)
return _areaTrig[index].collide(rect)._intersect;
return false;
}
void TMXMap::collideWithTrigger(const Rect rect, Common::Array<int> &collisionTable) {
int index = 0;
collisionTable.clear();
for (auto i = _areaTrig.begin(); i != _areaTrig.end(); ++i, ++index)
if (i->collide(rect)._intersect)
collisionTable.push_back(index);
}
bool TMXMap::collideWithExit(const Rect rect, LevelResult &res) {
for (const auto &i : _areaExit)
if (i._dim.collide(rect)._intersect) {
res._val = i._name;
res._x = i._entry.x;
res._y = i._entry.y;
return true;
}
return false;
}
bool TMXMap::collideWithStairs(const Rect rect, Vector2f &velMod) {
for (const auto &i : _areaStairs) {
if (i.collide(rect)._intersect) {
velMod = i._modifier;
return true;
}
}
// We are not colliding with any stairs, reset the modifier
velMod.x = 1.0f;
velMod.y = 1.0f;
return false;
}
bool TMXMap::collideWithMusic(const Rect rect, pyrodactyl::level::MusicInfo &music) {
for (const auto &i : _areaMusic) {
if (i.collide(rect)._intersect) {
music._id = i._id;
music._track = i._track;
music._loops = i._loops;
return true;
}
}
return false;
}
} // End of namespace Crab