Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions EEex/copy/EEex_scripts/EEex_Action.lua
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,20 @@ EEex_Action_Private_Switch = {
[479] = function(aiBase, curAction)
return EEex_Action_Private_SpellObjectOffset(aiBase, curAction, false, 337, CGameAIBase.ForceSpellPoint)
end,

-- EEex_AttackOnce
[480] = function(aiBase, curAction)

if not aiBase:isSprite(true) then
return EEex_Action_ReturnType.ACTION_ERROR
end

-- Store the EEex-only roll/damage/ammo state, then let vanilla Attack()
-- own target validation, movement, range, animation, and interruption.
EEex.StartAttackOnce(aiBase, aiBase:GetTargetShare(), curAction.m_specificID, curAction.m_specificID2, curAction.m_specificID3)
curAction.m_actionID = 3
return aiBase:virtual_ExecuteAction()
end,
}

function EEex_Action_Hook_OnEvaluatingUnknown(aiBase)
Expand Down
52 changes: 27 additions & 25 deletions EEex/copy/EEex_scripts/EEex_Action_Patch.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,33 @@
EEex_DisableCodeProtection()

--[[
+--------------------------------------------------------------------------------+
| Implement new actions |
+--------------------------------------------------------------------------------+
| 472 EEex_LuaAction(S:Chunk*) |
| 473 EEex_MatchObject(S:Chunk*) |
| 473 EEex_MatchObjectEx(S:Chunk*,I:Nth*,I:Range*,I:Flags*X-MATOBJ) |
| 474 EEex_SetTarget(S:Name*,O:Target*) |
| 476 EEex_SpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*) |
| 476 EEex_SpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*) |
| 477 EEex_SpellObjectOffsetNoDec(O:Target*,I:Spell*Spell,P:Offset*) |
| 477 EEex_SpellObjectOffsetNoDecRES(S:RES*,O:Target*,P:Offset*) |
| 478 EEex_ForceSpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*) |
| 478 EEex_ForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*) |
| 479 EEex_ReallyForceSpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*) |
| 479 EEex_ReallyForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*) |
+--------------------------------------------------------------------------------+
| [Lua] EEex_Action_Hook_OnEvaluatingUnknown(evaluator: CGameAIBase) -> number |
| return -> Set as internal action return value: |
| -> EEex_Action_ReturnType.ACTION_STOPPED |
| -> EEex_Action_ReturnType.ACTION_ERROR |
| -> EEex_Action_ReturnType.ACTION_DONE |
| -> EEex_Action_ReturnType.ACTION_NORMAL |
| -> EEex_Action_ReturnType.ACTION_INTERRUPTABLE |
| -> EEex_Action_ReturnType.ACTION_NO_ACTION |
+--------------------------------------------------------------------------------+
+---------------------------------------------------------------------------------+
| Implement new actions |
+---------------------------------------------------------------------------------+
| 472 EEex_LuaAction(S:Chunk*) |
| 473 EEex_MatchObject(S:Chunk*) |
| 473 EEex_MatchObjectEx(S:Chunk*,I:Nth*,I:Range*,I:Flags*X-MATOBJ) |
| 474 EEex_SetTarget(S:Name*,O:Target*) |
| 476 EEex_SpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*) |
| 476 EEex_SpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*) |
| 477 EEex_SpellObjectOffsetNoDec(O:Target*,I:Spell*Spell,P:Offset*) |
| 477 EEex_SpellObjectOffsetNoDecRES(S:RES*,O:Target*,P:Offset*) |
| 478 EEex_ForceSpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*) |
| 478 EEex_ForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*) |
| 479 EEex_ReallyForceSpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*) |
| 479 EEex_ReallyForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*) |
| 480 EEex_AttackOnce(O:Target*,I:BaseAttackRollMod*, |
| I:OverrideBaseDamageRoll*,I:ConsumeAmmo*Boolean) |
+---------------------------------------------------------------------------------+
| [Lua] EEex_Action_Hook_OnEvaluatingUnknown(evaluator: CGameAIBase) -> number |
| return -> Set as internal action return value: |
| -> EEex_Action_ReturnType.ACTION_STOPPED |
| -> EEex_Action_ReturnType.ACTION_ERROR |
| -> EEex_Action_ReturnType.ACTION_DONE |
| -> EEex_Action_ReturnType.ACTION_NORMAL |
| -> EEex_Action_ReturnType.ACTION_INTERRUPTABLE |
| -> EEex_Action_ReturnType.ACTION_NO_ACTION |
+---------------------------------------------------------------------------------+
--]]

EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameAIBase::ExecuteAction()-DefaultCase"), 0, 7, 7, {
Expand Down
5 changes: 5 additions & 0 deletions EEex/copy/EEex_scripts/EEex_Sprite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,11 @@ function EEex_Sprite_Hook_OnSetCurrAction(sprite)
local spriteAux = EEex_GetUDAux(sprite)
spriteAux["EEex_Fix_HasSpellOrSpellPointStartedCasting"] = 0
spriteAux["EEex_Sprite_DamageEntriesSinceActionStarted"] = {}
-- AttackOnce uses NoAction to end the current Attack while its consumed
-- state still guards any remaining same-swing Hit() callsites.
if sprite.m_curAction.m_actionID ~= 0 then
EEex.ClearAttackOnce(sprite)
end
end

--------------------------------------------------------------------------------------------
Expand Down
140 changes: 140 additions & 0 deletions EEex/copy/EEex_scripts/EEex_Sprite_Patch.lua
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,146 @@
callAlterBaseWeaponDamageHook(EEex_Label("Hook-CGameSprite::Swing()-CGameSprite::Damage()-1"))
callAlterBaseWeaponDamageHook(EEex_Label("Hook-CGameSprite::Swing()-CGameSprite::Damage()-2"))

--[[
+------------------------------------------------------------------------------------------------------------------+
| Implement EEex_AttackOnce() - vanilla Attack() that stops after the first real attack roll |
+------------------------------------------------------------------------------------------------------------------+
| [EEex.dll] EEex::Sprite_Hook_AttackOnceGetBaseAttackRoll(sprite, target, attackRoll) -> short |
| [EEex.dll] EEex::Sprite_Hook_AttackOnceConsumeAmmoForRoll(sprite, target, weapon, abilityNum) |
| [EEex.dll] EEex::Sprite_Hook_AttackOnceShouldEndAction(sprite) -> boolean |
| [EEex.dll] EEex::Sprite_Hook_AttackOnceGetDamageRoll(sprite, target, damageRoll, useModifiers) -> int |
+------------------------------------------------------------------------------------------------------------------+
--]]

-- #MAKE_SHADOW_SPACE(X) already includes the mandatory 32-byte call shadow;
-- X is only the extra scratch used by #SHADOW_SPACE_BOTTOM() saves.

-- di is the raw d20 result. Modify it before the engine copies it into r12
-- so both the displayed Roll value and the later hit math use the same base roll.
EEex_HookBeforeRestoreWithLabels(EEex_Label("Hook-CGameSprite::Hit()-AttackOnceBaseAttackRoll"), 0, 7, 7, {
{"hook_integrity_watchdog_ignore_registers", {
EEex_HookIntegrityWatchdogRegister.RAX, EEex_HookIntegrityWatchdogRegister.RCX, EEex_HookIntegrityWatchdogRegister.RDX,
EEex_HookIntegrityWatchdogRegister.R8, EEex_HookIntegrityWatchdogRegister.R9, EEex_HookIntegrityWatchdogRegister.R10,
EEex_HookIntegrityWatchdogRegister.R11, EEex_HookIntegrityWatchdogRegister.RDI,
}}},
{[[
#MAKE_SHADOW_SPACE(16)
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx

mov rcx, rbx
mov rdx, r14
movsx r8d, di
call #L(EEex::Sprite_Hook_AttackOnceGetBaseAttackRoll)
mov di, ax

mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)]
mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)]
#DESTROY_SHADOW_SPACE
]]}
)

local checkAttackOnceShouldEndAction = function(address, labelSuffix)
local assembly = [[
#MAKE_SHADOW_SPACE(32)
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)], r8
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)], r9

mov rcx, rbx
call #L(EEex::Sprite_Hook_AttackOnceShouldEndAction)
mov r10b, al
mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)]
mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)]
mov r8, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)]
mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)]
#DESTROY_SHADOW_SPACE

test r10b, r10b
jz attack_once_consume_ammo_]] .. labelSuffix .. [[

xor eax, eax
jmp #L(return_skip)

attack_once_consume_ammo_]] .. labelSuffix .. [[: #ENDL
#MAKE_SHADOW_SPACE(32)
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)], r8
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)], r9

; r8/r9 are already the resolved weapon stack and ability number that
; CGameSprite::Hit() is about to use. Consume here so misses spend ammo
; too, while the later Hit() roll hook still owns AttackOnce completion.
mov rcx, rbx
mov rdx, r15
call #L(EEex::Sprite_Hook_AttackOnceConsumeAmmoForRoll)

mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)]
mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)]
mov r8, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)]
mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)]
#DESTROY_SHADOW_SPACE

attack_once_continue_]] .. labelSuffix .. [[: #ENDL
]]

EEex_HookBeforeCallWithLabels(address, {
{"hook_integrity_watchdog_ignore_registers", {
EEex_HookIntegrityWatchdogRegister.RAX, EEex_HookIntegrityWatchdogRegister.RCX, EEex_HookIntegrityWatchdogRegister.RDX,
EEex_HookIntegrityWatchdogRegister.R8, EEex_HookIntegrityWatchdogRegister.R9, EEex_HookIntegrityWatchdogRegister.R10,
EEex_HookIntegrityWatchdogRegister.R11, EEex_HookIntegrityWatchdogRegister.RDI,
}}},
{assembly}
)
end

