refactor: Refactor map

- Extract map logic into a separate class
- Load map properties as part of the area
This commit is contained in:
Vsevolod Kremianskii 2020-12-07 08:38:31 +07:00
parent 74fe30beaa
commit 5677bf29cb
9 changed files with 258 additions and 108 deletions

View file

@ -399,6 +399,7 @@ set(GAME_HEADERS
src/game/gui/partyselect.h
src/game/gui/saveload.h
src/game/gui/selectoverlay.h
src/game/map.h
src/game/object/area.h
src/game/object/camera.h
src/game/object/creature.h
@ -486,6 +487,7 @@ set(GAME_SOURCES
src/game/gui/partyselect.cpp
src/game/gui/saveload.cpp
src/game/gui/selectoverlay.cpp
src/game/map.cpp
src/game/object/area.cpp
src/game/object/camera.cpp
src/game/object/creature.cpp

View file

@ -17,18 +17,16 @@
#include "map.h"
#include "../../../common/log.h"
#include "../../../render/mesh/quad.h"
#include "../../../render/textures.h"
#include <stdexcept>
#include "../../game.h"
#include "../../map.h"
#include "../colors.h"
using namespace std;
using namespace reone::gui;
using namespace reone::render;
using namespace reone::resource;
namespace reone {
@ -56,98 +54,22 @@ void MapMenu::load() {
disableControl("BTN_PRTYSLCT");
disableControl("BTN_RETURN");
string arrowTex("mm_barrow");
if (_version == GameVersion::TheSithLords) {
arrowTex += "_p";
}
_arrow = Textures::instance().get(arrowTex, TextureType::GUI);
}
void MapMenu::update(float dt) {
const ModuleInfo &info = _game->module()->info();
string mapResRef("lbl_map" + info.entryArea);
_map = Textures::instance().get(mapResRef, TextureType::GUI);
}
void MapMenu::render() const {
GUI::render();
if (!_map) return;
drawMap();
drawPartyLeader();
}
void MapMenu::drawMap() const {
Control &label = getControl("LBL_Map");
const Control::Extent &extent = label.extent();
glm::mat4 transform(1.0f);
transform = glm::translate(transform, glm::vec3(_controlOffset.x + extent.left, _controlOffset.y + extent.top, 0.0f));
transform = glm::scale(transform, glm::vec3(extent.width, extent.height, 1.0f));
glm::vec4 bounds(
_controlOffset.x + extent.left,
_controlOffset.y + extent.top,
extent.width,
extent.height);
LocalUniforms locals;
locals.general.model = transform;
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
_map->bind(0);
Quad::getDefault().renderTriangles();
}
void MapMenu::drawPartyLeader() const {
shared_ptr<Creature> partyLeader(_game->party().leader());
if (!partyLeader) return;
const ModuleInfo &info = _game->module()->info();
glm::vec3 leaderPos(partyLeader->position());
float scaleX, scaleY, relX, relY;
switch (info.northAxis) {
case 0:
case 1:
scaleX = (info.mapPoint1.x - info.mapPoint2.x) / (info.worldPoint1.x - info.worldPoint2.x);
scaleY = (info.mapPoint1.y - info.mapPoint2.y) / (info.worldPoint1.y - info.worldPoint2.y);
relX = (leaderPos.x - info.worldPoint1.x) * scaleX + info.mapPoint1.x;
relY = (leaderPos.y - info.worldPoint1.y) * scaleY + info.mapPoint1.y;
break;
case 2:
case 3:
scaleX = (info.mapPoint1.y - info.mapPoint2.y) / (info.worldPoint1.x - info.worldPoint2.x);
scaleY = (info.mapPoint1.x - info.mapPoint2.x) / (info.worldPoint1.y - info.worldPoint2.y);
relX = (leaderPos.y - info.worldPoint1.y) * scaleY + info.mapPoint1.x;
relY = (leaderPos.x - info.worldPoint1.x) * scaleX + info.mapPoint1.y;
break;
default:
warn("Map: invalid north axis: " + to_string(info.northAxis));
return;
}
Control &label = getControl("LBL_Map");
const Control::Extent &extent = label.extent();
relX *= extent.width / static_cast<float>(_map->width());
relY *= extent.height / static_cast<float>(_map->height());
glm::vec3 arrowPos(
_controlOffset.x + extent.left + relX * extent.width - 0.5f * _arrow->width(),
_controlOffset.y + extent.top + relY * extent.height - 0.5f * _arrow->height(),
0.0f);
glm::mat4 transform(1.0f);
transform = glm::translate(transform, arrowPos);
transform = glm::scale(transform, glm::vec3(_arrow->width(), _arrow->height(), 1.0f));
LocalUniforms locals;
locals.general.model = transform;
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
_arrow->bind(0);
Quad::getDefault().renderTriangles();
shared_ptr<Area> area(_game->module()->area());
area->map().render(Map::Mode::Default, bounds);
}
void MapMenu::onClick(const string &control) {

View file

@ -17,6 +17,8 @@
#pragma once
#include <memory>
#include "../../../gui/gui.h"
namespace reone {
@ -24,24 +26,19 @@ namespace reone {
namespace game {
class Game;
class Map;
class MapMenu : public gui::GUI {
public:
MapMenu(Game *game);
void load() override;
void update(float dt) override;
void render() const override;
private:
Game *_game { nullptr };
std::shared_ptr<render::Texture> _map;
std::shared_ptr<render::Texture> _arrow;
void onClick(const std::string &control) override;
void drawMap() const;
void drawPartyLeader() const;
};
} // namespace game

156
src/game/map.cpp Normal file
View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2020 The reone project contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "map.h"
#include <stdexcept>
#include "glm/mat4x4.hpp"
#include "../common/log.h"
#include "../render/mesh/quad.h"
#include "../render/textures.h"
#include "../resource/types.h"
#include "game.h"
using namespace std;
using namespace reone::render;
using namespace reone::resource;
namespace reone {
namespace game {
Map::Map(Game *game) : _game(game) {
if (!game) {
throw invalid_argument("game must not be null");
}
}
void Map::load(const string &area, const GffStruct &gffs) {
loadProperties(gffs);
loadTexture(area);
if (!_arrow) {
loadArrow();
}
}
void Map::loadProperties(const GffStruct &gffs) {
_northAxis = gffs.getInt("NorthAxis");
_worldPoint1 = glm::vec2(gffs.getFloat("WorldPt1X"), gffs.getFloat("WorldPt1Y"));
_worldPoint2 = glm::vec2(gffs.getFloat("WorldPt2X"), gffs.getFloat("WorldPt2Y"));
_mapPoint1 = glm::vec2(gffs.getFloat("MapPt1X"), gffs.getFloat("MapPt1Y"));
_mapPoint2 = glm::vec2(gffs.getFloat("MapPt2X"), gffs.getFloat("MapPt2Y"));
}
void Map::loadTexture(const string &area) {
string resRef("lbl_map" + area);
_texture = Textures::instance().get(resRef, TextureType::GUI);
}
void Map::loadArrow() {
string resRef("mm_barrow");
if (_game->version() == GameVersion::TheSithLords) {
resRef += "_p";
}
_arrow = Textures::instance().get(resRef, TextureType::GUI);
}
void Map::render(Mode mode, const glm::vec4 &bounds) const {
if (!_texture) return;
drawArea(mode, bounds);
drawPartyLeader(mode, bounds);
}
void Map::drawArea(Mode mode, const glm::vec4 &bounds) const {
glm::mat4 transform(1.0f);
transform = glm::translate(transform, glm::vec3(bounds[0], bounds[1], 0.0f));
transform = glm::scale(transform, glm::vec3(bounds[2], bounds[3], 1.0f));
LocalUniforms locals;
locals.general.model = transform;
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
_texture->bind(0);
Quad::getDefault().renderTriangles();
}
void Map::drawPartyLeader(Mode mode, const glm::vec4 &bounds) const {
shared_ptr<Creature> partyLeader(_game->party().leader());
if (!partyLeader) return;
glm::vec3 worldPos(partyLeader->position());
glm::vec2 mapPos(getMapPosition(worldPos));
mapPos.x *= bounds[2] / static_cast<float>(_texture->width());
mapPos.y *= bounds[3] / static_cast<float>(_texture->height());
glm::vec3 arrowPos(
bounds[0] + mapPos.x * bounds[2] - 0.5f * _arrow->width(),
bounds[1] + mapPos.y * bounds[3] - 0.5f * _arrow->height(),
0.0f);
glm::mat4 transform(1.0f);
transform = glm::translate(transform, arrowPos);
transform = glm::scale(transform, glm::vec3(_arrow->width(), _arrow->height(), 1.0f));
LocalUniforms locals;
locals.general.model = transform;
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
_arrow->bind(0);
Quad::getDefault().renderTriangles();
}
glm::vec2 Map::getMapPosition(const glm::vec2 &world) const {
float scaleX, scaleY;
glm::vec2 result(0.0f);
switch (_northAxis) {
case 0:
case 1:
scaleX = (_mapPoint1.x - _mapPoint2.x) / (_worldPoint1.x - _worldPoint2.x);
scaleY = (_mapPoint1.y - _mapPoint2.y) / (_worldPoint1.y - _worldPoint2.y);
result.x = (world.x - _worldPoint1.x) * scaleX + _mapPoint1.x;
result.y = (world.y - _worldPoint1.y) * scaleY + _mapPoint1.y;
break;
case 2:
case 3:
scaleX = (_mapPoint1.y - _mapPoint2.y) / (_worldPoint1.x - _worldPoint2.x);
scaleY = (_mapPoint1.x - _mapPoint2.x) / (_worldPoint1.y - _worldPoint2.y);
result.x = (world.y - _worldPoint1.y) * scaleY + _mapPoint1.x;
result.y = (world.x - _worldPoint1.x) * scaleX + _mapPoint1.y;
break;
default:
warn("Map: invalid north axis: " + to_string(_northAxis));
break;
}
return move(result);
}
} // namespace game
} // namespace reone

70
src/game/map.h Normal file
View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2020 The reone project contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <string>
#include "glm/vec2.hpp"
#include "glm/vec4.hpp"
#include "../render/texture.h"
#include "../resource/gfffile.h"
namespace reone {
namespace game {
class Game;
class Map {
public:
enum class Mode {
Default,
Minimap
};
Map(Game *game);
void load(const std::string &area, const resource::GffStruct &gffs);
void render(Mode mode, const glm::vec4 &bounds) const;
private:
int _northAxis { 0 };
glm::vec2 _worldPoint1 { 0.0f };
glm::vec2 _worldPoint2 { 0.0f };
glm::vec2 _mapPoint1 { 0.0f };
glm::vec2 _mapPoint2 { 0.0f };
Game *_game { nullptr };
std::shared_ptr<render::Texture> _texture;
std::shared_ptr<render::Texture> _arrow;
void loadProperties(const resource::GffStruct &gffs);
void loadTexture(const std::string &area);
void loadArrow();
void drawArea(Mode mode, const glm::vec4 &bounds) const;
void drawPartyLeader(Mode mode, const glm::vec4 &bounds) const;
glm::vec2 getMapPosition(const glm::vec2 &world) const;
};
} // namespace game
} // namespace reone

View file

@ -68,7 +68,8 @@ Area::Area(uint32_t id, Game *game) :
_collisionDetector(this),
_objectSelector(this, &game->party()),
_actionExecutor(game),
_combat(game) {
_combat(game),
_map(game) {
const GraphicsOptions &opts = _game->options().graphics;
_cameraAspect = opts.width / static_cast<float>(opts.height);
@ -140,6 +141,7 @@ void Area::loadARE(const GffStruct &are) {
loadCameraStyle(are);
loadAmbientColor(are);
loadScripts(are);
loadMap(are);
}
void Area::loadCameraStyle(const GffStruct &are) {
@ -171,6 +173,10 @@ void Area::loadScripts(const GffStruct &are) {
_onUserDefined = are.getString("OnUserDefined");
}
void Area::loadMap(const GffStruct &are) {
_map.load(_name, are.getStruct("Map"));
}
void Area::loadGIT(const GffStruct &git) {
loadProperties(git);
loadCreatures(git);
@ -816,6 +822,10 @@ Combat &Area::combat() {
return _combat;
}
Map &Area::map() {
return _map;
}
const ObjectList &Area::objects() const {
return _objects;
}

View file

@ -38,6 +38,7 @@
#include "../camera/thirdperson.h"
#include "../camera/types.h"
#include "../collisiondetect.h"
#include "../map.h"
#include "../objectselect.h"
#include "../pathfinder.h"
@ -90,6 +91,7 @@ public:
const Pathfinder &pathfinder() const;
const RoomMap &rooms() const;
Combat &combat();
Map &map();
// Objects
@ -128,6 +130,7 @@ private:
CameraStyle _cameraStyle;
std::string _music;
float _heartbeatTimeout { kHeartbeatInterval };
Map _map;
// Scripts
@ -180,10 +183,13 @@ private:
void loadVIS();
void loadPTH();
void loadARE(const resource::GffStruct &are);
void loadGIT(const resource::GffStruct &gffs);
void loadCameraStyle(const resource::GffStruct &are);
void loadAmbientColor(const resource::GffStruct &are);
void loadScripts(const resource::GffStruct &are);
void loadGIT(const resource::GffStruct &gffs);
void loadMap(const resource::GffStruct &are);
void loadProperties(const resource::GffStruct &git);
void loadCreatures(const resource::GffStruct &git);
void loadDoors(const resource::GffStruct &git);

View file

@ -76,14 +76,6 @@ void Module::loadArea(const GffStruct &ifo) {
shared_ptr<Area> area(_game->objectFactory().newArea());
area->load(_info.entryArea, *are, *git);
_area = move(area);
// TODO: move map properties to Area
auto &map = are->getStruct("Map");
_info.northAxis = map.getInt("NorthAxis");
_info.worldPoint1 = glm::vec2(map.getFloat("WorldPt1X"), map.getFloat("WorldPt1Y"));
_info.worldPoint2 = glm::vec2(map.getFloat("WorldPt2X"), map.getFloat("WorldPt2Y"));
_info.mapPoint1 = glm::vec2(map.getFloat("MapPt1X"), map.getFloat("MapPt1Y"));
_info.mapPoint2 = glm::vec2(map.getFloat("MapPt2X"), map.getFloat("MapPt2Y"));
}
void Module::loadPlayer() {

View file

@ -46,11 +46,6 @@ struct ModuleInfo {
std::string entryArea;
glm::vec3 entryPosition { 0.0f };
float entryHeading { 0.0f };
int northAxis { 0 };
glm::vec2 worldPoint1 { 0.0f };
glm::vec2 worldPoint2 { 0.0f };
glm::vec2 mapPoint1 { 0.0f };
glm::vec2 mapPoint2 { 0.0f };
};
class Door;