Taskbar: 7 weapon sets and 7 combat-art slots
cUI_Taskbar2's slot arrays move behind the (enlarged) object with room for 7 each; all instructions addressing them, the relative accesses between the two arrays, the slot loops and the count clamp are patched from tables in game/addresses.h (belt loops untouched). Slots 6 / 7 unlock at levels 45 / 65; AssignArtToSlot accepts slots up to 6. Slot keys call the taskbar's selection directly (honouring COMBINE_SLOTS). Shared code: re::PatchAll / PatchInstructionByte for patch tables, game/engine/objects.h for object, RTTI and kernel calls, slot-hook templates in skillslots.cpp. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
998e315d98
commit
dbc632750b
12 files changed
+364
-145
No files matched your search
@@ -0,0 +1,135 @@
|
||||
// 7 weapon-set and 7 combat-art slots on the taskbar.
|
||||
//
|
||||
// cUI_Taskbar2 keeps its slot controls in two packed arrays of 5 (+0x164
|
||||
// weapon sets, +0x178 art slots) and walks them in loops of 5. The arrays move
|
||||
// to the end of the taskbar object, which is allocated 0x40 bytes larger, with
|
||||
// room for 7 each; every instruction addressing them, the loops and the slot
|
||||
// count clamp are patched (tables in game/addresses.h, found by reviewing all
|
||||
// of 0x6df340-0x6e6000, docs/skill-slots.md). The layout code then creates 7
|
||||
// controls per row at the original spacing: weapon sets grow leftwards from
|
||||
// the centre, art slots rightwards.
|
||||
//
|
||||
// The old arrays keep a copy of the first 5 controls, so code that was missed
|
||||
// still sees valid controls.
|
||||
//
|
||||
// Also: 7 entries in the slot-count-by-level table (6 at level 45, 7 at 65),
|
||||
// and cCreature::AssignArtToSlot accepts slots up to 6.
|
||||
|
||||
#include "mods/skillbar.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include "game/addresses.h"
|
||||
#include "mods/skillslots.h"
|
||||
#include "re/hook.h"
|
||||
#include "re/log.h"
|
||||
#include "re/patch.h"
|
||||
|
||||
namespace mods::skillbar {
|
||||
namespace {
|
||||
|
||||
using namespace game::addr;
|
||||
using mods::skillslots::kGameSlots;
|
||||
using mods::skillslots::kSlots;
|
||||
|
||||
constexpr uint32_t kWeaponSlots = kTaskbarSize; // new arrays behind the old object
|
||||
constexpr uint32_t kSkillSlots = kWeaponSlots + kSlots * 4;
|
||||
constexpr uint32_t kNewTaskbarSize = kSkillSlots + kSlots * 4 + 0x8;
|
||||
constexpr int8_t kOldDistance = static_cast<int8_t>(kTaskbarSkillSlots - kTaskbarWeaponSlots); // 0x14
|
||||
constexpr int8_t kNewDistance = static_cast<int8_t>(kSkillSlots - kWeaponSlots); // 0x1c
|
||||
|
||||
// (level, slots): the game's table plus slots 6 and 7.
|
||||
constexpr uint16_t kSlotLevels[kSlots] = {0, 2, 8, 16, 30, 45, 65};
|
||||
|
||||
std::atomic<uint8_t*> g_taskbar{nullptr};
|
||||
|
||||
void Startup() {
|
||||
int failed = 0;
|
||||
failed += !re::PatchPushImm32(kTaskbarAllocSizePush, kTaskbarSize, kNewTaskbarSize);
|
||||
failed += re::PatchAll(kTaskbarWeaponSlotUsers, kTaskbarWeaponSlots, kWeaponSlots);
|
||||
failed += re::PatchAll(kTaskbarSkillSlotUsers, kTaskbarSkillSlots, kSkillSlots);
|
||||
failed += re::PatchAll(kTaskbarSlotDistanceDown, static_cast<uint8_t>(-kNewDistance));
|
||||
failed += re::PatchAll(kTaskbarSlotDistanceUp, static_cast<uint8_t>(kNewDistance));
|
||||
failed += re::PatchAll(kTaskbarSlotLoopBounds, static_cast<uint8_t>(kSlots));
|
||||
failed += re::PatchAll(kAssignArtMaxSlot, static_cast<uint8_t>(kSlots - 1));
|
||||
static_assert(kOldDistance == 0x14);
|
||||
re::Log("skillbar: %u weapon sets / art slots on the taskbar (%d patches failed)", kSlots, failed);
|
||||
}
|
||||
RE_STARTUP(SkillBarStartup, Startup);
|
||||
|
||||
using SelectFn = void(__fastcall*)(void* taskbar, void* edx, uint32_t slot, int notify);
|
||||
|
||||
void Select(uint32_t function, unsigned slot) {
|
||||
if (uint8_t* taskbar = g_taskbar.load()) {
|
||||
reinterpret_cast<SelectFn>(function)(taskbar, nullptr, slot, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SelectSkillSlot(unsigned slot) {
|
||||
Select(0x006e17d0, slot); // cUI_Taskbar2::SelectSkillSlot
|
||||
}
|
||||
|
||||
// With the game option COMBINE_SLOTS (Options::CombineSlots 0x81c020) a
|
||||
// weapon set also selects the art slot with the same number, as the digit
|
||||
// keys do (cUI_Taskbar2::HandleEvent).
|
||||
void SelectWeaponSet(unsigned slot) {
|
||||
Select(0x006e1440, slot); // cUI_Taskbar2::SelectWeaponSet
|
||||
using CombineSlotsFn = char(__cdecl*)();
|
||||
if (reinterpret_cast<CombineSlotsFn>(0x0081c020)()) {
|
||||
SelectSkillSlot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
void OnTaskbar(void* taskbar) {
|
||||
g_taskbar = static_cast<uint8_t*>(taskbar);
|
||||
}
|
||||
|
||||
// Copy of the first 5 controls of each row into the old arrays.
|
||||
void MirrorOldArrays(void* taskbar) {
|
||||
auto* p = static_cast<uint8_t*>(taskbar);
|
||||
std::memcpy(p + kTaskbarWeaponSlots, p + kWeaponSlots, kGameSlots * 4);
|
||||
std::memcpy(p + kTaskbarSkillSlots, p + kSkillSlots, kGameSlots * 4);
|
||||
}
|
||||
|
||||
uint16_t SlotCountForLevel(unsigned level) {
|
||||
uint16_t count = 1;
|
||||
for (unsigned i = 0; i < kSlots; ++i) {
|
||||
if (level >= kSlotLevels[i]) {
|
||||
count = static_cast<uint16_t>(i + 1);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace mods::skillbar
|
||||
|
||||
namespace game {
|
||||
|
||||
// cUI_Taskbar2's layout (0x6e4ba0, from the constructor): creates the slot
|
||||
// controls.
|
||||
void __fastcall UiTaskbar_Layout(void* self, void* edx);
|
||||
RE_HOOK(0x006e4ba0, UiTaskbar_Layout);
|
||||
void __fastcall UiTaskbar_Layout(void* self, void* edx) {
|
||||
mods::skillbar::OnTaskbar(self);
|
||||
UiTaskbar_Layout_original(self, edx);
|
||||
mods::skillbar::MirrorOldArrays(self);
|
||||
}
|
||||
|
||||
// cUI_Taskbar2::SetSlotCount (0x6dffa0): shows the first n slots of each row.
|
||||
void __fastcall UiTaskbar_SetSlotCount(void* self, void* edx, uint32_t count);
|
||||
RE_HOOK(0x006dffa0, UiTaskbar_SetSlotCount);
|
||||
void __fastcall UiTaskbar_SetSlotCount(void* self, void* edx, uint32_t count) {
|
||||
mods::skillbar::OnTaskbar(self);
|
||||
UiTaskbar_SetSlotCount_original(self, edx, count);
|
||||
}
|
||||
|
||||
uint32_t __stdcall SlotCountForLevel(uint32_t level);
|
||||
RE_HOOK(addr::SlotCountForLevel, SlotCountForLevel);
|
||||
uint32_t __stdcall SlotCountForLevel(uint32_t level) {
|
||||
return mods::skillbar::SlotCountForLevel(level & 0xffff);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
Reference in new issue
Block a user