Skip to content

Commit

Permalink
Merge pull request #1683 from Cjreek/attack_target_change_event
Browse files Browse the repository at this point in the history
Added NWNX_ON_ATTACK_TARGET_CHANGE_BEFORE/AFTER events
  • Loading branch information
Daztek authored Jul 26, 2023
2 parents 92e3b7a + 167c679 commit e909fb8
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ https://github.com/nwnxee/unified/compare/build8193.35.40...HEAD
- Feat: added modifier `NWNX_FEAT_MODIFIER_SPELLSAVEDCFORSPELL` to modify a creature's spell DC for an individual spell
- Events: added event `NWNX_ON_SPELL_FAILED_{BEFORE|AFTER}` which fires when the casting of a spell did not finish for any reason.
- Tweaks: added `NWNX_TWEAKS_FIX_AUTOMAP_CRASH` which fixes a server crash that happens when automap data is outdated for a player.
- Events: added event `NWNX_ON_ATTACK_TARGET_CHANGE_{BEFORE|AFTER}` which fires when a creature changes its attack target.

##### New Plugins
- Resources: Adds `RESOURCES_*` variables for adding NWSync as a resource source, and specifying a replacement hak list.
Expand Down
11 changes: 11 additions & 0 deletions NWNXLib/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ std::string ObjectIDToString(const ObjectID id)
return ss.str();
}

ObjectID StringToObjectID(const std::string idStr)
{
ObjectID oidResult;

std::stringstream ss;
ss << std::hex << idStr;
ss >> oidResult;

return oidResult;
}

std::string GetCurrentScript()
{
auto *pVM = API::Globals::VirtualMachine();
Expand Down
1 change: 1 addition & 0 deletions NWNXLib/nwnx.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ namespace String
namespace Utils
{
std::string ObjectIDToString(const ObjectID id);
ObjectID StringToObjectID(const std::string idStr);

std::string GetCurrentScript();
void ExecuteScript(const std::string& script, ObjectID oidOwner);
Expand Down
103 changes: 103 additions & 0 deletions Plugins/Events/Events/CombatEvents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ static Hooks::Hook s_AddAttackActionsHook;
static Hooks::Hook s_AddAttackOfOpportunityHook;
static Hooks::Hook s_SetBroadcastedAOOToHook;
static Hooks::Hook s_PlayBattleMusicHook;
static Hooks::Hook s_AddActionHook;
static Hooks::Hook s_ChangeAttackTargetHook;

static void StartCombatRoundHook(CNWSCombatRound*, ObjectID);
static int32_t ApplyDisarmHook(CNWSEffectListHandler*, CNWSObject*, CGameEffect*, BOOL);
Expand All @@ -40,6 +42,11 @@ static BOOL AddAttackActionsHook(CNWSCreature*, ObjectID, BOOL, BOOL, BOOL);
static void AddAttackOfOpportunityHook(CNWSCombatRound*, ObjectID);
static void SetBroadcastedAOOToHook(CNWSCreature*, BOOL);
static void PlayBattleMusicHook(CNWSAmbientSound*, BOOL);
static void AddActionHook(CNWSObject*, uint32_t, uint16_t,
uint32_t, void*, uint32_t, void*, uint32_t, void*, uint32_t, void*,
uint32_t, void*, uint32_t, void*, uint32_t, void*, uint32_t, void*,
uint32_t, void*, uint32_t, void*, uint32_t, void*, uint32_t, void*);
static void ChangeAttackTargetHook(CNWSCreature*, CNWSObjectActionNode*, const OBJECT_ID);

static bool s_InBroadcastAttackOfOpportunity;
static bool s_SkipPushAndSignalCombatAttackOfOpportunityBefore;
Expand Down Expand Up @@ -95,6 +102,10 @@ void CombatEvents()
API::Functions::_ZN16CNWSAmbientSound15PlayBattleMusicEi,
(void*)PlayBattleMusicHook, Hooks::Order::Early);
});
InitOnFirstSubscribe("NWNX_ON_ATTACK_TARGET_CHANGE_.*", []() {
s_AddActionHook = Hooks::HookFunction(&CNWSObject::AddAction, &AddActionHook, Hooks::Order::Early);
s_ChangeAttackTargetHook = Hooks::HookFunction(&CNWSCreature::ChangeAttackTarget, &ChangeAttackTargetHook, Hooks::Order::Early);
});
}

