diff --git a/.gitignore b/.gitignore index 11791d1da..b8fb91599 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .cache/ +.agent/ .vs/ .vscode/ build/ + +engine.log +reone.cfg diff --git a/include/reone/game/game.h b/include/reone/game/game.h index 2f28420bc..d3c2bf7ba 100644 --- a/include/reone/game/game.h +++ b/include/reone/game/game.h @@ -17,8 +17,11 @@ #pragma once +#include + #include "reone/audio/source.h" #include "reone/graphics/cursor.h" +#include "reone/graphics/types.h" #include "reone/input/event.h" #include "reone/movie/movie.h" #include "reone/script/routines.h" @@ -71,6 +74,12 @@ class GUI; } +namespace graphics { + +class Font; + +} + namespace game { class Game : boost::noncopyable { @@ -320,6 +329,16 @@ class Game : boost::noncopyable { Screen _screen {Screen::None}; + struct DeveloperOverlay { + bool visible {false}; + bool triggers {true}; + bool actorLabels {true}; + bool watchedValues {true}; + }; + + DeveloperOverlay _developerOverlay; + std::shared_ptr _developerFont; + std::shared_ptr _movie; resource::CursorType _cursorType {resource::CursorType::None}; std::shared_ptr _cursor; @@ -399,6 +418,7 @@ class Game : boost::noncopyable { bool handleMouseMotion(const input::MouseMotionEvent &event); bool handleMouseButtonDown(const input::MouseButtonEvent &event); bool handleMouseButtonUp(const input::MouseButtonEvent &event); + bool handleDeveloperKeyDown(const input::KeyEvent &event); void onModuleSelected(const std::string &name); void renderHUD(); @@ -419,6 +439,15 @@ class Game : boost::noncopyable { void renderScene(); void renderGUI(); + void renderDeveloperOverlay(); + void renderDeveloperBanner(); + void renderDeveloperTriggerOverlay(const glm::mat4 &projection, const glm::mat4 &view); + void renderDeveloperActorLabels(const glm::mat4 &projection, const glm::mat4 &view); + void renderDeveloperWatchedValues(); + void renderDeveloperText(const std::string &text, const glm::vec3 &position, const glm::vec3 &color, graphics::TextGravity gravity = graphics::TextGravity::LeftTop); + void renderDeveloperPanel(const std::vector &lines, glm::vec2 position, glm::vec3 color); + void renderDeveloperLine(glm::vec2 a, glm::vec2 b, glm::vec4 color, float width); + void renderDeveloperRect(glm::vec2 position, glm::vec2 size, glm::vec4 color); // END Rendering diff --git a/include/reone/game/object.h b/include/reone/game/object.h index 5f50f2909..69ea5e78b 100644 --- a/include/reone/game/object.h +++ b/include/reone/game/object.h @@ -256,6 +256,7 @@ class Object : public scene::IUser, boost::noncopyable { std::deque> _actions; std::vector _delayed; + std::weak_ptr _executingAction; // END Actions diff --git a/include/reone/game/object/door.h b/include/reone/game/object/door.h index 4978215ad..932207cb6 100644 --- a/include/reone/game/object/door.h +++ b/include/reone/game/object/door.h @@ -66,6 +66,7 @@ class Door : public Object { const std::string &getOnFailToOpen() const { return _onFailToOpen; } int genericType() const { return _genericType; } + Faction faction() const { return _faction; } const std::string &linkedToModule() const { return _linkedToModule; } const std::string &linkedTo() const { return _linkedTo; } const std::string &transitionDestin() const { return _transitionDestin; } diff --git a/include/reone/game/object/placeable.h b/include/reone/game/object/placeable.h index a31ee74f2..12cc88867 100644 --- a/include/reone/game/object/placeable.h +++ b/include/reone/game/object/placeable.h @@ -55,6 +55,7 @@ class Placeable : public Object { bool isUsable() const { return _usable; } int appearance() const { return _appearance; } + Faction faction() const { return _faction; } std::shared_ptr walkmesh() const { return _walkmesh; } // Scripts diff --git a/include/reone/game/object/trigger.h b/include/reone/game/object/trigger.h index 26024b05b..a1dd42a4c 100644 --- a/include/reone/game/object/trigger.h +++ b/include/reone/game/object/trigger.h @@ -29,6 +29,13 @@ namespace game { class Trigger : public Object { public: + enum class DebugState { + Default, + Tested, + Inside, + Entered + }; + Trigger( uint32_t id, std::string sceneName, @@ -56,6 +63,12 @@ class Trigger : public Object { bool isIn(const glm::vec2 &point) const; bool isTenant(const std::shared_ptr &object) const; + const std::vector &geometry() const { return _geometry; } + DebugState debugState() const; + + void markDebugTested(bool inside); + void markDebugEntered(); + const std::string &getOnEnter() const { return _onEnter; } const std::string &getOnExit() const { return _onExit; } @@ -79,6 +92,9 @@ class Trigger : public Object { std::vector _geometry; std::set> _tenants; std::string _keyName; + float _debugTestAge {0.0f}; + float _debugInsideAge {0.0f}; + float _debugEnterAge {0.0f}; // Scripts diff --git a/include/reone/system/logger.h b/include/reone/system/logger.h index 1f87ea718..c894e5a6b 100644 --- a/include/reone/system/logger.h +++ b/include/reone/system/logger.h @@ -34,6 +34,8 @@ class Logger { LogChannel channel, LogSeverity severity); + void flush(); + bool isChannelEnabled(LogChannel channel) const { return _enabledChannels.count(channel) > 0; } diff --git a/src/apps/engine/main.cpp b/src/apps/engine/main.cpp index bb4bfd753..ddb8d5603 100644 --- a/src/apps/engine/main.cpp +++ b/src/apps/engine/main.cpp @@ -22,6 +22,7 @@ #endif #include "reone/system/logger.h" +#include "reone/system/logutil.h" #include "reone/system/threadutil.h" #include "engine.h" @@ -31,6 +32,7 @@ using namespace reone; using namespace reone::graphics; static constexpr char kLogFilename[] = "engine.log"; +static constexpr char kEngineStartupMessage[] = "reone smoke signal: engine startup"; int main(int argc, char **argv) { #ifdef _WIN32 @@ -48,6 +50,8 @@ int main(int argc, char **argv) { } try { Logger::instance.init(options->logging.severity, options->logging.channels, kLogFilename); + info(kEngineStartupMessage); + Logger::instance.flush(); } catch (const std::exception &ex) { std::cerr << "Error initializing logging: " << ex.what() << std::endl; return 2; diff --git a/src/apps/launcher/frame.cpp b/src/apps/launcher/frame.cpp index 915642f71..8c18447c0 100644 --- a/src/apps/launcher/frame.cpp +++ b/src/apps/launcher/frame.cpp @@ -50,6 +50,8 @@ LauncherFrame::LauncherFrame() : _checkBoxDev = new wxCheckBox(this, wxID_ANY, "Developer Mode", wxDefaultPosition, wxDefaultSize); _checkBoxDev->SetValue(_config.devMode); + auto devToolsHelp = new wxStaticText(this, wxID_ANY, "Developer tools: Ctrl+Shift+D overlay, Ctrl+Shift+T triggers, Ctrl+Shift+A labels, Ctrl+Shift+W watch", wxDefaultPosition, wxDefaultSize); + devToolsHelp->Wrap(600); // END Setup controls @@ -341,6 +343,7 @@ LauncherFrame::LauncherFrame() : topSizer2->SetMinSize(640, 100); topSizer2->Add(gameSizer, wxSizerFlags(0).Expand().Border(wxALL, 3)); topSizer2->Add(_checkBoxDev, wxSizerFlags(0).Expand().Border(wxALL, 3)); + topSizer2->Add(devToolsHelp, wxSizerFlags(0).Expand().Border(wxLEFT | wxRIGHT | wxBOTTOM, 3)); topSizer2->Add(topSizer, wxSizerFlags(0).Expand().Border(wxALL, 3)); topSizer2->Add(new wxButton(this, WindowID::launch, "Launch"), wxSizerFlags(0).Expand().Border(wxALL, 3)); topSizer2->Add(new wxButton(this, WindowID::saveConfig, "Save Configuration"), wxSizerFlags(0).Expand().Border(wxALL, 3)); diff --git a/src/libs/game/game.cpp b/src/libs/game/game.cpp index 221e3c576..816fd4e22 100644 --- a/src/libs/game/game.cpp +++ b/src/libs/game/game.cpp @@ -35,6 +35,7 @@ #include "reone/game/surfaces.h" #include "reone/graphics/context.h" #include "reone/graphics/di/services.h" +#include "reone/graphics/font.h" #include "reone/graphics/format/tgawriter.h" #include "reone/graphics/meshregistry.h" #include "reone/graphics/renderbuffer.h" @@ -53,6 +54,7 @@ #include "reone/resource/provider/audioclips.h" #include "reone/resource/provider/cursors.h" #include "reone/resource/provider/dialogs.h" +#include "reone/resource/provider/fonts.h" #include "reone/resource/provider/gffs.h" #include "reone/resource/provider/lips.h" #include "reone/resource/provider/models.h" @@ -87,6 +89,138 @@ namespace reone { namespace game { +static constexpr char kDeveloperOverlayToggleHelp[] = "Ctrl+Shift+D"; +static constexpr char kDeveloperTriggerToggleHelp[] = "Ctrl+Shift+T"; +static constexpr char kDeveloperActorToggleHelp[] = "Ctrl+Shift+A"; +static constexpr char kDeveloperWatchToggleHelp[] = "Ctrl+Shift+W"; +static constexpr float kDeveloperActorLabelDistance = 32.0f; +static constexpr float kDeveloperLineWidth = 2.0f; + +static const char *screenName(Game::Screen screen) { + switch (screen) { + case Game::Screen::None: + return "None"; + case Game::Screen::MainMenu: + return "MainMenu"; + case Game::Screen::Loading: + return "Loading"; + case Game::Screen::CharacterGeneration: + return "CharacterGeneration"; + case Game::Screen::InGame: + return "InGame"; + case Game::Screen::InGameMenu: + return "InGameMenu"; + case Game::Screen::Conversation: + return "Conversation"; + case Game::Screen::Container: + return "Container"; + case Game::Screen::PartySelection: + return "PartySelection"; + case Game::Screen::SaveLoad: + return "SaveLoad"; + default: + return "Unknown"; + } +} + +static const char *cameraTypeName(CameraType type) { + switch (type) { + case CameraType::FirstPerson: + return "FirstPerson"; + case CameraType::ThirdPerson: + return "ThirdPerson"; + case CameraType::Static: + return "Static"; + case CameraType::Animated: + return "Animated"; + case CameraType::Dialog: + return "Dialog"; + default: + return "Unknown"; + } +} + +static const char *objectTypeName(ObjectType type) { + switch (type) { + case ObjectType::Creature: + return "creature"; + case ObjectType::Item: + return "item"; + case ObjectType::Trigger: + return "trigger"; + case ObjectType::Door: + return "door"; + case ObjectType::Waypoint: + return "waypoint"; + case ObjectType::Placeable: + return "placeable"; + case ObjectType::Store: + return "store"; + case ObjectType::Encounter: + return "encounter"; + case ObjectType::Sound: + return "sound"; + case ObjectType::Module: + return "module"; + case ObjectType::Area: + return "area"; + case ObjectType::Room: + return "room"; + case ObjectType::Camera: + return "camera"; + default: + return "object"; + } +} + +static bool isDeveloperOverlayChord(const input::KeyEvent &event) { + bool control = (event.mod & input::KeyModifiers::control) != 0; + bool shift = (event.mod & input::KeyModifiers::shift) != 0; + return control && shift; +} + +static const char *triggerDebugStateName(Trigger::DebugState state) { + switch (state) { + case Trigger::DebugState::Entered: + return "enter"; + case Trigger::DebugState::Inside: + return "inside"; + case Trigger::DebugState::Tested: + return "tested"; + default: + return "default"; + } +} + +static glm::vec4 triggerDebugColor(Trigger::DebugState state) { + switch (state) { + case Trigger::DebugState::Entered: + return glm::vec4(1.0f, 0.42f, 0.12f, 0.95f); + case Trigger::DebugState::Inside: + return glm::vec4(0.16f, 0.95f, 0.38f, 0.95f); + case Trigger::DebugState::Tested: + return glm::vec4(1.0f, 0.88f, 0.18f, 0.95f); + default: + return glm::vec4(0.48f, 0.74f, 1.0f, 0.85f); + } +} + +static int getDebugFaction(const std::shared_ptr &object) { + if (!object) { + return -1; + } + if (auto creature = std::dynamic_pointer_cast(object)) { + return static_cast(creature->faction()); + } + if (auto door = std::dynamic_pointer_cast(object)) { + return static_cast(door->faction()); + } + if (auto placeable = std::dynamic_pointer_cast(object)) { + return static_cast(placeable->faction()); + } + return -1; +} + void Game::init() { initConsole(); initLocalServices(); @@ -254,6 +388,10 @@ bool Game::handleKeyDown(const input::KeyEvent &event) { if (event.repeat) return false; + if (handleDeveloperKeyDown(event)) { + return true; + } + switch (event.code) { case input::KeyCode::Minus: if (_options.game.developer && _gameSpeed > 1.0f) { @@ -283,6 +421,47 @@ bool Game::handleKeyDown(const input::KeyEvent &event) { return false; } +bool Game::handleDeveloperKeyDown(const input::KeyEvent &event) { + if (!_options.game.developer || _screen != Screen::InGame) { + return false; + } + if (!isDeveloperOverlayChord(event)) { + return false; + } + + switch (event.code) { + case input::KeyCode::D: + _developerOverlay.visible = !_developerOverlay.visible; + return true; + case input::KeyCode::T: + if (!_developerOverlay.visible) { + _developerOverlay.visible = true; + _developerOverlay.triggers = true; + } else { + _developerOverlay.triggers = !_developerOverlay.triggers; + } + return true; + case input::KeyCode::A: + if (!_developerOverlay.visible) { + _developerOverlay.visible = true; + _developerOverlay.actorLabels = true; + } else { + _developerOverlay.actorLabels = !_developerOverlay.actorLabels; + } + return true; + case input::KeyCode::W: + if (!_developerOverlay.visible) { + _developerOverlay.visible = true; + _developerOverlay.watchedValues = true; + } else { + _developerOverlay.watchedValues = !_developerOverlay.watchedValues; + } + return true; + default: + return false; + } +} + bool Game::handleMouseMotion(const input::MouseMotionEvent &event) { _cursor->setPosition({event.x, event.y}); return false; @@ -524,6 +703,296 @@ void Game::renderGUI() { if (_cursor && !_relativeMouseMode) { _cursor->render(); } + renderDeveloperOverlay(); +} + +void Game::renderDeveloperOverlay() { + if (!_options.game.developer || !_developerOverlay.visible || !_module || _screen != Screen::InGame) { + return; + } + if (!_developerFont) { + _developerFont = _services.resource.fonts.get("fnt_console"); + } + if (!_developerFont) { + return; + } + + auto camera = getActiveCamera(); + bool hasCamera = camera != nullptr; + glm::mat4 projection(1.0f); + glm::mat4 view(1.0f); + if (camera) { + projection = camera->cameraSceneNode()->camera()->projection(); + view = camera->cameraSceneNode()->camera()->view(); + } + + _services.graphics.uniforms.setGlobals([this](auto &globals) { + globals.reset(); + globals.projection = glm::ortho( + 0.0f, + static_cast(_options.graphics.width), + static_cast(_options.graphics.height), + 0.0f, 0.0f, 100.0f); + globals.projectionInv = glm::inverse(globals.projection); + }); + _services.graphics.context.withBlendMode(BlendMode::Normal, [this]() { + renderDeveloperBanner(); + }); + _services.graphics.context.withBlendMode(BlendMode::Normal, [this, hasCamera, &projection, &view]() { + if (_developerOverlay.triggers && hasCamera) { + renderDeveloperTriggerOverlay(projection, view); + } + if (_developerOverlay.actorLabels && hasCamera) { + renderDeveloperActorLabels(projection, view); + } + if (_developerOverlay.watchedValues) { + renderDeveloperWatchedValues(); + } + }); +} + +void Game::renderDeveloperBanner() { + std::vector lines; + lines.push_back("DEV OBSERVABILITY"); + lines.push_back(str(boost::format("%s overlay | %s triggers | %s labels") % + kDeveloperOverlayToggleHelp % + kDeveloperTriggerToggleHelp % + kDeveloperActorToggleHelp)); + lines.push_back(str(boost::format("%s watch | ` console | F5 profiler") % + kDeveloperWatchToggleHelp)); + lines.push_back("V camera | +/- speed"); + + float maxWidth = 0.0f; + for (const auto &line : lines) { + maxWidth = glm::max(maxWidth, _developerFont->measure(line)); + } + renderDeveloperPanel( + lines, + glm::vec2(0.5f * (static_cast(_options.graphics.width) - maxWidth - 14.0f), 12.0f), + glm::vec3(0.58f, 1.0f, 0.58f)); +} + +void Game::renderDeveloperTriggerOverlay(const glm::mat4 &projection, const glm::mat4 &view) { + static glm::vec4 viewport(0.0f, 0.0f, 1.0f, 1.0f); + auto area = _module ? _module->area() : nullptr; + if (!area) { + return; + } + + const auto &opts = _options.graphics; + for (const auto &object : area->getObjectsByType(ObjectType::Trigger)) { + auto trigger = std::static_pointer_cast(object); + const auto &geometry = trigger->geometry(); + if (geometry.size() < 2) { + continue; + } + + std::vector points; + points.reserve(geometry.size()); + glm::vec3 centroid(0.0f); + bool anyVisible = false; + for (const auto &localPoint : geometry) { + glm::vec3 worldPoint = trigger->position() + localPoint; + centroid += worldPoint; + glm::vec3 screen = glm::project(worldPoint, view, projection, viewport); + points.push_back(glm::vec2(opts.width * screen.x, opts.height * (1.0f - screen.y))); + if (screen.z < 1.0f) { + anyVisible = true; + } + } + if (!anyVisible) { + continue; + } + + auto state = trigger->debugState(); + glm::vec4 color = triggerDebugColor(state); + for (size_t i = 0; i < points.size(); ++i) { + size_t next = (i + 1) % points.size(); + renderDeveloperLine(points[i], points[next], color, kDeveloperLineWidth); + } + + centroid /= static_cast(geometry.size()); + glm::vec3 labelScreen = glm::project(centroid, view, projection, viewport); + if (labelScreen.z < 1.0f) { + std::string label = str(boost::format("#%u %s %s [%s]") % + trigger->id() % + trigger->tag() % + (trigger->getOnEnter().empty() ? "-" : trigger->getOnEnter()) % + triggerDebugStateName(state)); + glm::vec3 position(opts.width * labelScreen.x, opts.height * (1.0f - labelScreen.y), 0.0f); + renderDeveloperText(label, position, glm::vec3(color), TextGravity::CenterBottom); + } + } +} + +void Game::renderDeveloperActorLabels(const glm::mat4 &projection, const glm::mat4 &view) { + auto area = _module ? _module->area() : nullptr; + auto leader = _party.getLeader(); + if (!area || !leader) { + return; + } + + const auto &opts = _options.graphics; + int rendered = 0; + for (const auto &object : area->objects()) { + bool supported = object->type() == ObjectType::Creature || + object->type() == ObjectType::Door || + object->type() == ObjectType::Placeable; + bool inspected = object == area->hilightedObject() || object == area->selectedObject(); + if (!supported && !inspected) { + continue; + } + + float distance = object->getDistanceTo(*leader); + if (!inspected && distance > kDeveloperActorLabelDistance) { + continue; + } + + glm::vec3 screen = area->getSelectableScreenCoords(object, projection, view); + if (screen.z >= 1.0f) { + continue; + } + + bool hostile = false; + auto creature = std::dynamic_pointer_cast(object); + if (creature) { + hostile = !creature->isDead() && _services.game.reputes.getIsEnemy(*leader, *creature); + } + + glm::vec3 color = inspected ? glm::vec3(1.0f, 1.0f, 1.0f) : (hostile ? glm::vec3(1.0f, 0.42f, 0.36f) : glm::vec3(0.68f, 0.92f, 1.0f)); + std::string label = str(boost::format("#%u %s %s f=%d H=%d sel=%d cmd=%d vis=%d plot=%d") % + object->id() % + object->tag() % + object->blueprintResRef() % + getDebugFaction(object) % + static_cast(hostile) % + static_cast(object->isSelectable()) % + static_cast(object->isCommandable()) % + static_cast(object->visible()) % + static_cast(object->plotFlag())); + + glm::vec3 position(opts.width * screen.x, opts.height * (1.0f - screen.y) - 18.0f - (rendered % 2) * 10.0f, 0.0f); + renderDeveloperText(label, position, color, TextGravity::CenterBottom); + if (++rendered >= 16) { + break; + } + } +} + +void Game::renderDeveloperWatchedValues() { + auto area = _module ? _module->area() : nullptr; + auto leader = _party.getLeader(); + auto selected = area ? area->selectedObject() : nullptr; + auto hover = area ? area->hilightedObject() : nullptr; + std::string room = leader && leader->room() ? leader->room()->name() : "-"; + glm::vec3 position = leader ? leader->position() : glm::vec3(0.0f); + + std::vector lines; + lines.push_back(str(boost::format("Watch (%s)") % kDeveloperWatchToggleHelp)); + lines.push_back(str(boost::format("screen=%s module=%s area=%s camera=%s") % + screenName(_screen) % + (_module ? _module->name() : "-") % + (area ? area->localizedName() : "-") % + cameraTypeName(_cameraType))); + lines.push_back(str(boost::format("speed=%.1fx paused=%d relativeMouse=%d room=%s") % + _gameSpeed % + static_cast(_paused) % + static_cast(_relativeMouseMode) % + room)); + lines.push_back(str(boost::format("leader=#%u %s hp=%d/%d pos=%.2f,%.2f,%.2f") % + (leader ? leader->id() : 0) % + (leader ? leader->tag() : "-") % + (leader ? leader->currentHitPoints() : -1) % + (leader ? leader->maxHitPoints() : -1) % + position.x % + position.y % + position.z)); + lines.push_back(str(boost::format("selected=#%u %s/%s type=%s hp=%d/%d") % + (selected ? selected->id() : 0) % + (selected ? selected->tag() : "-") % + (selected ? selected->blueprintResRef() : "-") % + (selected ? objectTypeName(selected->type()) : "-") % + (selected ? selected->currentHitPoints() : -1) % + (selected ? selected->maxHitPoints() : -1))); + lines.push_back(str(boost::format("hover=#%u %s/%s type=%s hp=%d/%d") % + (hover ? hover->id() : 0) % + (hover ? hover->tag() : "-") % + (hover ? hover->blueprintResRef() : "-") % + (hover ? objectTypeName(hover->type()) : "-") % + (hover ? hover->currentHitPoints() : -1) % + (hover ? hover->maxHitPoints() : -1))); + + renderDeveloperPanel( + lines, + glm::vec2(static_cast(_options.graphics.width) - 460.0f, 16.0f), + glm::vec3(0.92f)); +} + +void Game::renderDeveloperText(const std::string &text, const glm::vec3 &position, const glm::vec3 &color, TextGravity gravity) { + if (!_developerFont) { + return; + } + _developerFont->render(text, position + glm::vec3(1.0f, 1.0f, 0.0f), glm::vec3(0.0f), gravity); + _developerFont->render(text, position, color, gravity); +} + +void Game::renderDeveloperPanel(const std::vector &lines, glm::vec2 position, glm::vec3 color) { + if (!_developerFont || lines.empty()) { + return; + } + + float maxWidth = 0.0f; + for (const auto &line : lines) { + maxWidth = glm::max(maxWidth, _developerFont->measure(line)); + } + float lineHeight = _developerFont->height() + 2.0f; + glm::vec2 size(maxWidth + 14.0f, lineHeight * lines.size() + 10.0f); + position.x = glm::clamp(position.x, 4.0f, static_cast(_options.graphics.width) - size.x - 4.0f); + position.y = glm::clamp(position.y, 4.0f, static_cast(_options.graphics.height) - size.y - 4.0f); + + renderDeveloperRect(position, size, glm::vec4(0.0f, 0.0f, 0.0f, 0.58f)); + glm::vec3 textPosition(position.x + 7.0f, position.y + 5.0f, 0.0f); + for (const auto &line : lines) { + renderDeveloperText(line, textPosition, color, TextGravity::LeftTop); + textPosition.y += lineHeight; + } +} + +void Game::renderDeveloperLine(glm::vec2 a, glm::vec2 b, glm::vec4 color, float width) { + glm::vec2 delta = b - a; + float length = glm::length(delta); + if (length < 0.1f) { + return; + } + + float angle = std::atan2(delta.y, delta.x); + glm::mat4 transform(1.0f); + transform = glm::translate(transform, glm::vec3(a.x, a.y, 0.0f)); + transform = glm::rotate(transform, angle, glm::vec3(0.0f, 0.0f, 1.0f)); + transform = glm::translate(transform, glm::vec3(0.0f, -0.5f * width, 0.0f)); + transform = glm::scale(transform, glm::vec3(length, width, 1.0f)); + + _services.graphics.uniforms.setLocals([transform, color](auto &locals) { + locals.reset(); + locals.model = transform; + locals.color = color; + }); + _services.graphics.context.useProgram(_services.graphics.shaderRegistry.get(ShaderProgramId::mvpColor)); + _services.graphics.meshRegistry.get(MeshName::quad).draw(_services.graphics.statistic); +} + +void Game::renderDeveloperRect(glm::vec2 position, glm::vec2 size, glm::vec4 color) { + glm::mat4 transform(1.0f); + transform = glm::translate(transform, glm::vec3(position.x, position.y, 0.0f)); + transform = glm::scale(transform, glm::vec3(size.x, size.y, 1.0f)); + + _services.graphics.uniforms.setLocals([transform, color](auto &locals) { + locals.reset(); + locals.model = transform; + locals.color = color; + }); + _services.graphics.context.useProgram(_services.graphics.shaderRegistry.get(ShaderProgramId::mvpColor)); + _services.graphics.meshRegistry.get(MeshName::quad).draw(_services.graphics.statistic); } void Game::updateMovie(float dt) { diff --git a/src/libs/game/object.cpp b/src/libs/game/object.cpp index ebe9c6716..0eb2b96fb 100644 --- a/src/libs/game/object.cpp +++ b/src/libs/game/object.cpp @@ -65,6 +65,23 @@ void Object::setLocalNumber(int index, int value) { } void Object::clearAllActions(bool force) { + if (!force) { + auto executingAction = _executingAction.lock(); + if (executingAction) { + while (!_actions.empty() && _actions.front() != executingAction) { + const std::shared_ptr &action = _actions.front(); + if (action->locked()) { + break; + } + action->cancel(action, *this); + _actions.pop_front(); + } + if (!_actions.empty() && _actions.front() == executingAction) { + return; + } + } + } + while (!_actions.empty()) { const std::shared_ptr &action = _actions.back(); if (!force && action->locked()) { @@ -129,7 +146,14 @@ void Object::executeActions(float dt) { return; } std::shared_ptr action(_actions.front()); - action->execute(action, *this, dt); + _executingAction = action; + try { + action->execute(action, *this, dt); + } catch (...) { + _executingAction.reset(); + throw; + } + _executingAction.reset(); } bool Object::hasUserActionsPending() const { diff --git a/src/libs/game/object/area.cpp b/src/libs/game/object/area.cpp index 6661dc415..9a9a0f38f 100644 --- a/src/libs/game/object/area.cpp +++ b/src/libs/game/object/area.cpp @@ -802,7 +802,7 @@ void Area::startDialog(const std::shared_ptr &object, const std::string if (resRef.empty()) { finalResRef = object->conversation(); } - if (resRef.empty()) { + if (finalResRef.empty()) { return; } _game.startDialog(object, finalResRef); @@ -877,11 +877,14 @@ void Area::checkTriggersIntersection(const std::shared_ptr &triggerrer) for (auto &object : _objectsByType[ObjectType::Trigger]) { auto trigger = std::static_pointer_cast(object); - if (trigger->isTenant(triggerrer) || !trigger->isIn(position2d)) { + bool inside = trigger->isIn(position2d); + trigger->markDebugTested(inside); + if (trigger->isTenant(triggerrer) || !inside) { continue; } debug(str(boost::format("Trigger '%s' triggerred by '%s'") % trigger->tag() % triggerrer->tag())); trigger->addTenant(triggerrer); + trigger->markDebugEntered(); if (!trigger->linkedToModule().empty()) { _game.scheduleModuleTransition(trigger->linkedToModule(), trigger->linkedTo()); diff --git a/src/libs/game/object/trigger.cpp b/src/libs/game/object/trigger.cpp index 29779a80c..7035c4810 100644 --- a/src/libs/game/object/trigger.cpp +++ b/src/libs/game/object/trigger.cpp @@ -37,6 +37,10 @@ namespace reone { namespace game { +static constexpr float kDebugTestDuration = 0.25f; +static constexpr float kDebugInsideDuration = 0.25f; +static constexpr float kDebugEnterDuration = 1.5f; + void Trigger::loadFromGIT(const resource::generated::GIT_TriggerList &git) { std::string templateResRef(boost::to_lower_copy(git.TemplateResRef)); loadFromBlueprint(templateResRef); @@ -83,6 +87,12 @@ void Trigger::loadFromBlueprint(const std::string &resRef) { } void Trigger::update(float dt) { + Object::update(dt); + + _debugTestAge = glm::max(0.0f, _debugTestAge - dt); + _debugInsideAge = glm::max(0.0f, _debugInsideAge - dt); + _debugEnterAge = glm::max(0.0f, _debugEnterAge - dt); + std::set> tenantsToRemove; for (auto &tenant : _tenants) { if (tenant) { @@ -128,6 +138,30 @@ bool Trigger::isTenant(const std::shared_ptr &object) const { return maybeTenant != _tenants.end(); } +Trigger::DebugState Trigger::debugState() const { + if (_debugEnterAge > 0.0f) { + return DebugState::Entered; + } + if (!_tenants.empty() || _debugInsideAge > 0.0f) { + return DebugState::Inside; + } + if (_debugTestAge > 0.0f) { + return DebugState::Tested; + } + return DebugState::Default; +} + +void Trigger::markDebugTested(bool inside) { + _debugTestAge = kDebugTestDuration; + if (inside) { + _debugInsideAge = kDebugInsideDuration; + } +} + +void Trigger::markDebugEntered() { + _debugEnterAge = kDebugEnterDuration; +} + void Trigger::loadUTT(const resource::generated::UTT &utt) { _tag = boost::to_lower_copy(utt.Tag); _blueprintResRef = boost::to_lower_copy(utt.TemplateResRef); diff --git a/src/libs/system/logger.cpp b/src/libs/system/logger.cpp index a0592cb13..178ec9500 100644 --- a/src/libs/system/logger.cpp +++ b/src/libs/system/logger.cpp @@ -98,6 +98,13 @@ void Logger::append(std::string message, } } +void Logger::flush() { + if (!_inited || !buffer) { + return; + } + flush(buffer->get()); +} + void Logger::flush(std::ostringstream &buffer) { if (!_stream) { return; @@ -106,6 +113,7 @@ void Logger::flush(std::ostringstream &buffer) { { std::lock_guard lock {_streamMutex}; _stream->write(&str[0], str.length()); + _stream->flush(); } buffer.str(""); }