feat(game): Implement map notes

This commit is contained in:
Vsevolod Kremianskii 2021-01-30 23:22:15 +07:00
parent e34d21a7b0
commit 53fbfe49fc
14 changed files with 304 additions and 63 deletions

View file

@ -381,6 +381,7 @@ set(GAME_HEADERS
src/game/blueprint/placeable.h
src/game/blueprint/sound.h
src/game/blueprint/trigger.h
src/game/blueprint/waypoint.h
src/game/camera/animatedcamera.h
src/game/camera/camera.h
src/game/camera/camerastyle.h
@ -494,6 +495,7 @@ set(GAME_SOURCES
src/game/blueprint/placeable.cpp
src/game/blueprint/sound.cpp
src/game/blueprint/trigger.cpp
src/game/blueprint/waypoint.cpp
src/game/camera/animatedcamera.cpp
src/game/camera/camera.cpp
src/game/camera/camerastyle.cpp

View file

@ -40,6 +40,7 @@ void Blueprints::invalidateCache() {
_placeableCache.clear();
_soundCache.clear();
_triggerCache.clear();
_waypointCache.clear();
}
template <class T>
@ -85,6 +86,10 @@ shared_ptr<TriggerBlueprint> Blueprints::getTrigger(const string &resRef) {
return getBlueprint<TriggerBlueprint>(resRef, ResourceType::Utt, _triggerCache);
}
shared_ptr<WaypointBlueprint> Blueprints::getWaypoint(const string &resRef) {
return getBlueprint<WaypointBlueprint>(resRef, ResourceType::Utw, _waypointCache);
}
} // namespace game
} // namespace reone

View file

