diff --git a/Code/client/Games/Skyrim/Forms/TESRace.h b/Code/client/Games/Skyrim/Forms/TESRace.h index ea686aea5..dd20fd749 100644 --- a/Code/client/Games/Skyrim/Forms/TESRace.h +++ b/Code/client/Games/Skyrim/Forms/TESRace.h @@ -5,4 +5,16 @@ struct TESRace : TESForm, TESFullName { + // RACE_DATA::Flag + enum : uint32_t + { + kPlayable = 1 << 0, + }; + + [[nodiscard]] bool IsPlayable() const noexcept { return (raceFlags & kPlayable) != 0; } + + uint8_t unk30[0x108 - 0x30]; + uint32_t raceFlags; // RACE_DATA (DATA) at 0xE8, its flags at +0x20 }; + +static_assert(offsetof(TESRace, raceFlags) == 0x108); diff --git a/Code/client/Services/Generic/PlayerService.cpp b/Code/client/Services/Generic/PlayerService.cpp index c0eab4d95..b6f5a0580 100644 --- a/Code/client/Services/Generic/PlayerService.cpp +++ b/Code/client/Services/Generic/PlayerService.cpp @@ -402,9 +402,16 @@ void PlayerService::RunBeastFormDetection() const noexcept static uint32_t lastRaceFormID = 0; static std::chrono::steady_clock::time_point lastSendTimePoint; constexpr auto cDelayBetweenUpdates = 250ms; - // Werewolf (0xCDD84) and Vampire Lord (0x200283A) - constexpr auto isBeastRace = [](uint32_t aFormId) noexcept { - return aFormId == 0xCDD84 || aFormId == 0x200283A; + // Werewolf (0xCDD84) and Vampire Lord (0x200283A), and any other non-playable race: mods transform the + // player with SetRace into their own beast races (e.g. Enderal's Theriantrophist wolf form), and without + // the respawn the remote puppet keeps its old body on the new race's skeleton and behavior. Playable races + // are excluded so race changes in the RaceSex menu don't respawn the puppet. + const auto isBeastRace = [](uint32_t aFormId) noexcept { + if (aFormId == 0xCDD84 || aFormId == 0x200283A) + return true; + + const TESRace* pRace = Cast(TESForm::GetById(aFormId)); + return pRace && !pRace->IsPlayable(); }; const auto now = std::chrono::steady_clock::now();