Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Weapon types"-based animation replacement #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
71 changes: 46 additions & 25 deletions nvse/nvse/NiTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,56 +209,77 @@ struct NiTSet
// this is a NiTPointerMap <UInt32, T_Data>
// todo: generalize key
template <typename T_Data>
class NiTPointerMap
class NiTPointerMap // credits to Yvileapsis for the changes
{
public:
NiTPointerMap();
virtual ~NiTPointerMap();

struct Entry
{
Entry * next;
Entry* next;
UInt32 key;
T_Data * data;
T_Data* data;
};

virtual void Destroy(bool doFree);
virtual UInt32 CalculateBucket(UInt32 key);
virtual bool CompareKey(UInt32 lhs, UInt32 rhs);
virtual void FillEntry(Entry* entry, UInt32 key, T_Data data);
virtual void FreeKey(Entry* entry);
virtual Entry* AllocNewEntry();
virtual void FreeEntry(Entry* entry);

T_Data* Lookup(UInt32 key);
bool Insert(Entry* nuEntry);

// void ** _vtbl; // 0
UInt32 m_numBuckets; // 4
Entry** m_buckets; // 8
UInt32 m_numItems; // C

// note: traverses in non-numerical order
class Iterator
{
friend NiTPointerMap;

NiTPointerMap* m_table;
Entry* m_entry;
Entry** m_bucket;

void FindValid();
void FindNonEmpty()
{
for (Entry** end = &m_table->m_buckets[m_table->m_numBuckets]; m_bucket != end; m_bucket++)
if (m_entry = *m_bucket) return;
}

public:
Iterator(NiTPointerMap * table, Entry * entry = NULL, UInt32 bucket = 0)
:m_table(table), m_entry(entry), m_bucket(bucket) { FindValid(); }
Iterator(NiTPointerMap& _table) : m_table(&_table), m_bucket(m_table->m_buckets), m_entry(NULL) { FindNonEmpty(); }

~Iterator() { }

T_Data * Get(void);
T_Data* Get(void);
UInt32 GetKey(void);
bool Next(void);
bool Done(void);

private:
void FindValid(void);
T_Data Get() const { return m_entry->data; }
// T_Key Key() const { return m_entry->key; }

NiTPointerMap * m_table;
Entry * m_entry;
UInt32 m_bucket;
explicit operator bool() const { return m_entry != NULL; }
void operator++()
{
m_entry = m_entry->next;
if (!m_entry)
{
m_bucket++;
FindNonEmpty();
}
}
};

virtual UInt32 CalculateBucket(UInt32 key);
virtual bool CompareKey(UInt32 lhs, UInt32 rhs);
virtual void Fn_03(UInt32 arg0, UInt32 arg1, UInt32 arg2); // assign to entry
virtual void Fn_04(UInt32 arg);
virtual void Fn_05(void); // locked operations
virtual void Fn_06(void); // locked operations

T_Data * Lookup(UInt32 key);
bool Insert(Entry* nuEntry);

// void ** _vtbl; // 0
UInt32 m_numBuckets; // 4
Entry ** m_buckets; // 8
UInt32 m_numItems; // C
Iterator Begin() { return Iterator(*this); }
};

template <typename T_Data>
Expand Down
216 changes: 213 additions & 3 deletions nvse_plugin_example/file_animations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ Script* CompileConditionScript(const std::string& condString, const std::string&
return condition.release();
}



void HandleJson(const std::filesystem::path& path)
{
Log("\nReading from JSON file " + path.string());
Expand Down Expand Up @@ -274,7 +276,183 @@ void HandleJson(const std::filesystem::path& path)
DebugPrint("The JSON is incorrectly formatted! It will not be applied.");
DebugPrint(FormatString("JSON error: %s\n", e.what()));
}


}

