Eingraben: aimed escape to a point in sight, 3 s barrier, no lock
Eingraben becomes ground-targeted; after its own case the dwarf moves to the end of Phasenverschiebung's walk towards the point (stops before walls and cliffs). The barrier's bonus lasts 3 s and its lock in place (effect kind 0x3a) is dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
55e48dd302
commit
198bbab6f9
6 files changed
+183
-2
No files matched your search
@@ -88,6 +88,7 @@ constexpr uint16_t kArtEnergieblitze = 0x3fb;
|
||||
constexpr uint16_t kArtFlammenwerfer = 0x43d;
|
||||
constexpr uint16_t kArtMoerser = 0x43e; // Mörsergranate (gui_spells_moerser)
|
||||
constexpr uint16_t kArtKanone = 0x43f; // Kanonenschuss (gui_spells_kanone)
|
||||
constexpr uint16_t kArtEingraben = 0x39; // spell (gui_moves_eingraben)
|
||||
|
||||
// cCreatureArts fields.
|
||||
constexpr size_t kArtsCreature = 0x4; // cCreature*: the owner
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "game/engine/fx.h"
|
||||
#include "game/engine/objects.h"
|
||||
#include "game/engine/orders.h"
|
||||
#include "mods/dwarfarts.h"
|
||||
#include "re/hook.h"
|
||||
#include "re/log.h"
|
||||
#include "re/patch.h"
|
||||
@@ -126,6 +127,9 @@ 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)) {
|
||||
return 1;
|
||||
}
|
||||
if (self && mods::aurarecast::Renew(static_cast<uint8_t*>(self), kind, duration, art)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
+124
-1
@@ -34,6 +34,27 @@
|
||||
// the point; the circle is drawn after the world view's ground (0x62d530,
|
||||
// before the objects) until the impact gathers its targets (the grenade
|
||||
// object itself stays about 30 s longer).
|
||||
//
|
||||
// Eingraben (spell 0x39), the dwarf's escape (request of 2026-10-28): "cast a
|
||||
// protective barrier around the Dwarf (current visual effect), clear him of
|
||||
// all movement impeding effects and then teleport to the position marked by
|
||||
// the cursor (if possible)", shown as the dwarf going underground and coming
|
||||
// out at the target, 1 s each; range the visible area; the barrier is the
|
||||
// skill's current defensive bonus for 3 s; "he should be able to see the
|
||||
// target location"; like the battlemage's Phasenverschiebung. The spells run
|
||||
// in cCreature::CastSpell (0x5222c0, thiscall (cast), ret 4; cast +0x08 /
|
||||
// +0x0c the ground point, +0x20 the spell). Case 0x39 creates object type
|
||||
// 0x480 at the caster with cEventFX 0x9d (owner and target the caster) and
|
||||
// plays sound 0x840: the barrier, which gives the bonus. Case 1
|
||||
// (Phasenverschiebung) walks from the caster towards the target in steps of
|
||||
// 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.
|
||||
|
||||
#include "mods/dwarfarts.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -46,6 +67,7 @@
|
||||
|
||||
#include "game/engine/art.h"
|
||||
#include "game/engine/arts.h"
|
||||
#include "game/engine/events.h"
|
||||
#include "game/engine/objects.h"
|
||||
#include "game/engine/orders.h"
|
||||
#include "game/engine/worldview.h"
|
||||
@@ -406,8 +428,88 @@ void DrawMarker(const uint8_t* view, void* device, const Grenade& grenade) {
|
||||
game::FillTriangles(device, vertices.data(), vertices.size());
|
||||
}
|
||||
|
||||
// --- Eingraben ----------------------------------------------------------------
|
||||
|
||||
constexpr float kBarrierSeconds = 3.0f;
|
||||
// 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..),
|
||||
// the defensive bonus.
|
||||
constexpr uint32_t kEffectDugIn = 0x3a;
|
||||
constexpr int32_t kWalkStep = 10; // Phasenverschiebung's step (0x52288a: 10.0)
|
||||
constexpr size_t kSpellLaunchType = 0x3c;
|
||||
|
||||
// The world (cWorld, the object manager's first field), as the art code
|
||||
// passes it to its cell checks.
|
||||
void* World() {
|
||||
void* manager = game::ObjectManager();
|
||||
return manager ? *static_cast<void**>(manager) : nullptr;
|
||||
}
|
||||
|
||||
// cWorld 0x636b60 (thiscall (position), ret 4): the cell is passable.
|
||||
bool Passable(void* world, game::MapPosition& at) {
|
||||
using Fn = bool(__fastcall*)(void*, void*, game::MapPosition*);
|
||||
return reinterpret_cast<Fn>(0x00636b60)(world, nullptr, &at);
|
||||
}
|
||||
|
||||
// cWorld 0x636eb0 (thiscall (creature id, position), ret 8): the creature can
|
||||
// stand there (may correct the position's area).
|
||||
bool Standable(void* world, uint32_t creature, game::MapPosition& at) {
|
||||
using Fn = bool(__fastcall*)(void*, void*, uint32_t, game::MapPosition*);
|
||||
return reinterpret_cast<Fn>(0x00636eb0)(world, nullptr, creature, &at);
|
||||
}
|
||||
|
||||
// Phasenverschiebung's walk (0x5227a6..): from the creature towards (x, y),
|
||||
// the last point it can stand on before a cell that is not passable.
|
||||
game::MapPosition DigDestination(uint8_t* creature, int32_t x, int32_t y) {
|
||||
const auto& from = *reinterpret_cast<const game::MapPosition*>(creature + game::kObjectPosition);
|
||||
game::MapPosition best = from;
|
||||
void* world = World();
|
||||
const float dx = static_cast<float>(x - from.x), dy = static_cast<float>(y - from.y);
|
||||
const float length = std::sqrt(dx * dx + dy * dy);
|
||||
if (!world || length < kWalkStep) {
|
||||
return best;
|
||||
}
|
||||
const auto stepX = static_cast<int32_t>(dx / length * kWalkStep);
|
||||
const auto stepY = static_cast<int32_t>(dy / length * kWalkStep);
|
||||
const auto steps = static_cast<int32_t>(length / kWalkStep);
|
||||
const uint32_t id = *reinterpret_cast<const uint32_t*>(creature + game::kCreatureId);
|
||||
game::MapPosition at = from;
|
||||
for (int32_t i = 0; i <= steps && Passable(world, at); ++i) {
|
||||
game::MapPosition stand = at;
|
||||
if (Standable(world, id, stand)) {
|
||||
best = stand;
|
||||
}
|
||||
at.x += stepX;
|
||||
at.y += stepY;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
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);
|
||||
re::Log("dwarfarts: Eingraben %d,%d towards %d,%d -> %d,%d", from.x, from.y, x, y, to.x, to.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));
|
||||
}
|
||||
|
||||
bool AimEingraben() {
|
||||
auto* def = const_cast<uint8_t*>(game::FindSpellDef(game::kArtEingraben));
|
||||
if (!def || def[kSpellLaunchType] != static_cast<uint8_t>(game::LaunchType::AroundSelf)) {
|
||||
return false;
|
||||
}
|
||||
def[kSpellLaunchType] = static_cast<uint8_t>(game::LaunchType::Ground);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Startup() {
|
||||
const bool impact = re::RedirectCall(kImpactCreate, kCreateAt, reinterpret_cast<const void*>(&CreateImpactThunk));
|
||||
const bool aim = AimEingraben();
|
||||
re::Log("dwarfarts: Eingraben aimed at a point, barrier %g s%s", kBarrierSeconds, aim ? "" : " (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)];
|
||||
std::memset(call, 0x90, sizeof(call));
|
||||
@@ -438,6 +540,14 @@ struct 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;
|
||||
}
|
||||
|
||||
uint32_t* __fastcall GatherTargets(void* manager, void*, uint32_t* out, const void* position, uint32_t type,
|
||||
uint32_t only, int32_t radius, const uint8_t* fx) {
|
||||
if (type == kGrenadeImpactType && radius < 0 && fx) {
|
||||
@@ -481,4 +591,17 @@ void __fastcall WorldView_DrawGroundQuads(uint8_t* self, void* edx, void* render
|
||||
}
|
||||
}
|
||||
|
||||
// cCreature::CastSpell (0x5222c0, thiscall (cast), ret 4): the spells; after
|
||||
// Eingraben's own case the dwarf digs to the point (cast +0x08 / +0x0c).
|
||||
void __fastcall Creature_CastSpell(uint8_t* self, void* edx, uint32_t* cast);
|
||||
RE_HOOK(0x005222c0, Creature_CastSpell);
|
||||
void __fastcall Creature_CastSpell(uint8_t* self, void* edx, uint32_t* cast) {
|
||||
const bool dig = self && cast && cast[8] == kArtEingraben;
|
||||
const int32_t x = dig ? static_cast<int32_t>(cast[2]) : 0, y = dig ? static_cast<int32_t>(cast[3]) : 0;
|
||||
Creature_CastSpell_original(self, edx, cast);
|
||||
if (dig) {
|
||||
mods::dwarfarts::Dig(self, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
// The dwarf's arts (dwarfarts.cpp).
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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);
|
||||
|
||||
} // namespace mods::dwarfarts
|
||||
Reference in new issue
Block a user