void StartCombatRoundHook(CNWSCombatRound* thisPtr, ObjectID oidTarget)
Expand Down Expand Up @@ -310,4 +321,96 @@ void PlayBattleMusicHook(CNWSAmbientSound *pThis, BOOL bPlay)
}
}

static void AddActionHook(CNWSObject* pObject, uint32_t nActionId, uint16_t nGroupId, uint32_t nParamType1, void* pParameter1,
uint32_t nParamType2, void* pParameter2, uint32_t nParamType3, void* pParameter3, uint32_t nParamType4, void* pParameter4,
uint32_t nParamType5, void* pParameter5, uint32_t nParamType6, void* pParameter6, uint32_t nParamType7, void* pParameter7,
uint32_t nParamType8, void* pParameter8, uint32_t nParamType9, void* pParameter9, uint32_t nParamType10, void* pParameter10,
uint32_t nParamType11, void* pParameter11, uint32_t nParamType12, void* pParameter12)
{
bool bOriginalCalled = false;
if ((pObject->m_nObjectType == Constants::ObjectType::Creature) &&
((nActionId == 12 /* Attack */) || (nActionId == 1 /* MoveToPoint */) || (nActionId == 51 /* Drivemode */)))
{
auto* pCreature = Utils::AsNWSCreature(pObject);

OBJECT_ID oidLastAttackTarget = Constants::OBJECT_INVALID;
if (auto oidLastAttackTargetOpt = pCreature->nwnxGet<int32_t>("LAST_ATTACK_TARGET"))
oidLastAttackTarget = oidLastAttackTargetOpt.value();

OBJECT_ID oidNewTarget = (nActionId == 12) ? *(OBJECT_ID*)pParameter1 : Constants::OBJECT_INVALID;
if (oidNewTarget != oidLastAttackTarget)
{
std::string sResult = "";
auto PushAndSignal = [&](const std::string& event, OBJECT_ID oidNewTargetParam, bool retargetable, std::string* result = nullptr) -> void {
PushEventData("OLD_TARGET_OBJECT_ID", Utils::ObjectIDToString(oidLastAttackTarget));
PushEventData("NEW_TARGET_OBJECT_ID", Utils::ObjectIDToString(oidNewTargetParam));
PushEventData("AUTOMATIC_CHANGE", std::to_string(false));
PushEventData("RETARGETABLE", std::to_string(retargetable));
SignalEvent(event, pCreature->m_idSelf, result);
};

PushAndSignal("NWNX_ON_ATTACK_TARGET_CHANGE_BEFORE", oidNewTarget, nActionId == 12, &sResult);
if ((nActionId == 12) && (sResult != ""))
{
oidNewTarget = Utils::StringToObjectID(sResult);
*(OBJECT_ID*)pParameter1 = oidNewTarget;
}

s_AddActionHook->CallOriginal<void>(pObject, nActionId, nGroupId, nParamType1, pParameter1,
nParamType2, pParameter2, nParamType3, pParameter3, nParamType4, pParameter4,
nParamType5, pParameter5, nParamType6, pParameter6, nParamType7, pParameter7,
nParamType8, pParameter8, nParamType9, pParameter9, nParamType10, pParameter10,
nParamType11, pParameter11, nParamType12, pParameter12);
bOriginalCalled = true;

PushAndSignal("NWNX_ON_ATTACK_TARGET_CHANGE_AFTER", oidNewTarget, false);

pCreature->nwnxSet("LAST_ATTACK_TARGET", (int32_t)oidNewTarget);
}
}

if (!bOriginalCalled)
{
s_AddActionHook->CallOriginal<void>(pObject, nActionId, nGroupId, nParamType1, pParameter1,
nParamType2, pParameter2, nParamType3, pParameter3, nParamType4, pParameter4,
nParamType5, pParameter5, nParamType6, pParameter6, nParamType7, pParameter7,
nParamType8, pParameter8, nParamType9, pParameter9, nParamType10, pParameter10,
nParamType11, pParameter11, nParamType12, pParameter12);
}
}

