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:
khallaandClaude Opus 5.5 committed 2026-10-28 01:28:07 +01:00
1 parent 198bbab6f9
commit 471b8b13d5
7 files changed
+138 -22

No files matched your search

+21 -4
View File
@@ -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
Phasenverschiebung's walk towards the point (the cursor keeps it within the
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
2026-10-28: aimed, stops before walls and cliffs, casts at once at the screen
edge, the barrier moves with him, no lock, bonus for 3 s. Open: clearing
slows and stuns, 1 s digging in and out.
0x3a for art 0x39. Confirmed 2026-10-28: aimed, stops before walls and
cliffs, casts at once at the screen edge, the barrier moves with him, no lock.
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.
+14
View File
@@ -15,8 +15,17 @@ std::vector<CreatureOrderFn>& Callbacks() {
return callbacks;
}
std::vector<CreatureOrderFilter>& Filters() {
static std::vector<CreatureOrderFilter> filters;
return filters;
}
} // namespace
void FilterCreatureOrders(CreatureOrderFilter filter) {
Filters().push_back(filter);
}
void OnCreatureOrder(CreatureOrderFn callback) {
Callbacks().push_back(callback);
}
@@ -26,6 +35,11 @@ void __fastcall Creature_ReceiveOrder(void* self, void* edx, CreatureOrder* orde
RE_HOOK(0x0052e400, Creature_ReceiveOrder);
void __fastcall Creature_ReceiveOrder(void* self, void* edx, CreatureOrder* order) {
if (self && order) {
for (CreatureOrderFilter filter : Filters()) {
if (!filter(self, *order)) {
return;
}
}
void* caller = _ReturnAddress();
for (CreatureOrderFn callback : Callbacks()) {
callback(self, *order, caller);
+5
View File
@@ -63,6 +63,11 @@ inline void* ControlledCreature(uint8_t* engine) {
using CreatureOrderFn = void (*)(void* creature, const CreatureOrder& order, void* caller);
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.
constexpr size_t kCreatureId = 0xc;
constexpr size_t kCreatureX = 0x1c;
+1 -1
View File
@@ -127,7 +127,7 @@ uint32_t __fastcall Arts_AddEffect(void* self, void* edx, uint32_t kind, uint32_
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 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;
}
if (self && mods::aurarecast::Renew(static_cast<uint8_t*>(self), kind, duration, art)) {
+90 -13
View File
@@ -50,9 +50,12 @@
// 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
// 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
// cut to kBarrierSeconds) and moves the dwarf to the end of that walk; the
// cursor limits it to the visible area.
// +0x3c: 5 around the caster -> 3 a point), runs the original case (the barrier
// and its bonus last kBarrierSeconds) and moves the dwarf to the end of that
// 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"
@@ -430,7 +433,14 @@ void DrawMarker(const uint8_t* view, void* device, const Grenade& grenade) {
// --- 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
// 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..),
@@ -486,6 +496,18 @@ game::MapPosition DigDestination(uint8_t* creature, int32_t x, int32_t y) {
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) {
const auto& from = *reinterpret_cast<const game::MapPosition*>(creature + game::kObjectPosition);
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) {
return;
}
game::Teleport(creature, static_cast<int32_t>(to.x / game::kMapCellSize),
static_cast<int32_t>(to.y / game::kMapCellSize));
g_dig = {*reinterpret_cast<const uint32_t*>(creature + game::kCreatureId), GetTickCount(), to, false};
}
// 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() {
@@ -503,12 +576,17 @@ bool AimEingraben() {
return false;
}
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;
}
void Startup() {
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));
re::Log("dwarfarts: Kanonenschuss impact only on stuns%s", impact ? "" : " (patch FAILED)");
uint8_t call[sizeof(kPushStepCode)];
@@ -534,18 +612,17 @@ RE_STARTUP(DwarfArtsStartup, Startup);
struct Registration {
Registration() {
game::OnCreatureOrder(&OnOrder);
game::FilterCreatureOrders(&AcceptOrder);
game::OnGameEnd(&OnGameEnd);
game::OnEngineTick(&OnTick);
game::AddTooltipFilter(game::kArtMoerser, &RewriteTooltip);
}
} g_registration;
} // namespace
bool KeepEffect(uint32_t kind, uint32_t art, float& duration) {
if (art != game::kArtEingraben) {
return true;
}
duration = std::fmin(duration, kBarrierSeconds);
return kind != kEffectDugIn;
bool KeepEffect(uint32_t kind, uint32_t art) {
return art != game::kArtEingraben || kind != kEffectDugIn;
}
uint32_t* __fastcall GatherTargets(void* manager, void*, uint32_t* out, const void* position, uint32_t type,
+3 -4
View File
@@ -5,9 +5,8 @@
namespace mods::dwarfarts {
// Whether cCreatureArts::AddEffect adds an effect of `kind` for art `art`,
// and its duration: Eingraben no longer holds the dwarf in place, and its
// defensive bonus lasts only while the barrier does.
bool KeepEffect(uint32_t kind, uint32_t art, float& duration);
// Whether cCreatureArts::AddEffect adds an effect of `kind` for art `art`:
// Eingraben no longer holds the dwarf in place.
bool KeepEffect(uint32_t kind, uint32_t art);
} // namespace mods::dwarfarts
+4
View File
@@ -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
0x00636b60 cWorld::IsPassable # thiscall (position), ret 4: world = *ObjectManager
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)