Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Code/client/Games/Skyrim/Forms/TESFaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,58 @@

#include <Forms/TESForm.h>
#include <Components/TESFullName.h>
#include <BSCore/BSTHashMap.h>
#include <Games/Primitives.h>

struct TESObjectREFR;
struct BGSListForm;
struct BGSOutfit;
struct TESNPC;

struct TESReactionForm : BaseFormComponent
{
GameValueList<void*> reactions;
uint8_t groupFormType;
uint8_t pad19;
uint16_t pad1A;
uint32_t pad1C;
};

static_assert(sizeof(TESReactionForm) == 0x20);
static_assert(offsetof(TESReactionForm, reactions) == 0x08);
static_assert(offsetof(TESReactionForm, groupFormType) == 0x18);

struct TESFaction : TESForm
{
// Where the game sends the player's belongings when they are arrested by this faction.
struct CrimeData
{
TESObjectREFR* jailMarker; // JAIL
TESObjectREFR* waitMarker; // WAIT
TESObjectREFR* stolenGoodsContainer; // STOL: stolen items taken on arrest
TESObjectREFR* playerInventoryContainer; // PLCN: the rest of the player's inventory, returned on release
BGSListForm* crimeGroup; // CRGR
BGSOutfit* jailOutfit; // JOUT
uint8_t crimeValues[0x14]; // CRVA
uint32_t pad44;
};

TESFullName fullname;
TESReactionForm reactionForm;
creation::BSTHashMap<const TESNPC*, uint32_t>* crimeGoldMap;
uint32_t factionFlags; // DATA
uint32_t pad5C;
CrimeData crimeData;
// Remaining vendor data, ranks, crime counts, and timestamps are not accessed here.
uint8_t padA8[0x100 - 0xA8];
};

static_assert(sizeof(TESFaction::CrimeData) == 0x48);
static_assert(sizeof(TESFaction) == 0x100);
static_assert(offsetof(TESFaction, fullname) == 0x20);
static_assert(offsetof(TESFaction, reactionForm) == 0x30);
static_assert(offsetof(TESFaction, crimeGoldMap) == 0x50);
static_assert(offsetof(TESFaction, factionFlags) == 0x58);
static_assert(offsetof(TESFaction, crimeData) == 0x60);
static_assert(offsetof(TESFaction, crimeData.stolenGoodsContainer) == 0x70);
static_assert(offsetof(TESFaction, crimeData.playerInventoryContainer) == 0x78);
8 changes: 7 additions & 1 deletion Code/client/Games/TES.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ struct TESObjectCELL;
struct TESWorldSpace;
struct NiPoint3;
struct TESForm;
struct TESFaction;
struct Actor;
struct ImageSpaceModifierInstance;

Expand Down Expand Up @@ -109,12 +110,17 @@ struct ModManager
Mod* GetByName(const char* acpName) const noexcept;
TESObjectCELL* GetCellFromCoordinates(int32_t aX, int32_t aY, TESWorldSpace* aWorldSpace, bool aSpawnCell) noexcept;

uint8_t pad0[0x748];
// Form arrays start at 0x10 and are indexed by FormType, 0x18 bytes each.
uint8_t pad0[0x118];
GameArray<TESFaction*> factions;
uint8_t pad130[0x748 - 0x130];
GameArray<TESQuest*> quests;
uint8_t pad760[0xD60 - 0x760];
GameList<Mod> mods;
};

static_assert(offsetof(ModManager, factions) == 0x118);
static_assert(offsetof(ModManager, quests) == 0x748);
static_assert(offsetof(ModManager, mods) == 0xD60);

struct Setting
Expand Down
38 changes: 36 additions & 2 deletions Code/client/Services/Generic/ObjectService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include <Forms/TESObjectCELL.h>
#include <Forms/TESWorldSpace.h>
#include <Forms/BGSEncounterZone.h>
#include <Forms/TESFaction.h>
#include <Games/TES.h>

#include <inttypes.h>

Expand Down Expand Up @@ -60,11 +62,41 @@ bool IsPlayerHome(const TESObjectCELL* pCell) noexcept
return false;
}

bool ShouldSyncObject(const TESObjectREFR* apObject) noexcept
// Find each loaded faction's containers for a jailed player's belongings and stolen items.
// Do not sync them: each player's items must stay separate (#700).
// Only compare the container pointers; never read through them.
Set<const TESObjectREFR*> GetPlayerStashContainers() noexcept
{
Set<const TESObjectREFR*> containers{};

ModManager* pModManager = ModManager::Get();
if (!pModManager)
return containers;

for (const TESFaction* pFaction : pModManager->factions)
{
if (!pFaction)
continue;

if (pFaction->crimeData.playerInventoryContainer)
containers.insert(pFaction->crimeData.playerInventoryContainer);

if (pFaction->crimeData.stolenGoodsContainer)
containers.insert(pFaction->crimeData.stolenGoodsContainer);
}

return containers;
}

bool ShouldSyncObject(const TESObjectREFR* apObject, const Set<const TESObjectREFR*>& acPlayerStashContainers) noexcept
{
if (!apObject)
return false;

if (acPlayerStashContainers.contains(apObject))
return false;

// Quest chests that take the player's whole inventory without going through faction crime data.
switch (apObject->formID)
{
case 0x39CF1: // Don't sync the chest in the "Diplomatic Immunity" quest
Expand Down Expand Up @@ -117,9 +149,11 @@ void ObjectService::OnCellChange(const CellChangeEvent& acEvent) noexcept

AssignObjectsRequest request{};

const Set<const TESObjectREFR*> playerStashContainers = GetPlayerStashContainers();

for (TESObjectREFR* pObject : objects)
{
if (!ShouldSyncObject(pObject))
if (!ShouldSyncObject(pObject, playerStashContainers))
{
spdlog::warn("Excluding sync for {:X}", pObject->formID);
continue;
Expand Down