Eingraben: 2 s move inside a barrier turning solid, 4 s duration
The barrier's model alpha goes to opaque over 1 s, the dwarf is moved, and it clears over 1 s; orders to him are refused meanwhile (new game::FilterCreatureOrders). The art's duration in its definition is a fixed 4 s (was 7 + level), which the barrier, its bonus and the tooltip use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
198bbab6f9
commit
471b8b13d5
7 files changed
+138
-22
No files matched your search
+21
-4
@@ -120,7 +120,24 @@ Mod (`mods/dwarfarts.cpp`): Eingraben's definition +0x3c becomes 3 (a point);
|
|||||||
after the original case the dwarf is teleported to the end of
|
after the original case the dwarf is teleported to the end of
|
||||||
Phasenverschiebung's walk towards the point (the cursor keeps it within the
|
Phasenverschiebung's walk towards the point (the cursor keeps it within the
|
||||||
visible area). The effect hook (`aurarecast.cpp`, `KeepEffect`) drops kind
|
visible area). The effect hook (`aurarecast.cpp`, `KeepEffect`) drops kind
|
||||||
0x3a for art 0x39 and cuts the art's other effects to 3 s. Confirmed
|
0x3a for art 0x39. Confirmed 2026-10-28: aimed, stops before walls and
|
||||||
2026-10-28: aimed, stops before walls and cliffs, casts at once at the screen
|
cliffs, casts at once at the screen edge, the barrier moves with him, no lock.
|
||||||
edge, the barrier moves with him, no lock, bonus for 3 s. Open: clearing
|
|
||||||
slows and stuns, 1 s digging in and out.
|
Duration ("more like 8 seconds as described in the tooltip, i want that to go
|
||||||
|
down to 4 seconds"): an art's duration is 0x4d24f0 on its definition,
|
||||||
|
+0x76 + level x +0x78, at most +0x82 when set (Eingraben: 7 + level, at most
|
||||||
|
15). The barrier's lifetime (shield +0x20a8), its bonus (the effect applier,
|
||||||
|
0x5b56ab) and the tooltip all use it; the mod sets 4 / 0 / 0.
|
||||||
|
|
||||||
|
Digging in and out: lowering the dwarf's height (creature +0xf0) moved only
|
||||||
|
his marker; lowering the terrain height the game asks for him (0x449b20)
|
||||||
|
moved the camera with him, so he never looked sunk. Instead ("change the
|
||||||
|
barrier animation to turn from transparent to solid and back"): the barrier
|
||||||
|
is `cParticleSystem_dwarf_shield` (constructor 0x7b8fc0, update vf08
|
||||||
|
0x7b91a0, draw vf04 0x7b92b0; models fx_shield_phase1..4.grn). Its draw
|
||||||
|
passes the model with alpha pulse x 128 (pulse 0.4..1 at +0x20b8; __ftol at
|
||||||
|
0x7b9483), then again additively. The mod redirects that __ftol: during a dig
|
||||||
|
the alpha goes to 255 over 1 s, the dwarf is moved (engine tick), and it goes
|
||||||
|
back over 1 s. Orders to the dwarf are refused meanwhile
|
||||||
|
(`game::FilterCreatureOrders`). Confirmed 2026-10-28, barrier and bonus 4 s.
|
||||||
|
Open: clearing slows, roots and stuns.
|
||||||
@@ -15,8 +15,17 @@ std::vector<CreatureOrderFn>& Callbacks() {
|
|||||||
return callbacks;
|
return callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<CreatureOrderFilter>& Filters() {
|
||||||
|
static std::vector<CreatureOrderFilter> filters;
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void FilterCreatureOrders(CreatureOrderFilter filter) {
|
||||||
|
Filters().push_back(filter);
|
||||||
|
}
|
||||||
|
|
||||||
void OnCreatureOrder(CreatureOrderFn callback) {
|
void OnCreatureOrder(CreatureOrderFn callback) {
|
||||||
Callbacks().push_back(callback);
|
Callbacks().push_back(callback);
|
||||||
}
|
}
|
||||||
@@ -26,6 +35,11 @@ void __fastcall Creature_ReceiveOrder(void* self, void* edx, CreatureOrder* orde
|
|||||||
RE_HOOK(0x0052e400, Creature_ReceiveOrder);
|
RE_HOOK(0x0052e400, Creature_ReceiveOrder);
|
||||||
void __fastcall Creature_ReceiveOrder(void* self, void* edx, CreatureOrder* order) {
|
void __fastcall Creature_ReceiveOrder(void* self, void* edx, CreatureOrder* order) {
|
||||||
if (self && order) {
|
if (self && order) {
|
||||||
|
for (CreatureOrderFilter filter : Filters()) {
|
||||||
|
if (!filter(self, *order)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
void* caller = _ReturnAddress();
|
void* caller = _ReturnAddress();
|
||||||
for (CreatureOrderFn callback : Callbacks()) {
|
for (CreatureOrderFn callback : Callbacks()) {
|
||||||
callback(self, *order, caller);
|
callback(self, *order, caller);
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ inline void* ControlledCreature(uint8_t* engine) {
|
|||||||
using CreatureOrderFn = void (*)(void* creature, const CreatureOrder& order, void* caller);
|
using CreatureOrderFn = void (*)(void* creature, const CreatureOrder& order, void* caller);
|
||||||
void OnCreatureOrder(CreatureOrderFn callback);
|
void OnCreatureOrder(CreatureOrderFn callback);
|
||||||
|
|
||||||
|
// Asked before the callbacks: an order any filter refuses (returns false) is
|
||||||
|
// dropped, the creature never gets it.
|
||||||
|
using CreatureOrderFilter = bool (*)(void* creature, const CreatureOrder& order);
|
||||||
|
void FilterCreatureOrders(CreatureOrderFilter filter);
|
||||||
|
|
||||||
// Creature fields.
|
// Creature fields.
|
||||||
constexpr size_t kCreatureId = 0xc;
|
constexpr size_t kCreatureId = 0xc;
|
||||||
constexpr size_t kCreatureX = 0x1c;
|
constexpr size_t kCreatureX = 0x1c;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ uint32_t __fastcall Arts_AddEffect(void* self, void* edx, uint32_t kind, uint32_
|
|||||||
RE_HOOK(0x00582c10, Arts_AddEffect);
|
RE_HOOK(0x00582c10, Arts_AddEffect);
|
||||||
uint32_t __fastcall Arts_AddEffect(void* self, void* edx, uint32_t kind, uint32_t a2, float value, float duration,
|
uint32_t __fastcall Arts_AddEffect(void* self, void* edx, uint32_t kind, uint32_t a2, float value, float duration,
|
||||||
uint32_t art, uint32_t a6, uint32_t a7, uint32_t a8) {
|
uint32_t art, uint32_t a6, uint32_t a7, uint32_t a8) {
|
||||||
if (!mods::dwarfarts::KeepEffect(kind, art, duration)) {
|
if (!mods::dwarfarts::KeepEffect(kind, art)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (self && mods::aurarecast::Renew(static_cast<uint8_t*>(self), kind, duration, art)) {
|
if (self && mods::aurarecast::Renew(static_cast<uint8_t*>(self), kind, duration, art)) {
|
||||||
|
|||||||
+90
-13
@@ -50,9 +50,12 @@
|
|||||||
// 10 while the cell is passable (world 0x636b60) and keeps the last point a
|
// 10 while the cell is passable (world 0x636b60) and keeps the last point a
|
||||||
// creature can stand on (0x636eb0); a wall in the way ends the walk, so the
|
// creature can stand on (0x636eb0); a wall in the way ends the walk, so the
|
||||||
// target has to be in sight. The mod aims Eingraben at a point (definition
|
// target has to be in sight. The mod aims Eingraben at a point (definition
|
||||||
// +0x3c: 5 around the caster -> 3 a point), runs the original case (the bonus
|
// +0x3c: 5 around the caster -> 3 a point), runs the original case (the barrier
|
||||||
// cut to kBarrierSeconds) and moves the dwarf to the end of that walk; the
|
// and its bonus last kBarrierSeconds) and moves the dwarf to the end of that
|
||||||
// cursor limits it to the visible area.
|
// walk; the cursor limits it to the visible area. The move takes 2 s without
|
||||||
|
// orders: the barrier turns solid, he moves inside it, it clears again
|
||||||
|
// ("not instant ... from transparent to solid and back", 2026-10-28; sinking
|
||||||
|
// him into the ground failed: the camera follows his height).
|
||||||
|
|
||||||
#include "mods/dwarfarts.h"
|
#include "mods/dwarfarts.h"
|
||||||
|
|
||||||
@@ -430,7 +433,14 @@ void DrawMarker(const uint8_t* view, void* device, const Grenade& grenade) {
|
|||||||
|
|
||||||
// --- Eingraben ----------------------------------------------------------------
|
// --- Eingraben ----------------------------------------------------------------
|
||||||
|
|
||||||
constexpr float kBarrierSeconds = 3.0f;
|
// The art's duration (0x4d24f0: definition +0x76 + level × +0x78, at most
|
||||||
|
// +0x82 when set; Eingraben 7 + level, at most 15), which the barrier, its
|
||||||
|
// bonus and the tooltip use: a fixed kBarrierSeconds ("down to 4 seconds",
|
||||||
|
// 2026-10-28).
|
||||||
|
constexpr int16_t kBarrierSeconds = 4;
|
||||||
|
constexpr size_t kSpellDurationBase = 0x76; // i16
|
||||||
|
constexpr size_t kSpellDurationPerLevel = 0x78; // i16
|
||||||
|
constexpr size_t kSpellDurationMax = 0x82; // u16, 0: none
|
||||||
// The barrier's effects (logged 2026-10-28, all with art 0x39): kind 0x3a
|
// The barrier's effects (logged 2026-10-28, all with art 0x39): kind 0x3a
|
||||||
// value 0 from its particles (0x7b9602), which holds the dwarf in place, and
|
// value 0 from its particles (0x7b9602), which holds the dwarf in place, and
|
||||||
// kind 2 value 0.5 plus kinds 7..0xa value 30 from its update (0x5b5862..),
|
// kind 2 value 0.5 plus kinds 7..0xa value 30 from its update (0x5b5862..),
|
||||||
@@ -486,6 +496,18 @@ game::MapPosition DigDestination(uint8_t* creature, int32_t x, int32_t y) {
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The dig takes kFadeMs twice: the barrier turns solid, the dwarf moves
|
||||||
|
// inside it, and it turns transparent again; he takes no orders meanwhile.
|
||||||
|
constexpr DWORD kFadeMs = 1000;
|
||||||
|
|
||||||
|
struct Digging {
|
||||||
|
uint32_t id = 0; // 0: none
|
||||||
|
DWORD start = 0; // GetTickCount of the cast, or of the move once moved
|
||||||
|
game::MapPosition to;
|
||||||
|
bool moved = false;
|
||||||
|
};
|
||||||
|
Digging g_dig;
|
||||||
|
|
||||||
void Dig(uint8_t* creature, int32_t x, int32_t y) {
|
void Dig(uint8_t* creature, int32_t x, int32_t y) {
|
||||||
const auto& from = *reinterpret_cast<const game::MapPosition*>(creature + game::kObjectPosition);
|
const auto& from = *reinterpret_cast<const game::MapPosition*>(creature + game::kObjectPosition);
|
||||||
const game::MapPosition to = DigDestination(creature, x, y);
|
const game::MapPosition to = DigDestination(creature, x, y);
|
||||||
@@ -493,8 +515,59 @@ void Dig(uint8_t* creature, int32_t x, int32_t y) {
|
|||||||
if (to.x == from.x && to.y == from.y) {
|
if (to.x == from.x && to.y == from.y) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
game::Teleport(creature, static_cast<int32_t>(to.x / game::kMapCellSize),
|
g_dig = {*reinterpret_cast<const uint32_t*>(creature + game::kCreatureId), GetTickCount(), to, false};
|
||||||
static_cast<int32_t>(to.y / game::kMapCellSize));
|
}
|
||||||
|
|
||||||
|
// Engine tick: once the barrier is solid, the move; the dig's end.
|
||||||
|
void OnTick(uint8_t*) {
|
||||||
|
auto* creature = g_dig.id ? static_cast<uint8_t*>(game::GetCreature(g_dig.id)) : nullptr;
|
||||||
|
if (!creature || GetTickCount() - g_dig.start < kFadeMs) {
|
||||||
|
g_dig.id = creature ? g_dig.id : 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (g_dig.moved) {
|
||||||
|
g_dig.id = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game::Teleport(creature, static_cast<int32_t>(g_dig.to.x / game::kMapCellSize),
|
||||||
|
static_cast<int32_t>(g_dig.to.y / game::kMapCellSize));
|
||||||
|
g_dig.moved = true;
|
||||||
|
g_dig.start = GetTickCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AcceptOrder(void* creature, const game::CreatureOrder&) {
|
||||||
|
return !g_dig.id || *reinterpret_cast<const uint32_t*>(static_cast<uint8_t*>(creature) + game::kCreatureId) != g_dig.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGameEnd() {
|
||||||
|
g_dig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The barrier (cParticleSystem_dwarf_shield, draw 0x7b92b0) draws its model
|
||||||
|
// with alpha pulse × 128 (pulse 0.4..1, +0x20b8; __ftol at 0x7b9483), then
|
||||||
|
// again additively. While a dig runs that alpha goes to opaque and back.
|
||||||
|
constexpr uint32_t kShieldAlphaToInt = 0x007b9483;
|
||||||
|
constexpr uint32_t kFloatToInt = 0x00848f94; // __ftol
|
||||||
|
constexpr int32_t kOpaque = 255;
|
||||||
|
|
||||||
|
int32_t __cdecl ShieldAlpha(int32_t alpha) {
|
||||||
|
if (!g_dig.id) {
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
const float part = std::fmin(static_cast<float>(GetTickCount() - g_dig.start) / kFadeMs, 1.0f);
|
||||||
|
const float solid = g_dig.moved ? 1.0f - part : part;
|
||||||
|
return alpha + static_cast<int32_t>(static_cast<float>(kOpaque - alpha) * solid);
|
||||||
|
}
|
||||||
|
|
||||||
|
__declspec(naked) void ShieldAlphaThunk() {
|
||||||
|
__asm {
|
||||||
|
mov eax, kFloatToInt
|
||||||
|
call eax
|
||||||
|
push eax
|
||||||
|
call ShieldAlpha
|
||||||
|
add esp, 4
|
||||||
|
ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AimEingraben() {
|
bool AimEingraben() {
|
||||||
@@ -503,12 +576,17 @@ bool AimEingraben() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
def[kSpellLaunchType] = static_cast<uint8_t>(game::LaunchType::Ground);
|
def[kSpellLaunchType] = static_cast<uint8_t>(game::LaunchType::Ground);
|
||||||
|
*reinterpret_cast<int16_t*>(def + kSpellDurationBase) = kBarrierSeconds;
|
||||||
|
*reinterpret_cast<int16_t*>(def + kSpellDurationPerLevel) = 0;
|
||||||
|
*reinterpret_cast<uint16_t*>(def + kSpellDurationMax) = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Startup() {
|
void Startup() {
|
||||||
const bool aim = AimEingraben();
|
const bool aim = AimEingraben();
|
||||||
re::Log("dwarfarts: Eingraben aimed at a point, barrier %g s%s", kBarrierSeconds, aim ? "" : " (patch FAILED)");
|
re::Log("dwarfarts: Eingraben aimed at a point, barrier %d s%s", kBarrierSeconds, aim ? "" : " (patch FAILED)");
|
||||||
|
const bool fade = re::RedirectCall(kShieldAlphaToInt, kFloatToInt, reinterpret_cast<const void*>(&ShieldAlphaThunk));
|
||||||
|
re::Log("dwarfarts: Eingraben barrier turns solid%s", fade ? "" : " (patch FAILED)");
|
||||||
const bool impact = re::RedirectCall(kImpactCreate, kCreateAt, reinterpret_cast<const void*>(&CreateImpactThunk));
|
const bool impact = re::RedirectCall(kImpactCreate, kCreateAt, reinterpret_cast<const void*>(&CreateImpactThunk));
|
||||||
re::Log("dwarfarts: Kanonenschuss impact only on stuns%s", impact ? "" : " (patch FAILED)");
|
re::Log("dwarfarts: Kanonenschuss impact only on stuns%s", impact ? "" : " (patch FAILED)");
|
||||||
uint8_t call[sizeof(kPushStepCode)];
|
uint8_t call[sizeof(kPushStepCode)];
|
||||||
@@ -534,18 +612,17 @@ RE_STARTUP(DwarfArtsStartup, Startup);
|
|||||||
struct Registration {
|
struct Registration {
|
||||||
Registration() {
|
Registration() {
|
||||||
game::OnCreatureOrder(&OnOrder);
|
game::OnCreatureOrder(&OnOrder);
|
||||||
|
game::FilterCreatureOrders(&AcceptOrder);
|
||||||
|
game::OnGameEnd(&OnGameEnd);
|
||||||
|
game::OnEngineTick(&OnTick);
|
||||||
game::AddTooltipFilter(game::kArtMoerser, &RewriteTooltip);
|
game::AddTooltipFilter(game::kArtMoerser, &RewriteTooltip);
|
||||||
}
|
}
|
||||||
} g_registration;
|
} g_registration;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool KeepEffect(uint32_t kind, uint32_t art, float& duration) {
|
bool KeepEffect(uint32_t kind, uint32_t art) {
|
||||||
if (art != game::kArtEingraben) {
|
return art != game::kArtEingraben || kind != kEffectDugIn;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
duration = std::fmin(duration, kBarrierSeconds);
|
|
||||||
return kind != kEffectDugIn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t* __fastcall GatherTargets(void* manager, void*, uint32_t* out, const void* position, uint32_t type,
|
uint32_t* __fastcall GatherTargets(void* manager, void*, uint32_t* out, const void* position, uint32_t type,
|
||||||
|
|||||||
@@ -5,9 +5,8 @@
|
|||||||
|
|
||||||
namespace mods::dwarfarts {
|
namespace mods::dwarfarts {
|
||||||
|
|
||||||
// Whether cCreatureArts::AddEffect adds an effect of `kind` for art `art`,
|
// Whether cCreatureArts::AddEffect adds an effect of `kind` for art `art`:
|
||||||
// and its duration: Eingraben no longer holds the dwarf in place, and its
|
// Eingraben no longer holds the dwarf in place.
|
||||||
// defensive bonus lasts only while the barrier does.
|
bool KeepEffect(uint32_t kind, uint32_t art);
|
||||||
bool KeepEffect(uint32_t kind, uint32_t art, float& duration);
|
|
||||||
|
|
||||||
} // namespace mods::dwarfarts
|
} // namespace mods::dwarfarts
|
||||||
@@ -451,3 +451,7 @@
|
|||||||
0x005222c0 cCreature::CastSpell # thiscall (cast), ret 4: switch on the spell (cast +0x20); +0x08/+0x0c ground point; case 1 Phasenverschiebung, 0x39 Eingraben
|
0x005222c0 cCreature::CastSpell # thiscall (cast), ret 4: switch on the spell (cast +0x20); +0x08/+0x0c ground point; case 1 Phasenverschiebung, 0x39 Eingraben
|
||||||
0x00636b60 cWorld::IsPassable # thiscall (position), ret 4: world = *ObjectManager
|
0x00636b60 cWorld::IsPassable # thiscall (position), ret 4: world = *ObjectManager
|
||||||
0x00636eb0 cWorld::CanStand # thiscall (creature id, position), ret 8: may correct the position's area
|
0x00636eb0 cWorld::CanStand # thiscall (creature id, position), ret 8: may correct the position's area
|
||||||
|
0x004d24f0 cArtDef::Duration # thiscall (index, level): +0x76 + level * +0x78 (i16), at most +0x82 when set
|
||||||
|
0x007b8fc0 cParticleSystem_dwarf_shield::cParticleSystem_dwarf_shield # Eingraben's barrier (type 0x480)
|
||||||
|
0x007b91a0 cParticleSystem_dwarf_shield::Update # vf08 (dt), ret 4; pulse +0x20b8, phase +0x20ac, end +0x20a8
|
||||||
|
0x007b92b0 cParticleSystem_dwarf_shield::Draw # vf04, ret 0x44; model alpha pulse * 128 (__ftol at 0x7b9483)
|
||||||
Reference in new issue
Block a user