checkAttackOnceShouldEndAction(EEex_Label("Hook-CGameSprite::Swing()-AttackOnceHitCall-1"), "1")
checkAttackOnceShouldEndAction(EEex_Label("Hook-CGameSprite::Swing()-AttackOnceHitCall-2"), "2")

local hookAttackOnceWeaponDamageRoll = function(address)

-- si is the constructed weapon damage roll at both hook sites. Replace it
-- before the common Roll formatter consumes it. The high word of eax keeps
-- the early modifier/detail branch enabled, so all non-roll feedback stays
-- vanilla even when OverrideBaseDamageRoll is 0.
EEex_HookBeforeRestoreWithLabels(address, 0, 5, 5, {
{"hook_integrity_watchdog_ignore_registers", {
EEex_HookIntegrityWatchdogRegister.RAX, EEex_HookIntegrityWatchdogRegister.RCX, EEex_HookIntegrityWatchdogRegister.RDX,
EEex_HookIntegrityWatchdogRegister.R8, EEex_HookIntegrityWatchdogRegister.R9, EEex_HookIntegrityWatchdogRegister.R10,
EEex_HookIntegrityWatchdogRegister.R11, EEex_HookIntegrityWatchdogRegister.RSI, EEex_HookIntegrityWatchdogRegister.R12,
}}},
{[[
#MAKE_SHADOW_SPACE(32)
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)], rcx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)], rdx
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)], r8
mov qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)], r9

mov rcx, rdi
mov rdx, r15
movsx r8d, si
mov r9d, r12d
call #L(EEex::Sprite_Hook_AttackOnceGetDamageRoll)
mov si, ax
shr eax, 16
mov r12d, eax

mov rcx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-8)]
mov rdx, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-16)]
mov r8, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-24)]
mov r9, qword ptr ss:[rsp+#SHADOW_SPACE_BOTTOM(-32)]
#DESTROY_SHADOW_SPACE
]]}
)
end

-- CGameSprite::Damage() has separate primary/no-launcher and launcher roll
-- construction paths that merge just before feedback text is formatted.
hookAttackOnceWeaponDamageRoll(EEex_Label("Hook-CGameSprite::Damage()-AttackOnceAfterPrimaryWeaponRoll"))
hookAttackOnceWeaponDamageRoll(EEex_Label("Hook-CGameSprite::Damage()-AttackOnceAfterLauncherWeaponRoll"))

EEex_EnableCodeProtection()

end)()
20 changes: 20 additions & 0 deletions EEex/loader/InfinityLoader.db
Original file line number Diff line number Diff line change
Expand Up @@ -2356,6 +2356,10 @@ Operations=ADD 10
Pattern=488D4DD08D7801
Operations=ADD 18

[Hook-CGameSprite::Hit()-AttackOnceBaseAttackRoll]
Pattern=488D4DD08D7801
Operations=ADD 7

[Hook-CGameSprite::Hit()-MeleeingWithRangedPenalty]
Pattern=2BF90FAFC0
Operations=ADD 9
Expand Down Expand Up @@ -2452,6 +2456,22 @@ Operations=ADD 55
Pattern=498D57304C8B442470
Operations=ADD 49

[Hook-CGameSprite::Damage()-AttackOnceAfterLauncherWeaponRoll]
Pattern=75D0448B642444
Operations=ADD 52

[Hook-CGameSprite::Damage()-AttackOnceAfterPrimaryWeaponRoll]
Pattern=75D0448B6C2458
Operations=ADD 56

[Hook-CGameSprite::Swing()-AttackOnceHitCall-1]
Pattern=488D442460488B7C2458
Operations=ADD 31

[Hook-CGameSprite::Swing()-AttackOnceHitCall-2]
Pattern=488D4424604C8B642458
Operations=ADD 31

[Hook-CGameSprite::Swing()-CImmunitiesWeapon::OnList()-Melee]
Pattern=0F85B5070000
Operations=ADD -7
Expand Down
3 changes: 2 additions & 1 deletion EEex/patch/ACTION.IDS
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
478 EEex_ForceSpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*)
478 EEex_ForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*)
479 EEex_ReallyForceSpellObjectOffset(O:Target*,I:Spell*Spell,P:Offset*)
479 EEex_ReallyForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*)
479 EEex_ReallyForceSpellObjectOffsetRES(S:RES*,O:Target*,P:Offset*)
480 EEex_AttackOnce(O:Target*,I:BaseAttackRollMod*,I:OverrideBaseDamageRoll*,I:ConsumeAmmo*Boolean)