static void ChangeAttackTargetHook(CNWSCreature* pCreature, CNWSObjectActionNode* pNode, const OBJECT_ID oidAttackTarget)
{
OBJECT_ID oidLastAttackTarget = Constants::OBJECT_INVALID;
if (auto oidLastAttackTargetOpt = pCreature->nwnxGet<int32_t>("LAST_ATTACK_TARGET"))
oidLastAttackTarget = oidLastAttackTargetOpt.value();

OBJECT_ID oidNewAttackTarget = oidAttackTarget;
if (oidNewAttackTarget != oidLastAttackTarget)
{
std::string sResult = "";
auto PushAndSignal = [&](const std::string& event, OBJECT_ID oidNewTargetParam, bool retargetable, std::string* result = nullptr) -> void {
PushEventData("OLD_TARGET_OBJECT_ID", Utils::ObjectIDToString(oidLastAttackTarget));
PushEventData("NEW_TARGET_OBJECT_ID", Utils::ObjectIDToString(oidNewTargetParam));
PushEventData("AUTOMATIC_CHANGE", std::to_string(true));
PushEventData("RETARGETABLE", std::to_string(retargetable));
SignalEvent(event, pCreature->m_idSelf, result);
};

PushAndSignal("NWNX_ON_ATTACK_TARGET_CHANGE_BEFORE", oidNewAttackTarget, true, &sResult);
if (sResult != "")
oidNewAttackTarget = Utils::StringToObjectID(sResult);

s_ChangeAttackTargetHook->CallOriginal<void>(pCreature, pNode, oidNewAttackTarget);

PushAndSignal("NWNX_ON_ATTACK_TARGET_CHANGE_AFTER", oidNewAttackTarget, false);

pCreature->nwnxSet("LAST_ATTACK_TARGET", (int32_t)oidNewAttackTarget);
}
else
{
s_ChangeAttackTargetHook->CallOriginal<void>(pCreature, pNode, oidNewAttackTarget);
}
}

}
16 changes: 16 additions & 0 deletions Plugins/Events/NWScript/nwnx_events.nss
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,19 @@ _______________________________________
Event Data Tag | Type | Notes
----------------------|--------|-------
PLAY | int | TRUE if the area is starting to play battle music, FALSE if stopping. |
_______________________________________
## Combat Attack Target Change Events
- NWNX_ON_ATTACK_TARGET_CHANGE_BEFORE
- NWNX_ON_ATTACK_TARGET_CHANGE_AFTER
`OBJECT_SELF` = The creature changing the target its attacking.
Event Data Tag | Type | Notes
----------------------|--------|-------
OLD_TARGET_OBJECT_ID | object | The old attack target. OBJECT_INVALID if there was no old target. Old target may be dead/invalid. Convert to object with StringToObject() |
NEW_TARGET_OBJECT_ID | object | The new attack target. OBJECT_INVALID if there is no new target. Convert to object with StringToObject() |
AUTOMATIC_CHANGE | int | TRUE if the game automatically decided on the new target, FALSE if explicitly chosen |
RETARGETABLE | int | TRUE if the new target can be changed using NWNX_Events_SetEventResult() (Only in BEFORE) |
_______________________________________
*/

Expand Down Expand Up @@ -1997,6 +2010,8 @@ const string NWNX_ON_COMBAT_ATTACK_OF_OPPORTUNITY_BEFORE = "NWNX_ON_COMBAT_ATTAC
const string NWNX_ON_COMBAT_ATTACK_OF_OPPORTUNITY_AFTER = "NWNX_ON_COMBAT_ATTACK_OF_OPPORTUNITY_AFTER";
const string NWNX_ON_AREA_PLAY_BATTLE_MUSIC_BEFORE = "NWNX_ON_AREA_PLAY_BATTLE_MUSIC_BEFORE";
const string NWNX_ON_AREA_PLAY_BATTLE_MUSIC_AFTER = "NWNX_ON_AREA_PLAY_BATTLE_MUSIC_AFTER";
const string NWNX_ON_ATTACK_TARGET_CHANGE_BEFORE = "NWNX_ON_ATTACK_TARGET_CHANGE_BEFORE";
const string NWNX_ON_ATTACK_TARGET_CHANGE_AFTER = "NWNX_ON_ATTACK_TARGET_CHANGE_AFTER";
/// @}

/// @name Events ObjectType Constants
Expand Down Expand Up @@ -2176,6 +2191,7 @@ void NWNX_Events_SkipEvent();
/// - Stealth event -> "1" to perform HiPS (without the feat), "0" to bypass HiPS
/// - Faction set reputation event -> The new reputation to apply instead. ("0" - "100")
/// - CharacterSheetPermitted event -> "1" allow the player to view the character sheet or "0" to disallow
/// - Attack target change event -> The new target object. Convert to string with ObjectToString()
void NWNX_Events_SetEventResult(string data);

/// Returns the current event name
Expand Down

0 comments on commit e909fb8

Please sign in to comment.