void HandleJson2(const std::filesystem::path& path, std::map<std::string, std::vector<int>>& typesMap)
{
Log("\nReading types from JSON file " + path.string());

const auto processTypesMod = [](int modIDX, Script* condition, bool pollCondition, int& priority, std::vector<std::string>& weapType, std::map<std::string, std::vector<int>>& typesMap)
{

const auto GetHasType2 = [](std::vector<int>& weapParams, std::vector<int>& typesData) // -1 is "any" for better flexibility
{
if (
(weapParams[0] == typesData[0] || typesData[0] == -1) &&
(weapParams[1] == typesData[1] || typesData[1] == -1) &&
(weapParams[2] == typesData[2] || typesData[2] == -1) &&
(weapParams[3] == typesData[3] || typesData[3] == -1)
) {

return true;
}
return false;
};

NiTPointerMap<TESForm>* formsMap = *(NiTPointerMap<TESForm>**)0x11C54C0;

for (auto mIter = formsMap->Begin(); mIter; ++mIter) { // credits to Yvileapsis for the iteration example

TESForm* form = mIter.Get();
if (form -> GetModIndex() == modIDX || modIDX == -1) {

if (form->IsWeapon()) {
auto weap = static_cast<TESObjectWEAP*>(form);
std::string test = weap->GetName();

Log("Type to " + test + " : mod ID " + std::to_string(form->GetModIndex()));

std::vector<int> weapParams;
weapParams.push_back(weap->eWeaponType); //Animation type
weapParams.push_back(weap->handGrip); //Grip type
weapParams.push_back(weap->reloadAnim); //Reload animation (non-modded)
weapParams.push_back(weap->attackAnim); //Attack animation
Log("Animation type: " + std::to_string(weapParams[0]));
Log("Grip type: " + std::to_string(weapParams[1]));
Log("Reload animation: " + std::to_string(weapParams[2]));
Log("Attack animation: " + std::to_string(weapParams[3]));
// the log messages above are actually usefull for working with types
// there's also a "reference" in tesEdit code

for (int i = 0; i < weapType.size(); i++) {

try {
if (GetHasType2(weapParams, typesMap.at(weapType[i]))) {
Log("Replacing animations with " + weapType[i] + " for " + test);
g_jsonEntries.emplace_back(weapType[i], form, condition, pollCondition, priority);
}
}
catch (const std::out_of_range& oor) {
DebugPrint("This weapontype wasn't defined: " + weapType[i]);
}
}
}
else {
//Log("Type: wrong item " + std::to_string(traverse->key) + "; Current rem: " + std::to_string(traverse->key % formsMap->m_numBuckets));
}
}

}

return true;
};

try
{
std::ifstream i(path);
nlohmann::json j;
i >> j;
if (j.is_array())
{


for (auto& elem : j)
{
if (!elem.is_object())
{
DebugPrint("JSON error: expected object with mod, form and folder fields");
continue;
}
int priority = 0;
if (elem.contains("priority"))
priority = elem["priority"].get<int>();


auto modName = elem.contains("mod") ? elem["mod"].get<std::string>() : "";
auto typeData = elem.contains("typedata") ? &elem["typedata"] : nullptr;
//auto typeName = elem["name"].get<std::string>();
const auto* mod = !modName.empty() ? DataHandler::Get()->LookupModByName(modName.c_str()) : nullptr;

const auto& types = elem.contains("weapontypes") ? &elem["weapontypes"] : nullptr;

const auto& folderType = elem.contains("folderType") ? elem["folderType"].get<std::string>() : "";

std::vector<std::string> weapType;


if (typeData && folderType != "") { // defining types
if (!typeData->is_array())
{
continue;
}
else {
std::ranges::transform(*typeData, std::back_inserter(typesMap[folderType]), [&](auto& i) {return i; });

}

}

if (types) { // reading applied types

if (!types->is_array())
{
continue;
}
else {
std::ranges::transform(*types, std::back_inserter(weapType), [&](auto& i) {return i.template get<std::string>(); });
}
}

if (!mod && !modName.empty()) // stop, since types don't have mods, and mods don't have type definitions
{
DebugPrint("Mod name " + modName + " was not found");
continue;
}

const auto& folder = elem.contains("folder") ? elem["folder"].get<std::string>() : "";
auto* formElem = elem.contains("form") ? &elem["form"] : nullptr;
Script* condition = nullptr;
auto pollCondition = false;
if (!formElem) { // no forms should be here
if (mod)
{
if (folder != "") { // same as folder named after a plugin, but in json, saves space a little

const auto dirX = GetCurPath() + R"(\Data\Meshes\AnimGroupOverride\)" + folder;
Log(FormatString("Registered all forms for folder %s", dirX));
std::filesystem::path folderX(dirX);
LoadModAnimPaths(folderX, mod);
}
else if (types) { // the main thing

Log("Found type in " + modName + " with " + weapType[1] + " replaced by " + folder.c_str() + "; mod IDX = " + std::to_string(mod->modIndex));

processTypesMod(mod->modIndex, condition, pollCondition, priority, weapType, typesMap);
}

}
else if (types) // global replacement
{

const UInt8 lastModIndex = DataHandler::Get()->GetActiveModCount();

Log("Found type without a mod, counting: " + std::to_string(lastModIndex));

processTypesMod(-1, condition, pollCondition, priority, weapType, typesMap);
}
}
}
}
else
DebugPrint(path.string() + " does not start as a JSON array");
}
catch (nlohmann::json::exception& e)
{
DebugPrint("The JSON is incorrectly formatted! It will not be applied.");
DebugPrint(FormatString("JSON error: %s\n", e.what()));
}

}

void LoadJsonEntries()
Expand All @@ -301,8 +479,38 @@ void LoadJsonEntries()
g_jsonEntries = std::vector<JSONEntry>();
}

void LoadFileAnimPaths2()
{
Log("Loading file anims 2");
const auto dir = GetCurPath() + R"(\Data\Meshes\AnimGroupOverride)";
const auto then = std::chrono::system_clock::now();
std::map<std::string, std::vector<int>> typesMap;
if (std::filesystem::exists(dir))
{
for (std::filesystem::directory_iterator iter(dir.c_str()), end; iter != end; ++iter)
{
const auto& path = iter->path();
const auto& fileName = path.filename().string();
if (_stricmp(path.extension().string().c_str(), ".json") == 0 && fileName.find("_types_") == 0)
{
HandleJson2(iter->path(), typesMap);
}
}
}
else
{
Log(dir + " does not exist.");
}
//LoadJsonEntries2();
const auto now = std::chrono::system_clock::now();
const auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - then);
DebugPrint(FormatString("Loaded AnimGroupOverride2 in %d ms", diff.count()));
}

void LoadFileAnimPaths()
{
LoadFileAnimPaths2(); // making sure that _types_ are loaded first

Log("Loading file anims");
const auto dir = GetCurPath() + R"(\Data\Meshes\AnimGroupOverride)";
const auto then = std::chrono::system_clock::now();
Expand All @@ -315,9 +523,9 @@ void LoadFileAnimPaths()
if (iter->is_directory())
{
Log(iter->path().string() + " found");

const auto* mod = DataHandler::Get()->LookupModByName(fileName.string().c_str());

if (mod)
LoadModAnimPaths(path, mod);
else if (_stricmp(fileName.extension().string().c_str(), ".esp") == 0 || _stricmp(fileName.extension().string().c_str(), ".esm") == 0)
Expand All @@ -342,3 +550,5 @@ void LoadFileAnimPaths()
const auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - then);
DebugPrint(FormatString("Loaded AnimGroupOverride in %d ms", diff.count()));
}