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
|
||||
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.
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user