From 8500d70c3d67964d76fafb0d3ba5a0dee53276d6 Mon Sep 17 00:00:00 2001 From: Eldbury Date: Sat, 25 Apr 2026 12:25:57 +0930 Subject: [PATCH 1/2] Expose bash context action for eligible doors --- src/libs/game/object/module.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/libs/game/object/module.cpp b/src/libs/game/object/module.cpp index e6cad98d9..6cc419cfc 100644 --- a/src/libs/game/object/module.cpp +++ b/src/libs/game/object/module.cpp @@ -40,6 +40,24 @@ namespace reone { namespace game { +static bool isHostileDoorFaction(Faction faction) { + switch (faction) { + case Faction::Hostile1: + case Faction::Hostile2: + return true; + default: + return false; + } +} + +static bool canBashDoor(const Door &door) { + return door.isSelectable() && + !door.isDead() && + !door.plotFlag() && + isHostileDoorFaction(door.faction()) && + (door.hitPoints() > 0 || door.currentHitPoints() > 0); +} + void Module::load(std::string name, const Gff &ifo, bool fromSave) { _name = std::move(name); @@ -342,6 +360,9 @@ std::vector Module::getContextActions(const std::shared_ptr(object); + if (canBashDoor(*door)) { + actions.push_back(ContextAction(ActionType::AttackObject)); + } if (door->isLocked() && !door->isKeyRequired() && _game.party().getLeader()->attributes().hasSkill(SkillType::Security)) { actions.push_back(ContextAction(SkillType::Security)); } From 69288302e51972b137015be91bf749ab69c0b2b4 Mon Sep 17 00:00:00 2001 From: Eldbury Date: Sat, 25 Apr 2026 21:44:21 +0930 Subject: [PATCH 2/2] Add minimal door bash damage and eligibility --- include/reone/game/object/door.h | 5 +++ include/reone/game/reputes.h | 6 +++- src/libs/game/object/door.cpp | 54 ++++++++++++++++++++++++++++++++ src/libs/game/object/module.cpp | 23 +++++--------- src/libs/game/reputes.cpp | 14 ++++++--- 5 files changed, 81 insertions(+), 21 deletions(-) diff --git a/include/reone/game/object/door.h b/include/reone/game/object/door.h index 932207cb6..459bbab76 100644 --- a/include/reone/game/object/door.h +++ b/include/reone/game/object/door.h @@ -51,6 +51,7 @@ class Door : public Object { void loadFromBlueprint(const std::string &resRef); bool isSelectable() const override; + void damage(int amount, uint32_t damager) override; void open(); void close(); @@ -58,6 +59,7 @@ class Door : public Object { bool isLocked() const { return _locked; } bool isStatic() const { return _static; } bool isKeyRequired() const { return _keyRequired; } + bool isNotBlastable() const { return _notBlastable; } void onOpen(const Object &triggerer); void onFailToOpen(const Object &triggerer); @@ -95,6 +97,7 @@ class Door : public Object { int _hardness {0}; int _fortitude {0}; bool _lockable {false}; + bool _notBlastable {false}; std::string _keyName; // Walkmeshes @@ -121,6 +124,8 @@ class Door : public Object { void loadUTD(const resource::generated::UTD &utd); void loadTransformFromGIT(const resource::generated::GIT_Door_List &git); + void runDamagedScript(uint32_t damagerId); + void runDeathScript(uint32_t damagerId); void updateTransform() override; }; diff --git a/include/reone/game/reputes.h b/include/reone/game/reputes.h index e9e590af9..6b7eac98b 100644 --- a/include/reone/game/reputes.h +++ b/include/reone/game/reputes.h @@ -17,6 +17,8 @@ #pragma once +#include "types.h" + namespace reone { namespace resource { @@ -34,6 +36,7 @@ class IReputes { virtual ~IReputes() = default; virtual bool getIsEnemy(const Creature &left, const Creature &right) const = 0; + virtual bool getIsEnemy(Faction left, Faction right) const = 0; virtual bool getIsFriend(const Creature &left, const Creature &right) const = 0; virtual bool getIsNeutral(const Creature &left, const Creature &right) const = 0; }; @@ -47,13 +50,14 @@ class Reputes : public IReputes, boost::noncopyable { void init(); bool getIsEnemy(const Creature &left, const Creature &right) const override; + bool getIsEnemy(Faction left, Faction right) const override; bool getIsFriend(const Creature &left, const Creature &right) const override; bool getIsNeutral(const Creature &left, const Creature &right) const override; private: resource::TwoDAs &_twoDas; - int getRepute(const Creature &left, const Creature &right) const; + int getRepute(Faction left, Faction right) const; }; } // namespace game diff --git a/src/libs/game/object/door.cpp b/src/libs/game/object/door.cpp index f3c056445..6070a2cc9 100644 --- a/src/libs/game/object/door.cpp +++ b/src/libs/game/object/door.cpp @@ -17,6 +17,9 @@ #include "reone/game/object/door.h" +#include +#include + #include "reone/graphics/di/services.h" #include "reone/resource/2da.h" #include "reone/resource/di/services.h" @@ -112,6 +115,34 @@ bool Door::isSelectable() const { return !_static && !_open; } +void Door::damage(int amount, uint32_t damager) { + if (_dead || _plot || _notBlastable) { + return; + } + if (amount <= 0) { + return; + } + + int currentHitPoints = _currentHitPoints > 0 ? _currentHitPoints : _hitPoints; + if (amount == std::numeric_limits::max()) { + _currentHitPoints = isMinOneHP() ? 1 : 0; + } else { + _currentHitPoints = std::max(isMinOneHP() ? 1 : 0, currentHitPoints - amount); + } + + damager = damager ? damager : script::kObjectInvalid; + runDamagedScript(damager); + + if (_currentHitPoints > 0) { + return; + } + + _dead = true; + _locked = false; + open(); + runDeathScript(damager); +} + void Door::open() { auto model = std::static_pointer_cast(_sceneNode); if (model) { @@ -171,6 +202,28 @@ void Door::onFailToOpen(const Object &triggerer) { {script::ArgKind::ClickingObject, Variable::ofObject(triggerer.id())}}); } +void Door::runDamagedScript(uint32_t damagerId) { + if (_onDamaged.empty()) { + return; + } + _game.scriptRunner().run( + _onDamaged, + {{script::ArgKind::Caller, Variable::ofObject(_id)}, + {script::ArgKind::LastAttacker, Variable::ofObject(damagerId)}, + {script::ArgKind::LastDamager, Variable::ofObject(damagerId)}}); +} + +void Door::runDeathScript(uint32_t damagerId) { + if (_onDeath.empty()) { + return; + } + _game.scriptRunner().run( + _onDeath, + {{script::ArgKind::Caller, Variable::ofObject(_id)}, + {script::ArgKind::LastAttacker, Variable::ofObject(damagerId)}, + {script::ArgKind::LastDamager, Variable::ofObject(damagerId)}}); +} + void Door::setLocked(bool locked) { _locked = locked; } @@ -196,6 +249,7 @@ void Door::loadUTD(const resource::generated::UTD &utd) { _fortitude = utd.Fort; _genericType = utd.GenericType; _static = utd.Static; + _notBlastable = utd.NotBlastable; _onClosed = utd.OnClosed; // always empty, but could be useful _onDamaged = utd.OnDamaged; // always empty, but could be useful diff --git a/src/libs/game/object/module.cpp b/src/libs/game/object/module.cpp index 6cc419cfc..d780a36ef 100644 --- a/src/libs/game/object/module.cpp +++ b/src/libs/game/object/module.cpp @@ -40,21 +40,13 @@ namespace reone { namespace game { -static bool isHostileDoorFaction(Faction faction) { - switch (faction) { - case Faction::Hostile1: - case Faction::Hostile2: - return true; - default: - return false; - } -} - -static bool canBashDoor(const Door &door) { - return door.isSelectable() && +static bool canBashDoor(const Door &door, const Creature &actor, const IReputes &reputes) { + return door.isLocked() && + door.isSelectable() && !door.isDead() && !door.plotFlag() && - isHostileDoorFaction(door.faction()) && + !door.isNotBlastable() && + reputes.getIsEnemy(actor.faction(), door.faction()) && (door.hitPoints() > 0 || door.currentHitPoints() > 0); } @@ -360,10 +352,11 @@ std::vector Module::getContextActions(const std::shared_ptr(object); - if (canBashDoor(*door)) { + auto leader = _game.party().getLeader(); + if (canBashDoor(*door, *leader, _services.game.reputes)) { actions.push_back(ContextAction(ActionType::AttackObject)); } - if (door->isLocked() && !door->isKeyRequired() && _game.party().getLeader()->attributes().hasSkill(SkillType::Security)) { + if (door->isLocked() && !door->isKeyRequired() && leader->attributes().hasSkill(SkillType::Security)) { actions.push_back(ContextAction(SkillType::Security)); } break; diff --git a/src/libs/game/reputes.cpp b/src/libs/game/reputes.cpp index 47f46d6a5..efa4b3d6a 100644 --- a/src/libs/game/reputes.cpp +++ b/src/libs/game/reputes.cpp @@ -69,20 +69,24 @@ bool Reputes::getIsEnemy(const Creature &left, const Creature &right) const { return left.isMinOneHP() && right.isMinOneHP(); } + return getIsEnemy(left.faction(), right.faction()); +} + +bool Reputes::getIsEnemy(Faction left, Faction right) const { return getRepute(left, right) < 50; } bool Reputes::getIsFriend(const Creature &left, const Creature &right) const { - return getRepute(left, right) > 50; + return getRepute(left.faction(), right.faction()) > 50; } bool Reputes::getIsNeutral(const Creature &left, const Creature &right) const { - return getRepute(left, right) == 50; + return getRepute(left.faction(), right.faction()) == 50; } -int Reputes::getRepute(const Creature &left, const Creature &right) const { - int leftFaction = static_cast(left.faction()); - int rightFaction = static_cast(right.faction()); +int Reputes::getRepute(Faction left, Faction right) const { + int leftFaction = static_cast(left); + int rightFaction = static_cast(right); if (leftFaction < 0 || leftFaction >= g_factionValues.size() || rightFaction < 0 || rightFaction >= g_factionValues[leftFaction].size())