@ -29,6 +29,7 @@
#include "placeable.h"
#include "sound.h"
#include "trigger.h"
#include "waypoint.h"
namespace reone {
@ -46,6 +47,7 @@ public:
std::shared_ptr<PlaceableBlueprint> getPlaceable(const std::string &resRef);
std::shared_ptr<SoundBlueprint> getSound(const std::string &resRef);
std::shared_ptr<TriggerBlueprint> getTrigger(const std::string &resRef);
std::shared_ptr<WaypointBlueprint> getWaypoint(const std::string &resRef);
private:
std::unordered_map<std::string, std::shared_ptr<CreatureBlueprint>> _creatureCache;
@ -54,6 +56,7 @@ private:
std::unordered_map<std::string, std::shared_ptr<PlaceableBlueprint>> _placeableCache;
std::unordered_map<std::string, std::shared_ptr<SoundBlueprint>> _soundCache;
std::unordered_map<std::string, std::shared_ptr<TriggerBlueprint>> _triggerCache;
std::unordered_map<std::string, std::shared_ptr<WaypointBlueprint>> _waypointCache;
Blueprints() = default;
Blueprints(const Blueprints &) = delete;

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020-2021 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 "waypoint.h"
#include <stdexcept>
#include <boost/algorithm/string.hpp>
#include "../object/waypoint.h"
using namespace std;
using namespace reone::resource;
namespace reone {
namespace game {
WaypointBlueprint::WaypointBlueprint(const string &resRef, const shared_ptr<GffStruct> &utw) :
_resRef(resRef),
_utw(utw) {
if (!utw) {
throw invalid_argument("utw must not be null");
}
}
void WaypointBlueprint::load(Waypoint &waypoint) {
}
} // namespace resource
} // namespace reone

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2020-2021 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 <string>
#include <memory>
#include "../../resource/format/gfffile.h"
namespace reone {
namespace game {
class Waypoint;
class WaypointBlueprint {
public:
WaypointBlueprint(const std::string &resRef, const std::shared_ptr<resource::GffStruct> &utw);
void load(Waypoint &waypoint);
private:
std::string _resRef;
std::shared_ptr<resource::GffStruct> _utw;
WaypointBlueprint(const WaypointBlueprint &) = delete;
WaypointBlueprint &operator=(const WaypointBlueprint &) = delete;
};
} // namespace game
} // namespace reone

View file

@ -213,6 +213,7 @@ void InGameMenu::openJournal() {
}
void InGameMenu::openMap() {
_map->refreshControls();
changeTab(Tab::Map);
}

View file

@ -19,6 +19,8 @@
#include <stdexcept>
#include "../../../resource/resources.h"
#include "../../game.h"
#include "../../map.h"
@ -33,6 +35,8 @@ namespace reone {
namespace game {
static constexpr int kStrRefMapNote = 349;
MapMenu::MapMenu(Game *game) :
GameGUI(game->gameId(), game->options().graphics),
_game(game) {
@ -62,8 +66,23 @@ void MapMenu::render() const {
extent.width,
extent.height);
shared_ptr<Area> area(_game->module()->area());
area->map().render(Map::Mode::Default, bounds);
_game->module()->area()->map().render(Map::Mode::Default, bounds);
}
void MapMenu::refreshControls() {
setControlText("LBL_Area", _game->module()->area()->localizedName());
_notes.clear();
for (auto &object : _game->module()->area()->getObjectsByType(ObjectType::Waypoint)) {
auto &waypoint = static_pointer_cast<Waypoint>(object);
if (waypoint->isMapNoteEnabled() && !waypoint->mapNote().empty()) {
_notes.push_back(waypoint);
}
}
_selectedNoteIdx = 0;
refreshSelectedNote();
}
void MapMenu::onClick(const string &control) {
@ -71,9 +90,35 @@ void MapMenu::onClick(const string &control) {
if (control == "BTN_EXIT") {
_game->openInGame();
} else if (control == "BTN_UP") {
if (--_selectedNoteIdx == -1) {
_selectedNoteIdx = _notes.size() - 1;
}
refreshSelectedNote();
} else if (control == "BTN_DOWN") {
if (++_selectedNoteIdx == static_cast<int>(_notes.size())) {
_selectedNoteIdx = 0;
}
refreshSelectedNote();
}
}
void MapMenu::refreshSelectedNote() {
shared_ptr<Waypoint> note;
if (!_notes.empty()) {
note = _notes[_selectedNoteIdx];
string text(Resources::instance().getString(kStrRefMapNote));
text += ": ";
text += note->mapNote();
setControlText("LBL_MapNote", text);
}
_game->module()->area()->map().setSelectedNote(note);
}
} // namespace game
} // namespace reone

View file

@ -25,6 +25,7 @@ namespace game {
class Game;
class Map;
class Waypoint;
class MapMenu : public GameGUI {
public:
@ -33,10 +34,17 @@ public:
void load() override;
void render() const override;
void refreshControls();
private:
Game *_game { nullptr };
Game *_game;
std::vector<std::shared_ptr<Waypoint>> _notes;
int _selectedNoteIdx { 0 };
void onClick(const std::string &control) override;
void refreshSelectedNote();
};
} // namespace game

View file

@ -32,6 +32,7 @@
#include "../resource/types.h"
#include "game.h"
#include "gui/colorutil.h"
using namespace std;
@ -43,6 +44,8 @@ namespace reone {
namespace game {
static constexpr int kArrowSize = 32;
static constexpr int kMapNoteSize = 16;
static constexpr float kSelectedMapNoteScale = 1.5f;
Map::Map(Game *game) : _game(game) {
if (!game) {
@ -52,11 +55,7 @@ Map::Map(Game *game) : _game(game) {
void Map::load(const string &area, const GffStruct &gffs) {
loadProperties(gffs);
loadTexture(area);
if (!_arrow) {
loadArrow();
}
loadTextures(area);
}
void Map::loadProperties(const GffStruct &gffs) {
@ -67,23 +66,29 @@ void Map::loadProperties(const GffStruct &gffs) {
_mapPoint2 = glm::vec2(gffs.getFloat("MapPt2X"), gffs.getFloat("MapPt2Y"));
}
void Map::loadTexture(const string &area) {
void Map::loadTextures(const string &area) {
string resRef("lbl_map" + area);
_texture = Textures::instance().get(resRef, TextureType::GUI);
_areaTexture = Textures::instance().get(resRef, TextureType::GUI);
if (!_arrowTexture) {
string resRef("mm_barrow");
if (_game->gameId() == GameID::TSL) {
resRef += "_p";
}
_arrowTexture = Textures::instance().get(resRef, TextureType::GUI);
}
if (!_noteTexture) {
_noteTexture = Textures::instance().get("whitetarget", TextureType::GUI);
}
}
void Map::loadArrow() {
string resRef("mm_barrow");
if (_game->gameId() == GameID::TSL) {
resRef += "_p";
}
_arrow = Textures::instance().get(resRef, TextureType::GUI);
}
void Map::render(Mode mode, const glm::vec4 &bounds) const {
if (!_texture) return;
if (!_areaTexture) return;
drawArea(mode, bounds);
drawNotes(mode, bounds);
drawPartyLeader(mode, bounds);
}
@ -96,12 +101,12 @@ void Map::drawArea(Mode mode, const glm::vec4 &bounds) const {
glm::vec2 mapPos(getMapPosition(worldPos));
glm::vec3 topLeft(0.0f);
topLeft.x = bounds[0] + 0.5f * bounds[2] - mapPos.x * 440.0f / static_cast<float>(_texture->width()) * _texture->width();
topLeft.y = bounds[1] + 0.5f * bounds[3] - mapPos.y * _texture->height();
topLeft.x = bounds[0] + 0.5f * bounds[2] - mapPos.x * 440.0f / static_cast<float>(_areaTexture->width()) * _areaTexture->width();
topLeft.y = bounds[1] + 0.5f * bounds[3] - mapPos.y * _areaTexture->height();
glm::mat4 transform(1.0f);
transform = glm::translate(transform, topLeft);
transform = glm::scale(transform, glm::vec3(_texture->width(), _texture->height(), 1.0f));
transform = glm::scale(transform, glm::vec3(_areaTexture->width(), _areaTexture->height(), 1.0f));
LocalUniforms locals;
locals.general.model = transform;
@ -109,7 +114,7 @@ void Map::drawArea(Mode mode, const glm::vec4 &bounds) const {
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
setActiveTextureUnit(0);
_texture->bind();
_areaTexture->bind();
float height = _game->options().graphics.height;
glm::ivec4 scissorBounds(bounds[0], height - (bounds[1] + bounds[3]), bounds[2], bounds[3]);
@ -126,12 +131,74 @@ void Map::drawArea(Mode mode, const glm::vec4 &bounds) const {
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
setActiveTextureUnit(0);
_texture->bind();
_areaTexture->bind();
Quad::getDefault().renderTriangles();
}
}
void Map::drawNotes(Mode mode, const glm::vec4 &bounds) const {
if (mode != Mode::Default) return;
for (auto &object : _game->module()->area()->getObjectsByType(ObjectType::Waypoint)) {
auto waypoint = static_pointer_cast<Waypoint>(object);
if (!waypoint->isMapNoteEnabled() || waypoint->mapNote().empty()) continue;
glm::vec2 mapPos(getMapPosition(waypoint->position()));
mapPos.x *= bounds[2] / static_cast<float>(_areaTexture->width());
mapPos.y *= bounds[3] / static_cast<float>(_areaTexture->height());
glm::vec2 notePos;
notePos.x = bounds[0] + mapPos.x * bounds[2];
notePos.y = bounds[1] + mapPos.y * bounds[3];
bool selected = waypoint == _selectedNote;
float noteSize = (selected ? kSelectedMapNoteScale : 1.0f) * kMapNoteSize;
glm::mat4 transform(1.0f);
transform = glm::translate(transform, glm::vec3(notePos.x - 0.5f * noteSize, notePos.y - 0.5f * noteSize, 0.0f));
transform = glm::scale(transform, glm::vec3(noteSize, noteSize, 1.0f));
LocalUniforms locals;
locals.general.model = transform;
locals.general.color = glm::vec4(selected ? getHilightColor(_game->gameId()) : getBaseColor(_game->gameId()), 1.0f);
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
setActiveTextureUnit(0);
_noteTexture->bind();
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);
}
void Map::drawPartyLeader(Mode mode, const glm::vec4 &bounds) const {
shared_ptr<Creature> partyLeader(_game->party().getLeader());
if (!partyLeader) return;
@ -142,8 +209,8 @@ void Map::drawPartyLeader(Mode mode, const glm::vec4 &bounds) const {
glm::vec2 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());
mapPos.x *= bounds[2] / static_cast<float>(_areaTexture->width());
mapPos.y *= bounds[3] / static_cast<float>(_areaTexture->height());
arrowPos.x = bounds[0] + mapPos.x * bounds[2];
arrowPos.y = bounds[1] + mapPos.y * bounds[3];
@ -180,36 +247,13 @@ void Map::drawPartyLeader(Mode mode, const glm::vec4 &bounds) const {
Shaders::instance().activate(ShaderProgram::GUIGUI, locals);
setActiveTextureUnit(0);
_arrow->bind();
_arrowTexture->bind();
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);
void Map::setSelectedNote(const shared_ptr<Waypoint> &waypoint) {
_selectedNote = waypoint;
}
} // namespace game

View file

@ -31,6 +31,7 @@ namespace reone {
namespace game {
class Game;
class Waypoint;
class Map {
public:
@ -44,23 +45,29 @@ public:
void load(const std::string &area, const resource::GffStruct &gffs);
void render(Mode mode, const glm::vec4 &bounds) const;
void setSelectedNote(const std::shared_ptr<Waypoint> &waypoint);
private:
Game *_game;
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;
std::shared_ptr<render::Texture> _areaTexture;
std::shared_ptr<render::Texture> _arrowTexture;
std::shared_ptr<render::Texture> _noteTexture;
std::shared_ptr<Waypoint> _selectedNote;
void loadProperties(const resource::GffStruct &gffs);
void loadTexture(const std::string &area);
void loadArrow();
void loadTextures(const std::string &area);
void drawArea(Mode mode, const glm::vec4 &bounds) const;
void drawPartyLeader(Mode mode, const glm::vec4 &bounds) const;
void drawNotes(Mode mode, const glm::vec4 &bounds) const;
glm::vec2 getMapPosition(const glm::vec2 &world) const;
};

View file

@ -173,6 +173,8 @@ void Area::loadPTH() {
}
void Area::loadARE(const GffStruct &are) {
_localizedName = Resources::instance().getString(are.getInt("Name", -1));
loadCameraStyle(are);
loadAmbientColor(are);
loadScripts(are);

View file

@ -94,6 +94,7 @@ public:
const ObjectList &objects() const { return _objects; }
ObjectSelector &objectSelector() { return _objectSelector; }
const Pathfinder &pathfinder() const { return _pathfinder; }
const std::string &localizedName() const { return _localizedName; }
const RoomMap &rooms() const { return _rooms; }
Combat &combat() { return _combat; }
Map &map() { return _map; }
@ -159,13 +160,14 @@ public:
// END Scripts
private:
Game *_game { nullptr };
Game *_game;
CollisionDetector _collisionDetector;
ObjectSelector _objectSelector;
ActionExecutor _actionExecutor;
Combat _combat;
Pathfinder _pathfinder;
std::string _name;
std::string _localizedName;
RoomMap _rooms;
resource::Visibility _visibility;
CameraStyle _camStyleDefault;

View file

@ -21,6 +21,13 @@
#include "glm/glm.hpp"
#include "../../render/textures.h"
#include "../../resource/resources.h"
#include "../blueprint/blueprints.h"
using namespace std;
using namespace reone::render;
using namespace reone::resource;
using namespace reone::scene;
@ -44,8 +51,13 @@ Waypoint::Waypoint(
}
void Waypoint::load(const GffStruct &gffs) {
_tag = gffs.getString("Tag");
boost::to_lower(_tag);
loadBlueprint(gffs);
_tag = boost::to_lower_copy(gffs.getString("Tag"));
_localizedName = Resources::instance().getString(gffs.getInt("LocalizedName", -1));
_description = Resources::instance().getString(gffs.getInt("Description", -1));
_mapNote = Resources::instance().getString(gffs.getInt("MapNote", -1));
_mapNoteEnabled = gffs.getBool("MapNoteEnabled");
_position[0] = gffs.getFloat("XPosition");
_position[1] = gffs.getFloat("YPosition");
@ -58,6 +70,12 @@ void Waypoint::load(const GffStruct &gffs) {
updateTransform();
}
void Waypoint::loadBlueprint(const GffStruct &gffs) {
string resRef(gffs.getString("TemplateResRef"));
shared_ptr<WaypointBlueprint> blueprint(Blueprints::instance().getWaypoint(resRef));
blueprint->load(*this);
}
} // namespace game
} // namespace reone

View file

@ -25,8 +25,6 @@ namespace reone {
namespace game {
class WaypointBlueprint;
class Waypoint : public SpatialObject {
public:
Waypoint(
@ -37,8 +35,19 @@ public:
void load(const resource::GffStruct &gffs);
bool isMapNoteEnabled() const { return _mapNoteEnabled; }
const std::string &localizedName() const { return _localizedName; }
const std::string &description() const { return _description; }
const std::string &mapNote() const { return _mapNote; }
private:
std::shared_ptr<WaypointBlueprint> _blueprint;
std::string _localizedName;
std::string _description;
std::string _mapNote;
bool _mapNoteEnabled { false };
void loadBlueprint(const resource::GffStruct &gffs);
};
} // namespace game